Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-11 15:15:55 +00:00
parent 22391da126
commit a8281ac434
56 changed files with 642 additions and 223 deletions

View File

@ -463,7 +463,7 @@ db:backup_and_restore:
script:
- . scripts/prepare_build.sh
- bundle exec rake db:drop db:create db:structure:load db:seed_fu
- mkdir -p tmp/tests/public/uploads tmp/tests/{artifacts,pages,lfs-objects,terraform_state,registry}
- mkdir -p tmp/tests/public/uploads tmp/tests/{artifacts,pages,lfs-objects,terraform_state,registry,packages}
- bundle exec rake gitlab:backup:create
- date
- bundle exec rake gitlab:backup:restore

View File

@ -4,6 +4,7 @@ import initUserPopovers from '../../user_popovers';
import highlightCurrentUser from './highlight_current_user';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
import renderSandboxedMermaid from './render_sandboxed_mermaid';
import renderMetrics from './render_metrics';
// Render GitLab flavoured Markdown
@ -13,7 +14,11 @@ import renderMetrics from './render_metrics';
$.fn.renderGFM = function renderGFM() {
syntaxHighlight(this.find('.js-syntax-highlight').get());
renderMath(this.find('.js-render-math'));
renderMermaid(this.find('.js-render-mermaid'));
if (gon.features?.sandboxedMermaid) {
renderSandboxedMermaid(this.find('.js-render-mermaid'));
} else {
renderMermaid(this.find('.js-render-mermaid'));
}
highlightCurrentUser(this.find('.gfm-project_member').get());
initUserPopovers(this.find('.js-user-link').get());

View File

@ -0,0 +1,233 @@
import $ from 'jquery';
import { once, countBy } from 'lodash';
import { __ } from '~/locale';
import {
getBaseURL,
relativePathToAbsolute,
setUrlParams,
joinPaths,
} from '~/lib/utils/url_utility';
import { darkModeEnabled } from '~/lib/utils/color_utils';
import { setAttributes } from '~/lib/utils/dom_utils';
// Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class.
//
// Example markup:
//
// <pre class="js-render-mermaid">
// graph TD;
// A-- > B;
// A-- > C;
// B-- > D;
// C-- > D;
// </pre>
//
const SANDBOX_FRAME_PATH = '/-/sandbox/mermaid';
// This is an arbitrary number; Can be iterated upon when suitable.
const MAX_CHAR_LIMIT = 2000;
// Max # of mermaid blocks that can be rendered in a page.
const MAX_MERMAID_BLOCK_LIMIT = 50;
// Max # of `&` allowed in Chaining of links syntax
const MAX_CHAINING_OF_LINKS_LIMIT = 30;
// Keep a map of mermaid blocks we've already rendered.
const elsProcessingMap = new WeakMap();
let renderedMermaidBlocks = 0;
// Pages without any restrictions on mermaid rendering
const PAGES_WITHOUT_RESTRICTIONS = [
// Group wiki
'groups:wikis:show',
'groups:wikis:edit',
'groups:wikis:create',
// Project wiki
'projects:wikis:show',
'projects:wikis:edit',
'projects:wikis:create',
// Project files
'projects:show',
'projects:blob:show',
];
function shouldLazyLoadMermaidBlock(source) {
/**
* If source contains `&`, which means that it might
* contain Chaining of links a new syntax in Mermaid.
*/
if (countBy(source)['&'] > MAX_CHAINING_OF_LINKS_LIMIT) {
return true;
}
return false;
}
function fixElementSource(el) {
// Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
const source = el.textContent?.replace(/<br\s*\/>/g, '<br>');
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
return { source };
}
function getSandboxFrameSrc() {
const path = joinPaths(gon.relative_url_root || '', SANDBOX_FRAME_PATH);
if (!darkModeEnabled()) {
return path;
}
const absoluteUrl = relativePathToAbsolute(path, getBaseURL());
return setUrlParams({ darkMode: darkModeEnabled() }, absoluteUrl);
}
function renderMermaidEl(el, source) {
const iframeEl = document.createElement('iframe');
setAttributes(iframeEl, {
src: getSandboxFrameSrc(),
sandbox: 'allow-scripts',
frameBorder: 0,
scrolling: 'no',
});
// Add the original source into the DOM
// to allow Copy-as-GFM to access it.
const sourceEl = document.createElement('text');
sourceEl.textContent = source;
sourceEl.classList.add('gl-display-none');
const wrapper = document.createElement('div');
wrapper.appendChild(iframeEl);
wrapper.appendChild(sourceEl);
el.closest('pre').replaceWith(wrapper);
// Event Listeners
iframeEl.addEventListener('load', () => {
// Potential risk associated with '*' discussed in below thread
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74414#note_735183398
iframeEl.contentWindow.postMessage(source, '*');
});
window.addEventListener(
'message',
(event) => {
if (event.origin !== 'null' || event.source !== iframeEl.contentWindow) {
return;
}
const { h, w } = event.data;
iframeEl.width = w;
iframeEl.height = h;
},
false,
);
}
function renderMermaids($els) {
if (!$els.length) return;
const pageName = document.querySelector('body').dataset.page;
// A diagram may have been truncated in search results which will cause errors, so abort the render.
if (pageName === 'search:show') return;
let renderedChars = 0;
$els.each((i, el) => {
// Skipping all the elements which we've already queued in requestIdleCallback
if (elsProcessingMap.has(el)) {
return;
}
const { source } = fixElementSource(el);
/**
* Restrict the rendering to a certain amount of character
* and mermaid blocks to prevent mermaidjs from hanging
* up the entire thread and causing a DoS.
*/
if (
!PAGES_WITHOUT_RESTRICTIONS.includes(pageName) &&
((source && source.length > MAX_CHAR_LIMIT) ||
renderedChars > MAX_CHAR_LIMIT ||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
shouldLazyLoadMermaidBlock(source))
) {
const html = `
<div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
<div>
<div>
<div class="js-warning-text"></div>
<div class="gl-alert-actions">
<button type="button" class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md gl-button">Display</button>
</div>
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
`;
const $parent = $(el).parent();
if (!$parent.hasClass('lazy-alert-shown')) {
$parent.after(html);
$parent
.siblings()
.find('.js-warning-text')
.text(
__('Warning: Displaying this diagram might cause performance issues on this page.'),
);
$parent.addClass('lazy-alert-shown');
}
return;
}
renderedChars += source.length;
renderedMermaidBlocks += 1;
const requestId = window.requestIdleCallback(() => {
renderMermaidEl(el, source);
});
elsProcessingMap.set(el, requestId);
});
}
const hookLazyRenderMermaidEvent = once(() => {
$(document.body).on('click', '.js-lazy-render-mermaid', function eventHandler() {
const parent = $(this).closest('.js-lazy-render-mermaid-container');
const pre = parent.prev();
const el = pre.find('.js-render-mermaid');
parent.remove();
// sandbox update
const element = el.get(0);
const { source } = fixElementSource(element);
renderMermaidEl(element, source);
});
});
export default function renderMermaid($els) {
if (!$els.length) return;
const visibleMermaids = $els.filter(function filter() {
return $(this).closest('details').length === 0 && $(this).is(':visible');
});
renderMermaids(visibleMermaids);
$els.closest('details').one('toggle', function toggle() {
if (this.open) {
renderMermaids($(this).find('.js-render-mermaid'));
}
});
hookLazyRenderMermaidEvent();
}

View File

@ -0,0 +1,61 @@
import mermaid from 'mermaid';
import { getParameterByName } from '~/lib/utils/url_utility';
const setIframeRenderedSize = (h, w) => {
const { origin } = window.location;
window.parent.postMessage({ h, w }, origin);
};
const drawDiagram = (source) => {
const element = document.getElementById('app');
const insertSvg = (svgCode) => {
element.innerHTML = svgCode;
const height = parseInt(element.firstElementChild.getAttribute('height'), 10);
const width = parseInt(element.firstElementChild.style.maxWidth, 10);
setIframeRenderedSize(height, width);
};
mermaid.mermaidAPI.render('mermaid', source, insertSvg);
};
const darkModeEnabled = () => getParameterByName('darkMode') === 'true';
const initMermaid = () => {
let theme = 'neutral';
if (darkModeEnabled()) {
theme = 'dark';
}
mermaid.initialize({
// mermaid core options
mermaid: {
startOnLoad: false,
},
// mermaidAPI options
theme,
flowchart: {
useMaxWidth: true,
htmlLabels: true,
},
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'htmlLabels'],
securityLevel: 'strict',
});
};
const addListener = () => {
window.addEventListener(
'message',
(event) => {
if (event.origin !== window.location.origin) {
return;
}
drawDiagram(event.data);
},
false,
);
};
addListener();
initMermaid();
export default {};

