Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d62742b016
commit
3f98f1e47b
|
|
@ -199,7 +199,7 @@ semgrep-appsec-custom-rules:
|
|||
- |
|
||||
semgrep ci --gitlab-sast --metrics off --config "${CI_BUILDS_DIR}/sast-custom-rules" \
|
||||
--include app --include lib --include workhorse \
|
||||
--exclude '*_test.go' --exclude spec --exclude qa > gl-sast-report.json || true
|
||||
--exclude '*_test.go' --exclude spec --exclude qa --exclude tooling > gl-sast-report.json || true
|
||||
variables:
|
||||
CUSTOM_RULES_REPOSITORY: https://gitlab.com/gitlab-com/gl-security/appsec/sast-custom-rules.git
|
||||
artifacts:
|
||||
|
|
|
|||
|
|
@ -8,24 +8,34 @@ module Ci
|
|||
end
|
||||
|
||||
def execute
|
||||
items = runner_managers
|
||||
items = ::Ci::RunnerManager.for_runner(runner)
|
||||
|
||||
filter_by_status(items)
|
||||
items = by_status(items)
|
||||
items = by_system_id(items)
|
||||
|
||||
sort_items(items)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :runner, :params
|
||||
|
||||
def runner_managers
|
||||
::Ci::RunnerManager.for_runner(runner)
|
||||
end
|
||||
|
||||
def filter_by_status(items)
|
||||
def by_status(items)
|
||||
status = params[:status]
|
||||
return items if status.blank?
|
||||
return items if status.nil?
|
||||
|
||||
items.with_status(status)
|
||||
end
|
||||
|
||||
def by_system_id(items)
|
||||
system_id = params[:system_id]
|
||||
return items if system_id.nil?
|
||||
|
||||
items.with_system_xid(system_id)
|
||||
end
|
||||
|
||||
def sort_items(items)
|
||||
items.order_id_desc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
module Ci
|
||||
class RunnerManagersResolver < BaseResolver
|
||||
type Types::Ci::RunnerManagerType.connection_type, null: true
|
||||
|
||||
argument :system_id, ::GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Filter runner managers by system ID.'
|
||||
|
||||
argument :status, ::Types::Ci::RunnerStatusEnum,
|
||||
required: false,
|
||||
description: 'Filter runner managers by status.'
|
||||
|
||||
def resolve(**args)
|
||||
BatchLoader::GraphQL.for(object.id).batch(key: args[:system_id]) do |runner_ids, loader|
|
||||
runner_managers =
|
||||
::Ci::RunnerManagersFinder
|
||||
.new(runner: runner_ids, params: args.slice(:system_id, :status))
|
||||
.execute
|
||||
::Preloaders::RunnerManagerPolicyPreloader.new(runner_managers, current_user).execute
|
||||
|
||||
runner_managers_by_runner_id = runner_managers.group_by(&:runner_id)
|
||||
|
||||
runner_ids.each do |runner_id|
|
||||
runner_managers = Array.wrap(runner_managers_by_runner_id[runner_id])
|
||||
loader.call(runner_id, runner_managers)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -53,8 +53,7 @@ module Types
|
|||
field :groups, null: true,
|
||||
resolver: ::Resolvers::Ci::RunnerGroupsResolver,
|
||||
description: 'Groups the runner is associated with. For group runners only.'
|
||||
field :id, ::Types::GlobalIDType[::Ci::Runner], null: false,
|
||||
description: 'ID of the runner.'
|
||||
field :id, ::Types::GlobalIDType[::Ci::Runner], null: false, description: 'ID of the runner.'
|
||||
field :ip_address, GraphQL::Types::String, null: true,
|
||||
deprecated: { reason: "Use field in `manager` object instead", milestone: '16.2' },
|
||||
description: 'IP address of the runner.'
|
||||
|
|
@ -77,7 +76,8 @@ module Types
|
|||
field :maintenance_note, GraphQL::Types::String, null: true,
|
||||
description: 'Runner\'s maintenance notes.'
|
||||
field :managers, ::Types::Ci::RunnerManagerType.connection_type, null: true,
|
||||
description: 'Machines associated with the runner configuration.',
|
||||
description: 'Runner managers associated with the runner configuration.',
|
||||
resolver: Resolvers::Ci::RunnerManagersResolver,
|
||||
alpha: { milestone: '15.10' }
|
||||
field :maximum_timeout, GraphQL::Types::Int, null: true,
|
||||
description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
|
||||
|
|
@ -174,18 +174,6 @@ module Types
|
|||
end
|
||||
end
|
||||
|
||||
def managers
|
||||
BatchLoader::GraphQL.for(runner.id).batch(key: :runner_managers) do |runner_ids, loader|
|
||||
runner_managers_by_runner_id =
|
||||
::Ci::RunnerManager.for_runner(runner_ids).order_id_desc.group_by(&:runner_id)
|
||||
|
||||
runner_ids.each do |runner_id|
|
||||
runner_managers = Array.wrap(runner_managers_by_runner_id[runner_id])
|
||||
loader.call(runner_id, runner_managers)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def job_execution_status
|
||||
BatchLoader::GraphQL.for(runner.id).batch(key: :running_builds_exist) do |runner_ids, loader|
|
||||
statuses = ::Ci::Runner.id_in(runner_ids).with_running_builds.index_by(&:id)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
= gitlab_ui_form_for [:admin, @group] do |f|
|
||||
= form_errors(@group)
|
||||
= render ::Layouts::HorizontalSectionComponent.new(options: { class: 'gl-mb-6' }) do |c|
|
||||
= render ::Layouts::HorizontalSectionComponent.new(options: { class: 'gl-pb-5 gl-mb-6' }) do |c|
|
||||
- c.with_title { _('Naming, visibility') }
|
||||
- c.with_description do
|
||||
= _('Update your group name, description, avatar, and visibility.')
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
.form-group.gl-form-group{ role: 'group' }
|
||||
= f.label :avatar, _("Group avatar"), class: 'gl-display-block col-form-label'
|
||||
= render 'shared/choose_avatar_button', f: f
|
||||
= render 'shared/old_visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
|
||||
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
|
||||
|
||||
= render ::Layouts::HorizontalSectionComponent.new(options: { class: 'gl-pb-3 gl-mb-6' }) do |c|
|
||||
- c.with_title { _('Permissions and group features') }
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
- if any_form_based_providers_enabled?
|
||||
- if crowd_enabled?
|
||||
.login-box.tab-pane{ id: "crowd", role: 'tabpanel', class: active_when(form_based_auth_provider_has_active_class?(:crowd)) }
|
||||
.login-body
|
||||
= render 'devise/sessions/new_crowd', render_remember_me: false, submit_message: _('Enter admin mode')
|
||||
= render 'devise/sessions/new_crowd', render_remember_me: false, submit_message: _('Enter admin mode')
|
||||
|
||||
- ldap_servers.each_with_index do |server, i|
|
||||
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain)) }
|
||||
.login-body
|
||||
= render 'devise/sessions/new_ldap', server: server, render_remember_me: false, submit_message: _('Enter admin mode')
|
||||
= render 'devise/sessions/new_ldap', server: server, render_remember_me: false, submit_message: _('Enter admin mode')
|
||||
|
||||
= render_if_exists 'devise/sessions/new_smartcard'
|
||||
|
||||
- if allow_admin_mode_password_authentication_for_web?
|
||||
.login-box.tab-pane{ id: 'login-pane', role: 'tabpanel', class: active_when(!any_form_based_providers_enabled?) }
|
||||
.login-body
|
||||
= render 'admin/sessions/new_base'
|
||||
= render 'admin/sessions/new_base'
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@
|
|||
.login-page
|
||||
.borderless
|
||||
.login-box.gl-p-5
|
||||
.login-body
|
||||
- if current_user.two_factor_enabled?
|
||||
= render 'admin/sessions/two_factor_otp'
|
||||
- if current_user.two_factor_webauthn_enabled?
|
||||
= render 'authentication/authenticate', render_remember_me: false, target_path: admin_session_path
|
||||
- if current_user.two_factor_enabled?
|
||||
= render 'admin/sessions/two_factor_otp'
|
||||
- if current_user.two_factor_webauthn_enabled?
|
||||
= render 'authentication/authenticate', render_remember_me: false, target_path: admin_session_path
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
|
||||
.login-box.gl-p-5
|
||||
.login-body
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
|
||||
.devise-errors
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group
|
||||
= f.label :email
|
||||
= f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', title: _('Please provide a valid email address.'), value: nil
|
||||
.form-text.gl-text-secondary
|
||||
- emails_link = link_to('', profile_emails_url, target: '_blank', rel: 'noopener noreferrer')
|
||||
= safe_format(s_('Requires your primary GitLab email address. If you want to confirm a secondary email address, go to %{emails_link_start}Emails%{emails_link_end}'), tag_pair(emails_link, :emails_link_start, :emails_link_end))
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
|
||||
.devise-errors
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group
|
||||
= f.label :email
|
||||
= f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', title: _('Please provide a valid email address.'), value: nil
|
||||
.form-text.gl-text-secondary
|
||||
- emails_link = link_to('', profile_emails_url, target: '_blank', rel: 'noopener noreferrer')
|
||||
= safe_format(s_('Requires your primary GitLab email address. If you want to confirm a secondary email address, go to %{emails_link_start}Emails%{emails_link_end}'), tag_pair(emails_link, :emails_link_start, :emails_link_end))
|
||||
|
||||
%div
|
||||
- if recaptcha_enabled?
|
||||
= recaptcha_tags nonce: content_security_policy_nonce
|
||||
%div
|
||||
- if recaptcha_enabled?
|
||||
= recaptcha_tags nonce: content_security_policy_nonce
|
||||
|
||||
.gl-mt-5
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true) do
|
||||
= _("Resend")
|
||||
.gl-mt-5
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true) do
|
||||
= _("Resend")
|
||||
|
||||
.clearfix.prepend-top-20
|
||||
= render 'devise/shared/sign_in_link'
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
= render 'devise/shared/tab_single', tab_title: _('Change your password')
|
||||
.login-box
|
||||
.login-body
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: password_path(:user), html: { method: :put, class: 'gl-show-field-errors gl-pt-5' }) do |f|
|
||||
.devise-errors.gl-px-5
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
= f.hidden_field :reset_password_token
|
||||
.form-group.gl-px-5
|
||||
= f.label _('New password'), for: "user_password"
|
||||
= f.password_field :password, autocomplete: 'new-password', class: "form-control gl-form-input top js-password-complexity-validation", required: true, title: _('This field is required.'), data: { testid: 'password-field'}
|
||||
= render_if_exists 'shared/password_requirements_list'
|
||||
.form-group.gl-px-5
|
||||
= f.label _('Confirm new password'), for: "user_password_confirmation"
|
||||
= f.password_field :password_confirmation, autocomplete: 'new-password', class: "form-control gl-form-input bottom", title: _('This field is required.'), data: { testid: 'password-confirmation-field' }, required: true
|
||||
.clearfix.gl-px-5.gl-pb-5
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { testid: 'change-password-button' } }) do
|
||||
= _('Change your password')
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: password_path(:user), html: { method: :put, class: 'gl-show-field-errors gl-pt-5' }) do |f|
|
||||
.devise-errors.gl-px-5
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
= f.hidden_field :reset_password_token
|
||||
.form-group.gl-px-5
|
||||
= f.label _('New password'), for: "user_password"
|
||||
= f.password_field :password, autocomplete: 'new-password', class: "form-control gl-form-input top js-password-complexity-validation", required: true, title: _('This field is required.'), data: { testid: 'password-field'}
|
||||
= render_if_exists 'shared/password_requirements_list'
|
||||
.form-group.gl-px-5
|
||||
= f.label _('Confirm new password'), for: "user_password_confirmation"
|
||||
= f.password_field :password_confirmation, autocomplete: 'new-password', class: "form-control gl-form-input bottom", title: _('This field is required.'), data: { testid: 'password-confirmation-field' }, required: true
|
||||
.clearfix.gl-px-5.gl-pb-5
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { testid: 'change-password-button' } }) do
|
||||
= _('Change your password')
|
||||
|
||||
.clearfix.prepend-top-20
|
||||
%p
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
.login-box
|
||||
.login-body
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-p-5 gl-show-field-errors', aria: { live: 'assertive' }}) do |f|
|
||||
.devise-errors
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group
|
||||
= f.label :email, _('Email')
|
||||
= f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
|
||||
.form-text.text-muted
|
||||
= _('Requires your primary or verified secondary GitLab email address.')
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-p-5 gl-show-field-errors', aria: { live: 'assertive' }}) do |f|
|
||||
.devise-errors
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group
|
||||
= f.label :email, _('Email')
|
||||
= f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
|
||||
.form-text.text-muted
|
||||
= _('Requires your primary or verified secondary GitLab email address.')
|
||||
|
||||
- if recaptcha_enabled?
|
||||
.gl-mb-5
|
||||
= recaptcha_tags nonce: content_security_policy_nonce
|
||||
- if recaptcha_enabled?
|
||||
.gl-mb-5
|
||||
= recaptcha_tags nonce: content_security_policy_nonce
|
||||
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true) do
|
||||
= _('Reset password')
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true) do
|
||||
= _('Reset password')
|
||||
|
||||
= render 'devise/shared/sign_in_link'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
%div
|
||||
= render 'devise/shared/tab_single', tab_title: s_('IdentityVerification|Help us protect your account')
|
||||
.login-box.gl-p-5
|
||||
.login-body
|
||||
.js-email-verification{ data: verification_data(resource) }
|
||||
.js-email-verification{ data: verification_data(resource) }
|
||||
%p.gl-p-5.gl-text-secondary
|
||||
- support_link_start = '<a href="https://about.gitlab.com/support/" target="_blank" rel="noopener noreferrer">'.html_safe
|
||||
= s_("IdentityVerification|If you've lost access to the email associated to this account or having trouble with the code, %{link_start}here are some other steps you can take.%{link_end}").html_safe % { link_start: support_link_start, link_end: '</a>'.html_safe }
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
.login-box.gl-p-5
|
||||
.login-body
|
||||
- if @user.two_factor_enabled?
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_enabled?}", aria: { live: 'assertive' }}) do |f|
|
||||
.form-group
|
||||
= f.label :otp_attempt, _('Enter verification code')
|
||||
= f.text_field :otp_attempt, class: 'form-control gl-form-input', required: true, autofocus: true, autocomplete: 'off', inputmode: 'numeric', title: _('This field is required.'), data: { testid: 'two-fa-code-field' }
|
||||
%p.form-text.text-muted.hint
|
||||
= _("Enter the code from your two-factor authenticator app. If you've lost your device, you can enter one of your recovery codes.")
|
||||
- if @user.two_factor_enabled?
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_enabled?}", aria: { live: 'assertive' }}) do |f|
|
||||
.form-group
|
||||
= f.label :otp_attempt, _('Enter verification code')
|
||||
= f.text_field :otp_attempt, class: 'form-control gl-form-input', required: true, autofocus: true, autocomplete: 'off', inputmode: 'numeric', title: _('This field is required.'), data: { testid: 'two-fa-code-field' }
|
||||
%p.form-text.text-muted.hint
|
||||
= _("Enter the code from your two-factor authenticator app. If you've lost your device, you can enter one of your recovery codes.")
|
||||
|
||||
- if remember_me_enabled?
|
||||
- resource_params = params[resource_name].presence || params
|
||||
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
|
||||
- if remember_me_enabled?
|
||||
- resource_params = params[resource_name].presence || params
|
||||
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
|
||||
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { testid: 'verify-code-button' } }) do
|
||||
= _("Verify code")
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { testid: 'verify-code-button' } }) do
|
||||
= _("Verify code")
|
||||
|
||||
- if @user.two_factor_webauthn_enabled?
|
||||
= render "authentication/authenticate", params: params, resource: resource, resource_name: resource_name, render_remember_me: true, target_path: new_user_session_path
|
||||
- if @user.two_factor_webauthn_enabled?
|
||||
= render "authentication/authenticate", params: params, resource: resource, resource_name: resource_name, render_remember_me: true, target_path: new_user_session_path
|
||||
|
|
|
|||
|
|
@ -1,22 +1,18 @@
|
|||
- if any_form_based_providers_enabled?
|
||||
- if crowd_enabled?
|
||||
.login-box.tab-pane{ id: "crowd", role: 'tabpanel', class: active_when(form_based_auth_provider_has_active_class?(:crowd)) }
|
||||
.login-body
|
||||
= render 'devise/sessions/new_crowd'
|
||||
= render 'devise/sessions/new_crowd'
|
||||
|
||||
- ldap_servers.each_with_index do |server, i|
|
||||
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain)) }
|
||||
.login-body
|
||||
= render 'devise/sessions/new_ldap', server: server
|
||||
= render 'devise/sessions/new_ldap', server: server
|
||||
|
||||
= render_if_exists 'devise/sessions/new_smartcard'
|
||||
|
||||
- if password_authentication_enabled_for_web?
|
||||
.login-box.tab-pane{ id: 'login-pane', role: 'tabpanel' }
|
||||
.login-body
|
||||
= render 'devise/sessions/new_base'
|
||||
= render 'devise/sessions/new_base'
|
||||
|
||||
- elsif password_authentication_enabled_for_web?
|
||||
.login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
|
||||
.login-body
|
||||
= render 'devise/sessions/new_base'
|
||||
= render 'devise/sessions/new_base'
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
= render 'devise/shared/tab_single', tab_title: _('Resend unlock instructions')
|
||||
.login-box
|
||||
.login-body
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-p-5 gl-show-field-errors' }) do |f|
|
||||
.devise-errors
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group
|
||||
= f.label :email, _('Email')
|
||||
= f.email_field :email, class: 'form-control gl-form-input', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: _('Please provide a valid email address.')
|
||||
= gitlab_ui_form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-p-5 gl-show-field-errors' }) do |f|
|
||||
.devise-errors
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group
|
||||
= f.label :email, _('Email')
|
||||
= f.email_field :email, class: 'form-control gl-form-input', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: _('Please provide a valid email address.')
|
||||
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true) do
|
||||
= _('Resend unlock instructions')
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true) do
|
||||
= _('Resend unlock instructions')
|
||||
|
||||
= render 'devise/shared/sign_in_link'
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
= render Pajamas::ButtonComponent.new(icon: 'ellipsis_v', category: :tertiary, button_options: { class: 'note-action-button more-actions-toggle has-tooltip', data: { title: 'More actions', toggle: 'dropdown', container: 'body', testid: 'more-actions-dropdown' }})
|
||||
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
|
||||
%li
|
||||
= deprecated_clipboard_button(text: noteable_note_url(note), title: _('Copy reference'), button_text: _('Copy link'), class: 'btn-clipboard', hide_tooltip: true, hide_button_icon: true)
|
||||
= clipboard_button(text: noteable_note_url(note), title: _('Copy reference'), button_text: _('Copy link'), class: 'btn-clipboard gl-rounded-0!', size: :medium, hide_tooltip: true, hide_button_icon: true)
|
||||
- unless is_current_user
|
||||
.gl-ml-n2
|
||||
.js-report-abuse-dropdown-item{ data: { report_abuse_path: add_category_abuse_reports_path, reported_user_id: note.author.id, reported_from_url: noteable_note_url(note) } }
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
%fieldset.form-group.gl-form-group
|
||||
%legend.col-form-label.col-form-label
|
||||
= _('Visibility level')
|
||||
= link_to sprite_icon('question-o'), help_page_path('user/public_access'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_visibility_level, form_model: form_model, with_label: with_label
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
- milestone_url = @milestone.project_milestone? ? project_milestone_path(@project, @milestone) : group_milestone_path(@group, @milestone)
|
||||
|
||||
%button.gl-button.btn.btn-link.menu-item.js-delete-milestone-button{ data: { milestone_id: @milestone.id, milestone_title: markdown_field(@milestone, :title), milestone_url: milestone_url, milestone_issue_count: @milestone.issues.count, milestone_merge_request_count: @milestone.merge_requests.count }, disabled: true }
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary, button_options: { class: 'menu-item js-delete-milestone-button', data: { milestone_id: @milestone.id, milestone_title: markdown_field(@milestone, :title), milestone_url: milestone_url, milestone_issue_count: @milestone.issues.count, milestone_merge_request_count: @milestone.merge_requests.count }, disabled: true }) do
|
||||
.gl-dropdown-item-text-wrapper.gl-text-red-500
|
||||
= _('Delete')
|
||||
#js-delete-milestone-modal
|
||||
|
|
|
|||
|
|
@ -92,11 +92,8 @@ module Gitlab
|
|||
|
||||
# This preload is required to:
|
||||
#
|
||||
# 1. Convert legacy `database.yml`;
|
||||
# 1. Support providing sensitive DB configuration through an external script;
|
||||
# 2. Include Geo post-deployment migrations settings;
|
||||
#
|
||||
# TODO: In 15.0, this preload can be wrapped in a Gitlab.ee block
|
||||
# since we don't need to convert legacy `database.yml` anymore.
|
||||
config.class.prepend(::Gitlab::Patch::DatabaseConfig)
|
||||
|
||||
# Settings in config/environments/* take precedence over those specified here.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: github_import_lock_user_finder
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141826
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/438336
|
||||
milestone: '16.9'
|
||||
type: development
|
||||
group: group::import and integrate
|
||||
default_enabled: false
|
||||
|
|
@ -16048,7 +16048,6 @@ CI/CD variables for a project.
|
|||
| <a id="cirunnerlocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. |
|
||||
| <a id="cirunnermaintenancenote"></a>`maintenanceNote` | [`String`](#string) | Runner's maintenance notes. |
|
||||
| <a id="cirunnermaintenancenotehtml"></a>`maintenanceNoteHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `maintenance_note`. |
|
||||
| <a id="cirunnermanagers"></a>`managers` **{warning-solid}** | [`CiRunnerManagerConnection`](#cirunnermanagerconnection) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Machines associated with the runner configuration. |
|
||||
| <a id="cirunnermaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. |
|
||||
| <a id="cirunnerownerproject"></a>`ownerProject` | [`Project`](#project) | Project that owns the runner. For project runners only. |
|
||||
| <a id="cirunnerpaused"></a>`paused` | [`Boolean!`](#boolean) | Indicates the runner is paused and not available to run jobs. |
|
||||
|
|
@ -16098,6 +16097,27 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="cirunnerjobsstatuses"></a>`statuses` | [`[CiJobStatus!]`](#cijobstatus) | Filter jobs by status. |
|
||||
|
||||
##### `CiRunner.managers`
|
||||
|
||||
Runner managers associated with the runner configuration.
|
||||
|
||||
WARNING:
|
||||
**Introduced** in 15.10.
|
||||
This feature is an Experiment. It can be changed or removed at any time.
|
||||
|
||||
Returns [`CiRunnerManagerConnection`](#cirunnermanagerconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, and `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="cirunnermanagersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runner managers by status. |
|
||||
| <a id="cirunnermanagerssystemid"></a>`systemId` | [`String`](#string) | Filter runner managers by system ID. |
|
||||
|
||||
##### `CiRunner.projects`
|
||||
|
||||
Find projects the runner is associated with. For project runners only.
|
||||
|
|
@ -17319,7 +17339,7 @@ Represents a product analytics dashboard visualization.
|
|||
| <a id="customizablepermissionavailablefor"></a>`availableFor` | [`[String!]!`](#string) | Objects the permission is available for. |
|
||||
| <a id="customizablepermissiondescription"></a>`description` | [`String`](#string) | Description of the permission. |
|
||||
| <a id="customizablepermissionname"></a>`name` | [`String!`](#string) | Localized name of the permission. |
|
||||
| <a id="customizablepermissionrequirement"></a>`requirement` | [`MemberRolePermission`](#memberrolepermission) | Requirement of the permission. |
|
||||
| <a id="customizablepermissionrequirements"></a>`requirements` | [`[MemberRolePermission!]`](#memberrolepermission) | Requirements of the permission. |
|
||||
| <a id="customizablepermissionvalue"></a>`value` | [`MemberRolePermission!`](#memberrolepermission) | Value of the permission. |
|
||||
|
||||
### `DastPreScanVerification`
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
status: proposed
|
||||
status: ongoing
|
||||
creation-date: "2023-12-21"
|
||||
authors: [ "@peterhegman", "@svedova", "@pgascouvaillancourt" ]
|
||||
approvers: [ "@samdbeckham" ]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Maintainer role.
|
|||
Prerequisites:
|
||||
|
||||
- When granting the **Allowed to deploy** permission to a group or subgroup, the user configuring the protected environment must be a **direct member** of the group or subgroup to be added. Otherwise, the group or subgroup does not show up in the dropdown list. For more information see [issue #345140](https://gitlab.com/gitlab-org/gitlab/-/issues/345140).
|
||||
- When granting **Allowed to deploy** and **Approvers** permissions to a group or project by using the settings UI, only direct members of the group or project receive these permissions. To grant these permissions to inherited members also, [use the API](../../api/protected_environments.md#group-inheritance-types). For more information see [issue #422392](https://gitlab.com/gitlab-org/gitlab/-/issues/422392).
|
||||
- When granting **Allowed to deploy** permissions to a group or project by using the settings UI, only direct members of the group or project receive these permissions. To grant these permissions to inherited members also, [use the API](../../api/protected_environments.md#group-inheritance-types). For more information see [issue #422392](https://gitlab.com/gitlab-org/gitlab/-/issues/422392).
|
||||
|
||||
To protect an environment:
|
||||
|
||||
|
|
|
|||
|
|
@ -320,6 +320,7 @@ To add a new custom ability:
|
|||
| `milestone` | yes | Milestone in which this custom ability was added. |
|
||||
| `group_ability` | yes | Indicate whether this ability is checked on group level. |
|
||||
| `project_ability` | yes | Indicate whether this ability is checked on project level. |
|
||||
| `requirements` | no | The custom abilities that need to be enabled for this ability. |
|
||||
| `skip_seat_consumption` | yes | Indicate wheter this ability should be skiped when counting licensed users. |
|
||||
|
||||
### Privilege escalation consideration
|
||||
|
|
|
|||
|
|
@ -130,20 +130,32 @@ module Gitlab
|
|||
email = read_email_from_cache(username)
|
||||
|
||||
if email.blank? && !email_fetched_for_project?(username)
|
||||
# If an ETAG is available, make an API call with the ETAG.
|
||||
# Only make a rate-limited API call if the ETAG is not available and the email is nil.
|
||||
etag = read_etag_from_cache(username)
|
||||
email = fetch_email_from_github(username, etag: etag) || email
|
||||
feature_flag_in_lock(lease_key, ttl: 3.minutes, sleep_sec: 1.second, retries: 30) do |retried|
|
||||
# when retried, check the cache again as the other process that had the lease may have fetched the email
|
||||
if retried
|
||||
email = read_email_from_cache(username)
|
||||
|
||||
cache_email!(username, email)
|
||||
cache_etag!(username) if email.blank? && etag.nil?
|
||||
# early return if the other process fetched a non-empty email. If the email is empty, we'll attempt to
|
||||
# fetch it again in the lines below, but using the ETAG cached by the other process which won't count to
|
||||
# the rate limit.
|
||||
next email if email.present?
|
||||
end
|
||||
|
||||
# If a non-blank email is cached, we don't need the ETAG or project check caches.
|
||||
# Otherwise, indicate that the project has been checked.
|
||||
if email.present?
|
||||
clear_caches!(username)
|
||||
else
|
||||
set_project_as_checked!(username)
|
||||
# If an ETAG is available, make an API call with the ETAG.
|
||||
# Only make a rate-limited API call if the ETAG is not available and the email is nil.
|
||||
etag = read_etag_from_cache(username)
|
||||
email = fetch_email_from_github(username, etag: etag) || email
|
||||
|
||||
cache_email!(username, email)
|
||||
cache_etag!(username) if email.blank? && etag.nil?
|
||||
|
||||
# If a non-blank email is cached, we don't need the ETAG or project check caches.
|
||||
# Otherwise, indicate that the project has been checked.
|
||||
if email.present?
|
||||
clear_caches!(username)
|
||||
else
|
||||
set_project_as_checked!(username)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -240,20 +252,11 @@ module Gitlab
|
|||
end
|
||||
|
||||
def fetch_email_from_github(username, etag: nil)
|
||||
in_lock(lease_key, ttl: 3.minutes, sleep_sec: 1.second, retries: 30) do |retried|
|
||||
# when retried, check the cache again as the other process that had the lease may have fetched the email
|
||||
if retried
|
||||
email = read_email_from_cache(username)
|
||||
log(EMAIL_API_CALL_LOGGING_MESSAGE[etag.present?], username: username)
|
||||
|
||||
next email if email.present?
|
||||
end
|
||||
|
||||
log(EMAIL_API_CALL_LOGGING_MESSAGE[etag.present?], username: username)
|
||||
|
||||
# Only make a rate-limited API call if the ETAG is not available })
|
||||
user = client.user(username, { headers: { 'If-None-Match' => etag }.compact })
|
||||
user[:email] || '' if user
|
||||
end
|
||||
# Only make a rate-limited API call if the ETAG is not available })
|
||||
user = client.user(username, { headers: { 'If-None-Match' => etag }.compact })
|
||||
user[:email] || '' if user
|
||||
end
|
||||
|
||||
# Caches the email associated to the username
|
||||
|
|
@ -303,6 +306,14 @@ module Gitlab
|
|||
message: message
|
||||
)
|
||||
end
|
||||
|
||||
def feature_flag_in_lock(lease_key, ttl: 3.minutes, sleep_sec: 1.second, retries: 30)
|
||||
return yield(false) if Feature.disabled?(:github_import_lock_user_finder, project.creator)
|
||||
|
||||
in_lock(lease_key, ttl: ttl, sleep_sec: sleep_sec, retries: retries) do |retried|
|
||||
yield(retried)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../popen'
|
||||
|
||||
# The purpose of this code is to set the migrations path
|
||||
# for the Geo tracking database and the embedding database.
|
||||
module Gitlab
|
||||
|
|
@ -7,26 +9,61 @@ module Gitlab
|
|||
module DatabaseConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
CommandExecutionError = Class.new(StandardError)
|
||||
|
||||
def database_configuration
|
||||
super.to_h do |env, configs|
|
||||
parsed_config = parse_extra_config(configs)
|
||||
|
||||
if Gitlab.ee?
|
||||
ee_databases = %w[embedding geo]
|
||||
|
||||
ee_databases.each do |ee_db_name|
|
||||
next unless configs.key?(ee_db_name)
|
||||
next unless parsed_config.key?(ee_db_name)
|
||||
|
||||
migrations_paths = Array(configs[ee_db_name]['migrations_paths'])
|
||||
migrations_paths = Array(parsed_config[ee_db_name]['migrations_paths'])
|
||||
migrations_paths << File.join('ee', 'db', ee_db_name, 'migrate') if migrations_paths.empty?
|
||||
migrations_paths << File.join('ee', 'db', ee_db_name, 'post_migrate') unless ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS']
|
||||
|
||||
configs[ee_db_name]['migrations_paths'] = migrations_paths.uniq
|
||||
configs[ee_db_name]['schema_migrations_path'] = File.join('ee', 'db', ee_db_name, 'schema_migrations') if configs[ee_db_name]['schema_migrations_path'].blank?
|
||||
parsed_config[ee_db_name]['migrations_paths'] = migrations_paths.uniq
|
||||
parsed_config[ee_db_name]['schema_migrations_path'] = File.join('ee', 'db', ee_db_name, 'schema_migrations') if parsed_config[ee_db_name]['schema_migrations_path'].blank?
|
||||
end
|
||||
end
|
||||
|
||||
[env, configs]
|
||||
[env, parsed_config]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_extra_config(configs)
|
||||
command = configs.delete('config_command')
|
||||
return configs unless command.present?
|
||||
|
||||
config_from_command = extra_config_from_command(command)
|
||||
return configs unless config_from_command.present?
|
||||
|
||||
configs.deep_merge(config_from_command)
|
||||
end
|
||||
|
||||
def extra_config_from_command(command)
|
||||
cmd = command.split(" ")
|
||||
output, exit_status = Gitlab::Popen.popen(cmd)
|
||||
|
||||
if exit_status != 0
|
||||
raise CommandExecutionError,
|
||||
"database.yml: Execution of `#{command}` failed with exit code #{exit_status}. Output: #{output}"
|
||||
end
|
||||
|
||||
YAML.safe_load(output).deep_stringify_keys
|
||||
rescue Psych::SyntaxError => e
|
||||
error_message = <<~MSG
|
||||
database.yml: Execution of `#{command}` generated invalid yaml.
|
||||
Error: #{e.problem} #{e.context} at line #{e.line} column #{e.column}
|
||||
MSG
|
||||
|
||||
raise CommandExecutionError, error_message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23234,6 +23234,9 @@ msgstr ""
|
|||
msgid "Group information"
|
||||
msgstr ""
|
||||
|
||||
msgid "Group inheritance"
|
||||
msgstr ""
|
||||
|
||||
msgid "Group invite"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -39659,6 +39662,12 @@ msgstr ""
|
|||
msgid "ProtectedEnvironments|Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironments|Enable group inheritance"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironments|Group inheritance"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProtectedEnvironments|Number of approvals must be between 1 and 5"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -65,13 +65,37 @@ RSpec.describe Ci::RunnerManagersFinder, '#execute', feature_category: :fleet_vi
|
|||
end
|
||||
end
|
||||
|
||||
context 'without any filters' do
|
||||
describe 'filter by system_id' do
|
||||
let_it_be(:runner_manager1) { create(:ci_runner_machine, runner: runner) }
|
||||
let_it_be(:runner_manager2) { create(:ci_runner_machine, runner: runner) }
|
||||
|
||||
context "when system_id matches runner_manager1's" do
|
||||
let(:params) { { system_id: runner_manager1.system_xid } }
|
||||
|
||||
it { is_expected.to contain_exactly(runner_manager1) }
|
||||
end
|
||||
|
||||
context "when system_id matches runner_manager2's" do
|
||||
let(:params) { { system_id: runner_manager2.system_xid } }
|
||||
|
||||
it { is_expected.to contain_exactly(runner_manager2) }
|
||||
end
|
||||
|
||||
context "when system_id doesn't match" do
|
||||
let(:params) { { system_id: 'non-matching' } }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
context 'without any arguments' do
|
||||
let(:params) { {} }
|
||||
|
||||
let_it_be(:runner_manager) { create(:ci_runner_machine, runner: runner) }
|
||||
let_it_be(:runner_manager1) { create(:ci_runner_machine, runner: runner) }
|
||||
let_it_be(:runner_manager2) { create(:ci_runner_machine, runner: runner) }
|
||||
|
||||
it 'returns all runner managers' do
|
||||
expect(runner_managers).to contain_exactly(runner_manager)
|
||||
it 'returns all runner managers in id_desc order' do
|
||||
expect(runner_managers).to eq([runner_manager2, runner_manager1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -313,6 +313,19 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
|
|||
email_for_github_username
|
||||
end
|
||||
|
||||
context 'when github_import_lock_user_finder feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(github_import_lock_user_finder: false)
|
||||
end
|
||||
|
||||
it 'does not lock the finder' do
|
||||
expect(finder).not_to receive(:in_lock)
|
||||
expect(client).to receive(:user)
|
||||
|
||||
email_for_github_username
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the response contains an email' do
|
||||
before do
|
||||
allow(client).to receive(:user).and_return({ email: email })
|
||||
|
|
@ -478,8 +491,9 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
|
|||
it 'fetch user detail' do
|
||||
expect(finder).to receive(:read_email_from_cache).ordered.and_return('')
|
||||
expect(finder).to receive(:read_email_from_cache).ordered.and_return('')
|
||||
expect(finder).to receive(:read_etag_from_cache).and_return(etag)
|
||||
expect(finder).to receive(:in_lock).and_yield(true)
|
||||
expect(client).to receive(:user).with(username, { headers: {} }).and_return({ email: email }).once
|
||||
expect(client).to receive(:user).with(username, { headers: { 'If-None-Match' => etag } }).once
|
||||
|
||||
email_for_github_username
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RSpec.describe Gitlab::Patch::DatabaseConfig do
|
|||
expect(Rails::Application::Configuration).to include(described_class)
|
||||
end
|
||||
|
||||
describe 'config/database.yml' do
|
||||
describe '#database_configuration' do
|
||||
let(:configuration) { Rails::Application::Configuration.new(Rails.root) }
|
||||
|
||||
before do
|
||||
|
|
@ -68,5 +68,94 @@ RSpec.describe Gitlab::Patch::DatabaseConfig do
|
|||
end
|
||||
|
||||
include_examples 'hash containing main: connection name'
|
||||
|
||||
context 'when config/database.yml contains extra configuration through an external command' do
|
||||
let(:database_yml) do
|
||||
<<-EOS
|
||||
production:
|
||||
config_command: '/opt/database-config.sh'
|
||||
main:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: gitlabhq_production
|
||||
username: git
|
||||
password: "dummy password"
|
||||
host: localhost
|
||||
|
||||
development:
|
||||
config_command: '/opt/database-config.sh'
|
||||
main:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: gitlabhq_development
|
||||
username: postgres
|
||||
password: "dummy password"
|
||||
host: localhost
|
||||
variables:
|
||||
statement_timeout: 15s
|
||||
|
||||
test: &test
|
||||
config_command: '/opt/database-config.sh'
|
||||
main:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: gitlabhq_test
|
||||
username: postgres
|
||||
password:
|
||||
host: localhost
|
||||
prepared_statements: false
|
||||
variables:
|
||||
statement_timeout: 15s
|
||||
EOS
|
||||
end
|
||||
|
||||
context 'when the external command returns valid yaml' do
|
||||
before do
|
||||
allow(Gitlab::Popen)
|
||||
.to receive(:popen)
|
||||
.and_return(["---\nmain:\n password: 'secure password'\n", 0])
|
||||
end
|
||||
|
||||
it 'merges the extra configuration' do
|
||||
database_configuration = configuration.database_configuration
|
||||
|
||||
expect(database_configuration).to match(
|
||||
"production" => { "main" => a_hash_including("password" => "secure password") },
|
||||
"development" => { "main" => a_hash_including("password" => "secure password") },
|
||||
"test" => { "main" => a_hash_including("password" => "secure password") }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the external command returns invalid yaml' do
|
||||
before do
|
||||
allow(Gitlab::Popen)
|
||||
.to receive(:popen)
|
||||
.and_return(["---\nmain:\n password: 'secure password\n", 0])
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { configuration.database_configuration }
|
||||
.to raise_error(
|
||||
Gitlab::Patch::DatabaseConfig::CommandExecutionError,
|
||||
%r{database.yml: Execution of `/opt/database-config.sh` generated invalid yaml}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the external command fails' do
|
||||
before do
|
||||
allow(Gitlab::Popen).to receive(:popen).and_return(["", 125])
|
||||
end
|
||||
|
||||
it 'raises error' do
|
||||
expect { configuration.database_configuration }
|
||||
.to raise_error(
|
||||
Gitlab::Patch::DatabaseConfig::CommandExecutionError,
|
||||
%r{database.yml: Execution of `/opt/database-config.sh` failed}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -213,22 +213,126 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :fleet_visibi
|
|||
end
|
||||
end
|
||||
|
||||
context 'with build running' do
|
||||
let!(:pipeline) { create(:ci_pipeline, project: project1) }
|
||||
let!(:runner_manager) do
|
||||
context 'with runner managers' do
|
||||
let_it_be(:runner) { create(:ci_runner) }
|
||||
let_it_be(:runner_manager) do
|
||||
create(:ci_runner_machine,
|
||||
runner: runner, ip_address: '127.0.0.1', version: '16.3', revision: 'a', architecture: 'arm', platform: 'osx',
|
||||
contacted_at: 1.second.ago, executor_type: 'docker')
|
||||
end
|
||||
|
||||
let!(:runner) { create(:ci_runner) }
|
||||
let!(:build) { create(:ci_build, :running, runner: runner, pipeline: pipeline) }
|
||||
describe 'managers' do
|
||||
let_it_be(:runner2) { create(:ci_runner) }
|
||||
let_it_be(:runner_manager2_1) { create(:ci_runner_machine, runner: runner2) }
|
||||
let_it_be(:runner_manager2_2) { create(:ci_runner_machine, runner: runner2) }
|
||||
|
||||
before do
|
||||
create(:ci_runner_machine_build, runner_manager: runner_manager, build: build)
|
||||
context 'when filtering by status' do
|
||||
let!(:offline_runner_manager) { create(:ci_runner_machine, runner: runner2, contacted_at: 2.hours.ago) }
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
runner(id: "#{runner2.to_global_id}") {
|
||||
id
|
||||
managers(status: OFFLINE) { nodes { id } }
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'retrieves expected runner manager' do
|
||||
post_graphql(query, current_user: user)
|
||||
|
||||
expect(graphql_data).to match(a_hash_including(
|
||||
'runner' => a_graphql_entity_for(
|
||||
'managers' => {
|
||||
'nodes' => [a_graphql_entity_for(offline_runner_manager)]
|
||||
}
|
||||
)
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
context 'fetching by runner ID and runner system ID' do
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
runner1: runner(id: "#{runner.to_global_id}") {
|
||||
id
|
||||
managers(systemId: "#{runner_manager.system_xid}") { nodes { id } }
|
||||
}
|
||||
runner2: runner(id: "#{runner2.to_global_id}") {
|
||||
id
|
||||
managers(systemId: "#{runner_manager2_1.system_xid}") { nodes { id } }
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'retrieves expected runner managers' do
|
||||
post_graphql(query, current_user: user)
|
||||
|
||||
expect(graphql_data).to match(a_hash_including(
|
||||
'runner1' => a_graphql_entity_for(runner,
|
||||
'managers' => a_hash_including('nodes' => [a_graphql_entity_for(runner_manager)])),
|
||||
'runner2' => a_graphql_entity_for(runner2,
|
||||
'managers' => a_hash_including('nodes' => [a_graphql_entity_for(runner_manager2_1)]))
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
context 'fetching runner ID and all runner managers' do
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
runner(id: "#{runner2.to_global_id}") { id managers { nodes { id } } }
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'retrieves expected runner managers' do
|
||||
post_graphql(query, current_user: user)
|
||||
|
||||
expect(graphql_data).to match(a_hash_including(
|
||||
'runner' => a_graphql_entity_for(runner2,
|
||||
'managers' => a_hash_including('nodes' => [
|
||||
a_graphql_entity_for(runner_manager2_2), a_graphql_entity_for(runner_manager2_1)
|
||||
]))
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
context 'fetching mismatched runner ID and system ID' do
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
runner(id: "#{runner2.to_global_id}") {
|
||||
id
|
||||
managers(systemId: "#{runner_manager.system_xid}") { nodes { id } }
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'retrieves expected runner managers' do
|
||||
post_graphql(query, current_user: user)
|
||||
|
||||
expect(graphql_data).to match(a_hash_including(
|
||||
'runner' => a_graphql_entity_for(runner2, 'managers' => a_hash_including('nodes' => []))
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'runner details fetch'
|
||||
context 'with build running' do
|
||||
let!(:pipeline) { create(:ci_pipeline, project: project1) }
|
||||
let!(:build) { create(:ci_build, :running, runner: runner, pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
create(:ci_runner_machine_build, runner_manager: runner_manager, build: build)
|
||||
end
|
||||
|
||||
it_behaves_like 'runner details fetch'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue