Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6282dd7833
commit
11df4bf91b
122 changed files with 1509 additions and 285 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -53,4 +53,5 @@ export default ({
|
|||
|
||||
tagNotes: '',
|
||||
includeTagNotes: false,
|
||||
existingRelease: null,
|
||||
});
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
#import "ee_else_ce/runner/graphql/details/runner_details.fragment.graphql"
|
||||
|
||||
query getRunner($id: CiRunnerID!) {
|
||||
runner(id: $id) {
|
||||
...RunnerDetails
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
#import "./runner_details_shared.fragment.graphql"
|
||||
|
||||
fragment RunnerDetails on CiRunner {
|
||||
...RunnerDetailsShared
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#import "./runner_fields_shared.fragment.graphql"
|
||||
|
||||
fragment RunnerFields on CiRunner {
|
||||
...RunnerFieldsShared
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
fragment RunnerFieldsShared on CiRunner {
|
||||
__typename
|
||||
id
|
||||
shortSha
|
||||
runnerType
|
||||
active
|
||||
accessLevel
|
||||
runUntagged
|
||||
locked
|
||||
description
|
||||
maximumTimeout
|
||||
tagList
|
||||
createdAt
|
||||
status(legacyMode: null)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#import "ee_else_ce/runner/graphql/edit/runner_fields.fragment.graphql"
|
||||
|
||||
query getRunnerForm($id: CiRunnerID!) {
|
||||
runner(id: $id) {
|
||||
...RunnerFields
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
.form-group
|
||||
= 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' }
|
||||
|
@ -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'
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
%tr
|
||||
%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])
|
||||
|
|
|
@ -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
|
|
@ -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
|
19
data/deprecations/15-0-deprecate-postgresql-12.yml
Normal file
19
data/deprecations/15-0-deprecate-postgresql-12.yml
Normal file
|
@ -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
|
|
@ -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
|
|
@ -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
|
1
db/schema_migrations/20220426130217
Normal file
1
db/schema_migrations/20220426130217
Normal file
|
@ -0,0 +1 @@
|
|||
5a55099d1f50c3059778e340bbbe519d4fcd6c1eefb235191f8db02f92b7b49e
|
1
db/schema_migrations/20220505060011
Normal file
1
db/schema_migrations/20220505060011
Normal file
|
@ -0,0 +1 @@
|
|||
aa0e6f29d918bff13cbf499e465f63320dbb6ed5a6940c2c438fe015dcc7fcd6
|
|
@ -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);
|
||||
|
|
|
@ -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` |
|
||||
|
|
216
doc/api/cluster_agent_tokens.md
Normal file
216
doc/api/cluster_agent_tokens.md
Normal file
|
@ -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
|
||||
```
|
|
@ -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. |
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)**
|
||||
|
||||
|
|
|
@ -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). |
|
||||
|
|
|
@ -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**.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
98
lib/api/clusters/agent_tokens.rb
Normal file
98
lib/api/clusters/agent_tokens.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
11
lib/api/entities/clusters/agent_token.rb
Normal file
11
lib/api/entities/clusters/agent_token.rb
Normal file
|
@ -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
|
17
lib/api/entities/clusters/agent_token_basic.rb
Normal file
17
lib/api/entities/clusters/agent_token_basic.rb
Normal file
|
@ -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
|
11
lib/api/entities/clusters/agent_token_with_token.rb
Normal file
11
lib/api/entities/clusters/agent_token_with_token.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
module Clusters
|
||||
class AgentTokenWithToken < AgentToken
|
||||
expose :token
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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!
|
||||
|
|
|
@ -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.'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ module API
|
|||
before { authenticated_as_admin! }
|
||||
|
||||
feature_category :service_ping
|
||||
urgency :low
|
||||
|
||||
namespace 'usage_data' do
|
||||
before do
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
24
spec/fixtures/api/schemas/public_api/v4/agent_token.json
vendored
Normal file
24
spec/fixtures/api/schemas/public_api/v4/agent_token.json
vendored
Normal file
|
@ -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
|
||||
}
|
22
spec/fixtures/api/schemas/public_api/v4/agent_token_basic.json
vendored
Normal file
22
spec/fixtures/api/schemas/public_api/v4/agent_token_basic.json
vendored
Normal file
|
@ -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
|
||||
}
|
26
spec/fixtures/api/schemas/public_api/v4/agent_token_with_token.json
vendored
Normal file
26
spec/fixtures/api/schemas/public_api/v4/agent_token_with_token.json
vendored
Normal file
|
@ -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
|
||||
}
|
4
spec/fixtures/api/schemas/public_api/v4/agent_tokens.json
vendored
Normal file
4
spec/fixtures/api/schemas/public_api/v4/agent_tokens.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"type": "array",
|
||||
"items": { "$ref": "agent_token_basic.json" }
|
||||
}
|
|
@ -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~~
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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.'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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}`, () => {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue