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:
-rw-r--r--app/assets/javascripts/pages/admin/serverless/domains/index.js19
-rw-r--r--app/assets/stylesheets/pages/pages.scss11
-rw-r--r--app/controllers/admin/serverless/domains_controller.rb62
-rw-r--r--app/controllers/projects/environments_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/models/pages_domain.rb3
-rw-r--r--app/services/environments/auto_stop_service.rb2
-rw-r--r--app/services/metrics/dashboard/system_dashboard_service.rb1
-rw-r--r--app/services/snippets/create_service.rb4
-rw-r--r--app/views/admin/serverless/domains/_form.html.haml68
-rw-r--r--app/views/admin/serverless/domains/index.html.haml25
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml5
-rw-r--r--app/workers/environments/auto_stop_cron_worker.rb2
-rw-r--r--changelogs/unreleased/35968-add_path_to_edit_custom_metrics.yml5
-rw-r--r--changelogs/unreleased/ali-serverless-domains-be-1.yml5
-rw-r--r--config/initializers/0_license.rb2
-rw-r--r--config/initializers/load_balancing.rb2
-rw-r--r--config/routes/admin.rb8
-rw-r--r--db/post_migrate/20200207184023_add_temporary_index_to_promotion_notes.rb23
-rw-r--r--db/post_migrate/20200207185149_schedule_fix_orphan_promoted_issues.rb35
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/audit_events.md4
-rw-r--r--doc/administration/gitaly/index.md1
-rw-r--r--doc/api/commits.md2
-rw-r--r--doc/ci/environments.md67
-rw-r--r--doc/ci/img/environment_auto_stop_v12_8.pngbin0 -> 43534 bytes
-rw-r--r--doc/ci/review_apps/index.md5
-rw-r--r--doc/ci/yaml/README.md25
-rw-r--r--doc/development/understanding_explain_plans.md16
-rw-r--r--doc/user/clusters/applications.md28
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/gitlab/background_migration/fix_orphan_promoted_issues.rb13
-rw-r--r--lib/gitlab/database.rb2
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb2
-rw-r--r--lib/gitlab/marginalia.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/stages/project_metrics_details_inserter.rb40
-rw-r--r--locale/gitlab.pot24
-rw-r--r--spec/controllers/admin/serverless/domains_controller_spec.rb298
-rw-r--r--spec/features/admin/admin_serverless_domains_spec.rb59
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json3
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json3
-rw-r--r--spec/lib/gitlab/database_spec.rb6
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb13
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb15
-rw-r--r--spec/models/pages_domain_spec.rb30
-rw-r--r--spec/requests/api/commits_spec.rb26
-rw-r--r--spec/routing/admin/serverless/domains_controller_routing_spec.rb22
-rw-r--r--spec/services/snippets/create_service_spec.rb4
49 files changed, 975 insertions, 26 deletions
diff --git a/app/assets/javascripts/pages/admin/serverless/domains/index.js b/app/assets/javascripts/pages/admin/serverless/domains/index.js
new file mode 100644
index 00000000000..5be466886a5
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/serverless/domains/index.js
@@ -0,0 +1,19 @@
+import initSettingsPanels from '~/settings_panels';
+
+document.addEventListener('DOMContentLoaded', () => {
+ // Initialize expandable settings panels
+ initSettingsPanels();
+
+ const domainCard = document.querySelector('.js-domain-cert-show');
+ const domainForm = document.querySelector('.js-domain-cert-inputs');
+ const domainReplaceButton = document.querySelector('.js-domain-cert-replace-btn');
+ const domainSubmitButton = document.querySelector('.js-serverless-domain-submit');
+
+ if (domainReplaceButton && domainCard && domainForm) {
+ domainReplaceButton.addEventListener('click', () => {
+ domainCard.classList.add('hidden');
+ domainForm.classList.remove('hidden');
+ domainSubmitButton.removeAttribute('disabled');
+ });
+ }
+});
diff --git a/app/assets/stylesheets/pages/pages.scss b/app/assets/stylesheets/pages/pages.scss
index 374227fe16a..93caa345f8a 100644
--- a/app/assets/stylesheets/pages/pages.scss
+++ b/app/assets/stylesheets/pages/pages.scss
@@ -56,4 +56,15 @@
border-top-right-radius: $border-radius-default;
}
+ &.floating-status-badge {
+ position: absolute;
+ right: $gl-padding-24;
+ bottom: $gl-padding-4;
+ margin-bottom: 0;
+ }
+}
+
+.form-control.has-floating-status-badge {
+ position: relative;
+ padding-right: 120px;
}
diff --git a/app/controllers/admin/serverless/domains_controller.rb b/app/controllers/admin/serverless/domains_controller.rb
new file mode 100644
index 00000000000..c37aec13105
--- /dev/null
+++ b/app/controllers/admin/serverless/domains_controller.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+class Admin::Serverless::DomainsController < Admin::ApplicationController
+ before_action :check_feature_flag
+ before_action :domain, only: [:update, :verify]
+
+ def index
+ @domain = PagesDomain.instance_serverless.first_or_initialize
+ end
+
+ def create
+ if PagesDomain.instance_serverless.count > 0
+ return redirect_to admin_serverless_domains_path, notice: _('An instance-level serverless domain already exists.')
+ end
+
+ @domain = PagesDomain.instance_serverless.create(create_params)
+
+ if @domain.persisted?
+ redirect_to admin_serverless_domains_path, notice: _('Domain was successfully created.')
+ else
+ render 'index'
+ end
+ end
+
+ def update
+ if domain.update(update_params)
+ redirect_to admin_serverless_domains_path, notice: _('Domain was successfully updated.')
+ else
+ render 'index'
+ end
+ end
+
+ def verify
+ result = VerifyPagesDomainService.new(domain).execute
+
+ if result[:status] == :success
+ flash[:notice] = _('Successfully verified domain ownership')
+ else
+ flash[:alert] = _('Failed to verify domain ownership')
+ end
+
+ redirect_to admin_serverless_domains_path
+ end
+
+ private
+
+ def domain
+ @domain = PagesDomain.instance_serverless.find(params[:id])
+ end
+
+ def check_feature_flag
+ render_404 unless Feature.enabled?(:serverless_domain)
+ end
+
+ def update_params
+ params.require(:pages_domain).permit(:user_provided_certificate, :user_provided_key)
+ end
+
+ def create_params
+ params.require(:pages_domain).permit(:domain, :user_provided_certificate, :user_provided_key)
+ end
+end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 70c4b536854..5c49fa842a4 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -16,7 +16,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:prometheus_computed_alerts)
end
before_action do
- push_frontend_feature_flag(:auto_stop_environments)
+ push_frontend_feature_flag(:auto_stop_environments, default_enabled: true)
end
after_action :expire_etag_cache, only: [:cancel_auto_stop]
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 872497992e1..953b2ffeb0b 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -18,7 +18,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
end
def diffs_batch
- return render_404 unless Feature.enabled?(:diffs_batch_load, @merge_request.project)
+ return render_404 unless Feature.enabled?(:diffs_batch_load, @merge_request.project, default_enabled: true)
diffs = @compare.diffs_in_batch(params[:page], params[:per_page], diff_options: diff_options)
positions = @merge_request.note_positions_for_paths(diffs.diff_file_paths, current_user)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 8c0188e1783..c5f017efe8d 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -19,7 +19,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
before_action only: [:show] do
- push_frontend_feature_flag(:diffs_batch_load, @project)
+ push_frontend_feature_flag(:diffs_batch_load, @project, default_enabled: true)
push_frontend_feature_flag(:single_mr_diff_view, @project)
push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
end
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 91767c53f81..05cf427184c 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -14,6 +14,7 @@ class PagesDomain < ApplicationRecord
validates :domain, hostname: { allow_numeric_hostname: true }
validates :domain, uniqueness: { case_sensitive: false }
+ validates :certificate, :key, presence: true, if: :usage_serverless?
validates :certificate, presence: { message: 'must be present if HTTPS-only is enabled' },
if: :certificate_should_be_present?
validates :certificate, certificate: true, if: ->(domain) { domain.certificate.present? }
@@ -64,6 +65,8 @@ class PagesDomain < ApplicationRecord
scope :with_logging_info, -> { includes(project: [:namespace, :route]) }
+ scope :instance_serverless, -> { where(wildcard: true, scope: :instance, usage: :serverless) }
+
def verified?
!!verified_at
end
diff --git a/app/services/environments/auto_stop_service.rb b/app/services/environments/auto_stop_service.rb
index 6eef8138493..ee7f25a4d76 100644
--- a/app/services/environments/auto_stop_service.rb
+++ b/app/services/environments/auto_stop_service.rb
@@ -30,7 +30,7 @@ module Environments
def stop_in_batch
environments = Environment.auto_stoppable(BATCH_SIZE)
- return false unless environments.exists? && Feature.enabled?(:auto_stop_environments)
+ return false unless environments.exists? && Feature.enabled?(:auto_stop_environments, default_enabled: true)
Ci::StopEnvironmentsService.execute_in_batch(environments)
end
diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb
index bef65dbe1c2..aa8421e10d5 100644
--- a/app/services/metrics/dashboard/system_dashboard_service.rb
+++ b/app/services/metrics/dashboard/system_dashboard_service.rb
@@ -11,6 +11,7 @@ module Metrics
SEQUENCE = [
STAGES::CommonMetricsInserter,
STAGES::ProjectMetricsInserter,
+ STAGES::ProjectMetricsDetailsInserter,
STAGES::EndpointInserter,
STAGES::Sorter
].freeze
diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb
index 9e87bebbe4e..7ded185a6f9 100644
--- a/app/services/snippets/create_service.rb
+++ b/app/services/snippets/create_service.rb
@@ -24,8 +24,8 @@ module Snippets
spam_check(snippet, current_user)
snippet_saved = snippet.with_transaction_returning_status do
- if snippet.save && snippet.store_mentions!
- create_repository_for(snippet, current_user)
+ (snippet.save && snippet.store_mentions!).tap do |saved|
+ create_repository_for(snippet, current_user) if saved
end
end
diff --git a/app/views/admin/serverless/domains/_form.html.haml b/app/views/admin/serverless/domains/_form.html.haml
new file mode 100644
index 00000000000..8c1c1d41caa
--- /dev/null
+++ b/app/views/admin/serverless/domains/_form.html.haml
@@ -0,0 +1,68 @@
+- form_name = 'js-serverless-domain-settings'
+- form_url = @domain.persisted? ? admin_serverless_domain_path(@domain.id, anchor: form_name) : admin_serverless_domains_path(anchor: form_name)
+- show_certificate_card = @domain.persisted? && @domain.errors.blank?
+= form_for @domain, url: form_url, html: { class: 'fieldset-form' } do |f|
+ = form_errors(@domain)
+
+ %fieldset
+ - if @domain.persisted?
+ - dns_record = "*.#{@domain.domain} CNAME #{Settings.pages.host}."
+ - verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}"
+ .form-group.row
+ .col-sm-6.position-relative
+ = f.label :domain, _('Domain'), class: 'label-bold'
+ = f.text_field :domain, class: 'form-control has-floating-status-badge', readonly: true
+ .status-badge.floating-status-badge
+ - text, status = @domain.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success']
+ .badge{ class: status }
+ = text
+ = link_to sprite_icon("redo"), verify_admin_serverless_domain_path(@domain.id), method: :post, class: "btn has-tooltip", title: _("Retry verification")
+
+ .col-sm-6
+ = f.label :serverless_domain_dns, _('DNS'), class: 'label-bold'
+ .input-group
+ = text_field_tag :serverless_domain_dns, dns_record , class: "monospace js-select-on-focus form-control", readonly: true
+ .input-group-append
+ = clipboard_button(target: '#serverless_domain_dns', class: 'btn-default input-group-text d-none d-sm-block')
+
+ .col-sm-12.form-text.text-muted
+ = _("To access this domain create a new DNS record")
+
+ .form-group
+ = f.label :serverless_domain_verification, _('Verification status'), class: 'label-bold'
+ .input-group
+ = text_field_tag :serverless_domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
+ .input-group-append
+ = clipboard_button(target: '#serverless_domain_verification', class: 'btn-default d-none d-sm-block')
+ %p.form-text.text-muted
+ - link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
+ = _("To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration.").html_safe % { link_to_help: link_to_help }
+
+ - else
+ .form-group
+ = f.label :domain, _('Domain'), class: 'label-bold'
+ = f.text_field :domain, class: 'form-control'
+
+ - if show_certificate_card
+ .card.js-domain-cert-show
+ .card-header
+ = _('Certificate')
+ .d-flex.justify-content-between.align-items-center.p-3
+ %span
+ = @domain.subject || _('missing')
+ %button.btn.btn-remove.btn-sm.js-domain-cert-replace-btn{ type: 'button' }
+ = _('Replace')
+
+ .js-domain-cert-inputs{ class: ('hidden' if show_certificate_card) }
+ .form-group
+ = f.label :user_provided_certificate, _('Certificate (PEM)'), class: 'label-bold'
+ = f.text_area :user_provided_certificate, rows: 5, class: 'form-control', value: ''
+ %span.form-text.text-muted
+ = _("Upload a certificate for your domain with all intermediates")
+ .form-group
+ = f.label :user_provided_key, _('Key (PEM)'), class: 'label-bold'
+ = f.text_area :user_provided_key, rows: 5, class: 'form-control', value: ''
+ %span.form-text.text-muted
+ = _("Upload a private key for your certificate")
+
+ = f.submit @domain.persisted? ? _('Save changes') : _('Add domain'), class: "btn btn-success js-serverless-domain-submit", disabled: @domain.persisted?
diff --git a/app/views/admin/serverless/domains/index.html.haml b/app/views/admin/serverless/domains/index.html.haml
new file mode 100644
index 00000000000..bd3c6bc6e04
--- /dev/null
+++ b/app/views/admin/serverless/domains/index.html.haml
@@ -0,0 +1,25 @@
+- breadcrumb_title _("Operations")
+- page_title _("Operations")
+- @content_class = "limit-container-width" unless fluid_layout
+
+-# normally expanded_by_default? is used here, but since this is the only panel
+-# in this settings page, let's leave it always open by default
+- expanded = true
+
+%section.settings.as-serverless-domain.no-animate#js-serverless-domain-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Serverless domain')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _('Set an instance-wide domain that will be available to all clusters when installing Knative.')
+ .settings-content
+ - if Gitlab.config.pages.enabled
+ = render 'form'
+ - else
+ .card
+ .card-header
+ = s_('GitLabPages|Domains')
+ .nothing-here-block
+ = s_("GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it.")
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index e8e1da720cd..9f70124ba0d 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -254,6 +254,11 @@
= link_to ci_cd_admin_application_settings_path, title: _('CI/CD') do
%span
= _('CI/CD')
+ - if Feature.enabled?(:serverless_domain)
+ = nav_link(path: 'application_settings#operations') do
+ = link_to admin_serverless_domains_path, title: _('Operations') do
+ %span
+ = _('Operations')
= nav_link(path: 'application_settings#reporting') do
= link_to reporting_admin_application_settings_path, title: _('Reporting') do
%span
diff --git a/app/workers/environments/auto_stop_cron_worker.rb b/app/workers/environments/auto_stop_cron_worker.rb
index 8fcda35b414..fdc9490453c 100644
--- a/app/workers/environments/auto_stop_cron_worker.rb
+++ b/app/workers/environments/auto_stop_cron_worker.rb
@@ -8,7 +8,7 @@ module Environments
feature_category :continuous_delivery
def perform
- return unless Feature.enabled?(:auto_stop_environments)
+ return unless Feature.enabled?(:auto_stop_environments, default_enabled: true)
AutoStopService.new.execute
end
diff --git a/changelogs/unreleased/35968-add_path_to_edit_custom_metrics.yml b/changelogs/unreleased/35968-add_path_to_edit_custom_metrics.yml
new file mode 100644
index 00000000000..5820296828f
--- /dev/null
+++ b/changelogs/unreleased/35968-add_path_to_edit_custom_metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Adds path to edit custom metrics in dashboard response
+merge_request: 24645
+author:
+type: added
diff --git a/changelogs/unreleased/ali-serverless-domains-be-1.yml b/changelogs/unreleased/ali-serverless-domains-be-1.yml
new file mode 100644
index 00000000000..9d0095f27c7
--- /dev/null
+++ b/changelogs/unreleased/ali-serverless-domains-be-1.yml
@@ -0,0 +1,5 @@
+---
+title: Add admin settings panel for instance-level serverless domain (behind feature flag)
+merge_request: 21222
+author:
+type: added
diff --git a/config/initializers/0_license.rb b/config/initializers/0_license.rb
index 19c71c34904..5c4546f499f 100644
--- a/config/initializers/0_license.rb
+++ b/config/initializers/0_license.rb
@@ -10,7 +10,7 @@ Gitlab.ee do
end
# Needed to run migration
- if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
+ if Gitlab::Database.cached_table_exists?('licenses')
message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false)
if ::License.block_changes? && message.present?
warn "WARNING: #{message}"
diff --git a/config/initializers/load_balancing.rb b/config/initializers/load_balancing.rb
index a49bcbe1f96..7502a6299ae 100644
--- a/config/initializers/load_balancing.rb
+++ b/config/initializers/load_balancing.rb
@@ -3,7 +3,7 @@
# We need to run this initializer after migrations are done so it doesn't fail on CI
Gitlab.ee do
- if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
+ if Gitlab::Database.cached_table_exists?('licenses')
if Gitlab::Database::LoadBalancing.enable?
Gitlab::Database.disable_prepared_statements
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 5493e229f7e..5210b84c8ba 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -32,6 +32,14 @@ namespace :admin do
resources :abuse_reports, only: [:index, :destroy]
resources :gitaly_servers, only: [:index]
+ namespace :serverless do
+ resources :domains, only: [:index, :create, :update] do
+ member do
+ post '/verify', to: 'domains#verify'
+ end
+ end
+ end
+
resources :spam_logs, only: [:index, :destroy] do
member do
post :mark_as_ham
diff --git a/db/post_migrate/20200207184023_add_temporary_index_to_promotion_notes.rb b/db/post_migrate/20200207184023_add_temporary_index_to_promotion_notes.rb
new file mode 100644
index 00000000000..44a32938483
--- /dev/null
+++ b/db/post_migrate/20200207184023_add_temporary_index_to_promotion_notes.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddTemporaryIndexToPromotionNotes < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :notes,
+ :note,
+ where: "noteable_type = 'Issue' AND system IS TRUE AND note LIKE 'promoted to epic%'",
+ name: 'tmp_idx_on_promoted_notes'
+ end
+
+ def down
+ # NO OP
+ end
+end
diff --git a/db/post_migrate/20200207185149_schedule_fix_orphan_promoted_issues.rb b/db/post_migrate/20200207185149_schedule_fix_orphan_promoted_issues.rb
new file mode 100644
index 00000000000..83ba56501dd
--- /dev/null
+++ b/db/post_migrate/20200207185149_schedule_fix_orphan_promoted_issues.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class ScheduleFixOrphanPromotedIssues < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 100
+ BACKGROUND_MIGRATION = 'FixOrphanPromotedIssues'.freeze
+
+ disable_ddl_transaction!
+
+ class Note < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'notes'
+
+ scope :of_promotion, -> do
+ where(noteable_type: 'Issue')
+ .where('notes.system IS TRUE')
+ .where("notes.note LIKE 'promoted to epic%'")
+ end
+ end
+
+ def up
+ Note.of_promotion.each_batch(of: BATCH_SIZE) do |notes, index|
+ jobs = notes.map { |note| [BACKGROUND_MIGRATION, [note.id]] }
+
+ BackgroundMigrationWorker.bulk_perform_async(jobs)
+ end
+ end
+
+ def down
+ # NO OP
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0259c63d3e5..435d994e201 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -2811,6 +2811,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_220211) do
t.index ["id"], name: "epic_mentions_temp_index", where: "((note ~~ '%@%'::text) AND ((noteable_type)::text = 'Epic'::text))"
t.index ["line_code"], name: "index_notes_on_line_code"
t.index ["note"], name: "index_notes_on_note_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["note"], name: "tmp_idx_on_promoted_notes", where: "(((noteable_type)::text = 'Issue'::text) AND (system IS TRUE) AND (note ~~ 'promoted to epic%'::text))"
t.index ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type"
t.index ["project_id", "id"], name: "index_notes_on_project_id_and_id_and_system_false", where: "(NOT system)"
t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type"
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index fafd078c487..cf017c0167b 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -1,7 +1,3 @@
----
-last_updated: 2019-09-16
----
-
# Audit Events **(STARTER)**
GitLab offers a way to view the changes made within the GitLab server for owners and administrators on a [paid plan][ee].
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 030ed3bc96b..390e0ae05af 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -584,6 +584,7 @@ a few things that you need to do:
1. Configure [object storage for merge request diffs](../merge_request_diffs.md#using-object-storage).
1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature).
1. Configure [object storage for dependency proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature).
+1. Configure [object storage for Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage) (optional feature).
NOTE: **Note:**
One current feature of GitLab that still requires a shared directory (NFS) is
diff --git a/doc/api/commits.md b/doc/api/commits.md
index eb3fb7b2195..ee635a009bf 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -18,7 +18,7 @@ GET /projects/:id/repository/commits
| `all` | boolean | no | Retrieve every commit from the repository |
| `with_stats` | boolean | no | Stats about each commit will be added to the response |
| `first_parent` | boolean | no | Follow only the first parent commit upon seeing a merge commit |
-| `order` | string | no | List commits in order. Possible value: [`topo`](https://git-scm.com/docs/git-log#Documentation/git-log.txt---topo-order). |
+| `order` | string | no | List commits in order. Possible values: `default`, [`topo`](https://git-scm.com/docs/git-log#Documentation/git-log.txt---topo-order). Defaults to `default`, the commits are shown in reverse chronological order. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index eddda2031b0..65dc65f23f5 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -624,6 +624,73 @@ to automatically stop.
You can read more in the [`.gitlab-ci.yml` reference](yaml/README.md#environmenton_stop).
+#### Environments auto-stop
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/20956) in GitLab 12.8.
+
+You can set a expiry time to environments and stop them automatically after a certain period.
+
+For example, consider the use of this feature with Review Apps environments.
+When you set up Review Apps, sometimes they keep running for a long time
+because some merge requests are left as open. An example for this situation is when the author of the merge
+request is not actively working on it, due to priority changes or a different approach was decided on, and the merge requests was simply forgotten.
+Idle environments waste resources, therefore they
+should be terminated as soon as possible.
+
+To address this problem, you can specify an optional expiration date for
+Review Apps environments. When the expiry time is reached, GitLab will automatically trigger a job
+to stop the environment, eliminating the need of manually doing so. In case an environment is updated, the expiration is renewed
+ensuring that only active merge requests keep running Review Apps.
+
+To enable this feature, you need to specify the [`environment:auto_stop_in`](yaml/README.md#environmentauto_stop_in) keyword in `.gitlab-ci.yml`.
+You can specify a human-friendly date as the value, such as `1 hour and 30 minutes` or `1 day`.
+`auto_stop_in` uses the same format of [`artifacts:expire_in` docs](yaml/README.md#artifactsexpire_in).
+
+##### Auto-stop example
+
+In the following example, there is a basic review app setup that creates a new environment
+per merge request. The `review_app` job is triggered by every push and
+creates or updates an environment named `review/your-branch-name`.
+The environment keeps running until `stop_review_app` is executed:
+
+```yaml
+review_app:
+ script: deploy-review-app
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ on_stop: stop_review_app
+ auto_stop_in: 1 week
+
+stop_review_app:
+ script: stop-review-app
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ action: stop
+ when: manual
+```
+
+As long as a merge request is active and keeps getting new commits,
+the review app will not stop, so developers don't need to worry about
+re-initiating review app.
+
+On the other hand, since `stop_review_app` is set to `auto_stop_in: 1 week`,
+if a merge request becomes inactive for more than a week,
+GitLab automatically triggers the `stop_review_app` job to stop the environment.
+
+You can also check the expiration date of environments through the GitLab UI. To do so,
+go to **Operations > Environments > Environment**. You can see the auto-stop period
+at the left-top section and a pin-mark button at the right-top section. This pin-mark
+button can be used to prevent auto-stopping the environment. By clicking this button, the `auto_stop_in` setting is over-written
+and the environment will be active until it's stopped manually.
+
+![Environment auto stop](img/environment_auto_stop_v12_8.png)
+
+NOTE: **NOTE**
+Due to the resource limitation, a background worker for stopping environments only
+runs once every hour. This means environments will not be stopped at the exact
+timestamp as the specified period, but will be stopped when the hourly cron worker
+detects expired environments.
+
### Grouping similar environments
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/7015) in GitLab 8.14.
diff --git a/doc/ci/img/environment_auto_stop_v12_8.png b/doc/ci/img/environment_auto_stop_v12_8.png
new file mode 100644
index 00000000000..3a3c54ab62d
--- /dev/null
+++ b/doc/ci/img/environment_auto_stop_v12_8.png
Binary files differ
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index cc081f2543a..bdef2eca1f2 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -80,6 +80,11 @@ you can copy and paste into `.gitlab-ci.yml` as a starting point. To do so:
1. Feel free to tune this template to your own needs.
+## Review Apps auto-stop
+
+See how to [configure Review Apps environments to expire and auto-stop](../environments.md#environments-auto-stop)
+after a given period of time.
+
## Review Apps examples
The following are example projects that demonstrate Review App configuration:
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 3367fac5137..1a301481f05 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -105,7 +105,7 @@ The following table lists available parameters for jobs:
| [`tags`](#tags) | List of tags which are used to select Runner. |
| [`allow_failure`](#allow_failure) | Allow job to fail. Failed job doesn't contribute to commit status. |
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
-| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, and `environment:action`. |
+| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in` and `environment:action`. |
| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. |
| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, and `artifacts:reports:junit`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:codequality`, `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_management`, `artifacts:reports:performance` and `artifacts:reports:metrics`. |
| [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. |
@@ -1453,6 +1453,29 @@ The `stop_review_app` job is **required** to have the following keywords defined
- `stage` should be the same as the `review_app` in order for the environment
to stop automatically when the branch is deleted
+#### `environment:auto_stop_in`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/20956) in GitLab 12.8.
+
+The `auto_stop_in` keyword is for specifying life period of the environment,
+that when expired, GitLab GitLab automatically stops them.
+
+For example,
+
+```yaml
+review_app:
+ script: deploy-review-app
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ auto_stop_in: 1 day
+```
+
+When `review_app` job is executed and a review app is created, a life period of
+the environment is set to `1 day`.
+
+For more information, see
+[the environments auto-stop documentation](../environments.md#environments-auto-stop)
+
#### `environment:kubernetes`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/27630) in GitLab 12.6.
diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index 8f4caf286fe..8b1a6df72c4 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -707,7 +707,7 @@ For more information about the available options, run:
### `#database-lab`
-Another tool GitLab employees can use is a chatbot powered by [Joe](https://gitlab.com/postgres-ai/joe), available in the [`#database-lab`](https://gitlab.slack.com/archives/CLJMDRD8C) channel on Slack.
+Another tool GitLab employees can use is a chatbot powered by [Joe](https://gitlab.com/postgres-ai/joe) which uses [Database Lab](https://gitlab.com/postgres-ai/database-lab) to instantly provide developers with their own clone of the production database. Joe is available in the [`#database-lab`](https://gitlab.slack.com/archives/CLJMDRD8C) channel on Slack.
Unlike chatops, it gives you a way to execute DDL statements (like creating indexes and tables) and get query plan not only for `SELECT` but also `UPDATE` and `DELETE`.
For example, in order to test new index you can do the following:
@@ -742,6 +742,20 @@ For more information about the available options, run:
help
```
+#### Tips & Tricks
+
+The database connection is now maintained during your whole session, so you can use `exec set ...` for any session variables (such as `enable_seqscan` or `work_mem`). These settings will be applied to all subsequent commands until you reset them.
+
+It is also possible to use transactions. This may be useful when you are working on statements that modify the data, for example INSERT, UPDATE, and DELETE. The `explain` command will perform `EXPLAIN ANALYZE`, which executes the statement. In order to run each `explain` starting from a clean state you can wrap it in a transaction, for example:
+
+```sql
+exec BEGIN
+
+explain UPDATE some_table SET some_column = TRUE
+
+exec ROLLBACK
+```
+
## Further reading
A more extensive guide on understanding query plans can be found in
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index 55c51ef5fb6..c526f7339d5 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -488,6 +488,7 @@ Supported applications:
- [GitLab Runner](#install-gitlab-runner-using-gitlab-ci)
- [Cilium](#install-cilium-using-gitlab-ci)
- [JupyterHub](#install-jupyterhub-using-gitlab-ci)
+- [Elastic Stack](#install-elastic-stack-using-gitlab-ci)
### Usage
@@ -791,6 +792,33 @@ project. Refer to the
[chart reference](https://zero-to-jupyterhub.readthedocs.io/en/stable/reference.html)
for the available configuration options.
+### Install Elastic Stack using GitLab CI
+
+> [Introduced](https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/merge_requests/45) in GitLab 12.8.
+
+Elastic Stack is installed using GitLab CI by defining configuration in
+`.gitlab/managed-apps/config.yaml`.
+
+The following configuration is required to install Elastic Stack using GitLab CI:
+
+```yaml
+elasticStack:
+ installed: true
+```
+
+Elastic Stack is installed into the `gitlab-managed-apps` namespace of your cluster.
+
+You can check the default [values.yaml](https://gitlab.com/gitlab-org/gitlab/-/blob/master/vendor/elastic_stack/values.yaml) we set for this chart.
+
+You can customize the installation of Elastic Stack by defining
+`.gitlab/managed-apps/elastic-stack/values.yaml` file in your cluster
+management project. Refer to the
+[chart](https://github.com/helm/charts/blob/master/stable/elastic-stack/values.yaml) for the
+available configuration options.
+
+NOTE: **Note:**
+In this alpha implementation of installing Elastic Stack through CI, reading the environment pod logs through Elasticsearch is unsupported. This is supported if [installed via the UI](#elastic-stack).
+
## Upgrading applications
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24789) in GitLab 11.8.
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 4e04a99e97c..dfb0066ceb0 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -38,7 +38,7 @@ module API
optional :all, type: Boolean, desc: 'Every commit will be returned'
optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response'
optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges'
- optional :order, type: String, desc: 'List commits in order', values: %w[topo]
+ optional :order, type: String, desc: 'List commits in order', default: 'default', values: %w[default topo]
use :pagination
end
get ':id/repository/commits' do
diff --git a/lib/gitlab/background_migration/fix_orphan_promoted_issues.rb b/lib/gitlab/background_migration/fix_orphan_promoted_issues.rb
new file mode 100644
index 00000000000..d6ec56ae19e
--- /dev/null
+++ b/lib/gitlab/background_migration/fix_orphan_promoted_issues.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # No OP for CE
+ class FixOrphanPromotedIssues
+ def perform(note_id)
+ end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::FixOrphanPromotedIssues.prepend_if_ee('EE::Gitlab::BackgroundMigration::FixOrphanPromotedIssues')
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index a614e68703c..02005be1f6a 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -233,7 +233,7 @@ module Gitlab
end
def self.cached_table_exists?(table_name)
- connection.schema_cache.data_source_exists?(table_name)
+ exists? && connection.schema_cache.data_source_exists?(table_name)
end
def self.database_version
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index b981b9cf1bc..ac22f5bf419 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -324,7 +324,7 @@ module Gitlab
request.after = GitalyClient.timestamp(options[:after]) if options[:after]
request.before = GitalyClient.timestamp(options[:before]) if options[:before]
request.revision = encode_binary(options[:ref]) if options[:ref]
- request.order = options[:order].upcase if options[:order].present?
+ request.order = options[:order].upcase.sub('DEFAULT', 'NONE') if options[:order].present?
request.paths = encode_repeated(Array(options[:path])) if options[:path].present?
diff --git a/lib/gitlab/marginalia.rb b/lib/gitlab/marginalia.rb
index b4b9896e6b9..24e21a1d512 100644
--- a/lib/gitlab/marginalia.rb
+++ b/lib/gitlab/marginalia.rb
@@ -22,7 +22,7 @@ module Gitlab
def self.set_feature_cache
# During db:create and db:bootstrap skip feature query as DB is not available yet.
- return false unless ActiveRecord::Base.connected? && Gitlab::Database.cached_table_exists?('features')
+ return false unless Gitlab::Database.cached_table_exists?('features')
self.enabled = Feature.enabled?(MARGINALIA_FEATURE_FLAG)
end
diff --git a/lib/gitlab/metrics/dashboard/stages/project_metrics_details_inserter.rb b/lib/gitlab/metrics/dashboard/stages/project_metrics_details_inserter.rb
new file mode 100644
index 00000000000..a9d11f58255
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/project_metrics_details_inserter.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class ProjectMetricsDetailsInserter < BaseStage
+ def transform!
+ dashboard[:panel_groups].each do |panel_group|
+ next unless panel_group
+
+ has_custom_metrics = custom_group_titles.include?(panel_group[:group])
+ panel_group[:has_custom_metrics] = has_custom_metrics
+
+ panel_group[:panels].each do |panel|
+ next unless panel
+
+ panel[:metrics].each do |metric|
+ next unless metric
+
+ metric[:edit_path] = has_custom_metrics ? edit_path(metric) : nil
+ end
+ end
+ end
+ end
+
+ private
+
+ def custom_group_titles
+ @custom_group_titles ||= PrometheusMetricEnums.custom_group_details.values.map { |group_details| group_details[:group_title] }
+ end
+
+ def edit_path(metric)
+ Gitlab::Routing.url_helpers.edit_project_prometheus_metric_path(project, metric[:metric_id])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ea0a773a525..2514bf89c8a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1107,6 +1107,9 @@ msgstr ""
msgid "Add comment now"
msgstr ""
+msgid "Add domain"
+msgstr ""
+
msgid "Add email address"
msgstr ""
@@ -1919,6 +1922,9 @@ msgstr ""
msgid "An error occurred. Please try again."
msgstr ""
+msgid "An instance-level serverless domain already exists."
+msgstr ""
+
msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
msgstr ""
@@ -6807,6 +6813,12 @@ msgstr ""
msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
msgstr ""
+msgid "Domain was successfully created."
+msgstr ""
+
+msgid "Domain was successfully updated."
+msgstr ""
+
msgid "Don't have an account yet?"
msgstr ""
@@ -8184,6 +8196,9 @@ msgstr ""
msgid "Failed to upload object map file"
msgstr ""
+msgid "Failed to verify domain ownership"
+msgstr ""
+
msgid "Failure"
msgstr ""
@@ -17229,6 +17244,9 @@ msgstr ""
msgid "Serverless"
msgstr ""
+msgid "Serverless domain"
+msgstr ""
+
msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
msgstr ""
@@ -17334,6 +17352,9 @@ msgstr ""
msgid "Set a template repository for projects in this group"
msgstr ""
+msgid "Set an instance-wide domain that will be available to all clusters when installing Knative."
+msgstr ""
+
msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
msgstr ""
@@ -18548,6 +18569,9 @@ msgstr ""
msgid "Successfully unlocked"
msgstr ""
+msgid "Successfully verified domain ownership"
+msgstr ""
+
msgid "Suggest code changes which can be immediately applied in one click. Try it out!"
msgstr ""
diff --git a/spec/controllers/admin/serverless/domains_controller_spec.rb b/spec/controllers/admin/serverless/domains_controller_spec.rb
new file mode 100644
index 00000000000..aed83e190be
--- /dev/null
+++ b/spec/controllers/admin/serverless/domains_controller_spec.rb
@@ -0,0 +1,298 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Admin::Serverless::DomainsController do
+ let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
+
+ describe '#index' do
+ context 'non-admin user' do
+ before do
+ sign_in(user)
+ end
+
+ it 'responds with 404' do
+ get :index
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'admin user' do
+ before do
+ create(:pages_domain)
+ sign_in(admin)
+ end
+
+ context 'with serverless_domain feature disabled' do
+ before do
+ stub_feature_flags(serverless_domain: false)
+ end
+
+ it 'responds with 404' do
+ get :index
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when instance-level serverless domain exists' do
+ let!(:serverless_domain) { create(:pages_domain, :instance_serverless) }
+
+ it 'loads the instance serverless domain' do
+ get :index
+
+ expect(assigns(:domain).id).to eq(serverless_domain.id)
+ end
+ end
+
+ context 'when domain does not exist' do
+ it 'initializes an instance serverless domain' do
+ get :index
+
+ domain = assigns(:domain)
+
+ expect(domain.persisted?).to eq(false)
+ expect(domain.wildcard).to eq(true)
+ expect(domain.scope).to eq('instance')
+ expect(domain.usage).to eq('serverless')
+ end
+ end
+ end
+ end
+
+ describe '#create' do
+ let(:create_params) do
+ sample_domain = build(:pages_domain)
+
+ {
+ domain: 'serverless.gitlab.io',
+ user_provided_certificate: sample_domain.certificate,
+ user_provided_key: sample_domain.key
+ }
+ end
+
+ context 'non-admin user' do
+ before do
+ sign_in(user)
+ end
+
+ it 'responds with 404' do
+ post :create, params: { pages_domain: create_params }
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'admin user' do
+ before do
+ sign_in(admin)
+ end
+
+ context 'with serverless_domain feature disabled' do
+ before do
+ stub_feature_flags(serverless_domain: false)
+ end
+
+ it 'responds with 404' do
+ post :create, params: { pages_domain: create_params }
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when an instance-level serverless domain exists' do
+ let!(:serverless_domain) { create(:pages_domain, :instance_serverless) }
+
+ it 'does not create a new domain' do
+ expect { post :create, params: { pages_domain: create_params } }.not_to change { PagesDomain.instance_serverless.count }
+ end
+
+ it 'redirects to index' do
+ post :create, params: { pages_domain: create_params }
+
+ expect(response).to redirect_to admin_serverless_domains_path
+ expect(flash[:notice]).to include('An instance-level serverless domain already exists.')
+ end
+ end
+
+ context 'when an instance-level serverless domain does not exist' do
+ it 'creates an instance serverless domain with the provided attributes' do
+ expect { post :create, params: { pages_domain: create_params } }.to change { PagesDomain.instance_serverless.count }.by(1)
+
+ domain = PagesDomain.instance_serverless.first
+ expect(domain.domain).to eq(create_params[:domain])
+ expect(domain.certificate).to eq(create_params[:user_provided_certificate])
+ expect(domain.key).to eq(create_params[:user_provided_key])
+ expect(domain.wildcard).to eq(true)
+ expect(domain.scope).to eq('instance')
+ expect(domain.usage).to eq('serverless')
+ end
+
+ it 'redirects to index' do
+ post :create, params: { pages_domain: create_params }
+
+ expect(response).to redirect_to admin_serverless_domains_path
+ expect(flash[:notice]).to include('Domain was successfully created.')
+ end
+ end
+
+ context 'when there are errors' do
+ it 'renders index view' do
+ post :create, params: { pages_domain: { foo: 'bar' } }
+
+ expect(assigns(:domain).errors.size).to be > 0
+ expect(response).to render_template('index')
+ end
+ end
+ end
+ end
+
+ describe '#update' do
+ let(:domain) { create(:pages_domain, :instance_serverless) }
+
+ let(:update_params) do
+ sample_domain = build(:pages_domain)
+
+ {
+ user_provided_certificate: sample_domain.certificate,
+ user_provided_key: sample_domain.key
+ }
+ end
+
+ context 'non-admin user' do
+ before do
+ sign_in(user)
+ end
+
+ it 'responds with 404' do
+ put :update, params: { id: domain.id, pages_domain: update_params }
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'admin user' do
+ before do
+ sign_in(admin)
+ end
+
+ context 'with serverless_domain feature disabled' do
+ before do
+ stub_feature_flags(serverless_domain: false)
+ end
+
+ it 'responds with 404' do
+ put :update, params: { id: domain.id, pages_domain: update_params }
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when domain exists' do
+ it 'updates the domain with the provided attributes' do
+ new_certificate = build(:pages_domain, :ecdsa).certificate
+ new_key = build(:pages_domain, :ecdsa).key
+
+ put :update, params: { id: domain.id, pages_domain: { user_provided_certificate: new_certificate, user_provided_key: new_key } }
+
+ domain.reload
+
+ expect(domain.certificate).to eq(new_certificate)
+ expect(domain.key).to eq(new_key)
+ end
+
+ it 'does not update the domain name' do
+ put :update, params: { id: domain.id, pages_domain: { domain: 'new.com' } }
+
+ expect(domain.reload.domain).not_to eq('new.com')
+ end
+
+ it 'redirects to index' do
+ put :update, params: { id: domain.id, pages_domain: update_params }
+
+ expect(response).to redirect_to admin_serverless_domains_path
+ expect(flash[:notice]).to include('Domain was successfully updated.')
+ end
+ end
+
+ context 'when domain does not exist' do
+ it 'returns 404' do
+ put :update, params: { id: 0, pages_domain: update_params }
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when there are errors' do
+ it 'renders index view' do
+ put :update, params: { id: domain.id, pages_domain: { user_provided_certificate: 'bad certificate' } }
+
+ expect(assigns(:domain).errors.size).to be > 0
+ expect(response).to render_template('index')
+ end
+ end
+ end
+ end
+
+ describe '#verify' do
+ let(:domain) { create(:pages_domain, :instance_serverless) }
+
+ context 'non-admin user' do
+ before do
+ sign_in(user)
+ end
+
+ it 'responds with 404' do
+ post :verify, params: { id: domain.id }
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'admin user' do
+ before do
+ sign_in(admin)
+ end
+
+ def stub_service
+ service = double(:service)
+
+ expect(VerifyPagesDomainService).to receive(:new).with(domain).and_return(service)
+
+ service
+ end
+
+ context 'with serverless_domain feature disabled' do
+ before do
+ stub_feature_flags(serverless_domain: false)
+ end
+
+ it 'responds with 404' do
+ post :verify, params: { id: domain.id }
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ it 'handles verification success' do
+ expect(stub_service).to receive(:execute).and_return(status: :success)
+
+ post :verify, params: { id: domain.id }
+
+ expect(response).to redirect_to admin_serverless_domains_path
+ expect(flash[:notice]).to eq('Successfully verified domain ownership')
+ end
+
+ it 'handles verification failure' do
+ expect(stub_service).to receive(:execute).and_return(status: :failed)
+
+ post :verify, params: { id: domain.id }
+
+ expect(response).to redirect_to admin_serverless_domains_path
+ expect(flash[:alert]).to eq('Failed to verify domain ownership')
+ end
+ end
+ end
+end
diff --git a/spec/features/admin/admin_serverless_domains_spec.rb b/spec/features/admin/admin_serverless_domains_spec.rb
new file mode 100644
index 00000000000..85fe67004da
--- /dev/null
+++ b/spec/features/admin/admin_serverless_domains_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Admin Serverless Domains', :js do
+ let(:sample_domain) { build(:pages_domain) }
+
+ before do
+ allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
+ sign_in(create(:admin))
+ end
+
+ it 'Add domain with certificate' do
+ visit admin_serverless_domains_path
+
+ fill_in 'pages_domain[domain]', with: 'foo.com'
+ fill_in 'pages_domain[user_provided_certificate]', with: sample_domain.certificate
+ fill_in 'pages_domain[user_provided_key]', with: sample_domain.key
+ click_button 'Add domain'
+
+ expect(current_path).to eq admin_serverless_domains_path
+
+ expect(page).to have_field('pages_domain[domain]', with: 'foo.com')
+ expect(page).to have_field('serverless_domain_dns', with: /^\*\.foo\.com CNAME /)
+ expect(page).to have_field('serverless_domain_verification', with: /^_gitlab-pages-verification-code.foo.com TXT /)
+ expect(page).not_to have_field('pages_domain[user_provided_certificate]')
+ expect(page).not_to have_field('pages_domain[user_provided_key]')
+
+ expect(page).to have_content 'Unverified'
+ expect(page).to have_content '/CN=test-certificate'
+ end
+
+ it 'Update domain certificate' do
+ visit admin_serverless_domains_path
+
+ fill_in 'pages_domain[domain]', with: 'foo.com'
+ fill_in 'pages_domain[user_provided_certificate]', with: sample_domain.certificate
+ fill_in 'pages_domain[user_provided_key]', with: sample_domain.key
+ click_button 'Add domain'
+
+ expect(current_path).to eq admin_serverless_domains_path
+
+ expect(page).not_to have_field('pages_domain[user_provided_certificate]')
+ expect(page).not_to have_field('pages_domain[user_provided_key]')
+
+ click_button 'Replace'
+
+ expect(page).to have_field('pages_domain[user_provided_certificate]')
+ expect(page).to have_field('pages_domain[user_provided_key]')
+
+ fill_in 'pages_domain[user_provided_certificate]', with: sample_domain.certificate
+ fill_in 'pages_domain[user_provided_key]', with: sample_domain.key
+
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Domain was successfully updated'
+ expect(page).to have_content '/CN=test-certificate'
+ end
+end
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
index ac40f2dcd13..038f5ac5d4e 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
@@ -16,7 +16,8 @@
"label": { "type": "string" },
"track": { "type": "string" },
"prometheus_endpoint_path": { "type": "string" },
- "metric_id": { "type": "number" }
+ "metric_id": { "type": "number" },
+ "edit_path": { "type": ["string", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json
index f4afb4cbffc..d16fcd40359 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json
@@ -10,7 +10,8 @@
"panels": {
"type": "array",
"items": { "$ref": "panels.json" }
- }
+ },
+ "has_custom_metrics": { "type": "boolean" }
},
"additionalProperties": false
}
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 4a0eab3ea27..b99f311de29 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -394,6 +394,12 @@ describe Gitlab::Database do
expect(described_class.cached_table_exists?(:bogus_table_name)).to be_falsey
end
end
+
+ it 'returns false when database does not exist' do
+ expect(ActiveRecord::Base).to receive(:connection) { raise ActiveRecord::NoDatabaseError, 'broken' }
+
+ expect(described_class.cached_table_exists?(:projects)).to be(false)
+ end
end
describe '.exists?' do
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 5e1d6199c3c..5c36d6d35af 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -281,6 +281,19 @@ describe Gitlab::GitalyClient::CommitService do
end
describe '#find_commits' do
+ it 'sends an RPC request with NONE when default' do
+ request = Gitaly::FindCommitsRequest.new(
+ repository: repository_message,
+ disable_walk: true,
+ order: 'NONE'
+ )
+
+ expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
+ .with(request, kind_of(Hash)).and_return([])
+
+ client.find_commits(order: 'default')
+ end
+
it 'sends an RPC request' do
request = Gitaly::FindCommitsRequest.new(
repository: repository_message,
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 8c3942c7f7d..e8860d50437 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -12,6 +12,7 @@ describe Gitlab::Metrics::Dashboard::Processor do
[
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::ProjectMetricsInserter,
+ Gitlab::Metrics::Dashboard::Stages::ProjectMetricsDetailsInserter,
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter
]
@@ -25,6 +26,10 @@ describe Gitlab::Metrics::Dashboard::Processor do
end
end
+ it 'includes boolean to indicate if panel group has custom metrics' do
+ expect(dashboard[:panel_groups]).to all(include( { has_custom_metrics: boolean } ))
+ end
+
context 'when the dashboard is not present' do
let(:dashboard_yml) { nil }
@@ -145,7 +150,8 @@ describe Gitlab::Metrics::Dashboard::Processor do
unit: metric.unit,
label: metric.legend,
metric_id: metric.id,
- prometheus_endpoint_path: prometheus_path(metric.query)
+ prometheus_endpoint_path: prometheus_path(metric.query),
+ edit_path: edit_metric_path(metric)
}
end
@@ -165,4 +171,11 @@ describe Gitlab::Metrics::Dashboard::Processor do
identifier: metric
)
end
+
+ def edit_metric_path(metric)
+ Gitlab::Routing.url_helpers.edit_project_prometheus_metric_path(
+ project,
+ metric.id
+ )
+ end
end
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 99b7c4f148a..d2a54c3eea7 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -104,6 +104,14 @@ describe PagesDomain do
describe 'validate certificate' do
subject { domain }
+ context 'serverless domain' do
+ it 'requires certificate and key to be present' do
+ expect(build(:pages_domain, :without_certificate, :without_key, usage: :serverless)).not_to be_valid
+ expect(build(:pages_domain, :without_certificate, usage: :serverless)).not_to be_valid
+ expect(build(:pages_domain, :without_key, usage: :serverless)).not_to be_valid
+ end
+ end
+
context 'with matching key' do
let(:domain) { build(:pages_domain) }
@@ -555,6 +563,28 @@ describe PagesDomain do
end
end
+ describe '.instance_serverless' do
+ subject { described_class.instance_serverless }
+
+ before do
+ create(:pages_domain, wildcard: true)
+ create(:pages_domain, :instance_serverless)
+ create(:pages_domain, scope: :instance)
+ create(:pages_domain, :instance_serverless)
+ create(:pages_domain, usage: :serverless)
+ end
+
+ it 'returns domains that are wildcard, instance-level, and serverless' do
+ expect(subject.length).to eq(2)
+
+ subject.each do |domain|
+ expect(domain.wildcard).to eq(true)
+ expect(domain.usage).to eq('serverless')
+ expect(domain.scope).to eq('instance')
+ end
+ end
+ end
+
describe '.need_auto_ssl_renewal' do
subject { described_class.need_auto_ssl_renewal }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 170b9ccccf8..c179de249d5 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -266,8 +266,30 @@ describe API::Commits do
end
end
- context 'set to blank' do
- let(:order) { '' }
+ context 'set to default' do
+ let(:order) { 'default' }
+
+ # git log --graph -n 6 --pretty=format:"%h" --date-order 0031876
+ # * 0031876
+ # |\
+ # * | bf6e164
+ # | * 48ca272
+ # * | 9d526f8
+ # | * 335bc94
+ # |/
+ # * 1039376
+ it 'returns project commits ordered by default order' do
+ commits = project.repository.commits("0031876", limit: 6, order: 'default')
+
+ get api(route, current_user)
+
+ expect(json_response.size).to eq(6)
+ expect(json_response.map { |entry| entry["id"] }).to eq(commits.map(&:id))
+ end
+ end
+
+ context 'set to an invalid parameter' do
+ let(:order) { 'invalid' }
it_behaves_like '400 response' do
let(:request) { get api(route, current_user) }
diff --git a/spec/routing/admin/serverless/domains_controller_routing_spec.rb b/spec/routing/admin/serverless/domains_controller_routing_spec.rb
new file mode 100644
index 00000000000..18c0db6add1
--- /dev/null
+++ b/spec/routing/admin/serverless/domains_controller_routing_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Admin::Serverless::DomainsController do
+ it 'routes to #index' do
+ expect(get: '/admin/serverless/domains').to route_to('admin/serverless/domains#index')
+ end
+
+ it 'routes to #create' do
+ expect(post: '/admin/serverless/domains/').to route_to('admin/serverless/domains#create')
+ end
+
+ it 'routes to #update' do
+ expect(put: '/admin/serverless/domains/1').to route_to(controller: 'admin/serverless/domains', action: 'update', id: '1')
+ expect(patch: '/admin/serverless/domains/1').to route_to(controller: 'admin/serverless/domains', action: 'update', id: '1')
+ end
+
+ it 'routes #verify' do
+ expect(post: '/admin/serverless/domains/1/verify').to route_to(controller: 'admin/serverless/domains', action: 'verify', id: '1')
+ end
+end
diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb
index 1cf1cff51ed..a1cbec6748a 100644
--- a/spec/services/snippets/create_service_spec.rb
+++ b/spec/services/snippets/create_service_spec.rb
@@ -164,7 +164,9 @@ describe Snippets::CreateService do
it 'does not create snippet repository' do
stub_feature_flags(version_snippets: false)
- subject
+ expect do
+ subject
+ end.to change(Snippet, :count).by(1)
expect(snippet.repository_exists?).to be_falsey
end