Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-11 18:07:55 +00:00
parent 6282dd7833
commit 11df4bf91b
122 changed files with 1509 additions and 285 deletions

View file

@ -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

View file

@ -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;

View file

@ -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
>

View file

@ -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"

View file

@ -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);

View file

@ -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 */

View file

@ -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;

View file

@ -53,4 +53,5 @@ export default ({
tagNotes: '',
includeTagNotes: false,
existingRelease: null,
});

View file

@ -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),

View file

@ -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';

View file

@ -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';

View file

@ -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,

View file

@ -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 {

View file

@ -1,7 +0,0 @@
#import "ee_else_ce/runner/graphql/details/runner_details.fragment.graphql"
query getRunner($id: CiRunnerID!) {
runner(id: $id) {
...RunnerDetails
}
}

View file

@ -1,5 +0,0 @@
#import "./runner_details_shared.fragment.graphql"
fragment RunnerDetails on CiRunner {
...RunnerDetailsShared
}

View file

@ -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
}
}
}

View file

@ -0,0 +1,5 @@
#import "./runner_fields_shared.fragment.graphql"
fragment RunnerFields on CiRunner {
...RunnerFieldsShared
}

View file

@ -0,0 +1,15 @@
fragment RunnerFieldsShared on CiRunner {
__typename
id
shortSha
runnerType
active
accessLevel
runUntagged
locked
description
maximumTimeout
tagList
createdAt
status(legacyMode: null)
}

View file

@ -0,0 +1,7 @@
#import "ee_else_ce/runner/graphql/edit/runner_fields.fragment.graphql"
query getRunnerForm($id: CiRunnerID!) {
runner(id: $id) {
...RunnerFields
}
}

View file

@ -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
}

View file

@ -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
}
}
}
}

View file

@ -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"

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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?

View file

@ -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 }

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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')

View file

@ -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')

View file

@ -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

View file

@ -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'

View file

@ -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])

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1 @@
5a55099d1f50c3059778e340bbbe519d4fcd6c1eefb235191f8db02f92b7b49e

View file

@ -0,0 +1 @@
aa0e6f29d918bff13cbf499e465f63320dbb6ed5a6940c2c438fe015dcc7fcd6

View file

@ -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);

View file

@ -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` |

View 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
```

View file

@ -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. |

View file

@ -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"
}
```

View file

@ -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.

View file

@ -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. |

View file

@ -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

View file

@ -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

View file

@ -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).

View file

@ -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

View file

@ -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.

View file

@ -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)**

View file

@ -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). |

View file

@ -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**.

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View 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

View 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

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
module API
module Entities
module Clusters
class AgentTokenWithToken < AgentToken
expose :token
end
end
end
end

View file

@ -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!

View file

@ -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.'

View file

@ -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

View file

@ -5,6 +5,7 @@ module API
before { authenticated_as_admin! }
feature_category :service_ping
urgency :low
namespace 'usage_data' do
before do

View file

@ -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

View file

@ -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

View file

@ -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}'

View file

@ -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

View file

@ -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 ""

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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)) }

View file

@ -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

View file

@ -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

View 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
}

View 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
}

View 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
}

View file

@ -0,0 +1,4 @@
{
"type": "array",
"items": { "$ref": "agent_token_basic.json" }
}

View file

@ -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~~

View file

@ -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();
});
});
});

View file

@ -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}")

View file

@ -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.'));
});
});
});

View file

@ -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);
});

View file

@ -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: {

View file

@ -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}`, () => {

View file

@ -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(() => {

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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);
});

View file

@ -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