summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue46
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue4
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue11
-rw-r--r--app/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum.rb20
-rw-r--r--app/models/ci/build_trace_chunk.rb52
-rw-r--r--app/views/projects/commits/show.html.haml6
-rw-r--r--app/views/shared/wikis/_form.html.haml4
-rw-r--r--app/views/shared/wikis/_main_links.html.haml6
-rw-r--r--app/views/shared/wikis/_sidebar.html.haml11
-rw-r--r--app/views/shared/wikis/diff.html.haml2
-rw-r--r--app/views/shared/wikis/edit.html.haml2
-rw-r--r--app/views/shared/wikis/pages.html.haml4
-rw-r--r--app/workers/analytics/instance_statistics/count_job_trigger_worker.rb2
-rw-r--r--app/workers/ci/build_trace_chunk_flush_worker.rb6
-rw-r--r--changelogs/unreleased/-231206-projects-commits.yml5
-rw-r--r--changelogs/unreleased/-231217-shared-wikis.yml5
-rw-r--r--changelogs/unreleased/205157-cannot-include-downstream-pipeline-with-include-file.yml5
-rw-r--r--changelogs/unreleased/245337-integrations-list-icons.yml5
-rw-r--r--changelogs/unreleased/fix-gb-fix-chunks-migration-race.yml5
-rw-r--r--changelogs/unreleased/fix-unresolved-value-error-in-instance-statistics-graphql-api.yml5
-rw-r--r--changelogs/unreleased/mgill-user-gpg.yml5
-rw-r--r--changelogs/unreleased/remove-index-on-issues-relative-position.yml5
-rw-r--r--changelogs/unreleased/remove_store_instance_statistics_measurements_ff.yml5
-rw-r--r--changelogs/unreleased/vij-public-project-snippets.yml5
-rw-r--r--config/feature_flags/development/store_instance_statistics_measurements.yml7
-rw-r--r--config/gitlab.yml.example6
-rw-r--r--db/migrate/20201007115209_add_lock_version_to_ci_build_trace_chunk.rb9
-rw-r--r--db/post_migrate/20201001101136_remove_index_on_issues_relative_position.rb18
-rw-r--r--db/schema_migrations/202010011011361
-rw-r--r--db/schema_migrations/202010071152091
-rw-r--r--db/structure.sql5
-rw-r--r--doc/administration/logs.md22
-rw-r--r--doc/administration/pages/index.md6
-rw-r--r--doc/administration/pages/source.md4
-rw-r--r--doc/api/runners.md16
-rw-r--r--doc/api/users.md3
-rw-r--r--doc/ci/parent_child_pipelines.md12
-rw-r--r--doc/ci/yaml/README.md16
-rw-r--r--doc/gitlab-basics/add-file.md15
-rw-r--r--doc/integration/kerberos.md34
-rw-r--r--doc/topics/autodevops/index.md48
-rw-r--r--doc/user/group/epics/index.md8
-rw-r--r--doc/user/project/pages/redirects.md24
-rw-r--r--lib/api/project_snippets.rb12
-rw-r--r--lib/api/users.rb20
-rw-r--r--lib/gitlab/ci/config/entry/include.rb6
-rw-r--r--lib/gitlab/ci/config/entry/jobs.rb29
-rw-r--r--lib/gitlab/config/entry/composable_hash.rb47
-rw-r--r--lib/gitlab/metrics/requests_rack_middleware.rb13
-rw-r--r--locale/gitlab.pot8
-rw-r--r--spec/frontend/alert_settings/alerts_integrations_list_spec.js38
-rw-r--r--spec/lib/gitlab/ci/config/entry/include_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb58
-rw-r--r--spec/lib/gitlab/config/entry/composable_hash_spec.rb108
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb8
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb76
-rw-r--r--spec/requests/api/graphql/instance_statistics_measurements_spec.rb7
-rw-r--r--spec/requests/api/project_snippets_spec.rb29
-rw-r--r--spec/requests/api/snippets_spec.rb4
-rw-r--r--spec/requests/api/users_spec.rb25
-rw-r--r--spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb61
-rw-r--r--spec/support/shared_examples/requests/api/snippets_shared_examples.rb89
-rw-r--r--spec/workers/analytics/instance_statistics/count_job_trigger_worker_spec.rb12
-rw-r--r--spec/workers/ci/build_trace_chunk_flush_worker_spec.rb4
64 files changed, 888 insertions, 272 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
index a12155b418b..2810c9273dc 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
@@ -1,13 +1,19 @@
<script>
-import { GlTable } from '@gitlab/ui';
+import { GlTable, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { s__, __ } from '~/locale';
export const i18n = {
title: s__('AlertsIntegrations|Current integrations'),
emptyState: s__('AlertsIntegrations|No integrations have been added yet'),
status: {
- enabled: __('Enabled'),
- disabled: __('Disabled'),
+ enabled: {
+ name: __('Enabled'),
+ tooltip: s__('AlertsIntegrations|Alerts will be created through this integration'),
+ },
+ disabled: {
+ name: __('Disabled'),
+ tooltip: s__('AlertsIntegrations|Alerts will not be created through this integration'),
+ },
},
};
@@ -18,6 +24,10 @@ export default {
i18n,
components: {
GlTable,
+ GlIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
},
props: {
integrations: {
@@ -28,11 +38,8 @@ export default {
},
fields: [
{
- key: 'status',
+ key: 'activated',
label: __('Status'),
- formatter(enabled) {
- return enabled ? i18n.status.enabled : i18n.status.disabled;
- },
},
{
key: 'name',
@@ -63,6 +70,29 @@ export default {
stacked="md"
:tbody-tr-class="tbodyTrClass"
show-empty
- />
+ >
+ <template #cell(activated)="{ item }">
+ <span v-if="item.activated" data-testid="integration-activated-status">
+ <gl-icon
+ v-gl-tooltip
+ name="check-circle-filled"
+ :size="16"
+ class="gl-text-green-500 gl-hover-cursor-pointer gl-mr-3"
+ :title="$options.i18n.status.enabled.tooltip"
+ />
+ {{ $options.i18n.status.enabled.name }}
+ </span>
+ <span v-else data-testid="integration-activated-status">
+ <gl-icon
+ v-gl-tooltip
+ name="warning-solid"
+ :size="16"
+ class="gl-text-red-600 gl-hover-cursor-pointer gl-mr-3"
+ :title="$options.i18n.status.disabled.tooltip"
+ />
+ {{ $options.i18n.status.disabled.name }}
+ </span>
+ </template>
+ </gl-table>
</div>
</template>
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
index 6c563bb3d93..7036910f85f 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
@@ -159,12 +159,12 @@ export default {
{
name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'),
- status: this.generic.activated,
+ activated: this.generic.activated,
},
{
name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'),
- status: this.prometheus.activated,
+ activated: this.prometheus.activated,
},
];
},
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index f55429ecdae..3cb5e63fd36 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -1,12 +1,12 @@
<script>
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
const HIDDEN_VALUE = '••••••';
export default {
components: {
- GlDeprecatedButton,
+ GlButton,
},
props: {
trigger: {
@@ -55,11 +55,12 @@ export default {
<p class="trigger-variables-btn-container d-flex">
<span class="font-weight-bold">{{ __('Trigger variables:') }}</span>
- <gl-deprecated-button
+ <gl-button
v-if="hasValues"
- class="btn-sm group js-reveal-variables trigger-variables-btn"
+ class="group js-reveal-variables trigger-variables-btn"
+ size="small"
@click="toggleValues"
- >{{ getToggleButtonText }}</gl-deprecated-button
+ >{{ getToggleButtonText }}</gl-button
>
</p>
diff --git a/app/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum.rb b/app/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum.rb
index b9f7f616e13..c6ca5963588 100644
--- a/app/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum.rb
+++ b/app/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum.rb
@@ -8,16 +8,16 @@ module Types
graphql_name 'MeasurementIdentifier'
description 'Possible identifier types for a measurement'
- value 'PROJECTS', 'Project count', value: :projects
- value 'USERS', 'User count', value: :users
- value 'ISSUES', 'Issue count', value: :issues
- value 'MERGE_REQUESTS', 'Merge request count', value: :merge_requests
- value 'GROUPS', 'Group count', value: :groups
- value 'PIPELINES', 'Pipeline count', value: :pipelines
- value 'PIPELINES_SUCCEEDED', 'Pipeline count with success status', value: :pipelines_succeeded
- value 'PIPELINES_FAILED', 'Pipeline count with failed status', value: :pipelines_failed
- value 'PIPELINES_CANCELED', 'Pipeline count with canceled status', value: :pipelines_canceled
- value 'PIPELINES_SKIPPED', 'Pipeline count with skipped status', value: :pipelines_skipped
+ value 'PROJECTS', 'Project count', value: 'projects'
+ value 'USERS', 'User count', value: 'users'
+ value 'ISSUES', 'Issue count', value: 'issues'
+ value 'MERGE_REQUESTS', 'Merge request count', value: 'merge_requests'
+ value 'GROUPS', 'Group count', value: 'groups'
+ value 'PIPELINES', 'Pipeline count', value: 'pipelines'
+ value 'PIPELINES_SUCCEEDED', 'Pipeline count with success status', value: 'pipelines_succeeded'
+ value 'PIPELINES_FAILED', 'Pipeline count with failed status', value: 'pipelines_failed'
+ value 'PIPELINES_CANCELED', 'Pipeline count with canceled status', value: 'pipelines_canceled'
+ value 'PIPELINES_SKIPPED', 'Pipeline count with skipped status', value: 'pipelines_skipped'
end
end
end
diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb
index f8b419727ae..5fabed8feec 100644
--- a/app/models/ci/build_trace_chunk.rb
+++ b/app/models/ci/build_trace_chunk.rb
@@ -7,6 +7,7 @@ module Ci
include ::FastDestroyAll
include ::Checksummable
include ::Gitlab::ExclusiveLeaseHelpers
+ include ::Gitlab::OptimisticLocking
belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id
@@ -116,12 +117,8 @@ module Ci
(start_offset...end_offset)
end
- def persist_data!
- in_lock(*lock_params) { unsafe_persist_data! }
- end
-
def schedule_to_persist!
- return if persisted?
+ return if flushed?
Ci::BuildTraceChunkFlushWorker.perform_async(id)
end
@@ -131,13 +128,30 @@ module Ci
# happen that a chunk gets migrated after being loaded by another worker
# but before the worker acquires a lock to perform the migration.
#
- # We want to reset a chunk in that case and retry migration. If it fails
- # again, we want to re-raise the exception.
+ # We are using Redis locking to ensure that we perform this operation
+ # inside an exclusive lock, but this does not prevent us from running into
+ # race conditions related to updating a model representation in the
+ # database. Optimistic locking is another mechanism that help here.
+ #
+ # We are using optimistic locking combined with Redis locking to ensure
+ # that a chunk gets migrated properly.
#
- def flush!
- persist_data!
- rescue FailedToPersistDataError
- self.reset.persist_data!
+ def persist_data!
+ in_lock(*lock_params) do # exclusive Redis lock is acquired first
+ raise FailedToPersistDataError, 'Modifed build trace chunk detected' if has_changes_to_save?
+
+ self.reset.then do |chunk| # we ensure having latest lock_version
+ chunk.unsafe_persist_data! # we migrate the data and update data store
+ end
+ end
+ rescue ActiveRecord::StaleObjectError
+ raise FailedToPersistDataError, <<~MSG
+ Data migration race condition detected
+
+ store: #{data_store}
+ build: #{build.id}
+ index: #{chunk_index}
+ MSG
end
##
@@ -149,10 +163,14 @@ module Ci
build.pending_state.present? && chunks_max_index == chunk_index
end
- def persisted?
+ def flushed?
!redis?
end
+ def migrated?
+ flushed?
+ end
+
def live?
redis?
end
@@ -163,7 +181,7 @@ module Ci
self.chunk_index <=> other.chunk_index
end
- private
+ protected
def get_data
# Redis / database return UTF-8 encoded string by default
@@ -182,7 +200,7 @@ module Ci
data is not fulfilled in a bucket
size: #{current_size}
- state: #{build.pending_state.present?}
+ state: #{pending_state?}
max: #{chunks_max_index}
index: #{chunk_index}
MSG
@@ -239,6 +257,12 @@ module Ci
size == CHUNK_SIZE
end
+ private
+
+ def pending_state?
+ build.pending_state.present?
+ end
+
def current_store
self.class.get_store_class(data_store)
end
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 28b5dc0cc67..40dd3a685d4 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -17,16 +17,16 @@
.tree-controls
- if @merge_request.present?
.control.d-none.d-md-block
- = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
+ = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn gl-button'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control.d-none.d-md-block
- = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
+ = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn gl-button btn-success'
.control
= form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do
= search_field_tag :search, params[:search], { placeholder: _('Search by message'), id: 'commits-search', class: 'form-control search-text-input input-short gl-mt-3 gl-sm-mt-0 gl-min-w-full', spellcheck: false }
.control.d-none.d-md-block
- = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn btn-svg' do
+ = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn gl-button btn-svg' do
= sprite_icon('rss', css_class: 'qa-rss-icon')
= render_if_exists 'projects/commits/mirror_status'
diff --git a/app/views/shared/wikis/_form.html.haml b/app/views/shared/wikis/_form.html.haml
index 66c0f64c32c..2861ceb2b51 100644
--- a/app/views/shared/wikis/_form.html.haml
+++ b/app/views/shared/wikis/_form.html.haml
@@ -72,8 +72,8 @@
- if @page && @page.persisted?
= f.submit _("Save changes"), class: 'btn-success btn qa-save-changes-button'
.float-right
- = link_to _("Cancel"), wiki_page_path(@wiki, @page), class: 'btn btn-cancel btn-grouped'
+ = link_to _("Cancel"), wiki_page_path(@wiki, @page), class: 'btn gl-button btn-cancel btn-grouped'
- else
= f.submit s_("Wiki|Create page"), class: 'btn-success btn qa-create-page-button rspec-create-page-button'
.float-right
- = link_to _("Cancel"), wiki_path(@wiki), class: 'btn btn-cancel'
+ = link_to _("Cancel"), wiki_path(@wiki), class: 'btn gl-button btn-cancel'
diff --git a/app/views/shared/wikis/_main_links.html.haml b/app/views/shared/wikis/_main_links.html.haml
index e173ef72d11..4c18d1ad295 100644
--- a/app/views/shared/wikis/_main_links.html.haml
+++ b/app/views/shared/wikis/_main_links.html.haml
@@ -1,9 +1,9 @@
- if @page&.persisted?
- if can?(current_user, :create_wiki, @wiki.container)
- = link_to wiki_path(@wiki, action: :new), class: "btn btn-success", role: "button", data: { qa_selector: 'new_page_button' } do
+ = link_to wiki_path(@wiki, action: :new), class: "btn gl-button btn-success", role: "button", data: { qa_selector: 'new_page_button' } do
= s_("Wiki|New page")
- = link_to wiki_page_path(@wiki, @page, action: :history), class: "btn", role: "button", data: { qa_selector: 'page_history_button' } do
+ = link_to wiki_page_path(@wiki, @page, action: :history), class: "btn gl-button", role: "button", data: { qa_selector: 'page_history_button' } do
= s_("Wiki|Page history")
- if can?(current_user, :create_wiki, @wiki.container) && @page.latest? && @valid_encoding
- = link_to wiki_page_path(@wiki, @page, action: :edit), class: "btn js-wiki-edit", role: "button", data: { qa_selector: 'edit_page_button' } do
+ = link_to wiki_page_path(@wiki, @page, action: :edit), class: "btn gl-button js-wiki-edit", role: "button", data: { qa_selector: 'edit_page_button' } do
= _("Edit")
diff --git a/app/views/shared/wikis/_sidebar.html.haml b/app/views/shared/wikis/_sidebar.html.haml
index 54f285671a1..893661755ab 100644
--- a/app/views/shared/wikis/_sidebar.html.haml
+++ b/app/views/shared/wikis/_sidebar.html.haml
@@ -4,10 +4,11 @@
%a.gutter-toggle.float-right.d-block.d-sm-block.d-md-none.js-sidebar-wiki-toggle{ href: "#" }
= sprite_icon('chevron-double-lg-right', css_class: 'gl-icon')
- - git_access_url = wiki_path(@wiki, action: :git_access)
- = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '', data: { qa_selector: 'clone_repository_link' } do
- = sprite_icon('download', css_class: 'gl-mr-2')
- %span= _("Clone repository")
+ - if @wiki.container.is_a?(Project)
+ - git_access_url = wiki_path(@wiki, action: :git_access)
+ = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '', data: { qa_selector: 'clone_repository_link' } do
+ = sprite_icon('download', css_class: 'gl-mr-2')
+ %span= _("Clone repository")
.blocks-container
.block.block-first.w-100
@@ -18,5 +19,5 @@
= render @sidebar_wiki_entries, context: 'sidebar'
.block.w-100
- if @sidebar_limited
- = link_to wiki_path(@wiki, action: :pages), class: 'btn btn-block', data: { qa_selector: 'view_all_pages_button' } do
+ = link_to wiki_path(@wiki, action: :pages), class: 'btn gl-button btn-block', data: { qa_selector: 'view_all_pages_button' } do
= s_("Wiki|View All Pages")
diff --git a/app/views/shared/wikis/diff.html.haml b/app/views/shared/wikis/diff.html.haml
index 6fce3f5894e..ff6a4eb3bb7 100644
--- a/app/views/shared/wikis/diff.html.haml
+++ b/app/views/shared/wikis/diff.html.haml
@@ -12,7 +12,7 @@
= _('Changes')
.nav-controls.pb-md-3.pb-lg-0
- = link_to wiki_page_path(@wiki, @page, action: :history), class: 'btn', role: 'button', data: { qa_selector: 'page_history_button' } do
+ = link_to wiki_page_path(@wiki, @page, action: :history), class: 'btn gl-button', role: 'button', data: { qa_selector: 'page_history_button' } do
= s_('Wiki|Page history')
.page-content-header
diff --git a/app/views/shared/wikis/edit.html.haml b/app/views/shared/wikis/edit.html.haml
index 64a4816def6..54816a9aedb 100644
--- a/app/views/shared/wikis/edit.html.haml
+++ b/app/views/shared/wikis/edit.html.haml
@@ -17,7 +17,7 @@
.nav-controls.pb-md-3.pb-lg-0
- if @page.persisted?
- = link_to wiki_page_path(@wiki, @page, action: :history), class: "btn" do
+ = link_to wiki_page_path(@wiki, @page, action: :history), class: "btn gl-button" do
= s_("Wiki|Page history")
- if can?(current_user, :admin_wiki, @wiki.container)
#delete-wiki-modal-wrapper{ data: { delete_wiki_url: wiki_page_path(@wiki, @page), page_title: @page.human_title } }
diff --git a/app/views/shared/wikis/pages.html.haml b/app/views/shared/wikis/pages.html.haml
index 35a62ec2bb4..ef99d0aabed 100644
--- a/app/views/shared/wikis/pages.html.haml
+++ b/app/views/shared/wikis/pages.html.haml
@@ -10,14 +10,14 @@
= s_("Wiki|Wiki Pages")
.nav-controls.pb-md-3.pb-lg-0
- = link_to wiki_path(@wiki, action: :git_access), class: 'btn' do
+ = link_to wiki_path(@wiki, action: :git_access), class: 'btn gl-button' do
= sprite_icon('download')
= _("Clone repository")
.dropdown.inline.wiki-sort-dropdown
.btn-group{ role: 'group' }
.btn-group{ role: 'group' }
- %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
+ %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn gl-button btn-default' }
= sort_title
= sprite_icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
diff --git a/app/workers/analytics/instance_statistics/count_job_trigger_worker.rb b/app/workers/analytics/instance_statistics/count_job_trigger_worker.rb
index 01bddfea7de..bf57619fc6e 100644
--- a/app/workers/analytics/instance_statistics/count_job_trigger_worker.rb
+++ b/app/workers/analytics/instance_statistics/count_job_trigger_worker.rb
@@ -14,8 +14,6 @@ module Analytics
idempotent!
def perform
- return if Feature.disabled?(:store_instance_statistics_measurements, default_enabled: true)
-
recorded_at = Time.zone.now
worker_arguments = Gitlab::Analytics::InstanceStatistics::WorkersArgumentBuilder.new(
diff --git a/app/workers/ci/build_trace_chunk_flush_worker.rb b/app/workers/ci/build_trace_chunk_flush_worker.rb
index dfa1417f18e..89400247a7b 100644
--- a/app/workers/ci/build_trace_chunk_flush_worker.rb
+++ b/app/workers/ci/build_trace_chunk_flush_worker.rb
@@ -8,8 +8,10 @@ module Ci
idempotent!
# rubocop: disable CodeReuse/ActiveRecord
- def perform(chunk_id)
- ::Ci::BuildTraceChunk.find_by(id: chunk_id).try(&:flush!)
+ def perform(id)
+ ::Ci::BuildTraceChunk.find_by(id: id).try do |chunk|
+ chunk.persist_data!
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/changelogs/unreleased/-231206-projects-commits.yml b/changelogs/unreleased/-231206-projects-commits.yml
new file mode 100644
index 00000000000..981dc5b6da1
--- /dev/null
+++ b/changelogs/unreleased/-231206-projects-commits.yml
@@ -0,0 +1,5 @@
+---
+title: Apply GitLab UI button styles to buttons in app/views/projects/commits directory
+merge_request: 44331
+author: Lakshit
+type: other
diff --git a/changelogs/unreleased/-231217-shared-wikis.yml b/changelogs/unreleased/-231217-shared-wikis.yml
new file mode 100644
index 00000000000..7d565965941
--- /dev/null
+++ b/changelogs/unreleased/-231217-shared-wikis.yml
@@ -0,0 +1,5 @@
+---
+title: Apply GitLab UI button styles to buttons in app/views/shared/wikis directory
+merge_request: 44338
+author: Lakshit
+type: other
diff --git a/changelogs/unreleased/205157-cannot-include-downstream-pipeline-with-include-file.yml b/changelogs/unreleased/205157-cannot-include-downstream-pipeline-with-include-file.yml
new file mode 100644
index 00000000000..fea95fa43e1
--- /dev/null
+++ b/changelogs/unreleased/205157-cannot-include-downstream-pipeline-with-include-file.yml
@@ -0,0 +1,5 @@
+---
+title: Allow to include project files in parent-child pipelines
+merge_request: 43404
+author:
+type: fixed
diff --git a/changelogs/unreleased/245337-integrations-list-icons.yml b/changelogs/unreleased/245337-integrations-list-icons.yml
new file mode 100644
index 00000000000..72ef2d7514c
--- /dev/null
+++ b/changelogs/unreleased/245337-integrations-list-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Status icons for alerts integratiosn list
+merge_request: 44318
+author:
+type: added
diff --git a/changelogs/unreleased/fix-gb-fix-chunks-migration-race.yml b/changelogs/unreleased/fix-gb-fix-chunks-migration-race.yml
new file mode 100644
index 00000000000..6b796a0263f
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-chunks-migration-race.yml
@@ -0,0 +1,5 @@
+---
+title: Use optimistic locking to safely migrate a build trace chunk
+merge_request: 44588
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-unresolved-value-error-in-instance-statistics-graphql-api.yml b/changelogs/unreleased/fix-unresolved-value-error-in-instance-statistics-graphql-api.yml
new file mode 100644
index 00000000000..2f9a417e0bc
--- /dev/null
+++ b/changelogs/unreleased/fix-unresolved-value-error-in-instance-statistics-graphql-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix instance statistics GraphQL query with identifier
+merge_request: 44475
+author:
+type: fixed
diff --git a/changelogs/unreleased/mgill-user-gpg.yml b/changelogs/unreleased/mgill-user-gpg.yml
new file mode 100644
index 00000000000..ffcf5a0c3fb
--- /dev/null
+++ b/changelogs/unreleased/mgill-user-gpg.yml
@@ -0,0 +1,5 @@
+---
+title: API support for a specific GPG Key for given user
+merge_request: 43693
+author:
+type: added
diff --git a/changelogs/unreleased/remove-index-on-issues-relative-position.yml b/changelogs/unreleased/remove-index-on-issues-relative-position.yml
new file mode 100644
index 00000000000..690e358dbce
--- /dev/null
+++ b/changelogs/unreleased/remove-index-on-issues-relative-position.yml
@@ -0,0 +1,5 @@
+---
+title: Remove index on issues.relative_position
+merge_request: 43991
+author:
+type: performance
diff --git a/changelogs/unreleased/remove_store_instance_statistics_measurements_ff.yml b/changelogs/unreleased/remove_store_instance_statistics_measurements_ff.yml
new file mode 100644
index 00000000000..43354cdcb3c
--- /dev/null
+++ b/changelogs/unreleased/remove_store_instance_statistics_measurements_ff.yml
@@ -0,0 +1,5 @@
+---
+title: Remove the `store_instance_statistics_measurements` feature flag
+merge_request: 44566
+author:
+type: changed
diff --git a/changelogs/unreleased/vij-public-project-snippets.yml b/changelogs/unreleased/vij-public-project-snippets.yml
new file mode 100644
index 00000000000..2cbc97cb469
--- /dev/null
+++ b/changelogs/unreleased/vij-public-project-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Allow unauthenticated users access to public Project Snippets via the REST API
+merge_request: 44446
+author:
+type: fixed
diff --git a/config/feature_flags/development/store_instance_statistics_measurements.yml b/config/feature_flags/development/store_instance_statistics_measurements.yml
deleted file mode 100644
index 9483b9005df..00000000000
--- a/config/feature_flags/development/store_instance_statistics_measurements.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: store_instance_statistics_measurements
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41300
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247871
-group: group::analytics
-type: development
-default_enabled: true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 86df39830df..cce627fa540 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -854,6 +854,12 @@ production: &base
# (default: accept any service name in keytab file)
# service_principal_name: HTTP/gitlab.example.com@EXAMPLE.COM
+ # Kerberos realms/domains that are allowed to automatically link LDAP identities.
+ # By default, GitLab accepts a realm that matches the domain derived from the
+ # LDAP `base` DN. For example, `ou=users,dc=example,dc=com` would allow users
+ # with a realm matching `example.com`.
+ # simple_ldap_linking_allowed_realms: ['example.com','kerberos.example.com']
+
# Dedicated port: Git before 2.4 does not fall back to Basic authentication if Negotiate fails.
# To support both Basic and Negotiate methods with older versions of Git, configure
# nginx to proxy GitLab on an extra port (e.g. 8443) and uncomment the following lines
diff --git a/db/migrate/20201007115209_add_lock_version_to_ci_build_trace_chunk.rb b/db/migrate/20201007115209_add_lock_version_to_ci_build_trace_chunk.rb
new file mode 100644
index 00000000000..fdef5e2f52a
--- /dev/null
+++ b/db/migrate/20201007115209_add_lock_version_to_ci_build_trace_chunk.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddLockVersionToCiBuildTraceChunk < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :ci_build_trace_chunks, :lock_version, :integer, default: 0, null: false
+ end
+end
diff --git a/db/post_migrate/20201001101136_remove_index_on_issues_relative_position.rb b/db/post_migrate/20201001101136_remove_index_on_issues_relative_position.rb
new file mode 100644
index 00000000000..605a167c0d5
--- /dev/null
+++ b/db/post_migrate/20201001101136_remove_index_on_issues_relative_position.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveIndexOnIssuesRelativePosition < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ INDEX_NAME = 'index_issues_on_relative_position'
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:issues, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:issues, :relative_position, name: INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20201001101136 b/db/schema_migrations/20201001101136
new file mode 100644
index 00000000000..ecfc37cdfc5
--- /dev/null
+++ b/db/schema_migrations/20201001101136
@@ -0,0 +1 @@
+f3f9dd503d2c2695d5cd32ea87ff11e45832b1650df3186c7f71c984fc59ad24 \ No newline at end of file
diff --git a/db/schema_migrations/20201007115209 b/db/schema_migrations/20201007115209
new file mode 100644
index 00000000000..acd0a056bbd
--- /dev/null
+++ b/db/schema_migrations/20201007115209
@@ -0,0 +1 @@
+761cad9a584d98e3086e716f7a5c1d9b4aba87b084efcfcee7272cfdf1179372 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 0a8cd48e446..254cf40cf26 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9911,7 +9911,8 @@ CREATE TABLE ci_build_trace_chunks (
chunk_index integer NOT NULL,
data_store integer NOT NULL,
raw_data bytea,
- checksum bytea
+ checksum bytea,
+ lock_version integer DEFAULT 0 NOT NULL
);
CREATE SEQUENCE ci_build_trace_chunks_id_seq
@@ -20501,8 +20502,6 @@ CREATE UNIQUE INDEX index_issues_on_project_id_and_iid ON issues USING btree (pr
CREATE INDEX index_issues_on_promoted_to_epic_id ON issues USING btree (promoted_to_epic_id) WHERE (promoted_to_epic_id IS NOT NULL);
-CREATE INDEX index_issues_on_relative_position ON issues USING btree (relative_position);
-
CREATE INDEX index_issues_on_sprint_id ON issues USING btree (sprint_id);
CREATE INDEX index_issues_on_title_trigram ON issues USING gin (title gin_trgm_ops);
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 88736170d23..45ee56ea92d 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -967,19 +967,9 @@ When [troubleshooting](troubleshooting/index.md) issues that aren't localized to
previously listed components, it's helpful to simultaneously gather multiple logs and statistics
from a GitLab instance.
-### GitLabSOS
-
-If performance degradations or cascading errors occur that can't readily be attributed to one
-of the previously listed GitLab components, [GitLabSOS](https://gitlab.com/gitlab-com/support/toolbox/gitlabsos/)
-can provide a perspective spanning all of Omnibus GitLab. For more details and instructions
-to run it, see [the GitLabSOS documentation](https://gitlab.com/gitlab-com/support/toolbox/gitlabsos/#gitlabsos).
-
-NOTE: **Note:**
-GitLab Support likes to use this custom-made tool.
-
### Briefly tail the main logs
-If the bug or error is readily reproducible bug or error, save the main GitLab logs
+If the bug or error is readily reproducible, save the main GitLab logs
[to a file](troubleshooting/linux_cheat_sheet.md#files--dirs) while reproducing the
problem once or more times:
@@ -989,6 +979,16 @@ sudo gitlab-ctl tail | tee /tmp/<case-ID-and-keywords>.log
Conclude the log gathering with <kbd>Ctrl</kbd> + <kbd>C</kbd>.
+### GitLabSOS
+
+If performance degradations or cascading errors occur that can't readily be attributed to one
+of the previously listed GitLab components, [GitLabSOS](https://gitlab.com/gitlab-com/support/toolbox/gitlabsos/)
+can provide a perspective spanning all of Omnibus GitLab. For more details and instructions
+to run it, see [the GitLabSOS documentation](https://gitlab.com/gitlab-com/support/toolbox/gitlabsos/#gitlabsos).
+
+NOTE: **Note:**
+GitLab Support likes to use this custom-made tool.
+
### Fast-stats
[Fast-stats](https://gitlab.com/gitlab-com/support/toolbox/fast-stats) is a tool
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 53b45c0ac83..0d4cc397071 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -235,7 +235,7 @@ control over how the Pages daemon runs and serves content in your environment.
| `pages_path` | The directory on disk where pages are stored, defaults to `GITLAB-RAILS/shared/pages`.
| `pages_nginx[]` | |
| `enable` | Include a virtual host `server{}` block for Pages inside NGINX. Needed for NGINX to proxy traffic back to the Pages daemon. Set to `false` if the Pages daemon should directly receive all requests, for example, when using [custom domains](index.md#custom-domains).
-| `FF_ENABLE_REDIRECTS` | Feature flag to enable redirects. See the [redirects documentation](../../user/project/pages/redirects.md#enable-or-disable-redirects) for more info. |
+| `FF_ENABLE_REDIRECTS` | Feature flag to disable redirects (enabled by default). Read the [redirects documentation](../../user/project/pages/redirects.md#disable-redirects) for more info. |
---
@@ -424,10 +424,6 @@ Authority (CA) in the system certificate store.
For Omnibus, this is fixed by [installing a custom CA in Omnibus GitLab](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
-## Enable redirects
-
-In GitLab Pages, you can [enable the redirects feature](../../user/project/pages/redirects.md#enable-or-disable-redirects) to configure rules to forward one URL to another using HTTP redirects.
-
## Activate verbose logging for daemon
Verbose logging was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/2533) in
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index 662817e7411..87217b141a4 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -347,10 +347,6 @@ world. Custom domains and TLS are supported.
1. Restart NGINX
1. [Restart GitLab](../restart_gitlab.md#installations-from-source)
-## Enable redirects
-
-In GitLab Pages, you can [enable the redirects feature](../../user/project/pages/redirects.md#enable-or-disable-redirects) to configure rules to forward one URL to another using HTTP redirects.
-
## NGINX caveats
NOTE: **Note:**
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 55dcf1dc3ee..778ec406622 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -271,6 +271,22 @@ Example response:
}
```
+### Pause a runner
+
+Pause a specific runner.
+
+```plaintext
+PUT --form "active=false" /runners/:runner_id
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|---------------------|
+| `runner_id` | integer | yes | The ID of a runner |
+
+```shell
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "active=false" "https://gitlab.example.com/api/v4/runners/6"
+```
+
## List runner's jobs
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
diff --git a/doc/api/users.md b/doc/api/users.md
index dfd00571bcb..a8c032e2f65 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -980,7 +980,8 @@ Example response:
## Get a specific GPG key for a given user
-Get a specific GPG key for a given user. Available only for admins.
+Get a specific GPG key for a given user. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43693)
+in GitLab 13.5, this endpoint can be accessed without admin authentication.
```plaintext
GET /users/:id/gpg_keys/:key_id
diff --git a/doc/ci/parent_child_pipelines.md b/doc/ci/parent_child_pipelines.md
index f2a4020cc4a..7fdf9f785ce 100644
--- a/doc/ci/parent_child_pipelines.md
+++ b/doc/ci/parent_child_pipelines.md
@@ -71,6 +71,18 @@ microservice_a:
- template: Security/SAST.gitlab-ci.yml
```
+In [GitLab 13.5](https://gitlab.com/gitlab-org/gitlab/-/issues/205157) and later,
+you can use [`include:file`](yaml/README.md#includefile) to trigger child pipelines
+with a configuration file in a different project:
+
+```yaml
+microservice_a:
+ trigger:
+ include:
+ - project: 'my-group/my-pipeline-library'
+ file: 'path/to/ci-config.yml'
+```
+
NOTE: **Note:**
The max number of entries that are accepted for `trigger:include:` is three.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 919d0e2e156..34cf25142e4 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -3733,6 +3733,22 @@ child-pipeline:
The `generated-config.yml` is extracted from the artifacts and used as the configuration
for triggering the child pipeline.
+##### Trigger child pipeline with files from another project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/205157) in GitLab 13.5.
+
+To trigger child pipelines with files from another private project under the same
+GitLab instance, use [`include:file`](#includefile):
+
+```yaml
+child-pipeline:
+ trigger:
+ include:
+ - project: 'my-group/my-pipeline-library'
+ ref: 'master'
+ file: '/path/to/child-pipeline.yml'
+```
+
#### Linking pipelines with `trigger:strategy`
By default, the `trigger` job completes with the `success` status
diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md
index 659cab299aa..e1ac1744b4a 100644
--- a/doc/gitlab-basics/add-file.md
+++ b/doc/gitlab-basics/add-file.md
@@ -29,16 +29,17 @@ to the desired destination:
cd <destination folder>
```
-[Create a branch](create-branch.md) to add your file to, before it's added to the master
-(main) branch of the project. It's not strictly necessary, but working directly in
-the `master` branch is not recommended unless your project is very small, and you're
-the only person working on it. You can [switch to an existing branch](start-using-git.md#work-on-an-existing-branch),
-if you've one already.
+[Create a new branch](create-branch.md) to add your file into. Submitting changes directly
+to the default branch should be avoided unless your project is very small and you're the
+only person working on it.
+
+You can also [switch to an existing branch](start-using-git.md#work-on-an-existing-branch)
+if you have one already.
Using your standard tool for copying files (for example, Finder in macOS, or File Explorer
-in Windows), put the file into a directory within the GitLab project.
+on Windows), put the file into a directory within the GitLab project.
-Check if your file is actually present in the directory (if you're in Windows,
+Check if your file is actually present in the directory (if you're on Windows,
use `dir` instead):
```shell
diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md
index 11ea74f3430..6603b466251 100644
--- a/doc/integration/kerberos.md
+++ b/doc/integration/kerberos.md
@@ -114,6 +114,40 @@ Taken together, these rules mean that linking will only work if your users'
Kerberos usernames are of the form `foo@AD.EXAMPLE.COM` and their
LDAP Distinguished Names look like `sAMAccountName=foo,dc=ad,dc=example,dc=com`.
+### Custom allowed realms
+
+[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9962) in GitLab 13.5.
+
+You can configure custom allowed realms when
+the user's Kerberos realm doesn't match the domain from the user's LDAP DN. The
+configuration value must specify all domains that users may be expected to
+have. Any other domains will be ignored and an LDAP identity will not be linked.
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['kerberos_simple_ldap_linking_allowed_realms'] = ['example.com','kerberos.example.com']
+ ```
+
+1. Save the file and [reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
+ GitLab for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ kerberos:
+ simple_ldap_linking_allowed_realms: ['example.com','kerberos.example.com']
+ ```
+
+1. Save the file and [restart](../administration/restart_gitlab.md#installations-from-source)
+ GitLab for the changes to take effect.
+
## HTTP Git access
A linked Kerberos account enables you to `git pull` and `git push` using your
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index a7bd810abbe..2a4ca3977a5 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -418,6 +418,54 @@ DANGER: **Danger:**
Setting `POSTGRES_ENABLED` to `false` permanently deletes any existing
channel 1 database for your environment.
+### Error: unable to recognize "": no matches for kind "Deployment" in version "extensions/v1beta1"
+
+After upgrading your Kubernetes cluster to [v1.16+](stages.md#kubernetes-116),
+you may encounter this message when deploying with Auto DevOps:
+
+```plaintext
+UPGRADE FAILED
+Error: failed decoding reader into objects: unable to recognize "": no matches for kind "Deployment" in version "extensions/v1beta1"
+```
+
+This can occur if your current deployments on the environment namespace were deployed with a
+deprecated/removed API that doesn't exist in Kubernetes v1.16+. For example,
+if [your in-cluster PostgreSQL was installed in a legacy way](#detected-an-existing-postgresql-database),
+the resource was created via the `extensions/v1beta1` API. However, the deployment resource
+was moved to the `app/v1` API in v1.16.
+
+To recover such outdated resources, you must convert the current deployments by mapping legacy APIs
+to newer APIs. There is a helper tool called [`mapkubeapis`](https://github.com/hickeyma/helm-mapkubeapis)
+that works for this problem. Follow these steps to use the tool in Auto DevOps:
+
+1. Modify your `.gitlab-ci.yml` with:
+
+ ```yaml
+ include:
+ - template: Auto-DevOps.gitlab-ci.yml
+ - remote: https://gitlab.com/shinya.maeda/ci-templates/-/raw/master/map-deprecated-api.gitlab-ci.yml
+
+ variables:
+ HELM_VERSION_FOR_MAPKUBEAPIS: "v2" # If you're using auto-depoy-image v2 or above, please specify "v3".
+ ```
+
+1. Run the job `<environment-name>:map-deprecated-api`. Ensure that this job succeeds before moving
+ to the next step. You should see something like the following output:
+
+ ```shell
+ 2020/10/06 07:20:49 Found deprecated or removed Kubernetes API:
+ "apiVersion: extensions/v1beta1
+ kind: Deployment"
+ Supported API equivalent:
+ "apiVersion: apps/v1
+ kind: Deployment"
+ ```
+
+1. Revert your `.gitlab-ci.yml` to the previous version. You no longer need to include the
+ supplemental template `map-deprecated-api`.
+
+1. Continue the deployments as usual.
+
## Development guides
[Development guide for Auto DevOps](../../development/auto_devops.md)
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index e8bcb7219fc..f0283633eee 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -22,7 +22,7 @@ An epic's page contains the following tabs:
- Hover over the total counts to see a breakdown of open and closed items.
NOTE: **Note:**
- The number provided here includes all epics associated with this project. The number includes epics for which users may not currently have permission.
+ The number provided here includes all epics associated with this project. The number includes epics for which users may not yet have permission.
- **Roadmap**: a roadmap view of child epics which have start and due dates.
@@ -100,7 +100,7 @@ steps to create, move, reorder, or delete child epics.
To set a **Start date** and **Due date** for an epic, select one of the following:
- **Fixed**: Enter a fixed value.
-- **From milestones**: Inherit a dynamic value from the milestones currently assigned to the epic's issues.
+- **From milestones**: Inherit a dynamic value from the milestones that are assigned to the epic's issues.
Note that GitLab 12.5 replaced this option with **Inherited**.
- **Inherited**: Inherit a dynamic value from the epic's issues, child epics, and milestones ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7332) in GitLab 12.5 to replace **From milestones**).
@@ -109,9 +109,9 @@ To set a **Start date** and **Due date** for an epic, select one of the followin
> [Replaced](https://gitlab.com/gitlab-org/gitlab/-/issues/7332) in GitLab 12.5 by **Inherited**.
If you select **From milestones** for the start date, GitLab will automatically set the date to be earliest
-start date across all milestones that are currently assigned to the issues that are added to the epic.
+start date across all milestones that are assigned to the issues that are added to the epic.
Similarly, if you select **From milestones** for the due date, GitLab will set it to be the latest due date across
-all milestones that are currently assigned to those issues.
+all milestones that are assigned to those issues.
These are dynamic dates which are recalculated if any of the following occur:
diff --git a/doc/user/project/pages/redirects.md b/doc/user/project/pages/redirects.md
index ae7b1b4fa6e..0624145cca3 100644
--- a/doc/user/project/pages/redirects.md
+++ b/doc/user/project/pages/redirects.md
@@ -6,15 +6,15 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Create redirects for GitLab Pages
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/24) in GitLab Pages 1.25.0 and GitLab 13.4.
-> - It's [deployed behind a feature flag](#enable-or-disable-redirects), disabled by default.
-> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-redirects).
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/24) in GitLab Pages 1.25.0 and GitLab 13.4 behind a feature flag, disabled by default.
+> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/367) in GitLab 13.5.
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
-In GitLab Pages, you can [enable](#enable-or-disable-redirects) the redirects feature to configure rules to forward one URL to another using HTTP redirects. GitLab Pages uses
-[Netlify style redirects](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file).
+In GitLab Pages, you can configure rules to forward one URL to another using
+[Netlify style](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file)
+HTTP redirects.
## Supported features
@@ -42,7 +42,7 @@ Supported paths must start with a forward slash `/`.
## Create redirects
-To create redirects after [enabling](#enable-or-disable-redirects) the feature,
+To create redirects,
create a configuration file named `_redirects` in the `public/` directory of your
GitLab Pages site.
@@ -105,19 +105,19 @@ rule 10: valid
rule 11: valid
```
-## Enable or disable redirects
+## Disable redirects
-Redirects in GitLab Pages is under development and not ready for production use. It is
-deployed behind a feature flag that is **disabled by default**.
+Redirects in GitLab Pages is under development, and is deployed behind a feature flag
+that is **enabled by default**.
-For [Omnibus installations](../../../administration/pages/index.md), define the
+To disable redirects, for [Omnibus installations](../../../administration/pages/index.md), define the
`FF_ENABLE_REDIRECTS` environment variable in the
[global settings](../../../administration/pages/index.md#global-settings).
Add the following line to `/etc/gitlab/gitlab.rb` and
[reconfigure the instance](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
```ruby
-gitlab_pages['env']['FF_ENABLE_REDIRECTS'] = 'true'
+gitlab_pages['env']['FF_ENABLE_REDIRECTS'] = 'false'
```
For [source installations](../../../administration/pages/source.md), define the
@@ -125,6 +125,6 @@ For [source installations](../../../administration/pages/source.md), define the
[restart GitLab](../../../administration/restart_gitlab.md#installations-from-source):
```shell
-export FF_ENABLE_REDIRECTS="true"
+export FF_ENABLE_REDIRECTS="false"
/path/to/pages/bin/gitlab-pages -config gitlab-pages.conf
```
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index f6e87fece89..e2d531d7854 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -4,7 +4,6 @@ module API
class ProjectSnippets < Grape::API::Instance
include PaginationParams
- before { authenticate! }
before { check_snippets_enabled }
params do
@@ -37,6 +36,8 @@ module API
use :pagination
end
get ":id/snippets" do
+ authenticate!
+
present paginate(snippets_for_current_user), with: Entities::ProjectSnippet, current_user: current_user
end
@@ -48,6 +49,9 @@ module API
end
get ":id/snippets/:snippet_id" do
snippet = snippets_for_current_user.find(params[:snippet_id])
+
+ not_found!('Snippet') unless snippet
+
present snippet, with: Entities::ProjectSnippet, current_user: current_user
end
@@ -63,6 +67,8 @@ module API
use :create_file_params
end
post ":id/snippets" do
+ authenticate!
+
authorize! :create_snippet, user_project
snippet_params = process_create_params(declared_params(include_missing: false))
@@ -97,6 +103,8 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
put ":id/snippets/:snippet_id" do
+ authenticate!
+
snippet = snippets_for_current_user.find_by(id: params.delete(:snippet_id))
not_found!('Snippet') unless snippet
@@ -125,6 +133,8 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
delete ":id/snippets/:snippet_id" do
+ authenticate!
+
snippet = snippets_for_current_user.find_by(id: params[:snippet_id])
not_found!('Snippet') unless snippet
diff --git a/lib/api/users.rb b/lib/api/users.rb
index b20ee590124..b13be9c114e 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -365,6 +365,26 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ desc 'Get a specific GPG key for a given user.' do
+ detail 'This feature was added in GitLab 13.5'
+ success Entities::GpgKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ get ':id/gpg_keys/:key_id' do
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ key = user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ present key, with: Entities::GpgKey
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
desc 'Delete an existing GPG key from a specified user. Available only for admins.' do
detail 'This feature was added in GitLab 10.0'
end
diff --git a/lib/gitlab/ci/config/entry/include.rb b/lib/gitlab/ci/config/entry/include.rb
index 9c2e5f641d0..ad0ed00aa6f 100644
--- a/lib/gitlab/ci/config/entry/include.rb
+++ b/lib/gitlab/ci/config/entry/include.rb
@@ -10,7 +10,7 @@ module Gitlab
class Include < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
- ALLOWED_KEYS = %i[local file remote template artifact job].freeze
+ ALLOWED_KEYS = %i[local file remote template artifact job project ref].freeze
validations do
validates :config, hash_or_string: true
@@ -22,6 +22,10 @@ module Gitlab
if config[:artifact] && config[:job].blank?
errors.add(:config, "must specify the job where to fetch the artifact from")
end
+
+ if config[:project] && config[:file].blank?
+ errors.add(:config, "must specify the file where to fetch the config from")
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/jobs.rb b/lib/gitlab/ci/config/entry/jobs.rb
index b5ce42969a5..b0fd9cef10b 100644
--- a/lib/gitlab/ci/config/entry/jobs.rb
+++ b/lib/gitlab/ci/config/entry/jobs.rb
@@ -7,7 +7,7 @@ module Gitlab
##
# Entry that represents a set of jobs.
#
- class Jobs < ::Gitlab::Config::Entry::Node
+ class Jobs < ::Gitlab::Config::Entry::ComposableHash
include ::Gitlab::Config::Entry::Validatable
validations do
@@ -36,6 +36,10 @@ module Gitlab
end
end
+ def composable_class(name, config)
+ self.class.find_type(name, config)
+ end
+
TYPES = [Entry::Hidden, Entry::Job, Entry::Bridge].freeze
private_constant :TYPES
@@ -49,29 +53,6 @@ module Gitlab
type.matching?(name, config)
end
end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def compose!(deps = nil)
- super do
- @config.each do |name, config|
- node = self.class.find_type(name, config)
- next unless node
-
- factory = ::Gitlab::Config::Entry::Factory.new(node)
- .value(config || {})
- .metadata(name: name)
- .with(key: name, parent: self,
- description: "#{name} job definition.")
-
- @entries[name] = factory.create!
- end
-
- @entries.each_value do |entry|
- entry.compose!(deps)
- end
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/gitlab/config/entry/composable_hash.rb b/lib/gitlab/config/entry/composable_hash.rb
new file mode 100644
index 00000000000..74070915940
--- /dev/null
+++ b/lib/gitlab/config/entry/composable_hash.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Config
+ module Entry
+ ##
+ # Entry that represents a composable hash definition
+ # Where each hash key can be any value written by the user
+ #
+ class ComposableHash < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+
+ # TODO: Refactor Validatable so these validations will not apply to a child class
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/263231
+ validations do
+ validates :config, type: Hash
+ end
+
+ def compose!(deps = nil)
+ super do
+ @config.each do |name, config|
+ entry_class = composable_class(name, config)
+ raise ArgumentError, 'Missing Composable class' unless entry_class
+
+ entry_class_name = entry_class.name.demodulize.underscore
+
+ factory = ::Gitlab::Config::Entry::Factory.new(entry_class)
+ .value(config || {})
+ .with(key: name, parent: self, description: "#{name} #{entry_class_name} definition") # rubocop:disable CodeReuse/ActiveRecord
+ .metadata(name: name)
+
+ @entries[name] = factory.create!
+ end
+
+ @entries.each_value do |entry|
+ entry.compose!(deps)
+ end
+ end
+ end
+
+ def composable_class(name, config)
+ opt(:composable_class)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb
index f8ba254c2a7..d2b80d11c10 100644
--- a/lib/gitlab/metrics/requests_rack_middleware.rb
+++ b/lib/gitlab/metrics/requests_rack_middleware.rb
@@ -49,14 +49,9 @@ module Gitlab
method = 'INVALID' unless HTTP_METHODS.key?(method)
started = Time.now.to_f
health_endpoint = health_endpoint?(env['PATH_INFO'])
+ status = 'undefined'
begin
- if health_endpoint
- RequestsRackMiddleware.http_health_requests_total.increment(method: method)
- else
- RequestsRackMiddleware.http_request_total.increment(method: method)
- end
-
status, headers, body = @app.call(env)
elapsed = Time.now.to_f - started
@@ -69,6 +64,12 @@ module Gitlab
rescue
RequestsRackMiddleware.rack_uncaught_errors_count.increment
raise
+ ensure
+ if health_endpoint
+ RequestsRackMiddleware.http_health_requests_total.increment(method: method, status: status)
+ else
+ RequestsRackMiddleware.http_request_total.increment(method: method, status: status)
+ end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 292cb17bd74..f9bfc654b15 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2488,6 +2488,12 @@ msgstr ""
msgid "Alerts endpoint"
msgstr ""
+msgid "AlertsIntegrations|Alerts will be created through this integration"
+msgstr ""
+
+msgid "AlertsIntegrations|Alerts will not be created through this integration"
+msgstr ""
+
msgid "AlertsIntegrations|Current integrations"
msgstr ""
@@ -22814,7 +22820,7 @@ msgstr ""
msgid "SecurityApprovals|One or more of the security scanners must be enabled. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "SecurityApprovals|Requires approval for vulnerabilties of Critical, High, or Unknown severity. %{linkStart}More information%{linkEnd}"
+msgid "SecurityApprovals|Requires approval for vulnerabilities of Critical, High, or Unknown severity. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "SecurityApprovals|Requires license policy rules for licenses of Allowed, or Denied. %{linkStart}More information%{linkEnd}"
diff --git a/spec/frontend/alert_settings/alerts_integrations_list_spec.js b/spec/frontend/alert_settings/alerts_integrations_list_spec.js
index 7c72a78ed41..b7a388300e9 100644
--- a/spec/frontend/alert_settings/alerts_integrations_list_spec.js
+++ b/spec/frontend/alert_settings/alerts_integrations_list_spec.js
@@ -1,17 +1,17 @@
-import { GlTable } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlTable, GlIcon } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import AlertIntegrationsList, {
i18n,
} from '~/alerts_settings/components/alerts_integrations_list.vue';
const mockIntegrations = [
{
- status: true,
+ activated: true,
name: 'Integration 1',
type: 'HTTP endpoint',
},
{
- status: false,
+ activated: false,
name: 'Integration 2',
type: 'HTTP endpoint',
},
@@ -21,11 +21,14 @@ describe('AlertIntegrationsList', () => {
let wrapper;
function mountComponent(propsData = {}) {
- wrapper = shallowMount(AlertIntegrationsList, {
+ wrapper = mount(AlertIntegrationsList, {
propsData: {
integrations: mockIntegrations,
...propsData,
},
+ stubs: {
+ GlIcon: true,
+ },
});
}
@@ -41,9 +44,32 @@ describe('AlertIntegrationsList', () => {
});
const findTableComponent = () => wrapper.find(GlTable);
+ const finsStatusCell = () => wrapper.findAll('[data-testid="integration-activated-status"]');
it('renders a table', () => {
expect(findTableComponent().exists()).toBe(true);
- expect(findTableComponent().attributes('empty-text')).toBe(i18n.emptyState);
+ });
+
+ it('renders an empty state when no integrations provided', () => {
+ mountComponent({ integrations: [] });
+ expect(findTableComponent().text()).toContain(i18n.emptyState);
+ });
+
+ describe('integration status', () => {
+ it('enabled', () => {
+ const cell = finsStatusCell().at(0);
+ const activatedIcon = cell.find(GlIcon);
+ expect(cell.text()).toBe(i18n.status.enabled.name);
+ expect(activatedIcon.attributes('name')).toBe('check-circle-filled');
+ expect(activatedIcon.attributes('title')).toBe(i18n.status.enabled.tooltip);
+ });
+
+ it('disabled', () => {
+ const cell = finsStatusCell().at(1);
+ const notActivatedIcon = cell.find(GlIcon);
+ expect(cell.text()).toBe(i18n.status.disabled.name);
+ expect(notActivatedIcon.attributes('name')).toBe('warning-solid');
+ expect(notActivatedIcon.attributes('title')).toBe(i18n.status.disabled.tooltip);
+ });
});
});
diff --git a/spec/lib/gitlab/ci/config/entry/include_spec.rb b/spec/lib/gitlab/ci/config/entry/include_spec.rb
index 3e816f70c03..59f0b0e7a48 100644
--- a/spec/lib/gitlab/ci/config/entry/include_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include_spec.rb
@@ -61,6 +61,31 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Include do
end
end
end
+
+ context 'when using "project"' do
+ context 'and specifying "ref" and "file"' do
+ let(:config) { { project: 'my-group/my-pipeline-library', ref: 'master', file: 'test.yml' } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'without "ref"' do
+ let(:config) { { project: 'my-group/my-pipeline-library', file: 'test.yml' } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'without "file"' do
+ let(:config) { { project: 'my-group/my-pipeline-library' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'has specific error' do
+ expect(include_entry.errors)
+ .to include('include config must specify the file where to fetch the config from')
+ end
+ end
+ end
end
context 'when value is something else' do
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index d596494a987..31ccbdcd3c8 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -2240,47 +2240,49 @@ module Gitlab
end
describe 'with parent-child pipeline' do
+ let(:config) do
+ YAML.dump({
+ build1: { stage: 'build', script: 'test' },
+ test1: {
+ stage: 'test',
+ trigger: {
+ include: includes
+ }
+ }
+ })
+ end
+
context 'when artifact and job are specified' do
- let(:config) do
- YAML.dump({
- build1: { stage: 'build', script: 'test' },
- test1: { stage: 'test', trigger: {
- include: [{ artifact: 'generated.yml', job: 'build1' }]
- } }
- })
- end
+ let(:includes) { [{ artifact: 'generated.yml', job: 'build1' }] }
it { is_expected.to be_valid }
end
- context 'when job is not specified specified while artifact is' do
- let(:config) do
- YAML.dump({
- build1: { stage: 'build', script: 'test' },
- test1: { stage: 'test', trigger: {
- include: [{ artifact: 'generated.yml' }]
- } }
- })
- end
+ context 'when job is not specified while artifact is' do
+ let(:includes) { [{ artifact: 'generated.yml' }] }
it_behaves_like 'returns errors', /include config must specify the job where to fetch the artifact from/
end
- context 'when include is a string' do
- let(:config) do
- YAML.dump({
- build1: { stage: 'build', script: 'test' },
- test1: {
- stage: 'test',
- trigger: {
- include: 'generated.yml'
- }
- }
- })
+ context 'when project and file are specified' do
+ let(:includes) do
+ [{ file: 'generated.yml', project: 'my-namespace/my-project' }]
end
it { is_expected.to be_valid }
end
+
+ context 'when file is not specified while project is' do
+ let(:includes) { [{ project: 'something' }] }
+
+ it_behaves_like 'returns errors', /include config must specify the file where to fetch the config from/
+ end
+
+ context 'when include is a string' do
+ let(:includes) { 'generated.yml' }
+
+ it { is_expected.to be_valid }
+ end
end
describe "Error handling" do
diff --git a/spec/lib/gitlab/config/entry/composable_hash_spec.rb b/spec/lib/gitlab/config/entry/composable_hash_spec.rb
new file mode 100644
index 00000000000..15bbf2047c5
--- /dev/null
+++ b/spec/lib/gitlab/config/entry/composable_hash_spec.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Config::Entry::ComposableHash, :aggregate_failures do
+ let(:valid_config) do
+ {
+ DATABASE_SECRET: 'passw0rd',
+ API_TOKEN: 'passw0rd2'
+ }
+ end
+
+ let(:config) { valid_config }
+
+ shared_examples 'composes a hash' do
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ context 'is invalid' do
+ let(:config) { %w[one two] }
+
+ it { expect(entry).not_to be_valid }
+ end
+ end
+
+ describe '#value' do
+ context 'when config is a hash' do
+ it 'returns key value' do
+ expect(entry.value).to eq config
+ end
+ end
+ end
+
+ describe '#compose!' do
+ before do
+ entry.compose!
+ end
+
+ it 'composes child entry with configured value' do
+ expect(entry.value).to eq(config)
+ end
+
+ it 'composes child entries with configured values' do
+ expect(entry[:DATABASE_SECRET]).to be_a(Gitlab::Config::Entry::Node)
+ expect(entry[:DATABASE_SECRET].description).to eq('DATABASE_SECRET node definition')
+ expect(entry[:DATABASE_SECRET].key).to eq(:DATABASE_SECRET)
+ expect(entry[:DATABASE_SECRET].metadata).to eq(name: :DATABASE_SECRET)
+ expect(entry[:DATABASE_SECRET].parent.class).to eq(Gitlab::Config::Entry::ComposableHash)
+ expect(entry[:DATABASE_SECRET].value).to eq('passw0rd')
+ expect(entry[:API_TOKEN]).to be_a(Gitlab::Config::Entry::Node)
+ expect(entry[:API_TOKEN].description).to eq('API_TOKEN node definition')
+ expect(entry[:API_TOKEN].key).to eq(:API_TOKEN)
+ expect(entry[:API_TOKEN].metadata).to eq(name: :API_TOKEN)
+ expect(entry[:API_TOKEN].parent.class).to eq(Gitlab::Config::Entry::ComposableHash)
+ expect(entry[:API_TOKEN].value).to eq('passw0rd2')
+ end
+
+ describe '#descendants' do
+ it 'creates descendant nodes' do
+ expect(entry.descendants.first).to be_a(Gitlab::Config::Entry::Node)
+ expect(entry.descendants.first.value).to eq('passw0rd')
+ expect(entry.descendants.second).to be_a(Gitlab::Config::Entry::Node)
+ expect(entry.descendants.second.value).to eq('passw0rd2')
+ end
+ end
+ end
+ end
+
+ context 'when ComposableHash is instantiated' do
+ let(:entry) { described_class.new(config) }
+
+ before do
+ allow(entry).to receive(:composable_class).and_return(Gitlab::Config::Entry::Node)
+ end
+
+ it_behaves_like 'composes a hash'
+ end
+
+ context 'when ComposableHash entry is configured in the parent class' do
+ let(:composable_hash_parent_class) do
+ Class.new(Gitlab::Config::Entry::Node) do
+ include ::Gitlab::Config::Entry::Configurable
+
+ entry :secrets, ::Gitlab::Config::Entry::ComposableHash,
+ description: 'Configured secrets for this job',
+ inherit: false,
+ default: { hello: :world },
+ metadata: { composable_class: Gitlab::Config::Entry::Node }
+ end
+ end
+
+ let(:entry) do
+ parent_entry = composable_hash_parent_class.new(secrets: config)
+ parent_entry.compose!
+
+ parent_entry[:secrets]
+ end
+
+ it_behaves_like 'composes a hash'
+
+ it 'creates entry with configuration from parent class' do
+ expect(entry.default).to eq({ hello: :world })
+ expect(entry.metadata).to eq(composable_class: Gitlab::Config::Entry::Node)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 0c77dc540f3..ab62688989f 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
end
it 'increments requests count' do
- expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get')
+ expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get', status: 200)
subject.call(env)
end
@@ -45,7 +45,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
end
it 'increments health endpoint counter rather than overall counter' do
- expect(described_class).to receive_message_chain(:http_health_requests_total, :increment).with(method: 'get')
+ expect(described_class).to receive_message_chain(:http_health_requests_total, :increment).with(method: 'get', status: 200)
expect(described_class).not_to receive(:http_request_total)
subject.call(env)
@@ -68,7 +68,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
end
it 'increments overall counter rather than health endpoint counter' do
- expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get')
+ expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get', status: 200)
expect(described_class).not_to receive(:http_health_requests_total)
subject.call(env)
@@ -101,7 +101,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
end
it 'increments requests count' do
- expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get')
+ expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get', status: 'undefined')
expect { subject.call(env) }.to raise_error(StandardError)
end
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index a2f6bc1412c..d8e6d5f3276 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -502,6 +502,10 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
describe '#persist_data!' do
let(:build) { create(:ci_build, :running) }
+ before do
+ build_trace_chunk.save!
+ end
+
subject { build_trace_chunk.persist_data! }
shared_examples_for 'Atomic operation' do
@@ -575,6 +579,33 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
expect(build_trace_chunk.fog?).to be_truthy
end
end
+
+ context 'when the chunk has been modifed by a different worker' do
+ it 'reloads the chunk before migration' do
+ described_class
+ .find(build_trace_chunk.id)
+ .update!(data_store: :fog)
+
+ build_trace_chunk.persist_data!
+ end
+
+ it 'verifies the operation using optimistic locking' do
+ allow(build_trace_chunk)
+ .to receive(:save!)
+ .and_raise(ActiveRecord::StaleObjectError)
+
+ expect { build_trace_chunk.persist_data! }
+ .to raise_error(described_class::FailedToPersistDataError)
+ end
+
+ it 'does not allow flushing unpersisted chunk' do
+ build_trace_chunk.checksum = '12345'
+
+ expect { build_trace_chunk.persist_data! }
+ .to raise_error(described_class::FailedToPersistDataError,
+ /Modifed build trace chunk detected/)
+ end
+ end
end
end
@@ -780,51 +811,6 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end
end
- describe '#flush!' do
- context 'when chunk can be flushed without problems' do
- before do
- allow(build_trace_chunk).to receive(:persist_data!)
- end
-
- it 'completes migration successfully' do
- expect { build_trace_chunk.flush! }.not_to raise_error
- end
- end
-
- context 'when the flush operation fails at first' do
- it 'retries reloads the chunk' do
- expect(build_trace_chunk)
- .to receive(:persist_data!)
- .and_raise(described_class::FailedToPersistDataError)
- .ordered
- expect(build_trace_chunk).to receive(:reset)
- .and_return(build_trace_chunk)
- .ordered
- expect(build_trace_chunk)
- .to receive(:persist_data!)
- .ordered
-
- build_trace_chunk.flush!
- end
- end
-
- context 'when the flush constatly fails' do
- before do
- allow(build_trace_chunk)
- .to receive(:persist_data!)
- .and_raise(described_class::FailedToPersistDataError)
- end
-
- it 'attems to reset the chunk but eventually fails too' do
- expect(build_trace_chunk).to receive(:reset)
- .and_return(build_trace_chunk)
-
- expect { build_trace_chunk.flush! }
- .to raise_error(described_class::FailedToPersistDataError)
- end
- end
- end
-
describe 'comparable build trace chunks' do
describe '#<=>' do
context 'when chunks are associated with different builds' do
diff --git a/spec/requests/api/graphql/instance_statistics_measurements_spec.rb b/spec/requests/api/graphql/instance_statistics_measurements_spec.rb
index b8cbe54534a..5d7dbcf2e3c 100644
--- a/spec/requests/api/graphql/instance_statistics_measurements_spec.rb
+++ b/spec/requests/api/graphql/instance_statistics_measurements_spec.rb
@@ -9,13 +9,16 @@ RSpec.describe 'InstanceStatisticsMeasurements' do
let!(:instance_statistics_measurement_1) { create(:instance_statistics_measurement, :project_count, recorded_at: 20.days.ago, count: 5) }
let!(:instance_statistics_measurement_2) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago, count: 10) }
- let(:query) { graphql_query_for(:instanceStatisticsMeasurements, 'identifier: PROJECTS', 'nodes { count }') }
+ let(:query) { graphql_query_for(:instanceStatisticsMeasurements, 'identifier: PROJECTS', 'nodes { count identifier }') }
before do
post_graphql(query, current_user: current_user)
end
it 'returns measurement objects' do
- expect(graphql_data.dig('instanceStatisticsMeasurements', 'nodes')).to eq([{ "count" => 10 }, { "count" => 5 }])
+ expect(graphql_data.dig('instanceStatisticsMeasurements', 'nodes')).to eq([
+ { "count" => 10, 'identifier' => 'PROJECTS' },
+ { "count" => 5, 'identifier' => 'PROJECTS' }
+ ])
end
end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index dc063f14bf5..493d13694ca 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -16,8 +16,8 @@ RSpec.describe API::ProjectSnippets do
end
describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do
- let(:snippet) { create(:project_snippet, :public, project: project) }
- let!(:user_agent_detail) { create(:user_agent_detail, subject: snippet) }
+ let_it_be(:snippet) { create(:project_snippet, :public, project: project) }
+ let_it_be(:user_agent_detail) { create(:user_agent_detail, subject: snippet) }
it 'exposes known attributes' do
get api("/projects/#{project.id}/snippets/#{snippet.id}/user_agent_detail", admin)
@@ -86,8 +86,8 @@ RSpec.describe API::ProjectSnippets do
end
describe 'GET /projects/:project_id/snippets/:id' do
- let_it_be(:user) { create(:user) }
let_it_be(:snippet) { create(:project_snippet, :public, :repository, project: project) }
+ let_it_be(:private_snippet) { create(:project_snippet, :private, project: project) }
it 'returns snippet json' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
@@ -116,6 +116,10 @@ RSpec.describe API::ProjectSnippets do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123", user) }
end
end
+
+ it_behaves_like 'project snippet access levels' do
+ let(:path) { "/projects/#{snippet.project.id}/snippets/#{snippet.id}" }
+ end
end
describe 'POST /projects/:project_id/snippets/' do
@@ -396,7 +400,8 @@ RSpec.describe API::ProjectSnippets do
end
describe 'GET /projects/:project_id/snippets/:id/raw' do
- let_it_be(:snippet) { create(:project_snippet, :repository, author: admin, project: project) }
+ let_it_be(:snippet) { create(:project_snippet, :repository, :public, author: admin, project: project) }
+ let_it_be(:private_snippet) { create(:project_snippet, :repository, :private, author: admin, project: project) }
it 'returns raw text' do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
@@ -412,6 +417,10 @@ RSpec.describe API::ProjectSnippets do
expect(json_response['message']).to eq('404 Snippet Not Found')
end
+ it_behaves_like 'project snippet access levels' do
+ let(:path) { "/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw" }
+ end
+
context 'with snippets disabled' do
it_behaves_like '403 response' do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123/raw", admin) }
@@ -428,16 +437,12 @@ RSpec.describe API::ProjectSnippets do
describe 'GET /projects/:project_id/snippets/:id/files/:ref/:file_path/raw' do
let_it_be(:snippet) { create(:project_snippet, :repository, author: admin, project: project) }
- context 'with no user' do
- it 'requires authentication' do
- get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/files/master/%2Egitattributes/raw", nil)
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
-
it_behaves_like 'raw snippet files' do
let(:api_path) { "/projects/#{snippet.project.id}/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
end
+
+ it_behaves_like 'project snippet access levels' do
+ let(:path) { "/projects/#{snippet.project.id}/snippets/#{snippet.id}/files/master/%2Egitattributes/raw" }
+ end
end
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index ea39a3ccd70..bfe19054fe6 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -130,6 +130,10 @@ RSpec.describe API::Snippets do
it_behaves_like 'raw snippet files' do
let(:api_path) { "/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
end
+
+ it_behaves_like 'snippet access with different users' do
+ let(:path) { "/snippets/#{snippet.id}/files/master/%2Egitattributes/raw" }
+ end
end
describe 'GET /snippets/:id' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 72dd22038c9..72a826d91e6 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1479,6 +1479,31 @@ RSpec.describe API::Users, :do_not_mock_admin_mode do
end
end
+ describe 'GET /user/:id/gpg_keys/:key_id' do
+ it 'returns 404 for non-existing user' do
+ get api('/users/0/gpg_keys/1')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns 404 for non-existing key' do
+ get api("/users/#{user.id}/gpg_keys/0")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 GPG Key Not Found')
+ end
+
+ it 'returns a single GPG key' do
+ user.gpg_keys << gpg_key
+
+ get api("/users/#{user.id}/gpg_keys/#{gpg_key.id}")
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['key']).to eq(gpg_key.key)
+ end
+ end
+
describe 'DELETE /user/:id/gpg_keys/:key_id' do
context 'when unauthenticated' do
it 'returns authentication error' do
diff --git a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
index 016a5dfd18b..fb6cdf55be3 100644
--- a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
+++ b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do
end
shared_examples 'successful creation' do
- it 'creates bridge jobs correctly' do
+ it 'creates bridge jobs correctly', :aggregate_failures do
pipeline = create_pipeline!
test = pipeline.statuses.find_by(name: 'test')
@@ -221,6 +221,65 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do
end
end
end
+
+ context 'when including configs from a project' do
+ context 'when specifying all attributes' do
+ let(:config) do
+ <<~YAML
+ test:
+ script: rspec
+ deploy:
+ variables:
+ CROSS: downstream
+ stage: deploy
+ trigger:
+ include:
+ - project: my-namespace/my-project
+ file: 'path/to/child.yml'
+ ref: 'master'
+ YAML
+ end
+
+ it_behaves_like 'successful creation' do
+ let(:expected_bridge_options) do
+ {
+ 'trigger' => {
+ 'include' => [
+ {
+ 'file' => 'path/to/child.yml',
+ 'project' => 'my-namespace/my-project',
+ 'ref' => 'master'
+ }
+ ]
+ }
+ }
+ end
+ end
+ end
+
+ context 'without specifying file' do
+ let(:config) do
+ <<~YAML
+ test:
+ script: rspec
+ deploy:
+ variables:
+ CROSS: downstream
+ stage: deploy
+ trigger:
+ include:
+ - project: my-namespace/my-project
+ ref: 'master'
+ YAML
+ end
+
+ it_behaves_like 'creation failure' do
+ let(:expected_error) do
+ /include config must specify the file where to fetch the config from/
+ end
+ end
+ end
+ end
end
def create_pipeline!
diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
index 1a8acf67176..0efe3311adb 100644
--- a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
@@ -7,7 +7,9 @@ RSpec.shared_examples 'raw snippet files' do
let(:file_path) { '%2Egitattributes' }
let(:ref) { 'master' }
- shared_examples 'not found' do
+ context 'with an invalid snippet ID' do
+ let(:snippet_id) { 'invalid' }
+
it 'returns 404' do
get api(api_path, user)
@@ -16,18 +18,6 @@ RSpec.shared_examples 'raw snippet files' do
end
end
- context 'when not authorized' do
- let(:user) { unauthorized_user }
-
- it_behaves_like 'not found'
- end
-
- context 'with an invalid snippet ID' do
- let(:snippet_id) { 'invalid' }
-
- it_behaves_like 'not found'
- end
-
context 'with valid params' do
it 'returns the raw file info' do
expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
@@ -263,3 +253,76 @@ RSpec.shared_examples 'snippet access with different users' do
end
end
end
+
+RSpec.shared_examples 'expected response status' do
+ it 'returns the correct response' do
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+end
+
+RSpec.shared_examples 'unauthenticated project snippet access' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:user) { nil }
+
+ where(:project_visibility, :snippet_visibility, :status) do
+ :public | :public | :ok
+ :public | :private | :not_found
+ :public | :internal | :not_found
+ :internal | :public | :not_found
+ :private | :public | :not_found
+ end
+
+ with_them do
+ it_behaves_like 'expected response status'
+ end
+end
+
+RSpec.shared_examples 'non-member project snippet access' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility, :snippet_visibility, :status) do
+ :public | :public | :ok
+ :public | :internal | :ok
+ :internal | :public | :ok
+ :public | :private | :not_found
+ :private | :public | :not_found
+ end
+
+ with_them do
+ it_behaves_like 'expected response status'
+ end
+end
+
+RSpec.shared_examples 'member project snippet access' do
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.add_guest(user)
+ end
+
+ where(:project_visibility, :snippet_visibility, :status) do
+ :public | :public | :ok
+ :public | :internal | :ok
+ :internal | :public | :ok
+ :public | :private | :ok
+ :private | :public | :ok
+ end
+
+ with_them do
+ it_behaves_like 'expected response status'
+ end
+end
+
+RSpec.shared_examples 'project snippet access levels' do
+ let(:project) { create(:project, project_visibility) }
+ let(:snippet) { create(:project_snippet, :repository, snippet_visibility, project: project) }
+
+ it_behaves_like 'unauthenticated project snippet access'
+
+ it_behaves_like 'non-member project snippet access'
+
+ it_behaves_like 'member project snippet access'
+end
diff --git a/spec/workers/analytics/instance_statistics/count_job_trigger_worker_spec.rb b/spec/workers/analytics/instance_statistics/count_job_trigger_worker_spec.rb
index 620900b3402..ff692d0eda6 100644
--- a/spec/workers/analytics/instance_statistics/count_job_trigger_worker_spec.rb
+++ b/spec/workers/analytics/instance_statistics/count_job_trigger_worker_spec.rb
@@ -14,16 +14,4 @@ RSpec.describe Analytics::InstanceStatistics::CountJobTriggerWorker do
expect(Analytics::InstanceStatistics::CounterJobWorker.jobs.count).to eq(expected_count)
end
end
-
- context 'when the `store_instance_statistics_measurements` feature flag is off' do
- before do
- stub_feature_flags(store_instance_statistics_measurements: false)
- end
-
- it 'does not trigger any CounterJobWorker job' do
- subject.perform
-
- expect(Analytics::InstanceStatistics::CounterJobWorker.jobs.count).to eq(0)
- end
- end
end
diff --git a/spec/workers/ci/build_trace_chunk_flush_worker_spec.rb b/spec/workers/ci/build_trace_chunk_flush_worker_spec.rb
index 352ad6d4cf6..8aac80a02be 100644
--- a/spec/workers/ci/build_trace_chunk_flush_worker_spec.rb
+++ b/spec/workers/ci/build_trace_chunk_flush_worker_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Ci::BuildTraceChunkFlushWorker do
described_class.new.perform(chunk.id)
- expect(chunk.reload).to be_persisted
+ expect(chunk.reload).to be_migrated
end
describe '#perform' do
@@ -24,7 +24,7 @@ RSpec.describe Ci::BuildTraceChunkFlushWorker do
it 'migrates build trace chunk to a safe store' do
subject
- expect(chunk.reload).to be_persisted
+ expect(chunk.reload).to be_migrated
end
end
end