Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-26 15:12:36 +00:00
parent 613a8bc141
commit 2004f56282
67 changed files with 1231 additions and 373 deletions

View File

@ -15,11 +15,6 @@ Rails/SaveBang:
- spec/lib/gitlab/database/custom_structure_spec.rb
- spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
- spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
- spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
- spec/lib/gitlab/import_export/fork_spec.rb
- spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
- spec/lib/gitlab/import_export/group/relation_factory_spec.rb
- spec/lib/gitlab/import_export/group/tree_saver_spec.rb
- spec/lib/gitlab/import_export/importer_spec.rb
- spec/lib/gitlab/import_export/lfs_restorer_spec.rb
- spec/lib/gitlab/import_export/lfs_saver_spec.rb
@ -27,10 +22,3 @@ Rails/SaveBang:
- spec/lib/gitlab/import_export/project/relation_factory_spec.rb
- spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
- spec/lib/gitlab/import_export/project/tree_saver_spec.rb
- spec/lib/gitlab/import_export/repo_restorer_spec.rb
- spec/lib/gitlab/import_export/saver_spec.rb
- spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
- spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
- spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
- spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
- spec/lib/gitlab/import_export/uploads_manager_spec.rb

View File

@ -32,7 +32,6 @@ Style/OpenStructUse:
- spec/lib/gitlab/quick_actions/command_definition_spec.rb
- spec/models/design_management/design_action_spec.rb
- spec/models/design_management/design_at_version_spec.rb
- spec/services/packages/nuget/metadata_extraction_service_spec.rb
- spec/services/projects/import_service_spec.rb
- spec/services/system_note_service_spec.rb
- spec/support/helpers/import_spec_helper.rb

View File

@ -0,0 +1,51 @@
<script>
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { redirectTo } from '~/lib/utils/url_utility';
import MetricPopover from './metric_popover.vue';
export default {
name: 'MetricTile',
components: {
GlSingleStat,
MetricPopover,
},
props: {
metric: {
type: Object,
required: true,
},
},
computed: {
decimalPlaces() {
const parsedFloat = parseFloat(this.metric.value);
return Number.isNaN(parsedFloat) || Number.isInteger(parsedFloat) ? 0 : 1;
},
hasLinks() {
return this.metric.links?.length && this.metric.links[0].url;
},
},
methods: {
clickHandler({ links }) {
if (this.hasLinks) {
redirectTo(links[0].url);
}
},
},
};
</script>
<template>
<div v-bind="$attrs">
<gl-single-stat
:id="metric.identifier"
:value="`${metric.value}`"
:title="metric.label"
:unit="metric.unit || ''"
:should-animate="true"
:animation-decimal-places="decimalPlaces"
:class="{ 'gl-hover-cursor-pointer': hasLinks }"
tabindex="0"
@click="clickHandler(metric)"
/>
<metric-popover :metric="metric" :target="metric.identifier" />
</div>
</template>

View File

