Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-20 18:14:18 +00:00
parent c17eb7c970
commit 39cb2fdf01
100 changed files with 1228 additions and 804 deletions

View File

@ -335,7 +335,9 @@ rspec fast_spec_helper:
- .rspec-base-pg12
- .rails:rules:ee-and-foss-fast_spec_helper
script:
- bin/rspec spec/fast_spec_helper.rb
- fast_spec_helper_specs=$(git grep -l -E '^require.*fast_spec_helper')
# Load fast_spec_helper as well just in case there are no specs available.
- bin/rspec --dry-run spec/fast_spec_helper.rb $fast_spec_helper_specs
rspec fast_spec_helper minimal:
extends:

View File

@ -939,6 +939,8 @@
- <<: *if-merge-request-labels-run-all-rspec
- <<: *if-merge-request
changes: *core-backend-patterns
- <<: *if-merge-request
changes: *workhorse-patterns
- <<: *if-merge-request
changes: *ci-patterns
- <<: *if-automated-merge-request
@ -962,6 +964,9 @@
- <<: *if-merge-request
changes: *core-backend-patterns
when: never
- <<: *if-merge-request
changes: *workhorse-patterns
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
@ -1131,6 +1136,8 @@
- <<: *if-merge-request-labels-run-all-rspec
- <<: *if-merge-request
changes: *core-backend-patterns
- <<: *if-merge-request
changes: *workhorse-patterns
- <<: *if-merge-request
changes: *ci-patterns
- <<: *if-automated-merge-request
@ -1156,6 +1163,9 @@
- <<: *if-merge-request
changes: *core-backend-patterns
when: never
- <<: *if-merge-request
changes: *workhorse-patterns
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
@ -1283,6 +1293,8 @@
- <<: *if-merge-request-labels-run-all-rspec
- <<: *if-merge-request
changes: *core-backend-patterns
- <<: *if-merge-request
changes: *workhorse-patterns
- <<: *if-merge-request
changes: *ci-patterns
- <<: *if-automated-merge-request
@ -1307,6 +1319,9 @@
- <<: *if-merge-request
changes: *core-backend-patterns
when: never
- <<: *if-merge-request
changes: *workhorse-patterns
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never

View File

