summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue67
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue10
-rw-r--r--app/assets/javascripts/projects/project_new.js7
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js11
-rw-r--r--app/controllers/admin/application_settings_controller.rb4
-rw-r--r--app/helpers/tab_helper.rb6
-rw-r--r--app/models/concerns/strip_attribute.rb3
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/models/user.rb12
-rw-r--r--app/models/users_statistics.rb36
-rw-r--r--app/services/groups/import_export/import_service.rb2
-rw-r--r--app/views/admin/dashboard/stats.html.haml63
-rw-r--r--app/views/groups/dependency_proxies/show.html.haml3
-rw-r--r--app/views/projects/_new_project_fields.html.haml7
-rw-r--r--app/views/users/_overview.html.haml16
-rw-r--r--app/workers/concerns/application_worker.rb6
-rw-r--r--app/workers/concerns/limited_capacity/worker.rb2
-rw-r--r--config/feature_flags/development/configure_iac_scanning_via_mr.yml2
-rw-r--r--db/migrate/20211103062728_add_with_highest_role_minimal_access_to_users_statistics.rb7
-rw-r--r--db/schema_migrations/202111030627281
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/dependencies.md2
-rw-r--r--doc/ci/yaml/includes.md5
-rw-r--r--doc/user/application_security/iac_scanning/index.md98
-rw-r--r--doc/user/application_security/index.md27
-rw-r--r--doc/user/profile/index.md12
-rw-r--r--doc/user/project/settings/index.md4
-rw-r--r--lib/api/terraform/modules/v1/packages.rb3
-rw-r--r--lib/gitlab/ci/build/context/base.rb22
-rw-r--r--lib/gitlab/ci/build/rules/rule/clause/exists.rb12
-rw-r--r--lib/gitlab/ci/config/entry/include/rules/rule.rb4
-rw-r--r--lib/gitlab/ci/config/external/context.rb14
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml6
-rw-r--r--lib/gitlab/sidekiq_status.rb6
-rw-r--r--locale/gitlab.pot20
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb7
-rw-r--r--spec/factories/user_highest_roles.rb10
-rw-r--r--spec/features/profiles/user_visits_profile_spec.rb8
-rw-r--r--spec/frontend/members/mock_data.js2
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/app_spec.js99
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js22
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/mock_data.js3
-rw-r--r--spec/helpers/tab_helper_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/rules_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb78
-rw-r--r--spec/models/merge_request_spec.rb6
-rw-r--r--spec/models/user_spec.rb27
-rw-r--r--spec/models/users_statistics_spec.rb16
-rw-r--r--spec/requests/api/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/terraform/modules/v1/packages_spec.rb17
-rw-r--r--spec/requests/import/gitlab_groups_controller_spec.rb2
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb4
-rw-r--r--spec/services/groups/import_export/import_service_spec.rb6
-rw-r--r--spec/support/shared_examples/requests/self_monitoring_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/workers/self_monitoring_shared_examples.rb2
-rw-r--r--spec/workers/concerns/application_worker_spec.rb44
59 files changed, 785 insertions, 171 deletions
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
index 13e5d7c3019..71e8cf4f634 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
@@ -1,8 +1,16 @@
<script>
-import { GlAlert, GlFormGroup, GlFormInputGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlFormGroup,
+ GlFormInputGroup,
+ GlSkeletonLoader,
+ GlSprintf,
+ GlEmptyState,
+} from '@gitlab/ui';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
import {
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
DEPENDENCY_PROXY_DOCS_PATH,
@@ -13,15 +21,17 @@ import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency
export default {
components: {
- GlFormGroup,
GlAlert,
+ GlEmptyState,
+ GlFormGroup,
GlFormInputGroup,
+ GlSkeletonLoader,
GlSprintf,
ClipboardButton,
TitleArea,
- GlSkeletonLoader,
+ ManifestsList,
},
- inject: ['groupPath', 'dependencyProxyAvailable'],
+ inject: ['groupPath', 'dependencyProxyAvailable', 'noManifestsIllustration'],
i18n: {
proxyNotAvailableText: s__(
'DependencyProxy|Dependency Proxy feature is limited to public groups for now.',
@@ -33,6 +43,7 @@ export default {
copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
pageTitle: s__('DependencyProxy|Dependency Proxy'),
+ noManifestTitle: s__('DependencyProxy|There are no images in the cache'),
},
data() {
return {
@@ -46,7 +57,7 @@ export default {
return !this.dependencyProxyAvailable;
},
variables() {
- return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
+ return this.queryVariables;
},
},
},
@@ -62,6 +73,38 @@ export default {
dependencyProxyEnabled() {
return this.group?.dependencyProxySetting?.enabled;
},
+ queryVariables() {
+ return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
+ },
+ pageInfo() {
+ return this.group.dependencyProxyManifests.pageInfo;
+ },
+ manifests() {
+ return this.group.dependencyProxyManifests.nodes;
+ },
+ },
+ methods: {
+ fetchNextPage() {
+ this.fetchMore({
+ first: GRAPHQL_PAGE_SIZE,
+ after: this.pageInfo?.endCursor,
+ });
+ },
+ fetchPreviousPage() {
+ this.fetchMore({
+ first: null,
+ last: GRAPHQL_PAGE_SIZE,
+ before: this.pageInfo?.startCursor,
+ });
+ },
+ fetchMore(variables) {
+ this.$apollo.queries.group.fetchMore({
+ variables: { ...this.queryVariables, ...variables },
+ updateQuery(_, { fetchMoreResult }) {
+ return fetchMoreResult;
+ },
+ });
+ },
},
};
</script>
@@ -103,6 +146,20 @@ export default {
</span>
</template>
</gl-form-group>
+
+ <manifests-list
+ v-if="manifests && manifests.length"
+ :manifests="manifests"
+ :pagination="pageInfo"
+ @prev-page="fetchPreviousPage"
+ @next-page="fetchNextPage"
+ />
+
+ <gl-empty-state
+ v-else
+ :svg-path="noManifestsIllustration"
+ :title="$options.i18n.noManifestTitle"
+ />
</div>
<gl-alert v-else :dismissible="false" data-testid="proxy-disabled">
{{ $options.i18n.proxyDisabledText }}
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue
index f3ac017268b..005c8feea3a 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue
@@ -21,13 +21,18 @@ export default {
},
},
i18n: {
- listTitle: s__('DependencyProxy|Manifest list'),
+ listTitle: s__('DependencyProxy|Image list'),
+ },
+ computed: {
+ showPagination() {
+ return this.pagination.hasNextPage || this.pagination.hasPreviousPage;
+ },
},
};
</script>
<template>
- <div class="gl-mt-5">
+ <div class="gl-mt-6">
<h3 class="gl-font-base">{{ $options.i18n.listTitle }}</h3>
<div
class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column"
@@ -36,6 +41,7 @@ export default {
</div>
<div class="gl-display-flex gl-justify-content-center">
<gl-keyset-pagination
+ v-if="showPagination"
v-bind="pagination"
class="gl-mt-3"
@prev="$emit('prev-page')"
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index b350db0c838..8d71a3dab68 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -43,6 +43,8 @@ const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingPr
};
const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
+ const specialRepo = document.querySelector('.js-user-readme-repo');
+
// eslint-disable-next-line @gitlab/no-global-event-off
$projectNameInput.off('keyup change').on('keyup change', () => {
onProjectNameChange($projectNameInput, $projectPathInput);
@@ -54,6 +56,11 @@ const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
$projectPathInput.off('keyup change').on('keyup change', () => {
onProjectPathChange($projectNameInput, $projectPathInput, hasUserDefinedProjectName);
hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
+
+ specialRepo.classList.toggle(
+ 'gl-display-none',
+ $projectPathInput.val() !== $projectPathInput.data('username'),
+ );
});
};
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index d0a7b0d0b89..9c80506549e 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -37,10 +37,13 @@ export const SAST_IAC_SHORT_NAME = s__('ciReport|IaC Scanning');
export const SAST_IAC_DESCRIPTION = __(
'Analyze your infrastructure as code configuration files for known vulnerabilities.',
);
-export const SAST_IAC_HELP_PATH = helpPagePath('user/application_security/sast/index');
-export const SAST_IAC_CONFIG_HELP_PATH = helpPagePath('user/application_security/sast/index', {
- anchor: 'configuration',
-});
+export const SAST_IAC_HELP_PATH = helpPagePath('user/application_security/iac_scanning/index');
+export const SAST_IAC_CONFIG_HELP_PATH = helpPagePath(
+ 'user/application_security/iac_scanning/index',
+ {
+ anchor: 'configuration',
+ },
+);
export const DAST_NAME = __('Dynamic Application Security Testing (DAST)');
export const DAST_SHORT_NAME = s__('ciReport|DAST');
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 8039fac02ec..8644d95b96c 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -98,7 +98,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
# Specs are in spec/requests/self_monitoring_project_spec.rb
def create_self_monitoring_project
- job_id = SelfMonitoringProjectCreateWorker.perform_async # rubocop:disable CodeReuse/Worker
+ job_id = SelfMonitoringProjectCreateWorker.with_status.perform_async # rubocop:disable CodeReuse/Worker
render status: :accepted, json: {
job_id: job_id,
@@ -137,7 +137,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
# Specs are in spec/requests/self_monitoring_project_spec.rb
def delete_self_monitoring_project
- job_id = SelfMonitoringProjectDeleteWorker.perform_async # rubocop:disable CodeReuse/Worker
+ job_id = SelfMonitoringProjectDeleteWorker.with_status.perform_async # rubocop:disable CodeReuse/Worker
render status: :accepted, json: {
job_id: job_id,
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index d1046b7b668..e53e35baac3 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -33,7 +33,6 @@ module TabHelper
# :item_active - Overrides the default state focing the "active" css classes (optional).
#
def gl_tab_link_to(name = nil, options = {}, html_options = {}, &block)
- tab_class = 'nav-item'
link_classes = %w[nav-link gl-tab-nav-item]
active_link_classes = %w[active gl-tab-nav-item-active gl-tab-nav-item-active-indigo]
@@ -52,6 +51,8 @@ module TabHelper
end
html_options = html_options.except(:item_active)
+ extra_tab_classes = html_options.delete(:tab_class)
+ tab_class = %w[nav-item].push(*extra_tab_classes)
content_tag(:li, class: tab_class, role: 'presentation') do
if block_given?
@@ -215,6 +216,7 @@ def gl_tab_counter_badge(count, html_options = {})
badge_classes = %w[badge badge-muted badge-pill gl-badge sm gl-tab-counter-badge]
content_tag(:span,
count,
- class: [*html_options[:class], badge_classes].join(' ')
+ class: [*html_options[:class], badge_classes].join(' '),
+ data: html_options[:data]
)
end
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
index 1c433a3275e..817a4465f91 100644
--- a/app/models/concerns/strip_attribute.rb
+++ b/app/models/concerns/strip_attribute.rb
@@ -2,7 +2,8 @@
# == Strip Attribute module
#
-# Contains functionality to clean attributes before validation
+# Contains functionality to remove leading and trailing
+# whitespace from the attribute before validation
#
# Usage:
#
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f2f9b527cc0..d220f4b837a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -662,7 +662,7 @@ class MergeRequest < ApplicationRecord
# updates `merge_jid` with the MergeWorker#jid.
# This helps tracking enqueued and ongoing merge jobs.
def merge_async(user_id, params)
- jid = MergeWorker.perform_async(id, user_id, params.to_h)
+ jid = MergeWorker.with_status.perform_async(id, user_id, params.to_h)
update_column(:merge_jid, jid)
# merge_ongoing? depends on merge_jid
@@ -681,7 +681,7 @@ class MergeRequest < ApplicationRecord
# attribute is set *and* that the sidekiq job is still running. So a JID
# for a completed RebaseWorker is equivalent to a nil JID.
jid = Sidekiq::Worker.skipping_transaction_check do
- RebaseWorker.perform_async(id, user_id, skip_ci)
+ RebaseWorker.with_status.perform_async(id, user_id, skip_ci)
end
update_column(:rebase_jid, jid)
diff --git a/app/models/user.rb b/app/models/user.rb
index 2ca7909ebcb..394c64db3bf 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1992,6 +1992,18 @@ class User < ApplicationRecord
saved
end
+ def user_project
+ strong_memoize(:user_project) do
+ personal_projects.find_by(path: username, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ def user_readme
+ strong_memoize(:user_readme) do
+ user_project&.repository&.readme
+ end
+ end
+
protected
# override, from Devise::Validatable
diff --git a/app/models/users_statistics.rb b/app/models/users_statistics.rb
index a903541f69a..a314ae8920b 100644
--- a/app/models/users_statistics.rb
+++ b/app/models/users_statistics.rb
@@ -3,12 +3,6 @@
class UsersStatistics < ApplicationRecord
scope :order_created_at_desc, -> { order(created_at: :desc) }
- class << self
- def latest
- order_created_at_desc.first
- end
- end
-
def active
[
without_groups_and_projects,
@@ -26,30 +20,26 @@ class UsersStatistics < ApplicationRecord
end
class << self
- def create_current_stats!
- stats_by_role = highest_role_stats
+ def latest
+ order_created_at_desc.first
+ end
- create!(
- without_groups_and_projects: without_groups_and_projects_stats,
- with_highest_role_guest: stats_by_role[:guest],
- with_highest_role_reporter: stats_by_role[:reporter],
- with_highest_role_developer: stats_by_role[:developer],
- with_highest_role_maintainer: stats_by_role[:maintainer],
- with_highest_role_owner: stats_by_role[:owner],
- bots: bot_stats,
- blocked: blocked_stats
- )
+ def create_current_stats!
+ create!(highest_role_stats)
end
private
def highest_role_stats
{
- owner: batch_count_for_access_level(Gitlab::Access::OWNER),
- maintainer: batch_count_for_access_level(Gitlab::Access::MAINTAINER),
- developer: batch_count_for_access_level(Gitlab::Access::DEVELOPER),
- reporter: batch_count_for_access_level(Gitlab::Access::REPORTER),
- guest: batch_count_for_access_level(Gitlab::Access::GUEST)
+ without_groups_and_projects: without_groups_and_projects_stats,
+ with_highest_role_guest: batch_count_for_access_level(Gitlab::Access::GUEST),
+ with_highest_role_reporter: batch_count_for_access_level(Gitlab::Access::REPORTER),
+ with_highest_role_developer: batch_count_for_access_level(Gitlab::Access::DEVELOPER),
+ with_highest_role_maintainer: batch_count_for_access_level(Gitlab::Access::MAINTAINER),
+ with_highest_role_owner: batch_count_for_access_level(Gitlab::Access::OWNER),
+ bots: bot_stats,
+ blocked: blocked_stats
}
end
diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb
index f9db552f743..c8c2124078d 100644
--- a/app/services/groups/import_export/import_service.rb
+++ b/app/services/groups/import_export/import_service.rb
@@ -14,7 +14,7 @@ module Groups
def async_execute
group_import_state = GroupImportState.safe_find_or_create_by!(group: group, user: current_user)
- jid = GroupImportWorker.perform_async(current_user.id, group.id)
+ jid = GroupImportWorker.with_status.perform_async(current_user.id, group.id)
if jid.present?
group_import_state.update!(jid: jid)
diff --git a/app/views/admin/dashboard/stats.html.haml b/app/views/admin/dashboard/stats.html.haml
index b98d11b734b..e0701812ba3 100644
--- a/app/views/admin/dashboard/stats.html.haml
+++ b/app/views/admin/dashboard/stats.html.haml
@@ -1,74 +1,75 @@
- page_title s_('AdminArea|Users statistics')
-%h3.my-4
+%h3.gl-my-6
= s_('AdminArea|Users statistics')
%table.table.gl-text-gray-500
%tr
- %td.p-3
+ %td.gl-p-5!
= s_('AdminArea|Users without a Group and Project')
= render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
- %td.p-3.text-right
- = @users_statistics&.without_groups_and_projects.to_i
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.without_groups_and_projects
+ = render_if_exists 'admin/dashboard/minimal_access_stats_row', users_statistics: @users_statistics
%tr
- %td.p-3
+ %td.gl-p-5!
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Guest')
= render_if_exists 'admin/dashboard/included_free_in_license_tooltip'
- %td.p-3.text-right
- = @users_statistics&.with_highest_role_guest.to_i
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_guest
%tr
- %td.p-3
+ %td.gl-p-5!
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Reporter')
- %td.p-3.text-right
- = @users_statistics&.with_highest_role_reporter.to_i
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_reporter
%tr
- %td.p-3
+ %td.gl-p-5!
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Developer')
- %td.p-3.text-right
- = @users_statistics&.with_highest_role_developer.to_i
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_developer
%tr
- %td.p-3
+ %td.gl-p-5!
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Maintainer')
- %td.p-3.text-right
- = @users_statistics&.with_highest_role_maintainer.to_i
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_maintainer
%tr
- %td.p-3
+ %td.gl-p-5!
= s_('AdminArea|Users with highest role')
%strong
= s_('AdminArea|Owner')
- %td.p-3.text-right
- = @users_statistics&.with_highest_role_owner.to_i
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.with_highest_role_owner
%tr
- %td.p-3
+ %td.gl-p-5!
= s_('AdminArea|Bots')
- %td.p-3.text-right
- = @users_statistics&.bots.to_i
+ %td.gl-text-right{ class: 'gl-p-5!' }
+ = @users_statistics&.bots
= render_if_exists 'admin/dashboard/billable_users_row'
%tr.bg-gray-light.gl-text-gray-900
- %td.p-3
+ %td.gl-p-5!
%strong
= s_('AdminArea|Active users')
- %td.p-3.text-right
+ %td.gl-text-right{ class: 'gl-p-5!' }
%strong
- = @users_statistics&.active.to_i
+ = @users_statistics&.active
%tr.bg-gray-light.gl-text-gray-900
- %td.p-3
+ %td.gl-p-5!
%strong
= s_('AdminArea|Blocked users')
- %td.p-3.text-right
+ %td.gl-text-right{ class: 'gl-p-5!' }
%strong
- = @users_statistics&.blocked.to_i
+ = @users_statistics&.blocked
%tr.bg-gray-light.gl-text-gray-900
- %td.p-3
+ %td.gl-p-5!
%strong
= s_('AdminArea|Total users')
- %td.p-3.text-right
+ %td.gl-text-right{ class: 'gl-p-5!' }
%strong
- = @users_statistics&.total.to_i
+ = @users_statistics&.total
diff --git a/app/views/groups/dependency_proxies/show.html.haml b/app/views/groups/dependency_proxies/show.html.haml
index 83ab97f8e4f..47caec717af 100644
--- a/app/views/groups/dependency_proxies/show.html.haml
+++ b/app/views/groups/dependency_proxies/show.html.haml
@@ -3,4 +3,5 @@
- dependency_proxy_available = Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true) || @group.public?
#js-dependency-proxy{ data: { group_path: @group.full_path,
- dependency_proxy_available: dependency_proxy_available.to_s } }
+ dependency_proxy_available: dependency_proxy_available.to_s,
+ no_manifests_illustration: image_path('illustrations/docker-empty-state.svg') } }
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 5e20e21e314..c21240b340c 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -40,12 +40,17 @@
.form-group.project-path.col-sm-6
= f.label :path, class: 'label-bold' do
%span= _("Project slug")
- = f.text_field :path, placeholder: "my-awesome-project", class: "form-control gl-form-input", required: true, aria: { required: true }
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control gl-form-input", required: true, aria: { required: true }, data: { username: current_user.username }
- if current_user.can_create_group?
.form-text.text-muted
- link_start_group_path = '<a href="%{path}">' % { path: new_group_path }
- project_tip = s_('ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}') % { link_start: link_start_group_path, link_end: '</a>' }
= project_tip.html_safe
+.gl-alert.gl-alert-success.gl-mb-4.gl-display-none.js-user-readme-repo
+ = sprite_icon('check-circle', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-body
+ - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/profile/index', anchor: 'user-profile-readme') }
+ = html_escape(_('%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}')) % { project_path: "<strong>#{current_user.username} / #{current_user.username}</strong>".html_safe, help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
.form-group
= f.label :description, class: 'label-bold' do
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
index 8d33628da41..eb9caa796b5 100644
--- a/app/views/users/_overview.html.haml
+++ b/app/views/users/_overview.html.haml
@@ -9,6 +9,22 @@
%a.js-retry-load{ href: '#' }
= s_('UserProfile|Retry')
.user-calendar-activities
+- if @user.user_readme
+ .row
+ .col-12.gl-my-6
+ .gl-border-gray-100.gl-border-1.gl-border-solid.gl-rounded-small.gl-py-4.gl-px-6
+ .gl-display-flex
+ %ol.breadcrumb.gl-breadcrumb-list.gl-mb-4
+ %li.breadcrumb-item.gl-breadcrumb-item
+ = link_to @user.username, project_path(@user.user_project)
+ %span.gl-breadcrumb-separator
+ = sprite_icon("chevron-right", size: 16)
+ %li.breadcrumb-item.gl-breadcrumb-item
+ = link_to @user.user_readme.path, @user.user_project.readme_url
+ - if current_user == @user
+ .gl-ml-auto
+ = link_to _('Edit'), edit_blob_path(@user.user_project, @user.user_project.default_branch, @user.user_readme.path)
+ = render 'projects/blob/viewer', viewer: @user.user_readme.rich_viewer, load_async: false
.row
%div{ class: activity_pane_class }
- if can?(current_user, :read_cross_project)
diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb
index 1d55cd45840..03a0b5fae00 100644
--- a/app/workers/concerns/application_worker.rb
+++ b/app/workers/concerns/application_worker.rb
@@ -55,6 +55,12 @@ module ApplicationWorker
subclass.after_set_class_attribute { subclass.set_queue }
end
+ def with_status
+ status_from_class = self.sidekiq_options_hash['status_expiration']
+
+ set(status_expiration: status_from_class || Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
+ end
+
def generated_queue_name
Gitlab::SidekiqConfig::WorkerRouter.queue_name_from_worker_name(self)
end
diff --git a/app/workers/concerns/limited_capacity/worker.rb b/app/workers/concerns/limited_capacity/worker.rb
index b4cdfda680f..bcedb4efcc0 100644
--- a/app/workers/concerns/limited_capacity/worker.rb
+++ b/app/workers/concerns/limited_capacity/worker.rb
@@ -47,7 +47,7 @@ module LimitedCapacity
# would be occupied by a job that will be performed in the distant future.
# We let the cron worker enqueue new jobs, this could be seen as our retry and
# back off mechanism because the job might fail again if executed immediately.
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, status_expiration: Gitlab::SidekiqStatus::DEFAULT_EXPIRATION
deduplicate :none
end
diff --git a/config/feature_flags/development/configure_iac_scanning_via_mr.yml b/config/feature_flags/development/configure_iac_scanning_via_mr.yml
index 0e3e921058c..cef22644b8f 100644
--- a/config/feature_flags/development/configure_iac_scanning_via_mr.yml
+++ b/config/feature_flags/development/configure_iac_scanning_via_mr.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343966
milestone: '14.5'
type: development
group: group::static analysis
-default_enabled: false
+default_enabled: true
diff --git a/db/migrate/20211103062728_add_with_highest_role_minimal_access_to_users_statistics.rb b/db/migrate/20211103062728_add_with_highest_role_minimal_access_to_users_statistics.rb
new file mode 100644
index 00000000000..43cd7afbf06
--- /dev/null
+++ b/db/migrate/20211103062728_add_with_highest_role_minimal_access_to_users_statistics.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddWithHighestRoleMinimalAccessToUsersStatistics < Gitlab::Database::Migration[1.0]
+ def change
+ add_column :users_statistics, :with_highest_role_minimal_access, :integer, null: false, default: 0
+ end
+end
diff --git a/db/schema_migrations/20211103062728 b/db/schema_migrations/20211103062728
new file mode 100644
index 00000000000..45bb2fcda65
--- /dev/null
+++ b/db/schema_migrations/20211103062728
@@ -0,0 +1 @@
+a22322122144f28306b3b38dbe50b3465ad623c389f8bfe6fa97a0f71b1c7c21 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index f74fa143972..c2bf310d3c6 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20289,7 +20289,8 @@ CREATE TABLE users_statistics (
with_highest_role_maintainer integer DEFAULT 0 NOT NULL,
with_highest_role_owner integer DEFAULT 0 NOT NULL,
bots integer DEFAULT 0 NOT NULL,
- blocked integer DEFAULT 0 NOT NULL
+ blocked integer DEFAULT 0 NOT NULL,
+ with_highest_role_minimal_access integer DEFAULT 0 NOT NULL
);
CREATE SEQUENCE users_statistics_id_seq
diff --git a/doc/api/dependencies.md b/doc/api/dependencies.md
index b421a32b88a..1ecb78aa26d 100644
--- a/doc/api/dependencies.md
+++ b/doc/api/dependencies.md
@@ -34,7 +34,7 @@ GET /projects/:id/dependencies?package_manager=yarn,bundler
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding). |
-| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `conan`, `go`, `maven`, `npm`, `nuget`, `pip`, `yarn`, or `sbt`. |
+| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `conan`, `go`, `gradle`, `maven`, `npm`, `nuget`, `pip`, `pipenv`, `yarn`, `sbt`, or `setuptools`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/dependencies"
diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md
index a69a4b31324..1a0afc94eb3 100644
--- a/doc/ci/yaml/includes.md
+++ b/doc/ci/yaml/includes.md
@@ -269,10 +269,11 @@ see this [CI/CD variable demo](https://youtu.be/4XR8gw3Pkos).
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) GitLab 14.3.
> - [Feature flag `ci_include_rules` removed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.4.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.4.
+> - [Support for `exists` keyword added](https://gitlab.com/gitlab-org/gitlab/-/issues/341511) in GitLab 14.5.
You can use [`rules`](index.md#rules) with `include` to conditionally include other configuration files.
-You can only use [`if` rules](index.md#rulesif) in `include`, and only with [certain variables](#use-variables-with-include).
-`rules` keywords such as `changes` and `exists` are not supported.
+You can only use [`if` rules](index.md#rulesif) and [`exists` rules](index.md#rulesexists) in `include`, and only with
+[certain variables](#use-variables-with-include). `rules` keyword `changes` is not supported.
```yaml
include:
diff --git a/doc/user/application_security/iac_scanning/index.md b/doc/user/application_security/iac_scanning/index.md
new file mode 100644
index 00000000000..a58a00a869b
--- /dev/null
+++ b/doc/user/application_security/iac_scanning/index.md
@@ -0,0 +1,98 @@
+---
+stage: Secure
+group: Static Analysis
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Infrastructure as Code (IaC) Scanning
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6655) in GitLab 14.5.
+
+Infrastructure as Code (IaC) Scanning scans your IaC configuration files for known vulnerabilities.
+
+Currently, IaC scanning supports configuration files for Terraform, Ansible, AWS CloudFormation, and Kubernetes.
+
+## Requirements
+
+To run IaC scanning jobs, by default, you need GitLab Runner with the
+[`docker`](https://docs.gitlab.com/runner/executors/docker.html) or
+[`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor.
+If you're using the shared runners on GitLab.com, this is enabled by default.
+
+WARNING:
+Our IaC scanning jobs require a Linux container type. Windows containers are not yet supported.
+
+WARNING:
+If you use your own runners, make sure the Docker version installed
+is **not** `19.03.0`. See [troubleshooting information](../sast/index.md#error-response-from-daemon-error-processing-tar-file-docker-tar-relocation-error) for details.
+
+## Supported languages and frameworks
+
+GitLab IaC scanning supports a variety of IaC configuration files. Our IaC security scanners also feature automatic language detection which works even for mixed-language projects. If any supported configuration files are detected in project source code we automatically run the appropriate IaC analyzers.
+
+| Configuration File Type | Scan tool | Introduced in GitLab Version |
+|------------------------------------------|----------------------------------|-------------------------------|
+| Ansible | [kics](https://kics.io/) | 14.5 |
+| AWS CloudFormation | [kics](https://kics.io/) | 14.5 |
+| Kubernetes | [kics](https://kics.io/) | 14.5 |
+| Terraform | [kics](https://kics.io/) | 14.5 |
+
+### Making IaC analyzers available to all GitLab tiers
+
+All open source (OSS) analyzers are availibile with the GitLab Free tier. Future propietary analyzers may be restricted to higher tiers.
+
+#### Summary of features per tier
+
+Different features are available in different [GitLab tiers](https://about.gitlab.com/pricing/),
+as shown in the following table:
+
+| Capability | In Free | In Ultimate |
+|:---------------------------------------------------------------------------------------|:--------------------|:-------------------|
+| [Configure IaC Scanners](#configuration) v | **{check-circle}** | **{check-circle}** |
+| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** |
+| Presentation of JSON Report in Merge Request | **{dotted-circle}** | **{check-circle}** |
+| [Address vulnerabilities](../../application_security/vulnerabilities/index.md) | **{dotted-circle}** | **{check-circle}** |
+| [Access to Security Dashboard](../../application_security/security_dashboard/index.md) | **{dotted-circle}** | **{check-circle}** |
+
+## Contribute your scanner
+
+The [Security Scanner Integration](../../../development/integrations/secure.md) documentation explains how to integrate other security scanners into GitLab.
+
+## Configuration
+
+To configure IaC Scanning for a project you can:
+
+- [Configure IaC Scanning manually](#configure-iac-scanning-manually)
+- [Enable IaC Scanning via an automatic merge request](#enable-iac-scanning-via-an-automatic-merge-request)
+
+### Configure IaC Scanning manually
+
+To enable IaC Scanning you must [include](../../../ci/yaml/index.md#includetemplate) the
+[`SAST-IaC.latest.gitlab-ci.yml template`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST-IaC.latest.gitlab-ci.yml) provided as part of your GitLab installation.
+
+The included template creates IaC scanning jobs in your CI/CD pipeline and scans
+your project's configuration files for possible vulnerabilities.
+
+The results are saved as a
+[SAST report artifact](../../../ci/yaml/index.md#artifactsreportssast)
+that you can download and analyze.
+
+### Enable IaC Scanning via an automatic merge request
+
+To enable IaC Scanning in a project, you can create a merge request
+from the Security Configuration page:
+
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Security & Compliance > Configuration**.
+1. In the **Infrastructure as Code (IaC) Scanning** row, select **Configure via Merge Request**.
+
+This automatically creates a merge request with the changes necessary to enable IaC Scanning
+that you can review and merge to complete the configuration.
+
+## Reports JSON format
+
+The IaC tool emits a JSON report file in the existing SAST report format. For more information, see the
+[schema for this report](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json).
+
+The JSON report file can be downloaded from the CI pipelines page, or the
+pipelines tab on merge requests by [setting `artifacts: paths`](../../../ci/yaml/index.md#artifactspaths) to `gl-sast-report.json`. For more information see [Downloading artifacts](../../../ci/pipelines/job_artifacts.md).
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 35c027af670..91cfeae0c71 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -31,19 +31,20 @@ For an overview of GitLab application security, see [Shifting Security Left](htt
GitLab uses the following tools to scan and report known vulnerabilities found in your project.
-| Secure scanning tool | Description |
-|:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------|
-| [Container Scanning](container_scanning/index.md) | Scan Docker containers for known vulnerabilities. |
-| [Dependency List](dependency_list/index.md) | View your project's dependencies and their known vulnerabilities. |
-| [Dependency Scanning](dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. |
-| [Dynamic Application Security Testing (DAST)](dast/index.md) | Analyze running web applications for known vulnerabilities. |
-| [DAST API](dast_api/index.md) | Analyze running web APIs for known vulnerabilities. |
-| [API fuzzing](api_fuzzing/index.md) | Find unknown bugs and vulnerabilities in web APIs with fuzzing. |
-| [Secret Detection](secret_detection/index.md) | Analyze Git history for leaked secrets. |
-| [Security Dashboard](security_dashboard/index.md) | View vulnerabilities in all your projects and groups. |
-| [Static Application Security Testing (SAST)](sast/index.md) | Analyze source code for known vulnerabilities. |
-| [Coverage fuzzing](coverage_fuzzing/index.md) | Find unknown bugs and vulnerabilities with coverage-guided fuzzing. |
-| [Cluster Image Scanning](cluster_image_scanning/index.md) | Scan Kubernetes clusters for known vulnerabilities. |
+| Secure scanning tool | Description |
+| :------------------------------------------------------------- | :------------------------------------------------------------------ |
+| [Container Scanning](container_scanning/index.md) | Scan Docker containers for known vulnerabilities. |
+| [Dependency List](dependency_list/index.md) | View your project's dependencies and their known vulnerabilities. |
+| [Dependency Scanning](dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. |
+| [Dynamic Application Security Testing (DAST)](dast/index.md) | Analyze running web applications for known vulnerabilities. |
+| [DAST API](dast_api/index.md) | Analyze running web APIs for known vulnerabilities. |
+| [API fuzzing](api_fuzzing/index.md) | Find unknown bugs and vulnerabilities in web APIs with fuzzing. |
+| [Secret Detection](secret_detection/index.md) | Analyze Git history for leaked secrets. |
+| [Security Dashboard](security_dashboard/index.md) | View vulnerabilities in all your projects and groups. |
+| [Static Application Security Testing (SAST)](sast/index.md) | Analyze source code for known vulnerabilities. |
+| [Infrastructure as Code (IaC) Scanning](iac_scanning/index.md) | Analyze your IaC coniguration files for known vulnerabilities. |
+| [Coverage fuzzing](coverage_fuzzing/index.md) | Find unknown bugs and vulnerabilities with coverage-guided fuzzing. |
+| [Cluster Image Scanning](cluster_image_scanning/index.md) | Scan Kubernetes clusters for known vulnerabilities. |
## Security scanning with Auto DevOps
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 94953a48ba0..47fcf7577cd 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -100,6 +100,18 @@ When visiting the public page of a user, you can only see the projects which you
If the [public level is restricted](../admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels),
user profiles are only visible to signed-in users.
+## User profile README
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232157) in GitLab 14.5.
+
+You can add a README section to your profile that can include more information and formatting than
+your profile's bio.
+
+To add a README to your profile:
+
+1. Create a new public project with the same name as your GitLab username.
+1. Create a README file inside this project. The file can be any valid [README or index file](../project/repository/index.md#readme-and-index-files).
+
## Add external accounts to your user profile page
You can add links to certain other external accounts you might have, like Skype and Twitter.
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index b2b656ac243..e2ce8c12185 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -177,7 +177,9 @@ audit trail:
include: # Execute individual project's configuration (if project contains .gitlab-ci.yml)
project: '$CI_PROJECT_PATH'
file: '$CI_CONFIG_PATH'
- ref: '$CI_COMMIT_REF_NAME' # Must be defined or MR pipelines always use the use default branch.
+ ref: '$CI_COMMIT_REF_NAME' # Must be defined or MR pipelines always use the use default branch
+ rules:
+ - exists: '$CI_CONFIG_PATH'
```
##### Ensure compliance jobs are always run
diff --git a/lib/api/terraform/modules/v1/packages.rb b/lib/api/terraform/modules/v1/packages.rb
index aa59b6a4fee..ad5a4ae7ea6 100644
--- a/lib/api/terraform/modules/v1/packages.rb
+++ b/lib/api/terraform/modules/v1/packages.rb
@@ -46,7 +46,8 @@ module API
def finder_params
{
package_type: :terraform_module,
- package_name: "#{params[:module_name]}/#{params[:module_system]}"
+ package_name: "#{params[:module_name]}/#{params[:module_system]}",
+ exact_name: true
}.tap do |finder_params|
finder_params[:package_version] = params[:module_version] if params.has_key?(:module_version)
end
diff --git a/lib/gitlab/ci/build/context/base.rb b/lib/gitlab/ci/build/context/base.rb
index 02b97ea76e9..c7ea7c78e2f 100644
--- a/lib/gitlab/ci/build/context/base.rb
+++ b/lib/gitlab/ci/build/context/base.rb
@@ -5,6 +5,8 @@ module Gitlab
module Build
module Context
class Base
+ include Gitlab::Utils::StrongMemoize
+
attr_reader :pipeline
def initialize(pipeline)
@@ -15,6 +17,26 @@ module Gitlab
raise NotImplementedError
end
+ def project
+ pipeline.project
+ end
+
+ def sha
+ pipeline.sha
+ end
+
+ def top_level_worktree_paths
+ strong_memoize(:top_level_worktree_paths) do
+ project.repository.tree(sha).blobs.map(&:path)
+ end
+ end
+
+ def all_worktree_paths
+ strong_memoize(:all_worktree_paths) do
+ project.repository.ls_files(sha)
+ end
+ end
+
protected
def pipeline_attributes
diff --git a/lib/gitlab/ci/build/rules/rule/clause/exists.rb b/lib/gitlab/ci/build/rules/rule/clause/exists.rb
index 85e77438f51..e2b54797dc8 100644
--- a/lib/gitlab/ci/build/rules/rule/clause/exists.rb
+++ b/lib/gitlab/ci/build/rules/rule/clause/exists.rb
@@ -15,19 +15,21 @@ module Gitlab
@exact_globs, @pattern_globs = globs.partition(&method(:exact_glob?))
end
- def satisfied_by?(pipeline, context)
- paths = worktree_paths(pipeline)
+ def satisfied_by?(_pipeline, context)
+ paths = worktree_paths(context)
exact_matches?(paths) || pattern_matches?(paths)
end
private
- def worktree_paths(pipeline)
+ def worktree_paths(context)
+ return unless context.project
+
if @top_level_only
- pipeline.top_level_worktree_paths
+ context.top_level_worktree_paths
else
- pipeline.all_worktree_paths
+ context.all_worktree_paths
end
end
diff --git a/lib/gitlab/ci/config/entry/include/rules/rule.rb b/lib/gitlab/ci/config/entry/include/rules/rule.rb
index d3d0f098814..fa99a7204d6 100644
--- a/lib/gitlab/ci/config/entry/include/rules/rule.rb
+++ b/lib/gitlab/ci/config/entry/include/rules/rule.rb
@@ -9,9 +9,9 @@ module Gitlab
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
- ALLOWED_KEYS = %i[if].freeze
+ ALLOWED_KEYS = %i[if exists].freeze
- attributes :if
+ attributes :if, :exists
validations do
validates :config, presence: true
diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb
index e0adb1b19c2..51624dc30ea 100644
--- a/lib/gitlab/ci/config/external/context.rb
+++ b/lib/gitlab/ci/config/external/context.rb
@@ -5,6 +5,8 @@ module Gitlab
class Config
module External
class Context
+ include Gitlab::Utils::StrongMemoize
+
TimeoutError = Class.new(StandardError)
attr_reader :project, :sha, :user, :parent_pipeline, :variables
@@ -22,6 +24,18 @@ module Gitlab
yield self if block_given?
end
+ def top_level_worktree_paths
+ strong_memoize(:top_level_worktree_paths) do
+ project.repository.tree(sha).blobs.map(&:path)
+ end
+ end
+
+ def all_worktree_paths
+ strong_memoize(:all_worktree_paths) do
+ project.repository.ls_files(sha)
+ end
+ end
+
def mutate(attrs = {})
self.class.new(**attrs) do |ctx|
ctx.expandset = expandset
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index aa7b394a13c..197ce2438e6 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -74,6 +74,9 @@ gemnasium-maven-dependency_scanning:
# override the analyzer image with a custom value. This may be subject to change or
# breakage across GitLab releases.
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION"
+ # Stop reporting Gradle as "maven".
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
+ DS_REPORT_PACKAGE_MANAGER_MAVEN_WHEN_JAVA: "false"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED
when: never
@@ -97,6 +100,9 @@ gemnasium-python-dependency_scanning:
# override the analyzer image with a custom value. This may be subject to change or
# breakage across GitLab releases.
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium-python:$DS_MAJOR_VERSION"
+ # Stop reporting Pipenv and Setuptools as "pip".
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
+ DS_REPORT_PACKAGE_MANAGER_PIP_WHEN_PYTHON: "false"
rules:
- if: $DEPENDENCY_SCANNING_DISABLED
when: never
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index 623fdd89456..fbf2718d718 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -7,12 +7,16 @@ module Gitlab
# To check if a job has been completed, simply pass the job ID to the
# `completed?` method:
#
- # job_id = SomeWorker.perform_async(...)
+ # job_id = SomeWorker.with_status.perform_async(...)
#
# if Gitlab::SidekiqStatus.completed?(job_id)
# ...
# end
#
+ # If you do not use `with_status`, and the worker class does not declare
+ # `status_expiration` in its `sidekiq_options`, then this status will not be
+ # stored.
+ #
# For each job ID registered a separate key is stored in Redis, making lookups
# much faster than using Sidekiq's built-in job finding/status API. These keys
# expire after a certain period of time to prevent storing too many keys in
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 25673097f38..9fa1f64f025 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -815,6 +815,9 @@ msgstr ""
msgid "%{primary} (%{secondary})"
msgstr ""
+msgid "%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}"
+msgstr ""
+
msgid "%{ref} cannot be added: %{error}"
msgstr ""
@@ -2313,6 +2316,9 @@ msgstr ""
msgid "AdminArea|Maintainer"
msgstr ""
+msgid "AdminArea|Minimal access"
+msgstr ""
+
msgid "AdminArea|New group"
msgstr ""
@@ -11337,7 +11343,10 @@ msgstr ""
msgid "DependencyProxy|Enable Proxy"
msgstr ""
-msgid "DependencyProxy|Manifest list"
+msgid "DependencyProxy|Image list"
+msgstr ""
+
+msgid "DependencyProxy|There are no images in the cache"
msgstr ""
msgid "Depends on %d merge request being merged"
@@ -18547,12 +18556,18 @@ msgstr ""
msgid "Integrations|Default settings are inherited from the instance level."
msgstr ""
+msgid "Integrations|Edit project alias"
+msgstr ""
+
msgid "Integrations|Enable GitLab.com slash commands in a Slack workspace."
msgstr ""
msgid "Integrations|Enable comments"
msgstr ""
+msgid "Integrations|Enter your alias"
+msgstr ""
+
msgid "Integrations|Failed to link namespace. Please try again."
msgstr ""
@@ -18670,6 +18685,9 @@ msgstr ""
msgid "Integrations|You can now close this window and return to the GitLab for Jira application."
msgstr ""
+msgid "Integrations|You can use this alias in your Slack commands"
+msgstr ""
+
msgid "Integrations|You haven't activated any integrations yet."
msgstr ""
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index e2fc867db44..46b332a8938 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Projects::MergeRequestsController do
let_it_be_with_reload(:project_public_with_private_builds) { create(:project, :repository, :public, :builds_private) }
let(:user) { project.owner }
- let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: merge_request_source_project, allow_maintainer_to_push: false) }
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: merge_request_source_project, allow_collaboration: false) }
let(:merge_request_source_project) { project }
before do
@@ -507,6 +507,7 @@ RSpec.describe Projects::MergeRequestsController do
end
it 'starts the merge immediately with permitted params' do
+ allow(MergeWorker).to receive(:with_status).and_return(MergeWorker)
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, anything, { 'sha' => merge_request.diff_head_sha })
merge_with_sha
@@ -2078,6 +2079,10 @@ RSpec.describe Projects::MergeRequestsController do
post :rebase, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
end
+ before do
+ allow(RebaseWorker).to receive(:with_status).and_return(RebaseWorker)
+ end
+
def expect_rebase_worker_for(user)
expect(RebaseWorker).to receive(:perform_async).with(merge_request.id, user.id, false)
end
diff --git a/spec/factories/user_highest_roles.rb b/spec/factories/user_highest_roles.rb
index 761a8b6c583..ee5794b55fb 100644
--- a/spec/factories/user_highest_roles.rb
+++ b/spec/factories/user_highest_roles.rb
@@ -5,10 +5,10 @@ FactoryBot.define do
highest_access_level { nil }
user
- trait(:guest) { highest_access_level { GroupMember::GUEST } }
- trait(:reporter) { highest_access_level { GroupMember::REPORTER } }
- trait(:developer) { highest_access_level { GroupMember::DEVELOPER } }
- trait(:maintainer) { highest_access_level { GroupMember::MAINTAINER } }
- trait(:owner) { highest_access_level { GroupMember::OWNER } }
+ trait(:guest) { highest_access_level { GroupMember::GUEST } }
+ trait(:reporter) { highest_access_level { GroupMember::REPORTER } }
+ trait(:developer) { highest_access_level { GroupMember::DEVELOPER } }
+ trait(:maintainer) { highest_access_level { GroupMember::MAINTAINER } }
+ trait(:owner) { highest_access_level { GroupMember::OWNER } }
end
end
diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb
index 475fda5e7a1..273d52996d3 100644
--- a/spec/features/profiles/user_visits_profile_spec.rb
+++ b/spec/features/profiles/user_visits_profile_spec.rb
@@ -21,6 +21,14 @@ RSpec.describe 'User visits their profile' do
expect(page).to have_content "This information will appear on your profile"
end
+ it 'shows user readme' do
+ create(:project, :repository, :public, path: user.username, namespace: user.namespace)
+
+ visit(user_path(user))
+
+ expect(find('.file-content')).to have_content('testme')
+ end
+
context 'when user has groups' do
let(:group) do
create :group do |group|
diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js
index f42ee295511..218db0b587a 100644
--- a/spec/frontend/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
@@ -39,7 +39,7 @@ export const member = {
Developer: 30,
Maintainer: 40,
Owner: 50,
- 'Minimal Access': 5,
+ 'Minimal access': 5,
},
};
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
index 1f0252965b0..625f00a8666 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
@@ -1,32 +1,40 @@
-import { GlFormInputGroup, GlFormGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
+import {
+ GlFormInputGroup,
+ GlFormGroup,
+ GlSkeletonLoader,
+ GlSprintf,
+ GlEmptyState,
+} from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { stripTypenames } from 'helpers/graphql_helpers';
import waitForPromises from 'helpers/wait_for_promises';
+import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
import DependencyProxyApp from '~/packages_and_registries/dependency_proxy/app.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
-import { proxyDetailsQuery, proxyData } from './mock_data';
+import { proxyDetailsQuery, proxyData, pagination, proxyManifests } from './mock_data';
const localVue = createLocalVue();
describe('DependencyProxyApp', () => {
let wrapper;
let apolloProvider;
+ let resolver;
const provideDefaults = {
groupPath: 'gitlab-org',
dependencyProxyAvailable: true,
+ noManifestsIllustration: 'noManifestsIllustration',
};
- function createComponent({
- provide = provideDefaults,
- resolver = jest.fn().mockResolvedValue(proxyDetailsQuery()),
- } = {}) {
+ function createComponent({ provide = provideDefaults } = {}) {
localVue.use(VueApollo);
const requestHandlers = [[getDependencyProxyDetailsQuery, resolver]];
@@ -53,6 +61,12 @@ describe('DependencyProxyApp', () => {
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findMainArea = () => wrapper.findByTestId('main-area');
const findProxyCountText = () => wrapper.findByTestId('proxy-count');
+ const findManifestList = () => wrapper.findComponent(ManifestsList);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+
+ beforeEach(() => {
+ resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
+ });
afterEach(() => {
wrapper.destroy();
@@ -78,8 +92,8 @@ describe('DependencyProxyApp', () => {
});
it('does not call the graphql endpoint', async () => {
- const resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
- createComponent({ ...createComponentArguments, resolver });
+ resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
+ createComponent({ ...createComponentArguments });
await waitForPromises();
@@ -145,14 +159,73 @@ describe('DependencyProxyApp', () => {
it('from group has a description with proxy count', () => {
expect(findProxyCountText().text()).toBe('Contains 2 blobs of images (1024 Bytes)');
});
+
+ describe('manifest lists', () => {
+ describe('when there are no manifests', () => {
+ beforeEach(() => {
+ resolver = jest.fn().mockResolvedValue(
+ proxyDetailsQuery({
+ extend: { dependencyProxyManifests: { nodes: [], pageInfo: pagination() } },
+ }),
+ );
+ createComponent();
+ return waitForPromises();
+ });
+
+ it('shows the empty state message', () => {
+ expect(findEmptyState().props()).toMatchObject({
+ svgPath: provideDefaults.noManifestsIllustration,
+ title: DependencyProxyApp.i18n.noManifestTitle,
+ });
+ });
+
+ it('hides the list', () => {
+ expect(findManifestList().exists()).toBe(false);
+ });
+ });
+
+ describe('when there are manifests', () => {
+ it('hides the empty state message', () => {
+ expect(findEmptyState().exists()).toBe(false);
+ });
+
+ it('shows list', () => {
+ expect(findManifestList().props()).toMatchObject({
+ manifests: proxyManifests(),
+ pagination: stripTypenames(pagination()),
+ });
+ });
+
+ it('prev-page event on list fetches the previous page', () => {
+ findManifestList().vm.$emit('prev-page');
+
+ expect(resolver).toHaveBeenCalledWith({
+ before: pagination().startCursor,
+ first: null,
+ fullPath: provideDefaults.groupPath,
+ last: GRAPHQL_PAGE_SIZE,
+ });
+ });
+
+ it('next-page event on list fetches the next page', () => {
+ findManifestList().vm.$emit('next-page');
+
+ expect(resolver).toHaveBeenCalledWith({
+ after: pagination().endCursor,
+ first: GRAPHQL_PAGE_SIZE,
+ fullPath: provideDefaults.groupPath,
+ });
+ });
+ });
+ });
});
+
describe('when the dependency proxy is disabled', () => {
beforeEach(() => {
- createComponent({
- resolver: jest
- .fn()
- .mockResolvedValue(proxyDetailsQuery({ extendSettings: { enabled: false } })),
- });
+ resolver = jest
+ .fn()
+ .mockResolvedValue(proxyDetailsQuery({ extendSettings: { enabled: false } }));
+ createComponent();
return waitForPromises();
});
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
index 2c3b4e96098..9e4c747a1bd 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
@@ -26,42 +26,56 @@ describe('Manifests List', () => {
const findRows = () => wrapper.findAllComponents(ManifestRow);
const findPagination = () => wrapper.findComponent(GlKeysetPagination);
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
});
it('has the correct title', () => {
+ createComponent();
+
expect(wrapper.text()).toContain(Component.i18n.listTitle);
});
it('shows a row for every manifest', () => {
+ createComponent();
+
expect(findRows().length).toBe(defaultProps.manifests.length);
});
it('binds a manifest to each row', () => {
+ createComponent();
+
expect(findRows().at(0).props()).toMatchObject({
manifest: defaultProps.manifests[0],
});
});
describe('pagination', () => {
+ it('is hidden when there is no next or prev pages', () => {
+ createComponent({ ...defaultProps, pagination: {} });
+
+ expect(findPagination().exists()).toBe(false);
+ });
+
it('has the correct props', () => {
+ createComponent();
+
expect(findPagination().props()).toMatchObject({
...defaultProps.pagination,
});
});
it('emits the next-page event', () => {
+ createComponent();
+
findPagination().vm.$emit('next');
expect(wrapper.emitted('next-page')).toEqual([[]]);
});
it('emits the prev-page event', () => {
+ createComponent();
+
findPagination().vm.$emit('prev');
expect(wrapper.emitted('prev-page')).toEqual([[]]);
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js b/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js
index 119ec995462..8bad22b5287 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js
@@ -21,7 +21,7 @@ export const pagination = (extend) => ({
...extend,
});
-export const proxyDetailsQuery = ({ extendSettings = {} } = {}) => ({
+export const proxyDetailsQuery = ({ extendSettings = {}, extend } = {}) => ({
data: {
group: {
...proxyData(),
@@ -34,6 +34,7 @@ export const proxyDetailsQuery = ({ extendSettings = {} } = {}) => ({
nodes: proxyManifests(),
pageInfo: pagination(),
},
+ ...extend,
},
},
});
diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb
index 7286aa288cf..e5e88466946 100644
--- a/spec/helpers/tab_helper_spec.rb
+++ b/spec/helpers/tab_helper_spec.rb
@@ -36,7 +36,15 @@ RSpec.describe TabHelper do
expect(gl_tab_link_to('/url') { 'block content' }).to match(/block content/)
end
- it 'creates a tab with custom classes' do
+ it 'creates a tab with custom classes for enclosing list item without content block provided' do
+ expect(gl_tab_link_to('Link', '/url', { tab_class: 'my-class' })).to match(/<li class=".*my-class.*"/)
+ end
+
+ it 'creates a tab with custom classes for enclosing list item with content block provided' do
+ expect(gl_tab_link_to('/url', { tab_class: 'my-class' }) { 'Link' }).to match(/<li class=".*my-class.*"/)
+ end
+
+ it 'creates a tab with custom classes for anchor element' do
expect(gl_tab_link_to('Link', '/url', { class: 'my-class' })).to match(/<a class=".*my-class.*"/)
end
@@ -161,5 +169,11 @@ RSpec.describe TabHelper do
expect(gl_tab_counter_badge(1, { class: 'js-test' })).to eq('<span class="js-test badge badge-muted badge-pill gl-badge sm gl-tab-counter-badge">1</span>')
end
end
+
+ context 'with data attributes' do
+ it 'creates a tab counter badge with the data attributes' do
+ expect(gl_tab_counter_badge(1, { data: { some_attribute: 'foo' } })).to eq('<span class="badge badge-muted badge-pill gl-badge sm gl-tab-counter-badge" data-some-attribute="foo">1</span>')
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
index 86dd5569a96..f192862c1c4 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
@@ -3,10 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
- describe '#satisfied_by?' do
- let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
-
- subject { described_class.new(globs).satisfied_by?(pipeline, nil) }
+ shared_examples 'an exists rule with a context' do
+ subject { described_class.new(globs).satisfied_by?(pipeline, context) }
it_behaves_like 'a glob matching rule' do
let(:project) { create(:project, :custom_repo, files: files) }
@@ -24,4 +22,26 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
it { is_expected.to be_truthy }
end
end
+
+ describe '#satisfied_by?' do
+ let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
+
+ context 'when context is Build::Context::Build' do
+ it_behaves_like 'an exists rule with a context' do
+ let(:context) { Gitlab::Ci::Build::Context::Build.new(pipeline, sha: 'abc1234') }
+ end
+ end
+
+ context 'when context is Build::Context::Global' do
+ it_behaves_like 'an exists rule with a context' do
+ let(:context) { Gitlab::Ci::Build::Context::Global.new(pipeline, yaml_variables: {}) }
+ end
+ end
+
+ context 'when context is Config::External::Context' do
+ it_behaves_like 'an exists rule with a context' do
+ let(:context) { Gitlab::Ci::Config::External::Context.new(project: project, sha: project.repository.tree.sha) }
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
index b99048e2c18..0505b17ea91 100644
--- a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
@@ -5,7 +5,7 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
let(:factory) do
Gitlab::Config::Entry::Factory.new(described_class)
- .value(config)
+ .value(config)
end
subject(:entry) { factory.create! }
@@ -25,6 +25,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
it { is_expected.to be_valid }
end
+ context 'when specifying an exists: clause' do
+ let(:config) { { exists: './this.md' } }
+
+ it { is_expected.to be_valid }
+ end
+
context 'using a list of multiple expressions' do
let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
@@ -86,5 +92,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
expect(subject).to eq(if: '$THIS || $THAT')
end
end
+
+ context 'when specifying an exists: clause' do
+ let(:config) { { exists: './test.md' } }
+
+ it 'returns the config' do
+ expect(subject).to eq(exists: './test.md')
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index c2f28253f54..2e9e6f95071 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -406,7 +406,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
context 'when rules defined' do
context 'when a rule is invalid' do
let(:values) do
- { include: [{ local: 'builds.yml', rules: [{ exists: ['$MY_VAR'] }] }] }
+ { include: [{ local: 'builds.yml', rules: [{ changes: ['$MY_VAR'] }] }] }
end
it 'raises IncludeError' do
diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb
index 9a5c29befa2..1e42cb30ae7 100644
--- a/spec/lib/gitlab/ci/config/external/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::Rules do
let(:rule_hashes) {}
@@ -32,6 +32,26 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do
end
end
+ context 'when there is a rule with exists' do
+ let(:project) { create(:project, :repository) }
+ let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['test.md']) }
+ let(:rule_hashes) { [{ exists: 'Dockerfile' }] }
+
+ context 'when the file does not exist' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when the file exists' do
+ let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['Dockerfile']) }
+
+ before do
+ project.repository.create_file(project.owner, 'Dockerfile', "commit", message: 'test', branch_name: "master")
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
+
context 'when there is a rule with if and when' do
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] }
@@ -41,12 +61,12 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do
end
end
- context 'when there is a rule with exists' do
- let(:rule_hashes) { [{ exists: ['$MY_VAR'] }] }
+ context 'when there is a rule with changes' do
+ let(:rule_hashes) { [{ changes: ['$MY_VAR'] }] }
it 'raises an error' do
expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
- 'invalid include rule: {:exists=>["$MY_VAR"]}')
+ 'invalid include rule: {:changes=>["$MY_VAR"]}')
end
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index ebfa2388152..1b3e8a2ce4a 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -597,7 +597,7 @@ RSpec.describe Gitlab::Ci::Config do
job1: {
script: ["echo 'hello from main file'"],
variables: {
- VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
}
}
})
@@ -727,30 +727,70 @@ RSpec.describe Gitlab::Ci::Config do
end
end
- context "when an 'include' has rules with a project variable" do
- let(:gitlab_ci_yml) do
- <<~HEREDOC
- include:
- - local: #{local_location}
- rules:
- - if: $CI_PROJECT_ID == "#{project_id}"
- image: ruby:2.7
- HEREDOC
- end
+ context "when an 'include' has rules" do
+ context "when the rule is an if" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - local: #{local_location}
+ rules:
+ - if: $CI_PROJECT_ID == "#{project_id}"
+ image: ruby:2.7
+ HEREDOC
+ end
- context 'when the rules condition is satisfied' do
- let(:project_id) { project.id }
+ context 'when the rules condition is satisfied' do
+ let(:project_id) { project.id }
- it 'includes the file' do
- expect(config.to_hash).to include(local_location_hash)
+ it 'includes the file' do
+ expect(config.to_hash).to include(local_location_hash)
+ end
+ end
+
+ context 'when the rules condition is satisfied' do
+ let(:project_id) { non_existing_record_id }
+
+ it 'does not include the file' do
+ expect(config.to_hash).not_to include(local_location_hash)
+ end
end
end
- context 'when the rules condition is satisfied' do
- let(:project_id) { non_existing_record_id }
+ context "when the rule is an exists" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - local: #{local_location}
+ rules:
+ - exists: "#{filename}"
+ image: ruby:2.7
+ HEREDOC
+ end
- it 'does not include the file' do
- expect(config.to_hash).not_to include(local_location_hash)
+ before do
+ project.repository.create_file(
+ project.creator,
+ 'my_builds.yml',
+ local_file_content,
+ message: 'Add my_builds.yml',
+ branch_name: '12345'
+ )
+ end
+
+ context 'when the exists file does not exist' do
+ let(:filename) { 'not_a_real_file.md' }
+
+ it 'does not include the file' do
+ expect(config.to_hash).not_to include(local_location_hash)
+ end
+ end
+
+ context 'when the exists file does exist' do
+ let(:filename) { 'my_builds.yml' }
+
+ it 'does include the file' do
+ expect(config.to_hash).to include(local_location_hash)
+ end
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 22d54177f23..5618fb06157 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -2920,6 +2920,8 @@ RSpec.describe MergeRequest, factory_default: :keep do
params = {}
merge_jid = 'hash-123'
+ allow(MergeWorker).to receive(:with_status).and_return(MergeWorker)
+
expect(merge_request).to receive(:expire_etag_cache)
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, user_id, params) do
merge_jid
@@ -2938,6 +2940,10 @@ RSpec.describe MergeRequest, factory_default: :keep do
subject(:execute) { merge_request.rebase_async(user_id) }
+ before do
+ allow(RebaseWorker).to receive(:with_status).and_return(RebaseWorker)
+ end
+
it 'atomically enqueues a RebaseWorker job and updates rebase_jid' do
expect(RebaseWorker)
.to receive(:perform_async)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 2921d94c343..eee5525a3bd 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -6225,4 +6225,31 @@ RSpec.describe User do
expect(described_class.get_ids_by_username([user_name])).to match_array([user_id])
end
end
+
+ describe 'user_project' do
+ it 'returns users project matched by username and public visibility' do
+ user = create(:user)
+ public_project = create(:project, :public, path: user.username, namespace: user.namespace)
+ create(:project, namespace: user.namespace)
+
+ expect(user.user_project).to eq(public_project)
+ end
+ end
+
+ describe 'user_readme' do
+ it 'returns readme from user project' do
+ user = create(:user)
+ create(:project, :repository, :public, path: user.username, namespace: user.namespace)
+
+ expect(user.user_readme.name).to eq('README.md')
+ expect(user.user_readme.data).to include('testme')
+ end
+
+ it 'returns nil if project is private' do
+ user = create(:user)
+ create(:project, :repository, :private, path: user.username, namespace: user.namespace)
+
+ expect(user.user_readme).to be(nil)
+ end
+ end
end
diff --git a/spec/models/users_statistics_spec.rb b/spec/models/users_statistics_spec.rb
index b4b7ddb7c63..8553d0bfdb0 100644
--- a/spec/models/users_statistics_spec.rb
+++ b/spec/models/users_statistics_spec.rb
@@ -34,11 +34,11 @@ RSpec.describe UsersStatistics do
describe '.create_current_stats!' do
before do
- create_list(:user_highest_role, 4)
+ create_list(:user_highest_role, 1)
create_list(:user_highest_role, 2, :guest)
- create_list(:user_highest_role, 3, :reporter)
- create_list(:user_highest_role, 4, :developer)
- create_list(:user_highest_role, 3, :maintainer)
+ create_list(:user_highest_role, 2, :reporter)
+ create_list(:user_highest_role, 2, :developer)
+ create_list(:user_highest_role, 2, :maintainer)
create_list(:user_highest_role, 2, :owner)
create_list(:user, 2, :bot)
create_list(:user, 1, :blocked)
@@ -49,11 +49,11 @@ RSpec.describe UsersStatistics do
context 'when successful' do
it 'creates an entry with the current statistics values' do
expect(described_class.create_current_stats!).to have_attributes(
- without_groups_and_projects: 4,
+ without_groups_and_projects: 1,
with_highest_role_guest: 2,
- with_highest_role_reporter: 3,
- with_highest_role_developer: 4,
- with_highest_role_maintainer: 3,
+ with_highest_role_reporter: 2,
+ with_highest_role_developer: 2,
+ with_highest_role_maintainer: 2,
with_highest_role_owner: 2,
bots: 2,
blocked: 1
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index bdbc73a59d8..7c147419354 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -3278,6 +3278,8 @@ RSpec.describe API::MergeRequests do
context 'when skip_ci parameter is set' do
it 'enqueues a rebase of the merge request with skip_ci flag set' do
+ allow(RebaseWorker).to receive(:with_status).and_return(RebaseWorker)
+
expect(RebaseWorker).to receive(:perform_async).with(merge_request.id, user.id, true).and_call_original
Sidekiq::Testing.fake! do
diff --git a/spec/requests/api/terraform/modules/v1/packages_spec.rb b/spec/requests/api/terraform/modules/v1/packages_spec.rb
index b04f5ad9a94..b17bc11a451 100644
--- a/spec/requests/api/terraform/modules/v1/packages_spec.rb
+++ b/spec/requests/api/terraform/modules/v1/packages_spec.rb
@@ -28,10 +28,25 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/versions' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/versions") }
- let(:headers) { {} }
+ let(:headers) { { 'Authorization' => "Bearer #{tokens[:job_token]}" } }
subject { get(url, headers: headers) }
+ context 'with a conflicting package name' do
+ let!(:conflicting_package) { create(:terraform_module_package, project: project, name: "conflict-#{package.name}", version: '2.0.0') }
+
+ before do
+ group.add_developer(user)
+ end
+
+ it 'returns only one version' do
+ subject
+
+ expect(json_response['modules'][0]['versions'].size).to eq(1)
+ expect(json_response['modules'][0]['versions'][0]['version']).to eq('1.0.0')
+ end
+ end
+
context 'with valid namespace' do
where(:visibility, :user_role, :member, :token_type, :valid_token, :shared_examples_name, :expected_status) do
:public | :developer | true | :personal_access_token | true | 'returns terraform module packages' | :success
diff --git a/spec/requests/import/gitlab_groups_controller_spec.rb b/spec/requests/import/gitlab_groups_controller_spec.rb
index 1f6487986a3..4abf99cf994 100644
--- a/spec/requests/import/gitlab_groups_controller_spec.rb
+++ b/spec/requests/import/gitlab_groups_controller_spec.rb
@@ -60,6 +60,7 @@ RSpec.describe Import::GitlabGroupsController do
end
it 'imports the group data', :sidekiq_inline do
+ allow(GroupImportWorker).to receive(:with_status).and_return(GroupImportWorker)
allow(GroupImportWorker).to receive(:perform_async).and_call_original
import_request
@@ -67,7 +68,6 @@ RSpec.describe Import::GitlabGroupsController do
group = Group.find_by(name: 'test-group-import')
expect(GroupImportWorker).to have_received(:perform_async).with(user.id, group.id)
-
expect(group.description).to eq 'A voluptate non sequi temporibus quam at.'
expect(group.visibility_level).to eq Gitlab::VisibilityLevel::PRIVATE
end
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index eaa5f723bec..6f28f892f00 100644
--- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -24,6 +24,10 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do
project.add_maintainer(user)
end
+ before do
+ allow(MergeWorker).to receive(:with_status).and_return(MergeWorker)
+ end
+
describe "#available_for?" do
subject { service.available_for?(mr_merge_if_green_enabled) }
diff --git a/spec/services/groups/import_export/import_service_spec.rb b/spec/services/groups/import_export/import_service_spec.rb
index ad5c4364deb..292f2e2b86b 100644
--- a/spec/services/groups/import_export/import_service_spec.rb
+++ b/spec/services/groups/import_export/import_service_spec.rb
@@ -7,6 +7,10 @@ RSpec.describe Groups::ImportExport::ImportService do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
+ before do
+ allow(GroupImportWorker).to receive(:with_status).and_return(GroupImportWorker)
+ end
+
context 'when the job can be successfully scheduled' do
subject(:import_service) { described_class.new(group: group, user: user) }
@@ -20,6 +24,8 @@ RSpec.describe Groups::ImportExport::ImportService do
end
it 'enqueues an import job' do
+ allow(GroupImportWorker).to receive(:with_status).and_return(GroupImportWorker)
+
expect(GroupImportWorker).to receive(:perform_async).with(user.id, group.id)
import_service.async_execute
diff --git a/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb b/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
index ff87fc5d8df..f8a752a5673 100644
--- a/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
+++ b/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
@@ -39,6 +39,10 @@ end
# let(:status_api) { status_create_self_monitoring_project_admin_application_settings_path }
# subject { post create_self_monitoring_project_admin_application_settings_path }
RSpec.shared_examples 'triggers async worker, returns sidekiq job_id with response accepted' do
+ before do
+ allow(worker_class).to receive(:with_status).and_return(worker_class)
+ end
+
it 'returns sidekiq job_id of expected length' do
subject
diff --git a/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb b/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb
index 89c0841fbd6..e6da96e12ec 100644
--- a/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb
+++ b/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb
@@ -17,7 +17,7 @@ end
RSpec.shared_examples 'returns in_progress based on Sidekiq::Status' do
it 'returns true when job is enqueued' do
- jid = described_class.perform_async
+ jid = described_class.with_status.perform_async
expect(described_class.in_progress?(jid)).to eq(true)
end
diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb
index f08c5336506..fbf39b3c7cd 100644
--- a/spec/workers/concerns/application_worker_spec.rb
+++ b/spec/workers/concerns/application_worker_spec.rb
@@ -598,4 +598,48 @@ RSpec.describe ApplicationWorker do
end
end
end
+
+ describe '.with_status' do
+ around do |example|
+ Sidekiq::Testing.fake!(&example)
+ end
+
+ context 'when the worker does have status_expiration set' do
+ let(:status_expiration_worker) do
+ Class.new(worker) do
+ sidekiq_options status_expiration: 3
+ end
+ end
+
+ it 'uses status_expiration from the worker' do
+ status_expiration_worker.with_status.perform_async
+
+ expect(Sidekiq::Queues[status_expiration_worker.queue].first).to include('status_expiration' => 3)
+ expect(Sidekiq::Queues[status_expiration_worker.queue].length).to eq(1)
+ end
+
+ it 'uses status_expiration from the worker without with_status' do
+ status_expiration_worker.perform_async
+
+ expect(Sidekiq::Queues[status_expiration_worker.queue].first).to include('status_expiration' => 3)
+ expect(Sidekiq::Queues[status_expiration_worker.queue].length).to eq(1)
+ end
+ end
+
+ context 'when the worker does not have status_expiration set' do
+ it 'uses the default status_expiration' do
+ worker.with_status.perform_async
+
+ expect(Sidekiq::Queues[worker.queue].first).to include('status_expiration' => Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
+ expect(Sidekiq::Queues[worker.queue].length).to eq(1)
+ end
+
+ it 'does not set status_expiration without with_status' do
+ worker.perform_async
+
+ expect(Sidekiq::Queues[worker.queue].first).not_to include('status_expiration')
+ expect(Sidekiq::Queues[worker.queue].length).to eq(1)
+ end
+ end
+ end
end