diff options
60 files changed, 721 insertions, 159 deletions
diff --git a/.rubocop_todo/rails/save_bang.yml b/.rubocop_todo/rails/save_bang.yml index e3d97a45afd..8f2be251dc5 100644 --- a/.rubocop_todo/rails/save_bang.yml +++ b/.rubocop_todo/rails/save_bang.yml @@ -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 diff --git a/.rubocop_todo/style/open_struct_use.yml b/.rubocop_todo/style/open_struct_use.yml index d50278da557..c04493fe3bb 100644 --- a/.rubocop_todo/style/open_struct_use.yml +++ b/.rubocop_todo/style/open_struct_use.yml @@ -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 diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 62d572c550a..35310f489d4 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -e02b0d67e48ed5a4493b073c9836d376a780f34d +7055518ce76486791c3450a8a47b673891b6e2d6 @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index 238228f13fc..2a54b7aaaec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/javascripts/jobs/bridge/components/sidebar.vue b/app/assets/javascripts/jobs/bridge/components/sidebar.vue index 9c7ae1178ea..34cefb0d0e2 100644 --- a/app/assets/javascripts/jobs/bridge/components/sidebar.vue +++ b/app/assets/javascripts/jobs/bridge/components/sidebar.vue @@ -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" diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index cbe40d0bfbe..6363422259e 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -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 }; diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index a49ddac8c89..25468d4a697 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -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 })); diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js index 5376791469e..2caa6f4f0a0 100644 --- a/app/assets/javascripts/sidebar/stores/sidebar_store.js +++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js @@ -98,6 +98,10 @@ export default class SidebarStore { } } + overwrite(key, newData) { + this[key] = newData; + } + findAssignee(findAssignee) { return this.assignees.find(({ id }) => id === findAssignee.id); } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue index 386ba2e2d77..24cefd63ce3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue @@ -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) diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 662b02010ba..fa9517c3545 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -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 diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index fa7c62c34dd..bfc2fe6432d 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -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' diff --git a/app/helpers/ssh_keys_helper.rb b/app/helpers/ssh_keys_helper.rb index f5a9bea482b..17a9fd6146d 100644 --- a/app/helpers/ssh_keys_helper.rb +++ b/app/helpers/ssh_keys_helper.rb @@ -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 diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d11304e5285..7ae301cdf73 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -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) diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb index 12ddbc2cc40..ed3b422251f 100644 --- a/app/models/concerns/ci/contextable.rb +++ b/app/models/concerns/ci/contextable.rb @@ -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 diff --git a/app/models/group.rb b/app/models/group.rb index 28b945f7c03..cacfe202fd2 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -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 diff --git a/app/models/namespaces/traversal/linear_scopes.rb b/app/models/namespaces/traversal/linear_scopes.rb index 3ed525f34c7..9f0f49e729c 100644 --- a/app/models/namespaces/traversal/linear_scopes.rb +++ b/app/models/namespaces/traversal/linear_scopes.rb @@ -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) diff --git a/app/models/namespaces/traversal/recursive_scopes.rb b/app/models/namespaces/traversal/recursive_scopes.rb index 925d9b8bb0c..583c53f8221 100644 --- a/app/models/namespaces/traversal/recursive_scopes.rb +++ b/app/models/namespaces/traversal/recursive_scopes.rb @@ -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 diff --git a/app/models/project.rb b/app/models/project.rb index fa5e84ee83e..7e2c98c85b3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index 3dacff4a989..fec37172284 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/serializers/merge_request_sidebar_basic_entity.rb b/app/serializers/merge_request_sidebar_basic_entity.rb index 3c911bbe4c8..a0832655563 100644 --- a/app/serializers/merge_request_sidebar_basic_entity.rb +++ b/app/serializers/merge_request_sidebar_basic_entity.rb @@ -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 diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index d744881549a..b0d0c32abd1 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -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) diff --git a/app/services/merge_requests/handle_assignees_change_service.rb b/app/services/merge_requests/handle_assignees_change_service.rb index 1d9f7ab59f4..97be9fe8d9f 100644 --- a/app/services/merge_requests/handle_assignees_change_service.rb +++ b/app/services/merge_requests/handle_assignees_change_service.rb @@ -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 diff --git a/app/services/merge_requests/remove_approval_service.rb b/app/services/merge_requests/remove_approval_service.rb index 872e7e0c89c..198a21884b8 100644 --- a/app/services/merge_requests/remove_approval_service.rb +++ b/app/services/merge_requests/remove_approval_service.rb @@ -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 diff --git a/app/services/resource_access_tokens/revoke_service.rb b/app/services/resource_access_tokens/revoke_service.rb index 9543ea4b68d..2aaf4cc31d2 100644 --- a/app/services/resource_access_tokens/revoke_service.rb +++ b/app/services/resource_access_tokens/revoke_service.rb @@ -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 diff --git a/app/views/groups/settings/_ip_restriction_registration_features_cta.html.haml b/app/views/groups/settings/_ip_restriction_registration_features_cta.html.haml new file mode 100644 index 00000000000..3067220ea8f --- /dev/null +++ b/app/views/groups/settings/_ip_restriction_registration_features_cta.html.haml @@ -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' diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index 59c47634c2d..9a7a7521cec 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -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 diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml index bf9c77cb3ec..91af6953ee1 100644 --- a/app/views/profiles/gpg_keys/index.html.haml +++ b/app/views/profiles/gpg_keys/index.html.haml @@ -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 diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index 2b3109225a8..4b7dd42f5dc 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -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' diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 584bd44e386..7d4c3b6115f 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -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 diff --git a/app/views/shared/access_tokens/_table.html.haml b/app/views/shared/access_tokens/_table.html.haml index a6a063d75f8..1e3432ab08b 100644 --- a/app/views/shared/access_tokens/_table.html.haml +++ b/app/views/shared/access_tokens/_table.html.haml @@ -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 diff --git a/config/feature_flags/development/ci_skip_require_credit_card_for_addon_ci_minutes.yml b/config/feature_flags/development/ci_skip_require_credit_card_for_addon_ci_minutes.yml new file mode 100644 index 00000000000..e9c355bd119 --- /dev/null +++ b/config/feature_flags/development/ci_skip_require_credit_card_for_addon_ci_minutes.yml @@ -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 diff --git a/config/feature_flags/development/ci_use_variables_builder_definitions.yml b/config/feature_flags/development/ci_use_variables_builder_definitions.yml new file mode 100644 index 00000000000..c01e4e9958e --- /dev/null +++ b/config/feature_flags/development/ci_use_variables_builder_definitions.yml @@ -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 diff --git a/config/feature_flags/development/trigger_job_retry_action.yml b/config/feature_flags/development/trigger_job_retry_action.yml new file mode 100644 index 00000000000..79a8593fd05 --- /dev/null +++ b/config/feature_flags/development/trigger_job_retry_action.yml @@ -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 diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 5fac7a555d5..9c6191fe27c 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -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. | diff --git a/lib/api/entities/project_with_access.rb b/lib/api/entities/project_with_access.rb index ac89cb52e43..b541ccbadcf 100644 --- a/lib/api/entities/project_with_access.rb +++ b/lib/api/entities/project_with_access.rb @@ -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 diff --git a/lib/api/entities/resource_access_token.rb b/lib/api/entities/resource_access_token.rb index d16baed38f0..569fd16f488 100644 --- a/lib/api/entities/resource_access_token.rb +++ b/lib/api/entities/resource_access_token.rb @@ -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 diff --git a/lib/gitlab/ci/variables/builder.rb b/lib/gitlab/ci/variables/builder.rb index 3e2c2c7fc1a..4c777527ebc 100644 --- a/lib/gitlab/ci/variables/builder.rb +++ b/lib/gitlab/ci/variables/builder.rb @@ -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| diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb index e51ec38394d..314cc5e2db6 100644 --- a/lib/gitlab/ssh_public_key.rb +++ b/lib/gitlab/ssh_public_key.rb @@ -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) diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake index 7807b765615..efb0e1ef1e1 100644 --- a/lib/tasks/gitlab/db.rake +++ b/lib/tasks/gitlab/db.rake @@ -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) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 38e9c348d1c..c12c26c6075 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index f757b7c69cf..117c934ad5d 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -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 diff --git a/spec/frontend/jobs/bridge/components/sidebar_spec.js b/spec/frontend/jobs/bridge/components/sidebar_spec.js index 9debbb14c30..03f5a15f288 100644 --- a/spec/frontend/jobs/bridge/components/sidebar_spec.js +++ b/spec/frontend/jobs/bridge/components/sidebar_spec.js @@ -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); + }); + }); + }); }); diff --git a/spec/helpers/ssh_keys_helper_spec.rb b/spec/helpers/ssh_keys_helper_spec.rb new file mode 100644 index 00000000000..1aa604f19be --- /dev/null +++ b/spec/helpers/ssh_keys_helper_spec.rb @@ -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 diff --git a/spec/initializers/doorkeeper_spec.rb b/spec/initializers/doorkeeper_spec.rb index 164225a00b2..56e0a22d59f 100644 --- a/spec/initializers/doorkeeper_spec.rb +++ b/spec/initializers/doorkeeper_spec.rb @@ -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 diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb index 5ff34592b2f..8a87cbe45c1 100644 --- a/spec/lib/gitlab/ci/variables/builder_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder_spec.rb @@ -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) } + + 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 - subject.map { |env| env[:key] }.tap do |names| - expect(names).to include(*keys) + 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 diff --git a/spec/lib/gitlab/database/migrations/runner_spec.rb b/spec/lib/gitlab/database/migrations/runner_spec.rb index 4616bd6941e..7dc965c84fa 100644 --- a/spec/lib/gitlab/database/migrations/runner_spec.rb +++ b/spec/lib/gitlab/database/migrations/runner_spec.rb @@ -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 diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb index d2ff7f2d7df..38486b313cb 100644 --- a/spec/lib/gitlab/ssh_public_key_spec.rb +++ b/spec/lib/gitlab/ssh_public_key_spec.rb @@ -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 } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index a5af0cd32fd..9d1fa006cf8 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -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) } diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb index 5a682ee8532..bc325aad823 100644 --- a/spec/requests/api/maven_packages_spec.rb +++ b/spec/requests/api/maven_packages_spec.rb @@ -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 diff --git a/spec/requests/projects/merge_requests_discussions_spec.rb b/spec/requests/projects/merge_requests_discussions_spec.rb index 4921a43ab8b..6cf7bfb1795 100644 --- a/spec/requests/projects/merge_requests_discussions_spec.rb +++ b/spec/requests/projects/merge_requests_discussions_spec.rb @@ -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 diff --git a/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb index 017e55309f7..9287bbd29fb 100644 --- a/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb @@ -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 diff --git a/spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb index a2c34cdd4a1..601a53ed913 100644 --- a/spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb +++ b/spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb @@ -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 diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb index d5d137922eb..5b4b8c8fcc1 100644 --- a/spec/support/shared_examples/models/member_shared_examples.rb +++ b/spec/support/shared_examples/models/member_shared_examples.rb @@ -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 } diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb index 19b418d1d6d..b43b7946e69 100644 --- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb +++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb @@ -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 diff --git a/spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb b/spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb new file mode 100644 index 00000000000..535d078db63 --- /dev/null +++ b/spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb @@ -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 diff --git a/spec/support/system_exit_detected.rb b/spec/support/system_exit_detected.rb new file mode 100644 index 00000000000..86c6af3ba8c --- /dev/null +++ b/spec/support/system_exit_detected.rb @@ -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 diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb index 830d0dded2e..92c896b1ab0 100644 --- a/spec/tasks/gitlab/db_rake_spec.rb +++ b/spec/tasks/gitlab/db_rake_spec.rb @@ -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 diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb index 43e11d31611..748fe986d43 100644 --- a/spec/views/groups/edit.html.haml_spec.rb +++ b/spec/views/groups/edit.html.haml_spec.rb @@ -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 diff --git a/spec/views/profiles/keys/_form.html.haml_spec.rb b/spec/views/profiles/keys/_form.html.haml_spec.rb index d5a605958dc..624d7492aea 100644 --- a/spec/views/profiles/keys/_form.html.haml_spec.rb +++ b/spec/views/profiles/keys/_form.html.haml_spec.rb @@ -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 |