@ -1,13 +1,12 @@
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { flatten } from 'lodash';
import createFlash from '~/flash';
import { sprintf, s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import { METRICS_POPOVER_CONTENT } from '../constants';
import { removeFlash, prepareTimeMetricsData } from '../utils';
import MetricPopover from './metric_popover.vue';
import MetricTile from './metric_tile.vue';
const requestData = ({ request, endpoint, path, params, name }) => {
return request({ endpoint, params, requestPath: path })
@ -33,9 +32,8 @@ const fetchMetricsData = (reqs = [], path, params) => {
export default {
name: 'ValueStreamMetrics',
components: {
GlSingleStat,
GlSkeletonLoading,
MetricPopover,
MetricTile,
},
props: {
requestPath: {
@ -94,26 +92,14 @@ export default {
};
</script>
<template>
<div class="gl-display-flex gl-flex-wrap" data-testid="vsa-time-metrics">
<div class="gl-display-flex gl-flex-wrap" data-testid="vsa-metrics">
<gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3 gl-pr-9 gl-my-6" />
<div
<metric-tile
v-for="metric in metrics"
v-show="!isLoading"
:key="metric.identifier"
:metric="metric"
class="gl-my-6 gl-pr-9"
>
<gl-single-stat
:id="metric.identifier"
:value="`${metric.value}`"
:title="metric.label"
:unit="metric.unit || ''"
:should-animate="true"
:animation-decimal-places="getDecimalPlaces(metric.value)"
:class="{ 'gl-hover-cursor-pointer': hasLinks(metric.links) }"
tabindex="0"
@click="clickHandler(metric)"
/>
<metric-popover :metric="metric" :target="metric.identifier" />
</div>
/>
</div>
</template>

View File

@ -26,7 +26,7 @@ export default {
v-if="user"
:link-href="user.path"
:img-src="user.avatar_url"
:img-size="26"
:img-size="32"
:tooltip-text="user.name"
class="gl-ml-3 js-pipeline-url-user"
/>

View File

@ -1,15 +1,19 @@
<script>
import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
import { GlIcon, GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { SCHEDULE_ORIGIN } from '../../constants';
export default {
components: {
GlIcon,
GlLink,
GlPopover,
GlSprintf,
GlBadge,
TooltipOnTruncate,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -33,11 +37,12 @@ export default {
type: String,
required: true,
},
viewType: {
type: String,
required: true,
},
},
computed: {
user() {
return this.pipeline.user;
},
isScheduled() {
return this.pipeline.source === SCHEDULE_ORIGIN;
},
@ -53,12 +58,144 @@ export default {
autoDevopsHelpPath() {
return helpPagePath('topics/autodevops/index.md');
},
mergeRequestRef() {
return this.pipeline?.merge_request;
},
commitRef() {
return this.pipeline?.ref;
},
commitTag() {
return this.commitRef?.tag;
},
commitUrl() {
return this.pipeline?.commit?.commit_path;
},
commitShortSha() {
return this.pipeline?.commit?.short_id;
},
refUrl() {
return this.commitRef?.ref_url || this.commitRef?.path;
},
tooltipTitle() {
return this.mergeRequestRef?.title || this.commitRef?.name;
},
commitAuthor() {
let commitAuthorInformation;
const pipelineCommit = this.pipeline?.commit;
const pipelineCommitAuthor = pipelineCommit?.author;
if (!pipelineCommit) {
return null;
}
// 1. person who is an author of a commit might be a GitLab user
if (pipelineCommitAuthor) {
// 2. if person who is an author of a commit is a GitLab user
// they can have a GitLab avatar
if (pipelineCommitAuthor?.avatar_url) {
commitAuthorInformation = pipelineCommitAuthor;
// 3. If GitLab user does not have avatar, they might have a Gravatar
} else if (pipelineCommit.author_gravatar_url) {
commitAuthorInformation = {
...pipelineCommitAuthor,
avatar_url: pipelineCommit.author_gravatar_url,
};
}
// 4. If committer is not a GitLab User, they can have a Gravatar
} else {
commitAuthorInformation = {
avatar_url: pipelineCommit.author_gravatar_url,
path: `mailto:${pipelineCommit.author_email}`,
username: pipelineCommit.author_name,
};
}
return commitAuthorInformation;
},
commitIcon() {
let name = '';
if (this.commitTag) {
name = 'tag';
} else if (this.mergeRequestRef) {
name = 'git-merge';
} else {
name = 'branch';
}
return name;
},
commitTitle() {
return this.pipeline?.commit?.title;
},
hasAuthor() {
return (
this.commitAuthor?.avatar_url && this.commitAuthor?.path && this.commitAuthor?.username
);
},
userImageAltDescription() {
return this.commitAuthor?.username
? sprintf(__("%{username}'s avatar"), { username: this.commitAuthor.username })
: null;
},
rearrangePipelinesTable() {
return this.glFeatures?.rearrangePipelinesTable;
},
},
};
</script>
<template>
<div class="pipeline-tags" data-testid="pipeline-url-table-cell">
<template v-if="rearrangePipelinesTable">
<div class="commit-title gl-mb-2" data-testid="commit-title-container">
<span v-if="commitTitle" class="gl-display-flex">
<tooltip-on-truncate :title="commitTitle" class="flex-truncate-child gl-flex-grow-1">
<gl-link
:href="commitUrl"
class="commit-row-message gl-text-gray-900"
data-testid="commit-title"
>{{ commitTitle }}</gl-link
>
</tooltip-on-truncate>
</span>
<span v-else>{{ __("Can't find HEAD commit for this branch") }}</span>
</div>
<div class="gl-mb-2">
<gl-link
:href="pipeline.path"
class="gl-text-decoration-underline gl-text-blue-600!"
data-testid="pipeline-url-link"
data-qa-selector="pipeline_url_link"
>
#{{ pipeline[pipelineKey] }}
</gl-link>
<!--Commit row-->
<div class="icon-container gl-display-inline-block">
<gl-icon :name="commitIcon" />
</div>
<tooltip-on-truncate :title="tooltipTitle" truncate-target="child" placement="top">
<gl-link
v-if="mergeRequestRef"
:href="mergeRequestRef.path"
class="ref-name"
data-testid="merge-request-ref"
>{{ mergeRequestRef.iid }}</gl-link
>
<gl-link v-else :href="refUrl" class="ref-name" data-testid="commit-ref-name">{{
commitRef.name
}}</gl-link>
</tooltip-on-truncate>
<gl-icon name="commit" class="commit-icon" />
<gl-link :href="commitUrl" class="commit-sha mr-0" data-testid="commit-short-sha">{{
commitShortSha
}}</gl-link>
<!--End of commit row-->
</div>
</template>
<gl-link
v-if="!rearrangePipelinesTable"
:href="pipeline.path"
class="gl-text-decoration-underline"
data-testid="pipeline-url-link"
@ -66,7 +203,7 @@ export default {
>
#{{ pipeline[pipelineKey] }}
</gl-link>
<div class="label-container">
<div class="label-container gl-mt-1">
<gl-badge
v-if="isScheduled"
v-gl-tooltip

View File

@ -3,12 +3,16 @@ import CodeQualityWalkthrough from '~/code_quality_walkthrough/components/step.v
import { PIPELINE_STATUSES } from '~/code_quality_walkthrough/constants';
import { CHILD_VIEW } from '~/pipelines/constants';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import PipelinesTimeago from './time_ago.vue';
export default {
components: {
CodeQualityWalkthrough,
CiBadge,
PipelinesTimeago,
},
mixins: [glFeatureFlagsMixin()],
props: {
pipeline: {
type: Object,
@ -40,6 +44,9 @@ export default {
codeQualityBuildPath() {
return this.pipeline?.details?.code_quality_build_path;
},
rearrangePipelinesTable() {
return this.glFeatures?.rearrangePipelinesTable;
},
},
};
</script>
@ -48,11 +55,13 @@ export default {
<div>
<ci-badge
id="js-code-quality-walkthrough"
class="gl-mb-3"
:status="pipelineStatus"
:show-text="!isChildView"
:icon-classes="'gl-vertical-align-middle!'"
data-qa-selector="pipeline_commit_status"
/>
<pipelines-timeago v-if="rearrangePipelinesTable" class="gl-mt-3" :pipeline="pipeline" />
<code-quality-walkthrough
v-if="shouldRenderCodeQualityWalkthrough"
:step="codeQualityStep"

View File

@ -1,6 +1,7 @@
<script>
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import { s__, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub';
import PipelineMiniGraph from './pipeline_mini_graph.vue';
import PipelineOperations from './pipeline_operations.vue';
@ -33,6 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagMixin()],
props: {
pipelines: {
type: Array,
@ -72,16 +74,18 @@ export default {
key: 'status',
label: s__('Pipeline|Status'),
thClass: DEFAULT_TH_CLASSES,
columnClass: 'gl-w-10p',
columnClass: this.rearrangePipelinesTable ? 'gl-w-15p' : 'gl-w-10p',
tdClass: DEFAULT_TD_CLASS,
thAttr: { 'data-testid': 'status-th' },
},
{
key: 'pipeline',
label: this.pipelineKeyOption.label,
label: this.rearrangePipelinesTable ? __('Pipeline') : this.pipelineKeyOption.label,
thClass: DEFAULT_TH_CLASSES,
tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
columnClass: 'gl-w-10p',
tdClass: this.rearrangePipelinesTable
? `${DEFAULT_TD_CLASS}`
: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
columnClass: this.rearrangePipelinesTable ? 'gl-w-30p' : 'gl-w-10p',
thAttr: { 'data-testid': 'pipeline-th' },
},
{
@ -113,7 +117,7 @@ export default {
label: s__('Pipeline|Duration'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-15p',
columnClass: this.rearrangePipelinesTable ? 'gl-w-5p' : 'gl-w-15p',
thAttr: { 'data-testid': 'timeago-th' },
},
{
@ -124,7 +128,13 @@ export default {
thAttr: { 'data-testid': 'actions-th' },
},
];
return fields;
return !this.rearrangePipelinesTable
? fields
: fields.filter((field) => !['commit', 'timeago'].includes(field.key));
},
rearrangePipelinesTable() {
return this.glFeatures?.rearrangePipelinesTable;
},
},
watch: {
@ -182,6 +192,7 @@ export default {
:pipeline="item"
:pipeline-schedule-url="pipelineScheduleUrl"
:pipeline-key="pipelineKeyOption.key"
:view-type="viewType"
/>
</template>

View File

@ -54,11 +54,14 @@ export default {
showSkipped() {
return !this.duration && !this.finishedTime && this.skipped;
},
shouldDisplayAsBlock() {
return this.glFeatures?.rearrangePipelinesTable;
},
},
};
</script>
<template>
<div>
<div class="{ 'gl-display-block': shouldDisplayAsBlock }">
<span v-if="showInProgress" data-testid="pipeline-in-progress">
<gl-icon v-if="stuck" name="warning" class="gl-mr-2" :size="12" data-testid="warning-icon" />
<gl-icon

View File

@ -43,6 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml)
push_frontend_feature_flag(:rearrange_pipelines_table, project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)

View File

@ -13,6 +13,9 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :ensure_pipeline, only: [:show, :downloadable_artifacts]
before_action do
push_frontend_feature_flag(:rearrange_pipelines_table, project, default_enabled: :yaml)
end
before_action do
push_frontend_feature_flag(:jobs_tab_vue, @project, default_enabled: :yaml)

View File

@ -0,0 +1,8 @@
---
name: rearrange_pipelines_table
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72545
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343286
milestone: '14.8'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class RemoveCiPipelinesDastSiteProfilesPipelinesCiPipelineIdFk < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
return unless foreign_key_exists?(:dast_site_profiles_pipelines, :ci_pipelines, name: "fk_53849b0ad5")
with_lock_retries do
execute('LOCK ci_pipelines, dast_site_profiles_pipelines IN ACCESS EXCLUSIVE MODE') if transaction_open?
remove_foreign_key_if_exists(:dast_site_profiles_pipelines, :ci_pipelines, name: "fk_53849b0ad5")
end
end
def down
add_concurrent_foreign_key(:dast_site_profiles_pipelines, :ci_pipelines, name: "fk_53849b0ad5", column: :ci_pipeline_id, target_column: :id, on_delete: :cascade)
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class RemoveCiPipelinesVulnerabilityFeedbackPipelineIdFk < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
return unless foreign_key_exists?(:vulnerability_feedback, :ci_pipelines, name: "fk_rails_20976e6fd9")
with_lock_retries do
execute('LOCK ci_pipelines, vulnerability_feedback IN ACCESS EXCLUSIVE MODE') if transaction_open?
remove_foreign_key_if_exists(:vulnerability_feedback, :ci_pipelines, name: "fk_rails_20976e6fd9")
end
end
def down
add_concurrent_foreign_key(:vulnerability_feedback, :ci_pipelines, name: "fk_rails_20976e6fd9", column: :pipeline_id, target_column: :id, on_delete: :nullify)
end
end

View File

@ -0,0 +1 @@
2c3f7c587b2a20de1d8581584f7392fd81643af4eb7e25ffc8e08514b6ad83ab

View File

@ -0,0 +1 @@
75eb050fc789eb5775a5d3a88c2573dca5c38e16b63cd342bf46dca55d1adaef

View File

@ -29390,9 +29390,6 @@ ALTER TABLE ONLY alert_management_alerts
ALTER TABLE ONLY path_locks
ADD CONSTRAINT fk_5265c98f24 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_site_profiles_pipelines
ADD CONSTRAINT fk_53849b0ad5 FOREIGN KEY (ci_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY clusters_applications_prometheus
ADD CONSTRAINT fk_557e773639 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE;
@ -30200,9 +30197,6 @@ ALTER TABLE ONLY boards_epic_lists
ALTER TABLE ONLY approval_merge_request_rules_groups
ADD CONSTRAINT fk_rails_2020a7124a FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_feedback
ADD CONSTRAINT fk_rails_20976e6fd9 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
ALTER TABLE ONLY work_item_types
ADD CONSTRAINT fk_rails_20f694a960 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;

View File

@ -36,7 +36,7 @@ For example, data is not recorded and services do not run.
If you used a certain feature and identified a bug, a misbehavior, or an
error, it's very important that you [**provide feedback**](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue[title]=Docs%20-%20feature%20flag%20feedback%3A%20Feature%20Name&issue[description]=Describe%20the%20problem%20you%27ve%20encountered.%0A%0A%3C!--%20Don%27t%20edit%20below%20this%20line%20--%3E%0A%0A%2Flabel%20~%22docs%5C-comments%22%20) to GitLab as soon
as possible so we can improve or fix it while behind a flag. When you upgrade
GitLab to an earlier version, the feature flag status may change.
GitLab, the feature flag status may change.
## Risks when enabling features still in development
@ -144,3 +144,24 @@ Feature.enabled?(:my_awesome_feature)
Feature.disabled?(:my_awesome_feature)
=> false
```
When the feature is ready, GitLab removes the feature flag, and the option for
enabling and disabling it no longer exists. The feature becomes available in all instances.
### View set feature flags
You can view all GitLab administrator set feature flags:
```ruby
Feature.all
=> [#<Flipper::Feature:198220 name="my_awesome_feature", state=:on, enabled_gate_names=[:boolean], adapter=:memoizable>]
```
### Unset feature flag
You can unset a feature flag so that GitLab will fall back to the current defaults for that flag:
```ruby
Feature.remove(:my_awesome_feature)
=> true
```

View File

@ -204,6 +204,7 @@ This list of limitations only reflects the latest version of GitLab. If you are
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** site.
- GitLab Runners cannot register with a **secondary** site. Support for this is [planned for the future](https://gitlab.com/gitlab-org/gitlab/-/issues/3294).
- [Selective synchronization](replication/configuration.md#selective-synchronization) only limits what repositories and files are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accommodate compliance / export control use cases.
- [Pages access control](../../user/project/pages/pages_access_control.md) doesn't work on secondaries. See [GitLab issue #9336](https://gitlab.com/gitlab-org/gitlab/-/issues/9336) for details.
### Limitations on replication/verification

View File

@ -178,3 +178,22 @@ GitLab supports `authorized_keys` database lookups with [SELinux](https://en.wik
Because the SELinux policy is static, GitLab doesn't support the ability to change
internal webserver ports at the moment. Administrators would have to create a special `.te`
file for the environment, since it isn't generated dynamically.
## Troubleshooting
If your SSH traffic is [slow](https://github.com/linux-pam/linux-pam/issues/270)
or causing high CPU load, be sure to check the size of `/var/log/btmp`, and ensure it is rotated on a regular basis.
If this file is very large, GitLab SSH fast lookup can cause the bottleneck to be hit more frequently, thus decreasing performance even further.
If you are able to, you may consider disabling [`UsePAM` in your `sshd_config`](https://linux.die.net/man/5/sshd_config) to avoid reading `/var/log/btmp` altogether.
Running `strace` and `lsof` on a running `sshd: git` process can return useful debugging information. To get an `strace` on an in-progress Git over SSH connection for IP `x.x.x.x`, run:
```plaintext
sudo strace -s 10000 -p $(sudo netstat -tp | grep x.x.x.x | egrep 'ssh.*: git' | sed -e 's/.*ESTABLISHED *//' -e 's#/.*##')
```
Or get an `lsof` for a running Git over SSH process:
```plaintext
sudo lsof -p $(sudo netstat -tp | egrep 'ssh.*: git' | head -1 | sed -e 's/.*ESTABLISHED *//' -e 's#/.*##')
```

View File

@ -12,7 +12,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 10,000
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator#id=e77713f6-dc0b-4bb3-bcef-cea904ac8efd)
> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested daily with the [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 200 RPS, Web: 20 RPS, Git (Pull): 20 RPS, Git (Push): 4 RPS
@ -2234,7 +2234,7 @@ future with further specific cloud provider details.
| Sidekiq | 4 | 4 vCPU, 15 GB memory | `n1-standard-4` | 15.5 vCPU, 50 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 4 vCPU, 15 GB memory | `n1-standard-4` | 7.75 vCPU, 25 GB memory |
- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.

View File

@ -18,6 +18,7 @@ many organizations.
> - **Supported users (approximate):** 1,000
> - **High Availability:** No. For a highly-available environment, you can
> follow a modified [3K reference architecture](3k_users.md#supported-modifications-for-lower-user-counts-ha).
> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid:** No. For a cloud native hybrid environment, you
> can follow a [modified hybrid reference architecture](#cloud-native-hybrid-reference-architecture-with-helm-charts).
> - **Performance tested daily with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:

View File

@ -12,7 +12,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 25,000
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator#id=925386e1-c01c-4c0a-8d7d-ebde1824b7b0)
> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested weekly with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 500 RPS, Web: 50 RPS, Git (Pull): 50 RPS, Git (Push): 10 RPS
@ -2232,7 +2232,7 @@ future with further specific cloud provider details.
| Sidekiq | 4 | 4 vCPU, 15 GB memory | `n1-standard-4` | 15.5 vCPU, 50 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 4 vCPU, 15 GB memory | `n1-standard-4` | 7.75 vCPU, 25 GB memory |
- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.

View File

@ -13,7 +13,7 @@ For a full list of reference architectures, see
> - **Supported users (approximate):** 2,000
> - **High Availability:** No. For a highly-available environment, you can
> follow a modified [3K reference architecture](3k_users.md#supported-modifications-for-lower-user-counts-ha).
> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator#id=84d11491-d72a-493c-a16e-650931faa658)
> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested daily with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 40 RPS, Web: 4 RPS, Git (Pull): 4 RPS, Git (Push): 1 RPS
@ -1022,7 +1022,7 @@ future with further specific cloud provider details.
| Sidekiq | 2 | 2 vCPU, 7.5 GB memory | `n1-standard-2` | 3.9 vCPU, 11.8 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 1 vCPU, 3.75 GB memory | `n1-standard-1` | 1.9 vCPU, 5.5 GB memory |
- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.

View File

@ -22,7 +22,7 @@ For a full list of reference architectures, see
> - **Supported users (approximate):** 3,000
> - **High Availability:** Yes, although [Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution
> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator/#id=ac4838e6-9c40-4a36-ac43-6d1bc1843e08)
> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested weekly with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 60 RPS, Web: 6 RPS, Git (Pull): 6 RPS, Git (Push): 1 RPS
@ -2191,7 +2191,7 @@ future with further specific cloud provider details.
| Sidekiq | 3 | 4 vCPU, 15 GB memory | `n1-standard-4` | 11.8 vCPU, 38.9 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 2 vCPU, 7.5 GB memory | `n1-standard-2` | 3.9 vCPU, 11.8 GB memory |
- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.

View File

@ -12,7 +12,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 50,000
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator/#id=8006396b-88ee-40cd-a1c8-77cdefa4d3c8)
> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested weekly with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 1000 RPS, Web: 100 RPS, Git (Pull): 100 RPS, Git (Push): 20 RPS
@ -2248,7 +2248,7 @@ future with further specific cloud provider details.
| Sidekiq | 4 | 4 vCPU, 15 GB memory | `n1-standard-4` | 15.5 vCPU, 50 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 4 vCPU, 15 GB memory | `n1-standard-4` | 7.75 vCPU, 25 GB memory |
- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.

View File

@ -19,7 +19,7 @@ costly-to-operate environment by using the
> - **Supported users (approximate):** 5,000
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator/#id=8742e8ea-c08f-4e0a-b058-02f3a1c38a2f)
> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested weekly with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 100 RPS, Web: 10 RPS, Git (Pull): 10 RPS, Git (Push): 2 RPS
@ -2167,7 +2167,7 @@ future with further specific cloud provider details.
| Sidekiq | 3 | 4 vCPU, 15 GB memory | `n1-standard-4` | 11.8 vCPU, 38.9 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 2 vCPU, 7.5 GB memory | `n1-standard-2` | 3.9 vCPU, 11.8 GB memory |
- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.

View File

@ -83,6 +83,203 @@ to get assistance from Support with troubleshooting the [2,000 users](2k_users.m
and higher reference architectures.
[Read more about our definition of scaled architectures](https://about.gitlab.com/support/#definition-of-scaled-architecture).
### Validation and test results
The [Quality Engineering - Enablement team](https://about.gitlab.com/handbook/engineering/quality/quality-engineering/) does regular smoke and performance tests for the reference architectures to ensure they remain compliant.
- Testing occurs against all reference architectures and cloud providers in an automated and ad-hoc fashion. This is done by two tools:
- The [GitLab Environment Toolkit](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit) for building the environments.
- The [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance) for performance testing.
- Network latency on the test environments between components on all Cloud Providers were measured at <5ms. Note that this is shared as an observation and not as an implicit recommendation.
- We aim to have a "test smart" approach where architectures tested have a good range that can also apply to others. Testing focuses on 10k Omnibus on GCP as the testing has shown this is a good bellwether for the other architectures and cloud providers as well as Cloud Native Hybrids.
- Testing is done publicly and all results are shared.
The following table details the testing done against the reference architectures along with the frequency and results. Additional testing is continuously evaluated, and the table is updated accordingly.
<style>
table.test-coverage td {
border-left: 1px solid #dbdbdb;
border-right: 1px solid #dbdbdb;
border-bottom: 1px solid #dbdbdb;
}
table.test-coverage th {
border-left: 1px solid #dbdbdb;
border-right: 1px solid #dbdbdb;
border-bottom: 1px solid #dbdbdb;
}
</style>
<table class="test-coverage">
<col>
<colgroup span="2"></colgroup>
<colgroup span="2"></colgroup>
<tr>
<th rowspan="2">Reference<br/>Architecture</th>
<th style="text-align: center" colspan="2" scope="colgroup">GCP (* also proxy for Bare-Metal)</th>
<th style="text-align: center" colspan="2" scope="colgroup">AWS</th>
<th style="text-align: center" colspan="2" scope="colgroup">Azure</th>
</tr>
<tr>
<th scope="col">Omnibus</th>
<th scope="col">Cloud Native Hybrid</th>
<th scope="col">Omnibus</th>
<th scope="col">Cloud Native Hybrid</th>
<th scope="col">Omnibus</th>
<th scope="col">Cloud Native Hybrid</th>
</tr>
<tr>
<th scope="row">1k</th>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/1k">Daily</a> (to be moved to Weekly)</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">2k</th>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/2k">Daily</a> (to be moved to Weekly)</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">3k</th>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/3k">Weekly</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">5k</th>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/5k">Weekly</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">10k</th>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/10k">Daily</a></td>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k-Cloud-Native-Hybrid">Ad-Hoc</a></td>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k">Ad-Hoc (inc Cloud Services)</a></td>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k-Cloud-Native-Hybrid">Ad-Hoc</a></td>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k">Ad-Hoc</a></td>
<td></td>
</tr>
<tr>
<th scope="row">25k</th>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/25k">Weekly</a></td>
<td></td>
<td></td>
<td></td>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/25k">Ad-Hoc</a></td>
<td></td>
</tr>
<tr>
<th scope="row">50k</th>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/50k">Weekly</a></td>
<td></td>
<td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/50k">Ad-Hoc (inc Cloud Services)</a></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
The Standard Reference Architectures are designed to be platform agnostic, with everything being run on VMs via [Omnibus GitLab](https://docs.gitlab.com/omnibus/). While testing occurs primarily on GCP, ad-hoc testing has shown that they perform similarly on equivalently specced hardware on other Cloud Providers or if run on premises (bare-metal).
### Cost to run
<table class="test-coverage">
<col>
<colgroup span="2"></colgroup>
<colgroup span="2"></colgroup>
<tr>
<th rowspan="2">Reference<br/>Architecture</th>
<th style="text-align: center" colspan="2" scope="colgroup">GCP</th>
<th style="text-align: center" colspan="2" scope="colgroup">AWS</th>
<th style="text-align: center" colspan="2" scope="colgroup">Azure</th>
</tr>
<tr>
<th scope="col">Omnibus</th>
<th scope="col">Cloud Native Hybrid</th>
<th scope="col">Omnibus</th>
<th scope="col">Cloud Native Hybrid</th>
<th scope="col">Omnibus</th>
<th scope="col">Cloud Native Hybrid</th>
</tr>
<tr>
<th scope="row">1k</th>
<td><a href="https://cloud.google.com/products/calculator#id=a6d6a94a-c7dc-4c22-85c4-7c5747f272ed">Calculated cost</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">2k</th>
<td><a href="https://cloud.google.com/products/calculator#id=84d11491-d72a-493c-a16e-650931faa658">Calculated cost</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">3k</th>
<td><a href="https://cloud.google.com/products/calculator/#id=ac4838e6-9c40-4a36-ac43-6d1bc1843e08">Calculated cost</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">5k</th>
<td><a href="https://cloud.google.com/products/calculator/#id=8742e8ea-c08f-4e0a-b058-02f3a1c38a2f">Calculated cost</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">10k</th>
<td><a href="https://cloud.google.com/products/calculator#id=e77713f6-dc0b-4bb3-bcef-cea904ac8efd">Calculated cost</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">25k</th>
<td><a href="https://cloud.google.com/products/calculator#id=925386e1-c01c-4c0a-8d7d-ebde1824b7b0">Calculated cost</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">50k</th>
<td><a href="https://cloud.google.com/products/calculator/#id=8006396b-88ee-40cd-a1c8-77cdefa4d3c8">Calculated cost</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
## Availability Components
GitLab comes with the following components for your use, listed from least to
@ -191,33 +388,3 @@ The reference architectures for user counts [3,000](3k_users.md) and up support
In the specific case you have the requirement to achieve HA but have a lower user count, select modifications to the [3,000 user](3k_users.md) architecture are supported.
For more details, [refer to this section in the architecture's documentation](3k_users.md#supported-modifications-for-lower-user-counts-ha).
## Testing process and results
The [Quality Engineering - Enablement team](https://about.gitlab.com/handbook/engineering/quality/quality-engineering/) does regular smoke and performance tests for the reference architectures to ensure they remain compliant.
In this section, we detail some of the process as well as the results.
Note the following about the testing process:
- Testing occurs against all main reference architectures and cloud providers in an automated and ad-hoc fashion.
This is achieved through two tools built by the team:
- The [GitLab Environment Toolkit](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit) for building the environments.
- The [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance) for performance testing.
- Network latency on the test environments between components on all Cloud Providers were measured at <5ms. Note that this is shared as an observation and not as an implicit recommendation.
- We aim to have a "test smart" approach where architectures tested have a good range that can also apply to others. Testing focuses on 10k Omnibus on GCP as the testing has shown this is a good bellwether for the other architectures and cloud providers as well as Cloud Native Hybrids.
- Testing is done publicly and all results are shared.
Τhe following table details the testing done against the reference architectures along with the frequency and results. Note that this list above is non exhaustive. Additional testing is continuously evaluated and iterated on, and the table is updated accordingly.
| Reference<br/>Architecture<br/>Size | Bare-Metal | GCP | AWS | Azure |
|-----------------------------|------------|-----|-----|-------|
| 1k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/1k)<sup>1</sup> | - | - |
| 2k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/2k)<sup>1</sup> | - | - |
| 3k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/3k)<sup>1</sup> | - | - |
| 5k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/5k)<sup>1</sup> | - | - |
| 10k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Daily](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/10k)<sup>1</sup> <br/> [Standard (inc Cloud Services) - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k) <br/> [Cloud Native Hybrid - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k-Cloud-Native-Hybrid) | [Standard (inc Cloud Services) - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k) <br/> [Cloud Native Hybrid - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k-Cloud-Native-Hybrid) | [Standard - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k) |
| 25k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/25k)<sup>1</sup> | - | [Standard - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/25k) |
| 50k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/50k)<sup>1</sup> | [Standard (inc Cloud Services) - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/50k) | - |
1. The Standard Reference Architectures are designed to be platform agnostic, with everything being run on VMs via [Omnibus GitLab](https://docs.gitlab.com/omnibus/). While testing occurs primarily on GCP, ad-hoc testing has shown that they perform similarly on equivalently specced hardware on other Cloud Providers or if run on premises (bare-metal).

View File

@ -1377,3 +1377,18 @@ Gitlab::CurrentSettings.elasticsearch_url
Gitlab::CurrentSettings.elasticsearch_indexing
```
#### Changing the Elasticsearch password
```ruby
es_url = Gitlab::CurrentSettings.current_application_settings
# Confirm the current ElasticSearch URL
es_url.elasticsearch_url
# Set the ElasticSearch URL
es_url.elasticsearch_url = "http://<username>:<password>@your.es.host:<port>"
# Save the change
es_url.save!
```

View File

@ -10,14 +10,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Get a list of jobs in a project. Jobs are sorted in descending order of their IDs.
By default, this request returns 20 results at a time because the API results [are paginated](index.md#pagination)
```plaintext
GET /projects/:id/jobs
```
| Attribute | Type | Required | Description |
|-----------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
| Attribute | Type | Required | Description |
|-----------|--------------------------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `scope` | string **or** array of strings | **{dotted-circle}** No | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
```shell
curl --globoff --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running"
@ -155,16 +157,18 @@ Example of response
Get a list of jobs for a pipeline.
By default, this request returns 20 results at a time because the API results [are paginated](index.md#pagination)
```plaintext
GET /projects/:id/pipelines/:pipeline_id/jobs
```
| Attribute | Type | Required | Description |
|-------------------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `pipeline_id` | integer | yes | ID of a pipeline. Can also be obtained in CI jobs via the [predefined CI variable](../ci/variables/predefined_variables.md) `CI_PIPELINE_ID`. |
| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
| `include_retried` | boolean | no | Include retried jobs in the response. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/272627) in GitLab 13.9. |
| Attribute | Type | Required | Description |
|-------------------|--------------------------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `pipeline_id` | integer | **{check-circle}** Yes | ID of a pipeline. Can also be obtained in CI jobs via the [predefined CI variable](../ci/variables/predefined_variables.md) `CI_PIPELINE_ID`. |
| `scope` | string **or** array of strings | **{dotted-circle}** No | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
| `include_retried` | boolean | **{dotted-circle}** No | Include retried jobs in the response. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/272627) in GitLab 13.9. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running"
@ -316,11 +320,11 @@ Get a list of bridge jobs for a pipeline.
GET /projects/:id/pipelines/:pipeline_id/bridges
```
| Attribute | Type | Required | Description |
|---------------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `pipeline_id` | integer | yes | ID of a pipeline. |
| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
| Attribute | Type | Required | Description |
|---------------|--------------------------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `pipeline_id` | integer | **{check-circle}** Yes | ID of a pipeline. |
| `scope` | string **or** array of strings | **{dotted-circle}** No | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/6/bridges?scope[]=pending&scope[]=running"
@ -483,9 +487,9 @@ GET /job/allowed_agents
Supported attributes:
| Attribute | Type | Required | Description |
|:------------ |:---------|:---------|:----------------------|
| `CI_JOB_TOKEN` | string | yes | Token value associated with the GitLab-provided `CI_JOB_TOKEN` variable. |
| Attribute | Type | Required | Description |
|----------------|----------|------------------------|-------------|
| `CI_JOB_TOKEN` | string | **{check-circle}** Yes | Token value associated with the GitLab-provided `CI_JOB_TOKEN` variable. |
Example request:
@ -558,10 +562,10 @@ Get a single job of a project
GET /projects/:id/jobs/:job_id
```
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
| Attribute | Type | Required | Description |
|-----------|----------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8"
@ -635,10 +639,10 @@ Get a log (trace) of a specific job of a project:
GET /projects/:id/jobs/:job_id/trace
```
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
| Attribute | Type | Required | Description |
|-----------|----------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
@ -659,10 +663,10 @@ Cancel a single job of a project
POST /projects/:id/jobs/:job_id/cancel
```
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
| Attribute | Type | Required | Description |
|-----------|----------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/cancel"
@ -709,10 +713,10 @@ Retry a single job of a project
POST /projects/:id/jobs/:job_id/retry
```
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
| Attribute | Type | Required | Description |
|-----------|----------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/retry"
@ -761,10 +765,10 @@ POST /projects/:id/jobs/:job_id/erase
Parameters
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
| Attribute | Type | Required | Description |
|-----------|----------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
Example of request
@ -818,10 +822,10 @@ Triggers a manual action to start a job.
POST /projects/:id/jobs/:job_id/play
```
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
| Attribute | Type | Required | Description |
|-----------|----------------|------------------------|-------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/play"

View File

@ -108,6 +108,10 @@ To customize the path:
- Is on an external site, enter the full URL.
1. Select **Save changes**.
NOTE:
You cannot use your project's [pipeline editor](../pipeline_editor/index.md) to
edit CI/CD configuration files in other projects or on an external site.
### Custom CI/CD configuration file examples
If the CI/CD configuration file is not in the root directory, the path must be relative to it.

View File

@ -331,6 +331,10 @@ NOTE:
We don't support running GitLab with JavaScript disabled in the browser and have no plans of supporting that
in the future because we have features such as issue boards which require JavaScript extensively.
## Security
After installation, be sure to read and follow guidance on [maintaining a secure GitLab installation](../security/index.md).
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -828,6 +828,7 @@ For installations from source:
The `CRON=1` environment setting directs the backup script to hide all progress
output if there aren't any errors. This is recommended to reduce cron spam.
When troubleshooting backup problems, however, replace `CRON=1` with `--trace` to log verbosely.
### Limit backup lifetime for local files (prune old backups)

View File

@ -30,3 +30,5 @@ type: index
## Securing your GitLab installation
Consider access control features like [Sign up restrictions](../user/admin_area/settings/sign_up_restrictions.md) and [Authentication options](../topics/authentication/) to harden your GitLab instance and minimize the risk of unwanted user account creation.
Self-hosting GitLab customers and administrators are responsible for the security of their underlying hosts, and for keeping GitLab itself up to date. It is important to [regularly patch GitLab](../policy/maintenance.md), patch your operating system and its software, and harden your hosts in accordance with vendor guidance.

View File

@ -73,7 +73,7 @@ Download Ruby and compile it:
```shell
mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz"
curl --remote-name --location --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz"
echo '3043099089608859fc8cce7f9fdccaa1f53a462457e3838ec3b25a7d609fbc5b ruby-2.7.4.tar.gz' | sha256sum -c - && tar xzf ruby-2.7.4.tar.gz
cd ruby-2.7.4
@ -111,7 +111,7 @@ Download and install Go (for Linux, 64-bit):
# Remove former Go installation folder
sudo rm -rf /usr/local/go
curl --remote-name --progress-bar "https://go.dev/dl/go1.16.10.linux-amd64.tar.gz"
curl --remote-name --location --progress-bar "https://go.dev/dl/go1.16.10.linux-amd64.tar.gz"
echo '414cd18ce1d193769b9e97d2401ad718755ab47816e13b2a1cde203d263b55cf go1.16.10.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.16.10.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,gofmt} /usr/local/bin/

View File

@ -0,0 +1,65 @@
---
stage: Enablement
group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Spamcheck anti-spam service **(PREMIUM SELF)**
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259) in GitLab 14.8.
[Spamcheck](https://gitlab.com/gitlab-org/spamcheck) is an anti-spam engine
developed by GitLab originally to combat rising amount of spam in GitLab.com,
and later made public to be used in self-managed GitLab instances.
## Enable Spamcheck
Spamcheck is only available for package-based installations:
1. Edit `/etc/gitlab/gitlab.rb` and enable Spamcheck:
```ruby
spamcheck['enable'] = true
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Verify that the new services `spamcheck` and `spam-classifier` are
up and running:
```shell
sudo gitlab-ctl status
```
## Configure GitLab to use Spamcheck
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > Reporting**.
1. Expand **Spam and Anti-bot Protection**.
1. Update the Spam Check settings:
1. Check the "Enable Spam Check via external API endpoint" checkbox.
1. For **URL of the external Spam Check endpoint** use `grpc://localhost:8001`.
1. Leave **Spam Check API key** blank.
1. Select **Save changes**.
NOTE:
In single-node instances, Spamcehck runs over `localhost`, and hence is running
in an unauthenticated mode. If on multi-node instances where GitLab runs on one
server and Spamcheck runs on another server listening over a public endpoint, it
is recommended to enforce some sort of authentication using a reverse proxy in
front of the Spamcheck service that can be used along with an API key. One
example would be to use `JWT` authentication for this and specifying a bearer
token as the API key.
[Native authentication for Spamcheck is in the works](https://gitlab.com/gitlab-com/gl-security/engineering-and-research/automation-team/spam/spamcheck/-/issues/171).
## Running Spamcheck over TLS
Spamcheck service on its own can not communicate directly over TLS with GitLab.
However, Spamcheck can be deployed behind a reverse proxy which performs TLS
termination. In such a scenario, GitLab can be made to communicate with
Spamcheck over TLS by specifying `tls://` scheme for the external Spamcheck URL
instead of `grpc://` in the Admin settings.

View File

@ -160,7 +160,7 @@ The **Preferences** settings contain:
The **Reporting** settings contain:
- [Spam and Anti-bot Protection](../../../integration/recaptcha.md) -
Enable anti-spam services, like reCAPTCHA or Akismet, and set IP limits.
Enable anti-spam services, like reCAPTCHA, Akismet or [Spamcheck](../reporting/spamcheck.md), and set IP limits.
- [Abuse reports](../review_abuse_reports.md) - Set notification email for abuse reports.
### Repository

View File

@ -29,7 +29,7 @@ jobs provide a `KUBECONFIG` variable compatible with `kubectl`.
Also, each Agent has a separate context (`kubecontext`). By default,
there isn't any context selected.
Contexts are named in the following format: `<agent-configuration-project-path>:<agent-name>`.
Contexts are named in the following format: `<namespace>/<project-name>:<agent-name>`.
To get the list of available contexts, run `kubectl config get-contexts`.
## Share the CI/CD Tunnel provided by an Agent with other projects and groups

View File

@ -561,7 +561,7 @@ module API
def increment_counter(event_name)
feature_name = "usage_data_#{event_name}"
return unless Feature.enabled?(feature_name)
return unless Feature.enabled?(feature_name, default_enabled: :yaml)
Gitlab::UsageDataCounters.count(event_name)
rescue StandardError => error

View File

@ -94,9 +94,6 @@ ci_pipelines:
- table: users
column: user_id
on_delete: async_nullify
- table: merge_requests
column: merge_request_id
on_delete: async_delete
ci_project_mirrors:
- table: projects
column: project_id

View File

@ -28,7 +28,11 @@ module Gitlab
end
def self.loose_foreign_keys_yaml
@loose_foreign_keys_yaml ||= YAML.load_file(Rails.root.join('lib/gitlab/database/gitlab_loose_foreign_keys.yml'))
@loose_foreign_keys_yaml ||= YAML.load_file(self.loose_foreign_keys_yaml_path)
end
def self.loose_foreign_keys_yaml_path
@loose_foreign_keys_yaml_path ||= Rails.root.join('lib/gitlab/database/gitlab_loose_foreign_keys.yml')
end
private_class_method :build_definition

View File

@ -26,11 +26,11 @@ module QA
end
def wait_for_latest_pipeline_succeeded
wait_for_latest_pipeline_status { has_text?('passed') }
wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") }
end
def wait_for_latest_pipeline_completed
wait_for_latest_pipeline_status { has_text?('passed') || has_text?('failed') }
wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") || has_selector?(".ci-status-icon-failed") }
end
def wait_for_latest_pipeline_status

View File

@ -11,7 +11,7 @@ RSpec.describe 'Value Stream Analytics', :js do
let_it_be(:stage_table_event_title_selector) { '[data-testid="vsa-stage-event-title"]' }
let_it_be(:stage_table_pagination_selector) { '[data-testid="vsa-stage-pagination"]' }
let_it_be(:stage_table_duration_column_header_selector) { '[data-testid="vsa-stage-header-duration"]' }
let_it_be(:metrics_selector) { "[data-testid='vsa-time-metrics']" }
let_it_be(:metrics_selector) { "[data-testid='vsa-metrics']" }
let_it_be(:metric_value_selector) { "[data-testid='displayValue']" }
let(:stage_table) { find(stage_table_selector) }

View File

@ -125,6 +125,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
before do
stub_feature_flags(ci_disallow_to_create_merge_request_pipelines_in_target_project: false)
stub_feature_flags(rearrange_pipelines_table: false)
end
it 'creates a pipeline in the parent project when user proceeds with the warning' do

View File

@ -159,7 +159,7 @@ RSpec.describe 'Pipelines', :js do
end
end
context 'when pipeline is detached merge request pipeline' do
context 'when pipeline is detached merge request pipeline, with rearrange_pipelines_table feature flag turned off' do
let(:merge_request) do
create(:merge_request,
:with_detached_merge_request_pipeline,
@ -172,6 +172,8 @@ RSpec.describe 'Pipelines', :js do
let(:target_project) { project }
before do
stub_feature_flags(rearrange_pipelines_table: false)
visit project_pipelines_path(source_project)
end
@ -201,7 +203,47 @@ RSpec.describe 'Pipelines', :js do
end
end
context 'when pipeline is merge request pipeline' do
context 'when pipeline is detached merge request pipeline, with rearrange_pipelines_table feature flag turned on' do
let(:merge_request) do
create(:merge_request,
:with_detached_merge_request_pipeline,
source_project: source_project,
target_project: target_project)
end
let!(:pipeline) { merge_request.all_pipelines.first }
let(:source_project) { project }
let(:target_project) { project }
before do
stub_feature_flags(rearrange_pipelines_table: true)
visit project_pipelines_path(source_project)
end
shared_examples_for 'detached merge request pipeline' do
it 'shows pipeline information without pipeline ref', :sidekiq_might_not_need_inline do
within '.pipeline-tags' do
expect(page).to have_content('detached')
expect(page).to have_link(merge_request.iid,
href: project_merge_request_path(project, merge_request))
expect(page).not_to have_link(pipeline.ref)
end
end
end
it_behaves_like 'detached merge request pipeline'
context 'when source project is a forked project' do
let(:source_project) { fork_project(project, user, repository: true) }
it_behaves_like 'detached merge request pipeline'
end
end
context 'when pipeline is merge request pipeline, with rearrange_pipelines_table feature flag turned off' do
let(:merge_request) do
create(:merge_request,
:with_merge_request_pipeline,
@ -215,6 +257,8 @@ RSpec.describe 'Pipelines', :js do
let(:target_project) { project }
before do
stub_feature_flags(rearrange_pipelines_table: false)
visit project_pipelines_path(source_project)
end
@ -244,6 +288,47 @@ RSpec.describe 'Pipelines', :js do
end
end
context 'when pipeline is merge request pipeline, with rearrange_pipelines_table feature flag turned on' do
let(:merge_request) do
create(:merge_request,
:with_merge_request_pipeline,
source_project: source_project,
target_project: target_project,
merge_sha: target_project.commit.sha)
end
let!(:pipeline) { merge_request.all_pipelines.first }
let(:source_project) { project }
let(:target_project) { project }
before do
stub_feature_flags(rearrange_pipelines_table: true)
visit project_pipelines_path(source_project)
end
shared_examples_for 'Correct merge request pipeline information' do
it 'does not show detached tag for the pipeline, and shows the link of the merge request, and does not show the ref of the pipeline', :sidekiq_might_not_need_inline do
within '.pipeline-tags' do
expect(page).not_to have_content('detached')
expect(page).to have_link(merge_request.iid,
href: project_merge_request_path(project, merge_request))
expect(page).not_to have_link(pipeline.ref)
end
end
end
it_behaves_like 'Correct merge request pipeline information'
context 'when source project is a forked project' do
let(:source_project) { fork_project(project, user, repository: true) }
it_behaves_like 'Correct merge request pipeline information'
end
end
context 'when pipeline has configuration errors' do
let(:pipeline) do
create(:ci_pipeline, :invalid, project: project)
@ -587,6 +672,7 @@ RSpec.describe 'Pipelines', :js do
context 'with pipeline key selection' do
before do
stub_feature_flags(rearrange_pipelines_table: false)
visit project_pipelines_path(project)
wait_for_requests
end
@ -604,6 +690,27 @@ RSpec.describe 'Pipelines', :js do
expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
end
end
context 'with pipeline key selection and rearrange_pipelines_table ff on' do
before do
stub_feature_flags(rearrange_pipelines_table: true)
visit project_pipelines_path(project)
wait_for_requests
end
it 'changes the Pipeline ID column for Pipeline IID' do
page.find('[data-testid="pipeline-key-dropdown"]').click
within '.gl-new-dropdown-contents' do
dropdown_options = page.find_all '.gl-new-dropdown-item'
dropdown_options[1].click
end
expect(page.find('[data-testid="pipeline-th"]')).to have_content 'Pipeline'
expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
end
end
end
describe 'GET /:project/-/pipelines/show' do

View File

@ -0,0 +1,81 @@
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import MetricTile from '~/cycle_analytics/components/metric_tile.vue';
import MetricPopover from '~/cycle_analytics/components/metric_popover.vue';
import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
describe('MetricTile', () => {
let wrapper;
const createComponent = (props = {}) => {
return shallowMount(MetricTile, {
propsData: {
metric: {},
...props,
},
});
};
const findSingleStat = () => wrapper.findComponent(GlSingleStat);
const findPopover = () => wrapper.findComponent(MetricPopover);
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
describe('links', () => {
it('when the metric has links, it redirects the user on click', () => {
const metric = {
identifier: 'deploys',
value: '10',
label: 'Deploys',
links: [{ url: 'foo/bar' }],
};
wrapper = createComponent({ metric });
const singleStat = findSingleStat();
singleStat.vm.$emit('click');
expect(redirectTo).toHaveBeenCalledWith('foo/bar');
});
it("when the metric doesn't have links, it won't the user on click", () => {
const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
wrapper = createComponent({ metric });
const singleStat = findSingleStat();
singleStat.vm.$emit('click');
expect(redirectTo).not.toHaveBeenCalled();
});
});
describe('decimal places', () => {
it(`will render 0 decimal places for an integer value`, () => {
const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
wrapper = createComponent({ metric });
const singleStat = findSingleStat();
expect(singleStat.props('animationDecimalPlaces')).toBe(0);
});
it(`will render 1 decimal place for a float value`, () => {
const metric = { identifier: 'deploys', value: '10.5', label: 'Deploys' };
wrapper = createComponent({ metric });
const singleStat = findSingleStat();
expect(singleStat.props('animationDecimalPlaces')).toBe(1);
});
});
it('renders a metric popover', () => {
const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
wrapper = createComponent({ metric });
const popover = findPopover();
expect(popover.exists()).toBe(true);
expect(popover.props()).toMatchObject({ metric, target: metric.identifier });
});
});
});

View File

@ -1,17 +1,15 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import waitForPromises from 'helpers/wait_for_promises';
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import MetricTile from '~/cycle_analytics/components/metric_tile.vue';
import createFlash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility';
import { group } from './mock_data';
jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility');
describe('ValueStreamMetrics', () => {
let wrapper;
@ -35,7 +33,7 @@ describe('ValueStreamMetrics', () => {
});
};
const findMetrics = () => wrapper.findAllComponents(GlSingleStat);
const findMetrics = () => wrapper.findAllComponents(MetricTile);
const expectToHaveRequest = (fields) => {
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
@ -61,7 +59,7 @@ describe('ValueStreamMetrics', () => {
expect(wrapper.findComponent(GlSkeletonLoading).exists()).toBe(true);
});
it('renders hidden GlSingleStat components for each metric', async () => {
it('renders hidden MetricTile components for each metric', async () => {
await waitForPromises();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
@ -89,34 +87,17 @@ describe('ValueStreamMetrics', () => {
});
describe.each`
index | value | title | unit | animationDecimalPlaces | clickable
${0} | ${metricsData[0].value} | ${metricsData[0].title} | ${metricsData[0].unit} | ${0} | ${false}
${1} | ${metricsData[1].value} | ${metricsData[1].title} | ${metricsData[1].unit} | ${0} | ${false}
${2} | ${metricsData[2].value} | ${metricsData[2].title} | ${metricsData[2].unit} | ${0} | ${false}
${3} | ${metricsData[3].value} | ${metricsData[3].title} | ${metricsData[3].unit} | ${1} | ${true}
`('metric tiles', ({ index, value, title, unit, animationDecimalPlaces, clickable }) => {
it(`renders a single stat component for "${title}" with value and unit`, () => {
index | identifier | value | label
${0} | ${metricsData[0].identifier} | ${metricsData[0].value} | ${metricsData[0].title}
${1} | ${metricsData[1].identifier} | ${metricsData[1].value} | ${metricsData[1].title}
${2} | ${metricsData[2].identifier} | ${metricsData[2].value} | ${metricsData[2].title}
${3} | ${metricsData[3].identifier} | ${metricsData[3].value} | ${metricsData[3].title}
`('metric tiles', ({ identifier, index, value, label }) => {
it(`renders a metric tile component for "${label}"`, () => {
const metric = findMetrics().at(index);
expect(metric.props()).toMatchObject({ value, title, unit: unit ?? '' });
expect(metric.props('metric')).toMatchObject({ identifier, value, label });
expect(metric.isVisible()).toBe(true);
});
it(`${
clickable ? 'redirects' : "doesn't redirect"
} when the user clicks the "${title}" metric`, () => {
const metric = findMetrics().at(index);
metric.vm.$emit('click');
if (clickable) {
expect(redirectTo).toHaveBeenCalledWith(metricsData[index].links[0].url);
} else {
expect(redirectTo).not.toHaveBeenCalled();
}
});
it(`will render ${animationDecimalPlaces} decimal places for the ${title} metric with the value "${value}"`, () => {
const metric = findMetrics().at(index);
expect(metric.props('animationDecimalPlaces')).toBe(animationDecimalPlaces);
});
});
it('will not display a loading icon', () => {

View File

@ -634,3 +634,80 @@ export const mockPipelineJobsQueryResponse = {
},
},
};
export const mockPipeline = (projectPath) => {
return {
pipeline: {
id: 1,
user: {
id: 1,
name: 'Administrator',
username: 'root',
state: 'active',
avatar_url: '',
web_url: 'http://0.0.0.0:3000/root',
show_status: false,
path: '/root',
},
active: false,
source: 'merge_request_event',
created_at: '2021-10-19T21:17:38.698Z',
updated_at: '2021-10-21T18:00:42.758Z',
path: 'foo',
flags: {},
merge_request: {
iid: 1,
path: `/${projectPath}/1`,
title: 'commit',
source_branch: 'test-commit-name',
source_branch_path: `/${projectPath}`,
target_branch: 'main',
target_branch_path: `/${projectPath}/-/commit/main`,
},
ref: {
name: 'refs/merge-requests/1/head',
path: `/${projectPath}/-/commits/refs/merge-requests/1/head`,
tag: false,
branch: false,
merge_request: true,
},
commit: {
id: 'fd6df5b3229e213c97d308844a6f3e7fd71e8f8c',
short_id: 'fd6df5b3',
created_at: '2021-10-19T21:17:12.000+00:00',
parent_ids: ['7147906b84306e83cb3fec6582a25390b75713c6'],
title: 'Commit',
message: 'Commit',
author_name: 'Administrator',
author_email: 'admin@example.com',
authored_date: '2021-10-19T21:17:12.000+00:00',
committer_name: 'Administrator',
committer_email: 'admin@example.com',
committed_date: '2021-10-19T21:17:12.000+00:00',
trailers: {},
web_url: '',
author: {
id: 1,
name: 'Administrator',
username: 'root',
state: 'active',
avatar_url: '',
web_url: '',
show_status: false,
path: '/root',
},
author_gravatar_url: '',
commit_url: `/${projectPath}/fd6df5b3229e213c97d308844a6f3e7fd71e8f8c`,
commit_path: `/${projectPath}/commit/fd6df5b3229e213c97d308844a6f3e7fd71e8f8c`,
},
project: {
full_path: `/${projectPath}`,
},
triggered_by: null,
triggered: [],
},
pipelineScheduleUrl: 'foo',
pipelineKey: 'id',
viewType: 'root',
};
};

View File

@ -1,41 +1,41 @@
import { shallowMount } from '@vue/test-utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
import { mockPipeline } from './mock_data';
const projectPath = 'test/test';
describe('Pipeline Url Component', () => {
let wrapper;
const findTableCell = () => wrapper.find('[data-testid="pipeline-url-table-cell"]');
const findPipelineUrlLink = () => wrapper.find('[data-testid="pipeline-url-link"]');
const findScheduledTag = () => wrapper.find('[data-testid="pipeline-url-scheduled"]');
const findLatestTag = () => wrapper.find('[data-testid="pipeline-url-latest"]');
const findYamlTag = () => wrapper.find('[data-testid="pipeline-url-yaml"]');
const findFailureTag = () => wrapper.find('[data-testid="pipeline-url-failure"]');
const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]');
const findAutoDevopsTagLink = () => wrapper.find('[data-testid="pipeline-url-autodevops-link"]');
const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
const findForkTag = () => wrapper.find('[data-testid="pipeline-url-fork"]');
const findTrainTag = () => wrapper.find('[data-testid="pipeline-url-train"]');
const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell');
const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link');
const findScheduledTag = () => wrapper.findByTestId('pipeline-url-scheduled');
const findLatestTag = () => wrapper.findByTestId('pipeline-url-latest');
const findYamlTag = () => wrapper.findByTestId('pipeline-url-yaml');
const findFailureTag = () => wrapper.findByTestId('pipeline-url-failure');
const findAutoDevopsTag = () => wrapper.findByTestId('pipeline-url-autodevops');
const findAutoDevopsTagLink = () => wrapper.findByTestId('pipeline-url-autodevops-link');
const findStuckTag = () => wrapper.findByTestId('pipeline-url-stuck');
const findDetachedTag = () => wrapper.findByTestId('pipeline-url-detached');
const findForkTag = () => wrapper.findByTestId('pipeline-url-fork');
const findTrainTag = () => wrapper.findByTestId('pipeline-url-train');
const findRefName = () => wrapper.findByTestId('merge-request-ref');
const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha');
const defaultProps = {
pipeline: {
id: 1,
path: 'foo',
project: { full_path: `/${projectPath}` },
flags: {},
},
pipelineScheduleUrl: 'foo',
pipelineKey: 'id',
};
const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]');
const createComponent = (props) => {
wrapper = shallowMount(PipelineUrlComponent, {
const defaultProps = mockPipeline(projectPath);
const createComponent = (props, rearrangePipelinesTable = false) => {
wrapper = shallowMountExtended(PipelineUrlComponent, {
propsData: { ...defaultProps, ...props },
provide: {
targetProjectFullPath: projectPath,
glFeatures: {
rearrangePipelinesTable,
},
},
});
};
@ -45,158 +45,169 @@ describe('Pipeline Url Component', () => {
wrapper = null;
});
it('should render pipeline url table cell', () => {
createComponent();
describe('with the rearrangePipelinesTable feature flag turned off', () => {
it('should render pipeline url table cell', () => {
createComponent();
expect(findTableCell().exists()).toBe(true);
});
it('should render a link the provided path and id', () => {
createComponent();
expect(findPipelineUrlLink().attributes('href')).toBe('foo');
expect(findPipelineUrlLink().text()).toBe('#1');
});
it('should not render tags when flags are not set', () => {
createComponent();
expect(findStuckTag().exists()).toBe(false);
expect(findLatestTag().exists()).toBe(false);
expect(findYamlTag().exists()).toBe(false);
expect(findAutoDevopsTag().exists()).toBe(false);
expect(findFailureTag().exists()).toBe(false);
expect(findScheduledTag().exists()).toBe(false);
expect(findForkTag().exists()).toBe(false);
expect(findTrainTag().exists()).toBe(false);
});
it('should render the stuck tag when flag is provided', () => {
createComponent({
pipeline: {
flags: {
stuck: true,
},
},
expect(findTableCell().exists()).toBe(true);
});
expect(findStuckTag().text()).toContain('stuck');
});
it('should render a link the provided path and id', () => {
createComponent();
it('should render latest tag when flag is provided', () => {
createComponent({
pipeline: {
flags: {
latest: true,
},
},
expect(findPipelineUrlLink().attributes('href')).toBe('foo');
expect(findPipelineUrlLink().text()).toBe('#1');
});
expect(findLatestTag().text()).toContain('latest');
});
it('should not render tags when flags are not set', () => {
createComponent();
it('should render a yaml badge when it is invalid', () => {
createComponent({
pipeline: {
flags: {
yaml_errors: true,
},
},
expect(findStuckTag().exists()).toBe(false);
expect(findLatestTag().exists()).toBe(false);
expect(findYamlTag().exists()).toBe(false);
expect(findAutoDevopsTag().exists()).toBe(false);
expect(findFailureTag().exists()).toBe(false);
expect(findScheduledTag().exists()).toBe(false);
expect(findForkTag().exists()).toBe(false);
expect(findTrainTag().exists()).toBe(false);
});
expect(findYamlTag().text()).toContain('yaml invalid');
});
it('should render the stuck tag when flag is provided', () => {
const stuckPipeline = defaultProps.pipeline;
stuckPipeline.flags.stuck = true;
it('should render an autodevops badge when flag is provided', () => {
createComponent({
pipeline: {
...defaultProps.pipeline,
flags: {
auto_devops: true,
},
},
createComponent({
...stuckPipeline.pipeline,
});
expect(findStuckTag().text()).toContain('stuck');
});
expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
it('should render latest tag when flag is provided', () => {
const latestPipeline = defaultProps.pipeline;
latestPipeline.flags.latest = true;
expect(findAutoDevopsTagLink().attributes()).toMatchObject({
href: '/help/topics/autodevops/index.md',
target: '_blank',
createComponent({
...latestPipeline,
});
expect(findLatestTag().text()).toContain('latest');
});
it('should render a yaml badge when it is invalid', () => {
const yamlPipeline = defaultProps.pipeline;
yamlPipeline.flags.yaml_errors = true;
createComponent({
...yamlPipeline,
});
expect(findYamlTag().text()).toContain('yaml invalid');
});
it('should render an autodevops badge when flag is provided', () => {
const autoDevopsPipeline = defaultProps.pipeline;
autoDevopsPipeline.flags.auto_devops = true;
createComponent({
...autoDevopsPipeline,
});
expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
expect(findAutoDevopsTagLink().attributes()).toMatchObject({
href: '/help/topics/autodevops/index.md',
target: '_blank',
});
});
it('should render a detached badge when flag is provided', () => {
const detachedMRPipeline = defaultProps.pipeline;
detachedMRPipeline.flags.detached_merge_request_pipeline = true;
createComponent({
...detachedMRPipeline,
});
expect(findDetachedTag().text()).toContain('detached');
});
it('should render error badge when pipeline has a failure reason set', () => {
const failedPipeline = defaultProps.pipeline;
failedPipeline.flags.failure_reason = true;
failedPipeline.failure_reason = 'some reason';
createComponent({
...failedPipeline,
});
expect(findFailureTag().text()).toContain('error');
expect(findFailureTag().attributes('title')).toContain('some reason');
});
it('should render scheduled badge when pipeline was triggered by a schedule', () => {
const scheduledPipeline = defaultProps.pipeline;
scheduledPipeline.source = 'schedule';
createComponent({
...scheduledPipeline,
});
expect(findScheduledTag().exists()).toBe(true);
expect(findScheduledTag().text()).toContain('Scheduled');
});
it('should render the fork badge when the pipeline was run in a fork', () => {
const forkedPipeline = defaultProps.pipeline;
forkedPipeline.project.full_path = '/test/forked';
createComponent({
...forkedPipeline,
});
expect(findForkTag().exists()).toBe(true);
expect(findForkTag().text()).toBe('fork');
});
it('should render the train badge when the pipeline is a merge train pipeline', () => {
const mergeTrainPipeline = defaultProps.pipeline;
mergeTrainPipeline.flags.merge_train_pipeline = true;
createComponent({
...mergeTrainPipeline,
});
expect(findTrainTag().text()).toContain('train');
});
it('should not render the train badge when the pipeline is not a merge train pipeline', () => {
const mergeTrainPipeline = defaultProps.pipeline;
mergeTrainPipeline.flags.merge_train_pipeline = false;
createComponent({
...mergeTrainPipeline,
});
expect(findTrainTag().exists()).toBe(false);
});
it('should not render the commit wrapper and commit-short-sha', () => {
createComponent();
expect(findCommitTitleContainer().exists()).toBe(false);
expect(findCommitShortSha().exists()).toBe(false);
});
});
it('should render a detached badge when flag is provided', () => {
createComponent({
pipeline: {
flags: {
detached_merge_request_pipeline: true,
},
},
describe('with the rearrangePipelinesTable feature flag turned on', () => {
it('should render the commit title, commit reference and commit-short-sha', () => {
createComponent({}, true);
const commitWrapper = findCommitTitleContainer();
expect(findCommitTitle(commitWrapper).exists()).toBe(true);
expect(findRefName().exists()).toBe(true);
expect(findCommitShortSha().exists()).toBe(true);
});
expect(findDetachedTag().text()).toContain('detached');
});
it('should render error badge when pipeline has a failure reason set', () => {
createComponent({
pipeline: {
flags: {
failure_reason: true,
},
failure_reason: 'some reason',
},
});
expect(findFailureTag().text()).toContain('error');
expect(findFailureTag().attributes('title')).toContain('some reason');
});
it('should render scheduled badge when pipeline was triggered by a schedule', () => {
createComponent({
pipeline: {
flags: {},
source: 'schedule',
},
});
expect(findScheduledTag().exists()).toBe(true);
expect(findScheduledTag().text()).toContain('Scheduled');
});
it('should render the fork badge when the pipeline was run in a fork', () => {
createComponent({
pipeline: {
flags: {},
project: { fullPath: '/test/forked' },
},
});
expect(findForkTag().exists()).toBe(true);
expect(findForkTag().text()).toBe('fork');
});
it('should render the train badge when the pipeline is a merge train pipeline', () => {
createComponent({
pipeline: {
flags: {
merge_train_pipeline: true,
},
},
});
expect(findTrainTag().text()).toContain('train');
});
it('should not render the train badge when the pipeline is not a merge train pipeline', () => {
createComponent({
pipeline: {
flags: {
merge_train_pipeline: false,
},
},
});
expect(findTrainTag().exists()).toBe(false);
});
});

View File

@ -33,13 +33,18 @@ describe('Pipelines Table', () => {
return pipelines.find((p) => p.user !== null && p.commit !== null);
};
const createComponent = (props = {}) => {
const createComponent = (props = {}, rearrangePipelinesTable = false) => {
wrapper = extendedWrapper(
mount(PipelinesTable, {
propsData: {
...defaultProps,
...props,
},
provide: {
glFeatures: {
rearrangePipelinesTable,
},
},
}),
);
};
@ -71,7 +76,7 @@ describe('Pipelines Table', () => {
wrapper = null;
});
describe('Pipelines Table', () => {
describe('Pipelines Table with rearrangePipelinesTable feature flag turned off', () => {
beforeEach(() => {
createComponent({ pipelines: [pipeline], viewType: 'root' });
});
@ -189,4 +194,29 @@ describe('Pipelines Table', () => {
});
});
});
describe('Pipelines Table with rearrangePipelinesTable feature flag turned on', () => {
beforeEach(() => {
createComponent({ pipelines: [pipeline], viewType: 'root' }, true);
});
it('should render table head with correct columns', () => {
expect(findStatusTh().text()).toBe('Status');
expect(findPipelineTh().text()).toBe('Pipeline');
expect(findStagesTh().text()).toBe('Stages');
expect(findActionsTh().text()).toBe('Actions');
});
describe('triggerer cell', () => {
it('should render the pipeline triggerer', () => {
expect(findTriggerer().exists()).toBe(true);
});
});
describe('commit cell', () => {
it('should not render commit information', () => {
expect(findCommit().exists()).toBe(false);
});
});
});
});

View File

@ -18,6 +18,36 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
))
end
context 'ensure no duplicates are found' do
it 'does not have duplicate tables defined' do
# since we use hash to detect duplicate hash keys we need to parse YAML document
parsed = YAML.parse_file(described_class.loose_foreign_keys_yaml_path)
expect(parsed).to be_document
expect(parsed.children).to be_one, "YAML has a single document"
# require hash
mapping = parsed.children.first
expect(mapping).to be_mapping, "YAML has a top-level hash"
# find all scalars with names
table_names = mapping.children.select(&:scalar?).map(&:value)
expect(table_names).not_to be_empty, "YAML has a non-zero tables defined"
# expect to not have duplicates
expect(table_names).to contain_exactly(*table_names.uniq)
end
it 'does not have duplicate column definitions' do
# ignore other modifiers
all_definitions = definitions.map do |definition|
{ from_table: definition.from_table, to_table: definition.to_table, column: definition.column }
end
# expect to not have duplicates
expect(all_definitions).to contain_exactly(*all_definitions.uniq)
end
end
describe 'ensuring database integrity' do
def base_models_for(table)
parent_table_schema = Gitlab::Database::GitlabSchema.table_schema(table)

View File

@ -32,8 +32,6 @@ RSpec.describe 'cross-database foreign keys' do
ci_sources_projects.source_project_id
ci_stages.project_id
ci_unit_tests.project_id
dast_site_profiles_pipelines.ci_pipeline_id
vulnerability_feedback.pipeline_id
).freeze
end

View File

@ -240,7 +240,7 @@ RSpec.describe Gitlab::ImportExport::FastHashSerializer do
merge_request = create(:merge_request, source_project: project, milestone: milestone)
ci_build = create(:ci_build, project: project, when: nil)
ci_build.pipeline.update(project: project)
ci_build.pipeline.update!(project: project)
create(:commit_status, project: project, pipeline: ci_build.pipeline)
create_list(:ci_pipeline, 5, :success, project: project)

View File

@ -38,8 +38,8 @@ RSpec.describe 'forked project import' do
allow(instance).to receive(:storage_path).and_return(export_path)
end
saver.save
repo_saver.save
saver.save # rubocop:disable Rails/SaveBang
repo_saver.save # rubocop:disable Rails/SaveBang
repo_restorer.restore
restorer.restore

View File

@ -31,7 +31,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do
# ^ These are specific for the Group::LegacyTreeSaver
context 'JSON' do
let(:saved_group_json) do
group_tree_saver.save
group_tree_saver.save # rubocop:disable Rails/SaveBang
group_json(group_tree_saver.full_path)
end
@ -88,7 +88,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do
end
before do
user2.update(public_email: user2.email)
user2.update!(public_email: user2.email)
group.add_developer(user2)
end

View File

@ -9,7 +9,7 @@ RSpec.describe Gitlab::ImportExport::Group::RelationFactory do
let(:importer_user) { admin }
let(:excluded_keys) { [] }
let(:created_object) do
described_class.create(
described_class.create( # rubocop:disable Rails/SaveBang
relation_sym: relation_sym,
relation_hash: relation_hash,
relation_index: 1,

View File

@ -42,7 +42,7 @@ RSpec.describe Gitlab::ImportExport::Group::TreeSaver do
context 'exported files' do
before do
group_tree_saver.save
group_tree_saver.save # rubocop:disable Rails/SaveBang
end
it 'has one group per line' do

View File

@ -19,7 +19,7 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
before do
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
bundler.save
bundler.save # rubocop:disable Rails/SaveBang
end
after do

View File

@ -30,7 +30,7 @@ RSpec.describe Gitlab::ImportExport::Saver do
it 'saves the repo using object storage' do
stub_uploads_object_storage(ImportExportUploader)
subject.save
subject.save # rubocop:disable Rails/SaveBang
expect(ImportExportUpload.find_by(project: project).export_file.url)
.to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
@ -59,13 +59,13 @@ RSpec.describe Gitlab::ImportExport::Saver do
upload_bytes: anything
)).and_call_original
subject.save
subject.save # rubocop:disable Rails/SaveBang
end
it 'removes archive path and keeps base path untouched' do
allow(shared).to receive(:archive_path).and_return(archive_path)
subject.save
subject.save # rubocop:disable Rails/SaveBang
expect(FileUtils).not_to have_received(:rm_rf).with(base_path)
expect(FileUtils).to have_received(:rm_rf).with(archive_path)
@ -86,7 +86,7 @@ RSpec.describe Gitlab::ImportExport::Saver do
'correlation_id' => anything
)).and_call_original
subject.save
subject.save # rubocop:disable Rails/SaveBang
end
end
end

View File

@ -70,7 +70,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoRestorer do
let!(:snippet_with_repo) { create(:project_snippet, :repository, project: project, author: user) }
let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
let(:snippet_bundle_path) { File.join(bundle_path, "#{snippet_with_repo.hexdigest}.bundle") }
let(:result) { exporter.save }
let(:result) { exporter.save } # rubocop:disable Rails/SaveBang
let(:repository) { snippet.repository }
before do

View File

@ -38,7 +38,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoSaver do
aggregate_failures do
expect(snippet.repository).not_to receive(:bundle_to_disk)
bundler.save
bundler.save # rubocop:disable Rails/SaveBang
expect(Dir.empty?(bundle_path)).to be_truthy
end

View File

@ -64,7 +64,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer do
let!(:snippet2) { create(:project_snippet, project: project, author: user) }
before do
exporter.save
exporter.save # rubocop:disable Rails/SaveBang
expect(File.exist?(bundle_path(snippet1))).to be true
expect(File.exist?(bundle_path(snippet2))).to be false
@ -78,7 +78,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer do
let!(:snippet2) { create(:project_snippet, :repository, project: project, author: user) }
before do
exporter.save
exporter.save # rubocop:disable Rails/SaveBang
expect(File.exist?(bundle_path(snippet1))).to be true
expect(File.exist?(bundle_path(snippet2))).to be true

View File

@ -18,7 +18,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
snippets_dir = ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path)
expect(Dir.exist?(snippets_dir)).to be_falsey
bundler.save
bundler.save # rubocop:disable Rails/SaveBang
expect(Dir.exist?(snippets_dir)).to be_truthy
end
@ -27,7 +27,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
it 'does not perform any action' do
expect(Gitlab::ImportExport::SnippetRepoSaver).not_to receive(:new)
bundler.save
bundler.save # rubocop:disable Rails/SaveBang
end
end
@ -40,7 +40,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
allow(Gitlab::ImportExport::SnippetRepoSaver).to receive(:new).and_return(service)
expect(service).to receive(:save).and_return(true).twice
bundler.save
bundler.save # rubocop:disable Rails/SaveBang
end
context 'when one snippet cannot be saved' do

View File

@ -31,13 +31,13 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do
let(:upload) { create(:upload, :issuable_upload, :with_file, model: project) }
it 'does not cause errors' do
manager.save
manager.save # rubocop:disable Rails/SaveBang
expect(shared.errors).to be_empty
end
it 'copies the file in the correct location when there is an upload' do
manager.save
manager.save # rubocop:disable Rails/SaveBang
expect(File).to exist(exported_file_path)
end
@ -56,7 +56,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do
end
it 'excludes orphaned upload files' do
manager.save
manager.save # rubocop:disable Rails/SaveBang
expect(File).not_to exist(exported_orphan_path)
end
@ -68,7 +68,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do
end
it 'does not cause errors' do
manager.save
manager.save # rubocop:disable Rails/SaveBang
expect(shared.errors).to be_empty
end
@ -84,7 +84,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do
it 'ignores problematic upload and logs exception' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(instance_of(Errno::ENAMETOOLONG), project_id: project.id)
manager.save
manager.save # rubocop:disable Rails/SaveBang
expect(shared.errors).to be_empty
expect(File).not_to exist(exported_file_path)

View File

@ -86,6 +86,7 @@ RSpec.describe API::UsageData do
context 'with unknown event' do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
it 'returns status ok' do

View File

@ -78,7 +78,7 @@ RSpec.describe Packages::Nuget::MetadataExtractionService do
end
context 'with invalid package file id' do
let(:package_file) { OpenStruct.new(id: 555) }
let(:package_file) { double('file', id: 555) }
it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'invalid package file') }
end
@ -109,7 +109,7 @@ RSpec.describe Packages::Nuget::MetadataExtractionService do
context 'with a too big nuspec file' do
before do
allow_any_instance_of(Zip::File).to receive(:glob).and_return([OpenStruct.new(size: 6.megabytes)])
allow_any_instance_of(Zip::File).to receive(:glob).and_return([double('file', size: 6.megabytes)])
end
it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file too big') }

View File

@ -38,6 +38,12 @@ module Tooling
Knapsack.logger.info tests_to_run
Knapsack.logger.info
# Without this guard clause, we're run all the specs instead of none!
if tests_to_run.empty?
Knapsack.logger.info 'No tests to run on this node, exiting.'
return 0
end
Process.wait Process.spawn(*rspec_command)
Process.last_status.exitstatus
end