Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-12 18:08:46 +00:00
parent 5ab111376f
commit 0ba11d8461
101 changed files with 1047 additions and 941 deletions

View File

@ -409,7 +409,7 @@ group :development, :test do
end end
group :development, :test, :danger do group :development, :test, :danger do
gem 'gitlab-dangerfiles', '~> 3.4.2', require: false gem 'gitlab-dangerfiles', '~> 3.4.3', require: false
end end
group :development, :test, :coverage do group :development, :test, :coverage do

View File

@ -501,7 +501,7 @@ GEM
terminal-table (~> 1.5, >= 1.5.1) terminal-table (~> 1.5, >= 1.5.1)
gitlab-chronic (0.10.5) gitlab-chronic (0.10.5)
numerizer (~> 0.2) numerizer (~> 0.2)
gitlab-dangerfiles (3.4.2) gitlab-dangerfiles (3.4.3)
danger (>= 8.4.5) danger (>= 8.4.5)
danger-gitlab (>= 8.0.0) danger-gitlab (>= 8.0.0)
rake rake
@ -1560,7 +1560,7 @@ DEPENDENCIES
gitaly (~> 15.1.0.pre.rc1) gitaly (~> 15.1.0.pre.rc1)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 3.4.2) gitlab-dangerfiles (~> 3.4.3)
gitlab-experiment (~> 0.7.1) gitlab-experiment (~> 0.7.1)
gitlab-fog-azure-rm (~> 1.3.0) gitlab-fog-azure-rm (~> 1.3.0)
gitlab-labkit (~> 0.23.0) gitlab-labkit (~> 0.23.0)

View File

