diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index ac9dfe217bd..b9a2eb3ebc6 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -121,6 +121,7 @@ review-stop: QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa" QA_CAN_TEST_GIT_PROTOCOL_V2: "false" QA_DEBUG: "true" + QA_GENERATE_ALLURE_REPORT: "true" GITLAB_USERNAME: "root" GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}" GITLAB_ADMIN_USERNAME: "root" @@ -141,6 +142,23 @@ review-stop: expire_in: 7 days when: always +.allure-report-base: + image: + name: ${GITLAB_DEPENDENCY_PROXY}andrcuns/allure-report-publisher:0.0.6 + entrypoint: [""] + stage: post-qa + variables: + GIT_STRATEGY: none + STORAGE_CREDENTIALS: $QA_ALLURE_REPORT_GCS_CREDENTIALS + script: + - | + allure-report-publisher upload gcs \ + --results-glob="qa/gitlab-qa-run-*/**/allure-results/*" \ + --bucket="gitlab-qa-allure-reports" \ + --prefix="$ALLURE_REPORT_PATH_PREFIX/$CI_COMMIT_REF_SLUG" \ + --copy-latest \ + --color + review-qa-smoke: extends: - .review-qa-base @@ -210,6 +228,22 @@ parallel-spec-reports: junit: qa/gitlab-qa-run-*/**/rspec-*.xml expire_in: 31d +allure-report-qa-smoke: + extends: + - .allure-report-base + - .review:rules:review-qa-smoke + needs: ["review-qa-smoke"] + variables: + ALLURE_REPORT_PATH_PREFIX: gitlab-review-smoke + +allure-report-qa-all: + extends: + - .allure-report-base + - .review:rules:review-qa-all + needs: ["review-qa-all"] + variables: + ALLURE_REPORT_PATH_PREFIX: gitlab-review-all + danger-review: extends: - .default-retry diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 4fb55ecaf6b..ae15995215a 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -1309,7 +1309,6 @@ RSpec/AnyInstanceOf: - 'spec/support/shared_examples/workers/authorized_projects_worker_shared_example.rb' - 'spec/support/shared_examples/workers/reactive_cacheable_shared_examples.rb' - 'spec/support/snowplow.rb' - - 'spec/support/unicorn.rb' - 'spec/tasks/gitlab/cleanup_rake_spec.rb' - 'spec/tasks/gitlab/container_registry_rake_spec.rb' - 'spec/tasks/gitlab/db_rake_spec.rb' diff --git a/Gemfile b/Gemfile index 25fb430c290..0d0ac74bf1e 100644 --- a/Gemfile +++ b/Gemfile @@ -305,7 +305,7 @@ gem 'gitlab-license', '~> 1.5' gem 'rack-attack', '~> 6.3.0' # Sentry integration -gem 'sentry-raven', '~> 3.0' +gem 'sentry-raven', '~> 3.1' # PostgreSQL query parsing gem 'pg_query', '~> 2.0.3' diff --git a/Gemfile.lock b/Gemfile.lock index c08dea0c831..196eeb8ea60 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1170,7 +1170,7 @@ GEM selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) - sentry-raven (3.0.4) + sentry-raven (3.1.2) faraday (>= 1.0) settingslogic (2.0.9) sexp_processor (4.15.1) @@ -1620,7 +1620,7 @@ DEPENDENCIES sassc-rails (~> 2.1.0) seed-fu (~> 2.3.7) selenium-webdriver (~> 3.142) - sentry-raven (~> 3.0) + sentry-raven (~> 3.1) settingslogic (~> 2.0.9) shoulda-matchers (~> 4.0.1) sidekiq (~> 5.2.7) diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue index cf7970f41b1..4bbb292ad94 100644 --- a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue +++ b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue @@ -79,13 +79,20 @@ export default {
- + + + + + + + + + + + + + +
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index d1885b5ae08..086dadcf5b7 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -50,7 +50,7 @@ class IssuableFinder attr_reader :original_params attr_writer :parent - delegate(*%i[assignee milestones], to: :params) + delegate(*%i[milestones], to: :params) class << self def scalar_params @@ -148,7 +148,6 @@ class IssuableFinder # Negates all params found in `negatable_params` def filter_negated_items(items) - items = by_negated_assignee(items) items = by_negated_label(items) items = by_negated_milestone(items) items = by_negated_release(items) @@ -365,32 +364,21 @@ class IssuableFinder def by_author(items) Issuables::AuthorFilter.new( - items, params: original_params, or_filters_enabled: or_filters_enabled? - ).filter + ).filter(items) end def by_assignee(items) - if params.filter_by_no_assignee? - items.unassigned - elsif params.filter_by_any_assignee? - items.assigned - elsif params.assignee - items.assigned_to(params.assignee) - elsif params.assignee_id? || params.assignee_username? # assignee not found - items.none - else - items - end + assignee_filter.filter(items) end - def by_negated_assignee(items) - # We want CE users to be able to say "Issues not assigned to either PersonA nor PersonB" - if not_params.assignees.present? - items.not_assigned_to(not_params.assignees) - else - items + def assignee_filter + strong_memoize(:assignee_filter) do + Issuables::AssigneeFilter.new( + params: original_params, + or_filters_enabled: or_filters_enabled? + ) end end diff --git a/app/finders/issuable_finder/params.rb b/app/finders/issuable_finder/params.rb index a62210ceac5..51e12dde51d 100644 --- a/app/finders/issuable_finder/params.rb +++ b/app/finders/issuable_finder/params.rb @@ -27,14 +27,6 @@ class IssuableFinder params.present? end - def filter_by_no_assignee? - params[:assignee_id].to_s.downcase == FILTER_NONE - end - - def filter_by_any_assignee? - params[:assignee_id].to_s.downcase == FILTER_ANY - end - def filter_by_no_label? downcased = label_names.map(&:downcase) @@ -156,24 +148,6 @@ class IssuableFinder end end - # rubocop: disable CodeReuse/ActiveRecord - def assignees - strong_memoize(:assignees) do - if assignee_id? - User.where(id: params[:assignee_id]) - elsif assignee_username? - User.where(username: params[:assignee_username]) - else - User.none - end - end - end - # rubocop: enable CodeReuse/ActiveRecord - - def assignee - assignees.first - end - def label_names if labels? params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name] diff --git a/app/finders/issuables/assignee_filter.rb b/app/finders/issuables/assignee_filter.rb new file mode 100644 index 00000000000..2e58a6b34c9 --- /dev/null +++ b/app/finders/issuables/assignee_filter.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module Issuables + class AssigneeFilter < BaseFilter + def filter(issuables) + filtered = by_assignee(issuables) + filtered = by_assignee_union(filtered) + by_negated_assignee(filtered) + end + + def includes_user?(user) + Array(params[:assignee_ids]).include?(user.id) || + Array(params[:assignee_id]).include?(user.id) || + Array(params[:assignee_username]).include?(user.username) + end + + private + + def by_assignee(issuables) + if filter_by_no_assignee? + issuables.unassigned + elsif filter_by_any_assignee? + issuables.assigned + elsif has_assignee_param?(params) + filter_by_assignees(issuables) + else + issuables + end + end + + def by_assignee_union(issuables) + return issuables unless or_filters_enabled? && has_assignee_param?(or_params) + + issuables.assigned_to(assignee_ids(or_params)) + end + + def by_negated_assignee(issuables) + return issuables unless has_assignee_param?(not_params) + + issuables.not_assigned_to(assignee_ids(not_params)) + end + + def filter_by_no_assignee? + params[:assignee_id].to_s.downcase == FILTER_NONE + end + + def filter_by_any_assignee? + params[:assignee_id].to_s.downcase == FILTER_ANY + end + + def filter_by_assignees(issuables) + assignee_ids = assignee_ids(params) + + return issuables.none if assignee_ids.blank? + + assignee_ids.each do |assignee_id| + issuables = issuables.assigned_to(assignee_id) + end + + issuables + end + + def has_assignee_param?(specific_params) + return if specific_params.nil? + + specific_params[:assignee_ids].present? || specific_params[:assignee_id].present? || specific_params[:assignee_username].present? + end + + def assignee_ids(specific_params) + if specific_params[:assignee_ids].present? + Array(specific_params[:assignee_ids]) + elsif specific_params[:assignee_id].present? + Array(specific_params[:assignee_id]) + elsif specific_params[:assignee_username].present? + User.by_username(specific_params[:assignee_username]).select(:id) + end + end + end +end diff --git a/app/finders/issuables/author_filter.rb b/app/finders/issuables/author_filter.rb index 522751a384e..f36daae553d 100644 --- a/app/finders/issuables/author_filter.rb +++ b/app/finders/issuables/author_filter.rb @@ -2,7 +2,7 @@ module Issuables class AuthorFilter < BaseFilter - def filter + def filter(issuables) filtered = by_author(issuables) filtered = by_author_union(filtered) by_negated_author(filtered) @@ -21,7 +21,7 @@ module Issuables end def by_author_union(issuables) - return issuables unless or_filters_enabled? && or_params&.fetch(:author_username).present? + return issuables unless or_filters_enabled? && or_params&.fetch(:author_username, false).present? issuables.authored(User.by_username(or_params[:author_username])) end diff --git a/app/finders/issuables/base_filter.rb b/app/finders/issuables/base_filter.rb index 6d1a3f96062..7c607e2d048 100644 --- a/app/finders/issuables/base_filter.rb +++ b/app/finders/issuables/base_filter.rb @@ -2,10 +2,12 @@ module Issuables class BaseFilter - attr_reader :issuables, :params + attr_reader :params - def initialize(issuables, params:, or_filters_enabled: false) - @issuables = issuables + FILTER_NONE = 'none' + FILTER_ANY = 'any' + + def initialize(params:, or_filters_enabled: false) @params = params @or_filters_enabled = or_filters_enabled end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index eb9099fe256..40d6730d232 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -52,7 +52,7 @@ class IssuesFinder < IssuableFinder # can always see confidential issues assigned to them. This is just an # optimization since a very common usecase of this Finder is to load the # count of issues assigned to the user for the header bar. - return Issue.all if current_user && params.assignees.include?(current_user) + return Issue.all if current_user && assignee_filter.includes_user?(current_user) return Issue.where('issues.confidential IS NOT TRUE') if params.user_cannot_see_confidential_issues? diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 8331ba15cd5..91920277c50 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -200,7 +200,7 @@ module IssuesHelper jira_integration_path: help_page_url('integration/jira/', anchor: 'view-jira-issues'), markdown_help_path: help_page_path('user/markdown'), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), - new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }), + new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }), project_import_jira_path: project_import_jira_path(project), project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json), project_milestones_path: project_milestones_path(project, format: :json), diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 6d398471dcd..144595ff7b0 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -163,6 +163,8 @@ module Ci numericality: { greater_than_or_equal_to: 0.0, message: 'needs to be non-negative' } + validates :config, json_schema: { filename: 'ci_runner_config' } + # Searches for runners matching the given query. # # This method uses ILIKE on PostgreSQL. @@ -353,7 +355,7 @@ module Ci end def heartbeat(values) - values = values&.slice(:version, :revision, :platform, :architecture, :ip_address) || {} + values = values&.slice(:version, :revision, :platform, :architecture, :ip_address, :config) || {} values[:contacted_at] = Time.current cache_attributes(values) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index f5c70f10dc5..b4289a0d131 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -101,21 +101,20 @@ module Issuable scope :unassigned, -> do where("NOT EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE #{to_ability_name}_id = #{to_ability_name}s.id)") end - scope :assigned_to, ->(u) do - assignees_table = Arel::Table.new("#{to_ability_name}_assignees") - sql = assignees_table.project('true').where(assignees_table[:user_id].in(u.id)).where(Arel::Nodes::SqlLiteral.new("#{to_ability_name}_id = #{to_ability_name}s.id")) - where("EXISTS (#{sql.to_sql})") + scope :assigned_to, ->(users) do + assignees_class = self.reflect_on_association("#{to_ability_name}_assignees").klass + + condition = assignees_class.where(user_id: users).where(Arel.sql("#{to_ability_name}_id = #{to_ability_name}s.id")) + where(condition.arel.exists) + end + scope :not_assigned_to, ->(users) do + assignees_class = self.reflect_on_association("#{to_ability_name}_assignees").klass + + condition = assignees_class.where(user_id: users).where(Arel.sql("#{to_ability_name}_id = #{to_ability_name}s.id")) + where(condition.arel.exists.not) end # rubocop:enable GitlabSecurity/SqlInjection - scope :not_assigned_to, ->(users) do - assignees_table = Arel::Table.new("#{to_ability_name}_assignees") - sql = assignees_table.project('true') - .where(assignees_table[:user_id].in(users)) - .where(Arel::Nodes::SqlLiteral.new("#{to_ability_name}_id = #{to_ability_name}s.id")) - where(sql.exists.not) - end - scope :without_particular_labels, ->(label_names) do labels_table = Label.arel_table label_links_table = LabelLink.arel_table diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index b22a4fa9ef6..61e89125b5f 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -8,7 +8,7 @@ class GroupMember < Member belongs_to :group, foreign_key: 'source_id' alias_attribute :namespace_id, :source_id - delegate :update_two_factor_requirement, to: :user + delegate :update_two_factor_requirement, to: :user, allow_nil: true # Make sure group member points only to group as it source default_value_for :source_type, SOURCE_TYPE diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 32db878ffe0..1844be5d2e4 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -37,7 +37,7 @@ class MergeRequest < ApplicationRecord SORTING_PREFERENCE_FIELD = :merge_requests_sort ALLOWED_TO_USE_MERGE_BASE_PIPELINE_FOR_COMPARISON = { - 'Ci::CompareMetricsReportsService' => ->(project) { ::Gitlab::Ci::Features.merge_base_pipeline_for_metrics_comparison?(project) }, + 'Ci::CompareMetricsReportsService' => ->(project) { true }, 'Ci::CompareCodequalityReportsService' => ->(project) { true } }.freeze diff --git a/app/services/design_management/copy_design_collection/copy_service.rb b/app/services/design_management/copy_design_collection/copy_service.rb index 496103f9e58..b40f6a81174 100644 --- a/app/services/design_management/copy_design_collection/copy_service.rb +++ b/app/services/design_management/copy_design_collection/copy_service.rb @@ -86,7 +86,7 @@ module DesignManagement def with_temporary_branch(&block) target_repository.create_if_not_exists - create_master_branch! if target_repository.empty? + create_default_branch! if target_repository.empty? create_temporary_branch! yield @@ -95,9 +95,9 @@ module DesignManagement end # A project that does not have any designs will have a blank design - # repository. To create a temporary branch from `master` we need - # create `master` first by adding a file to it. - def create_master_branch! + # repository. To create a temporary branch from default branch we need to + # create default branch first by adding a file to it. + def create_default_branch! target_repository.create_file( git_user, ".CopyDesignCollectionService_#{Time.now.to_i}", @@ -121,7 +121,7 @@ module DesignManagement target_repository.rm_branch(git_user, temporary_branch) end - # Merge the temporary branch containing the commits to `master` + # Merge the temporary branch containing the commits to default branch # and update the state of the target_design_collection. def finalize! source_sha = shas.last diff --git a/app/services/design_management/design_service.rb b/app/services/design_management/design_service.rb index 5aa2a2f73bc..f337a9dc8e0 100644 --- a/app/services/design_management/design_service.rb +++ b/app/services/design_management/design_service.rb @@ -13,7 +13,7 @@ module DesignManagement attr_reader :issue def target_branch - repository.root_ref || "master" + repository.root_ref || Gitlab::DefaultBranch.value(object: project) end def collection diff --git a/app/validators/json_schemas/ci_runner_config.json b/app/validators/json_schemas/ci_runner_config.json new file mode 100644 index 00000000000..af1bcfcb183 --- /dev/null +++ b/app/validators/json_schemas/ci_runner_config.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "CI Runner config values", + "type": "object", + "properties": { + "gpus": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/app/views/notify/unknown_sign_in_email.html.haml b/app/views/notify/unknown_sign_in_email.html.haml index 8d0993e9ff8..464bcef1474 100644 --- a/app/views/notify/unknown_sign_in_email.html.haml +++ b/app/views/notify/unknown_sign_in_email.html.haml @@ -32,7 +32,7 @@ %td{ style: "#{default_style}border-top:1px solid #ededed;" } = _('Time') %td{ style: "#{default_style}color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } - = @time.strftime('%Y-%m-%d %l:%M:%S %p %Z') + = @time.strftime('%Y-%m-%d %k:%M:%S %Z') %tr.spacer %td{ style: spacer_style }   diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index c0fe788b56a..e6ded3ad912 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -83,7 +83,7 @@ .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') } - = form_for @project, html: { class: 'new_project gl-show-field-errors' } do |f| + = form_for @project, html: { class: 'new_project' } do |f| %hr = render "shared/import_form", f: f = render 'projects/new_project_fields', f: f, project_name_id: "import-url-name", hide_init_with_readme: true, track_label: track_label diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml index 06522d9d434..1289f7aa0c4 100644 --- a/app/views/projects/issues/_nav_btns.html.haml +++ b/app/views/projects/issues/_nav_btns.html.haml @@ -15,8 +15,7 @@ = button_tag _("Edit issues"), class: "gl-button btn btn-default gl-mr-3 js-bulk-update-toggle" - if show_new_issue_link?(@project) = link_to _("New issue"), new_project_issue_path(@project, - issue: { assignee_id: finder.assignee.try(:id), - milestone_id: finder.milestones.first.try(:id) }), + issue: { milestone_id: finder.milestones.first.try(:id) }), class: "gl-button btn btn-confirm", id: "new_issue_link" diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index cf9ee1a5231..65e02341936 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -6,14 +6,8 @@ = f.label :import_url, class: 'label-bold' do %span = _('Git repository URL') - = f.text_field :import_url, - value: import_url.sanitized_url, - autocomplete: 'off', - class: 'form-control gl-form-input', - placeholder: 'https://gitlab.company.com/group/project.git', - required: true, - pattern: '(?:git|https?):\/\/.*/.*\.git$', - title: _('Please provide a valid URL ending with .git') + = f.text_field :import_url, value: import_url.sanitized_url, + autocomplete: 'off', class: 'form-control gl-form-input', placeholder: 'https://gitlab.company.com/group/project.git', required: true .row .form-group.col-md-6 diff --git a/config/feature_flags/development/merge_base_pipeline_for_metrics_comparison.yml b/config/feature_flags/development/merge_base_pipeline_for_metrics_comparison.yml deleted file mode 100644 index 1fdb8d5bc6d..00000000000 --- a/config/feature_flags/development/merge_base_pipeline_for_metrics_comparison.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: merge_base_pipeline_for_metrics_comparison -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61282 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330809 -milestone: '13.12' -type: development -group: group::testing -default_enabled: false diff --git a/config/initializers/rbtrace.rb b/config/initializers/rbtrace.rb index 6a1b71bf4bd..2359fc9f6b5 100644 --- a/config/initializers/rbtrace.rb +++ b/config/initializers/rbtrace.rb @@ -2,8 +2,8 @@ if ENV['ENABLE_RBTRACE'] Gitlab::Cluster::LifecycleEvents.on_worker_start do - # Unicorn clears out signals before it forks, so rbtrace won't work - # unless it is enabled after the fork. + # We need to require `rbtrace` in a context of a worker process. + # See https://github.com/tmm1/rbtrace/issues/56#issuecomment-648683596. require 'rbtrace' end end diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example deleted file mode 100644 index c930e2ff761..00000000000 --- a/config/unicorn.rb.example +++ /dev/null @@ -1,144 +0,0 @@ -# Sample verbose configuration file for Unicorn (not Rack) -# -# This configuration file documents many features of Unicorn -# that may not be needed for some applications. See -# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb -# for a much simpler configuration file. -# -# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete -# documentation. - -# Note: If you change this file in a merge request, please also create a -# merge request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests - -# Relative URL support -# WARNING: We recommend using an FQDN to host GitLab in a root path instead -# of using a relative URL. -# Documentation: http://doc.gitlab.com/ce/install/relative_url.html -# Uncomment and customize the following line to run in a non-root path -# -# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" - -# Read about unicorn workers here: -# http://doc.gitlab.com/ee/install/requirements.html#unicorn-workers -# -worker_processes 3 - -# Since Unicorn is never exposed to outside clients, it does not need to -# run on the standard HTTP port (80), there is no reason to start Unicorn -# as root unless it's from system init scripts. -# If running the master process as root and the workers as an unprivileged -# user, do this to switch euid/egid in the workers (also chowns logs): -# user "unprivileged_user", "unprivileged_group" - -# Help ensure your application will always spawn in the symlinked -# "current" directory that Capistrano sets up. -working_directory "/home/git/gitlab" # available in 0.94.0+ - -# Listen on both a Unix domain socket and a TCP port. -# If you are load-balancing multiple Unicorn masters, lower the backlog -# setting to e.g. 64 for faster failover. -listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 1024 -listen "127.0.0.1:8080", :tcp_nopush => true - -# destroy workers after 30 seconds instead of 60 seconds (the default) -# -# NOTICE: git push over http depends on this value. -# If you want to be able to push huge amount of data to git repository over http -# you will have to increase this value too. -# -# Example of output if you try to push 1GB repo to GitLab over http. -# -> git push http://gitlab.... master -# -# error: RPC failed; result=18, HTTP code = 200 -# fatal: The remote end hung up unexpectedly -# fatal: The remote end hung up unexpectedly -# -# For more information see http://stackoverflow.com/a/21682112/752049 -# -timeout 60 - -# feel free to point this anywhere accessible on the filesystem -pid "/home/git/gitlab/tmp/pids/unicorn.pid" - -# By default, the Unicorn logger will write to stderr. -# Additionally, some applications/frameworks log to stderr or stdout, -# so prevent them from going to /dev/null when daemonized here: -stderr_path "/home/git/gitlab/log/unicorn.stderr.log" -stdout_path "/home/git/gitlab/log/unicorn.stdout.log" - -# Save memory by sharing the application code among multiple Unicorn workers -# with "preload_app true". See: -# https://www.rubydoc.info/gems/unicorn/5.1.0/Unicorn%2FConfigurator:preload_app -# https://brandur.org/ruby-memory#copy-on-write -preload_app true - -# Enable this flag to have unicorn test client connections by writing the -# beginning of the HTTP headers before calling the application. This -# prevents calling the application for connections that have disconnected -# while queued. This is only guaranteed to detect clients on the same -# host unicorn runs on, and unlikely to detect disconnects even on a -# fast LAN. -check_client_connection false - -require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events" -require_relative "/home/git/gitlab/lib/gitlab/log_timestamp_formatter.rb" - -before_exec do |server| - # Signal application hooks that we're about to restart - Gitlab::Cluster::LifecycleEvents.do_before_master_restart -end - -run_once = true - -before_fork do |server, worker| - if run_once - # There is a difference between Puma and Unicorn: - # - Puma calls before_fork once when booting up master process - # - Unicorn runs before_fork whenever new work is spawned - # To unify this behavior we call before_fork only once (we use - # this callback for deleting Prometheus files so for our purposes - # it makes sense to align behavior with Puma) - run_once = false - - # Signal application hooks that we're about to fork - Gitlab::Cluster::LifecycleEvents.do_before_fork - end - - # The following is only recommended for memory/DB-constrained - # installations. It is not needed if your system can house - # twice as many worker_processes as you have configured. - # - # This allows a new master process to incrementally - # phase out the old master process with SIGTTOU to avoid a - # thundering herd (especially in the "preload_app false" case) - # when doing a transparent upgrade. The last worker spawned - # will then kill off the old master process with a SIGQUIT. - old_pid = "#{server.config[:pid]}.oldbin" - if old_pid != server.pid - begin - sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU - Process.kill(sig, File.read(old_pid).to_i) - rescue Errno::ENOENT, Errno::ESRCH - end - end - # - # Throttle the master from forking too quickly by sleeping. Due - # to the implementation of standard Unix signal handlers, this - # helps (but does not completely) prevent identical, repeated signals - # from being lost when the receiving process is busy. - # sleep 1 -end - -after_fork do |server, worker| - # Signal application hooks of worker start - Gitlab::Cluster::LifecycleEvents.do_worker_start - - # per-process listener ports for debugging/admin/migrations - # addr = "127.0.0.1:#{9293 + worker.nr}" - # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) -end - -# Configure the default logger to use a custom formatter that formats the -# timestamps to be in UTC and in ISO8601.3 format -Configurator::DEFAULTS[:logger].formatter = Gitlab::LogTimestampFormatter.new diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development deleted file mode 100644 index 2c6e809f753..00000000000 --- a/config/unicorn.rb.example.development +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -# ------------------------------------------------------------------------- -# This file is used by the GDK to generate a default config/unicorn.rb file -# Note that `/home/git` will be substituted for the actual GDK root -# directory when this file is generated -# ------------------------------------------------------------------------- - -worker_processes 2 -timeout 60 - -listen '/home/git/gitlab.socket' - -preload_app true -check_client_connection false - -require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events" -require_relative "/home/git/gitlab/lib/gitlab/log_timestamp_formatter.rb" - -before_exec do |server| - # Signal application hooks that we're about to restart - Gitlab::Cluster::LifecycleEvents.do_before_master_restart -end - -run_once = true - -before_fork do |server, worker| - if run_once - # There is a difference between Puma and Unicorn: - # - Puma calls before_fork once when booting up master process - # - Unicorn runs before_fork whenever new work is spawned - # To unify this behavior we call before_fork only once (we use - # this callback for deleting Prometheus files so for our purposes - # it makes sense to align behavior with Puma) - run_once = false - - # Signal application hooks that we're about to fork - Gitlab::Cluster::LifecycleEvents.do_before_fork - end - - # The following is only recommended for memory/DB-constrained - # installations. It is not needed if your system can house - # twice as many worker_processes as you have configured. - # - # This allows a new master process to incrementally - # phase out the old master process with SIGTTOU to avoid a - # thundering herd (especially in the "preload_app false" case) - # when doing a transparent upgrade. The last worker spawned - # will then kill off the old master process with a SIGQUIT. - old_pid = "#{server.config[:pid]}.oldbin" - if old_pid != server.pid - begin - sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU - Process.kill(sig, File.read(old_pid).to_i) - rescue Errno::ENOENT, Errno::ESRCH - end - end - # - # Throttle the master from forking too quickly by sleeping. Due - # to the implementation of standard Unix signal handlers, this - # helps (but does not completely) prevent identical, repeated signals - # from being lost when the receiving process is busy. - # sleep 1 -end - -after_fork do |server, worker| - # Signal application hooks of worker start - Gitlab::Cluster::LifecycleEvents.do_worker_start - - # per-process listener ports for debugging/admin/migrations - # addr = "127.0.0.1:#{9293 + worker.nr}" - # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) -end - -# Configure the default logger to use a custom formatter that formats the -# timestamps to be in UTC and in ISO8601.3 format -Configurator::DEFAULTS[:logger].formatter = Gitlab::LogTimestampFormatter.new diff --git a/db/migrate/20210331000934_add_config_to_ci_runners.rb b/db/migrate/20210331000934_add_config_to_ci_runners.rb new file mode 100644 index 00000000000..e9a5fadc613 --- /dev/null +++ b/db/migrate/20210331000934_add_config_to_ci_runners.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddConfigToCiRunners < ActiveRecord::Migration[6.0] + def change + add_column :ci_runners, :config, :jsonb, default: {}, null: false + end +end diff --git a/db/schema_migrations/20210331000934 b/db/schema_migrations/20210331000934 new file mode 100644 index 00000000000..1d55b126d60 --- /dev/null +++ b/db/schema_migrations/20210331000934 @@ -0,0 +1 @@ +0bd47f9055aab927a4e8efb4f995f44532880926af9892af60f7d2b8dcdef4a6 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 5c5aeaf93a9..d550c0f7eaa 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11148,7 +11148,8 @@ CREATE TABLE ci_runners ( runner_type smallint NOT NULL, token_encrypted character varying, public_projects_minutes_cost_factor double precision DEFAULT 0.0 NOT NULL, - private_projects_minutes_cost_factor double precision DEFAULT 1.0 NOT NULL + private_projects_minutes_cost_factor double precision DEFAULT 1.0 NOT NULL, + config jsonb DEFAULT '{}'::jsonb NOT NULL ); CREATE SEQUENCE ci_runners_id_seq diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md index aff392f61ff..136580f5e0f 100644 --- a/doc/subscriptions/index.md +++ b/doc/subscriptions/index.md @@ -154,6 +154,8 @@ To change the namespace linked to a subscription: Subscription charges are calculated based on the total number of users in a group, including its subgroups and nested projects. If the total number of users exceeds the number of seats in your subscription, your account is charged for the additional users. +Only one namespace can be linked to a subscription. + ### Change Customers Portal account password To change the password for this customers portal account: diff --git a/doc/user/profile/img/unknown_sign_in_email_v13_1.png b/doc/user/profile/img/unknown_sign_in_email_v13_1.png deleted file mode 100644 index 586be483be9..00000000000 Binary files a/doc/user/profile/img/unknown_sign_in_email_v13_1.png and /dev/null differ diff --git a/doc/user/profile/img/unknown_sign_in_email_v14_0.png b/doc/user/profile/img/unknown_sign_in_email_v14_0.png new file mode 100644 index 00000000000..dec1251addb Binary files /dev/null and b/doc/user/profile/img/unknown_sign_in_email_v14_0.png differ diff --git a/doc/user/profile/unknown_sign_in_notification.md b/doc/user/profile/unknown_sign_in_notification.md index 1eec351e4da..7aa1ae89c9f 100644 --- a/doc/user/profile/unknown_sign_in_notification.md +++ b/doc/user/profile/unknown_sign_in_notification.md @@ -30,4 +30,4 @@ for a notification email to be sent. ## Example email -![Unknown sign in email](img/unknown_sign_in_email_v13_1.png) +![Unknown sign in email](img/unknown_sign_in_email_v14_0.png) diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 33980b38e2b..035711da1a0 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -98,6 +98,9 @@ module API optional :architecture, type: String, desc: %q(Runner's architecture) optional :executor, type: String, desc: %q(Runner's executor) optional :features, type: Hash, desc: %q(Runner's features) + optional :config, type: Hash, desc: %q(Runner's config) do + optional :gpus, type: String, desc: %q(GPUs enabled) + end end optional :session, type: Hash, desc: %q(Runner's session data) do optional :url, type: String, desc: %q(Session's url) diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 6f25cf507bc..e141abeef91 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -25,6 +25,7 @@ module API return get_runner_ip unless params['info'].present? attributes_for_keys(%w(name version revision platform architecture), params['info']) + .merge(get_runner_config_from_request) .merge(get_runner_ip) end @@ -91,6 +92,12 @@ module API def track_ci_minutes_usage!(_build, _runner) # noop: overridden in EE end + + private + + def get_runner_config_from_request + { config: attributes_for_keys(%w(gpus), params.dig('info', 'config')) } + end end end end diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index 37700131346..d1c1a4ecbde 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -18,10 +18,6 @@ module Gitlab Feature.enabled?(:ci_pipeline_status_omit_commit_sha_in_cache_key, project, default_enabled: true) end - def self.merge_base_pipeline_for_metrics_comparison?(project) - Feature.enabled?(:merge_base_pipeline_for_metrics_comparison, project, default_enabled: :yaml) - end - # NOTE: The feature flag `disallow_to_create_merge_request_pipelines_in_target_project` # is a safe switch to disable the feature for a particular project when something went wrong, # therefore it's not supposed to be enabled by default. diff --git a/lib/gitlab/cluster/lifecycle_events.rb b/lib/gitlab/cluster/lifecycle_events.rb index 6b7b9b09269..6159fb0a811 100644 --- a/lib/gitlab/cluster/lifecycle_events.rb +++ b/lib/gitlab/cluster/lifecycle_events.rb @@ -162,9 +162,6 @@ module Gitlab # Sidekiq doesn't fork return false if Gitlab::Runtime.sidekiq? - # Unicorn always forks - return true if Gitlab::Runtime.unicorn? - # Puma sometimes forks return true if in_clustered_puma? diff --git a/lib/gitlab/runtime.rb b/lib/gitlab/runtime.rb index b0bcea0ca69..f60cac0aff0 100644 --- a/lib/gitlab/runtime.rb +++ b/lib/gitlab/runtime.rb @@ -15,8 +15,7 @@ module Gitlab :rails_runner, :rake, :sidekiq, - :test_suite, - :unicorn + :test_suite ].freeze class << self @@ -36,11 +35,6 @@ module Gitlab !!defined?(::Puma) end - # For unicorn, we need to check for actual server instances to avoid false positives. - def unicorn? - !!(defined?(::Unicorn) && defined?(::Unicorn::HttpServer)) - end - def sidekiq? !!(defined?(::Sidekiq) && Sidekiq.server?) end @@ -66,7 +60,7 @@ module Gitlab end def web_server? - puma? || unicorn? + puma? end def action_cable? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0fd492b87f4..60b3c61ab0e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24645,9 +24645,6 @@ msgstr "" msgid "Please provide a valid URL" msgstr "" -msgid "Please provide a valid URL ending with .git" -msgstr "" - msgid "Please provide a valid URL." msgstr "" diff --git a/qa/.gitignore b/qa/.gitignore index 2095d5c722c..b54b8666e28 100644 --- a/qa/.gitignore +++ b/qa/.gitignore @@ -3,3 +3,4 @@ tmp/ .tool-versions .ruby-gemset urls.yml +reports/ diff --git a/qa/Gemfile b/qa/Gemfile index 8b3a9802000..3b9cf02c2f4 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -4,6 +4,7 @@ source 'https://rubygems.org' gem 'gitlab-qa' gem 'activesupport', '~> 6.0.3.3' # This should stay in sync with the root's Gemfile +gem 'allure-rspec', '~> 2.13.10' gem 'capybara', '~> 3.29.0' gem 'capybara-screenshot', '~> 1.0.23' gem 'rake', '~> 12.3.3' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 9bac86bad08..84ac3e0d69d 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -19,6 +19,14 @@ GEM rack-test (>= 1.1.0, < 2.0) rest-client (>= 2.0.2, < 3.0) rspec (~> 3.8) + allure-rspec (2.13.10) + allure-ruby-commons (= 2.13.10) + rspec-core (>= 3.8, < 4) + allure-ruby-commons (2.13.10) + mime-types (>= 3.3, < 4) + oj (>= 3.10, < 4) + require_all (>= 2, < 4) + uuid (>= 2.3, < 3) ast (2.4.1) binding_ninja (0.2.3) byebug (9.1.0) @@ -74,6 +82,8 @@ GEM rake launchy (2.4.3) addressable (~> 2.3) + macaddr (1.7.2) + systemu (~> 2.6.5) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) method_source (0.9.0) @@ -96,6 +106,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) + oj (3.11.5) parallel (1.19.2) parallel_tests (2.29.0) parallel @@ -119,6 +130,7 @@ GEM rack (>= 1.0, < 3) rake (12.3.3) regexp_parser (1.6.0) + require_all (3.0.0) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) @@ -154,6 +166,7 @@ GEM selenium-webdriver (3.142.6) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) + systemu (2.6.5) thread_safe (0.3.6) timecop (0.9.1) tzinfo (1.2.9) @@ -169,6 +182,8 @@ GEM equalizer (~> 0.0.9) parser (>= 2.6.5) procto (~> 0.0.2) + uuid (2.3.9) + macaddr (~> 1.0) watir (6.18.0) regexp_parser (>= 1.2, < 3) selenium-webdriver (>= 3.8) @@ -182,6 +197,7 @@ PLATFORMS DEPENDENCIES activesupport (~> 6.0.3.3) airborne (~> 0.3.4) + allure-rspec (~> 2.13.10) capybara (~> 3.29.0) capybara-screenshot (~> 1.0.23) chemlab (~> 0.5) diff --git a/qa/qa.rb b/qa/qa.rb index bf86fc5faec..ec8572fd7df 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -45,6 +45,7 @@ module QA autoload :IPAddress, 'qa/runtime/ip_address' autoload :Search, 'qa/runtime/search' autoload :ApplicationSettings, 'qa/runtime/application_settings' + autoload :AllureReport, 'qa/runtime/allure_report' module API autoload :Client, 'qa/runtime/api/client' diff --git a/qa/qa/runtime/allure_report.rb b/qa/qa/runtime/allure_report.rb new file mode 100644 index 00000000000..cf8d33e0a6d --- /dev/null +++ b/qa/qa/runtime/allure_report.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module QA + module Runtime + class AllureReport + class << self + # Configure allure reports + # + # @return [void] + def configure! + return unless Env.generate_allure_report? + + require 'allure-rspec' + + configure_allure + configure_attachments + configure_rspec + end + + private + + # Configure allure reporter + # + # @return [void] + def configure_allure + AllureRspec.configure do |config| + config.results_directory = 'tmp/allure-results' + config.clean_results_directory = true + end + end + + # Set up failure screenshot attachments + # + # @return [void] + def configure_attachments + Capybara::Screenshot.after_save_screenshot do |path| + Allure.add_attachment( + name: 'screenshot', + source: File.open(path), + type: Allure::ContentType::PNG, + test_case: true + ) + end + Capybara::Screenshot.after_save_html do |path| + Allure.add_attachment( + name: 'html', + source: File.open(path), + type: 'text/html', + test_case: true + ) + end + end + + # Configure rspec + # + # @return [void] + def configure_rspec + RSpec.configure do |config| + config.formatter = AllureRspecFormatter + + config.before do |example| + next if example.attempts && example.attempts > 0 + + testcase = example.metadata[:testcase] + example.tms('Testcase', testcase) if testcase + + issue = example.metadata.dig(:quarantine, :issue) + example.issue('Issue', issue) if issue + + example.add_link(name: "Job(#{ENV['CI_JOB_NAME']})", url: ENV['CI_JOB_URL']) if ENV['CI'] + end + end + end + end + end + end +end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index e649084a394..e7c9478fa54 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -2,7 +2,6 @@ require 'gitlab/qa' require 'uri' -require 'active_support/core_ext/object/blank' module QA module Runtime @@ -53,6 +52,10 @@ module QA enabled?(ENV['QA_DEBUG'], default: false) end + def generate_allure_report? + enabled?(ENV['QA_GENERATE_ALLURE_REPORT'], default: false) + end + def default_branch ENV['QA_DEFAULT_BRANCH'] || 'master' end diff --git a/qa/qa/runtime/scenario.rb b/qa/qa/runtime/scenario.rb index 3662ebe671b..d44cc846128 100644 --- a/qa/qa/runtime/scenario.rb +++ b/qa/qa/runtime/scenario.rb @@ -19,15 +19,15 @@ module QA define_singleton_method(attribute) do attributes[attribute.to_sym].tap do |value| - if value.to_s.empty? - raise ArgumentError, "Empty `#{attribute}` attribute!" - end + raise ArgumentError, "Empty `#{attribute}` attribute!" if value.to_s.empty? end end end def from_env(var) - JSON.parse(Runtime::Env.runtime_scenario_attributes).each { |k, v| define(k, v) } + return if var.blank? + + JSON.parse(var).each { |k, v| define(k, v) } end def method_missing(name, *) diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index 0c9643c830b..f4bfd57504e 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -4,6 +4,7 @@ require_relative '../qa' require 'rspec/retry' require 'rspec-parameterized' require 'active_support/core_ext/hash' +require 'active_support/core_ext/object/blank' if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK'] require 'knapsack' @@ -11,8 +12,8 @@ if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK'] end QA::Runtime::Browser.configure! - -QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes +QA::Runtime::AllureReport.configure! +QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) Dir[::File.join(__dir__, "support/helpers/*.rb")].sort.each { |f| require f } Dir[::File.join(__dir__, "support/matchers/*.rb")].sort.each { |f| require f } diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index a1523f9eb08..0bb22afdd15 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -356,16 +356,6 @@ RSpec.describe 'New project', :js do expect(git_import_instructions).to have_content 'Git repository URL' end - it 'reports error if repo URL does not end with .git' do - fill_in 'project_import_url', with: 'http://foo/bar' - fill_in 'project_name', with: 'import-project-without-git-suffix' - fill_in 'project_path', with: 'import-project-without-git-suffix' - - click_button 'Create project' - - expect(page).to have_text('Please provide a valid URL ending with .git') - end - it 'keeps "Import project" tab open after form validation error' do collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace) diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 27466ab563f..1c8c2af8e03 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -49,6 +49,11 @@ RSpec.describe IssuesFinder do let(:expected_issuables) { [issue3, issue4] } end + it_behaves_like 'assignee OR filter' do + let(:params) { { or: { assignee_id: [user.id, user2.id] } } } + let(:expected_issuables) { [issue1, issue2, issue3, issue5] } + end + context 'when assignee_id does not exist' do it_behaves_like 'assignee NOT ID filter' do let(:params) { { not: { assignee_id: -100 } } } @@ -79,6 +84,11 @@ RSpec.describe IssuesFinder do let(:expected_issuables) { [issue3, issue4] } end + it_behaves_like 'assignee OR filter' do + let(:params) { { or: { assignee_username: [user2.username, user3.username] } } } + let(:expected_issuables) { [issue2, issue3] } + end + context 'when assignee_username does not exist' do it_behaves_like 'assignee NOT username filter' do before do diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index ceca83d107b..59b42dfca20 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -312,7 +312,7 @@ RSpec.describe IssuesHelper do jira_integration_path: help_page_url('integration/jira/', anchor: 'view-jira-issues'), markdown_help_path: help_page_path('user/markdown'), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), - new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.id, milestone_id: finder.milestones.first.id }), + new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.id }), project_import_jira_path: project_import_jira_path(project), project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json), project_milestones_path: project_milestones_path(project, format: :json), diff --git a/spec/lib/api/helpers/runner_helpers_spec.rb b/spec/lib/api/helpers/runner_helpers_spec.rb new file mode 100644 index 00000000000..65b35845aab --- /dev/null +++ b/spec/lib/api/helpers/runner_helpers_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Helpers::Runner do + let(:ip_address) { '1.2.3.4' } + let(:runner_class) do + Class.new do + include API::Helpers + include API::Helpers::Runner + + attr_accessor :params + + def initialize(params) + @params = params + end + + def ip_address + '1.2.3.4' + end + end + end + + let(:runner_helper) { runner_class.new(runner_params) } + + describe '#get_runner_details_from_request' do + context 'when no runner info is present' do + let(:runner_params) { {} } + + it 'returns the runner IP' do + expect(runner_helper.get_runner_details_from_request).to eq({ ip_address: ip_address }) + end + end + + context 'when runner info is present' do + let(:name) { 'runner' } + let(:version) { '1.2.3' } + let(:revision) { '10.0' } + let(:platform) { 'test' } + let(:architecture) { 'arm' } + let(:config) { { 'gpus' => 'all' } } + let(:runner_params) do + { + 'info' => + { + 'name' => name, + 'version' => version, + 'revision' => revision, + 'platform' => platform, + 'architecture' => architecture, + 'config' => config, + 'ignored' => 1 + } + } + end + + subject(:details) { runner_helper.get_runner_details_from_request } + + it 'extracts the runner details', :aggregate_failures do + expect(details.keys).to match_array(%w(name version revision platform architecture config ip_address)) + expect(details['name']).to eq(name) + expect(details['version']).to eq(version) + expect(details['revision']).to eq(revision) + expect(details['platform']).to eq(platform) + expect(details['architecture']).to eq(architecture) + expect(details['config']).to eq(config) + expect(details['ip_address']).to eq(ip_address) + end + end + end +end diff --git a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb index 584eadb24a7..210829056c8 100644 --- a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb +++ b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb @@ -4,7 +4,15 @@ require 'spec_helper' RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do describe '.call' do - let(:event) { Raven::Event.new(payload) } + let(:required_options) do + { + configuration: Raven.configuration, + context: Raven.context, + breadcrumbs: Raven.breadcrumbs + } + end + + let(:event) { Raven::Event.new(required_options.merge(payload)) } let(:result_hash) { described_class.call(event).to_hash } before do diff --git a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb index 727b603feda..6076e525f06 100644 --- a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb +++ b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb @@ -4,7 +4,15 @@ require 'spec_helper' RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do describe '.call' do - let(:event) { Raven::Event.from_exception(exception, data) } + let(:required_options) do + { + configuration: Raven.configuration, + context: Raven.context, + breadcrumbs: Raven.breadcrumbs + } + end + + let(:event) { Raven::Event.from_exception(exception, required_options.merge(data)) } let(:result_hash) { described_class.call(event).to_hash } context 'when there is no GRPC exception' do diff --git a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb index c8a362fcf05..af5f11c9362 100644 --- a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb +++ b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb @@ -95,7 +95,15 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do end describe '.call' do - let(:event) { Raven::Event.new(wrapped_value) } + let(:required_options) do + { + configuration: Raven.configuration, + context: Raven.context, + breadcrumbs: Raven.breadcrumbs + } + end + + let(:event) { Raven::Event.new(required_options.merge(wrapped_value)) } let(:result_hash) { described_class.call(event).to_hash } context 'when there is Sidekiq data' do diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb index 8ac1f15d67e..f6895cab30e 100644 --- a/spec/mailers/emails/profile_spec.rb +++ b/spec/mailers/emails/profile_spec.rb @@ -337,7 +337,7 @@ RSpec.describe Emails::Profile do end it 'mentioned the time' do - is_expected.to have_body_text current_time.strftime('%Y-%m-%d %l:%M:%S %p %Z') + is_expected.to have_body_text current_time.strftime('%Y-%m-%d %k:%M:%S %Z') end it 'includes a link to the change password documentation' do diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 9f299b78a8a..491088a44a1 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -75,6 +75,22 @@ RSpec.describe Ci::Runner do expect { create(:group, runners: [project_runner]) } .to raise_error(ActiveRecord::RecordInvalid) end + + context 'when runner has config' do + it 'is valid' do + runner = build(:ci_runner, config: { gpus: "all" }) + + expect(runner).to be_valid + end + end + + context 'when runner has an invalid config' do + it 'is invalid' do + runner = build(:ci_runner, config: { test: 1 }) + + expect(runner).not_to be_valid + end + end end context 'cost factors validations' do @@ -653,7 +669,7 @@ RSpec.describe Ci::Runner do describe '#heartbeat' do let(:runner) { create(:ci_runner, :project) } - subject { runner.heartbeat(architecture: '18-bit') } + subject { runner.heartbeat(architecture: '18-bit', config: { gpus: "all" }) } context 'when database was updated recently' do before do @@ -701,6 +717,7 @@ RSpec.describe Ci::Runner do def does_db_update expect { subject }.to change { runner.reload.read_attribute(:contacted_at) } .and change { runner.reload.read_attribute(:architecture) } + .and change { runner.reload.read_attribute(:config) } end end diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 3a2db5d8516..8c942228059 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -37,6 +37,10 @@ RSpec.describe GroupMember do end end + describe 'delegations' do + it { is_expected.to delegate_method(:update_two_factor_requirement).to(:user).allow_nil } + end + describe '.access_level_roles' do it 'returns Gitlab::Access.options_with_owner' do expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner) @@ -93,6 +97,18 @@ RSpec.describe GroupMember do end end + describe '#destroy' do + context 'for an orphaned member' do + let!(:orphaned_group_member) do + create(:group_member).tap { |member| member.update_column(:user_id, nil) } + end + + it 'does not raise an error' do + expect { orphaned_group_member.destroy! }.not_to raise_error + end + end + end + describe '#after_accept_invite' do it 'calls #update_two_factor_requirement' do email = 'foo@email.com' diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index fa77e319c2c..b84b408cb4b 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -58,6 +58,16 @@ RSpec.describe ProjectMember do maintainer.destroy! expect(Event.recent.first).to be_left_action end + + context 'for an orphaned member' do + let!(:orphaned_project_member) do + owner.tap { |member| member.update_column(:user_id, nil) } + end + + it 'does not raise an error' do + expect { orphaned_project_member.destroy! }.not_to raise_error + end + end end describe '.import_team' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 3fb3225ebbc..7cbfefd421d 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -3912,14 +3912,6 @@ RSpec.describe MergeRequest, factory_default: :keep do let(:service_class) { 'Ci::CompareMetricsReportsService' } it { is_expected.to be_truthy } - - context 'with the metrics report flag disabled' do - before do - stub_feature_flags(merge_base_pipeline_for_metrics_comparison: false) - end - - it { is_expected.to be_falsey } - end end context 'when service class is Ci::CompareCodequalityReportsService' do diff --git a/spec/rack_servers/unicorn_spec.rb b/spec/rack_servers/unicorn_spec.rb deleted file mode 100644 index 52d44b6e7e0..00000000000 --- a/spec/rack_servers/unicorn_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -require 'fileutils' - -require 'excon' - -require 'spec_helper' - -RSpec.describe 'Unicorn' do - before(:all) do - project_root = File.expand_path('../..', __dir__) - - config_lines = File.read('config/unicorn.rb.example') - .gsub('/home/git/gitlab', project_root) - .gsub('/home/git', project_root) - .split("\n") - - # Remove these because they make setup harder. - config_lines = config_lines.reject do |line| - %w[ - worker_processes - listen - pid - stderr_path - stdout_path - ].any? { |prefix| line.start_with?(prefix) } - end - - config_lines << "working_directory '#{Rails.root}'" - - # We want to have exactly 1 worker process because that makes it - # predictable which process will handle our requests. - config_lines << 'worker_processes 1' - - @socket_path = File.join(project_root, 'tmp/tests/unicorn.socket') - config_lines << "listen '#{@socket_path}'" - - ready_file = File.join(project_root, 'tmp/tests/unicorn-worker-ready') - FileUtils.rm_f(ready_file) - after_fork_index = config_lines.index { |l| l.start_with?('after_fork') } - config_lines.insert(after_fork_index + 1, "File.write('#{ready_file}', Process.pid)") - - config_path = File.join(project_root, 'tmp/tests/unicorn.rb') - File.write(config_path, config_lines.join("\n") + "\n") - - cmd = %W[unicorn -E test -c #{config_path} spec/rack_servers/configs/config.ru] - @unicorn_master_pid = spawn(*cmd) - wait_unicorn_boot!(@unicorn_master_pid, ready_file) - WebMock.allow_net_connect! - end - - %w[SIGQUIT SIGTERM SIGKILL].each do |signal| - it "has a worker that self-terminates on signal #{signal}" do - response = Excon.get('unix://', socket: @socket_path) - expect(response.status).to eq(200) - - worker_pid = response.body.to_i - expect(worker_pid).to be > 0 - - begin - Excon.post("unix://?#{signal}", socket: @socket_path) - rescue Excon::Error::Socket - # The connection may be closed abruptly - end - - expect(pid_gone?(worker_pid)).to eq(true) - end - end - - after(:all) do - webmock_enable! - Process.kill('TERM', @unicorn_master_pid) - end - - def wait_unicorn_boot!(master_pid, ready_file) - # We have seen the boot timeout after 2 minutes in CI so let's set it to 5 minutes. - timeout = 5 * 60 - timeout.times do - return if File.exist?(ready_file) - - pid = Process.waitpid(master_pid, Process::WNOHANG) - raise "unicorn failed to boot: #{$?}" unless pid.nil? - - sleep 1 - end - - raise "unicorn boot timed out after #{timeout} seconds" - end - - def pid_gone?(pid) - # Worker termination should take less than a second. That makes 10 - # seconds a generous timeout. - 10.times do - begin - Process.kill(0, pid) - rescue Errno::ESRCH - return true - end - - sleep 1 - end - - false - end -end diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb index 63da3340a45..cd2fa2ded23 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -439,6 +439,13 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end end + it "sets the runner's config" do + request_job info: { 'config' => { 'gpus' => 'all', 'ignored' => 'hello' } } + + expect(response).to have_gitlab_http_status(:created) + expect(runner.reload.config).to eq( { 'gpus' => 'all' } ) + end + it "sets the runner's ip_address" do post api('/jobs/request'), params: { token: runner.token }, diff --git a/spec/services/design_management/copy_design_collection/copy_service_spec.rb b/spec/services/design_management/copy_design_collection/copy_service_spec.rb index 03242487b53..186d2481c19 100644 --- a/spec/services/design_management/copy_design_collection/copy_service_spec.rb +++ b/spec/services/design_management/copy_design_collection/copy_service_spec.rb @@ -195,6 +195,14 @@ RSpec.describe DesignManagement::CopyDesignCollection::CopyService, :clean_gitla expect { subject }.to change { target_repository.branch_names }.from([]).to(['master']) end + it 'does not create default branch when one exists' do + target_repository.create_if_not_exists + target_repository.create_file(user, '.meta', '.gitlab', branch_name: 'new-branch', message: 'message') + + expect { subject }.not_to change { target_repository.branch_names } + expect(target_repository.branch_names).to eq(['new-branch']) + end + it 'leaves the design collection in the correct copy state' do subject diff --git a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb index 96b05db4cd9..5cbbed1468f 100644 --- a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb +++ b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb @@ -24,6 +24,12 @@ RSpec.shared_examples 'assignee NOT username filter' do end end +RSpec.shared_examples 'assignee OR filter' do + it 'returns issuables assigned to the given users' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + RSpec.shared_examples 'no assignee filter' do let(:params) { { assignee_id: 'None' } }