View File

@ -212,7 +212,9 @@ export default {
</script>
<template>
<div class="js-pipeline-header-container">
<gl-alert v-if="hasError" :variant="failure.variant">{{ failure.text }}</gl-alert>
<gl-alert v-if="hasError" :variant="failure.variant" :dismissible="false">{{
failure.text
}}</gl-alert>
<ci-header
v-if="shouldRenderContent"
:status="pipeline.detailedStatus"

View File

@ -1,5 +1,5 @@
<script>
import { GlIcon, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { __, n__, sprintf } from '~/locale';
import createFlash from '~/flash';
import { convertToGraphQLId } from '~/graphql_shared/utils';
@ -10,6 +10,7 @@ import issueCrmContactsSubscription from './queries/issue_crm_contacts.subscript
export default {
components: {
GlIcon,
GlLink,
GlPopover,
},
directives: {
@ -85,9 +86,6 @@ export default {
);
},
},
i18n: {
help: __('Work in progress- click here to find out more'),
},
};
</script>
@ -97,11 +95,10 @@ export default {
<gl-icon name="users" />
<span> {{ contactCount }} </span>
</div>
<div
v-gl-tooltip.left.viewport="$options.i18n.help"
class="hide-collapsed help-button float-right"
>
<a href="https://gitlab.com/gitlab-org/gitlab/-/issues/2256"><gl-icon name="question-o" /></a>
<div class="hide-collapsed help-button gl-float-right">
<gl-link href="https://docs.gitlab.com/ee/user/crm/" target="_blank"
><gl-icon name="question-o"
/></gl-link>
</div>
<div class="title hide-collapsed gl-mb-2 gl-line-height-20">
{{ contactsLabel }}

View File

@ -247,16 +247,6 @@ $gl-line-height-42: px-to-rem(42px);
max-width: 50%;
}
// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1465
.gl-popover {
.popover-header {
.gl-button.close {
margin-top: -$gl-spacing-scale-3;
margin-right: -$gl-spacing-scale-4;
}
}
}
// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1490
.gl-w-grid-size-28 {
width: $grid-size * 28;

View File

@ -13,7 +13,7 @@ class Oauth::TokenInfoController < Doorkeeper::TokenInfoController
'expires_in_seconds' => token_json[:expires_in]
), status: :ok
else
error = Doorkeeper::OAuth::ErrorResponse.new(name: :invalid_request)
error = Doorkeeper::OAuth::InvalidTokenResponse.new
response.headers.merge!(error.headers)
render json: error.body, status: error.status
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class SandboxController < ApplicationController # rubocop:disable Gitlab/NamespacedClass
skip_before_action :authenticate_user!
feature_category :not_owned
def mermaid
render layout: false
end
end

View File

@ -9,10 +9,17 @@ module Resolvers
delegate :project, to: :agent
argument :status, Types::Clusters::AgentTokenStatusEnum,
required: false,
description: 'Status of the token.'
def resolve(**args)
return ::Clusters::AgentToken.none unless can_read_agent_tokens?
agent.last_used_agent_tokens
tokens = agent.last_used_agent_tokens
tokens = tokens.with_status(args[:status]) if args[:status].present?
tokens
end
private

View File

@ -24,7 +24,7 @@ module ResolvesPipelines
argument :source,
GraphQL::Types::String,
required: false,
description: "Filter pipelines by their source. Will be ignored if `dast_view_scans` feature flag is disabled."
description: "Filter pipelines by their source."
end
class_methods do
@ -38,8 +38,6 @@ module ResolvesPipelines
end
def resolve_pipelines(project, params = {})
params.delete(:source) unless Feature.enabled?(:dast_view_scans, project, default_enabled: :yaml)
Ci::PipelinesFinder.new(project, context[:current_user], params).execute
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Types
module Clusters
class AgentTokenStatusEnum < BaseEnum
graphql_name 'AgentTokenStatus'
description 'Agent token statuses'
::Clusters::AgentToken.statuses.keys.each do |status|
value status.upcase, value: status, description: "#{status.titleize} agent token."
end
end
end
end

View File

@ -45,7 +45,7 @@ module Types
description: 'Name given to the token.'
field :status,
GraphQL::Types::String,
Types::Clusters::AgentTokenStatusEnum,
null: true,
description: 'Current status of the token.'

View File

@ -22,6 +22,7 @@ module Clusters
validates :name, presence: true, length: { maximum: 255 }
scope :order_last_used_at_desc, -> { order(::Gitlab::Database.nulls_last_order('last_used_at', 'DESC')) }
scope :with_status, -> (status) { where(status: status) }
enum status: {
active: 0,

View File

@ -12,9 +12,6 @@ class NamespaceSetting < ApplicationRecord
validate :allow_mfa_for_group
validate :allow_resource_access_token_creation_for_group
before_save :set_prevent_sharing_groups_outside_hierarchy, if: -> { user_cap_enabled? }
after_save :disable_project_sharing!, if: -> { user_cap_enabled? }
before_validation :normalize_default_branch_name
enum jobs_to_be_done: { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5 }, _suffix: true
@ -59,18 +56,6 @@ class NamespaceSetting < ApplicationRecord
errors.add(:resource_access_token_creation_allowed, _('is not allowed since the group is not top-level group.'))
end
end
def set_prevent_sharing_groups_outside_hierarchy
self.prevent_sharing_groups_outside_hierarchy = true
end
def disable_project_sharing!
namespace.update_attribute(:share_with_group_lock, true)
end
def user_cap_enabled?
new_user_signups_cap.present? && namespace.root?
end
end
NamespaceSetting.prepend_mod_with('NamespaceSetting')

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<%= webpack_bundle_tag("sandboxed_mermaid") %>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -1,8 +1,8 @@
---
name: dast_view_scans
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69571
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340388
milestone: '14.3'
name: sandboxed_mermaid
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74414
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349755
milestone: '14.7'
type: development
group: group::dynamic analysis
default_enabled: true
group: group::analyzer frontend
default_enabled: false

View File

@ -8,7 +8,7 @@ product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: active
status: removed
time_frame: 28d
data_source: database
distribution:
@ -20,3 +20,4 @@ tier:
- ultimate
performance_indicator_type: []
milestone: "<13.9"
milestone_removed: '14.7'

View File

@ -7,7 +7,7 @@ product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: active
status: removed
time_frame: all
data_source: database
distribution:
@ -18,3 +18,4 @@ tier:
- premium
- ultimate
milestone: "<13.9"
milestone_removed: '14.7'

View File

@ -108,6 +108,9 @@ Rails.application.routes.draw do
get '/autocomplete/namespace_routes' => 'autocomplete#namespace_routes'
end
# sandbox
get '/sandbox/mermaid' => 'sandbox#mermaid'
get '/whats_new' => 'whats_new#index'
# '/-/health' implemented by BasicHealthCheck middleware

View File

@ -141,6 +141,7 @@ function generateEntries() {
sentry: './sentry/index.js',
performance_bar: './performance_bar/index.js',
jira_connect_app: './jira_connect/subscriptions/index.js',
sandboxed_mermaid: './lib/mermaid.js',
};
return Object.assign(manualEntries, incrementalCompiler.filterEntryPoints(autoEntries));

View File

@ -9027,10 +9027,27 @@ GitLab CI/CD configuration template.
| <a id="clusteragentid"></a>`id` | [`ID!`](#id) | ID of the cluster agent. |
| <a id="clusteragentname"></a>`name` | [`String`](#string) | Name of the cluster agent. |
| <a id="clusteragentproject"></a>`project` | [`Project`](#project) | Project this cluster agent is associated with. |
| <a id="clusteragenttokens"></a>`tokens` | [`ClusterAgentTokenConnection`](#clusteragenttokenconnection) | Tokens associated with the cluster agent. (see [Connections](#connections)) |
| <a id="clusteragentupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp the cluster agent was updated. |
| <a id="clusteragentwebpath"></a>`webPath` | [`String`](#string) | Web path of the cluster agent. |
#### Fields with arguments
##### `ClusterAgent.tokens`
Tokens associated with the cluster agent.
Returns [`ClusterAgentTokenConnection`](#clusteragenttokenconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="clusteragenttokensstatus"></a>`status` | [`AgentTokenStatus`](#agenttokenstatus) | Status of the token. |
### `ClusterAgentActivityEvent`
#### Fields
@ -9056,7 +9073,7 @@ GitLab CI/CD configuration template.
| <a id="clusteragenttokenid"></a>`id` | [`ClustersAgentTokenID!`](#clustersagenttokenid) | Global ID of the token. |
| <a id="clusteragenttokenlastusedat"></a>`lastUsedAt` | [`Time`](#time) | Timestamp the token was last used. |
| <a id="clusteragenttokenname"></a>`name` | [`String`](#string) | Name given to the token. |
| <a id="clusteragenttokenstatus"></a>`status` | [`String`](#string) | Current status of the token. |
| <a id="clusteragenttokenstatus"></a>`status` | [`AgentTokenStatus`](#agenttokenstatus) | Current status of the token. |
### `CodeCoverageActivity`
@ -9141,7 +9158,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="commitpipelinesref"></a>`ref` | [`String`](#string) | Filter pipelines by the ref they are run for. |
| <a id="commitpipelinesscope"></a>`scope` | [`PipelineScopeEnum`](#pipelinescopeenum) | Filter pipelines by scope. |
| <a id="commitpipelinessha"></a>`sha` | [`String`](#string) | Filter pipelines by the sha of the commit they are run for. |
| <a id="commitpipelinessource"></a>`source` | [`String`](#string) | Filter pipelines by their source. Will be ignored if `dast_view_scans` feature flag is disabled. |
| <a id="commitpipelinessource"></a>`source` | [`String`](#string) | Filter pipelines by their source. |
| <a id="commitpipelinesstatus"></a>`status` | [`PipelineStatusEnum`](#pipelinestatusenum) | Filter pipelines by their status. |
### `ComplianceFramework`
@ -11894,7 +11911,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="mergerequestpipelinesref"></a>`ref` | [`String`](#string) | Filter pipelines by the ref they are run for. |
| <a id="mergerequestpipelinesscope"></a>`scope` | [`PipelineScopeEnum`](#pipelinescopeenum) | Filter pipelines by scope. |
| <a id="mergerequestpipelinessha"></a>`sha` | [`String`](#string) | Filter pipelines by the sha of the commit they are run for. |
| <a id="mergerequestpipelinessource"></a>`source` | [`String`](#string) | Filter pipelines by their source. Will be ignored if `dast_view_scans` feature flag is disabled. |
| <a id="mergerequestpipelinessource"></a>`source` | [`String`](#string) | Filter pipelines by their source. |
| <a id="mergerequestpipelinesstatus"></a>`status` | [`PipelineStatusEnum`](#pipelinestatusenum) | Filter pipelines by their status. |
##### `MergeRequest.reference`
@ -12928,7 +12945,7 @@ Represents a file or directory in the project repository that has been locked.
| <a id="pipelineconfigsource"></a>`configSource` | [`PipelineConfigSourceEnum`](#pipelineconfigsourceenum) | Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE). |
| <a id="pipelinecoverage"></a>`coverage` | [`Float`](#float) | Coverage percentage. |
| <a id="pipelinecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of the pipeline's creation. |
| <a id="pipelinedastprofile"></a>`dastProfile` | [`DastProfile`](#dastprofile) | DAST profile associated with the pipeline. Returns `null`if `dast_view_scans` feature flag is disabled. |
| <a id="pipelinedastprofile"></a>`dastProfile` | [`DastProfile`](#dastprofile) | DAST profile associated with the pipeline. |
| <a id="pipelinedetailedstatus"></a>`detailedStatus` | [`DetailedStatus!`](#detailedstatus) | Detailed status of the pipeline. |
| <a id="pipelinedownstream"></a>`downstream` | [`PipelineConnection`](#pipelineconnection) | Pipelines this pipeline will trigger. (see [Connections](#connections)) |
| <a id="pipelineduration"></a>`duration` | [`Int`](#int) | Duration of the pipeline in seconds. |
@ -13360,7 +13377,7 @@ Returns [`DastProfile`](#dastprofile).
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectdastprofilehasdastprofileschedule"></a>`hasDastProfileSchedule` | [`Boolean`](#boolean) | Filter DAST Profiles by whether or not they have a schedule. Will be ignored if `dast_view_scans` feature flag is disabled. |
| <a id="projectdastprofilehasdastprofileschedule"></a>`hasDastProfileSchedule` | [`Boolean`](#boolean) | Filter DAST Profiles by whether or not they have a schedule. |
| <a id="projectdastprofileid"></a>`id` | [`DastProfileID!`](#dastprofileid) | ID of the DAST Profile. |
##### `Project.dastProfiles`
@ -13377,7 +13394,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectdastprofileshasdastprofileschedule"></a>`hasDastProfileSchedule` | [`Boolean`](#boolean) | Filter DAST Profiles by whether or not they have a schedule. Will be ignored if `dast_view_scans` feature flag is disabled. |
| <a id="projectdastprofileshasdastprofileschedule"></a>`hasDastProfileSchedule` | [`Boolean`](#boolean) | Filter DAST Profiles by whether or not they have a schedule. |
##### `Project.dastSiteProfile`
@ -13837,7 +13854,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="projectpipelinesref"></a>`ref` | [`String`](#string) | Filter pipelines by the ref they are run for. |
| <a id="projectpipelinesscope"></a>`scope` | [`PipelineScopeEnum`](#pipelinescopeenum) | Filter pipelines by scope. |
| <a id="projectpipelinessha"></a>`sha` | [`String`](#string) | Filter pipelines by the sha of the commit they are run for. |
| <a id="projectpipelinessource"></a>`source` | [`String`](#string) | Filter pipelines by their source. Will be ignored if `dast_view_scans` feature flag is disabled. |
| <a id="projectpipelinessource"></a>`source` | [`String`](#string) | Filter pipelines by their source. |
| <a id="projectpipelinesstatus"></a>`status` | [`PipelineStatusEnum`](#pipelinestatusenum) | Filter pipelines by their status. |
##### `Project.projectMembers`
@ -16150,6 +16167,15 @@ Access level to a resource.
| <a id="accesslevelenumowner"></a>`OWNER` | Owner access. |
| <a id="accesslevelenumreporter"></a>`REPORTER` | Reporter access. |
### `AgentTokenStatus`
Agent token statuses.
| Value | Description |
| ----- | ----------- |
| <a id="agenttokenstatusactive"></a>`ACTIVE` | Active agent token. |
| <a id="agenttokenstatusrevoked"></a>`REVOKED` | Revoked agent token. |
### `AlertManagementAlertSort`
Values for sorting alerts.

View File

@ -61,12 +61,12 @@ including:
- Terraform states ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331806) in GitLab 14.7)
- Container Registry images
- GitLab Pages content
- Packages ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/332006) in GitLab 14.7)
- Snippets
- [Group wikis](../user/project/wiki/group.md)
Backups do not include:
- [Package registry files](../administration/packages/index.md)
- [Mattermost data](https://docs.mattermost.com/administration/config-settings.html#file-storage)
WARNING:
@ -276,6 +276,7 @@ You can exclude specific directories from the backup by adding the environment v
- `registry` (Container Registry images)
- `pages` (Pages content)
- `repositories` (Git repositories data)
- `packages` (Packages)
All wikis are backed up as part of the `repositories` group. Non-existent wikis are skipped during a backup.

View File

@ -102,7 +102,7 @@ a license, upload the license in the **Admin Area** in the web user interface.
## What happens when your license expires
Fifteen days before the license expires, a message with the upcoming expiration
One month before the license expires, a message with the upcoming expiration
date displays to GitLab administrators.
When your license expires, GitLab locks features, like Git pushes

View File

@ -2,7 +2,7 @@
module Backup
class Manager
ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs terraform_state registry].freeze
ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs terraform_state registry packages].freeze
FOLDERS_TO_BACKUP = %w[repositories db].freeze
FILE_NAME_SUFFIX = '_gitlab_backup.tar'

13
lib/backup/packages.rb Normal file
View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Backup
class Packages < Backup::Files
attr_reader :progress
def initialize(progress)
@progress = progress
super('packages', Settings.packages.storage_path, excludes: ['tmp'])
end
end
end

View File

@ -29,8 +29,16 @@ secret_detection:
script:
- if [ -n "$CI_COMMIT_TAG" ]; then echo "Skipping Secret Detection for tags. No code changes have occurred."; exit 0; fi
- if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then echo "Running Secret Detection on default branch."; /analyzer run; exit 0; fi
- git fetch origin $CI_DEFAULT_BRANCH $CI_COMMIT_REF_NAME
- git log --left-right --cherry-pick --pretty=format:"%H" refs/remotes/origin/$CI_DEFAULT_BRANCH...refs/remotes/origin/$CI_COMMIT_REF_NAME > "$CI_COMMIT_SHA"_commit_list.txt
- export SECRET_DETECTION_COMMITS_FILE="$CI_COMMIT_SHA"_commit_list.txt
- |
git fetch origin $CI_DEFAULT_BRANCH $CI_COMMIT_REF_NAME
git log --left-right --cherry-pick --pretty=format:"%H" refs/remotes/origin/${CI_DEFAULT_BRANCH}..refs/remotes/origin/${CI_COMMIT_REF_NAME} >${CI_COMMIT_SHA}_commit_list.txt
if [[ $(wc -l <${CI_COMMIT_SHA}_commit_list.txt) -eq "0" ]]; then
# if git log produces 0 or 1 commits we should scan $CI_COMMIT_SHA only
export SECRET_DETECTION_COMMITS=$CI_COMMIT_SHA
else
# +1 because busybox wc only counts \n and there is no trailing \n
echo "scanning $(($(wc -l <${CI_COMMIT_SHA}_commit_list.txt) + 1)) commits"
export SECRET_DETECTION_COMMITS_FILE=${CI_COMMIT_SHA}_commit_list.txt
fi
- /analyzer run
- rm "$CI_COMMIT_SHA"_commit_list.txt

View File

@ -147,7 +147,7 @@ module Gitlab
# Using 'self' in the CSP introduces several CSP bypass opportunities
# for this reason we list the URLs where GitLab frames itself instead
def self.allow_framed_gitlab_paths(directives)
['/admin/', '/assets/', '/-/speedscope/index.html'].map do |path|
['/admin/', '/assets/', '/-/speedscope/index.html', '/-/sandbox/mermaid'].map do |path|
append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, path))
end
end

View File

@ -57,6 +57,7 @@ module Gitlab
push_frontend_feature_flag(:improved_emoji_picker, default_enabled: :yaml)
push_frontend_feature_flag(:new_header_search, default_enabled: :yaml)
push_frontend_feature_flag(:bootstrap_confirmation_modals, default_enabled: :yaml)
push_frontend_feature_flag(:sandboxed_mermaid, default_enabled: :yaml)
end
# Exposes the state of a feature flag to the frontend code.

View File

@ -522,11 +522,7 @@ module Gitlab
projects_with_disable_overriding_approvers_per_merge_request: count(::Project.where(time_period.merge(disable_overriding_approvers_per_merge_request: true))),
projects_without_disable_overriding_approvers_per_merge_request: count(::Project.where(time_period.merge(disable_overriding_approvers_per_merge_request: [false, nil]))),
remote_mirrors: distinct_count(::Project.with_remote_mirrors.where(time_period), :creator_id),
snippets: distinct_count(::Snippet.where(time_period), :author_id),
suggestions: distinct_count(::Note.with_suggestions.where(time_period),
:author_id,
start: minimum_id(::User),
finish: maximum_id(::User))
snippets: distinct_count(::Snippet.where(time_period), :author_id)
}.tap do |h|
if time_period.present?
h[:merge_requests_users] = merge_requests_users(time_period)

View File

@ -9,15 +9,9 @@ namespace :gitlab do
task create: :gitlab_environment do
warn_user_is_not_gitlab
Rake::Task['gitlab:backup:db:create'].invoke
Rake::Task['gitlab:backup:repo:create'].invoke
Rake::Task['gitlab:backup:uploads:create'].invoke
Rake::Task['gitlab:backup:builds:create'].invoke
Rake::Task['gitlab:backup:artifacts:create'].invoke
Rake::Task['gitlab:backup:pages:create'].invoke
Rake::Task['gitlab:backup:lfs:create'].invoke
Rake::Task['gitlab:backup:terraform_state:create'].invoke
Rake::Task['gitlab:backup:registry:create'].invoke
%w(db repo uploads builds artifacts pages lfs terraform_state registry packages).each do |type|
Rake::Task["gitlab:backup:#{type}:create"].invoke
end
backup = Backup::Manager.new(progress)
backup.write_info
@ -86,6 +80,7 @@ namespace :gitlab do
Rake::Task['gitlab:backup:lfs:restore'].invoke unless backup.skipped?('lfs')
Rake::Task['gitlab:backup:terraform_state:restore'].invoke unless backup.skipped?('terraform_state')
Rake::Task['gitlab:backup:registry:restore'].invoke unless backup.skipped?('registry')
Rake::Task['gitlab:backup:packages:restore'].invoke unless backup.skipped?('packages')
Rake::Task['gitlab:shell:setup'].invoke
Rake::Task['cache:clear'].invoke
@ -331,6 +326,25 @@ namespace :gitlab do
end
end
namespace :packages do
task create: :gitlab_environment do
puts_time "Dumping packages ... ".color(:blue)
if ENV['SKIP'] && ENV['SKIP'].include?('packages')
puts_time "[SKIPPED]".color(:cyan)
else
Backup::Packages.new(progress).dump
puts_time "done".color(:green)
end
end
task restore: :gitlab_environment do
puts_time "Restoring packages ...".color(:blue)
Backup::Packages.new(progress).restore
puts_time "done".color(:green)
end
end
def puts_time(msg)
progress.puts "#{Time.now} -- #{msg}"
Gitlab::BackupLogger.info(message: "#{Rainbow.uncolor(msg)}")

View File

@ -40360,9 +40360,6 @@ msgstr ""
msgid "Work in progress Limit"
msgstr ""
msgid "Work in progress- click here to find out more"
msgstr ""
msgid "WorkItem|Work Items"
msgstr ""
@ -41541,6 +41538,9 @@ msgstr ""
msgid "cannot be changed if shared runners are enabled"
msgstr ""
msgid "cannot be enabled"
msgstr ""
msgid "cannot be enabled because parent group does not allow it"
msgstr ""

View File

@ -55,9 +55,9 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "2.0.0",
"@gitlab/svgs": "2.2.0",
"@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "32.51.3",
"@gitlab/ui": "32.54.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.4-1",
"@rails/ujs": "6.1.4-1",

0
shared/packages/.gitkeep Normal file
View File

View File

@ -5,11 +5,11 @@ require 'spec_helper'
RSpec.describe Oauth::TokenInfoController do
describe '#show' do
context 'when the user is not authenticated' do
it 'responds with a 400' do
it 'responds with a 401' do
get :show
expect(response).to have_gitlab_http_status(:bad_request)
expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_request')
expect(response).to have_gitlab_http_status(:unauthorized)
expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_token')
end
end
@ -36,11 +36,11 @@ RSpec.describe Oauth::TokenInfoController do
end
context 'when the doorkeeper_token is not recognised' do
it 'responds with a 400' do
it 'responds with a 401' do
get :show, params: { access_token: 'unknown_token' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_request')
expect(response).to have_gitlab_http_status(:unauthorized)
expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_token')
end
end
@ -49,22 +49,22 @@ RSpec.describe Oauth::TokenInfoController do
create(:oauth_access_token, created_at: 2.days.ago, expires_in: 10.minutes)
end
it 'responds with a 400' do
it 'responds with a 401' do
get :show, params: { access_token: access_token.token }
expect(response).to have_gitlab_http_status(:bad_request)
expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_request')
expect(response).to have_gitlab_http_status(:unauthorized)
expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_token')
end
end
context 'when the token is revoked' do
let(:access_token) { create(:oauth_access_token, revoked_at: 2.days.ago) }
it 'responds with a 400' do
it 'responds with a 401' do
get :show, params: { access_token: access_token.token }
expect(response).to have_gitlab_http_status(:bad_request)
expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_request')
expect(response).to have_gitlab_http_status(:unauthorized)
expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_token')
end
end
end

View File

@ -7,5 +7,9 @@ FactoryBot.define do
token_encrypted { Gitlab::CryptoHelper.aes256_gcm_encrypt(SecureRandom.hex(50)) }
sequence(:name) { |n| "agent-token-#{n}" }
trait :revoked do
status { :revoked }
end
end
end

View File

@ -11,6 +11,7 @@ RSpec.describe "User comments on issue", :js do
before do
stub_feature_flags(tribute_autocomplete: false)
stub_feature_flags(sandboxed_mermaid: false)
project.add_guest(user)
sign_in(user)

View File

@ -5,6 +5,10 @@ require 'spec_helper'
RSpec.describe 'Mermaid rendering', :js do
let_it_be(:project) { create(:project, :public) }
before do
stub_feature_flags(sandboxed_mermaid: false)
end
it 'renders Mermaid diagrams correctly' do
description = <<~MERMAID
```mermaid

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Sandboxed Mermaid rendering', :js do
let_it_be(:project) { create(:project, :public) }
before do
stub_feature_flags(sandboxed_mermaid: true)
end
it 'includes mermaid frame correctly' do
description = <<~MERMAID
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
MERMAID
issue = create(:issue, project: project, description: description)
visit project_issue_path(project, issue)
wait_for_requests
expected = %(<iframe src="/-/sandbox/mermaid" sandbox="allow-scripts" frameborder="0" scrolling="no")
expect(page.html).to include(expected)
end
end

View File

@ -7,6 +7,7 @@ RSpec.describe Resolvers::Clusters::AgentTokensResolver do
it { expect(described_class.type).to eq(Types::Clusters::AgentTokenType) }
it { expect(described_class.null).to be_truthy }
it { expect(described_class.arguments.keys).to contain_exactly('status') }
describe '#resolve' do
let(:agent) { create(:cluster_agent) }
@ -23,6 +24,14 @@ RSpec.describe Resolvers::Clusters::AgentTokensResolver do
expect(subject).to eq([matching_token2, matching_token1])
end
context 'token status is specified' do
let!(:revoked_token) { create(:cluster_agent_token, :revoked, agent: agent) }
subject { resolve(described_class, obj: agent, ctx: ctx, args: { status: 'revoked' }) }
it { is_expected.to contain_exactly(revoked_token) }
end
context 'user does not have permission' do
let(:user) { create(:user, developer_projects: [agent.project]) }

View File

@ -62,24 +62,12 @@ RSpec.describe ResolvesPipelines do
context 'filtering by source' do
let_it_be(:source_pipeline) { create(:ci_pipeline, project: project, source: 'web') }
context 'when `dast_view_scans` feature flag is disabled' do
before do
stub_feature_flags(dast_view_scans: false)
end
it 'does not filter by source' do
expect(resolve_pipelines(source: 'web')).to contain_exactly(*all_pipelines, source_pipeline)
end
it 'does filter by source' do
expect(resolve_pipelines(source: 'web')).to contain_exactly(source_pipeline)
end
context 'when `dast_view_scans` feature flag is enabled' do
it 'does filter by source' do
expect(resolve_pipelines(source: 'web')).to contain_exactly(source_pipeline)
end
it 'returns all the pipelines' do
expect(resolve_pipelines).to contain_exactly(*all_pipelines, source_pipeline)
end
it 'returns all the pipelines' do
expect(resolve_pipelines).to contain_exactly(*all_pipelines, source_pipeline)
end
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Clusters::AgentTokenStatusEnum do
it { expect(described_class.graphql_name).to eq('AgentTokenStatus') }
it { expect(described_class.values.keys).to match_array(Clusters::AgentToken.statuses.keys.map(&:upcase)) }
end

View File

@ -15,7 +15,7 @@ RSpec.describe Backup::Manager do
end
describe '#pack' do
let(:expected_backup_contents) { %w(repositories db uploads.tar.gz builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz backup_information.yml) }
let(:expected_backup_contents) { %w(repositories db uploads.tar.gz builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz packages.tar.gz backup_information.yml) }
let(:tar_file) { '1546300800_2019_01_01_12.3_gitlab_backup.tar' }
let(:tar_system_options) { { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] } }
let(:tar_cmdline) { ['tar', '-cf', '-', *expected_backup_contents, tar_system_options] }
@ -57,7 +57,7 @@ RSpec.describe Backup::Manager do
end
context 'when skipped is set in backup_information.yml' do
let(:expected_backup_contents) { %w{db uploads.tar.gz builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz backup_information.yml} }
let(:expected_backup_contents) { %w{db uploads.tar.gz builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz packages.tar.gz backup_information.yml} }
let(:backup_information) do
{
backup_created_at: Time.zone.parse('2019-01-01'),
@ -74,7 +74,7 @@ RSpec.describe Backup::Manager do
end
context 'when a directory does not exist' do
let(:expected_backup_contents) { %w{db uploads.tar.gz builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz backup_information.yml} }
let(:expected_backup_contents) { %w{db uploads.tar.gz builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz packages.tar.gz backup_information.yml} }
before do
expect(Dir).to receive(:exist?).with(File.join(Gitlab.config.backup.path, 'repositories')).and_return(false)

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples 'backup object' do |setting|
let(:progress) { StringIO.new }
let(:backup_path) { "/var/#{setting}" }
subject(:backup) { described_class.new(progress) }
describe '#dump' do
before do
allow(File).to receive(:realpath).and_call_original
allow(File).to receive(:realpath).with(backup_path).and_return(backup_path)
allow(File).to receive(:realpath).with("#{backup_path}/..").and_return('/var')
allow(Settings.send(setting)).to receive(:storage_path).and_return(backup_path)
end
it 'uses the correct storage dir in tar command and excludes tmp', :aggregate_failures do
expect(backup.app_files_dir).to eq(backup_path)
expect(backup).to receive(:tar).and_return('blabla-tar')
expect(backup).to receive(:run_pipeline!).with([%W(blabla-tar --exclude=lost+found --exclude=./tmp -C #{backup_path} -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
backup.dump
end
end
end
RSpec.describe Backup::Packages do
it_behaves_like 'backup object', 'packages'
end
RSpec.describe Backup::TerraformState do
it_behaves_like 'backup object', 'terraform_state'
end

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Backup::TerraformState do
let(:progress) { StringIO.new }
subject(:backup) { described_class.new(progress) }
describe '#dump' do
before do
allow(File).to receive(:realpath).and_call_original
allow(File).to receive(:realpath).with('/var/terraform_state').and_return('/var/terraform_state')
allow(File).to receive(:realpath).with('/var/terraform_state/..').and_return('/var')
allow(Settings.terraform_state).to receive(:storage_path).and_return('/var/terraform_state')
end
it 'uses the correct storage dir in tar command and excludes tmp', :aggregate_failures do
expect(backup.app_files_dir).to eq('/var/terraform_state')
expect(backup).to receive(:tar).and_return('blabla-tar')
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/terraform_state -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
backup.dump
end
end
end

View File

@ -85,7 +85,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
expect(directives['style_src']).to eq("'self' 'unsafe-inline' https://cdn.example.com")
expect(directives['font_src']).to eq("'self' https://cdn.example.com")
expect(directives['worker_src']).to eq('http://localhost/assets/ blob: data: https://cdn.example.com')
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " https://cdn.example.com http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html")
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " https://cdn.example.com http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html http://localhost/-/sandbox/mermaid")
end
end
@ -113,7 +113,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'does not add CUSTOMER_PORTAL_URL to CSP' do
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html")
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html http://localhost/-/sandbox/mermaid")
end
end
@ -123,7 +123,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'adds CUSTOMER_PORTAL_URL to CSP' do
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " http://localhost/rails/letter_opener/ https://customers.example.com http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html")
expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " http://localhost/rails/letter_opener/ https://customers.example.com http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html http://localhost/-/sandbox/mermaid")
end
end
end

View File

@ -161,7 +161,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
another_project = create(:project, :repository, creator: another_user)
create(:remote_mirror, project: another_project, enabled: false)
create(:snippet, author: user)
create(:suggestion, note: create(:note, project: project))
end
expect(described_class.usage_activity_by_stage_create({})).to include(
@ -171,8 +170,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
projects_with_disable_overriding_approvers_per_merge_request: 2,
projects_without_disable_overriding_approvers_per_merge_request: 6,
remote_mirrors: 2,
snippets: 2,
suggestions: 2
snippets: 2
)
expect(described_class.usage_activity_by_stage_create(described_class.monthly_time_range_db_params)).to include(
deploy_keys: 1,
@ -181,8 +179,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
projects_with_disable_overriding_approvers_per_merge_request: 1,
projects_without_disable_overriding_approvers_per_merge_request: 3,
remote_mirrors: 1,
snippets: 1,
suggestions: 1
snippets: 1
)
end
end

View File

@ -48,7 +48,7 @@ RSpec.describe Ci::Runner do
let(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'disallows assigning group if already assigned to a group' do
runner.runner_namespaces << build(:ci_runner_namespace)
runner.runner_namespaces << create(:ci_runner_namespace)
expect(runner).not_to be_valid
expect(runner.errors.full_messages).to include('Runner needs to be assigned to exactly one group')

View File

@ -13,15 +13,25 @@ RSpec.describe Clusters::AgentToken do
describe 'scopes' do
describe '.order_last_used_at_desc' do
let_it_be(:token_1) { create(:cluster_agent_token, last_used_at: 7.days.ago) }
let_it_be(:token_2) { create(:cluster_agent_token, last_used_at: nil) }
let_it_be(:token_3) { create(:cluster_agent_token, last_used_at: 2.days.ago) }
let_it_be(:agent) { create(:cluster_agent) }
let_it_be(:token_1) { create(:cluster_agent_token, agent: agent, last_used_at: 7.days.ago) }
let_it_be(:token_2) { create(:cluster_agent_token, agent: agent, last_used_at: nil) }
let_it_be(:token_3) { create(:cluster_agent_token, agent: agent, last_used_at: 2.days.ago) }
it 'sorts by last_used_at descending, with null values at last' do
expect(described_class.order_last_used_at_desc)
.to eq([token_3, token_1, token_2])
end
end
describe '.with_status' do
let!(:active_token) { create(:cluster_agent_token) }
let!(:revoked_token) { create(:cluster_agent_token, :revoked) }
subject { described_class.with_status(:active) }
it { is_expected.to contain_exactly(active_token) }
end
end
describe '#token' do

View File

@ -126,57 +126,4 @@ RSpec.describe NamespaceSetting, type: :model do
end
end
end
describe 'hooks related to group user cap update' do
let(:settings) { create(:namespace_settings, new_user_signups_cap: user_cap) }
let(:group) { create(:group, namespace_settings: settings) }
before do
allow(group).to receive(:root?).and_return(true)
end
context 'when updating a group with a user cap' do
let(:user_cap) { nil }
it 'also sets share_with_group_lock and prevent_sharing_groups_outside_hierarchy to true' do
expect(group.new_user_signups_cap).to be_nil
expect(group.share_with_group_lock).to be_falsey
expect(settings.prevent_sharing_groups_outside_hierarchy).to be_falsey
settings.update!(new_user_signups_cap: 10)
group.reload
expect(group.new_user_signups_cap).to eq(10)
expect(group.share_with_group_lock).to be_truthy
expect(settings.reload.prevent_sharing_groups_outside_hierarchy).to be_truthy
end
it 'has share_with_group_lock and prevent_sharing_groups_outside_hierarchy returning true for descendent groups' do
descendent = create(:group, parent: group)
desc_settings = descendent.namespace_settings
expect(descendent.share_with_group_lock).to be_falsey
expect(desc_settings.prevent_sharing_groups_outside_hierarchy).to be_falsey
settings.update!(new_user_signups_cap: 10)
expect(descendent.reload.share_with_group_lock).to be_truthy
expect(desc_settings.reload.prevent_sharing_groups_outside_hierarchy).to be_truthy
end
end
context 'when removing a user cap from namespace settings' do
let(:user_cap) { 10 }
it 'leaves share_with_group_lock and prevent_sharing_groups_outside_hierarchy set to true to the related group' do
expect(group.share_with_group_lock).to be_truthy
expect(settings.prevent_sharing_groups_outside_hierarchy).to be_truthy
settings.update!(new_user_signups_cap: nil)
expect(group.reload.share_with_group_lock).to be_truthy
expect(settings.reload.prevent_sharing_groups_outside_hierarchy).to be_truthy
end
end
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe SandboxController do
describe 'GET #mermaid' do
it 'renders page without template' do
get sandbox_mermaid_path
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(layout: nil)
end
end
end

View File

@ -364,6 +364,12 @@ RSpec.describe AutocompleteController, 'routing' do
end
end
RSpec.describe SandboxController, 'routing' do
it 'to #mermaid' do
expect(get("/-/sandbox/mermaid")).to route_to('sandbox#mermaid')
end
end
RSpec.describe Snippets::BlobsController, "routing" do
it "to #raw" do
expect(get('/-/snippets/1/raw/master/lib/version.rb'))

View File

@ -17,7 +17,6 @@
- "./spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb"
- "./spec/models/ci/build_trace_chunk_spec.rb"
- "./spec/models/ci/job_artifact_spec.rb"
- "./spec/models/ci/runner_spec.rb"
- "./spec/models/clusters/applications/runner_spec.rb"
- "./spec/models/design_management/version_spec.rb"
- "./spec/models/hooks/system_hook_spec.rb"

View File

@ -150,6 +150,7 @@ module TestEnv
FileUtils.mkdir_p(artifacts_path)
FileUtils.mkdir_p(lfs_path)
FileUtils.mkdir_p(terraform_state_path)
FileUtils.mkdir_p(packages_path)
end
def setup_gitlab_shell
@ -424,6 +425,10 @@ module TestEnv
Gitlab.config.terraform_state.storage_path
end
def packages_path
Gitlab.config.packages.storage_path
end
# When no cached assets exist, manually hit the root path to create them
#
# Otherwise they'd be created by the first test, often timing out and

View File

@ -4,7 +4,7 @@ require 'rake_helper'
RSpec.describe 'gitlab:app namespace rake task', :delete do
let(:enable_registry) { true }
let(:backup_types) { %w{db repo uploads builds artifacts pages lfs terraform_state registry} }
let(:backup_types) { %w{db repo uploads builds artifacts pages lfs terraform_state registry packages} }
def tars_glob
Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar'))
@ -15,7 +15,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
end
def backup_files
%w(backup_information.yml artifacts.tar.gz builds.tar.gz lfs.tar.gz terraform_state.tar.gz pages.tar.gz)
%w(backup_information.yml artifacts.tar.gz builds.tar.gz lfs.tar.gz terraform_state.tar.gz pages.tar.gz packages.tar.gz)
end
def backup_directories
@ -137,6 +137,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:terraform_state:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:packages:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke)
end
@ -213,7 +214,8 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping lfs objects ... ")
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping terraform states ... ")
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping container registry images ... ")
expect(Gitlab::BackupLogger).to receive(:info).with(message: "done").exactly(8).times
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping packages ... ")
expect(Gitlab::BackupLogger).to receive(:info).with(message: "done").exactly(9).times
backup_types.each do |task|
run_rake_task("gitlab:backup:#{task}:create")
@ -279,9 +281,11 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout_from_any_process
tar_contents, exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz registry.tar.gz}
%W{tar -tvf #{backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz registry.tar.gz packages.tar.gz}
)
puts "CONTENT: #{tar_contents}"
expect(exit_status).to eq(0)
expect(tar_contents).to match('db')
expect(tar_contents).to match('uploads.tar.gz')
@ -292,6 +296,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect(tar_contents).to match('lfs.tar.gz')
expect(tar_contents).to match('terraform_state.tar.gz')
expect(tar_contents).to match('registry.tar.gz')
expect(tar_contents).to match('packages.tar.gz')
expect(tar_contents).not_to match(%r{^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)/$})
end
@ -299,7 +304,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout_from_any_process
temp_dirs = Dir.glob(
File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,pages,lfs,terraform_state,registry}')
File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,pages,lfs,terraform_state,registry,packages}')
)
expect(temp_dirs).to be_empty
@ -461,7 +466,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout_from_any_process
tar_contents, _exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz registry.tar.gz}
%W{tar -tvf #{backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz terraform_state.tar.gz registry.tar.gz packages.tar.gz}
)
expect(tar_contents).to match('db/')
@ -472,6 +477,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect(tar_contents).to match('terraform_state.tar.gz')
expect(tar_contents).to match('pages.tar.gz')
expect(tar_contents).to match('registry.tar.gz')
expect(tar_contents).to match('packages.tar.gz')
expect(tar_contents).not_to match('repositories/')
expect(tar_contents).to match('repositories: Not found in archive')
end
@ -492,6 +498,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:terraform_state:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:packages:restore']).to receive :invoke
expect(Rake::Task['gitlab:shell:setup']).to receive :invoke
expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout_from_any_process
end
@ -519,6 +526,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
'terraform_state.tar.gz',
'pages.tar.gz',
'registry.tar.gz',
'packages.tar.gz',
'repositories'
)
end

View File

@ -914,20 +914,20 @@
stylelint-declaration-strict-value "1.7.7"
stylelint-scss "3.18.0"
"@gitlab/svgs@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.0.0.tgz#06af5e91c36498ccf7e3e30e432eefcb3b1276c2"
integrity sha512-kBq7RZ0N+h41b4JbPOmwzx1X++fD+tz8HhaBmHTkOmRFY/7Ygvt2A8GodUUtpFK/NxRxy8O+knZvLNdfMLAIoQ==
"@gitlab/svgs@2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.2.0.tgz#95cf58d6ae634d535145159f08f5cff6241d4013"
integrity sha512-mCwR3KfNPsxRoojtTjMIZwdd4FFlBh5DlR9AeodP+7+k8rILdWGYxTZbJMPNXoPbZx16R94nG8c5bR7toD4QBw==
"@gitlab/tributejs@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
"@gitlab/ui@32.51.3":
version "32.51.3"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.51.3.tgz#1ba4802a16ecf5465774f4083e5ec1ed6adc4579"
integrity sha512-PWC0FtpsW9SM3O935XPU2el/JtLtvHQCL9qblKCeR98eJNYmIpjrw45ow+01jsqpjufcUI49n4id/sYHq8b6og==
"@gitlab/ui@32.54.0":
version "32.54.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.54.0.tgz#86002a6796bdd68fd54cd01e11292d2b29f361f7"
integrity sha512-a1QUbQ3KQtmmenOaSGMp8clwi0o8H/u2c8Q1s9EbWHuXN3UwVnhxP+0ncNUpdTlHyEPn4UbYpAZOowtnikXn8Q==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "2.20.1"