diff options
49 files changed, 626 insertions, 150 deletions
diff --git a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue index 3340bc1beb9..1431f156c0e 100644 --- a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue @@ -29,6 +29,7 @@ import RunnerJobStatusBadge from '../components/runner_job_status_badge.vue'; import { pausedTokenConfig } from '../components/search_tokens/paused_token_config'; import { statusTokenConfig } from '../components/search_tokens/status_token_config'; import { tagTokenConfig } from '../components/search_tokens/tag_token_config'; +import { versionTokenConfig } from '../components/search_tokens/version_token_config'; import { ADMIN_FILTERED_SEARCH_NAMESPACE, INSTANCE_TYPE, @@ -117,6 +118,7 @@ export default { return [ pausedTokenConfig, statusTokenConfig, + versionTokenConfig, { ...tagTokenConfig, recentSuggestionsStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`, diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/version_token_config.js b/app/assets/javascripts/ci/runner/components/search_tokens/version_token_config.js new file mode 100644 index 00000000000..23f82d06f6d --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/search_tokens/version_token_config.js @@ -0,0 +1,12 @@ +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; +import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; +import { PARAM_KEY_VERSION, I18N_VERSION } from '../../constants'; + +export const versionTokenConfig = { + icon: 'doc-versions', + title: I18N_VERSION, + type: PARAM_KEY_VERSION, + token: BaseToken, + operators: OPERATORS_IS, + suggestionsDisabled: true, +}; diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js index 298d2530564..d04d75b6e75 100644 --- a/app/assets/javascripts/ci/runner/constants.js +++ b/app/assets/javascripts/ci/runner/constants.js @@ -99,6 +99,7 @@ export const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted'); export const I18N_LOCKED_RUNNER_DESCRIPTION = s__( 'Runners|Runner is locked and available for currently assigned projects only. Only administrators can change the assigned projects.', ); +export const I18N_VERSION = s__('Runners|Version starts with'); export const I18N_VERSION_LABEL = s__('Runners|Version %{version}'); export const I18N_LAST_CONTACT_LABEL = s__('Runners|Last contact: %{timeAgo}'); @@ -157,6 +158,7 @@ export const PARAM_KEY_STATUS = 'status'; export const PARAM_KEY_PAUSED = 'paused'; export const PARAM_KEY_RUNNER_TYPE = 'runner_type'; export const PARAM_KEY_TAG = 'tag'; +export const PARAM_KEY_VERSION = 'version_prefix'; export const PARAM_KEY_SEARCH = 'search'; export const PARAM_KEY_MEMBERSHIP = 'membership'; diff --git a/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql b/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql index 15401c25c64..628ebfd2029 100644 --- a/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql +++ b/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql @@ -10,6 +10,7 @@ query getAllRunners( $type: CiRunnerType $tagList: [String!] $search: String + $versionPrefix: String $sort: CiRunnerSort ) { runners( @@ -22,6 +23,7 @@ query getAllRunners( type: $type tagList: $tagList search: $search + versionPrefix: $versionPrefix sort: $sort ) { ...AllRunnersConnection diff --git a/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql b/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql index 82591b88d3e..18f587495b0 100644 --- a/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql +++ b/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql @@ -4,8 +4,16 @@ query getAllRunnersCount( $type: CiRunnerType $tagList: [String!] $search: String + $versionPrefix: String ) { - runners(paused: $paused, status: $status, type: $type, tagList: $tagList, search: $search) { + runners( + paused: $paused + status: $status + type: $type + tagList: $tagList + search: $search + versionPrefix: $versionPrefix + ) { count } } diff --git a/app/assets/javascripts/ci/runner/runner_search_utils.js b/app/assets/javascripts/ci/runner/runner_search_utils.js index 8915198350f..e3aee15f42c 100644 --- a/app/assets/javascripts/ci/runner/runner_search_utils.js +++ b/app/assets/javascripts/ci/runner/runner_search_utils.js @@ -12,6 +12,7 @@ import { PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE, PARAM_KEY_TAG, + PARAM_KEY_VERSION, PARAM_KEY_SEARCH, PARAM_KEY_MEMBERSHIP, PARAM_KEY_SORT, @@ -151,7 +152,12 @@ export const fromUrlQueryToSearch = (query = window.location.search) => { membership: membership || DEFAULT_MEMBERSHIP, filters: prepareTokens( urlQueryToFilter(query, { - filterNamesAllowList: [PARAM_KEY_PAUSED, PARAM_KEY_STATUS, PARAM_KEY_TAG], + filterNamesAllowList: [ + PARAM_KEY_PAUSED, + PARAM_KEY_STATUS, + PARAM_KEY_TAG, + PARAM_KEY_VERSION, + ], filteredSearchTermKey: PARAM_KEY_SEARCH, }), ), @@ -178,6 +184,7 @@ export const fromSearchToUrl = ( [PARAM_KEY_MEMBERSHIP]: [], [PARAM_KEY_TAG]: [], [PARAM_KEY_PAUSED]: [], + [PARAM_KEY_VERSION]: [], // Current filters ...filterToQueryObject(processFilters(filters), { filteredSearchTermKey: PARAM_KEY_SEARCH, @@ -229,6 +236,7 @@ export const fromSearchToVariables = ({ [filterVariables.status] = queryObj[PARAM_KEY_STATUS] || []; filterVariables.search = queryObj[PARAM_KEY_SEARCH]; filterVariables.tagList = queryObj[PARAM_KEY_TAG]; + [filterVariables.versionPrefix] = queryObj[PARAM_KEY_VERSION] || []; if (queryObj[PARAM_KEY_PAUSED]) { filterVariables.paused = parseBoolean(queryObj[PARAM_KEY_PAUSED]); diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js index b5661af352c..b3754cecce4 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_create.js +++ b/app/assets/javascripts/protected_tags/protected_tag_create.js @@ -41,7 +41,7 @@ export default class ProtectedTagCreate { accessLevel: ACCESS_LEVELS.CREATE, accessLevelsData: gon.create_access_levels, searchEnabled: dropdownEl.dataset.filter !== undefined, - testId: 'allowed_to_create_dropdown', + testId: 'allowed-to-create-dropdown', }); this.protectedTagAccessDropdown.$on('select', (selected) => { diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit.vue b/app/assets/javascripts/protected_tags/protected_tag_edit.vue index 82b2ecc5f5c..7fe1dc9c01a 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_edit.vue +++ b/app/assets/javascripts/protected_tags/protected_tag_edit.vue @@ -101,7 +101,7 @@ export default { <template> <access-dropdown toggle-class="js-allowed-to-create gl-max-w-34" - test-id="allowed_to_create_dropdown" + test-id="allowed-to-create-dropdown" :has-license="hasLicense" :access-level="$options.ACCESS_LEVELS.CREATE" :access-levels-data="accessLevelsData" diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index 802ffd99e41..5a419aab8e1 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -6,14 +6,15 @@ class Projects::JobsController < Projects::ApplicationController include ContinueParams include ProjectStatsRefreshConflictsGuard - urgency :low, [:index, :show, :trace, :retry, :play, :cancel, :unschedule, :erase, :raw] + urgency :low, [:index, :show, :trace, :retry, :play, :cancel, :unschedule, :erase, :raw, :test_report_summary] before_action :find_job_as_build, except: [:index, :play, :retry, :show] before_action :find_job_as_processable, only: [:play, :retry, :show] before_action :authorize_read_build_trace!, only: [:trace, :raw] - before_action :authorize_read_build! + before_action :authorize_read_build!, except: [:test_report_summary] + before_action :authorize_read_build_report_results!, only: [:test_report_summary] before_action :authorize_update_build!, - except: [:index, :show, :raw, :trace, :erase, :cancel, :unschedule] + except: [:index, :show, :raw, :trace, :erase, :cancel, :unschedule, :test_report_summary] before_action :authorize_erase_build!, only: [:erase] before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize] before_action :verify_api_request!, only: :terminal_websocket_authorize @@ -153,6 +154,20 @@ class Projects::JobsController < Projects::ApplicationController end end + def test_report_summary + return not_found unless @build.report_results.present? + + summary = Gitlab::Ci::Reports::TestReportSummary.new(@build.report_results) + + respond_to do |format| + format.json do + render json: TestReportSummarySerializer + .new(project: project, current_user: @current_user) + .represent(summary) + end + end + end + def terminal end @@ -170,6 +185,10 @@ class Projects::JobsController < Projects::ApplicationController attr_reader :build + def authorize_read_build_report_results! + return access_denied! unless can?(current_user, :read_build_report_results, build) + end + def authorize_update_build! return access_denied! unless can?(current_user, :update_build, @build) end diff --git a/app/models/project.rb b/app/models/project.rb index bc6d2600d83..9fb7745d432 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1530,9 +1530,9 @@ class Project < ApplicationRecord limit = creator.projects_limit error = if limit == 0 - _('Personal project creation is not allowed. Please contact your administrator with questions') + _('You cannot create projects in your personal namespace. Contact your GitLab administrator.') else - _('Your project limit is %{limit} projects! Please contact your administrator to increase it') + _("You've reached your limit of %{limit} projects created. Contact your GitLab administrator.") end self.errors.add(:limit_reached, error % { limit: limit }) diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 74dc2277f54..7527f32274a 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,5 +1,6 @@ -= content_for :flash_message do - = render 'shared/project_limit' +- if params[:personal] + = content_for :flash_message do + = render 'shared/project_limit' .page-title-holder.gl-display-flex.gl-align-items-center %h1.page-title.gl-font-size-h-display= _('Projects') diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml index 2dba22d3be6..9c478f245dc 100644 --- a/app/views/projects/_errors.html.haml +++ b/app/views/projects/_errors.html.haml @@ -1 +1 @@ -= form_errors(@project) += form_errors(@project, custom_message: [:limit_reached]) diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml index c4cf128a62a..f72b0d582b7 100644 --- a/app/views/projects/mirrors/_authentication_method.html.haml +++ b/app/views/projects/mirrors/_authentication_method.html.haml @@ -5,7 +5,7 @@ = f.label :auth_method, _('Authentication method'), class: 'label-bold' = f.select :auth_method, options_for_select(auth_options, mirror.auth_method), - {}, { class: "custom-select gl-form-select js-mirror-auth-type gl-max-w-34 gl-display-block", data: { qa_selector: 'authentication_method_field' } } + {}, { class: "custom-select gl-form-select js-mirror-auth-type gl-max-w-34 gl-display-block", data: { testid: 'authentication-method-field' } } = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type" .form-group diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index a7ebafed1a9..7b27062f782 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -35,7 +35,7 @@ %div= form_errors(@project) .form-group.has-feedback = label_tag :url, _('Git repository URL'), class: 'label-light' - = text_field_tag :url, nil, class: 'form-control gl-form-input js-mirror-url js-repo-url gl-form-input-xl', placeholder: _('Input the remote repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", autocomplete: 'new-password', data: { qa_selector: 'mirror_repository_url_field' } + = text_field_tag :url, nil, class: 'form-control gl-form-input js-mirror-url js-repo-url gl-form-input-xl', placeholder: _('Input the remote repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", autocomplete: 'new-password', data: { testid: 'mirror-repository-url-field' } = render 'projects/mirrors/instructions' @@ -43,7 +43,7 @@ = render 'projects/mirrors/branch_filter' - = f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { qa_selector: 'mirror_repository_button' } + = f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { testid: 'mirror-repository-button' } = render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do = _('Cancel') diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml index 8378a74311f..24cda3445de 100644 --- a/app/views/projects/mirrors/_mirror_repos_form.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml @@ -1,7 +1,7 @@ .form-group = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light' .select-wrapper - = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control gl-form-select select-control js-mirror-direction gl-max-w-34 gl-display-block', disabled: true, data: { qa_selector: 'mirror_direction_field' } + = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control gl-form-select select-control js-mirror-direction gl-max-w-34 gl-display-block', disabled: true, data: { testid: 'mirror-direction-field' } = sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200") = render partial: "projects/mirrors/mirror_repos_push", locals: { f: f } diff --git a/app/views/projects/mirrors/_mirror_repos_list.html.haml b/app/views/projects/mirrors/_mirror_repos_list.html.haml index 59611db941f..5e3c4889d1d 100644 --- a/app/views/projects/mirrors/_mirror_repos_list.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_list.html.haml @@ -17,24 +17,24 @@ = render_if_exists 'projects/mirrors/table_pull_row' - @project.remote_mirrors.each_with_index do |mirror, index| - next if mirror.new_record? - %tr.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?), data: { qa_selector: 'mirrored_repository_row_container' } } - %td{ data: { qa_selector: 'mirror_repository_url_content' } } + %tr.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?), data: { testid: 'mirrored-repository-row-container' } } + %td{ data: { testid: 'mirror-repository-url-content' } } = mirror.safe_url || _('Invalid URL') = render_if_exists 'projects/mirrors/mirror_branches_setting_badge', record: mirror %td= _('Push') %td = mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never') - %td{ data: { qa_selector: 'mirror_last_update_at_content' } }= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never') + %td{ data: { testid: 'mirror-last-update-at-content' } }= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never') %td - if mirror.disabled? = render 'projects/mirrors/disabled_mirror_badge' - if mirror.last_error.present? - = gl_badge_tag _('Error'), { variant: :danger }, { data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge_content' }, title: html_escape(mirror.last_error.try(:strip)) } + = gl_badge_tag _('Error'), { variant: :danger }, { data: { toggle: 'tooltip', html: 'true', testid: 'mirror-error-badge-content' }, title: html_escape(mirror.last_error.try(:strip)) } %td - if mirror_settings_enabled .btn-group.mirror-actions-group{ role: 'group' } - if mirror.ssh_key_auth? - = clipboard_button(text: mirror.ssh_public_key, variant: :default, category: :primary, size: :medium, title: _('Copy SSH public key'), testid: 'copy_public_key_button') + = clipboard_button(text: mirror.ssh_public_key, variant: :default, category: :primary, size: :medium, title: _('Copy SSH public key'), testid: 'copy-public-key-button') = render 'shared/remote_mirror_update_button', remote_mirror: mirror = render Pajamas::ButtonComponent.new(variant: :danger, icon: 'remove', diff --git a/app/views/projects/mirrors/_ssh_host_keys.html.haml b/app/views/projects/mirrors/_ssh_host_keys.html.haml index d367f383e5a..cd9580d15e9 100644 --- a/app/views/projects/mirrors/_ssh_host_keys.html.haml +++ b/app/views/projects/mirrors/_ssh_host_keys.html.haml @@ -3,13 +3,13 @@ - verified_at = mirror.ssh_known_hosts_verified_at .form-group.js-ssh-host-keys-section{ class: ('collapse' unless mirror.ssh_mirror_url?) } - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-detect-host-keys gl-mr-3', data: { qa_selector: 'detect_host_keys' } }) do + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-detect-host-keys gl-mr-3', data: { testid: 'detect-host-keys' } }) do = gl_loading_icon(inline: true, css_class: 'js-spinner gl-display-none gl-mr-2') = _('Detect host keys') .fingerprint-ssh-info.js-fingerprint-ssh-info.gl-mt-3.gl-mb-3{ class: ('collapse' unless mirror.ssh_mirror_url?) } %label.label-bold = _('Fingerprints') - .fingerprints-list.js-fingerprints-list{ data: { qa_selector: 'fingerprints_list' } } + .fingerprints-list.js-fingerprints-list{ data: { testid: 'fingerprints-list' } } - mirror.ssh_known_hosts_fingerprints.each do |fp| %code= fp.fingerprint_sha256 || fp.fingerprint - if verified_at diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml index 8dcc59a09d0..cd49f064613 100644 --- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml @@ -14,7 +14,7 @@ .create_access_levels-container = yield :create_access_levels - = f.submit _('Protect'), pajamas_button: true, disabled: true, data: { qa_selector: 'protect_tag_button' } + = f.submit _('Protect'), pajamas_button: true, disabled: true, data: { testid: 'protect-tag-button' } = render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do = _('Cancel') diff --git a/app/views/projects/protected_tags/shared/_dropdown.html.haml b/app/views/projects/protected_tags/shared/_dropdown.html.haml index 758df7b3c1e..b1e29768be2 100644 --- a/app/views/projects/protected_tags/shared/_dropdown.html.haml +++ b/app/views/projects/protected_tags/shared/_dropdown.html.haml @@ -6,7 +6,7 @@ footer_content: true, data: { show_no: true, show_any: true, show_upcoming: true, selected: params[:protected_tag_name], - project_id: @project.try(:id), qa_selector: 'tags_dropdown' } }) do + project_id: @project.try(:id), testid: 'tags-dropdown' } }) do %ul.dropdown-footer-list %li diff --git a/app/views/protected_branches/shared/_create_protected_branch.html.haml b/app/views/protected_branches/shared/_create_protected_branch.html.haml index 34ffbf498d3..bb1d56dcc61 100644 --- a/app/views/protected_branches/shared/_create_protected_branch.html.haml +++ b/app/views/protected_branches/shared/_create_protected_branch.html.haml @@ -43,6 +43,6 @@ - force_push_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: force_push_docs_url } = (s_("ProtectedBranch|Allow all users with push access to %{tag_start}force push%{tag_end}.") % { tag_start: force_push_link_start, tag_end: '</a>' }).html_safe = render_if_exists 'protected_branches/ee/code_owner_approval_form', f: f, protected_branch_entity: protected_branch_entity - = f.submit s_('ProtectedBranch|Protect'), disabled: true, data: { qa_selector: 'protect_button' }, pajamas_button: true + = f.submit s_('ProtectedBranch|Protect'), disabled: true, data: { testid: 'protect-button' }, pajamas_button: true = render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do = _('Cancel') diff --git a/app/views/shared/_project_limit.html.haml b/app/views/shared/_project_limit.html.haml index a99db32c40e..914c20fb7b0 100644 --- a/app/views/shared/_project_limit.html.haml +++ b/app/views/shared/_project_limit.html.haml @@ -3,7 +3,7 @@ dismissible: false, alert_options: { class: 'project-limit-message' }) do |c| - c.with_body do - = _("You won't be able to create new projects because you have reached your project limit.") + = _("You cannot create new projects in your personal namespace because you have reached your personal project limit.") - c.with_actions do = link_button_to _('Remind later'), '#', class: 'alert-link hide-project-limit-message', variant: :confirm = link_button_to _("Don't show again"), profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link gl-ml-3' diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml index fa5c862b768..ec897e59d4a 100644 --- a/app/views/shared/_remote_mirror_update_button.html.haml +++ b/app/views/shared/_remote_mirror_update_button.html.haml @@ -3,4 +3,4 @@ button_options: { class: 'disabled', title: _('Updating'), data: { toggle: 'tooltip', container: 'body' } }, icon_classes: 'spin') - elsif remote_mirror.enabled? - = link_button_to nil, update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: 'rspec-update-now-button', data: { toggle: 'tooltip', container: 'body', qa_selector: 'update_now_button' }, title: _('Update now'), icon: 'retry' + = link_button_to nil, update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: 'rspec-update-now-button', data: { toggle: 'tooltip', container: 'body', testid: 'update-now-button' }, title: _('Update now'), icon: 'retry' diff --git a/config/routes/project.rb b/config/routes/project.rb index fe99e0b89eb..947ed6b5413 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -69,6 +69,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get :raw get :terminal get :proxy + get :test_report_summary # These routes are also defined in gitlab-workhorse. Make sure to update accordingly. get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', format: false diff --git a/data/deprecations/16-6-proxy-based-dast-deprecation.yml b/data/deprecations/16-6-proxy-based-dast-deprecation.yml new file mode 100644 index 00000000000..34a47fe8ea5 --- /dev/null +++ b/data/deprecations/16-6-proxy-based-dast-deprecation.yml @@ -0,0 +1,9 @@ +- title: "Proxy-based DAST deprecated" + removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed + announcement_milestone: "16.6" # (required) The milestone when this feature was first announced as deprecated. + breaking_change: true # (required) Change to false if this is not a breaking change. + reporter: smeadzinger # (required) GitLab username of the person reporting the change + stage: Secure # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430966 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + As of GitLab 17.0, Proxy-based DAST will not be supported. Please migrate to Browser-based DAST to continue analyzing your projects for security findings via dynamic analysis. diff --git a/doc/administration/logs/log_parsing.md b/doc/administration/logs/log_parsing.md index 21ce3d7f17f..3049e4ccc32 100644 --- a/doc/administration/logs/log_parsing.md +++ b/doc/administration/logs/log_parsing.md @@ -273,8 +273,8 @@ jq --raw-output --slurp ' .[2]."grpc.time_ms", .[0]."grpc.request.glProjectPath" ] - | @sh' current \ -| awk 'BEGIN { printf "%7s %10s %10s %10s\t%s\n", "CT", "MAX DURS", "", "", "PROJECT" } + | @sh' current | + awk 'BEGIN { printf "%7s %10s %10s %10s\t%s\n", "CT", "MAX DURS", "", "", "PROJECT" } { printf "%7u %7u ms, %7u ms, %7u ms\t%s\n", $1, $2, $3, $4, $5 }' ``` @@ -288,12 +288,18 @@ jq --raw-output --slurp ' ... ``` +#### Types of user and project activity overview + +```shell +jq --raw-output '[.username, ."grpc.method", ."grpc.request.glProjectPath"] | @tsv' current | sort | uniq -c | sort -n +``` + #### Find all projects affected by a fatal Git problem ```shell -grep "fatal: " current | \ - jq '."grpc.request.glProjectPath"' | \ - sort | uniq +grep "fatal: " current | + jq '."grpc.request.glProjectPath"' | + sort | uniq ``` ### Parsing `gitlab-shell/gitlab-shell.log` diff --git a/doc/administration/settings/jira_cloud_app.md b/doc/administration/settings/jira_cloud_app.md index 7a53a67ec86..ef6a23a2984 100644 --- a/doc/administration/settings/jira_cloud_app.md +++ b/doc/administration/settings/jira_cloud_app.md @@ -327,6 +327,8 @@ To resolve this issue, ensure all prerequisites for your installation method hav - [Prerequisites for connecting the GitLab for Jira Cloud app](#prerequisites) - [Prerequisites for installing the GitLab for Jira Cloud app manually](#prerequisites-1) +If you have configured a Jira Connect Proxy URL and the problem persists after checking the prerequisites, review [Debugging Jira Connect Proxy issues](#debugging-jira-connect-proxy-issues). + If you're using GitLab 15.8 and earlier and have previously enabled both the `jira_connect_oauth_self_managed` and the `jira_connect_oauth` feature flags, you must disable the `jira_connect_oauth_self_managed` flag due to a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/388943). To check for these flags: @@ -344,6 +346,46 @@ due to a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/388943). To Feature.disable(:jira_connect_oauth_self_managed) ``` +#### Debugging Jira Connect Proxy issues + +If you are using a self-managed GitLab instance and you have configured `https://gitlab.com` for the Jira Connect Proxy URL when +[setting up the OAuth authentication](#set-up-oauth-authentication), you can inspect the network traffic in your browser's development +tools while reproducing the `Failed to update the GitLab instance` error to see a more precise error. + +You should see a `GET` request to `https://gitlab.com/-/jira_connect/installations`. + +This request should return a `200` status code, but it can return a `422` status code if there was a problem. The response body can be checked for the error. + +If you cannot resolve the problem and you are a GitLab customer, contact [GitLab Support](https://about.gitlab.com/support/) for assistance. Provide +GitLab Support with: + +1. Your GitLab self-managed instance URL. +1. Your GitLab.com username. +1. If possible, the `X-Request-Id` response header for the failed `GET` request to `https://gitlab.com/-/jira_connect/installations`. +1. Optional. [A HAR file that captured the problem](https://support.zendesk.com/hc/en-us/articles/4408828867098-Generating-a-HAR-file-for-troubleshooting). + +The GitLab Support team can then look up why this is failing in the GitLab.com server logs. + +##### Process for GitLab Support + +NOTE: +These steps can only be completed by GitLab Support. + +In Kibana, the logs should be filtered for `json.meta.caller_id: JiraConnect::InstallationsController#update` and `NOT json.status: 200`. +If you have been provided the `X-Request-Id` value, you can use that against `json.correlation_id` to narrow down the results. + +Each `GET` request to the Jira Connect Proxy URL `https://gitlab.com/-/jira_connect/installations` generates two log entries. + +For the first log: + +- `json.status` is `422`. +- `json.params.value` should match the GitLab self-managed URL `[[FILTERED], {"instance_url"=>"https://gitlab.example.com"}]`. + +For the second log: + +- `json.message` is `Proxy lifecycle event received error response` or similar. +- `json.jira_status_code` and `json.jira_body` might contain details on why GitLab.com wasn't able to connect back to the self-managed instance. + ### `Failed to link group` After you connect the GitLab for Jira Cloud app for self-managed instances, you might get one of these errors: diff --git a/doc/api/dependency_list_export.md b/doc/api/dependency_list_export.md index 077f9e8dc1e..db43ea238c1 100644 --- a/doc/api/dependency_list_export.md +++ b/doc/api/dependency_list_export.md @@ -59,7 +59,7 @@ Example response: Get a single dependency list export. ```plaintext -GET /security/dependency_list_exports/:id +GET /dependency_list_exports/:id ``` | Attribute | Type | Required | Description | @@ -67,7 +67,7 @@ GET /security/dependency_list_exports/:id | `id` | integer | yes | The ID of the dependency list export. | ```shell -curl --header "PRIVATE-TOKEN: <private_token>" "https://gitlab.example.com/api/v4/security/dependency_list_exports/2" +curl --header "PRIVATE-TOKEN: <private_token>" "https://gitlab.example.com/api/v4/dependency_list_exports/2" ``` The status code is `202 Accepted` when the dependency list export is being generated, and `200 OK` when it's ready. @@ -88,7 +88,7 @@ Example response: Download a single dependency list export. ```plaintext -GET /security/dependency_list_exports/:id/download +GET /dependency_list_exports/:id/download ``` | Attribute | Type | Required | Description | @@ -96,7 +96,7 @@ GET /security/dependency_list_exports/:id/download | `id` | integer | yes | The ID of the dependency list export. | ```shell -curl --header "PRIVATE-TOKEN: <private_token>" "https://gitlab.example.com/api/v4/security/dependency_list_exports/2/download" +curl --header "PRIVATE-TOKEN: <private_token>" "https://gitlab.example.com/api/v4/dependency_list_exports/2/download" ``` The response is `404 Not Found` if the dependency list export is not finished yet or was not found. diff --git a/doc/api/runners.md b/doc/api/runners.md index ea7016fa1a2..372ce397332 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -52,13 +52,14 @@ GET /runners?paused=true GET /runners?tag_list=tag1,tag2 ``` -| Attribute | Type | Required | Description | -|------------|--------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided | -| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` | -| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in a future version of the REST API | -| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | -| `tag_list` | string array | no | A list of runner tags | +| Attribute | Type | Required | Description | +|------------------|--------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided | +| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` | +| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in a future version of the REST API | +| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | +| `tag_list` | string array | no | A list of runner tags | +| `version_prefix` | string | no | The prefix of the version of the runners to return. For example, `15.0`, `14`, `16.1.241` | ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners" @@ -117,13 +118,14 @@ GET /runners/all?paused=true GET /runners/all?tag_list=tag1,tag2 ``` -| Attribute | Type | Required | Description | -|------------|--------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `specific`, `shared`, `active`, `paused`, `online` and `offline`; showing all runners if none provided | -| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` | -| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in a future version of the REST API | -| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | -| `tag_list` | string array | no | A list of runner tags | +| Attribute | Type | Required | Description | +|------------------|--------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `specific`, `shared`, `active`, `paused`, `online` and `offline`; showing all runners if none provided | +| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` | +| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in a future version of the REST API | +| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | +| `tag_list` | string array | no | A list of runner tags | +| `version_prefix` | string | no | The prefix of the version of the runners to return. For example, `15.0`, `14`, `16.1.241` | ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all" @@ -468,14 +470,15 @@ GET /projects/:id/runners/all?paused=true GET /projects/:id/runners?tag_list=tag1,tag2 ``` -| Attribute | Type | Required | Description | -|------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user | -| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided | -| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` | -| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in a future version of the REST API | -| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | -| `tag_list` | string array | no | A list of runner tags | +| Attribute | Type | Required | Description | +|------------------|----------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user | +| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided | +| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` | +| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in a future version of the REST API | +| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | +| `tag_list` | string array | no | A list of runner tags | +| `version_prefix` | string | no | The prefix of the version of the runners to return. For example, `15.0`, `14`, `16.1.241` | ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners" @@ -585,13 +588,14 @@ GET /groups/:id/runners/all?paused=true GET /groups/:id/runners?tag_list=tag1,tag2 ``` -| Attribute | Type | Required | Description | -|------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `id` | integer | yes | The ID of the group owned by the authenticated user | -| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type`. The `project_type` value is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351466) and will be removed in a future version of the REST API | -| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in a future version of the REST API | -| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | -| `tag_list` | string array | no | A list of runner tags | +| Attribute | Type | Required | Description | +|------------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | integer | yes | The ID of the group owned by the authenticated user | +| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type`. The `project_type` value is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351466) and will be removed in GitLab 15.0 | +| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in a future version of the REST API | +| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | +| `tag_list` | string array | no | A list of runner tags | +| `version_prefix` | string | no | The prefix of the version of the runners to return. For example, `15.0`, `14`, `16.1.241` | ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/9/runners" diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md index 0565efb4def..b90ae3c3b79 100644 --- a/doc/integration/jenkins.md +++ b/doc/integration/jenkins.md @@ -157,7 +157,7 @@ If you cannot [provide GitLab with your Jenkins server URL and authentication in - [GitLab Jenkins Integration](https://about.gitlab.com/solutions/jenkins/) - [How to set up Jenkins on your local machine](../development/integrations/jenkins.md) - [How to migrate from Jenkins to GitLab CI/CD](../ci/migration/jenkins.md) -- Get more information about how to choose the right migration strategy in [Jenkins to GitLab: The ultimate guide to modernizing your CI/CD environment](https://about.gitlab.com/blog/2023/11/01/jenkins-gitlab-ultimate-guide-to-modernizing-cicd-environment/?utm_campaign=devrel&utm_source=twitter&utm_medium=social&utm_budget=devrel) +- [Jenkins to GitLab: The ultimate guide to modernizing your CI/CD environment](https://about.gitlab.com/blog/2023/11/01/jenkins-gitlab-ultimate-guide-to-modernizing-cicd-environment/?utm_campaign=devrel&utm_source=twitter&utm_medium=social&utm_budget=devrel) ## Troubleshooting diff --git a/doc/solutions/cloud/aws/gitlab_aws_integration.md b/doc/solutions/cloud/aws/gitlab_aws_integration.md new file mode 100644 index 00000000000..0e15b3dc9b2 --- /dev/null +++ b/doc/solutions/cloud/aws/gitlab_aws_integration.md @@ -0,0 +1,108 @@ +--- +stage: Solutions Architecture +group: Solutions Architecture +info: This page is owned by the Solutions Architecture team. +description: "Integrations Solutions Index for GitLab and AWS." +--- + +# Integrate with AWS + +Learn how to integrate GitLab and AWS. + +This content is intended for GitLab team members as well as members of the wider community. + +This page attempts to index the ways in which GitLab can integrate with AWS. It does so whether the integration is the result of configuring general functionality, was built in to AWS or GitLab or is provided as a solution. + +| Text Tag | Configuration / Built / Solution | Support/Maintenance | +| -------------------- | ------------------------------------------------------------ | ------------------- | +| `[AWS Configuration]` | Integration via Configuring Existing AWS Functionality | AWS | +| `[GitLab Configuration]` | Integration via Configuring Existing GitLab Functionality | GitLab | +| `[AWS Built]` | Built into AWS by Product Team to Address AWS Integration | AWS | +| `[GitLab Built]` | Built into GitLab by Product Team to Address AWS Integration | GitLab | +| `[AWS Solution]` | Built as Solution Example by AWS or AWS Partners | Community/Example | +| `[GitLab Solution]` | Built as Solution Example by GitLab or GitLab Partners | Community/Example | +| `[CI Solution]` | Built, at least in part, using GitLab CI and therefore <br />more customer customizable. | Items tagged `[CI Solution will]` <br />also carry one of the other tags <br />that indicates the maintenance status. | + +## Partnership Information + +### AWS known issues list + +Known issues are gathered from within GitLab and from customer reported issues. Customers successfully implement GitLab with a variety of “as a Service” components that GitLab has not specifically been designed for, nor has ongoing testing for. While GitLab does take partner technologies very seriously, the highlighting of known issues here is a convenience for implementers and it does not imply that GitLab has targeted compatibility with, nor carries any type of guarantee of running on the partner technology where the issues occur. Consult individual issues to understand the GitLab stance and plans on any given known issue. + +See the [GitLab AWS known issues list](https://gitlab.com/gitlab-com/alliances/aws/public-tracker/-/issues?label_name[]=AWS+Known+Issue) for a complete list. + +## Integrations For Development Activities + +### SCM Integrations + +- **AWS CodeStar Connections** - enables SCM connections to multiple AWS Services. **Currently for GitLab.com SaaS only**. [Configure GitLab](https://docs.aws.amazon.com/dtconsole/latest/userguide/connections-create-gitlab.html). [Supported Providers](https://docs.aws.amazon.com/dtconsole/latest/userguide/supported-versions-connections.html). [Supported AWS Services](https://docs.aws.amazon.com/dtconsole/latest/userguide/integrations-connections.html) - each one may have to make updates to support GitLab, so here is the subset that currently support GitLab `[AWS Built]` + - [AWS CodePipeline Integration](https://docs.aws.amazon.com/codepipeline/latest/userguide/connections-gitlab.html) - use GitLab as source for CodePipeline. `[AWS Built]` + - **AWS CodeBuild Integration** - indirectly through CodePipeline support. `[AWS Built]` + - **Amazon CodeWhisperer Customization Capability** [can connect to a GitLab repo](https://aws.amazon.com/blogs/aws/new-customization-capability-in-amazon-codewhisperer-generates-even-better-suggestions-preview/). `[AWS Built]` + - **AWS Service Catalog** directly inherits CodeStar Connections, there is not any specific documentation about GitLab since it just uses any GitLab CodeStar Connection that has been created in the account. `[AWS Built]` + - **AWS Proton** directly inherits CodeStar Connections, there is not any specific documentation about GitLab since it just uses any GitLab CodeStar Connection that has been created in the account. `[AWS Built]` + - **AWS Glue Notebook Jobs** directly inherit CodeStar Connections, there is not any specific documentation about GitLab since it just uses any GitLab CodeStar Connection that has been created in the account. `[AWS Built]` + - **Amazon SageMaker MLOps Projects** are done in CodePipeline and so directly inherit CodeStar Connections ([as noted here](https://docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-projects-walkthrough-3rdgit.html#sagemaker-proejcts-walkthrough-connect-3rdgit)), there is not any specific documentation about GitLab since it just uses any GitLab CodeStar Connection that has been created in the account. `[AWS Built]` + - **Amazon SageMaker Notebooks** [allow Git repositories to be specified by the Git clone URL](https://docs.aws.amazon.com/sagemaker/latest/dg/nbi-git-resource.html) and configuration of a secret - so GitLab is configurable. `[AWS Configuration]` + - **AWS CloudFormation** publishing of public extensions - **not yet supported**. `[AWS Built]` + - **Amazon CodeGuru Reviewer Repositories** - **not yet supported**. `[AWS Built]` +- [GitLab Push Mirroring to CodeCommit](../../../user/project/repository/mirror/push.md#set-up-a-push-mirror-from-gitlab-to-aws-codecommit) Workaround enables GitLab repositories to leverage CodePipeline SCM Triggers. GitLab can already leverage S3 and Container Triggers for CodePipeline. **Still required for Self-Managed and Dedicated for the time being.** `[GitLab Configuration]` + +### CI Integrations + +- **Direct CI Integrations That Use Keys, IAM or OIDC/JWT to Authenticate to AWS Services from GitLab Runners** + - **Amazon CodeGuru Reviewer CI workflows using GitLab CI** - can be done, not yet documented. `[AWS Solution]` `[CI Solution]` + - [Amazon CodeGuru Secure Scanning using GitLab CI](https://docs.aws.amazon.com/codeguru/latest/security-ug/get-started-gitlab.html) `[AWS Solution]` `[CI Solution]` + +### CD and Operations Integrations + +- **AWS CodeDeploy Integration** - indirectly through CodePipeline support. `[AWS Built]` +- [Integrate EKS clusters for application deployment](../../../user/infrastructure/clusters/connect/new_eks_cluster.md). `[GitLab Built]` + +## Solutions For Specific Development Frameworks and Ecosystems + +Generally solutions demonstrate end-to-end capabilities for the development framework - leveraging all relevant integration techniques to show the art of maximum value for using GitLab and AWS together. + +### Serverless Development + +- [Serverless Framework Deployment to AWS with GitLab Serverless SAST Scanning and Managed DevOps Environments](https://gitlab.com/guided-explorations/aws/serverless/serverless-framework-aws) - working example code and tutorials. `[GitLab Solution]` `[CI Solution]` + - [Tutorial: Serverless Framework Deployment to AWS with GitLab Serverless SAST Scanning](https://gitlab.com/guided-explorations/aws/serverless/serverless-framework-aws/-/blob/master/TUTORIAL.md) `[GitLab Solution]` `[CI Solution]` + - [Tutorial: Secure Serverless Framework Development with GitLab Security Policy Approval Rules and Managed DevOps Environments](https://gitlab.com/guided-explorations/aws/serverless/serverless-framework-aws/-/blob/master/TUTORIAL2-SecurityAndManagedEnvs.md) `[GitLab Solution]` `[CI Solution]` + +### Infrastructure as Code + +- [Terraform Deployment to AWS with GitLab MR Managed DevOps Environments](https://gitlab.com/guided-explorations/aws/terraform/terraform-web-server-cluster) + - [Tutorial: Terraform Deployment to AWS with GitLab IaC SAST Scanning](https://gitlab.com/guided-explorations/aws/terraform/terraform-web-server-cluster/-/blob/prod/TUTORIAL.md) `[GitLab Solution]` `[CI Solution]` + - [Terraform Deployment to AWS with GitLab Security Policy Approval Rules and Managed DevOps Environments](https://gitlab.com/guided-explorations/aws/terraform/terraform-web-server-cluster/-/blob/prod/TUTORIAL2-SecurityAndManagedEnvs.md) `[GitLab Solution]` `[CI Solution]` +- [Tutorial: CloudFormation Deployment With GitLab MR Managed DevOps Environments](https://gitlab.com/guided-explorations/aws/cloudformation-deploy) `[GitLab Solution]` `[CI Solution]` + +### .Net on AWS + +- [Working Example Code for Scaling .NET Framework 4.x Runners on AWS](https://gitlab.com/guided-explorations/aws/dotnet-aws-toolkit) `[GitLab Solution]` `[CI Solution]` +- [Video Walkthrough of Code and Building a .NET Framework 4.x Project](https://www.youtube.com/watch?v=_4r79ZLmDuo) `[GitLab Solution]` `[CI Solution]` + +## Authentication Integration + +- [Runner Job Authentication using Open ID & JWT Authentication](../../../ci/cloud_services/aws/index.md). `[GitLab Built]` + - [Configure OpenID Connect between GitLab and AWS](https://gitlab.com/guided-explorations/aws/configure-openid-connect-in-aws) `[GitLab Solution]` `[CI Solution]` + - [OIDC and Multi-Account Deployment with GitLab and ECS](https://gitlab.com/guided-explorations/aws/oidc-and-multi-account-deployment-with-ecs) `[GitLab Solution]` `[CI Solution]` + +## GitLab Instance Compute & Operations Integration + +- Installing GitLab Self-Managed on AWS + - GitLab Single EC2 Instance. `[GitLab Built]` + - [Using 5 Seat AWS marketplace subscription](../../../install/aws/index.md#marketplace-subscription) + - [Using Prepared AMIs](../../../install/aws/index.md#official-gitlab-releases-as-amis) - Bring Your Own License for Enterprise Edition. + + - GitLab Cloud Native Hybrid Scaled on AWS EKS and Paas. `[GitLab Built]` + - Using GitLab Environment Toolkit (GET) - `[GitLab Solution]` + + - GitLab Instance Scaled on AWS EC2 and PaaS. `[GitLab Built]` + - Using GitLab Environment Toolkit (GET) - `[GitLab Solution]` + +- [Amazon Managed Grafana](https://docs.aws.amazon.com/grafana/latest/userguide/gitlab-AMG-datasource.html) for GitLab self-managed Prometheus metrics. `[AWS Built]` +- [Autoscaling GitLab Runner on AWS EC2](https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/). `[GitLab Built]` +- [GitLab HA Scaling Runner Vending Machine for AWS EC2 ASG](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/). `[GitLab Solution]` + - Runner vending machine training resources. + +- [GitLab EKS Fargate Runners](https://gitlab.com/guided-explorations/aws/eks-runner-configs/gitlab-runner-eks-fargate/-/blob/main/README.md). `[GitLab Solution]` diff --git a/doc/solutions/cloud/aws/index.md b/doc/solutions/cloud/aws/index.md new file mode 100644 index 00000000000..3d5c777de30 --- /dev/null +++ b/doc/solutions/cloud/aws/index.md @@ -0,0 +1,11 @@ +--- +stage: Solutions Architecture +group: Solutions Architecture +info: This page is owned by the Solutions Architecture team. +--- + +# AWS Solutions + +This documentation covers solutions relating to Amazon Web Services (AWS). + +[GitLab AWS Integration Index](gitlab_aws_integration.md) diff --git a/doc/solutions/cloud/index.md b/doc/solutions/cloud/index.md new file mode 100644 index 00000000000..27a90223382 --- /dev/null +++ b/doc/solutions/cloud/index.md @@ -0,0 +1,13 @@ +--- +stage: Solutions Architecture +group: Solutions Architecture +info: This page is owned by the Solutions Architecture team. +--- + +# Cloud solutions + +This documentation section covers a variety of Cloud Solutions. + +## Cloud solutions by provider + +[AWS Solutions](aws/index.md) diff --git a/doc/solutions/index.md b/doc/solutions/index.md index bf165eadeea..c1aa356fe64 100644 --- a/doc/solutions/index.md +++ b/doc/solutions/index.md @@ -1,7 +1,7 @@ --- stage: Solutions Architecture group: Solutions Architecture -info: This file is owned by the Solutions Architecture team. +info: This page is owned by the Solutions Architecture team. --- # Solutions architecture diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 8c141559cd2..308c8ae2311 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -842,6 +842,20 @@ PostgreSQL 14 will also be supported for instances that want to upgrade prior to <div class="deprecation breaking-change" data-milestone="17.0"> +### Proxy-based DAST deprecated + +<div class="deprecation-notes"> +- Announced in GitLab <span class="milestone">16.6</span> +- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change)) +- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/430966). +</div> + +As of GitLab 17.0, Proxy-based DAST will not be supported. Please migrate to Browser-based DAST to continue analyzing your projects for security findings via dynamic analysis. + +</div> + +<div class="deprecation breaking-change" data-milestone="17.0"> + ### Queue selector for running Sidekiq is deprecated <div class="deprecation-notes"> diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index b55f2411a0a..64fa5d7b448 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -63,7 +63,8 @@ To add new email to your account: 1. Verify your email address with the verification email received. NOTE: -[Making your email non-public](#set-your-public-email) does not prevent it from being utilised for commit matching. +[Making your email non-public](#set-your-public-email) does not prevent it from being used for commit matching, +[project imports](../project/import/index.md), and [group migrations](../group/import/index.md). ## Make your user profile page private diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 42817c782f4..17bee275c51 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -24,6 +24,9 @@ module API desc: 'The status of runners to return' optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'A list of runner tags', documentation: { example: "['macos', 'shell']" } + optional :version_prefix, type: String, desc: 'The version prefix of runners to return', documentation: { example: "'15.1.' or '16.'" }, + regexp: /^[\d+.]+/ + use :pagination end @@ -46,6 +49,7 @@ module API runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES) runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) runners = filter_runners(runners, params[:paused] ? 'paused' : 'active', allowed_scopes: %w[paused active]) if params.include?(:paused) + runners = runners.with_version_prefix(params[:version_prefix]) if params[:version_prefix] runners = runners.tagged_with(params[:tag_list]) if params[:tag_list] runners diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0ee88c49fe4..e4ed3e83e59 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -34652,9 +34652,6 @@ msgstr "" msgid "Personal access token" msgstr "" -msgid "Personal project creation is not allowed. Please contact your administrator with questions" -msgstr "" - msgid "Personal projects" msgstr "" @@ -41748,6 +41745,9 @@ msgstr "" msgid "Runners|Version %{version}" msgstr "" +msgid "Runners|Version starts with" +msgstr "" + msgid "Runners|View installation instructions" msgstr "" @@ -55169,6 +55169,12 @@ msgstr "" msgid "You cannot combine replace_ids with add_ids or remove_ids" msgstr "" +msgid "You cannot create new projects in your personal namespace because you have reached your personal project limit." +msgstr "" + +msgid "You cannot create projects in your personal namespace. Contact your GitLab administrator." +msgstr "" + msgid "You cannot impersonate a blocked user" msgstr "" @@ -55519,9 +55525,6 @@ msgstr "" msgid "You will receive notifications only for comments in which you were @mentioned" msgstr "" -msgid "You won't be able to create new projects because you have reached your project limit." -msgstr "" - msgid "You'll be charged for %{true_up_start}users over license%{true_up_end} on a quarterly or annual basis, depending on the terms of your agreement." msgstr "" @@ -55591,6 +55594,9 @@ msgstr "" msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication." msgstr "" +msgid "You've reached your limit of %{limit} projects created. Contact your GitLab administrator." +msgstr "" + msgid "You've rejected %{user}" msgstr "" @@ -55886,9 +55892,6 @@ msgstr "" msgid "Your profile" msgstr "" -msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it" -msgstr "" - msgid "Your projects" msgstr "" diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb index 404f9184290..ddd8bf8b337 100644 --- a/qa/qa/page/project/settings/mirroring_repositories.rb +++ b/qa/qa/page/project/settings/mirroring_repositories.rb @@ -6,41 +6,41 @@ module QA module Settings class MirroringRepositories < Page::Base view 'app/views/projects/mirrors/_authentication_method.html.haml' do - element :authentication_method_field + element 'authentication-method-field' element 'username-field' element 'password-field' end view 'app/views/projects/mirrors/_mirror_repos.html.haml' do - element :mirror_repository_url_field - element :mirror_repository_button + element 'mirror-repository-url-field' + element 'mirror-repository-button' element 'add-new-mirror' end view 'app/views/projects/mirrors/_mirror_repos_list.html.haml' do - element :mirror_repository_url_content - element :mirror_last_update_at_content - element :mirror_error_badge_content - element :mirrored_repository_row_container - element :copy_public_key_button + element 'mirror-repository-url-content' + element 'mirror-last-update-at-content' + element 'mirror-error-badge-content' + element 'mirrored-repository-row-container' + element 'copy-public-key-button' end view 'app/views/projects/mirrors/_mirror_repos_form.html.haml' do - element :mirror_direction_field + element 'mirror-direction-field' end view 'app/views/shared/_remote_mirror_update_button.html.haml' do - element :update_now_button + element 'update-now-button' end view 'app/views/projects/mirrors/_ssh_host_keys.html.haml' do - element :detect_host_keys - element :fingerprints_list + element 'detect-host-keys' + element 'fingerprints-list' end def repository_url=(value) click_element 'add-new-mirror' - fill_element :mirror_repository_url_field, value + fill_element 'mirror-repository-url-field', value end def username=(value) @@ -54,12 +54,12 @@ module QA def mirror_direction=(value) raise ArgumentError, "Mirror direction must be 'Push' or 'Pull'" unless %w[Push Pull].include?(value) - select_element(:mirror_direction_field, value) + select_element('mirror-direction-field', value) # Changing the mirror direction causes the fields below to change, # and that change is animated, so we need to wait for the animation # to complete otherwise changes to those fields could fail - wait_for_animated_element :authentication_method_field + wait_for_animated_element 'authentication-method-field' end def authentication_method=(value) @@ -67,35 +67,35 @@ module QA raise ArgumentError, "Authentication method must be 'SSH public key', 'Password', or 'None'" end - select_element(:authentication_method_field, value) + select_element('authentication-method-field', value) end def public_key(url) row_index = find_repository_row_index url - within_element_by_index(:mirrored_repository_row_container, row_index) do - find_element(:copy_public_key_button)['data-clipboard-text'] + within_element_by_index('mirrored-repository-row-container', row_index) do + find_element('copy-public-key-button')['data-clipboard-text'] end end def detect_host_keys - click_element :detect_host_keys + click_element 'detect-host-keys' # The host key detection process is interrupted if we navigate away # from the page before the fingerprint appears. - find_element(:fingerprints_list, text: /.*/, wait: 60) + find_element('fingerprints-list', text: /.*/, wait: 60) end def mirror_repository - click_element :mirror_repository_button + click_element 'mirror-repository-button' end def update(url) row_index = find_repository_row_index(url) - within_element_by_index(:mirrored_repository_row_container, row_index) do + within_element_by_index('mirrored-repository-row-container', row_index) do # When a repository is first mirrored, the update process might # already be started, so the button is already "clicked" - click_element :update_now_button if has_element?(:update_now_button, wait: 0) + click_element 'update-now-button' if has_element?('update-now-button', wait: 0) end end @@ -105,16 +105,16 @@ module QA row_index = find_repository_row_index(url) wait_until(sleep_interval: 1) do - within_element_by_index(:mirrored_repository_row_container, row_index) do - last_update = find_element(:mirror_last_update_at_content, wait: 0) + within_element_by_index('mirrored-repository-row-container', row_index) do + last_update = find_element('mirror-last-update-at-content', wait: 0) last_update.has_text?('just now') || last_update.has_text?('seconds') end end # Fail early if the page still shows that there has been no update - within_element_by_index(:mirrored_repository_row_container, row_index) do - find_element(:mirror_last_update_at_content, wait: 0).assert_no_text('Never') - assert_no_element(:mirror_error_badge_content) + within_element_by_index('mirrored-repository-row-container', row_index) do + find_element('mirror-last-update-at-content', wait: 0).assert_no_text('Never') + assert_no_element('mirror-error-badge-content') end end @@ -122,7 +122,7 @@ module QA def find_repository_row_index(target_url) wait_until(max_duration: 5, reload: false) do - all_elements(:mirror_repository_url_content, minimum: 1).index do |url| + all_elements('mirror-repository-url-content', minimum: 1).index do |url| # The url might be a sanitized url but the target_url won't be so # we compare just the paths instead of the full url # We also must remove any badges from the url (e.g. All Branches) diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index 8358a4ae33e..07b17faa9bd 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -22,7 +22,7 @@ module QA end view 'app/views/protected_branches/shared/_create_protected_branch.html.haml' do - element :protect_button + element 'protect-button' end def select_branch(branch_name) @@ -43,7 +43,7 @@ module QA end def protect_branch - click_element(:protect_button, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) + click_element('protect-button', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) wait_for_requests end diff --git a/qa/qa/page/project/settings/protected_tags.rb b/qa/qa/page/project/settings/protected_tags.rb index 5923bc7dc78..c23ade31fb1 100644 --- a/qa/qa/page/project/settings/protected_tags.rb +++ b/qa/qa/page/project/settings/protected_tags.rb @@ -8,36 +8,36 @@ module QA include Page::Component::DropdownFilter view 'app/views/projects/protected_tags/shared/_dropdown.html.haml' do - element :tags_dropdown + element 'tags-dropdown' end view 'app/assets/javascripts/protected_tags/protected_tag_create.js' do - element :allowed_to_create_dropdown + element 'allowed-to-create-dropdown' end view 'app/views/projects/protected_tags/shared/_create_protected_tag.html.haml' do - element :protect_tag_button + element 'protect-tag-button' end def set_tag(tag_name) click_button 'Add tag' - click_element :tags_dropdown + click_element 'tags-dropdown' filter_and_select(tag_name) end def choose_access_level_role(role) - return if find_element(:allowed_to_create_dropdown).text == role + return if find_element('allowed-to-create-dropdown').text == role - click_element :allowed_to_create_dropdown - within_element :allowed_to_create_dropdown do + click_element 'allowed-to-create-dropdown' + within_element 'allowed-to-create-dropdown' do click_on role end # confirm selection and remove dropdown - click_element :allowed_to_create_dropdown + click_element 'allowed-to-create-dropdown' end def click_protect_tag_button - click_element :protect_tag_button + click_element 'protect-tag-button' end end end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 9851153bd39..58da1d37904 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -621,6 +621,64 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu end end + describe 'GET test_report_summary.json' do + let_it_be(:build) { create(:ci_build, :success, :test_reports, project: project) } + + before do + sign_in(user) + end + + context 'when the user has access' do + let(:user) { developer } + + context 'when the summary has been generated' do + let!(:report_result) { create(:ci_build_report_result, build: build, project: project) } + + before do + get_test_report_summary + end + + it 'returns the summary as json' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('job/test_report_summary') + end + end + + context 'when the summary has not been generated' do + before do + get_test_report_summary + end + + it 'returns a 404 response' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + context 'when the user does not have access' do + let(:user) { guest } + + before do + project.update!(public_builds: false) + get_test_report_summary + end + + it 'returns not_found status' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + + def get_test_report_summary + get :test_report_summary, + params: { + namespace_id: project.namespace, + project_id: project, + id: build.id + }, + format: :json + end + end + describe 'GET trace.json' do before do get_trace diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 1416124497a..750f5f8d4b9 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -202,6 +202,36 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end end + describe 'filter by version prefix' do + before_all do + runner_v15 = create(:ci_runner, :instance, description: 'runner-v15') + runner_v14 = create(:ci_runner, :instance, description: 'runner-v14') + + create(:ci_runner_machine, runner: runner_v15, version: '15.0.0') + create(:ci_runner_machine, runner: runner_v14, version: '14.0.0') + end + + before do + visit admin_runners_path + end + + it 'shows all runners' do + expect(page).to have_link('All 2') + + expect(page).to have_content 'runner-v15' + expect(page).to have_content 'runner-v14' + end + + it 'shows filtered runner based on supplied prefix' do + input_filtered_search_filter_is_only(s_('Runners|Version starts with'), '15.0') + + expect(page).to have_link('All 1') + + expect(page).not_to have_content 'runner-v14' + expect(page).to have_content 'runner-v15' + end + end + describe 'filter by status' do let_it_be(:never_contacted) do create(:ci_runner, :instance, description: 'runner-never-contacted', contacted_at: nil) diff --git a/spec/fixtures/api/schemas/job/test_report_summary.json b/spec/fixtures/api/schemas/job/test_report_summary.json new file mode 100644 index 00000000000..056a02854d4 --- /dev/null +++ b/spec/fixtures/api/schemas/job/test_report_summary.json @@ -0,0 +1,34 @@ +{ + "type": "object", + "properties": { + "total": { + "type": "object", + "properties": { + "time": { "type": "number" }, + "count": { "type": "number" }, + "success": { "type": "number" }, + "failed": { "type": "number" }, + "skipped": { "type": "number" }, + "error": { "type": "number" }, + "suite_error": { "type": ["string", "null"] } + } + }, + "test_suites": { + "type": "array", + "items": { + "name": { "type": "string" }, + "total_time": { "type": "number" }, + "total_count": { "type": "number" }, + "success_count": { "type": "number" }, + "failed_count": { "type": "number" }, + "skipped_count": { "type": "number" }, + "error_count": { "type": "number" }, + "build_ids": { + "type": "array", + "items": { "type": "number" } + }, + "suite_error": { "type": ["string", "null"] } + } + } + } +} diff --git a/spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js index bc28147db27..4f5f9c43cb4 100644 --- a/spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js @@ -43,6 +43,7 @@ import { PARAM_KEY_PAUSED, PARAM_KEY_STATUS, PARAM_KEY_TAG, + PARAM_KEY_VERSION, STATUS_ONLINE, DEFAULT_MEMBERSHIP, RUNNER_PAGE_SIZE, @@ -255,6 +256,10 @@ describe('AdminRunnersApp', () => { options: expect.any(Array), }), expect.objectContaining({ + type: PARAM_KEY_VERSION, + title: 'Version starts with', + }), + expect.objectContaining({ type: PARAM_KEY_TAG, recentSuggestionsStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`, }), diff --git a/spec/frontend/ci/runner/mock_data.js b/spec/frontend/ci/runner/mock_data.js index b8eb9f0ba1b..51556650c32 100644 --- a/spec/frontend/ci/runner/mock_data.js +++ b/spec/frontend/ci/runner/mock_data.js @@ -310,6 +310,23 @@ export const mockSearchExamples = [ first: RUNNER_PAGE_SIZE, }, }, + { + name: 'version prefix', + urlQuery: '?version_prefix[]=16.', + search: { + runnerType: null, + membership: DEFAULT_MEMBERSHIP, + filters: [{ type: 'version_prefix', value: { data: '16.', operator: '=' } }], + pagination: {}, + sort: CREATED_DESC, + }, + graphqlVariables: { + versionPrefix: '16.', + membership: DEFAULT_MEMBERSHIP, + sort: CREATED_DESC, + first: RUNNER_PAGE_SIZE, + }, + }, ]; export const onlineContactTimeoutSecs = 2 * 60 * 60; diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 290f557cdd6..ce896af0799 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -7121,7 +7121,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr project.check_personal_projects_limit expect(project.errors[:limit_reached].first) - .to match(/Personal project creation is not allowed/) + .to eq('You cannot create projects in your personal namespace. Contact your GitLab administrator.') end end @@ -7134,7 +7134,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr project.check_personal_projects_limit expect(project.errors[:limit_reached].first) - .to match(/Your project limit is 5 projects/) + .to eq("You've reached your limit of 5 projects created. Contact your GitLab administrator.") end end end diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index 2b2d2e0def8..7b69e9d1cb0 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -249,6 +249,39 @@ RSpec.describe API::Ci::Runners, :aggregate_failures, feature_category: :runner_ a_hash_including('description' => 'Runner tagged with tag1 and tag2') ] end + + context 'with ci_runner_machines' do + let_it_be(:version_ci_runner) { create(:ci_runner, :project, description: 'Runner with machine') } + let_it_be(:version_ci_runner_machine) { create(:ci_runner_machine, runner: version_ci_runner, version: '15.0.3') } + let_it_be(:version_16_ci_runner) { create(:ci_runner, :project, description: 'Runner with machine version 16') } + let_it_be(:version_16_ci_runner_machine) { create(:ci_runner_machine, runner: version_16_ci_runner, version: '16.0.1') } + + it 'filters runners by version_prefix when prefix is "15.0"' do + get api('/runners/all?version_prefix=15.0', admin, admin_mode: true) + + expect(json_response).to match_array [ + a_hash_including('description' => 'Runner with machine', 'active' => true, 'paused' => false) + ] + end + + it 'filters runners by version_prefix when prefix is "16"' do + get api('/runners/all?version_prefix=16', admin, admin_mode: true) + expect(json_response).to match_array [ + a_hash_including('description' => 'Runner with machine version 16', 'active' => true, 'paused' => false) + ] + end + + it 'filters runners by version_prefix when prefix is "25"' do + get api('/runners/all?version_prefix=25', admin, admin_mode: true) + expect(json_response).to match_array [] + end + + it 'does not filter runners by version_prefix when prefix is invalid ("V15")' do + get api('/runners/all?version_prefix=v15', admin, admin_mode: true) + + expect(response).to have_gitlab_http_status(:bad_request) + end + end end context 'without admin privileges' do diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb index c5571086700..0e2712d742d 100644 --- a/spec/requests/api/graphql/ci/runners_spec.rb +++ b/spec/requests/api/graphql/ci/runners_spec.rb @@ -35,28 +35,16 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do end context 'with filters' do - let(:query) do - %( - query { - runners(type: #{runner_type}, status: #{status}) { - #{fields} - } - } - ) - end - - before do - allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance| - allow(instance).to receive(:check_runner_upgrade_suggestion) - end - - post_graphql(query, current_user: current_user) - end - shared_examples 'a working graphql query returning expected runner' do - it_behaves_like 'a working graphql query' + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end it 'returns expected runner' do + post_graphql(query, current_user: current_user) + expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner)) end @@ -86,22 +74,63 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do end end - context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do - let(:runner_type) { 'INSTANCE_TYPE' } - let(:status) { 'ACTIVE' } + context 'when filtered on type and status' do + let(:query) do + %( + query { + runners(type: #{runner_type}, status: #{status}) { + #{fields} + } + } + ) + end - let!(:expected_runner) { instance_runner } + before do + allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance| + allow(instance).to receive(:check_runner_upgrade_suggestion) + end + end - it_behaves_like 'a working graphql query returning expected runner' + context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do + let(:runner_type) { 'INSTANCE_TYPE' } + let(:status) { 'ACTIVE' } + + let!(:expected_runner) { instance_runner } + + it_behaves_like 'a working graphql query returning expected runner' + end + + context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do + let(:runner_type) { 'PROJECT_TYPE' } + let(:status) { 'NEVER_CONTACTED' } + + let!(:expected_runner) { project_runner } + + it_behaves_like 'a working graphql query returning expected runner' + end end - context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do - let(:runner_type) { 'PROJECT_TYPE' } - let(:status) { 'NEVER_CONTACTED' } + context 'when filtered on version prefix' do + let_it_be(:version_runner) { create(:ci_runner, :project, active: false, description: 'Runner with machine') } + let_it_be(:version_runner_machine) { create(:ci_runner_machine, runner: version_runner, version: '15.11.0') } + + let(:query) do + %( + query { + runners(versionPrefix: "#{version_prefix}") { + #{fields} + } + } + ) + end + + context 'version_prefix is "15."' do + let(:version_prefix) { '15.' } - let!(:expected_runner) { project_runner } + let!(:expected_runner) { version_runner } - it_behaves_like 'a working graphql query returning expected runner' + it_behaves_like 'a working graphql query returning expected runner' + end end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index ce7e5188c7b..899ed477180 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -320,7 +320,7 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :groups_an it 'cannot create a project' do expect(project.errors.errors.length).to eq 1 - expect(project.errors.messages[:limit_reached].first).to eq(_('Personal project creation is not allowed. Please contact your administrator with questions')) + expect(project.errors.messages[:limit_reached].first).to eq(_('You cannot create projects in your personal namespace. Contact your GitLab administrator.')) end end |