@ -24,7 +24,7 @@ function setVisibilityOptions({ name, visibility, showPath, editPath }) {
optionInput.disabled = true; optionInput.disabled = true;
const reason = option.querySelector('.option-disabled-reason'); const reason = option.querySelector('.option-disabled-reason');
if (reason) { if (reason) {
const optionTitle = option.querySelector('.form-check-label span'); const optionTitle = option.querySelector('.js-visibility-level-radio span');
const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : ''; const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : '';
reason.innerHTML = sprintf( reason.innerHTML = sprintf(
__( __(

View File

@ -103,14 +103,12 @@ export default {
return this.rowNumbers[key]; return this.rowNumbers[key];
}, },
getCommit(fileName, type) { getCommit(fileName) {
if (!this.glFeatures.lazyLoadCommits) { if (!this.glFeatures.lazyLoadCommits) {
return {}; return {};
} }
return this.commits.find( return this.commits.find((commitEntry) => commitEntry.fileName === fileName);
(commitEntry) => commitEntry.fileName === fileName && commitEntry.type === type,
);
}, },
}, },
}; };
@ -152,7 +150,7 @@ export default {
:loading-path="loadingPath" :loading-path="loadingPath"
:total-entries="totalEntries" :total-entries="totalEntries"
:row-number="generateRowNumber(entry.flatPath, entry.id, index)" :row-number="generateRowNumber(entry.flatPath, entry.id, index)"
:commit-info="getCommit(entry.name, entry.type)" :commit-info="getCommit(entry.name)"
v-on="$listeners" v-on="$listeners"
/> />
</template> </template>

View File

@ -43,7 +43,6 @@ export default {
variables() { variables() {
return { return {
fileName: this.name, fileName: this.name,
type: this.type,
path: this.currentPath, path: this.currentPath,
projectPath: this.projectPath, projectPath: this.projectPath,
maxOffset: this.totalEntries, maxOffset: this.totalEntries,

View File

@ -9,7 +9,7 @@ Vue.use(VueApollo);
const defaultClient = createDefaultClient( const defaultClient = createDefaultClient(
{ {
Query: { Query: {
commit(_, { path, fileName, type, maxOffset }) { commit(_, { path, fileName, maxOffset }) {
return new Promise((resolve) => { return new Promise((resolve) => {
fetchLogsTree( fetchLogsTree(
defaultClient, defaultClient,
@ -19,7 +19,6 @@ const defaultClient = createDefaultClient(
resolve, resolve,
entry: { entry: {
name: fileName, name: fileName,
type,
}, },
}, },
maxOffset, maxOffset,

View File

@ -16,9 +16,7 @@ function setNextOffset(offset) {
} }
export function resolveCommit(commits, path, { resolve, entry }) { export function resolveCommit(commits, path, { resolve, entry }) {
const commit = commits.find( const commit = commits.find((c) => c.filePath === `${path}/${entry.name}`);
(c) => c.filePath === `${path}/${entry.name}` && c.type === entry.type,
);
if (commit) { if (commit) {
resolve(commit); resolve(commit);

View File

@ -6,5 +6,4 @@ fragment TreeEntryCommit on LogTreeCommit {
commitPath commitPath
fileName fileName
filePath filePath
type
} }

View File

@ -1,7 +1,7 @@
#import "ee_else_ce/repository/queries/commit.fragment.graphql" #import "ee_else_ce/repository/queries/commit.fragment.graphql"
query getCommit($fileName: String!, $type: String!, $path: String!, $maxOffset: Number!) { query getCommit($fileName: String!, $path: String!, $maxOffset: Number!) {
commit(path: $path, fileName: $fileName, type: $type, maxOffset: $maxOffset) @client { commit(path: $path, fileName: $fileName, maxOffset: $maxOffset) @client {
...TreeEntryCommit ...TreeEntryCommit
} }
} }

View File

@ -7,7 +7,6 @@ export function normalizeData(data, path, extra = () => {}) {
commitPath: d.commit_path, commitPath: d.commit_path,
fileName: d.file_name, fileName: d.file_name,
filePath: `${path}/${d.file_name}`, filePath: `${path}/${d.file_name}`,
type: d.type,
__typename: 'LogTreeCommit', __typename: 'LogTreeCommit',
...extra(d), ...extra(d),
})); }));

View File

@ -1,5 +1,5 @@
<script> <script>
import { GlBadge, GlTab, GlTooltipDirective } from '@gitlab/ui'; import { GlBadge, GlTabs, GlTab, GlTooltipDirective } from '@gitlab/ui';
import { createAlert, VARIANT_SUCCESS } from '~/flash'; import { createAlert, VARIANT_SUCCESS } from '~/flash';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants'; import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId } from '~/graphql_shared/utils';
@ -11,7 +11,7 @@ import RunnerPauseButton from '../components/runner_pause_button.vue';
import RunnerHeader from '../components/runner_header.vue'; import RunnerHeader from '../components/runner_header.vue';
import RunnerDetails from '../components/runner_details.vue'; import RunnerDetails from '../components/runner_details.vue';
import RunnerJobs from '../components/runner_jobs.vue'; import RunnerJobs from '../components/runner_jobs.vue';
import { I18N_FETCH_ERROR } from '../constants'; import { I18N_DETAILS, I18N_FETCH_ERROR } from '../constants';
import runnerQuery from '../graphql/show/runner.query.graphql'; import runnerQuery from '../graphql/show/runner.query.graphql';
import { captureException } from '../sentry_utils'; import { captureException } from '../sentry_utils';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage'; import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
@ -20,6 +20,7 @@ export default {
name: 'AdminRunnerShowApp', name: 'AdminRunnerShowApp',
components: { components: {
GlBadge, GlBadge,
GlTabs,
GlTab, GlTab,
RunnerDeleteButton, RunnerDeleteButton,
RunnerEditButton, RunnerEditButton,
@ -84,6 +85,7 @@ export default {
redirectTo(this.runnersPath); redirectTo(this.runnersPath);
}, },
}, },
I18N_DETAILS,
}; };
</script> </script>
<template> <template>
@ -96,24 +98,27 @@ export default {
</template> </template>
</runner-header> </runner-header>
<runner-details :runner="runner"> <gl-tabs>
<template #jobs-tab> <gl-tab>
<gl-tab> <template #title>{{ $options.I18N_DETAILS }}</template>
<template #title>
{{ s__('Runners|Jobs') }}
<gl-badge
v-if="jobCount"
data-testid="job-count-badge"
class="gl-tab-counter-badge"
size="sm"
>
{{ jobCount }}
</gl-badge>
</template>
<runner-jobs v-if="runner" :runner="runner" /> <runner-details v-if="runner" :runner="runner" />
</gl-tab> </gl-tab>
</template> <gl-tab>
</runner-details> <template #title>
{{ s__('Runners|Jobs') }}
<gl-badge
v-if="jobCount"
data-testid="job-count-badge"
class="gl-tab-counter-badge"
size="sm"
>
{{ jobCount }}
</gl-badge>
</template>
<runner-jobs v-if="runner" :runner="runner" />
</gl-tab>
</gl-tabs>
</div> </div>
</template> </template>

View File

@ -1,5 +1,5 @@
<script> <script>
import { GlTabs, GlTab, GlIntersperse } from '@gitlab/ui'; import { GlIntersperse } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
@ -11,8 +11,6 @@ import RunnerTags from './runner_tags.vue';
export default { export default {
components: { components: {
GlTabs,
GlTab,
GlIntersperse, GlIntersperse,
RunnerDetail, RunnerDetail,
RunnerMaintenanceNoteDetail: () => RunnerMaintenanceNoteDetail: () =>
@ -65,64 +63,57 @@ export default {
</script> </script>
<template> <template>
<gl-tabs> <div>
<gl-tab> <runner-upgrade-status-alert class="gl-my-4" :runner="runner" />
<template #title>{{ s__('Runners|Details') }}</template> <div class="gl-pt-4">
<dl class="gl-mb-0" data-testid="runner-details-list">
<template v-if="runner"> <runner-detail :label="s__('Runners|Description')" :value="runner.description" />
<runner-upgrade-status-alert class="gl-my-4" :runner="runner" /> <runner-detail
<div class="gl-pt-4"> :label="s__('Runners|Last contact')"
<dl class="gl-mb-0" data-testid="runner-details-list"> :empty-value="s__('Runners|Never contacted')"
<runner-detail :label="s__('Runners|Description')" :value="runner.description" /> >
<runner-detail <template #value>
:label="s__('Runners|Last contact')" <time-ago v-if="runner.contactedAt" :time="runner.contactedAt" />
:empty-value="s__('Runners|Never contacted')" </template>
> </runner-detail>
<template #value> <runner-detail :label="s__('Runners|Version')">
<time-ago v-if="runner.contactedAt" :time="runner.contactedAt" /> <template v-if="runner.version" #value>
</template> {{ runner.version }}
</runner-detail> <runner-upgrade-status-badge size="sm" :runner="runner" />
<runner-detail :label="s__('Runners|Version')"> </template>
<template v-if="runner.version" #value> </runner-detail>
{{ runner.version }} <runner-detail :label="s__('Runners|IP Address')" :value="runner.ipAddress" />
<runner-upgrade-status-badge size="sm" :runner="runner" /> <runner-detail :label="s__('Runners|Executor')" :value="runner.executorName" />
</template> <runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" />
</runner-detail> <runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" />
<runner-detail :label="s__('Runners|IP Address')" :value="runner.ipAddress" /> <runner-detail :label="s__('Runners|Configuration')">
<runner-detail :label="s__('Runners|Executor')" :value="runner.executorName" /> <template #value>
<runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" /> <gl-intersperse v-if="configTextProtected || configTextUntagged">
<runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" /> <span v-if="configTextProtected">{{ configTextProtected }}</span>
<runner-detail :label="s__('Runners|Configuration')"> <span v-if="configTextUntagged">{{ configTextUntagged }}</span>
<template #value> </gl-intersperse>
<gl-intersperse v-if="configTextProtected || configTextUntagged"> </template>
<span v-if="configTextProtected">{{ configTextProtected }}</span> </runner-detail>
<span v-if="configTextUntagged">{{ configTextUntagged }}</span> <runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" />
</gl-intersperse> <runner-detail :label="s__('Runners|Tags')">
</template> <template #value>
</runner-detail> <runner-tags
<runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" /> v-if="runner.tagList && runner.tagList.length"
<runner-detail :label="s__('Runners|Tags')"> class="gl-vertical-align-middle"
<template #value> :tag-list="runner.tagList"
<runner-tags size="sm"
v-if="runner.tagList && runner.tagList.length"
class="gl-vertical-align-middle"
:tag-list="runner.tagList"
size="sm"
/>
</template>
</runner-detail>
<runner-maintenance-note-detail
class="gl-pt-4 gl-border-t-gray-100 gl-border-t-1 gl-border-t-solid"
:value="runner.maintenanceNoteHtml"
/> />
</dl> </template>
</div> </runner-detail>
<runner-groups v-if="isGroupRunner" :runner="runner" /> <runner-maintenance-note-detail
<runner-projects v-if="isProjectRunner" :runner="runner" /> class="gl-pt-4 gl-border-t-gray-100 gl-border-t-1 gl-border-t-solid"
</template> :value="runner.maintenanceNoteHtml"
</gl-tab> />
<slot name="jobs-tab"></slot> </dl>
</gl-tabs> </div>
<runner-groups v-if="isGroupRunner" :runner="runner" />
<runner-projects v-if="isProjectRunner" :runner="runner" />
</div>
</template> </template>

View File

@ -81,6 +81,7 @@ export const I18N_LOCKED_RUNNER_DESCRIPTION = s__(
// Runner details // Runner details
export const I18N_DETAILS = s__('Runners|Details');
export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})'); export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})');
export const I18N_NONE = __('None'); export const I18N_NONE = __('None');
export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.'); export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.');

View File

@ -89,6 +89,6 @@ export default {
</template> </template>
</runner-header> </runner-header>
<runner-details :runner="runner" /> <runner-details v-if="runner" :runner="runner" />
</div> </div>
</template> </template>

View File

@ -315,6 +315,7 @@ export default {
@mouseup="onRowMouseUp" @mouseup="onRowMouseUp"
> >
<status-icon <status-icon
:level="1"
:name="$options.label || $options.name" :name="$options.label || $options.name"
:is-loading="isLoadingSummary" :is-loading="isLoadingSummary"
:icon-name="statusIconName" :icon-name="statusIconName"

View File

@ -9,6 +9,11 @@ export default {
GlIcon, GlIcon,
}, },
props: { props: {
level: {
type: Number,
required: false,
default: 0,
},
name: { name: {
type: String, type: String,
required: false, required: false,
@ -27,7 +32,7 @@ export default {
size: { size: {
type: Number, type: Number,
required: false, required: false,
default: 16, default: 12,
}, },
}, },
computed: { computed: {
@ -44,8 +49,8 @@ export default {
<div <div
:class="[ :class="[
$options.EXTENSION_ICON_CLASS[iconName], $options.EXTENSION_ICON_CLASS[iconName],
{ 'mr-widget-extension-icon': !isLoading && size === 16 }, { 'mr-widget-extension-icon': !isLoading && level === 1 },
{ 'gl-p-2': isLoading || size === 16 }, { 'gl-p-2': isLoading || level === 1 },
]" ]"
class="gl-rounded-full gl-mr-3 gl-relative gl-p-2" class="gl-rounded-full gl-mr-3 gl-relative gl-p-2"
> >

View File

@ -32,7 +32,7 @@ export default {
// Status icon to be used next to the summary text // Status icon to be used next to the summary text
// Receives the collapsed data as an argument // Receives the collapsed data as an argument
statusIcon(count) { statusIcon(count) {
return EXTENSION_ICONS.warning; return EXTENSION_ICONS.failed;
}, },
// Tertiary action buttons that will take the user elsewhere // Tertiary action buttons that will take the user elsewhere
// in the GitLab app // in the GitLab app

View File

@ -8,6 +8,10 @@ import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_optio
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import { registerExtension } from './components/extensions';
import issuesExtension from './extensions/issues';
registerExtension(issuesExtension);
Vue.use(Translate); Vue.use(Translate);
Vue.use(VueApollo); Vue.use(VueApollo);

View File

@ -630,6 +630,24 @@ $tabs-holder-z-index: 250;
height: 24px; height: 24px;
} }
.mr-widget-extension-icon::after {
@include gl-content-empty;
@include gl-absolute;
@include gl-rounded-full;
top: 4px;
left: 4px;
width: 16px;
height: 16px;
border: 4px solid currentColor;
}
.mr-widget-extension-icon svg {
position: relative;
top: 2px;
left: 2px;
}
.mr-widget-heading { .mr-widget-heading {
position: relative; position: relative;
border: 1px solid var(--border-color, $border-color); border: 1px solid var(--border-color, $border-color);

View File

@ -221,7 +221,7 @@ class RegistrationsController < Devise::RegistrationsController
return unless member return unless member
Gitlab::Tracking.event(self.class.name, 'accepted', label: 'invite_email', property: member.id.to_s) Gitlab::Tracking.event(self.class.name, 'accepted', label: 'invite_email', property: member.id.to_s, user: resource)
end end
def context_user def context_user

View File

@ -40,6 +40,8 @@ module Types
authorize: :download_code authorize: :download_code
field :upcoming_release, GraphQL::Types::Boolean, null: true, method: :upcoming_release?, field :upcoming_release, GraphQL::Types::Boolean, null: true, method: :upcoming_release?,
description: 'Indicates the release is an upcoming release.' description: 'Indicates the release is an upcoming release.'
field :historical_release, GraphQL::Types::Boolean, null: true, method: :historical_release?,
description: 'Indicates the release is an historical release.'
field :author, Types::UserType, null: true, field :author, Types::UserType, null: true,
description: 'User that created the release.' description: 'User that created the release.'

View File

@ -17,6 +17,13 @@ module Types
field :allows_multiple_assignees, GraphQL::Types::Boolean, null: true, method: :allows_multiple_assignees?, field :allows_multiple_assignees, GraphQL::Types::Boolean, null: true, method: :allows_multiple_assignees?,
description: 'Indicates whether multiple assignees are allowed.' description: 'Indicates whether multiple assignees are allowed.'
field :can_invite_members, GraphQL::Types::Boolean, null: false, resolver_method: :can_invite_members?,
description: 'Indicates whether the current user can invite members to the work item\'s project.'
def can_invite_members?
Ability.allowed?(current_user, :admin_project_member, object.work_item.project)
end
end end
# rubocop:enable Graphql/AuthorizeTypes # rubocop:enable Graphql/AuthorizeTypes
end end

View File

@ -58,14 +58,6 @@ module TreeHelper
"#{username}-#{ref}-patch-#{epoch}" "#{username}-#{ref}-patch-#{epoch}"
end end
def tree_edit_project(project = @project)
if can?(current_user, :push_code, project)
project
elsif current_user && current_user.already_forked?(project)
current_user.fork_of(project)
end
end
def edit_in_new_fork_notice_now def edit_in_new_fork_notice_now
_("You're not allowed to make changes to this project directly. "\ _("You're not allowed to make changes to this project directly. "\
"A fork of this project is being created that you can make changes in, so you can submit a merge request.") "A fork of this project is being created that you can make changes in, so you can submit a merge request.")
@ -111,16 +103,6 @@ module TreeHelper
end end
end end
def up_dir_path
file = File.join(@path, "..")
tree_join(@ref, file)
end
# returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(root_path, tree)
tree.flat_path.sub(%r{\A#{Regexp.escape(root_path)}/}, '')
end
def selected_branch def selected_branch
@branch_name || tree_edit_branch @branch_name || tree_edit_branch
end end

View File

@ -30,10 +30,6 @@ module Ci
self.upsert(entry.attributes.compact, returning: %w[build_id], unique_by: :build_id) self.upsert(entry.attributes.compact, returning: %w[build_id], unique_by: :build_id)
end end
def maintain_denormalized_data?
::Feature.enabled?(:ci_pending_builds_maintain_denormalized_data)
end
private private
def args_from_build(build) def args_from_build(build)
@ -43,13 +39,13 @@ module Ci
build: build, build: build,
project: project, project: project,
protected: build.protected?, protected: build.protected?,
namespace: project.namespace namespace: project.namespace,
tag_ids: build.tags_ids,
instance_runners_enabled: shared_runners_enabled?(project)
} }
if maintain_denormalized_data? if group_runners_enabled?(project)
args.store(:tag_ids, build.tags_ids) args.store(:namespace_traversal_ids, project.namespace.traversal_ids)
args.store(:instance_runners_enabled, shared_runners_enabled?(project))
args.store(:namespace_traversal_ids, project.namespace.traversal_ids) if group_runners_enabled?(project)
end end
args args

View File

@ -2,11 +2,13 @@
module Ci module Ci
class RunnerVersion < Ci::ApplicationRecord class RunnerVersion < Ci::ApplicationRecord
include EachBatch
include EnumWithNil include EnumWithNil
include BulkInsertSafe # include this last (see https://docs.gitlab.com/ee/development/insert_into_tables_in_batches.html#prepare-applicationrecords-for-bulk-insertion)
enum_with_nil status: { enum_with_nil status: {
not_processed: nil, not_processed: nil,
invalid_version: -1, invalid_version: -1, # Named invalid_version to avoid clash with auto-generated `invalid?` ActiveRecord method
unknown: 0, unknown: 0,
not_available: 1, not_available: 1,
available: 2, available: 2,
@ -24,6 +26,10 @@ module Ci
# Override auto generated negative scope (from available) so the scope has expected behavior # Override auto generated negative scope (from available) so the scope has expected behavior
scope :not_available, -> { where(status: :not_available) } scope :not_available, -> { where(status: :not_available) }
# This scope returns all versions that might need recalculating. For instance, once a version is considered
# :recommended, it normally doesn't change status even if the instance is upgraded
scope :potentially_outdated, -> { where(status: [nil, :not_available, :available, :unknown]) }
validates :version, length: { maximum: 2048 } validates :version, length: { maximum: 2048 }
end end
end end

View File

@ -56,7 +56,8 @@ module LooseIndexScan
.project(Arel::Nodes::Grouping.new(Arel.sql(inner_query.to_sql)).as(column.to_s)) .project(Arel::Nodes::Grouping.new(Arel.sql(inner_query.to_sql)).as(column.to_s))
unscoped do unscoped do
with select(column)
.with
.recursive(cte.to_arel) .recursive(cte.to_arel)
.from(cte.alias_to(arel_table)) .from(cte.alias_to(arel_table))
.where(arel_column.not_eq(nil)) # filtering out the last NULL value .where(arel_column.not_eq(nil)) # filtering out the last NULL value

View File

@ -24,25 +24,7 @@ module Ci
# rubocop:disable CodeReuse/ActiveRecord # rubocop:disable CodeReuse/ActiveRecord
def builds_for_group_runner def builds_for_group_runner
if strategy.use_denormalized_data_strategy? strategy.builds_for_group_runner
strategy.builds_for_group_runner
else
# Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL`
groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces)
hierarchy_groups = Gitlab::ObjectHierarchy
.new(groups)
.base_and_descendants
projects = Project.where(namespace_id: hierarchy_groups)
.with_group_runners_enabled
.with_builds_enabled
.without_deleted
relation = new_builds.where(project: projects)
order(relation)
end
end end
def builds_for_project_runner def builds_for_project_runner
@ -80,11 +62,7 @@ module Ci
def strategy def strategy
strong_memoize(:strategy) do strong_memoize(:strategy) do
if ::Feature.enabled?(:ci_pending_builds_queue_source, runner) Queue::PendingBuildsStrategy.new(runner)
Queue::PendingBuildsStrategy.new(runner)
else
Queue::BuildsTableStrategy.new(runner)
end
end end
end end

View File

@ -1,75 +0,0 @@
# frozen_string_literal: true
module Ci
module Queue
class BuildsTableStrategy
attr_reader :runner
def initialize(runner)
@runner = runner
end
# rubocop:disable CodeReuse/ActiveRecord
def builds_for_shared_runner
relation = new_builds
# don't run projects which have not enabled shared runners and builds
.joins('INNER JOIN projects ON ci_builds.project_id = projects.id')
.where(projects: { shared_runners_enabled: true, pending_delete: false })
.joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
if Feature.enabled?(:ci_queueing_disaster_recovery_disable_fair_scheduling, runner, type: :ops)
# if disaster recovery is enabled, we fallback to FIFO scheduling
relation.order('ci_builds.id ASC')
else
# Implement fair scheduling
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
relation
.joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id = project_builds.project_id")
.order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_builds.id ASC')
end
end
def builds_for_group_runner
raise NotImplementedError
end
def builds_matching_tag_ids(relation, ids)
# pick builds that does not have other tags than runner's one
relation.matches_tag_ids(ids)
end
def builds_with_any_tags(relation)
# pick builds that have at least one tag
relation.with_any_tags
end
def order(relation)
relation.order('id ASC')
end
def new_builds
::Ci::Build.pending.unstarted
end
def build_ids(relation)
relation.pluck(:id)
end
def use_denormalized_data_strategy?
false
end
private
def running_builds_for_shared_runners
::Ci::Build.running
.where(runner: ::Ci::Runner.instance_type)
.group(:project_id)
.select(:project_id, 'COUNT(*) AS running_builds')
end
# rubocop:enable CodeReuse/ActiveRecord
end
end
end

View File

@ -23,19 +23,11 @@ module Ci
end end
def builds_matching_tag_ids(relation, ids) def builds_matching_tag_ids(relation, ids)
if use_denormalized_data_strategy? relation.for_tags(runner.tags_ids)
relation.for_tags(runner.tags_ids)
else
relation.merge(CommitStatus.matches_tag_ids(ids, table: 'ci_pending_builds', column: 'build_id'))
end
end end
def builds_with_any_tags(relation) def builds_with_any_tags(relation)
if use_denormalized_data_strategy? relation.where('cardinality(tag_ids) > 0')
relation.where('cardinality(tag_ids) > 0')
else
relation.merge(CommitStatus.with_any_tags(table: 'ci_pending_builds', column: 'build_id'))
end
end end
def order(relation) def order(relation)
@ -50,23 +42,10 @@ module Ci
relation.pluck(:build_id) relation.pluck(:build_id)
end end
def use_denormalized_data_strategy?
::Feature.enabled?(:ci_queuing_use_denormalized_data_strategy)
end
private private
def builds_available_for_shared_runners def builds_available_for_shared_runners
if use_denormalized_data_strategy? new_builds.with_instance_runners
new_builds.with_instance_runners
else
new_builds
# don't run projects which have not enabled shared runners and builds
.joins('INNER JOIN projects ON ci_pending_builds.project_id = projects.id')
.where(projects: { shared_runners_enabled: true, pending_delete: false })
.joins('LEFT JOIN project_features ON ci_pending_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
end
end end
def builds_ordered_for_shared_runners(relation) def builds_ordered_for_shared_runners(relation)

View File

@ -0,0 +1,83 @@
# frozen_string_literal: true
module Ci
module Runners
class ReconcileExistingRunnerVersionsService
include BaseServiceUtility
VERSION_BATCH_SIZE = 100
def execute
insert_result = insert_runner_versions
total_deleted = cleanup_runner_versions(insert_result[:versions_from_runners])
total_updated = update_status_on_outdated_runner_versions(insert_result[:versions_from_runners])
success({
total_inserted: insert_result[:new_record_count],
total_updated: total_updated,
total_deleted: total_deleted
})
end
private
def upgrade_check
Gitlab::Ci::RunnerUpgradeCheck.instance
end
# rubocop: disable CodeReuse/ActiveRecord
def insert_runner_versions
versions_from_runners = Set[]
new_record_count = 0
Ci::Runner.distinct_each_batch(column: :version, of: VERSION_BATCH_SIZE) do |version_batch|
batch_versions = version_batch.pluck(:version)
versions_from_runners += batch_versions
new_record_count += Ci::RunnerVersion.bulk_insert!(
version_batch,
returns: :ids,
skip_duplicates: true,
validate: false).count
end
{ versions_from_runners: versions_from_runners, new_record_count: new_record_count }
end
def cleanup_runner_versions(versions_from_runners)
Ci::RunnerVersion.where.not(version: versions_from_runners).delete_all
end
# rubocop: enable CodeReuse/ActiveRecord
def outdated_runner_versions
Ci::RunnerVersion.potentially_outdated
end
def update_status_on_outdated_runner_versions(versions_from_runners)
total_updated = 0
outdated_runner_versions.each_batch(of: VERSION_BATCH_SIZE) do |version_batch|
updated = version_batch
.select { |runner_version| versions_from_runners.include?(runner_version['version']) }
.filter_map { |runner_version| runner_version_with_updated_status(runner_version) }
if updated.any?
total_updated += Ci::RunnerVersion.upsert_all(updated, unique_by: :version).count
end
end
total_updated
end
def runner_version_with_updated_status(runner_version)
version = runner_version['version']
new_status = upgrade_check.check_runner_upgrade_status(version)
if new_status != :error && new_status != runner_version['status'].to_sym
{
version: version,
status: Ci::RunnerVersion.statuses[new_status]
}
end
end
end
end
end

View File

@ -14,8 +14,6 @@ module Ci
# Add a build to the pending builds queue # Add a build to the pending builds queue
# #
def push(build, transition) def push(build, transition)
return unless maintain_pending_builds_queue?
raise InvalidQueueTransition unless transition.to == 'pending' raise InvalidQueueTransition unless transition.to == 'pending'
transition.within_transaction do transition.within_transaction do
@ -33,8 +31,6 @@ module Ci
# Remove a build from the pending builds queue # Remove a build from the pending builds queue
# #
def pop(build, transition) def pop(build, transition)
return unless maintain_pending_builds_queue?
raise InvalidQueueTransition unless transition.from == 'pending' raise InvalidQueueTransition unless transition.from == 'pending'
transition.within_transaction { remove!(build) } transition.within_transaction { remove!(build) }
@ -57,7 +53,6 @@ module Ci
# Add shared runner build tracking entry (used for queuing). # Add shared runner build tracking entry (used for queuing).
# #
def track(build, transition) def track(build, transition)
return unless maintain_pending_builds_queue?
return unless build.shared_runner_build? return unless build.shared_runner_build?
raise InvalidQueueTransition unless transition.to == 'running' raise InvalidQueueTransition unless transition.to == 'running'
@ -78,7 +73,6 @@ module Ci
# queuing). # queuing).
# #
def untrack(build, transition) def untrack(build, transition)
return unless maintain_pending_builds_queue?
return unless build.shared_runner_build? return unless build.shared_runner_build?
raise InvalidQueueTransition unless transition.from == 'running' raise InvalidQueueTransition unless transition.from == 'running'
@ -115,9 +109,5 @@ module Ci
runner.pick_build!(build) runner.pick_build!(build)
end end
end end
def maintain_pending_builds_queue?
::Ci::PendingBuild.maintain_denormalized_data?
end
end end
end end

View File

@ -15,8 +15,6 @@ module Ci
end end
def execute def execute
return unless ::Ci::PendingBuild.maintain_denormalized_data?
@model.pending_builds.each_batch do |relation| @model.pending_builds.each_batch do |relation|
relation.update_all(@update_params) relation.update_all(@update_params)
end end

View File

@ -1,25 +1,42 @@
= gitlab_ui_form_for [:admin, @group] do |f| = gitlab_ui_form_for [:admin, @group] do |f|
= form_errors(@group, pajamas_alert: true) = form_errors(@group, pajamas_alert: true)
= render 'shared/group_form', f: f .gl-border-b.gl-mb-6
= render 'shared/group_form_description', f: f .row
.col-lg-4
%h4.gl-mt-0
= _('Naming, visibility')
%p
= _('Update your group name, description, avatar, and visibility.')
= link_to _('Learn more about groups.'), help_page_path('user/group/index')
.col-lg-8
= render 'shared/group_form', f: f
= render 'shared/group_form_description', f: f
.form-group.gl-form-group{ role: 'group' }
= f.label :avatar, _("Group avatar"), class: 'gl-display-block col-form-label'
= render 'shared/choose_avatar_button', f: f
= render 'shared/old_visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
= render 'shared/admin/admin_note_form', f: f .gl-border-b.gl-pb-3.gl-mb-6
.row
= render_if_exists 'shared/old_repository_size_limit_setting', form: f, type: :group .col-lg-4
= render_if_exists 'admin/namespace_plan', f: f %h4.gl-mt-0
= _('Permissions and group features')
.form-group.gl-form-group{ role: 'group' } %p
= f.label :avatar, _("Group avatar"), class: 'gl-display-block col-form-label' = _('Configure advanced permissions, Large File Storage, two-factor authentication, and CI/CD settings.')
= render 'shared/choose_avatar_button', f: f .col-lg-8
= render_if_exists 'shared/old_repository_size_limit_setting', form: f, type: :group
= render 'shared/old_visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false = render_if_exists 'admin/namespace_plan', f: f
.form-group.gl-form-group{ role: 'group' }
.form-group.gl-form-group{ role: 'group' } = render 'shared/allow_request_access', form: f
= render 'shared/allow_request_access', form: f = render 'groups/group_admin_settings', f: f
= render_if_exists 'namespaces/shared_runners_minutes_settings', group: @group, form: f
= render 'groups/group_admin_settings', f: f .gl-mb-3
.row
= render_if_exists 'namespaces/shared_runners_minutes_settings', group: @group, form: f .col-lg-4
%h4.gl-mt-0
= _('Admin notes')
.col-lg-8
= render 'shared/admin/admin_note_form', f: f
- if @group.new_record? - if @group.new_record?
= render Pajamas::AlertComponent.new(dismissible: false) do |c| = render Pajamas::AlertComponent.new(dismissible: false) do |c|

View File

@ -24,6 +24,6 @@
.form-group.gl-form-group{ role: 'group' } .form-group.gl-form-group{ role: 'group' }
= f.label :two_factor_grace_period, _('Two-factor authentication grace period'), class: 'gl-display-block col-form-label' = f.label :two_factor_grace_period, _('Two-factor authentication grace period'), class: 'gl-display-block col-form-label'
= f.text_field :two_factor_grace_period, class: 'form-control gl-form-input' = f.text_field :two_factor_grace_period, class: 'form-control gl-form-input gl-form-input-sm'
%small.form-text.text-gl-muted %small.form-text.text-gl-muted
= _("Time (in hours) that users are allowed to skip forced configuration of two-factor authentication.") = _("Time (in hours) that users are allowed to skip forced configuration of two-factor authentication.")

View File

@ -2,7 +2,7 @@
= render 'shared/group_form', f: f, autofocus: true = render 'shared/group_form', f: f, autofocus: true
.row .row
.form-group.col-sm-12.gl-mb-0 .form-group.gl-form-group.col-sm-12
%label.label-bold %label.label-bold
= _('Visibility level') = _('Visibility level')
%p %p

View File

@ -30,6 +30,6 @@
- if @group.avatar? - if @group.avatar?
%hr %hr
= link_to s_('Groups|Remove avatar'), group_avatar_path(@group.to_param), aria: { label: s_('Groups|Remove avatar') }, data: { confirm: s_('Groups|Avatar will be removed. Are you sure?'), 'confirm-btn-variant': 'danger' }, method: :delete, class: 'gl-button btn btn-danger-secondary' = link_to s_('Groups|Remove avatar'), group_avatar_path(@group.to_param), aria: { label: s_('Groups|Remove avatar') }, data: { confirm: s_('Groups|Avatar will be removed. Are you sure?'), 'confirm-btn-variant': 'danger' }, method: :delete, class: 'gl-button btn btn-danger-secondary'
.form-group.gl-form-group
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
= f.submit s_('Groups|Save changes'), class: 'btn gl-button btn-confirm mt-4 js-dirty-submit', data: { qa_selector: 'save_name_visibility_settings_button' } = f.submit s_('Groups|Save changes'), class: 'btn gl-button btn-confirm js-dirty-submit', data: { qa_selector: 'save_name_visibility_settings_button' }

View File

@ -52,10 +52,11 @@
- unless Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? || !Gitlab.com? - unless Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? || !Gitlab.com?
.js-deployment-target-select .js-deployment-target-select
= f.label :visibility_level, class: 'label-bold' do .form-group.gl-form-group
= s_('ProjectsNew|Visibility Level') = f.label :visibility_level, class: 'label-bold' do
= link_to sprite_icon('question-o'), help_page_path('user/public_access'), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer' = s_('ProjectsNew|Visibility Level')
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false, data: { qa_selector: 'visibility_radios'} = link_to sprite_icon('question-o'), help_page_path('user/public_access'), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false, data: { qa_selector: 'visibility_radios'}
- if !hide_init_with_readme - if !hide_init_with_readme
= f.label :project_configuration, class: 'label-bold' do = f.label :project_configuration, class: 'label-bold' do

View File

@ -67,5 +67,5 @@
%ul.list-unstyled.mr_target_commit %ul.list-unstyled.mr_target_commit
- if @merge_request.errors.any? - if @merge_request.errors.any?
= form_errors(@merge_request) = form_errors(@merge_request, pajamas_alert: true)
= f.submit 'Compare branches and continue', class: "gl-button btn btn-confirm mr-compare-btn gl-mt-4", data: { qa_selector: "compare_branches_button" } = f.submit 'Compare branches and continue', class: "gl-button btn btn-confirm mr-compare-btn gl-mt-4", data: { qa_selector: "compare_branches_button" }

View File

@ -1,6 +1,6 @@
- with_label = local_assigns.fetch(:with_label, true) - with_label = local_assigns.fetch(:with_label, true)
.form-group.visibility-level-setting .visibility-level-setting
- if with_label - if with_label
= f.label :visibility_level, _('Visibility level'), class: 'label-bold gl-mb-0' = f.label :visibility_level, _('Visibility level'), class: 'label-bold gl-mb-0'
%p %p

View File

@ -7,7 +7,7 @@
"#{visibility_level_icon(level)} #{visibility_level_label(level)}".html_safe, "#{visibility_level_icon(level)} #{visibility_level_label(level)}".html_safe,
help_text: '<span class="option-description">%{visibility_level_description}</span><span class="option-disabled-reason"></span>'.html_safe % { visibility_level_description: visibility_level_description(level, form_model)}, help_text: '<span class="option-description">%{visibility_level_description}</span><span class="option-disabled-reason"></span>'.html_safe % { visibility_level_description: visibility_level_description(level, form_model)},
radio_options: { checked: (selected_level == level), data: { track_label: "blank_project", track_action: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "", qa_selector: "#{visibility_level_label(level).downcase}_radio" } }, radio_options: { checked: (selected_level == level), data: { track_label: "blank_project", track_action: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "", qa_selector: "#{visibility_level_label(level).downcase}_radio" } },
label_options: { class: 'form-check-label gl-mb-2' } label_options: { class: 'js-visibility-level-radio' }
.text-muted .text-muted

View File

@ -219,6 +219,15 @@
:weight: 1 :weight: 1
:idempotent: false :idempotent: false
:tags: [] :tags: []
- :name: cronjob:ci_runners_reconcile_existing_runner_versions_cron
:worker_name: Ci::Runners::ReconcileExistingRunnerVersionsCronWorker
:feature_category: :runner_fleet
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: cronjob:ci_schedule_delete_objects_cron - :name: cronjob:ci_schedule_delete_objects_cron
:worker_name: Ci::ScheduleDeleteObjectsCronWorker :worker_name: Ci::ScheduleDeleteObjectsCronWorker
:feature_category: :continuous_integration :feature_category: :continuous_integration

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module Ci
module Runners
class ReconcileExistingRunnerVersionsCronWorker
include ApplicationWorker
# This worker does not schedule other workers that require context.
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
data_consistency :sticky
feature_category :runner_fleet
urgency :low
idempotent!
def perform
result = ::Ci::Runners::ReconcileExistingRunnerVersionsService.new.execute
result.each { |key, value| log_extra_metadata_on_done(key, value) }
end
end
end
end

View File

@ -9,14 +9,21 @@ module Packages
def perform_work def perform_work
return unless artifact return unless artifact
artifact.transaction do begin
log_metadata(artifact) artifact.transaction do
log_metadata(artifact)
artifact.destroy! artifact.destroy!
rescue StandardError end
rescue StandardError => exception
unless artifact&.destroyed? unless artifact&.destroyed?
artifact&.update_column(:status, :error) artifact&.update_column(:status, :error)
end end
Gitlab::ErrorTracking.log_exception(
exception,
class: self.class.name
)
end end
after_destroy after_destroy

View File

@ -1,8 +0,0 @@
---
name: ci_pending_builds_maintain_denormalized_data
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75425
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354496
milestone: '14.6'
type: development
group: group::pipeline execution
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: ci_pending_builds_queue_source
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354496
milestone: '14.0'
type: development
group: group::pipeline execution
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: ci_queuing_use_denormalized_data_strategy
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76543
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354496
milestone: '14.6'
type: development
group: group::pipeline execution
default_enabled: true

View File

@ -540,6 +540,10 @@ production: &base
ci_platform_metrics_update_cron_worker: ci_platform_metrics_update_cron_worker:
cron: "47 9 * * *" cron: "47 9 * * *"
# Periodically update ci_runner_versions table with up-to-date versions and status.
ci_runner_versions_reconciliation_worker:
cron: "20 * * * *"
# GitLab EE only jobs. These jobs are automatically enabled for an EE # GitLab EE only jobs. These jobs are automatically enabled for an EE
# installation, and ignored for a CE installation. # installation, and ignored for a CE installation.
ee_cron_jobs: ee_cron_jobs:

View File

@ -630,6 +630,9 @@ Settings.cron_jobs['inactive_projects_deletion_cron_worker']['job_class'] = 'Pro
Settings.cron_jobs['loose_foreign_keys_cleanup_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['loose_foreign_keys_cleanup_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['cron'] ||= '*/1 * * * *' Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['job_class'] = 'LooseForeignKeys::CleanupWorker' Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['job_class'] = 'LooseForeignKeys::CleanupWorker'
Settings.cron_jobs['ci_runner_versions_reconciliation_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_runner_versions_reconciliation_worker']['cron'] ||= '20 * * * *'
Settings.cron_jobs['ci_runner_versions_reconciliation_worker']['job_class'] = 'Ci::Runners::ReconcileExistingRunnerVersionsCronWorker'
Gitlab.ee do Gitlab.ee do
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})

View File

@ -9,6 +9,7 @@ def get_ci_config_files(files)
end end
schema_path = 'app/assets/javascripts/editor/schema/ci.json' schema_path = 'app/assets/javascripts/editor/schema/ci.json'
schema_docs_path = 'https://docs.gitlab.com/ee/development/cicd/schema.html#write-specs'
has_schema_update = all_changed_files.include?(schema_path) has_schema_update = all_changed_files.include?(schema_path)
return if has_schema_update return if has_schema_update
@ -17,4 +18,4 @@ return if ci_config_files.empty?
file_list = "- #{ci_config_files.map { |path| "`#{path}`" }.join("\n- ")}" file_list = "- #{ci_config_files.map { |path| "`#{path}`" }.join("\n- ")}"
warn "This merge request changed CI config files but did not update the schema. Please consider updating [the schema](#{schema_path}) to reflect these changes:\n#{file_list}" warn "This merge request changed CI config files but did not update the schema. Please consider updating [the schema](#{schema_path}) to reflect these changes:\n#{file_list}.\n\nRefer to the [docs](#{schema_docs_path}) for help on how to run and write specs for the CI schema."

View File

@ -16419,6 +16419,7 @@ Represents a release.
| <a id="releasedescription"></a>`description` | [`String`](#string) | Description (also known as "release notes") of the release. | | <a id="releasedescription"></a>`description` | [`String`](#string) | Description (also known as "release notes") of the release. |
| <a id="releasedescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. | | <a id="releasedescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="releaseevidences"></a>`evidences` | [`ReleaseEvidenceConnection`](#releaseevidenceconnection) | Evidence for the release. (see [Connections](#connections)) | | <a id="releaseevidences"></a>`evidences` | [`ReleaseEvidenceConnection`](#releaseevidenceconnection) | Evidence for the release. (see [Connections](#connections)) |
| <a id="releasehistoricalrelease"></a>`historicalRelease` | [`Boolean`](#boolean) | Indicates the release is an historical release. |
| <a id="releaseid"></a>`id` | [`ReleaseID!`](#releaseid) | Global ID of the release. | | <a id="releaseid"></a>`id` | [`ReleaseID!`](#releaseid) | Global ID of the release. |
| <a id="releaselinks"></a>`links` | [`ReleaseLinks`](#releaselinks) | Links of the release. | | <a id="releaselinks"></a>`links` | [`ReleaseLinks`](#releaselinks) | Links of the release. |
| <a id="releasemilestones"></a>`milestones` | [`MilestoneConnection`](#milestoneconnection) | Milestones associated to the release. (see [Connections](#connections)) | | <a id="releasemilestones"></a>`milestones` | [`MilestoneConnection`](#milestoneconnection) | Milestones associated to the release. (see [Connections](#connections)) |
@ -18498,6 +18499,7 @@ Represents an assignees widget.
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="workitemwidgetassigneesallowsmultipleassignees"></a>`allowsMultipleAssignees` | [`Boolean`](#boolean) | Indicates whether multiple assignees are allowed. | | <a id="workitemwidgetassigneesallowsmultipleassignees"></a>`allowsMultipleAssignees` | [`Boolean`](#boolean) | Indicates whether multiple assignees are allowed. |
| <a id="workitemwidgetassigneesassignees"></a>`assignees` | [`UserCoreConnection`](#usercoreconnection) | Assignees of the work item. (see [Connections](#connections)) | | <a id="workitemwidgetassigneesassignees"></a>`assignees` | [`UserCoreConnection`](#usercoreconnection) | Assignees of the work item. (see [Connections](#connections)) |
| <a id="workitemwidgetassigneescaninvitemembers"></a>`canInviteMembers` | [`Boolean!`](#boolean) | Indicates whether the current user can invite members to the work item's project. |
| <a id="workitemwidgetassigneestype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | | <a id="workitemwidgetassigneestype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
### `WorkItemWidgetDescription` ### `WorkItemWidgetDescription`

View File

@ -141,16 +141,7 @@ created with one or more of the following options:
### Considerations for index names ### Considerations for index names
Index names don't have any significance in the database, so they should Check our [Constraints naming conventions](database/constraint_naming_convention.md) page.
attempt to communicate intent to others. The most important rule to
remember is that generic names are more likely to conflict or be duplicated,
and should not be used. Some other points to consider:
- For general indexes, use a template, like: `index_{table}_{column}_{options}`.
- For indexes added to solve a very specific problem, it may make sense
for the name to reflect their use.
- Identifiers in PostgreSQL have a maximum length of 63 bytes.
- Check `db/structure.sql` for conflicts and ideas.
### Why explicit names are required ### Why explicit names are required
@ -205,8 +196,7 @@ that would otherwise not be used.
In these cases, consider a temporary index. To specify a In these cases, consider a temporary index. To specify a
temporary index: temporary index:
1. Prefix the index name with `tmp_` and follow the [naming conventions](database/constraint_naming_convention.md) 1. Prefix the index name with `tmp_` and follow the [naming conventions](database/constraint_naming_convention.md).
and [requirements for naming indexes](#requirements-for-naming-indexes) for the rest of the name.
1. Create a follow-up issue to remove the index in the next (or future) milestone. 1. Create a follow-up issue to remove the index in the next (or future) milestone.
1. Add a comment in the migration mentioning the removal issue. 1. Add a comment in the migration mentioning the removal issue.

View File

@ -71,34 +71,34 @@ It picks reviewers and maintainers from the list at the
[engineering projects](https://about.gitlab.com/handbook/engineering/projects/) [engineering projects](https://about.gitlab.com/handbook/engineering/projects/)
page, with these behaviors: page, with these behaviors:
1. It doesn't pick people whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status): - It doesn't pick people whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status):
- Contains the string `OOO`, `PTO`, `Parental Leave`, or `Friends and Family`. - Contains the string `OOO`, `PTO`, `Parental Leave`, or `Friends and Family`.
- GitLab user **Busy** indicator is set to `True`. - GitLab user **Busy** indicator is set to `True`.
- Emoji is from one of these categories: - Emoji is from one of these categories:
- **On leave** - 🌴 `:palm_tree:`, 🏖️ `:beach:`, ⛱ `:beach_umbrella:`, 🏖 `:beach_with_umbrella:`, 🌞 `:sun_with_face:`, 🎡 `:ferris_wheel:` - **On leave** - 🌴 `:palm_tree:`, 🏖️ `:beach:`, ⛱ `:beach_umbrella:`, 🏖 `:beach_with_umbrella:`, 🌞 `:sun_with_face:`, 🎡 `:ferris_wheel:`
- **Out sick** - 🌡️ `:thermometer:`, 🤒 `:face_with_thermometer:` - **Out sick** - 🌡️ `:thermometer:`, 🤒 `:face_with_thermometer:`
- **At capacity** - 🔴 `:red_circle:` - **At capacity** - 🔴 `:red_circle:`
- **Focus mode** - 💡 `:bulb:` (focusing on their team's work) - **Focus mode** - 💡 `:bulb:` (focusing on their team's work)
1. It doesn't pick people who are already assigned a number of reviews that is equal to - It doesn't pick people who are already assigned a number of reviews that is equal to
or greater than their chosen "review limit". The review limit is the maximum number of or greater than their chosen "review limit". The review limit is the maximum number of
reviews people are ready to handle at a time. Set a review limit by using one of the following reviews people are ready to handle at a time. Set a review limit by using one of the following
as a Slack or [GitLab status](../user/profile/index.md#set-your-current-status): as a Slack or [GitLab status](../user/profile/index.md#set-your-current-status):
- 0⃣ - `:zero:` (similar to `:red_circle:`) - 0⃣ - `:zero:` (similar to `:red_circle:`)
- 1⃣ - `:one:` - 1⃣ - `:one:`
- 2⃣ - `:two:` - 2⃣ - `:two:`
- 3⃣ - `:three:` - 3⃣ - `:three:`
- 4⃣ - `:four:` - 4⃣ - `:four:`
- 5⃣ - `:five:` - 5⃣ - `:five:`
1. Team members whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status) emoji - Team members whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status) emoji
is 🔵 `:large_blue_circle:` are more likely to be picked. This applies to both reviewers and trainee maintainers. is 🔵 `:large_blue_circle:` are more likely to be picked. This applies to both reviewers and trainee maintainers.
- Reviewers with 🔵 `:large_blue_circle:` are two times as likely to be picked as other reviewers. - Reviewers with 🔵 `:large_blue_circle:` are two times as likely to be picked as other reviewers.
- [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer) with 🔵 `:large_blue_circle:` are three times as likely to be picked as other reviewers. - [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer) with 🔵 `:large_blue_circle:` are three times as likely to be picked as other reviewers.
1. People whose [GitLab status](../user/profile/index.md#set-your-current-status) emoji - People whose [GitLab status](../user/profile/index.md#set-your-current-status) emoji
is 🔶 `:large_orange_diamond:` or 🔸 `:small_orange_diamond:` are half as likely to be picked. is 🔶 `:large_orange_diamond:` or 🔸 `:small_orange_diamond:` are half as likely to be picked.
1. It always picks the same reviewers and maintainers for the same - It always picks the same reviewers and maintainers for the same
branch name (unless their out-of-office (`OOO`) status changes, as in point 1). It branch name (unless their out-of-office (`OOO`) status changes, as in point 1). It
removes leading `ce-` and `ee-`, and trailing `-ce` and `-ee`, so removes leading `ce-` and `ee-`, and trailing `-ce` and `-ee`, so
that it can be stable for backport branches. that it can be stable for backport branches.
The [Roulette dashboard](https://gitlab-org.gitlab.io/gitlab-roulette) contains: The [Roulette dashboard](https://gitlab-org.gitlab.io/gitlab-roulette) contains:

View File

@ -25,5 +25,7 @@ The intent is not to retroactively change names in existing databases but rather
## Observations ## Observations
- Check `db/structure.sql` for conflicts.
- Prefixes are preferred over suffices because they make it easier to identify the type of a given constraint quickly, as well as group them alphabetically; - Prefixes are preferred over suffices because they make it easier to identify the type of a given constraint quickly, as well as group them alphabetically;
- The `_and_` that joins column names can be omitted to keep the identifiers under the 63 characters' length limit defined by PostgreSQL. Additionally, the notation may be abbreviated to the best of our ability if struggling to keep under this limit. - The `_and_` that joins column names can be omitted to keep the identifiers under the 63 characters' length limit defined by PostgreSQL. Additionally, the notation may be abbreviated to the best of our ability if struggling to keep under this limit.
- For indexes added to solve a very specific problem, it may make sense for the name to reflect their use.

View File

@ -0,0 +1,151 @@
---
stage: Package
group: Package
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
---
# Debian Repository
This guide explains:
1. A basic overview of how Debian packages are structured
1. What package managers, clients, and tools are used to manage Debian packages
1. How the GitLab Debian repository functions
## Debian package basics
There are two types of [Debian packages](https://www.debian.org/doc/manuals/debian-faq/pkg-basics.en.html): binary and source.
- **Binary** - These are usually `.deb` files and contain executables, config files, and other data. A binary package must match your OS or architecture since it is already compiled. These are usually installed using `dpkg`. Dependencies must already exist on the system when installing a binary package.
- **Source** - These are usual made up of `.dsc` files and `.gz` files. A source package is compiled on your system. These are fetched and installed with [`apt`](https://manpages.debian.org/bullseye/apt/apt.8.en.html), which then uses `dpkg` after the package is compiled. When you use `apt`, it will fetch and install the necessary dependencies.
The `.deb` file follows the naming convention `<PackageName>_<VersionNumber>-<DebianRevisionNumber>_<DebianArchitecture>.deb`
It includes a `control file` that contains metadata about the package. You can view the control file by using `dpkg --info <deb_file>`
The [`.changes` file](https://www.debian.org/doc/debian-policy/ch-controlfields.html#debian-changes-files-changes) is used to tell the Debian repository how to process updates to packages. It contains a variety of metadata for the package, including architecture, distribution, and version. In addition to the metadata, they contain three lists of checksums: `sha1`, `sha256`, and `md5` in the `Files` section. Refer to [sample_1.2.3~alpha2_amd64.changes](https://gitlab.com/gitlab-org/gitlab/-/blob/dd1e70d3676891025534dc4a1e89ca9383178fe7/spec/fixtures/packages/debian/sample_1.2.3~alpha2_amd64.changes) for an example of how these files are structured.
## How do people get Debian packages?
While you can download a single `.deb` file and install it with [`dpkg`](https://manpages.debian.org/bullseye/dpkg/dpkg.1.en.html), most users consume Debian packages with [`apt`](https://manpages.debian.org/bullseye/apt/apt.8.en.html) using `apt-get`. `apt` wraps `dpkg`, adding dependency management and compilation.
## How do people publish Debian packages?
It is not uncommon to use `curl` to publish packages depending on the type of Debian repository you are working with. However, `dput-ng` is the best tool to use as it will upload the relevant files based on the `.changes` file.
## What is all this distribution business?
When it comes to Debian, packages don't exist on their own. They belong to a _distribution_. This can mean many things, but the main thing to note is users are used to having to specify the distribution.
## What does a Debian Repository look like?
- A [Debian repository](https://wiki.debian.org/DebianRepository) is made up of many releases.
- Each release is given a **codename**. For the public Debian repository, these are things like "bullseye" and "jesse".
- There is also the concept of **suites** which are essentially aliases of codenames synonymous with release channels like "stable" and "edge".
- Each release has many **components**. In the public repository, these are "main", "contrib", and "non-free".
- Each release has many **architectures** such as "amd64", "arm64", or "i386".
- Each release has a signed **Release** file (see below about [GPG signing](#what-are-gpg-keys-and-what-are-signed-releases))
A standard directory-based Debian repository would be organized as:
```plaintext
dists\
|--jessie/
|--bullseye\
|Changelog
|Release
|InRelease
|Release.gpg
|--main\
|--amd64\
|--arm64\
|--contrib\
|--non-free\
pool\
|--this is where the .deb files for all releases live
```
You can explore a mirror of the public Debian repository here: <http://ftp.us.debian.org/debian/>
In the public Debian repository, the entire directory structure, release files, GPG keys, and other files are all generated by a series of scripts called the [Debian Archive Kit, or dak](https://salsa.debian.org/ftp-team/dak).
In the GitLab Debian repository, we don't deal with specific file directories. Instead, we use code and an underlying [PostgreSQL database to organize the relationships](structure.md#debian-packages) between these different pieces.
## What does a Debian Repository do?
The Debian community created many package repository systems before things like object storage existed, and they used FTP to upload artifacts to a remote server. Most current package repositories and registries are just directories on a server somewhere. Packages added to the [official Debian distribution](https://www.debian.org/distrib/packages) exist in a central public repository that a group of open source maintainers curates. The package maintainers use the [Debian Archive Kit, or dak](https://salsa.debian.org/ftp-team/dak) scripts to generate release files and do other maintenance tasks. So, in addition to storing and serving files, a complete Debian repository needs to accomplish the same behavior that dak provides. This behavior is what the GitLab Debian registry aims to do.
## What are GPG keys, and what are signed releases
A [GPG key](https://www.gnupg.org/) is a public/private key pair for secure data transmission. Similar to an SSH key, there is a private and public key. Whoever has the _public key can encrypt data_, and whoever has the _private key can decrypt data_ that was encrypted using the public key. You can also use GPG keys to sign data. Whoever has the private key can sign data or a file, and whoever has the public key can then check the signature and trust it came from the person with the matching private key.
We use GPG to sign the release file for the Debian packages. The release file is an index of all packages within a given distribution and their respective digests.
In the GitLab Debian registry, a background process generates a new release file whenever a user publishes a new package to their Debian repository. A GPG key is created for each distribution. If a user requests a release for that distribution, they can request the signed version and the public GPG key to verify the authenticity of that release file.
## GitLab repository internals
When a [file upload](../../api/packages/debian.md#upload-a-package-file) occurs:
1. A new "incoming" package record is found or created. All new files are assigned to the "incoming" package. It is a holding area used until we know what package the file is actually associated with.
1. A new "unknown" file is stored. It is unknown because we do not yet know if this file belongs to an existing package or not.
Once we know which package the file belongs to, it is associated with that package, and the "incoming" package is removed if no more files remain. The "unknown" status of the file is updated to the correct file type.
Next, if the file is a `.changes` format:
1. The `.changes` file is parsed and any files listed within it are updated. All uploaded non-`.changes` files are correctly associated with various distributions and packages.
1. The `::Packages::Debian::GenerateDistributionWorker` and thus `::Packages::Debian::GenerateDistributionService` are run.
1. Component files are created or updated. Since we just updated package files that were listed in the `.changes` file, we now check the component/architecture files based on the changed checksum values.
1. A new release is generated:
1. A new GPG key is generated if one does not already exist for the distribution
1. A [Release file](https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files) is written, signed by the GPG key, and then stored.
1. Old component files are destroyed.
This diagram shows the path taken after a file is uploaded to the Debian API:
```mermaid
sequenceDiagram
Client->>+DebianProjectPackages: PUT projects/:id/packages/debian/:file_name
DebianProjectPackages->>+FindOrCreateIncomingService: Create "incoming" package
DebianProjectPackages->>+CreatePackageFileService: Create "unknown" file
Note over DebianProjectPackages: If `.changes` file
DebianProjectPackages->>+ProcessChangesWorker:
DebianProjectPackages->>+Client: 202 Created
ProcessChangesWorker->>+ProcessChangesService:
ProcessChangesService->>+ExtractChangesMetadataService:
ExtractChangesMetadataService->>+ExtractMetadataService:
ExtractMetadataService->>+ParseDebian822Service:
ExtractMetadataService->>+ExtractDebMetadataService: If .deb or .udeb
ExtractDebMetadataService->>+ParseDebian822Service: run `dpkg --field` to get control file
ParseDebian822Service-->>-ExtractDebMetadataService: Parse String as Debian RFC822 control data format
ExtractDebMetadataService-->>-ExtractMetadataService: Return the parsed control file
ExtractMetadataService->>+ParseDebian822Service: if .dsc, .changes, or buildinfo
ParseDebian822Service-->>-ExtractMetadataService: Parse String as Debian RFC822 control data format
ExtractMetadataService-->>-ExtractChangesMetadataService: Parse Metadata file
ExtractChangesMetadataService-->>-ProcessChangesService: Return list of files and hashes from the .changes file
loop process files listed in .changes
ProcessChangesService->>+ExtractMetadataService:
ExtractMetadataService->>+ParseDebian822Service:
ExtractMetadataService->>+ExtractDebMetadataService: If .deb or .udeb
ExtractDebMetadataService->>+ParseDebian822Service: run `dpkg --field` to get control file
ParseDebian822Service-->>-ExtractDebMetadataService: Parse String as Debian RFC822 control data format
ExtractDebMetadataService-->>-ExtractMetadataService: Return the parsed control file
ExtractMetadataService->>+ParseDebian822Service: if .dsc, .changes, or buildinfo
ParseDebian822Service-->>-ExtractMetadataService: Parse String as Debian RFC822 control data format
ExtractMetadataService-->>-ProcessChangesService: Use parsed metadata to update "unknown" (or known) file
end
ProcessChangesService->>+GenerateDistributionWorker:
GenerateDistributionWorker->>+GenerateDistributionService:
GenerateDistributionService->>+GenerateDistributionService: generate component files based on new archs and updates from .changes
GenerateDistributionService->>+GenerateDistributionKeyService: generate GPG key for distribution
GenerateDistributionKeyService-->>-GenerateDistributionService: GPG key
GenerateDistributionService-->>-GenerateDistributionService: Generate distribution file
GenerateDistributionService->>+SignDistributionService: Sign release file with GPG key
SignDistributionService-->>-GenerateDistributionService: Save the signed release file
GenerateDistributionWorker->>+GenerateDistributionService: destroy no longer used component files
```
### Distributions
You must create a distribution before publishing a package to it. When you create or update a distribution using the project or group distribution API, in addition to creating the initial backing records in the database, the `GenerateDistributionService` run as shown in the above sequence diagram.

View File

@ -39,7 +39,6 @@ erDiagram
projects }|--|| namespaces : "" projects }|--|| namespaces : ""
packages_packages }|--|| projects : "" packages_packages }|--|| projects : ""
packages_package_files }o--|| packages_packages : "" packages_package_files }o--|| packages_packages : ""
package_debian_file_metadatum |o--|| packages_package_files : ""
packages_debian_group_architectures }|--|| packages_debian_group_distributions : "" packages_debian_group_architectures }|--|| packages_debian_group_distributions : ""
packages_debian_group_component_files }|--|| packages_debian_group_components : "" packages_debian_group_component_files }|--|| packages_debian_group_components : ""
packages_debian_group_component_files }|--|| packages_debian_group_architectures : "" packages_debian_group_component_files }|--|| packages_debian_group_architectures : ""

View File

@ -77,29 +77,14 @@ microservices-based distributed systems - and displays results within GitLab.
- [Trace the performance and health](tracing.md) of a deployed application. - [Trace the performance and health](tracing.md) of a deployed application.
## Aggregate and store logs (DEPRECATED) **(FREE SELF)** <!--- start_remove The following content will be removed on remove_date: '2022-10-18'--->
> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7. ## Aggregate and store logs (removed) **(FREE SELF)**
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/360182) behind a [feature flag](../administration/feature_flags.md) named `monitor_logging` in GitLab 15.0. Disabled by default.
WARNING: This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/360193) in GitLab 15.2.
in GitLab 14.7.
It will be removed completely in GitLab 15.2.
FLAG: <!--- end_remove -->
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `monitor_logging`.
On GitLab.com, this feature is not available.
This feature is not recommended for production use.
Developers need to troubleshoot application changes in development, and incident
responders need aggregated, real-time logs when troubleshooting problems with
production services. GitLab provides centralized, aggregated log storage for your
distributed application, enabling you to collect logs across multiple services and
infrastructure.
- [View logs of pods](../user/project/clusters/kubernetes_pod_logs.md)
in connected Kubernetes clusters.
## Manage your infrastructure in code ## Manage your infrastructure in code

View File

@ -135,9 +135,6 @@ The options are:
- **Expand panel** - Displays a larger version of a visualization. To return to - **Expand panel** - Displays a larger version of a visualization. To return to
the dashboard, select the **Back** button in your browser, or press the <kbd>Escape</kbd> key. the dashboard, select the **Back** button in your browser, or press the <kbd>Escape</kbd> key.
([Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3100) in GitLab 13.0.) ([Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3100) in GitLab 13.0.)
- **View logs** **(ULTIMATE)** - Displays [Logs](../../../user/project/clusters/kubernetes_pod_logs.md),
if they are enabled. If used in conjunction with the [timeline zoom](#timeline-zoom-and-url-sharing)
feature, logs narrow down to the selected time range. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/122013) in GitLab 12.8.)
- **Download CSV** - Data from Prometheus charts on the metrics dashboard can be downloaded as CSV. - **Download CSV** - Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
- [Copy link to chart](../embed.md#embedding-gitlab-managed-kubernetes-metrics) - [Copy link to chart](../embed.md#embedding-gitlab-managed-kubernetes-metrics)

View File

@ -65,4 +65,3 @@ responsibility. The Application Development Platform integrates key performance
into GitLab, automatically. The following features are included: into GitLab, automatically. The following features are included:
- [Auto Monitoring](../autodevops/stages.md#auto-monitoring) - [Auto Monitoring](../autodevops/stages.md#auto-monitoring)
- [In-app Kubernetes Logs](../../user/project/clusters/kubernetes_pod_logs.md)

View File

@ -43,22 +43,17 @@ This workflow is considered push-based, because GitLab is pushing requests from
GitLab supports the following Kubernetes versions. You can upgrade your GitLab supports the following Kubernetes versions. You can upgrade your
Kubernetes version to a supported version at any time: Kubernetes version to a supported version at any time:
- 1.24 (support ends on September 22, 2023) - 1.24 (support ends on September 22, 2023 or when 1.27 becomes supported)
- 1.23 (support ends on February 22, 2023) - 1.23 (support ends on February 22, 2023 or when 1.26 becomes supported)
- 1.22 (support ends on October 22, 2022) - 1.22 (support ends on October 22, 2022)
- 1.21 (support ends on September 22, 2022) - 1.21 (support ends on August 22, 2022)
GitLab supports at least two production-ready Kubernetes minor GitLab aims to support a new minor Kubernetes version three months after its initial release. GitLab supports at least three production-ready Kubernetes minor
versions at any given time. GitLab regularly reviews the supported versions and versions at any given time.
provides a three-month deprecation period before removing support for a specific
version. The list of supported versions is based on:
- The versions supported by major managed Kubernetes providers. Support for deprecated APIs can be removed from the GitLab codebase when we drop support for the Kubernetes version that only supports the deprecated API.
- The versions [supported by the Kubernetes community](https://kubernetes.io/releases/version-skew-policy/#supported-versions).
[This epic](https://gitlab.com/groups/gitlab-org/-/epics/4827) tracks support for other Kubernetes versions. Some GitLab features might work on versions not listed here. [This epic](https://gitlab.com/groups/gitlab-org/-/epics/4827) tracks support for Kubernetes versions.
Some GitLab features might work on versions not listed here.
## Migrate to the agent from the legacy certificate-based integration ## Migrate to the agent from the legacy certificate-based integration

View File

@ -34,6 +34,7 @@ To create an epic in the group you're in:
- To [make the epic confidential](#make-an-epic-confidential), select the checkbox under **Confidentiality**. - To [make the epic confidential](#make-an-epic-confidential), select the checkbox under **Confidentiality**.
- Choose labels. - Choose labels.
- Select a start and due date, or [inherit](#start-and-due-date-inheritance) them. - Select a start and due date, or [inherit](#start-and-due-date-inheritance) them.
- Select a [color](#epic-color).
1. Select **Create epic**. 1. Select **Create epic**.
The newly created epic opens. The newly created epic opens.
@ -62,6 +63,18 @@ Because the epic's dates can inherit dates from its children, the start date and
If the start date of a child epic on the lowest level changes, that becomes the earliest possible start date for its parent epic. If the start date of a child epic on the lowest level changes, that becomes the earliest possible start date for its parent epic.
The parent epic's start date then reflects this change and propagates upwards to the top epic. The parent epic's start date then reflects this change and propagates upwards to the top epic.
### Epic color
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79940) in GitLab 14.9 [with a flag](../../../administration/feature_flags.md) named `epic_color_highlight`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `epic_color_highlight`.
On GitLab.com, this feature is available but can be configured by GitLab.com administrators only.
The feature is not ready for production use.
When you create or edit an epic, you can select its color.
An epic's color is shown in [roadmaps](../roadmap/index.md), and [epic boards](epic_boards.md).
## Edit an epic ## Edit an epic
After you create an epic, you can edit the following details: After you create an epic, you can edit the following details:
@ -71,6 +84,7 @@ After you create an epic, you can edit the following details:
- Start date - Start date
- Due date - Due date
- Labels - Labels
- [Color](#epic-color)
Prerequisites: Prerequisites:

View File

@ -59,7 +59,6 @@ This feature flag re-enables the certificate-based Kubernetes integration.
- [Cluster environments](../../clusters/environments.md) - [Cluster environments](../../clusters/environments.md)
- [Show Canary Ingress deployments on deploy boards](../../project/canary_deployments.md#show-canary-ingress-deployments-on-deploy-boards-deprecated) - [Show Canary Ingress deployments on deploy boards](../../project/canary_deployments.md#show-canary-ingress-deployments-on-deploy-boards-deprecated)
- [Deploy Boards](../../project/deploy_boards.md) - [Deploy Boards](../../project/deploy_boards.md)
- [Pod logs](../../project/clusters/kubernetes_pod_logs.md)
- [Clusters health](manage/clusters_health.md) - [Clusters health](manage/clusters_health.md)
- [Web terminals](../../../administration/integration/terminal.md) - [Web terminals](../../../administration/integration/terminal.md)

View File

@ -69,6 +69,11 @@ Once built, a chart can be uploaded to the desired channel with `curl` or `helm
- `<project_id>`: the project ID (like `42`). - `<project_id>`: the project ID (like `42`).
- `<channel>`: the name of the channel (like `stable`). - `<channel>`: the name of the channel (like `stable`).
### Release channels
You can publish Helm charts to channels in GitLab. Channels are a method you can use to differentiate Helm chart repositories.
For example, you can use `stable` and `devel` as channels to allow users to add the `stable` repo while `devel` charts are isolated.
## Use CI/CD to publish a Helm package ## Use CI/CD to publish a Helm package
To publish a Helm package automated through [GitLab CI/CD](../../../ci/index.md), you can use To publish a Helm package automated through [GitLab CI/CD](../../../ci/index.md), you can use

View File

@ -69,7 +69,6 @@ The following table lists project permissions available for each role:
| [Application security](application_security/index.md):<br>View [dependency list](application_security/dependency_list/index.md) | | | ✓ | ✓ | ✓ | | [Application security](application_security/index.md):<br>View [dependency list](application_security/dependency_list/index.md) | | | ✓ | ✓ | ✓ |
| [Application security](application_security/index.md):<br>Create a [CVE ID Request](application_security/cve_id_request.md) | | | | ✓ | ✓ | | [Application security](application_security/index.md):<br>Create a [CVE ID Request](application_security/cve_id_request.md) | | | | ✓ | ✓ |
| [Application security](application_security/index.md):<br>Create or assign [security policy project](application_security/policies/index.md) | | | | | ✓ | | [Application security](application_security/index.md):<br>Create or assign [security policy project](application_security/policies/index.md) | | | | | ✓ |
| [Clusters](infrastructure/clusters/index.md):<br>View [pod logs](project/clusters/kubernetes_pod_logs.md) | | | ✓ | ✓ | ✓ |
| [Clusters](infrastructure/clusters/index.md):<br>View clusters | | | ✓ | ✓ | ✓ | | [Clusters](infrastructure/clusters/index.md):<br>View clusters | | | ✓ | ✓ | ✓ |
| [Clusters](infrastructure/clusters/index.md):<br>Manage clusters | | | | ✓ | ✓ | | [Clusters](infrastructure/clusters/index.md):<br>Manage clusters | | | | ✓ | ✓ |
| [Container Registry](packages/container_registry/index.md):<br>Create, edit, delete [cleanup policies](packages/container_registry/index.md#delete-images-by-using-a-cleanup-policy) | | | | ✓ | ✓ | | [Container Registry](packages/container_registry/index.md):<br>Create, edit, delete [cleanup policies](packages/container_registry/index.md#delete-images-by-using-a-cleanup-policy) | | | | ✓ | ✓ |

View File

@ -2,85 +2,11 @@
stage: Monitor stage: Monitor
group: Respond group: Respond
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 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
remove_date: '2022-18-10'
redirect_to: '../../clusters/agent/index.md'
--- ---
# Kubernetes Logs (DEPRECATED) **(FREE SELF)** # Kubernetes Logs (removed) **(FREE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4752) in GitLab 11.0. This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26383) from GitLab Ultimate to GitLab Free 12.9. and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/360193) in GitLab 15.2.
> - [Deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/360182) behind a [feature flag](../../../administration/feature_flags.md) named `monitor_logging` in GitLab 15.0. Disabled by default.
> - [Disabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/353410) in GitLab 15.0.
WARNING:
This feature is in its end-of-life process.
This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
It will be [removed completely](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 15.2.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `monitor_logging` and the one named `certificate_based_clusters`.
On GitLab.com, this feature is not available.
This feature is not recommended for production use.
GitLab makes it easy to view the logs of running pods in
[connected Kubernetes clusters](index.md). By displaying the logs directly in GitLab
in the **Log Explorer**, developers can avoid managing console tools or jumping
to a different interface. The **Log Explorer** interface provides a set of filters
above the log file data, depending on your configuration:
![Pod logs](img/kubernetes_pod_logs_v12_10.png)
- **Namespace** - Select the environment to display. Users with Maintainer or
greater [permissions](../../permissions.md) can also see pods in the
`gitlab-managed-apps` namespace.
- **Scroll to bottom** **{scroll_down}** - Scroll to the end of the displayed logs.
- **Refresh** **{retry}** - Reload the displayed logs.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
To learn more about the Log Explorer, see [APM - Log Explorer](https://www.youtube.com/watch?v=hWclZHA7Dgw).
[Learn more about Kubernetes + GitLab](https://about.gitlab.com/solutions/kubernetes/).
Everything you need to build, test, deploy, and run your application at scale.
## Requirements
[Deploying to a Kubernetes environment](../deploy_boards.md#enabling-deploy-boards)
is required to use Logs.
## Accessing the log explorer
To access the **Log explorer**, select the **More actions** **{ellipsis_v}** menu on
a [metrics dashboard](../../../operations/metrics/index.md) and select **View logs**, or:
1. Sign in as a user with the _View pod logs_
[permissions](../../permissions.md#project-members-permissions) in the project.
1. To navigate to the **Log Explorer** from the sidebar menu, go to **Monitor > Logs**
([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22011) in GitLab 12.5.).
1. To navigate to the **Log Explorer** from a specific pod on a [deploy board](../deploy_boards.md):
1. Go to **Deployments > Environments** and find the environment
which contains the desired pod, like `production`.
1. On the **Environments** page, you should see the status of the environment's
pods with [deploy boards](../deploy_boards.md).
1. When mousing over the list of pods, GitLab displays a tooltip with the exact pod name
and status.
![deploy boards pod list](img/pod_logs_deploy_board.png)
1. Select the desired pod to display the **Log Explorer**.
### Logs view
The **Log Explorer** lets you filter the logs by:
- Pods.
- [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/issues/5769), environments.
- [From GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/197879), dates.
- [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/208790), managed apps.
Loading more than 500 log lines is possible from
[GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/198050) onward.
Support for pods with multiple containers is coming
[in a future release](https://gitlab.com/gitlab-org/gitlab/-/issues/13404).
Support for historical data is coming
[in a future release](https://gitlab.com/gitlab-org/gitlab/-/issues/196191).

View File

@ -24,6 +24,13 @@ members.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) in GitLab 14.9. > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) in GitLab 14.9.
[Feature flag `invite_members_group_modal`](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) removed. [Feature flag `invite_members_group_modal`](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) removed.
You can share a project only with:
- Groups for which you have an explicitly defined [membership](index.md).
- Groups that contain a nested subgroup or project for which you have an explicitly defined role.
Administrators can share projects with any group in the namespace.
The primary mechanism to give a group of users, say 'Engineering', access to a project, The primary mechanism to give a group of users, say 'Engineering', access to a project,
say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project
Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'? Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'?
@ -42,12 +49,11 @@ After sharing 'Project Acme' with 'Engineering':
- The group is listed in the **Groups** tab. - The group is listed in the **Groups** tab.
- The project is listed on the group dashboard. - The project is listed on the group dashboard.
You can share a project only with: When you share a project, be aware of the following restrictions and outcomes:
- Groups for which you have an explicitly defined membership. - [Maximum access level](#maximum-access-level)
- Groups that contain a nested subgroup or project for which you have an explicitly defined role. - [Sharing a public project with a private group](#share-a-public-project-with-private-group)
- [Sharing project with group lock](#share-project-with-group-lock)
Administrators can share projects with any group in the system.
## Maximum access level ## Maximum access level
@ -61,9 +67,13 @@ in. That means you can only share down the hierarchy. For example, `group/subgro
- Can not be shared with `group`. - Can not be shared with `group`.
- Can be shared with `group/subgroup02` or `group/subgroup01/subgroup03`. - Can be shared with `group/subgroup02` or `group/subgroup01/subgroup03`.
## Share public project with private group ## Share a public project with private group
When sharing a public project with a private group, owners and maintainers of the project see the name of the group in the `members` page. Owners also have the possibility to see members of the private group they don't have access to when mentioning them in the issue or merge request. When you share a public project with a private group, be aware of the following outcomes:
- The name of the group is no longer private and is visible to all users in the project members page.
- Owners of the project have access to members of the private group when they mention them in issues or merge requests.
- Project members who are direct or indirect members of the private group can see private group members listed in addition to members of the project.
## Share project with group lock ## Share project with group lock

View File

@ -250,11 +250,7 @@ module Gitlab
end end
def running_jobs_relation(job) def running_jobs_relation(job)
if ::Feature.enabled?(:ci_pending_builds_maintain_denormalized_data) ::Ci::RunningBuild.instance_type.where(project_id: job.project_id)
::Ci::RunningBuild.instance_type.where(project_id: job.project_id)
else
job.project.builds.running.where(runner: ::Ci::Runner.instance_type)
end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end

View File

@ -8,7 +8,7 @@ module Gitlab
def check_runner_upgrade_status(runner_version) def check_runner_upgrade_status(runner_version)
runner_version = ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true) runner_version = ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true)
return :invalid unless runner_version.valid? return :invalid_version unless runner_version.valid?
releases = RunnerReleases.instance.releases releases = RunnerReleases.instance.releases
return :error unless releases return :error unless releases

View File

@ -63,10 +63,7 @@ module Gitlab
def tree_entries_with_flat_path_from_rugged(repository, sha, path, recursive) def tree_entries_with_flat_path_from_rugged(repository, sha, path, recursive)
tree_entries_from_rugged(repository, sha, path, recursive).tap do |entries| tree_entries_from_rugged(repository, sha, path, recursive).tap do |entries|
# This was an optimization to reduce N+1 queries for Gitaly # This was an optimization to reduce N+1 queries for Gitaly
# (https://gitlab.com/gitlab-org/gitaly/issues/530). It # (https://gitlab.com/gitlab-org/gitaly/issues/530).
# used to be done lazily in the view via
# TreeHelper#flatten_tree, so it's possible there's a
# performance impact by loading this eagerly.
rugged_populate_flat_path(repository, sha, path, entries) rugged_populate_flat_path(repository, sha, path, entries)
end end
end end

View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Importer
module Events
class Renamed
def initialize(project, user_id)
@project = project
@user_id = user_id
end
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`
def execute(issue_event)
Note.create!(note_params(issue_event))
end
private
attr_reader :project, :user_id
def note_params(issue_event)
{
noteable_id: issue_event.issue_db_id,
noteable_type: Issue.name,
project_id: project.id,
author_id: user_id,
note: parse_body(issue_event),
system: true,
created_at: issue_event.created_at,
updated_at: issue_event.created_at,
system_note_metadata: SystemNoteMetadata.new(
{
action: "title",
created_at: issue_event.created_at,
updated_at: issue_event.created_at
}
)
}
end
def parse_body(issue_event)
old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(
issue_event.old_title, issue_event.new_title
).inline_diffs
marked_old_title = Gitlab::Diff::InlineDiffMarkdownMarker.new(issue_event.old_title).mark(old_diffs)
marked_new_title = Gitlab::Diff::InlineDiffMarkdownMarker.new(issue_event.new_title).mark(new_diffs)
"changed title from **#{marked_old_title}** to **#{marked_new_title}**"
end
end
end
end
end
end

View File

@ -27,6 +27,9 @@ module Gitlab
when 'labeled', 'unlabeled' when 'labeled', 'unlabeled'
Gitlab::GithubImport::Importer::Events::ChangedLabel.new(project, author_id) Gitlab::GithubImport::Importer::Events::ChangedLabel.new(project, author_id)
.execute(issue_event) .execute(issue_event)
when 'renamed'
Gitlab::GithubImport::Importer::Events::Renamed.new(project, author_id)
.execute(issue_event)
else else
Gitlab::GithubImport::Logger.debug( Gitlab::GithubImport::Logger.debug(
message: 'UNSUPPORTED_EVENT_TYPE', message: 'UNSUPPORTED_EVENT_TYPE',

View File

@ -9,7 +9,7 @@ module Gitlab
attr_reader :attributes attr_reader :attributes
expose_attribute :id, :actor, :event, :commit_id, :label_title, :created_at expose_attribute :id, :actor, :event, :commit_id, :label_title, :old_title, :new_title, :created_at
expose_attribute :issue_db_id # set in SingleEndpointIssueEventsImporter#each_associated expose_attribute :issue_db_id # set in SingleEndpointIssueEventsImporter#each_associated
# Builds a event from a GitHub API response. # Builds a event from a GitHub API response.
@ -22,6 +22,8 @@ module Gitlab
event: event.event, event: event.event,
commit_id: event.commit_id, commit_id: event.commit_id,
label_title: event.label && event.label[:name], label_title: event.label && event.label[:name],
old_title: event.rename && event.rename[:from],
new_title: event.rename && event.rename[:to],
issue_db_id: event.issue_db_id, issue_db_id: event.issue_db_id,
created_at: event.created_at created_at: event.created_at
) )
@ -30,7 +32,7 @@ module Gitlab
# Builds a event using a Hash that was built from a JSON payload. # Builds a event using a Hash that was built from a JSON payload.
def self.from_json_hash(raw_hash) def self.from_json_hash(raw_hash)
hash = Representation.symbolize_hash(raw_hash) hash = Representation.symbolize_hash(raw_hash)
hash[:actor] = Representation::User.from_json_hash(hash[:actor]) if hash[:actor] hash[:actor] &&= Representation::User.from_json_hash(hash[:actor])
new(hash) new(hash)
end end

View File

@ -8,10 +8,7 @@ module Gitlab
CACHE_EXPIRE_IN = 1.hour CACHE_EXPIRE_IN = 1.hour
MAX_OFFSET = 2**31 MAX_OFFSET = 2**31
attr_reader :commit, :project, :path, :offset, :limit, :user attr_reader :commit, :project, :path, :offset, :limit, :user, :resolved_commits
attr_reader :resolved_commits
private :resolved_commits
def initialize(commit, project, user, params = {}) def initialize(commit, project, user, params = {})
@commit = commit @commit = commit
@ -34,44 +31,37 @@ module Gitlab
# #
# - An Array of Hashes containing the following keys: # - An Array of Hashes containing the following keys:
# - file_name: The full path of the tree entry # - file_name: The full path of the tree entry
# - type: One of :blob, :tree, or :submodule
# - commit: The last ::Commit to touch this entry in the tree # - commit: The last ::Commit to touch this entry in the tree
# - commit_path: URI of the commit in the web interface # - commit_path: URI of the commit in the web interface
# - An Array of the unique ::Commit objects in the first value # - commit_title_html: Rendered commit title
def summarize def summarize
summary = contents commits_hsh = fetch_last_cached_commits_list
.tap { |summary| fill_last_commits!(summary) } prerender_commit_full_titles!(commits_hsh.values)
[summary, commits] commits_hsh.map do |path_key, commit|
commit = cache_commit(commit)
{
file_name: File.basename(path_key).force_encoding(Encoding::UTF_8),
commit: commit,
commit_path: commit_path(commit),
commit_title_html: markdown_field(commit, :full_title)
}
end
end end
def fetch_logs def fetch_logs
logs, _ = summarize logs = summarize
new_offset = next_offset if more? [logs.first(limit).as_json, next_offset(logs.size)]
[logs.as_json, new_offset]
end
# Does the tree contain more entries after the given offset + limit?
def more?
all_contents[next_offset].present?
end
# The offset of the next batch of tree entries. If more? returns false, this
# batch will be empty
def next_offset
[all_contents.size + 1, offset + limit].min
end end
private private
def contents def next_offset(entries_count)
all_contents[offset, limit] || [] return if entries_count <= limit
end
def commits offset + limit
resolved_commits.values
end end
def repository def repository
@ -83,32 +73,12 @@ module Gitlab
File.join(*[path, ""]) if path File.join(*[path, ""]) if path
end end
def entry_path(entry)
File.join(*[path, entry[:file_name]].compact).force_encoding(Encoding::ASCII_8BIT)
end
def fill_last_commits!(entries)
commits_hsh = fetch_last_cached_commits_list
prerender_commit_full_titles!(commits_hsh.values)
entries.each do |entry|
path_key = entry_path(entry)
commit = cache_commit(commits_hsh[path_key])
if commit
entry[:commit] = commit
entry[:commit_path] = commit_path(commit)
entry[:commit_title_html] = markdown_field(commit, :full_title)
end
end
end
def fetch_last_cached_commits_list def fetch_last_cached_commits_list
cache_key = ['projects', project.id, 'last_commits', commit.id, ensured_path, offset, limit] cache_key = ['projects', project.id, 'last_commits', commit.id, ensured_path, offset, limit + 1]
commits = Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRE_IN) do commits = Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRE_IN) do
repository repository
.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit, literal_pathspec: true) .list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit + 1, literal_pathspec: true)
.transform_values! { |commit| commit_to_hash(commit) } .transform_values! { |commit| commit_to_hash(commit) }
end end
@ -131,26 +101,6 @@ module Gitlab
Gitlab::Routing.url_helpers.project_commit_path(project, commit) Gitlab::Routing.url_helpers.project_commit_path(project, commit)
end end
def all_contents
strong_memoize(:all_contents) { cached_contents }
end
def cached_contents
cache_key = ['projects', project.id, 'content', commit.id, path]
Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRE_IN) do
[
*tree.trees,
*tree.blobs,
*tree.submodules
].map { |entry| { file_name: entry.name, type: entry.type } }
end
end
def tree
strong_memoize(:tree) { repository.tree(commit.id, path) }
end
def prerender_commit_full_titles!(commits) def prerender_commit_full_titles!(commits)
# Preload commit authors as they are used in rendering # Preload commit authors as they are used in rendering
commits.each(&:lazy_author) commits.each(&:lazy_author)

View File

@ -9669,6 +9669,9 @@ msgstr ""
msgid "Configure a %{codeStart}.gitlab-webide.yml%{codeEnd} file in the %{codeStart}.gitlab%{codeEnd} directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}" msgid "Configure a %{codeStart}.gitlab-webide.yml%{codeEnd} file in the %{codeStart}.gitlab%{codeEnd} directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
msgstr "" msgstr ""
msgid "Configure advanced permissions, Large File Storage, two-factor authentication, and CI/CD settings."
msgstr ""
msgid "Configure advanced permissions, Large File Storage, two-factor authentication, and customer relations settings." msgid "Configure advanced permissions, Large File Storage, two-factor authentication, and customer relations settings."
msgstr "" msgstr ""

View File

@ -200,9 +200,9 @@
"yaml": "^2.0.0-10" "yaml": "^2.0.0-10"
}, },
"devDependencies": { "devDependencies": {
"@gitlab/eslint-plugin": "13.0.0", "@gitlab/eslint-plugin": "13.1.0",
"@gitlab/stylelint-config": "4.1.0", "@gitlab/stylelint-config": "4.1.0",
"@graphql-eslint/eslint-plugin": "3.10.4", "@graphql-eslint/eslint-plugin": "3.10.5",
"@testing-library/dom": "^7.16.2", "@testing-library/dom": "^7.16.2",
"@types/jest": "^26.0.24", "@types/jest": "^26.0.24",
"@vue/test-utils": "1.3.0", "@vue/test-utils": "1.3.0",

View File

@ -178,7 +178,8 @@ RSpec.describe RegistrationsController do
category: 'RegistrationsController', category: 'RegistrationsController',
action: 'accepted', action: 'accepted',
label: 'invite_email', label: 'invite_email',
property: member.id.to_s property: member.id.to_s,
user: member.reload.user
) )
end end
end end

View File

@ -221,7 +221,8 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
category: 'RegistrationsController', category: 'RegistrationsController',
action: 'accepted', action: 'accepted',
label: 'invite_email', label: 'invite_email',
property: group_invite.id.to_s property: group_invite.id.to_s,
user: group_invite.reload.user
) )
end end
end end

View File

@ -357,6 +357,10 @@ RSpec.describe 'Pipelines', :js do
end end
it 'enqueues the delayed job', :js do it 'enqueues the delayed job', :js do
find('[data-testid="mini-pipeline-graph-dropdown"]').click
within('[data-testid="mini-pipeline-graph-dropdown"]') { find('.ci-status-icon-pending') }
expect(delayed_job.reload).to be_pending expect(delayed_job.reload).to be_pending
end end
end end

View File

@ -27,9 +27,9 @@ RSpec.describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :c
render_views render_views
it 'deploy_keys/keys.json' do it 'deploy_keys/keys.json' do
create(:rsa_deploy_key_2048, public: true) create(:rsa_deploy_key_5120, public: true)
project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com') project_key = create(:deploy_key)
internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com') internal_key = create(:deploy_key)
create(:deploy_keys_project, project: project, deploy_key: project_key) create(:deploy_keys_project, project: project, deploy_key: project_key)
create(:deploy_keys_project, project: project2, deploy_key: internal_key) create(:deploy_keys_project, project: project2, deploy_key: internal_key)
create(:deploy_keys_project, project: project3, deploy_key: project_key) create(:deploy_keys_project, project: project3, deploy_key: project_key)

View File

@ -16,19 +16,18 @@ const mockData = [
commit_path: `https://test.com`, commit_path: `https://test.com`,
commit_title_html: 'commit title', commit_title_html: 'commit title',
file_name: 'index.js', file_name: 'index.js',
type: 'blob',
}, },
]; ];
describe('resolveCommit', () => { describe('resolveCommit', () => {
it('calls resolve when commit found', () => { it('calls resolve when commit found', () => {
const resolver = { const resolver = {
entry: { name: 'index.js', type: 'blob' }, entry: { name: 'index.js' },
resolve: jest.fn(), resolve: jest.fn(),
}; };
const commits = [ const commits = [
{ fileName: 'index.js', filePath: '/index.js', type: 'blob' }, { fileName: 'index.js', filePath: '/index.js' },
{ fileName: 'index.js', filePath: '/app/assets/index.js', type: 'blob' }, { fileName: 'index.js', filePath: '/app/assets/index.js' },
]; ];
resolveCommit(commits, '', resolver); resolveCommit(commits, '', resolver);
@ -36,7 +35,6 @@ describe('resolveCommit', () => {
expect(resolver.resolve).toHaveBeenCalledWith({ expect(resolver.resolve).toHaveBeenCalledWith({
fileName: 'index.js', fileName: 'index.js',
filePath: '/index.js', filePath: '/index.js',
type: 'blob',
}); });
}); });
}); });
@ -56,7 +54,7 @@ describe('fetchLogsTree', () => {
global.gon = { relative_url_root: '' }; global.gon = { relative_url_root: '' };
resolver = { resolver = {
entry: { name: 'index.js', type: 'blob' }, entry: { name: 'index.js' },
resolve: jest.fn(), resolve: jest.fn(),
}; };
@ -119,7 +117,6 @@ describe('fetchLogsTree', () => {
filePath: '/index.js', filePath: '/index.js',
message: 'testing message', message: 'testing message',
sha: '123', sha: '123',
type: 'blob',
}), }),
); );
})); }));
@ -136,7 +133,6 @@ describe('fetchLogsTree', () => {
message: 'testing message', message: 'testing message',
sha: '123', sha: '123',
titleHtml: 'commit title', titleHtml: 'commit title',
type: 'blob',
}), }),
], ],
}); });

View File

@ -10,7 +10,6 @@ const mockData = [
commit_path: `https://test.com`, commit_path: `https://test.com`,
commit_title_html: 'testing message', commit_title_html: 'testing message',
file_name: 'index.js', file_name: 'index.js',
type: 'blob',
}, },
]; ];
@ -24,7 +23,6 @@ describe('normalizeData', () => {
commitPath: 'https://test.com', commitPath: 'https://test.com',
fileName: 'index.js', fileName: 'index.js',
filePath: '/index.js', filePath: '/index.js',
type: 'blob',
titleHtml: 'testing message', titleHtml: 'testing message',
__typename: 'LogTreeCommit', __typename: 'LogTreeCommit',
}, },

View File

@ -9,6 +9,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/runner/components/runner_header.vue'; import RunnerHeader from '~/runner/components/runner_header.vue';
import RunnerDetails from '~/runner/components/runner_details.vue';
import RunnerPauseButton from '~/runner/components/runner_pause_button.vue'; import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue'; import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
import RunnerEditButton from '~/runner/components/runner_edit_button.vue'; import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
@ -37,6 +38,7 @@ describe('AdminRunnerShowApp', () => {
let mockRunnerQuery; let mockRunnerQuery;
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader); const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
const findRunnerDeleteButton = () => wrapper.findComponent(RunnerDeleteButton); const findRunnerDeleteButton = () => wrapper.findComponent(RunnerDeleteButton);
const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton); const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton);
const findRunnerPauseButton = () => wrapper.findComponent(RunnerPauseButton); const findRunnerPauseButton = () => wrapper.findComponent(RunnerPauseButton);
@ -179,12 +181,32 @@ describe('AdminRunnerShowApp', () => {
}); });
}); });
describe('When loading', () => {
beforeEach(() => {
mockRunnerQueryResult();
createComponent();
});
it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
it('does not show runner jobs', () => {
expect(findRunnersJobs().exists()).toBe(false);
});
});
describe('When there is an error', () => { describe('When there is an error', () => {
beforeEach(async () => { beforeEach(async () => {
mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!')); mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
await createComponent(); await createComponent();
}); });
it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
it('error is reported to sentry', () => { it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({ expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'), error: new Error('Error!'),
@ -201,13 +223,6 @@ describe('AdminRunnerShowApp', () => {
const stubs = { const stubs = {
GlTab, GlTab,
GlTabs, GlTabs,
RunnerDetails: {
template: `
<div>
<slot name="jobs-tab"></slot>
</div>
`,
},
}; };
it('without a runner, shows no jobs', () => { it('without a runner, shows no jobs', () => {

View File

@ -25,12 +25,7 @@ describe('RunnerDetails', () => {
const findDetailGroups = () => wrapper.findComponent(RunnerGroups); const findDetailGroups = () => wrapper.findComponent(RunnerGroups);
const createComponent = ({ const createComponent = ({ props = {}, stubs, mountFn = shallowMountExtended } = {}) => {
props = {},
stubs,
mountFn = shallowMountExtended,
...options
} = {}) => {
wrapper = mountFn(RunnerDetails, { wrapper = mountFn(RunnerDetails, {
propsData: { propsData: {
...props, ...props,
@ -39,7 +34,6 @@ describe('RunnerDetails', () => {
RunnerDetail, RunnerDetail,
...stubs, ...stubs,
}, },
...options,
}); });
}; };
@ -47,16 +41,6 @@ describe('RunnerDetails', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('when no runner is present, no contents are shown', () => {
createComponent({
props: {
runner: null,
},
});
expect(wrapper.text()).toBe('');
});
describe('Details tab', () => { describe('Details tab', () => {
describe.each` describe.each`
field | runner | expectedValue field | runner | expectedValue
@ -141,18 +125,4 @@ describe('RunnerDetails', () => {
}); });
}); });
}); });
describe('Jobs tab slot', () => {
it('shows job tab slot', () => {
const JOBS_TAB = '<div>Jobs Tab</div>';
createComponent({
slots: {
'jobs-tab': JOBS_TAB,
},
});
expect(wrapper.html()).toContain(JOBS_TAB);
});
});
}); });

View File

@ -177,12 +177,28 @@ describe('GroupRunnerShowApp', () => {
}); });
}); });
describe('When loading', () => {
beforeEach(() => {
mockRunnerQueryResult();
createComponent();
});
it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
});
describe('When there is an error', () => { describe('When there is an error', () => {
beforeEach(async () => { beforeEach(async () => {
mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!')); mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
await createComponent(); await createComponent();
}); });
it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
it('error is reported to sentry', () => { it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({ expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'), error: new Error('Error!'),

View File

@ -11,7 +11,8 @@ RSpec.describe GitlabSchema.types['Release'] do
description description_html description description_html
name milestones evidences author commit name milestones evidences author commit
assets links assets links
created_at released_at created_at released_at upcoming_release
historical_release
] ]
expect(described_class).to include_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Types::WorkItems::Widgets::AssigneesType do RSpec.describe Types::WorkItems::Widgets::AssigneesType do
it 'exposes the expected fields' do it 'exposes the expected fields' do
expected_fields = %i[assignees allows_multiple_assignees type] expected_fields = %i[assignees allows_multiple_assignees can_invite_members type]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)
end end

View File

@ -3,63 +3,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe TreeHelper do RSpec.describe TreeHelper do
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository } let(:repository) { project.repository }
let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' } let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
def create_file(filename)
project.repository.create_file(
project.creator,
filename,
'test this',
message: "Automatically created file #{filename}",
branch_name: 'master'
)
end
describe 'flatten_tree' do
let(:tree) { repository.tree(sha, 'files') }
let(:root_path) { 'files' }
let(:tree_item) { tree.entries.find { |entry| entry.path == path } }
subject { flatten_tree(root_path, tree_item) }
context "on a directory containing more than one file/directory" do
let(:path) { 'files/html' }
it "returns the directory name" do
expect(subject).to match('html')
end
end
context "on a directory containing only one directory" do
let(:path) { 'files/flat' }
it "returns the flattened path" do
expect(subject).to match('flat/path/correct')
end
context "with a nested root path" do
let(:root_path) { 'files/flat' }
it "returns the flattened path with the root path suffix removed" do
expect(subject).to match('path/correct')
end
end
end
context 'when the root path contains a plus character' do
let(:root_path) { 'gtk/C++' }
let(:tree_item) { double(flat_path: 'gtk/C++/glade') }
it 'returns the flattened path' do
expect(subject).to eq('glade')
end
end
end
describe '#commit_in_single_accessible_branch' do describe '#commit_in_single_accessible_branch' do
it 'escapes HTML from the branch name' do it 'escapes HTML from the branch name' do
helper.instance_variable_set(:@branch_name, "<script>alert('escape me!');</script>") helper.instance_variable_set(:@branch_name, "<script>alert('escape me!');</script>")
@ -163,6 +112,7 @@ RSpec.describe TreeHelper do
context 'user does not have write access but a personal fork exists' do context 'user does not have write access but a personal fork exists' do
include ProjectForksHelper include ProjectForksHelper
let(:project) { create(:project, :repository) }
let(:forked_project) { create(:project, :repository, namespace: user.namespace) } let(:forked_project) { create(:project, :repository, namespace: user.namespace) }
before do before do

View File

@ -65,16 +65,16 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
context 'with nil runner_version' do context 'with nil runner_version' do
let(:runner_version) { nil } let(:runner_version) { nil }
it 'returns :invalid' do it 'returns :invalid_version' do
is_expected.to eq(:invalid) is_expected.to eq(:invalid_version)
end end
end end
context 'with invalid runner_version' do context 'with invalid runner_version' do
let(:runner_version) { 'junk' } let(:runner_version) { 'junk' }
it 'returns :invalid' do it 'returns :invalid_version' do
is_expected.to eq(:invalid) is_expected.to eq(:invalid_version)
end end
end end

View File

@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::GitalyClient::CommitService do RSpec.describe Gitlab::GitalyClient::CommitService do
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage } let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' } let(:relative_path) { project.disk_path + '.git' }
let(:repository) { project.repository } let(:repository) { project.repository }

View File

@ -0,0 +1,68 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
subject(:importer) { described_class.new(project, user.id) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
'actor' => { 'id' => 4, 'login' => 'alice' },
'event' => 'renamed',
'commit_id' => nil,
'created_at' => '2022-04-26 18:30:53 UTC',
'old_title' => 'old title',
'new_title' => 'new title',
'issue_db_id' => issue.id
)
end
let(:expected_note_attrs) do
{
noteable_id: issue.id,
noteable_type: Issue.name,
project_id: project.id,
author_id: user.id,
note: "changed title from **{-old-} title** to **{+new+} title**",
system: true,
created_at: issue_event.created_at,
updated_at: issue_event.created_at
}.stringify_keys
end
let(:expected_system_note_metadata_attrs) do
{
action: "title",
created_at: issue_event.created_at,
updated_at: issue_event.created_at
}.stringify_keys
end
describe '#execute' do
it 'creates expected note' do
expect { importer.execute(issue_event) }.to change { issue.notes.count }
.from(0).to(1)
expect(issue.notes.last)
.to have_attributes(expected_note_attrs)
end
it 'creates expected system note metadata' do
expect { importer.execute(issue_event) }.to change { SystemNoteMetadata.count }
.from(0).to(1)
expect(SystemNoteMetadata.last)
.to have_attributes(
expected_system_note_metadata_attrs.merge(
note_id: Note.last.id
)
)
end
end
end

View File

@ -80,6 +80,13 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
Gitlab::GithubImport::Importer::Events::ChangedLabel Gitlab::GithubImport::Importer::Events::ChangedLabel
end end
context "when it's renamed issue event" do
let(:event_name) { 'renamed' }
it_behaves_like 'triggers specific event importer',
Gitlab::GithubImport::Importer::Events::Renamed
end
context "when it's unknown issue event" do context "when it's unknown issue event" do
let(:event_name) { 'fake' } let(:event_name) { 'fake' }

View File

@ -57,6 +57,22 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
end end
end end
context 'when rename field is present' do
it 'includes the old_title and new_title fields' do
expect(issue_event.old_title).to eq('old title')
expect(issue_event.new_title).to eq('new title')
end
end
context 'when rename field is empty' do
let(:with_rename) { false }
it 'does not return such info' do
expect(issue_event.old_title).to eq nil
expect(issue_event.new_title).to eq nil
end
end
it 'includes the created timestamp' do it 'includes the created timestamp' do
expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC') expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
end end
@ -73,7 +89,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:response) do let(:response) do
event_resource = Struct.new( event_resource = Struct.new(
:id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label, :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label,
:issue_db_id, :created_at, :performed_via_github_app, :rename, :issue_db_id, :created_at, :performed_via_github_app,
keyword_init: true keyword_init: true
) )
user_resource = Struct.new(:id, :login, keyword_init: true) user_resource = Struct.new(:id, :login, keyword_init: true)
@ -86,6 +102,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d', commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\ commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\
'/570e7b2abdd848b95f2f578043fc23bd6f6fd24d', '/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
rename: with_rename ? { from: 'old title', to: 'new title' } : nil,
issue_db_id: 100500, issue_db_id: 100500,
label: with_label ? { name: 'label title' } : nil, label: with_label ? { name: 'label title' } : nil,
created_at: '2022-04-26 18:30:53 UTC', created_at: '2022-04-26 18:30:53 UTC',
@ -95,6 +112,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_actor) { true } let(:with_actor) { true }
let(:with_label) { true } let(:with_label) { true }
let(:with_rename) { true }
it_behaves_like 'an IssueEvent' do it_behaves_like 'an IssueEvent' do
let(:issue_event) { described_class.from_api_response(response) } let(:issue_event) { described_class.from_api_response(response) }
@ -114,6 +132,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
'commit_url' => 'commit_url' =>
'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d', 'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
'label_title' => (with_label ? 'label title' : nil), 'label_title' => (with_label ? 'label title' : nil),
'old_title' => with_rename ? 'old title' : nil,
'new_title' => with_rename ? 'new title' : nil,
"issue_db_id" => 100500, "issue_db_id" => 100500,
'created_at' => '2022-04-26 18:30:53 UTC', 'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil 'performed_via_github_app' => nil
@ -122,6 +142,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_actor) { true } let(:with_actor) { true }
let(:with_label) { true } let(:with_label) { true }
let(:with_rename) { true }
let(:issue_event) { described_class.from_json_hash(hash) } let(:issue_event) { described_class.from_json_hash(hash) }
end end

View File

@ -30,50 +30,31 @@ RSpec.describe Gitlab::TreeSummary do
describe '#summarize' do describe '#summarize' do
let(:project) { create(:project, :custom_repo, files: { 'a.txt' => '' }) } let(:project) { create(:project, :custom_repo, files: { 'a.txt' => '' }) }
subject(:summarized) { summary.summarize } subject(:entries) { summary.summarize }
it 'returns an array of entries, and an array of commits' do it 'returns an array of entries' do
expect(summarized).to be_a(Array) expect(entries).to be_a(Array)
expect(summarized.size).to eq(2) expect(entries.size).to eq(1)
entries, commits = *summarized
aggregate_failures do aggregate_failures do
expect(entries).to contain_exactly( expect(entries).to contain_exactly(
a_hash_including(file_name: 'a.txt', commit: have_attributes(id: commit.id)) a_hash_including(file_name: 'a.txt', commit: have_attributes(id: commit.id))
) )
expect(commits).to match_array(entries.map { |entry| entry[:commit] }) expect(summary.resolved_commits.values).to match_array(entries.map { |entry| entry[:commit] })
end
end
context 'when offset is over the limit' do
let(:offset) { 100 }
it 'returns an empty array' do
expect(summarized).to eq([[], []])
end end
end end
context 'with caching', :use_clean_rails_memory_store_caching do context 'with caching', :use_clean_rails_memory_store_caching do
subject { Rails.cache.fetch(key) } subject { Rails.cache.fetch(key) }
context 'Repository tree cache' do
let(:key) { ['projects', project.id, 'content', commit.id, path] }
it 'creates a cache for repository content' do
summarized
is_expected.to eq([{ file_name: 'a.txt', type: :blob }])
end
end
context 'Commits list cache' do context 'Commits list cache' do
let(:offset) { 0 } let(:offset) { 0 }
let(:limit) { 25 } let(:limit) { 25 }
let(:key) { ['projects', project.id, 'last_commits', commit.id, path, offset, limit] } let(:key) { ['projects', project.id, 'last_commits', commit.id, path, offset, limit + 1] }
it 'creates a cache for commits list' do it 'creates a cache for commits list' do
summarized entries
is_expected.to eq('a.txt' => commit.to_hash) is_expected.to eq('a.txt' => commit.to_hash)
end end
@ -93,7 +74,7 @@ RSpec.describe Gitlab::TreeSummary do
let(:expected_message) { message[0...1021] + '...' } let(:expected_message) { message[0...1021] + '...' }
it 'truncates commit message to 1 kilobyte' do it 'truncates commit message to 1 kilobyte' do
summarized entries
is_expected.to include('long.txt' => a_hash_including(message: expected_message)) is_expected.to include('long.txt' => a_hash_including(message: expected_message))
end end
@ -102,7 +83,7 @@ RSpec.describe Gitlab::TreeSummary do
end end
end end
describe '#summarize (entries)' do describe '#fetch_logs' do
let(:limit) { 4 } let(:limit) { 4 }
custom_files = { custom_files = {
@ -116,33 +97,32 @@ RSpec.describe Gitlab::TreeSummary do
let!(:project) { create(:project, :custom_repo, files: custom_files) } let!(:project) { create(:project, :custom_repo, files: custom_files) }
let(:commit) { repo.head_commit } let(:commit) { repo.head_commit }
subject(:entries) { summary.summarize.first } subject(:entries) { summary.fetch_logs.first }
it 'summarizes the entries within the window' do it 'summarizes the entries within the window' do
is_expected.to contain_exactly( is_expected.to contain_exactly(
a_hash_including(type: :tree, file_name: 'directory'), a_hash_including('file_name' => 'directory'),
a_hash_including(type: :blob, file_name: 'a.txt'), a_hash_including('file_name' => 'a.txt'),
a_hash_including(type: :blob, file_name: ':file'), a_hash_including('file_name' => ':file'),
a_hash_including(type: :tree, file_name: ':dir') a_hash_including('file_name' => ':dir')
# b.txt is excluded by the limit # b.txt is excluded by the limit
) )
end end
it 'references the commit and commit path in entries' do it 'references the commit and commit path in entries' do
# There are 2 trees and the summary is not ordered # There are 2 trees and the summary is not ordered
entry = entries.find { |entry| entry[:commit].id == commit.id } entry = entries.find { |entry| entry['commit']['id'] == commit.id }
expected_commit_path = Gitlab::Routing.url_helpers.project_commit_path(project, commit) expected_commit_path = Gitlab::Routing.url_helpers.project_commit_path(project, commit)
expect(entry[:commit]).to be_a(::Commit) expect(entry['commit_path']).to eq(expected_commit_path)
expect(entry[:commit_path]).to eq(expected_commit_path) expect(entry['commit_title_html']).to eq(commit.message)
expect(entry[:commit_title_html]).to eq(commit.message)
end end
context 'in a good subdirectory' do context 'in a good subdirectory' do
let(:path) { 'directory' } let(:path) { 'directory' }
it 'summarizes the entries in the subdirectory' do it 'summarizes the entries in the subdirectory' do
is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'c.txt')) is_expected.to contain_exactly(a_hash_including('file_name' => 'c.txt'))
end end
end end
@ -150,7 +130,7 @@ RSpec.describe Gitlab::TreeSummary do
let(:path) { ':dir' } let(:path) { ':dir' }
it 'summarizes the entries in the subdirectory' do it 'summarizes the entries in the subdirectory' do
is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'test.txt')) is_expected.to contain_exactly(a_hash_including('file_name' => 'test.txt'))
end end
end end
@ -164,7 +144,25 @@ RSpec.describe Gitlab::TreeSummary do
let(:offset) { 4 } let(:offset) { 4 }
it 'returns entries from the offset' do it 'returns entries from the offset' do
is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'b.txt')) is_expected.to contain_exactly(a_hash_including('file_name' => 'b.txt'))
end
end
context 'next offset' do
subject { summary.fetch_logs.last }
context 'when there are more entries to fetch' do
it 'returns next offset' do
is_expected.to eq(4)
end
end
context 'when there are no more entries to fetch' do
let(:limit) { 5 }
it 'returns next offset' do
is_expected.to be_nil
end
end end
end end
end end
@ -178,10 +176,11 @@ RSpec.describe Gitlab::TreeSummary do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:commit) { repo.commit(test_commit_sha) } let(:commit) { repo.commit(test_commit_sha) }
let(:limit) { nil } let(:limit) { nil }
let(:entries) { summary.summarize.first } let(:entries) { summary.summarize }
subject(:commits) do subject(:commits) do
summary.summarize.last summary.summarize
summary.resolved_commits.values
end end
it 'returns an Array of ::Commit objects' do it 'returns an Array of ::Commit objects' do
@ -227,7 +226,7 @@ RSpec.describe Gitlab::TreeSummary do
let_it_be(:project) { create(:project, :empty_repo) } let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:issue) { create(:issue, project: project) } let_it_be(:issue) { create(:issue, project: project) }
let(:entries) { summary.summarize.first } let(:entries) { summary.summarize }
let(:entry) { entries.find { |entry| entry[:file_name] == 'issue.txt' } } let(:entry) { entries.find { |entry| entry[:file_name] == 'issue.txt' } }
before_all do before_all do
@ -264,67 +263,6 @@ RSpec.describe Gitlab::TreeSummary do
end end
end end
describe '#more?' do
let(:path) { 'tmp/more' }
where(:num_entries, :offset, :limit, :expected_result) do
0 | 0 | 0 | false
0 | 0 | 1 | false
1 | 0 | 0 | true
1 | 0 | 1 | false
1 | 1 | 0 | false
1 | 1 | 1 | false
2 | 0 | 0 | true
2 | 0 | 1 | true
2 | 0 | 2 | false
2 | 0 | 3 | false
2 | 1 | 0 | true
2 | 1 | 1 | false
2 | 2 | 0 | false
2 | 2 | 1 | false
end
with_them do
before do
create_file('dummy', path: 'other') if num_entries == 0
1.upto(num_entries) { |n| create_file(n, path: path) }
end
subject { summary.more? }
it { is_expected.to eq(expected_result) }
end
end
describe '#next_offset' do
let(:path) { 'tmp/next_offset' }
where(:num_entries, :offset, :limit, :expected_result) do
0 | 0 | 0 | 0
0 | 0 | 1 | 1
0 | 1 | 0 | 1
0 | 1 | 1 | 1
1 | 0 | 0 | 0
1 | 0 | 1 | 1
1 | 1 | 0 | 1
1 | 1 | 1 | 2
end
with_them do
before do
create_file('dummy', path: 'other') if num_entries == 0
1.upto(num_entries) { |n| create_file(n, path: path) }
end
subject { summary.next_offset }
it { is_expected.to eq(expected_result) }
end
end
def create_file(unique, path:) def create_file(unique, path:)
repo.create_file( repo.create_file(
project.creator, project.creator,

View File

@ -118,41 +118,27 @@ RSpec.describe Ci::PendingBuild do
project.shared_runners_enabled = true project.shared_runners_enabled = true
end end
context 'when ci_pending_builds_maintain_denormalized_data is enabled' do it 'sets instance_runners_enabled to true' do
it 'sets instance_runners_enabled to true' do described_class.upsert_from_build!(build)
expect(described_class.last.instance_runners_enabled).to be_truthy
end
context 'when project is about to be deleted' do
before do
build.project.update!(pending_delete: true)
end
it 'sets instance_runners_enabled to false' do
described_class.upsert_from_build!(build) described_class.upsert_from_build!(build)
expect(described_class.last.instance_runners_enabled).to be_truthy expect(described_class.last.instance_runners_enabled).to be_falsey
end
context 'when project is about to be deleted' do
before do
build.project.update!(pending_delete: true)
end
it 'sets instance_runners_enabled to false' do
described_class.upsert_from_build!(build)
expect(described_class.last.instance_runners_enabled).to be_falsey
end
end
context 'when builds are disabled' do
before do
build.project.project_feature.update!(builds_access_level: false)
end
it 'sets instance_runners_enabled to false' do
described_class.upsert_from_build!(build)
expect(described_class.last.instance_runners_enabled).to be_falsey
end
end end
end end
context 'when ci_pending_builds_maintain_denormalized_data is disabled' do context 'when builds are disabled' do
before do before do
stub_feature_flags(ci_pending_builds_maintain_denormalized_data: false) build.project.project_feature.update!(builds_access_level: false)
end end
it 'sets instance_runners_enabled to false' do it 'sets instance_runners_enabled to false' do
@ -168,24 +154,10 @@ RSpec.describe Ci::PendingBuild do
subject(:ci_pending_build) { described_class.last } subject(:ci_pending_build) { described_class.last }
context 'when ci_pending_builds_maintain_denormalized_data is enabled' do it 'sets tag_ids' do
it 'sets tag_ids' do described_class.upsert_from_build!(build)
described_class.upsert_from_build!(build)
expect(ci_pending_build.tag_ids).to eq(build.tags_ids) expect(ci_pending_build.tag_ids).to eq(build.tags_ids)
end
end
context 'when ci_pending_builds_maintain_denormalized_data is disabled' do
before do
stub_feature_flags(ci_pending_builds_maintain_denormalized_data: false)
end
it 'does not set tag_ids' do
described_class.upsert_from_build!(build)
expect(ci_pending_build.tag_ids).to be_empty
end
end end
end end

View File

@ -5,13 +5,40 @@ require 'spec_helper'
RSpec.describe Ci::RunnerVersion do RSpec.describe Ci::RunnerVersion do
it_behaves_like 'having unique enum values' it_behaves_like 'having unique enum values'
let_it_be(:runner_version_not_available) do
create(:ci_runner_version, version: 'abc123', status: :not_available)
end
let_it_be(:runner_version_recommended) do
create(:ci_runner_version, version: 'abc234', status: :recommended)
end
describe '.not_available' do describe '.not_available' do
subject { described_class.not_available } subject { described_class.not_available }
let!(:runner_version1) { create(:ci_runner_version, version: 'abc123', status: :not_available) } it { is_expected.to match_array([runner_version_not_available]) }
let!(:runner_version2) { create(:ci_runner_version, version: 'abc234', status: :recommended) } end
it { is_expected.to match_array([runner_version1]) } describe '.potentially_outdated' do
subject { described_class.potentially_outdated }
let_it_be(:runner_version_nil) { create(:ci_runner_version, version: 'abc345', status: nil) }
let_it_be(:runner_version_available) do
create(:ci_runner_version, version: 'abc456', status: :available)
end
let_it_be(:runner_version_unknown) do
create(:ci_runner_version, version: 'abc567', status: :unknown)
end
it 'contains any runner version that is not already recommended' do
is_expected.to match_array([
runner_version_nil,
runner_version_not_available,
runner_version_available,
runner_version_unknown
])
end
end end
describe 'validation' do describe 'validation' do

View File

@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Tree do RSpec.describe Tree do
let(:repository) { create(:project, :repository).repository } let_it_be(:repository) { create(:project, :repository).repository }
let(:sha) { repository.root_ref } let(:sha) { repository.root_ref }
subject(:tree) { described_class.new(repository, '54fcc214') } subject(:tree) { described_class.new(repository, '54fcc214') }

View File

@ -200,6 +200,7 @@ RSpec.describe 'Query.work_item(id)' do
type type
... on WorkItemWidgetAssignees { ... on WorkItemWidgetAssignees {
allowsMultipleAssignees allowsMultipleAssignees
canInviteMembers
assignees { assignees {
nodes { nodes {
id id
@ -218,6 +219,7 @@ RSpec.describe 'Query.work_item(id)' do
hash_including( hash_including(
'type' => 'ASSIGNEES', 'type' => 'ASSIGNEES',
'allowsMultipleAssignees' => boolean, 'allowsMultipleAssignees' => boolean,
'canInviteMembers' => boolean,
'assignees' => { 'assignees' => {
'nodes' => match_array( 'nodes' => match_array(
assignees.map { |a| { 'id' => a.to_gid.to_s, 'username' => a.username } } assignees.map { |a| { 'id' => a.to_gid.to_s, 'username' => a.username } }

View File

@ -750,41 +750,7 @@ module Ci
end end
context 'when using pending builds table' do context 'when using pending builds table' do
before do include_examples 'handles runner assignment'
stub_feature_flags(ci_pending_builds_queue_source: true)
end
context 'with ci_queuing_use_denormalized_data_strategy enabled' do
before do
stub_feature_flags(ci_queuing_use_denormalized_data_strategy: true)
end
include_examples 'handles runner assignment'
end
context 'with ci_queuing_use_denormalized_data_strategy disabled' do
before do
skip_if_multiple_databases_are_setup
stub_feature_flags(ci_queuing_use_denormalized_data_strategy: false)
end
around do |example|
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332952') do
example.run
end
end
include_examples 'handles runner assignment'
end
context 'with ci_queuing_use_denormalized_data_strategy enabled' do
before do
stub_feature_flags(ci_queuing_use_denormalized_data_strategy: true)
end
include_examples 'handles runner assignment'
end
context 'when a conflicting data is stored in denormalized table' do context 'when a conflicting data is stored in denormalized table' do
let!(:specific_runner) { create(:ci_runner, :project, projects: [project], tag_list: %w[conflict]) } let!(:specific_runner) { create(:ci_runner, :project, projects: [project], tag_list: %w[conflict]) }
@ -805,22 +771,6 @@ module Ci
end end
end end
end end
context 'when not using pending builds table' do
before do
skip_if_multiple_databases_are_setup
stub_feature_flags(ci_pending_builds_queue_source: false)
end
around do |example|
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332952') do
example.run
end
end
include_examples 'handles runner assignment'
end
end end
describe '#register_success' do describe '#register_success' do
@ -888,14 +838,6 @@ module Ci
shared_examples 'metrics collector' do shared_examples 'metrics collector' do
it_behaves_like 'attempt counter collector' it_behaves_like 'attempt counter collector'
it_behaves_like 'jobs queueing time histogram collector' it_behaves_like 'jobs queueing time histogram collector'
context 'when using denormalized data is disabled' do
before do
stub_feature_flags(ci_pending_builds_maintain_denormalized_data: false)
end
it_behaves_like 'jobs queueing time histogram collector'
end
end end
context 'when shared runner is used' do context 'when shared runner is used' do

View File

@ -0,0 +1,113 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' do
subject(:execute) { described_class.new.execute }
let_it_be(:runner_14_0_1) { create(:ci_runner, version: '14.0.1') }
let_it_be(:runner_version_14_0_1) do
create(:ci_runner_version, version: '14.0.1', status: :not_available)
end
before do
stub_const('Ci::Runners::ReconcileExistingRunnerVersionsService::VERSION_BATCH_SIZE', 1)
allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
.to receive(:check_runner_upgrade_status)
.and_return(:recommended)
end
context 'with runner with new version' do
let!(:runner_14_0_2) { create(:ci_runner, version: '14.0.2') }
let!(:runner_version_14_0_0) { create(:ci_runner_version, version: '14.0.0', status: :not_available) }
let!(:runner_14_0_0) { create(:ci_runner, version: '14.0.0') }
before do
allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
.to receive(:check_runner_upgrade_status)
.with('14.0.2')
.and_return(:not_available)
.once
end
it 'creates and updates expected ci_runner_versions entries', :aggregate_failures do
result = nil
expect { result = execute }
.to change { runner_version_14_0_0.reload.status }.from('not_available').to('recommended')
.and change { runner_version_14_0_1.reload.status }.from('not_available').to('recommended')
.and change { ::Ci::RunnerVersion.find_by(version: '14.0.2')&.status }.from(nil).to('not_available')
expect(result).to eq({
status: :success,
total_inserted: 1, # 14.0.2 is inserted
total_updated: 3, # 14.0.0, 14.0.1 are updated, and newly inserted 14.0.2's status is calculated
total_deleted: 0
})
end
end
context 'with orphan ci_runner_version' do
let!(:runner_version_14_0_2) { create(:ci_runner_version, version: '14.0.2', status: :not_available) }
before do
allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
.to receive(:check_runner_upgrade_status)
.and_return(:not_available)
end
it 'deletes orphan ci_runner_versions entry', :aggregate_failures do
result = nil
expect { result = execute }
.to change { ::Ci::RunnerVersion.find_by_version('14.0.2')&.status }.from('not_available').to(nil)
.and not_change { runner_version_14_0_1.reload.status }.from('not_available')
expect(result).to eq({
status: :success,
total_inserted: 0,
total_updated: 0,
total_deleted: 1 # 14.0.2 is deleted
})
end
end
context 'with no runner version changes' do
before do
allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
.to receive(:check_runner_upgrade_status)
.and_return(:not_available)
end
it 'does not modify ci_runner_versions entries', :aggregate_failures do
result = nil
expect { result = execute }.not_to change { runner_version_14_0_1.reload.status }.from('not_available')
expect(result).to eq({
status: :success,
total_inserted: 0,
total_updated: 0,
total_deleted: 0
})
end
end
context 'with failing version check' do
before do
allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
.to receive(:check_runner_upgrade_status)
.and_return(:error)
end
it 'makes no changes to ci_runner_versions', :aggregate_failures do
result = nil
expect { result = execute }.not_to change { runner_version_14_0_1.reload.status }.from('not_available')
expect(result).to eq({
status: :success,
total_inserted: 0,
total_updated: 0,
total_deleted: 0
})
end
end
end

View File

@ -42,19 +42,6 @@ RSpec.describe Ci::UpdatePendingBuildService do
expect(pending_build_1.instance_runners_enabled).to be_truthy expect(pending_build_1.instance_runners_enabled).to be_truthy
expect(pending_build_2.instance_runners_enabled).to be_truthy expect(pending_build_2.instance_runners_enabled).to be_truthy
end end
context 'when ci_pending_builds_maintain_denormalized_data is disabled' do
before do
stub_feature_flags(ci_pending_builds_maintain_denormalized_data: false)
end
it 'does not update all pending builds', :aggregate_failures do
update_pending_builds
expect(pending_build_1.instance_runners_enabled).to be_falsey
expect(pending_build_2.instance_runners_enabled).to be_truthy
end
end
end end
context 'when model is a project with pending builds' do context 'when model is a project with pending builds' do
@ -66,19 +53,6 @@ RSpec.describe Ci::UpdatePendingBuildService do
expect(pending_build_1.instance_runners_enabled).to be_truthy expect(pending_build_1.instance_runners_enabled).to be_truthy
expect(pending_build_2.instance_runners_enabled).to be_truthy expect(pending_build_2.instance_runners_enabled).to be_truthy
end end
context 'when ci_pending_builds_maintain_denormalized_data is disabled' do
before do
stub_feature_flags(ci_pending_builds_maintain_denormalized_data: false)
end
it 'does not update all pending builds', :aggregate_failures do
update_pending_builds
expect(pending_build_1.instance_runners_enabled).to be_falsey
expect(pending_build_2.instance_runners_enabled).to be_truthy
end
end
end end
end end
end end

View File

@ -52,6 +52,7 @@ RSpec.describe Packages::CleanupPackageFileWorker do
end end
it 'handles the error' do it 'handles the error' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(instance_of(RuntimeError), class: described_class.name)
expect { subject }.to change { Packages::PackageFile.error.count }.from(0).to(1) expect { subject }.to change { Packages::PackageFile.error.count }.from(0).to(1)
expect(package_file.reload).to be_error expect(package_file.reload).to be_error
end end
@ -71,7 +72,9 @@ RSpec.describe Packages::CleanupPackageFileWorker do
end end
it 'handles the error' do it 'handles the error' do
expect { subject }.to change { Packages::PackageFile.count }.by(-1) expect(Gitlab::ErrorTracking).to receive(:log_exception).with(instance_of(RuntimeError), class: described_class.name)
expect { subject }.not_to change { Packages::PackageFile.count }
expect(package_file.reload).to be_error
end end
end end
end end

Some files were not shown because too many files have changed in this diff Show More