Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c17eb7c970
commit
39cb2fdf01
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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+$/)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue