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-05-11 21:07:55 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-11 21:07:55 +0300
commit11df4bf91b8cf9ac7bb601241992e300eebf684c (patch)
treed3c2360dbd3edec006a09ed150267dc202020a91
parent6282dd78339f98cbc5624e7fdf744a342d3d8b73 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/review-apps/qa.gitlab-ci.yml34
-rw-r--r--app/assets/javascripts/environments/components/environment_folder.vue8
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue2
-rw-r--r--app/assets/javascripts/releases/components/tag_field_new.vue12
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/actions.js2
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/getters.js10
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/mutations.js1
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/state.js1
-rw-r--r--app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue4
-rw-r--r--app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue2
-rw-r--r--app/assets/javascripts/runner/components/runner_jobs.vue2
-rw-r--r--app/assets/javascripts/runner/components/runner_projects.vue2
-rw-r--r--app/assets/javascripts/runner/components/runner_update_form.vue2
-rw-r--r--app/assets/javascripts/runner/graphql/details/runner.query.graphql7
-rw-r--r--app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql5
-rw-r--r--app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql39
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql5
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql15
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql7
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql (renamed from app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql)4
-rw-r--r--app/assets/javascripts/runner/graphql/show/runner.query.graphql41
-rw-r--r--app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql (renamed from app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql)0
-rw-r--r--app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql (renamed from app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql)0
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue2
-rw-r--r--app/controllers/projects_controller.rb5
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/merge_requests_helper.rb6
-rw-r--r--app/helpers/profiles_helper.rb4
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/application_setting_implementation.rb1
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/instance_configuration.rb1
-rw-r--r--app/models/key.rb5
-rw-r--r--app/models/project.rb11
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb4
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml5
-rw-r--r--app/views/help/instance_configuration/_size_limits.html.haml3
-rw-r--r--config/feature_flags/development/ci_authenticate_running_job_token_for_artifacts.yml8
-rw-r--r--config/feature_flags/development/ci_expose_running_job_token_for_artifacts.yml8
-rw-r--r--data/deprecations/15-0-deprecate-postgresql-12.yml19
-rw-r--r--db/migrate/20220426130217_add_max_export_size_to_application_settings.rb7
-rw-r--r--db/post_migrate/20220505060011_remove_namespaces_id_parent_id_partial_index.rb15
-rw-r--r--db/schema_migrations/202204261302171
-rw-r--r--db/schema_migrations/202205050600111
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/api_resources.md1
-rw-r--r--doc/api/cluster_agent_tokens.md216
-rw-r--r--doc/api/graphql/reference/index.md4
-rw-r--r--doc/api/managed_licenses.md16
-rw-r--r--doc/api/project_import_export.md6
-rw-r--r--doc/api/settings.md3
-rw-r--r--doc/development/merge_request_application_and_rate_limit_guidelines.md2
-rw-r--r--doc/development/migration_style_guide.md6
-rw-r--r--doc/development/pipelines.md2
-rw-r--r--doc/update/deprecations.md21
-rw-r--r--doc/update/index.md3
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md28
-rw-r--r--doc/user/application_security/dependency_scanning/index.md2
-rw-r--r--doc/user/ssh.md2
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/ci/helpers/runner.rb37
-rw-r--r--lib/api/ci/runner.rb9
-rw-r--r--lib/api/clusters/agent_tokens.rb98
-rw-r--r--lib/api/entities/ci/job_request/dependency.rb11
-rw-r--r--lib/api/entities/ci/job_request/response.rb4
-rw-r--r--lib/api/entities/clusters/agent_token.rb11
-rw-r--r--lib/api/entities/clusters/agent_token_basic.rb17
-rw-r--r--lib/api/entities/clusters/agent_token_with_token.rb11
-rw-r--r--lib/api/project_export.rb10
-rw-r--r--lib/api/settings.rb1
-rw-r--r--lib/api/usage_data.rb2
-rw-r--r--lib/api/usage_data_non_sql_metrics.rb1
-rw-r--r--lib/banzai/filter/references/abstract_reference_filter.rb5
-rw-r--r--lib/banzai/filter/references/issue_reference_filter.rb4
-rw-r--r--lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml17
-rw-r--r--lib/gitlab/git_access.rb4
-rw-r--r--locale/gitlab.pot41
-rw-r--r--qa/qa/support/knapsack_report.rb4
-rw-r--r--qa/tasks/knapsack.rake2
-rw-r--r--spec/controllers/projects_controller_spec.rb35
-rw-r--r--spec/factories/clusters/agent_tokens.rb1
-rw-r--r--spec/factories/keys.rb10
-rw-r--r--spec/features/admin/admin_settings_spec.rb10
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/agent_token.json24
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/agent_token_basic.json22
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/agent_token_with_token.json26
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/agent_tokens.json4
-rw-r--r--spec/fixtures/markdown/markdown_golden_master_examples.yml2
-rw-r--r--spec/frontend/environments/environment_folder_spec.js19
-rw-r--r--spec/frontend/fixtures/runner.rb22
-rw-r--r--spec/frontend/releases/components/tag_field_new_spec.js13
-rw-r--r--spec/frontend/releases/stores/modules/detail/actions_spec.js2
-rw-r--r--spec/frontend/releases/stores/modules/detail/getters_spec.js10
-rw-r--r--spec/frontend/releases/stores/modules/detail/mutations_spec.js3
-rw-r--r--spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js10
-rw-r--r--spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js2
-rw-r--r--spec/frontend/runner/components/runner_jobs_spec.js2
-rw-r--r--spec/frontend/runner/components/runner_projects_spec.js2
-rw-r--r--spec/frontend/runner/components/runner_update_form_spec.js25
-rw-r--r--spec/frontend/runner/mock_data.js22
-rw-r--r--spec/helpers/profiles_helper_spec.rb9
-rw-r--r--spec/lib/api/entities/ci/job_request/dependency_spec.rb17
-rw-r--r--spec/lib/banzai/filter/references/issue_reference_filter_spec.rb22
-rw-r--r--spec/lib/gitlab/git_access_spec.rb9
-rw-r--r--spec/models/application_setting_spec.rb8
-rw-r--r--spec/models/instance_configuration_spec.rb9
-rw-r--r--spec/models/key_spec.rb8
-rw-r--r--spec/models/project_spec.rb30
-rw-r--r--spec/models/user_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/jobs_artifacts_spec.rb183
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb31
-rw-r--r--spec/requests/api/clusters/agent_tokens_spec.rb179
-rw-r--r--spec/requests/api/internal/base_spec.rb8
-rw-r--r--spec/requests/api/project_export_spec.rb21
-rw-r--r--spec/requests/api/settings_spec.rb3
-rw-r--r--spec/requests/api/users_spec.rb8
-rw-r--r--spec/services/keys/expiry_notification_service_spec.rb2
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb25
-rw-r--r--spec/views/profiles/keys/_form.html.haml_spec.rb4
-rw-r--r--spec/views/profiles/keys/_key.html.haml_spec.rb8
-rw-r--r--spec/workers/ssh_keys/expired_notification_worker_spec.rb10
-rw-r--r--spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb2
122 files changed, 1507 insertions, 283 deletions
diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
index a1bd57ce4f6..98928ce4715 100644
--- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
@@ -5,6 +5,12 @@ include:
- /ci/allure-report.yml
- /ci/knapsack-report.yml
+.bundler_variables:
+ variables:
+ BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES: "true"
+ BUNDLE_SILENCE_ROOT_WARNING: "true"
+ BUNDLE_PATH: vendor
+
.test_variables:
variables:
QA_DEBUG: "true"
@@ -21,16 +27,17 @@ include:
- .use-docker-in-docker
- .qa-cache
- .test_variables
+ - .bundler_variables
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:debian-bullseye-ruby-2.7-bundler-2.3-git-2.33-lfs-2.9-chrome-99-docker-20.10.14-gcloud-383-kubectl-1.23
stage: qa
- needs: ["review-deploy"]
+ needs:
+ - review-deploy
+ - download-knapsack-report
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: /certs
DOCKER_CERT_PATH: /certs/client
DOCKER_TLS_VERIFY: 1
- BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES: "true"
- BUNDLE_PATH: vendor
before_script:
- export EE_LICENSE="$(cat $REVIEW_APPS_EE_LICENSE_FILE)"
- export QA_GITLAB_URL="$(cat environment_url.txt)"
@@ -71,6 +78,25 @@ include:
ALLURE_MERGE_REQUEST_IID: $CI_MERGE_REQUEST_IID
ALLURE_RESULTS_GLOB: qa/tmp/allure-results/*
+# Store knapsack report as artifact so the same report is reused across all jobs
+download-knapsack-report:
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-2.7:bundler-2.3-git-2.33-chrome-99
+ extends:
+ - .qa-cache
+ - .bundler_variables
+ - .review:rules:review-qa-reliable
+ stage: prepare
+ before_script:
+ - cd qa && bundle install
+ script:
+ - QA_KNAPSACK_REPORT_NAME=review-qa-reliable bundle exec rake "knapsack:download"
+ - QA_KNAPSACK_REPORT_NAME=review-qa-all bundle exec rake "knapsack:download"
+ allow_failure: true
+ artifacts:
+ paths:
+ - qa/knapsack/review-qa-*.json
+ expire_in: 1 day
+
review-qa-smoke:
extends:
- .review-qa-base
@@ -145,7 +171,7 @@ allure-report-qa-all:
variables:
ALLURE_JOB_NAME: review-qa-all
-knapsack-report:
+upload-knapsack-report:
extends:
- .generate-knapsack-report-base
stage: post-qa
diff --git a/app/assets/javascripts/environments/components/environment_folder.vue b/app/assets/javascripts/environments/components/environment_folder.vue
index d5c6d26cfd0..788c3ba6fed 100644
--- a/app/assets/javascripts/environments/components/environment_folder.vue
+++ b/app/assets/javascripts/environments/components/environment_folder.vue
@@ -34,9 +34,6 @@ export default {
variables() {
return { environment: this.nestedEnvironment.latest, scope: this.scope };
},
- pollInterval() {
- return this.interval;
- },
},
interval: {
query: pollIntervalQuery,
@@ -73,6 +70,11 @@ export default {
methods: {
toggleCollapse() {
this.visible = !this.visible;
+ if (this.visible) {
+ this.$apollo.queries.folder.startPolling(this.interval);
+ } else {
+ this.$apollo.queries.folder.stopPolling();
+ }
},
isFirstEnvironment(index) {
return index === 0;
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index a9948fed3b6..3432fe12705 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -386,7 +386,7 @@ export default {
data-testid="add-to-review-button"
type="submit"
category="primary"
- variant="success"
+ variant="confirm"
@click.prevent="handleSaveDraft()"
>{{ __('Add to review') }}</gl-button
>
diff --git a/app/assets/javascripts/releases/components/tag_field_new.vue b/app/assets/javascripts/releases/components/tag_field_new.vue
index dcdf89ae0d9..d3b6d07590f 100644
--- a/app/assets/javascripts/releases/components/tag_field_new.vue
+++ b/app/assets/javascripts/releases/components/tag_field_new.vue
@@ -52,7 +52,10 @@ export default {
},
},
showTagNameValidationError() {
- return this.isInputDirty && this.validationErrors.isTagNameEmpty;
+ return (
+ this.isInputDirty &&
+ (this.validationErrors.isTagNameEmpty || this.validationErrors.existingRelease)
+ );
},
tagNameInputId() {
return uniqueId('tag-name-input-');
@@ -60,6 +63,11 @@ export default {
createFromSelectorId() {
return uniqueId('create-from-selector-');
},
+ tagFeedback() {
+ return this.validationErrors.existingRelease
+ ? __('Selected tag is already in use. Choose another option.')
+ : __('Tag name is required.');
+ },
},
methods: {
...mapActions('editNew', ['updateReleaseTagName', 'updateCreateFrom', 'fetchTagNotes']),
@@ -112,7 +120,7 @@ export default {
<gl-form-group
data-testid="tag-name-field"
:state="!showTagNameValidationError"
- :invalid-feedback="__('Tag name is required')"
+ :invalid-feedback="tagFeedback"
:label="$options.translations.tagName.label"
:label-for="tagNameInputId"
:label-description="$options.translations.tagName.labelDescription"
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/actions.js b/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
index 0a9bca97012..08197377f61 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
@@ -236,7 +236,7 @@ export const fetchTagNotes = ({ commit, state }, tagName) => {
})
.catch((error) => {
createFlash({
- message: s__('Release|Something went wrong while getting the tag notes.'),
+ message: s__('Release|Unable to fetch the tag notes.'),
});
commit(types.RECEIVE_TAG_NOTES_ERROR, error);
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/getters.js b/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
index 036bf4f3eaf..0ca5eb9931a 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
@@ -53,6 +53,10 @@ export const validationErrors = (state) => {
errors.isTagNameEmpty = true;
}
+ if (state.existingRelease) {
+ errors.existingRelease = true;
+ }
+
// Each key of this object is a URL, and the value is an
// array of Release link objects that share this URL.
// This is used for detecting duplicate URLs.
@@ -114,7 +118,11 @@ export const validationErrors = (state) => {
/** Returns whether or not the release object is valid */
export const isValid = (_state, getters) => {
const errors = getters.validationErrors;
- return Object.values(errors.assets.links).every(isEmpty) && !errors.isTagNameEmpty;
+ return (
+ Object.values(errors.assets.links).every(isEmpty) &&
+ !errors.isTagNameEmpty &&
+ !errors.existingRelease
+ );
};
/** Returns all the variables for a `releaseUpdate` GraphQL mutation */
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js b/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
index 38153e4c67b..6b22468bbfe 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
@@ -103,6 +103,7 @@ export default {
state.fetchError = undefined;
state.isFetchingTagNotes = false;
state.tagNotes = data.message;
+ state.existingRelease = data.release;
},
[types.RECEIVE_TAG_NOTES_ERROR](state, error) {
state.fetchError = error;
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/state.js b/app/assets/javascripts/releases/stores/modules/edit_new/state.js
index 9f8146997c1..33cb3ee06d0 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/state.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/state.js
@@ -53,4 +53,5 @@ export default ({
tagNotes: '',
includeTagNotes: false,
+ existingRelease: null,
});
diff --git a/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
index 7c2a575bf93..40787cf72da 100644
--- a/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
+++ b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
@@ -5,7 +5,7 @@ import { convertToGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '../components/runner_header.vue';
import RunnerUpdateForm from '../components/runner_update_form.vue';
import { I18N_FETCH_ERROR } from '../constants';
-import runnerQuery from '../graphql/details/runner.query.graphql';
+import runnerFormQuery from '../graphql/edit/runner_form.query.graphql';
import { captureException } from '../sentry_utils';
export default {
@@ -32,7 +32,7 @@ export default {
},
apollo: {
runner: {
- query: runnerQuery,
+ query: runnerFormQuery,
variables() {
return {
id: convertToGraphQLId(TYPE_CI_RUNNER, this.runnerId),
diff --git a/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue b/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue
index ef48d7799f8..c3f317b40b0 100644
--- a/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue
+++ b/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue
@@ -10,7 +10,7 @@ import RunnerPauseButton from '../components/runner_pause_button.vue';
import RunnerHeader from '../components/runner_header.vue';
import RunnerDetails from '../components/runner_details.vue';
import { I18N_FETCH_ERROR } from '../constants';
-import runnerQuery from '../graphql/details/runner.query.graphql';
+import runnerQuery from '../graphql/show/runner.query.graphql';
import { captureException } from '../sentry_utils';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
diff --git a/app/assets/javascripts/runner/components/runner_jobs.vue b/app/assets/javascripts/runner/components/runner_jobs.vue
index b25d92d049e..4eb1312b204 100644
--- a/app/assets/javascripts/runner/components/runner_jobs.vue
+++ b/app/assets/javascripts/runner/components/runner_jobs.vue
@@ -1,7 +1,7 @@
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { createAlert } from '~/flash';
-import runnerJobsQuery from '../graphql/details/runner_jobs.query.graphql';
+import runnerJobsQuery from '../graphql/show/runner_jobs.query.graphql';
import { I18N_FETCH_ERROR, I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '../constants';
import { captureException } from '../sentry_utils';
import { getPaginationVariables } from '../utils';
diff --git a/app/assets/javascripts/runner/components/runner_projects.vue b/app/assets/javascripts/runner/components/runner_projects.vue
index d080d34fdd3..daca718e2b5 100644
--- a/app/assets/javascripts/runner/components/runner_projects.vue
+++ b/app/assets/javascripts/runner/components/runner_projects.vue
@@ -2,7 +2,7 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { sprintf, formatNumber } from '~/locale';
import { createAlert } from '~/flash';
-import runnerProjectsQuery from '../graphql/details/runner_projects.query.graphql';
+import runnerProjectsQuery from '../graphql/show/runner_projects.query.graphql';
import {
I18N_ASSIGNED_PROJECTS,
I18N_NONE,
diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue
index a87976d0240..56c9007a781 100644
--- a/app/assets/javascripts/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/runner/components/runner_update_form.vue
@@ -18,7 +18,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants';
-import runnerUpdateMutation from '../graphql/details/runner_update.mutation.graphql';
+import runnerUpdateMutation from '../graphql/edit/runner_update.mutation.graphql';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
diff --git a/app/assets/javascripts/runner/graphql/details/runner.query.graphql b/app/assets/javascripts/runner/graphql/details/runner.query.graphql
deleted file mode 100644
index df6ce19fd09..00000000000
--- a/app/assets/javascripts/runner/graphql/details/runner.query.graphql
+++ /dev/null
@@ -1,7 +0,0 @@
-#import "ee_else_ce/runner/graphql/details/runner_details.fragment.graphql"
-
-query getRunner($id: CiRunnerID!) {
- runner(id: $id) {
- ...RunnerDetails
- }
-}
diff --git a/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql b/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql
deleted file mode 100644
index 2449ee0fc0f..00000000000
--- a/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql
+++ /dev/null
@@ -1,5 +0,0 @@
-#import "./runner_details_shared.fragment.graphql"
-
-fragment RunnerDetails on CiRunner {
- ...RunnerDetailsShared
-}
diff --git a/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql
deleted file mode 100644
index b79ad4d9280..00000000000
--- a/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql
+++ /dev/null
@@ -1,39 +0,0 @@
-fragment RunnerDetailsShared on CiRunner {
- __typename
- id
- shortSha
- runnerType
- active
- accessLevel
- runUntagged
- locked
- ipAddress
- executorName
- architectureName
- platformName
- description
- maximumTimeout
- jobCount
- tagList
- createdAt
- status(legacyMode: null)
- contactedAt
- version
- editAdminUrl
- userPermissions {
- updateRunner
- deleteRunner
- }
- groups {
- # Only a single group can be loaded here, while projects
- # are loaded separately using the query with pagination
- # parameters `runner_projects.query.graphql`.
- nodes {
- id
- avatarUrl
- name
- fullName
- webUrl
- }
- }
-}
diff --git a/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql b/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql
new file mode 100644
index 00000000000..b732d587d70
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql
@@ -0,0 +1,5 @@
+#import "./runner_fields_shared.fragment.graphql"
+
+fragment RunnerFields on CiRunner {
+ ...RunnerFieldsShared
+}
diff --git a/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql
new file mode 100644
index 00000000000..f900a0450e5
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql
@@ -0,0 +1,15 @@
+fragment RunnerFieldsShared on CiRunner {
+ __typename
+ id
+ shortSha
+ runnerType
+ active
+ accessLevel
+ runUntagged
+ locked
+ description
+ maximumTimeout
+ tagList
+ createdAt
+ status(legacyMode: null)
+}
diff --git a/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql b/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql
new file mode 100644
index 00000000000..0bf66c223fc
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql
@@ -0,0 +1,7 @@
+#import "ee_else_ce/runner/graphql/edit/runner_fields.fragment.graphql"
+
+query getRunnerForm($id: CiRunnerID!) {
+ runner(id: $id) {
+ ...RunnerFields
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql b/app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql
index 352b95c1a39..8694a51b5a4 100644
--- a/app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql
+++ b/app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql
@@ -1,4 +1,4 @@
-#import "ee_else_ce/runner/graphql/details/runner_details.fragment.graphql"
+#import "ee_else_ce/runner/graphql/edit/runner_fields.fragment.graphql"
# Mutation for updates from the runner form, loads
# attributes shown in the runner details.
@@ -6,7 +6,7 @@
mutation runnerUpdate($input: RunnerUpdateInput!) {
runnerUpdate(input: $input) {
runner {
- ...RunnerDetails
+ ...RunnerFields
}
errors
}
diff --git a/app/assets/javascripts/runner/graphql/show/runner.query.graphql b/app/assets/javascripts/runner/graphql/show/runner.query.graphql
new file mode 100644
index 00000000000..178816b58bd
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/show/runner.query.graphql
@@ -0,0 +1,41 @@
+query getRunner($id: CiRunnerID!) {
+ runner(id: $id) {
+ __typename
+ id
+ shortSha
+ runnerType
+ active
+ accessLevel
+ runUntagged
+ locked
+ ipAddress
+ executorName
+ architectureName
+ platformName
+ description
+ maximumTimeout
+ jobCount
+ tagList
+ createdAt
+ status(legacyMode: null)
+ contactedAt
+ version
+ editAdminUrl
+ userPermissions {
+ updateRunner
+ deleteRunner
+ }
+ groups {
+ # Only a single group can be loaded here, while projects
+ # are loaded separately using the query with pagination
+ # parameters `runner_projects.query.graphql`.
+ nodes {
+ id
+ avatarUrl
+ name
+ fullName
+ webUrl
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql
index 14585e62bf2..14585e62bf2 100644
--- a/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql
+++ b/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql
diff --git a/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql
index cb27de7c200..cb27de7c200 100644
--- a/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql
+++ b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
index ddfd7e66bcb..dfa2ca2d20c 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
@@ -199,7 +199,7 @@ export default {
>
<gl-button
category="primary"
- variant="success"
+ variant="confirm"
:disabled="!Boolean(selectedProject)"
class="gl-text-center! issuable-move-button"
@click="handleMoveClick"
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index dfb9ef54f7c..5667617b610 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -238,6 +238,11 @@ class ProjectsController < Projects::ApplicationController
edit_project_path(@project, anchor: 'js-export-project'),
notice: _("Project export started. A download link will be sent by email and made available on this page.")
)
+ rescue Project::ExportLimitExceeded => ex
+ redirect_to(
+ edit_project_path(@project, anchor: 'js-export-project'),
+ alert: ex.to_s
+ )
end
def download_export
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index aa0e8c55470..f6fff68e98b 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -272,6 +272,7 @@ module ApplicationSettingsHelper
:invisible_captcha_enabled,
:max_artifacts_size,
:max_attachment_size,
+ :max_export_size,
:max_import_size,
:max_pages_size,
:max_yaml_size_bytes,
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 6eaeaf1b025..29b2f2eb12f 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -246,15 +246,15 @@ module MergeRequestsHelper
''
end
- link_to branch, branch_path, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2'
+ link_to branch, branch_path, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2 gl-display-inline-block gl-text-truncate gl-w-30p gl-mb-n3'
end
def merge_request_header(project, merge_request)
link_to_author = link_to_member(project, merge_request.author, size: 24, extra_class: 'gl-font-weight-bold', avatar: false)
copy_button = clipboard_button(text: merge_request.source_branch, title: _('Copy branch name'), class: 'btn btn-default btn-sm gl-button btn-default-tertiary btn-icon gl-display-none! gl-md-display-inline-block! js-source-branch-copy')
- target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2'
+ target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2 gl-display-inline-block gl-text-truncate gl-w-20p gl-mb-n3'
- _('%{author} requested to merge %{span_start}%{source_branch} %{copy_button}%{span_end} into %{target_branch} %{created_at}').html_safe % { author: link_to_author.html_safe, source_branch: merge_request_source_branch(merge_request).html_safe, copy_button: copy_button.html_safe, target_branch: target_branch.html_safe, created_at: time_ago_with_tooltip(merge_request.created_at, html_class: 'gl-display-inline-block').html_safe, span_start: '<span class="gl-display-inline-block">'.html_safe, span_end: '</span>'.html_safe }
+ _('%{author} requested to merge %{source_branch} %{copy_button} into %{target_branch} %{created_at}').html_safe % { author: link_to_author.html_safe, source_branch: merge_request_source_branch(merge_request).html_safe, copy_button: copy_button.html_safe, target_branch: target_branch.html_safe, created_at: time_ago_with_tooltip(merge_request.created_at, html_class: 'gl-display-inline-block').html_safe }
end
end
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
index 0d514773891..20d0dd9b30c 100644
--- a/app/helpers/profiles_helper.rb
+++ b/app/helpers/profiles_helper.rb
@@ -53,13 +53,11 @@ module ProfilesHelper
# Overridden in EE::ProfilesHelper#ssh_key_expiration_tooltip
def ssh_key_expiration_tooltip(key)
return key.errors.full_messages.join(', ') if key.errors.full_messages.any?
-
- s_('Profiles|Key usable beyond expiration date.') if key.expired?
end
# Overridden in EE::ProfilesHelper#ssh_key_expires_field_description
def ssh_key_expires_field_description
- s_('Profiles|Key can still be used after expiration.')
+ s_('Profiles|Key becomes invalid on this date.')
end
# Overridden in EE::ProfilesHelper#ssh_key_expiration_policy_enabled?
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index a49658ce7e0..bf68101934b 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -13,6 +13,7 @@ class ApplicationSetting < ApplicationRecord
ignore_column %i[max_package_files_for_package_destruction], remove_with: '14.9', remove_after: '2022-03-22'
ignore_column :user_email_lookup_limit, remove_with: '15.0', remove_after: '2022-04-18'
ignore_column :pseudonymizer_enabled, remove_with: '15.1', remove_after: '2022-06-22'
+ ignore_column :enforce_ssh_key_expiration, remove_with: '15.2', remove_after: '2022-07-22'
ignore_column :enforce_pat_expiration, remove_with: '15.2', remove_after: '2022-07-22'
INSTANCE_REVIEW_MIN_USERS = 50
@@ -201,6 +202,10 @@ class ApplicationSetting < ApplicationRecord
presence: true,
numericality: { only_integer: true, greater_than: 0 }
+ validates :max_export_size,
+ presence: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+
validates :max_import_size,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 194356acc51..36366b375b9 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -108,6 +108,7 @@ module ApplicationSettingImplementation
mailgun_events_enabled: false,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
+ max_export_size: 0,
max_import_size: 0,
max_yaml_size_bytes: 1.megabyte,
max_yaml_depth: 100,
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5622f228d83..5bb9f8fcfe1 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -757,7 +757,7 @@ module Ci
end
def valid_token?(token)
- self.token && ActiveSupport::SecurityUtils.secure_compare(token, self.token)
+ self.token && token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token)
end
# acts_as_taggable uses this method create/remove tags with contexts
diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb
index 00e55d0fd89..49e8a04e251 100644
--- a/app/models/instance_configuration.rb
+++ b/app/models/instance_configuration.rb
@@ -47,6 +47,7 @@ class InstanceConfiguration
{
max_attachment_size: application_settings[:max_attachment_size].megabytes,
receive_max_input_size: application_settings[:receive_max_input_size]&.megabytes,
+ max_export_size: application_settings[:max_export_size] > 0 ? application_settings[:max_export_size].megabytes : nil,
max_import_size: application_settings[:max_import_size] > 0 ? application_settings[:max_import_size].megabytes : nil,
diff_max_patch_bytes: application_settings[:diff_max_patch_bytes].bytes,
max_artifacts_size: application_settings[:max_artifacts_size].megabytes,
diff --git a/app/models/key.rb b/app/models/key.rb
index 5e61ecf73f5..e093f9faad3 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -29,6 +29,7 @@ class Key < ApplicationRecord
presence: { message: 'cannot be generated' }
validate :key_meets_restrictions
+ validate :expiration, on: :create
delegate :name, :email, to: :user, prefix: true
@@ -148,6 +149,10 @@ class Key < ApplicationRecord
"type is forbidden. Must be #{Gitlab::Utils.to_exclusive_sentence(allowed_types)}"
end
+
+ def expiration
+ errors.add(:key, message: 'has expired') if expired?
+ end
end
Key.prepend_mod_with('Key')
diff --git a/app/models/project.rb b/app/models/project.rb
index 528f87972b3..dd5bc9ded6a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -49,6 +49,7 @@ class Project < ApplicationRecord
ignore_columns :container_registry_enabled, remove_after: '2021-09-22', remove_with: '14.4'
BoardLimitExceeded = Class.new(StandardError)
+ ExportLimitExceeded = Class.new(StandardError)
ignore_columns :mirror_last_update_at, :mirror_last_successful_update_at, remove_after: '2021-09-22', remove_with: '14.4'
ignore_columns :pull_mirror_branch_prefix, remove_after: '2021-09-22', remove_with: '14.4'
@@ -2054,6 +2055,8 @@ class Project < ApplicationRecord
end
def add_export_job(current_user:, after_export_strategy: nil, params: {})
+ check_project_export_limit!
+
job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params)
if job_id
@@ -3112,6 +3115,14 @@ class Project < ApplicationRecord
Projects::SyncEvent.enqueue_worker
end
end
+
+ def check_project_export_limit!
+ return if Gitlab::CurrentSettings.current_application_settings.max_export_size == 0
+
+ if self.statistics.storage_size > Gitlab::CurrentSettings.current_application_settings.max_export_size.megabytes
+ raise ExportLimitExceeded, _('The project size exceeds the export limit.')
+ end
+ end
end
Project.prepend_mod_with('Project')
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index adbe3ddfdad..2b81967b1e3 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -147,6 +147,10 @@ module MergeRequests
params[:milestone] = milestone if milestone
end
+ if params.key?(:description)
+ params[:description] = params[:description].gsub('\n', "\n")
+ end
+
params
end
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index d55efbaf701..f914de138a9 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -19,6 +19,10 @@
= f.label :receive_max_input_size, _('Maximum push size (MB)'), class: 'label-light'
= f.number_field :receive_max_input_size, class: 'form-control gl-form-input qa-receive-max-input-size-field', title: _('Maximum size limit for a single commit.'), data: { toggle: 'tooltip', container: 'body' }
.form-group
+ = f.label :max_export_size, _('Maximum export size (MB)'), class: 'label-light'
+ = f.number_field :max_export_size, class: 'form-control gl-form-input', title: _('Maximum size of export files.'), data: { toggle: 'tooltip', container: 'body' }
+ %span.form-text.text-muted= _('Set to 0 for no size limit.')
+ .form-group
= f.label :max_import_size, _('Maximum import size (MB)'), class: 'label-light'
= f.number_field :max_import_size, class: 'form-control gl-form-input qa-receive-max-import-size-field', title: _('Maximum size of import files.'), data: { toggle: 'tooltip', container: 'body' }
%span.form-text.text-muted= _('Only effective when remote storage is enabled. Set to 0 for no size limit.')
@@ -30,7 +34,6 @@
= render_if_exists 'admin/application_settings/git_two_factor_session_expiry', form: f
= render_if_exists 'admin/application_settings/personal_access_token_expiration_policy', form: f
= render_if_exists 'admin/application_settings/ssh_key_expiration_policy', form: f
- = render_if_exists 'admin/application_settings/enforce_ssh_key_expiration', form: f
.form-group
= f.label :user_oauth_applications, _('User OAuth applications'), class: 'label-bold'
diff --git a/app/views/help/instance_configuration/_size_limits.html.haml b/app/views/help/instance_configuration/_size_limits.html.haml
index b592eeed020..90501450385 100644
--- a/app/views/help/instance_configuration/_size_limits.html.haml
+++ b/app/views/help/instance_configuration/_size_limits.html.haml
@@ -24,6 +24,9 @@
%td= _('Maximum push size')
%td= instance_configuration_human_size_cell(size_limits[:receive_max_input_size])
%tr
+ %td= _('Maximum export size')
+ %td= instance_configuration_human_size_cell(size_limits[:max_export_size])
+ %tr
%td= _('Maximum import size')
%td= instance_configuration_human_size_cell(size_limits[:max_import_size])
%tr
diff --git a/config/feature_flags/development/ci_authenticate_running_job_token_for_artifacts.yml b/config/feature_flags/development/ci_authenticate_running_job_token_for_artifacts.yml
new file mode 100644
index 00000000000..653380f445e
--- /dev/null
+++ b/config/feature_flags/development/ci_authenticate_running_job_token_for_artifacts.yml
@@ -0,0 +1,8 @@
+---
+name: ci_authenticate_running_job_token_for_artifacts
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83713
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357075
+milestone: '15.0'
+type: development
+group: group::pipeline insights
+default_enabled: false
diff --git a/config/feature_flags/development/ci_expose_running_job_token_for_artifacts.yml b/config/feature_flags/development/ci_expose_running_job_token_for_artifacts.yml
new file mode 100644
index 00000000000..aa6bcbeb1d1
--- /dev/null
+++ b/config/feature_flags/development/ci_expose_running_job_token_for_artifacts.yml
@@ -0,0 +1,8 @@
+---
+name: ci_expose_running_job_token_for_artifacts
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83713
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357075
+milestone: '15.0'
+type: development
+group: group::pipeline insights
+default_enabled: false
diff --git a/data/deprecations/15-0-deprecate-postgresql-12.yml b/data/deprecations/15-0-deprecate-postgresql-12.yml
new file mode 100644
index 00000000000..bebfba64405
--- /dev/null
+++ b/data/deprecations/15-0-deprecate-postgresql-12.yml
@@ -0,0 +1,19 @@
+- name: "PostgreSQL 12 deprecated"
+ announcement_milestone: "15.0"
+ announcement_date: "2022-05-22"
+ removal_milestone: "16.0"
+ removal_date: "2023-05-22"
+ breaking_change: true
+ reporter: iroussos
+ body: | # Do not modify this line, instead modify the lines below.
+ Support for PostgreSQL 12 is scheduled for removal in GitLab 16.0.
+ In GitLab 16.0, PostgreSQL 13 becomes the minimum required PostgreSQL version.
+
+ PostgreSQL 12 will be supported for the full GitLab 15 release cycle.
+ PostgreSQL 13 will also be supported for instances that want to upgrade prior to GitLab 16.0.
+
+ Upgrading to PostgreSQL 13 is not yet supported for GitLab instances with Geo enabled. Geo support for PostgreSQL 13 will be announced in a minor release version of GitLab 15, after the process is fully supported and validated. For more information, read the Geo related verifications on the [support epic for PostgreSQL 13](https://gitlab.com/groups/gitlab-org/-/epics/3832).
+ stage: Enablement
+ tiers: [Free, Premium, Ultimate]
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349185
+ documentation_url: https://docs.gitlab.com/omnibus/development/managing-postgresql-versions.html
diff --git a/db/migrate/20220426130217_add_max_export_size_to_application_settings.rb b/db/migrate/20220426130217_add_max_export_size_to_application_settings.rb
new file mode 100644
index 00000000000..d1488a77d14
--- /dev/null
+++ b/db/migrate/20220426130217_add_max_export_size_to_application_settings.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddMaxExportSizeToApplicationSettings < Gitlab::Database::Migration[2.0]
+ def change
+ add_column :application_settings, :max_export_size, :integer, default: 0
+ end
+end
diff --git a/db/post_migrate/20220505060011_remove_namespaces_id_parent_id_partial_index.rb b/db/post_migrate/20220505060011_remove_namespaces_id_parent_id_partial_index.rb
new file mode 100644
index 00000000000..5125a97af7e
--- /dev/null
+++ b/db/post_migrate/20220505060011_remove_namespaces_id_parent_id_partial_index.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class RemoveNamespacesIdParentIdPartialIndex < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ NAME = 'index_namespaces_id_parent_id_is_null'
+
+ def up
+ remove_concurrent_index :namespaces, :id, name: NAME
+ end
+
+ def down
+ add_concurrent_index :namespaces, :id, where: 'parent_id IS NULL', name: NAME
+ end
+end
diff --git a/db/schema_migrations/20220426130217 b/db/schema_migrations/20220426130217
new file mode 100644
index 00000000000..d8df97c8516
--- /dev/null
+++ b/db/schema_migrations/20220426130217
@@ -0,0 +1 @@
+5a55099d1f50c3059778e340bbbe519d4fcd6c1eefb235191f8db02f92b7b49e \ No newline at end of file
diff --git a/db/schema_migrations/20220505060011 b/db/schema_migrations/20220505060011
new file mode 100644
index 00000000000..dd31c727827
--- /dev/null
+++ b/db/schema_migrations/20220505060011
@@ -0,0 +1 @@
+aa0e6f29d918bff13cbf499e465f63320dbb6ed5a6940c2c438fe015dcc7fcd6 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index e4a24a866d4..3a955ccac67 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -11292,6 +11292,7 @@ CREATE TABLE application_settings (
inactive_projects_send_warning_email_after_months integer DEFAULT 1 NOT NULL,
delayed_group_deletion boolean DEFAULT true NOT NULL,
arkose_labs_namespace text DEFAULT 'client'::text NOT NULL,
+ max_export_size integer DEFAULT 0,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
@@ -28385,8 +28386,6 @@ CREATE UNIQUE INDEX index_namespace_statistics_on_namespace_id ON namespace_stat
CREATE INDEX index_namespaces_id_parent_id_is_not_null ON namespaces USING btree (id) WHERE (parent_id IS NOT NULL);
-CREATE INDEX index_namespaces_id_parent_id_is_null ON namespaces USING btree (id) WHERE (parent_id IS NULL);
-
CREATE UNIQUE INDEX index_namespaces_name_parent_id_type ON namespaces USING btree (name, parent_id, type);
CREATE INDEX index_namespaces_on_created_at ON namespaces USING btree (created_at);
diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md
index 9408e7c25a6..bb7317d04eb 100644
--- a/doc/api/api_resources.md
+++ b/doc/api/api_resources.md
@@ -26,6 +26,7 @@ The following API resources are available in the project context:
| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
| [Access tokens](project_access_tokens.md) | `/projects/:id/access_tokens` (also available for groups) |
| [Agents](cluster_agents.md) | `/projects/:id/cluster_agents` |
+| [Agent Tokens](cluster_agent_tokens.md) | `/projects/:id/cluster_agents/:agent_id/tokens` |
| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
diff --git a/doc/api/cluster_agent_tokens.md b/doc/api/cluster_agent_tokens.md
new file mode 100644
index 00000000000..92814e5e9af
--- /dev/null
+++ b/doc/api/cluster_agent_tokens.md
@@ -0,0 +1,216 @@
+---
+stage: Configure
+group: Configure
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Agent Tokens API **(FREE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/347046) in GitLab 15.0.
+
+Use the Agent Tokens API to manage tokens for the GitLab agent for Kubernetes.
+
+## List tokens for an agent
+
+Returns a list of tokens for an agent.
+
+You must have at least the Developer role to use this endpoint.
+
+```plaintext
+GET /projects/:id/cluster_agents/:agent_id/tokens
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+|------------|-------------------|-----------|------------------------------------------------------------------------------------------------------------------|
+| `id` | integer or string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) maintained by the authenticated user. |
+| `agent_id` | integer or string | yes | ID of the agent. |
+
+Response:
+
+The response is a list of tokens with the following fields:
+
+| Attribute | Type | Description |
+|----------------------|----------------|-------------------------------------------------------------------|
+| `id` | integer | ID of the token. |
+| `name` | string | Name of the token. |
+| `description` | string or null | Description of the token. |
+| `agent_id` | integer | ID of the agent the token belongs to. |
+| `status` | string | The status of the token. Valid values are `active` and `revoked`. |
+| `created_at` | string | ISO8601 datetime when the token was created. |
+| `created_by_user_id` | string | User ID of the user who created the token. |
+
+Example request:
+
+```shell
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/20/cluster_agents/5/tokens"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "abcd",
+ "description": "Some token",
+ "agent_id": 5,
+ "status": "active",
+ "created_at": "2022-03-25T14:12:11.497Z",
+ "created_by_user_id": 1
+ },
+ {
+ "id": 2,
+ "name": "foobar",
+ "description": null,
+ "agent_id": 5,
+ "status": "active",
+ "created_at": "2022-03-25T14:12:11.497Z",
+ "created_by_user_id": 1
+ }
+]
+```
+
+NOTE:
+The `last_used_at` field for a token is only returned when getting a single agent token.
+
+## Get a single agent token
+
+Gets a single agent token.
+
+You must have at least the Developer role to use this endpoint.
+
+```shell
+GET /projects/:id/cluster_agents/:agent_id/tokens/:token_id
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+|------------|-------------------|----------|-------------------------------------------------------------------------------------------------------------------|
+| `id` | integer or string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) maintained by the authenticated user. |
+| `agent_id` | integer | yes | ID of the agent. |
+| `token_id` | integer | yes | ID of the token. |
+
+Response:
+
+The response is a single token with the following fields:
+
+| Attribute | Type | Description |
+|----------------------|----------------|-------------------------------------------------------------------|
+| `id` | integer | ID of the token. |
+| `name` | string | Name of the token. |
+| `description` | string or null | Description of the token. |
+| `agent_id` | integer | ID of the agent the token belongs to. |
+| `status` | string | The status of the token. Valid values are `active` and `revoked`. |
+| `created_at` | string | ISO8601 datetime when the token was created. |
+| `created_by_user_id` | string | User ID of the user who created the token. |
+| `last_used_at` | string or null | ISO8601 datetime when the token was last used. |
+
+Example request:
+
+```shell
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/20/cluster_agents/5/token/1"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "abcd",
+ "description": "Some token",
+ "agent_id": 5,
+ "status": "active",
+ "created_at": "2022-03-25T14:12:11.497Z",
+ "created_by_user_id": 1,
+ "last_used_at": null
+}
+```
+
+## Create an agent token
+
+Creates a new token for an agent.
+
+You must have at least the Maintainer role to use this endpoint.
+
+```shell
+POST /projects/:id/cluster_agents/:agent_id/tokens
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+|---------------|-------------------|----------|------------------------------------------------------------------------------------------------------------------|
+| `id` | integer or string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) maintained by the authenticated user. |
+| `agent_id` | integer | yes | ID of the agent. |
+| `name` | string | yes | Name for the token. |
+| `description` | string | no | Description for the token. |
+
+Response:
+
+The response is the new token with the following fields:
+
+| Attribute | Type | Description |
+|----------------------|----------------|-------------------------------------------------------------------|
+| `id` | integer | ID of the token. |
+| `name` | string | Name of the token. |
+| `description` | string or null | Description of the token. |
+| `agent_id` | integer | ID of the agent the token belongs to. |
+| `status` | string | The status of the token. Valid values are `active` and `revoked`. |
+| `created_at` | string | ISO8601 datetime when the token was created. |
+| `created_by_user_id` | string | User ID of the user who created the token. |
+| `last_used_at` | string or null | ISO8601 datetime when the token was last used. |
+| `token` | string | The secret token value. |
+
+NOTE:
+The `token` is only returned in the response of the `POST` endpoint and cannot be retrieved afterwards.
+
+Example request:
+
+```shell
+curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/20/cluster_agents/5/tokens" \
+ -H "Content-Type:application/json" \
+ -X POST --data '{"name":"some-token"}'
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "abcd",
+ "description": "Some token",
+ "agent_id": 5,
+ "status": "active",
+ "created_at": "2022-03-25T14:12:11.497Z",
+ "created_by_user_id": 1,
+ "last_used_at": null,
+ "token": "qeY8UVRisx9y3Loxo1scLxFuRxYcgeX3sxsdrpP_fR3Loq4xyg"
+}
+```
+
+## Revoke an agent token
+
+Revokes an agent token.
+
+You must have at least the Maintainer role to use this endpoint.
+
+```plaintext
+DELETE /projects/:id/cluster_agents/:agent_id/tokens/:token_id
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+|------------|-------------------|----------|---------------------------------------------------------------------------------------------------------------- -|
+| `id` | integer or string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) maintained by the authenticated user. |
+| `agent_id` | integer | yes | ID of the agent. |
+| `token_id` | integer | yes | ID of the token. |
+
+Example request:
+
+```shell
+curl --request DELETE --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/20/cluster_agents/5/tokens/1
+```
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index bc8feb29e8e..df16eb54132 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -9462,7 +9462,7 @@ Represents the total number of issues and their weights for a particular day.
| <a id="ciminutesnamespacemonthlyusageminutes"></a>`minutes` | [`Int`](#int) | Total number of minutes used by all projects in the namespace. |
| <a id="ciminutesnamespacemonthlyusagemonth"></a>`month` | [`String`](#string) | Month related to the usage data. |
| <a id="ciminutesnamespacemonthlyusagemonthiso8601"></a>`monthIso8601` | [`ISO8601Date`](#iso8601date) | Month related to the usage data in ISO 8601 date format. |
-| <a id="ciminutesnamespacemonthlyusageprojects"></a>`projects` | [`CiMinutesProjectMonthlyUsageConnection`](#ciminutesprojectmonthlyusageconnection) | CI minutes usage data for projects in the namespace. (see [Connections](#connections)) |
+| <a id="ciminutesnamespacemonthlyusageprojects"></a>`projects` | [`CiMinutesProjectMonthlyUsageConnection`](#ciminutesprojectmonthlyusageconnection) | CI/CD minutes usage data for projects in the namespace. (see [Connections](#connections)) |
| <a id="ciminutesnamespacemonthlyusagesharedrunnersduration"></a>`sharedRunnersDuration` | [`Int`](#int) | Total duration (in seconds) of shared runners use by the namespace for the month. |
### `CiMinutesProjectMonthlyUsage`
@@ -9471,7 +9471,7 @@ Represents the total number of issues and their weights for a particular day.
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="ciminutesprojectmonthlyusageminutes"></a>`minutes` | [`Int`](#int) | Number of CI minutes used by the project in the month. |
+| <a id="ciminutesprojectmonthlyusageminutes"></a>`minutes` | [`Int`](#int) | Number of CI/CD minutes used by the project in the month. |
| <a id="ciminutesprojectmonthlyusagename"></a>`name` | [`String`](#string) | Name of the project. |
| <a id="ciminutesprojectmonthlyusagesharedrunnersduration"></a>`sharedRunnersDuration` | [`Int`](#int) | Total duration (in seconds) of shared runners use by the project for the month. |
diff --git a/doc/api/managed_licenses.md b/doc/api/managed_licenses.md
index f2626574bf0..31f1cb41eb3 100644
--- a/doc/api/managed_licenses.md
+++ b/doc/api/managed_licenses.md
@@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Managed Licenses API **(ULTIMATE)**
WARNING:
-"approval" and "blacklisted" approval statuses are deprecated and scheduled to be changed to "allowed" and "denied" in GitLab 15.0.
+"approval" and "blacklisted" approval statuses are changed to "allowed" and "denied" in GitLab 15.0.
## List managed licenses
@@ -32,12 +32,12 @@ Example response:
{
"id": 1,
"name": "MIT",
- "approval_status": "approved"
+ "approval_status": "allowed"
},
{
"id": 3,
"name": "ISC",
- "approval_status": "blacklisted"
+ "approval_status": "denied"
}
]
```
@@ -65,7 +65,7 @@ Example response:
{
"id": 1,
"name": "MIT",
- "approval_status": "blacklisted"
+ "approval_status": "denied"
}
```
@@ -81,7 +81,7 @@ POST /projects/:id/managed_licenses
| ------------- | ------- | -------- | ---------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the managed license |
-| `approval_status` | string | yes | The approval status of the license. "allowed" or "denied". "blacklisted" and "approved" are deprecated. |
+| `approval_status` | string | yes | The approval status of the license. "allowed" or "denied". |
```shell
curl --data "name=MIT&approval_status=denied" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/managed_licenses"
@@ -93,7 +93,7 @@ Example response:
{
"id": 1,
"name": "MIT",
- "approval_status": "approved"
+ "approval_status": "allowed"
}
```
@@ -128,7 +128,7 @@ PATCH /projects/:id/managed_licenses/:managed_license_id
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
| `managed_license_id` | integer/string | yes | The ID or URL-encoded name of the license belonging to the project |
-| `approval_status` | string | yes | The approval status of the license. "allowed" or "denied". "blacklisted" and "approved" are deprecated. |
+| `approval_status` | string | yes | The approval status of the license. "allowed" or "denied". |
```shell
curl --request PATCH --data "approval_status=denied" \
@@ -141,6 +141,6 @@ Example response:
{
"id": 1,
"name": "MIT",
- "approval_status": "blacklisted"
+ "approval_status": "denied"
}
```
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index 3b35a43398e..3f2cc09aa1e 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -49,6 +49,12 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
NOTE:
The upload request is sent with `Content-Type: application/gzip` header. Ensure that your pre-signed URL includes this as part of the signature.
+NOTE:
+As an administrator, you can modify the maximum export file size. By default,
+it is set to `0`, for unlimited. To change this value, edit `max_export_size`
+in the [Application settings API](settings.md#change-application-settings)
+or the [Admin UI](../user/admin_area/settings/account_and_limit_settings.md).
+
## Export status
Get the status of export.
diff --git a/doc/api/settings.md b/doc/api/settings.md
index cf20cd279fc..a55ce223084 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -36,6 +36,7 @@ Example response:
"password_authentication_enabled_for_web" : true,
"after_sign_out_path" : null,
"max_attachment_size" : 10,
+ "max_export_size": 50,
"max_import_size": 50,
"user_oauth_applications" : true,
"updated_at" : "2016-01-04T15:44:55.176Z",
@@ -147,6 +148,7 @@ Example response:
"default_branch_protection": 2,
"restricted_visibility_levels": [],
"max_attachment_size": 10,
+ "max_export_size": 50,
"max_import_size": 50,
"session_expire_delay": 10080,
"default_ci_config_path" : null,
@@ -367,6 +369,7 @@ listed in the descriptions of the relevant settings.
| `maintenance_mode` **(PREMIUM)** | boolean | no | When instance is in maintenance mode, non-administrative users can sign in with read-only access and make read-only API requests. |
| `max_artifacts_size` | integer | no | Maximum artifacts size in MB. |
| `max_attachment_size` | integer | no | Limit attachment size in MB. |
+| `max_export_size` | integer | no | Maximum export size in MB. 0 for unlimited. Default = 0 (unlimited). |
| `max_import_size` | integer | no | Maximum import size in MB. 0 for unlimited. Default = 0 (unlimited) [Modified](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50MB to 0 in GitLab 13.8. |
| `max_pages_size` | integer | no | Maximum size of pages repositories in MB. |
| `max_personal_access_token_lifetime` **(ULTIMATE SELF)** | integer | no | Maximum allowable lifetime for access tokens in days. |
diff --git a/doc/development/merge_request_application_and_rate_limit_guidelines.md b/doc/development/merge_request_application_and_rate_limit_guidelines.md
index 94ae126802a..62bf62f6275 100644
--- a/doc/development/merge_request_application_and_rate_limit_guidelines.md
+++ b/doc/development/merge_request_application_and_rate_limit_guidelines.md
@@ -14,7 +14,7 @@ Every new feature should have safe usage limits included in its implementation.
Limits are applicable for:
- System-level resource pools such as API requests, SSHD connections, database connections, storage, and so on.
-- Domain-level objects such as CI minutes, groups, sign-in attempts, and so on.
+- Domain-level objects such as CI/CD minutes, groups, sign-in attempts, and so on.
## When limits are required
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 6dfd8202ac9..d26e20a42b9 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -127,9 +127,9 @@ scripts/regenerate-schema
TARGET=12-9-stable-ee scripts/regenerate-schema
```
-There may be times when the `scripts/regenerate-schema` script creates
-additional differences. In this case, a manual procedure can be used,
-where <migration ID> is the DATETIME part of the migration file.
+The `scripts/regenerate-schema` script can create additional differences.
+If this happens, use a manual procedure where `<migration ID>` is the `DATETIME`
+part of the migration file.
```shell
# Rebase against master
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index cb1e224f062..8b5fd3952ba 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -93,7 +93,7 @@ In addition, there are a few circumstances where we would always run the full Je
### Fork pipelines
We only run the minimal RSpec & Jest jobs for fork pipelines unless the `pipeline:run-all-rspec`
-label is set on the MR. The goal is to reduce the CI minutes consumed by fork pipelines.
+label is set on the MR. The goal is to reduce the CI/CD minutes consumed by fork pipelines.
See the [experiment issue](https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1170).
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index fea778625ea..2e104ea79d9 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -69,6 +69,27 @@ be present during the 16.x cycle to avoid breaking the API signature, and will b
**Planned removal milestone: <span class="removal-milestone">16.0</span> (2023-05-22)**
</div>
+<div class="deprecation removal-160 breaking-change">
+
+### PostgreSQL 12 deprecated
+
+WARNING:
+This feature will be changed or removed in 16.0
+as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
+Before updating GitLab, review the details carefully to determine if you need to make any
+changes to your code, settings, or workflow.
+
+Support for PostgreSQL 12 is scheduled for removal in GitLab 16.0.
+In GitLab 16.0, PostgreSQL 13 becomes the minimum required PostgreSQL version.
+
+PostgreSQL 12 will be supported for the full GitLab 15 release cycle.
+PostgreSQL 13 will also be supported for instances that want to upgrade prior to GitLab 16.0.
+
+Upgrading to PostgreSQL 13 is not yet supported for GitLab instances with Geo enabled. Geo support for PostgreSQL 13 will be announced in a minor release version of GitLab 15, after the process is fully supported and validated. For more information, read the Geo related verifications on the [support epic for PostgreSQL 13](https://gitlab.com/groups/gitlab-org/-/epics/3832).
+
+**Planned removal milestone: <span class="removal-milestone">16.0</span> (2023-05-22)**
+</div>
+
<div class="deprecation removal-152">
### Vulnerability Report sort by State
diff --git a/doc/update/index.md b/doc/update/index.md
index 19fe0b1b84e..7fe1d67168b 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -406,7 +406,8 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
### 14.10.0
-- The upgrade to GitLab 14.10 executes a [concurrent index drop](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84308) of unneeded
+- Before upgrading to GitLab 14.10, you need to already have the latest 14.9.Z installed on your instance.
+ The upgrade to GitLab 14.10 executes a [concurrent index drop](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84308) of unneeded
entries from the `ci_job_artifacts` database table. This could potentially run for multiple minutes, especially if the table has a lot of
traffic and the migration is unable to acquire a lock. It is advised to let this process finish as restarting may result in data loss.
diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md
index 9b1a32e9083..fe713baeb39 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -66,6 +66,16 @@ because the [web server](../../../development/architecture.md#components)
must receive the file before GitLab can generate the commit.
Use [Git LFS](../../../topics/git/lfs/index.md) to add large files to a repository.
+## Max export size
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86124) in GitLab 15.0.
+
+To modify the maximum file size for exports in GitLab:
+
+1. On the top bar, select **Menu > Admin**.
+1. On the left sidebar, select **Settings > General**, then expand **Account and limit**.
+1. Increase or decrease by changing the value in **Maximum export size (MB)**.
+
## Max import size
> [Modified](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50 MB to unlimited in GitLab 13.8.
@@ -231,25 +241,17 @@ Once a lifetime for SSH keys is set, GitLab:
NOTE:
When a user's SSH key becomes invalid they can delete and re-add the same key again.
-## Allow expired SSH keys to be used (DEPRECATED) **(ULTIMATE SELF)**
+<!--- start_remove The following content will be removed on remove_date: '2022-08-22' -->
+## Allow expired SSH keys to be used (removed) **(ULTIMATE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250480) in GitLab 13.9.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/320970) in GitLab 14.0.
> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351963) in GitLab 14.8.
+> - [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/351963) in GitLab 15.0.
-WARNING:
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351963) in GitLab 14.8.
-
-By default, expired SSH keys **are not usable**.
-
-To allow the use of expired SSH keys:
-
-1. On the top bar, select **Menu > Admin**.
-1. On the left sidebar, select **Settings > General**.
-1. Expand the **Account and limit** section.
-1. Uncheck the **Enforce SSH key expiration** checkbox.
-
-Disabling SSH key expiration immediately enables all expired SSH keys.
+This feature was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/351963) in GitLab 15.0.
+<!--- end_remove -->
## Limit the lifetime of access tokens **(ULTIMATE SELF)**
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 5e31cca51bd..62854810bdb 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -580,7 +580,7 @@ The following variables allow configuration of global dependency scanning settin
| ----------------------------|------------ |
| `ADDITIONAL_CA_CERT_BUNDLE` | Bundle of CA certs to trust. The bundle of certificates provided here is also used by other tools during the scanning process, such as `git`, `yarn`, or `npm`. See [Using a custom SSL CA certificate authority](#using-a-custom-ssl-ca-certificate-authority) for more details. |
| `DS_EXCLUDED_ANALYZERS` | Specify the analyzers (by name) to exclude from Dependency Scanning. For more information, see [Dependency Scanning Analyzers](analyzers.md). |
-| `DS_DEFAULT_ANALYZERS` | ([**DEPRECATED - use `DS_EXCLUDED_ANALYZERS` instead**](https://gitlab.com/gitlab-org/gitlab/-/issues/287691)) Override the names of the official default images. For more information, see [Dependency Scanning Analyzers](analyzers.md). |
+| `DS_DEFAULT_ANALYZERS` | This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351963) in GitLab 14.8 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/351963) in 15.0. Use `DS_EXCLUDED_ANALYZERS` instead. |
| `DS_EXCLUDED_PATHS` | Exclude files and directories from the scan based on the paths. A comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec`). Parent directories also match patterns. Default: `"spec, test, tests, tmp"`. |
| `DS_IMAGE_SUFFIX` | Suffix added to the image name. If set to `-fips`, `FIPS-enabled` images are used for scan. See [FIPS-enabled images](#fips-enabled-images) for more details. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354796) in GitLab 14.10. |
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
diff --git a/doc/user/ssh.md b/doc/user/ssh.md
index 41f2d294e57..27bb7124afe 100644
--- a/doc/user/ssh.md
+++ b/doc/user/ssh.md
@@ -294,8 +294,6 @@ To use SSH with GitLab, copy your public key to your GitLab account:
- GitLab 13.12 and earlier, the expiration date is informational only. It doesn't prevent
you from using the key. Administrators can view expiration dates and use them for
guidance when [deleting keys](admin_area/credentials_inventory.md#delete-a-users-ssh-key).
- - GitLab 14.0 and later, the expiration date is enforced. Administrators can
- [allow expired keys to be used](admin_area/settings/account_and_limit_settings.md#allow-expired-ssh-keys-to-be-used-deprecated).
- GitLab checks all SSH keys at 02:00 AM UTC every day. It emails an expiration notice for all SSH keys that expire on the current date. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322637) in GitLab 13.11.)
- GitLab checks all SSH keys at 01:00 AM UTC every day. It emails an expiration notice for all SSH keys that are scheduled to expire seven days from now. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322637) in GitLab 13.11.)
1. Select **Add key**.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 8a7333ffed2..65e784fe6d6 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -184,6 +184,7 @@ module API
mount ::API::Ci::Triggers
mount ::API::Ci::Variables
mount ::API::Clusters::Agents
+ mount ::API::Clusters::AgentTokens
mount ::API::CommitStatuses
mount ::API::Commits
mount ::API::ComposerPackages
diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb
index 44f9ebcf6c6..72e36d95dc5 100644
--- a/lib/api/ci/helpers/runner.rb
+++ b/lib/api/ci/helpers/runner.rb
@@ -53,7 +53,7 @@ module API
# https://gitlab.com/gitlab-org/gitlab/-/issues/327703
forbidden! unless job
- forbidden! unless job_token_valid?(job)
+ forbidden! unless job.valid_token?(job_token)
forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete?
forbidden!('Job has been erased!') if job.erased?
@@ -77,6 +77,12 @@ module API
job
end
+ def authenticate_job_via_dependent_job!
+ forbidden! unless current_authenticated_job
+ forbidden! unless current_job
+ forbidden! unless can?(current_authenticated_job.user, :read_build, current_job)
+ end
+
def current_job
id = params[:id]
@@ -91,9 +97,28 @@ module API
end
end
- def job_token_valid?(job)
- token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
- token && job.valid_token?(token)
+ # TODO: Replace this with `#current_authenticated_job from API::Helpers`
+ # after the feature flag `ci_authenticate_running_job_token_for_artifacts`
+ # is removed.
+ #
+ # For the time being, this needs to be overridden because the API
+ # GET api/v4/jobs/:id/artifacts
+ # needs to allow requests using token whose job is not running.
+ #
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83713#note_942368526
+ def current_authenticated_job
+ strong_memoize(:current_authenticated_job) do
+ ::Ci::AuthJobFinder.new(token: job_token).execute
+ end
+ end
+
+ # The token used by runner to authenticate a request.
+ # In most cases, the runner uses the token belonging to the requested job.
+ # However, when requesting for job artifacts, the runner would use
+ # the token that belongs to downstream jobs that depend on the job that owns
+ # the artifacts.
+ def job_token
+ @job_token ||= (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
end
def job_forbidden!(job, reason)
@@ -120,6 +145,10 @@ module API
def get_runner_config_from_request
{ config: attributes_for_keys(%w(gpus), params.dig('info', 'config')) }
end
+
+ def request_using_running_job_token?
+ current_job.present? && current_authenticated_job.present? && current_job != current_authenticated_job
+ end
end
end
end
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index 095f46e6885..72ba39861a9 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -324,9 +324,14 @@ module API
optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts)
end
get '/:id/artifacts', feature_category: :build_artifacts do
- job = authenticate_job!(require_running: false)
+ if Feature.enabled?(:ci_authenticate_running_job_token_for_artifacts, current_job&.project) &&
+ request_using_running_job_token?
+ authenticate_job_via_dependent_job!
+ else
+ authenticate_job!(require_running: false)
+ end
- present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download])
+ present_carrierwave_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download])
end
end
end
diff --git a/lib/api/clusters/agent_tokens.rb b/lib/api/clusters/agent_tokens.rb
new file mode 100644
index 00000000000..1e52790f26b
--- /dev/null
+++ b/lib/api/clusters/agent_tokens.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module API
+ module Clusters
+ class AgentTokens < ::API::Base
+ include PaginationParams
+
+ before { authenticate! }
+
+ feature_category :kubernetes_management
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ params do
+ requires :agent_id, type: Integer, desc: 'The ID of an agent'
+ end
+ resource ':id/cluster_agents/:agent_id' do
+ resource :tokens do
+ desc 'List agent tokens' do
+ detail 'This feature was introduced in GitLab 15.0.'
+ success Entities::Clusters::AgentTokenBasic
+ end
+ params do
+ use :pagination
+ end
+ get do
+ authorize! :read_cluster, user_project
+
+ agent = user_project.cluster_agents.find(params[:agent_id])
+
+ present paginate(agent.agent_tokens), with: Entities::Clusters::AgentTokenBasic
+ end
+
+ desc 'Get a single agent token' do
+ detail 'This feature was introduced in GitLab 15.0.'
+ success Entities::Clusters::AgentToken
+ end
+ params do
+ requires :token_id, type: Integer, desc: 'The ID of the agent token'
+ end
+ get ':token_id' do
+ authorize! :read_cluster, user_project
+
+ agent = user_project.cluster_agents.find(params[:agent_id])
+ token = agent.agent_tokens.find(params[:token_id])
+
+ present token, with: Entities::Clusters::AgentToken
+ end
+
+ desc 'Create an agent token' do
+ detail 'This feature was introduced in GitLab 15.0.'
+ success Entities::Clusters::AgentTokenWithToken
+ end
+ params do
+ requires :name, type: String, desc: 'The name for the token'
+ optional :description, type: String, desc: 'The description for the token'
+ end
+ post do
+ authorize! :create_cluster, user_project
+
+ token_params = declared_params(include_missing: false)
+
+ agent = user_project.cluster_agents.find(params[:agent_id])
+
+ result = ::Clusters::AgentTokens::CreateService.new(
+ container: agent.project, current_user: current_user, params: token_params.merge(agent_id: agent.id)
+ ).execute
+
+ bad_request!(result[:message]) if result[:status] == :error
+
+ present result[:token], with: Entities::Clusters::AgentTokenWithToken
+ end
+
+ desc 'Revoke an agent token' do
+ detail 'This feature was introduced in GitLab 15.0.'
+ end
+ params do
+ requires :token_id, type: Integer, desc: 'The ID of the agent token'
+ end
+ delete ':token_id' do
+ authorize! :admin_cluster, user_project
+
+ agent = user_project.cluster_agents.find(params[:agent_id])
+ token = agent.agent_tokens.find(params[:token_id])
+
+ # Skipping explicit error handling and relying on exceptions
+ token.revoked!
+
+ status :no_content
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/dependency.rb b/lib/api/entities/ci/job_request/dependency.rb
index 2672a4a245b..791bfba1079 100644
--- a/lib/api/entities/ci/job_request/dependency.rb
+++ b/lib/api/entities/ci/job_request/dependency.rb
@@ -5,7 +5,16 @@ module API
module Ci
module JobRequest
class Dependency < Grape::Entity
- expose :id, :name, :token
+ expose :id, :name
+
+ expose :token do |job, options|
+ if ::Feature.enabled?(:ci_expose_running_job_token_for_artifacts, job.project)
+ options[:running_job]&.token
+ else
+ job.token
+ end
+ end
+
expose :artifacts_file, using: Entities::Ci::JobArtifactFile, if: ->(job, _) { job.available_artifacts? }
end
end
diff --git a/lib/api/entities/ci/job_request/response.rb b/lib/api/entities/ci/job_request/response.rb
index 86c945cb236..9de415ebacb 100644
--- a/lib/api/entities/ci/job_request/response.rb
+++ b/lib/api/entities/ci/job_request/response.rb
@@ -28,8 +28,10 @@ module API
expose :artifacts, using: Entities::Ci::JobRequest::Artifacts
expose :cache, using: Entities::Ci::JobRequest::Cache
expose :credentials, using: Entities::Ci::JobRequest::Credentials
- expose :all_dependencies, as: :dependencies, using: Entities::Ci::JobRequest::Dependency
expose :features
+ expose :dependencies do |job, options|
+ Entities::Ci::JobRequest::Dependency.represent(job.all_dependencies, options.merge(running_job: job))
+ end
end
end
end
diff --git a/lib/api/entities/clusters/agent_token.rb b/lib/api/entities/clusters/agent_token.rb
new file mode 100644
index 00000000000..e8cc1009361
--- /dev/null
+++ b/lib/api/entities/clusters/agent_token.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Clusters
+ class AgentToken < AgentTokenBasic
+ expose :last_used_at
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/clusters/agent_token_basic.rb b/lib/api/entities/clusters/agent_token_basic.rb
new file mode 100644
index 00000000000..793ec8188b7
--- /dev/null
+++ b/lib/api/entities/clusters/agent_token_basic.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Clusters
+ class AgentTokenBasic < Grape::Entity
+ expose :id
+ expose :name
+ expose :description
+ expose :agent_id
+ expose :status
+ expose :created_at
+ expose :created_by_user_id
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/clusters/agent_token_with_token.rb b/lib/api/entities/clusters/agent_token_with_token.rb
new file mode 100644
index 00000000000..8b84c80795f
--- /dev/null
+++ b/lib/api/entities/clusters/agent_token_with_token.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Clusters
+ class AgentTokenWithToken < AgentToken
+ expose :token
+ end
+ end
+ end
+end
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
index e7d34c75b05..d610b5e4f95 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -66,9 +66,13 @@ module API
if export_strategy&.invalid?
render_validation_error!(export_strategy)
else
- user_project.add_export_job(current_user: current_user,
- after_export_strategy: export_strategy,
- params: project_export_params)
+ begin
+ user_project.add_export_job(current_user: current_user,
+ after_export_strategy: export_strategy,
+ params: project_export_params)
+ rescue Project::ExportLimitExceeded => e
+ render_api_error!(e.message, 400)
+ end
end
accepted!
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 774ab472f2d..62f4aeb038a 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -95,6 +95,7 @@ module API
optional :invisible_captcha_enabled, type: Boolean, desc: 'Enable Invisible Captcha spam detection during signup.'
optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts"
optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
+ optional :max_export_size, type: Integer, desc: 'Maximum export size in MB'
optional :max_import_size, type: Integer, desc: 'Maximum import size in MB'
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
optional :metrics_method_call_threshold, type: Integer, desc: 'A method call is only tracked when it takes longer to complete than the given amount of milliseconds.'
diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb
index c5f0a9ca91e..6e81a578d4a 100644
--- a/lib/api/usage_data.rb
+++ b/lib/api/usage_data.rb
@@ -40,7 +40,7 @@ module API
desc 'Get a list of all metric definitions' do
detail 'This feature was introduced in GitLab 13.11.'
end
- get 'metric_definitions' do
+ get 'metric_definitions', urgency: :low do
content_type 'application/yaml'
env['api.format'] = :binary
diff --git a/lib/api/usage_data_non_sql_metrics.rb b/lib/api/usage_data_non_sql_metrics.rb
index c764a942f5f..41f369a43b8 100644
--- a/lib/api/usage_data_non_sql_metrics.rb
+++ b/lib/api/usage_data_non_sql_metrics.rb
@@ -5,6 +5,7 @@ module API
before { authenticated_as_admin! }
feature_category :service_ping
+ urgency :low
namespace 'usage_data' do
before do
diff --git a/lib/banzai/filter/references/abstract_reference_filter.rb b/lib/banzai/filter/references/abstract_reference_filter.rb
index a34519799d5..521fd7bf4cc 100644
--- a/lib/banzai/filter/references/abstract_reference_filter.rb
+++ b/lib/banzai/filter/references/abstract_reference_filter.rb
@@ -206,6 +206,7 @@ module Banzai
link_content: !!link_content,
link_reference: link_reference)
data_attributes[:reference_format] = matches[:format] if matches.names.include?("format")
+ data_attributes.merge!(additional_object_attributes(object))
data = data_attribute(data_attributes)
@@ -294,6 +295,10 @@ module Banzai
placeholder_data[Regexp.last_match(1).to_i]
end
end
+
+ def additional_object_attributes(object)
+ {}
+ end
end
end
end
diff --git a/lib/banzai/filter/references/issue_reference_filter.rb b/lib/banzai/filter/references/issue_reference_filter.rb
index 1053501de7b..337075b7ff8 100644
--- a/lib/banzai/filter/references/issue_reference_filter.rb
+++ b/lib/banzai/filter/references/issue_reference_filter.rb
@@ -31,6 +31,10 @@ module Banzai
private
+ def additional_object_attributes(issue)
+ { issue_type: issue.issue_type }
+ end
+
def issue_path(issue, project)
Gitlab::Routing.url_helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue.iid)
end
diff --git a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
index d41182ec9be..5b3baebd6fb 100644
--- a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
@@ -12,7 +12,6 @@ variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
- DS_DEFAULT_ANALYZERS: "bundler-audit, retire.js, gemnasium, gemnasium-maven, gemnasium-python"
DS_EXCLUDED_ANALYZERS: ""
DS_EXCLUDED_PATHS: "spec, test, tests, tmp"
DS_MAJOR_VERSION: 2
@@ -65,8 +64,7 @@ gemnasium-dependency_scanning:
- if: $DS_EXCLUDED_ANALYZERS =~ /gemnasium([^-]|$)/
when: never
- if: $CI_COMMIT_BRANCH &&
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
- $DS_DEFAULT_ANALYZERS =~ /gemnasium([^-]|$)/
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/
exists:
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
- '{composer.lock,*/composer.lock,*/*/composer.lock}'
@@ -93,8 +91,7 @@ gemnasium-maven-dependency_scanning:
- if: $DS_EXCLUDED_ANALYZERS =~ /gemnasium-maven/
when: never
- if: $CI_COMMIT_BRANCH &&
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
- $DS_DEFAULT_ANALYZERS =~ /gemnasium-maven/
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/
exists:
- '{build.gradle,*/build.gradle,*/*/build.gradle}'
- '{build.gradle.kts,*/build.gradle.kts,*/*/build.gradle.kts}'
@@ -116,8 +113,7 @@ gemnasium-python-dependency_scanning:
- if: $DS_EXCLUDED_ANALYZERS =~ /gemnasium-python/
when: never
- if: $CI_COMMIT_BRANCH &&
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
- $DS_DEFAULT_ANALYZERS =~ /gemnasium-python/
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/
exists:
- '{requirements.txt,*/requirements.txt,*/*/requirements.txt}'
- '{requirements.pip,*/requirements.pip,*/*/requirements.pip}'
@@ -128,7 +124,6 @@ gemnasium-python-dependency_scanning:
# See https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#configuring-specific-analyzers-used-by-dependency-scanning
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
- $DS_DEFAULT_ANALYZERS =~ /gemnasium-python/ &&
$PIP_REQUIREMENTS_FILE
bundler-audit-dependency_scanning:
@@ -141,8 +136,7 @@ bundler-audit-dependency_scanning:
- if: $DS_EXCLUDED_ANALYZERS =~ /bundler-audit/
when: never
- if: $CI_COMMIT_BRANCH &&
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
- $DS_DEFAULT_ANALYZERS =~ /bundler-audit/
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/
exists:
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
@@ -156,7 +150,6 @@ retire-js-dependency_scanning:
- if: $DS_EXCLUDED_ANALYZERS =~ /retire.js/
when: never
- if: $CI_COMMIT_BRANCH &&
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
- $DS_DEFAULT_ANALYZERS =~ /retire.js/
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/
exists:
- '{package.json,*/package.json,*/*/package.json}'
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index f98fb66ad21..cba63b3c6c7 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -177,8 +177,10 @@ module Gitlab
def check_valid_actor!
return unless key?
- unless actor.valid?
+ if !actor.valid?
raise ForbiddenError, "Your SSH key #{actor.errors[:key].first}."
+ elsif actor.expired?
+ raise ForbiddenError, "Your SSH key has expired."
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 48675b765a6..0c54855ebf7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -454,7 +454,7 @@ msgstr ""
msgid "%{authorsName}'s thread"
msgstr ""
-msgid "%{author} requested to merge %{span_start}%{source_branch} %{copy_button}%{span_end} into %{target_branch} %{created_at}"
+msgid "%{author} requested to merge %{source_branch} %{copy_button} into %{target_branch} %{created_at}"
msgstr ""
msgid "%{board_target} not found"
@@ -13960,9 +13960,6 @@ msgstr ""
msgid "Ends: %{endsAt}"
msgstr ""
-msgid "Enforce SSH key expiration"
-msgstr ""
-
msgid "Enforce two-factor authentication"
msgstr ""
@@ -23312,6 +23309,12 @@ msgstr ""
msgid "Maximum duration of a session."
msgstr ""
+msgid "Maximum export size"
+msgstr ""
+
+msgid "Maximum export size (MB)"
+msgstr ""
+
msgid "Maximum field length"
msgstr ""
@@ -23432,6 +23435,9 @@ msgstr ""
msgid "Maximum size of Elasticsearch bulk indexing requests."
msgstr ""
+msgid "Maximum size of export files."
+msgstr ""
+
msgid "Maximum size of import files."
msgstr ""
@@ -28807,9 +28813,6 @@ msgstr ""
msgid "Profiles|Expiration date"
msgstr ""
-msgid "Profiles|Expired key is not valid."
-msgstr ""
-
msgid "Profiles|Expired:"
msgstr ""
@@ -28837,9 +28840,6 @@ msgstr ""
msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
msgstr ""
-msgid "Profiles|Invalid key."
-msgstr ""
-
msgid "Profiles|Invalid password"
msgstr ""
@@ -28858,15 +28858,9 @@ msgstr ""
msgid "Profiles|Key becomes invalid on this date. Maximum lifetime for SSH keys is %{max_ssh_key_lifetime} days"
msgstr ""
-msgid "Profiles|Key can still be used after expiration."
-msgstr ""
-
msgid "Profiles|Key titles are publicly visible."
msgstr ""
-msgid "Profiles|Key usable beyond expiration date."
-msgstr ""
-
msgid "Profiles|Last used:"
msgstr ""
@@ -31196,10 +31190,10 @@ msgstr ""
msgid "Release|Something went wrong while getting the release details."
msgstr ""
-msgid "Release|Something went wrong while getting the tag notes."
+msgid "Release|Something went wrong while saving the release details."
msgstr ""
-msgid "Release|Something went wrong while saving the release details."
+msgid "Release|Unable to fetch the tag notes."
msgstr ""
msgid "Release|You can edit the content later by editing the release. %{linkStart}How do I edit a release?%{linkEnd}"
@@ -34256,6 +34250,9 @@ msgstr ""
msgid "Selected projects"
msgstr ""
+msgid "Selected tag is already in use. Choose another option."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By %{link_open}@johnsmith%{link_close}\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
@@ -34553,6 +34550,9 @@ msgstr ""
msgid "Set time estimate to %{time_estimate}."
msgstr ""
+msgid "Set to 0 for no size limit."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -36815,7 +36815,7 @@ msgstr ""
msgid "Tag name"
msgstr ""
-msgid "Tag name is required"
+msgid "Tag name is required."
msgstr ""
msgid "Tag push"
@@ -37746,6 +37746,9 @@ msgstr ""
msgid "The project is still being deleted. Please try again later."
msgstr ""
+msgid "The project size exceeds the export limit."
+msgstr ""
+
msgid "The project was successfully forked."
msgstr ""
diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb
index b8cf6743746..998802fe8b7 100644
--- a/qa/qa/support/knapsack_report.rb
+++ b/qa/qa/support/knapsack_report.rb
@@ -32,6 +32,8 @@ module QA
# @return [void]
def download_report
logger.debug("Downloading latest knapsack report for '#{report_name}' to '#{report_path}'")
+ return logger.debug("Report already exists, skipping!") if File.exist?(report_path)
+
file = client.get_object(BUCKET, report_file)
File.write(report_path, file[:body])
rescue StandardError => e
@@ -146,7 +148,7 @@ module QA
#
# @return [String]
def report_name
- @report_name ||= ENV["CI_JOB_NAME"].split(" ").first.tr(":", "-")
+ @report_name ||= ENV["QA_KNAPSACK_REPORT_NAME"] || ENV["CI_JOB_NAME"].split(" ").first.tr(":", "-")
end
# GCS credentials json
diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake
index 61c153291b7..cfc11d0ba24 100644
--- a/qa/tasks/knapsack.rake
+++ b/qa/tasks/knapsack.rake
@@ -18,7 +18,7 @@ namespace :knapsack do
desc "Download latest knapsack report"
task :download do
- QA::Support::KnapsackReport.download
+ QA::Support::KnapsackReport.download_report
end
desc "Merge and upload knapsack report"
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index a69317032ef..537f7aa5fee 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -1454,6 +1454,41 @@ RSpec.describe ProjectsController do
expect(response).to have_gitlab_http_status(:found)
end
+
+ context 'when the project storage_size exceeds the application setting max_export_size' do
+ it 'returns 302 with alert' do
+ stub_application_setting(max_export_size: 1)
+ project.statistics.update!(lfs_objects_size: 2.megabytes, repository_size: 2.megabytes)
+
+ post action, params: { namespace_id: project.namespace, id: project }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(flash[:alert]).to include('The project size exceeds the export limit.')
+ end
+ end
+
+ context 'when the project storage_size does not exceed the application setting max_export_size' do
+ it 'returns 302 without alert' do
+ stub_application_setting(max_export_size: 1)
+ project.statistics.update!(lfs_objects_size: 0.megabytes, repository_size: 0.megabytes)
+
+ post action, params: { namespace_id: project.namespace, id: project }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(flash[:alert]).to be_nil
+ end
+ end
+
+ context 'when application setting max_export_size is not set' do
+ it 'returns 302 without alert' do
+ project.statistics.update!(lfs_objects_size: 2.megabytes, repository_size: 2.megabytes)
+
+ post action, params: { namespace_id: project.namespace, id: project }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(flash[:alert]).to be_nil
+ end
+ end
end
context 'when project export is disabled' do
diff --git a/spec/factories/clusters/agent_tokens.rb b/spec/factories/clusters/agent_tokens.rb
index 03f765123db..3ca6c95d0df 100644
--- a/spec/factories/clusters/agent_tokens.rb
+++ b/spec/factories/clusters/agent_tokens.rb
@@ -3,6 +3,7 @@
FactoryBot.define do
factory :cluster_agent_token, class: 'Clusters::AgentToken' do
association :agent, factory: :cluster_agent
+ association :created_by_user, factory: :user
token_encrypted { Gitlab::CryptoHelper.aes256_gcm_encrypt(SecureRandom.hex(50)) }
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index 6b800e3d790..a7478ce2657 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -5,6 +5,16 @@ FactoryBot.define do
title
key { SSHData::PrivateKey::RSA.generate(1024, unsafe_allow_small_key: true).public_key.openssh(comment: 'dummy@gitlab.com') }
+ trait :expired do
+ to_create { |key| key.save!(validate: false) }
+ expires_at { 2.days.ago }
+ end
+
+ trait :expired_today do
+ to_create { |key| key.save!(validate: false) }
+ expires_at { Date.today.beginning_of_day + 3.hours }
+ end
+
factory :key_without_comment do
key { SSHData::PrivateKey::RSA.generate(1024, unsafe_allow_small_key: true).public_key.openssh }
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 32ae9c00c9e..daccd3a3925 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -111,6 +111,16 @@ RSpec.describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
+ it 'change Maximum export size' do
+ page.within('.as-account-limit') do
+ fill_in 'Maximum export size (MB)', with: 25
+ click_button 'Save changes'
+ end
+
+ expect(current_settings.max_export_size).to eq 25
+ expect(page).to have_content "Application settings saved successfully"
+ end
+
it 'change Maximum import size' do
page.within('.as-account-limit') do
fill_in 'Maximum import size (MB)', with: 15
diff --git a/spec/fixtures/api/schemas/public_api/v4/agent_token.json b/spec/fixtures/api/schemas/public_api/v4/agent_token.json
new file mode 100644
index 00000000000..8251ddd4de1
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/agent_token.json
@@ -0,0 +1,24 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "description",
+ "agent_id",
+ "status",
+ "created_at",
+ "created_by_user_id",
+ "last_used_at"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "agent_id": { "type": "integer" },
+ "status": { "type": "string" },
+ "created_at": { "type": "string", "format": "date-time" },
+ "created_by_user_id": { "type": "integer" },
+ "last_used_at": { "type": ["string", "null"], "format": "date-time" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/agent_token_basic.json b/spec/fixtures/api/schemas/public_api/v4/agent_token_basic.json
new file mode 100644
index 00000000000..90d9f35d0e1
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/agent_token_basic.json
@@ -0,0 +1,22 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "description",
+ "agent_id",
+ "status",
+ "created_at",
+ "created_by_user_id"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "agent_id": { "type": "integer" },
+ "status": { "type": "string" },
+ "created_at": { "type": "string", "format": "date-time" },
+ "created_by_user_id": { "type": "integer" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/agent_token_with_token.json b/spec/fixtures/api/schemas/public_api/v4/agent_token_with_token.json
new file mode 100644
index 00000000000..99d80817d0f
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/agent_token_with_token.json
@@ -0,0 +1,26 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "description",
+ "agent_id",
+ "status",
+ "created_at",
+ "created_by_user_id",
+ "last_used_at",
+ "token"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "agent_id": { "type": "integer" },
+ "status": { "type": "string" },
+ "created_at": { "type": "string", "format": "date-time" },
+ "created_by_user_id": { "type": "integer" },
+ "last_used_at": { "type": ["string", "null"], "format": "date-time" },
+ "token": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/agent_tokens.json b/spec/fixtures/api/schemas/public_api/v4/agent_tokens.json
new file mode 100644
index 00000000000..f3d14d09b3d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/agent_tokens.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "agent_token_basic.json" }
+}
diff --git a/spec/fixtures/markdown/markdown_golden_master_examples.yml b/spec/fixtures/markdown/markdown_golden_master_examples.yml
index 08b456d276b..5847e9f2cdf 100644
--- a/spec/fixtures/markdown/markdown_golden_master_examples.yml
+++ b/spec/fixtures/markdown/markdown_golden_master_examples.yml
@@ -750,7 +750,7 @@
markdown: |-
Hi @gfm_user - thank you for reporting this bug (#1) we hope to fix it in %1.1 as part of !1
html: |-
- <p data-sourcepos="1:1-1:92" dir="auto">Hi <a href="/gfm_user" data-user="1" data-reference-type="user" data-container="body" data-placement="top" class="gfm gfm-project_member js-user-link" title="John Doe1">@gfm_user</a> - thank you for reporting this bug (<a href="/group1/project1/-/issues/1" data-original="#1" data-link="false" data-link-reference="false" data-project="11" data-issue="11" data-reference-type="issue" data-container="body" data-placement="top" title="My title 1" class="gfm gfm-issue has-tooltip">#1</a>) we hope to fix it in <a href="/group1/project1/-/milestones/1" data-original="%1.1" data-link="false" data-link-reference="false" data-project="11" data-milestone="11" data-reference-type="milestone" data-container="body" data-placement="top" title="" class="gfm gfm-milestone has-tooltip">%1.1</a> as part of <a href="/group1/project1/-/merge_requests/1" data-original="!1" data-link="false" data-link-reference="false" data-project="11" data-merge-request="11" data-project-path="group1/project1" data-iid="1" data-mr-title="My title 2" data-reference-type="merge_request" data-container="body" data-placement="top" title="" class="gfm gfm-merge_request">!1</a></p>
+ <p data-sourcepos="1:1-1:92" dir="auto">Hi <a href="/gfm_user" data-user="1" data-reference-type="user" data-container="body" data-placement="top" class="gfm gfm-project_member js-user-link" title="John Doe1">@gfm_user</a> - thank you for reporting this bug (<a href="/group1/project1/-/issues/1" data-original="#1" data-link="false" data-link-reference="false" data-project="11" data-issue="11" data-issue-type="issue" data-reference-type="issue" data-container="body" data-placement="top" title="My title 1" class="gfm gfm-issue has-tooltip">#1</a>) we hope to fix it in <a href="/group1/project1/-/milestones/1" data-original="%1.1" data-link="false" data-link-reference="false" data-project="11" data-milestone="11" data-reference-type="milestone" data-container="body" data-placement="top" title="" class="gfm gfm-milestone has-tooltip">%1.1</a> as part of <a href="/group1/project1/-/merge_requests/1" data-original="!1" data-link="false" data-link-reference="false" data-project="11" data-merge-request="11" data-project-path="group1/project1" data-iid="1" data-mr-title="My title 2" data-reference-type="merge_request" data-container="body" data-placement="top" title="" class="gfm gfm-merge_request">!1</a></p>
- name: strike
markdown: |-
~~del~~
diff --git a/spec/frontend/environments/environment_folder_spec.js b/spec/frontend/environments/environment_folder_spec.js
index f2027252f05..37b897bf65d 100644
--- a/spec/frontend/environments/environment_folder_spec.js
+++ b/spec/frontend/environments/environment_folder_spec.js
@@ -15,12 +15,13 @@ Vue.use(VueApollo);
describe('~/environments/components/environments_folder.vue', () => {
let wrapper;
let environmentFolderMock;
+ let intervalMock;
let nestedEnvironment;
const findLink = () => wrapper.findByRole('link', { name: s__('Environments|Show all') });
const createApolloProvider = () => {
- const mockResolvers = { Query: { folder: environmentFolderMock } };
+ const mockResolvers = { Query: { folder: environmentFolderMock, interval: intervalMock } };
return createMockApollo([], mockResolvers);
};
@@ -40,6 +41,8 @@ describe('~/environments/components/environments_folder.vue', () => {
environmentFolderMock = jest.fn();
[nestedEnvironment] = resolvedEnvironmentsApp.environments;
environmentFolderMock.mockReturnValue(resolvedFolder);
+ intervalMock = jest.fn();
+ intervalMock.mockReturnValue(2000);
});
afterEach(() => {
@@ -70,6 +73,8 @@ describe('~/environments/components/environments_folder.vue', () => {
beforeEach(() => {
collapse = wrapper.findComponent(GlCollapse);
icons = wrapper.findAllComponents(GlIcon);
+ jest.spyOn(wrapper.vm.$apollo.queries.folder, 'startPolling');
+ jest.spyOn(wrapper.vm.$apollo.queries.folder, 'stopPolling');
});
it('is collapsed by default', () => {
@@ -93,6 +98,8 @@ describe('~/environments/components/environments_folder.vue', () => {
expect(iconNames).toEqual(['angle-down', 'folder-open']);
expect(folderName.classes('gl-font-weight-bold')).toBe(true);
expect(link.attributes('href')).toBe(nestedEnvironment.latest.folderPath);
+
+ expect(wrapper.vm.$apollo.queries.folder.startPolling).toHaveBeenCalledWith(2000);
});
it('displays all environments when opened', async () => {
@@ -106,6 +113,16 @@ describe('~/environments/components/environments_folder.vue', () => {
.wrappers.map((w) => w.text());
expect(environments).toEqual(expect.arrayContaining(names));
});
+
+ it('stops polling on click', async () => {
+ await button.trigger('click');
+ expect(wrapper.vm.$apollo.queries.folder.startPolling).toHaveBeenCalledWith(2000);
+
+ const collapseButton = wrapper.findByRole('button', { name: __('Collapse') });
+ await collapseButton.trigger('click');
+
+ expect(wrapper.vm.$apollo.queries.folder.stopPolling).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/frontend/fixtures/runner.rb b/spec/frontend/fixtures/runner.rb
index 25049ee4722..e17e73a93c4 100644
--- a/spec/frontend/fixtures/runner.rb
+++ b/spec/frontend/fixtures/runner.rb
@@ -67,7 +67,7 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
end
describe GraphQL::Query, type: :request do
- runner_query = 'details/runner.query.graphql'
+ runner_query = 'show/runner.query.graphql'
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{runner_query}")
@@ -91,7 +91,7 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
end
describe GraphQL::Query, type: :request do
- runner_projects_query = 'details/runner_projects.query.graphql'
+ runner_projects_query = 'show/runner_projects.query.graphql'
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{runner_projects_query}")
@@ -107,7 +107,23 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
end
describe GraphQL::Query, type: :request do
- runner_jobs_query = 'details/runner_jobs.query.graphql'
+ runner_jobs_query = 'show/runner_jobs.query.graphql'
+
+ let_it_be(:query) do
+ get_graphql_query_as_string("#{query_path}#{runner_jobs_query}")
+ end
+
+ it "#{fixtures_path}#{runner_jobs_query}.json" do
+ post_graphql(query, current_user: admin, variables: {
+ id: instance_runner.to_global_id.to_s
+ })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ describe GraphQL::Query, type: :request do
+ runner_jobs_query = 'edit/runner_form.query.graphql'
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{runner_jobs_query}")
diff --git a/spec/frontend/releases/components/tag_field_new_spec.js b/spec/frontend/releases/components/tag_field_new_spec.js
index ede2c384233..9f500c318ea 100644
--- a/spec/frontend/releases/components/tag_field_new_spec.js
+++ b/spec/frontend/releases/components/tag_field_new_spec.js
@@ -188,6 +188,18 @@ describe('releases/components/tag_field_new', () => {
await expectValidationMessageToBe('hidden');
});
+
+ it('displays a validation error if the tag has an associated release', async () => {
+ findTagNameDropdown().vm.$emit('input', 'vTest');
+ findTagNameDropdown().vm.$emit('hide');
+
+ store.state.editNew.existingRelease = {};
+
+ await expectValidationMessageToBe('shown');
+ expect(findTagNameFormGroup().text()).toContain(
+ __('Selected tag is already in use. Choose another option.'),
+ );
+ });
});
describe('when the user has interacted with the component and the value is empty', () => {
@@ -196,6 +208,7 @@ describe('releases/components/tag_field_new', () => {
findTagNameDropdown().vm.$emit('hide');
await expectValidationMessageToBe('shown');
+ expect(findTagNameFormGroup().text()).toContain(__('Tag name is required.'));
});
});
});
diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js
index c2e6c2851a8..41653f62ebf 100644
--- a/spec/frontend/releases/stores/modules/detail/actions_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js
@@ -608,7 +608,7 @@ describe('Release edit/new actions', () => {
);
expect(createFlash).toHaveBeenCalledWith({
- message: s__('Release|Something went wrong while getting the tag notes.'),
+ message: s__('Release|Unable to fetch the tag notes.'),
});
expect(getTag).toHaveBeenCalledWith(state.projectId, tagName);
});
diff --git a/spec/frontend/releases/stores/modules/detail/getters_spec.js b/spec/frontend/releases/stores/modules/detail/getters_spec.js
index 3a3289c9cf0..c42c6c00f56 100644
--- a/spec/frontend/releases/stores/modules/detail/getters_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/getters_spec.js
@@ -146,6 +146,8 @@ describe('Release edit/new getters', () => {
],
},
},
+ // tag has an existing release
+ existingRelease: {},
};
actualErrors = getters.validationErrors(state);
@@ -159,6 +161,14 @@ describe('Release edit/new getters', () => {
expect(actualErrors).toMatchObject(expectedErrors);
});
+ it('returns a validation error if the tag has an existing release', () => {
+ const expectedErrors = {
+ existingRelease: true,
+ };
+
+ expect(actualErrors).toMatchObject(expectedErrors);
+ });
+
it('returns a validation error if links share a URL', () => {
const expectedErrors = {
assets: {
diff --git a/spec/frontend/releases/stores/modules/detail/mutations_spec.js b/spec/frontend/releases/stores/modules/detail/mutations_spec.js
index 0c97524b704..85844831e0b 100644
--- a/spec/frontend/releases/stores/modules/detail/mutations_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/mutations_spec.js
@@ -249,9 +249,10 @@ describe('Release edit/new mutations', () => {
state.isFetchingTagNotes = true;
const message = 'tag notes';
- mutations[types.RECEIVE_TAG_NOTES_SUCCESS](state, { message });
+ mutations[types.RECEIVE_TAG_NOTES_SUCCESS](state, { message, release });
expect(state.tagNotes).toBe(message);
expect(state.isFetchingTagNotes).toBe(false);
+ expect(state.existingRelease).toBe(release);
});
});
describe(`${types.RECEIVE_TAG_NOTES_ERROR}`, () => {
diff --git a/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js b/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js
index f40eb6938a8..8a34cb14d8b 100644
--- a/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js
+++ b/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js
@@ -8,16 +8,16 @@ import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/runner/components/runner_header.vue';
import RunnerUpdateForm from '~/runner/components/runner_update_form.vue';
-import runnerQuery from '~/runner/graphql/details/runner.query.graphql';
+import runnerFormQuery from '~/runner/graphql/edit/runner_form.query.graphql';
import AdminRunnerEditApp from '~//runner/admin_runner_edit/admin_runner_edit_app.vue';
import { captureException } from '~/runner/sentry_utils';
-import { runnerData } from '../mock_data';
+import { runnerFormData } from '../mock_data';
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
-const mockRunner = runnerData.data.runner;
+const mockRunner = runnerFormData.data.runner;
const mockRunnerGraphqlId = mockRunner.id;
const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
const mockRunnerPath = `/admin/runners/${mockRunnerId}`;
@@ -33,7 +33,7 @@ describe('AdminRunnerEditApp', () => {
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
wrapper = mountFn(AdminRunnerEditApp, {
- apolloProvider: createMockApollo([[runnerQuery, mockRunnerQuery]]),
+ apolloProvider: createMockApollo([[runnerFormQuery, mockRunnerQuery]]),
propsData: {
runnerId: mockRunnerId,
runnerPath: mockRunnerPath,
@@ -45,7 +45,7 @@ describe('AdminRunnerEditApp', () => {
};
beforeEach(() => {
- mockRunnerQuery = jest.fn().mockResolvedValue(runnerData);
+ mockRunnerQuery = jest.fn().mockResolvedValue(runnerFormData);
});
afterEach(() => {
diff --git a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
index 9ac1e772418..07259ec3538 100644
--- a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
+++ b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
@@ -11,7 +11,7 @@ import RunnerHeader from '~/runner/components/runner_header.vue';
import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
-import runnerQuery from '~/runner/graphql/details/runner.query.graphql';
+import runnerQuery from '~/runner/graphql/show/runner.query.graphql';
import AdminRunnerShowApp from '~/runner/admin_runner_show/admin_runner_show_app.vue';
import { captureException } from '~/runner/sentry_utils';
import { saveAlertToLocalStorage } from '~/runner/local_storage_alert/save_alert_to_local_storage';
diff --git a/spec/frontend/runner/components/runner_jobs_spec.js b/spec/frontend/runner/components/runner_jobs_spec.js
index 9e40e911448..8ac5685a0dd 100644
--- a/spec/frontend/runner/components/runner_jobs_spec.js
+++ b/spec/frontend/runner/components/runner_jobs_spec.js
@@ -11,7 +11,7 @@ import RunnerPagination from '~/runner/components/runner_pagination.vue';
import { captureException } from '~/runner/sentry_utils';
import { I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '~/runner/constants';
-import runnerJobsQuery from '~/runner/graphql/details/runner_jobs.query.graphql';
+import runnerJobsQuery from '~/runner/graphql/show/runner_jobs.query.graphql';
import { runnerData, runnerJobsData } from '../mock_data';
diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js
index 62ebc6539e2..04627e2307b 100644
--- a/spec/frontend/runner/components/runner_projects_spec.js
+++ b/spec/frontend/runner/components/runner_projects_spec.js
@@ -16,7 +16,7 @@ import RunnerAssignedItem from '~/runner/components/runner_assigned_item.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
import { captureException } from '~/runner/sentry_utils';
-import runnerProjectsQuery from '~/runner/graphql/details/runner_projects.query.graphql';
+import runnerProjectsQuery from '~/runner/graphql/show/runner_projects.query.graphql';
import { runnerData, runnerProjectsData } from '../mock_data';
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js
index dbe15f8e9c6..3037364d941 100644
--- a/spec/frontend/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/runner/components/runner_update_form_spec.js
@@ -14,17 +14,17 @@ import {
ACCESS_LEVEL_REF_PROTECTED,
ACCESS_LEVEL_NOT_PROTECTED,
} from '~/runner/constants';
-import runnerUpdateMutation from '~/runner/graphql/details/runner_update.mutation.graphql';
+import runnerUpdateMutation from '~/runner/graphql/edit/runner_update.mutation.graphql';
import { captureException } from '~/runner/sentry_utils';
import { saveAlertToLocalStorage } from '~/runner/local_storage_alert/save_alert_to_local_storage';
-import { runnerData } from '../mock_data';
+import { runnerFormData } from '../mock_data';
jest.mock('~/runner/local_storage_alert/save_alert_to_local_storage');
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility');
-const mockRunner = runnerData.data.runner;
+const mockRunner = runnerFormData.data.runner;
const mockRunnerPath = '/admin/runners/1';
Vue.use(VueApollo);
@@ -127,24 +127,7 @@ describe('RunnerUpdateForm', () => {
await submitFormAndWait();
// Some read-only fields are not submitted
- const {
- __typename,
- shortSha,
- ipAddress,
- executorName,
- architectureName,
- platformName,
- runnerType,
- createdAt,
- status,
- editAdminUrl,
- contactedAt,
- userPermissions,
- version,
- groups,
- jobCount,
- ...submitted
- } = mockRunner;
+ const { __typename, shortSha, runnerType, createdAt, status, ...submitted } = mockRunner;
expectToHaveSubmittedRunnerContaining(submitted);
});
diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js
index fbe8926124c..e4351e9c90c 100644
--- a/spec/frontend/runner/mock_data.js
+++ b/spec/frontend/runner/mock_data.js
@@ -8,11 +8,14 @@ import groupRunnersData from 'test_fixtures/graphql/runner/list/group_runners.qu
import groupRunnersDataPaginated from 'test_fixtures/graphql/runner/list/group_runners.query.graphql.paginated.json';
import groupRunnersCountData from 'test_fixtures/graphql/runner/list/group_runners_count.query.graphql.json';
-// Details queries
-import runnerData from 'test_fixtures/graphql/runner/details/runner.query.graphql.json';
-import runnerWithGroupData from 'test_fixtures/graphql/runner/details/runner.query.graphql.with_group.json';
-import runnerProjectsData from 'test_fixtures/graphql/runner/details/runner_projects.query.graphql.json';
-import runnerJobsData from 'test_fixtures/graphql/runner/details/runner_jobs.query.graphql.json';
+// Show runner queries
+import runnerData from 'test_fixtures/graphql/runner/show/runner.query.graphql.json';
+import runnerWithGroupData from 'test_fixtures/graphql/runner/show/runner.query.graphql.with_group.json';
+import runnerProjectsData from 'test_fixtures/graphql/runner/show/runner_projects.query.graphql.json';
+import runnerJobsData from 'test_fixtures/graphql/runner/show/runner_jobs.query.graphql.json';
+
+// Edit runner queries
+import runnerFormData from 'test_fixtures/graphql/runner/edit/runner_form.query.graphql.json';
// Other mock data
export const onlineContactTimeoutSecs = 2 * 60 * 60;
@@ -20,13 +23,14 @@ export const staleTimeoutSecs = 5259492; // Ruby's `2.months`
export {
runnersData,
- runnersCountData,
runnersDataPaginated,
+ runnersCountData,
+ groupRunnersData,
+ groupRunnersDataPaginated,
+ groupRunnersCountData,
runnerData,
runnerWithGroupData,
runnerProjectsData,
runnerJobsData,
- groupRunnersData,
- groupRunnersCountData,
- groupRunnersDataPaginated,
+ runnerFormData,
};
diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb
index c3a3c2a0178..399726263db 100644
--- a/spec/helpers/profiles_helper_spec.rb
+++ b/spec/helpers/profiles_helper_spec.rb
@@ -111,7 +111,6 @@ RSpec.describe ProfilesHelper do
where(:error, :expired, :result) do
false | false | nil
true | false | error_message
- false | true | 'Key usable beyond expiration date.'
true | true | error_message
end
@@ -130,13 +129,9 @@ RSpec.describe ProfilesHelper do
end
describe "#ssh_key_expires_field_description" do
- before do
- allow(Key).to receive(:enforce_ssh_key_expiration_feature_available?).and_return(false)
- end
+ subject { helper.ssh_key_expires_field_description }
- it 'returns the description' do
- expect(helper.ssh_key_expires_field_description).to eq('Key can still be used after expiration.')
- end
+ it { is_expected.to eq('Key becomes invalid on this date.') }
end
describe '#middle_dot_divider_classes' do
diff --git a/spec/lib/api/entities/ci/job_request/dependency_spec.rb b/spec/lib/api/entities/ci/job_request/dependency_spec.rb
index fa5f3da554c..b45885e9982 100644
--- a/spec/lib/api/entities/ci/job_request/dependency_spec.rb
+++ b/spec/lib/api/entities/ci/job_request/dependency_spec.rb
@@ -3,8 +3,9 @@
require 'spec_helper'
RSpec.describe API::Entities::Ci::JobRequest::Dependency do
+ let(:running_job) { create(:ci_build, :artifacts) }
let(:job) { create(:ci_build, :artifacts) }
- let(:entity) { described_class.new(job) }
+ let(:entity) { described_class.new(job, { running_job: running_job }) }
subject { entity.as_json }
@@ -16,8 +17,18 @@ RSpec.describe API::Entities::Ci::JobRequest::Dependency do
expect(subject[:name]).to eq(job.name)
end
- it 'returns the dependency token' do
- expect(subject[:token]).to eq(job.token)
+ it 'returns the token belonging to the running job' do
+ expect(subject[:token]).to eq(running_job.token)
+ end
+
+ context 'when ci_expose_running_job_token_for_artifacts is disabled' do
+ before do
+ stub_feature_flags(ci_expose_running_job_token_for_artifacts: false)
+ end
+
+ it 'returns the token belonging to the dependency job' do
+ expect(subject[:token]).to eq(job.token)
+ end
end
it 'returns the dependency artifacts_file', :aggregate_failures do
diff --git a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
index c493cb77c98..c6f0e592cdf 100644
--- a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
@@ -15,6 +15,14 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
let(:issue_path) { "/#{issue.project.namespace.path}/#{issue.project.path}/-/issues/#{issue.iid}" }
let(:issue_url) { "http://#{Gitlab.config.gitlab.host}#{issue_path}" }
+ shared_examples 'a reference with issue type information' do
+ it 'contains issue-type as a data attribute' do
+ doc = reference_filter("Fixed #{reference}")
+
+ expect(doc.css('a').first.attr('data-issue-type')).to eq('issue')
+ end
+ end
+
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
@@ -44,6 +52,8 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'a reference with issue type information'
+
it 'links to a valid reference' do
doc = reference_filter("Fixed #{reference}")
@@ -158,6 +168,8 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'a reference with issue type information'
+
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object)
.with(project2, issue.iid)
@@ -208,6 +220,8 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'a reference with issue type information'
+
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object)
.with(project2, issue.iid)
@@ -258,6 +272,8 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'a reference with issue type information'
+
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object)
.with(project2, issue.iid)
@@ -307,6 +323,8 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'a reference with issue type information'
+
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
@@ -342,6 +360,8 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'a reference with issue type information'
+
it 'links to a valid reference' do
doc = reference_filter("See #{reference_link}")
@@ -371,6 +391,8 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
it_behaves_like 'a reference containing an element node'
+ it_behaves_like 'a reference with issue type information'
+
it 'links to a valid reference' do
doc = reference_filter("See #{reference_link}")
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index d6ef1836ad9..e628a06a542 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -228,6 +228,15 @@ RSpec.describe Gitlab::GitAccess do
project.add_maintainer(user)
end
+ context 'key is expired' do
+ let(:actor) { create(:rsa_key_2048, :expired) }
+
+ it 'does not allow expired keys', :aggregate_failures do
+ expect { pull_access_check }.to raise_forbidden('Your SSH key has expired.')
+ expect { push_access_check }.to raise_forbidden('Your SSH key has expired.')
+ end
+ end
+
context 'key is too small' do
before do
stub_application_setting(rsa_key_restriction: 4096)
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 541fa1ac77a..9f0f056d14e 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -417,6 +417,14 @@ RSpec.describe ApplicationSetting do
.is_greater_than(0)
end
+ it { is_expected.to validate_presence_of(:max_export_size) }
+
+ specify do
+ is_expected.to validate_numericality_of(:max_export_size)
+ .only_integer
+ .is_greater_than_or_equal_to(0)
+ end
+
it { is_expected.to validate_presence_of(:max_import_size) }
specify do
diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb
index 3af717798c3..4ff743991a6 100644
--- a/spec/models/instance_configuration_spec.rb
+++ b/spec/models/instance_configuration_spec.rb
@@ -99,6 +99,7 @@ RSpec.describe InstanceConfiguration do
max_attachment_size: 10,
receive_max_input_size: 20,
max_import_size: 30,
+ max_export_size: 40,
diff_max_patch_bytes: 409600,
max_artifacts_size: 50,
max_pages_size: 60,
@@ -112,6 +113,7 @@ RSpec.describe InstanceConfiguration do
expect(size_limits[:max_attachment_size]).to eq(10.megabytes)
expect(size_limits[:receive_max_input_size]).to eq(20.megabytes)
expect(size_limits[:max_import_size]).to eq(30.megabytes)
+ expect(size_limits[:max_export_size]).to eq(40.megabytes)
expect(size_limits[:diff_max_patch_bytes]).to eq(400.kilobytes)
expect(size_limits[:max_artifacts_size]).to eq(50.megabytes)
expect(size_limits[:max_pages_size]).to eq(60.megabytes)
@@ -127,11 +129,16 @@ RSpec.describe InstanceConfiguration do
end
it 'returns nil if set to 0 (unlimited)' do
- Gitlab::CurrentSettings.current_application_settings.update!(max_import_size: 0, max_pages_size: 0)
+ Gitlab::CurrentSettings.current_application_settings.update!(
+ max_import_size: 0,
+ max_export_size: 0,
+ max_pages_size: 0
+ )
size_limits = subject.settings[:size_limits]
expect(size_limits[:max_import_size]).to be_nil
+ expect(size_limits[:max_export_size]).to be_nil
expect(size_limits[:max_pages_size]).to be_nil
end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 6d01b6ae256..225c9714187 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -102,15 +102,15 @@ RSpec.describe Key, :mailer do
context 'expiration scopes' do
let_it_be(:user) { create(:user) }
- let_it_be(:expired_today_not_notified) { create(:key, expires_at: Time.current, user: user) }
- let_it_be(:expired_today_already_notified) { create(:key, expires_at: Time.current, user: user, expiry_notification_delivered_at: Time.current) }
- let_it_be(:expired_yesterday) { create(:key, expires_at: 1.day.ago, user: user) }
+ let_it_be(:expired_today_not_notified) { create(:key, :expired_today, user: user) }
+ let_it_be(:expired_today_already_notified) { create(:key, :expired_today, user: user, expiry_notification_delivered_at: Time.current) }
+ let_it_be(:expired_yesterday) { create(:key, :expired, user: user) }
let_it_be(:expiring_soon_unotified) { create(:key, expires_at: 3.days.from_now, user: user) }
let_it_be(:expiring_soon_notified) { create(:key, expires_at: 4.days.from_now, user: user, before_expiry_notification_delivered_at: Time.current) }
let_it_be(:future_expiry) { create(:key, expires_at: 1.month.from_now, user: user) }
describe '.expired_today_and_not_notified' do
- it 'returns keys that expire today and in the past' do
+ it 'returns keys that expire today and have not been notified' do
expect(described_class.expired_today_and_not_notified).to contain_exactly(expired_today_not_notified)
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d315b62b23e..1431a159f1d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -7158,11 +7158,33 @@ RSpec.describe Project, factory_default: :keep do
end
describe '#add_export_job' do
- context 'if not already present' do
- it 'starts project export job' do
- user = create(:user)
- project = build(:project)
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ context 'when project storage_size does not exceed the application setting max_export_size' do
+ it 'starts project export worker' do
+ stub_application_setting(max_export_size: 1)
+ allow(project.statistics).to receive(:storage_size).and_return(0.megabytes)
+
+ expect(ProjectExportWorker).to receive(:perform_async).with(user.id, project.id, nil, {})
+
+ project.add_export_job(current_user: user)
+ end
+ end
+
+ context 'when project storage_size exceeds the application setting max_export_size' do
+ it 'raises Project::ExportLimitExceeded' do
+ stub_application_setting(max_export_size: 1)
+ allow(project.statistics).to receive(:storage_size).and_return(2.megabytes)
+
+ expect(ProjectExportWorker).not_to receive(:perform_async).with(user.id, project.id, nil, {})
+ expect { project.add_export_job(current_user: user) }.to raise_error(Project::ExportLimitExceeded)
+ end
+ end
+ context 'when application setting max_export_size is not set' do
+ it 'starts project export worker' do
+ allow(project.statistics).to receive(:storage_size).and_return(2.megabytes)
expect(ProjectExportWorker).to receive(:perform_async).with(user.id, project.id, nil, {})
project.add_export_job(current_user: user)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 05dcabd96ba..71171f98492 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1048,8 +1048,8 @@ RSpec.describe User do
context 'SSH key expiration scopes' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
- let_it_be(:expired_today_not_notified) { create(:key, expires_at: Time.current, user: user1) }
- let_it_be(:expired_today_already_notified) { create(:key, expires_at: Time.current, user: user2, expiry_notification_delivered_at: Time.current) }
+ let_it_be(:expired_today_not_notified) { create(:key, :expired_today, user: user1) }
+ let_it_be(:expired_today_already_notified) { create(:key, :expired_today, user: user2, expiry_notification_delivered_at: Time.current) }
let_it_be(:expiring_soon_not_notified) { create(:key, expires_at: 2.days.from_now, user: user2) }
let_it_be(:expiring_soon_notified) { create(:key, expires_at: 2.days.from_now, user: user1, before_expiry_notification_delivered_at: Time.current) }
diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
index f627f207d98..dd9e2c32925 100644
--- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
@@ -7,8 +7,20 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include RedisHelpers
include WorkhorseHelpers
+ let_it_be_with_reload(:parent_group) { create(:group) }
+ let_it_be_with_reload(:group) { create(:group, parent: parent_group) }
+ let_it_be_with_reload(:project) { create(:project, namespace: group, shared_runners_enabled: false) }
+
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
+ let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let_it_be(:user) { create(:user) }
+
let(:registration_token) { 'abcdefg123456' }
+ before_all do
+ project.add_developer(user)
+ end
+
before do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
@@ -17,12 +29,6 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
describe '/api/v4/jobs' do
- let(:parent_group) { create(:group) }
- let(:group) { create(:group, parent: parent_group) }
- let(:project) { create(:project, namespace: group, shared_runners_enabled: false) }
- let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
- let(:runner) { create(:ci_runner, :project, projects: [project]) }
- let(:user) { create(:user) }
let(:job) do
create(:ci_build, :artifacts, :extended_options,
pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
@@ -817,25 +823,23 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
context 'when job has artifacts' do
- let(:job) { create(:ci_build) }
+ let(:job) { create(:ci_build, pipeline: pipeline, user: user) }
let(:store) { JobArtifactUploader::Store::LOCAL }
before do
create(:ci_job_artifact, :archive, file_store: store, job: job)
end
- context 'when using job token' do
+ shared_examples 'successful artifact download' do
context 'when artifacts are stored locally' do
let(:download_headers) do
{ 'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) }
end
- before do
+ it 'downloads artifacts' do
download_artifact
- end
- it 'download artifacts' do
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers.to_h).to include download_headers
end
@@ -843,26 +847,20 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
context 'when artifacts are stored remotely' do
let(:store) { JobArtifactUploader::Store::REMOTE }
- let!(:job) { create(:ci_build) }
context 'when proxy download is being used' do
- before do
+ it 'uses workhorse send-url' do
download_artifact(direct_download: false)
- end
- it 'uses workhorse send-url' do
expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers.to_h).to include(
- 'Gitlab-Workhorse-Send-Data' => /send-url:/)
+ expect(response.headers.to_h).to include('Gitlab-Workhorse-Send-Data' => /send-url:/)
end
end
context 'when direct download is being used' do
- before do
+ it 'receives redirect for downloading artifacts' do
download_artifact(direct_download: true)
- end
- it 'receive redirect for downloading artifacts' do
expect(response).to have_gitlab_http_status(:found)
expect(response.headers).to include('Location')
end
@@ -870,16 +868,151 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when using runnners token' do
- let(:token) { job.project.runners_token }
+ shared_examples 'forbidden request' do
+ it 'responds with forbidden' do
+ download_artifact
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when using job token' do
+ let(:token) { job.token }
+
+ it_behaves_like 'successful artifact download'
+
+ context 'when the job is no longer running' do
+ before do
+ job.success!
+ end
+
+ it_behaves_like 'successful artifact download'
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_authenticate_running_job_token_for_artifacts: false)
+ end
+
+ it_behaves_like 'successful artifact download'
+
+ context 'when the job is no longer running' do
+ before do
+ job.success!
+ end
+
+ it_behaves_like 'successful artifact download'
+ end
+ end
+ end
+
+ context 'when using token belonging to the dependent job' do
+ let!(:dependent_job) { create(:ci_build, :running, :dependent, user: user, pipeline: pipeline) }
+ let!(:job) { dependent_job.all_dependencies.first }
+
+ let(:token) { dependent_job.token }
+
+ it_behaves_like 'successful artifact download'
+
+ context 'when the dependent job is no longer running' do
+ before do
+ dependent_job.success!
+ end
+
+ it_behaves_like 'forbidden request'
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_authenticate_running_job_token_for_artifacts: false)
+ end
+
+ it_behaves_like 'forbidden request'
+ end
+ end
+
+ context 'when using token belonging to another job created by another project member' do
+ let!(:ci_build) { create(:ci_build, :running, :dependent, user: user, pipeline: pipeline) }
+ let!(:job) { ci_build.all_dependencies.first }
+
+ let!(:another_dev) { create(:user) }
+
+ let(:token) { ci_build.token }
before do
- download_artifact
+ project.add_developer(another_dev)
+ ci_build.update!(user: another_dev)
end
- it 'responds with forbidden' do
- expect(response).to have_gitlab_http_status(:forbidden)
+ it_behaves_like 'successful artifact download'
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_authenticate_running_job_token_for_artifacts: false)
+ end
+
+ it_behaves_like 'forbidden request'
+ end
+ end
+
+ context 'when using token belonging to a pending dependent job' do
+ let!(:ci_build) { create(:ci_build, :pending, :dependent, user: user, project: project, pipeline: pipeline) }
+ let!(:job) { ci_build.all_dependencies.first }
+
+ let(:token) { ci_build.token }
+
+ it_behaves_like 'forbidden request'
+ end
+
+ context 'when using a token from a cross pipeline build' do
+ let!(:ci_build) { create(:ci_build, :pending, :dependent, user: user, project: project, pipeline: pipeline) }
+ let!(:job) { ci_build.all_dependencies.first }
+
+ let!(:options) do
+ {
+ cross_dependencies: [
+ {
+ pipeline: pipeline.id,
+ job: job.name,
+ artifacts: true
+ }
+ ]
+
+ }
end
+
+ let!(:cross_pipeline) { create(:ci_pipeline, project: project, child_of: pipeline) }
+ let!(:cross_pipeline_build) { create(:ci_build, :running, project: project, user: user, options: options, pipeline: cross_pipeline) }
+
+ let(:token) { cross_pipeline_build.token }
+
+ before do
+ job.success!
+ end
+
+ it_behaves_like 'successful artifact download'
+ end
+
+ context 'when using a token from an unrelated project' do
+ let!(:ci_build) { create(:ci_build, :running, :dependent, user: user, project: project, pipeline: pipeline) }
+ let!(:job) { ci_build.all_dependencies.first }
+
+ let!(:unrelated_ci_build) { create(:ci_build, :running, user: create(:user)) }
+ let(:token) { unrelated_ci_build.token }
+
+ it_behaves_like 'forbidden request'
+ end
+
+ context 'when using runnners token' do
+ let(:token) { job.project.runners_token }
+
+ it_behaves_like 'forbidden request'
+ end
+
+ context 'when using an invalid token' do
+ let(:token) { 'invalid-token' }
+
+ it_behaves_like 'forbidden request'
end
end
diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
index a662c77e5a2..b54ad811e6e 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -496,15 +496,32 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
job2.success
end
- it 'returns dependent jobs' do
+ it 'returns dependent jobs with the token of the test job' do
request_job
expect(response).to have_gitlab_http_status(:created)
expect(json_response['id']).to eq(test_job.id)
expect(json_response['dependencies'].count).to eq(2)
expect(json_response['dependencies']).to include(
- { 'id' => job.id, 'name' => job.name, 'token' => job.token },
- { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token })
+ { 'id' => job.id, 'name' => job.name, 'token' => test_job.token },
+ { 'id' => job2.id, 'name' => job2.name, 'token' => test_job.token })
+ end
+
+ context 'when ci_expose_running_job_token_for_artifacts is disabled' do
+ before do
+ stub_feature_flags(ci_expose_running_job_token_for_artifacts: false)
+ end
+
+ it 'returns dependent jobs with the dependency job tokens' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(test_job.id)
+ expect(json_response['dependencies'].count).to eq(2)
+ expect(json_response['dependencies']).to include(
+ { 'id' => job.id, 'name' => job.name, 'token' => job.token },
+ { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token })
+ end
end
describe 'preloading job_artifacts_archive' do
@@ -526,14 +543,14 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
job.success
end
- it 'returns dependent jobs' do
+ it 'returns dependent jobs with the token of the test job' do
request_job
expect(response).to have_gitlab_http_status(:created)
expect(json_response['id']).to eq(test_job.id)
expect(json_response['dependencies'].count).to eq(1)
expect(json_response['dependencies']).to include(
- { 'id' => job.id, 'name' => job.name, 'token' => job.token,
+ { 'id' => job.id, 'name' => job.name, 'token' => test_job.token,
'artifacts_file' => { 'filename' => 'ci_build_artifacts.zip', 'size' => 107464 } })
end
end
@@ -552,13 +569,13 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
job2.success
end
- it 'returns dependent jobs' do
+ it 'returns dependent jobs with the token of the test job' do
request_job
expect(response).to have_gitlab_http_status(:created)
expect(json_response['id']).to eq(test_job.id)
expect(json_response['dependencies'].count).to eq(1)
- expect(json_response['dependencies'][0]).to include('id' => job2.id, 'name' => job2.name, 'token' => job2.token)
+ expect(json_response['dependencies'][0]).to include('id' => job2.id, 'name' => job2.name, 'token' => test_job.token)
end
end
diff --git a/spec/requests/api/clusters/agent_tokens_spec.rb b/spec/requests/api/clusters/agent_tokens_spec.rb
new file mode 100644
index 00000000000..ba26faa45a3
--- /dev/null
+++ b/spec/requests/api/clusters/agent_tokens_spec.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Clusters::AgentTokens do
+ let_it_be(:agent) { create(:cluster_agent) }
+ let_it_be(:agent_token_one) { create(:cluster_agent_token, agent: agent) }
+ let_it_be(:agent_token_two) { create(:cluster_agent_token, agent: agent) }
+ let_it_be(:project) { agent.project }
+ let_it_be(:user) { agent.created_by_user }
+ let_it_be(:unauthorized_user) { create(:user) }
+
+ before_all do
+ project.add_maintainer(user)
+ project.add_guest(unauthorized_user)
+ end
+
+ describe 'GET /projects/:id/cluster_agents/:agent_id/tokens' do
+ context 'with authorized user' do
+ it 'returns tokens' do
+ get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", user)
+
+ aggregate_failures "testing response" do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response).to match_response_schema('public_api/v4/agent_tokens')
+ expect(json_response.count).to eq(2)
+ expect(json_response.first['name']).to eq(agent_token_one.name)
+ expect(json_response.first['agent_id']).to eq(agent.id)
+ expect(json_response.second['name']).to eq(agent_token_two.name)
+ expect(json_response.second['agent_id']).to eq(agent.id)
+ end
+ end
+ end
+
+ context 'with unauthorized user' do
+ it 'cannot access agent tokens' do
+ get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ it 'avoids N+1 queries', :request_store do
+ # Establish baseline
+ get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", user)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", user)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ # Now create a second record and ensure that the API does not execute
+ # any more queries than before
+ create(:cluster_agent_token, agent: agent)
+
+ expect do
+ get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", user)
+ end.not_to exceed_query_limit(control)
+ end
+ end
+
+ describe 'GET /projects/:id/cluster_agents/:agent_id/tokens/:token_id' do
+ context 'with authorized user' do
+ it 'returns an agent token' do
+ get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{agent_token_one.id}", user)
+
+ aggregate_failures "testing response" do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/agent_token')
+ expect(json_response['id']).to eq(agent_token_one.id)
+ expect(json_response['name']).to eq(agent_token_one.name)
+ expect(json_response['agent_id']).to eq(agent.id)
+ end
+ end
+
+ it 'returns a 404 error if agent token id is not available' do
+ get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{non_existing_record_id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'with unauthorized user' do
+ it 'cannot access single agent token' do
+ get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{agent_token_one.id}", unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'cannot access token from agent of another project' do
+ another_project = create(:project, namespace: unauthorized_user.namespace)
+ another_agent = create(:cluster_agent, project: another_project, created_by_user: unauthorized_user)
+
+ get api("/projects/#{another_project.id}/cluster_agents/#{another_agent.id}/tokens/#{agent_token_one.id}",
+ unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/cluster_agents/:agent_id/tokens' do
+ it 'creates a new agent token' do
+ params = {
+ name: 'test-token',
+ description: 'Test description'
+ }
+ post(api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", user), params: params)
+
+ aggregate_failures "testing response" do
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('public_api/v4/agent_token_with_token')
+ expect(json_response['name']).to eq(params[:name])
+ expect(json_response['description']).to eq(params[:description])
+ expect(json_response['agent_id']).to eq(agent.id)
+ end
+ end
+
+ it 'returns a 400 error if name not given' do
+ post api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns 404 error if project does not exist' do
+ post api("/projects/#{non_existing_record_id}/cluster_agents/tokens", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 error if agent does not exist' do
+ post api("/projects/#{project.id}/cluster_agents/#{non_existing_record_id}/tokens", user),
+ params: { name: "some" }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'with unauthorized user' do
+ it 'prevents to create agent token' do
+ post api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", unauthorized_user),
+ params: { name: "some" }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/cluster_agents/:agent_id/tokens/:token_id' do
+ it 'revokes agent token' do
+ delete api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{agent_token_one.id}", user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(agent_token_one.reload).to be_revoked
+ end
+
+ it 'returns a 404 error when revoking non existent agent token' do
+ delete api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{non_existing_record_id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns a 404 if the user is unauthorized to revoke' do
+ delete api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{agent_token_one.id}", unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'cannot revoke token from agent of another project' do
+ another_project = create(:project, namespace: unauthorized_user.namespace)
+ another_agent = create(:cluster_agent, project: another_project, created_by_user: unauthorized_user)
+
+ delete api("/projects/#{another_project.id}/cluster_agents/#{another_agent.id}/tokens/#{agent_token_one.id}",
+ unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+end
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index 2f452ce806e..c33e2bce65e 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -802,13 +802,13 @@ RSpec.describe API::Internal::Base do
context 'git pull' do
context 'with a key that has expired' do
- let(:key) { create(:key, user: user, expires_at: 2.days.ago) }
+ let(:key) { create(:key, :expired, user: user) }
- it 'includes the `key expired` message in the response' do
+ it 'includes the `key expired` message in the response and fails' do
pull(key, project)
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['gl_console_messages']).to eq(['INFO: Your SSH key has expired. Please generate a new key.'])
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(json_response['message']).to eq('Your SSH key has expired.')
end
end
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 07efd56fef4..8a8cd8512f8 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -410,6 +410,27 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do
it_behaves_like 'post project export start'
+ context 'with project export size limit' do
+ before do
+ stub_application_setting(max_export_size: 1)
+ end
+
+ it 'starts if limit not exceeded' do
+ post api(path, user)
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+
+ it '400 response if limit exceeded' do
+ project.statistics.update!(lfs_objects_size: 2.megabytes, repository_size: 2.megabytes)
+
+ post api(path, user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response["message"]).to include('The project size exceeds the export limit.')
+ end
+ end
+
context 'when rate limit is exceeded across projects' do
before do
allow(Gitlab::ApplicationRateLimiter)
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index c724c69045e..2c1b4dc8da2 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -54,6 +54,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
expect(json_response['runner_token_expiration_interval']).to be_nil
expect(json_response['group_runner_token_expiration_interval']).to be_nil
expect(json_response['project_runner_token_expiration_interval']).to be_nil
+ expect(json_response['max_export_size']).to eq(0)
end
end
@@ -138,6 +139,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
spam_check_api_key: 'SPAM_CHECK_API_KEY',
mailgun_events_enabled: true,
mailgun_signing_key: 'MAILGUN_SIGNING_KEY',
+ max_export_size: 6,
disabled_oauth_sign_in_sources: 'unknown',
import_sources: 'github,bitbucket',
wiki_page_max_content_bytes: 12345,
@@ -193,6 +195,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
expect(json_response['spam_check_api_key']).to eq('SPAM_CHECK_API_KEY')
expect(json_response['mailgun_events_enabled']).to be(true)
expect(json_response['mailgun_signing_key']).to eq('MAILGUN_SIGNING_KEY')
+ expect(json_response['max_export_size']).to eq(6)
expect(json_response['disabled_oauth_sign_in_sources']).to eq([])
expect(json_response['import_sources']).to match_array(%w(github bitbucket))
expect(json_response['wiki_page_max_content_bytes']).to eq(12345)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index c554463df76..040ac4f74a7 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1679,13 +1679,13 @@ RSpec.describe API::Users do
end
it 'creates SSH key with `expires_at` attribute' do
- optional_attributes = { expires_at: '2016-01-21T00:00:00.000Z' }
+ optional_attributes = { expires_at: 3.weeks.from_now }
attributes = attributes_for(:key).merge(optional_attributes)
post api("/users/#{user.id}/keys", admin), params: attributes
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['expires_at']).to eq(optional_attributes[:expires_at])
+ expect(json_response['expires_at'].to_date).to eq(optional_attributes[:expires_at].to_date)
end
it "returns 400 for invalid ID" do
@@ -2373,13 +2373,13 @@ RSpec.describe API::Users do
end
it 'creates SSH key with `expires_at` attribute' do
- optional_attributes = { expires_at: '2016-01-21T00:00:00.000Z' }
+ optional_attributes = { expires_at: 3.weeks.from_now }
attributes = attributes_for(:key).merge(optional_attributes)
post api("/user/keys", user), params: attributes
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['expires_at']).to eq(optional_attributes[:expires_at])
+ expect(json_response['expires_at'].to_date).to eq(optional_attributes[:expires_at].to_date)
end
it "returns a 401 error if unauthorized" do
diff --git a/spec/services/keys/expiry_notification_service_spec.rb b/spec/services/keys/expiry_notification_service_spec.rb
index 1d1da179cf7..7cb6cbce311 100644
--- a/spec/services/keys/expiry_notification_service_spec.rb
+++ b/spec/services/keys/expiry_notification_service_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe Keys::ExpiryNotificationService do
end
context 'with key expiring today', :mailer do
- let_it_be_with_reload(:key) { create(:key, expires_at: Time.current, user: user) }
+ let_it_be_with_reload(:key) { create(:key, expires_at: 10.minutes.from_now, user: user) }
let(:expiring_soon) { false }
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index 348ea9ad7d4..d40a87e5165 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -21,6 +21,14 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
let(:target_branch) { 'feature' }
let(:title) { 'my title' }
let(:description) { 'my description' }
+ let(:multiline_description) do
+ <<~MD.chomp
+ Line 1
+ Line 2
+ Line 3
+ MD
+ end
+
let(:label1) { 'mylabel1' }
let(:label2) { 'mylabel2' }
let(:label3) { 'mylabel3' }
@@ -64,6 +72,16 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
end
end
+ shared_examples_for 'a service that can set the multiline description of a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets the multiline description' do
+ service.execute
+
+ expect(last_mr.description).to eq(multiline_description)
+ end
+ end
+
shared_examples_for 'a service that can set the milestone of a merge request' do
subject(:last_mr) { MergeRequest.last }
@@ -417,6 +435,13 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it_behaves_like 'a service that can set the description of a merge request'
+
+ context 'with a multiline description' do
+ let(:push_options) { { description: "Line 1\\nLine 2\\nLine 3" } }
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the multiline description of a merge request'
+ end
end
it_behaves_like 'with a deleted branch'
diff --git a/spec/views/profiles/keys/_form.html.haml_spec.rb b/spec/views/profiles/keys/_form.html.haml_spec.rb
index ba8394178d9..c807512a11a 100644
--- a/spec/views/profiles/keys/_form.html.haml_spec.rb
+++ b/spec/views/profiles/keys/_form.html.haml_spec.rb
@@ -15,8 +15,6 @@ RSpec.describe 'profiles/keys/_form.html.haml' do
context 'when the form partial is used' do
before do
- allow(view).to receive(:ssh_key_expires_field_description).and_return('Key can still be used after expiration.')
-
render
end
@@ -37,7 +35,7 @@ RSpec.describe 'profiles/keys/_form.html.haml' do
it 'has the expires at field', :aggregate_failures do
expect(rendered).to have_field('Expiration date', type: 'date')
expect(page.find_field('Expiration date')['min']).to eq(l(1.day.from_now, format: "%Y-%m-%d"))
- expect(rendered).to have_text('Key can still be used after expiration.')
+ expect(rendered).to have_text('Key becomes invalid on this date')
end
it 'has the validation warning', :aggregate_failures do
diff --git a/spec/views/profiles/keys/_key.html.haml_spec.rb b/spec/views/profiles/keys/_key.html.haml_spec.rb
index ed8026d2453..1040541332d 100644
--- a/spec/views/profiles/keys/_key.html.haml_spec.rb
+++ b/spec/views/profiles/keys/_key.html.haml_spec.rb
@@ -59,11 +59,7 @@ RSpec.describe 'profiles/keys/_key.html.haml' do
end
context 'when the key has expired' do
- let_it_be(:key) do
- create(:personal_key,
- user: user,
- expires_at: 2.days.ago)
- end
+ let_it_be(:key) { create(:personal_key, :expired, user: user) }
it 'renders "Expired:" as the expiration date label' do
render
@@ -91,8 +87,6 @@ RSpec.describe 'profiles/keys/_key.html.haml' do
where(:valid, :expiry, :result) do
false | 2.days.from_now | 'Key type is forbidden. Must be DSA, ECDSA, ED25519, ECDSA_SK, or ED25519_SK'
- false | 2.days.ago | 'Key type is forbidden. Must be DSA, ECDSA, ED25519, ECDSA_SK, or ED25519_SK'
- true | 2.days.ago | 'Key usable beyond expiration date.'
true | 2.days.from_now | ''
end
diff --git a/spec/workers/ssh_keys/expired_notification_worker_spec.rb b/spec/workers/ssh_keys/expired_notification_worker_spec.rb
index be38391ff8c..26d9460d73e 100644
--- a/spec/workers/ssh_keys/expired_notification_worker_spec.rb
+++ b/spec/workers/ssh_keys/expired_notification_worker_spec.rb
@@ -16,12 +16,12 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do
let_it_be(:user) { create(:user) }
context 'with a large batch' do
+ let_it_be_with_reload(:keys) { create_list(:key, 20, :expired_today, user: user) }
+
before do
stub_const("SshKeys::ExpiredNotificationWorker::BATCH_SIZE", 5)
end
- let_it_be_with_reload(:keys) { create_list(:key, 20, expires_at: Time.current, user: user) }
-
it 'updates all keys regardless of batch size' do
worker.perform
@@ -30,7 +30,7 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do
end
context 'with expiring key today' do
- let_it_be_with_reload(:expired_today) { create(:key, expires_at: Time.current, user: user) }
+ let_it_be_with_reload(:expired_today) { create(:key, :expired_today, user: user) }
it 'invoke the notification service' do
expect_next_instance_of(Keys::ExpiryNotificationService) do |expiry_service|
@@ -52,7 +52,7 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do
end
context 'when key has expired in the past' do
- let_it_be(:expired_past) { create(:key, expires_at: 1.day.ago, user: user) }
+ let_it_be(:expired_past) { create(:key, :expired, user: user) }
it 'does not update notified column' do
expect { worker.perform }.not_to change { expired_past.reload.expiry_notification_delivered_at }
@@ -60,7 +60,7 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do
context 'when key has already been notified of expiration' do
before do
- expired_past.update!(expiry_notification_delivered_at: 1.day.ago)
+ expired_past.update_attribute(:expiry_notification_delivered_at, 1.day.ago)
end
it 'does not update notified column' do
diff --git a/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb b/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb
index 0a1d4a14ad0..e907d035020 100644
--- a/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb
+++ b/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe SshKeys::ExpiringSoonNotificationWorker, type: :worker do
end
context 'when key has expired in the past' do
- let_it_be(:expired_past) { create(:key, expires_at: 1.day.ago, user: user) }
+ let_it_be(:expired_past) { create(:key, :expired, user: user) }
it 'does not update notified column' do
expect { worker.perform }.not_to change { expired_past.reload.before_expiry_notification_delivered_at }