Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-17 03:08:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-17 03:08:20 +0300
commit3f98f1e47b16b2b1d7a2e8a86252e002c2496098 (patch)
tree197eb008d51c312f3fc06c1e4cd2ecdda69576ea
parentd62742b0169769191b32038cf20445a47db3b287 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/static-analysis.gitlab-ci.yml2
-rw-r--r--app/finders/ci/runner_managers_finder.rb26
-rw-r--r--app/graphql/resolvers/ci/runner_managers_resolver.rb34
-rw-r--r--app/graphql/types/ci/runner_type.rb18
-rw-r--r--app/views/admin/groups/_form.html.haml4
-rw-r--r--app/views/admin/sessions/_signin_box.html.haml9
-rw-r--r--app/views/admin/sessions/two_factor.html.haml9
-rw-r--r--app/views/devise/confirmations/new.html.haml31
-rw-r--r--app/views/devise/passwords/edit.html.haml29
-rw-r--r--app/views/devise/passwords/new.html.haml27
-rw-r--r--app/views/devise/sessions/email_verification.haml3
-rw-r--r--app/views/devise/sessions/two_factor.html.haml29
-rw-r--r--app/views/devise/shared/_signin_box.html.haml12
-rw-r--r--app/views/devise/unlocks/new.html.haml17
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml2
-rw-r--r--app/views/shared/_old_visibility_level.html.haml5
-rw-r--r--app/views/shared/milestones/_delete_button.html.haml2
-rw-r--r--config/application.rb5
-rw-r--r--config/feature_flags/development/github_import_lock_user_finder.yml8
-rw-r--r--doc/api/graphql/reference/index.md24
-rw-r--r--doc/architecture/blueprints/tailwindcss/index.md2
-rw-r--r--doc/ci/environments/protected_environments.md2
-rw-r--r--doc/development/permissions/custom_roles.md1
-rw-r--r--lib/gitlab/github_import/user_finder.rb65
-rw-r--r--lib/gitlab/patch/database_config.rb47
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/finders/ci/runner_managers_finder_spec.rb32
-rw-r--r--spec/lib/gitlab/github_import/user_finder_spec.rb16
-rw-r--r--spec/lib/gitlab/patch/database_config_spec.rb91
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb120
30 files changed, 504 insertions, 177 deletions
diff --git a/.gitlab/ci/static-analysis.gitlab-ci.yml b/.gitlab/ci/static-analysis.gitlab-ci.yml
index 3344bb3a06d..997dfde4271 100644
--- a/.gitlab/ci/static-analysis.gitlab-ci.yml
+++ b/.gitlab/ci/static-analysis.gitlab-ci.yml
@@ -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:
diff --git a/app/finders/ci/runner_managers_finder.rb b/app/finders/ci/runner_managers_finder.rb
index f24be74bbeb..1f8a2b1c9c1 100644
--- a/app/finders/ci/runner_managers_finder.rb
+++ b/app/finders/ci/runner_managers_finder.rb
@@ -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
diff --git a/app/graphql/resolvers/ci/runner_managers_resolver.rb b/app/graphql/resolvers/ci/runner_managers_resolver.rb
new file mode 100644
index 00000000000..1bdd6cb64e7
--- /dev/null
+++ b/app/graphql/resolvers/ci/runner_managers_resolver.rb
@@ -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
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index c9f92c05975..dcacfb48997 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -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)
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 2da28910af3..afa3c11dcba 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -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') }
diff --git a/app/views/admin/sessions/_signin_box.html.haml b/app/views/admin/sessions/_signin_box.html.haml
index 114b32ca581..03fa0fee260 100644
--- a/app/views/admin/sessions/_signin_box.html.haml
+++ b/app/views/admin/sessions/_signin_box.html.haml
@@ -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'
diff --git a/app/views/admin/sessions/two_factor.html.haml b/app/views/admin/sessions/two_factor.html.haml
index 898d47f446a..2c4a1c2456a 100644
--- a/app/views/admin/sessions/two_factor.html.haml
+++ b/app/views/admin/sessions/two_factor.html.haml
@@ -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
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
index 0ce6d9b1095..eeec6e1fa40 100644
--- a/app/views/devise/confirmations/new.html.haml
+++ b/app/views/devise/confirmations/new.html.haml
@@ -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'
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 7dd4d119a62..ea5ab378291 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -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
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index 536d4c9fd4b..b8f47608432 100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -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'
diff --git a/app/views/devise/sessions/email_verification.haml b/app/views/devise/sessions/email_verification.haml
index 085204fb6bf..290cdc47d80 100644
--- a/app/views/devise/sessions/email_verification.haml
+++ b/app/views/devise/sessions/email_verification.haml
@@ -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 }
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 454b89e40f8..c39e6230f69 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -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
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index 60f1ff02e76..a5251f9e182 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -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'
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
index 393f42cd197..b4d0a8f5b8e 100644
--- a/app/views/devise/unlocks/new.html.haml
+++ b/app/views/devise/unlocks/new.html.haml
@@ -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'
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index b64824bf509..7941f35fdbb 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -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) } }
diff --git a/app/views/shared/_old_visibility_level.html.haml b/app/views/shared/_old_visibility_level.html.haml
deleted file mode 100644
index 6bcac2b0e6b..00000000000
--- a/app/views/shared/_old_visibility_level.html.haml
+++ /dev/null
@@ -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
diff --git a/app/views/shared/milestones/_delete_button.html.haml b/app/views/shared/milestones/_delete_button.html.haml
index caab7710fa8..0aa8ad2628e 100644
--- a/app/views/shared/milestones/_delete_button.html.haml
+++ b/app/views/shared/milestones/_delete_button.html.haml
@@ -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
diff --git a/config/application.rb b/config/application.rb
index 12c7444ef39..0634bbf5165 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -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.
diff --git a/config/feature_flags/development/github_import_lock_user_finder.yml b/config/feature_flags/development/github_import_lock_user_finder.yml
new file mode 100644
index 00000000000..812e899ae21
--- /dev/null
+++ b/config/feature_flags/development/github_import_lock_user_finder.yml
@@ -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
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 1fa12bd1ee0..70d4d3cf7da 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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`
diff --git a/doc/architecture/blueprints/tailwindcss/index.md b/doc/architecture/blueprints/tailwindcss/index.md
index 0409f802038..6d58e0dae8e 100644
--- a/doc/architecture/blueprints/tailwindcss/index.md
+++ b/doc/architecture/blueprints/tailwindcss/index.md
@@ -1,5 +1,5 @@
---
-status: proposed
+status: ongoing
creation-date: "2023-12-21"
authors: [ "@peterhegman", "@svedova", "@pgascouvaillancourt" ]
approvers: [ "@samdbeckham" ]
diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md
index 69f7c558b8b..9b3332bddba 100644
--- a/doc/ci/environments/protected_environments.md
+++ b/doc/ci/environments/protected_environments.md
@@ -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:
diff --git a/doc/development/permissions/custom_roles.md b/doc/development/permissions/custom_roles.md
index 53589658f56..b8f6eba3340 100644
--- a/doc/development/permissions/custom_roles.md
+++ b/doc/development/permissions/custom_roles.md
@@ -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
diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb
index bec4c7fc4d4..fbde1778a89 100644
--- a/lib/gitlab/github_import/user_finder.rb
+++ b/lib/gitlab/github_import/user_finder.rb
@@ -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
-
- 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)
+ 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)
+
+ # 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 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)
-
- next email if email.present?
- end
+ log(EMAIL_API_CALL_LOGGING_MESSAGE[etag.present?], username: username)
- 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
diff --git a/lib/gitlab/patch/database_config.rb b/lib/gitlab/patch/database_config.rb
index 8a7566f6e0e..33be63a31c0 100644
--- a/lib/gitlab/patch/database_config.rb
+++ b/lib/gitlab/patch/database_config.rb
@@ -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,25 +9,60 @@ 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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8f6ef41edb8..e262305d5be 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/finders/ci/runner_managers_finder_spec.rb b/spec/finders/ci/runner_managers_finder_spec.rb
index c62c05d415e..0581330e65b 100644
--- a/spec/finders/ci/runner_managers_finder_spec.rb
+++ b/spec/finders/ci/runner_managers_finder_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb
index 998fa8b2c9f..e3415c12b6f 100644
--- a/spec/lib/gitlab/github_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/user_finder_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/patch/database_config_spec.rb b/spec/lib/gitlab/patch/database_config_spec.rb
index b06d28dbcd5..73452853050 100644
--- a/spec/lib/gitlab/patch/database_config_spec.rb
+++ b/spec/lib/gitlab/patch/database_config_spec.rb
@@ -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
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 1b6948d0380..c528100dafa 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -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) }
+
+ 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
- before do
- create(:ci_runner_machine_build, runner_manager: runner_manager, build: build)
+ 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