summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/maintenance_mode_settings/components/app.vue44
-rw-r--r--app/assets/javascripts/maintenance_mode_settings/index.js20
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue3
-rw-r--r--app/assets/stylesheets/framework/common.scss37
-rw-r--r--app/assets/stylesheets/framework/variables.scss32
-rw-r--r--app/assets/stylesheets/utilities.scss3
-rw-r--r--app/controllers/concerns/sends_blob.rb14
-rw-r--r--app/controllers/concerns/snippets_actions.rb31
-rw-r--r--app/controllers/projects/avatars_controller.rb2
-rw-r--r--app/controllers/projects/import/jira_controller.rb20
-rw-r--r--app/controllers/projects/raw_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb2
-rw-r--r--app/graphql/types/notes/note_type.rb3
-rw-r--r--app/models/ci/runner.rb10
-rw-r--r--app/serializers/discussion_entity.rb1
-rw-r--r--app/services/jira_import/start_import_service.rb65
-rw-r--r--app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb27
-rw-r--r--app/views/admin/application_settings/general.html.haml12
-rw-r--r--app/workers/pages_domain_ssl_renewal_cron_worker.rb5
20 files changed, 293 insertions, 42 deletions
diff --git a/app/assets/javascripts/maintenance_mode_settings/components/app.vue b/app/assets/javascripts/maintenance_mode_settings/components/app.vue
new file mode 100644
index 00000000000..47150c9dc95
--- /dev/null
+++ b/app/assets/javascripts/maintenance_mode_settings/components/app.vue
@@ -0,0 +1,44 @@
+<script>
+import { GlToggle, GlFormGroup, GlFormTextarea, GlButton } from '@gitlab/ui';
+
+export default {
+ name: 'MaintenanceModeSettingsApp',
+ components: {
+ GlToggle,
+ GlFormGroup,
+ GlFormTextarea,
+ GlButton,
+ },
+ data() {
+ return {
+ inMaintenanceMode: false,
+ bannerMessage: '',
+ };
+ },
+};
+</script>
+<template>
+ <article>
+ <div class="d-flex align-items-center mb-3">
+ <gl-toggle v-model="inMaintenanceMode" class="mb-0" />
+ <div class="ml-2">
+ <p class="mb-0">{{ __('Enable maintenance mode') }}</p>
+ <p class="mb-0 text-secondary-500">
+ {{
+ __('Non-admin users can sign in with read-only access and make read-only API requests.')
+ }}
+ </p>
+ </div>
+ </div>
+ <gl-form-group label="Banner Message" label-for="maintenanceBannerMessage">
+ <gl-form-textarea
+ id="maintenanceBannerMessage"
+ v-model="bannerMessage"
+ :placeholder="__(`GitLab is undergoing maintenance and is operating in a read-only mode.`)"
+ />
+ </gl-form-group>
+ <div class="mt-4">
+ <gl-button variant="success">{{ __('Save changes') }}</gl-button>
+ </div>
+ </article>
+</template>
diff --git a/app/assets/javascripts/maintenance_mode_settings/index.js b/app/assets/javascripts/maintenance_mode_settings/index.js
new file mode 100644
index 00000000000..7a80233faf0
--- /dev/null
+++ b/app/assets/javascripts/maintenance_mode_settings/index.js
@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import MaintenanceModeSettingsApp from './components/app.vue';
+
+Vue.use(Translate);
+
+export default () => {
+ const el = document.getElementById('js-maintenance-mode-settings');
+
+ return new Vue({
+ el,
+ components: {
+ MaintenanceModeSettingsApp,
+ },
+
+ render(createElement) {
+ return createElement('maintenance-mode-settings-app');
+ },
+ });
+};
diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js
index 78b7e29ae53..493c216cc6e 100644
--- a/app/assets/javascripts/pages/admin/application_settings/index.js
+++ b/app/assets/javascripts/pages/admin/application_settings/index.js
@@ -1,9 +1,11 @@
import initSettingsPanels from '~/settings_panels';
import projectSelect from '~/project_select';
import selfMonitor from '~/self_monitor';
+import maintenanceModeSettings from '~/maintenance_mode_settings';
document.addEventListener('DOMContentLoaded', () => {
selfMonitor();
+ maintenanceModeSettings();
// Initialize expandable settings panels
initSettingsPanels();
projectSelect();
diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
index dadb20e511b..12e16b79d37 100644
--- a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
+++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
@@ -37,7 +37,8 @@ export default {
text() {
return sprintf(
s__(`Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}.
- Existing project labels with the same title will be merged. This action cannot be reversed.`),
+ Existing project labels with the same title will be merged. If a group label with the same title exists,
+ it will also be merged. This action cannot be reversed.`),
{
labelTitle: this.labelTitle,
groupName: this.groupName,
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 4d8ae8a5652..0aae2f20671 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -483,6 +483,34 @@ img.emoji {
}
/** COMMON SPACING CLASSES **/
+/**
+ 🚨 Do not use these classes — they are deprecated and being removed. 🚨
+ See https://gitlab.com/gitlab-org/gitlab/issues/36857 for more details.
+
+ Instead, if you need a spacing class, add it below using the following values.
+ $gl-spacing-scale-0: 0;
+ $gl-spacing-scale-1: 2px;
+ $gl-spacing-scale-2: 4px;
+ $gl-spacing-scale-3: 8px;
+ $gl-spacing-scale-4: 12px;
+ $gl-spacing-scale-5: 16px;
+ $gl-spacing-scale-6: 24px;
+ $gl-spacing-scale-7: 32px;
+ $gl-spacing-scale-8: 40px;
+ $gl-spacing-scale-9: 48px;
+ $gl-spacing-scale-10: 56px;
+ $gl-spacing-scale-11: 64px;
+ $gl-spacing-scale-12: 80px;
+ $gl-spacing-scale-13: 96px;
+
+ E.g., a padding top of 96px can be added using:
+ .gl-shim-pt-13 {
+ padding-top: 96px;
+ }
+
+ Please use -shim- so it can be differentiated from the old scale classes.
+ These will be replaced when the Gitlab UI utilities are included.
+**/
@each $index, $padding in $spacing-scale {
#{'.gl-p-#{$index}'} { padding: $padding; }
#{'.gl-pl-#{$index}'} { padding-left: $padding; }
@@ -583,13 +611,11 @@ img.emoji {
.gl-font-size-large { font-size: $gl-font-size-large; }
.gl-line-height-24 { line-height: $gl-line-height-24; }
-.gl-line-height-14 { line-height: $gl-line-height-14; }
.gl-font-size-0 { font-size: 0; }
.gl-font-size-12 { font-size: $gl-font-size-12; }
.gl-font-size-14 { font-size: $gl-font-size-14; }
.gl-font-size-16 { font-size: $gl-font-size-16; }
-.gl-font-size-20 { font-size: $gl-font-size-20; }
.gl-font-size-28 { font-size: $gl-font-size-28; }
.gl-font-size-42 { font-size: $gl-font-size-42; }
@@ -599,3 +625,10 @@ img.emoji {
border-top: 1px solid $border-color;
}
+
+/**
+🚨 Do not use these classes — they clash with the Gitlab UI design system and will be removed. 🚨
+See https://gitlab.com/gitlab-org/gitlab/issues/36857 for more details.
+**/
+.gl-line-height-14 { line-height: $gl-line-height-14; }
+.gl-font-size-20 { font-size: $gl-font-size-20; }
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4d858f88921..86b65ea34f3 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -11,6 +11,38 @@ $default-transition-duration: 0.15s;
$contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px;
$toggle-sidebar-height: 48px;
+
+/**
+ 🚨 Do not use this spacing scale — it is deprecated and being removed. 🚨
+ See https://gitlab.com/gitlab-org/gitlab/issues/36857 for more details.
+
+ Instead, if you need a spacing class, add it to app/assets/stylesheets/framework/common.scss,
+ using the following values.
+
+ $gl-spacing-scale-0: 0;
+ $gl-spacing-scale-1: 2px;
+ $gl-spacing-scale-2: 4px;
+ $gl-spacing-scale-3: 8px;
+ $gl-spacing-scale-4: 12px;
+ $gl-spacing-scale-5: 16px;
+ $gl-spacing-scale-6: 24px;
+ $gl-spacing-scale-7: 32px;
+ $gl-spacing-scale-8: 40px;
+ $gl-spacing-scale-9: 48px;
+ $gl-spacing-scale-10: 56px;
+ $gl-spacing-scale-11: 64px;
+ $gl-spacing-scale-12: 80px;
+ $gl-spacing-scale-13: 96px;
+
+ E.g., a padding top of 96px can be added using:
+ .gl-shim-pt-13 {
+ padding-top: 96px;
+ }
+
+ Please use -shim- so it can be differentiated from the old scale classes.
+
+ These will be replaced when the Gitlab UI utilities are included.
+**/
$spacing-scale: (
0: 0,
1: #{0.5 * $grid-size},
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index f161d76c623..925c15262e9 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -67,7 +67,8 @@
.gl-bg-purple-light { background-color: $purple-light; }
// Classes using mixins coming from @gitlab-ui
-// can be removed once https://gitlab.com/gitlab-org/gitlab/merge_requests/19021 has been merged
+// can be removed once the mixins are added.
+// See https://gitlab.com/gitlab-org/gitlab/issues/36857 for more details.
.gl-bg-blue-50 { @include gl-bg-blue-50; }
.gl-bg-red-100 { @include gl-bg-red-100; }
.gl-bg-orange-100 { @include gl-bg-orange-100; }
diff --git a/app/controllers/concerns/sends_blob.rb b/app/controllers/concerns/sends_blob.rb
index 8ecdaced9f5..9bba61fda84 100644
--- a/app/controllers/concerns/sends_blob.rb
+++ b/app/controllers/concerns/sends_blob.rb
@@ -8,16 +8,16 @@ module SendsBlob
include SendFileUpload
end
- def send_blob(repository, blob, params = {})
+ def send_blob(repository, blob, inline: true, allow_caching: false)
if blob
headers['X-Content-Type-Options'] = 'nosniff'
- return if cached_blob?(blob)
+ return if cached_blob?(blob, allow_caching: allow_caching)
if blob.stored_externally?
- send_lfs_object(blob)
+ send_lfs_object(blob, repository.project)
else
- send_git_blob(repository, blob, params)
+ send_git_blob(repository, blob, inline: inline)
end
else
render_404
@@ -26,11 +26,11 @@ module SendsBlob
private
- def cached_blob?(blob)
+ def cached_blob?(blob, allow_caching: false)
stale = stale?(etag: blob.id) # The #stale? method sets cache headers.
# Because we are opinionated we set the cache headers ourselves.
- response.cache_control[:public] = project.public?
+ response.cache_control[:public] = allow_caching
response.cache_control[:max_age] =
if @ref && @commit && @ref == @commit.id # rubocop:disable Gitlab/ModuleWithInstanceVariables
@@ -48,7 +48,7 @@ module SendsBlob
!stale
end
- def send_lfs_object(blob)
+ def send_lfs_object(blob, project)
lfs_object = find_lfs_object(blob)
if lfs_object && lfs_object.project_allowed_access?(project)
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index 749afb71923..b12aee346ed 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -2,6 +2,7 @@
module SnippetsActions
extend ActiveSupport::Concern
+ include SendsBlob
def edit
# We need to load some info from the existing blob
@@ -12,16 +13,26 @@ module SnippetsActions
end
def raw
- disposition = params[:inline] == 'false' ? 'attachment' : 'inline'
-
workhorse_set_content_type!
- send_data(
- convert_line_endings(blob.data),
- type: 'text/plain; charset=utf-8',
- disposition: disposition,
- filename: Snippet.sanitized_file_name(blob.name)
- )
+ # Until we don't migrate all snippets to version
+ # snippets we need to support old `SnippetBlob`
+ # blobs
+ if defined?(blob.snippet)
+ send_data(
+ convert_line_endings(blob.data),
+ type: 'text/plain; charset=utf-8',
+ disposition: content_disposition,
+ filename: Snippet.sanitized_file_name(blob.name)
+ )
+ else
+ send_blob(
+ snippet.repository,
+ blob,
+ inline: content_disposition == 'inline',
+ allow_caching: snippet.public?
+ )
+ end
end
def js_request?
@@ -30,6 +41,10 @@ module SnippetsActions
private
+ def content_disposition
+ @disposition ||= params[:inline] == 'false' ? 'attachment' : 'inline'
+ end
+
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def blob
return unless snippet
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 1f4a25f82e9..6e6bf09a32a 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -8,7 +8,7 @@ class Projects::AvatarsController < Projects::ApplicationController
def show
@blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git)
- send_blob(@repository, @blob)
+ send_blob(@repository, @blob, allow_caching: @project.public?)
end
def destroy
diff --git a/app/controllers/projects/import/jira_controller.rb b/app/controllers/projects/import/jira_controller.rb
index d38d9e27347..e88d28e9db4 100644
--- a/app/controllers/projects/import/jira_controller.rb
+++ b/app/controllers/projects/import/jira_controller.rb
@@ -16,9 +16,8 @@ module Projects
end
def import
- import_state = @project.import_state || @project.create_import_state
-
- schedule_import(jira_import_params) unless import_state.in_progress?
+ response = ::JiraImport::StartImportService.new(current_user, @project, jira_import_params[:jira_project_key]).execute
+ flash[:notice] = response.message if response.message.present?
redirect_to project_import_jira_path(@project)
end
@@ -39,21 +38,6 @@ module Projects
redirect_to project_issues_path(@project)
end
- def schedule_import(params)
- import_data = @project.create_or_update_import_data(data: {}).becomes(JiraImportData)
-
- jira_project_details = JiraImportData::JiraProjectDetails.new(
- params[:jira_project_key],
- Time.now.strftime('%Y-%m-%d %H:%M:%S'),
- { user_id: current_user.id, name: current_user.name }
- )
- import_data << jira_project_details
- import_data.force_import!
-
- @project.import_type = 'jira'
- @project.import_state.schedule if @project.save
- end
-
def jira_import_params
params.permit(:jira_project_key)
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index f7bc6898112..69a3898af55 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -17,7 +17,7 @@ class Projects::RawController < Projects::ApplicationController
def show
@blob = @repository.blob_at(@commit.id, @path)
- send_blob(@repository, @blob, inline: (params[:inline] != 'false'))
+ send_blob(@repository, @blob, inline: (params[:inline] != 'false'), allow_caching: @project.public?)
end
private
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index cfc0925d9e1..90ff798077a 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -45,7 +45,7 @@ class Projects::WikisController < Projects::ApplicationController
render 'show'
elsif file_blob
- send_blob(@project_wiki.repository, file_blob)
+ send_blob(@project_wiki.repository, file_blob, allow_caching: @project.public?)
elsif show_create_form?
# Assign a title to the WikiPage unless `id` is a randomly generated slug from #new
title = params[:id] unless params[:random_title].present?
diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb
index b60fc96bd03..d48cc868434 100644
--- a/app/graphql/types/notes/note_type.rb
+++ b/app/graphql/types/notes/note_type.rb
@@ -51,6 +51,9 @@ module Types
description: "Timestamp of the note's resolution"
field :position, Types::Notes::DiffPositionType, null: true,
description: 'The position of this note on a diff'
+ field :confidential, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if this note is confidential',
+ method: :confidential?
end
end
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 3f409b8bb22..690aa978716 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -70,6 +70,16 @@ module Ci
joins(:runner_projects).where(ci_runner_projects: { project_id: project_id })
}
+ scope :belonging_to_group, -> (group_id, include_ancestors: false) {
+ groups = ::Group.where(id: group_id)
+
+ if include_ancestors
+ groups = Gitlab::ObjectHierarchy.new(groups).base_and_ancestors
+ end
+
+ joins(:runner_namespaces).where(ci_runner_namespaces: { namespace_id: groups })
+ }
+
scope :belonging_to_parent_group_of_project, -> (project_id) {
project_groups = ::Group.joins(:projects).where(projects: { id: project_id })
hierarchy_groups = Gitlab::ObjectHierarchy.new(project_groups).base_and_ancestors
diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb
index b2d9d52bd22..e302672042e 100644
--- a/app/serializers/discussion_entity.rb
+++ b/app/serializers/discussion_entity.rb
@@ -48,6 +48,7 @@ class DiscussionEntity < Grape::Entity
expose :for_commit?, as: :for_commit
expose :commit_id
+ expose :confidential?, as: :confidential
private
diff --git a/app/services/jira_import/start_import_service.rb b/app/services/jira_import/start_import_service.rb
new file mode 100644
index 00000000000..91a7956e585
--- /dev/null
+++ b/app/services/jira_import/start_import_service.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module JiraImport
+ class StartImportService
+ attr_reader :user, :project, :jira_project_key
+
+ def initialize(user, project, jira_project_key)
+ @user = user
+ @project = project
+ @jira_project_key = jira_project_key
+ end
+
+ def execute
+ validation_response = validate
+ return validation_response if validation_response&.error?
+
+ create_and_schedule_import
+ end
+
+ private
+
+ def create_and_schedule_import
+ import_data = project.create_or_update_import_data(data: {}).becomes(JiraImportData)
+ jira_project_details = JiraImportData::JiraProjectDetails.new(
+ jira_project_key,
+ Time.now.strftime('%Y-%m-%d %H:%M:%S'),
+ { user_id: user.id, name: user.name }
+ )
+ import_data << jira_project_details
+ import_data.force_import!
+
+ project.import_type = 'jira'
+ project.import_state.schedule if project.save!
+
+ ServiceResponse.success(payload: { import_data: import_data } )
+ rescue => ex
+ # in case project.save! raises an erorr
+ Gitlab::ErrorTracking.track_exception(ex, project_id: project.id)
+ build_error_response(ex.message)
+ end
+
+ def validate
+ return build_error_response(_('Jira import feature is disabled.')) unless Feature.enabled?(:jira_issue_import, project)
+ return build_error_response(_('You do not have permissions to run the import.')) unless user.can?(:admin_project, project)
+ return build_error_response(_('Jira integration not configured.')) unless project.jira_service&.active?
+ return build_error_response(_('Unable to find Jira project to import data from.')) if jira_project_key.blank?
+ return build_error_response(_('Jira import is already running.')) if import_in_progress?
+ end
+
+ def build_error_response(message)
+ import_data = JiraImportData.new(project: project)
+ import_data.errors.add(:base, message)
+ ServiceResponse.error(
+ message: import_data.errors.full_messages.to_sentence,
+ http_status: 400,
+ payload: { import_data: import_data }
+ )
+ end
+
+ def import_in_progress?
+ import_state = project.import_state || project.create_import_state
+ import_state.in_progress?
+ end
+ end
+end
diff --git a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
index 58f795e639e..93445dd4ddd 100644
--- a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
+++ b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
@@ -36,8 +36,8 @@ module PagesDomains
when 'valid'
save_certificate(acme_order.private_key, api_order)
acme_order.destroy!
- # when 'invalid'
- # TODO: implement error handling
+ when 'invalid'
+ save_order_error(acme_order, api_order)
end
end
@@ -47,5 +47,28 @@ module PagesDomains
certificate = api_order.certificate
pages_domain.update!(gitlab_provided_key: private_key, gitlab_provided_certificate: certificate)
end
+
+ def save_order_error(acme_order, api_order)
+ log_error(api_order)
+
+ return unless Feature.enabled?(:pages_letsencrypt_errors, pages_domain.project)
+
+ pages_domain.assign_attributes(auto_ssl_failed: true)
+ pages_domain.save!(validate: false)
+
+ acme_order.destroy!
+ end
+
+ def log_error(api_order)
+ Gitlab::AppLogger.error(
+ message: "Failed to obtain Let's Encrypt certificate",
+ acme_error: api_order.challenge_error,
+ project_id: pages_domain.project_id,
+ pages_domain: pages_domain.domain
+ )
+ rescue => e
+ # getting authorizations is an additional network request which can raise errors
+ Gitlab::ErrorTracking.track_exception(e)
+ end
end
end
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index 1a029996aaf..bebda385886 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -104,6 +104,18 @@
= f.submit _('Save changes'), class: "btn btn-success"
+- if Feature.enabled?(:maintenance_mode)
+ %section.settings.no-animate#js-maintenance-mode-toggle{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Maintenance mode')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Prevent users from performing write operations on GitLab while performing maintenance.')
+ .settings-content
+ #js-maintenance-mode-settings
+
- if Feature.enabled?(:instance_level_integrations)
= render_if_exists 'admin/application_settings/elasticsearch_form'
= render 'admin/application_settings/plantuml'
diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
index fe6d516d3cf..43fb35c5298 100644
--- a/app/workers/pages_domain_ssl_renewal_cron_worker.rb
+++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
@@ -10,6 +10,11 @@ class PagesDomainSslRenewalCronWorker # rubocop:disable Scalability/IdempotentWo
return unless ::Gitlab::LetsEncrypt.enabled?
PagesDomain.need_auto_ssl_renewal.with_logging_info.find_each do |domain|
+ # Ideally that should be handled in PagesDomain.need_auto_ssl_renewal scope
+ # but it's hard to make scope work with feature flags
+ # once we remove feature flag we can modify scope to implement this behaviour
+ next if Feature.enabled?(:pages_letsencrypt_errors, domain.project) && domain.auto_ssl_failed
+
with_context(project: domain.project) do
PagesDomainSslRenewalWorker.perform_async(domain.id)
end