Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0e0ec3ddd5
commit
b82c4935ec
|
|
@ -1588,6 +1588,7 @@ Gitlab/NamespacedClass:
|
|||
- 'app/models/list_user_preference.rb'
|
||||
- 'app/models/member.rb'
|
||||
- 'app/models/members/group_member.rb'
|
||||
- 'app/models/members/last_group_owner_assigner.rb'
|
||||
- 'app/models/members/project_member.rb'
|
||||
- 'app/models/members_preloader.rb'
|
||||
- 'app/models/merge_request.rb'
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ export function isAbsolute(url) {
|
|||
* @param {String} url
|
||||
*/
|
||||
export function isRootRelative(url) {
|
||||
return /^\//.test(url);
|
||||
return /^\/(?!\/)/.test(url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
|
||||
|
||||
export default {
|
||||
name: 'FileSha',
|
||||
components: {
|
||||
DetailsRow,
|
||||
ClipboardButton,
|
||||
},
|
||||
props: {
|
||||
sha: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
copyButtonTitle: s__('PackageRegistry|Copy SHA'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<details-row dashed>
|
||||
<div class="gl-px-4">
|
||||
{{ title }}:
|
||||
{{ sha }}
|
||||
<clipboard-button
|
||||
:text="sha"
|
||||
:title="$options.i18n.copyButtonTitle"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</details-row>
|
||||
</template>
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
<script>
|
||||
import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon } from '@gitlab/ui';
|
||||
import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui';
|
||||
import { last } from 'lodash';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { __ } from '~/locale';
|
||||
import FileSha from '~/packages/details/components/file_sha.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
|
@ -15,8 +16,10 @@ export default {
|
|||
GlIcon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlButton,
|
||||
FileIcon,
|
||||
TimeAgoTooltip,
|
||||
FileSha,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
props: {
|
||||
|
|
@ -76,6 +79,9 @@ export default {
|
|||
formatSize(size) {
|
||||
return numberToHumanSize(size);
|
||||
},
|
||||
hasDetails(item) {
|
||||
return item.file_sha256 || item.file_md5 || item.file_sha1;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
deleteFile: __('Delete file'),
|
||||
|
|
@ -91,7 +97,15 @@ export default {
|
|||
:items="filesTableRows"
|
||||
:tbody-tr-attr="{ 'data-testid': 'file-row' }"
|
||||
>
|
||||
<template #cell(name)="{ item }">
|
||||
<template #cell(name)="{ item, toggleDetails, detailsShowing }">
|
||||
<gl-button
|
||||
v-if="hasDetails(item)"
|
||||
:icon="detailsShowing ? 'angle-up' : 'angle-down'"
|
||||
:aria-label="detailsShowing ? __('Collapse') : __('Expand')"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
@click="toggleDetails"
|
||||
/>
|
||||
<gl-link
|
||||
:href="item.download_path"
|
||||
class="gl-text-gray-500"
|
||||
|
|
@ -131,6 +145,21 @@ export default {
|
|||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
|
||||
<template #row-details="{ item }">
|
||||
<div
|
||||
class="gl-display-flex gl-flex-direction-column gl-flex-fill-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100"
|
||||
>
|
||||
<file-sha
|
||||
v-if="item.file_sha256"
|
||||
data-testid="sha-256"
|
||||
title="SHA-256"
|
||||
:sha="item.file_sha256"
|
||||
/>
|
||||
<file-sha v-if="item.file_md5" data-testid="md5" title="MD5" :sha="item.file_md5" />
|
||||
<file-sha v-if="item.file_sha1" data-testid="sha-1" title="SHA-1" :sha="item.file_sha1" />
|
||||
</div>
|
||||
</template>
|
||||
</gl-table>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
restrictedVisibilityLevels: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -66,6 +70,7 @@ export default {
|
|||
:project-path="projectPath"
|
||||
:project-description="projectDescription"
|
||||
:project-visibility="projectVisibility"
|
||||
:restricted-visibility-levels="restrictedVisibilityLevels"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -95,6 +95,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
restrictedVisibilityLevels: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const form = {
|
||||
|
|
@ -111,7 +115,7 @@ export default {
|
|||
required: false,
|
||||
skipValidation: true,
|
||||
}),
|
||||
visibility: initFormField({ value: this.projectVisibility }),
|
||||
visibility: initFormField({ value: this.getInitialVisibilityValue() }),
|
||||
},
|
||||
};
|
||||
return {
|
||||
|
|
@ -134,13 +138,28 @@ export default {
|
|||
visibilityLevelCap() {
|
||||
return Math.min(this.projectVisibilityLevel, this.namespaceVisibilityLevel);
|
||||
},
|
||||
restrictedVisibilityLevelsSet() {
|
||||
return new Set(this.restrictedVisibilityLevels);
|
||||
},
|
||||
allowedVisibilityLevels() {
|
||||
return Object.entries(VISIBILITY_LEVEL).reduce((levels, [levelName, levelValue]) => {
|
||||
if (levelValue <= this.visibilityLevelCap) {
|
||||
levels.push(levelName);
|
||||
}
|
||||
return levels;
|
||||
}, []);
|
||||
const allowedLevels = Object.entries(VISIBILITY_LEVEL).reduce(
|
||||
(levels, [levelName, levelValue]) => {
|
||||
if (
|
||||
!this.restrictedVisibilityLevelsSet.has(levelValue) &&
|
||||
levelValue <= this.visibilityLevelCap
|
||||
) {
|
||||
levels.push(levelName);
|
||||
}
|
||||
return levels;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
if (!allowedLevels.length) {
|
||||
return [PRIVATE_VISIBILITY];
|
||||
}
|
||||
|
||||
return allowedLevels;
|
||||
},
|
||||
visibilityLevels() {
|
||||
return [
|
||||
|
|
@ -173,7 +192,8 @@ export default {
|
|||
watch: {
|
||||
// eslint-disable-next-line func-names
|
||||
'form.fields.namespace.value': function () {
|
||||
this.form.fields.visibility.value = PRIVATE_VISIBILITY;
|
||||
this.form.fields.visibility.value =
|
||||
this.restrictedVisibilityLevels.length !== 0 ? null : PRIVATE_VISIBILITY;
|
||||
},
|
||||
// eslint-disable-next-line func-names
|
||||
'form.fields.name.value': function (newVal) {
|
||||
|
|
@ -191,6 +211,9 @@ export default {
|
|||
isVisibilityLevelDisabled(visibility) {
|
||||
return !this.allowedVisibilityLevels.includes(visibility);
|
||||
},
|
||||
getInitialVisibilityValue() {
|
||||
return this.restrictedVisibilityLevels.length !== 0 ? null : this.projectVisibility;
|
||||
},
|
||||
async onSubmit() {
|
||||
this.form.showValidation = true;
|
||||
|
||||
|
|
@ -340,6 +363,7 @@ export default {
|
|||
v-model="form.fields.visibility.value"
|
||||
data-testid="fork-visibility-radio-group"
|
||||
name="visibility"
|
||||
:aria-label="__('visibility')"
|
||||
required
|
||||
>
|
||||
<gl-form-radio
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ if (gon.features.forkProjectForm) {
|
|||
projectPath,
|
||||
projectDescription,
|
||||
projectVisibility,
|
||||
restrictedVisibilityLevels,
|
||||
} = mountElement.dataset;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
|
|
@ -38,6 +39,7 @@ if (gon.features.forkProjectForm) {
|
|||
projectPath,
|
||||
projectDescription,
|
||||
projectVisibility,
|
||||
restrictedVisibilityLevels: JSON.parse(restrictedVisibilityLevels),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ import refQuery from './queries/ref.query.graphql';
|
|||
const fetchpromises = {};
|
||||
const resolvers = {};
|
||||
let maxOffset;
|
||||
let nextOffset;
|
||||
let currentPath;
|
||||
|
||||
function setNextOffset(offset) {
|
||||
nextOffset = offset || null;
|
||||
}
|
||||
|
||||
export function resolveCommit(commits, path, { resolve, entry }) {
|
||||
const commit = commits.find(
|
||||
|
|
@ -24,7 +30,17 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset
|
|||
maxOffset = _maxOffset;
|
||||
}
|
||||
|
||||
if (Number(offset) > maxOffset) {
|
||||
if (!currentPath || currentPath !== path) {
|
||||
// ensures the nextOffset is reset if the user changed directories
|
||||
setNextOffset(null);
|
||||
}
|
||||
|
||||
currentPath = path;
|
||||
|
||||
const offsetNumber = Number(offset);
|
||||
|
||||
if (!nextOffset && offsetNumber > maxOffset) {
|
||||
setNextOffset(offsetNumber - 25); // ensures commit data is fetched for newly added rows that need data from the previous request (requests are made in batches of 25).
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +63,7 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset
|
|||
path.replace(/^\//, ''),
|
||||
)}`,
|
||||
{
|
||||
params: { format: 'json', offset },
|
||||
params: { format: 'json', offset: nextOffset || offset },
|
||||
},
|
||||
)
|
||||
.then(({ data: newData, headers }) => {
|
||||
|
|
@ -66,10 +82,12 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset
|
|||
delete fetchpromises[path];
|
||||
|
||||
if (headerLogsOffset) {
|
||||
setNextOffset(null);
|
||||
fetchLogsTree(client, path, headerLogsOffset);
|
||||
} else {
|
||||
delete resolvers[path];
|
||||
maxOffset = null;
|
||||
setNextOffset(null);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ export default {
|
|||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
|
|
@ -34,7 +35,7 @@ export default {
|
|||
class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all"
|
||||
:class="[padding, borderClass]"
|
||||
>
|
||||
<gl-icon :name="icon" class="gl-mr-4" />
|
||||
<gl-icon v-if="icon" :name="icon" class="gl-mr-4" />
|
||||
<span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
|
|||
end
|
||||
|
||||
def cluster_application_params
|
||||
params.permit(:application, :hostname, :pages_domain_id, :email, :stack, :modsecurity_enabled, :modsecurity_mode, :host, :port, :protocol, :waf_log_enabled, :cilium_log_enabled)
|
||||
params.permit(:application, :hostname, :pages_domain_id, :email, :stack, :host, :port, :protocol, :cilium_log_enabled)
|
||||
end
|
||||
|
||||
def cluster_application_destroy_params
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::Analytics::CycleAnalytics::SummaryController < Projects::ApplicationController
|
||||
include CycleAnalyticsParams
|
||||
|
||||
respond_to :json
|
||||
|
||||
feature_category :planning_analytics
|
||||
|
||||
before_action :authorize_read_cycle_analytics!
|
||||
|
||||
def show
|
||||
render json: project_level.summary
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_level
|
||||
@project_level ||= Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(allowed_params))
|
||||
end
|
||||
|
||||
def allowed_params
|
||||
params.permit(:created_after, :created_before)
|
||||
end
|
||||
end
|
||||
|
||||
Projects::Analytics::CycleAnalytics::SummaryController.prepend_mod_with('Projects::Analytics::CycleAnalytics::SummaryController')
|
||||
|
|
@ -53,7 +53,7 @@ module Projects
|
|||
end
|
||||
|
||||
def cycle_analytics
|
||||
@cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project, options: options(cycle_analytics_project_params))
|
||||
@cycle_analytics ||= ::Analytics::CycleAnalytics::ProjectLevel.new(project: project, options: options(cycle_analytics_project_params))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
|
|||
feature_category :planning_analytics
|
||||
|
||||
def show
|
||||
@cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params))
|
||||
@cycle_analytics = Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(cycle_analytics_project_params))
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
|
|
|||
|
|
@ -176,11 +176,7 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def retry
|
||||
if Gitlab::Ci::Features.background_pipeline_retry_endpoint?(@project)
|
||||
::Ci::RetryPipelineWorker.perform_async(pipeline.id, current_user.id) # rubocop:disable CodeReuse/Worker
|
||||
else
|
||||
pipeline.retry_failed(current_user)
|
||||
end
|
||||
::Ci::RetryPipelineWorker.perform_async(pipeline.id, current_user.id) # rubocop:disable CodeReuse/Worker
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ module Ci
|
|||
class RunnersFinder < UnionFinder
|
||||
include Gitlab::Allowable
|
||||
|
||||
ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze
|
||||
DEFAULT_SORT = 'created_at_desc'
|
||||
|
||||
def initialize(current_user:, group: nil, params:)
|
||||
@params = params
|
||||
@group = group
|
||||
|
|
@ -24,11 +27,7 @@ module Ci
|
|||
end
|
||||
|
||||
def sort_key
|
||||
if @params[:sort] == 'contacted_asc'
|
||||
'contacted_asc'
|
||||
else
|
||||
'created_date'
|
||||
end
|
||||
ALLOWED_SORTS.include?(@params[:sort]) ? @params[:sort] : DEFAULT_SORT
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ module Mutations
|
|||
required: false,
|
||||
description: 'Indicates if the latest artifact should be kept for this project.'
|
||||
|
||||
argument :job_token_scope_enabled, GraphQL::BOOLEAN_TYPE,
|
||||
required: false,
|
||||
description: 'Indicates CI job tokens generated in this project have restricted access to resources.'
|
||||
|
||||
field :ci_cd_settings,
|
||||
Types::Ci::CiCdSettingType,
|
||||
null: false,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ module Types
|
|||
field :keep_latest_artifact, GraphQL::BOOLEAN_TYPE, null: true,
|
||||
description: 'Whether to keep the latest builds artifacts.',
|
||||
method: :keep_latest_artifacts_available?
|
||||
field :job_token_scope_enabled, GraphQL::BOOLEAN_TYPE, null: true,
|
||||
description: 'Indicates CI job tokens generated in this project have restricted access to resources.',
|
||||
method: :job_token_scope_enabled?
|
||||
field :project, Types::ProjectType, null: true,
|
||||
description: 'Project the CI/CD settings belong to.'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ module Types
|
|||
description 'Values for sorting runners'
|
||||
|
||||
value 'CONTACTED_ASC', 'Ordered by contacted_at in ascending order.', value: :contacted_asc
|
||||
value 'CREATED_DESC', 'Ordered by created_date in descending order.', value: :created_date
|
||||
value 'CONTACTED_DESC', 'Ordered by contacted_at in descending order.', value: :contacted_desc
|
||||
value 'CREATED_ASC', 'Ordered by created_at in ascending order.', value: :created_at_asc
|
||||
value 'CREATED_DESC', 'Ordered by created_at in descending order.', value: :created_at_desc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
class ProjectLevel
|
||||
attr_reader :project, :options
|
||||
|
||||
def initialize(project:, options:)
|
||||
@project = project
|
||||
@options = options.merge(project: project)
|
||||
end
|
||||
|
||||
def summary
|
||||
@summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project,
|
||||
options: options,
|
||||
current_user: options[:current_user]).data
|
||||
end
|
||||
|
||||
def permissions(user:)
|
||||
Gitlab::CycleAnalytics::Permissions.get(user: user, project: project)
|
||||
end
|
||||
|
||||
def stats
|
||||
@stats ||= default_stage_names.map do |stage_name|
|
||||
self[stage_name].as_json
|
||||
end
|
||||
end
|
||||
|
||||
def [](stage_name)
|
||||
::CycleAnalytics::ProjectLevelStageAdapter.new(build_stage(stage_name), options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_stage(stage_name)
|
||||
stage_params = stage_params_by_name(stage_name).merge(project: project)
|
||||
Analytics::CycleAnalytics::ProjectStage.new(stage_params)
|
||||
end
|
||||
|
||||
def stage_params_by_name(name)
|
||||
Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(name)
|
||||
end
|
||||
|
||||
def default_stage_names
|
||||
Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -134,6 +134,8 @@ module Ci
|
|||
end
|
||||
|
||||
scope :order_contacted_at_asc, -> { order(contacted_at: :asc) }
|
||||
scope :order_contacted_at_desc, -> { order(contacted_at: :desc) }
|
||||
scope :order_created_at_asc, -> { order(created_at: :asc) }
|
||||
scope :order_created_at_desc, -> { order(created_at: :desc) }
|
||||
scope :with_tags, -> { preload(:tags) }
|
||||
|
||||
|
|
@ -190,8 +192,13 @@ module Ci
|
|||
end
|
||||
|
||||
def self.order_by(order)
|
||||
if order == 'contacted_asc'
|
||||
case order
|
||||
when 'contacted_asc'
|
||||
order_contacted_at_asc
|
||||
when 'contacted_desc'
|
||||
order_contacted_at_desc
|
||||
when 'created_at_asc'
|
||||
order_created_at_asc
|
||||
else
|
||||
order_created_at_desc
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@ module Clusters
|
|||
include ::Clusters::Concerns::ApplicationStatus
|
||||
include ::Clusters::Concerns::ApplicationVersion
|
||||
include ::Clusters::Concerns::ApplicationData
|
||||
include IgnorableColumns
|
||||
|
||||
default_value_for :version, VERSION
|
||||
default_value_for :port, 514
|
||||
default_value_for :protocol, :tcp
|
||||
default_value_for :waf_log_enabled, false
|
||||
|
||||
ignore_column :waf_log_enabled, remove_with: '14.2', remove_after: '2021-07-22'
|
||||
|
||||
enum protocol: { tcp: 0, udp: 1 }
|
||||
|
||||
|
|
@ -48,9 +50,7 @@ module Clusters
|
|||
private
|
||||
|
||||
def has_at_least_one_log_enabled?
|
||||
if !waf_log_enabled && !cilium_log_enabled
|
||||
errors.add(:base, _("At least one logging option is required to be enabled"))
|
||||
end
|
||||
errors.add(:base, _("At least one logging option is required to be enabled")) unless cilium_log_enabled
|
||||
end
|
||||
|
||||
def content_values
|
||||
|
|
@ -113,7 +113,6 @@ module Clusters
|
|||
|
||||
def path_to_logs
|
||||
path = []
|
||||
path << "/var/log/containers/*#{Ingress::MODSECURITY_LOG_CONTAINER_NAME}*.log" if waf_log_enabled
|
||||
path << "/var/log/containers/*#{CILIUM_CONTAINER_NAME}*.log" if cilium_log_enabled
|
||||
path.join(',')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ module Clusters
|
|||
class Ingress < ApplicationRecord
|
||||
VERSION = '1.40.2'
|
||||
INGRESS_CONTAINER_NAME = 'nginx-ingress-controller'
|
||||
MODSECURITY_LOG_CONTAINER_NAME = 'modsecurity-log'
|
||||
MODSECURITY_MODE_LOGGING = "DetectionOnly"
|
||||
MODSECURITY_MODE_BLOCKING = "On"
|
||||
MODSECURITY_OWASP_RULES_FILE = "/etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf"
|
||||
|
||||
self.table_name = 'clusters_applications_ingress'
|
||||
|
||||
|
|
@ -20,22 +16,18 @@ module Clusters
|
|||
include ::Clusters::Concerns::ApplicationData
|
||||
include AfterCommitQueue
|
||||
include UsageStatistics
|
||||
include IgnorableColumns
|
||||
|
||||
default_value_for :ingress_type, :nginx
|
||||
default_value_for :modsecurity_enabled, true
|
||||
default_value_for :version, VERSION
|
||||
default_value_for :modsecurity_mode, :logging
|
||||
|
||||
ignore_column :modsecurity_enabled, remove_with: '14.2', remove_after: '2021-07-22'
|
||||
ignore_column :modsecurity_mode, remove_with: '14.2', remove_after: '2021-07-22'
|
||||
|
||||
enum ingress_type: {
|
||||
nginx: 1
|
||||
}
|
||||
|
||||
enum modsecurity_mode: { logging: 0, blocking: 1 }
|
||||
|
||||
scope :modsecurity_not_installed, -> { where(modsecurity_enabled: nil) }
|
||||
scope :modsecurity_enabled, -> { where(modsecurity_enabled: true) }
|
||||
scope :modsecurity_disabled, -> { where(modsecurity_enabled: false) }
|
||||
|
||||
FETCH_IP_ADDRESS_DELAY = 30.seconds
|
||||
|
||||
state_machine :status do
|
||||
|
|
@ -92,96 +84,13 @@ module Clusters
|
|||
|
||||
private
|
||||
|
||||
def specification
|
||||
return {} unless modsecurity_enabled
|
||||
|
||||
{
|
||||
"controller" => {
|
||||
"config" => {
|
||||
"enable-modsecurity" => "true",
|
||||
"enable-owasp-modsecurity-crs" => "false",
|
||||
"modsecurity-snippet" => modsecurity_snippet_content,
|
||||
"modsecurity.conf" => modsecurity_config_content
|
||||
},
|
||||
"extraContainers" => [
|
||||
{
|
||||
"name" => MODSECURITY_LOG_CONTAINER_NAME,
|
||||
"image" => "busybox",
|
||||
"args" => [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"tail -F /var/log/modsec/audit.log"
|
||||
],
|
||||
"volumeMounts" => [
|
||||
{
|
||||
"name" => "modsecurity-log-volume",
|
||||
"mountPath" => "/var/log/modsec",
|
||||
"readOnly" => true
|
||||
}
|
||||
],
|
||||
"livenessProbe" => {
|
||||
"exec" => {
|
||||
"command" => [
|
||||
"ls",
|
||||
"/var/log/modsec/audit.log"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraVolumeMounts" => [
|
||||
{
|
||||
"name" => "modsecurity-template-volume",
|
||||
"mountPath" => "/etc/nginx/modsecurity/modsecurity.conf",
|
||||
"subPath" => "modsecurity.conf"
|
||||
},
|
||||
{
|
||||
"name" => "modsecurity-log-volume",
|
||||
"mountPath" => "/var/log/modsec"
|
||||
}
|
||||
],
|
||||
"extraVolumes" => [
|
||||
{
|
||||
"name" => "modsecurity-template-volume",
|
||||
"configMap" => {
|
||||
"name" => "ingress-#{INGRESS_CONTAINER_NAME}",
|
||||
"items" => [
|
||||
{
|
||||
"key" => "modsecurity.conf",
|
||||
"path" => "modsecurity.conf"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name" => "modsecurity-log-volume",
|
||||
"emptyDir" => {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def modsecurity_config_content
|
||||
File.read(modsecurity_config_file_path)
|
||||
end
|
||||
|
||||
def modsecurity_config_file_path
|
||||
Rails.root.join('vendor', 'ingress', 'modsecurity.conf')
|
||||
end
|
||||
|
||||
def content_values
|
||||
YAML.load_file(chart_values_file).deep_merge!(specification)
|
||||
YAML.load_file(chart_values_file)
|
||||
end
|
||||
|
||||
def application_jupyter_installed?
|
||||
cluster.application_jupyter&.installed?
|
||||
end
|
||||
|
||||
def modsecurity_snippet_content
|
||||
sec_rule_engine = logging? ? MODSECURITY_MODE_LOGGING : MODSECURITY_MODE_BLOCKING
|
||||
"SecRuleEngine #{sec_rule_engine}\nInclude #{MODSECURITY_OWASP_RULES_FILE}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -138,7 +138,6 @@ module Clusters
|
|||
scope :gcp_installed, -> { gcp_provided.joins(:provider_gcp).merge(Clusters::Providers::Gcp.with_status(:created)) }
|
||||
scope :aws_installed, -> { aws_provided.joins(:provider_aws).merge(Clusters::Providers::Aws.with_status(:created)) }
|
||||
|
||||
scope :with_enabled_modsecurity, -> { joins(:application_ingress).merge(::Clusters::Applications::Ingress.modsecurity_enabled) }
|
||||
scope :with_available_elasticstack, -> { joins(:application_elastic_stack).merge(::Clusters::Applications::ElasticStack.available) }
|
||||
scope :with_available_cilium, -> { joins(:application_cilium).merge(::Clusters::Applications::Cilium.available) }
|
||||
scope :distinct_with_deployed_environments, -> { joins(:environments).merge(::Deployment.success).distinct }
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module CycleAnalytics
|
||||
class ProjectLevel
|
||||
attr_reader :project, :options
|
||||
|
||||
def initialize(project, options:)
|
||||
@project = project
|
||||
@options = options.merge(project: project)
|
||||
end
|
||||
|
||||
def summary
|
||||
@summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project,
|
||||
from: options[:from],
|
||||
to: options[:to],
|
||||
current_user: options[:current_user]).data
|
||||
end
|
||||
|
||||
def permissions(user:)
|
||||
Gitlab::CycleAnalytics::Permissions.get(user: user, project: project)
|
||||
end
|
||||
|
||||
def stats
|
||||
@stats ||= default_stage_names.map do |stage_name|
|
||||
self[stage_name].as_json
|
||||
end
|
||||
end
|
||||
|
||||
def [](stage_name)
|
||||
CycleAnalytics::ProjectLevelStageAdapter.new(build_stage(stage_name), options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_stage(stage_name)
|
||||
stage_params = stage_params_by_name(stage_name).merge(project: project)
|
||||
Analytics::CycleAnalytics::ProjectStage.new(stage_params)
|
||||
end
|
||||
|
||||
def stage_params_by_name(name)
|
||||
Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(name)
|
||||
end
|
||||
|
||||
def default_stage_names
|
||||
Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,46 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Members
|
||||
class LastGroupOwnerAssigner
|
||||
def initialize(group, members)
|
||||
@group = group
|
||||
@members = members
|
||||
end
|
||||
class LastGroupOwnerAssigner
|
||||
def initialize(group, members)
|
||||
@group = group
|
||||
@members = members
|
||||
end
|
||||
|
||||
def execute
|
||||
@last_blocked_owner = no_owners_in_heirarchy? && group.single_blocked_owner?
|
||||
@group_single_owner = owners.size == 1
|
||||
def execute
|
||||
@last_blocked_owner = no_owners_in_heirarchy? && group.single_blocked_owner?
|
||||
@group_single_owner = owners.size == 1
|
||||
|
||||
members.each { |member| set_last_owner(member) }
|
||||
end
|
||||
members.each { |member| set_last_owner(member) }
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
attr_reader :group, :members, :last_blocked_owner, :group_single_owner
|
||||
attr_reader :group, :members, :last_blocked_owner, :group_single_owner
|
||||
|
||||
def no_owners_in_heirarchy?
|
||||
owners.empty?
|
||||
end
|
||||
def no_owners_in_heirarchy?
|
||||
owners.empty?
|
||||
end
|
||||
|
||||
def set_last_owner(member)
|
||||
member.last_owner = member.id.in?(owner_ids) && group_single_owner
|
||||
member.last_blocked_owner = member.id.in?(blocked_owner_ids) && last_blocked_owner
|
||||
end
|
||||
def set_last_owner(member)
|
||||
member.last_owner = member.id.in?(owner_ids) && group_single_owner
|
||||
member.last_blocked_owner = member.id.in?(blocked_owner_ids) && last_blocked_owner
|
||||
end
|
||||
|
||||
def owner_ids
|
||||
@owner_ids ||= owners.where(id: member_ids).ids
|
||||
end
|
||||
def owner_ids
|
||||
@owner_ids ||= owners.where(id: member_ids).ids
|
||||
end
|
||||
|
||||
def blocked_owner_ids
|
||||
@blocked_owner_ids ||= group.blocked_owners.where(id: member_ids).ids
|
||||
end
|
||||
def blocked_owner_ids
|
||||
@blocked_owner_ids ||= group.blocked_owners.where(id: member_ids).ids
|
||||
end
|
||||
|
||||
def member_ids
|
||||
@members_ids ||= members.pluck(:id)
|
||||
end
|
||||
def member_ids
|
||||
@members_ids ||= members.pluck(:id)
|
||||
end
|
||||
|
||||
def owners
|
||||
@owners ||= group.members_with_parents.owners.load
|
||||
end
|
||||
def owners
|
||||
@owners ||= group.members_with_parents.owners.load
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,15 +10,12 @@ class ClusterApplicationEntity < Grape::Entity
|
|||
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
|
||||
expose :email, if: -> (e, _) { e.respond_to?(:email) }
|
||||
expose :stack, if: -> (e, _) { e.respond_to?(:stack) }
|
||||
expose :modsecurity_enabled, if: -> (e, _) { e.respond_to?(:modsecurity_enabled) }
|
||||
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
|
||||
expose :can_uninstall?, as: :can_uninstall
|
||||
expose :available_domains, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:available_domains) }
|
||||
expose :pages_domain, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:pages_domain) }
|
||||
expose :modsecurity_mode, if: -> (e, _) { e.respond_to?(:modsecurity_mode) }
|
||||
expose :host, if: -> (e, _) { e.respond_to?(:host) }
|
||||
expose :port, if: -> (e, _) { e.respond_to?(:port) }
|
||||
expose :protocol, if: -> (e, _) { e.respond_to?(:protocol) }
|
||||
expose :waf_log_enabled, if: -> (e, _) { e.respond_to?(:waf_log_enabled) }
|
||||
expose :cilium_log_enabled, if: -> (e, _) { e.respond_to?(:cilium_log_enabled) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ class MemberSerializer < BaseSerializer
|
|||
entity MemberEntity
|
||||
|
||||
def represent(members, opts = {})
|
||||
Members::LastGroupOwnerAssigner.new(opts[:group], members).execute unless opts[:source].is_a?(Project)
|
||||
LastGroupOwnerAssigner.new(opts[:group], members).execute unless opts[:source].is_a?(Project)
|
||||
|
||||
super(members, opts)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,14 +29,6 @@ module Clusters
|
|||
application.stack = params[:stack]
|
||||
end
|
||||
|
||||
if application.has_attribute?(:modsecurity_enabled)
|
||||
application.modsecurity_enabled = params[:modsecurity_enabled] || false
|
||||
end
|
||||
|
||||
if application.has_attribute?(:modsecurity_mode)
|
||||
application.modsecurity_mode = params[:modsecurity_mode] || 0
|
||||
end
|
||||
|
||||
apply_fluentd_related_attributes(application)
|
||||
|
||||
if application.respond_to?(:oauth_application)
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@
|
|||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :diff_max_patch_bytes, 'Maximum diff patch size (Bytes)', class: 'label-light'
|
||||
= f.label :diff_max_patch_bytes, 'Maximum diff patch size in bytes', class: 'label-light'
|
||||
= f.number_field :diff_max_patch_bytes, class: 'form-control gl-form-input'
|
||||
%span.form-text.text-muted
|
||||
Diff files surpassing this limit will be presented as 'too large'
|
||||
and won't be expandable.
|
||||
Collapse diffs larger than this size, and show a 'too large' message instead.
|
||||
|
||||
= link_to sprite_icon('question-o'),
|
||||
help_page_path('user/admin_area/diff_limits',
|
||||
anchor: 'maximum-diff-patch-size')
|
||||
help_page_path('user/admin_area/diff_limits')
|
||||
= f.submit _('Save changes'), class: 'gl-button btn btn-confirm'
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Diff content limits')
|
||||
= _('Set size limits for displaying diffs in the browser.')
|
||||
.settings-content
|
||||
= render 'diff_limits'
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
project_name: @project.name,
|
||||
project_path: @project.path,
|
||||
project_description: @project.description,
|
||||
project_visibility: @project.visibility } }
|
||||
project_visibility: @project.visibility,
|
||||
restricted_visibility_levels: Gitlab::CurrentSettings.restricted_visibility_levels.to_json } }
|
||||
- else
|
||||
.row.gl-mt-3
|
||||
.col-lg-3
|
||||
|
|
|
|||
|
|
@ -37,10 +37,12 @@
|
|||
data: { placeholder: _('Select branch'), endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
|
||||
|
||||
- if source_level < target_level
|
||||
.gl-alert.gl-alert-warning.gl-mt-4
|
||||
= sprite_icon('warning', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
.gl-alert-body
|
||||
= visibilityMismatchString
|
||||
%br
|
||||
= _('Review the target project before submitting to avoid exposing %{source} changes.') % { source: source_visibility }
|
||||
.gl-alert.gl-alert-warning.gl-alert-not-dismissible.gl-max-content.gl-mt-4
|
||||
.gl-alert-container
|
||||
.gl-alert-content{ role: 'alert' }
|
||||
= sprite_icon('warning', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
.gl-alert-body
|
||||
= visibilityMismatchString
|
||||
%br
|
||||
= _('Review the target project before submitting to avoid exposing %{source} changes.') % { source: source_visibility }
|
||||
%hr
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ module SshKeys
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def perform
|
||||
return unless ::Feature.enabled?(:ssh_key_expiration_email_notification, default_enabled: :yaml)
|
||||
|
||||
order = Gitlab::Pagination::Keyset::Order.build([
|
||||
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
|
||||
attribute_name: 'expires_at_utc',
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ module SshKeys
|
|||
idempotent!
|
||||
|
||||
def perform
|
||||
return unless ::Feature.enabled?(:ssh_key_expiration_email_notification, default_enabled: :yaml)
|
||||
|
||||
# rubocop:disable CodeReuse/ActiveRecord
|
||||
User.with_ssh_key_expiring_soon.find_each(batch_size: 10_000) do |user|
|
||||
with_context(user: user) do
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: background_pipeline_retry_endpoint
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61270
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330915
|
||||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: ci_dynamic_child_pipeline
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23790
|
||||
rollout_issue_url:
|
||||
milestone: '12.9'
|
||||
type: development
|
||||
group: group::pipeline execution
|
||||
default_enabled: true
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: remove_release_notes_from_tags_api
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63392
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290311
|
||||
name: sidekiq_load_balancing_rotate_up_to_date_replica
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63413/
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/333153
|
||||
milestone: '14.0'
|
||||
type: development
|
||||
group: group::release
|
||||
group: group::memory
|
||||
default_enabled: false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: ssh_key_expiration_email_notification
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56888
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326386
|
||||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::compliance
|
||||
default_enabled: true
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
key_path: usage_activity_by_stage_monthly.secure.dependency_scanning_scans
|
||||
description: ''
|
||||
product_section: ''
|
||||
product_stage: ''
|
||||
product_group: ''
|
||||
product_category: ''
|
||||
value_type: number
|
||||
status: data_available
|
||||
time_frame: 28d
|
||||
data_source:
|
||||
distribution:
|
||||
- ce
|
||||
tier:
|
||||
- free
|
||||
skip_validation: true
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
key_path: counts.license_management_jobs
|
||||
description: Name on the GitLab license
|
||||
product_section: growth
|
||||
product_stage: growth
|
||||
product_group: group::product intelligence
|
||||
product_category: collection
|
||||
value_type: number
|
||||
status: data_available
|
||||
time_frame: none
|
||||
data_source: database
|
||||
distribution:
|
||||
- ce
|
||||
tier:
|
||||
- premium
|
||||
- ultimate
|
||||
skip_validation: true
|
||||
|
|
@ -273,6 +273,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
resources :value_streams, only: [:index] do
|
||||
resources :stages, only: [:index]
|
||||
end
|
||||
resource :summary, controller: :summary, only: :show
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class AddRunnersCreatedAtIndex < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :ci_runners, [:created_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_created_at_and_id_desc'
|
||||
add_concurrent_index :ci_runners, [:created_at, :id], order: { created_at: :desc, id: :desc }, name: 'index_ci_runners_on_created_at_desc_and_id_desc'
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index :ci_runners, [:created_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_created_at_and_id_desc'
|
||||
remove_concurrent_index :ci_runners, [:created_at, :id], order: { created_at: :desc, id: :desc }, name: 'index_ci_runners_on_created_at_desc_and_id_desc'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class ReplaceRunnersContactedAtIndex < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
OLD_INDEX_NAME = 'index_ci_runners_on_contacted_at'
|
||||
|
||||
def up
|
||||
add_concurrent_index :ci_runners, [:contacted_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_contacted_at_and_id_desc', using: 'btree'
|
||||
add_concurrent_index :ci_runners, [:contacted_at, :id], order: { contacted_at: :desc, id: :desc }, name: 'index_ci_runners_on_contacted_at_desc_and_id_desc', using: 'btree'
|
||||
|
||||
remove_concurrent_index_by_name :ci_runners, OLD_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :ci_runners, 'index_ci_runners_on_contacted_at_and_id_desc'
|
||||
remove_concurrent_index_by_name :ci_runners, 'index_ci_runners_on_contacted_at_desc_and_id_desc'
|
||||
|
||||
add_concurrent_index :ci_runners, :contacted_at, name: OLD_INDEX_NAME, using: 'btree'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
fc500e4dd555a6baad91ad3c9fb8a2f8541e1613dd64afdbdd28b19447a28caf
|
||||
|
|
@ -0,0 +1 @@
|
|||
4dcf6277439e8abe52534540100fa621fedcecb3eaf71ad5685ac0230cd2e5bb
|
||||
|
|
@ -22904,7 +22904,13 @@ CREATE INDEX index_ci_runner_projects_on_project_id ON ci_runner_projects USING
|
|||
|
||||
CREATE INDEX index_ci_runner_projects_on_runner_id ON ci_runner_projects USING btree (runner_id);
|
||||
|
||||
CREATE INDEX index_ci_runners_on_contacted_at ON ci_runners USING btree (contacted_at);
|
||||
CREATE INDEX index_ci_runners_on_contacted_at_and_id_desc ON ci_runners USING btree (contacted_at, id DESC);
|
||||
|
||||
CREATE INDEX index_ci_runners_on_contacted_at_desc_and_id_desc ON ci_runners USING btree (contacted_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX index_ci_runners_on_created_at_and_id_desc ON ci_runners USING btree (created_at, id DESC);
|
||||
|
||||
CREATE INDEX index_ci_runners_on_created_at_desc_and_id_desc ON ci_runners USING btree (created_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX index_ci_runners_on_description_trigram ON ci_runners USING gin (description gin_trgm_ops);
|
||||
|
||||
|
|
|
|||
|
|
@ -63,8 +63,11 @@ need to restart GitLab to apply a new file hook.
|
|||
If a file hook executes with non-zero exit code or GitLab fails to execute it, a
|
||||
message is logged to:
|
||||
|
||||
- `gitlab-rails/plugin.log` in an Omnibus installation.
|
||||
- `log/plugin.log` in a source installation.
|
||||
- `gitlab-rails/file_hook.log` in an Omnibus installation.
|
||||
- `log/file_hook.log` in a source installation.
|
||||
|
||||
NOTE:
|
||||
Before 14.0 release, the file name was `plugin.log`
|
||||
|
||||
## Creating file hooks
|
||||
|
||||
|
|
|
|||
|
|
@ -766,6 +766,7 @@ Input type: `CiCdSettingsUpdateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcicdsettingsupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcicdsettingsupdatefullpath"></a>`fullPath` | [`ID!`](#id) | Full Path of the project the settings belong to. |
|
||||
| <a id="mutationcicdsettingsupdatejobtokenscopeenabled"></a>`jobTokenScopeEnabled` | [`Boolean`](#boolean) | Indicates CI job tokens generated in this project have restricted access to resources. |
|
||||
| <a id="mutationcicdsettingsupdatekeeplatestartifact"></a>`keepLatestArtifact` | [`Boolean`](#boolean) | Indicates if the latest artifact should be kept for this project. |
|
||||
| <a id="mutationcicdsettingsupdatemergepipelinesenabled"></a>`mergePipelinesEnabled` | [`Boolean`](#boolean) | Indicates if merge pipelines are enabled for the project. |
|
||||
| <a id="mutationcicdsettingsupdatemergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Indicates if merge trains are enabled for the project. |
|
||||
|
|
@ -11853,6 +11854,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectcicdsettingjobtokenscopeenabled"></a>`jobTokenScopeEnabled` | [`Boolean`](#boolean) | Indicates CI job tokens generated in this project have restricted access to resources. |
|
||||
| <a id="projectcicdsettingkeeplatestartifact"></a>`keepLatestArtifact` | [`Boolean`](#boolean) | Whether to keep the latest builds artifacts. |
|
||||
| <a id="projectcicdsettingmergepipelinesenabled"></a>`mergePipelinesEnabled` | [`Boolean`](#boolean) | Whether merge pipelines are enabled. |
|
||||
| <a id="projectcicdsettingmergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Whether merge trains are enabled. |
|
||||
|
|
@ -13841,7 +13843,9 @@ Values for sorting runners.
|
|||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="cirunnersortcontacted_asc"></a>`CONTACTED_ASC` | Ordered by contacted_at in ascending order. |
|
||||
| <a id="cirunnersortcreated_desc"></a>`CREATED_DESC` | Ordered by created_date in descending order. |
|
||||
| <a id="cirunnersortcontacted_desc"></a>`CONTACTED_DESC` | Ordered by contacted_at in descending order. |
|
||||
| <a id="cirunnersortcreated_asc"></a>`CREATED_ASC` | Ordered by created_at in ascending order. |
|
||||
| <a id="cirunnersortcreated_desc"></a>`CREATED_DESC` | Ordered by created_at in descending order. |
|
||||
|
||||
### `CiRunnerStatus`
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@ Parameters:
|
|||
| `tag_name` | string | yes | The name of a tag |
|
||||
| `ref` | string | yes | Create tag using commit SHA, another tag name, or branch name |
|
||||
| `message` | string | no | Creates annotated tag |
|
||||
| `release_description` | string | no | This parameter is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311) in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/tags?tag_name=test&ref=master"
|
||||
|
|
@ -149,10 +148,7 @@ Example response:
|
|||
"committer_email": "jack@example.com",
|
||||
"committed_date": "2012-05-28T04:42:42-07:00"
|
||||
},
|
||||
"release": {
|
||||
"tag_name": "1.0.0",
|
||||
"description": "Amazing release. Wow"
|
||||
},
|
||||
"release": null,
|
||||
"name": "v1.0.0",
|
||||
"target": "2695effb5807a22ff3d138d593fd856244e155e7",
|
||||
"message": null,
|
||||
|
|
@ -182,82 +178,3 @@ Parameters:
|
|||
| ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `tag_name` | string | yes | The name of a tag |
|
||||
|
||||
## Create a new release
|
||||
|
||||
WARNING:
|
||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766)
|
||||
for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311)
|
||||
in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead.
|
||||
|
||||
Add release notes to the existing Git tag. If there
|
||||
already exists a release for the given tag, status code `409` is returned.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/repository/tags/:tag_name/release
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `tag_name` | string | yes | The name of a tag |
|
||||
|
||||
Request body:
|
||||
|
||||
- `description` (required) - Release notes with Markdown support
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Amazing release. Wow"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"tag_name": "1.0.0",
|
||||
"description": "Amazing release. Wow"
|
||||
}
|
||||
```
|
||||
|
||||
## Update a release
|
||||
|
||||
WARNING:
|
||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766)
|
||||
for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311)
|
||||
in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead.
|
||||
|
||||
Updates the release notes of a given release.
|
||||
|
||||
```plaintext
|
||||
PUT /projects/:id/repository/tags/:tag_name/release
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `tag_name` | string | yes | The name of a tag |
|
||||
|
||||
Request body:
|
||||
|
||||
- `description` (required) - Release notes with Markdown support
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Amazing release. Wow"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"tag_name": "1.0.0",
|
||||
"description": "Amazing release. Wow"
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ the feature categories in the [Secure](https://about.gitlab.com/stages-devops-li
|
|||
- `AppSec::ContainerScanning`: Container Scanning code.
|
||||
- `AppSec::Dast`: DAST code.
|
||||
- `AppSec::DependencyScanning`: Dependency Scanning code.
|
||||
- `AppSec::Fuzzing::Api`: API Fuzzing code.
|
||||
- `AppSec::Fuzzing::API`: API Fuzzing code.
|
||||
- `AppSec::Fuzzing::Coverage`: Coverage Fuzzing code.
|
||||
- `AppSec::Fuzzing`: Shared fuzzing code.
|
||||
- `AppSec::LicenseCompliance`: License Compliance code.
|
||||
|
|
|
|||
|
|
@ -3288,15 +3288,15 @@ Tiers: `free`
|
|||
|
||||
### `counts.license_management_jobs`
|
||||
|
||||
Name on the GitLab license
|
||||
Count of License Scanning jobs run
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/license/20210204124854_license_management_jobs.yml)
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210204124854_license_management_jobs.yml)
|
||||
|
||||
Group: `group::product intelligence`
|
||||
Group: `group::composition analysis`
|
||||
|
||||
Status: `data_available`
|
||||
|
||||
Tiers: `premium`, `ultimate`
|
||||
Tiers: `ultimate`
|
||||
|
||||
### `counts.licenses_list_views`
|
||||
|
||||
|
|
@ -17320,7 +17320,7 @@ Tiers:
|
|||
|
||||
### `usage_activity_by_stage.secure.dependency_scanning_scans`
|
||||
|
||||
Counts dependency scanning jobs
|
||||
Total number of users running Dependency Scanning Scans
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175220_dependency_scanning_scans.yml)
|
||||
|
||||
|
|
@ -17416,7 +17416,7 @@ Tiers: `free`
|
|||
|
||||
### `usage_activity_by_stage.secure.user_dependency_scanning_jobs`
|
||||
|
||||
no idea, Count of Dependency Scanning jobs run, it implies user but AFAIK we don't track per user
|
||||
Total number of users running Dependency Scanning jobs
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175216_user_dependency_scanning_jobs.yml)
|
||||
|
||||
|
|
@ -17428,9 +17428,9 @@ Tiers: `ultimate`
|
|||
|
||||
### `usage_activity_by_stage.secure.user_license_management_jobs`
|
||||
|
||||
no idea, Count of License Scanning jobs run, it implies user but AFAIK we don't track per user
|
||||
Total number of users running License Scanning jobs
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/license/20210216175218_user_license_management_jobs.yml)
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175218_user_license_management_jobs.yml)
|
||||
|
||||
Group: `group::composition analysis`
|
||||
|
||||
|
|
@ -19324,7 +19324,7 @@ Tiers: `free`
|
|||
|
||||
### `usage_activity_by_stage_monthly.secure.dependency_scanning_pipeline`
|
||||
|
||||
no idea, what is this when did it get added? guess pipelines containing a DS job
|
||||
Count of pipelines with successful Dependency Scanning jobs
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175226_dependency_scanning_pipeline.yml)
|
||||
|
||||
|
|
@ -19336,15 +19336,15 @@ Tiers: `ultimate`
|
|||
|
||||
### `usage_activity_by_stage_monthly.secure.dependency_scanning_scans`
|
||||
|
||||
Missing description
|
||||
Monthly number of users running Dependency Scanning Scans
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml)
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml)
|
||||
|
||||
Group: ``
|
||||
Group: `group::composition analysis`
|
||||
|
||||
Status: `data_available`
|
||||
|
||||
Tiers: `free`
|
||||
Tiers: `ultimate`
|
||||
|
||||
### `usage_activity_by_stage_monthly.secure.sast_pipeline`
|
||||
|
||||
|
|
@ -19456,7 +19456,7 @@ Tiers: `free`
|
|||
|
||||
### `usage_activity_by_stage_monthly.secure.user_dependency_scanning_jobs`
|
||||
|
||||
no idea, Count of Dependency Scanning jobs run, it implies user and monthly, but AFAIK we don't track per user
|
||||
Monthly number of users creating Dependency Scanning jobs
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175222_user_dependency_scanning_jobs.yml)
|
||||
|
||||
|
|
@ -19468,9 +19468,9 @@ Tiers: `ultimate`
|
|||
|
||||
### `usage_activity_by_stage_monthly.secure.user_license_management_jobs`
|
||||
|
||||
no idea, Count of License Scanning jobs run, it implies user and monthly, but AFAIK we don't track per user
|
||||
Monthly number of users running License Scanning jobs
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/license/20210216175224_user_license_management_jobs.yml)
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175224_user_license_management_jobs.yml)
|
||||
|
||||
Group: `group::composition analysis`
|
||||
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ Users are notified of the following events:
|
|||
| Event | Sent to | Settings level |
|
||||
|------------------------------|---------------------|------------------------------|
|
||||
| New SSH key added | User | Security email, always sent. |
|
||||
| SSH key has expired | User | Security email, always sent. |
|
||||
| New email added | User | Security email, always sent. |
|
||||
| Email changed | User | Security email, always sent. |
|
||||
| Password changed | User | Security email, always sent when user changes their own password |
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ module API
|
|||
expose :issue_type,
|
||||
as: :type,
|
||||
format_with: :upcase,
|
||||
documentation: { type: "String", desc: "One of #{Issue.issue_types.keys.map(&:upcase)}" }
|
||||
documentation: { type: "String", desc: "One of #{::Issue.issue_types.keys.map(&:upcase)}" }
|
||||
|
||||
expose :assignee, using: ::API::Entities::UserBasic do |issue|
|
||||
issue.assignees.first
|
||||
|
|
|
|||
|
|
@ -51,14 +51,12 @@ module API
|
|||
end
|
||||
|
||||
desc 'Create a new repository tag' do
|
||||
detail 'This optional release_description parameter was deprecated in GitLab 11.7.'
|
||||
success Entities::Tag
|
||||
end
|
||||
params do
|
||||
requires :tag_name, type: String, desc: 'The name of the tag'
|
||||
requires :ref, type: String, desc: 'The commit sha or branch name'
|
||||
optional :message, type: String, desc: 'Specifying a message creates an annotated tag'
|
||||
optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)'
|
||||
end
|
||||
post ':id/repository/tags', :release_orchestration do
|
||||
deprecate_release_notes unless params[:release_description].blank?
|
||||
|
|
@ -69,19 +67,6 @@ module API
|
|||
.execute(params[:tag_name], params[:ref], params[:message])
|
||||
|
||||
if result[:status] == :success
|
||||
# Release creation with Tags API was deprecated in GitLab 11.7
|
||||
if params[:release_description].present?
|
||||
release_create_params = {
|
||||
tag: params[:tag_name],
|
||||
name: params[:tag_name], # Name can be specified in new API
|
||||
description: params[:release_description]
|
||||
}
|
||||
|
||||
::Releases::CreateService
|
||||
.new(user_project, current_user, release_create_params)
|
||||
.execute
|
||||
end
|
||||
|
||||
present result[:tag],
|
||||
with: Entities::Tag,
|
||||
project: user_project
|
||||
|
|
@ -111,82 +96,6 @@ module API
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Add a release note to a tag' do
|
||||
detail 'This feature was deprecated in GitLab 11.7.'
|
||||
success Entities::TagRelease
|
||||
end
|
||||
params do
|
||||
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
|
||||
requires :description, type: String, desc: 'Release notes with markdown support'
|
||||
end
|
||||
post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
|
||||
deprecate_release_notes
|
||||
authorize_create_release!
|
||||
|
||||
##
|
||||
# Legacy API does not support tag auto creation.
|
||||
not_found!('Tag') unless user_project.repository.find_tag(params[:tag])
|
||||
|
||||
release_create_params = {
|
||||
tag: params[:tag],
|
||||
name: params[:tag], # Name can be specified in new API
|
||||
description: params[:description]
|
||||
}
|
||||
|
||||
result = ::Releases::CreateService
|
||||
.new(user_project, current_user, release_create_params)
|
||||
.execute
|
||||
|
||||
if result[:status] == :success
|
||||
present result[:release], with: Entities::TagRelease
|
||||
else
|
||||
render_api_error!(result[:message], result[:http_status])
|
||||
end
|
||||
end
|
||||
|
||||
desc "Update a tag's release note" do
|
||||
detail 'This feature was deprecated in GitLab 11.7.'
|
||||
success Entities::TagRelease
|
||||
end
|
||||
params do
|
||||
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
|
||||
requires :description, type: String, desc: 'Release notes with markdown support'
|
||||
end
|
||||
put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
|
||||
deprecate_release_notes
|
||||
authorize_update_release!
|
||||
|
||||
result = ::Releases::UpdateService
|
||||
.new(user_project, current_user, declared_params(include_missing: false))
|
||||
.execute
|
||||
|
||||
if result[:status] == :success
|
||||
present result[:release], with: Entities::TagRelease
|
||||
else
|
||||
render_api_error!(result[:message], result[:http_status])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
helpers do
|
||||
def authorize_create_release!
|
||||
authorize! :create_release, user_project
|
||||
end
|
||||
|
||||
def authorize_update_release!
|
||||
authorize! :update_release, release
|
||||
end
|
||||
|
||||
def release
|
||||
@release ||= user_project.releases.find_by_tag(params[:tag])
|
||||
end
|
||||
|
||||
def deprecate_release_notes
|
||||
return unless Feature.enabled?(:remove_release_notes_from_tags_api, user_project, default_enabled: :yaml)
|
||||
|
||||
render_api_error!("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311", 400)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,11 +28,6 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def matching?
|
||||
super &&
|
||||
Feature.enabled?(:ci_dynamic_child_pipeline, project, default_enabled: true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project
|
||||
|
|
|
|||
|
|
@ -41,10 +41,6 @@ module Gitlab
|
|||
def self.gldropdown_tags_enabled?
|
||||
::Feature.enabled?(:gldropdown_tags, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
def self.background_pipeline_retry_endpoint?(project)
|
||||
::Feature.enabled?(:background_pipeline_retry_endpoint, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ build-job: # This job runs in the build stage, which runs first.
|
|||
stage: build
|
||||
script:
|
||||
- echo "Compiling the code..."
|
||||
- echo "Compile complete.
|
||||
- echo "Compile complete."
|
||||
|
||||
unit-test-job: # This job runs in the test stage.
|
||||
stage: test # It only starts when the job in the build stage completes successfully.
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@
|
|||
module Gitlab
|
||||
module CycleAnalytics
|
||||
class StageSummary
|
||||
def initialize(project, from:, to: nil, current_user:)
|
||||
def initialize(project, options:, current_user:)
|
||||
@project = project
|
||||
@from = from
|
||||
@to = to
|
||||
@options = options
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
|
|
@ -20,15 +19,15 @@ module Gitlab
|
|||
private
|
||||
|
||||
def issue_stats
|
||||
serialize(Summary::Issue.new(project: @project, from: @from, to: @to, current_user: @current_user))
|
||||
serialize(Summary::Issue.new(project: @project, options: @options, current_user: @current_user))
|
||||
end
|
||||
|
||||
def commit_stats
|
||||
serialize(Summary::Commit.new(project: @project, from: @from, to: @to))
|
||||
serialize(Summary::Commit.new(project: @project, options: @options))
|
||||
end
|
||||
|
||||
def deployments_summary
|
||||
@deployments_summary ||= Summary::Deploy.new(project: @project, from: @from, to: @to)
|
||||
@deployments_summary ||= Summary::Deploy.new(project: @project, options: @options)
|
||||
end
|
||||
|
||||
def deploy_stats
|
||||
|
|
@ -39,8 +38,7 @@ module Gitlab
|
|||
serialize(
|
||||
Summary::DeploymentFrequency.new(
|
||||
deployments: deployments_summary.value.raw_value,
|
||||
from: @from,
|
||||
to: @to),
|
||||
options: @options),
|
||||
with_unit: true
|
||||
)
|
||||
end
|
||||
|
|
@ -50,8 +48,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def serialize(summary_object, with_unit: false)
|
||||
AnalyticsSummarySerializer.new.represent(
|
||||
summary_object, with_unit: with_unit)
|
||||
AnalyticsSummarySerializer.new.represent(summary_object, with_unit: with_unit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ module Gitlab
|
|||
module CycleAnalytics
|
||||
module Summary
|
||||
class Base
|
||||
def initialize(project:, from:, to: nil)
|
||||
def initialize(project:, options:)
|
||||
@project = project
|
||||
@from = from
|
||||
@to = to
|
||||
@options = options
|
||||
end
|
||||
|
||||
def title
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ module Gitlab
|
|||
def commits_count
|
||||
return unless ref
|
||||
|
||||
@commits_count ||= gitaly_commit_client.commit_count(ref, after: @from, before: @to)
|
||||
@commits_count ||= gitaly_commit_client.commit_count(ref, after: @options[:from], before: @options[:to])
|
||||
end
|
||||
|
||||
def gitaly_commit_client
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ module Gitlab
|
|||
|
||||
def deployments_count
|
||||
DeploymentsFinder
|
||||
.new(project: @project, finished_after: @from, finished_before: @to, status: :success, order_by: :finished_at)
|
||||
.new(project: @project, finished_after: @options[:from], finished_before: @options[:to], status: :success, order_by: :finished_at)
|
||||
.execute
|
||||
.count
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ module Gitlab
|
|||
class DeploymentFrequency < Base
|
||||
include SummaryHelper
|
||||
|
||||
def initialize(deployments:, from:, to: nil, project: nil)
|
||||
def initialize(deployments:, options:, project: nil)
|
||||
@deployments = deployments
|
||||
|
||||
super(project: project, from: from, to: to)
|
||||
super(project: project, options: options)
|
||||
end
|
||||
|
||||
def title
|
||||
|
|
@ -17,7 +17,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def value
|
||||
@value ||= frequency(@deployments, @from, @to || Time.now)
|
||||
@value ||= frequency(@deployments, @options[:from], @options[:to] || Time.current)
|
||||
end
|
||||
|
||||
def unit
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ module Gitlab
|
|||
module CycleAnalytics
|
||||
module Summary
|
||||
class Issue < Base
|
||||
def initialize(project:, from:, to: nil, current_user:)
|
||||
def initialize(project:, options:, current_user:)
|
||||
@project = project
|
||||
@from = from
|
||||
@to = to
|
||||
@options = options
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
|
|
@ -23,10 +22,18 @@ module Gitlab
|
|||
|
||||
def issues_count
|
||||
IssuesFinder
|
||||
.new(@current_user, project_id: @project.id, created_after: @from, created_before: @to)
|
||||
.new(@current_user, finder_params)
|
||||
.execute
|
||||
.count
|
||||
end
|
||||
|
||||
def finder_params
|
||||
@options.dup.tap do |hash|
|
||||
hash[:created_after] = hash.delete(:from)
|
||||
hash[:created_before] = hash.delete(:to)
|
||||
hash[:project_id] = @project.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -176,6 +176,20 @@ module Gitlab
|
|||
true
|
||||
end
|
||||
|
||||
# Returns true if there was at least one host that has caught up with the given transaction.
|
||||
# Similar to `#select_caught_up_hosts`, picks a random host, to rotate replicas we use.
|
||||
# Unlike `#select_caught_up_hosts`, does not iterate over all hosts if finds any.
|
||||
def select_up_to_date_host(location)
|
||||
all_hosts = @host_list.hosts.shuffle
|
||||
host = all_hosts.find { |host| host.caught_up?(location) }
|
||||
|
||||
return false unless host
|
||||
|
||||
RequestStore[CACHE_KEY] = host
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def set_consistent_hosts_for_request(hosts)
|
||||
RequestStore[VALID_HOSTS_CACHE_KEY] = hosts
|
||||
end
|
||||
|
|
|
|||
|
|
@ -59,7 +59,11 @@ module Gitlab
|
|||
end
|
||||
|
||||
def replica_caught_up?(location)
|
||||
load_balancer.host.caught_up?(location)
|
||||
if Feature.enabled?(:sidekiq_load_balancing_rotate_up_to_date_replica)
|
||||
load_balancer.select_up_to_date_host(location)
|
||||
else
|
||||
load_balancer.host.caught_up?(location)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
module MigrationHelpers
|
||||
include Migrations::BackgroundMigrationHelpers
|
||||
include DynamicModelHelpers
|
||||
include Migrations::RenameTableHelpers
|
||||
include RenameTableHelpers
|
||||
|
||||
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
MAX_IDENTIFIER_NAME_LENGTH = 63
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module Gitlab
|
||||
class FileHookLogger < Gitlab::Logger
|
||||
def self.file_name_noext
|
||||
'plugin'
|
||||
'file_hook'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Graphql
|
||||
StandardGraphqlError = Class.new(StandardError)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Cop/CustomErrorClass
|
||||
|
||||
module Gitlab
|
||||
module Graphql
|
||||
class StandardGraphqlError < StandardError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -11402,9 +11402,6 @@ msgstr ""
|
|||
msgid "Didn't receive unlock instructions?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Diff content limits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Diff limits"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -23339,6 +23336,9 @@ msgstr ""
|
|||
msgid "PackageRegistry|Copy Pip command"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Copy SHA"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Copy add Gradle Groovy DSL repository command"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29645,6 +29645,9 @@ msgstr ""
|
|||
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set size limits for displaying diffs in the browser."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set target branch"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -39511,6 +39514,9 @@ msgstr ""
|
|||
msgid "view the source"
|
||||
msgstr ""
|
||||
|
||||
msgid "visibility"
|
||||
msgstr ""
|
||||
|
||||
msgid "vulnerability"
|
||||
msgid_plural "vulnerabilities"
|
||||
msgstr[0] ""
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class GitalyTestBuild
|
|||
# If we have the binaries from the cache, we can skip building them again
|
||||
if File.exist?(tmp_tests_gitaly_bin_dir)
|
||||
GitalySetup::LOGGER.debug "Gitaly binary already built. Skip building...\n"
|
||||
# We still need to install the gems in that case
|
||||
install_gitaly_gems
|
||||
else
|
||||
abort 'gitaly build failed' unless build_gitaly
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:params) { { namespace_id: project.namespace.to_param, project_id: project.to_param, created_after: '2010-01-01', created_before: '2010-01-02' } }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'GET "show"' do
|
||||
subject { get :show, params: params }
|
||||
|
||||
it 'succeeds' do
|
||||
project.add_reporter(user)
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to match_response_schema('analytics/cycle_analytics/summary')
|
||||
end
|
||||
|
||||
context 'when analytics_disabled features are disabled' do
|
||||
it 'renders 404' do
|
||||
project.add_reporter(user)
|
||||
project.project_feature.update!(analytics_access_level: ProjectFeature::DISABLED)
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not part of the project' do
|
||||
it 'renders 404' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -878,19 +878,6 @@ RSpec.describe Projects::PipelinesController do
|
|||
expect(::Ci::RetryPipelineWorker).to have_received(:perform_async).with(pipeline.id, user.id)
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(background_pipeline_retry_endpoint: false)
|
||||
end
|
||||
|
||||
it 'retries the pipeline without returning any content' do
|
||||
post_retry
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
expect(build.reload).to be_retried
|
||||
end
|
||||
end
|
||||
|
||||
context 'when builds are disabled' do
|
||||
let(:feature) { ProjectFeature::DISABLED }
|
||||
|
||||
|
|
|
|||
|
|
@ -96,26 +96,7 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
factory :clusters_applications_ingress, class: 'Clusters::Applications::Ingress' do
|
||||
modsecurity_enabled { false }
|
||||
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
|
||||
|
||||
trait :modsecurity_blocking do
|
||||
modsecurity_enabled { true }
|
||||
modsecurity_mode { :blocking }
|
||||
end
|
||||
|
||||
trait :modsecurity_logging do
|
||||
modsecurity_enabled { true }
|
||||
modsecurity_mode { :logging }
|
||||
end
|
||||
|
||||
trait :modsecurity_disabled do
|
||||
modsecurity_enabled { false }
|
||||
end
|
||||
|
||||
trait :modsecurity_not_installed do
|
||||
modsecurity_enabled { nil }
|
||||
end
|
||||
end
|
||||
|
||||
factory :clusters_applications_cert_manager, class: 'Clusters::Applications::CertManager' do
|
||||
|
|
@ -153,7 +134,6 @@ FactoryBot.define do
|
|||
|
||||
factory :clusters_applications_fluentd, class: 'Clusters::Applications::Fluentd' do
|
||||
host { 'example.com' }
|
||||
waf_log_enabled { true }
|
||||
cilium_log_enabled { true }
|
||||
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ FactoryBot.define do
|
|||
import_last_error { nil }
|
||||
forward_deployment_enabled { nil }
|
||||
restrict_user_defined_variables { nil }
|
||||
ci_job_token_scope_enabled { nil }
|
||||
end
|
||||
|
||||
before(:create) do |project, evaluator|
|
||||
|
|
@ -88,6 +89,7 @@ FactoryBot.define do
|
|||
project.merge_trains_enabled = evaluator.merge_trains_enabled unless evaluator.merge_trains_enabled.nil?
|
||||
project.keep_latest_artifact = evaluator.keep_latest_artifact unless evaluator.keep_latest_artifact.nil?
|
||||
project.restrict_user_defined_variables = evaluator.restrict_user_defined_variables unless evaluator.restrict_user_defined_variables.nil?
|
||||
project.ci_job_token_scope_enabled = evaluator.ci_job_token_scope_enabled unless evaluator.ci_job_token_scope_enabled.nil?
|
||||
|
||||
if evaluator.import_status
|
||||
import_state = project.import_state || project.build_import_state
|
||||
|
|
|
|||
|
|
@ -51,23 +51,55 @@ RSpec.describe Ci::RunnersFinder do
|
|||
end
|
||||
|
||||
context 'sort' do
|
||||
context 'without sort param' do
|
||||
it 'sorts by created_at' do
|
||||
runner1 = create :ci_runner, created_at: '2018-07-12 07:00'
|
||||
runner2 = create :ci_runner, created_at: '2018-07-12 08:00'
|
||||
runner3 = create :ci_runner, created_at: '2018-07-12 09:00'
|
||||
let_it_be(:runner1) { create :ci_runner, created_at: '2018-07-12 07:00', contacted_at: 1.minute.ago }
|
||||
let_it_be(:runner2) { create :ci_runner, created_at: '2018-07-12 08:00', contacted_at: 3.minutes.ago }
|
||||
let_it_be(:runner3) { create :ci_runner, created_at: '2018-07-12 09:00', contacted_at: 2.minutes.ago }
|
||||
|
||||
expect(described_class.new(current_user: admin, params: {}).execute).to eq [runner3, runner2, runner1]
|
||||
subject do
|
||||
described_class.new(current_user: admin, params: params).execute
|
||||
end
|
||||
|
||||
shared_examples 'sorts by created_at descending' do
|
||||
it 'sorts by created_at descending' do
|
||||
is_expected.to eq [runner3, runner2, runner1]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sort param' do
|
||||
it 'sorts by specified attribute' do
|
||||
runner1 = create :ci_runner, contacted_at: 1.minute.ago
|
||||
runner2 = create :ci_runner, contacted_at: 3.minutes.ago
|
||||
runner3 = create :ci_runner, contacted_at: 2.minutes.ago
|
||||
context 'without sort param' do
|
||||
let(:params) { {} }
|
||||
|
||||
expect(described_class.new(current_user: admin, params: { sort: 'contacted_asc' }).execute).to eq [runner2, runner3, runner1]
|
||||
it_behaves_like 'sorts by created_at descending'
|
||||
end
|
||||
|
||||
%w(created_date created_at_desc).each do |sort|
|
||||
context "with sort param equal to #{sort}" do
|
||||
let(:params) { { sort: sort } }
|
||||
|
||||
it_behaves_like 'sorts by created_at descending'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sort param equal to created_at_asc' do
|
||||
let(:params) { { sort: 'created_at_asc' } }
|
||||
|
||||
it 'sorts by created_at ascending' do
|
||||
is_expected.to eq [runner1, runner2, runner3]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sort param equal to contacted_asc' do
|
||||
let(:params) { { sort: 'contacted_asc' } }
|
||||
|
||||
it 'sorts by contacted_at ascending' do
|
||||
is_expected.to eq [runner2, runner3, runner1]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sort param equal to contacted_desc' do
|
||||
let(:params) { { sort: 'contacted_desc' } }
|
||||
|
||||
it 'sorts by contacted_at descending' do
|
||||
is_expected.to eq [runner1, runner3, runner2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -246,8 +278,8 @@ RSpec.describe Ci::RunnersFinder do
|
|||
subject { described_class.new(current_user: user, group: group, params: params).sort_key }
|
||||
|
||||
context 'no params' do
|
||||
it 'returns created_date' do
|
||||
expect(subject).to eq('created_date')
|
||||
it 'returns created_at_desc' do
|
||||
expect(subject).to eq('created_at_desc')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"minItems": 2,
|
||||
"maxItems": 3,
|
||||
"type": "object",
|
||||
"required": ["value", "title"],
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,6 @@
|
|||
"host": {"type": ["string", "null"]},
|
||||
"port": {"type": ["integer", "514"]},
|
||||
"protocol": {"type": ["integer", "0"]},
|
||||
"waf_log_enabled": {"type": ["boolean", "true"]},
|
||||
"cilium_log_enabled": {"type": ["boolean", "true"]},
|
||||
"update_available": { "type": ["boolean", "null"] },
|
||||
"can_uninstall": { "type": "boolean" },
|
||||
|
|
|
|||
|
|
@ -471,6 +471,7 @@ describe('URL utility', () => {
|
|||
${'notaurl'} | ${false}
|
||||
${'../relative_url'} | ${false}
|
||||
${'<a></a>'} | ${false}
|
||||
${'//other-host.test'} | ${false}
|
||||
`('returns $valid for $url', ({ url, valid }) => {
|
||||
expect(urlUtils.isRootRelative(url)).toBe(valid);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FileSha renders 1`] = `
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all gl-py-2 gl-border-b-solid gl-border-gray-100 gl-border-b-1"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<span>
|
||||
<div
|
||||
class="gl-px-4"
|
||||
>
|
||||
|
||||
bar:
|
||||
foo
|
||||
|
||||
<gl-button-stub
|
||||
aria-label="Copy this value"
|
||||
buttontextclasses=""
|
||||
category="tertiary"
|
||||
data-clipboard-text="foo"
|
||||
icon="copy-to-clipboard"
|
||||
size="small"
|
||||
title="Copy SHA"
|
||||
variant="default"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import FileSha from '~/packages/details/components/file_sha.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
|
||||
|
||||
describe('FileSha', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = { sha: 'foo', title: 'bar' };
|
||||
|
||||
function createComponent() {
|
||||
wrapper = shallowMount(FileSha, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
},
|
||||
stubs: {
|
||||
ClipboardButton,
|
||||
DetailsRow,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { GlDropdown } from '@gitlab/ui';
|
||||
import { GlDropdown, GlButton } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue/';
|
||||
import stubChildren from 'helpers/stub_children';
|
||||
import component from '~/packages/details/components/package_files.vue';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
|
|
@ -20,6 +21,8 @@ describe('Package Files', () => {
|
|||
const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip);
|
||||
const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown);
|
||||
const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]');
|
||||
const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton);
|
||||
const findFirstRowShaComponent = (id) => wrapper.find(`[data-testid="${id}"]`);
|
||||
|
||||
const createComponent = ({ packageFiles = npmFiles, canDelete = true } = {}) => {
|
||||
wrapper = mount(component, {
|
||||
|
|
@ -181,4 +184,76 @@ describe('Package Files', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('additional details', () => {
|
||||
describe('details toggle button', () => {
|
||||
it('exists', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findFirstToggleDetailsButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('is hidden when no details is present', () => {
|
||||
const [{ ...noShaFile }] = npmFiles;
|
||||
noShaFile.file_sha256 = null;
|
||||
noShaFile.file_md5 = null;
|
||||
noShaFile.file_sha1 = null;
|
||||
createComponent({ packageFiles: [noShaFile] });
|
||||
|
||||
expect(findFirstToggleDetailsButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('toggles the details row', async () => {
|
||||
createComponent();
|
||||
|
||||
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down');
|
||||
|
||||
findFirstToggleDetailsButton().vm.$emit('click');
|
||||
await nextTick();
|
||||
|
||||
expect(findFirstRowShaComponent('sha-256').exists()).toBe(true);
|
||||
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-up');
|
||||
|
||||
findFirstToggleDetailsButton().vm.$emit('click');
|
||||
await nextTick();
|
||||
|
||||
expect(findFirstRowShaComponent('sha-256').exists()).toBe(false);
|
||||
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down');
|
||||
});
|
||||
});
|
||||
|
||||
describe('file shas', () => {
|
||||
const showShaFiles = () => {
|
||||
findFirstToggleDetailsButton().vm.$emit('click');
|
||||
return nextTick();
|
||||
};
|
||||
|
||||
it.each`
|
||||
selector | title | sha
|
||||
${'sha-256'} | ${'SHA-256'} | ${'file_sha256'}
|
||||
${'md5'} | ${'MD5'} | ${'file_md5'}
|
||||
${'sha-1'} | ${'SHA-1'} | ${'file_sha1'}
|
||||
`('has a $title row', async ({ selector, title, sha }) => {
|
||||
createComponent();
|
||||
|
||||
await showShaFiles();
|
||||
|
||||
expect(findFirstRowShaComponent(selector).props()).toMatchObject({
|
||||
title,
|
||||
sha,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not display a row when the data is missing', async () => {
|
||||
const [{ ...missingMd5 }] = npmFiles;
|
||||
missingMd5.file_md5 = null;
|
||||
|
||||
createComponent({ packageFiles: [missingMd5] });
|
||||
|
||||
await showShaFiles();
|
||||
|
||||
expect(findFirstRowShaComponent('md5').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ export const npmFiles = [
|
|||
pipelines: [
|
||||
{ id: 1, project: { commit_url: 'http://foo.bar' }, git_commit_message: 'foo bar baz?' },
|
||||
],
|
||||
file_sha256: 'file_sha256',
|
||||
file_md5: 'file_md5',
|
||||
file_sha1: 'file_sha1',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ describe('App component', () => {
|
|||
projectPath: 'project-name',
|
||||
projectDescription: 'some project description',
|
||||
projectVisibility: 'private',
|
||||
restrictedVisibilityLevels: [],
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadio, GlFormSelect } from '@gitlab/ui';
|
||||
import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
|
||||
import { getByRole, getAllByRole } from '@testing-library/dom';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import axios from 'axios';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
|
|
@ -44,6 +45,7 @@ describe('ForkForm component', () => {
|
|||
projectPath: 'project-name',
|
||||
projectDescription: 'some project description',
|
||||
projectVisibility: 'private',
|
||||
restrictedVisibilityLevels: [],
|
||||
};
|
||||
|
||||
const mockGetRequest = (data = {}, statusCode = httpStatus.OK) => {
|
||||
|
|
@ -68,6 +70,7 @@ describe('ForkForm component', () => {
|
|||
stubs: {
|
||||
GlFormInputGroup,
|
||||
GlFormInput,
|
||||
GlFormRadioGroup,
|
||||
GlFormRadio,
|
||||
},
|
||||
});
|
||||
|
|
@ -89,7 +92,7 @@ describe('ForkForm component', () => {
|
|||
axiosMock.restore();
|
||||
});
|
||||
|
||||
const findFormSelect = () => wrapper.find(GlFormSelect);
|
||||
const findFormSelectOptions = () => wrapper.find('select[name="namespace"]').findAll('option');
|
||||
const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]');
|
||||
const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]');
|
||||
const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]');
|
||||
|
|
@ -230,7 +233,7 @@ describe('ForkForm component', () => {
|
|||
expect(wrapper.findAll(GlFormRadio)).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('resets the visibility to default "private" when the namespace is changed', async () => {
|
||||
describe('when the namespace is changed', () => {
|
||||
const namespaces = [
|
||||
{
|
||||
visibility: 'private',
|
||||
|
|
@ -243,42 +246,114 @@ describe('ForkForm component', () => {
|
|||
},
|
||||
];
|
||||
|
||||
mockGetRequest();
|
||||
createComponent(
|
||||
{
|
||||
projectVisibility: 'public',
|
||||
},
|
||||
{
|
||||
namespaces,
|
||||
},
|
||||
);
|
||||
beforeEach(() => {
|
||||
mockGetRequest();
|
||||
});
|
||||
|
||||
expect(wrapper.vm.form.fields.visibility.value).toBe('public');
|
||||
findFormSelect().vm.$emit('input', namespaces[1]);
|
||||
it('resets the visibility to default "private"', async () => {
|
||||
createFullComponent({ projectVisibility: 'public' }, { namespaces });
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.vm.form.fields.visibility.value).toBe('public');
|
||||
await findFormSelectOptions().at(1).setSelected();
|
||||
|
||||
expect(wrapper.vm.form.fields.visibility.value).toBe('private');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(getByRole(wrapper.element, 'radio', { name: /private/i }).checked).toBe(true);
|
||||
});
|
||||
|
||||
it('sets the visibility to be null when restrictedVisibilityLevels is set', async () => {
|
||||
createFullComponent({ restrictedVisibilityLevels: [10] }, { namespaces });
|
||||
|
||||
await findFormSelectOptions().at(1).setSelected();
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const container = getByRole(wrapper.element, 'radiogroup', { name: /visibility/i });
|
||||
const visibilityRadios = getAllByRole(container, 'radio');
|
||||
expect(visibilityRadios.filter((e) => e.checked)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled
|
||||
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
|
||||
${'private'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'}
|
||||
${'private'} | ${'public'} | ${undefined} | ${'true'} | ${'true'}
|
||||
${'internal'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
|
||||
${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'}
|
||||
${'internal'} | ${'public'} | ${undefined} | ${undefined} | ${'true'}
|
||||
${'public'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
|
||||
${'public'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'}
|
||||
${'public'} | ${'public'} | ${undefined} | ${undefined} | ${undefined}
|
||||
project | restrictedVisibilityLevels
|
||||
${'private'} | ${[]}
|
||||
${'internal'} | ${[]}
|
||||
${'public'} | ${[]}
|
||||
${'private'} | ${[0]}
|
||||
${'private'} | ${[10]}
|
||||
${'private'} | ${[20]}
|
||||
${'private'} | ${[0, 10]}
|
||||
${'private'} | ${[0, 20]}
|
||||
${'private'} | ${[10, 20]}
|
||||
${'private'} | ${[0, 10, 20]}
|
||||
${'internal'} | ${[0]}
|
||||
${'internal'} | ${[10]}
|
||||
${'internal'} | ${[20]}
|
||||
${'internal'} | ${[0, 10]}
|
||||
${'internal'} | ${[0, 20]}
|
||||
${'internal'} | ${[10, 20]}
|
||||
${'internal'} | ${[0, 10, 20]}
|
||||
${'public'} | ${[0]}
|
||||
${'public'} | ${[10]}
|
||||
${'public'} | ${[0, 10]}
|
||||
${'public'} | ${[0, 20]}
|
||||
${'public'} | ${[10, 20]}
|
||||
${'public'} | ${[0, 10, 20]}
|
||||
`('checks the correct radio button', async ({ project, restrictedVisibilityLevels }) => {
|
||||
mockGetRequest();
|
||||
createFullComponent({
|
||||
projectVisibility: project,
|
||||
restrictedVisibilityLevels,
|
||||
});
|
||||
|
||||
if (restrictedVisibilityLevels.length === 0) {
|
||||
expect(wrapper.find('[name="visibility"]:checked').attributes('value')).toBe(project);
|
||||
} else {
|
||||
expect(wrapper.find('[name="visibility"]:checked').exists()).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it.each`
|
||||
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled | restrictedVisibilityLevels
|
||||
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
|
||||
${'private'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
|
||||
${'private'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
|
||||
${'internal'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
|
||||
${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
|
||||
${'internal'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
|
||||
${'public'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
|
||||
${'public'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
|
||||
${'public'} | ${'public'} | ${undefined} | ${undefined} | ${undefined} | ${[]}
|
||||
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[0]}
|
||||
${'internal'} | ${'internal'} | ${'true'} | ${undefined} | ${'true'} | ${[0]}
|
||||
${'public'} | ${'public'} | ${'true'} | ${undefined} | ${undefined} | ${[0]}
|
||||
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[10]}
|
||||
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[10]}
|
||||
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${undefined} | ${[10]}
|
||||
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[20]}
|
||||
${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[20]}
|
||||
${'public'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} | ${[20]}
|
||||
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
|
||||
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
|
||||
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
|
||||
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
|
||||
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
|
||||
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
|
||||
`(
|
||||
'sets appropriate radio button disabled state',
|
||||
async ({ project, namespace, privateIsDisabled, internalIsDisabled, publicIsDisabled }) => {
|
||||
async ({
|
||||
project,
|
||||
namespace,
|
||||
privateIsDisabled,
|
||||
internalIsDisabled,
|
||||
publicIsDisabled,
|
||||
restrictedVisibilityLevels,
|
||||
}) => {
|
||||
mockGetRequest();
|
||||
createComponent(
|
||||
{
|
||||
projectVisibility: project,
|
||||
restrictedVisibilityLevels,
|
||||
},
|
||||
{
|
||||
form: { fields: { namespace: { value: { visibility: namespace } } } },
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@ describe('fetchLogsTree', () => {
|
|||
mock.restore();
|
||||
});
|
||||
|
||||
it('persists the offset for a given page if offset is larger than maximum offset', async () => {
|
||||
await fetchLogsTree(client, 'path', '1000', resolver, 900).then(() => {});
|
||||
|
||||
await fetchLogsTree(client, 'path', '1100', resolver, 1200).then(() => {
|
||||
expect(axios.get).toHaveBeenCalledWith('/gitlab-org/gitlab-foss/-/refs/main/logs_tree/path', {
|
||||
params: { format: 'json', offset: 975 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call axios get if offset is larger than the maximum offset', () =>
|
||||
fetchLogsTree(client, '', '1000', resolver, 900).then(() => {
|
||||
expect(axios.get).not.toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -50,13 +50,29 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
|
|||
it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner]) }
|
||||
end
|
||||
|
||||
context "set to :created_date" do
|
||||
context "set to :contacted_desc" do
|
||||
let(:args) do
|
||||
{ sort: :created_date }
|
||||
{ sort: :contacted_desc }
|
||||
end
|
||||
|
||||
it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner].reverse) }
|
||||
end
|
||||
|
||||
context "set to :created_at_desc" do
|
||||
let(:args) do
|
||||
{ sort: :created_at_desc }
|
||||
end
|
||||
|
||||
it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner]) }
|
||||
end
|
||||
|
||||
context "set to :created_at_asc" do
|
||||
let(:args) do
|
||||
{ sort: :created_at_asc }
|
||||
end
|
||||
|
||||
it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner].reverse) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when type is filtered' do
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::CycleAnalytics::StageSummary do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:options) { { from: 1.day.ago, current_user: user } }
|
||||
let(:options) { { from: 1.day.ago } }
|
||||
let(:args) { { options: options, current_user: user } }
|
||||
let(:user) { create(:user, :admin) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
let(:stage_summary) { described_class.new(project, **options).data }
|
||||
let(:stage_summary) { described_class.new(project, **args).data }
|
||||
|
||||
describe "#new_issues" do
|
||||
subject { stage_summary.first }
|
||||
|
|
@ -117,11 +118,11 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
|
|||
|
||||
before do
|
||||
project.add_guest(guest_user)
|
||||
options.merge!({ current_user: guest_user })
|
||||
args.merge!({ current_user: guest_user })
|
||||
end
|
||||
|
||||
it 'does not include commit stats' do
|
||||
data = described_class.new(project, **options).data
|
||||
data = described_class.new(project, **args).data
|
||||
expect(includes_commits?(data)).to be_falsy
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
|||
expect(hosts).to all(receive(:caught_up?).with(location).and_return(false))
|
||||
end
|
||||
|
||||
it 'returns true and has does not set the valid hosts' do
|
||||
it 'returns false and does not set the valid hosts' do
|
||||
expect(subject).to be false
|
||||
expect(valid_host_list).to be_nil
|
||||
end
|
||||
|
|
@ -487,4 +487,36 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#select_caught_up_hosts' do
|
||||
let(:location) { 'AB/12345'}
|
||||
let(:hosts) { lb.host_list.hosts }
|
||||
let(:set_host) { RequestStore[described_class::CACHE_KEY] }
|
||||
|
||||
subject { lb.select_up_to_date_host(location) }
|
||||
|
||||
context 'when none of the replicas are caught up' do
|
||||
before do
|
||||
expect(hosts).to all(receive(:caught_up?).with(location).and_return(false))
|
||||
end
|
||||
|
||||
it 'returns false and does not update the host thread-local variable' do
|
||||
expect(subject).to be false
|
||||
expect(set_host).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when any of the replicas is caught up' do
|
||||
before do
|
||||
# `allow` for non-caught up host, because we may not even check it, if will find the caught up one earlier
|
||||
allow(hosts[0]).to receive(:caught_up?).with(location).and_return(false)
|
||||
expect(hosts[1]).to receive(:caught_up?).with(location).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns true and sets host thread-local variable' do
|
||||
expect(subject).to be true
|
||||
expect(set_host).to eq(hosts[1])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
|
|||
context 'when replica is not up to date' do
|
||||
before do
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :release_host)
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :host, :caught_up?).and_return(false)
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :select_up_to_date_host).and_return(false)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
|
|
@ -157,6 +157,20 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
|
|||
expect(job[:database_chosen]).to eq('primary')
|
||||
end
|
||||
end
|
||||
|
||||
context 'replica selection mechanism feature flag rollout' do
|
||||
before do
|
||||
stub_feature_flags(sidekiq_load_balancing_rotate_up_to_date_replica: false)
|
||||
end
|
||||
|
||||
it 'uses different implmentation' do
|
||||
expect(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :host, :caught_up?).and_return(false)
|
||||
|
||||
expect do
|
||||
process_job(job)
|
||||
end.to raise_error(Sidekiq::JobRetry::Skip)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
end
|
||||
|
||||
shared_context 'LoadBalancing setup' do
|
||||
let(:development_db_config) { ActiveRecord::Base.configurations.default_hash("development").with_indifferent_access }
|
||||
let(:development_db_config) { ActiveRecord::Base.configurations.configs_for(env_name: 'development').first.configuration_hash }
|
||||
let(:hosts) { [development_db_config[:host]] }
|
||||
let(:model) do
|
||||
Class.new(ApplicationRecord) do
|
||||
|
|
|
|||
|
|
@ -38,22 +38,6 @@ RSpec.describe Gitlab::Usage::Metrics::NameSuggestion do
|
|||
end
|
||||
|
||||
context 'joined relations' do
|
||||
context 'counted attribute comes from joined relation' do
|
||||
it_behaves_like 'name suggestion' do
|
||||
let(:operation) { :distinct_count }
|
||||
let(:column) { ::Deployment.arel_table[:environment_id] }
|
||||
let(:relation) do
|
||||
::Clusters::Applications::Ingress.modsecurity_enabled.logging
|
||||
.joins(cluster: :deployments)
|
||||
.merge(::Clusters::Cluster.enabled)
|
||||
.merge(Deployment.success)
|
||||
end
|
||||
|
||||
let(:constraints) { /'\(clusters_applications_ingress\.modsecurity_enabled = TRUE AND clusters_applications_ingress\.modsecurity_mode = \d+ AND clusters.enabled = TRUE AND deployments.status = \d+\)'/ }
|
||||
let(:name_suggestion) { /count_distinct_environment_id_from_<adjective describing\: #{constraints}>_deployments_<with>_<adjective describing\: #{constraints}>_clusters_<having>_<adjective describing\: #{constraints}>_clusters_applications_ingress/ }
|
||||
end
|
||||
end
|
||||
|
||||
context 'counted attribute comes from source relation' do
|
||||
it_behaves_like 'name suggestion' do
|
||||
# corresponding metric is collected with count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Applications::Fluentd do
|
||||
let(:waf_log_enabled) { true }
|
||||
let(:cilium_log_enabled) { true }
|
||||
let(:fluentd) { create(:clusters_applications_fluentd, waf_log_enabled: waf_log_enabled, cilium_log_enabled: cilium_log_enabled) }
|
||||
let(:fluentd) { create(:clusters_applications_fluentd, cilium_log_enabled: cilium_log_enabled) }
|
||||
|
||||
include_examples 'cluster application core specs', :clusters_applications_fluentd
|
||||
include_examples 'cluster application status specs', :clusters_applications_fluentd
|
||||
|
|
@ -51,13 +50,11 @@ RSpec.describe Clusters::Applications::Fluentd do
|
|||
end
|
||||
|
||||
describe '#values' do
|
||||
let(:modsecurity_log_path) { "/var/log/containers/*#{Clusters::Applications::Ingress::MODSECURITY_LOG_CONTAINER_NAME}*.log" }
|
||||
let(:cilium_log_path) { "/var/log/containers/*#{described_class::CILIUM_CONTAINER_NAME}*.log" }
|
||||
|
||||
subject { fluentd.values }
|
||||
|
||||
context 'with both logs variables set to false' do
|
||||
let(:waf_log_enabled) { false }
|
||||
context 'with cilium_log_enabled set to false' do
|
||||
let(:cilium_log_enabled) { false }
|
||||
|
||||
it "raises ActiveRecord::RecordInvalid" do
|
||||
|
|
@ -65,18 +62,8 @@ RSpec.describe Clusters::Applications::Fluentd do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with both logs variables set to true' do
|
||||
it { is_expected.to include("#{modsecurity_log_path},#{cilium_log_path}") }
|
||||
end
|
||||
|
||||
context 'with waf_log_enabled set to true' do
|
||||
let(:cilium_log_enabled) { false }
|
||||
|
||||
it { is_expected.to include(modsecurity_log_path) }
|
||||
end
|
||||
|
||||
context 'with cilium_log_enabled set to true' do
|
||||
let(:waf_log_enabled) { false }
|
||||
let(:cilium_log_enabled) { true }
|
||||
|
||||
it { is_expected.to include(cilium_log_path) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -172,94 +172,4 @@ RSpec.describe Clusters::Applications::Ingress do
|
|||
expect(values).to include('clusterIP')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#values' do
|
||||
subject { ingress }
|
||||
|
||||
context 'when modsecurity_enabled is enabled' do
|
||||
before do
|
||||
allow(subject).to receive(:modsecurity_enabled).and_return(true)
|
||||
end
|
||||
|
||||
it 'includes modsecurity module enablement' do
|
||||
expect(subject.values).to include("enable-modsecurity: 'true'")
|
||||
end
|
||||
|
||||
it 'includes modsecurity core ruleset enablement set to false' do
|
||||
expect(subject.values).to include("enable-owasp-modsecurity-crs: 'false'")
|
||||
end
|
||||
|
||||
it 'includes modsecurity snippet with information related to security rules' do
|
||||
expect(subject.values).to include("SecRuleEngine DetectionOnly")
|
||||
expect(subject.values).to include("Include #{described_class::MODSECURITY_OWASP_RULES_FILE}")
|
||||
end
|
||||
|
||||
context 'when modsecurity_mode is set to :blocking' do
|
||||
before do
|
||||
subject.blocking!
|
||||
end
|
||||
|
||||
it 'includes modsecurity snippet with information related to security rules' do
|
||||
expect(subject.values).to include("SecRuleEngine On")
|
||||
expect(subject.values).to include("Include #{described_class::MODSECURITY_OWASP_RULES_FILE}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'includes modsecurity.conf content' do
|
||||
expect(subject.values).to include('modsecurity.conf')
|
||||
# Includes file content from Ingress#modsecurity_config_content
|
||||
expect(subject.values).to include('SecAuditLog')
|
||||
|
||||
expect(subject.values).to include('extraVolumes')
|
||||
expect(subject.values).to include('extraVolumeMounts')
|
||||
end
|
||||
|
||||
it 'includes modsecurity sidecar container' do
|
||||
expect(subject.values).to include('modsecurity-log-volume')
|
||||
|
||||
expect(subject.values).to include('extraContainers')
|
||||
end
|
||||
|
||||
it 'executes command to tail modsecurity logs with -F option' do
|
||||
args = YAML.safe_load(subject.values).dig('controller', 'extraContainers', 0, 'args')
|
||||
|
||||
expect(args).to eq(['/bin/sh', '-c', 'tail -F /var/log/modsec/audit.log'])
|
||||
end
|
||||
|
||||
it 'includes livenessProbe for modsecurity sidecar container' do
|
||||
probe_config = YAML.safe_load(subject.values).dig('controller', 'extraContainers', 0, 'livenessProbe')
|
||||
|
||||
expect(probe_config).to eq('exec' => { 'command' => ['ls', '/var/log/modsec/audit.log'] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when modsecurity_enabled is disabled' do
|
||||
before do
|
||||
allow(subject).to receive(:modsecurity_enabled).and_return(false)
|
||||
end
|
||||
|
||||
it 'excludes modsecurity module enablement' do
|
||||
expect(subject.values).not_to include('enable-modsecurity')
|
||||
end
|
||||
|
||||
it 'excludes modsecurity core ruleset enablement' do
|
||||
expect(subject.values).not_to include('enable-owasp-modsecurity-crs')
|
||||
end
|
||||
|
||||
it 'excludes modsecurity.conf content' do
|
||||
expect(subject.values).not_to include('modsecurity.conf')
|
||||
# Excludes file content from Ingress#modsecurity_config_content
|
||||
expect(subject.values).not_to include('SecAuditLog')
|
||||
|
||||
expect(subject.values).not_to include('extraVolumes')
|
||||
expect(subject.values).not_to include('extraVolumeMounts')
|
||||
end
|
||||
|
||||
it 'excludes modsecurity sidecar container' do
|
||||
expect(subject.values).not_to include('modsecurity-log-volume')
|
||||
|
||||
expect(subject.values).not_to include('extraContainers')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -196,28 +196,6 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.with_enabled_modsecurity' do
|
||||
subject { described_class.with_enabled_modsecurity }
|
||||
|
||||
let_it_be(:cluster) { create(:cluster) }
|
||||
|
||||
context 'cluster has ingress application with enabled modsecurity' do
|
||||
let!(:application) { create(:clusters_applications_ingress, :installed, :modsecurity_logging, cluster: cluster) }
|
||||
|
||||
it { is_expected.to include(cluster) }
|
||||
end
|
||||
|
||||
context 'cluster has ingress application with disabled modsecurity' do
|
||||
let!(:application) { create(:clusters_applications_ingress, :installed, :modsecurity_disabled, cluster: cluster) }
|
||||
|
||||
it { is_expected.not_to include(cluster) }
|
||||
end
|
||||
|
||||
context 'cluster does not have ingress application' do
|
||||
it { is_expected.not_to include(cluster) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_available_elasticstack' do
|
||||
subject { described_class.with_available_elasticstack }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Members::LastGroupOwnerAssigner do
|
||||
RSpec.describe LastGroupOwnerAssigner do
|
||||
describe "#execute" do
|
||||
let_it_be(:user, reload: true) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ RSpec.describe 'Getting Ci Cd Setting' do
|
|||
expect(settings_data['mergePipelinesEnabled']).to eql project.ci_cd_settings.merge_pipelines_enabled?
|
||||
expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled?
|
||||
expect(settings_data['keepLatestArtifact']).to eql project.keep_latest_artifacts_available?
|
||||
expect(settings_data['jobTokenScopeEnabled']).to eql project.ci_cd_settings.job_token_scope_enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,8 +5,16 @@ require 'spec_helper'
|
|||
RSpec.describe 'CiCdSettingsUpdate' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project) { create(:project, keep_latest_artifact: true) }
|
||||
let(:variables) { { full_path: project.full_path, keep_latest_artifact: false } }
|
||||
let_it_be(:project) { create(:project, keep_latest_artifact: true, ci_job_token_scope_enabled: true) }
|
||||
|
||||
let(:variables) do
|
||||
{
|
||||
full_path: project.full_path,
|
||||
keep_latest_artifact: false,
|
||||
job_token_scope_enabled: false
|
||||
}
|
||||
end
|
||||
|
||||
let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) }
|
||||
|
||||
context 'when unauthorized' do
|
||||
|
|
@ -45,6 +53,26 @@ RSpec.describe 'CiCdSettingsUpdate' do
|
|||
expect(project.keep_latest_artifact).to eq(false)
|
||||
end
|
||||
|
||||
it 'updates job_token_scope_enabled' do
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
|
||||
project.reload
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(project.ci_job_token_scope_enabled).to eq(false)
|
||||
end
|
||||
|
||||
it 'does not update job_token_scope_enabled if not specified' do
|
||||
variables.except!(:job_token_scope_enabled)
|
||||
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
|
||||
project.reload
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(project.ci_job_token_scope_enabled).to eq(true)
|
||||
end
|
||||
|
||||
context 'when bad arguments are provided' do
|
||||
let(:variables) { { full_path: '', keep_latest_artifact: false } }
|
||||
|
||||
|
|
|
|||
|
|
@ -358,26 +358,6 @@ RSpec.describe API::Tags do
|
|||
expect(json_response['message']).to eq('Target foo is invalid')
|
||||
end
|
||||
|
||||
context 'when release_description is passed' do
|
||||
it 'returns error' do
|
||||
post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311")
|
||||
end
|
||||
|
||||
it 'creates a new tag with release if feature is enabled' do
|
||||
stub_feature_flags(remove_release_notes_from_tags_api: false)
|
||||
|
||||
post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(response).to match_response_schema('public_api/v4/tag')
|
||||
expect(json_response['name']).to eq(tag_name)
|
||||
expect(json_response['release']['description']).to eq('Wow')
|
||||
end
|
||||
end
|
||||
|
||||
context 'annotated tag' do
|
||||
it 'creates a new annotated tag' do
|
||||
# Identity must be set in .gitconfig to create annotated tag.
|
||||
|
|
@ -449,148 +429,4 @@ RSpec.describe API::Tags do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/:id/repository/tags/:tag_name/release' do
|
||||
let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" }
|
||||
let(:description) { 'Awesome release!' }
|
||||
|
||||
before do
|
||||
stub_feature_flags(remove_release_notes_from_tags_api: false)
|
||||
end
|
||||
|
||||
shared_examples_for 'repository new release' do
|
||||
it 'creates description for existing git tag' do
|
||||
post api(route, user), params: { description: description }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(response).to match_response_schema('public_api/v4/release/tag_release')
|
||||
expect(json_response['tag_name']).to eq(tag_name)
|
||||
expect(json_response['description']).to eq(description)
|
||||
end
|
||||
|
||||
it 'returns error if feature is removed' do
|
||||
stub_feature_flags(remove_release_notes_from_tags_api: true)
|
||||
|
||||
post api(route, user), params: { description: description }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311")
|
||||
end
|
||||
|
||||
context 'when tag does not exist' do
|
||||
let(:tag_name) { 'unknown' }
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { post api(route, current_user), params: { description: description } }
|
||||
let(:message) { '404 Tag Not Found' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { post api(route, current_user), params: { description: description } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a maintainer' do
|
||||
let(:current_user) { user }
|
||||
|
||||
it_behaves_like 'repository new release'
|
||||
|
||||
context 'requesting with the escaped project full path' do
|
||||
let(:project_id) { CGI.escape(project.full_path) }
|
||||
|
||||
it_behaves_like 'repository new release'
|
||||
end
|
||||
|
||||
context 'on tag with existing release' do
|
||||
let!(:release) { create(:release, :legacy, project: project, tag: tag_name, description: description) }
|
||||
|
||||
it 'returns 409 if there is already a release' do
|
||||
post api(route, user), params: { description: description }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response['message']).to eq('Release already exists')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT id/repository/tags/:tag_name/release' do
|
||||
let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" }
|
||||
let(:description) { 'Awesome release!' }
|
||||
let(:new_description) { 'The best release!' }
|
||||
|
||||
before do
|
||||
stub_feature_flags(remove_release_notes_from_tags_api: false)
|
||||
end
|
||||
|
||||
shared_examples_for 'repository update release' do
|
||||
context 'on tag with existing release' do
|
||||
let!(:release) do
|
||||
create(:release,
|
||||
:legacy,
|
||||
project: project,
|
||||
tag: tag_name,
|
||||
description: description)
|
||||
end
|
||||
|
||||
it 'updates the release description' do
|
||||
put api(route, current_user), params: { description: new_description }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['tag_name']).to eq(tag_name)
|
||||
expect(json_response['description']).to eq(new_description)
|
||||
end
|
||||
|
||||
it 'returns error if feature is removed' do
|
||||
stub_feature_flags(remove_release_notes_from_tags_api: true)
|
||||
|
||||
put api(route, current_user), params: { description: new_description }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tag does not exist' do
|
||||
let(:tag_name) { 'unknown' }
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { put api(route, current_user), params: { description: new_description } }
|
||||
let(:message) { '403 Forbidden' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { put api(route, current_user), params: { description: new_description } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a maintainer' do
|
||||
let(:current_user) { user }
|
||||
|
||||
it_behaves_like 'repository update release'
|
||||
|
||||
context 'requesting with the escaped project full path' do
|
||||
let(:project_id) { CGI.escape(project.full_path) }
|
||||
|
||||
it_behaves_like 'repository update release'
|
||||
end
|
||||
|
||||
context 'when release does not exist' do
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { put api(route, current_user), params: { description: new_description } }
|
||||
let(:message) { '403 Forbidden' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ RSpec.describe AnalyticsSummarySerializer do
|
|||
|
||||
let(:resource) do
|
||||
Gitlab::CycleAnalytics::Summary::Issue
|
||||
.new(project: double, from: 1.day.ago, current_user: user)
|
||||
.new(project: double, options: { from: 1.day.ago }, current_user: user)
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
@ -36,7 +36,7 @@ RSpec.describe AnalyticsSummarySerializer do
|
|||
context 'when representing with unit' do
|
||||
let(:resource) do
|
||||
Gitlab::CycleAnalytics::Summary::DeploymentFrequency
|
||||
.new(deployments: 10, from: 1.day.ago)
|
||||
.new(deployments: 10, options: { from: 1.day.ago })
|
||||
end
|
||||
|
||||
subject { described_class.new.represent(resource, with_unit: true) }
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue