Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-12 15:13:54 +00:00
parent da646aac6c
commit 462b603802
60 changed files with 721 additions and 159 deletions

View File

@ -18,16 +18,6 @@ Rails/SaveBang:
- ee/spec/models/visible_approvable_spec.rb
- ee/spec/models/vulnerabilities/feedback_spec.rb
- ee/spec/models/vulnerabilities/issue_link_spec.rb
- ee/spec/services/ee/merge_requests/update_service_spec.rb
- ee/spec/services/ee/notes/quick_actions_service_spec.rb
- ee/spec/services/ee/notification_service_spec.rb
- ee/spec/services/epic_links/create_service_spec.rb
- ee/spec/services/epics/close_service_spec.rb
- ee/spec/services/epics/issue_promote_service_spec.rb
- ee/spec/services/epics/reopen_service_spec.rb
- ee/spec/services/epics/tree_reorder_service_spec.rb
- ee/spec/services/epics/update_dates_service_spec.rb
- ee/spec/services/epics/update_service_spec.rb
- ee/spec/services/geo/blob_verification_secondary_service_spec.rb
- ee/spec/services/geo/files_expire_service_spec.rb
- ee/spec/services/geo/metrics_update_service_spec.rb

View File

@ -25,9 +25,7 @@ Style/OpenStructUse:
- spec/graphql/mutations/commits/create_spec.rb
- spec/helpers/application_settings_helper_spec.rb
- spec/helpers/profiles_helper_spec.rb
- spec/initializers/doorkeeper_spec.rb
- spec/lib/gitlab/auth/o_auth/provider_spec.rb
- spec/lib/gitlab/database/migrations/runner_spec.rb
- spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb
- spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
- spec/lib/gitlab/legacy_github_import/project_creator_spec.rb

View File

@ -1 +1 @@
e02b0d67e48ed5a4493b073c9836d376a780f34d
7055518ce76486791c3450a8a47b673891b6e2d6

View File

@ -313,7 +313,7 @@ gem 'pg_query', '~> 2.1'
gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation
gem 'gitlab-labkit', '~> 0.21.1'
gem 'gitlab-labkit', '~> 0.21.3'
# Thrift is a dependency of gitlab-labkit, we want a version higher than 0.14.0
# because of https://gitlab.com/gitlab-org/gitlab/-/issues/321900
gem 'thrift', '>= 0.14.0'
@ -485,14 +485,14 @@ end
gem 'spamcheck', '~> 0.1.0'
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 14.4.0.pre.rc43'
gem 'gitaly', '~> 14.6.0.pre.rc1'
# KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.0.2'
gem 'grpc', '~> 1.30.2'
gem 'grpc', '~> 1.42.0'
gem 'google-protobuf', '~> 3.17.1'
gem 'google-protobuf', '~> 3.19.0'
gem 'toml-rb', '~> 2.0'

View File

@ -452,7 +452,7 @@ GEM
rails (>= 3.2.0)
git (1.7.0)
rchardet (~> 1.8)
gitaly (14.4.0.pre.rc43)
gitaly (14.6.0.pre.rc1)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab (4.16.1)
@ -474,10 +474,10 @@ GEM
fog-json (~> 1.2.0)
mime-types
ms_rest_azure (~> 0.12.0)
gitlab-labkit (0.21.1)
gitlab-labkit (0.21.3)
actionpack (>= 5.0.0, < 7.0.0)
activesupport (>= 5.0.0, < 7.0.0)
grpc (~> 1.30.2)
grpc (>= 1.37)
jaeger-client (~> 1.1)
opentracing (~> 0.4)
pg_query (~> 2.1)
@ -531,8 +531,8 @@ GEM
signet (~> 0.12)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-protobuf (3.17.3)
googleapis-common-protos-types (1.1.0)
google-protobuf (3.19.1)
googleapis-common-protos-types (1.3.0)
google-protobuf (~> 3.14)
googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0)
@ -580,8 +580,8 @@ GEM
graphql (~> 1.6)
html-pipeline (~> 2.8)
sass (~> 3.4)
grpc (1.30.2)
google-protobuf (~> 3.12)
grpc (1.42.0)
google-protobuf (~> 3.18)
googleapis-common-protos-types (~> 1.0)
gssapi (1.2.0)
ffi (>= 1.0.1)
@ -1473,13 +1473,13 @@ DEPENDENCIES
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 14.4.0.pre.rc43)
gitaly (~> 14.6.0.pre.rc1)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 2.6.1)
gitlab-experiment (~> 0.6.5)
gitlab-fog-azure-rm (~> 1.2.0)
gitlab-labkit (~> 0.21.1)
gitlab-labkit (~> 0.21.3)
gitlab-license (~> 2.0)
gitlab-license_finder (~> 6.0)
gitlab-mail_room (~> 0.0.9)
@ -1492,7 +1492,7 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.4.0)
google-api-client (~> 0.33)
google-protobuf (~> 3.17.1)
google-protobuf (~> 3.19.0)
gpgme (~> 2.0.19)
grape (~> 1.5.2)
grape-entity (~> 0.10.0)
@ -1502,7 +1502,7 @@ DEPENDENCIES
graphlient (~> 0.4.0)
graphql (~> 1.11.10)
graphql-docs (~> 1.6.0)
grpc (~> 1.30.2)
grpc (~> 1.42.0)
gssapi
guard-rspec
haml_lint (~> 0.36.0)

View File

@ -1,6 +1,7 @@
<script>
import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { JOB_SIDEBAR } from '../../constants';
import CommitBlock from '../../components/commit_block.vue';
@ -25,6 +26,7 @@ export default {
GlDropdownItem,
TooltipOnTruncate,
},
mixins: [glFeatureFlagsMixin()],
props: {
bridgeJob: {
type: Object,
@ -54,7 +56,10 @@ export default {
</h4>
</tooltip-on-truncate>
<!-- TODO: implement retry actions -->
<div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right">
<div
v-if="glFeatures.triggerJobRetryAction"
class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right"
>
<gl-dropdown
:text="$options.i18n.retryButton"
category="primary"

View File

@ -26,6 +26,7 @@ import trackShowInviteMemberLink from '~/sidebar/track_invite_members';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { LabelType } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import eventHub from '~/sidebar/event_hub';
import Translate from '../vue_shared/translate';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue';
@ -600,6 +601,12 @@ export function mountSidebar(mediator, store) {
mountTimeTrackingComponent();
mountSeverityComponent();
if (window.gon?.features?.mrAttentionRequests) {
eventHub.$on('removeCurrentUserAttentionRequested', () =>
mediator.removeCurrentUserAttentionRequested(),
);
}
}
export { getSidebarOptions };

View File

@ -30,7 +30,7 @@ export default class SidebarMediator {
this.store.addAssignee(this.store.currentUser);
}
saveAssignees(field) {
async saveAssignees(field) {
const selected = this.store.assignees.map((u) => u.id);
// If there are no ids, that means we have to unassign (which is id = 0)
@ -38,10 +38,22 @@ export default class SidebarMediator {
const assignees = selected.length === 0 ? [0] : selected;
const data = { assignee_ids: assignees };
return this.service.update(field, data);
try {
const res = await this.service.update(field, data);
this.store.overwrite('assignees', res.data.assignees);
if (res.data.reviewers) {
this.store.overwrite('reviewers', res.data.reviewers);
}
return Promise.resolve(res);
} catch (e) {
return Promise.reject(e);
}
}
saveReviewers(field) {
async saveReviewers(field) {
const selected = this.store.reviewers.map((u) => u.id);
// If there are no ids, that means we have to unassign (which is id = 0)
@ -49,7 +61,16 @@ export default class SidebarMediator {
const reviewers = selected.length === 0 ? [0] : selected;
const data = { reviewer_ids: reviewers };
return this.service.update(field, data);
try {
const res = await this.service.update(field, data);
this.store.overwrite('reviewers', res.data.reviewers);
this.store.overwrite('assignees', res.data.assignees);
return Promise.resolve(res);
} catch (e) {
return Promise.reject();
}
}
requestReview({ userId, callback }) {
@ -63,6 +84,19 @@ export default class SidebarMediator {
.catch(() => callback(userId, false));
}
removeCurrentUserAttentionRequested() {
const currentUserId = gon.current_user_id;
const currentUserReviewer = this.store.findReviewer({ id: currentUserId });
const currentUserAssignee = this.store.findAssignee({ id: currentUserId });
if (currentUserReviewer?.attention_requested || currentUserAssignee?.attention_requested) {
// Update current users attention_requested state
this.store.updateReviewer(currentUserId, 'attention_requested');
this.store.updateAssignee(currentUserId, 'attention_requested');
}
}
async toggleAttentionRequested(type, { user, callback }) {
try {
const isReviewer = type === 'reviewer';
@ -82,15 +116,7 @@ export default class SidebarMediator {
const currentUserId = gon.current_user_id;
if (currentUserId !== user.id) {
const currentUserReviewerOrAssignee = isReviewer
? this.store.findReviewer({ id: currentUserId })
: this.store.findAssignee({ id: currentUserId });
if (currentUserReviewerOrAssignee?.attention_requested) {
// Update current users attention_requested state
this.store.updateReviewer(currentUserId, 'attention_requested');
this.store.updateAssignee(currentUserId, 'attention_requested');
}
this.removeCurrentUserAttentionRequested();
}
toast(sprintf(__('Requested attention from @%{username}'), { username: user.username }));

View File

@ -98,6 +98,10 @@ export default class SidebarStore {
}
}
overwrite(key, newData) {
this[key] = newData;
}
findAssignee(findAssignee) {
return this.assignees.find(({ id }) => id === findAssignee.id);
}

View File

@ -3,6 +3,7 @@ import { GlButton } from '@gitlab/ui';
import createFlash from '~/flash';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { s__ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
import eventHub from '../../event_hub';
import approvalsMixin from '../../mixins/approvals';
import MrWidgetContainer from '../mr_widget_container.vue';
@ -172,6 +173,7 @@ export default {
this.mr.setApprovals(data);
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('ApprovalUpdated');
sidebarEventHub.$emit('removeCurrentUserAttentionRequested');
this.$emit('updated');
})
.catch(errFn)

View File

@ -41,7 +41,7 @@ class Import::GitlabController < Import::BaseController
override :importable_repos
def importable_repos
client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS)
client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS).to_a
end
override :incompatible_repos

View File

@ -19,6 +19,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:infinitely_collapsible_sections, @project, default_enabled: :yaml)
push_frontend_feature_flag(:trigger_job_retry_action, @project, default_enabled: :yaml)
end
layout 'project'

View File

@ -18,4 +18,14 @@ module SshKeysHelper
container: 'body'
}
end
def ssh_key_allowed_algorithms
allowed_algorithms = Gitlab::CurrentSettings.allowed_key_types.flat_map do |ssh_key_type_name|
Gitlab::SSHPublicKey.supported_algorithms_for_name(ssh_key_type_name)
end
quoted_allowed_algorithms = allowed_algorithms.map { |name| "'#{name}'" }
Gitlab::Utils.to_exclusive_sentence(quoted_allowed_algorithms)
end
end

View File

@ -1292,6 +1292,12 @@ module Ci
end
end
def use_variables_builder_definitions?
strong_memoize(:use_variables_builder_definitions) do
::Feature.enabled?(:ci_use_variables_builder_definitions, project, default_enabled: :yaml)
end
end
private
def add_message(severity, content)

View File

@ -13,6 +13,8 @@ module Ci
track_duration do
variables = pipeline.variables_builder.scoped_variables(self, environment: environment, dependencies: dependencies)
next variables if pipeline.use_variables_builder_definitions?
variables.concat(project.predefined_variables)
variables.concat(pipeline.predefined_variables)
variables.concat(runner.predefined_variables) if runnable? && runner
@ -60,49 +62,27 @@ module Ci
end
def user_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables if user.blank?
variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
variables.append(key: 'GITLAB_USER_NAME', value: user.name)
end
pipeline.variables_builder.user_variables(user)
end
def kubernetes_variables
::Gitlab::Ci::Variables::Collection.new.tap do |collection|
# Should get merged with the cluster kubeconfig in deployment_variables, see
# https://gitlab.com/gitlab-org/gitlab/-/issues/335089
template = ::Ci::GenerateKubeconfigService.new(self).execute
if template.valid?
collection.append(key: 'KUBECONFIG', value: template.to_yaml, public: false, file: true)
end
end
pipeline.variables_builder.kubernetes_variables(self)
end
def deployment_variables(environment:)
return [] unless environment
project.deployment_variables(
environment: environment,
kubernetes_namespace: expanded_kubernetes_namespace
)
pipeline.variables_builder.deployment_variables(job: self, environment: environment)
end
def secret_instance_variables
project.ci_instance_variables_for(ref: git_ref)
pipeline.variables_builder.secret_instance_variables(ref: git_ref)
end
def secret_group_variables(environment: expanded_environment_name)
return [] unless project.group
project.group.ci_variables_for(git_ref, project, environment: environment)
pipeline.variables_builder.secret_group_variables(environment: environment, ref: git_ref)
end
def secret_project_variables(environment: expanded_environment_name)
project.ci_variables_for(ref: git_ref, environment: environment)
pipeline.variables_builder.secret_project_variables(environment: environment, ref: git_ref)
end
end
end

View File

@ -627,14 +627,13 @@ class Group < Namespace
end
end
def group_member(user)
def member(user)
if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id }
else
group_members.find_by(user_id: user)
end
end
alias_method :resource_member, :group_member
def highest_group_member(user)
GroupMember.where(source_id: self_and_ancestors_ids, user_id: user.id).order(:access_level).last

View File

@ -22,7 +22,7 @@ module Namespaces
unscoped.where(id: root_ids)
end
def self_and_ancestors(include_self: true, hierarchy_order: nil)
def self_and_ancestors(include_self: true, upto: nil, hierarchy_order: nil)
return super unless use_traversal_ids_for_ancestor_scopes?
ancestors_cte, base_cte = ancestor_ctes
@ -35,11 +35,15 @@ module Namespaces
.where(namespaces[:id].eq(ancestors_cte.table[:ancestor_id]))
.order_by_depth(hierarchy_order)
if include_self
records
else
records.where(ancestors_cte.table[:base_id].not_eq(ancestors_cte.table[:ancestor_id]))
unless include_self
records = records.where(ancestors_cte.table[:base_id].not_eq(ancestors_cte.table[:ancestor_id]))
end
if upto
records = records.where.not(id: unscoped.where(id: upto).select('unnest(traversal_ids)'))
end
records
end
def self_and_ancestor_ids(include_self: true)

View File

@ -17,8 +17,8 @@ module Namespaces
.where(namespaces: { parent_id: nil })
end
def self_and_ancestors(include_self: true, hierarchy_order: nil)
records = Gitlab::ObjectHierarchy.new(all).base_and_ancestors(hierarchy_order: hierarchy_order)
def self_and_ancestors(include_self: true, upto: nil, hierarchy_order: nil)
records = Gitlab::ObjectHierarchy.new(all).base_and_ancestors(upto: upto, hierarchy_order: hierarchy_order)
if include_self
records

View File

@ -1661,14 +1661,13 @@ class Project < ApplicationRecord
attrs
end
def project_member(user)
def member(user)
if project_members.loaded?
project_members.find { |member| member.user_id == user.id }
else
project_members.find_by(user_id: user)
end
end
alias_method :resource_member, :project_member
def membership_locked?
false

View File

@ -1336,7 +1336,7 @@ class User < ApplicationRecord
def can_leave_project?(project)
project.namespace != namespace &&
project.project_member(self)
project.member(self)
end
def full_website_url

View File

@ -5,5 +5,9 @@ class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity
expose :can_merge do |merge_request|
merge_request.can_be_merged_by?(current_user)
end
expose :can_update_merge_request do |merge_request|
current_user.can?(:update_merge_request, merge_request)
end
end
end

View File

@ -59,7 +59,9 @@ module MergeRequests
merge_request_activity_counter.track_users_review_requested(users: new_reviewers)
merge_request_activity_counter.track_reviewers_changed_action(user: current_user)
remove_attention_requested(merge_request, current_user)
unless new_reviewers.include?(current_user)
remove_attention_requested(merge_request, current_user)
end
end
def cleanup_environments(merge_request)

View File

@ -23,7 +23,9 @@ module MergeRequests
execute_assignees_hooks(merge_request, old_assignees) if options[:execute_hooks]
remove_attention_requested(merge_request, current_user)
unless new_assignees.include?(current_user)
remove_attention_requested(merge_request, current_user)
end
end
private

View File

@ -17,6 +17,7 @@ module MergeRequests
reset_approvals_cache(merge_request)
create_note(merge_request)
merge_request_activity_counter.track_unapprove_mr_action(user: current_user)
remove_attention_requested(merge_request, current_user)
end
success

View File

@ -43,13 +43,9 @@ module ResourceAccessTokens
def find_member
strong_memoize(:member) do
if resource.is_a?(Project)
resource.project_member(bot_user)
elsif resource.is_a?(Group)
resource.group_member(bot_user)
else
false
end
next false unless resource.is_a?(Project) || resource.is_a?(Group)
resource.member(bot_user)
end
end

View File

@ -0,0 +1,8 @@
- return unless registration_features_can_be_prompted?
.form-group
= f.label :disabled_ip_restriction_ranges, class: 'label-bold' do
= _('Allow access to the following IP addresses')
= f.text_field :disabled_ip_restriction_ranges, value: '', class: 'form-control', disabled: true
%span.form-text.text-muted
= render 'shared/registration_features_discovery_message'

View File

@ -31,6 +31,7 @@
= render 'groups/settings/project_access_token_creation', f: f, group: @group
= render_if_exists 'groups/settings/delayed_project_removal', f: f, group: @group
= render 'groups/settings/ip_restriction_registration_features_cta', f: f
= render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
= render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group
= render 'groups/settings/lfs', f: f

View File

@ -12,7 +12,7 @@
= _('Add a GPG key')
%p.profile-settings-content
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/repository/gpg_signed_commits/index.md') }
= _('Before you can add a GPG key you need to %{help_link_start}Generate it.%{help_link_end}'.html_safe) % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
= _('Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
= render 'form'
%hr
%h5

View File

@ -5,8 +5,8 @@
.form-group
= f.label :key, s_('Profiles|Key'), class: 'label-bold'
%p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity.")
= f.text_area :key, class: "form-control gl-form-input js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-ed25519 …" or "ssh-rsa …"')
= f.text_area :key, class: "form-control gl-form-input js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true
%p.form-text.text-muted= s_('Profiles|Begins with %{ssh_key_algorithms}.') % { ssh_key_algorithms: ssh_key_allowed_algorithms }
.form-row
.col.form-group
= f.label :title, _('Title'), class: 'label-bold'

View File

@ -11,11 +11,8 @@
%h5.gl-mt-0
= _('Add an SSH key')
%p.profile-settings-content
- generate_link_url = help_page_path("ssh/index", anchor: 'generate-an-ssh-key-pair')
- existing_link_url = help_page_path("ssh/index", anchor: 'see-if-you-have-an-existing-ssh-key-pair')
- generate_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_link_url }
- existing_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: existing_link_url }
= _('To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}.').html_safe % { generate_link_start: generate_link_start, existing_link_start: existing_link_start, link_end: '</a>'.html_safe }
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('ssh/index.md') }
= _('Add an SSH key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
= render 'form'
%hr
%h5

View File

@ -55,7 +55,7 @@
- else
%span.token-never-expires-label= _('Never')
- if project
%td= project.project_member(token.user).human_access
%td= project.member(token.user).human_access
%td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: "gl-button btn btn-danger btn-sm float-right qa-revoke-button #{'btn-danger-secondary' unless token.expires?}", data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
- else
.settings-message.text-center

View File

@ -0,0 +1,8 @@
---
name: ci_skip_require_credit_card_for_addon_ci_minutes
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77829
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349841
milestone: '14.7'
type: development
group: group::fulfillment
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: ci_use_variables_builder_definitions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75254
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349049
milestone: '14.7'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: trigger_job_retry_action
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77951
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349966
milestone: '14.7'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -2941,6 +2941,27 @@ Input type: `IssueSetEpicInput`
| <a id="mutationissuesetepicerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationissuesetepicissue"></a>`issue` | [`Issue`](#issue) | Issue after mutation. |
### `Mutation.issueSetEscalationPolicy`
Input type: `IssueSetEscalationPolicyInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationissuesetescalationpolicyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationissuesetescalationpolicyescalationpolicyid"></a>`escalationPolicyId` | [`IncidentManagementEscalationPolicyID`](#incidentmanagementescalationpolicyid) | Global ID of the escalation policy to assign to the issue. Policy will be removed if absent or set to null. |
| <a id="mutationissuesetescalationpolicyiid"></a>`iid` | [`String!`](#string) | IID of the issue to mutate. |
| <a id="mutationissuesetescalationpolicyprojectpath"></a>`projectPath` | [`ID!`](#id) | Project the issue to mutate is in. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationissuesetescalationpolicyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationissuesetescalationpolicyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationissuesetescalationpolicyissue"></a>`issue` | [`Issue`](#issue) | Issue after mutation. |
### `Mutation.issueSetIteration`
Input type: `IssueSetIterationInput`
@ -10294,6 +10315,7 @@ Relationship between an epic and an issue.
| <a id="epicissueemailsdisabled"></a>`emailsDisabled` | [`Boolean!`](#boolean) | Indicates if a project has email notifications disabled: `true` if email notifications are disabled. |
| <a id="epicissueepic"></a>`epic` | [`Epic`](#epic) | Epic to which this issue belongs. |
| <a id="epicissueepicissueid"></a>`epicIssueId` | [`ID!`](#id) | ID of the epic-issue relation. |
| <a id="epicissueescalationpolicy"></a>`escalationPolicy` | [`EscalationPolicyType`](#escalationpolicytype) | Escalation policy associated with the issue. Available for issues which support escalation. |
| <a id="epicissuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Current health status. |
| <a id="epicissuehidden"></a>`hidden` | [`Boolean`](#boolean) | Indicates the issue is hidden because the author has been banned. Will always return `null` if `ban_user_feature_flag` feature flag is disabled. |
| <a id="epicissuehumantimeestimate"></a>`humanTimeEstimate` | [`String`](#string) | Human-readable time estimate of the issue. |
@ -11476,6 +11498,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="issueduedate"></a>`dueDate` | [`Time`](#time) | Due date of the issue. |
| <a id="issueemailsdisabled"></a>`emailsDisabled` | [`Boolean!`](#boolean) | Indicates if a project has email notifications disabled: `true` if email notifications are disabled. |
| <a id="issueepic"></a>`epic` | [`Epic`](#epic) | Epic to which this issue belongs. |
| <a id="issueescalationpolicy"></a>`escalationPolicy` | [`EscalationPolicyType`](#escalationpolicytype) | Escalation policy associated with the issue. Available for issues which support escalation. |
| <a id="issuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Current health status. |
| <a id="issuehidden"></a>`hidden` | [`Boolean`](#boolean) | Indicates the issue is hidden because the author has been banned. Will always return `null` if `ban_user_feature_flag` feature flag is disabled. |
| <a id="issuehumantimeestimate"></a>`humanTimeEstimate` | [`String`](#string) | Human-readable time estimate of the issue. |

View File

@ -8,7 +8,7 @@ module API
if options[:project_members]
options[:project_members].find { |member| member.source_id == project.id }
else
project.project_member(options[:current_user])
project.member(options[:current_user])
end
end

View File

@ -4,7 +4,7 @@ module API
module Entities
class ResourceAccessToken < Entities::PersonalAccessToken
expose :access_level do |token, options|
options[:resource].resource_member(token.user).access_level
options[:resource].member(token.user).access_level
end
end
end

View File

@ -13,12 +13,76 @@ module Gitlab
def scoped_variables(job, environment:, dependencies:)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.concat(predefined_variables(job))
next variables unless pipeline.use_variables_builder_definitions?
variables.concat(project.predefined_variables)
variables.concat(pipeline.predefined_variables)
variables.concat(job.runner.predefined_variables) if job.runnable? && job.runner
variables.concat(kubernetes_variables(job))
variables.concat(deployment_variables(environment: environment, job: job))
variables.concat(job.yaml_variables)
variables.concat(user_variables(job.user))
variables.concat(job.dependency_variables) if dependencies
variables.concat(secret_instance_variables(ref: job.git_ref))
variables.concat(secret_group_variables(environment: environment, ref: job.git_ref))
variables.concat(secret_project_variables(environment: environment, ref: job.git_ref))
variables.concat(job.trigger_request.user_variables) if job.trigger_request
variables.concat(pipeline.variables)
variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
end
end
def kubernetes_variables(job)
::Gitlab::Ci::Variables::Collection.new.tap do |collection|
# Should get merged with the cluster kubeconfig in deployment_variables, see
# https://gitlab.com/gitlab-org/gitlab/-/issues/335089
template = ::Ci::GenerateKubeconfigService.new(job).execute
if template.valid?
collection.append(key: 'KUBECONFIG', value: template.to_yaml, public: false, file: true)
end
end
end
def deployment_variables(environment:, job:)
return [] unless environment
project.deployment_variables(
environment: environment,
kubernetes_namespace: job.expanded_kubernetes_namespace
)
end
def user_variables(user)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables if user.blank?
variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
variables.append(key: 'GITLAB_USER_NAME', value: user.name)
end
end
def secret_instance_variables(ref:)
project.ci_instance_variables_for(ref: ref)
end
def secret_group_variables(environment:, ref:)
return [] unless project.group
project.group.ci_variables_for(ref, project, environment: environment)
end
def secret_project_variables(environment:, ref:)
project.ci_variables_for(ref: ref, environment: environment)
end
private
attr_reader :pipeline
delegate :project, to: :pipeline
def predefined_variables(job)
Gitlab::Ci::Variables::Collection.new.tap do |variables|

View File

@ -2,13 +2,15 @@
module Gitlab
class SSHPublicKey
Technology = Struct.new(:name, :key_class, :supported_sizes)
Technology = Struct.new(:name, :key_class, :supported_sizes, :supported_algorithms)
# 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]),
Technology.new(:dsa, OpenSSL::PKey::DSA, [1024, 2048, 3072]),
Technology.new(:ecdsa, OpenSSL::PKey::EC, [256, 384, 521]),
Technology.new(:ed25519, Net::SSH::Authentication::ED25519::PubKey, [256])
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))
].freeze
def self.technology(name)
@ -24,7 +26,15 @@ module Gitlab
end
def self.supported_sizes(name)
technology(name)&.supported_sizes
technology(name).supported_sizes
end
def self.supported_algorithms
TECHNOLOGIES.flat_map { |tech| tech.supported_algorithms }
end
def self.supported_algorithms_for_name(name)
technology(name).supported_algorithms
end
def self.sanitize(key_content)

View File

@ -179,7 +179,7 @@ namespace :gitlab do
task reindex: :environment do
unless Gitlab::Database::Reindexing.enabled?
puts "This feature (database_reindexing) is currently disabled.".color(:yellow)
next
exit
end
Gitlab::Database::Reindexing.invoke
@ -193,7 +193,7 @@ namespace :gitlab do
task database_name => :environment do
unless Gitlab::Database::Reindexing.enabled?
puts "This feature (database_reindexing) is currently disabled.".color(:yellow)
next
exit
end
Gitlab::Database::Reindexing.invoke(database_name)

View File

@ -2009,6 +2009,9 @@ msgstr ""
msgid "Add a GPG key"
msgstr ""
msgid "Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}"
msgstr ""
msgid "Add a Jaeger URL to replace this page with a link to your Jaeger server. You first need to %{link_start_tag}install Jaeger%{link_end_tag}."
msgstr ""
@ -2075,6 +2078,9 @@ msgstr ""
msgid "Add an SSH key"
msgstr ""
msgid "Add an SSH key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}"
msgstr ""
msgid "Add an existing issue"
msgstr ""
@ -4620,6 +4626,9 @@ msgstr ""
msgid "Are you sure you want to %{action} %{name}?"
msgstr ""
msgid "Are you sure you want to approve %{user}?"
msgstr ""
msgid "Are you sure you want to attempt to merge?"
msgstr ""
@ -5535,9 +5544,15 @@ msgstr ""
msgid "Billings|Your account has been validated"
msgstr ""
msgid "Billing|%{user} was successfully approved"
msgstr ""
msgid "Billing|An email address is only visible for users with public emails."
msgstr ""
msgid "Billing|An error occurred while approving %{user}"
msgstr ""
msgid "Billing|An error occurred while getting a billable member details"
msgstr ""
@ -9070,6 +9085,9 @@ msgstr ""
msgid "Confirm"
msgstr ""
msgid "Confirm approval"
msgstr ""
msgid "Confirm new password"
msgstr ""
@ -25702,9 +25720,6 @@ msgstr ""
msgid "Paste this DSN into your Sentry SDK"
msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity."
msgstr ""
msgid "Patch to apply"
msgstr ""
@ -27109,6 +27124,9 @@ msgstr ""
msgid "Profiles|Avatar will be removed. Are you sure?"
msgstr ""
msgid "Profiles|Begins with %{ssh_key_algorithms}."
msgstr ""
msgid "Profiles|Bio"
msgstr ""
@ -27358,9 +27376,6 @@ msgstr ""
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr ""
msgid "Profiles|Typically starts with \"ssh-ed25519 …\" or \"ssh-rsa …\""
msgstr ""
msgid "Profiles|Update profile settings"
msgstr ""
@ -37077,9 +37092,6 @@ msgstr ""
msgid "To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr ""
msgid "To add the entry manually, provide the following details to the application on your phone."
msgstr ""

View File

@ -30,6 +30,14 @@ RSpec.describe Import::GitlabController do
expect(session[:gitlab_access_token]).to eq(token)
expect(controller).to redirect_to(status_import_gitlab_url)
end
it "importable_repos should return an array" do
allow_next_instance_of(Gitlab::GitlabImport::Client) do |instance|
allow(instance).to receive(:projects).and_return([{ "id": 1 }].to_enum)
end
expect(controller.send(:importable_repos)).to be_an_instance_of(Array)
end
end
describe "GET status" do

View File

@ -7,12 +7,16 @@ import { mockCommit, mockJob } from '../mock_data';
describe('Bridge Sidebar', () => {
let wrapper;
const createComponent = (props) => {
const createComponent = ({ featureFlag } = {}) => {
wrapper = shallowMount(BridgeSidebar, {
provide: {
glFeatures: {
triggerJobRetryAction: featureFlag,
},
},
propsData: {
bridgeJob: mockJob,
commit: mockCommit,
...props,
},
});
};
@ -35,10 +39,6 @@ describe('Bridge Sidebar', () => {
expect(findJobTitle().text()).toBe(mockJob.name);
});
it('renders retry dropdown', () => {
expect(findRetryDropdown().exists()).toBe(true);
});
it('renders commit information', () => {
expect(findCommitBlock().exists()).toBe(true);
});
@ -57,4 +57,24 @@ describe('Bridge Sidebar', () => {
expect(wrapper.emitted('toggleSidebar')).toHaveLength(1);
});
});
describe('retry action', () => {
describe('when feature flag is ON', () => {
beforeEach(() => {
createComponent({ featureFlag: true });
});
it('renders retry dropdown', () => {
expect(findRetryDropdown().exists()).toBe(true);
});
});
describe('when feature flag is OFF', () => {
it('does not render retry dropdown', () => {
createComponent({ featureFlag: false });
expect(findRetryDropdown().exists()).toBe(false);
});
});
});
});

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe SshKeysHelper do
describe '#ssh_key_allowed_algorithms' do
it 'returns string with the names of allowed algorithms that are quoted and joined by commas' do
allowed_algorithms = Gitlab::CurrentSettings.allowed_key_types.flat_map do |ssh_key_type_name|
Gitlab::SSHPublicKey.supported_algorithms_for_name(ssh_key_type_name)
end
quoted_allowed_algorithms = allowed_algorithms.map { |name| "'#{name}'" }
expected_string = Gitlab::Utils.to_exclusive_sentence(quoted_allowed_algorithms)
expect(ssh_key_allowed_algorithms).to eq(expected_string)
end
it 'returns only allowed algorithms' do
expect(ssh_key_allowed_algorithms).to match('ed25519')
stub_application_setting(ed25519_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE)
expect(ssh_key_allowed_algorithms).not_to match('ed25519')
end
end
end

View File

@ -24,7 +24,7 @@ RSpec.describe Doorkeeper.configuration do
before do
allow(controller).to receive(:current_user).and_return(current_user)
allow(controller).to receive(:session).and_return({})
allow(controller).to receive(:request).and_return(OpenStruct.new(fullpath: '/return-path'))
allow(controller).to receive(:request).and_return(double('request', fullpath: '/return-path'))
allow(controller).to receive(:redirect_to)
allow(controller).to receive(:new_user_session_url).and_return('/login')
end

View File

@ -3,25 +3,201 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Variables::Builder do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:user) { project.owner }
let_it_be(:job) do
create(:ci_build,
pipeline: pipeline,
user: user,
yaml_variables: [{ key: 'YAML_VARIABLE', value: 'value' }]
)
end
let(:builder) { described_class.new(pipeline) }
let(:pipeline) { create(:ci_pipeline) }
let(:job) { create(:ci_build, pipeline: pipeline) }
describe '#scoped_variables' do
let(:environment) { job.expanded_environment_name }
let(:dependencies) { true }
let(:predefined_variables) do
[
{ key: 'CI_JOB_NAME',
value: job.name },
{ key: 'CI_JOB_STAGE',
value: job.stage },
{ key: 'CI_NODE_TOTAL',
value: '1' },
{ key: 'CI_BUILD_NAME',
value: job.name },
{ key: 'CI_BUILD_STAGE',
value: job.stage },
{ key: 'CI',
value: 'true' },
{ key: 'GITLAB_CI',
value: 'true' },
{ key: 'CI_SERVER_URL',
value: Gitlab.config.gitlab.url },
{ key: 'CI_SERVER_HOST',
value: Gitlab.config.gitlab.host },
{ key: 'CI_SERVER_PORT',
value: Gitlab.config.gitlab.port.to_s },
{ key: 'CI_SERVER_PROTOCOL',
value: Gitlab.config.gitlab.protocol },
{ key: 'CI_SERVER_NAME',
value: 'GitLab' },
{ key: 'CI_SERVER_VERSION',
value: Gitlab::VERSION },
{ key: 'CI_SERVER_VERSION_MAJOR',
value: Gitlab.version_info.major.to_s },
{ key: 'CI_SERVER_VERSION_MINOR',
value: Gitlab.version_info.minor.to_s },
{ key: 'CI_SERVER_VERSION_PATCH',
value: Gitlab.version_info.patch.to_s },
{ key: 'CI_SERVER_REVISION',
value: Gitlab.revision },
{ key: 'GITLAB_FEATURES',
value: project.licensed_features.join(',') },
{ key: 'CI_PROJECT_ID',
value: project.id.to_s },
{ key: 'CI_PROJECT_NAME',
value: project.path },
{ key: 'CI_PROJECT_TITLE',
value: project.title },
{ key: 'CI_PROJECT_PATH',
value: project.full_path },
{ key: 'CI_PROJECT_PATH_SLUG',
value: project.full_path_slug },
{ key: 'CI_PROJECT_NAMESPACE',
value: project.namespace.full_path },
{ key: 'CI_PROJECT_ROOT_NAMESPACE',
value: project.namespace.root_ancestor.path },
{ key: 'CI_PROJECT_URL',
value: project.web_url },
{ key: 'CI_PROJECT_VISIBILITY',
value: "private" },
{ key: 'CI_PROJECT_REPOSITORY_LANGUAGES',
value: project.repository_languages.map(&:name).join(',').downcase },
{ key: 'CI_PROJECT_CLASSIFICATION_LABEL',
value: project.external_authorization_classification_label },
{ key: 'CI_DEFAULT_BRANCH',
value: project.default_branch },
{ key: 'CI_CONFIG_PATH',
value: project.ci_config_path_or_default },
{ key: 'CI_PAGES_DOMAIN',
value: Gitlab.config.pages.host },
{ key: 'CI_PAGES_URL',
value: project.pages_url },
{ key: 'CI_API_V4_URL',
value: API::Helpers::Version.new('v4').root_url },
{ key: 'CI_PIPELINE_IID',
value: pipeline.iid.to_s },
{ key: 'CI_PIPELINE_SOURCE',
value: pipeline.source },
{ key: 'CI_PIPELINE_CREATED_AT',
value: pipeline.created_at.iso8601 },
{ key: 'CI_COMMIT_SHA',
value: job.sha },
{ key: 'CI_COMMIT_SHORT_SHA',
value: job.short_sha },
{ key: 'CI_COMMIT_BEFORE_SHA',
value: job.before_sha },
{ key: 'CI_COMMIT_REF_NAME',
value: job.ref },
{ key: 'CI_COMMIT_REF_SLUG',
value: job.ref_slug },
{ key: 'CI_COMMIT_BRANCH',
value: job.ref },
{ key: 'CI_COMMIT_MESSAGE',
value: pipeline.git_commit_message },
{ key: 'CI_COMMIT_TITLE',
value: pipeline.git_commit_title },
{ key: 'CI_COMMIT_DESCRIPTION',
value: pipeline.git_commit_description },
{ key: 'CI_COMMIT_REF_PROTECTED',
value: (!!pipeline.protected_ref?).to_s },
{ key: 'CI_COMMIT_TIMESTAMP',
value: pipeline.git_commit_timestamp },
{ key: 'CI_COMMIT_AUTHOR',
value: pipeline.git_author_full_text },
{ key: 'CI_BUILD_REF',
value: job.sha },
{ key: 'CI_BUILD_BEFORE_SHA',
value: job.before_sha },
{ key: 'CI_BUILD_REF_NAME',
value: job.ref },
{ key: 'CI_BUILD_REF_SLUG',
value: job.ref_slug },
{ key: 'YAML_VARIABLE',
value: 'value' },
{ key: 'GITLAB_USER_ID',
value: user.id.to_s },
{ key: 'GITLAB_USER_EMAIL',
value: user.email },
{ key: 'GITLAB_USER_LOGIN',
value: user.username },
{ key: 'GITLAB_USER_NAME',
value: user.name }
].map { |var| var.merge(public: true, masked: false) }
end
subject { builder.scoped_variables(job, environment: environment, dependencies: dependencies) }
it 'returns the expected variables' do
keys = %w[CI_JOB_NAME
CI_JOB_STAGE
CI_NODE_TOTAL
CI_BUILD_NAME
CI_BUILD_STAGE]
it { is_expected.to be_instance_of(Gitlab::Ci::Variables::Collection) }
subject.map { |env| env[:key] }.tap do |names|
expect(names).to include(*keys)
it { expect(subject.to_runner_variables).to eq(predefined_variables) }
context 'variables ordering' do
def var(name, value)
{ key: name, value: value.to_s, public: true, masked: false }
end
before do
allow(builder).to receive(:predefined_variables) { [var('A', 1), var('B', 1)] }
allow(project).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] }
allow(pipeline).to receive(:predefined_variables) { [var('C', 3), var('D', 3)] }
allow(job).to receive(:runner) { double(predefined_variables: [var('D', 4), var('E', 4)]) }
allow(builder).to receive(:kubernetes_variables) { [var('E', 5), var('F', 5)] }
allow(builder).to receive(:deployment_variables) { [var('F', 6), var('G', 6)] }
allow(job).to receive(:yaml_variables) { [var('G', 7), var('H', 7)] }
allow(builder).to receive(:user_variables) { [var('H', 8), var('I', 8)] }
allow(job).to receive(:dependency_variables) { [var('I', 9), var('J', 9)] }
allow(builder).to receive(:secret_instance_variables) { [var('J', 10), var('K', 10)] }
allow(builder).to receive(:secret_group_variables) { [var('K', 11), var('L', 11)] }
allow(builder).to receive(:secret_project_variables) { [var('L', 12), var('M', 12)] }
allow(job).to receive(:trigger_request) { double(user_variables: [var('M', 13), var('N', 13)]) }
allow(pipeline).to receive(:variables) { [var('N', 14), var('O', 14)] }
allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('O', 15), var('P', 15)]) }
end
it 'returns variables in order depending on resource hierarchy' do
expect(subject.to_runner_variables).to eq(
[var('A', 1), var('B', 1),
var('B', 2), var('C', 2),
var('C', 3), var('D', 3),
var('D', 4), var('E', 4),
var('E', 5), var('F', 5),
var('F', 6), var('G', 6),
var('G', 7), var('H', 7),
var('H', 8), var('I', 8),
var('I', 9), var('J', 9),
var('J', 10), var('K', 10),
var('K', 11), var('L', 11),
var('L', 12), var('M', 12),
var('M', 13), var('N', 13),
var('N', 14), var('O', 14),
var('O', 15), var('P', 15)])
end
it 'overrides duplicate keys depending on resource hierarchy' do
expect(subject.to_hash).to match(
'A' => '1', 'B' => '2',
'C' => '3', 'D' => '4',
'E' => '5', 'F' => '6',
'G' => '7', 'H' => '8',
'I' => '9', 'J' => '10',
'K' => '11', 'L' => '12',
'M' => '13', 'N' => '14',
'O' => '15', 'P' => '15')
end
end
end

View File

@ -28,7 +28,7 @@ RSpec.describe Gitlab::Database::Migrations::Runner do
allow(ActiveRecord::Migrator).to receive(:new) do |dir, _all_migrations, _schema_migration_class, version_to_migrate|
migrator = double(ActiveRecord::Migrator)
expect(migrator).to receive(:run) do
migration_runs << OpenStruct.new(dir: dir, version_to_migrate: version_to_migrate)
migration_runs << double('migrator', dir: dir, version_to_migrate: version_to_migrate)
end
migrator
end

View File

@ -39,14 +39,43 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do
]
end
subject { described_class.supported_sizes(name) }
with_them do
it { expect(described_class.supported_sizes(name)).to eq(sizes) }
it { expect(described_class.supported_sizes(name.to_s)).to eq(sizes) }
end
end
describe '.supported_algorithms' do
it 'returns all supported algorithms' do
expect(described_class.supported_algorithms).to eq(
%w(
ssh-rsa
ssh-dss
ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521
ssh-ed25519
)
)
end
end
describe '.supported_algorithms_for_name' do
where(:name, :algorithms) do
[
[:rsa, %w(ssh-rsa)],
[:dsa, %w(ssh-dss)],
[:ecdsa, %w(ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521)],
[:ed25519, %w(ssh-ed25519)]
]
end
with_them do
it "returns all supported algorithms for #{params[:name]}" do
expect(described_class.supported_algorithms_for_name(name)).to eq(algorithms)
expect(described_class.supported_algorithms_for_name(name.to_s)).to eq(algorithms)
end
end
end
describe '.sanitize(key_content)' do
let(:content) { build(:key).key }

View File

@ -2824,7 +2824,7 @@ RSpec.describe Ci::Build do
allow(build).to receive(:dependency_variables) { [job_dependency_var] }
allow(build).to receive(:dependency_proxy_variables) { [dependency_proxy_var] }
allow(build.project)
allow(build.pipeline.project)
.to receive(:predefined_variables) { [project_pre_var] }
project.variables.create!(key: 'secret', value: 'value')
@ -3124,7 +3124,7 @@ RSpec.describe Ci::Build do
context 'when the branch is protected' do
before do
allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end
it { is_expected.to include(protected_variable) }
@ -3132,7 +3132,7 @@ RSpec.describe Ci::Build do
context 'when the tag is protected' do
before do
allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end
it { is_expected.to include(protected_variable) }
@ -3171,7 +3171,7 @@ RSpec.describe Ci::Build do
context 'when the branch is protected' do
before do
allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end
it { is_expected.to include(protected_variable) }
@ -3179,7 +3179,7 @@ RSpec.describe Ci::Build do
context 'when the tag is protected' do
before do
allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end
it { is_expected.to include(protected_variable) }
@ -3566,6 +3566,20 @@ RSpec.describe Ci::Build do
build.scoped_variables
end
context 'when variables builder is used' do
it 'returns the same variables' do
build.user = create(:user)
allow(build.pipeline).to receive(:use_variables_builder_definitions?).and_return(false)
legacy_variables = build.scoped_variables.to_hash
allow(build.pipeline).to receive(:use_variables_builder_definitions?).and_return(true)
new_variables = build.scoped_variables.to_hash
expect(new_variables).to eq(legacy_variables)
end
end
end
describe '#simple_variables_without_dependencies' do
@ -3578,7 +3592,8 @@ RSpec.describe Ci::Build do
shared_examples "secret CI variables" do
context 'when ref is branch' do
let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, ref: 'master', tag: false, pipeline: pipeline, project: project) }
context 'when ref is protected' do
before do
@ -3594,7 +3609,8 @@ RSpec.describe Ci::Build do
end
context 'when ref is tag' do
let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, pipeline: pipeline, project: project) }
context 'when ref is protected' do
before do
@ -3692,8 +3708,6 @@ RSpec.describe Ci::Build do
.and_return(project_variables)
end
it { is_expected.to eq(project_variables) }
context 'environment is nil' do
let(:environment) { nil }
@ -3701,6 +3715,35 @@ RSpec.describe Ci::Build do
end
end
describe '#user_variables' do
subject { build.user_variables.to_hash }
context 'with user' do
let(:expected_variables) do
{
'GITLAB_USER_EMAIL' => user.email,
'GITLAB_USER_ID' => user.id.to_s,
'GITLAB_USER_LOGIN' => user.username,
'GITLAB_USER_NAME' => user.name
}
end
before do
build.user = user
end
it { is_expected.to eq(expected_variables) }
end
context 'without user' do
before do
expect(build).to receive(:user).and_return(nil)
end
it { is_expected.to be_empty }
end
end
describe '#any_unmet_prerequisites?' do
let(:build) { create(:ci_build, :created) }

View File

@ -425,7 +425,7 @@ RSpec.describe API::MavenPackages do
context 'internal project' do
before do
group.group_member(user).destroy!
group.member(user).destroy!
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end

View File

@ -244,7 +244,7 @@ RSpec.describe 'merge requests discussions' do
context 'when current_user role changes' do
before do
Members::UpdateService.new(owner, access_level: Gitlab::Access::GUEST).execute(project.project_member(user))
Members::UpdateService.new(owner, access_level: Gitlab::Access::GUEST).execute(project.member(user))
end
it_behaves_like 'cache miss' do

View File

@ -50,7 +50,7 @@ RSpec.shared_examples 'project access tokens available #create' do
expect(created_token.name).to eq(access_token_params[:name])
expect(created_token.scopes).to eq(access_token_params[:scopes])
expect(created_token.expires_at).to eq(access_token_params[:expires_at])
expect(project.project_member(created_token.user).access_level).to eq(access_level)
expect(project.member(created_token.user).access_level).to eq(access_level)
end
it 'creates project bot user' do

View File

@ -233,7 +233,7 @@ RSpec.shared_examples 'snippet visibility' do
project.update!(visibility_level: Gitlab::VisibilityLevel.level_value(project_visibility.to_s), snippets_access_level: feature_visibility)
if user_type == :external
member = project.project_member(external)
member = project.member(external)
if project.private?
project.add_developer(external) unless member

View File

@ -3,7 +3,7 @@
RSpec.shared_examples 'inherited access level as a member of entity' do
let(:parent_entity) { create(:group) }
let(:user) { create(:user) }
let(:member) { entity.is_a?(Group) ? entity.group_member(user) : entity.project_member(user) }
let(:member) { entity.member(user) }
context 'with root parent_entity developer member' do
before do
@ -49,7 +49,7 @@ RSpec.shared_examples 'inherited access level as a member of entity' do
entity.add_maintainer(non_member_user)
non_member = entity.is_a?(Group) ? entity.group_member(non_member_user) : entity.project_member(non_member_user)
non_member = entity.member(non_member_user)
expect { non_member.update!(access_level: Gitlab::Access::GUEST) }
.to change { non_member.reload.access_level }

View File

@ -130,6 +130,12 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(group_2, nested_group_2, deep_nested_group_2) }
end
context 'with upto' do
subject { described_class.where(id: deep_nested_group_1).self_and_ancestors(upto: nested_group_1.id) }
it { is_expected.to contain_exactly(deep_nested_group_1) }
end
end
describe '.self_and_ancestors' do

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
RSpec.shared_examples 'renders registration features prompt' do |disabled_field, feature_title|
it 'renders a placeholder input with registration features message' 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: 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, feature_title|
it 'does not render a placeholder input with registration features message' 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: feature_title || s_('RegistrationFeatures|use this feature') })
expect(rendered).not_to have_link(s_('RegistrationFeatures|Registration Features Program'))
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
SystemExitDetected = Class.new(RuntimeError)
RSpec.configure do |config|
config.around do |example|
example.run
rescue SystemExit
# In any cases, we cannot raise SystemExit in the tests,
# because it'll skip any following tests from running.
# Convert it to something that won't skip everything.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/350060
raise SystemExitDetected, "SystemExit should be rescued in the tests!"
end
end

View File

@ -214,7 +214,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
expect(Gitlab::Database::Reindexing).to receive(:enabled?).and_return(false)
expect(Gitlab::Database::Reindexing).not_to receive(:invoke)
run_rake_task('gitlab:db:reindex')
expect { run_rake_task('gitlab:db:reindex') }.to raise_error(SystemExit)
end
end
end
@ -233,7 +233,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
expect(Gitlab::Database::Reindexing).to receive(:enabled?).and_return(false)
expect(Gitlab::Database::Reindexing).not_to receive(:invoke).with(database_name)
run_rake_task("gitlab:db:reindex:#{database_name}")
expect { run_rake_task("gitlab:db:reindex:#{database_name}") }.to raise_error(SystemExit)
end
end
end

View File

@ -115,4 +115,40 @@ RSpec.describe 'groups/edit.html.haml' do
end
end
end
context 'ip_restriction' do
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
group.add_owner(user)
assign(:group, group)
allow(view).to receive(:current_user) { user }
end
context 'prompt user about registration features' do
before do
if Gitlab.ee?
allow(License).to receive(:current).and_return(nil)
end
end
context 'with service ping disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
end
it_behaves_like 'renders registration features prompt', :group_disabled_ip_restriction_ranges
end
context 'with service ping enabled' do
before do
stub_application_setting(usage_ping_enabled: true)
end
it_behaves_like 'does not render registration features prompt', :group_disabled_ip_restriction_ranges
end
end
end
end

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'profiles/keys/_form.html.haml' do
include SshKeysHelper
let_it_be(:key) { Key.new }
let(:page) { Capybara::Node::Simple.new(rendered) }
@ -23,8 +25,8 @@ RSpec.describe 'profiles/keys/_form.html.haml' do
end
it 'has the key field', :aggregate_failures do
expect(rendered).to have_field('Key', type: 'textarea', placeholder: 'Typically starts with "ssh-ed25519 …" or "ssh-rsa …"')
expect(rendered).to have_text("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity.")
expect(rendered).to have_field('Key', type: 'textarea')
expect(rendered).to have_text(s_('Profiles|Begins with %{ssh_key_algorithms}.') % { ssh_key_algorithms: ssh_key_allowed_algorithms })
end
it 'has the title field', :aggregate_failures do