Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-25 21:11:55 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-25 21:11:55 +0300
commit2b2d833ab3e78f8c9f626af950a16d43fc38c9f8 (patch)
tree13c101679f3cda5808affea46709207a24f4a3c9
parent7d8d5a3dab415672a41ab29c3bfa9581f275dc50 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/memory.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml62
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml12
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml5
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile9
-rw-r--r--Gemfile.lock5
-rw-r--r--app/assets/javascripts/batch_comments/components/draft_note.vue11
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue32
-rw-r--r--app/assets/javascripts/ide/components/activity_bar.vue11
-rw-r--r--app/assets/javascripts/main.js2
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss12
-rw-r--r--app/graphql/resolvers/ci/runners_resolver.rb12
-rw-r--r--app/models/concerns/issuable.rb10
-rw-r--r--app/models/container_repository.rb157
-rw-r--r--app/services/service_ping/submit_service.rb22
-rw-r--r--app/services/update_container_registry_info_service.rb6
-rw-r--r--app/validators/x509_certificate_credentials_validator.rb2
-rw-r--r--config/feature_flags/development/container_registry_migration_phase2_capacity_1.yml8
-rw-r--r--config/feature_flags/development/container_registry_migration_phase2_capacity_10.yml8
-rw-r--r--config/feature_flags/development/container_registry_migration_phase2_capacity_25.yml8
-rw-r--r--config/feature_flags/development/container_registry_migration_phase2_enabled.yml8
-rw-r--r--config/feature_flags/development/container_registry_migration_phase2_enqueue_speed_fast.yml8
-rw-r--r--config/feature_flags/development/container_registry_migration_phase2_enqueue_speed_slow.yml8
-rw-r--r--config/initializers/rspec_profiling.rb2
-rw-r--r--db/post_migrate/20220124145019_remove_projects_external_pull_requests_project_id_fk.rb19
-rw-r--r--db/post_migrate/20220124152824_remove_projects_ci_subscriptions_projects_downstream_project_id_fk.rb19
-rw-r--r--db/post_migrate/20220124215857_remove_projects_ci_job_token_project_scope_links_source_project_id_fk.rb19
-rw-r--r--db/schema_migrations/202201241450191
-rw-r--r--db/schema_migrations/202201241528241
-rw-r--r--db/schema_migrations/202201242158571
-rw-r--r--db/structure.sql9
-rw-r--r--doc/api/graphql/reference/index.md6
-rw-r--r--doc/ci/git_submodules.md9
-rw-r--r--doc/ci/runners/configure_runners.md48
-rw-r--r--doc/development/pipelines.md9
-rw-r--r--doc/development/service_ping/index.md21
-rw-r--r--lib/container_registry/base_client.rb136
-rw-r--r--lib/container_registry/client.rb135
-rw-r--r--lib/container_registry/gitlab_api_client.rb54
-rw-r--r--lib/container_registry/migration.rb40
-rw-r--r--lib/container_registry/registry.rb13
-rw-r--r--lib/gitlab/database/gitlab_loose_foreign_keys.yml11
-rw-r--r--lib/gitlab/import_export/saver.rb45
-rw-r--r--locale/gitlab.pot14
-rw-r--r--rubocop/cop/file_decompression.rb45
-rwxr-xr-xscripts/insert-rspec-profiling-data2
-rw-r--r--scripts/rspec_helpers.sh136
-rw-r--r--scripts/utils.sh12
-rw-r--r--spec/factories/container_repositories.rb46
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js33
-rw-r--r--spec/graphql/resolvers/ci/runners_resolver_spec.rb111
-rw-r--r--spec/lib/container_registry/client_spec.rb110
-rw-r--r--spec/lib/container_registry/gitlab_api_client_spec.rb178
-rw-r--r--spec/lib/container_registry/migration_spec.rb142
-rw-r--r--spec/lib/container_registry/registry_spec.rb6
-rw-r--r--spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/saver_spec.rb44
-rw-r--r--spec/mailers/notify_spec.rb2
-rw-r--r--spec/models/ability_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb2
-rw-r--r--spec/models/ci/job_token/project_scope_link_spec.rb7
-rw-r--r--spec/models/ci/pipeline_spec.rb4
-rw-r--r--spec/models/ci/trigger_spec.rb2
-rw-r--r--spec/models/commit_spec.rb2
-rw-r--r--spec/models/container_repository_spec.rb333
-rw-r--r--spec/models/environment_status_spec.rb2
-rw-r--r--spec/models/event_spec.rb20
-rw-r--r--spec/models/external_pull_request_spec.rb7
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/models/issue_collection_spec.rb4
-rw-r--r--spec/models/issue_spec.rb2
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/project_spec.rb4
-rw-r--r--spec/models/project_team_spec.rb8
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb6
-rw-r--r--spec/policies/namespaces/project_namespace_policy_spec.rb2
-rw-r--r--spec/policies/project_member_policy_spec.rb2
-rw-r--r--spec/policies/project_policy_spec.rb4
-rw-r--r--spec/rubocop/cop/file_decompression_spec.rb48
-rw-r--r--spec/services/service_ping/submit_service_ping_service_spec.rb15
-rw-r--r--spec/services/update_container_registry_info_service_spec.rb15
-rw-r--r--spec/spec_helper.rb10
-rw-r--r--spec/support/flaky_tests.rb11
-rw-r--r--spec/support/helpers/features/iteration_helpers.rb6
-rw-r--r--spec/support/shared_contexts/lib/container_registry/client_shared_context.rb27
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb2
-rw-r--r--spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb6
-rw-r--r--spec/tooling/rspec_flaky/config_spec.rb33
-rw-r--r--spec/tooling/rspec_flaky/listener_spec.rb6
-rwxr-xr-xtooling/bin/parallel_rspec2
-rw-r--r--tooling/lib/tooling/parallel_rspec_runner.rb8
-rw-r--r--tooling/rspec_flaky/config.rb10
95 files changed, 2048 insertions, 481 deletions
diff --git a/.gitignore b/.gitignore
index 03f77ed89e5..bdd3ac98876 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,6 +79,7 @@ eslint-report.html
/deprecations/
/knapsack/
/rspec_flaky/
+/rspec/
/locale/**/LC_MESSAGES
/locale/**/*.time_stamp
/.rspec
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 314f99c5f41..ababac4571f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -69,10 +69,14 @@ variables:
GET_SOURCES_ATTEMPTS: "3"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/report-master.json
- FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/report-suite.json
+ FLAKY_RSPEC_SUITE_REPORT_PATH: rspec/flaky/report-suite.json
RSPEC_TESTS_MAPPING_PATH: crystalball/mapping.json
RSPEC_PACKED_TESTS_MAPPING_PATH: crystalball/packed-mapping.json
+ RSPEC_PROFILING_FOLDER_PATH: rspec/profiling
FRONTEND_FIXTURES_MAPPING_PATH: crystalball/frontend_fixtures_mapping.json
+ RSPEC_LAST_RUN_RESULTS_FILE: rspec/rspec_last_run_results.txt
+ JUNIT_RESULT_FILE: rspec/junit_rspec.xml
+ JUNIT_RETRY_FILE: rspec/junit_rspec-retry.xml
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
ELASTIC_URL: "http://elastic:changeme@elasticsearch:9200"
diff --git a/.gitlab/ci/memory.gitlab-ci.yml b/.gitlab/ci/memory.gitlab-ci.yml
index 9234b116ff8..520f77cd8ca 100644
--- a/.gitlab/ci/memory.gitlab-ci.yml
+++ b/.gitlab/ci/memory.gitlab-ci.yml
@@ -11,7 +11,9 @@
metrics: "${METRICS_FILE}"
expire_in: 31d
-memory-static:
+# Disabled since it causes intermittent SIGSEGV in CI:
+# https://gitlab.com/gitlab-org/gitlab/-/issues/350296
+.memory-static:
extends: .only-code-memory-job-base
stage: test
needs: ["setup-test-env"]
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 4b9c91e4e94..2647e38a793 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -29,7 +29,9 @@
GITLAB_USE_MODEL_LOAD_BALANCING: "true"
.rspec-base:
- extends: .rails-job-base
+ extends:
+ - .rails-job-base
+ - .base-artifacts
stage: test
variables:
RUBY_GC_MALLOC_LIMIT: 67108864
@@ -40,6 +42,8 @@
script:
- !reference [.base-script, script]
- rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration"
+
+.base-artifacts:
artifacts:
expire_in: 31d
when: always
@@ -48,16 +52,17 @@
- crystalball/
- deprecations/
- knapsack/
- - rspec_flaky/
- - rspec_profiling/
+ - rspec/
- tmp/capybara/
- tmp/memory_test/
- log/*.log
reports:
- junit: junit_rspec.xml
+ junit: ${JUNIT_RESULT_FILE}
.rspec-base-migration:
- extends: .rails:rules:ee-and-foss-migration
+ extends:
+ - .base-artifacts
+ - .rails:rules:ee-and-foss-migration
script:
- !reference [.base-script, script]
- rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag level:migration"
@@ -550,7 +555,6 @@ rspec:coverage:
- rspec-ee integration pg12 geo minimal
- rspec-ee system pg12 geo minimal
# Memory jobs
- - memory-static
- memory-on-boot
# As-if-FOSS jobs
- rspec migration pg12-as-if-foss
@@ -617,54 +621,26 @@ rspec:feature-flags:
run_timed_command "bundle exec scripts/used-feature-flags";
fi
-rspec:skipped-flaky-tests-report:
+rspec:flaky-tests-report:
extends:
- .default-retry
- - .rails:rules:skipped-flaky-tests-report
- image: ruby:2.7-alpine
+ - .rails:rules:flaky-tests-report
stage: post-test
# We cannot use needs since it would mean needing 84 jobs (since most are parallelized)
# so we use `dependencies` here.
- dependencies:
- # FOSS/EE jobs
- - rspec migration pg12
- - rspec unit pg12
- - rspec integration pg12
- - rspec system pg12
- # FOSS/EE minimal jobs
- - rspec migration pg12 minimal
- - rspec unit pg12 minimal
- - rspec integration pg12 minimal
- - rspec system pg12 minimal
- # EE jobs
- - rspec-ee migration pg12
- - rspec-ee unit pg12
- - rspec-ee integration pg12
- - rspec-ee system pg12
- # EE minimal jobs
- - rspec-ee migration pg12 minimal
- - rspec-ee unit pg12 minimal
- - rspec-ee integration pg12 minimal
- - rspec-ee system pg12 minimal
- # Geo jobs
- - rspec-ee unit pg12 geo
- - rspec-ee integration pg12 geo
- - rspec-ee system pg12 geo
- # Geo minimal jobs
- - rspec-ee unit pg12 geo minimal
- - rspec-ee integration pg12 geo minimal
- - rspec-ee system pg12 geo minimal
+ dependencies: !reference ["rspec:coverage", "dependencies"]
variables:
- SKIPPED_FLAKY_TESTS_REPORT: skipped_flaky_tests_report.txt
+ SKIPPED_FLAKY_TESTS_REPORT_PATH: rspec/flaky/skipped_flaky_tests_report.txt
+ RETRIED_TESTS_REPORT_PATH: rspec/flaky/retried_tests_report.txt
before_script:
- - 'echo "SKIP_FLAKY_TESTS_AUTOMATICALLY: $SKIP_FLAKY_TESTS_AUTOMATICALLY"'
- - mkdir -p rspec_flaky
+ - source scripts/utils.sh
+ - source scripts/rspec_helpers.sh
script:
- - find rspec_flaky/ -type f -name 'skipped_flaky_tests_*_report.txt' -exec cat {} + >> "${SKIPPED_FLAKY_TESTS_REPORT}"
+ - generate_flaky_tests_reports
artifacts:
expire_in: 31d
paths:
- - ${SKIPPED_FLAKY_TESTS_REPORT}
+ - rspec/
# EE/FOSS: default refs (MRs, default branch, schedules) jobs #
#######################################################
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 58f5e716bc5..5e183177a63 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -121,9 +121,6 @@
.if-security-pipeline-merge-result: &if-security-pipeline-merge-result
if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH && $CI_PROJECT_NAMESPACE == "gitlab-org/security" && $GITLAB_USER_LOGIN == "gitlab-release-tools-bot"'
-.if-skip-flaky-tests-automatically: &if-skip-flaky-tests-automatically
- if: '$SKIP_FLAKY_TESTS_AUTOMATICALLY == "true"'
-
####################
# Changes patterns #
####################
@@ -1436,13 +1433,16 @@
when: never
- changes: *code-backstage-patterns
-.rails:rules:skipped-flaky-tests-report:
+.rails:rules:flaky-tests-report:
rules:
- <<: *if-not-ee
when: never
- - <<: *if-skip-flaky-tests-automatically
+ - if: '$SKIP_FLAKY_TESTS_AUTOMATICALLY == "true" || $RETRY_FAILED_TESTS_IN_NEW_PROCESS == "true"'
changes: *code-backstage-patterns
- - changes: *ci-patterns
+ when: always
+ - if: '$SKIP_FLAKY_TESTS_AUTOMATICALLY == "true" || $RETRY_FAILED_TESTS_IN_NEW_PROCESS == "true"'
+ changes: *ci-patterns
+ when: always
#########################
# Static analysis rules #
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
index d0d45cb9294..8f4aca730e7 100644
--- a/.gitlab/ci/test-metadata.gitlab-ci.yml
+++ b/.gitlab/ci/test-metadata.gitlab-ci.yml
@@ -6,8 +6,7 @@
expire_in: 31d
paths:
- knapsack/
- - rspec_flaky/
- - rspec_profiling/
+ - rspec/
- crystalball/
retrieve-tests-metadata:
@@ -44,6 +43,6 @@ update-tests-metadata:
script:
- run_timed_command "retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document"
- source ./scripts/rspec_helpers.sh
- - test -f rspec_flaky/report-suite.json || echo -e "\e[31m" 'Consider add ~"pipeline:run-all-rspec" to run full rspec jobs' "\e[0m"
+ - test -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo -e "\e[31m" 'Consider add ~"pipeline:run-all-rspec" to run full rspec jobs' "\e[0m"
- update_tests_metadata
- update_tests_mapping
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 35d41ddb27d..9e9323fc86a 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-13.22.2
+13.23.0
diff --git a/Gemfile b/Gemfile
index edf30c67301..2d734e0b510 100644
--- a/Gemfile
+++ b/Gemfile
@@ -466,16 +466,9 @@ gem 'sys-filesystem', '~> 1.4.3'
# NTP client
gem 'net-ntp'
-# SSH host key support
-gem 'net-ssh', '~> 6.0'
+# SSH keys support
gem 'ssh_data', '~> 1.2'
-# Required for ED25519 SSH host key support
-group :ed25519 do
- gem 'ed25519', '~> 1.2'
- gem 'bcrypt_pbkdf', '~> 1.1'
-end
-
# Spamcheck GRPC protocol definitions
gem 'spamcheck', '~> 0.1.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1c47f52dff1..2dee6391d82 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -137,7 +137,6 @@ GEM
base32 (0.3.2)
batch-loader (2.0.1)
bcrypt (3.1.16)
- bcrypt_pbkdf (1.1.0)
benchmark (0.1.1)
benchmark-ips (2.3.0)
benchmark-memory (0.1.2)
@@ -308,7 +307,6 @@ GEM
e2mmap (0.1.0)
ecma-re-validator (0.3.0)
regexp_parser (~> 2.0)
- ed25519 (1.2.4)
elasticsearch (6.8.2)
elasticsearch-api (= 6.8.2)
elasticsearch-transport (= 6.8.2)
@@ -1408,7 +1406,6 @@ DEPENDENCIES
base32 (~> 0.3.0)
batch-loader (~> 2.0.1)
bcrypt (~> 3.1, >= 3.1.14)
- bcrypt_pbkdf (~> 1.1)
benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1)
better_errors (~> 2.9.0)
@@ -1441,7 +1438,6 @@ DEPENDENCIES
discordrb-webhooks (~> 3.4)
doorkeeper (~> 5.5.0.rc2)
doorkeeper-openid_connect (~> 1.7.5)
- ed25519 (~> 1.2)
elasticsearch-api (~> 6.8.2)
elasticsearch-model (~> 6.1)
elasticsearch-rails (~> 6.1)
@@ -1542,7 +1538,6 @@ DEPENDENCIES
multi_json (~> 1.14.1)
net-ldap (~> 0.16.3)
net-ntp
- net-ssh (~> 6.0)
nokogiri (~> 1.12)
oauth2 (~> 1.4)
octokit (~> 4.15)
diff --git a/app/assets/javascripts/batch_comments/components/draft_note.vue b/app/assets/javascripts/batch_comments/components/draft_note.vue
index a218624f2d4..ae728d834e2 100644
--- a/app/assets/javascripts/batch_comments/components/draft_note.vue
+++ b/app/assets/javascripts/batch_comments/components/draft_note.vue
@@ -115,10 +115,15 @@ export default {
></div>
<p class="draft-note-actions d-flex">
- <publish-button :show-count="true" :should-publish="false" category="secondary" />
+ <publish-button
+ :show-count="true"
+ :should-publish="false"
+ category="secondary"
+ :disabled="isPublishingDraft(draft.id)"
+ />
<gl-button
- ref="publishNowButton"
- :loading="isPublishingDraft(draft.id) || isPublishing"
+ :disabled="isPublishing"
+ :loading="isPublishingDraft(draft.id)"
class="gl-ml-3"
@click="publishNow"
>
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index 156029b62b0..0320b4d925e 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -184,29 +184,15 @@ export default {
:issuable-type="issuableType"
data-testid="sidebar-milestones"
/>
- <template v-if="!glFeatures.iterationCadences">
- <sidebar-dropdown-widget
- v-if="iterationFeatureAvailable && !isIncidentSidebar"
- :iid="activeBoardItem.iid"
- issuable-attribute="iteration"
- :workspace-path="projectPathForActiveIssue"
- :attr-workspace-path="groupPathForActiveIssue"
- :issuable-type="issuableType"
- class="gl-mt-5"
- data-testid="iteration-edit"
- />
- </template>
- <template v-else>
- <iteration-sidebar-dropdown-widget
- v-if="iterationFeatureAvailable && !isIncidentSidebar"
- :iid="activeBoardItem.iid"
- :workspace-path="projectPathForActiveIssue"
- :attr-workspace-path="groupPathForActiveIssue"
- :issuable-type="issuableType"
- class="gl-mt-5"
- data-testid="iteration-edit"
- />
- </template>
+ <iteration-sidebar-dropdown-widget
+ v-if="iterationFeatureAvailable && !isIncidentSidebar"
+ :iid="activeBoardItem.iid"
+ :workspace-path="projectPathForActiveIssue"
+ :attr-workspace-path="groupPathForActiveIssue"
+ :issuable-type="issuableType"
+ class="gl-mt-5"
+ data-testid="iteration-edit"
+ />
</div>
<board-sidebar-time-tracker />
<sidebar-date-widget
diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue
index 846b4d92724..92dacf8c94a 100644
--- a/app/assets/javascripts/ide/components/activity_bar.vue
+++ b/app/assets/javascripts/ide/components/activity_bar.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlIcon, GlTooltipDirective, GlBadge } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { leftSidebarViews } from '../constants';
@@ -7,6 +7,7 @@ import { leftSidebarViews } from '../constants';
export default {
components: {
GlIcon,
+ GlBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -82,9 +83,13 @@ export default {
@click.prevent="changedActivityView($event, $options.leftSidebarViews.commit.name)"
>
<gl-icon name="commit" />
- <div v-if="stagedFiles.length > 0" class="ide-commit-badge badge badge-pill">
+ <gl-badge
+ v-if="stagedFiles.length"
+ class="gl-absolute gl-px-2 gl-top-3 gl-right-3 gl-font-weight-bold gl-bg-gray-900! gl-text-white!"
+ size="sm"
+ >
{{ stagedFiles.length }}
- </div>
+ </gl-badge>
</button>
</li>
</ul>
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 376134afef0..f78b4da181e 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -15,11 +15,11 @@ import { initRails } from '~/lib/utils/rails_ujs';
import * as popovers from '~/popovers';
import * as tooltips from '~/tooltips';
import { initPrefetchLinks } from '~/lib/utils/navigation_utility';
+import { logHelloDeferred } from 'jh_else_ce/lib/logger/hello_deferred';
import initAlertHandler from './alert_handler';
import { addDismissFlashClickListener } from './flash';
import initTodoToggle from './header';
import initLayoutNav from './layout_nav';
-import { logHelloDeferred } from './lib/logger/hello_deferred';
import { handleLocationHash, addSelectOnFocusBehaviour } from './lib/utils/common_utils';
import { localTimeAgo } from './lib/utils/datetime/timeago_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index d37171bc75e..6c270852e53 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -630,7 +630,6 @@ $ide-commit-header-height: 48px;
width: 1px;
background: var(--ide-highlight-background, $white);
}
-
}
&.is-right {
@@ -642,17 +641,6 @@ $ide-commit-header-height: 48px;
left: -1px;
}
}
-
- .ide-commit-badge {
- background-color: var(--ide-highlight-accent, $almost-black) !important;
- color: var(--ide-highlight-background, $white) !important;
- position: absolute;
- left: 38px;
- top: $gl-padding-8;
- font-size: $gl-font-size-12;
- padding: 2px $gl-padding-4;
- font-weight: $gl-font-weight-bold !important;
- }
}
.ide-activity-bar {
diff --git a/app/graphql/resolvers/ci/runners_resolver.rb b/app/graphql/resolvers/ci/runners_resolver.rb
index 9848b5a503f..e221dfea4d0 100644
--- a/app/graphql/resolvers/ci/runners_resolver.rb
+++ b/app/graphql/resolvers/ci/runners_resolver.rb
@@ -9,7 +9,12 @@ module Resolvers
argument :active, ::GraphQL::Types::Boolean,
required: false,
- description: 'Filter runners by active (true) or paused (false) status.'
+ description: 'Filter runners by `active` (true) or `paused` (false) status.',
+ deprecated: { reason: :renamed, replacement: 'paused', milestone: '14.8' }
+
+ argument :paused, ::GraphQL::Types::Boolean,
+ required: false,
+ description: 'Filter runners by `paused` (true) or `active` (false) status.'
argument :status, ::Types::Ci::RunnerStatusEnum,
required: false,
@@ -41,8 +46,11 @@ module Resolvers
protected
def runners_finder_params(params)
+ # Give preference to paused argument over the deprecated 'active' argument
+ paused = params.fetch(:paused, params[:active] ? !params[:active] : nil)
+
{
- active: params[:active],
+ active: paused.nil? ? nil : !paused,
status_status: params[:status]&.to_s,
type_type: params[:type],
tag_name: params[:tag_list],
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index dcd80201d3f..b32ca064954 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -363,9 +363,10 @@ module Issuable
end
# Includes table keys in group by clause when sorting
- # preventing errors in postgres
+ # preventing errors in Postgres
+ #
+ # Returns an array of Arel columns
#
- # Returns an array of arel columns
def grouping_columns(sort)
sort = sort.to_s
grouping_columns = [arel_table[:id]]
@@ -384,9 +385,10 @@ module Issuable
end
# Includes all table keys in group by clause when sorting
- # preventing errors in postgres when using CTE search optimisation
+ # preventing errors in Postgres when using CTE search optimization
+ #
+ # Returns an array of Arel columns
#
- # Returns an array of arel columns
def issue_grouping_columns(use_cte: false)
if use_cte
attribute_names.map { |attr| arel_table[attr.to_sym] }
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 4b83eee84fb..e14555f2907 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -8,12 +8,16 @@ class ContainerRepository < ApplicationRecord
WAITING_CLEANUP_STATUSES = %i[cleanup_scheduled cleanup_unfinished].freeze
REQUIRING_CLEANUP_STATUSES = %i[cleanup_unscheduled cleanup_scheduled].freeze
+ IDLE_MIGRATION_STATES = %w[default pre_import_done import_done import_aborted import_skipped].freeze
+ ACTIVE_MIGRATION_STATES = %w[pre_importing importing].freeze
+ MIGRATION_STATES = (IDLE_MIGRATION_STATES + ACTIVE_MIGRATION_STATES).freeze
belongs_to :project
validates :name, length: { minimum: 0, allow_nil: false }
validates :name, uniqueness: { scope: :project_id }
- validates :migration_state, presence: true
+ validates :migration_state, presence: true, inclusion: { in: MIGRATION_STATES }
+ validates :migration_aborted_in_state, inclusion: { in: ACTIVE_MIGRATION_STATES }, allow_nil: true
validates :migration_retries_count, presence: true,
numericality: { greater_than_or_equal_to: 0 },
@@ -23,7 +27,7 @@ class ContainerRepository < ApplicationRecord
enum expiration_policy_cleanup_status: { cleanup_unscheduled: 0, cleanup_scheduled: 1, cleanup_unfinished: 2, cleanup_ongoing: 3 }
enum migration_skipped_reason: { not_in_plan: 0, too_many_retries: 1, too_many_tags: 2, root_namespace_in_deny_list: 3 }
- delegate :client, to: :registry
+ delegate :client, :gitlab_api_client, to: :registry
scope :ordered, -> { order(:name) }
scope :with_api_entity_associations, -> { preload(project: [:route, { namespace: :route }]) }
@@ -41,6 +45,119 @@ class ContainerRepository < ApplicationRecord
scope :expiration_policy_started_at_nil_or_before, ->(timestamp) { where('expiration_policy_started_at < ? OR expiration_policy_started_at IS NULL', timestamp) }
scope :with_stale_ongoing_cleanup, ->(threshold) { cleanup_ongoing.where('expiration_policy_started_at < ?', threshold) }
+ state_machine :migration_state, initial: :default do
+ state :pre_importing do
+ validates :migration_pre_import_started_at, presence: true
+ validates :migration_pre_import_done_at, presence: false
+ end
+
+ state :pre_import_done do
+ validates :migration_pre_import_started_at,
+ :migration_pre_import_done_at,
+ presence: true
+ end
+
+ state :importing do
+ validates :migration_import_started_at, presence: true
+ validates :migration_import_done_at, presence: false
+ end
+
+ state :import_done
+
+ state :import_skipped do
+ validates :migration_skipped_reason,
+ :migration_skipped_at,
+ presence: true
+ end
+
+ state :import_aborted do
+ validates :migration_aborted_at, presence: true
+ validates :migration_retries_count, presence: true, numericality: { greater_than_or_equal_to: 1 }
+ end
+
+ event :start_pre_import do
+ transition default: :pre_importing
+ end
+
+ event :finish_pre_import do
+ transition pre_importing: :pre_import_done
+ end
+
+ event :start_import do
+ transition pre_import_done: :importing
+ end
+
+ event :finish_import do
+ transition importing: :import_done
+ end
+
+ event :already_migrated do
+ transition default: :import_done
+ end
+
+ event :abort_import do
+ transition %i[pre_importing importing] => :import_aborted
+ end
+
+ event :skip_import do
+ transition %i[default pre_importing importing] => :import_skipped
+ end
+
+ event :retry_pre_import do
+ transition import_aborted: :pre_importing
+ end
+
+ event :retry_import do
+ transition import_aborted: :importing
+ end
+
+ before_transition any => :pre_importing do |container_repository|
+ container_repository.migration_pre_import_started_at = Time.zone.now
+ container_repository.migration_pre_import_done_at = nil
+ end
+
+ after_transition any => :pre_importing do |container_repository|
+ container_repository.abort_import unless container_repository.migration_pre_import == :ok
+ end
+
+ before_transition pre_importing: :pre_import_done do |container_repository|
+ container_repository.migration_pre_import_done_at = Time.zone.now
+ end
+
+ before_transition any => :importing do |container_repository|
+ container_repository.migration_import_started_at = Time.zone.now
+ container_repository.migration_import_done_at = nil
+ end
+
+ after_transition any => :importing do |container_repository|
+ container_repository.abort_import unless container_repository.migration_import == :ok
+ end
+
+ before_transition importing: :import_done do |container_repository|
+ container_repository.migration_import_done_at = Time.zone.now
+ end
+
+ before_transition any => :import_aborted do |container_repository|
+ container_repository.migration_aborted_in_state = container_repository.migration_state
+ container_repository.migration_aborted_at = Time.zone.now
+ container_repository.migration_retries_count += 1
+ end
+
+ before_transition import_aborted: any do |container_repository|
+ container_repository.migration_aborted_at = nil
+ container_repository.migration_aborted_in_state = nil
+ end
+
+ before_transition any => :import_skipped do |container_repository|
+ container_repository.migration_skipped_at = Time.zone.now
+ end
+
+ before_transition any => %i[import_done import_aborted] do
+ # EnqueuerJob.enqueue perform_async or perform_in depending on the speed FF
+ # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/349744
+ end
+ end
+
def self.exists_by_path?(path)
where(
project: path.repository_project,
@@ -64,6 +181,30 @@ class ContainerRepository < ApplicationRecord
with_enabled_policy.cleanup_unfinished
end
+ def skip_import(reason:)
+ self.migration_skipped_reason = reason
+
+ super
+ end
+
+ def start_pre_import
+ return false unless ContainerRegistry::Migration.enabled?
+
+ super
+ end
+
+ def retry_pre_import
+ return false unless ContainerRegistry::Migration.enabled?
+
+ super
+ end
+
+ def retry_import
+ return false unless ContainerRegistry::Migration.enabled?
+
+ super
+ end
+
# rubocop: disable CodeReuse/ServiceClass
def registry
@registry ||= begin
@@ -150,6 +291,18 @@ class ContainerRepository < ApplicationRecord
migration_state == 'importing'
end
+ def migration_pre_import
+ return :error unless gitlab_api_client.supports_gitlab_api?
+
+ gitlab_api_client.pre_import_repository(self.path)
+ end
+
+ def migration_import
+ return :error unless gitlab_api_client.supports_gitlab_api?
+
+ gitlab_api_client.import_repository(self.path)
+ end
+
def self.build_from_path(path)
self.new(project: path.repository_project,
name: path.repository_name)
diff --git a/app/services/service_ping/submit_service.rb b/app/services/service_ping/submit_service.rb
index d3d9dcecb2b..3e8c1848f88 100644
--- a/app/services/service_ping/submit_service.rb
+++ b/app/services/service_ping/submit_service.rb
@@ -5,6 +5,7 @@ module ServicePing
PRODUCTION_BASE_URL = 'https://version.gitlab.com'
STAGING_BASE_URL = 'https://gitlab-services-version-gitlab-com-staging.gs-staging.gitlab.org'
USAGE_DATA_PATH = 'usage_data'
+ ERROR_PATH = 'usage_ping_errors'
SubmissionError = Class.new(StandardError)
@@ -15,12 +16,23 @@ module ServicePing
def execute
return unless ServicePing::ServicePingSettings.product_intelligence_enabled?
+ start = Time.current
begin
usage_data = BuildPayloadService.new.execute
response = submit_usage_data_payload(usage_data)
- rescue StandardError
+ rescue StandardError => e
return unless Gitlab::CurrentSettings.usage_ping_enabled?
+ error_payload = {
+ time: Time.current,
+ uuid: Gitlab::UsageData.add_metric('UuidMetric'),
+ hostname: Gitlab::UsageData.add_metric('HostnameMetric'),
+ version: Gitlab::UsageData.alt_usage_data { Gitlab::VERSION },
+ message: e.message,
+ elapsed: (Time.current - start).round(1)
+ }
+ submit_payload({ error: error_payload }, url: error_url)
+
usage_data = Gitlab::UsageData.data(force_refresh: true)
response = submit_usage_data_payload(usage_data)
end
@@ -42,12 +54,16 @@ module ServicePing
URI.join(base_url, USAGE_DATA_PATH)
end
+ def error_url
+ URI.join(base_url, ERROR_PATH)
+ end
+
private
- def submit_payload(usage_data)
+ def submit_payload(payload, url: self.url)
Gitlab::HTTP.post(
url,
- body: usage_data.to_json,
+ body: payload.to_json,
allow_local_requests: true,
headers: { 'Content-type' => 'application/json' }
)
diff --git a/app/services/update_container_registry_info_service.rb b/app/services/update_container_registry_info_service.rb
index 531335839a9..7d79b257687 100644
--- a/app/services/update_container_registry_info_service.rb
+++ b/app/services/update_container_registry_info_service.rb
@@ -15,6 +15,12 @@ class UpdateContainerRegistryInfoService
client = ContainerRegistry::Client.new(registry_config.api_url, token: token)
info = client.registry_info
+ gitlab_api_client = ContainerRegistry::GitlabApiClient.new(registry_config.api_url, token: token)
+ if gitlab_api_client.supports_gitlab_api?
+ info[:features] ||= []
+ info[:features] << ContainerRegistry::GitlabApiClient::REGISTRY_GITLAB_V1_API_FEATURE
+ end
+
Gitlab::CurrentSettings.update!(
container_registry_vendor: info[:vendor] || '',
container_registry_version: info[:version] || '',
diff --git a/app/validators/x509_certificate_credentials_validator.rb b/app/validators/x509_certificate_credentials_validator.rb
index d2f18e956c3..11b53d59c7d 100644
--- a/app/validators/x509_certificate_credentials_validator.rb
+++ b/app/validators/x509_certificate_credentials_validator.rb
@@ -41,7 +41,7 @@ class X509CertificateCredentialsValidator < ActiveModel::Validator
return if private_key.nil? || certificate.nil?
- unless certificate.public_key.fingerprint == private_key.public_key.fingerprint
+ unless certificate.check_private_key(private_key)
record.errors.add(options[:pkey], _('private key does not match certificate.'))
end
end
diff --git a/config/feature_flags/development/container_registry_migration_phase2_capacity_1.yml b/config/feature_flags/development/container_registry_migration_phase2_capacity_1.yml
new file mode 100644
index 00000000000..846bc8b690b
--- /dev/null
+++ b/config/feature_flags/development/container_registry_migration_phase2_capacity_1.yml
@@ -0,0 +1,8 @@
+---
+name: container_registry_migration_phase2_capacity_1
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79061
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350543
+milestone: '14.8'
+type: development
+group: group::package
+default_enabled: false
diff --git a/config/feature_flags/development/container_registry_migration_phase2_capacity_10.yml b/config/feature_flags/development/container_registry_migration_phase2_capacity_10.yml
new file mode 100644
index 00000000000..fcbcc5bfb48
--- /dev/null
+++ b/config/feature_flags/development/container_registry_migration_phase2_capacity_10.yml
@@ -0,0 +1,8 @@
+---
+name: container_registry_migration_phase2_capacity_10
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79061
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350543
+milestone: '14.8'
+type: development
+group: group::package
+default_enabled: false
diff --git a/config/feature_flags/development/container_registry_migration_phase2_capacity_25.yml b/config/feature_flags/development/container_registry_migration_phase2_capacity_25.yml
new file mode 100644
index 00000000000..b52693e0aba
--- /dev/null
+++ b/config/feature_flags/development/container_registry_migration_phase2_capacity_25.yml
@@ -0,0 +1,8 @@
+---
+name: container_registry_migration_phase2_capacity_25
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79061
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350543
+milestone: '14.8'
+type: development
+group: group::package
+default_enabled: false
diff --git a/config/feature_flags/development/container_registry_migration_phase2_enabled.yml b/config/feature_flags/development/container_registry_migration_phase2_enabled.yml
new file mode 100644
index 00000000000..c48cbdb435e
--- /dev/null
+++ b/config/feature_flags/development/container_registry_migration_phase2_enabled.yml
@@ -0,0 +1,8 @@
+---
+name: container_registry_migration_phase2_enabled
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79061
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350543
+milestone: '14.8'
+type: development
+group: group::package
+default_enabled: false
diff --git a/config/feature_flags/development/container_registry_migration_phase2_enqueue_speed_fast.yml b/config/feature_flags/development/container_registry_migration_phase2_enqueue_speed_fast.yml
new file mode 100644
index 00000000000..9a312161824
--- /dev/null
+++ b/config/feature_flags/development/container_registry_migration_phase2_enqueue_speed_fast.yml
@@ -0,0 +1,8 @@
+---
+name: container_registry_migration_phase2_enqueue_speed_fast
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79061
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350543
+milestone: '14.8'
+type: development
+group: group::package
+default_enabled: false
diff --git a/config/feature_flags/development/container_registry_migration_phase2_enqueue_speed_slow.yml b/config/feature_flags/development/container_registry_migration_phase2_enqueue_speed_slow.yml
new file mode 100644
index 00000000000..f02259be928
--- /dev/null
+++ b/config/feature_flags/development/container_registry_migration_phase2_enqueue_speed_slow.yml
@@ -0,0 +1,8 @@
+---
+name: container_registry_migration_phase2_enqueue_speed_slow
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79061
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350543
+milestone: '14.8'
+type: development
+group: group::package
+default_enabled: false
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
index 5edea6489ed..2ab7bdade31 100644
--- a/config/initializers/rspec_profiling.rb
+++ b/config/initializers/rspec_profiling.rb
@@ -62,7 +62,7 @@ RspecProfiling.configure do |config|
config.collector = RspecProfilingExt::Collectors::CSVWithTimestamps
config.csv_path = -> do
prefix = "#{ENV['CI_JOB_NAME']}-".gsub(%r{[ /]}, '-') if ENV['CI_JOB_NAME']
- "rspec_profiling/#{prefix}#{Time.now.to_i}-#{SecureRandom.hex(8)}-rspec-data.csv"
+ "#{ENV['RSPEC_PROFILING_FOLDER_PATH']}/#{prefix}#{Time.now.to_i}-#{SecureRandom.hex(8)}-rspec-data.csv"
end
end
end
diff --git a/db/post_migrate/20220124145019_remove_projects_external_pull_requests_project_id_fk.rb b/db/post_migrate/20220124145019_remove_projects_external_pull_requests_project_id_fk.rb
new file mode 100644
index 00000000000..b16234fc812
--- /dev/null
+++ b/db/post_migrate/20220124145019_remove_projects_external_pull_requests_project_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveProjectsExternalPullRequestsProjectIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:external_pull_requests, :projects, name: "fk_rails_bcae9b5c7b")
+
+ with_lock_retries do
+ execute('LOCK projects, external_pull_requests IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:external_pull_requests, :projects, name: "fk_rails_bcae9b5c7b")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:external_pull_requests, :projects, name: "fk_rails_bcae9b5c7b", column: :project_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/post_migrate/20220124152824_remove_projects_ci_subscriptions_projects_downstream_project_id_fk.rb b/db/post_migrate/20220124152824_remove_projects_ci_subscriptions_projects_downstream_project_id_fk.rb
new file mode 100644
index 00000000000..8596b1f14ba
--- /dev/null
+++ b/db/post_migrate/20220124152824_remove_projects_ci_subscriptions_projects_downstream_project_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveProjectsCiSubscriptionsProjectsDownstreamProjectIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:ci_subscriptions_projects, :projects, name: "fk_rails_0818751483")
+
+ with_lock_retries do
+ execute('LOCK projects, ci_subscriptions_projects IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:ci_subscriptions_projects, :projects, name: "fk_rails_0818751483")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:ci_subscriptions_projects, :projects, name: "fk_rails_0818751483", column: :downstream_project_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/post_migrate/20220124215857_remove_projects_ci_job_token_project_scope_links_source_project_id_fk.rb b/db/post_migrate/20220124215857_remove_projects_ci_job_token_project_scope_links_source_project_id_fk.rb
new file mode 100644
index 00000000000..8bfe9586a50
--- /dev/null
+++ b/db/post_migrate/20220124215857_remove_projects_ci_job_token_project_scope_links_source_project_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveProjectsCiJobTokenProjectScopeLinksSourceProjectIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:ci_job_token_project_scope_links, :projects, name: "fk_rails_4b2ee3290b")
+
+ with_lock_retries do
+ execute('LOCK projects, ci_job_token_project_scope_links IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:ci_job_token_project_scope_links, :projects, name: "fk_rails_4b2ee3290b")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:ci_job_token_project_scope_links, :projects, name: "fk_rails_4b2ee3290b", column: :source_project_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/schema_migrations/20220124145019 b/db/schema_migrations/20220124145019
new file mode 100644
index 00000000000..8de01024798
--- /dev/null
+++ b/db/schema_migrations/20220124145019
@@ -0,0 +1 @@
+3b1f7a7b6481a960ac25523e5e1b5abc6c1436332d64d4319d9e4112b0fa765e \ No newline at end of file
diff --git a/db/schema_migrations/20220124152824 b/db/schema_migrations/20220124152824
new file mode 100644
index 00000000000..5a0db5859f0
--- /dev/null
+++ b/db/schema_migrations/20220124152824
@@ -0,0 +1 @@
+acf680cbf06d6eb2c0da44d416ff947e0460593cad74499acda91eb4bec63894 \ No newline at end of file
diff --git a/db/schema_migrations/20220124215857 b/db/schema_migrations/20220124215857
new file mode 100644
index 00000000000..495e428d2c4
--- /dev/null
+++ b/db/schema_migrations/20220124215857
@@ -0,0 +1 @@
+785c2404175faef7d3d6f81ae8652ca0ced0f4b01def8d6364a4450f0db49ecf \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 4e6592174cc..6f85319d656 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -30041,9 +30041,6 @@ ALTER TABLE ONLY ip_restrictions
ALTER TABLE ONLY terraform_state_versions
ADD CONSTRAINT fk_rails_04f176e239 FOREIGN KEY (terraform_state_id) REFERENCES terraform_states(id) ON DELETE CASCADE;
-ALTER TABLE ONLY ci_subscriptions_projects
- ADD CONSTRAINT fk_rails_0818751483 FOREIGN KEY (downstream_project_id) REFERENCES projects(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY trending_projects
ADD CONSTRAINT fk_rails_09feecd872 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -30455,9 +30452,6 @@ ALTER TABLE ONLY user_custom_attributes
ALTER TABLE ONLY upcoming_reconciliations
ADD CONSTRAINT fk_rails_497b4938ac FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
-ALTER TABLE ONLY ci_job_token_project_scope_links
- ADD CONSTRAINT fk_rails_4b2ee3290b FOREIGN KEY (source_project_id) REFERENCES projects(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY group_deletion_schedules
ADD CONSTRAINT fk_rails_4b8c694a6c FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -31151,9 +31145,6 @@ ALTER TABLE ONLY projects_sync_events
ALTER TABLE ONLY approval_merge_request_rules_users
ADD CONSTRAINT fk_rails_bc8972fa55 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
-ALTER TABLE ONLY external_pull_requests
- ADD CONSTRAINT fk_rails_bcae9b5c7b FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY elasticsearch_indexed_projects
ADD CONSTRAINT fk_rails_bd13bbdc3d FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index f2d713e9c97..458a9a2b98a 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -376,7 +376,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="queryrunnersactive"></a>`active` | [`Boolean`](#boolean) | Filter runners by active (true) or paused (false) status. |
+| <a id="queryrunnersactive"></a>`active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 14.8. This was renamed. Use: `paused`. |
+| <a id="queryrunnerspaused"></a>`paused` | [`Boolean`](#boolean) | Filter runners by `paused` (true) or `active` (false) status. |
| <a id="queryrunnerssearch"></a>`search` | [`String`](#string) | Filter by full token or partial text in description field. |
| <a id="queryrunnerssort"></a>`sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
| <a id="queryrunnersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runners by status. |
@@ -11213,8 +11214,9 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="grouprunnersactive"></a>`active` | [`Boolean`](#boolean) | Filter runners by active (true) or paused (false) status. |
+| <a id="grouprunnersactive"></a>`active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 14.8. This was renamed. Use: `paused`. |
| <a id="grouprunnersmembership"></a>`membership` | [`RunnerMembershipFilter`](#runnermembershipfilter) | Control which runners to include in the results. |
+| <a id="grouprunnerspaused"></a>`paused` | [`Boolean`](#boolean) | Filter runners by `paused` (true) or `active` (false) status. |
| <a id="grouprunnerssearch"></a>`search` | [`String`](#string) | Filter by full token or partial text in description field. |
| <a id="grouprunnerssort"></a>`sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
| <a id="grouprunnersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runners by status. |
diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md
index cdc75fd2bec..23055514839 100644
--- a/doc/ci/git_submodules.md
+++ b/doc/ci/git_submodules.md
@@ -60,6 +60,15 @@ To make submodules work correctly in CI/CD jobs:
GIT_SUBMODULE_STRATEGY: recursive
```
+1. You can provide additional flags to control advanced checkout behavior using
+ [`GIT_SUBMODULE_UPDATE_FLAGS`](runners/configure_runners.md#git-submodule-update-flags).
+
+ ```yaml
+ variables:
+ GIT_SUBMODULE_STRATEGY: recursive
+ GIT_SUBMODULE_UPDATE_FLAGS: --jobs 4
+ ```
+
If you use the [`CI_JOB_TOKEN`](jobs/ci_job_token.md) to clone a submodule in a
pipeline job, the user executing the job must be assigned to a role that has
[permission](../user/permissions.md#gitlab-cicd-permissions) to trigger a pipeline
diff --git a/doc/ci/runners/configure_runners.md b/doc/ci/runners/configure_runners.md
index d826b28acce..8860c43cb11 100644
--- a/doc/ci/runners/configure_runners.md
+++ b/doc/ci/runners/configure_runners.md
@@ -291,6 +291,7 @@ globally or for individual jobs:
- [`GIT_CHECKOUT`](#git-checkout)
- [`GIT_CLEAN_FLAGS`](#git-clean-flags)
- [`GIT_FETCH_EXTRA_FLAGS`](#git-fetch-extra-flags)
+- [`GIT_SUBMODULE_UPDATE_FLAGS`](#git-submodule-update-flags)
- [`GIT_DEPTH`](#shallow-cloning) (shallow cloning)
- [`GIT_CLONE_PATH`](#custom-build-directories) (custom build directories)
- [`TRANSFER_METER_FREQUENCY`](#artifact-and-cache-settings) (artifact/cache meter update frequency)
@@ -382,6 +383,8 @@ For this feature to work correctly, the submodules must be configured
- a relative path to another repository on the same GitLab server. See the
[Git submodules](../git_submodules.md) documentation.
+You can provide additional flags to control advanced behavior using [`GIT_SUBMODULE_UPDATE_FLAGS`](#git-submodule-update-flags).
+
### Git checkout
> Introduced in GitLab Runner 9.3.
@@ -442,14 +445,14 @@ script:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/4142) in GitLab Runner 13.1.
-The `GIT_FETCH_EXTRA_FLAGS` variable is used to control the behavior of
+Use the `GIT_FETCH_EXTRA_FLAGS` variable to control the behavior of
`git fetch`. You can set it globally or per-job in the [`variables`](../yaml/index.md#variables) section.
`GIT_FETCH_EXTRA_FLAGS` accepts all options of the [`git fetch`](https://git-scm.com/docs/git-fetch) command. However, `GIT_FETCH_EXTRA_FLAGS` flags are appended after the default flags that can't be modified.
The default flags are:
-- [GIT_DEPTH](#shallow-cloning).
+- [`GIT_DEPTH`](#shallow-cloning).
- The list of [refspecs](https://git-scm.com/book/en/v2/Git-Internals-The-Refspec).
- A remote called `origin`.
@@ -475,6 +478,47 @@ git fetch origin $REFSPECS --depth 50 --prune
Where `$REFSPECS` is a value provided to the runner internally by GitLab.
+### Git submodule update flags
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3192) in GitLab Runner 14.8.
+
+Use the `GIT_SUBMODULE_UPDATE_FLAGS` variable to control the behavior of `git submodule update`
+when [`GIT_SUBMODULE_STRATEGY`](#git-submodule-strategy) is set to either `normal` or `recursive`.
+You can set it globally or per-job in the [`variables`](../yaml/index.md#variables) section.
+
+`GIT_SUBMODULE_UPDATE_FLAGS` accepts all options of the
+[`git submodule update`](https://git-scm.com/docs/git-submodule#Documentation/git-submodule.txt-update--init--remote-N--no-fetch--no-recommend-shallow-f--force--checkout--rebase--merge--referenceltrepositorygt--depthltdepthgt--recursive--jobsltngt--no-single-branch--ltpathgt82308203)
+subcommand. However, note that `GIT_SUBMODULE_UPDATE_FLAGS` flags are appended after a few default flags:
+
+- `--init`, if [`GIT_SUBMODULE_STRATEGY`](#git-submodule-strategy) was set to `normal` or `recursive`.
+- `--recursive`, if [`GIT_SUBMODULE_STRATEGY`](#git-submodule-strategy) was set to `recursive`.
+- [`GIT_DEPTH`](#shallow-cloning). See the default value below.
+
+Git honors the last occurrence of a flag in the list of arguments, so manually
+providing them in `GIT_SUBMODULE_UPDATE_FLAGS` will also override these default flags.
+
+You can use this variable to fetch the latest remote `HEAD` instead of the commit tracked,
+in the repository, or to speed up the checkout by fetching submodules in multiple parallel jobs:
+
+```yaml
+variables:
+ GIT_SUBMODULE_STRATEGY: recursive
+ GIT_SUBMODULE_UPDATE_FLAGS: --remote --jobs 4
+script:
+ - ls -al .git/modules/
+```
+
+The configuration above results in `git submodule update` being called this way:
+
+```shell
+git submodule update --init --depth 50 --recursive --remote --jobs 4
+```
+
+WARNING:
+You should be aware of the implications for the security, stability, and reproducibility of
+your builds when using the `--remote` flag. In most cases, it is better to explicitly track
+submodule commits as designed, and update them using an auto-remediation/dependency bot.
+
### Shallow cloning
> Introduced in GitLab 8.9 as an experimental feature.
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index f478bad6d74..2ab7e46b096 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -170,10 +170,19 @@ After that, the next pipeline uses the up-to-date `knapsack/report-master.json`
### Flaky tests
+#### Automatic skipping of flaky tests
+
Tests that are [known to be flaky](testing_guide/flaky_tests.md#automatic-retries-and-flaky-tests-detection) are
skipped unless the `$SKIP_FLAKY_TESTS_AUTOMATICALLY` variable is set to `false` or if the `~"pipeline:run-flaky-tests"`
label is set on the MR.
+#### Automatic retry of failing tests in a separate process
+
+When the `$RETRY_FAILED_TESTS_IN_NEW_PROCESS` variable is set to `true`, RSpec tests that failed are automatically retried once in a separate
+RSpec process. The goal is to get rid of most side-effects from previous tests that may lead to a subsequent test failure.
+
+We keep track of retried tests in the `$RETRIED_TESTS_REPORT_FILE` file saved as artifact by the `rspec:flaky-tests-report` job.
+
### Monitoring
The GitLab test suite is [monitored](performance.md#rspec-profiling) for the `main` branch, and any branch
diff --git a/doc/development/service_ping/index.md b/doc/development/service_ping/index.md
index 428a34461d5..c9fd51addd0 100644
--- a/doc/development/service_ping/index.md
+++ b/doc/development/service_ping/index.md
@@ -205,6 +205,25 @@ sequenceDiagram
If a firewall exception is needed, the required URL depends on several things. If
the hostname is `version.gitlab.com`, the protocol is `TCP`, and the port number is `443`,
the required URL is <https://version.gitlab.com/>.
+1. In case of an error, it will be reported to the Version application along with following pieces of information:
+
+- `uuid` - GitLab instance unique identifier
+- `hostname` - GitLab instance hostname
+- `version` - GitLab instance current versions
+- `elapsed` - Amount of time which passed since Service Ping report process started and moment of error occurrence
+- `message` - Error message
+
+<pre>
+<code>
+{
+ "uuid"=>"02333324-1cd7-4c3b-a45b-a4993f05fb1d",
+ "hostname"=>"127.0.0.1",
+ "version"=>"14.7.0-pre",
+ "elapsed"=>0.006946,
+ "message"=>'PG::UndefinedColumn: ERROR: column \"non_existent_attribute\" does not exist\nLINE 1: SELECT COUNT(non_existent_attribute) FROM \"issues\" /*applica...'
+}
+</code>
+</pre>
### On a Geo secondary site
@@ -510,7 +529,7 @@ To generate Service Ping, use [Teleport](https://goteleport.com/docs/) or a deta
### Verification (After approx 30 hours)
-#### Verify with a detached screen session
+#### Verify with Teleport
1. Follow [the steps](https://gitlab.com/gitlab-com/runbooks/-/blob/master/docs/Teleport/Connect_to_Rails_Console_via_Teleport.md#how-to-use-teleport-to-connect-to-rails-console) to request a new access to the required environment and connect to the Rails console
1. Check the last payload in `raw_usage_data` table: `RawUsageData.last.payload`
diff --git a/lib/container_registry/base_client.rb b/lib/container_registry/base_client.rb
new file mode 100644
index 00000000000..5438d7a6a8c
--- /dev/null
+++ b/lib/container_registry/base_client.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+require 'faraday'
+require 'faraday_middleware'
+require 'digest'
+
+module ContainerRegistry
+ class BaseClient
+ DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE = 'application/vnd.docker.distribution.manifest.v2+json'
+ DOCKER_DISTRIBUTION_MANIFEST_LIST_V2_TYPE = 'application/vnd.docker.distribution.manifest.list.v2+json'
+ OCI_MANIFEST_V1_TYPE = 'application/vnd.oci.image.manifest.v1+json'
+ CONTAINER_IMAGE_V1_TYPE = 'application/vnd.docker.container.image.v1+json'
+
+ ACCEPTED_TYPES = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
+ ACCEPTED_TYPES_RAW = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE, DOCKER_DISTRIBUTION_MANIFEST_LIST_V2_TYPE].freeze
+
+ RETRY_EXCEPTIONS = [Faraday::Request::Retry::DEFAULT_EXCEPTIONS, Faraday::ConnectionFailed].flatten.freeze
+ RETRY_OPTIONS = {
+ max: 1,
+ interval: 5,
+ exceptions: RETRY_EXCEPTIONS
+ }.freeze
+
+ ERROR_CALLBACK_OPTIONS = {
+ callback: -> (env, exception) do
+ Gitlab::ErrorTracking.log_exception(
+ exception,
+ class: name,
+ url: env[:url]
+ )
+ end
+ }.freeze
+
+ # Taken from: FaradayMiddleware::FollowRedirects
+ REDIRECT_CODES = Set.new [301, 302, 303, 307]
+
+ class << self
+ private
+
+ def with_dummy_client(return_value_if_disabled: nil)
+ registry_config = Gitlab.config.registry
+ unless registry_config.enabled && registry_config.api_url.present?
+ return return_value_if_disabled
+ end
+
+ token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
+ yield new(registry_config.api_url, token: token)
+ end
+ end
+
+ def initialize(base_uri, options = {})
+ @base_uri = base_uri
+ @options = options
+ end
+
+ private
+
+ def faraday(timeout_enabled: true)
+ @faraday ||= faraday_base(timeout_enabled: timeout_enabled) do |conn|
+ initialize_connection(conn, @options, &method(:accept_manifest))
+ end
+ end
+
+ def faraday_base(timeout_enabled: true, &block)
+ request_options = timeout_enabled ? Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS : nil
+
+ Faraday.new(
+ @base_uri,
+ headers: { user_agent: "GitLab/#{Gitlab::VERSION}" },
+ request: request_options,
+ &block
+ )
+ end
+
+ def initialize_connection(conn, options)
+ conn.request :json
+
+ if options[:user] && options[:password]
+ conn.request(:basic_auth, options[:user].to_s, options[:password].to_s)
+ elsif options[:token]
+ conn.request(:authorization, :bearer, options[:token].to_s)
+ end
+
+ yield(conn) if block_given?
+
+ conn.request(:retry, RETRY_OPTIONS)
+ conn.request(:gitlab_error_callback, ERROR_CALLBACK_OPTIONS)
+ conn.adapter :net_http
+ end
+
+ def response_body(response, allow_redirect: false)
+ if allow_redirect && REDIRECT_CODES.include?(response.status)
+ response = redirect_response(response.headers['location'])
+ end
+
+ response.body if response && response.success?
+ end
+
+ def redirect_response(location)
+ return unless location
+
+ uri = URI(@base_uri).merge(location)
+ raise ArgumentError, "Invalid scheme for #{location}" unless %w[http https].include?(uri.scheme)
+
+ faraday_redirect.get(uri)
+ end
+
+ def accept_manifest(conn)
+ conn.headers['Accept'] = ACCEPTED_TYPES
+
+ conn.response :json, content_type: 'application/json'
+ conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws'
+ conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json'
+ conn.response :json, content_type: DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
+ conn.response :json, content_type: OCI_MANIFEST_V1_TYPE
+ end
+
+ # Create a new request to make sure the Authorization header is not inserted
+ # via the Faraday middleware
+ def faraday_redirect
+ @faraday_redirect ||= faraday_base do |conn|
+ conn.request :json
+
+ conn.request(:retry, RETRY_OPTIONS)
+ conn.request(:gitlab_error_callback, ERROR_CALLBACK_OPTIONS)
+ conn.adapter :net_http
+ end
+ end
+
+ def delete_if_exists(path)
+ result = faraday.delete(path)
+
+ result.success? || result.status == 404
+ end
+ end
+end
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index c2ad9e6ae89..add238350dd 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -1,68 +1,25 @@
# frozen_string_literal: true
-require 'faraday'
-require 'faraday_middleware'
-require 'digest'
-
module ContainerRegistry
- class Client
+ class Client < BaseClient
include Gitlab::Utils::StrongMemoize
attr_accessor :uri
- DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE = 'application/vnd.docker.distribution.manifest.v2+json'
- DOCKER_DISTRIBUTION_MANIFEST_LIST_V2_TYPE = 'application/vnd.docker.distribution.manifest.list.v2+json'
- OCI_MANIFEST_V1_TYPE = 'application/vnd.oci.image.manifest.v1+json'
- CONTAINER_IMAGE_V1_TYPE = 'application/vnd.docker.container.image.v1+json'
REGISTRY_VERSION_HEADER = 'gitlab-container-registry-version'
REGISTRY_FEATURES_HEADER = 'gitlab-container-registry-features'
REGISTRY_TAG_DELETE_FEATURE = 'tag_delete'
- ACCEPTED_TYPES = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
-
- ACCEPTED_TYPES_RAW = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE, DOCKER_DISTRIBUTION_MANIFEST_LIST_V2_TYPE].freeze
-
- # Taken from: FaradayMiddleware::FollowRedirects
- REDIRECT_CODES = Set.new [301, 302, 303, 307]
-
- RETRY_EXCEPTIONS = [Faraday::Request::Retry::DEFAULT_EXCEPTIONS, Faraday::ConnectionFailed].flatten.freeze
- RETRY_OPTIONS = {
- max: 1,
- interval: 5,
- exceptions: RETRY_EXCEPTIONS
- }.freeze
-
- ERROR_CALLBACK_OPTIONS = {
- callback: -> (env, exception) do
- Gitlab::ErrorTracking.log_exception(
- exception,
- class: name,
- url: env[:url]
- )
- end
- }.freeze
-
def self.supports_tag_delete?
- registry_config = Gitlab.config.registry
- return false unless registry_config.enabled && registry_config.api_url.present?
-
- token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
- client = new(registry_config.api_url, token: token)
- client.supports_tag_delete?
+ with_dummy_client(return_value_if_disabled: false) do |client|
+ client.supports_tag_delete?
+ end
end
def self.registry_info
- registry_config = Gitlab.config.registry
- return unless registry_config.enabled && registry_config.api_url.present?
-
- token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
- client = new(registry_config.api_url, token: token)
- client.registry_info
- end
-
- def initialize(base_uri, options = {})
- @base_uri = base_uri
- @options = options
+ with_dummy_client do |client|
+ client.registry_info
+ end
end
def registry_info
@@ -176,89 +133,11 @@ module ContainerRegistry
private
- def initialize_connection(conn, options)
- conn.request :json
-
- if options[:user] && options[:password]
- conn.request(:basic_auth, options[:user].to_s, options[:password].to_s)
- elsif options[:token]
- conn.request(:authorization, :bearer, options[:token].to_s)
- end
-
- yield(conn) if block_given?
-
- conn.request(:retry, RETRY_OPTIONS)
- conn.request(:gitlab_error_callback, ERROR_CALLBACK_OPTIONS)
- conn.adapter :net_http
- end
-
- def accept_manifest(conn)
- conn.headers['Accept'] = ACCEPTED_TYPES
-
- conn.response :json, content_type: 'application/json'
- conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws'
- conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json'
- conn.response :json, content_type: DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
- conn.response :json, content_type: OCI_MANIFEST_V1_TYPE
- end
-
- def response_body(response, allow_redirect: false)
- if allow_redirect && REDIRECT_CODES.include?(response.status)
- response = redirect_response(response.headers['location'])
- end
-
- response.body if response && response.success?
- end
-
- def redirect_response(location)
- return unless location
-
- uri = URI(@base_uri).merge(location)
- raise ArgumentError, "Invalid scheme for #{location}" unless %w[http https].include?(uri.scheme)
-
- faraday_redirect.get(uri)
- end
-
- def faraday(timeout_enabled: true)
- @faraday ||= faraday_base(timeout_enabled: timeout_enabled) do |conn|
- initialize_connection(conn, @options, &method(:accept_manifest))
- end
- end
-
def faraday_blob
@faraday_blob ||= faraday_base do |conn|
initialize_connection(conn, @options)
end
end
-
- # Create a new request to make sure the Authorization header is not inserted
- # via the Faraday middleware
- def faraday_redirect
- @faraday_redirect ||= faraday_base do |conn|
- conn.request :json
-
- conn.request(:retry, RETRY_OPTIONS)
- conn.request(:gitlab_error_callback, ERROR_CALLBACK_OPTIONS)
- conn.adapter :net_http
- end
- end
-
- def faraday_base(timeout_enabled: true, &block)
- request_options = timeout_enabled ? Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS : nil
-
- Faraday.new(
- @base_uri,
- headers: { user_agent: "GitLab/#{Gitlab::VERSION}" },
- request: request_options,
- &block
- )
- end
-
- def delete_if_exists(path)
- result = faraday.delete(path)
-
- result.success? || result.status == 404
- end
end
end
diff --git a/lib/container_registry/gitlab_api_client.rb b/lib/container_registry/gitlab_api_client.rb
new file mode 100644
index 00000000000..38929e5e904
--- /dev/null
+++ b/lib/container_registry/gitlab_api_client.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module ContainerRegistry
+ class GitlabApiClient < BaseClient
+ include Gitlab::Utils::StrongMemoize
+
+ IMPORT_RESPONSES = {
+ 200 => :already_imported,
+ 202 => :ok,
+ 401 => :unauthorized,
+ 404 => :not_found,
+ 409 => :already_being_imported,
+ 424 => :pre_import_failed,
+ 425 => :already_being_imported,
+ 429 => :too_many_imports
+ }.freeze
+
+ REGISTRY_GITLAB_V1_API_FEATURE = 'gitlab_v1_api'
+
+ def self.supports_gitlab_api?
+ with_dummy_client(return_value_if_disabled: false) do |client|
+ client.supports_gitlab_api?
+ end
+ end
+
+ def supports_gitlab_api?
+ strong_memoize(:supports_gitlab_api) do
+ registry_features = Gitlab::CurrentSettings.container_registry_features || []
+ next true if ::Gitlab.com? && registry_features.include?(REGISTRY_GITLAB_V1_API_FEATURE)
+
+ response = faraday.get('/gitlab/v1/')
+ response.success? || response.status == 401
+ end
+ end
+
+ def pre_import_repository(path)
+ response = start_import_for(path, pre: true)
+ IMPORT_RESPONSES.fetch(response.status, :error)
+ end
+
+ def import_repository(path)
+ response = start_import_for(path, pre: false)
+ IMPORT_RESPONSES.fetch(response.status, :error)
+ end
+
+ private
+
+ def start_import_for(path, pre:)
+ faraday.put("/gitlab/v1/import/#{path}") do |req|
+ req.params['pre'] = pre.to_s
+ end
+ end
+ end
+end
diff --git a/lib/container_registry/migration.rb b/lib/container_registry/migration.rb
new file mode 100644
index 00000000000..5983c3b8b93
--- /dev/null
+++ b/lib/container_registry/migration.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module ContainerRegistry
+ class Migration
+ class << self
+ delegate :container_registry_import_max_tags_count, to: ::Gitlab::CurrentSettings
+ delegate :container_registry_import_max_retries, to: ::Gitlab::CurrentSettings
+ delegate :container_registry_import_start_max_retries, to: ::Gitlab::CurrentSettings
+ delegate :container_registry_import_max_step_duration, to: ::Gitlab::CurrentSettings
+ delegate :container_registry_import_target_plan, to: ::Gitlab::CurrentSettings
+ delegate :container_registry_import_created_before, to: ::Gitlab::CurrentSettings
+
+ alias_method :max_tags_count, :container_registry_import_max_tags_count
+ alias_method :max_retries, :container_registry_import_max_retries
+ alias_method :start_max_retries, :container_registry_import_start_max_retries
+ alias_method :max_step_duration, :container_registry_import_max_step_duration
+ alias_method :target_plan_name, :container_registry_import_target_plan
+ alias_method :created_before, :container_registry_import_created_before
+ end
+
+ def self.enabled?
+ Feature.enabled?(:container_registry_migration_phase2_enabled)
+ end
+
+ def self.enqueue_waiting_time
+ return 0 if Feature.enabled?(:container_registry_migration_phase2_enqueue_speed_fast)
+ return 6.hours if Feature.enabled?(:container_registry_migration_phase2_enqueue_speed_slow)
+
+ 1.hour
+ end
+
+ def self.capacity
+ return 25 if Feature.enabled?(:container_registry_migration_phase2_capacity_25)
+ return 10 if Feature.enabled?(:container_registry_migration_phase2_capacity_10)
+ return 1 if Feature.enabled?(:container_registry_migration_phase2_capacity_1)
+
+ 0
+ end
+ end
+end
diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb
index 523364ac7c7..b69db564b25 100644
--- a/lib/container_registry/registry.rb
+++ b/lib/container_registry/registry.rb
@@ -2,12 +2,21 @@
module ContainerRegistry
class Registry
+ include Gitlab::Utils::StrongMemoize
+
attr_reader :uri, :client, :path
def initialize(uri, options = {})
@uri = uri
- @path = options[:path] || default_path
- @client = ContainerRegistry::Client.new(uri, options)
+ @options = options
+ @path = @options[:path] || default_path
+ @client = ContainerRegistry::Client.new(@uri, @options)
+ end
+
+ def gitlab_api_client
+ strong_memoize(:gitlab_api_client) do
+ ContainerRegistry::GitlabApiClient.new(@uri, @options)
+ end
end
private
diff --git a/lib/gitlab/database/gitlab_loose_foreign_keys.yml b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
index 235b49a1878..e3793c14ee2 100644
--- a/lib/gitlab/database/gitlab_loose_foreign_keys.yml
+++ b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
@@ -35,6 +35,9 @@ ci_job_token_project_scope_links:
- table: users
column: added_by_id
on_delete: async_nullify
+ - table: projects
+ column: source_project_id
+ on_delete: async_delete
ci_daily_build_group_report_results:
- table: namespaces
column: group_id
@@ -42,6 +45,10 @@ ci_daily_build_group_report_results:
- table: projects
column: project_id
on_delete: async_delete
+external_pull_requests:
+ - table: projects
+ column: project_id
+ on_delete: async_delete
ci_freeze_periods:
- table: projects
column: project_id
@@ -161,6 +168,10 @@ requirements_management_test_reports:
- table: ci_builds
column: build_id
on_delete: async_nullify
+ci_subscriptions_projects:
+ - table: projects
+ column: downstream_project_id
+ on_delete: async_delete
security_scans:
- table: ci_builds
column: build_id
diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb
index a20e545f6aa..07f4b333378 100644
--- a/lib/gitlab/import_export/saver.rb
+++ b/lib/gitlab/import_export/saver.rb
@@ -16,19 +16,20 @@ module Gitlab
def save
if compress_and_save
- Gitlab::Export::Logger.info(
- message: 'Export archive saved',
- exportable_class: @exportable.class.to_s,
- archive_file: archive_file
- )
+ log_export_results('Export archive saved')
save_upload
+
+ log_export_results('Export archive uploaded')
else
@shared.error(Gitlab::ImportExport::Error.new(error_message))
+
false
end
rescue StandardError => e
@shared.error(e)
+ log_export_results('Export archive saver failed')
+
false
ensure
remove_archive_tmp_dir
@@ -36,8 +37,16 @@ module Gitlab
private
+ attr_accessor :compress_duration_s, :assign_duration_s, :upload_duration_s, :upload_bytes
+
def compress_and_save
- tar_czf(archive: archive_file, dir: @shared.export_path)
+ result = nil
+
+ @compress_duration_s = Benchmark.realtime do
+ result = tar_czf(archive: archive_file, dir: @shared.export_path)
+ end
+
+ result
end
def remove_archive_tmp_dir
@@ -51,9 +60,12 @@ module Gitlab
def save_upload
upload = initialize_upload
- File.open(archive_file) { |file| upload.export_file = file }
+ @upload_bytes = File.size(archive_file)
+ @assign_duration_s = Benchmark.realtime do
+ File.open(archive_file) { |file| upload.export_file = file }
+ end
- upload.save!
+ @upload_duration_s = Benchmark.realtime { upload.save! }
true
end
@@ -67,6 +79,23 @@ module Gitlab
ImportExportUpload.find_or_initialize_by(Hash[exportable_kind, @exportable])
end
+
+ def log_export_results(message)
+ Gitlab::Export::Logger.info(message: message, **log_data)
+ end
+
+ def log_data
+ ApplicationContext.current.merge(
+ {
+ exportable_class: @exportable.class.to_s,
+ archive_file: archive_file,
+ compress_duration_s: compress_duration_s&.round(6),
+ assign_duration_s: assign_duration_s&.round(6),
+ upload_duration_s: upload_duration_s&.round(6),
+ upload_bytes: upload_bytes
+ }
+ ).compact
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index fa5fe2dea99..6a42992d50c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4494,22 +4494,28 @@ msgstr ""
msgid "ApprovalSettings|Merge request approval settings have been updated."
msgstr ""
+msgid "ApprovalSettings|Prevent approval by author"
+msgstr ""
+
msgid "ApprovalSettings|Prevent approval by author."
msgstr ""
+msgid "ApprovalSettings|Prevent approvals by users who add commits"
+msgstr ""
+
msgid "ApprovalSettings|Prevent approvals by users who add commits."
msgstr ""
-msgid "ApprovalSettings|Prevent editing approval rules in merge requests."
+msgid "ApprovalSettings|Prevent editing approval rules in merge requests"
msgstr ""
msgid "ApprovalSettings|Prevent editing approval rules in projects and merge requests."
msgstr ""
-msgid "ApprovalSettings|Remove all approvals when commits are added to the source branch."
+msgid "ApprovalSettings|Remove all approvals when commits are added to the source branch"
msgstr ""
-msgid "ApprovalSettings|Require user password to approve."
+msgid "ApprovalSettings|Require user password to approve"
msgstr ""
msgid "ApprovalSettings|There was an error loading merge request approval settings."
@@ -31543,7 +31549,7 @@ msgstr ""
msgid "SecurityApprovals|License-Check"
msgstr ""
-msgid "SecurityApprovals|Requires approval for Denied licenses. %{linkStart}More information%{linkEnd}"
+msgid "SecurityApprovals|Requires approval for Denied licenses. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "SecurityApprovals|Requires approval for decreases in test coverage. %{linkStart}Learn more.%{linkEnd}"
diff --git a/rubocop/cop/file_decompression.rb b/rubocop/cop/file_decompression.rb
new file mode 100644
index 00000000000..44813244028
--- /dev/null
+++ b/rubocop/cop/file_decompression.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ # Check for symlinks when extracting files to avoid arbitrary file reading.
+ class FileDecompression < RuboCop::Cop::Cop
+ MSG = <<~EOF
+ While extracting files check for symlink to avoid arbitrary file reading.
+ https://gitlab.com/gitlab-com/gl-infra/production/-/issues/6132
+ EOF
+
+ def_node_matcher :system?, <<~PATTERN
+ (send {nil? | const} {:system | :exec | :spawn | :popen}
+ (str $_))
+ PATTERN
+
+ def_node_matcher :subshell?, <<~PATTERN
+ (xstr
+ (str $_))
+ PATTERN
+
+ FORBIDDEN_COMMANDS = %w[gunzip gzip zip tar].freeze
+
+ def on_xstr(node)
+ subshell?(node) do |match|
+ add_offense(node, message: MSG) if forbidden_command?(match)
+ end
+ end
+
+ def on_send(node)
+ system?(node) do |match|
+ add_offense(node, location: :expression, message: MSG) if forbidden_command?(match)
+ end
+ end
+
+ private
+
+ def forbidden_command?(cmd)
+ FORBIDDEN_COMMANDS.any? do |forbidden|
+ cmd.match?(forbidden)
+ end
+ end
+ end
+ end
+end
diff --git a/scripts/insert-rspec-profiling-data b/scripts/insert-rspec-profiling-data
index be25972644c..996ad78ba5f 100755
--- a/scripts/insert-rspec-profiling-data
+++ b/scripts/insert-rspec-profiling-data
@@ -43,4 +43,4 @@ def insert_data(path)
end
end
-insert_data('rspec_profiling') if ENV['RSPEC_PROFILING_POSTGRES_URL'].present?
+insert_data(ENV['RSPEC_PROFILING_FOLDER_PATH']) if ENV['RSPEC_PROFILING_POSTGRES_URL'].present?
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index 2a6eb91a1f3..a6d9899bf2a 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -1,15 +1,18 @@
#!/usr/bin/env bash
function retrieve_tests_metadata() {
- mkdir -p $(dirname "$KNAPSACK_RSPEC_SUITE_REPORT_PATH") $(dirname "$FLAKY_RSPEC_SUITE_REPORT_PATH") rspec_profiling/
+ mkdir -p $(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}") $(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}") "${RSPEC_PROFILING_FOLDER_PATH}"
if [[ -n "${RETRIEVE_TESTS_METADATA_FROM_PAGES}" ]]; then
if [[ ! -f "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ]]; then
- curl --location -o "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "https://gitlab-org.gitlab.io/gitlab/${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
+ curl --location -o "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "https://gitlab-org.gitlab.io/gitlab/${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ||
+ echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
fi
if [[ ! -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ]]; then
- curl --location -o "${FLAKY_RSPEC_SUITE_REPORT_PATH}" "https://gitlab-org.gitlab.io/gitlab/${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
+ curl --location -o "${FLAKY_RSPEC_SUITE_REPORT_PATH}" "https://gitlab-org.gitlab.io/gitlab/${FLAKY_RSPEC_SUITE_REPORT_PATH}" ||
+ curl --location -o "${FLAKY_RSPEC_SUITE_REPORT_PATH}" "https://gitlab-org.gitlab.io/gitlab/rspec_flaky/report-suite.json" || # temporary back-compat
+ echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
fi
else
# ${CI_DEFAULT_BRANCH} might not be master in other forks but we want to
@@ -31,7 +34,14 @@ function retrieve_tests_metadata() {
fi
if [[ ! -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ]]; then
- scripts/api/download_job_artifact.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
+ scripts/api/download_job_artifact.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ||
+ scripts/api/download_job_artifact.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "rspec_flaky/report-suite.json" || # temporary back-compat
+ echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
+
+ # temporary back-compat
+ if [[ -f "rspec_flaky/report-suite.json" ]]; then
+ mv "rspec_flaky/report-suite.json" "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
+ fi
fi
else
echo "test_metadata_job_id couldn't be found!"
@@ -42,21 +52,24 @@ function retrieve_tests_metadata() {
}
function update_tests_metadata() {
+ local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}")/"
+ local knapsack_folder_path="$(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}")/"
+
echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
- scripts/merge-reports "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" knapsack/rspec*.json
- rm -f knapsack/rspec*.json
+ scripts/merge-reports "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ${knapsack_folder_path}rspec*.json
export FLAKY_RSPEC_GENERATE_REPORT="true"
- scripts/merge-reports "${FLAKY_RSPEC_SUITE_REPORT_PATH}" rspec_flaky/all_*.json
+ scripts/merge-reports "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ${rspec_flaky_folder_path}all_*.json
scripts/flaky_examples/prune-old-flaky-examples "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
- rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then
scripts/insert-rspec-profiling-data
else
echo "Not inserting profiling data as the pipeline is not a scheduled one."
fi
+
+ cleanup_individual_job_reports
}
function retrieve_tests_mapping() {
@@ -158,12 +171,20 @@ function retrieve_previous_failed_tests() {
scripts/failed_tests.rb --previous-tests-report-path "${pipeline_report_path}" --output-directory "${directory_for_output_reports}" --rspec-pg-regex "${rspec_pg_regex}" --rspec-ee-pg-regex "${rspec_ee_pg_regex}"
}
-function rspec_simple_job() {
+function rspec_args() {
local rspec_opts="${1}"
+ local junit_report_file="${2:-${JUNIT_RESULT_FILE}}"
+
+ echo "-Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out ${junit_report_file} ${rspec_opts}"
+}
+function rspec_simple_job() {
export NO_KNAPSACK="1"
- eval "bin/rspec -Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}"
+ local rspec_cmd="bin/rspec $(rspec_args "${1}" "${2}")"
+ echoinfo "Running RSpec command: ${rspec_cmd}"
+
+ eval "${rspec_cmd}"
}
function rspec_db_library_code() {
@@ -172,6 +193,24 @@ function rspec_db_library_code() {
rspec_simple_job "-- ${db_files}"
}
+function debug_rspec_variables() {
+ echoinfo "SKIP_FLAKY_TESTS_AUTOMATICALLY: ${SKIP_FLAKY_TESTS_AUTOMATICALLY}"
+ echoinfo "RETRY_FAILED_TESTS_IN_NEW_PROCESS: ${RETRY_FAILED_TESTS_IN_NEW_PROCESS}"
+
+ echoinfo "KNAPSACK_GENERATE_REPORT: ${KNAPSACK_GENERATE_REPORT}"
+ echoinfo "FLAKY_RSPEC_GENERATE_REPORT: ${FLAKY_RSPEC_GENERATE_REPORT}"
+
+ echoinfo "KNAPSACK_TEST_FILE_PATTERN: ${KNAPSACK_TEST_FILE_PATTERN}"
+ echoinfo "KNAPSACK_LOG_LEVEL: ${KNAPSACK_LOG_LEVEL}"
+ echoinfo "KNAPSACK_REPORT_PATH: ${KNAPSACK_REPORT_PATH}"
+
+ echoinfo "FLAKY_RSPEC_SUITE_REPORT_PATH: ${FLAKY_RSPEC_SUITE_REPORT_PATH}"
+ echoinfo "FLAKY_RSPEC_REPORT_PATH: ${FLAKY_RSPEC_REPORT_PATH}"
+ echoinfo "NEW_FLAKY_RSPEC_REPORT_PATH: ${NEW_FLAKY_RSPEC_REPORT_PATH}"
+ echoinfo "SKIPPED_FLAKY_TESTS_REPORT_PATH: ${SKIPPED_FLAKY_TESTS_REPORT_PATH}"
+ echoinfo "RETRIED_TESTS_REPORT_PATH: ${RETRIED_TESTS_REPORT_PATH}"
+}
+
function rspec_paralellized_job() {
read -ra job_name <<< "${CI_JOB_NAME}"
local test_tool="${job_name[0]}"
@@ -179,6 +218,9 @@ function rspec_paralellized_job() {
local report_name=$(echo "${CI_JOB_NAME}" | sed -E 's|[/ ]|_|g') # e.g. 'rspec unit pg12 1/24' would become 'rspec_unit_pg12_1_24'
local rspec_opts="${1}"
local spec_folder_prefixes=""
+ local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}")/"
+ local knapsack_folder_path="$(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}")/"
+ local rspec_run_status=0
if [[ "${test_tool}" =~ "-ee" ]]; then
spec_folder_prefixes="'ee/'"
@@ -193,7 +235,7 @@ function rspec_paralellized_job() {
fi
export KNAPSACK_LOG_LEVEL="debug"
- export KNAPSACK_REPORT_PATH="knapsack/${report_name}_report.json"
+ export KNAPSACK_REPORT_PATH="${knapsack_folder_path}${report_name}_report.json"
# There's a bug where artifacts are sometimes not downloaded. Since specs can run without the Knapsack report, we can
# handle the missing artifact gracefully here. See https://gitlab.com/gitlab-org/gitlab/-/issues/212349.
@@ -208,16 +250,14 @@ function rspec_paralellized_job() {
export KNAPSACK_TEST_FILE_PATTERN="${pattern}"
fi
- echo "KNAPSACK_TEST_FILE_PATTERN: ${KNAPSACK_TEST_FILE_PATTERN}"
- echo "SKIP_FLAKY_TESTS_AUTOMATICALLY: ${SKIP_FLAKY_TESTS_AUTOMATICALLY}"
+ export FLAKY_RSPEC_REPORT_PATH="${rspec_flaky_folder_path}all_${report_name}_report.json"
+ export NEW_FLAKY_RSPEC_REPORT_PATH="${rspec_flaky_folder_path}new_${report_name}_report.json"
+ export SKIPPED_FLAKY_TESTS_REPORT_PATH="${rspec_flaky_folder_path}skipped_flaky_tests_${report_name}_report.txt"
+ export RETRIED_TESTS_REPORT_PATH="${rspec_flaky_folder_path}retried_tests_${report_name}_report.txt"
if [[ -d "ee/" ]]; then
export KNAPSACK_GENERATE_REPORT="true"
export FLAKY_RSPEC_GENERATE_REPORT="true"
- export SUITE_FLAKY_RSPEC_REPORT_PATH="${FLAKY_RSPEC_SUITE_REPORT_PATH}"
- export FLAKY_RSPEC_REPORT_PATH="rspec_flaky/all_${report_name}_report.json"
- export NEW_FLAKY_RSPEC_REPORT_PATH="rspec_flaky/new_${report_name}_report.json"
- export SKIPPED_FLAKY_TESTS_REPORT_PATH="rspec_flaky/skipped_flaky_tests_${report_name}_report.txt"
if [[ ! -f $FLAKY_RSPEC_REPORT_PATH ]]; then
echo "{}" > "${FLAKY_RSPEC_REPORT_PATH}"
@@ -228,19 +268,44 @@ function rspec_paralellized_job() {
fi
fi
+ debug_rspec_variables
+
mkdir -p tmp/memory_test
export MEMORY_TEST_PATH="tmp/memory_test/${report_name}_memory.csv"
- local rspec_args="-Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}"
-
if [[ -n $RSPEC_TESTS_MAPPING_ENABLED ]]; then
- tooling/bin/parallel_rspec --rspec_args "${rspec_args}" --filter "tmp/matching_tests.txt"
+ tooling/bin/parallel_rspec --rspec_args "$(rspec_args)" --filter "tmp/matching_tests.txt" || rspec_run_status=$?
else
- tooling/bin/parallel_rspec --rspec_args "${rspec_args}"
+ tooling/bin/parallel_rspec --rspec_args "$(rspec_args "${rspec_opts}")" || rspec_run_status=$?
fi
- date
+ echoinfo "RSpec exited with ${rspec_run_status}."
+
+ # Experiment to retry failed examples in a new RSpec process: https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1148
+ if [[ $rspec_run_status -ne 0 ]]; then
+ if [[ "${RETRY_FAILED_TESTS_IN_NEW_PROCESS}" == "true" ]]; then
+ # Keep track of the tests that are retried, later consolidated in a single file by the `rspec:flaky-tests-report` job
+ local failed_examples=$(grep " failed" ${RSPEC_LAST_RUN_RESULTS_FILE})
+ echo "${CI_JOB_URL}" > "${RETRIED_TESTS_REPORT_PATH}"
+ echo $failed_examples >> "${RETRIED_TESTS_REPORT_PATH}"
+
+ echoinfo "Retrying the failing examples in a new RSpec proces..."
+
+ install_junit_merge_gem
+
+ # Retry only the tests that failed on first try
+ rspec_simple_job "--only-failures" "${JUNIT_RETRY_FILE}"
+ rspec_run_status=$?
+
+ # Merge the JUnit report from retry into the first-try report
+ junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}"
+ fi
+ else
+ echosuccess "No examples to retry, congrats!"
+ fi
+
+ exit $rspec_run_status
}
function rspec_rerun_previous_failed_tests() {
@@ -330,3 +395,30 @@ function generate_frontend_fixtures_mapping() {
rspec_simple_job "--pattern \"${pattern}\""
}
+
+function cleanup_individual_job_reports() {
+ local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}")/"
+ local knapsack_folder_path="$(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}")/"
+
+ rm -rf ${knapsack_folder_path}rspec*.json \
+ ${rspec_flaky_folder_path}all_*.json \
+ ${rspec_flaky_folder_path}new_*.json \
+ ${rspec_flaky_folder_path}skipped_flaky_tests_*_report.txt \
+ ${rspec_flaky_folder_path}retried_tests_*_report.txt \
+ ${RSPEC_LAST_RUN_RESULTS_FILE} \
+ ${RSPEC_PROFILING_FOLDER_PATH}/**/*
+ rmdir ${RSPEC_PROFILING_FOLDER_PATH} || true
+}
+
+function generate_flaky_tests_reports() {
+ local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}")/"
+
+ debug_rspec_variables
+
+ mkdir -p ${rspec_flaky_folder_path}
+
+ find ${rspec_flaky_folder_path} -type f -name 'skipped_flaky_tests_*_report.txt' -exec cat {} + >> "${SKIPPED_FLAKY_TESTS_REPORT_PATH}"
+ find ${rspec_flaky_folder_path} -type f -name 'retried_tests_*_report.txt' -exec cat {} + >> "${RETRIED_TESTS_REPORT_PATH}"
+
+ cleanup_individual_job_reports
+}
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 15047d35fc3..31afc225019 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -64,16 +64,20 @@ function setup_db() {
}
function install_api_client_dependencies_with_apk() {
- apk add --update openssl curl jq
+ run_timed_command "apk add --update openssl curl jq"
}
function install_gitlab_gem() {
- gem install httparty --no-document --version 0.18.1
- gem install gitlab --no-document --version 4.17.0
+ run_timed_command "gem install httparty --no-document --version 0.18.1"
+ run_timed_command "gem install gitlab --no-document --version 4.17.0"
}
function install_tff_gem() {
- gem install test_file_finder --version 0.1.1
+ run_timed_command "gem install test_file_finder --no-document --version 0.1.1"
+}
+
+function install_junit_merge_gem() {
+ run_timed_command "gem install junit_merge --no-document --version 0.1.2"
}
function run_timed_command() {
diff --git a/spec/factories/container_repositories.rb b/spec/factories/container_repositories.rb
index 86bb129067f..ce83e9e8006 100644
--- a/spec/factories/container_repositories.rb
+++ b/spec/factories/container_repositories.rb
@@ -33,6 +33,52 @@ FactoryBot.define do
expiration_policy_cleanup_status { :cleanup_ongoing }
end
+ trait :default do
+ migration_state { 'default' }
+ end
+
+ trait :pre_importing do
+ migration_state { 'pre_importing' }
+ migration_pre_import_started_at { Time.zone.now }
+ end
+
+ trait :pre_import_done do
+ migration_state { 'pre_import_done' }
+ migration_pre_import_started_at { Time.zone.now }
+ migration_pre_import_done_at { Time.zone.now }
+ end
+
+ trait :importing do
+ migration_state { 'importing' }
+ migration_pre_import_started_at { Time.zone.now }
+ migration_pre_import_done_at { Time.zone.now }
+ migration_import_started_at { Time.zone.now }
+ end
+
+ trait :import_done do
+ migration_state { 'import_done' }
+ migration_pre_import_started_at { Time.zone.now }
+ migration_pre_import_done_at { Time.zone.now }
+ migration_import_started_at { Time.zone.now }
+ migration_import_done_at { Time.zone.now }
+ end
+
+ trait :import_aborted do
+ migration_state { 'import_aborted' }
+ migration_pre_import_started_at { Time.zone.now }
+ migration_pre_import_done_at { Time.zone.now }
+ migration_import_started_at { Time.zone.now }
+ migration_aborted_at { Time.zone.now }
+ migration_aborted_in_state { 'importing' }
+ migration_retries_count { 1 }
+ end
+
+ trait :import_skipped do
+ migration_state { 'import_skipped' }
+ migration_skipped_at { Time.zone.now }
+ migration_skipped_reason { :too_many_tags }
+ end
+
after(:build) do |repository, evaluator|
next if evaluator.tags.to_a.none?
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index f93daf91c2f..145af3a2d5b 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -1,8 +1,10 @@
+import { nextTick } from 'vue';
+import { GlButton } from '@gitlab/ui';
import { getByRole } from '@testing-library/dom';
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
import DraftNote from '~/batch_comments/components/draft_note.vue';
+import PublishButton from '~/batch_comments/components/publish_button.vue';
import { createStore } from '~/batch_comments/stores';
import NoteableNote from '~/notes/components/noteable_note.vue';
import '~/behaviors/markdown/render_gfm';
@@ -28,6 +30,8 @@ describe('Batch comments draft note component', () => {
};
const getList = () => getByRole(wrapper.element, 'list');
+ const findSubmitReviewButton = () => wrapper.findComponent(PublishButton);
+ const findAddCommentButton = () => wrapper.findComponent(GlButton);
const createComponent = (propsData = { draft }) => {
wrapper = shallowMount(DraftNote, {
@@ -63,7 +67,7 @@ describe('Batch comments draft note component', () => {
describe('add comment now', () => {
it('dispatches publishSingleDraft when clicking', () => {
createComponent();
- const publishNowButton = wrapper.find({ ref: 'publishNowButton' });
+ const publishNowButton = findAddCommentButton();
publishNowButton.vm.$emit('click');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith(
@@ -77,10 +81,33 @@ describe('Batch comments draft note component', () => {
wrapper.vm.$store.state.batchComments.currentlyPublishingDrafts.push(1);
await nextTick();
- const publishNowButton = wrapper.find({ ref: 'publishNowButton' });
+ const publishNowButton = findAddCommentButton();
expect(publishNowButton.props().loading).toBe(true);
});
+
+ it('sets as disabled when review is publishing', async () => {
+ createComponent();
+ wrapper.vm.$store.state.batchComments.isPublishing = true;
+
+ await nextTick();
+ const publishNowButton = findAddCommentButton();
+
+ expect(publishNowButton.props().disabled).toBe(true);
+ expect(publishNowButton.props().loading).toBe(false);
+ });
+ });
+
+ describe('submit review', () => {
+ it('sets as disabled when draft is publishing', async () => {
+ createComponent();
+ wrapper.vm.$store.state.batchComments.currentlyPublishingDrafts.push(1);
+
+ await nextTick();
+ const publishNowButton = findSubmitReviewButton();
+
+ expect(publishNowButton.attributes().disabled).toBeTruthy();
+ });
});
describe('update', () => {
diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb
index df6490df915..9251fbf24d9 100644
--- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb
+++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb
@@ -43,34 +43,99 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
# Only thing we can do is to verify that args from the resolver is correctly transformed to params of the Finder and we return the Finder's result back.
describe 'Allowed query arguments' do
let(:finder) { instance_double(::Ci::RunnersFinder) }
- let(:args) do
- {
- active: true,
- status: 'active',
- type: :instance_type,
- tag_list: ['active_runner'],
- search: 'abc',
- sort: :contacted_asc
- }
+
+ context 'with active filter' do
+ let(:args) do
+ {
+ active: true,
+ status: 'active',
+ type: :instance_type,
+ tag_list: ['active_runner'],
+ search: 'abc',
+ sort: :contacted_asc
+ }
+ end
+
+ let(:expected_params) do
+ {
+ active: true,
+ status_status: 'active',
+ type_type: :instance_type,
+ tag_name: ['active_runner'],
+ preload: { tag_name: nil },
+ search: 'abc',
+ sort: 'contacted_asc'
+ }
+ end
+
+ it 'calls RunnersFinder with expected arguments' do
+ expect(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
+ allow(finder).to receive(:execute).once.and_return([:execute_return_value])
+
+ expect(subject.items.to_a).to eq([:execute_return_value])
+ end
+ end
+
+ context 'with both active and paused filter' do
+ let(:args) do
+ {
+ active: true,
+ paused: true
+ }
+ end
+
+ let(:expected_params) do
+ {
+ active: false,
+ preload: { tag_name: nil }
+ }
+ end
+
+ it 'calls RunnersFinder with expected arguments' do
+ expect(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
+ allow(finder).to receive(:execute).once.and_return([:execute_return_value])
+
+ expect(subject.items.to_a).to eq([:execute_return_value])
+ end
end
- let(:expected_params) do
- {
- active: true,
- status_status: 'active',
- type_type: :instance_type,
- tag_name: ['active_runner'],
- preload: { tag_name: nil },
- search: 'abc',
- sort: 'contacted_asc'
- }
+ context 'with paused filter' do
+ let(:args) do
+ { paused: true }
+ end
+
+ let(:expected_params) do
+ {
+ active: false,
+ preload: { tag_name: nil }
+ }
+ end
+
+ it 'calls RunnersFinder with expected arguments' do
+ expect(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
+ allow(finder).to receive(:execute).once.and_return([:execute_return_value])
+
+ expect(subject.items.to_a).to eq([:execute_return_value])
+ end
end
- it 'calls RunnersFinder with expected arguments' do
- allow(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
- allow(finder).to receive(:execute).once.and_return([:execute_return_value])
+ context 'with neither paused or active filters' do
+ let(:args) do
+ {}
+ end
+
+ let(:expected_params) do
+ {
+ preload: { tag_name: nil }
+ }
+ end
+
+ it 'calls RunnersFinder with expected arguments' do
+ expect(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
+ allow(finder).to receive(:execute).once.and_return([:execute_return_value])
- expect(subject.items.to_a).to eq([:execute_return_value])
+ expect(subject.items.to_a).to eq([:execute_return_value])
+ end
end
end
end
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index 259d7d5ad13..974c3478ddc 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -5,29 +5,7 @@ require 'spec_helper'
RSpec.describe ContainerRegistry::Client do
using RSpec::Parameterized::TableSyntax
- let(:token) { '12345' }
- let(:options) { { token: token } }
- let(:registry_api_url) { 'http://container-registry' }
- let(:client) { described_class.new(registry_api_url, options) }
- let(:push_blob_headers) do
- {
- 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
- 'Authorization' => "bearer #{token}",
- 'Content-Type' => 'application/octet-stream',
- 'User-Agent' => "GitLab/#{Gitlab::VERSION}"
- }
- end
-
- let(:headers_with_accept_types) do
- {
- 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
- 'Authorization' => "bearer #{token}",
- 'User-Agent' => "GitLab/#{Gitlab::VERSION}"
- }
- end
-
- let(:expected_faraday_headers) { { user_agent: "GitLab/#{Gitlab::VERSION}" } }
- let(:expected_faraday_request_options) { Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS }
+ include_context 'container registry client'
shared_examples 'handling timeouts' do
let(:retry_options) do
@@ -48,14 +26,14 @@ RSpec.describe ContainerRegistry::Client do
retry_block: -> (_, _, _, _) { actual_retries += 1 }
)
- stub_const('ContainerRegistry::Client::RETRY_OPTIONS', retry_options_with_block)
+ stub_const('ContainerRegistry::BaseClient::RETRY_OPTIONS', retry_options_with_block)
expect { subject }.to raise_error(Faraday::ConnectionFailed)
expect(actual_retries).to eq(retry_options_with_block[:max])
end
it 'logs the error' do
- stub_const('ContainerRegistry::Client::RETRY_OPTIONS', retry_options)
+ stub_const('ContainerRegistry::BaseClient::RETRY_OPTIONS', retry_options)
expect(Gitlab::ErrorTracking)
.to receive(:log_exception)
@@ -63,7 +41,7 @@ RSpec.describe ContainerRegistry::Client do
.times
.with(
an_instance_of(Faraday::ConnectionFailed),
- class: described_class.name,
+ class: ::ContainerRegistry::BaseClient.name,
url: URI(url)
)
@@ -325,14 +303,14 @@ RSpec.describe ContainerRegistry::Client do
subject { client.supports_tag_delete? }
where(:registry_tags_support_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do
- true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true
- true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | true
- true | true | [] | true | true
- true | false | [] | true | true
- false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true
- false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | false
- false | true | [] | true | false
- false | false | [] | true | false
+ true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true
+ true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | true
+ true | true | [] | true | true
+ true | false | [] | true | true
+ false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true
+ false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | false
+ false | true | [] | true | false
+ false | false | [] | true | false
end
with_them do
@@ -366,38 +344,38 @@ RSpec.describe ContainerRegistry::Client do
subject { described_class.supports_tag_delete? }
where(:registry_api_url, :registry_enabled, :registry_tags_support_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do
- 'http://sandbox.local' | true | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true
- 'http://sandbox.local' | true | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | true
- 'http://sandbox.local' | true | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true
- 'http://sandbox.local' | true | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | false
- 'http://sandbox.local' | false | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- 'http://sandbox.local' | false | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- 'http://sandbox.local' | false | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- 'http://sandbox.local' | false | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- 'http://sandbox.local' | true | true | true | [] | true | true
- 'http://sandbox.local' | true | true | false | [] | true | true
- 'http://sandbox.local' | true | false | true | [] | true | false
- 'http://sandbox.local' | true | false | false | [] | true | false
- 'http://sandbox.local' | false | true | true | [] | false | false
- 'http://sandbox.local' | false | true | false | [] | false | false
- 'http://sandbox.local' | false | false | true | [] | false | false
- 'http://sandbox.local' | false | false | false | [] | false | false
- '' | true | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | true | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | true | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | true | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | false | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | false | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | false | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | false | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | true | true | true | [] | false | false
- '' | true | true | false | [] | false | false
- '' | true | false | true | [] | false | false
- '' | true | false | false | [] | false | false
- '' | false | true | true | [] | false | false
- '' | false | true | false | [] | false | false
- '' | false | false | true | [] | false | false
- '' | false | false | false | [] | false | false
+ 'http://sandbox.local' | true | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true
+ 'http://sandbox.local' | true | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | true
+ 'http://sandbox.local' | true | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true
+ 'http://sandbox.local' | true | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | false
+ 'http://sandbox.local' | false | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ 'http://sandbox.local' | false | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ 'http://sandbox.local' | false | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ 'http://sandbox.local' | false | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ 'http://sandbox.local' | true | true | true | [] | true | true
+ 'http://sandbox.local' | true | true | false | [] | true | true
+ 'http://sandbox.local' | true | false | true | [] | true | false
+ 'http://sandbox.local' | true | false | false | [] | true | false
+ 'http://sandbox.local' | false | true | true | [] | false | false
+ 'http://sandbox.local' | false | true | false | [] | false | false
+ 'http://sandbox.local' | false | false | true | [] | false | false
+ 'http://sandbox.local' | false | false | false | [] | false | false
+ '' | true | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | true | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | true | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | true | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | false | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | false | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | false | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | false | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | true | true | true | [] | false | false
+ '' | true | true | false | [] | false | false
+ '' | true | false | true | [] | false | false
+ '' | true | false | false | [] | false | false
+ '' | false | true | true | [] | false | false
+ '' | false | true | false | [] | false | false
+ '' | false | false | true | [] | false | false
+ '' | false | false | false | [] | false | false
end
with_them do
diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb
new file mode 100644
index 00000000000..251e15390b1
--- /dev/null
+++ b/spec/lib/container_registry/gitlab_api_client_spec.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::GitlabApiClient do
+ using RSpec::Parameterized::TableSyntax
+
+ include_context 'container registry client'
+
+ describe '#supports_gitlab_api?' do
+ subject { client.supports_gitlab_api? }
+
+ where(:registry_gitlab_api_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do
+ false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true
+ true | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | true
+ true | true | [] | true | true
+ true | false | [] | true | true
+ false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true
+ false | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | false
+ false | true | [] | true | false
+ false | false | [] | true | false
+ end
+
+ with_them do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(is_on_dot_com)
+ stub_registry_gitlab_api_support(registry_gitlab_api_enabled)
+ stub_application_setting(container_registry_features: container_registry_features)
+ end
+
+ it 'returns the expected result' do
+ if expect_registry_to_be_pinged
+ expect_next_instance_of(Faraday::Connection) do |connection|
+ expect(connection).to receive(:run_request).and_call_original
+ end
+ else
+ expect(Faraday::Connection).not_to receive(:new)
+ end
+
+ expect(subject).to be expected_result
+ end
+ end
+
+ context 'with 401 response' do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(false)
+ stub_application_setting(container_registry_features: [])
+ stub_request(:get, "#{registry_api_url}/gitlab/v1/")
+ .to_return(status: 401, body: '')
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#pre_import_repository' do
+ let(:path) { 'namespace/path/to/repository' }
+
+ subject { client.pre_import_repository('namespace/path/to/repository') }
+
+ where(:status_code, :expected_result) do
+ 200 | :already_imported
+ 202 | :ok
+ 401 | :unauthorized
+ 404 | :not_found
+ 409 | :already_being_imported
+ 418 | :error
+ 424 | :pre_import_failed
+ 425 | :already_being_imported
+ 429 | :too_many_imports
+ end
+
+ with_them do
+ before do
+ stub_pre_import(path, status_code, pre: true)
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#pre_import_repository' do
+ let(:path) { 'namespace/path/to/repository' }
+
+ subject { client.import_repository('namespace/path/to/repository') }
+
+ where(:status_code, :expected_result) do
+ 200 | :already_imported
+ 202 | :ok
+ 401 | :unauthorized
+ 404 | :not_found
+ 409 | :already_being_imported
+ 418 | :error
+ 424 | :pre_import_failed
+ 425 | :already_being_imported
+ 429 | :too_many_imports
+ end
+
+ with_them do
+ before do
+ stub_pre_import(path, status_code, pre: false)
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '.supports_gitlab_api?' do
+ subject { described_class.supports_gitlab_api? }
+
+ where(:registry_gitlab_api_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do
+ true | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true
+ true | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | true
+ false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true
+ false | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | false
+ true | true | [] | true | true
+ true | false | [] | true | true
+ false | true | [] | true | false
+ false | false | [] | true | false
+ end
+
+ with_them do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(is_on_dot_com)
+ stub_container_registry_config(enabled: true, api_url: registry_api_url, key: 'spec/fixtures/x509_certificate_pk.key')
+ stub_registry_gitlab_api_support(registry_gitlab_api_enabled)
+ stub_application_setting(container_registry_features: container_registry_features)
+ end
+
+ it 'returns the expected result' do
+ if expect_registry_to_be_pinged
+ expect_next_instance_of(Faraday::Connection) do |connection|
+ expect(connection).to receive(:run_request).and_call_original
+ end
+ else
+ expect(Faraday::Connection).not_to receive(:new)
+ end
+
+ expect(subject).to be expected_result
+ end
+ end
+
+ context 'with the registry disabled' do
+ before do
+ stub_container_registry_config(enabled: false, api_url: 'http://sandbox.local', key: 'spec/fixtures/x509_certificate_pk.key')
+ end
+
+ it 'returns false' do
+ expect(Faraday::Connection).not_to receive(:new)
+
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'with a blank registry url' do
+ before do
+ stub_container_registry_config(enabled: true, api_url: '', key: 'spec/fixtures/x509_certificate_pk.key')
+ end
+
+ it 'returns false' do
+ expect(Faraday::Connection).not_to receive(:new)
+
+ expect(subject).to be_falsey
+ end
+ end
+ end
+
+ def stub_pre_import(path, status_code, pre:)
+ stub_request(:put, "#{registry_api_url}/gitlab/v1/import/#{path}?pre=#{pre}")
+ .to_return(status: status_code, body: '')
+ end
+
+ def stub_registry_gitlab_api_support(supported = true)
+ status_code = supported ? 200 : 404
+ stub_request(:get, "#{registry_api_url}/gitlab/v1/")
+ .to_return(status: status_code, body: '')
+ end
+end
diff --git a/spec/lib/container_registry/migration_spec.rb b/spec/lib/container_registry/migration_spec.rb
new file mode 100644
index 00000000000..cc20fc24d33
--- /dev/null
+++ b/spec/lib/container_registry/migration_spec.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::Migration do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '.enabled?' do
+ subject { described_class.enabled? }
+
+ it { is_expected.to eq(true) }
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(container_registry_migration_phase2_enabled: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '.enqueue_waiting_time' do
+ subject { described_class.enqueue_waiting_time }
+
+ where(:slow_enabled, :fast_enabled, :expected_result) do
+ false | false | 1.hour
+ true | false | 6.hours
+ false | true | 0
+ true | true | 0
+ end
+
+ with_them do
+ before do
+ stub_feature_flags(
+ container_registry_migration_phase2_enqueue_speed_slow: slow_enabled,
+ container_registry_migration_phase2_enqueue_speed_fast: fast_enabled
+ )
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '.capacity' do
+ subject { described_class.capacity }
+
+ where(:ff_1_enabled, :ff_10_enabled, :ff_25_enabled, :expected_result) do
+ false | false | false | 0
+ true | false | false | 1
+ true | true | false | 10
+ true | true | true | 25
+ false | true | false | 10
+ false | true | true | 25
+ false | false | true | 25
+ true | false | true | 25
+ end
+
+ with_them do
+ before do
+ stub_feature_flags(
+ container_registry_migration_phase2_capacity_1: ff_1_enabled,
+ container_registry_migration_phase2_capacity_10: ff_10_enabled,
+ container_registry_migration_phase2_capacity_25: ff_25_enabled
+ )
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '.max_tags_count' do
+ let(:value) { 1 }
+
+ before do
+ stub_application_setting(container_registry_import_max_tags_count: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.max_tags_count).to eq(value)
+ end
+ end
+
+ describe '.max_retries' do
+ let(:value) { 1 }
+
+ before do
+ stub_application_setting(container_registry_import_max_retries: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.max_retries).to eq(value)
+ end
+ end
+
+ describe '.start_max_retries' do
+ let(:value) { 1 }
+
+ before do
+ stub_application_setting(container_registry_import_start_max_retries: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.start_max_retries).to eq(value)
+ end
+ end
+
+ describe '.max_step_duration' do
+ let(:value) { 5.minutes }
+
+ before do
+ stub_application_setting(container_registry_import_max_step_duration: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.max_step_duration).to eq(value)
+ end
+ end
+
+ describe '.target_plan_name' do
+ let(:value) { 'free' }
+
+ before do
+ stub_application_setting(container_registry_import_target_plan: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.target_plan_name).to eq(value)
+ end
+ end
+
+ describe '.created_before' do
+ let(:value) { 1.day.ago }
+
+ before do
+ stub_application_setting(container_registry_import_created_before: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.created_before).to eq(value)
+ end
+ end
+end
diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb
index d6e2b17f53b..405ef1170ae 100644
--- a/spec/lib/container_registry/registry_spec.rb
+++ b/spec/lib/container_registry/registry_spec.rb
@@ -27,4 +27,10 @@ RSpec.describe ContainerRegistry::Registry do
it { is_expected.to eq(path) }
end
end
+
+ describe '#gitlab_api_client' do
+ subject { registry.gitlab_api_client }
+
+ it { is_expected.to be_instance_of(ContainerRegistry::GitlabApiClient) }
+ end
end
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index 112ff2c7380..3a8c14fc2f6 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -19,7 +19,6 @@ RSpec.describe 'cross-database foreign keys' do
ci_freeze_periods.project_id
ci_job_artifacts.project_id
ci_job_token_project_scope_links.added_by_id
- ci_job_token_project_scope_links.source_project_id
ci_job_token_project_scope_links.target_project_id
ci_pending_builds.namespace_id
ci_pending_builds.project_id
@@ -33,11 +32,9 @@ RSpec.describe 'cross-database foreign keys' do
ci_running_builds.project_id
ci_sources_projects.source_project_id
ci_stages.project_id
- ci_subscriptions_projects.downstream_project_id
ci_subscriptions_projects.upstream_project_id
ci_unit_tests.project_id
dast_site_profiles_pipelines.ci_pipeline_id
- external_pull_requests.project_id
vulnerability_feedback.pipeline_id
).freeze
end
diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb
index 877474dd862..90accfaf3a9 100644
--- a/spec/lib/gitlab/import_export/saver_spec.rb
+++ b/spec/lib/gitlab/import_export/saver_spec.rb
@@ -36,6 +36,32 @@ RSpec.describe Gitlab::ImportExport::Saver do
.to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
end
+ it 'logs metrics after saving' do
+ stub_uploads_object_storage(ImportExportUploader)
+ expect(Gitlab::Export::Logger).to receive(:info).with(
+ hash_including(
+ message: 'Export archive saved',
+ exportable_class: 'Project',
+ 'correlation_id' => anything,
+ archive_file: anything,
+ compress_duration_s: anything
+ )).and_call_original
+
+ expect(Gitlab::Export::Logger).to receive(:info).with(
+ hash_including(
+ message: 'Export archive uploaded',
+ exportable_class: 'Project',
+ 'correlation_id' => anything,
+ archive_file: anything,
+ compress_duration_s: anything,
+ assign_duration_s: anything,
+ upload_duration_s: anything,
+ upload_bytes: anything
+ )).and_call_original
+
+ subject.save
+ end
+
it 'removes archive path and keeps base path untouched' do
allow(shared).to receive(:archive_path).and_return(archive_path)
@@ -45,4 +71,22 @@ RSpec.describe Gitlab::ImportExport::Saver do
expect(FileUtils).to have_received(:rm_rf).with(archive_path)
expect(Dir.exist?(archive_path)).to eq(false)
end
+
+ context 'when save throws an exception' do
+ before do
+ expect(subject).to receive(:save_upload).and_raise(SocketError.new)
+ end
+
+ it 'logs a saver error' do
+ allow(Gitlab::Export::Logger).to receive(:info).with(anything).and_call_original
+ expect(Gitlab::Export::Logger).to receive(:info).with(
+ hash_including(
+ message: 'Export archive saver failed',
+ exportable_class: 'Project',
+ 'correlation_id' => anything
+ )).and_call_original
+
+ subject.save
+ end
+ end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 0fbdc09a206..978118ed1b1 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -714,7 +714,7 @@ RSpec.describe Notify do
describe 'project access requested' do
let(:project) do
create(:project, :public) do |project|
- project.add_maintainer(project.owner)
+ project.add_maintainer(project.first_owner)
end
end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index bb8d476f257..5bd69ad9fad 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -394,7 +394,7 @@ RSpec.describe Ability do
describe '.project_disabled_features_rules' do
let(:project) { create(:project, :wiki_disabled) }
- subject { described_class.policy_for(project.owner, project) }
+ subject { described_class.policy_for(project.first_owner, project) }
context 'wiki named abilities' do
it 'disables wiki abilities if the project has no wiki' do
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index 31c7c7a44bc..e08fe196d65 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -851,7 +851,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
context 'when project is destroyed' do
let(:subject) do
- Projects::DestroyService.new(project, project.owner).execute
+ Projects::DestroyService.new(project, project.first_owner).execute
end
it_behaves_like 'deletes all build_trace_chunk and data in redis'
diff --git a/spec/models/ci/job_token/project_scope_link_spec.rb b/spec/models/ci/job_token/project_scope_link_spec.rb
index 8d7bb44bd16..8494446476f 100644
--- a/spec/models/ci/job_token/project_scope_link_spec.rb
+++ b/spec/models/ci/job_token/project_scope_link_spec.rb
@@ -88,4 +88,11 @@ RSpec.describe Ci::JobToken::ProjectScopeLink do
it { is_expected.to be_nil }
end
end
+
+ context 'loose foreign key on ci_job_token_project_scope_links.source_project_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_job_token_project_scope_link, source_project: parent) }
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 8c0f843fdea..9ca4092bba7 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -3427,7 +3427,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
create(:ci_pipeline,
project: project,
sha: project.commit('master').sha,
- user: project.owner)
+ user: project.first_owner)
end
before do
@@ -4502,7 +4502,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe '#reset_source_bridge!' do
let(:pipeline) { create(:ci_pipeline, :created, project: project) }
- subject(:reset_bridge) { pipeline.reset_source_bridge!(project.owner) }
+ subject(:reset_bridge) { pipeline.reset_source_bridge!(project.first_owner) }
context 'when the pipeline is a child pipeline and the bridge is depended' do
let!(:parent_pipeline) { create(:ci_pipeline) }
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index c9ae8519c63..4ac8720780c 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Ci::Trigger do
end
it_behaves_like 'includes Limitable concern' do
- subject { build(:ci_trigger, owner: project.owner, project: project) }
+ subject { build(:ci_trigger, owner: project.first_owner, project: project) }
end
context 'loose foreign key on ci_triggers.owner_id' do
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 2176eea75bc..4d3a2fac0fc 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -486,7 +486,7 @@ eos
it 'uses the CachedMarkdownField cache instead of the Mentionable cache', :use_clean_rails_redis_caching do
expect(commit.title_html).not_to be_present
- commit.all_references(project.owner).all
+ commit.all_references(project.first_owner).all
expect(commit.title_html).to be_present
expect(Rails.cache.read("banzai/commit:#{commit.id}/safe_message/single_line")).to be_nil
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 6cfd7def013..27281060935 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ContainerRepository do
+RSpec.describe ContainerRepository, :aggregate_failures do
using RSpec::Parameterized::TableSyntax
let(:group) { create(:group, name: 'group') }
@@ -36,7 +36,282 @@ RSpec.describe ContainerRepository do
describe 'validations' do
it { is_expected.to validate_presence_of(:migration_retries_count) }
it { is_expected.to validate_numericality_of(:migration_retries_count).is_greater_than_or_equal_to(0) }
- it { is_expected.to validate_presence_of(:migration_state) }
+
+ it { is_expected.to validate_inclusion_of(:migration_aborted_in_state).in_array(ContainerRepository::ACTIVE_MIGRATION_STATES) }
+ it { is_expected.to allow_value(nil).for(:migration_aborted_in_state) }
+
+ context 'migration_state' do
+ it { is_expected.to validate_presence_of(:migration_state) }
+ it { is_expected.to validate_inclusion_of(:migration_state).in_array(ContainerRepository::MIGRATION_STATES) }
+
+ describe 'pre_importing' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'pre_importing')).to be_invalid
+ expect(build(:container_repository, :pre_importing)).to be_valid
+ end
+ end
+
+ describe 'pre_import_done' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'pre_import_done')).to be_invalid
+ expect(build(:container_repository, :pre_import_done)).to be_valid
+ end
+ end
+
+ describe 'importing' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'importing')).to be_invalid
+ expect(build(:container_repository, :importing)).to be_valid
+ end
+ end
+
+ describe 'import_skipped' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'import_skipped')).to be_invalid
+ expect(build(:container_repository, :import_skipped)).to be_valid
+ end
+ end
+
+ describe 'import_aborted' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'import_aborted')).to be_invalid
+ expect(build(:container_repository, :import_aborted)).to be_valid
+ end
+ end
+ end
+ end
+
+ context ':migration_state state_machine' do
+ shared_examples 'no action when feature flag is disabled' do
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(container_registry_migration_phase2_enabled: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ shared_examples 'transitioning to pre_importing', skip_pre_import_success: true do
+ before do
+ repository.update_column(:migration_pre_import_done_at, Time.zone.now)
+ end
+
+ it_behaves_like 'no action when feature flag is disabled'
+
+ context 'successful pre_import request' do
+ it 'sets migration_pre_import_started_at and resets migration_pre_import_done_at' do
+ expect(repository).to receive(:migration_pre_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_pre_import_started_at }
+ .and change { repository.migration_pre_import_done_at }.to(nil)
+
+ expect(repository).to be_pre_importing
+ end
+ end
+
+ context 'failed pre_import request' do
+ it 'sets migration_pre_import_started_at and resets migration_pre_import_done_at' do
+ expect(repository).to receive(:migration_pre_import).and_return(:error)
+
+ expect { subject }.to change { repository.reload.migration_pre_import_started_at }
+ .and change { repository.migration_aborted_at }
+ .and change { repository.migration_pre_import_done_at }.to(nil)
+
+ expect(repository.migration_aborted_in_state).to eq('pre_importing')
+ expect(repository).to be_import_aborted
+ end
+ end
+ end
+
+ shared_examples 'transitioning to importing', skip_import_success: true do
+ before do
+ repository.update_columns(migration_import_done_at: Time.zone.now)
+ end
+
+ context 'successful import request' do
+ it 'sets migration_import_started_at and resets migration_import_done_at' do
+ expect(repository).to receive(:migration_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_import_started_at }
+ .and change { repository.migration_import_done_at }.to(nil)
+
+ expect(repository).to be_importing
+ end
+ end
+
+ context 'failed import request' do
+ it 'sets migration_import_started_at and resets migration_import_done_at' do
+ expect(repository).to receive(:migration_import).and_return(:error)
+
+ expect { subject }.to change { repository.reload.migration_import_started_at }
+ .and change { repository.migration_aborted_at }
+
+ expect(repository.migration_aborted_in_state).to eq('importing')
+ expect(repository).to be_import_aborted
+ end
+ end
+ end
+
+ shared_examples 'transitioning out of import_aborted' do
+ it 'resets migration_aborted_at and migration_aborted_in_state' do
+ expect { subject }.to change { repository.reload.migration_aborted_in_state }.to(nil)
+ .and change { repository.migration_aborted_at }.to(nil)
+ end
+ end
+
+ shared_examples 'transitioning from allowed states' do |allowed_states|
+ ContainerRepository::MIGRATION_STATES.each do |state|
+ result = allowed_states.include?(state)
+
+ context "when transitioning from #{state}" do
+ let(:repository) { create(:container_repository, state.to_sym) }
+
+ it "returns #{result}" do
+ expect(subject).to eq(result)
+ end
+ end
+ end
+ end
+
+ describe '#start_pre_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository) }
+
+ subject { repository.start_pre_import }
+
+ before do |example|
+ unless example.metadata[:skip_pre_import_success]
+ allow(repository).to receive(:migration_pre_import).and_return(:ok)
+ end
+ end
+
+ it_behaves_like 'transitioning from allowed states', %w[default]
+ it_behaves_like 'transitioning to pre_importing'
+ end
+
+ describe '#retry_pre_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :import_aborted) }
+
+ subject { repository.retry_pre_import }
+
+ before do |example|
+ unless example.metadata[:skip_pre_import_success]
+ allow(repository).to receive(:migration_pre_import).and_return(:ok)
+ end
+ end
+
+ it_behaves_like 'transitioning from allowed states', %w[import_aborted]
+ it_behaves_like 'transitioning to pre_importing'
+ it_behaves_like 'transitioning out of import_aborted'
+ end
+
+ describe '#finish_pre_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :pre_importing) }
+
+ subject { repository.finish_pre_import }
+
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing]
+
+ it 'sets migration_pre_import_done_at' do
+ expect { subject }.to change { repository.reload.migration_pre_import_done_at }
+
+ expect(repository).to be_pre_import_done
+ end
+ end
+
+ describe '#start_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :pre_import_done) }
+
+ subject { repository.start_import }
+
+ before do |example|
+ unless example.metadata[:skip_import_success]
+ allow(repository).to receive(:migration_import).and_return(:ok)
+ end
+ end
+
+ it_behaves_like 'transitioning from allowed states', %w[pre_import_done]
+ it_behaves_like 'transitioning to importing'
+ end
+
+ describe '#retry_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :import_aborted) }
+
+ subject { repository.retry_import }
+
+ before do |example|
+ unless example.metadata[:skip_import_success]
+ allow(repository).to receive(:migration_import).and_return(:ok)
+ end
+ end
+
+ it_behaves_like 'transitioning from allowed states', %w[import_aborted]
+ it_behaves_like 'transitioning to importing'
+ it_behaves_like 'no action when feature flag is disabled'
+ end
+
+ describe '#finish_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :importing) }
+
+ subject { repository.finish_import }
+
+ it_behaves_like 'transitioning from allowed states', %w[importing]
+
+ it 'sets migration_import_done_at' do
+ expect { subject }.to change { repository.reload.migration_import_done_at }
+
+ expect(repository).to be_import_done
+ end
+ end
+
+ describe '#already_migrated' do
+ let_it_be_with_reload(:repository) { create(:container_repository) }
+
+ subject { repository.already_migrated }
+
+ it_behaves_like 'transitioning from allowed states', %w[default]
+
+ it 'sets migration_import_done_at' do
+ subject
+
+ expect(repository).to be_import_done
+ end
+ end
+
+ describe '#abort_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :importing) }
+
+ subject { repository.abort_import }
+
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing importing]
+
+ it 'sets migration_aborted_at and migration_aborted_at and increments the retry count' do
+ expect { subject }.to change { repository.migration_aborted_at }
+ .and change { repository.reload.migration_retries_count }.by(1)
+
+ expect(repository.migration_aborted_in_state).to eq('importing')
+ expect(repository).to be_import_aborted
+ end
+ end
+
+ describe '#skip_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository) }
+
+ subject { repository.skip_import(reason: :too_many_retries) }
+
+ it_behaves_like 'transitioning from allowed states', %w[default pre_importing importing]
+
+ it 'sets migration_skipped_at and migration_skipped_reason' do
+ expect { subject }.to change { repository.reload.migration_skipped_at }
+
+ expect(repository.migration_skipped_reason).to eq('too_many_retries')
+ expect(repository).to be_import_skipped
+ end
+
+ it 'raises and error if a reason is not given' do
+ expect { repository.skip_import }.to raise_error(ArgumentError)
+ end
+ end
end
describe '#tag' do
@@ -209,6 +484,46 @@ RSpec.describe ContainerRepository do
end
end
+ context 'registry migration' do
+ shared_examples 'handling the migration step' do |step|
+ let(:client_response) { :foobar }
+
+ before do
+ allow(repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(true)
+ end
+
+ it 'returns the same response as the client' do
+ expect(repository.gitlab_api_client)
+ .to receive(step).with(repository.path).and_return(client_response)
+ expect(subject).to eq(client_response)
+ end
+
+ context 'when the gitlab_api feature is not supported' do
+ before do
+ allow(repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(false)
+ end
+
+ it 'returns :error' do
+ expect(repository.gitlab_api_client).not_to receive(step)
+
+ expect(subject).to eq(:error)
+ end
+ end
+ end
+
+ describe '#migration_pre_import' do
+ subject { repository.migration_pre_import }
+
+ it_behaves_like 'handling the migration step', :pre_import_repository
+ end
+
+ describe '#migration_import' do
+ subject { repository.migration_import }
+
+ it_behaves_like 'handling the migration step', :import_repository
+ end
+ end
+
describe '.build_from_path' do
let(:registry_path) do
ContainerRegistry::Path.new(project.full_path + '/some/image')
@@ -505,20 +820,14 @@ RSpec.describe ContainerRepository do
end
describe '#migration_importing?' do
- let_it_be_with_reload(:container_repository) { create(:container_repository, migration_state: 'importing')}
-
subject { container_repository.migration_importing? }
- it { is_expected.to eq(true) }
+ ContainerRepository::MIGRATION_STATES.each do |state|
+ context "when in #{state} migration_state" do
+ let(:container_repository) { create(:container_repository, state.to_sym)}
- context 'when not importing' do
- before do
- # Technical debt: when the state machine is added, we should iterate through all possible states
- # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78499
- container_repository.update_column(:migration_state, 'default')
+ it { is_expected.to eq(state == 'importing') }
end
-
- it { is_expected.to eq(false) }
end
end
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index 1b9b38a0932..1db1171401c 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -161,7 +161,7 @@ RSpec.describe EnvironmentStatus do
let!(:build) { create(:ci_build, :with_deployment, :deploy_to_production, pipeline: pipeline) }
let(:environment) { build.deployment.environment }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
context 'when environment is created on a forked project', :sidekiq_inline do
let(:project) { create(:project, :repository) }
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 9119ef83034..f099015e63e 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Event do
expect(instance).to receive(:reset_project_activity)
end
- create_push_event(project, project.owner)
+ create_push_event(project, project.first_owner)
end
end
@@ -34,7 +34,7 @@ RSpec.describe Event do
it 'updates the project last_repository_updated_at and updated_at' do
project.touch(:last_repository_updated_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations
- event = create_push_event(project, project.owner)
+ event = create_push_event(project, project.first_owner)
project.reload
@@ -47,7 +47,7 @@ RSpec.describe Event do
it 'does not update the project last_repository_updated_at' do
project.update!(last_repository_updated_at: 1.year.ago)
- create(:closed_issue_event, project: project, author: project.owner)
+ create(:closed_issue_event, project: project, author: project.first_owner)
project.reload
@@ -63,14 +63,14 @@ RSpec.describe Event do
project.reload # a reload removes fractions of seconds
expect do
- create_push_event(project, project.owner)
+ create_push_event(project, project.first_owner)
project.reload
end.not_to change { project.last_repository_updated_at }
end
end
describe 'after_create UserInteractedProject.track' do
- let(:event) { build(:push_event, project: project, author: project.owner) }
+ let(:event) { build(:push_event, project: project, author: project.first_owner) }
it 'passes event to UserInteractedProject.track' do
expect(UserInteractedProject).to receive(:track).with(event)
@@ -157,7 +157,7 @@ RSpec.describe Event do
describe "Push event" do
let(:project) { create(:project, :private) }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
let(:event) { create_push_event(project, user) }
it do
@@ -173,7 +173,7 @@ RSpec.describe Event do
describe '#target_title' do
let_it_be(:project) { create(:project) }
- let(:author) { project.owner }
+ let(:author) { project.first_owner }
let(:target) { nil }
let(:event) do
@@ -746,7 +746,7 @@ RSpec.describe Event do
target = kind == :project ? nil : build(kind, **extra_data)
- [kind, build(:event, :created, author: project.owner, project: project, target: target)]
+ [kind, build(:event, :created, author: project.first_owner, project: project, target: target)]
end
end
@@ -830,7 +830,7 @@ RSpec.describe Event do
expect(project).not_to receive(:update_column)
.with(:last_activity_at, a_kind_of(Time))
- create_push_event(project, project.owner)
+ create_push_event(project, project.first_owner)
end
end
@@ -838,7 +838,7 @@ RSpec.describe Event do
it 'updates the project' do
project.touch(:last_activity_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations
- event = create_push_event(project, project.owner)
+ event = create_push_event(project, project.first_owner)
project.reload
diff --git a/spec/models/external_pull_request_spec.rb b/spec/models/external_pull_request_spec.rb
index b141600c4fd..82da7cdf34b 100644
--- a/spec/models/external_pull_request_spec.rb
+++ b/spec/models/external_pull_request_spec.rb
@@ -236,4 +236,11 @@ RSpec.describe ExternalPullRequest do
it_behaves_like 'it has loose foreign keys' do
let(:factory_name) { :external_pull_request }
end
+
+ context 'loose foreign key on external_pull_requests.project_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:external_pull_request, project: parent) }
+ end
+ end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 05ee2166245..4bc4df02c24 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1249,7 +1249,7 @@ RSpec.describe Group do
let(:common_id) { [Project.maximum(:id).to_i, Namespace.maximum(:id).to_i].max + 999 }
let!(:group) { create(:group, id: common_id) }
let!(:unrelated_project) { create(:project, id: common_id) }
- let(:user) { unrelated_project.owner }
+ let(:user) { unrelated_project.first_owner }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
index d67bd8debce..183082bab26 100644
--- a/spec/models/issue_collection_spec.rb
+++ b/spec/models/issue_collection_spec.rb
@@ -50,7 +50,9 @@ RSpec.describe IssueCollection do
end
end
- context 'using a user that is the owner of a project' do
+ # TODO update when we have multiple owners of a project
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/350605
+ context 'using a user that is an owner of a project' do
it 'returns the issues of the project' do
expect(collection.updatable_by_user(project.namespace.owner))
.to eq([issue1, issue2])
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index b91b299467d..5af42cc67ea 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -887,6 +887,8 @@ RSpec.describe Issue do
end
end
+ # TODO update when we have multiple owners of a project
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/350605
context 'with an owner' do
before do
project.add_maintainer(user)
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 9d9cca0678a..f7d32dc1a7b 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -617,7 +617,7 @@ RSpec.describe Note do
let(:note) do
create :note,
noteable: ext_issue, project: ext_proj,
- note: "mentioned in #{ext_proj.owner.to_reference}",
+ note: "mentioned in #{ext_proj.first_owner.to_reference}",
system: true
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 0d1ab22c520..d78d0a56f10 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -871,6 +871,8 @@ RSpec.describe Project, factory_default: :keep do
end
describe 'reference methods' do
+ # TODO update when we have multiple owners of a project
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/350605
let_it_be(:owner) { create(:user, name: 'Gitlab') }
let_it_be(:namespace) { create(:namespace, name: 'Sample namespace', path: 'sample-namespace', owner: owner) }
let_it_be(:project) { create(:project, name: 'Sample project', path: 'sample-project', namespace: namespace) }
@@ -2874,7 +2876,7 @@ RSpec.describe Project, factory_default: :keep do
end
before do
- project.repository.rm_branch(project.owner, branch.name)
+ project.repository.rm_branch(project.first_owner, branch.name)
end
subject { project.latest_pipeline(branch.name) }
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index d5af2d80906..bfdebbc33df 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe ProjectTeam do
describe 'owner methods' do
context 'personal project' do
let(:project) { create(:project) }
- let(:owner) { project.owner }
+ let(:owner) { project.first_owner }
specify { expect(project.team.owners).to contain_exactly(owner) }
specify { expect(project.team.owner?(owner)).to be_truthy }
@@ -108,10 +108,12 @@ RSpec.describe ProjectTeam do
let(:project) { create(:project) }
it 'returns project members' do
+ # TODO this can be updated when we have multiple project owners
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/350605
user = create(:user)
project.add_guest(user)
- expect(project.team.members).to contain_exactly(user, project.owner)
+ expect(project.team.members).to contain_exactly(user, project.first_owner)
end
it 'returns project members of a specified level' do
@@ -129,7 +131,7 @@ RSpec.describe ProjectTeam do
group_access: Gitlab::Access::GUEST)
expect(project.team.members)
- .to contain_exactly(group_member.user, project.owner)
+ .to contain_exactly(group_member.user, project.first_owner)
end
it 'returns invited members of a group of a specified level' do
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
index 9a65823c950..b68bb966820 100644
--- a/spec/policies/ci/pipeline_policy_spec.rb
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -89,7 +89,7 @@ RSpec.describe Ci::PipelinePolicy, :models do
let(:project) { create(:project, :public) }
context 'when user has owner access' do
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
it 'is enabled' do
expect(policy).to be_allowed :destroy_pipeline
@@ -107,7 +107,7 @@ RSpec.describe Ci::PipelinePolicy, :models do
let(:project) { create(:project, :public) }
context 'when user has owner access' do
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
it 'is enabled' do
expect(policy).to be_allowed :read_pipeline_variable
@@ -129,7 +129,7 @@ RSpec.describe Ci::PipelinePolicy, :models do
end
context 'when user is developer and it is not the creator of the pipeline' do
- let(:pipeline) { create(:ci_empty_pipeline, project: project, user: project.owner) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, user: project.first_owner) }
before do
project.add_developer(user)
diff --git a/spec/policies/namespaces/project_namespace_policy_spec.rb b/spec/policies/namespaces/project_namespace_policy_spec.rb
index f6fe4ae552a..f1022747fab 100644
--- a/spec/policies/namespaces/project_namespace_policy_spec.rb
+++ b/spec/policies/namespaces/project_namespace_policy_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Namespaces::ProjectNamespacePolicy do
end
context 'parent owner' do
- let_it_be(:current_user) { parent.owner }
+ let_it_be(:current_user) { parent.first_owner }
it { is_expected.to be_disallowed(*permissions) }
end
diff --git a/spec/policies/project_member_policy_spec.rb b/spec/policies/project_member_policy_spec.rb
index aebbe685bb3..12b3e60fdb2 100644
--- a/spec/policies/project_member_policy_spec.rb
+++ b/spec/policies/project_member_policy_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe ProjectMemberPolicy do
end
context 'when user is project owner' do
- let(:member_user) { project.owner }
+ let(:member_user) { project.first_owner }
let(:member) { project.members.find_by!(user: member_user) }
it { is_expected.to be_allowed(:read_project) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 38e4e18c894..793b1fffd5f 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -117,7 +117,7 @@ RSpec.describe ProjectPolicy do
end
describe 'for unconfirmed user' do
- let(:current_user) { project.owner.tap { |u| u.update!(confirmed_at: nil) } }
+ let(:current_user) { project.first_owner.tap { |u| u.update!(confirmed_at: nil) } }
it 'disallows to modify pipelines' do
expect_disallowed(:create_pipeline)
@@ -144,7 +144,7 @@ RSpec.describe ProjectPolicy do
end
describe 'for project owner' do
- let(:current_user) { project.owner }
+ let(:current_user) { project.first_owner }
it 'allows :destroy_pipeline' do
expect(current_user.can?(:destroy_pipeline, pipeline)).to be_truthy
diff --git a/spec/rubocop/cop/file_decompression_spec.rb b/spec/rubocop/cop/file_decompression_spec.rb
new file mode 100644
index 00000000000..7be1a784001
--- /dev/null
+++ b/spec/rubocop/cop/file_decompression_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative '../../../rubocop/cop/file_decompression'
+
+RSpec.describe RuboCop::Cop::FileDecompression do
+ subject(:cop) { described_class.new }
+
+ it 'does not flag when using a system command not related to file decompression' do
+ expect_no_offenses('system("ls")')
+ end
+
+ described_class::FORBIDDEN_COMMANDS.map { [_1, '^' * _1.length] }.each do |cmd, len|
+ it "flags the when using '#{cmd}' system command" do
+ expect_offense(<<~SOURCE)
+ system('#{cmd}')
+ ^^^^^^^^#{len}^^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+
+ expect_offense(<<~SOURCE)
+ exec('#{cmd}')
+ ^^^^^^#{len}^^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+
+ expect_offense(<<~SOURCE)
+ Kernel.spawn('#{cmd}')
+ ^^^^^^^^^^^^^^#{len}^^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+
+ expect_offense(<<~SOURCE)
+ IO.popen('#{cmd}')
+ ^^^^^^^^^^#{len}^^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+ end
+
+ it "flags the when using '#{cmd}' subshell command" do
+ expect_offense(<<~SOURCE)
+ `#{cmd}`
+ ^#{len}^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+
+ expect_offense(<<~SOURCE)
+ %x(#{cmd})
+ ^^^#{len}^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+ end
+ end
+end
diff --git a/spec/services/service_ping/submit_service_ping_service_spec.rb b/spec/services/service_ping/submit_service_ping_service_spec.rb
index 2971c9a9309..b5c28e0346a 100644
--- a/spec/services/service_ping/submit_service_ping_service_spec.rb
+++ b/spec/services/service_ping/submit_service_ping_service_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe ServicePing::SubmitService do
shared_examples 'does not send a blank usage ping payload' do
it do
- expect(Gitlab::HTTP).not_to receive(:post)
+ expect(Gitlab::HTTP).not_to receive(:post).with(subject.url, any_args)
expect { subject.execute }.to raise_error(described_class::SubmissionError) do |error|
expect(error.message).to include('Usage data is blank')
@@ -129,6 +129,7 @@ RSpec.describe ServicePing::SubmitService do
stub_usage_data_connections
stub_database_flavor_check
stub_application_setting(usage_ping_enabled: true)
+ stub_response(body: nil, url: subject.error_url, status: 201)
end
context 'and user requires usage stats consent' do
@@ -277,7 +278,8 @@ RSpec.describe ServicePing::SubmitService do
context 'if payload service fails' do
before do
stub_response(body: with_dev_ops_score_params)
- allow(ServicePing::BuildPayloadService).to receive(:execute).and_raise(described_class::SubmissionError, 'SubmissionError')
+ allow(ServicePing::BuildPayloadService).to receive_message_chain(:new, :execute)
+ .and_raise(described_class::SubmissionError, 'SubmissionError')
end
it 'calls UsageData .data method' do
@@ -287,6 +289,15 @@ RSpec.describe ServicePing::SubmitService do
subject.execute
end
+
+ it 'submits error' do
+ expect(Gitlab::HTTP).to receive(:post).with(subject.url, any_args)
+ .and_call_original
+ expect(Gitlab::HTTP).to receive(:post).with(subject.error_url, any_args)
+ .and_call_original
+
+ subject.execute
+ end
end
context 'calls BuildPayloadService first' do
diff --git a/spec/services/update_container_registry_info_service_spec.rb b/spec/services/update_container_registry_info_service_spec.rb
index 740e53b0472..64071e79508 100644
--- a/spec/services/update_container_registry_info_service_spec.rb
+++ b/spec/services/update_container_registry_info_service_spec.rb
@@ -48,6 +48,7 @@ RSpec.describe UpdateContainerRegistryInfoService do
before do
stub_registry_info({})
+ stub_supports_gitlab_api(false)
end
it 'uses a token with no access permissions' do
@@ -63,6 +64,7 @@ RSpec.describe UpdateContainerRegistryInfoService do
context 'when unabled to detect the container registry type' do
it 'sets the application settings to their defaults' do
stub_registry_info({})
+ stub_supports_gitlab_api(false)
subject
@@ -76,20 +78,23 @@ RSpec.describe UpdateContainerRegistryInfoService do
context 'when able to detect the container registry type' do
context 'when using the GitLab container registry' do
it 'updates application settings accordingly' do
- stub_registry_info(vendor: 'gitlab', version: '2.9.1-gitlab', features: %w[a,b,c])
+ stub_registry_info(vendor: 'gitlab', version: '2.9.1-gitlab', features: %w[a b c])
+ stub_supports_gitlab_api(true)
subject
application_settings.reload
expect(application_settings.container_registry_vendor).to eq('gitlab')
expect(application_settings.container_registry_version).to eq('2.9.1-gitlab')
- expect(application_settings.container_registry_features).to eq(%w[a,b,c])
+ expect(application_settings.container_registry_features)
+ .to match_array(%W[a b c #{ContainerRegistry::GitlabApiClient::REGISTRY_GITLAB_V1_API_FEATURE}])
end
end
context 'when using a third-party container registry' do
it 'updates application settings accordingly' do
stub_registry_info(vendor: 'other', version: nil, features: nil)
+ stub_supports_gitlab_api(false)
subject
@@ -112,4 +117,10 @@ RSpec.describe UpdateContainerRegistryInfoService do
allow(client).to receive(:registry_info).and_return(output)
end
end
+
+ def stub_supports_gitlab_api(output)
+ allow_next_instance_of(ContainerRegistry::GitlabApiClient) do |client|
+ allow(client).to receive(:supports_gitlab_api?).and_return(output)
+ end
+ end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 17f091278e5..2b3b7b114c8 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -112,11 +112,11 @@ RSpec.configure do |config|
# falling back to all tests when there is no `:focus` example.
config.filter_run focus: true
config.run_all_when_everything_filtered = true
-
- # Re-run failures locally with `--only-failures`
- config.example_status_persistence_file_path = './spec/examples.txt'
end
+ # Re-run failures locally with `--only-failures`
+ config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', './spec/examples.txt')
+
config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata|
location = metadata[:location]
@@ -208,7 +208,9 @@ RSpec.configure do |config|
config.exceptions_to_hard_fail = [DeprecationToolkitEnv::DeprecationBehaviors::SelectiveRaise::RaiseDisallowedDeprecation]
end
- if ENV['FLAKY_RSPEC_GENERATE_REPORT']
+ require_relative '../tooling/rspec_flaky/config'
+
+ if RspecFlaky::Config.generate_report?
require_relative '../tooling/rspec_flaky/listener'
config.reporter.register_listener(
diff --git a/spec/support/flaky_tests.rb b/spec/support/flaky_tests.rb
index 5ce55c47aab..4df0d23bfc3 100644
--- a/spec/support/flaky_tests.rb
+++ b/spec/support/flaky_tests.rb
@@ -4,14 +4,14 @@ return unless ENV['CI']
return if ENV['SKIP_FLAKY_TESTS_AUTOMATICALLY'] == "false"
return if ENV['CI_MERGE_REQUEST_LABELS'].to_s.include?('pipeline:run-flaky-tests')
+require_relative '../../tooling/rspec_flaky/config'
require_relative '../../tooling/rspec_flaky/report'
RSpec.configure do |config|
$flaky_test_example_ids = begin # rubocop:disable Style/GlobalVars
- raise "$SUITE_FLAKY_RSPEC_REPORT_PATH is empty." if ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'].to_s.empty?
- raise "#{ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']} doesn't exist" unless File.exist?(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'])
+ raise "#{RspecFlaky::Config.suite_flaky_examples_report_path} doesn't exist" unless File.exist?(RspecFlaky::Config.suite_flaky_examples_report_path)
- RspecFlaky::Report.load(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']).map { |_, flaky_test_data| flaky_test_data.to_h[:example_id] }
+ RspecFlaky::Report.load(RspecFlaky::Config.suite_flaky_examples_report_path).map { |_, flaky_test_data| flaky_test_data.to_h[:example_id] }
rescue => e # rubocop:disable Style/RescueStandardError
puts e
[]
@@ -29,8 +29,9 @@ RSpec.configure do |config|
end
config.after(:suite) do
- next unless ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH']
+ next unless RspecFlaky::Config.skipped_flaky_tests_report_path
+ next if $skipped_flaky_tests_report.empty? # rubocop:disable Style/GlobalVars
- File.write(ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'], "#{$skipped_flaky_tests_report.join("\n")}\n") # rubocop:disable Style/GlobalVars
+ File.write(RspecFlaky::Config.skipped_flaky_tests_report_path, "#{ENV['CI_JOB_URL']}\n#{$skipped_flaky_tests_report.join("\n")}\n\n") # rubocop:disable Style/GlobalVars
end
end
diff --git a/spec/support/helpers/features/iteration_helpers.rb b/spec/support/helpers/features/iteration_helpers.rb
new file mode 100644
index 00000000000..8e1d252f55f
--- /dev/null
+++ b/spec/support/helpers/features/iteration_helpers.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+module IterationHelpers
+ def iteration_period(iteration)
+ "#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}"
+ end
+end
diff --git a/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb b/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb
new file mode 100644
index 00000000000..a87a3247b95
--- /dev/null
+++ b/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'container registry client' do
+ let(:token) { '12345' }
+ let(:options) { { token: token } }
+ let(:registry_api_url) { 'http://container-registry' }
+ let(:client) { described_class.new(registry_api_url, options) }
+ let(:push_blob_headers) do
+ {
+ 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
+ 'Authorization' => "bearer #{token}",
+ 'Content-Type' => 'application/octet-stream',
+ 'User-Agent' => "GitLab/#{Gitlab::VERSION}"
+ }
+ end
+
+ let(:headers_with_accept_types) do
+ {
+ 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
+ 'Authorization' => "bearer #{token}",
+ 'User-Agent' => "GitLab/#{Gitlab::VERSION}"
+ }
+ end
+
+ let(:expected_faraday_headers) { { user_agent: "GitLab/#{Gitlab::VERSION}" } }
+ let(:expected_faraday_request_options) { Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS }
+end
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index 4713e2fc789..ac5029edc82 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -1142,7 +1142,7 @@ RSpec.shared_examples 'a container registry auth service' do
end
context 'when importing' do
- let_it_be(:container_repository) { create(:container_repository, :root, migration_state: 'importing') }
+ let_it_be(:container_repository) { create(:container_repository, :root, :importing) }
let_it_be(:current_project) { container_repository.project }
let_it_be(:current_user) { create(:user) }
diff --git a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
index 4b44d991d89..34542668730 100644
--- a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
+++ b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
@@ -18,7 +18,9 @@ RSpec.describe Tooling::ParallelRSpecRunner do # rubocop:disable RSpec/FilePath
allow(File).to receive(:exist?).with(filter_tests_file).and_return(true)
allow(File).to receive(:read).and_call_original
allow(File).to receive(:read).with(filter_tests_file).and_return(filter_tests)
- allow(subject).to receive(:exec)
+ allow(Process).to receive(:spawn)
+ allow(Process).to receive(:wait)
+ allow(Process).to receive(:last_status).and_return(double(exitstatus: 0))
end
subject { described_class.new(allocator: allocator, filter_tests_file: filter_tests_file, rspec_args: rspec_args) }
@@ -86,7 +88,7 @@ RSpec.describe Tooling::ParallelRSpecRunner do # rubocop:disable RSpec/FilePath
end
def expect_command(cmd)
- expect(subject).to receive(:exec).with(*cmd)
+ expect(Process).to receive(:spawn).with(*cmd)
end
end
end
diff --git a/spec/tooling/rspec_flaky/config_spec.rb b/spec/tooling/rspec_flaky/config_spec.rb
index 12b5ed74cb2..c95e5475d66 100644
--- a/spec/tooling/rspec_flaky/config_spec.rb
+++ b/spec/tooling/rspec_flaky/config_spec.rb
@@ -11,9 +11,10 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
before do
# Stub these env variables otherwise specs don't behave the same on the CI
stub_env('FLAKY_RSPEC_GENERATE_REPORT', nil)
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', nil)
+ stub_env('FLAKY_RSPEC_SUITE_REPORT_PATH', nil)
stub_env('FLAKY_RSPEC_REPORT_PATH', nil)
stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', nil)
+ stub_env('SKIPPED_FLAKY_TESTS_REPORT_PATH', nil)
# Ensure the behavior is the same locally and on CI (where Rails is defined since we run this test as part of the whole suite), i.e. Rails isn't defined
allow(described_class).to receive(:rails_path).and_wrap_original do |method, path|
path
@@ -51,15 +52,15 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
end
describe '.suite_flaky_examples_report_path' do
- context "when ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] is not set" do
+ context "when ENV['FLAKY_RSPEC_SUITE_REPORT_PATH'] is not set" do
it 'returns the default path' do
- expect(described_class.suite_flaky_examples_report_path).to eq('rspec_flaky/suite-report.json')
+ expect(described_class.suite_flaky_examples_report_path).to eq('rspec/flaky/suite-report.json')
end
end
- context "when ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] is set" do
+ context "when ENV['FLAKY_RSPEC_SUITE_REPORT_PATH'] is set" do
before do
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', 'foo/suite-report.json')
+ stub_env('FLAKY_RSPEC_SUITE_REPORT_PATH', 'foo/suite-report.json')
end
it 'returns the value of the env variable' do
@@ -71,7 +72,7 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
describe '.flaky_examples_report_path' do
context "when ENV['FLAKY_RSPEC_REPORT_PATH'] is not set" do
it 'returns the default path' do
- expect(described_class.flaky_examples_report_path).to eq('rspec_flaky/report.json')
+ expect(described_class.flaky_examples_report_path).to eq('rspec/flaky/report.json')
end
end
@@ -89,7 +90,7 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
describe '.new_flaky_examples_report_path' do
context "when ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] is not set" do
it 'returns the default path' do
- expect(described_class.new_flaky_examples_report_path).to eq('rspec_flaky/new-report.json')
+ expect(described_class.new_flaky_examples_report_path).to eq('rspec/flaky/new-report.json')
end
end
@@ -103,4 +104,22 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
end
end
end
+
+ describe '.skipped_flaky_tests_report_path' do
+ context "when ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'] is not set" do
+ it 'returns the default path' do
+ expect(described_class.skipped_flaky_tests_report_path).to eq('rspec/flaky/skipped_flaky_tests_report.txt')
+ end
+ end
+
+ context "when ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'] is set" do
+ before do
+ stub_env('SKIPPED_FLAKY_TESTS_REPORT_PATH', 'foo/skipped_flaky_tests_report.txt')
+ end
+
+ it 'returns the value of the env variable' do
+ expect(described_class.skipped_flaky_tests_report_path).to eq('foo/skipped_flaky_tests_report.txt')
+ end
+ end
+ end
end
diff --git a/spec/tooling/rspec_flaky/listener_spec.rb b/spec/tooling/rspec_flaky/listener_spec.rb
index 51a815dafbf..62bbe53cac1 100644
--- a/spec/tooling/rspec_flaky/listener_spec.rb
+++ b/spec/tooling/rspec_flaky/listener_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe RspecFlaky::Listener, :aggregate_failures do
before do
# Stub these env variables otherwise specs don't behave the same on the CI
stub_env('CI_JOB_URL', nil)
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', nil)
+ stub_env('FLAKY_RSPEC_SUITE_REPORT_PATH', nil)
end
describe '#initialize' do
@@ -73,11 +73,11 @@ RSpec.describe RspecFlaky::Listener, :aggregate_failures do
it_behaves_like 'a valid Listener instance'
end
- context 'when SUITE_FLAKY_RSPEC_REPORT_PATH is set' do
+ context 'when FLAKY_RSPEC_SUITE_REPORT_PATH is set' do
let(:report_file_path) { 'foo/report.json' }
before do
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file_path)
+ stub_env('FLAKY_RSPEC_SUITE_REPORT_PATH', report_file_path)
end
context 'and report file exists' do
diff --git a/tooling/bin/parallel_rspec b/tooling/bin/parallel_rspec
index 06e5603a6b8..08bfa4997f2 100755
--- a/tooling/bin/parallel_rspec
+++ b/tooling/bin/parallel_rspec
@@ -16,4 +16,4 @@ OptionParser.new do |opts|
end
end.parse!
-Tooling::ParallelRSpecRunner.run(**options)
+exit Tooling::ParallelRSpecRunner.run(**options)
diff --git a/tooling/lib/tooling/parallel_rspec_runner.rb b/tooling/lib/tooling/parallel_rspec_runner.rb
index b482160d3c0..d3fa5af01e4 100644
--- a/tooling/lib/tooling/parallel_rspec_runner.rb
+++ b/tooling/lib/tooling/parallel_rspec_runner.rb
@@ -38,12 +38,8 @@ module Tooling
Knapsack.logger.info tests_to_run
Knapsack.logger.info
- if tests_to_run.empty?
- Knapsack.logger.info 'No tests to run on this node, exiting.'
- return
- end
-
- exec(*rspec_command)
+ Process.wait Process.spawn(*rspec_command)
+ Process.last_status.exitstatus
end
private
diff --git a/tooling/rspec_flaky/config.rb b/tooling/rspec_flaky/config.rb
index ea18a601c11..36e35671587 100644
--- a/tooling/rspec_flaky/config.rb
+++ b/tooling/rspec_flaky/config.rb
@@ -7,15 +7,19 @@ module RspecFlaky
end
def self.suite_flaky_examples_report_path
- ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] || rails_path("rspec_flaky/suite-report.json")
+ ENV['FLAKY_RSPEC_SUITE_REPORT_PATH'] || rails_path("rspec/flaky/suite-report.json")
end
def self.flaky_examples_report_path
- ENV['FLAKY_RSPEC_REPORT_PATH'] || rails_path("rspec_flaky/report.json")
+ ENV['FLAKY_RSPEC_REPORT_PATH'] || rails_path("rspec/flaky/report.json")
end
def self.new_flaky_examples_report_path
- ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] || rails_path("rspec_flaky/new-report.json")
+ ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] || rails_path("rspec/flaky/new-report.json")
+ end
+
+ def self.skipped_flaky_tests_report_path
+ ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'] || rails_path("rspec/flaky/skipped_flaky_tests_report.txt")
end
def self.rails_path(path)