@ -39,7 +39,7 @@ They are frequently updated, and everyone should make sure they are aware of the
- [ ] If the deprecation is a [breaking change](https://about.gitlab.com/handbook/product/gitlab-the-product/#breaking-change), add label `breaking change`.
- [ ] Follow the process to [create a deprecation YAML file](https://about.gitlab.com/handbook/marketing/blog/release-posts/#creating-a-deprecation-entry).
- [ ] Add reviewers by the 10th.
- [ ] When ready to be merged and not later than the 15th, add the ~ready label and @ message the TW for final review and merge.
- [ ] When ready to be merged and not later than the 15th, add the `~ready` label and @ message the TW for final review and merge.
## Reviewers
@ -68,7 +68,7 @@ yourself as a reviewer if it's not ready for merge yet.
- [ ] Title:
- Length limit: 7 words (not including articles or prepositions).
- Capitalization: ensure the title is [sentence cased](https://design.gitlab.com/content/punctuation#case).
- No Markdown `` `code` `` formatting in the title, as it doesn't render correctly in the release post.
- Rewrite to exclude the words `deprecation`, `deprecate`, `removal`, and `remove` if necessary.
- [ ] Consistency:
- Ensure that all resources (docs, deprecation, etc.) refer to the feature with the same term / feature name.
- [ ] Content:
@ -98,4 +98,4 @@ must be updated before this MR is merged:
1. Commit the updated file and push the changes.
1. Set the MR to merge when the pipeline succeeds (or merge if the pipeline is already complete).
If you have trouble running the rake task, check the [troubleshooting steps](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecation-rake-task-troubleshooting).
If you have trouble running the Rake task, check the [troubleshooting steps](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecation-rake-task-troubleshooting).

View File

@ -468,7 +468,7 @@ gem 'net-ntp'
# SSH host key support
gem 'net-ssh', '~> 6.0'
gem 'sshkey', '~> 2.0'
gem 'ssh_data', '~> 1.2'
# Required for ED25519 SSH host key support
group :ed25519 do

View File

@ -1232,7 +1232,7 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
sshkey (2.0.0)
ssh_data (1.2.0)
ssrf_filter (1.0.7)
stackprof (0.2.15)
state_machines (0.5.0)
@ -1639,7 +1639,7 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.4)
sprite-factory (~> 1.7)
sprockets (~> 3.7.0)
sshkey (~> 2.0)
ssh_data (~> 1.2)
stackprof (~> 0.2.15)
state_machines-activerecord (~> 0.8.0)
sys-filesystem (~> 1.4.3)

View File

@ -176,8 +176,8 @@ export const I18N_CLUSTERS_EMPTY_STATE = {
export const AGENT_CARD_INFO = {
tabName: 'agent',
title: sprintf(s__('ClusterAgents|%{number} of %{total} agents')),
emptyTitle: s__('ClusterAgents|No agents'),
title: sprintf(s__('ClusterAgents|%{number} of %{total} Agents')),
emptyTitle: s__('ClusterAgents|No Agents'),
tooltip: {
label: s__('ClusterAgents|Recommended'),
title: s__('ClusterAgents|GitLab Agent'),
@ -188,7 +188,7 @@ export const AGENT_CARD_INFO = {
),
link: helpPagePath('user/clusters/agent/index'),
},
actionText: s__('ClusterAgents|Install a new agent'),
actionText: s__('ClusterAgents|Install new Agent'),
footerText: sprintf(s__('ClusterAgents|View all %{number} agents')),
};
@ -226,7 +226,7 @@ export const CLUSTERS_TABS = [
export const CLUSTERS_ACTIONS = {
actionsButton: s__('ClusterAgents|Actions'),
createNewCluster: s__('ClusterAgents|Create a new cluster'),
connectWithAgent: s__('ClusterAgents|Connect with the Agent'),
connectWithAgent: s__('ClusterAgents|Connect with Agent'),
connectExistingCluster: s__('ClusterAgents|Connect with a certificate'),
};

View File

@ -1,5 +1,5 @@
<script>
import { GlBadge } from '@gitlab/ui';
import { GlBadge, GlIcon, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import { s__ } from '~/locale';
import DeploymentStatusBadge from './deployment_status_badge.vue';
@ -7,6 +7,10 @@ export default {
components: {
DeploymentStatusBadge,
GlBadge,
GlIcon,
},
directives: {
GlTooltip,
},
props: {
deployment: {
@ -23,9 +27,13 @@ export default {
status() {
return this.deployment?.status;
},
iid() {
return this.deployment?.iid;
},
},
i18n: {
latestBadge: s__('Deployment|Latest Deployed'),
deploymentId: s__('Deployment|Deployment ID'),
},
};
</script>
@ -33,5 +41,13 @@ export default {
<div class="gl-display-flex gl-align-items-center gl-gap-x-3">
<deployment-status-badge v-if="status" :status="status" />
<gl-badge v-if="latest" variant="info">{{ $options.i18n.latestBadge }}</gl-badge>
<div
v-if="iid"
v-gl-tooltip
:title="$options.i18n.deploymentId"
:aria-label="$options.i18n.deploymentId"
>
<gl-icon ref="deployment-iid-icon" name="deployments" /> {{ iid }}
</div>
</div>
</template>

View File

@ -20,8 +20,10 @@ class AbuseReportsController < ApplicationController
message = _("Thank you for your report. A GitLab administrator will look into it shortly.")
redirect_to root_path, notice: message
else
elsif report_params[:user_id].present?
render :new
else
redirect_to root_path, alert: _("Cannot create the abuse report. The reported user was invalid. Please try again or contact support.")
end
end

View File

@ -17,7 +17,10 @@ module IssuableActions
def show
respond_to do |format|
format.html do
@show_crm_contacts = issuable.is_a?(Issue) && can?(current_user, :read_crm_contact, issuable.project.group) # rubocop:disable Gitlab/ModuleWithInstanceVariables
@show_crm_contacts = issuable.is_a?(Issue) && # rubocop:disable Gitlab/ModuleWithInstanceVariables
can?(current_user, :read_crm_contact, issuable.project.group) &&
CustomerRelations::Contact.exists_for_group?(issuable.project.group)
@issuable_sidebar = serializer.represent(issuable, serializer: 'sidebar') # rubocop:disable Gitlab/ModuleWithInstanceVariables
render 'show'
end

View File

@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
VERSION = '0.36.0'
VERSION = '0.37.1'
self.table_name = 'clusters_applications_runners'

View File

@ -4,11 +4,6 @@
# implements support for persisting the necessary data in a `credentials`
# serialized attribute. It also needs an `url` method to be defined
module MirrorAuthentication
SSH_PRIVATE_KEY_OPTS = {
type: 'RSA',
bits: 4096
}.freeze
extend ActiveSupport::Concern
included do
@ -84,10 +79,10 @@ module MirrorAuthentication
return if ssh_private_key.blank?
comment = "git@#{::Gitlab.config.gitlab.host}"
::SSHKey.new(ssh_private_key, comment: comment).ssh_public_key
SSHData::PrivateKey.parse(ssh_private_key).first.public_key.openssh(comment: comment)
end
def generate_ssh_private_key!
self.ssh_private_key = ::SSHKey.generate(SSH_PRIVATE_KEY_OPTS).private_key
self.ssh_private_key = SSHData::PrivateKey::RSA.generate(4096).openssl.to_pem
end
end

View File

@ -33,6 +33,12 @@ class CustomerRelations::Contact < ApplicationRecord
.pluck(:id)
end
def self.exists_for_group?(group)
return false unless group
exists?(group_id: group.self_and_ancestor_ids)
end
private
def validate_email_format

View File

@ -147,7 +147,7 @@ class InstanceConfiguration
end
def ssh_algorithm_sha256(ssh_file_content)
Gitlab::SSHPublicKey.new(ssh_file_content).fingerprint('SHA256')
Gitlab::SSHPublicKey.new(ssh_file_content).fingerprint_sha256
end
def application_settings

View File

@ -130,7 +130,7 @@ class Key < ApplicationRecord
return unless public_key.valid?
self.fingerprint_md5 = public_key.fingerprint
self.fingerprint_sha256 = public_key.fingerprint("SHA256").gsub("SHA256:", "")
self.fingerprint_sha256 = public_key.fingerprint_sha256.gsub("SHA256:", "")
end
def key_meets_restrictions

View File

@ -140,6 +140,10 @@ module Projects
destroy_project_bots!
destroy_ci_records!
if ::Feature.enabled?(:extract_mr_diff_commit_deletions, default_enabled: :yaml)
destroy_mr_diff_commits!
end
# Rails attempts to load all related records into memory before
# destroying: https://github.com/rails/rails/issues/22510
# This ensures we delete records in batches.
@ -154,6 +158,33 @@ module Projects
log_info("Attempting to destroy #{project.full_path} (#{project.id})")
end
# Projects will have at least one merge_request_diff_commit for every commit
# contained in every MR, which deleting via `project.destroy!` and
# cascading deletes may exceed statement timeouts, causing failures.
# (see https://gitlab.com/gitlab-org/gitlab/-/issues/346166)
#
# rubocop: disable CodeReuse/ActiveRecord
def destroy_mr_diff_commits!
mr_batch_size = 100
delete_batch_size = 1000
project.merge_requests.each_batch(column: :iid, of: mr_batch_size) do |relation_ids|
loop do
inner_query = MergeRequestDiffCommit
.select(:merge_request_diff_id, :relative_order)
.where(merge_request_diff_id: MergeRequestDiff.where(merge_request_id: relation_ids).select(:id))
.limit(delete_batch_size)
deleted_rows = MergeRequestDiffCommit
.where('(merge_request_diff_commits.merge_request_diff_id, merge_request_diff_commits.relative_order) IN (?)', inner_query)
.delete_all
break if deleted_rows == 0
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
def destroy_ci_records!
project.all_pipelines.find_each(batch_size: BATCH_SIZE) do |pipeline| # rubocop: disable CodeReuse/ActiveRecord
# Destroy artifacts, then builds, then pipelines

View File

@ -15,6 +15,7 @@
= f.label :max_attachment_size, _('Maximum attachment size (MB)'), class: 'label-bold'
= f.number_field :max_attachment_size, class: 'form-control gl-form-input', title: _('Maximum size of individual attachments in comments.'), data: { toggle: 'tooltip', container: 'body' }
= render 'admin/application_settings/repository_size_limit_setting_registration_features_cta', form: f
= render_if_exists 'admin/application_settings/repository_size_limit_setting', form: f
.form-group

View File

@ -0,0 +1,8 @@
- return unless registration_features_can_be_prompted?
.form-group
= form.label :disabled_repository_size_limit, class: 'label-bold' do
= _('Size limit per repository (MB)')
= form.number_field :disabled_repository_size_limit, value: '', class: 'form-control gl-form-input', disabled: true
%span.form-text.text-muted
= render 'shared/registration_features_discovery_message'

View File

@ -18,6 +18,7 @@
= f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
.form-text.text-muted= _('Optional.')
= render 'shared/repository_size_limit_setting_registration_features_cta', form: f
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
.form-group.gl-mt-3.gl-mb-6

View File

@ -27,6 +27,7 @@
.row= render_if_exists 'projects/classification_policy_settings', f: f
= render 'shared/repository_size_limit_setting_registration_features_cta', form: f
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
.form-group.gl-mt-3.gl-mb-3

View File

@ -1,9 +1,8 @@
- feature_title = local_assigns.fetch(:feature_title, s_('RegistrationFeatures|use this feature'))
- registration_features_docs_path = help_page_path('development/service_ping/index.md', anchor: 'registration-features-program')
- service_ping_settings_path = metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings')
- registration_features_link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: registration_features_docs_path }
%div
%span= sprintf(s_('RegistrationFeatures|Want to %{feature_title} for free?'), { feature_title: feature_title })
- if Gitlab.ee?
= link_to s_('RegistrationFeatures|Enable Service Ping and register for this feature.'), service_ping_settings_path
= sprintf(s_('RegistrationFeatures|Read more about the %{linkStart}%{label}%{linkEnd}.') , { linkStart: "<a href=\"#{registration_features_docs_path}\" target=\"_blank\">", label: s_('RegistrationFeatures|Registration Features Program'), linkEnd: "</a>" }).html_safe
= render_if_exists 'shared/registration_features_discovery_settings_link'
= html_escape(s_('RegistrationFeatures|Read more about the %{link_start}Registration Features Program%{link_end}.')) % { link_start: registration_features_link_start, link_end: '</a>'.html_safe }

View File

@ -0,0 +1,9 @@
- return unless registration_features_can_be_prompted?
.row
.form-group.col-md-9
= form.label :disabled_repository_size_limit, class: 'label-bold' do
= _('Repository size limit (MB)')
= form.number_field :disabled_repository_size_limit, value: '', class: 'form-control', disabled: true
%span.form-text.text-muted
= render 'shared/registration_features_discovery_message'

View File

@ -0,0 +1,8 @@
---
name: extract_mr_diff_commit_deletions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75963
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347073
milestone: '14.6'
type: development
group: group::code review
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: rate_limit_frontend_requests
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78082
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350623
milestone: '14.8'
type: development
group: group::integrations
default_enabled: false

View File

@ -1,4 +1,4 @@
- name: "NFS for Git repository storage deprecated" # The name of the feature to be deprecated
- name: "NFS for Git repository storage" # The name of the feature to be deprecated
announcement_milestone: "14.0" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-06-22" # The date of the milestone release when this feature was first announced as deprecated
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Converting an instance (shared) runner to a project (specific) runner is deprecated"
- name: "Converting an instance (shared) runner to a project (specific) runner"
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22"
removal_milestone: "15.0" # the milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Removal of `defaultMergeCommitMessageWithDescription` GraphQL API field" # The name of the feature to be deprecated
- name: "`defaultMergeCommitMessageWithDescription` GraphQL API field" # The name of the feature to be deprecated
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Deprecate support for SLES 12 SP2" # The name of the feature to be deprecated
- name: "Support for SLES 12 SP2" # The name of the feature to be deprecated
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22"
removal_milestone: "15.0" # the milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Deprecate `Versions` on base `PackageType`"
- name: "`Versions` on base `PackageType`"
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Removal of `promote-db` command from `gitlab-ctl`" # The name of the feature to be deprecated
- name: "`promote-db` command from `gitlab-ctl`" # The name of the feature to be deprecated
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Removal of `promote-to-primary-node` command from `gitlab-ctl`" # The name of the feature to be deprecated
- name: "`promote-to-primary-node` command from `gitlab-ctl`" # The name of the feature to be deprecated
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Remove the `:dependency_proxy_for_private_groups` feature flag" # The name of the feature to be deprecated
- name: "`dependency_proxy_for_private_groups` feature flag" # The name of the feature to be deprecated
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Remove the `pipelines` field from the `version` field" # The name of the feature to be deprecated
- name: "`pipelines` field from the `version` field" # The name of the feature to be deprecated
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Remove `type` and `types` keyword in CI/CD configuration" # The name of the feature to be deprecated
- name: "`type` and `types` keyword in CI/CD configuration" # The name of the feature to be deprecated
announcement_milestone: "14.6" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-12-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Deprecate legacy approval status names from License Compliance API" # The name of the feature to be deprecated
- name: "Legacy approval status names from License Compliance API" # The name of the feature to be deprecated
announcement_milestone: "14.6" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-12-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Deprecation of bundler-audit Dependency Scanning tool" # The name of the feature to be deprecated
- name: "bundler-audit Dependency Scanning tool" # The name of the feature to be deprecated
announcement_milestone: "14.6" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-12-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Deprecate `pipelines` fields in the Package GraphQL types"
- name: "`pipelines` fields in the Package GraphQL types"
announcement_milestone: "14.6" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-12-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -1,4 +1,4 @@
- name: "Deprecation of Runner status `not_connected` API value"
- name: "Runner status `not_connected` API value"
announcement_milestone: "14.6" # The milestone when this feature was first announced as deprecated.
removal_milestone: "15.0" # the milestone when this feature is planned to be removed
removal_date: "2022-05-22"

View File

@ -1,4 +1,4 @@
- name: "Removal of `artifacts:report:cobertura` keyword"
- name: "`artifacts:report:cobertura` keyword"
announcement_milestone: "14.7"
announcement_date: "2022-01-22"
removal_milestone: "15.0"

View File

@ -1,4 +1,4 @@
- name: "Removal of Static Site Editor" # The name of the feature to be deprecated
- name: "Static Site Editor" # The name of the feature to be deprecated
announcement_milestone: "14.7" # The milestone when this feature was first announced as deprecated.
announcement_date: "2022-01-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed

View File

@ -13,4 +13,3 @@
documentation_url: https://docs.gitlab.com/ee/operations/tracing.html#tracing # (optional) This is a link to the current documentation page
image_url: # (optional) This is a link to a thumbnail image depicting the feature
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg

View File

@ -38,7 +38,7 @@ For deprecation reviewers (Technical Writers only):
## 14.0
### NFS for Git repository storage deprecated
### NFS for Git repository storage
WARNING:
This feature will be changed or removed in 15.0
@ -160,7 +160,7 @@ For a more robust, secure, forthcoming, and reliable integration with Kubernetes
**Planned removal milestone: 15.0 (2022-05-22)**
### Converting an instance (shared) runner to a project (specific) runner is deprecated
### Converting an instance (shared) runner to a project (specific) runner
WARNING:
This feature will be changed or removed in 15.0
@ -172,32 +172,6 @@ In GitLab 15.0, we will remove the feature that enables you to convert an instan
**Planned removal milestone: 15.0 (2022-05-22)**
### Deprecate `Versions` on base `PackageType`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
As part of the work to create a [Package Registry GraphQL API](https://gitlab.com/groups/gitlab-org/-/epics/6318), the Package group deprecated the `Version` type for the basic `PackageType` type and moved it to [`PackageDetailsType`](https://docs.gitlab.com/ee/api/graphql/reference/index.html#packagedetailstype).
In milestone 15.0, we will completely remove `Version` from `PackageType`.
**Planned removal milestone: 15.0 (2022-05-22)**
### Deprecate support for SLES 12 SP2
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
Long term service and support (LTSS) for SUSE Linux Enterprise Server (SLES) 12 SP2 [ended on March 31, 2021](https://www.suse.com/lifecycle/). The CA certificates on SP2 include the expired DST root certificate, and it's not getting new CA certificate package updates. We have implemented some [workarounds](https://gitlab.com/gitlab-org/gitlab-omnibus-builder/-/merge_requests/191), but we will not be able to continue to keep the build running properly.
**Planned removal milestone: 15.0 (2022-05-22)**
### Known host required for GitLab Runner SSH executor
WARNING:
@ -258,7 +232,7 @@ When checking if a runner is `paused`, API users are advised to check the boolea
**Planned removal milestone: 15.0 (2022-05-22)**
### Removal of `defaultMergeCommitMessageWithDescription` GraphQL API field
### Support for SLES 12 SP2
WARNING:
This feature will be changed or removed in 15.0
@ -266,62 +240,7 @@ as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#brea
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
The GraphQL API field `defaultMergeCommitMessageWithDescription` has been deprecated and will be removed in GitLab 15.0. For projects with a commit message template set, it will ignore the template.
**Planned removal milestone: 15.0 (2022-05-22)**
### Removal of `promote-db` command from `gitlab-ctl`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
In GitLab 14.5, we introduced the command `gitlab-ctl promote` to promote any Geo secondary node to a primary during a failover. This command replaces `gitlab-ctl promote-db` which is used to promote database nodes in multi-node Geo secondary sites. `gitlab-ctl promote-db` will continue to function as-is and be available until GitLab 15.0. We recommend that Geo customers begin testing the new `gitlab-ctl promote` command in their staging environments and incorporating the new command in their failover procedures.
**Planned removal milestone: 15.0 (2022-05-22)**
### Removal of `promote-to-primary-node` command from `gitlab-ctl`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
In GitLab 14.5, we introduced the command `gitlab-ctl promote` to promote any Geo secondary node to a primary during a failover. This command replaces `gitlab-ctl promote-to-primary-node` which was only usable for single-node Geo sites. `gitlab-ctl promote-to-primary-node` will continue to function as-is and be available until GitLab 15.0. We recommend that Geo customers begin testing the new `gitlab-ctl promote` command in their staging environments and incorporating the new command in their failover procedures.
**Planned removal milestone: 15.0 (2022-05-22)**
### Remove the `:dependency_proxy_for_private_groups` feature flag
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
We added a feature flag because [GitLab-#11582](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) changed how public groups use the Dependency Proxy. Prior to this change, you could use the Dependency Proxy without authentication. The change requires authentication to use the Dependency Proxy.
In milestone 15.0, we will remove the feature flag entirely. Moving forward, you must authenticate when using the Dependency Proxy.
**Planned removal milestone: 15.0 (2022-05-22)**
### Remove the `pipelines` field from the `version` field
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
In GraphQL, there are two `pipelines` fields that you can use in a [`PackageDetailsType`](https://docs.gitlab.com/ee/api/graphql/reference/#packagedetailstype) to get the pipelines for package versions:
- The `versions` field's `pipelines` field. This returns all the pipelines associated with all the package's versions, which can pull an unbounded number of objects in memory and create performance concerns.
- The `pipelines` field of a specific `version`. This returns only the pipelines associated with that single package version.
To mitigate possible performance problems, we will remove the `versions` field's `pipelines` field in milestone 15.0. Although you will no longer be able to get all pipelines for all versions of a package, you can still get the pipelines of a single version through the remaining `pipelines` field for that version.
Long term service and support (LTSS) for SUSE Linux Enterprise Server (SLES) 12 SP2 [ended on March 31, 2021](https://www.suse.com/lifecycle/). The CA certificates on SP2 include the expired DST root certificate, and it's not getting new CA certificate package updates. We have implemented some [workarounds](https://gitlab.com/gitlab-org/gitlab-omnibus-builder/-/merge_requests/191), but we will not be able to continue to keep the build running properly.
**Planned removal milestone: 15.0 (2022-05-22)**
@ -353,6 +272,87 @@ If you monitor Value Stream Analytics metrics and rely on the date filter, to av
**Planned removal milestone: 15.0 (2022-05-22)**
### `Versions` on base `PackageType`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
As part of the work to create a [Package Registry GraphQL API](https://gitlab.com/groups/gitlab-org/-/epics/6318), the Package group deprecated the `Version` type for the basic `PackageType` type and moved it to [`PackageDetailsType`](https://docs.gitlab.com/ee/api/graphql/reference/index.html#packagedetailstype).
In milestone 15.0, we will completely remove `Version` from `PackageType`.
**Planned removal milestone: 15.0 (2022-05-22)**
### `defaultMergeCommitMessageWithDescription` GraphQL API field
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
The GraphQL API field `defaultMergeCommitMessageWithDescription` has been deprecated and will be removed in GitLab 15.0. For projects with a commit message template set, it will ignore the template.
**Planned removal milestone: 15.0 (2022-05-22)**
### `dependency_proxy_for_private_groups` feature flag
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
We added a feature flag because [GitLab-#11582](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) changed how public groups use the Dependency Proxy. Prior to this change, you could use the Dependency Proxy without authentication. The change requires authentication to use the Dependency Proxy.
In milestone 15.0, we will remove the feature flag entirely. Moving forward, you must authenticate when using the Dependency Proxy.
**Planned removal milestone: 15.0 (2022-05-22)**
### `pipelines` field from the `version` field
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
In GraphQL, there are two `pipelines` fields that you can use in a [`PackageDetailsType`](https://docs.gitlab.com/ee/api/graphql/reference/#packagedetailstype) to get the pipelines for package versions:
- The `versions` field's `pipelines` field. This returns all the pipelines associated with all the package's versions, which can pull an unbounded number of objects in memory and create performance concerns.
- The `pipelines` field of a specific `version`. This returns only the pipelines associated with that single package version.
To mitigate possible performance problems, we will remove the `versions` field's `pipelines` field in milestone 15.0. Although you will no longer be able to get all pipelines for all versions of a package, you can still get the pipelines of a single version through the remaining `pipelines` field for that version.
**Planned removal milestone: 15.0 (2022-05-22)**
### `promote-db` command from `gitlab-ctl`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
In GitLab 14.5, we introduced the command `gitlab-ctl promote` to promote any Geo secondary node to a primary during a failover. This command replaces `gitlab-ctl promote-db` which is used to promote database nodes in multi-node Geo secondary sites. `gitlab-ctl promote-db` will continue to function as-is and be available until GitLab 15.0. We recommend that Geo customers begin testing the new `gitlab-ctl promote` command in their staging environments and incorporating the new command in their failover procedures.
**Planned removal milestone: 15.0 (2022-05-22)**
### `promote-to-primary-node` command from `gitlab-ctl`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
In GitLab 14.5, we introduced the command `gitlab-ctl promote` to promote any Geo secondary node to a primary during a failover. This command replaces `gitlab-ctl promote-to-primary-node` which was only usable for single-node Geo sites. `gitlab-ctl promote-to-primary-node` will continue to function as-is and be available until GitLab 15.0. We recommend that Geo customers begin testing the new `gitlab-ctl promote` command in their staging environments and incorporating the new command in their failover procedures.
**Planned removal milestone: 15.0 (2022-05-22)**
### openSUSE Leap 15.2 packages
Distribution support and security updates for openSUSE Leap 15.2 are [ending December 2021](https://en.opensuse.org/Lifetime#openSUSE_Leap).
@ -389,21 +389,7 @@ In GitLab 15.0 we are going to limit the number of characters in CI/CD job names
**Planned removal milestone: 15.0 (2022-05-22)**
### Deprecate `pipelines` fields in the Package GraphQL types
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
As part of the work to create a [Package Registry GraphQL API](https://gitlab.com/groups/gitlab-org/-/epics/6318), the Package group deprecated the `pipelines` fields in all Package-related GraphQL types. As of GitLab 14.6, the `pipelines` field is deprecated in [`Package`](https://docs.gitlab.com/ee/api/graphql/reference/index.html#package) and [`PackageDetailsType`](https://docs.gitlab.com/ee/api/graphql/reference/index.html#packagedetailstype) due to scalability and performance concerns.
In milestone 15.0, we will completely remove `pipelines` from `Package` and `PackageDetailsType`. You can follow and contribute to work on a replacement in the epic [GitLab-#7214](https://gitlab.com/groups/gitlab-org/-/epics/7214).
**Planned removal milestone: 15.0 (2022-05-22)**
### Deprecate legacy approval status names from License Compliance API
### Legacy approval status names from License Compliance API
WARNING:
This feature will be changed or removed in 15.0
@ -417,7 +403,7 @@ If you are using our License Compliance API you should stop using the `approved`
**Planned removal milestone: 15.0 (2022-05-22)**
### Deprecation of Runner status `not_connected` API value
### Runner status `not_connected` API value
WARNING:
This feature will be changed or removed in 15.0
@ -432,7 +418,7 @@ Runners that have never contacted the GitLab instance will also return `stale` i
**Planned removal milestone: 15.0 (2022-05-22)**
### Deprecation of bundler-audit Dependency Scanning tool
### `pipelines` fields in the Package GraphQL types
WARNING:
This feature will be changed or removed in 15.0
@ -440,13 +426,13 @@ as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#brea
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
As of 14.6 bundler-audit is being deprecated from Dependency Scanning. It will continue to be in our CI/CD template while deprecated. We are removing bundler-audit from Dependency Scanning on May 22, 2022 in 15.0. After this removal Ruby scanning functionality will not be affected as it is still being covered by Gemnasium.
As part of the work to create a [Package Registry GraphQL API](https://gitlab.com/groups/gitlab-org/-/epics/6318), the Package group deprecated the `pipelines` fields in all Package-related GraphQL types. As of GitLab 14.6, the `pipelines` field is deprecated in [`Package`](https://docs.gitlab.com/ee/api/graphql/reference/index.html#package) and [`PackageDetailsType`](https://docs.gitlab.com/ee/api/graphql/reference/index.html#packagedetailstype) due to scalability and performance concerns.
If you have explicitly excluded bundler-audit using DS_EXCLUDED_ANALYZERS you will need to clean up (remove the reference) in 15.0. If you have customized your pipeline's Dependency Scanning configuration, for example to edit the `bundler-audit-dependency_scanning` job, you will want to switch to gemnasium-dependency_scanning before removal in 15.0, to prevent your pipeline from failing. If you have not used the DS_EXCLUDED_ANALYZERS to reference bundler-audit, or customized your template specifically for bundler-audit, you will not need to take action.
In milestone 15.0, we will completely remove `pipelines` from `Package` and `PackageDetailsType`. You can follow and contribute to work on a replacement in the epic [GitLab-#7214](https://gitlab.com/groups/gitlab-org/-/epics/7214).
**Planned removal milestone: 15.0 (2022-05-22)**
### Remove `type` and `types` keyword in CI/CD configuration
### `type` and `types` keyword in CI/CD configuration
WARNING:
This feature will be changed or removed in 15.0
@ -472,6 +458,20 @@ which isn't being used in GitLab anymore.
**Planned removal milestone: 15.0 (2022-05-22)**
### bundler-audit Dependency Scanning tool
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
As of 14.6 bundler-audit is being deprecated from Dependency Scanning. It will continue to be in our CI/CD template while deprecated. We are removing bundler-audit from Dependency Scanning on May 22, 2022 in 15.0. After this removal Ruby scanning functionality will not be affected as it is still being covered by Gemnasium.
If you have explicitly excluded bundler-audit using DS_EXCLUDED_ANALYZERS you will need to clean up (remove the reference) in 15.0. If you have customized your pipeline's Dependency Scanning configuration, for example to edit the `bundler-audit-dependency_scanning` job, you will want to switch to gemnasium-dependency_scanning before removal in 15.0, to prevent your pipeline from failing. If you have not used the DS_EXCLUDED_ANALYZERS to reference bundler-audit, or customized your template specifically for bundler-audit, you will not need to take action.
**Planned removal milestone: 15.0 (2022-05-22)**
## 14.7
### Container scanning schemas below 14.0.0
@ -604,21 +604,6 @@ It is now considered deprecated, and will be removed in GitLab 15.0.
**Planned removal milestone: 15.0 (2022-05-22)**
### Removal of Static Site Editor
The Static Site Editor will no longer be available starting in GitLab 15.0. Improvements to the Markdown editing experience across GitLab will deliver smiliar benefit but with a wider reach. Incoming requests to the Static Site Editor will be redirected to the Web IDE. Current users of the Static Site Editor can view the [documentation](https://docs.gitlab.com/ee/user/project/static_site_editor/) for more information, including how to remove the configuration files from existing projects.
**Planned removal milestone: 15.0 (2022-05-22)**
### Removal of `artifacts:report:cobertura` keyword
Currently, test coverage visualizations in GitLab only support Cobertura reports. Starting 15.0, the
`artifacts:report:cobertura` keyword will be replaced by
[`artifacts:reports:coverage_report`](https://gitlab.com/gitlab-org/gitlab/-/issues/344533). Cobertura will be the
only supported report file in 15.0, but this is the first step towards GitLab supporting other report types.
**Planned removal milestone: 15.0 (2022-05-22)**
### SAST schemas below 14.0.0
[SAST report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/releases)
@ -685,6 +670,12 @@ to serve the Sidekiq metrics, similar to the way Sidekiq will behave in 15.0.
**Planned removal milestone: 15.0 (2022-05-22)**
### Static Site Editor
The Static Site Editor will no longer be available starting in GitLab 15.0. Improvements to the Markdown editing experience across GitLab will deliver smiliar benefit but with a wider reach. Incoming requests to the Static Site Editor will be redirected to the Web IDE. Current users of the Static Site Editor can view the [documentation](https://docs.gitlab.com/ee/user/project/static_site_editor/) for more information, including how to remove the configuration files from existing projects.
**Planned removal milestone: 15.0 (2022-05-22)**
### Tracing in GitLab
WARNING:
@ -697,6 +688,15 @@ Tracing in GitLab is an integration with Jaeger, an open-source end-to-end distr
**Planned removal milestone: 15.0 (2022-05-22)**
### `artifacts:report:cobertura` keyword
Currently, test coverage visualizations in GitLab only support Cobertura reports. Starting 15.0, the
`artifacts:report:cobertura` keyword will be replaced by
[`artifacts:reports:coverage_report`](https://gitlab.com/gitlab-org/gitlab/-/issues/344533). Cobertura will be the
only supported report file in 15.0, but this is the first step towards GitLab supporting other report types.
**Planned removal milestone: 15.0 (2022-05-22)**
### merged_by API field
The `merged_by` field in the [merge request API](https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-requests) is being deprecated and will be removed in GitLab 15.0. This field is being replaced with the `merge_user` field (already present in GraphQL) which more correctly identifies who merged a merge request when performing actions (merge when pipeline succeeds, add to merge train) other than a simple merge.

View File

@ -22,6 +22,10 @@ NOTE:
By default, all Git operations are first tried unauthenticated. Because of this, HTTP Git operations
may trigger the rate limits configured for unauthenticated requests.
NOTE:
The rate limits for API requests don't affect requests made by the frontend, as these are always
counted as web traffic.
## Enable unauthenticated API request rate limit
To enable the unauthenticated request rate limit:

View File

@ -33,7 +33,7 @@ Dependency Scanning is pre-configured with a set of **default images** that are
maintained by GitLab, but users can also integrate their own **custom images**.
WARNING:
The `bundler-audit` analyzer is deprecated and will be removed in GitLab 15.0 since it duplicates the functionality of the `gemnasium` analyzer. For more information, read the [deprecation announcement](../../../update/deprecations.md#deprecation-of-bundler-audit-dependency-scanning-tool).
The `bundler-audit` analyzer is deprecated and will be removed in GitLab 15.0 since it duplicates the functionality of the `gemnasium` analyzer. For more information, read the [deprecation announcement](../../../update/deprecations.md#bundler-audit-dependency-scanning-tool).
## Official default analyzers

View File

@ -21,7 +21,7 @@ module Gitlab
validates :config, allowed_keys: ALLOWED_KEYS
end
entry :before_script, Entry::Script,
entry :before_script, Entry::Commands,
description: 'Script that will be executed before each job.',
inherit: true
@ -33,7 +33,7 @@ module Gitlab
description: 'Docker images that will be linked to the container.',
inherit: true
entry :after_script, Entry::Script,
entry :after_script, Entry::Commands,
description: 'Script that will be executed after each job.',
inherit: true

View File

@ -45,7 +45,7 @@ module Gitlab
end
end
entry :before_script, Entry::Script,
entry :before_script, Entry::Commands,
description: 'Global before script overridden in this job.',
inherit: true
@ -55,9 +55,10 @@ module Gitlab
entry :type, Entry::Stage,
description: 'Deprecated: stage this job will be executed into.',
inherit: false
inherit: false,
deprecation: { deprecated: '9.0', warning: '14.8', removed: '15.0' }
entry :after_script, Entry::Script,
entry :after_script, Entry::Commands,
description: 'Commands that will be executed when finishing job.',
inherit: true
@ -134,8 +135,11 @@ module Gitlab
def compose!(deps = nil)
super do
# The type keyword will be removed in 15.0:
# https://gitlab.com/gitlab-org/gitlab/-/issues/346823
if type_defined? && !stage_defined?
@entries[:stage] = @entries[:type]
log_and_warn_deprecated_entry(@entries[:type])
end
@entries.delete(:type)

View File

@ -32,7 +32,7 @@ module Gitlab
description: 'List of external YAML files to include.',
reserved: true
entry :before_script, Entry::Script,
entry :before_script, Entry::Commands,
description: 'Script that will be executed before each job.',
reserved: true
@ -44,7 +44,7 @@ module Gitlab
description: 'Docker images that will be linked to the container.',
reserved: true
entry :after_script, Entry::Script,
entry :after_script, Entry::Commands,
description: 'Script that will be executed after each job.',
reserved: true
@ -60,7 +60,7 @@ module Gitlab
entry :types, Entry::Stages,
description: 'Deprecated: stages for this pipeline.',
reserved: true,
deprecation: { deprecated: '9.0', warning: '14.8', removed: '15.0', documentation: 'https://docs.gitlab.com/ee/ci/yaml/#deprecated-keywords' }
deprecation: { deprecated: '9.0', warning: '14.8', removed: '15.0' }
entry :cache, Entry::Caches,
description: 'Configure caching between build jobs.',
@ -122,6 +122,8 @@ module Gitlab
##
# Deprecated `:types` key workaround - if types are defined and
# stages are not defined we use types definition as stages.
# This keyword will be removed in 15.0:
# https://gitlab.com/gitlab-org/gitlab/-/issues/346823
#
if types_defined?
@entries[:stages] = @entries[:types] unless stages_defined?

View File

@ -1,24 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
##
# Entry that represents a script.
#
class Script < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
validations do
validates :config, nested_array_of_strings: true
end
def value
config.flatten(1)
end
end
end
end
end
end

View File

@ -278,20 +278,6 @@ module Gitlab
end
end
class NestedArrayOfStringsValidator < ArrayOfStringsOrStringValidator
def validate_each(record, attribute, value)
unless validate_nested_array_of_strings(value)
record.errors.add(attribute, 'should be an array containing strings and arrays of strings')
end
end
private
def validate_nested_array_of_strings(values)
values.is_a?(Array) && values.all? { |element| validate_array_of_strings_or_string(element) }
end
end
class StringOrNestedArrayOfStringsValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
include NestedArrayHelpers

View File

@ -290,7 +290,8 @@ module Gitlab
params 'contact@example.com person@example.org'
types Issue
condition do
current_user.can?(:set_issue_crm_contacts, quick_action_target)
current_user.can?(:set_issue_crm_contacts, quick_action_target) &&
CustomerRelations::Contact.exists_for_group?(quick_action_target.project.group)
end
execution_message do
_('One or more contacts were successfully added.')
@ -304,7 +305,8 @@ module Gitlab
params 'contact@example.com person@example.org'
types Issue
condition do
current_user.can?(:set_issue_crm_contacts, quick_action_target)
current_user.can?(:set_issue_crm_contacts, quick_action_target) &&
CustomerRelations::Contact.exists_for_group?(quick_action_target.project.group)
end
execution_message do
_('One or more contacts were successfully removed.')

View File

@ -3,6 +3,8 @@
module Gitlab
module RackAttack
module Request
include ::Gitlab::Utils::StrongMemoize
FILES_PATH_REGEX = %r{^/api/v\d+/projects/[^/]+/repository/files/.+}.freeze
GROUP_PATH_REGEX = %r{^/api/v\d+/groups/[^/]+/?$}.freeze
@ -30,15 +32,15 @@ module Gitlab
end
def api_internal_request?
path =~ %r{^/api/v\d+/internal/}
path.match?(%r{^/api/v\d+/internal/})
end
def health_check_request?
path =~ %r{^/-/(health|liveness|readiness|metrics)}
path.match?(%r{^/-/(health|liveness|readiness|metrics)})
end
def container_registry_event?
path =~ %r{^/api/v\d+/container_registry_event/}
path.match?(%r{^/api/v\d+/container_registry_event/})
end
def product_analytics_collector_request?
@ -58,7 +60,7 @@ module Gitlab
end
def protected_path_regex
path =~ protected_paths_regex
path.match?(protected_paths_regex)
end
def throttle?(throttle, authenticated:)
@ -70,6 +72,7 @@ module Gitlab
def throttle_unauthenticated_api?
api_request? &&
!should_be_skipped? &&
!frontend_request? &&
!throttle_unauthenticated_packages_api? &&
!throttle_unauthenticated_files_api? &&
!throttle_unauthenticated_deprecated_api? &&
@ -78,7 +81,7 @@ module Gitlab
end
def throttle_unauthenticated_web?
web_request? &&
(web_request? || frontend_request?) &&
!should_be_skipped? &&
# TODO: Column will be renamed in https://gitlab.com/gitlab-org/gitlab/-/issues/340031
Gitlab::Throttle.settings.throttle_unauthenticated_enabled &&
@ -87,6 +90,7 @@ module Gitlab
def throttle_authenticated_api?
api_request? &&
!frontend_request? &&
!throttle_authenticated_packages_api? &&
!throttle_authenticated_files_api? &&
!throttle_authenticated_deprecated_api? &&
@ -94,7 +98,7 @@ module Gitlab
end
def throttle_authenticated_web?
web_request? &&
(web_request? || frontend_request?) &&
!throttle_authenticated_git_lfs? &&
Gitlab::Throttle.settings.throttle_authenticated_web_enabled
end
@ -178,15 +182,26 @@ module Gitlab
end
def packages_api_path?
path =~ ::Gitlab::Regex::Packages::API_PATH_REGEX
path.match?(::Gitlab::Regex::Packages::API_PATH_REGEX)
end
def git_lfs_path?
path =~ Gitlab::PathRegex.repository_git_lfs_route_regex
path.match?(Gitlab::PathRegex.repository_git_lfs_route_regex)
end
def files_api_path?
path =~ FILES_PATH_REGEX
path.match?(FILES_PATH_REGEX)
end
def frontend_request?
return false unless Feature.enabled?(:rate_limit_frontend_requests, default_enabled: :yaml)
strong_memoize(:frontend_request) do
next false unless env.include?('HTTP_X_CSRF_TOKEN') && session.include?(:_csrf_token)
# CSRF tokens are not verified for GET/HEAD requests, so we pretend that we always have a POST request.
Gitlab::RequestForgeryProtection.verified?(env.merge('REQUEST_METHOD' => 'POST'))
end
end
def deprecated_api_request?
@ -195,7 +210,7 @@ module Gitlab
with_projects = params['with_projects']
with_projects = true if with_projects.blank?
path =~ GROUP_PATH_REGEX && Gitlab::Utils.to_boolean(with_projects)
path.match?(GROUP_PATH_REGEX) && Gitlab::Utils.to_boolean(with_projects)
end
end
end

View File

@ -7,10 +7,10 @@ module Gitlab
# See https://man.openbsd.org/sshd#AUTHORIZED_KEYS_FILE_FORMAT for the list of
# supported algorithms.
TECHNOLOGIES = [
Technology.new(:rsa, OpenSSL::PKey::RSA, [1024, 2048, 3072, 4096], %w(ssh-rsa)),
Technology.new(:dsa, OpenSSL::PKey::DSA, [1024, 2048, 3072], %w(ssh-dss)),
Technology.new(:ecdsa, OpenSSL::PKey::EC, [256, 384, 521], %w(ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521)),
Technology.new(:ed25519, Net::SSH::Authentication::ED25519::PubKey, [256], %w(ssh-ed25519))
Technology.new(:rsa, SSHData::PublicKey::RSA, [1024, 2048, 3072, 4096], %w(ssh-rsa)),
Technology.new(:dsa, SSHData::PublicKey::DSA, [1024, 2048, 3072], %w(ssh-dss)),
Technology.new(:ecdsa, SSHData::PublicKey::ECDSA, [256, 384, 521], %w(ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521)),
Technology.new(:ed25519, SSHData::PublicKey::ED25519, [256], %w(ssh-ed25519))
].freeze
def self.technology(name)
@ -18,7 +18,7 @@ module Gitlab
end
def self.technology_for_key(key)
TECHNOLOGIES.find { |tech| key.is_a?(tech.key_class) }
TECHNOLOGIES.find { |tech| key.instance_of?(tech.key_class) }
end
def self.supported_types
@ -45,7 +45,7 @@ module Gitlab
parts.each_with_object(+"#{ssh_type} ").with_index do |(part, content), index|
content << part
if Gitlab::SSHPublicKey.new(content).valid?
if self.new(content).valid?
break [content, parts[index + 1]].compact.join(' ') # Add the comment part if present
elsif parts.size == index + 1 # return original content if we've reached the last element
break key_content
@ -55,41 +55,49 @@ module Gitlab
attr_reader :key_text, :key
# Unqualified MD5 fingerprint for compatibility
delegate :fingerprint, to: :key, allow_nil: true
def initialize(key_text)
@key_text = key_text
# We need to strip options to parse key with options or in known_hosts
# format. See https://man.openbsd.org/sshd#AUTHORIZED_KEYS_FILE_FORMAT
# and https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT
key_text_without_options = @key_text.to_s.match(/(\A|\s)(#{self.class.supported_algorithms.join('|')}).*/).to_s
@key =
begin
Net::SSH::KeyFactory.load_data_public_key(key_text)
rescue StandardError, NotImplementedError
SSHData::PublicKey.parse_openssh(key_text_without_options)
rescue SSHData::DecodeError
end
end
def valid?
SSHKey.valid_ssh_public_key?(key_text)
key.present?
end
def type
technology.name if key.present?
technology.name if valid?
end
def fingerprint
key.fingerprint(md5: true) if valid?
end
def fingerprint_sha256
'SHA256:' + key.fingerprint(md5: false) if valid?
end
def bits
return if key.blank?
return unless valid?
case type
when :rsa
key.n&.num_bits
key.n.num_bits
when :dsa
key.p&.num_bits
key.p.num_bits
when :ecdsa
key.group.order&.num_bits
key.openssl.group.order.num_bits
when :ed25519
256
else
raise "Unsupported key type: #{type}"
end
end
@ -97,7 +105,11 @@ module Gitlab
def technology
@technology ||=
self.class.technology_for_key(key) || raise("Unsupported key type: #{key.class}")
self.class.technology_for_key(key) || raise_unsupported_key_type_error
end
def raise_unsupported_key_type_error
raise("Unsupported key type: #{key.class}")
end
end
end

View File

@ -28,7 +28,7 @@ module Gitlab
end
end
entry :before_script, ::Gitlab::Ci::Config::Entry::Script,
entry :before_script, ::Gitlab::Ci::Config::Entry::Commands,
description: 'Global before script overridden in this job.'
entry :script, ::Gitlab::Ci::Config::Entry::Commands,

View File

@ -10,20 +10,20 @@ module Tasks
JH_ASSET_FOLDERS = %w[jh/app/assets].freeze
JS_ASSET_PATTERNS = %w[*.js config/**/*.js].freeze
JS_ASSET_FILES = %w[package.json yarn.lock].freeze
MASTER_MD5_HASH_FILE = 'master-assets-hash.txt'
HEAD_MD5_HASH_FILE = 'assets-hash.txt'
MASTER_SHA256_HASH_FILE = 'master-assets-hash.txt'
HEAD_SHA256_HASH_FILE = 'assets-hash.txt'
PUBLIC_ASSETS_WEBPACK_DIR = 'public/assets/webpack'
def self.md5_of_assets_impacting_webpack_compilation
def self.sha256_of_assets_impacting_webpack_compilation
start_time = Time.now
asset_files = assets_impacting_webpack_compilation
puts "Generating the MD5 hash for #{assets_impacting_webpack_compilation.size} Webpack-related assets..."
puts "Generating the SHA256 hash for #{assets_impacting_webpack_compilation.size} Webpack-related assets..."
asset_file_md5s = asset_files.map do |asset_file|
Digest::MD5.file(asset_file).hexdigest
asset_file_sha256s = asset_files.map do |asset_file|
Digest::SHA256.file(asset_file).hexdigest
end
Digest::MD5.hexdigest(asset_file_md5s.join).tap { |md5| puts "=> MD5 generated in #{Time.now - start_time}: #{md5}" }
Digest::SHA256.hexdigest(asset_file_sha256s.join).tap { |sha256| puts "=> SHA256 generated in #{Time.now - start_time}: #{sha256}" }
end
def self.assets_impacting_webpack_compilation
@ -63,25 +63,25 @@ namespace :gitlab do
desc 'GitLab | Assets | Compile all Webpack assets'
task :compile_webpack_if_needed do
FileUtils.mv(Tasks::Gitlab::Assets::HEAD_MD5_HASH_FILE, Tasks::Gitlab::Assets::MASTER_MD5_HASH_FILE, force: true)
FileUtils.mv(Tasks::Gitlab::Assets::HEAD_SHA256_HASH_FILE, Tasks::Gitlab::Assets::MASTER_SHA256_HASH_FILE, force: true)
master_assets_md5 =
if File.exist?(Tasks::Gitlab::Assets::MASTER_MD5_HASH_FILE)
File.read(Tasks::Gitlab::Assets::MASTER_MD5_HASH_FILE)
master_assets_sha256 =
if File.exist?(Tasks::Gitlab::Assets::MASTER_SHA256_HASH_FILE)
File.read(Tasks::Gitlab::Assets::MASTER_SHA256_HASH_FILE)
else
'missing!'
end
head_assets_md5 = Tasks::Gitlab::Assets.md5_of_assets_impacting_webpack_compilation.tap do |md5|
File.write(Tasks::Gitlab::Assets::HEAD_MD5_HASH_FILE, md5)
head_assets_sha256 = Tasks::Gitlab::Assets.sha256_of_assets_impacting_webpack_compilation.tap do |sha256|
File.write(Tasks::Gitlab::Assets::HEAD_SHA256_HASH_FILE, sha256)
end
puts "Webpack assets MD5 for `master`: #{master_assets_md5}"
puts "Webpack assets MD5 for `HEAD`: #{head_assets_md5}"
puts "Webpack assets SHA256 for `master`: #{master_assets_sha256}"
puts "Webpack assets SHA256 for `HEAD`: #{head_assets_sha256}"
public_assets_webpack_dir_exists = Dir.exist?(Tasks::Gitlab::Assets::PUBLIC_ASSETS_WEBPACK_DIR)
if head_assets_md5 != master_assets_md5 || !public_assets_webpack_dir_exists
if head_assets_sha256 != master_assets_sha256 || !public_assets_webpack_dir_exists
FileUtils.rm_r(Tasks::Gitlab::Assets::PUBLIC_ASSETS_WEBPACK_DIR) if public_assets_webpack_dir_exists
unless system('yarn webpack')

View File

@ -3542,7 +3542,7 @@ msgstr ""
msgid "Allow this key to push to this repository"
msgstr ""
msgid "Allow this secondary node to replicate content on Object Storage"
msgid "Allow this secondary site to replicate content on Object Storage"
msgstr ""
msgid "Allow use of licensed EE features"
@ -6533,6 +6533,9 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
msgid "Cannot create the abuse report. The reported user was invalid. Please try again or contact support."
msgstr ""
msgid "Cannot create the abuse report. The user has been deleted."
msgstr ""
@ -7518,7 +7521,7 @@ msgstr ""
msgid "ClusterAgents|%{name} successfully deleted"
msgstr ""
msgid "ClusterAgents|%{number} of %{total} agents"
msgid "ClusterAgents|%{number} of %{total} Agents"
msgstr ""
msgid "ClusterAgents|%{number} of %{total} clusters connected through cluster certificates"
@ -7590,10 +7593,10 @@ msgstr ""
msgid "ClusterAgents|Connect existing cluster"
msgstr ""
msgid "ClusterAgents|Connect with a certificate"
msgid "ClusterAgents|Connect with Agent"
msgstr ""
msgid "ClusterAgents|Connect with the Agent"
msgid "ClusterAgents|Connect with a certificate"
msgstr ""
msgid "ClusterAgents|Connect with the GitLab Agent"
@ -7656,7 +7659,7 @@ msgstr ""
msgid "ClusterAgents|How to register an agent?"
msgstr ""
msgid "ClusterAgents|Install a new agent"
msgid "ClusterAgents|Install new Agent"
msgstr ""
msgid "ClusterAgents|Last connected %{timeAgo}."
@ -7683,7 +7686,7 @@ msgstr ""
msgid "ClusterAgents|Never connected"
msgstr ""
msgid "ClusterAgents|No agents"
msgid "ClusterAgents|No Agents"
msgstr ""
msgid "ClusterAgents|No clusters connected through cluster certificates"
@ -11994,6 +11997,9 @@ msgstr ""
msgid "Deployment|Created"
msgstr ""
msgid "Deployment|Deployment ID"
msgstr ""
msgid "Deployment|Failed"
msgstr ""
@ -15842,12 +15848,6 @@ msgstr ""
msgid "Geo|No available replication slots"
msgstr ""
msgid "Geo|Node name can't be blank"
msgstr ""
msgid "Geo|Node name should be between 1 and 255 characters"
msgstr ""
msgid "Geo|Not synced yet"
msgstr ""
@ -15902,13 +15902,13 @@ msgstr ""
msgid "Geo|Remove entry"
msgstr ""
msgid "Geo|Remove node"
msgid "Geo|Remove site"
msgstr ""
msgid "Geo|Remove tracking database entry"
msgstr ""
msgid "Geo|Removing a Geo node stops the synchronization to and from that node. Are you sure?"
msgid "Geo|Removing a Geo site stops the synchronization to and from that site. Are you sure?"
msgstr ""
msgid "Geo|Replicated data is verified with the secondary site(s) using checksums"
@ -15971,6 +15971,12 @@ msgstr ""
msgid "Geo|Selective (%{syncLabel})"
msgstr ""
msgid "Geo|Site name can't be blank"
msgstr ""
msgid "Geo|Site name should be between 1 and 255 characters"
msgstr ""
msgid "Geo|Site's status was updated %{timeAgo}."
msgstr ""
@ -16007,10 +16013,10 @@ msgstr ""
msgid "Geo|There are no %{replicable_type} to show"
msgstr ""
msgid "Geo|There was an error deleting the Geo Node"
msgid "Geo|There was an error deleting the Geo Site"
msgstr ""
msgid "Geo|There was an error fetching the Geo Nodes"
msgid "Geo|There was an error fetching the Geo Sites"
msgstr ""
msgid "Geo|This will resync all %{replicableType}. It may take some time to complete. Are you sure you want to continue?"
@ -29445,7 +29451,7 @@ msgstr ""
msgid "RegistrationFeatures|Enable Service Ping and register for this feature."
msgstr ""
msgid "RegistrationFeatures|Read more about the %{linkStart}%{label}%{linkEnd}."
msgid "RegistrationFeatures|Read more about the %{link_start}Registration Features Program%{link_end}."
msgstr ""
msgid "RegistrationFeatures|Registration Features Program"
@ -30211,6 +30217,9 @@ msgstr ""
msgid "Repository size is above the limit."
msgstr ""
msgid "Repository size limit (MB)"
msgstr ""
msgid "Repository storage"
msgstr ""
@ -36140,7 +36149,7 @@ msgstr ""
msgid "There was an error fetching the Geo Settings"
msgstr ""
msgid "There was an error fetching the Node's Groups"
msgid "There was an error fetching the Sites's Groups"
msgstr ""
msgid "There was an error fetching the deploy freezes."
@ -36191,7 +36200,7 @@ msgstr ""
msgid "There was an error retrieving the Jira users."
msgstr ""
msgid "There was an error saving this Geo Node."
msgid "There was an error saving this Geo Site"
msgstr ""
msgid "There was an error saving your changes."

View File

@ -6,6 +6,10 @@ module Gitlab
class Subscription < Chemlab::Page
path '/admin/subscription'
div :subscription_details
text_field :activation_code
button :activate
label :terms_of_services, text: /I agree that/
p :plan
p :started
p :name
@ -16,6 +20,33 @@ module Gitlab
h2 :users_in_subscription
h2 :users_over_subscription
table :subscription_history
def accept_terms
terms_of_services_element.click # workaround for hidden checkbox
end
# Checks if a subscription record exists in subscription history table
#
# @param plan [Hash] Name of the plan
# @option plan [Hash] Support::Helpers::FREE
# @option plan [Hash] Support::Helpers::PREMIUM
# @option plan [Hash] Support::Helpers::PREMIUM_SELF_MANAGED
# @option plan [Hash] Support::Helpers::ULTIMATE
# @option plan [Hash] Support::Helpers::ULTIMATE_SELF_MANAGED
# @option plan [Hash] Support::Helpers::CI_MINUTES
# @option plan [Hash] Support::Helpers::STORAGE
# @param users_in_license [Integer] Number of users in license
# @param license_type [Hash] Type of the license
# @option license_type [String] 'license file'
# @option license_type [String] 'cloud license'
# @return [Boolean] True if record exsists, false if not
def has_subscription_record?(plan, users_in_license, license_type)
# find any records that have a matching plan and seats and type
subscription_history_element.hashes.any? do |record|
record['Plan'] == plan[:name].capitalize && record['Seats'] == users_in_license.to_s && \
record['Type'].strip.downcase == license_type
end
end
end
end
end

View File

@ -4,6 +4,112 @@ module Gitlab
module Page
module Admin
module Subscription
# @note Defined as +h6 :subscription_details+
# @return [String] The text content or value of +subscription_details+
def subscription_details
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription.subscription_details_element).to exist
# end
# @return [Watir::H6] The raw +H6+ element
def subscription_details_element
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription).to be_subscription_details
# end
# @return [Boolean] true if the +subscription_details+ element is present on the page
def subscription_details?
# This is a stub, used for indexing. The method is dynamically generated.
end
# @note Defined as +text_field :activation_code+
# @return [String] The text content or value of +activation_code+
def activation_code
# This is a stub, used for indexing. The method is dynamically generated.
end
# Set the value of activation_code
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# subscription.activation_code = 'value'
# end
# @param value [String] The value to set.
def activation_code=(value)
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription.activation_code_element).to exist
# end
# @return [Watir::TextField] The raw +TextField+ element
def activation_code_element
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription).to be_activation_code
# end
# @return [Boolean] true if the +activation_code+ element is present on the page
def activation_code?
# This is a stub, used for indexing. The method is dynamically generated.
end
# @note Defined as +label :terms_of_services+
# @return [String] The text content or value of +terms_of_services+
def terms_of_services
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription.terms_of_services_element).to exist
# end
# @return [Watir::Label] The raw +Label+ element
def terms_of_services_element
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription).to be_terms_of_services
# end
# @return [Boolean] true if the +terms_of_services+ element is present on the page
def terms_of_services?
# This is a stub, used for indexing. The method is dynamically generated.
end
# @note Defined as +button :activate+
# Clicks +activate+
def activate
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription.activate_element).to exist
# end
# @return [Watir::Button] The raw +Button+ element
def activate_element
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription).to be_activate
# end
# @return [Boolean] true if the +activate+ element is present on the page
def activate?
# This is a stub, used for indexing. The method is dynamically generated.
end
# @note Defined as +p :plan+
# @return [String] The text content or value of +plan+
def plan

View File

@ -434,6 +434,10 @@ module QA
ENV.fetch('QA_TEST_RESOURCES_CREATED_FILEPATH', File.join(Path.qa_root, 'tmp', file_name))
end
def ee_activation_code
ENV['QA_EE_ACTIVATION_CODE']
end
private
def remote_grid_credentials

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Package Registry', :orchestrated, :packages, :object_storage do
RSpec.describe 'Package Registry', :orchestrated, :reliable, :packages, :object_storage do
describe 'npm instance level endpoint' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Package Registry', :orchestrated, :packages, :object_storage do
RSpec.describe 'Package Registry', :orchestrated, :reliable, :packages, :object_storage do
describe 'npm project level endpoint' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures

View File

@ -3,15 +3,11 @@
require 'spec_helper'
RSpec.describe ApplicationCable::Connection, :clean_gitlab_redis_sessions do
let(:session_id) { Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') }
include SessionHelpers
context 'when session cookie is set' do
before do
Gitlab::Redis::Sessions.with do |redis|
redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash))
end
cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id
stub_session(session_hash)
end
context 'when user is logged in' do

View File

@ -22,7 +22,7 @@ RSpec.describe 'ClusterAgents', :js do
end
it 'displays empty state', :aggregate_failures do
expect(page).to have_content('Install a new agent')
expect(page).to have_content('Install new Agent')
expect(page).to have_selector('.empty-state')
end
end

View File

@ -5,12 +5,13 @@ import DeploymentStatusBadge from '~/environments/components/deployment_status_b
import { resolvedEnvironment } from './graphql/mock_data';
describe('~/environments/components/deployment.vue', () => {
const deployment = resolvedEnvironment.lastDeployment;
let wrapper;
const createWrapper = ({ propsData = {} } = {}) =>
mountExtended(Deployment, {
propsData: {
deployment: resolvedEnvironment.lastDeployment,
deployment,
...propsData,
},
});
@ -22,9 +23,7 @@ describe('~/environments/components/deployment.vue', () => {
describe('status', () => {
it('should pass the deployable status to the badge', () => {
wrapper = createWrapper();
expect(wrapper.findComponent(DeploymentStatusBadge).props('status')).toBe(
resolvedEnvironment.lastDeployment.status,
);
expect(wrapper.findComponent(DeploymentStatusBadge).props('status')).toBe(deployment.status);
});
});
@ -45,4 +44,41 @@ describe('~/environments/components/deployment.vue', () => {
expect(badge.exists()).toBe(false);
});
});
describe('iid', () => {
const findIid = () => wrapper.findByTitle(s__('Deployment|Deployment ID'));
const findDeploymentIcon = () => wrapper.findComponent({ ref: 'deployment-iid-icon' });
describe('is present', () => {
beforeEach(() => {
wrapper = createWrapper();
});
it('should show the iid', () => {
const iid = findIid();
expect(iid.exists()).toBe(true);
});
it('should show an icon for the iid', () => {
const deploymentIcon = findDeploymentIcon();
expect(deploymentIcon.props('name')).toBe('deployments');
});
});
describe('is not present', () => {
beforeEach(() => {
wrapper = createWrapper({ propsData: { deployment: { ...deployment, iid: '' } } });
});
it('should not show the iid', () => {
const iid = findIid();
expect(iid.exists()).toBe(false);
});
it('should not show an icon for the iid', () => {
const deploymentIcon = findDeploymentIcon();
expect(deploymentIcon.exists()).toBe(false);
});
});
});
});

View File

@ -1,9 +1,13 @@
import Vue from 'vue';
import { GlLink } from '@gitlab/ui';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
extendedWrapper,
shallowMountExtended,
mountExtended,
} from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
@ -46,8 +50,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
updateHistory: jest.fn(),
}));
const localVue = createLocalVue();
localVue.use(VueApollo);
Vue.use(VueApollo);
describe('AdminRunnersApp', () => {
let wrapper;
@ -65,22 +68,19 @@ describe('AdminRunnersApp', () => {
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
const handlers = [
[getRunnersQuery, mockRunnersQuery],
[getRunnersCountQuery, mockRunnersCountQuery],
];
wrapper = extendedWrapper(
mountFn(AdminRunnersApp, {
localVue,
apolloProvider: createMockApollo(handlers),
propsData: {
registrationToken: mockRegistrationToken,
...props,
},
}),
);
wrapper = mountFn(AdminRunnersApp, {
apolloProvider: createMockApollo(handlers),
propsData: {
registrationToken: mockRegistrationToken,
...props,
},
});
};
beforeEach(async () => {
@ -98,7 +98,7 @@ describe('AdminRunnersApp', () => {
});
it('shows total runner counts', async () => {
createComponent({ mountFn: mount });
createComponent({ mountFn: mountExtended });
await waitForPromises();
@ -129,7 +129,7 @@ describe('AdminRunnersApp', () => {
return Promise.resolve({ data: { runners: { count } } });
});
createComponent({ mountFn: mount });
createComponent({ mountFn: mountExtended });
await waitForPromises();
expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
@ -157,7 +157,7 @@ describe('AdminRunnersApp', () => {
return Promise.resolve({ data: { runners: { count } } });
});
createComponent({ mountFn: mount });
createComponent({ mountFn: mountExtended });
await waitForPromises();
expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
@ -175,7 +175,7 @@ describe('AdminRunnersApp', () => {
});
it('runner item links to the runner admin page', async () => {
createComponent({ mountFn: mount });
createComponent({ mountFn: mountExtended });
await waitForPromises();
@ -198,7 +198,7 @@ describe('AdminRunnersApp', () => {
});
it('sets tokens in the filtered search', () => {
createComponent({ mountFn: mount });
createComponent({ mountFn: mountExtended });
expect(findFilteredSearch().props('tokens')).toEqual([
expect.objectContaining({
@ -310,7 +310,7 @@ describe('AdminRunnersApp', () => {
beforeEach(() => {
mockRunnersQuery = jest.fn().mockResolvedValue(runnersDataPaginated);
createComponent({ mountFn: mount });
createComponent({ mountFn: mountExtended });
});
it('more pages can be selected', () => {

View File

@ -1,7 +1,7 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { createAlert } from '~/flash';
@ -21,8 +21,7 @@ const mockRunner = runnersData.data.runners.nodes[0];
const getRunnersQueryName = getRunnersQuery.definitions[0].name.value;
const getGroupRunnersQueryName = getGroupRunnersQuery.definitions[0].name.value;
const localVue = createLocalVue();
localVue.use(VueApollo);
Vue.use(VueApollo);
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
@ -41,35 +40,32 @@ describe('RunnerTypeCell', () => {
const getTooltip = (w) => getBinding(w.element, 'gl-tooltip')?.value;
const createComponent = (runner = {}, options) => {
wrapper = extendedWrapper(
shallowMount(RunnerActionCell, {
propsData: {
runner: {
id: mockRunner.id,
shortSha: mockRunner.shortSha,
editAdminUrl: mockRunner.editAdminUrl,
userPermissions: mockRunner.userPermissions,
active: mockRunner.active,
...runner,
},
wrapper = shallowMountExtended(RunnerActionCell, {
propsData: {
runner: {
id: mockRunner.id,
shortSha: mockRunner.shortSha,
editAdminUrl: mockRunner.editAdminUrl,
userPermissions: mockRunner.userPermissions,
active: mockRunner.active,
...runner,
},
localVue,
apolloProvider: createMockApollo([
[runnerDeleteMutation, runnerDeleteMutationHandler],
[runnerActionsUpdateMutation, runnerActionsUpdateMutationHandler],
]),
directives: {
GlTooltip: createMockDirective(),
GlModal: createMockDirective(),
},
apolloProvider: createMockApollo([
[runnerDeleteMutation, runnerDeleteMutationHandler],
[runnerActionsUpdateMutation, runnerActionsUpdateMutationHandler],
]),
directives: {
GlTooltip: createMockDirective(),
GlModal: createMockDirective(),
},
mocks: {
$toast: {
show: mockToastShow,
},
mocks: {
$toast: {
show: mockToastShow,
},
},
...options,
}),
);
},
...options,
});
};
beforeEach(() => {

View File

@ -1,8 +1,8 @@
import { GlDropdown, GlDropdownItem, GlDropdownForm } from '@gitlab/ui';
import { createLocalVue, mount, shallowMount, createWrapper } from '@vue/test-utils';
import { createLocalVue, createWrapper } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
@ -33,17 +33,15 @@ describe('RegistrationDropdown', () => {
const findToggleMaskButton = () => wrapper.findByTestId('toggle-masked');
const createComponent = ({ props = {}, ...options } = {}, mountFn = shallowMount) => {
wrapper = extendedWrapper(
mountFn(RegistrationDropdown, {
propsData: {
registrationToken: mockToken,
type: INSTANCE_TYPE,
...props,
},
...options,
}),
);
const createComponent = ({ props = {}, ...options } = {}, mountFn = shallowMountExtended) => {
wrapper = mountFn(RegistrationDropdown, {
propsData: {
registrationToken: mockToken,
type: INSTANCE_TYPE,
...props,
},
...options,
});
};
it.each`
@ -52,7 +50,7 @@ describe('RegistrationDropdown', () => {
${GROUP_TYPE} | ${'Register a group runner'}
${PROJECT_TYPE} | ${'Register a project runner'}
`('Dropdown text for type $type is "$text"', () => {
createComponent({ props: { type: INSTANCE_TYPE } }, mount);
createComponent({ props: { type: INSTANCE_TYPE } }, mountExtended);
expect(wrapper.text()).toContain('Register an instance runner');
});
@ -93,7 +91,7 @@ describe('RegistrationDropdown', () => {
// Use `attachTo` to find the modal
attachTo: document.body,
},
mount,
mountExtended,
);
findRegistrationInstructionsDropdownItem().trigger('click');
@ -131,7 +129,7 @@ describe('RegistrationDropdown', () => {
});
it('Displays masked value by default', () => {
createComponent({}, mount);
createComponent({}, mountExtended);
expect(findTokenDropdownItem().text()).toMatchInterpolatedText(
`Registration token ${maskToken}`,
@ -154,7 +152,7 @@ describe('RegistrationDropdown', () => {
});
it('Updates the token when it gets reset', async () => {
createComponent({}, mount);
createComponent({}, mountExtended);
const newToken = 'mock1';

View File

@ -1,7 +1,7 @@
import Vue, { nextTick } from 'vue';
import { GlDropdownItem, GlLoadingIcon, GlToast, GlModal } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
@ -14,9 +14,8 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
const localVue = createLocalVue();
localVue.use(VueApollo);
localVue.use(GlToast);
Vue.use(VueApollo);
Vue.use(GlToast);
const mockNewToken = 'NEW_TOKEN';
const modalID = 'token-reset-modal';
@ -33,8 +32,7 @@ describe('RegistrationTokenResetDropdownItem', () => {
const clickSubmit = () => findModal().vm.$emit('primary', mockEvent);
const createComponent = ({ props, provide = {} } = {}) => {
wrapper = shallowMount(RegistrationTokenResetDropdownItem, {
localVue,
wrapper = shallowMountExtended(RegistrationTokenResetDropdownItem, {
provide,
propsData: {
type: INSTANCE_TYPE,

View File

@ -1,7 +1,7 @@
import { nextTick } from 'vue';
import { GlToast } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { createLocalVue } from '@vue/test-utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RegistrationToken from '~/runner/components/registration/registration_token.vue';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
@ -25,15 +25,13 @@ describe('RegistrationToken', () => {
const createComponent = ({ props = {}, withGlToast = true } = {}) => {
const localVue = withGlToast ? vueWithGlToast() : undefined;
wrapper = extendedWrapper(
shallowMount(RegistrationToken, {
propsData: {
value: mockToken,
...props,
},
localVue,
}),
);
wrapper = shallowMountExtended(RegistrationToken, {
propsData: {
value: mockToken,
...props,
},
localVue,
});
showToast = wrapper.vm.$toast ? jest.spyOn(wrapper.vm.$toast, 'show') : null;
};

View File

@ -1,6 +1,5 @@
import { GlFilteredSearch, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import { statusTokenConfig } from '~/runner/components/search_tokens/status_token_config';
import TagToken from '~/runner/components/search_tokens/tag_token.vue';
@ -29,27 +28,25 @@ describe('RunnerList', () => {
};
const createComponent = ({ props = {}, options = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(RunnerFilteredSearchBar, {
propsData: {
namespace: 'runners',
tokens: [],
value: {
runnerType: null,
filters: [],
sort: mockDefaultSort,
},
...props,
wrapper = shallowMountExtended(RunnerFilteredSearchBar, {
propsData: {
namespace: 'runners',
tokens: [],
value: {
runnerType: null,
filters: [],
sort: mockDefaultSort,
},
stubs: {
FilteredSearch,
GlFilteredSearch,
GlDropdown,
GlDropdownItem,
},
...options,
}),
);
...props,
},
stubs: {
FilteredSearch,
GlFilteredSearch,
GlDropdown,
GlDropdownItem,
},
...options,
});
};
beforeEach(() => {

View File

@ -1,6 +1,9 @@
import { GlTable, GlSkeletonLoader } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
extendedWrapper,
shallowMountExtended,
mountExtended,
} from 'helpers/vue_test_utils_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerList from '~/runner/components/runner_list.vue';
import { runnersData } from '../mock_data';
@ -18,20 +21,18 @@ describe('RunnerList', () => {
const findCell = ({ row = 0, fieldKey }) =>
extendedWrapper(findRows().at(row).find(`[data-testid="td-${fieldKey}"]`));
const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
wrapper = extendedWrapper(
mountFn(RunnerList, {
propsData: {
runners: mockRunners,
activeRunnersCount: mockActiveRunnersCount,
...props,
},
}),
);
const createComponent = ({ props = {} } = {}, mountFn = shallowMountExtended) => {
wrapper = mountFn(RunnerList, {
propsData: {
runners: mockRunners,
activeRunnersCount: mockActiveRunnersCount,
...props,
},
});
};
beforeEach(() => {
createComponent({}, mount);
createComponent({}, mountExtended);
});
afterEach(() => {
@ -54,7 +55,7 @@ describe('RunnerList', () => {
});
it('Sets runner id as a row key', () => {
createComponent({}, shallowMount);
createComponent({});
expect(findTable().attributes('primary-key')).toBe('id');
});
@ -107,7 +108,7 @@ describe('RunnerList', () => {
it('Formats job counts', () => {
mockRunnersCopy[0].jobCount = 1;
createComponent({ props: { runners: mockRunnersCopy } }, mount);
createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1');
});
@ -115,7 +116,7 @@ describe('RunnerList', () => {
it('Formats large job counts', () => {
mockRunnersCopy[0].jobCount = 1000;
createComponent({ props: { runners: mockRunnersCopy } }, mount);
createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1,000');
});
@ -123,7 +124,7 @@ describe('RunnerList', () => {
it('Formats large job counts with a plus symbol', () => {
mockRunnersCopy[0].jobCount = 1001;
createComponent({ props: { runners: mockRunnersCopy } }, mount);
createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1,000+');
});
@ -143,13 +144,13 @@ describe('RunnerList', () => {
});
it('when there are no runners, shows an skeleton loader', () => {
createComponent({ props: { runners: [], loading: true } }, mount);
createComponent({ props: { runners: [], loading: true } }, mountExtended);
expect(findSkeletonLoader().exists()).toBe(true);
});
it('when there are runners, shows a busy indicator skeleton loader', () => {
createComponent({ props: { loading: true } }, mount);
createComponent({ props: { loading: true } }, mountExtended);
expect(findSkeletonLoader().exists()).toBe(false);
});

View File

@ -1,9 +1,8 @@
import Vue, { nextTick } from 'vue';
import { GlForm } from '@gitlab/ui';
import { createLocalVue, mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert, VARIANT_SUCCESS } from '~/flash';
import RunnerUpdateForm from '~/runner/components/runner_update_form.vue';
@ -23,8 +22,7 @@ jest.mock('~/runner/sentry_utils');
const mockRunner = runnerData.data.runner;
const localVue = createLocalVue();
localVue.use(VueApollo);
Vue.use(VueApollo);
describe('RunnerUpdateForm', () => {
let wrapper;
@ -61,16 +59,13 @@ describe('RunnerUpdateForm', () => {
});
const createComponent = ({ props } = {}) => {
wrapper = extendedWrapper(
mount(RunnerUpdateForm, {
localVue,
propsData: {
runner: mockRunner,
...props,
},
apolloProvider: createMockApollo([[runnerUpdateMutation, runnerUpdateHandler]]),
}),
);
wrapper = mountExtended(RunnerUpdateForm, {
propsData: {
runner: mockRunner,
...props,
},
apolloProvider: createMockApollo([[runnerUpdateMutation, runnerUpdateHandler]]),
});
};
const expectToHaveSubmittedRunnerContaining = (submittedRunner) => {

View File

@ -1,6 +1,6 @@
import { nextTick } from 'vue';
import Vue, { nextTick } from 'vue';
import { GlLink } from '@gitlab/ui';
import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import { shallowMount, mount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
@ -33,8 +33,7 @@ import { captureException } from '~/runner/sentry_utils';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { groupRunnersData, groupRunnersDataPaginated, groupRunnersCountData } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
Vue.use(VueApollo);
const mockGroupFullPath = 'group1';
const mockRegistrationToken = 'AABBCC';
@ -69,7 +68,6 @@ describe('GroupRunnersApp', () => {
];
wrapper = mountFn(GroupRunnersApp, {
localVue,
apolloProvider: createMockApollo(handlers),
propsData: {
registrationToken: mockRegistrationToken,

View File

@ -55,13 +55,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
}
end
context 'when deprecated types keyword is defined' do
context 'when deprecated types/type keywords are defined' do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:hash) do
{ types: %w(test deploy),
rspec: { script: 'rspec' } }
rspec: { script: 'rspec', type: 'test' } }
end
before do
@ -69,11 +69,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
end
it 'returns array of types as stages with a warning' do
expect(root.jobs_value[:rspec][:stage]).to eq 'test'
expect(root.stages_value).to eq %w[test deploy]
expect(root.warnings).to match_array(["root `types` is deprecated in 9.0 and will be removed in 15.0."])
expect(root.warnings).to match_array([
"root `types` is deprecated in 9.0 and will be removed in 15.0.",
"jobs:rspec `type` is deprecated in 9.0 and will be removed in 15.0."
])
end
it 'logs usage of types keyword' do
it 'logs usage of keywords' do
expect(Gitlab::AppJsonLogger).to(
receive(:info)
.with(event: 'ci_used_deprecated_keyword',
@ -350,9 +354,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
root.compose!
end
context 'when before script is not an array' do
context 'when before script is a number' do
let(:hash) do
{ before_script: 'ls' }
{ before_script: 123 }
end
describe '#valid?' do
@ -364,7 +368,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
describe '#errors' do
it 'reports errors from child nodes' do
expect(root.errors)
.to include 'before_script config should be an array containing strings and arrays of strings'
.to include 'before_script config should be a string or a nested array of strings up to 10 levels deep'
end
end
end

View File

@ -1,109 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Script do
let(:entry) { described_class.new(config) }
describe 'validations' do
context 'when entry config value is array of strings' do
let(:config) { %w(ls pwd) }
describe '#value' do
it 'returns array of strings' do
expect(entry.value).to eq config
end
end
describe '#errors' do
it 'does not append errors' do
expect(entry.errors).to be_empty
end
end
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
end
context 'when entry config value is array of arrays of strings' do
let(:config) { [['ls'], ['pwd', 'echo 1']] }
describe '#value' do
it 'returns array of strings' do
expect(entry.value).to eq ['ls', 'pwd', 'echo 1']
end
end
describe '#errors' do
it 'does not append errors' do
expect(entry.errors).to be_empty
end
end
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
end
context 'when entry config value is array containing strings and arrays of strings' do
let(:config) { ['ls', ['pwd', 'echo 1']] }
describe '#value' do
it 'returns array of strings' do
expect(entry.value).to eq ['ls', 'pwd', 'echo 1']
end
end
describe '#errors' do
it 'does not append errors' do
expect(entry.errors).to be_empty
end
end
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
end
context 'when entry value is string' do
let(:config) { 'ls' }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'script config should be an array containing strings and arrays of strings'
end
end
describe '#valid?' do
it 'is not valid' do
expect(entry).not_to be_valid
end
end
end
context 'when entry value is multi-level nested array' do
let(:config) { [['ls', ['echo 1']], 'pwd'] }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'script config should be an array containing strings and arrays of strings'
end
end
describe '#valid?' do
it 'is not valid' do
expect(entry).not_to be_valid
end
end
end
end
end

View File

@ -710,16 +710,16 @@ module Gitlab
end
end
context 'when script is array of arrays of strings' do
context 'when script is nested arrays of strings' do
let(:config) do
{
before_script: [["global script", "echo 1"], ["ls"], "pwd"],
before_script: [[["global script"], "echo 1"], "echo 2", ["ls"], "pwd"],
test: { script: ["script"] }
}
end
it "return commands with scripts concatenated" do
expect(subject[:options][:before_script]).to eq(["global script", "echo 1", "ls", "pwd"])
expect(subject[:options][:before_script]).to eq(["global script", "echo 1", "echo 2", "ls", "pwd"])
end
end
end
@ -737,15 +737,15 @@ module Gitlab
end
end
context 'when script is array of arrays of strings' do
context 'when script is nested arrays of strings' do
let(:config) do
{
test: { script: [["script"], ["echo 1"], "ls"] }
test: { script: [[["script"], "echo 1", "echo 2"], "ls"] }
}
end
it "return commands with scripts concatenated" do
expect(subject[:options][:script]).to eq(["script", "echo 1", "ls"])
expect(subject[:options][:script]).to eq(["script", "echo 1", "echo 2", "ls"])
end
end
end
@ -790,16 +790,16 @@ module Gitlab
end
end
context 'when script is array of arrays of strings' do
context 'when script is nested arrays of strings' do
let(:config) do
{
after_script: [["global script", "echo 1"], ["ls"], "pwd"],
after_script: [[["global script"], "echo 1"], "echo 2", ["ls"], "pwd"],
test: { script: ["script"] }
}
end
it "return after_script in options" do
expect(subject[:options][:after_script]).to eq(["global script", "echo 1", "ls", "pwd"])
expect(subject[:options][:after_script]).to eq(["global script", "echo 1", "echo 2", "ls", "pwd"])
end
end
end
@ -2469,40 +2469,16 @@ module Gitlab
it_behaves_like 'returns errors', 'jobs:rspec:tags config should be an array of strings'
end
context 'returns errors if before_script parameter is invalid' do
let(:config) { YAML.dump({ before_script: "bundle update", rspec: { script: "test" } }) }
it_behaves_like 'returns errors', 'before_script config should be an array containing strings and arrays of strings'
end
context 'returns errors if job before_script parameter is not an array of strings' do
let(:config) { YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } }) }
it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be an array containing strings and arrays of strings'
end
context 'returns errors if job before_script parameter is multi-level nested array of strings' do
let(:config) { YAML.dump({ rspec: { script: "test", before_script: [["ls", ["pwd"]], "test"] } }) }
it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be an array containing strings and arrays of strings'
end
context 'returns errors if after_script parameter is invalid' do
let(:config) { YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) }
it_behaves_like 'returns errors', 'after_script config should be an array containing strings and arrays of strings'
it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be a string or a nested array of strings up to 10 levels deep'
end
context 'returns errors if job after_script parameter is not an array of strings' do
let(:config) { YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } }) }
it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be an array containing strings and arrays of strings'
end
context 'returns errors if job after_script parameter is multi-level nested array of strings' do
let(:config) { YAML.dump({ rspec: { script: "test", after_script: [["ls", ["pwd"]], "test"] } }) }
it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be an array containing strings and arrays of strings'
it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be a string or a nested array of strings up to 10 levels deep'
end
context 'returns errors if image parameter is invalid' do

View File

@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe Gitlab::Config::Entry::Factory do
describe '#create!' do
before do
stub_const('Script', Class.new(Gitlab::Config::Entry::Node))
Script.class_eval do
stub_const('Commands', Class.new(Gitlab::Config::Entry::Node))
Commands.class_eval do
include Gitlab::Config::Entry::Validatable
validations do
@ -15,7 +15,7 @@ RSpec.describe Gitlab::Config::Entry::Factory do
end
end
let(:entry) { Script }
let(:entry) { Commands }
let(:factory) { described_class.new(entry) }
context 'when setting a concrete value' do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require_relative "../../support/matchers/be_request_urgency"
require_relative "../../../lib/gitlab/endpoint_attributes"
require_relative '../../support/matchers/be_request_urgency'
require_relative '../../../lib/gitlab/endpoint_attributes/config'
require_relative '../../../lib/gitlab/endpoint_attributes'
RSpec.describe Gitlab::EndpointAttributes do
let(:base_controller) do

View File

@ -5,6 +5,19 @@ require 'spec_helper'
RSpec.describe Gitlab::RackAttack::Request do
using RSpec::Parameterized::TableSyntax
let(:env) { {} }
let(:session) { {} }
let(:request) do
::Rack::Attack::Request.new(
env.reverse_merge(
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => path,
'rack.input' => StringIO.new,
'rack.session' => session
)
)
end
describe 'FILES_PATH_REGEX' do
subject { described_class::FILES_PATH_REGEX }
@ -16,11 +29,80 @@ RSpec.describe Gitlab::RackAttack::Request do
it { is_expected.not_to match('/api/v4/projects/some/nested/repo/repository/files/README') }
end
describe '#deprecated_api_request?' do
let(:env) { { 'REQUEST_METHOD' => 'GET', 'rack.input' => StringIO.new, 'PATH_INFO' => path, 'QUERY_STRING' => query } }
let(:request) { ::Rack::Attack::Request.new(env) }
describe '#api_request?' do
subject { request.api_request? }
subject { !!request.__send__(:deprecated_api_request?) }
where(:path, :expected) do
'/' | false
'/groups' | false
'/api' | true
'/api/v4/groups/1' | true
end
with_them do
it { is_expected.to eq(expected) }
end
end
describe '#web_request?' do
subject { request.web_request? }
where(:path, :expected) do
'/' | true
'/groups' | true
'/api' | false
'/api/v4/groups/1' | false
end
with_them do
it { is_expected.to eq(expected) }
end
end
describe '#frontend_request?', :allow_forgery_protection do
subject { request.send(:frontend_request?) }
let(:path) { '/' }
# Define these as local variables so we can use them in the `where` block.
valid_token = SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH)
other_token = SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH)
where(:session, :env, :expected) do
{} | {} | false # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
{} | { 'HTTP_X_CSRF_TOKEN' => valid_token } | false
{ _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => other_token } | false
{ _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => valid_token } | true
end
with_them do
it { is_expected.to eq(expected) }
end
context 'when the feature flag is disabled' do
before do
stub_feature_flags(rate_limit_frontend_requests: false)
end
where(:session, :env) do
{} | {} # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
{} | { 'HTTP_X_CSRF_TOKEN' => valid_token }
{ _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => other_token }
{ _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => valid_token }
end
with_them do
it { is_expected.to be(false) }
end
end
end
describe '#deprecated_api_request?' do
subject { request.send(:deprecated_api_request?) }
let(:env) { { 'QUERY_STRING' => query } }
where(:path, :query, :expected) do
'/' | '' | false

View File

@ -129,6 +129,26 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do
let(:key) { attributes_for(factory)[:key] }
it { is_expected.to be_valid }
context 'when key begins with options' do
let(:key) { "restrict,command='dump /home' #{attributes_for(factory)[:key]}" }
it { is_expected.to be_valid }
end
context 'when key is in known_hosts format' do
context "when key begins with 'example.com'" do
let(:key) { "example.com #{attributes_for(factory)[:key]}" }
it { is_expected.to be_valid }
end
context "when key begins with '@revoked other.example.com'" do
let(:key) { "@revoked other.example.com #{attributes_for(factory)[:key]}" }
it { is_expected.to be_valid }
end
end
end
end
@ -137,6 +157,40 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do
it { is_expected.not_to be_valid }
end
context 'when an unsupported SSH key algorithm' do
let(:key) { "unsupported-#{attributes_for(:rsa_key_2048)[:key]}" }
it { is_expected.not_to be_valid }
end
end
shared_examples 'raises error when the key is represented by a class that is not in the list of supported technologies' do
context 'when the key is represented by a class that is not in the list of supported technologies' do
it 'raises error' do
klass = Class.new
key = klass.new
allow(public_key).to receive(:key).and_return(key)
expect { subject }.to raise_error("Unsupported key type: #{key.class}")
end
end
context 'when the key is represented by a subclass of the class that is in the list of supported technologies' do
it 'raises error' do
rsa_subclass = Class.new(described_class.technology(:rsa).key_class) do
def initialize
end
end
key = rsa_subclass.new
allow(public_key).to receive(:key).and_return(key)
expect { subject }.to raise_error("Unsupported key type: #{key.class}")
end
end
end
describe '#type' do
@ -162,6 +216,8 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do
it { is_expected.to be_nil }
end
include_examples 'raises error when the key is represented by a class that is not in the list of supported technologies'
end
describe '#bits' do
@ -190,6 +246,8 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do
it { is_expected.to be_nil }
end
include_examples 'raises error when the key is represented by a class that is not in the list of supported technologies'
end
describe '#fingerprint' do
@ -220,18 +278,18 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do
end
end
describe '#fingerprint in SHA256 format' do
subject { public_key.fingerprint("SHA256").gsub("SHA256:", "") if public_key.fingerprint("SHA256") }
describe '#fingerprint_sha256' do
subject { public_key.fingerprint_sha256 }
where(:factory, :fingerprint_sha256) do
[
[:rsa_key_2048, 'GdtgO0eHbwLB+mK47zblkoXujkqKRZjgMQrHH6Kks3E'],
[:rsa_key_4096, 'ByDU7hQ1JB95l6p53rHrffc4eXvEtqGUtQhS+Dhyy7g'],
[:rsa_key_5120, 'PCCupLbFHScm4AbEufbGDvhBU27IM0MVAor715qKQK8'],
[:rsa_key_8192, 'CtHFQAS+9Hb8z4vrv4gVQPsHjNN0WIZhWODaB1mQLs4'],
[:dsa_key_2048, '+a3DQ7cU5GM+gaYOfmc0VWNnykHQSuth3VRcCpWuYNI'],
[:ecdsa_key_256, 'C+I5k3D+IGeM6k5iBR1ZsphqTKV+7uvL/XZ5hcrTr7g'],
[:ed25519_key_256, 'DCKAjzxWrdOTjaGKBBjtCW8qY5++GaiAJflrHPmp6W0']
[:rsa_key_2048, 'SHA256:GdtgO0eHbwLB+mK47zblkoXujkqKRZjgMQrHH6Kks3E'],
[:rsa_key_4096, 'SHA256:ByDU7hQ1JB95l6p53rHrffc4eXvEtqGUtQhS+Dhyy7g'],
[:rsa_key_5120, 'SHA256:PCCupLbFHScm4AbEufbGDvhBU27IM0MVAor715qKQK8'],
[:rsa_key_8192, 'SHA256:CtHFQAS+9Hb8z4vrv4gVQPsHjNN0WIZhWODaB1mQLs4'],
[:dsa_key_2048, 'SHA256:+a3DQ7cU5GM+gaYOfmc0VWNnykHQSuth3VRcCpWuYNI'],
[:ecdsa_key_256, 'SHA256:C+I5k3D+IGeM6k5iBR1ZsphqTKV+7uvL/XZ5hcrTr7g'],
[:ed25519_key_256, 'SHA256:DCKAjzxWrdOTjaGKBBjtCW8qY5++GaiAJflrHPmp6W0']
]
end
@ -249,10 +307,19 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do
end
describe '#key_text' do
let(:key) { 'this is not a key' }
where(:key_value) do
[
'this is not a key',
nil
]
end
it 'carries the unmodified key data' do
expect(public_key.key_text).to eq(key)
with_them do
let(:key) { key_value }
it 'carries the unmodified key data' do
expect(public_key.key_text).to eq(key)
end
end
end
end

View File

@ -108,7 +108,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Global do
describe '#errors' do
it 'reports errors about missing script' do
expect(global.errors)
.to include "terminal:before_script config should be an array containing strings and arrays of strings"
.to include "terminal:before_script config should be a string or a nested array of strings up to 10 levels deep"
end
end
end

View File

@ -56,7 +56,7 @@ RSpec.describe Gitlab::WebIde::Config do
end
context 'when config logic is incorrect' do
let(:yml) { 'terminal: { before_script: "ls" }' }
let(:yml) { 'terminal: { before_script: 123 }' }
describe '#valid?' do
it 'is not valid' do

View File

@ -98,4 +98,31 @@ RSpec.describe CustomerRelations::Contact, type: :model do
expect { described_class.find_ids_by_emails(group, Array(0..too_many_emails)) }.to raise_error(ArgumentError)
end
end
describe '#self.exists_for_group?' do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
context 'with no contacts in group or parent' do
it 'returns false' do
expect(described_class.exists_for_group?(subgroup)).to be_falsey
end
end
context 'with contacts in group' do
it 'returns true' do
create(:contact, group: subgroup)
expect(described_class.exists_for_group?(subgroup)).to be_truthy
end
end
context 'with contacts in parent' do
it 'returns true' do
create(:contact, group: group)
expect(described_class.exists_for_group?(subgroup)).to be_truthy
end
end
end
end

View File

@ -21,7 +21,7 @@ RSpec.describe AbuseReportsController do
user_id = user.id
user.destroy!
get :new, params: { user_id: user_id }
get new_abuse_report_path(user_id: user_id)
expect(response).to redirect_to root_path
expect(flash[:alert]).to eq(_('Cannot create the abuse report. The user has been deleted.'))
@ -32,7 +32,7 @@ RSpec.describe AbuseReportsController do
it 'redirects the reporter to the user\'s profile' do
user.block
get :new, params: { user_id: user.id }
get new_abuse_report_path(user_id: user.id)
expect(response).to redirect_to user
expect(flash[:alert]).to eq(_('Cannot create the abuse report. This user has been blocked.'))
@ -44,7 +44,7 @@ RSpec.describe AbuseReportsController do
context 'with valid attributes' do
it 'saves the abuse report' do
expect do
post :create, params: { abuse_report: attrs }
post abuse_reports_path(abuse_report: attrs)
end.to change { AbuseReport.count }.by(1)
end
@ -53,22 +53,22 @@ RSpec.describe AbuseReportsController do
expect(instance).to receive(:notify)
end
post :create, params: { abuse_report: attrs }
post abuse_reports_path(abuse_report: attrs)
end
it 'redirects back to root' do
post :create, params: { abuse_report: attrs }
post abuse_reports_path(abuse_report: attrs)
expect(response).to redirect_to root_path
end
end
context 'with invalid attributes' do
it 'renders new' do
it 'redirects back to root' do
attrs.delete(:user_id)
post :create, params: { abuse_report: attrs }
post abuse_reports_path(abuse_report: attrs)
expect(response).to render_template(:new)
expect(response).to redirect_to root_path
end
end
end

View File

@ -5,6 +5,7 @@ require 'mime/types'
RSpec.describe API::Commits do
include ProjectForksHelper
include SessionHelpers
let(:user) { create(:user) }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
@ -378,14 +379,7 @@ RSpec.describe API::Commits do
context 'when using warden' do
it 'increments usage counters', :clean_gitlab_redis_sessions do
session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d')
session_hash = { 'warden.user.user.key' => [[user.id], user.encrypted_password[0, 29]] }
Gitlab::Redis::Sessions.with do |redis|
redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash))
end
cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id
stub_session('warden.user.user.key' => [[user.id], user.encrypted_password[0, 29]])
expect(::Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_commits_count)
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_web_ide_edit_action)

View File

@ -225,7 +225,7 @@ RSpec.describe 'Query.ciConfig' do
context 'when using deprecated keywords' do
let_it_be(:content) do
YAML.dump(
rspec: { script: 'ls' },
rspec: { script: 'ls', type: 'test' },
types: ['test']
)
end
@ -233,7 +233,10 @@ RSpec.describe 'Query.ciConfig' do
it 'returns a warning' do
post_graphql_query
expect(graphql_data['ciConfig']['warnings']).to include('root `types` is deprecated in 9.0 and will be removed in 15.0.')
expect(graphql_data['ciConfig']['warnings']).to include(
'root `types` is deprecated in 9.0 and will be removed in 15.0.',
'jobs:rspec `type` is deprecated in 9.0 and will be removed in 15.0.'
)
end
end

View File

@ -154,7 +154,7 @@ RSpec.describe API::Lint do
end
context 'with valid .gitlab-ci.yaml using deprecated keywords' do
let(:yaml_content) { { job: { script: 'ls' }, types: ['test'] }.to_yaml }
let(:yaml_content) { { job: { script: 'ls', type: 'test' }, types: ['test'] }.to_yaml }
it 'passes validation but returns warnings' do
post api('/ci/lint', api_user), params: { content: yaml_content }

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_caching do
include RackAttackSpecHelpers
include SessionHelpers
let(:settings) { Gitlab::CurrentSettings.current_application_settings }
@ -63,6 +64,22 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
end
end
describe 'API requests from the frontend', :api, :clean_gitlab_redis_sessions do
context 'when unauthenticated' do
it_behaves_like 'rate-limited frontend API requests' do
let(:throttle_setting_prefix) { 'throttle_unauthenticated' }
end
end
context 'when authenticated' do
it_behaves_like 'rate-limited frontend API requests' do
let_it_be(:personal_access_token) { create(:personal_access_token) }
let(:throttle_setting_prefix) { 'throttle_authenticated' }
end
end
end
describe 'API requests authenticated with personal access token', :api do
let_it_be(:user) { create(:user) }
let_it_be(:token) { create(:personal_access_token, user: user) }

View File

@ -97,6 +97,20 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do
end
end
shared_examples_for "deleting a project with merge requests" do
let!(:merge_request) { create(:merge_request, source_project: project) }
it "deletes merge request and related records" do
merge_request_diffs = merge_request.merge_request_diffs
expect(merge_request_diffs.size).to eq(1)
mrdc_count = MergeRequestDiffCommit.where(merge_request_diff_id: merge_request_diffs.first.id).count
expect { destroy_project(project, user, {}) }
.to change { MergeRequestDiffCommit.count }.by(-mrdc_count)
end
end
it_behaves_like 'deleting the project'
it 'invalidates personal_project_count cache' do
@ -105,6 +119,25 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do
destroy_project(project, user, {})
end
context "extract_mr_diff_commit_deletions feature flag" do
context "with flag enabled" do
before do
stub_feature_flags(extract_mr_diff_commit_deletions: true)
allow(project).to receive(:destroy!).and_return(true)
end
it_behaves_like "deleting a project with merge requests"
end
context "with flag disabled" do
before do
stub_feature_flags(extract_mr_diff_commit_deletions: false)
end
it_behaves_like "deleting a project with merge requests"
end
end
context 'with running pipelines' do
let!(:pipelines) { create_list(:ci_pipeline, 3, :running, project: project) }
let(:destroy_pipeline_service) { double('DestroyPipelineService', execute: nil) }

View File

@ -2598,6 +2598,45 @@ RSpec.describe QuickActions::InterpretService do
expect(service.commands_executed_count).to eq(3)
end
end
describe 'crm commands' do
let(:add_contacts) { '/add_contacts' }
let(:remove_contacts) { '/remove_contacts' }
before_all do
group.add_developer(developer)
end
context 'when group has no contacts' do
it '/add_contacts is not available' do
_, explanations = service.explain(add_contacts, issue)
expect(explanations).to be_empty
end
it '/remove_contacts is not available' do
_, explanations = service.explain(remove_contacts, issue)
expect(explanations).to be_empty
end
end
context 'when group has contacts' do
let!(:contact) { create(:contact, group: group) }
it '/add_contacts is available' do
_, explanations = service.explain(add_contacts, issue)
expect(explanations).to contain_exactly("Add customer relation contact(s).")
end
it '/remove_contacts is available' do
_, explanations = service.explain(remove_contacts, issue)
expect(explanations).to contain_exactly("Remove customer relation contact(s).")
end
end
end
end
describe '#available_commands' do

View File

@ -26,14 +26,14 @@ module RackAttackSpecHelpers
{ 'AUTHORIZATION' => "Basic #{encoded_login}" }
end
def expect_rejection(&block)
def expect_rejection(name = nil, &block)
yield
expect(response).to have_gitlab_http_status(:too_many_requests)
expect(response.headers.to_h).to include(
'RateLimit-Limit' => a_string_matching(/^\d+$/),
'RateLimit-Name' => a_string_matching(/^throttle_.*$/),
'RateLimit-Name' => name || a_string_matching(/^throttle_.*$/),
'RateLimit-Observed' => a_string_matching(/^\d+$/),
'RateLimit-Remaining' => a_string_matching(/^\d+$/),
'Retry-After' => a_string_matching(/^\d+$/)

View File

@ -1,6 +1,22 @@
# frozen_string_literal: true
module SessionHelpers
# Stub a session in Redis, for use in request specs where we can't mock the session directly.
# This also needs the :clean_gitlab_redis_sessions tag on the spec.
def stub_session(session_hash)
unless RSpec.current_example.metadata[:clean_gitlab_redis_sessions]
raise 'Add :clean_gitlab_redis_sessions to your spec!'
end
session_id = Rack::Session::SessionId.new(SecureRandom.hex)
Gitlab::Redis::Sessions.with do |redis|
redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash))
end
cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id
end
def expect_single_session_with_authenticated_ttl
expect_single_session_with_expiration(Settings.gitlab['session_expire_delay'] * 60)
end

View File

@ -10,6 +10,8 @@ RSpec.shared_examples 'when the snippet is not found' do
end
RSpec.shared_examples 'snippet edit usage data counters' do
include SessionHelpers
context 'when user is sessionless' do
it 'does not track usage data actions' do
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_snippet_editor_edit_action)
@ -20,14 +22,7 @@ RSpec.shared_examples 'snippet edit usage data counters' do
context 'when user is not sessionless', :clean_gitlab_redis_sessions do
before do
session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d')
session_hash = { 'warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]] }
Gitlab::Redis::Sessions.with do |redis|
redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash))
end
cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id
stub_session('warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]])
end
it 'tracks usage data actions', :clean_gitlab_redis_sessions do

View File

@ -580,3 +580,88 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do
end
end
end
# Requires let variables:
# * throttle_setting_prefix: "throttle_authenticated", "throttle_unauthenticated"
RSpec.shared_examples 'rate-limited frontend API requests' do
let(:requests_per_period) { 1 }
let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) }
let(:csrf_session) { { _csrf_token: csrf_token } }
let(:personal_access_token) { nil }
let(:api_path) { '/projects' }
# These don't actually exist, so a 404 is the expected response.
let(:files_api_path) { '/projects/1/repository/files/ref/path' }
let(:packages_api_path) { '/projects/1/packages/foo' }
let(:deprecated_api_path) { '/groups/1?with_projects=true' }
def get_api(path: api_path, csrf: false)
headers = csrf ? { 'X-CSRF-Token' => csrf_token } : nil
get api(path, personal_access_token: personal_access_token), headers: headers
end
def expect_not_found(&block)
yield
expect(response).to have_gitlab_http_status(:not_found)
end
before do
stub_application_setting(
"#{throttle_setting_prefix}_enabled" => true,
"#{throttle_setting_prefix}_requests_per_period" => requests_per_period,
"#{throttle_setting_prefix}_api_enabled" => true,
"#{throttle_setting_prefix}_api_requests_per_period" => requests_per_period,
"#{throttle_setting_prefix}_web_enabled" => true,
"#{throttle_setting_prefix}_web_requests_per_period" => requests_per_period,
"#{throttle_setting_prefix}_files_api_enabled" => true,
"#{throttle_setting_prefix}_packages_api_enabled" => true,
"#{throttle_setting_prefix}_deprecated_api_enabled" => true
)
stub_session(csrf_session)
end
context 'with a CSRF token' do
it 'uses the rate limit for web requests' do
requests_per_period.times { get_api csrf: true }
expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true }
expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: files_api_path }
expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: packages_api_path }
expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: deprecated_api_path }
# API rate limit is not triggered yet
expect_ok { get_api }
expect_not_found { get_api path: files_api_path }
expect_not_found { get_api path: packages_api_path }
expect_not_found { get_api path: deprecated_api_path }
end
context 'without a CSRF session' do
let(:csrf_session) { nil }
it 'always uses the rate limit for API requests' do
requests_per_period.times { get_api csrf: true }
expect_rejection("#{throttle_setting_prefix}_api") { get_api csrf: true }
expect_rejection("#{throttle_setting_prefix}_api") { get_api }
end
end
end
context 'without a CSRF token' do
it 'uses the rate limit for API requests' do
requests_per_period.times { get_api }
expect_rejection("#{throttle_setting_prefix}_api") { get_api }
# Web and custom API rate limits are not triggered yet
expect_ok { get_api csrf: true }
expect_not_found { get_api path: files_api_path }
expect_not_found { get_api path: packages_api_path }
expect_not_found { get_api path: deprecated_api_path }
end
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
RSpec.shared_examples 'renders registration features prompt' do |disabled_field|
it 'renders a placeholder input with registration features message', :aggregate_failures do
render
if disabled_field
expect(rendered).to have_field(disabled_field, disabled: true)
end
expect(rendered).to have_content(s_("RegistrationFeatures|Want to %{feature_title} for free?") % { feature_title: s_('RegistrationFeatures|use this feature') })
expect(rendered).to have_link(s_('RegistrationFeatures|Registration Features Program'))
end
end
RSpec.shared_examples 'does not render registration features prompt' do |disabled_field|
it 'does not render a placeholder input with registration features message', :aggregate_failures do
render
if disabled_field
expect(rendered).not_to have_field(disabled_field, disabled: true)
end
expect(rendered).not_to have_content(s_("RegistrationFeatures|Want to %{feature_title} for free?") % { feature_title: s_('RegistrationFeatures|use this feature') })
expect(rendered).not_to have_link(s_('RegistrationFeatures|Registration Features Program'))
end
end

View File

@ -55,6 +55,14 @@ RSpec.describe X509CertificateCredentialsValidator do
expect(record.errors[:private_key]).to include('could not read private key, is the passphrase correct?')
end
it 'adds an error when private key does not match certificate' do
record.private_key = SSHData::PrivateKey::RSA.generate(4096).openssl.to_pem
validator.validate(record)
expect(record.errors[:private_key]).to include('private key does not match certificate.')
end
it 'has no error when the private key is correct' do
record.private_key = pkey_data
@ -85,7 +93,7 @@ RSpec.describe X509CertificateCredentialsValidator do
validator.validate(record)
expect(record.errors[:private_key]).not_to be_empty
expect(record.errors[:private_key]).to include('could not read private key, is the passphrase correct?')
end
end
end

View File

@ -33,4 +33,31 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
end
end
end
describe 'prompt user about registration features' do
before do
assign(:application_setting, app_settings)
allow(view).to receive(:current_user).and_return(user)
end
context 'when service ping is enabled' do
before do
stub_application_setting(usage_ping_enabled: true)
end
it_behaves_like 'does not render registration features prompt', :application_setting_disabled_repository_size_limit
end
context 'with no license and service ping disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
if Gitlab.ee?
allow(License).to receive(:current).and_return(nil)
end
end
it_behaves_like 'renders registration features prompt', :application_setting_disabled_repository_size_limit
end
end
end

View File

@ -139,4 +139,26 @@ RSpec.describe 'projects/edit' do
end
end
end
describe 'prompt user about registration features' do
context 'when service ping is enabled' do
before do
stub_application_setting(usage_ping_enabled: true)
end
it_behaves_like 'does not render registration features prompt', :project_disabled_repository_size_limit
end
context 'with no license and service ping disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
if Gitlab.ee?
allow(License).to receive(:current).and_return(nil)
end
end
it_behaves_like 'renders registration features prompt', :project_disabled_repository_size_limit
end
end
end

View File

@ -10,7 +10,6 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/prometheus/client_golang/prometheus"
@ -31,11 +30,8 @@ const maxFilesAllowed = 10
var (
ErrInjectedClientParam = errors.New("injected client parameter")
ErrTooManyFilesUploaded = fmt.Errorf("upload request contains more than %v files", maxFilesAllowed)
ErrUnexpectedFilePart = errors.New("Content-Disposition contains unexpected filepart")
)
var filePartRegex = regexp.MustCompile(`;\s*filename\b`)
var (
multipartUploadRequests = promauto.NewCounterVec(
prometheus.CounterOpts{
@ -111,11 +107,6 @@ func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, pr
if p.FileName() != "" {
err = rew.handleFilePart(r.Context(), name, p, opts)
} else {
err = verifyContentDisposition(p)
if err != nil {
return err
}
err = rew.copyPart(r.Context(), name, p)
}
@ -127,14 +118,6 @@ func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, pr
return nil
}
func verifyContentDisposition(p *multipart.Part) error {
if filePartRegex.MatchString(p.Header.Get("Content-Disposition")) {
return ErrUnexpectedFilePart
}
return nil
}
func (rew *rewriter) handleFilePart(ctx context.Context, name string, p *multipart.Part, opts *filestore.SaveFileOpts) error {
if rew.filter.Count() >= maxFilesAllowed {
return ErrTooManyFilesUploaded

View File

@ -1,8 +1,6 @@
package upload
import (
"mime/multipart"
"net/textproto"
"os"
"runtime"
"testing"
@ -56,47 +54,3 @@ func TestImageTypeRecongition(t *testing.T) {
})
}
}
func TestVerifyContentDisposition(t *testing.T) {
tests := []struct {
desc string
contentDisposition string
error error
}{
{
desc: "without content disposition",
contentDisposition: "",
error: nil,
}, {
desc: "content disposition without filename",
contentDisposition: `form-data; name="filename"`,
error: nil,
}, {
desc: "with filename",
contentDisposition: `form-data; name="file"; filename=foobar`,
error: ErrUnexpectedFilePart,
}, {
desc: "with filename*",
contentDisposition: `form-data; name="file"; filename*=UTF-8''foobar`,
error: ErrUnexpectedFilePart,
}, {
desc: "filename and filename*",
contentDisposition: `form-data; name="file"; filename=foobar; filename*=UTF-8''foobar`,
error: ErrUnexpectedFilePart,
},
}
for _, testCase := range tests {
t.Run(testCase.desc, func(t *testing.T) {
h := make(textproto.MIMEHeader)
if testCase.contentDisposition != "" {
h.Set("Content-Disposition", testCase.contentDisposition)
h.Set("Content-Type", "application/octet-stream")
}
require.Equal(t, testCase.error, verifyContentDisposition(&multipart.Part{Header: h}))
})
}
}

View File

@ -35,7 +35,7 @@ func HandleFileUploads(w http.ResponseWriter, r *http.Request, h http.Handler, p
switch err {
case ErrInjectedClientParam:
helper.CaptureAndFail(w, r, err, "Bad Request", http.StatusBadRequest)
case ErrTooManyFilesUploaded, ErrUnexpectedFilePart:
case ErrTooManyFilesUploaded:
helper.CaptureAndFail(w, r, err, err.Error(), http.StatusBadRequest)
case http.ErrNotMultipart:
h.ServeHTTP(w, r)

View File

@ -4,12 +4,10 @@ import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/textproto"
"os"
"regexp"
"strconv"
@ -386,99 +384,6 @@ func TestInvalidFileNames(t *testing.T) {
}
}
func TestContentDisposition(t *testing.T) {
testhelper.ConfigureSecret()
tempPath, err := ioutil.TempDir("", "uploads")
require.NoError(t, err)
defer os.RemoveAll(tempPath)
tests := []struct {
desc string
contentDisposition string
code int
body string
}{
{
desc: "empty header",
contentDisposition: "",
code: 200,
body: "",
},
{
desc: "without filename",
contentDisposition: `form-data; name="filename"`,
code: 200,
body: "",
},
{
desc: "with filename",
contentDisposition: `form-data; name="file"; filename=foobar`,
code: 200,
body: "",
},
{
desc: "with filename* supported charset",
contentDisposition: `form-data; name="file"; filename*=UTF-8''foobar`,
code: 200,
body: "",
},
{
desc: "with both filename and filename* supported charset",
contentDisposition: `form-data; name="file"; filename=foobar; filename*=UTF-8''foobar`,
code: 200,
body: "",
},
{
desc: "with filename and filename* unsupported charset",
contentDisposition: `form-data; name="file"; filename=foobar; filename*=UTF-16''foobar`,
code: 200,
body: "",
},
{
desc: "unsupported charset",
contentDisposition: `form-data; name="file"; filename*=UTF-16''foobar`,
code: 400,
body: ErrUnexpectedFilePart.Error() + "\n",
},
}
for _, testCase := range tests {
t.Run(testCase.desc, func(t *testing.T) {
// Create custom Content-Disposition with filename* and charset
// Example: filename*=UTF-8''application.log
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", testCase.contentDisposition)
h.Set("Content-Type", "application/octet-stream")
buffer := &bytes.Buffer{}
writer := multipart.NewWriter(buffer)
file, err := writer.CreatePart(h)
require.NoError(t, err)
fmt.Fprint(file, "test")
writer.Close()
httpRequest := httptest.NewRequest("POST", "/example", buffer)
httpRequest.Header.Set("Content-Type", writer.FormDataContentType())
response := httptest.NewRecorder()
apiResponse := &api.Response{TempPath: tempPath}
preparer := &DefaultPreparer{}
opts, _, err := preparer.Prepare(apiResponse)
require.NoError(t, err)
HandleFileUploads(response, httpRequest, nilHandler, apiResponse, &SavedFileTracker{Request: httpRequest}, opts)
body, err := io.ReadAll(response.Body)
require.NoError(t, err)
require.Equal(t, testCase.code, response.Code)
require.Equal(t, testCase.body, string(body))
})
}
}
func TestUploadHandlerRemovingExif(t *testing.T) {
content, err := ioutil.ReadFile("exif/testdata/sample_exif.jpg")
require.NoError(t, err)