Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5ab111376f
commit
0ba11d8461
2
Gemfile
2
Gemfile
|
@ -409,7 +409,7 @@ group :development, :test do
|
|||
end
|
||||
|
||||
group :development, :test, :danger do
|
||||
gem 'gitlab-dangerfiles', '~> 3.4.2', require: false
|
||||
gem 'gitlab-dangerfiles', '~> 3.4.3', require: false
|
||||
end
|
||||
|
||||
group :development, :test, :coverage do
|
||||
|
|
|
@ -501,7 +501,7 @@ GEM
|
|||
terminal-table (~> 1.5, >= 1.5.1)
|
||||
gitlab-chronic (0.10.5)
|
||||
numerizer (~> 0.2)
|
||||
gitlab-dangerfiles (3.4.2)
|
||||
gitlab-dangerfiles (3.4.3)
|
||||
danger (>= 8.4.5)
|
||||
danger-gitlab (>= 8.0.0)
|
||||
rake
|
||||
|
@ -1560,7 +1560,7 @@ DEPENDENCIES
|
|||
gitaly (~> 15.1.0.pre.rc1)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 3.4.2)
|
||||
gitlab-dangerfiles (~> 3.4.3)
|
||||
gitlab-experiment (~> 0.7.1)
|
||||
gitlab-fog-azure-rm (~> 1.3.0)
|
||||
gitlab-labkit (~> 0.23.0)
|
||||
|
|
|
@ -24,7 +24,7 @@ function setVisibilityOptions({ name, visibility, showPath, editPath }) {
|
|||
optionInput.disabled = true;
|
||||
const reason = option.querySelector('.option-disabled-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() : '';
|
||||
reason.innerHTML = sprintf(
|
||||
__(
|
||||
|
|
|
@ -103,14 +103,12 @@ export default {
|
|||
|
||||
return this.rowNumbers[key];
|
||||
},
|
||||
getCommit(fileName, type) {
|
||||
getCommit(fileName) {
|
||||
if (!this.glFeatures.lazyLoadCommits) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return this.commits.find(
|
||||
(commitEntry) => commitEntry.fileName === fileName && commitEntry.type === type,
|
||||
);
|
||||
return this.commits.find((commitEntry) => commitEntry.fileName === fileName);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -152,7 +150,7 @@ export default {
|
|||
:loading-path="loadingPath"
|
||||
:total-entries="totalEntries"
|
||||
:row-number="generateRowNumber(entry.flatPath, entry.id, index)"
|
||||
:commit-info="getCommit(entry.name, entry.type)"
|
||||
:commit-info="getCommit(entry.name)"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -43,7 +43,6 @@ export default {
|
|||
variables() {
|
||||
return {
|
||||
fileName: this.name,
|
||||
type: this.type,
|
||||
path: this.currentPath,
|
||||
projectPath: this.projectPath,
|
||||
maxOffset: this.totalEntries,
|
||||
|
|
|
@ -9,7 +9,7 @@ Vue.use(VueApollo);
|
|||
const defaultClient = createDefaultClient(
|
||||
{
|
||||
Query: {
|
||||
commit(_, { path, fileName, type, maxOffset }) {
|
||||
commit(_, { path, fileName, maxOffset }) {
|
||||
return new Promise((resolve) => {
|
||||
fetchLogsTree(
|
||||
defaultClient,
|
||||
|
@ -19,7 +19,6 @@ const defaultClient = createDefaultClient(
|
|||
resolve,
|
||||
entry: {
|
||||
name: fileName,
|
||||
type,
|
||||
},
|
||||
},
|
||||
maxOffset,
|
||||
|
|
|
@ -16,9 +16,7 @@ function setNextOffset(offset) {
|
|||
}
|
||||
|
||||
export function resolveCommit(commits, path, { resolve, entry }) {
|
||||
const commit = commits.find(
|
||||
(c) => c.filePath === `${path}/${entry.name}` && c.type === entry.type,
|
||||
);
|
||||
const commit = commits.find((c) => c.filePath === `${path}/${entry.name}`);
|
||||
|
||||
if (commit) {
|
||||
resolve(commit);
|
||||
|
|
|
@ -6,5 +6,4 @@ fragment TreeEntryCommit on LogTreeCommit {
|
|||
commitPath
|
||||
fileName
|
||||
filePath
|
||||
type
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#import "ee_else_ce/repository/queries/commit.fragment.graphql"
|
||||
|
||||
query getCommit($fileName: String!, $type: String!, $path: String!, $maxOffset: Number!) {
|
||||
commit(path: $path, fileName: $fileName, type: $type, maxOffset: $maxOffset) @client {
|
||||
query getCommit($fileName: String!, $path: String!, $maxOffset: Number!) {
|
||||
commit(path: $path, fileName: $fileName, maxOffset: $maxOffset) @client {
|
||||
...TreeEntryCommit
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ export function normalizeData(data, path, extra = () => {}) {
|
|||
commitPath: d.commit_path,
|
||||
fileName: d.file_name,
|
||||
filePath: `${path}/${d.file_name}`,
|
||||
type: d.type,
|
||||
__typename: 'LogTreeCommit',
|
||||
...extra(d),
|
||||
}));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlBadge, GlTab, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlBadge, GlTabs, GlTab, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/flash';
|
||||
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
|
||||
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 RunnerDetails from '../components/runner_details.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 { captureException } from '../sentry_utils';
|
||||
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
|
||||
|
@ -20,6 +20,7 @@ export default {
|
|||
name: 'AdminRunnerShowApp',
|
||||
components: {
|
||||
GlBadge,
|
||||
GlTabs,
|
||||
GlTab,
|
||||
RunnerDeleteButton,
|
||||
RunnerEditButton,
|
||||
|
@ -84,6 +85,7 @@ export default {
|
|||
redirectTo(this.runnersPath);
|
||||
},
|
||||
},
|
||||
I18N_DETAILS,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -96,24 +98,27 @@ export default {
|
|||
</template>
|
||||
</runner-header>
|
||||
|
||||
<runner-details :runner="runner">
|
||||
<template #jobs-tab>
|
||||
<gl-tab>
|
||||
<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>
|
||||
<gl-tabs>
|
||||
<gl-tab>
|
||||
<template #title>{{ $options.I18N_DETAILS }}</template>
|
||||
|
||||
<runner-jobs v-if="runner" :runner="runner" />
|
||||
</gl-tab>
|
||||
</template>
|
||||
</runner-details>
|
||||
<runner-details v-if="runner" :runner="runner" />
|
||||
</gl-tab>
|
||||
<gl-tab>
|
||||
<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>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlTabs, GlTab, GlIntersperse } from '@gitlab/ui';
|
||||
import { GlIntersperse } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
|
||||
|
@ -11,8 +11,6 @@ import RunnerTags from './runner_tags.vue';
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlTabs,
|
||||
GlTab,
|
||||
GlIntersperse,
|
||||
RunnerDetail,
|
||||
RunnerMaintenanceNoteDetail: () =>
|
||||
|
@ -65,64 +63,57 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-tabs>
|
||||
<gl-tab>
|
||||
<template #title>{{ s__('Runners|Details') }}</template>
|
||||
|
||||
<template v-if="runner">
|
||||
<runner-upgrade-status-alert class="gl-my-4" :runner="runner" />
|
||||
<div class="gl-pt-4">
|
||||
<dl class="gl-mb-0" data-testid="runner-details-list">
|
||||
<runner-detail :label="s__('Runners|Description')" :value="runner.description" />
|
||||
<runner-detail
|
||||
:label="s__('Runners|Last contact')"
|
||||
:empty-value="s__('Runners|Never contacted')"
|
||||
>
|
||||
<template #value>
|
||||
<time-ago v-if="runner.contactedAt" :time="runner.contactedAt" />
|
||||
</template>
|
||||
</runner-detail>
|
||||
<runner-detail :label="s__('Runners|Version')">
|
||||
<template v-if="runner.version" #value>
|
||||
{{ runner.version }}
|
||||
<runner-upgrade-status-badge size="sm" :runner="runner" />
|
||||
</template>
|
||||
</runner-detail>
|
||||
<runner-detail :label="s__('Runners|IP Address')" :value="runner.ipAddress" />
|
||||
<runner-detail :label="s__('Runners|Executor')" :value="runner.executorName" />
|
||||
<runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" />
|
||||
<runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" />
|
||||
<runner-detail :label="s__('Runners|Configuration')">
|
||||
<template #value>
|
||||
<gl-intersperse v-if="configTextProtected || configTextUntagged">
|
||||
<span v-if="configTextProtected">{{ configTextProtected }}</span>
|
||||
<span v-if="configTextUntagged">{{ configTextUntagged }}</span>
|
||||
</gl-intersperse>
|
||||
</template>
|
||||
</runner-detail>
|
||||
<runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" />
|
||||
<runner-detail :label="s__('Runners|Tags')">
|
||||
<template #value>
|
||||
<runner-tags
|
||||
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"
|
||||
<div>
|
||||
<runner-upgrade-status-alert class="gl-my-4" :runner="runner" />
|
||||
<div class="gl-pt-4">
|
||||
<dl class="gl-mb-0" data-testid="runner-details-list">
|
||||
<runner-detail :label="s__('Runners|Description')" :value="runner.description" />
|
||||
<runner-detail
|
||||
:label="s__('Runners|Last contact')"
|
||||
:empty-value="s__('Runners|Never contacted')"
|
||||
>
|
||||
<template #value>
|
||||
<time-ago v-if="runner.contactedAt" :time="runner.contactedAt" />
|
||||
</template>
|
||||
</runner-detail>
|
||||
<runner-detail :label="s__('Runners|Version')">
|
||||
<template v-if="runner.version" #value>
|
||||
{{ runner.version }}
|
||||
<runner-upgrade-status-badge size="sm" :runner="runner" />
|
||||
</template>
|
||||
</runner-detail>
|
||||
<runner-detail :label="s__('Runners|IP Address')" :value="runner.ipAddress" />
|
||||
<runner-detail :label="s__('Runners|Executor')" :value="runner.executorName" />
|
||||
<runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" />
|
||||
<runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" />
|
||||
<runner-detail :label="s__('Runners|Configuration')">
|
||||
<template #value>
|
||||
<gl-intersperse v-if="configTextProtected || configTextUntagged">
|
||||
<span v-if="configTextProtected">{{ configTextProtected }}</span>
|
||||
<span v-if="configTextUntagged">{{ configTextUntagged }}</span>
|
||||
</gl-intersperse>
|
||||
</template>
|
||||
</runner-detail>
|
||||
<runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" />
|
||||
<runner-detail :label="s__('Runners|Tags')">
|
||||
<template #value>
|
||||
<runner-tags
|
||||
v-if="runner.tagList && runner.tagList.length"
|
||||
class="gl-vertical-align-middle"
|
||||
:tag-list="runner.tagList"
|
||||
size="sm"
|
||||
/>
|
||||
</dl>
|
||||
</div>
|
||||
</template>
|
||||
</runner-detail>
|
||||
|
||||
<runner-groups v-if="isGroupRunner" :runner="runner" />
|
||||
<runner-projects v-if="isProjectRunner" :runner="runner" />
|
||||
</template>
|
||||
</gl-tab>
|
||||
<slot name="jobs-tab"></slot>
|
||||
</gl-tabs>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<runner-groups v-if="isGroupRunner" :runner="runner" />
|
||||
<runner-projects v-if="isProjectRunner" :runner="runner" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -81,6 +81,7 @@ export const I18N_LOCKED_RUNNER_DESCRIPTION = s__(
|
|||
|
||||
// Runner details
|
||||
|
||||
export const I18N_DETAILS = s__('Runners|Details');
|
||||
export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})');
|
||||
export const I18N_NONE = __('None');
|
||||
export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.');
|
||||
|
|
|
@ -89,6 +89,6 @@ export default {
|
|||
</template>
|
||||
</runner-header>
|
||||
|
||||
<runner-details :runner="runner" />
|
||||
<runner-details v-if="runner" :runner="runner" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -315,6 +315,7 @@ export default {
|
|||
@mouseup="onRowMouseUp"
|
||||
>
|
||||
<status-icon
|
||||
:level="1"
|
||||
:name="$options.label || $options.name"
|
||||
:is-loading="isLoadingSummary"
|
||||
:icon-name="statusIconName"
|
||||
|
|
|
@ -9,6 +9,11 @@ export default {
|
|||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
level: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -27,7 +32,7 @@ export default {
|
|||
size: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 16,
|
||||
default: 12,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
@ -44,8 +49,8 @@ export default {
|
|||
<div
|
||||
:class="[
|
||||
$options.EXTENSION_ICON_CLASS[iconName],
|
||||
{ 'mr-widget-extension-icon': !isLoading && size === 16 },
|
||||
{ 'gl-p-2': isLoading || size === 16 },
|
||||
{ 'mr-widget-extension-icon': !isLoading && level === 1 },
|
||||
{ 'gl-p-2': isLoading || level === 1 },
|
||||
]"
|
||||
class="gl-rounded-full gl-mr-3 gl-relative gl-p-2"
|
||||
>
|
||||
|
|
|
@ -32,7 +32,7 @@ export default {
|
|||
// Status icon to be used next to the summary text
|
||||
// Receives the collapsed data as an argument
|
||||
statusIcon(count) {
|
||||
return EXTENSION_ICONS.warning;
|
||||
return EXTENSION_ICONS.failed;
|
||||
},
|
||||
// Tertiary action buttons that will take the user elsewhere
|
||||
// in the GitLab app
|
||||
|
|
|
@ -8,6 +8,10 @@ import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_optio
|
|||
import createDefaultClient from '~/lib/graphql';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import Translate from '../vue_shared/translate';
|
||||
import { registerExtension } from './components/extensions';
|
||||
import issuesExtension from './extensions/issues';
|
||||
|
||||
registerExtension(issuesExtension);
|
||||
|
||||
Vue.use(Translate);
|
||||
Vue.use(VueApollo);
|
||||
|
|
|
@ -630,6 +630,24 @@ $tabs-holder-z-index: 250;
|
|||
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 {
|
||||
position: relative;
|
||||
border: 1px solid var(--border-color, $border-color);
|
||||
|
|
|
@ -221,7 +221,7 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
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
|
||||
|
||||
def context_user
|
||||
|
|
|
@ -40,6 +40,8 @@ module Types
|
|||
authorize: :download_code
|
||||
field :upcoming_release, GraphQL::Types::Boolean, null: true, method: :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,
|
||||
description: 'User that created the release.'
|
||||
|
|
|
@ -17,6 +17,13 @@ module Types
|
|||
|
||||
field :allows_multiple_assignees, GraphQL::Types::Boolean, null: true, method: :allows_multiple_assignees?,
|
||||
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
|
||||
# rubocop:enable Graphql/AuthorizeTypes
|
||||
end
|
||||
|
|
|
@ -58,14 +58,6 @@ module TreeHelper
|
|||
"#{username}-#{ref}-patch-#{epoch}"
|
||||
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
|
||||
_("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.")
|
||||
|
@ -111,16 +103,6 @@ module TreeHelper
|
|||
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
|
||||
@branch_name || tree_edit_branch
|
||||
end
|
||||
|
|
|
@ -30,10 +30,6 @@ module Ci
|
|||
self.upsert(entry.attributes.compact, returning: %w[build_id], unique_by: :build_id)
|
||||
end
|
||||
|
||||
def maintain_denormalized_data?
|
||||
::Feature.enabled?(:ci_pending_builds_maintain_denormalized_data)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def args_from_build(build)
|
||||
|
@ -43,13 +39,13 @@ module Ci
|
|||
build: build,
|
||||
project: project,
|
||||
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?
|
||||
args.store(:tag_ids, build.tags_ids)
|
||||
args.store(:instance_runners_enabled, shared_runners_enabled?(project))
|
||||
args.store(:namespace_traversal_ids, project.namespace.traversal_ids) if group_runners_enabled?(project)
|
||||
if group_runners_enabled?(project)
|
||||
args.store(:namespace_traversal_ids, project.namespace.traversal_ids)
|
||||
end
|
||||
|
||||
args
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
module Ci
|
||||
class RunnerVersion < Ci::ApplicationRecord
|
||||
include EachBatch
|
||||
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: {
|
||||
not_processed: nil,
|
||||
invalid_version: -1,
|
||||
invalid_version: -1, # Named invalid_version to avoid clash with auto-generated `invalid?` ActiveRecord method
|
||||
unknown: 0,
|
||||
not_available: 1,
|
||||
available: 2,
|
||||
|
@ -24,6 +26,10 @@ module Ci
|
|||
# Override auto generated negative scope (from available) so the scope has expected behavior
|
||||
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 }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,7 +56,8 @@ module LooseIndexScan
|
|||
.project(Arel::Nodes::Grouping.new(Arel.sql(inner_query.to_sql)).as(column.to_s))
|
||||
|
||||
unscoped do
|
||||
with
|
||||
select(column)
|
||||
.with
|
||||
.recursive(cte.to_arel)
|
||||
.from(cte.alias_to(arel_table))
|
||||
.where(arel_column.not_eq(nil)) # filtering out the last NULL value
|
||||
|
|
|
@ -24,25 +24,7 @@ module Ci
|
|||
|
||||
# rubocop:disable CodeReuse/ActiveRecord
|
||||
def builds_for_group_runner
|
||||
if strategy.use_denormalized_data_strategy?
|
||||
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
|
||||
strategy.builds_for_group_runner
|
||||
end
|
||||
|
||||
def builds_for_project_runner
|
||||
|
@ -80,11 +62,7 @@ module Ci
|
|||
|
||||
def strategy
|
||||
strong_memoize(:strategy) do
|
||||
if ::Feature.enabled?(:ci_pending_builds_queue_source, runner)
|
||||
Queue::PendingBuildsStrategy.new(runner)
|
||||
else
|
||||
Queue::BuildsTableStrategy.new(runner)
|
||||
end
|
||||
Queue::PendingBuildsStrategy.new(runner)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
|
@ -23,19 +23,11 @@ module Ci
|
|||
end
|
||||
|
||||
def builds_matching_tag_ids(relation, ids)
|
||||
if use_denormalized_data_strategy?
|
||||
relation.for_tags(runner.tags_ids)
|
||||
else
|
||||
relation.merge(CommitStatus.matches_tag_ids(ids, table: 'ci_pending_builds', column: 'build_id'))
|
||||
end
|
||||
relation.for_tags(runner.tags_ids)
|
||||
end
|
||||
|
||||
def builds_with_any_tags(relation)
|
||||
if use_denormalized_data_strategy?
|
||||
relation.where('cardinality(tag_ids) > 0')
|
||||
else
|
||||
relation.merge(CommitStatus.with_any_tags(table: 'ci_pending_builds', column: 'build_id'))
|
||||
end
|
||||
relation.where('cardinality(tag_ids) > 0')
|
||||
end
|
||||
|
||||
def order(relation)
|
||||
|
@ -50,23 +42,10 @@ module Ci
|
|||
relation.pluck(:build_id)
|
||||
end
|
||||
|
||||
def use_denormalized_data_strategy?
|
||||
::Feature.enabled?(:ci_queuing_use_denormalized_data_strategy)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def builds_available_for_shared_runners
|
||||
if use_denormalized_data_strategy?
|
||||
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
|
||||
new_builds.with_instance_runners
|
||||
end
|
||||
|
||||
def builds_ordered_for_shared_runners(relation)
|
||||
|
|
|
@ -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
|
|
@ -14,8 +14,6 @@ module Ci
|
|||
# Add a build to the pending builds queue
|
||||
#
|
||||
def push(build, transition)
|
||||
return unless maintain_pending_builds_queue?
|
||||
|
||||
raise InvalidQueueTransition unless transition.to == 'pending'
|
||||
|
||||
transition.within_transaction do
|
||||
|
@ -33,8 +31,6 @@ module Ci
|
|||
# Remove a build from the pending builds queue
|
||||
#
|
||||
def pop(build, transition)
|
||||
return unless maintain_pending_builds_queue?
|
||||
|
||||
raise InvalidQueueTransition unless transition.from == 'pending'
|
||||
|
||||
transition.within_transaction { remove!(build) }
|
||||
|
@ -57,7 +53,6 @@ module Ci
|
|||
# Add shared runner build tracking entry (used for queuing).
|
||||
#
|
||||
def track(build, transition)
|
||||
return unless maintain_pending_builds_queue?
|
||||
return unless build.shared_runner_build?
|
||||
|
||||
raise InvalidQueueTransition unless transition.to == 'running'
|
||||
|
@ -78,7 +73,6 @@ module Ci
|
|||
# queuing).
|
||||
#
|
||||
def untrack(build, transition)
|
||||
return unless maintain_pending_builds_queue?
|
||||
return unless build.shared_runner_build?
|
||||
|
||||
raise InvalidQueueTransition unless transition.from == 'running'
|
||||
|
@ -115,9 +109,5 @@ module Ci
|
|||
runner.pick_build!(build)
|
||||
end
|
||||
end
|
||||
|
||||
def maintain_pending_builds_queue?
|
||||
::Ci::PendingBuild.maintain_denormalized_data?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,8 +15,6 @@ module Ci
|
|||
end
|
||||
|
||||
def execute
|
||||
return unless ::Ci::PendingBuild.maintain_denormalized_data?
|
||||
|
||||
@model.pending_builds.each_batch do |relation|
|
||||
relation.update_all(@update_params)
|
||||
end
|
||||
|
|
|
@ -1,25 +1,42 @@
|
|||
= gitlab_ui_form_for [:admin, @group] do |f|
|
||||
= form_errors(@group, pajamas_alert: true)
|
||||
= render 'shared/group_form', f: f
|
||||
= render 'shared/group_form_description', f: f
|
||||
.gl-border-b.gl-mb-6
|
||||
.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
|
||||
|
||||
= render_if_exists 'shared/old_repository_size_limit_setting', form: f, type: :group
|
||||
= render_if_exists 'admin/namespace_plan', 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
|
||||
|
||||
.form-group.gl-form-group{ role: 'group' }
|
||||
= 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
|
||||
.gl-border-b.gl-pb-3.gl-mb-6
|
||||
.row
|
||||
.col-lg-4
|
||||
%h4.gl-mt-0
|
||||
= _('Permissions and group features')
|
||||
%p
|
||||
= _('Configure advanced permissions, Large File Storage, two-factor authentication, and CI/CD settings.')
|
||||
.col-lg-8
|
||||
= render_if_exists 'shared/old_repository_size_limit_setting', form: f, type: :group
|
||||
= render_if_exists 'admin/namespace_plan', f: f
|
||||
.form-group.gl-form-group{ role: 'group' }
|
||||
= 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
|
||||
.gl-mb-3
|
||||
.row
|
||||
.col-lg-4
|
||||
%h4.gl-mt-0
|
||||
= _('Admin notes')
|
||||
.col-lg-8
|
||||
= render 'shared/admin/admin_note_form', f: f
|
||||
|
||||
- if @group.new_record?
|
||||
= render Pajamas::AlertComponent.new(dismissible: false) do |c|
|
||||
|
|
|
@ -24,6 +24,6 @@
|
|||
|
||||
.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.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
|
||||
= _("Time (in hours) that users are allowed to skip forced configuration of two-factor authentication.")
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
= render 'shared/group_form', f: f, autofocus: true
|
||||
|
||||
.row
|
||||
.form-group.col-sm-12.gl-mb-0
|
||||
.form-group.gl-form-group.col-sm-12
|
||||
%label.label-bold
|
||||
= _('Visibility level')
|
||||
%p
|
||||
|
|
|
@ -30,6 +30,6 @@
|
|||
- if @group.avatar?
|
||||
%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'
|
||||
|
||||
= 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' }
|
||||
.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
|
||||
= f.submit s_('Groups|Save changes'), class: 'btn gl-button btn-confirm js-dirty-submit', data: { qa_selector: 'save_name_visibility_settings_button' }
|
||||
|
|
|
@ -52,10 +52,11 @@
|
|||
- unless Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? || !Gitlab.com?
|
||||
.js-deployment-target-select
|
||||
|
||||
= f.label :visibility_level, class: 'label-bold' do
|
||||
= s_('ProjectsNew|Visibility Level')
|
||||
= 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'}
|
||||
.form-group.gl-form-group
|
||||
= f.label :visibility_level, class: 'label-bold' do
|
||||
= s_('ProjectsNew|Visibility Level')
|
||||
= 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
|
||||
= f.label :project_configuration, class: 'label-bold' do
|
||||
|
|
|
@ -67,5 +67,5 @@
|
|||
%ul.list-unstyled.mr_target_commit
|
||||
|
||||
- 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" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- with_label = local_assigns.fetch(:with_label, true)
|
||||
|
||||
.form-group.visibility-level-setting
|
||||
.visibility-level-setting
|
||||
- if with_label
|
||||
= f.label :visibility_level, _('Visibility level'), class: 'label-bold gl-mb-0'
|
||||
%p
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"#{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)},
|
||||
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
|
||||
|
|
|
@ -219,6 +219,15 @@
|
|||
:weight: 1
|
||||
:idempotent: false
|
||||
: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
|
||||
:worker_name: Ci::ScheduleDeleteObjectsCronWorker
|
||||
:feature_category: :continuous_integration
|
||||
|
|
|
@ -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
|
|
@ -9,14 +9,21 @@ module Packages
|
|||
def perform_work
|
||||
return unless artifact
|
||||
|
||||
artifact.transaction do
|
||||
log_metadata(artifact)
|
||||
begin
|
||||
artifact.transaction do
|
||||
log_metadata(artifact)
|
||||
|
||||
artifact.destroy!
|
||||
rescue StandardError
|
||||
artifact.destroy!
|
||||
end
|
||||
rescue StandardError => exception
|
||||
unless artifact&.destroyed?
|
||||
artifact&.update_column(:status, :error)
|
||||
end
|
||||
|
||||
Gitlab::ErrorTracking.log_exception(
|
||||
exception,
|
||||
class: self.class.name
|
||||
)
|
||||
end
|
||||
|
||||
after_destroy
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -540,6 +540,10 @@ production: &base
|
|||
ci_platform_metrics_update_cron_worker:
|
||||
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
|
||||
# installation, and ignored for a CE installation.
|
||||
ee_cron_jobs:
|
||||
|
|
|
@ -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']['cron'] ||= '*/1 * * * *'
|
||||
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
|
||||
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
|
||||
|
|
|
@ -9,6 +9,7 @@ def get_ci_config_files(files)
|
|||
end
|
||||
|
||||
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)
|
||||
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- ")}"
|
||||
|
||||
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."
|
||||
|
|
|
@ -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="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="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="releaselinks"></a>`links` | [`ReleaseLinks`](#releaselinks) | Links of the release. |
|
||||
| <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="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. |
|
||||
|
||||
### `WorkItemWidgetDescription`
|
||||
|
|
|
@ -141,16 +141,7 @@ created with one or more of the following options:
|
|||
|
||||
### Considerations for index names
|
||||
|
||||
Index names don't have any significance in the database, so they should
|
||||
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.
|
||||
Check our [Constraints naming conventions](database/constraint_naming_convention.md) page.
|
||||
|
||||
### 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
|
||||
temporary index:
|
||||
|
||||
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. Prefix the index name with `tmp_` and follow the [naming conventions](database/constraint_naming_convention.md).
|
||||
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.
|
||||
|
||||
|
|
|
@ -71,34 +71,34 @@ It picks reviewers and maintainers from the list at the
|
|||
[engineering projects](https://about.gitlab.com/handbook/engineering/projects/)
|
||||
page, with these behaviors:
|
||||
|
||||
1. 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`.
|
||||
- GitLab user **Busy** indicator is set to `True`.
|
||||
- Emoji is from one of these categories:
|
||||
- **On leave** - 🌴 `:palm_tree:`, 🏖️ `:beach:`, ⛱ `:beach_umbrella:`, 🏖 `:beach_with_umbrella:`, 🌞 `:sun_with_face:`, 🎡 `:ferris_wheel:`
|
||||
- **Out sick** - 🌡️ `:thermometer:`, 🤒 `:face_with_thermometer:`
|
||||
- **At capacity** - 🔴 `:red_circle:`
|
||||
- **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
|
||||
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
|
||||
as a Slack or [GitLab status](../user/profile/index.md#set-your-current-status):
|
||||
- 0️⃣ - `:zero:` (similar to `:red_circle:`)
|
||||
- 1️⃣ - `:one:`
|
||||
- 2️⃣ - `:two:`
|
||||
- 3️⃣ - `:three:`
|
||||
- 4️⃣ - `:four:`
|
||||
- 5️⃣ - `:five:`
|
||||
1. 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.
|
||||
- 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.
|
||||
1. 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.
|
||||
1. 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
|
||||
removes leading `ce-` and `ee-`, and trailing `-ce` and `-ee`, so
|
||||
that it can be stable for backport branches.
|
||||
- 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`.
|
||||
- GitLab user **Busy** indicator is set to `True`.
|
||||
- Emoji is from one of these categories:
|
||||
- **On leave** - 🌴 `:palm_tree:`, 🏖️ `:beach:`, ⛱ `:beach_umbrella:`, 🏖 `:beach_with_umbrella:`, 🌞 `:sun_with_face:`, 🎡 `:ferris_wheel:`
|
||||
- **Out sick** - 🌡️ `:thermometer:`, 🤒 `:face_with_thermometer:`
|
||||
- **At capacity** - 🔴 `:red_circle:`
|
||||
- **Focus mode** - 💡 `:bulb:` (focusing on their team's work)
|
||||
- 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
|
||||
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):
|
||||
- 0️⃣ - `:zero:` (similar to `:red_circle:`)
|
||||
- 1️⃣ - `:one:`
|
||||
- 2️⃣ - `:two:`
|
||||
- 3️⃣ - `:three:`
|
||||
- 4️⃣ - `:four:`
|
||||
- 5️⃣ - `:five:`
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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
|
||||
removes leading `ce-` and `ee-`, and trailing `-ce` and `-ee`, so
|
||||
that it can be stable for backport branches.
|
||||
|
||||
The [Roulette dashboard](https://gitlab-org.gitlab.io/gitlab-roulette) contains:
|
||||
|
||||
|
|
|
@ -25,5 +25,7 @@ The intent is not to retroactively change names in existing databases but rather
|
|||
|
||||
## 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;
|
||||
- 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.
|
||||
|
|
|
@ -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.
|
|
@ -39,7 +39,6 @@ erDiagram
|
|||
projects }|--|| namespaces : ""
|
||||
packages_packages }|--|| projects : ""
|
||||
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_component_files }|--|| packages_debian_group_components : ""
|
||||
packages_debian_group_component_files }|--|| packages_debian_group_architectures : ""
|
||||
|
|
|
@ -77,29 +77,14 @@ microservices-based distributed systems - and displays results within GitLab.
|
|||
|
||||
- [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.
|
||||
> - [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.
|
||||
## Aggregate and store logs (removed) **(FREE SELF)**
|
||||
|
||||
WARNING:
|
||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485)
|
||||
in GitLab 14.7.
|
||||
It will be removed completely in GitLab 15.2.
|
||||
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7
|
||||
and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/360193) 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`.
|
||||
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.
|
||||
<!--- end_remove -->
|
||||
|
||||
## Manage your infrastructure in code
|
||||
|
||||
|
|
|
@ -135,9 +135,6 @@ The options are:
|
|||
- **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.
|
||||
([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.
|
||||
- [Copy link to chart](../embed.md#embedding-gitlab-managed-kubernetes-metrics)
|
||||
|
||||
|
|
|
@ -65,4 +65,3 @@ responsibility. The Application Development Platform integrates key performance
|
|||
into GitLab, automatically. The following features are included:
|
||||
|
||||
- [Auto Monitoring](../autodevops/stages.md#auto-monitoring)
|
||||
- [In-app Kubernetes Logs](../../user/project/clusters/kubernetes_pod_logs.md)
|
||||
|
|
|
@ -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
|
||||
Kubernetes version to a supported version at any time:
|
||||
|
||||
- 1.24 (support ends on September 22, 2023)
|
||||
- 1.23 (support ends on February 22, 2023)
|
||||
- 1.24 (support ends on September 22, 2023 or when 1.27 becomes supported)
|
||||
- 1.23 (support ends on February 22, 2023 or when 1.26 becomes supported)
|
||||
- 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
|
||||
versions at any given time. GitLab regularly reviews the supported versions and
|
||||
provides a three-month deprecation period before removing support for a specific
|
||||
version. The list of supported versions is based on:
|
||||
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.
|
||||
|
||||
- The versions supported by major managed Kubernetes providers.
|
||||
- The versions [supported by the Kubernetes community](https://kubernetes.io/releases/version-skew-policy/#supported-versions).
|
||||
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.
|
||||
|
||||
[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.
|
||||
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.
|
||||
|
||||
## Migrate to the agent from the legacy certificate-based integration
|
||||
|
||||
|
|
|
@ -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**.
|
||||
- Choose labels.
|
||||
- Select a start and due date, or [inherit](#start-and-due-date-inheritance) them.
|
||||
- Select a [color](#epic-color).
|
||||
1. Select **Create epic**.
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
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
|
||||
- Due date
|
||||
- Labels
|
||||
- [Color](#epic-color)
|
||||
|
||||
Prerequisites:
|
||||
|
||||
|
|
|
@ -59,7 +59,6 @@ This feature flag re-enables the certificate-based Kubernetes integration.
|
|||
- [Cluster environments](../../clusters/environments.md)
|
||||
- [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)
|
||||
- [Pod logs](../../project/clusters/kubernetes_pod_logs.md)
|
||||
- [Clusters health](manage/clusters_health.md)
|
||||
- [Web terminals](../../../administration/integration/terminal.md)
|
||||
|
||||
|
|
|
@ -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`).
|
||||
- `<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
|
||||
|
||||
To publish a Helm package automated through [GitLab CI/CD](../../../ci/index.md), you can use
|
||||
|
|
|
@ -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>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) | | | | | ✓ |
|
||||
| [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>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) | | | | ✓ | ✓ |
|
||||
|
|
|
@ -2,85 +2,11 @@
|
|||
stage: Monitor
|
||||
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
|
||||
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.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26383) from GitLab Ultimate to GitLab Free 12.9.
|
||||
> - [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).
|
||||
This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5
|
||||
and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/360193) in GitLab 15.2.
|
||||
|
|
|
@ -24,6 +24,13 @@ members.
|
|||
> - [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.
|
||||
|
||||
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,
|
||||
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'?
|
||||
|
@ -42,12 +49,11 @@ After sharing 'Project Acme' with 'Engineering':
|
|||
- The group is listed in the **Groups** tab.
|
||||
- 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.
|
||||
- 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 system.
|
||||
- [Maximum access level](#maximum-access-level)
|
||||
- [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)
|
||||
|
||||
## 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 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
|
||||
|
||||
|
|
|
@ -250,11 +250,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def running_jobs_relation(job)
|
||||
if ::Feature.enabled?(:ci_pending_builds_maintain_denormalized_data)
|
||||
::Ci::RunningBuild.instance_type.where(project_id: job.project_id)
|
||||
else
|
||||
job.project.builds.running.where(runner: ::Ci::Runner.instance_type)
|
||||
end
|
||||
::Ci::RunningBuild.instance_type.where(project_id: job.project_id)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module Gitlab
|
|||
def check_runner_upgrade_status(runner_version)
|
||||
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
|
||||
return :error unless releases
|
||||
|
|
|
@ -63,10 +63,7 @@ module Gitlab
|
|||
def tree_entries_with_flat_path_from_rugged(repository, sha, path, recursive)
|
||||
tree_entries_from_rugged(repository, sha, path, recursive).tap do |entries|
|
||||
# This was an optimization to reduce N+1 queries for Gitaly
|
||||
# (https://gitlab.com/gitlab-org/gitaly/issues/530). It
|
||||
# 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.
|
||||
# (https://gitlab.com/gitlab-org/gitaly/issues/530).
|
||||
rugged_populate_flat_path(repository, sha, path, entries)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -27,6 +27,9 @@ module Gitlab
|
|||
when 'labeled', 'unlabeled'
|
||||
Gitlab::GithubImport::Importer::Events::ChangedLabel.new(project, author_id)
|
||||
.execute(issue_event)
|
||||
when 'renamed'
|
||||
Gitlab::GithubImport::Importer::Events::Renamed.new(project, author_id)
|
||||
.execute(issue_event)
|
||||
else
|
||||
Gitlab::GithubImport::Logger.debug(
|
||||
message: 'UNSUPPORTED_EVENT_TYPE',
|
||||
|
|
|
@ -9,7 +9,7 @@ module Gitlab
|
|||
|
||||
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
|
||||
|
||||
# Builds a event from a GitHub API response.
|
||||
|
@ -22,6 +22,8 @@ module Gitlab
|
|||
event: event.event,
|
||||
commit_id: event.commit_id,
|
||||
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,
|
||||
created_at: event.created_at
|
||||
)
|
||||
|
@ -30,7 +32,7 @@ module Gitlab
|
|||
# Builds a event using a Hash that was built from a JSON payload.
|
||||
def self.from_json_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)
|
||||
end
|
||||
|
|
|
@ -8,10 +8,7 @@ module Gitlab
|
|||
CACHE_EXPIRE_IN = 1.hour
|
||||
MAX_OFFSET = 2**31
|
||||
|
||||
attr_reader :commit, :project, :path, :offset, :limit, :user
|
||||
|
||||
attr_reader :resolved_commits
|
||||
private :resolved_commits
|
||||
attr_reader :commit, :project, :path, :offset, :limit, :user, :resolved_commits
|
||||
|
||||
def initialize(commit, project, user, params = {})
|
||||
@commit = commit
|
||||
|
@ -34,44 +31,37 @@ module Gitlab
|
|||
#
|
||||
# - An Array of Hashes containing the following keys:
|
||||
# - 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_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
|
||||
summary = contents
|
||||
.tap { |summary| fill_last_commits!(summary) }
|
||||
commits_hsh = fetch_last_cached_commits_list
|
||||
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
|
||||
|
||||
def fetch_logs
|
||||
logs, _ = summarize
|
||||
logs = summarize
|
||||
|
||||
new_offset = next_offset if more?
|
||||
|
||||
[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
|
||||
[logs.first(limit).as_json, next_offset(logs.size)]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contents
|
||||
all_contents[offset, limit] || []
|
||||
end
|
||||
def next_offset(entries_count)
|
||||
return if entries_count <= limit
|
||||
|
||||
def commits
|
||||
resolved_commits.values
|
||||
offset + limit
|
||||
end
|
||||
|
||||
def repository
|
||||
|
@ -83,32 +73,12 @@ module Gitlab
|
|||
File.join(*[path, ""]) if path
|
||||
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
|
||||
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
|
||||
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) }
|
||||
end
|
||||
|
||||
|
@ -131,26 +101,6 @@ module Gitlab
|
|||
Gitlab::Routing.url_helpers.project_commit_path(project, commit)
|
||||
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)
|
||||
# Preload commit authors as they are used in rendering
|
||||
commits.each(&:lazy_author)
|
||||
|
|
|
@ -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}"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -200,9 +200,9 @@
|
|||
"yaml": "^2.0.0-10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gitlab/eslint-plugin": "13.0.0",
|
||||
"@gitlab/eslint-plugin": "13.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",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@vue/test-utils": "1.3.0",
|
||||
|
|
|
@ -178,7 +178,8 @@ RSpec.describe RegistrationsController do
|
|||
category: 'RegistrationsController',
|
||||
action: 'accepted',
|
||||
label: 'invite_email',
|
||||
property: member.id.to_s
|
||||
property: member.id.to_s,
|
||||
user: member.reload.user
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -221,7 +221,8 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
category: 'RegistrationsController',
|
||||
action: 'accepted',
|
||||
label: 'invite_email',
|
||||
property: group_invite.id.to_s
|
||||
property: group_invite.id.to_s,
|
||||
user: group_invite.reload.user
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -357,6 +357,10 @@ RSpec.describe 'Pipelines', :js do
|
|||
end
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,9 +27,9 @@ RSpec.describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :c
|
|||
render_views
|
||||
|
||||
it 'deploy_keys/keys.json' do
|
||||
create(:rsa_deploy_key_2048, public: true)
|
||||
project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com')
|
||||
internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com')
|
||||
create(:rsa_deploy_key_5120, public: true)
|
||||
project_key = create(:deploy_key)
|
||||
internal_key = create(:deploy_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: project3, deploy_key: project_key)
|
||||
|
|
|
@ -16,19 +16,18 @@ const mockData = [
|
|||
commit_path: `https://test.com`,
|
||||
commit_title_html: 'commit title',
|
||||
file_name: 'index.js',
|
||||
type: 'blob',
|
||||
},
|
||||
];
|
||||
|
||||
describe('resolveCommit', () => {
|
||||
it('calls resolve when commit found', () => {
|
||||
const resolver = {
|
||||
entry: { name: 'index.js', type: 'blob' },
|
||||
entry: { name: 'index.js' },
|
||||
resolve: jest.fn(),
|
||||
};
|
||||
const commits = [
|
||||
{ fileName: 'index.js', filePath: '/index.js', type: 'blob' },
|
||||
{ fileName: 'index.js', filePath: '/app/assets/index.js', type: 'blob' },
|
||||
{ fileName: 'index.js', filePath: '/index.js' },
|
||||
{ fileName: 'index.js', filePath: '/app/assets/index.js' },
|
||||
];
|
||||
|
||||
resolveCommit(commits, '', resolver);
|
||||
|
@ -36,7 +35,6 @@ describe('resolveCommit', () => {
|
|||
expect(resolver.resolve).toHaveBeenCalledWith({
|
||||
fileName: 'index.js',
|
||||
filePath: '/index.js',
|
||||
type: 'blob',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -56,7 +54,7 @@ describe('fetchLogsTree', () => {
|
|||
global.gon = { relative_url_root: '' };
|
||||
|
||||
resolver = {
|
||||
entry: { name: 'index.js', type: 'blob' },
|
||||
entry: { name: 'index.js' },
|
||||
resolve: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -119,7 +117,6 @@ describe('fetchLogsTree', () => {
|
|||
filePath: '/index.js',
|
||||
message: 'testing message',
|
||||
sha: '123',
|
||||
type: 'blob',
|
||||
}),
|
||||
);
|
||||
}));
|
||||
|
@ -136,7 +133,6 @@ describe('fetchLogsTree', () => {
|
|||
message: 'testing message',
|
||||
sha: '123',
|
||||
titleHtml: 'commit title',
|
||||
type: 'blob',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
|
@ -10,7 +10,6 @@ const mockData = [
|
|||
commit_path: `https://test.com`,
|
||||
commit_title_html: 'testing message',
|
||||
file_name: 'index.js',
|
||||
type: 'blob',
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -24,7 +23,6 @@ describe('normalizeData', () => {
|
|||
commitPath: 'https://test.com',
|
||||
fileName: 'index.js',
|
||||
filePath: '/index.js',
|
||||
type: 'blob',
|
||||
titleHtml: 'testing message',
|
||||
__typename: 'LogTreeCommit',
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
|
|||
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
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 RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
|
||||
import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
|
||||
|
@ -37,6 +38,7 @@ describe('AdminRunnerShowApp', () => {
|
|||
let mockRunnerQuery;
|
||||
|
||||
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
|
||||
const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
|
||||
const findRunnerDeleteButton = () => wrapper.findComponent(RunnerDeleteButton);
|
||||
const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton);
|
||||
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', () => {
|
||||
beforeEach(async () => {
|
||||
mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('does not show runner details', () => {
|
||||
expect(findRunnerDetails().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('error is reported to sentry', () => {
|
||||
expect(captureException).toHaveBeenCalledWith({
|
||||
error: new Error('Error!'),
|
||||
|
@ -201,13 +223,6 @@ describe('AdminRunnerShowApp', () => {
|
|||
const stubs = {
|
||||
GlTab,
|
||||
GlTabs,
|
||||
RunnerDetails: {
|
||||
template: `
|
||||
<div>
|
||||
<slot name="jobs-tab"></slot>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
it('without a runner, shows no jobs', () => {
|
||||
|
|
|
@ -25,12 +25,7 @@ describe('RunnerDetails', () => {
|
|||
|
||||
const findDetailGroups = () => wrapper.findComponent(RunnerGroups);
|
||||
|
||||
const createComponent = ({
|
||||
props = {},
|
||||
stubs,
|
||||
mountFn = shallowMountExtended,
|
||||
...options
|
||||
} = {}) => {
|
||||
const createComponent = ({ props = {}, stubs, mountFn = shallowMountExtended } = {}) => {
|
||||
wrapper = mountFn(RunnerDetails, {
|
||||
propsData: {
|
||||
...props,
|
||||
|
@ -39,7 +34,6 @@ describe('RunnerDetails', () => {
|
|||
RunnerDetail,
|
||||
...stubs,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -47,16 +41,6 @@ describe('RunnerDetails', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('when no runner is present, no contents are shown', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
runner: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe('');
|
||||
});
|
||||
|
||||
describe('Details tab', () => {
|
||||
describe.each`
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
beforeEach(async () => {
|
||||
mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('does not show runner details', () => {
|
||||
expect(findRunnerDetails().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('error is reported to sentry', () => {
|
||||
expect(captureException).toHaveBeenCalledWith({
|
||||
error: new Error('Error!'),
|
||||
|
|
|
@ -11,7 +11,8 @@ RSpec.describe GitlabSchema.types['Release'] do
|
|||
description description_html
|
||||
name milestones evidences author commit
|
||||
assets links
|
||||
created_at released_at
|
||||
created_at released_at upcoming_release
|
||||
historical_release
|
||||
]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
|
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Types::WorkItems::Widgets::AssigneesType 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)
|
||||
end
|
||||
|
|
|
@ -3,63 +3,12 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe TreeHelper do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let(:repository) { project.repository }
|
||||
let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' }
|
||||
|
||||
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
|
||||
it 'escapes HTML from the branch name' do
|
||||
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
|
||||
include ProjectForksHelper
|
||||
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:forked_project) { create(:project, :repository, namespace: user.namespace) }
|
||||
|
||||
before do
|
||||
|
|
|
@ -65,16 +65,16 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
|
|||
context 'with nil runner_version' do
|
||||
let(:runner_version) { nil }
|
||||
|
||||
it 'returns :invalid' do
|
||||
is_expected.to eq(:invalid)
|
||||
it 'returns :invalid_version' do
|
||||
is_expected.to eq(:invalid_version)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid runner_version' do
|
||||
let(:runner_version) { 'junk' }
|
||||
|
||||
it 'returns :invalid' do
|
||||
is_expected.to eq(:invalid)
|
||||
it 'returns :invalid_version' do
|
||||
is_expected.to eq(:invalid_version)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
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(:relative_path) { project.disk_path + '.git' }
|
||||
let(:repository) { project.repository }
|
||||
|
|
|
@ -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
|
|
@ -80,6 +80,13 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
|
|||
Gitlab::GithubImport::Importer::Events::ChangedLabel
|
||||
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
|
||||
let(:event_name) { 'fake' }
|
||||
|
||||
|
|
|
@ -57,6 +57,22 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
|
|||
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
|
||||
expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
|
||||
end
|
||||
|
@ -73,7 +89,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
|
|||
let(:response) do
|
||||
event_resource = Struct.new(
|
||||
: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
|
||||
)
|
||||
user_resource = Struct.new(:id, :login, keyword_init: true)
|
||||
|
@ -86,6 +102,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
|
|||
commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||
commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\
|
||||
'/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||
rename: with_rename ? { from: 'old title', to: 'new title' } : nil,
|
||||
issue_db_id: 100500,
|
||||
label: with_label ? { name: 'label title' } : nil,
|
||||
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_label) { true }
|
||||
let(:with_rename) { true }
|
||||
|
||||
it_behaves_like 'an IssueEvent' do
|
||||
let(:issue_event) { described_class.from_api_response(response) }
|
||||
|
@ -114,6 +132,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
|
|||
'commit_url' =>
|
||||
'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||
'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,
|
||||
'created_at' => '2022-04-26 18:30:53 UTC',
|
||||
'performed_via_github_app' => nil
|
||||
|
@ -122,6 +142,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
|
|||
|
||||
let(:with_actor) { true }
|
||||
let(:with_label) { true }
|
||||
let(:with_rename) { true }
|
||||
|
||||
let(:issue_event) { described_class.from_json_hash(hash) }
|
||||
end
|
||||
|
|
|
@ -30,50 +30,31 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
describe '#summarize' do
|
||||
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
|
||||
expect(summarized).to be_a(Array)
|
||||
expect(summarized.size).to eq(2)
|
||||
it 'returns an array of entries' do
|
||||
expect(entries).to be_a(Array)
|
||||
expect(entries.size).to eq(1)
|
||||
|
||||
entries, commits = *summarized
|
||||
aggregate_failures do
|
||||
expect(entries).to contain_exactly(
|
||||
a_hash_including(file_name: 'a.txt', commit: have_attributes(id: commit.id))
|
||||
)
|
||||
|
||||
expect(commits).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([[], []])
|
||||
expect(summary.resolved_commits.values).to match_array(entries.map { |entry| entry[:commit] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with caching', :use_clean_rails_memory_store_caching do
|
||||
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
|
||||
let(:offset) { 0 }
|
||||
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
|
||||
summarized
|
||||
entries
|
||||
|
||||
is_expected.to eq('a.txt' => commit.to_hash)
|
||||
end
|
||||
|
@ -93,7 +74,7 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
let(:expected_message) { message[0...1021] + '...' }
|
||||
|
||||
it 'truncates commit message to 1 kilobyte' do
|
||||
summarized
|
||||
entries
|
||||
|
||||
is_expected.to include('long.txt' => a_hash_including(message: expected_message))
|
||||
end
|
||||
|
@ -102,7 +83,7 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#summarize (entries)' do
|
||||
describe '#fetch_logs' do
|
||||
let(:limit) { 4 }
|
||||
|
||||
custom_files = {
|
||||
|
@ -116,33 +97,32 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
let!(:project) { create(:project, :custom_repo, files: custom_files) }
|
||||
let(:commit) { repo.head_commit }
|
||||
|
||||
subject(:entries) { summary.summarize.first }
|
||||
subject(:entries) { summary.fetch_logs.first }
|
||||
|
||||
it 'summarizes the entries within the window' do
|
||||
is_expected.to contain_exactly(
|
||||
a_hash_including(type: :tree, file_name: 'directory'),
|
||||
a_hash_including(type: :blob, file_name: 'a.txt'),
|
||||
a_hash_including(type: :blob, file_name: ':file'),
|
||||
a_hash_including(type: :tree, file_name: ':dir')
|
||||
a_hash_including('file_name' => 'directory'),
|
||||
a_hash_including('file_name' => 'a.txt'),
|
||||
a_hash_including('file_name' => ':file'),
|
||||
a_hash_including('file_name' => ':dir')
|
||||
# b.txt is excluded by the limit
|
||||
)
|
||||
end
|
||||
|
||||
it 'references the commit and commit path in entries' do
|
||||
# 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)
|
||||
|
||||
expect(entry[:commit]).to be_a(::Commit)
|
||||
expect(entry[:commit_path]).to eq(expected_commit_path)
|
||||
expect(entry[:commit_title_html]).to eq(commit.message)
|
||||
expect(entry['commit_path']).to eq(expected_commit_path)
|
||||
expect(entry['commit_title_html']).to eq(commit.message)
|
||||
end
|
||||
|
||||
context 'in a good subdirectory' do
|
||||
let(:path) { 'directory' }
|
||||
|
||||
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
|
||||
|
||||
|
@ -150,7 +130,7 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
let(:path) { ':dir' }
|
||||
|
||||
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
|
||||
|
||||
|
@ -164,7 +144,25 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
let(:offset) { 4 }
|
||||
|
||||
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
|
||||
|
@ -178,10 +176,11 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
let(:project) { create(:project, :repository) }
|
||||
let(:commit) { repo.commit(test_commit_sha) }
|
||||
let(:limit) { nil }
|
||||
let(:entries) { summary.summarize.first }
|
||||
let(:entries) { summary.summarize }
|
||||
|
||||
subject(:commits) do
|
||||
summary.summarize.last
|
||||
summary.summarize
|
||||
summary.resolved_commits.values
|
||||
end
|
||||
|
||||
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(:issue) { create(:issue, project: project) }
|
||||
|
||||
let(:entries) { summary.summarize.first }
|
||||
let(:entries) { summary.summarize }
|
||||
let(:entry) { entries.find { |entry| entry[:file_name] == 'issue.txt' } }
|
||||
|
||||
before_all do
|
||||
|
@ -264,67 +263,6 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
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:)
|
||||
repo.create_file(
|
||||
project.creator,
|
||||
|
|
|
@ -118,41 +118,27 @@ RSpec.describe Ci::PendingBuild do
|
|||
project.shared_runners_enabled = true
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
expect(described_class.last.instance_runners_enabled).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ci_pending_builds_maintain_denormalized_data is disabled' do
|
||||
context 'when builds are disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_pending_builds_maintain_denormalized_data: false)
|
||||
build.project.project_feature.update!(builds_access_level: false)
|
||||
end
|
||||
|
||||
it 'sets instance_runners_enabled to false' do
|
||||
|
@ -168,24 +154,10 @@ RSpec.describe Ci::PendingBuild do
|
|||
|
||||
subject(:ci_pending_build) { described_class.last }
|
||||
|
||||
context 'when ci_pending_builds_maintain_denormalized_data is enabled' do
|
||||
it 'sets tag_ids' do
|
||||
described_class.upsert_from_build!(build)
|
||||
it 'sets tag_ids' do
|
||||
described_class.upsert_from_build!(build)
|
||||
|
||||
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
|
||||
expect(ci_pending_build.tag_ids).to eq(build.tags_ids)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,13 +5,40 @@ require 'spec_helper'
|
|||
RSpec.describe Ci::RunnerVersion do
|
||||
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
|
||||
subject { described_class.not_available }
|
||||
|
||||
let!(:runner_version1) { create(:ci_runner_version, version: 'abc123', status: :not_available) }
|
||||
let!(:runner_version2) { create(:ci_runner_version, version: 'abc234', status: :recommended) }
|
||||
it { is_expected.to match_array([runner_version_not_available]) }
|
||||
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
|
||||
|
||||
describe 'validation' do
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Tree do
|
||||
let(:repository) { create(:project, :repository).repository }
|
||||
let_it_be(:repository) { create(:project, :repository).repository }
|
||||
|
||||
let(:sha) { repository.root_ref }
|
||||
|
||||
subject(:tree) { described_class.new(repository, '54fcc214') }
|
||||
|
|
|
@ -200,6 +200,7 @@ RSpec.describe 'Query.work_item(id)' do
|
|||
type
|
||||
... on WorkItemWidgetAssignees {
|
||||
allowsMultipleAssignees
|
||||
canInviteMembers
|
||||
assignees {
|
||||
nodes {
|
||||
id
|
||||
|
@ -218,6 +219,7 @@ RSpec.describe 'Query.work_item(id)' do
|
|||
hash_including(
|
||||
'type' => 'ASSIGNEES',
|
||||
'allowsMultipleAssignees' => boolean,
|
||||
'canInviteMembers' => boolean,
|
||||
'assignees' => {
|
||||
'nodes' => match_array(
|
||||
assignees.map { |a| { 'id' => a.to_gid.to_s, 'username' => a.username } }
|
||||
|
|
|
@ -750,41 +750,7 @@ module Ci
|
|||
end
|
||||
|
||||
context 'when using pending builds table' do
|
||||
before do
|
||||
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
|
||||
include_examples 'handles runner assignment'
|
||||
|
||||
context 'when a conflicting data is stored in denormalized table' do
|
||||
let!(:specific_runner) { create(:ci_runner, :project, projects: [project], tag_list: %w[conflict]) }
|
||||
|
@ -805,22 +771,6 @@ module Ci
|
|||
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
|
||||
|
||||
describe '#register_success' do
|
||||
|
@ -888,14 +838,6 @@ module Ci
|
|||
shared_examples 'metrics collector' do
|
||||
it_behaves_like 'attempt counter 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
|
||||
|
||||
context 'when shared runner is used' do
|
||||
|
|
|
@ -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
|
|
@ -42,19 +42,6 @@ RSpec.describe Ci::UpdatePendingBuildService do
|
|||
expect(pending_build_1.instance_runners_enabled).to be_truthy
|
||||
expect(pending_build_2.instance_runners_enabled).to be_truthy
|
||||
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
|
||||
|
||||
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_2.instance_runners_enabled).to be_truthy
|
||||
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
|
||||
|
|
|
@ -52,6 +52,7 @@ RSpec.describe Packages::CleanupPackageFileWorker do
|
|||
end
|
||||
|
||||
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(package_file.reload).to be_error
|
||||
end
|
||||
|
@ -71,7 +72,9 @@ RSpec.describe Packages::CleanupPackageFileWorker do
|
|||
end
|
||||
|
||||
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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue