Add latest changes from gitlab-org/gitlab@master

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

View File

@ -409,7 +409,7 @@ group :development, :test do
end
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,10 @@ import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_optio
import createDefaultClient from '~/lib/graphql';
import { 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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,19 +23,11 @@ module Ci
end
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)

View File

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

View File

@ -14,8 +14,6 @@ module Ci
# Add a build to the pending builds queue
#
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,14 +9,21 @@ module Packages
def perform_work
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

View File

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

View File

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

View File

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

View File

@ -540,6 +540,10 @@ production: &base
ci_platform_metrics_update_cron_worker:
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:

View File

@ -630,6 +630,9 @@ Settings.cron_jobs['inactive_projects_deletion_cron_worker']['job_class'] = 'Pro
Settings.cron_jobs['loose_foreign_keys_cleanup_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['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({})

View File

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

View File

@ -16419,6 +16419,7 @@ Represents a release.
| <a id="releasedescription"></a>`description` | [`String`](#string) | Description (also known as "release notes") of the release. |
| <a id="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`

View File

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

View File

@ -71,34 +71,34 @@ It picks reviewers and maintainers from the list at the
[engineering projects](https://about.gitlab.com/handbook/engineering/projects/)
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:

View File

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

View File

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

View File

@ -39,7 +39,6 @@ erDiagram
projects }|--|| namespaces : ""
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 : ""

View File

@ -77,29 +77,14 @@ microservices-based distributed systems - and displays results within GitLab.
- [Trace the performance and health](tracing.md) of a deployed application.
## 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

View File

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

View File

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

View File

@ -43,22 +43,17 @@ This workflow is considered push-based, because GitLab is pushing requests from
GitLab supports the following Kubernetes versions. You can upgrade your
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

View File

@ -34,6 +34,7 @@ To create an epic in the group you're in:
- To [make the epic confidential](#make-an-epic-confidential), select the checkbox under **Confidentiality**.
- 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:

View File

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

View File

@ -69,6 +69,11 @@ Once built, a chart can be uploaded to the desired channel with `curl` or `helm
- `<project_id>`: the project ID (like `42`).
- `<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

View File

@ -69,7 +69,6 @@ The following table lists project permissions available for each role:
| [Application security](application_security/index.md):<br>View [dependency list](application_security/dependency_list/index.md) | | | ✓ | ✓ | ✓ |
| [Application security](application_security/index.md):<br>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) | | | | ✓ | ✓ |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,6 +27,9 @@ module Gitlab
when 'labeled', 'unlabeled'
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',

View File

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

View File

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

View File

@ -9669,6 +9669,9 @@ msgstr ""
msgid "Configure a %{codeStart}.gitlab-webide.yml%{codeEnd} file in the %{codeStart}.gitlab%{codeEnd} directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
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 ""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -177,12 +177,28 @@ describe('GroupRunnerShowApp', () => {
});
});
describe('When loading', () => {
beforeEach(() => {
mockRunnerQueryResult();
createComponent();
});
it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
});
describe('When there is an error', () => {
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!'),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -80,6 +80,13 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
Gitlab::GithubImport::Importer::Events::ChangedLabel
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' }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -42,19 +42,6 @@ RSpec.describe Ci::UpdatePendingBuildService do
expect(pending_build_1.instance_runners_enabled).to be_truthy
expect(pending_build_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

View File

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