diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-07 18:08:34 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-07 18:08:34 +0000 |
commit | 1850d48925997ccc467fe0cbe6144d1d67fbdb55 (patch) | |
tree | 1c2081b4f9b986e9017761db5b6915ff3db8de79 | |
parent | 1d3a583ac1d4affb8871a76121815b1c104ef93a (diff) | |
download | gitlab-ce-1850d48925997ccc467fe0cbe6144d1d67fbdb55.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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 |