Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-21 15:10:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-21 15:10:22 +0300
commit9e68395a98e71e2a0e9a6200f15ad1e7bae9ea87 (patch)
treeb8508b7503f056b2438cc2fef6f2f9b1edfa0279
parent202268ad93e9a1556f5700326be5ec89bd641a97 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitignore1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile.lock8
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue26
-rw-r--r--app/assets/javascripts/repository/components/tree_content.vue19
-rw-r--r--app/graphql/resolvers/project_members_resolver.rb10
-rw-r--r--app/graphql/types/group_member_type.rb2
-rw-r--r--app/graphql/types/member_interface.rb20
-rw-r--r--app/graphql/types/project_member_type.rb9
-rw-r--r--app/graphql/types/project_type.rb2
-rw-r--r--app/helpers/environments_helper.rb78
-rw-r--r--app/models/merge_request.rb11
-rw-r--r--changelogs/unreleased/229297-migrate-bootstrap-button-to-gitlab-ui-glbutton-in-ee-app-assets-ja.yml5
-rw-r--r--changelogs/unreleased/232636-add-support-for-fixing-composer-404.yml5
-rw-r--r--changelogs/unreleased/235672-convert-show-more-to-table-row.yml5
-rw-r--r--changelogs/unreleased/docs-file_hook.yml5
-rw-r--r--changelogs/unreleased/project_member_list.yml5
-rw-r--r--config/feature_flags/development/personal_snippet_reference_filters.yml4
-rw-r--r--config/gitlab.yml.example5
-rw-r--r--config/initializers/1_settings.rb6
-rw-r--r--config/initializers/gitlab_kas_secret.rb1
-rw-r--r--doc/administration/file_hooks.md2
-rw-r--r--doc/api/graphql/index.md17
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql97
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json315
-rw-r--r--doc/api/graphql/reference/index.md15
-rw-r--r--doc/api/issues.md2
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/development/contributing/issue_workflow.md10
-rw-r--r--doc/development/internal_api.md4
-rw-r--r--doc/install/requirements.md2
-rw-r--r--doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.pngbin73101 -> 0 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_3.pngbin0 -> 51201 bytes
-rw-r--r--doc/user/application_security/security_dashboard/index.md2
-rw-r--r--doc/user/packages/composer_repository/index.md7
-rw-r--r--doc/user/project/import/gemnasium.md2
-rw-r--r--lib/api/internal/kubernetes.rb8
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb6
-rw-r--r--lib/gitlab/json.rb2
-rw-r--r--lib/gitlab/kas.rb28
-rw-r--r--lib/gitlab/usage_data.rb2
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/frontend/monitoring/fixture_data.js11
-rw-r--r--spec/frontend/repository/components/table/index_spec.js28
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js35
-rw-r--r--spec/graphql/resolvers/project_members_resolver_spec.rb16
-rw-r--r--spec/graphql/types/member_interface_spec.rb43
-rw-r--r--spec/graphql/types/project_type_spec.rb2
-rw-r--r--spec/helpers/environments_helper_spec.rb82
-rw-r--r--spec/lib/gitlab/kas_spec.rb61
-rw-r--r--spec/models/merge_request_spec.rb6
-rw-r--r--spec/models/service_spec.rb16
-rw-r--r--spec/requests/api/composer_packages_spec.rb107
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb48
-rw-r--r--spec/requests/api/internal/kubernetes_spec.rb58
-rw-r--r--spec/services/merge_requests/conflicts/list_service_spec.rb1
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb24
57 files changed, 1029 insertions, 264 deletions
diff --git a/.gitignore b/.gitignore
index 38beab164cb..04384be78c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,7 @@ eslint-report.html
/builds*
/.gitlab_workhorse_secret
/.gitlab_pages_secret
+/.gitlab_kas_secret
/webpack-report/
/knapsack/
/rspec_flaky/
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 2df7975119c..6020e69afd8 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0fe0cfaccc979592610cbf65807f19b307957750
+b2e2ec1069625c4a4f220b0ec72154b1f2bdf834
diff --git a/Gemfile.lock b/Gemfile.lock
index 78fcf73133e..40dc62cbc97 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -338,7 +338,7 @@ GEM
fast_blank (1.0.0)
fast_gettext (1.6.0)
ffaker (2.10.0)
- ffi (1.12.2)
+ ffi (1.13.1)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
@@ -695,7 +695,7 @@ GEM
faraday (>= 0.9, < 2.0.0)
faraday-cookie_jar (~> 0.0.6)
ms_rest (~> 0.7.6)
- msgpack (1.3.1)
+ msgpack (1.3.3)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.1.1)
@@ -804,7 +804,7 @@ GEM
validate_url
webfinger (>= 1.0.1)
opentracing (0.5.0)
- optimist (3.0.0)
+ optimist (3.0.1)
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
@@ -904,7 +904,7 @@ GEM
ffi (>= 0.5.0, < 2)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
- rbtrace (0.4.11)
+ rbtrace (0.4.14)
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
optimist (>= 3.0.0)
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index d0cc617d755..fd70a6419fc 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -1,5 +1,5 @@
<script>
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlSkeletonLoading, GlButton } from '@gitlab/ui';
import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref';
import projectPathQuery from '../../queries/project_path.query.graphql';
@@ -13,6 +13,7 @@ export default {
TableHeader,
TableRow,
ParentRow,
+ GlButton,
},
mixins: [getRefMixin],
apollo: {
@@ -39,6 +40,10 @@ export default {
required: false,
default: '',
},
+ hasMore: {
+ type: Boolean,
+ required: true,
+ },
},
data() {
return {
@@ -65,6 +70,11 @@ export default {
return !this.isLoading && ['', '/'].indexOf(this.path) === -1;
},
},
+ methods: {
+ showMore() {
+ this.$emit('showMore');
+ },
+ },
};
</script>
@@ -110,6 +120,20 @@ export default {
<td><gl-skeleton-loading :lines="1" class="ml-auto h-auto w-50" /></td>
</tr>
</template>
+ <template v-if="hasMore">
+ <tr>
+ <td align="center" colspan="3" class="gl-p-0!">
+ <gl-button
+ variant="link"
+ class="gl-display-flex gl-w-full gl-py-4!"
+ :loading="isLoading"
+ @click="showMore"
+ >
+ {{ s__('ProjectFileTree|Show more') }}
+ </gl-button>
+ </td>
+ </tr>
+ </template>
</tbody>
</table>
</div>
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
index fe3065a2145..365b6cbb550 100644
--- a/app/assets/javascripts/repository/components/tree_content.vue
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -1,5 +1,4 @@
<script>
-import { GlButton } from '@gitlab/ui';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { __ } from '../../locale';
import FileTable from './table/index.vue';
@@ -17,7 +16,6 @@ export default {
components: {
FileTable,
FilePreview,
- GlButton,
},
mixins: [getRefMixin],
apollo: {
@@ -127,7 +125,7 @@ export default {
.concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
.find(({ hasNextPage }) => hasNextPage);
},
- showMore() {
+ handleShowMore() {
this.clickedShowMore = true;
this.fetchFiles();
},
@@ -142,20 +140,9 @@ export default {
:entries="entries"
:is-loading="isLoadingFiles"
:loading-path="loadingPath"
+ :has-more="hasShowMore"
+ @showMore="handleShowMore"
/>
- <div
- v-if="hasShowMore"
- class="gl-border-1 gl-border-gray-100 gl-rounded-base gl-border-t-none gl-border-b-solid gl-border-l-solid gl-border-r-solid gl-rounded-top-right-none gl-rounded-top-left-none gl-mt-n1"
- >
- <gl-button
- variant="link"
- class="gl-display-flex gl-w-full gl-py-4!"
- :loading="isLoadingFiles"
- @click="showMore"
- >
- {{ s__('ProjectFileTree|Show more') }}
- </gl-button>
- </div>
<file-preview v-if="readme" :blob="readme" />
</div>
</template>
diff --git a/app/graphql/resolvers/project_members_resolver.rb b/app/graphql/resolvers/project_members_resolver.rb
index 3846531762e..7ee635014f4 100644
--- a/app/graphql/resolvers/project_members_resolver.rb
+++ b/app/graphql/resolvers/project_members_resolver.rb
@@ -2,19 +2,23 @@
module Resolvers
class ProjectMembersResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query'
- type Types::ProjectMemberType, null: true
+ type Types::MemberInterface, null: true
+
+ authorize :read_project_member
alias_method :project, :object
def resolve(**args)
- return Member.none unless project.present?
+ authorize!(project)
MembersFinder
- .new(project, context[:current_user], params: args)
+ .new(project, current_user, params: args)
.execute
end
end
diff --git a/app/graphql/types/group_member_type.rb b/app/graphql/types/group_member_type.rb
index ffffa3247db..6cca0a50647 100644
--- a/app/graphql/types/group_member_type.rb
+++ b/app/graphql/types/group_member_type.rb
@@ -8,7 +8,7 @@ module Types
implements MemberInterface
graphql_name 'GroupMember'
- description 'Represents a Group Member'
+ description 'Represents a Group Membership'
field :group, Types::GroupType, null: true,
description: 'Group that a User is a member of',
diff --git a/app/graphql/types/member_interface.rb b/app/graphql/types/member_interface.rb
index 976836221bc..fed7272c0e3 100644
--- a/app/graphql/types/member_interface.rb
+++ b/app/graphql/types/member_interface.rb
@@ -4,6 +4,9 @@ module Types
module MemberInterface
include BaseInterface
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the member'
+
field :access_level, Types::AccessLevelType, null: true,
description: 'GitLab::Access level'
@@ -18,5 +21,22 @@ module Types
field :expires_at, Types::TimeType, null: true,
description: 'Date and time the membership expires'
+
+ field :user, Types::UserType, null: false,
+ description: 'User that is associated with the member object',
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.user_id).find }
+
+ definition_methods do
+ def resolve_type(object, context)
+ case object
+ when GroupMember
+ Types::GroupMemberType
+ when ProjectMember
+ Types::ProjectMemberType
+ else
+ raise ::Gitlab::Graphql::Errors::BaseError, "Unknown member type #{object.class.name}"
+ end
+ end
+ end
end
end
diff --git a/app/graphql/types/project_member_type.rb b/app/graphql/types/project_member_type.rb
index e9ccb51886b..f08781238d0 100644
--- a/app/graphql/types/project_member_type.rb
+++ b/app/graphql/types/project_member_type.rb
@@ -3,7 +3,7 @@
module Types
class ProjectMemberType < BaseObject
graphql_name 'ProjectMember'
- description 'Represents a Project Member'
+ description 'Represents a Project Membership'
expose_permissions Types::PermissionTypes::Project
@@ -11,13 +11,6 @@ module Types
authorize :read_project
- field :id, GraphQL::ID_TYPE, null: false,
- description: 'ID of the member'
-
- field :user, Types::UserType, null: false,
- description: 'User that is associated with the member object',
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.user_id).find }
-
field :project, Types::ProjectType, null: true,
description: 'Project that User is a member of',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, obj.source_id).find }
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 5562db69de6..36e37654812 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -159,7 +159,7 @@ module Types
resolver: Resolvers::ProjectMilestonesResolver
field :project_members,
- Types::ProjectMemberType.connection_type,
+ Types::MemberInterface.connection_type,
description: 'Members of the project',
resolver: Resolvers::ProjectMembersResolver
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 39be8ae9f60..7f0c59f65a0 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -12,8 +12,8 @@ module EnvironmentsHelper
def environments_folder_list_view_data
{
"endpoint" => folder_project_environments_path(@project, @folder, format: :json),
- "folder-name" => @folder,
- "can-read-environment" => can?(current_user, :read_environment, @project).to_s
+ "folder_name" => @folder,
+ "can_read_environment" => can?(current_user, :read_environment, @project).to_s
}
end
@@ -33,11 +33,11 @@ module EnvironmentsHelper
def environment_logs_data(project, environment)
{
- "environment-name": environment.name,
- "environments-path": project_environments_path(project, format: :json),
- "environment-id": environment.id,
- "cluster-applications-documentation-path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
- "clusters-path": project_clusters_path(project, format: :json)
+ "environment_name": environment.name,
+ "environments_path": project_environments_path(project, format: :json),
+ "environment_id": environment.id,
+ "cluster_applications_documentation_path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
+ "clusters_path": project_clusters_path(project, format: :json)
}
end
@@ -51,18 +51,18 @@ module EnvironmentsHelper
return {} unless project
{
- 'settings-path' => edit_project_service_path(project, 'prometheus'),
- 'clusters-path' => project_clusters_path(project),
- 'dashboards-endpoint' => project_performance_monitoring_dashboards_path(project, format: :json),
- 'default-branch' => project.default_branch,
- 'project-path' => project_path(project),
- 'tags-path' => project_tags_path(project),
- 'external-dashboard-url' => project.metrics_setting_external_dashboard_url,
- 'custom-metrics-path' => project_prometheus_metrics_path(project),
- 'validate-query-path' => validate_query_project_prometheus_metrics_path(project),
- 'custom-metrics-available' => "#{custom_metrics_available?(project)}",
- 'prometheus-alerts-available' => "#{can?(current_user, :read_prometheus_alerts, project)}",
- 'dashboard-timezone' => project.metrics_setting_dashboard_timezone.to_s.upcase
+ 'settings_path' => edit_project_service_path(project, 'prometheus'),
+ 'clusters_path' => project_clusters_path(project),
+ 'dashboards_endpoint' => project_performance_monitoring_dashboards_path(project, format: :json),
+ 'default_branch' => project.default_branch,
+ 'project_path' => project_path(project),
+ 'tags_path' => project_tags_path(project),
+ 'external_dashboard_url' => project.metrics_setting_external_dashboard_url,
+ 'custom_metrics_path' => project_prometheus_metrics_path(project),
+ 'validate_query_path' => validate_query_project_prometheus_metrics_path(project),
+ 'custom_metrics_available' => "#{custom_metrics_available?(project)}",
+ 'prometheus_alerts_available' => "#{can?(current_user, :read_prometheus_alerts, project)}",
+ 'dashboard_timezone' => project.metrics_setting_dashboard_timezone.to_s.upcase
}
end
@@ -70,11 +70,11 @@ module EnvironmentsHelper
return {} unless environment
{
- 'metrics-dashboard-base-path' => metrics_dashboard_base_path(environment, project),
- 'current-environment-name' => environment.name,
- 'has-metrics' => "#{environment.has_metrics?}",
- 'prometheus-status' => "#{environment.prometheus_status}",
- 'environment-state' => "#{environment.state}"
+ 'metrics_dashboard_base_path' => metrics_dashboard_base_path(environment, project),
+ 'current_environment_name' => environment.name,
+ 'has_metrics' => "#{environment.has_metrics?}",
+ 'prometheus_status' => "#{environment.prometheus_status}",
+ 'environment_state' => "#{environment.state}"
}
end
@@ -93,26 +93,26 @@ module EnvironmentsHelper
return {} unless project && environment
{
- 'metrics-endpoint' => additional_metrics_project_environment_path(project, environment, format: :json),
- 'dashboard-endpoint' => metrics_dashboard_project_environment_path(project, environment, format: :json),
- 'deployments-endpoint' => project_environment_deployments_path(project, environment, format: :json),
- 'alerts-endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
- 'operations-settings-path' => project_settings_operations_path(project),
- 'can-access-operations-settings' => can?(current_user, :admin_operations, project).to_s,
- 'panel-preview-endpoint' => project_metrics_dashboards_builder_path(project, format: :json)
+ 'metrics_endpoint' => additional_metrics_project_environment_path(project, environment, format: :json),
+ 'dashboard_endpoint' => metrics_dashboard_project_environment_path(project, environment, format: :json),
+ 'deployments_endpoint' => project_environment_deployments_path(project, environment, format: :json),
+ 'alerts_endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
+ 'operations_settings_path' => project_settings_operations_path(project),
+ 'can_access_operations_settings' => can?(current_user, :admin_operations, project).to_s,
+ 'panel_preview_endpoint' => project_metrics_dashboards_builder_path(project, format: :json)
}
end
def static_metrics_data
{
- 'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
- 'add-dashboard-documentation-path' => help_page_path('operations/metrics/dashboards/index.md', anchor: 'add-a-new-dashboard-to-your-project'),
- 'empty-getting-started-svg-path' => image_path('illustrations/monitoring/getting_started.svg'),
- 'empty-loading-svg-path' => image_path('illustrations/monitoring/loading.svg'),
- 'empty-no-data-svg-path' => image_path('illustrations/monitoring/no_data.svg'),
- 'empty-no-data-small-svg-path' => image_path('illustrations/chart-empty-state-small.svg'),
- 'empty-unable-to-connect-svg-path' => image_path('illustrations/monitoring/unable_to_connect.svg'),
- 'custom-dashboard-base-path' => Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT
+ 'documentation_path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'add_dashboard_documentation_path' => help_page_path('operations/metrics/dashboards/index.md', anchor: 'add-a-new-dashboard-to-your-project'),
+ 'empty_getting_started_svg_path' => image_path('illustrations/monitoring/getting_started.svg'),
+ 'empty_loading_svg_path' => image_path('illustrations/monitoring/loading.svg'),
+ 'empty_no_data_svg_path' => image_path('illustrations/monitoring/no_data.svg'),
+ 'empty_no_data_small_svg_path' => image_path('illustrations/chart-empty-state-small.svg'),
+ 'empty_unable_to_connect_svg_path' => image_path('illustrations/monitoring/unable_to_connect.svg'),
+ 'custom_dashboard_base_path' => Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT
}
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 36e458d9353..ff72aa7f563 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -105,7 +105,6 @@ class MergeRequest < ApplicationRecord
after_create :ensure_merge_request_diff
after_update :clear_memoized_shas
- after_update :clear_memoized_source_branch_exists
after_update :reload_diff_if_branch_changed
after_commit :ensure_metrics, on: [:create, :update], unless: :importing?
after_commit :expire_etag_cache, unless: :importing?
@@ -871,10 +870,6 @@ class MergeRequest < ApplicationRecord
clear_memoization(:target_branch_head)
end
- def clear_memoized_source_branch_exists
- clear_memoization(:source_branch_exists)
- end
-
def reload_diff_if_branch_changed
if (saved_change_to_source_branch? || saved_change_to_target_branch?) &&
(source_branch_head && target_branch_head)
@@ -1126,11 +1121,9 @@ class MergeRequest < ApplicationRecord
end
def source_branch_exists?
- strong_memoize(:source_branch_exists) do
- next false unless self.source_project
+ return false unless self.source_project
- self.source_project.repository.branch_exists?(self.source_branch)
- end
+ self.source_project.repository.branch_exists?(self.source_branch)
end
def target_branch_exists?
diff --git a/changelogs/unreleased/229297-migrate-bootstrap-button-to-gitlab-ui-glbutton-in-ee-app-assets-ja.yml b/changelogs/unreleased/229297-migrate-bootstrap-button-to-gitlab-ui-glbutton-in-ee-app-assets-ja.yml
new file mode 100644
index 00000000000..82edc6a1ac3
--- /dev/null
+++ b/changelogs/unreleased/229297-migrate-bootstrap-button-to-gitlab-ui-glbutton-in-ee-app-assets-ja.yml
@@ -0,0 +1,5 @@
+---
+title: GlButton migrations for pipeline security tab
+merge_request: 39651
+author:
+type: performance
diff --git a/changelogs/unreleased/232636-add-support-for-fixing-composer-404.yml b/changelogs/unreleased/232636-add-support-for-fixing-composer-404.yml
new file mode 100644
index 00000000000..775268e3af9
--- /dev/null
+++ b/changelogs/unreleased/232636-add-support-for-fixing-composer-404.yml
@@ -0,0 +1,5 @@
+---
+title: Fix composer 404 issues with http auth
+merge_request: 38641
+author:
+type: fixed
diff --git a/changelogs/unreleased/235672-convert-show-more-to-table-row.yml b/changelogs/unreleased/235672-convert-show-more-to-table-row.yml
new file mode 100644
index 00000000000..b95a7293ae0
--- /dev/null
+++ b/changelogs/unreleased/235672-convert-show-more-to-table-row.yml
@@ -0,0 +1,5 @@
+---
+title: Change show more button to be a table row so to remove manual CSS styling
+merge_request: 39788
+author:
+type: changed
diff --git a/changelogs/unreleased/docs-file_hook.yml b/changelogs/unreleased/docs-file_hook.yml
new file mode 100644
index 00000000000..576029a13b6
--- /dev/null
+++ b/changelogs/unreleased/docs-file_hook.yml
@@ -0,0 +1,5 @@
+---
+title: Fix example within file_hooks documentation
+merge_request: 40071
+author: Roger Meier
+type: fixed
diff --git a/changelogs/unreleased/project_member_list.yml b/changelogs/unreleased/project_member_list.yml
new file mode 100644
index 00000000000..f448fdf092b
--- /dev/null
+++ b/changelogs/unreleased/project_member_list.yml
@@ -0,0 +1,5 @@
+---
+title: Include also inherited project members in GraphQL API
+merge_request: 39444
+author:
+type: fixed
diff --git a/config/feature_flags/development/personal_snippet_reference_filters.yml b/config/feature_flags/development/personal_snippet_reference_filters.yml
index 6a9aefbb379..44b9ac2f862 100644
--- a/config/feature_flags/development/personal_snippet_reference_filters.yml
+++ b/config/feature_flags/development/personal_snippet_reference_filters.yml
@@ -1,7 +1,7 @@
---
name: personal_snippet_reference_filters
-introduced_by_url:
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38571
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235155
group: group::editor
type: development
-default_enabled: false \ No newline at end of file
+default_enabled: false
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 5d217332634..19dcea84d86 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -1093,6 +1093,11 @@ production: &base
# Default is '.gitlab_workhorse_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_workhorse_secret
+ gitlab_kas:
+ # File that contains the secret key for verifying access for gitlab-kas.
+ # Default is '.gitlab_kas_secret' relative to Rails.root (i.e. root of the GitLab app).
+ # secret_file: /home/git/gitlab/.gitlab_kas_secret
+
## GitLab Elasticsearch settings
elasticsearch:
indexer_path: /home/git/gitlab-elasticsearch-indexer/
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 628d9c65ce0..da275b34f5d 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -635,6 +635,12 @@ Settings['workhorse'] ||= Settingslogic.new({})
Settings.workhorse['secret_file'] ||= Rails.root.join('.gitlab_workhorse_secret')
#
+# GitLab KAS
+#
+Settings['gitlab_kas'] ||= Settingslogic.new({})
+Settings.gitlab_kas['secret_file'] ||= Rails.root.join('.gitlab_kas_secret')
+
+#
# Repositories
#
Settings['repositories'] ||= Settingslogic.new({})
diff --git a/config/initializers/gitlab_kas_secret.rb b/config/initializers/gitlab_kas_secret.rb
new file mode 100644
index 00000000000..5e86e954684
--- /dev/null
+++ b/config/initializers/gitlab_kas_secret.rb
@@ -0,0 +1 @@
+Gitlab::Kas.ensure_secret!
diff --git a/doc/administration/file_hooks.md b/doc/administration/file_hooks.md
index c0b31769e7f..a6610188381 100644
--- a/doc/administration/file_hooks.md
+++ b/doc/administration/file_hooks.md
@@ -71,9 +71,9 @@ Below is an example that will only response on the event `project_create` and
will inform the admins from the GitLab instance that a new project has been created.
```ruby
+#!/opt/gitlab/embedded/bin/ruby
# By using the embedded ruby version we eliminate the possibility that our chosen language
# would be unavailable from
-#!/opt/gitlab/embedded/bin/ruby
require 'json'
require 'mail'
diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md
index c513dea239a..7e87d520ce3 100644
--- a/doc/api/graphql/index.md
+++ b/doc/api/graphql/index.md
@@ -59,6 +59,23 @@ There are no plans to deprecate the REST API. To reduce the technical burden of
supporting two APIs in parallel, they should share implementations as much as
possible.
+### Deprecation process
+
+Fields marked for removal from the GitLab GraphQL API are first **deprecated** but still available
+for at least six releases, and then **removed entirely**.
+Removals occur at X.0 and X.6 releases.
+
+For example, a field can be marked as deprecated (but still usable) in %12.7, but can be used until its removal in %13.6.
+When marked as deprecated, an alternative should be provided if there is one.
+That gives consumers of the GraphQL API a minimum of six months to update their GraphQL queries.
+
+The process is as follows:
+
+1. The field is listed as deprecated in [GraphQL API Reference](reference/index.md).
+1. Removals are announced at least one release prior in the Deprecation Warnings section of the
+ release post (at or prior to X.11 and X.5 releases).
+1. Fields meeting criteria are removed in X.0 or X.6.
+
## Available queries
The GraphQL API includes the following queries at the root level:
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index f1bc1f49353..c6c5bd15b5c 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -1655,6 +1655,41 @@ type ClusterAgent {
updatedAt: Time
}
+"""
+Autogenerated input type of ClusterAgentDelete
+"""
+input ClusterAgentDeleteInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Global id of the cluster agent that will be deleted
+ """
+ id: ClustersAgentID!
+}
+
+"""
+Autogenerated return type of ClusterAgentDelete
+"""
+type ClusterAgentDeletePayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+}
+
+"""
+Identifier of Clusters::Agent
+"""
+scalar ClustersAgentID
+
type Commit {
"""
Author of the commit
@@ -6658,7 +6693,7 @@ type Group {
}
"""
-Represents a Group Member
+Represents a Group Membership
"""
type GroupMember implements MemberInterface {
"""
@@ -6687,11 +6722,21 @@ type GroupMember implements MemberInterface {
group: Group
"""
+ ID of the member
+ """
+ id: ID!
+
+ """
Date and time the membership was last updated
"""
updatedAt: Time
"""
+ User that is associated with the member object
+ """
+ user: User!
+
+ """
Permissions for the current user on the resource
"""
userPermissions: GroupPermissions!
@@ -8341,9 +8386,54 @@ interface MemberInterface {
expiresAt: Time
"""
+ ID of the member
+ """
+ id: ID!
+
+ """
Date and time the membership was last updated
"""
updatedAt: Time
+
+ """
+ User that is associated with the member object
+ """
+ user: User!
+}
+
+"""
+The connection type for MemberInterface.
+"""
+type MemberInterfaceConnection {
+ """
+ A list of edges.
+ """
+ edges: [MemberInterfaceEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [MemberInterface]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type MemberInterfaceEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: MemberInterface
}
type MergeRequest implements Noteable {
@@ -9591,6 +9681,7 @@ type Mutation {
awardEmojiToggle(input: AwardEmojiToggleInput!): AwardEmojiTogglePayload
boardListCreate(input: BoardListCreateInput!): BoardListCreatePayload
boardListUpdateLimitMetrics(input: BoardListUpdateLimitMetricsInput!): BoardListUpdateLimitMetricsPayload
+ clusterAgentDelete(input: ClusterAgentDeleteInput!): ClusterAgentDeletePayload
commitCreate(input: CommitCreateInput!): CommitCreatePayload
configureSast(input: ConfigureSastInput!): ConfigureSastPayload
createAlertIssue(input: CreateAlertIssueInput!): CreateAlertIssuePayload
@@ -11567,7 +11658,7 @@ type Project {
Search query
"""
search: String
- ): ProjectMemberConnection
+ ): MemberInterfaceConnection
"""
Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts
@@ -12001,7 +12092,7 @@ type ProjectEdge {
}
"""
-Represents a Project Member
+Represents a Project Membership
"""
type ProjectMember implements MemberInterface {
"""
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 91b7eef7816..53adde5bdc3 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -4519,6 +4519,104 @@
"possibleTypes": null
},
{
+ "kind": "INPUT_OBJECT",
+ "name": "ClusterAgentDeleteInput",
+ "description": "Autogenerated input type of ClusterAgentDelete",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": "Global id of the cluster agent that will be deleted",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ClustersAgentID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ClusterAgentDeletePayload",
+ "description": "Autogenerated return type of ClusterAgentDelete",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "ClustersAgentID",
+ "description": "Identifier of Clusters::Agent",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "Commit",
"description": null,
@@ -18300,7 +18398,7 @@
{
"kind": "OBJECT",
"name": "GroupMember",
- "description": "Represents a Group Member",
+ "description": "Represents a Group Membership",
"fields": [
{
"name": "accessLevel",
@@ -18373,6 +18471,24 @@
"deprecationReason": null
},
{
+ "name": "id",
+ "description": "ID of the member",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updatedAt",
"description": "Date and time the membership was last updated",
"args": [
@@ -18387,6 +18503,24 @@
"deprecationReason": null
},
{
+ "name": "user",
+ "description": "User that is associated with the member object",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "User",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "userPermissions",
"description": "Permissions for the current user on the resource",
"args": [
@@ -23178,6 +23312,24 @@
"deprecationReason": null
},
{
+ "name": "id",
+ "description": "ID of the member",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updatedAt",
"description": "Date and time the membership was last updated",
"args": [
@@ -23190,6 +23342,24 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "user",
+ "description": "User that is associated with the member object",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "User",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
@@ -23210,6 +23380,118 @@
},
{
"kind": "OBJECT",
+ "name": "MemberInterfaceConnection",
+ "description": "The connection type for MemberInterface.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "MemberInterfaceEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "INTERFACE",
+ "name": "MemberInterface",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "MemberInterfaceEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "INTERFACE",
+ "name": "MemberInterface",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "MergeRequest",
"description": null,
"fields": [
@@ -27090,6 +27372,33 @@
"deprecationReason": null
},
{
+ "name": "clusterAgentDelete",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "ClusterAgentDeleteInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ClusterAgentDeletePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "commitCreate",
"description": null,
"args": [
@@ -33999,7 +34308,7 @@
],
"type": {
"kind": "OBJECT",
- "name": "ProjectMemberConnection",
+ "name": "MemberInterfaceConnection",
"ofType": null
},
"isDeprecated": false,
@@ -35133,7 +35442,7 @@
{
"kind": "OBJECT",
"name": "ProjectMember",
- "description": "Represents a Project Member",
+ "description": "Represents a Project Membership",
"fields": [
{
"name": "accessLevel",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index c33ee00a959..f36156411b7 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -274,6 +274,15 @@ Autogenerated return type of BoardListUpdateLimitMetrics
| `project` | Project | The project this cluster agent is associated with |
| `updatedAt` | Time | Timestamp the cluster agent was updated |
+## ClusterAgentDeletePayload
+
+Autogenerated return type of ClusterAgentDelete
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+
## Commit
| Name | Type | Description |
@@ -999,7 +1008,7 @@ Autogenerated return type of EpicTreeReorder
## GroupMember
-Represents a Group Member
+Represents a Group Membership
| Name | Type | Description |
| --- | ---- | ---------- |
@@ -1008,7 +1017,9 @@ Represents a Group Member
| `createdBy` | User | User that authorized membership |
| `expiresAt` | Time | Date and time the membership expires |
| `group` | Group | Group that a User is a member of |
+| `id` | ID! | ID of the member |
| `updatedAt` | Time | Date and time the membership was last updated |
+| `user` | User! | User that is associated with the member object |
| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
## GroupPermissions
@@ -1690,7 +1701,7 @@ Information about pagination in a connection.
## ProjectMember
-Represents a Project Member
+Represents a Project Membership
| Name | Type | Description |
| --- | ---- | ---------- |
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 2ff84a79d62..d471fe7092e 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -428,7 +428,7 @@ GET /projects/:id/issues?confidential=true
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
| `weight` **(STARTER)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
-| `with_labels_details` | boolean | no | If `true`, the response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. `description_html` Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) |
+| `with_labels_details` | boolean | no | If `true`, the response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. `description_html` was introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues"
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 694754a33d1..295cdbe8b38 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -2046,7 +2046,7 @@ This example creates four paths of execution:
because of `only/except` rules or otherwise does not exist, the
pipeline will be created with YAML error.
- The maximum number of jobs that a single job can need in the `needs:` array is limited:
- - For GitLab.com, the limit is ten. For more information, see our
+ - For GitLab.com, the limit is 50. For more information, see our
[infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/7541).
- For self-managed instances, the limit is: 50. This limit [can be changed](#changing-the-needs-job-limit-core-only).
- If `needs:` refers to a job that is marked as `parallel:`.
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 76175cb7b66..ea24e216d58 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -40,7 +40,7 @@ scheduling into milestones. Labeling is a task for everyone.
Most issues will have labels for at least one of the following:
-- Type: `~feature`, `~bug`, `~backstage`, `~documentation`, etc.
+- Type: `~feature`, `~bug`, `~tooling`, `~documentation`, etc.
- Stage: `~"devops::plan"`, `~"devops::create"`, etc.
- Group: `~"group::source code"`, `~"group::knowledge"`, `~"group::editor"`, etc.
- Category: `~"Category:Code Analytics"`, `~"Category:DevOps Score"`, `~"Category:Templates"`, etc.
@@ -67,7 +67,7 @@ The current type labels are:
- ~feature
- ~bug
-- ~backstage
+- ~tooling
- ~"support request"
- ~meta
- ~documentation
@@ -93,9 +93,9 @@ Following is a non-exhaustive list of facet labels:
- ~enhancement: This label can refine an issue that has the ~feature label.
- ~"master:broken": This label can refine an issue that has the ~bug label.
- ~"failure::flaky-test": This label can refine an issue that has the ~bug label.
-- ~"technical debt": This label can refine an issue that has the ~backstage label.
-- ~"static analysis": This label can refine an issue that has the ~backstage label.
-- ~"ci-build": This label can refine an issue that has the ~backstage label.
+- ~"technical debt": This label can refine an issue that has the ~tooling label.
+- ~"static analysis": This label can refine an issue that has the ~tooling label.
+- ~"ci-build": This label can refine an issue that has the ~tooling label.
- ~performance: A performance issue could describe a ~bug or a ~feature.
- ~security: A security issue could describe a ~bug or a ~feature.
- ~database: A database issue could describe a ~bug or a ~feature.
diff --git a/doc/development/internal_api.md b/doc/development/internal_api.md
index c51bf66be46..569128c9612 100644
--- a/doc/development/internal_api.md
+++ b/doc/development/internal_api.md
@@ -26,8 +26,8 @@ file, and include the token Base64 encoded in a `secret_token` parameter
or in the `Gitlab-Shared-Secret` header.
NOTE: **Note:**
-The internal API used by GitLab Pages uses a different kind of
-authentication.
+The internal API used by GitLab Pages, and GitLab Kubernetes Agent Server (kas) uses JSON Web Token (JWT)
+authentication, which is different from GitLab Shell.
## Git Authentication
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 54fcfeb2ee4..4d9f09bb8b0 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -11,7 +11,7 @@ as the hardware requirements that are needed to install and use GitLab.
### Supported Linux distributions
-- Ubuntu (16.04/18.04)
+- Ubuntu (16.04/18.04/20.04)
- Debian (8/9/10)
- CentOS (6/7/8)
- openSUSE (Leap 15.1/Enterprise Server 12.2)
diff --git a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png
deleted file mode 100644
index 591a08f4d7a..00000000000
--- a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_3.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_3.png
new file mode 100644
index 00000000000..c89179e739d
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_3.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index b8fcc513cb1..2ef6dd2e8f4 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -43,7 +43,7 @@ To use the instance, group, project, or pipeline security dashboard:
At the pipeline level, the Security section displays the vulnerabilities present in the branch of the project the pipeline was run against.
-![Pipeline Security Dashboard](img/pipeline_security_dashboard_v13_2.png)
+![Pipeline Security Dashboard](img/pipeline_security_dashboard_v13_3.png)
Visit the page for any pipeline that ran any of the [supported reports](#supported-reports). To view
the pipeline's security findings, select the **Security** tab when viewing the pipeline.
diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md
index 9b1f23f6d59..cc3c1c48a62 100644
--- a/doc/user/packages/composer_repository/index.md
+++ b/doc/user/packages/composer_repository/index.md
@@ -130,11 +130,8 @@ You also need to create a `auth.json` file with your GitLab credentials:
```json
{
- "http-basic": {
- "gitlab.com": {
- "username": "___token___",
- "password": "<personal_access_token>"
- }
+ "gitlab-token": {
+ "gitlab.com": "<personal_access_token>"
}
}
```
diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md
index 3838289aec4..8957960d098 100644
--- a/doc/user/project/import/gemnasium.md
+++ b/doc/user/project/import/gemnasium.md
@@ -105,7 +105,7 @@ back to both GitLab and GitHub when completed.
1. The result of the job will be visible directly from the pipeline view:
- ![Security Dashboard](../../application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png)
+ ![Security Dashboard](../../application_security/security_dashboard/img/pipeline_security_dashboard_v13_3.png)
NOTE: **Note:**
If you don't commit very often to your project, you may want to use
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index 7f64fd7efe3..49ace13f322 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -4,7 +4,15 @@ module API
# Kubernetes Internal API
module Internal
class Kubernetes < Grape::API::Instance
+ before do
+ authenticate_gitlab_kas_request!
+ end
+
helpers do
+ def authenticate_gitlab_kas_request!
+ unauthorized! unless Gitlab::Kas.verify_api_request(headers)
+ end
+
def agent_token
@agent_token ||= cluster_agent_token_from_authorization_token
end
diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
index b64990d6a7a..72ef0a8d067 100644
--- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -33,7 +33,7 @@ module Gitlab
def kubernetes_namespace
strong_memoize(:kubernetes_namespace) do
- Clusters::KubernetesNamespaceFinder.new(
+ ::Clusters::KubernetesNamespaceFinder.new(
deployment_cluster,
project: environment.project,
environment_name: environment.name,
@@ -47,7 +47,7 @@ module Gitlab
return if conflicting_ci_namespace_requested?(namespace)
- Clusters::Kubernetes::CreateOrUpdateNamespaceService.new(
+ ::Clusters::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: deployment_cluster,
kubernetes_namespace: namespace
).execute
@@ -71,7 +71,7 @@ module Gitlab
end
def build_namespace_record
- Clusters::BuildKubernetesNamespaceService.new(
+ ::Clusters::BuildKubernetesNamespaceService.new(
deployment_cluster,
environment: environment
).execute
diff --git a/lib/gitlab/json.rb b/lib/gitlab/json.rb
index d6681347f42..29cfec443e8 100644
--- a/lib/gitlab/json.rb
+++ b/lib/gitlab/json.rb
@@ -22,7 +22,7 @@ module Gitlab
string = string.to_s unless string.is_a?(String)
legacy_mode = legacy_mode_enabled?(opts.delete(:legacy_mode))
- data = adapter_load(string, opts)
+ data = adapter_load(string, **opts)
handle_legacy_mode!(data) if legacy_mode
diff --git a/lib/gitlab/kas.rb b/lib/gitlab/kas.rb
new file mode 100644
index 00000000000..08dde98e965
--- /dev/null
+++ b/lib/gitlab/kas.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kas
+ INTERNAL_API_REQUEST_HEADER = 'Gitlab-Kas-Api-Request'
+ JWT_ISSUER = 'gitlab-kas'
+
+ include JwtAuthenticatable
+
+ class << self
+ def verify_api_request(request_headers)
+ decode_jwt_for_issuer(JWT_ISSUER, request_headers[INTERNAL_API_REQUEST_HEADER])
+ rescue JWT::DecodeError
+ nil
+ end
+
+ def secret_path
+ Gitlab.config.gitlab_kas.secret_file
+ end
+
+ def ensure_secret!
+ return if File.exist?(secret_path)
+
+ write_secret
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 19faaaacf9a..42be49bdbd6 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -409,7 +409,7 @@ module Gitlab
def successful_deployments_with_cluster(scope)
scope
.joins(cluster: :deployments)
- .merge(Clusters::Cluster.enabled)
+ .merge(::Clusters::Cluster.enabled)
.merge(Deployment.success)
end
# rubocop: enable UsageData/LargeTable
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6e7c11c29d3..80d6d166da5 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5114,6 +5114,9 @@ msgstr ""
msgid "ClusterAgent|You have insufficient permissions to create a cluster agent for this project"
msgstr ""
+msgid "ClusterAgent|You have insufficient permissions to delete this cluster agent"
+msgstr ""
+
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
diff --git a/spec/frontend/monitoring/fixture_data.js b/spec/frontend/monitoring/fixture_data.js
index 30040d3f89f..18ec74550b4 100644
--- a/spec/frontend/monitoring/fixture_data.js
+++ b/spec/frontend/monitoring/fixture_data.js
@@ -1,8 +1,7 @@
import { stateAndPropsFromDataset } from '~/monitoring/utils';
import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
import { metricStates } from '~/monitoring/constants';
-import { convertObjectProps } from '~/lib/utils/common_utils';
-import { convertToCamelCase } from '~/lib/utils/text_utility';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { metricsResult } from './mock_data';
@@ -14,13 +13,7 @@ export const metricsDashboardResponse = getJSONFixture(
export const metricsDashboardPayload = metricsDashboardResponse.dashboard;
const datasetState = stateAndPropsFromDataset(
- // It's preferable to have props in snake_case, this will be addressed at:
- // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33574
- convertObjectProps(
- // Some props use kebab-case, convert to snake_case first
- key => convertToCamelCase(key.replace(/-/g, '_')),
- metricsDashboardResponse.metrics_data,
- ),
+ convertObjectPropsToCamelCase(metricsDashboardResponse.metrics_data),
);
// new properties like addDashboardDocumentationPath prop and alertsEndpoint
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 10669330b61..6f3e12a0789 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlSkeletonLoading, GlButton } from '@gitlab/ui';
import Table from '~/repository/components/table/index.vue';
import TableRow from '~/repository/components/table/row.vue';
@@ -34,12 +34,13 @@ const MOCK_BLOBS = [
},
];
-function factory({ path, isLoading = false, entries = {} }) {
+function factory({ path, isLoading = false, hasMore = true, entries = {} }) {
vm = shallowMount(Table, {
propsData: {
path,
isLoading,
entries,
+ hasMore,
},
mocks: {
$apollo,
@@ -88,4 +89,27 @@ describe('Repository table component', () => {
expect(rows.length).toEqual(3);
expect(rows.at(2).attributes().mode).toEqual('120000');
});
+
+ describe('Show more button', () => {
+ const showMoreButton = () => vm.find(GlButton);
+
+ it.each`
+ hasMore | expectButtonToExist
+ ${true} | ${true}
+ ${false} | ${false}
+ `('renders correctly', ({ hasMore, expectButtonToExist }) => {
+ factory({ path: '/', hasMore });
+ expect(showMoreButton().exists()).toBe(expectButtonToExist);
+ });
+
+ it('when is clicked, emits showMore event', async () => {
+ factory({ path: '/' });
+
+ showMoreButton().vm.$emit('click');
+
+ await vm.vm.$nextTick();
+
+ expect(vm.emitted('showMore')).toHaveLength(1);
+ });
+ });
});
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
index ea85cd34743..70dbfaea551 100644
--- a/spec/frontend/repository/components/tree_content_spec.js
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import { GlButton } from '@gitlab/ui';
import TreeContent, { INITIAL_FETCH_COUNT } from '~/repository/components/tree_content.vue';
import FilePreview from '~/repository/components/preview/index.vue';
+import FileTable from '~/repository/components/table/index.vue';
let vm;
let $apollo;
@@ -82,41 +82,36 @@ describe('Repository table component', () => {
});
});
- describe('Show more button', () => {
- const showMoreButton = () => vm.find(GlButton);
-
+ describe('FileTable showMore', () => {
describe('when is present', () => {
+ const fileTable = () => vm.find(FileTable);
+
beforeEach(async () => {
factory('/');
-
- vm.setData({ fetchCounter: 10, clickedShowMore: false });
-
- await vm.vm.$nextTick();
});
- it('is not rendered once it is clicked', async () => {
- showMoreButton().vm.$emit('click');
+ it('is changes hasShowMore to false when "showMore" event is emitted', async () => {
+ fileTable().vm.$emit('showMore');
+
await vm.vm.$nextTick();
- expect(showMoreButton().exists()).toBe(false);
+ expect(vm.vm.hasShowMore).toBe(false);
});
- it('is rendered', async () => {
- expect(showMoreButton().exists()).toBe(true);
- });
+ it('changes clickedShowMore when "showMore" event is emitted', async () => {
+ fileTable().vm.$emit('showMore');
- it('changes clickedShowMore when show more button is clicked', async () => {
- showMoreButton().vm.$emit('click');
+ await vm.vm.$nextTick();
expect(vm.vm.clickedShowMore).toBe(true);
});
- it('triggers fetchFiles when show more button is clicked', async () => {
+ it('triggers fetchFiles when "showMore" event is emitted', () => {
jest.spyOn(vm.vm, 'fetchFiles');
- showMoreButton().vm.$emit('click');
+ fileTable().vm.$emit('showMore');
- expect(vm.vm.fetchFiles).toBeCalled();
+ expect(vm.vm.fetchFiles).toHaveBeenCalled();
});
});
@@ -127,7 +122,7 @@ describe('Repository table component', () => {
await vm.vm.$nextTick();
- expect(showMoreButton().exists()).toBe(false);
+ expect(vm.vm.hasShowMore).toBe(false);
});
it('has limit of 1000 files on initial load', () => {
diff --git a/spec/graphql/resolvers/project_members_resolver_spec.rb b/spec/graphql/resolvers/project_members_resolver_spec.rb
index 602225cf632..91406d5c749 100644
--- a/spec/graphql/resolvers/project_members_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_members_resolver_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Resolvers::ProjectMembersResolver do
let_it_be(:root_group) { create(:group) }
let_it_be(:group_1) { create(:group, parent: root_group) }
let_it_be(:group_2) { create(:group, parent: root_group) }
- let_it_be(:project) { create(:project, :public, group: group_1) }
+ let_it_be(:project) { create(:project, group: group_1) }
let_it_be(:user_1) { create(:user, name: 'test user') }
let_it_be(:user_2) { create(:user, name: 'test user 2') }
@@ -24,7 +24,7 @@ RSpec.describe Resolvers::ProjectMembersResolver do
let(:args) { {} }
subject do
- resolve(described_class, obj: project, args: args, ctx: { context: user_4 })
+ resolve(described_class, obj: project, args: args, ctx: { current_user: user_4 })
end
describe '#resolve' do
@@ -50,11 +50,15 @@ RSpec.describe Resolvers::ProjectMembersResolver do
end
end
- context 'when project is nil' do
- let(:project) { nil }
+ context 'when user can not see project members' do
+ let_it_be(:other_user) { create(:user) }
- it 'returns nil' do
- expect(subject).to be_empty
+ subject do
+ resolve(described_class, obj: project, args: args, ctx: { current_user: other_user })
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
diff --git a/spec/graphql/types/member_interface_spec.rb b/spec/graphql/types/member_interface_spec.rb
new file mode 100644
index 00000000000..11fd09eb335
--- /dev/null
+++ b/spec/graphql/types/member_interface_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::MemberInterface do
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ id
+ access_level
+ created_by
+ created_at
+ updated_at
+ expires_at
+ user
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+
+ describe '.resolve_type' do
+ subject { described_class.resolve_type(object, {}) }
+
+ context 'for project member' do
+ let(:object) { build(:project_member) }
+
+ it { is_expected.to be Types::ProjectMemberType }
+ end
+
+ context 'for group member' do
+ let(:object) { build(:group_member) }
+
+ it { is_expected.to be Types::GroupMemberType }
+ end
+
+ context 'for an unkown type' do
+ let(:object) { build(:user) }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::BaseError)
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 8a5d0cdf12d..243aa1e68cc 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -108,7 +108,7 @@ RSpec.describe GitlabSchema.types['Project'] do
describe 'members field' do
subject { described_class.fields['projectMembers'] }
- it { is_expected.to have_graphql_type(Types::ProjectMemberType.connection_type) }
+ it { is_expected.to have_graphql_type(Types::MemberInterface.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::ProjectMembersResolver) }
end
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
index cb7d12b331a..d316f2b0a0a 100644
--- a/spec/helpers/environments_helper_spec.rb
+++ b/spec/helpers/environments_helper_spec.rb
@@ -18,34 +18,34 @@ RSpec.describe EnvironmentsHelper do
it 'returns data' do
expect(metrics_data).to include(
- 'settings-path' => edit_project_service_path(project, 'prometheus'),
- 'clusters-path' => project_clusters_path(project),
- 'metrics-dashboard-base-path' => environment_metrics_path(environment),
- 'current-environment-name' => environment.name,
- 'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
- 'add-dashboard-documentation-path' => help_page_path('operations/metrics/dashboards/index.md', anchor: 'add-a-new-dashboard-to-your-project'),
- 'empty-getting-started-svg-path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
- 'empty-loading-svg-path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'),
- 'empty-no-data-svg-path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
- 'empty-unable-to-connect-svg-path' => match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'),
- 'metrics-endpoint' => additional_metrics_project_environment_path(project, environment, format: :json),
- 'deployments-endpoint' => project_environment_deployments_path(project, environment, format: :json),
- 'default-branch' => 'master',
- 'project-path' => project_path(project),
- 'tags-path' => project_tags_path(project),
- 'has-metrics' => "#{environment.has_metrics?}",
- 'prometheus-status' => "#{environment.prometheus_status}",
- 'external-dashboard-url' => nil,
- 'environment-state' => environment.state,
- 'custom-metrics-path' => project_prometheus_metrics_path(project),
- 'validate-query-path' => validate_query_project_prometheus_metrics_path(project),
- 'custom-metrics-available' => 'true',
- 'alerts-endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
- 'prometheus-alerts-available' => 'true',
- 'custom-dashboard-base-path' => Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT,
- 'operations-settings-path' => project_settings_operations_path(project),
- 'can-access-operations-settings' => 'true',
- 'panel-preview-endpoint' => project_metrics_dashboards_builder_path(project, format: :json)
+ 'settings_path' => edit_project_service_path(project, 'prometheus'),
+ 'clusters_path' => project_clusters_path(project),
+ 'metrics_dashboard_base_path' => environment_metrics_path(environment),
+ 'current_environment_name' => environment.name,
+ 'documentation_path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'add_dashboard_documentation_path' => help_page_path('operations/metrics/dashboards/index.md', anchor: 'add-a-new-dashboard-to-your-project'),
+ 'empty_getting_started_svg_path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
+ 'empty_loading_svg_path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'),
+ 'empty_no_data_svg_path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
+ 'empty_unable_to_connect_svg_path' => match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'),
+ 'metrics_endpoint' => additional_metrics_project_environment_path(project, environment, format: :json),
+ 'deployments_endpoint' => project_environment_deployments_path(project, environment, format: :json),
+ 'default_branch' => 'master',
+ 'project_path' => project_path(project),
+ 'tags_path' => project_tags_path(project),
+ 'has_metrics' => "#{environment.has_metrics?}",
+ 'prometheus_status' => "#{environment.prometheus_status}",
+ 'external_dashboard_url' => nil,
+ 'environment_state' => environment.state,
+ 'custom_metrics_path' => project_prometheus_metrics_path(project),
+ 'validate_query_path' => validate_query_project_prometheus_metrics_path(project),
+ 'custom_metrics_available' => 'true',
+ 'alerts_endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
+ 'prometheus_alerts_available' => 'true',
+ 'custom_dashboard_base_path' => Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT,
+ 'operations_settings_path' => project_settings_operations_path(project),
+ 'can_access_operations_settings' => 'true',
+ 'panel_preview_endpoint' => project_metrics_dashboards_builder_path(project, format: :json)
)
end
@@ -58,7 +58,7 @@ RSpec.describe EnvironmentsHelper do
specify do
expect(metrics_data).to include(
- 'can-access-operations-settings' => 'false'
+ 'can_access_operations_settings' => 'false'
)
end
end
@@ -72,7 +72,7 @@ RSpec.describe EnvironmentsHelper do
it 'returns false' do
expect(metrics_data).to include(
- 'prometheus-alerts-available' => 'false'
+ 'prometheus_alerts_available' => 'false'
)
end
end
@@ -83,7 +83,7 @@ RSpec.describe EnvironmentsHelper do
end
it 'adds external_dashboard_url' do
- expect(metrics_data['external-dashboard-url']).to eq('http://gitlab.com')
+ expect(metrics_data['external_dashboard_url']).to eq('http://gitlab.com')
end
end
@@ -94,7 +94,7 @@ RSpec.describe EnvironmentsHelper do
subject { metrics_data }
- it { is_expected.to include('environment-state' => 'stopped') }
+ it { is_expected.to include('environment_state' => 'stopped') }
end
context 'when request is from project scoped metrics path' do
@@ -107,16 +107,16 @@ RSpec.describe EnvironmentsHelper do
context '/:namespace/:project/-/metrics' do
let(:path) { project_metrics_dashboard_path(project) }
- it 'uses correct path for metrics-dashboard-base-path' do
- expect(metrics_data['metrics-dashboard-base-path']).to eq(project_metrics_dashboard_path(project))
+ it 'uses correct path for metrics_dashboard_base_path' do
+ expect(metrics_data['metrics_dashboard_base_path']).to eq(project_metrics_dashboard_path(project))
end
end
context '/:namespace/:project/-/metrics/some_custom_dashboard.yml' do
let(:path) { "#{project_metrics_dashboard_path(project)}/some_custom_dashboard.yml" }
- it 'uses correct path for metrics-dashboard-base-path' do
- expect(metrics_data['metrics-dashboard-base-path']).to eq(project_metrics_dashboard_path(project))
+ it 'uses correct path for metrics_dashboard_base_path' do
+ expect(metrics_data['metrics_dashboard_base_path']).to eq(project_metrics_dashboard_path(project))
end
end
end
@@ -143,11 +143,11 @@ RSpec.describe EnvironmentsHelper do
describe '#environment_logs_data' do
it 'returns logs data' do
expected_data = {
- "environment-name": environment.name,
- "environments-path": project_environments_path(project, format: :json),
- "environment-id": environment.id,
- "cluster-applications-documentation-path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
- "clusters-path": project_clusters_path(project, format: :json)
+ "environment_name": environment.name,
+ "environments_path": project_environments_path(project, format: :json),
+ "environment_id": environment.id,
+ "cluster_applications_documentation_path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
+ "clusters_path": project_clusters_path(project, format: :json)
}
expect(helper.environment_logs_data(project, environment)).to eq(expected_data)
diff --git a/spec/lib/gitlab/kas_spec.rb b/spec/lib/gitlab/kas_spec.rb
new file mode 100644
index 00000000000..ce22f36e9fd
--- /dev/null
+++ b/spec/lib/gitlab/kas_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Kas do
+ let(:jwt_secret) { SecureRandom.random_bytes(described_class::SECRET_LENGTH) }
+
+ before do
+ allow(described_class).to receive(:secret).and_return(jwt_secret)
+ end
+
+ describe '.verify_api_request' do
+ let(:payload) { { 'iss' => described_class::JWT_ISSUER } }
+
+ it 'returns nil if fails to validate the JWT' do
+ encoded_token = JWT.encode(payload, 'wrongsecret', 'HS256')
+ headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
+
+ expect(described_class.verify_api_request(headers)).to be_nil
+ end
+
+ it 'returns the decoded JWT' do
+ encoded_token = JWT.encode(payload, described_class.secret, 'HS256')
+ headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
+
+ expect(described_class.verify_api_request(headers)).to eq([{ "iss" => described_class::JWT_ISSUER }, { "alg" => "HS256" }])
+ end
+ end
+
+ describe '.secret_path' do
+ it 'returns default gitlab config' do
+ expect(described_class.secret_path).to eq(Gitlab.config.gitlab_kas.secret_file)
+ end
+ end
+
+ describe '.ensure_secret!' do
+ context 'secret file exists' do
+ before do
+ allow(File).to receive(:exist?).with(Gitlab.config.gitlab_kas.secret_file).and_return(true)
+ end
+
+ it 'does not call write_secret' do
+ expect(described_class).not_to receive(:write_secret)
+
+ described_class.ensure_secret!
+ end
+ end
+
+ context 'secret file does not exist' do
+ before do
+ allow(File).to receive(:exist?).with(Gitlab.config.gitlab_kas.secret_file).and_return(false)
+ end
+
+ it 'calls write_secret' do
+ expect(described_class).to receive(:write_secret)
+
+ described_class.ensure_secret!
+ end
+ end
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 656a93cee62..da84b2b6a8f 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1257,10 +1257,8 @@ RSpec.describe MergeRequest do
let(:repository) { merge_request.source_project.repository }
context 'when the source project is set' do
- it 'memoizes the value and returns the result' do
- expect(repository).to receive(:branch_exists?).once.with(merge_request.source_branch).and_return(true)
-
- 2.times { expect(merge_request.source_branch_exists?).to eq(true) }
+ it 'returns true when the branch exists' do
+ expect(merge_request.source_branch_exists?).to eq(true)
end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index c4a9c0329c7..d001b6ec0f1 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -138,16 +138,16 @@ RSpec.describe Service do
describe '#can_test?' do
subject { service.can_test? }
- let(:service) { create(:service, project: project) }
+ let(:service) { build(:service, project: project) }
context 'when repository is not empty' do
- let(:project) { create(:project, :repository) }
+ let(:project) { build(:project, :repository) }
it { is_expected.to be true }
end
context 'when repository is empty' do
- let(:project) { create(:project) }
+ let(:project) { build(:project) }
it { is_expected.to be true }
end
@@ -165,10 +165,10 @@ RSpec.describe Service do
describe '#test' do
let(:data) { 'test' }
- let(:service) { create(:service, project: project) }
+ let(:service) { build(:service, project: project) }
context 'when repository is not empty' do
- let(:project) { create(:project, :repository) }
+ let(:project) { build(:project, :repository) }
it 'test runs execute' do
expect(service).to receive(:execute).with(data)
@@ -178,7 +178,7 @@ RSpec.describe Service do
end
context 'when repository is empty' do
- let(:project) { create(:project) }
+ let(:project) { build(:project) }
it 'test runs execute' do
expect(service).to receive(:execute).with(data)
@@ -622,8 +622,8 @@ RSpec.describe Service do
end
context 'logging' do
- let(:project) { create(:project) }
- let(:service) { create(:service, project: project) }
+ let(:project) { build(:project) }
+ let(:service) { build(:service, project: project) }
let(:test_message) { "test message" }
let(:arguments) do
{
diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb
index f5b8ebb545b..f5279af0483 100644
--- a/spec/requests/api/composer_packages_spec.rb
+++ b/spec/requests/api/composer_packages_spec.rb
@@ -26,30 +26,61 @@ RSpec.describe API::ComposerPackages do
group.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
- where(:project_visibility_level, :user_role, :member, :user_token, :include_package) do
- 'PUBLIC' | :developer | true | true | :include_package
- 'PUBLIC' | :developer | true | false | :include_package
- 'PUBLIC' | :developer | false | false | :include_package
- 'PUBLIC' | :developer | false | true | :include_package
- 'PUBLIC' | :guest | true | true | :include_package
- 'PUBLIC' | :guest | true | false | :include_package
- 'PUBLIC' | :guest | false | true | :include_package
- 'PUBLIC' | :guest | false | false | :include_package
- 'PUBLIC' | :anonymous | false | true | :include_package
- 'PRIVATE' | :developer | true | true | :include_package
- 'PRIVATE' | :developer | true | false | :does_not_include_package
- 'PRIVATE' | :developer | false | true | :does_not_include_package
- 'PRIVATE' | :developer | false | false | :does_not_include_package
- 'PRIVATE' | :guest | true | true | :does_not_include_package
- 'PRIVATE' | :guest | true | false | :does_not_include_package
- 'PRIVATE' | :guest | false | true | :does_not_include_package
- 'PRIVATE' | :guest | false | false | :does_not_include_package
- 'PRIVATE' | :anonymous | false | true | :does_not_include_package
+ context 'with basic auth' do
+ where(:project_visibility_level, :user_role, :member, :user_token, :include_package) do
+ 'PUBLIC' | :developer | true | true | :include_package
+ 'PUBLIC' | :developer | false | true | :include_package
+ 'PUBLIC' | :guest | true | true | :include_package
+ 'PUBLIC' | :guest | false | true | :include_package
+ 'PUBLIC' | :anonymous | false | true | :include_package
+ 'PRIVATE' | :developer | true | true | :include_package
+ 'PRIVATE' | :developer | false | true | :does_not_include_package
+ 'PRIVATE' | :guest | true | true | :does_not_include_package
+ 'PRIVATE' | :guest | false | true | :does_not_include_package
+ 'PRIVATE' | :anonymous | false | true | :does_not_include_package
+ 'PRIVATE' | :guest | false | false | :does_not_include_package
+ 'PRIVATE' | :guest | true | false | :does_not_include_package
+ 'PRIVATE' | :developer | false | false | :does_not_include_package
+ 'PRIVATE' | :developer | true | false | :does_not_include_package
+ 'PUBLIC' | :developer | true | false | :include_package
+ 'PUBLIC' | :guest | true | false | :include_package
+ 'PUBLIC' | :developer | false | false | :include_package
+ 'PUBLIC' | :guest | false | false | :include_package
+ end
+
+ with_them do
+ include_context 'Composer api project access', params[:project_visibility_level], params[:user_role], params[:user_token], :basic do
+ it_behaves_like 'Composer package index', params[:user_role], :success, params[:member], params[:include_package]
+ end
+ end
end
- with_them do
- include_context 'Composer api project access', params[:project_visibility_level], params[:user_role], params[:user_token] do
- it_behaves_like 'Composer package index', params[:user_role], :success, params[:member], params[:include_package]
+ context 'with private token header auth' do
+ where(:project_visibility_level, :user_role, :member, :user_token, :expected_status, :include_package) do
+ 'PUBLIC' | :developer | true | true | :success | :include_package
+ 'PUBLIC' | :developer | false | true | :success | :include_package
+ 'PUBLIC' | :guest | true | true | :success | :include_package
+ 'PUBLIC' | :guest | false | true | :success | :include_package
+ 'PUBLIC' | :anonymous | false | true | :success | :include_package
+ 'PRIVATE' | :developer | true | true | :success | :include_package
+ 'PRIVATE' | :developer | false | true | :success | :does_not_include_package
+ 'PRIVATE' | :guest | true | true | :success | :does_not_include_package
+ 'PRIVATE' | :guest | false | true | :success | :does_not_include_package
+ 'PRIVATE' | :anonymous | false | true | :success | :does_not_include_package
+ 'PRIVATE' | :guest | false | false | :unauthorized | nil
+ 'PRIVATE' | :guest | true | false | :unauthorized | nil
+ 'PRIVATE' | :developer | false | false | :unauthorized | nil
+ 'PRIVATE' | :developer | true | false | :unauthorized | nil
+ 'PUBLIC' | :developer | true | false | :unauthorized | nil
+ 'PUBLIC' | :guest | true | false | :unauthorized | nil
+ 'PUBLIC' | :developer | false | false | :unauthorized | nil
+ 'PUBLIC' | :guest | false | false | :unauthorized | nil
+ end
+
+ with_them do
+ include_context 'Composer api project access', params[:project_visibility_level], params[:user_role], params[:user_token], :token do
+ it_behaves_like 'Composer package index', params[:user_role], params[:expected_status], params[:member], params[:include_package]
+ end
end
end
end
@@ -105,22 +136,22 @@ RSpec.describe API::ComposerPackages do
context 'with valid project' do
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'Composer provider index' | :success
- 'PUBLIC' | :developer | true | false | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | true | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :developer | false | true | 'Composer provider index' | :success
- 'PUBLIC' | :developer | false | false | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | false | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :guest | true | true | 'Composer provider index' | :success
- 'PUBLIC' | :guest | true | false | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | true | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :guest | false | true | 'Composer provider index' | :success
- 'PUBLIC' | :guest | false | false | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | false | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'Composer provider index' | :success
'PRIVATE' | :developer | true | true | 'Composer provider index' | :success
- 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
- 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :guest | true | true | 'Composer empty provider index' | :success
- 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
- 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
end
@@ -151,22 +182,22 @@ RSpec.describe API::ComposerPackages do
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'Composer package api request' | :success
- 'PUBLIC' | :developer | true | false | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | true | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :developer | false | true | 'Composer package api request' | :success
- 'PUBLIC' | :developer | false | false | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | false | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :guest | true | true | 'Composer package api request' | :success
- 'PUBLIC' | :guest | true | false | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | true | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :guest | false | true | 'Composer package api request' | :success
- 'PUBLIC' | :guest | false | false | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | false | false | 'process Composer api request' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'Composer package api request' | :success
'PRIVATE' | :developer | true | true | 'Composer package api request' | :success
- 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
- 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :guest | true | true | 'process Composer api request' | :not_found
- 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
- 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
end
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
index c6049e098be..4b8ffb0675c 100644
--- a/spec/requests/api/graphql/project_query_spec.rb
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -5,7 +5,8 @@ require 'spec_helper'
RSpec.describe 'getting project information' do
include GraphqlHelpers
- let(:project) { create(:project, :repository) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, group: group) }
let(:current_user) { create(:user) }
let(:query) do
@@ -60,6 +61,51 @@ RSpec.describe 'getting project information' do
expect(graphql_data['project']['pipelines']['edges'].size).to eq(1)
end
end
+
+ it 'includes inherited members in project_members' do
+ group_member = create(:group_member, group: group)
+ project_member = create(:project_member, project: project)
+ member_query = <<~GQL
+ query {
+ project(fullPath: "#{project.full_path}") {
+ projectMembers {
+ nodes {
+ id
+ user {
+ username
+ }
+ ... on ProjectMember {
+ project {
+ id
+ }
+ }
+ ... on GroupMember {
+ group {
+ id
+ }
+ }
+ }
+ }
+ }
+ }
+ GQL
+
+ post_graphql(member_query, current_user: current_user)
+
+ member_ids = graphql_data.dig('project', 'projectMembers', 'nodes')
+ expect(member_ids).to include(
+ a_hash_including(
+ 'id' => group_member.to_global_id.to_s,
+ 'group' => { 'id' => group.to_global_id.to_s }
+ )
+ )
+ expect(member_ids).to include(
+ a_hash_including(
+ 'id' => project_member.to_global_id.to_s,
+ 'project' => { 'id' => project.to_global_id.to_s }
+ )
+ )
+ end
end
describe 'performance' do
diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb
index 555ca441fe7..a802e3a858a 100644
--- a/spec/requests/api/internal/kubernetes_spec.rb
+++ b/spec/requests/api/internal/kubernetes_spec.rb
@@ -3,21 +3,45 @@
require 'spec_helper'
RSpec.describe API::Internal::Kubernetes do
+ let(:jwt_auth_headers) do
+ jwt_token = JWT.encode({ 'iss' => Gitlab::Kas::JWT_ISSUER }, Gitlab::Kas.secret, 'HS256')
+
+ { Gitlab::Kas::INTERNAL_API_REQUEST_HEADER => jwt_token }
+ end
+
+ let(:jwt_secret) { SecureRandom.random_bytes(Gitlab::Kas::SECRET_LENGTH) }
+
+ before do
+ allow(Gitlab::Kas).to receive(:secret).and_return(jwt_secret)
+ end
+
describe "GET /internal/kubernetes/agent_info" do
+ def send_request(headers: {}, params: {})
+ get api('/internal/kubernetes/agent_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
+ end
+
+ context 'not authenticated' do
+ it 'returns 401' do
+ send_request(headers: { Gitlab::Kas::INTERNAL_API_REQUEST_HEADER => '' })
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
context 'kubernetes_agent_internal_api feature flag disabled' do
before do
stub_feature_flags(kubernetes_agent_internal_api: false)
end
it 'returns 404' do
- get api('/internal/kubernetes/agent_info')
+ send_request
expect(response).to have_gitlab_http_status(:not_found)
end
end
it 'returns 403 if Authorization header not sent' do
- get api('/internal/kubernetes/agent_info')
+ send_request
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -29,7 +53,7 @@ RSpec.describe API::Internal::Kubernetes do
let(:project) { agent.project }
it 'returns expected data', :aggregate_failures do
- get api('/internal/kubernetes/agent_info'), headers: { 'Authorization' => "Bearer #{agent_token.token}" }
+ send_request(headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:success)
@@ -56,7 +80,7 @@ RSpec.describe API::Internal::Kubernetes do
context 'no such agent exists' do
it 'returns 404' do
- get api('/internal/kubernetes/agent_info'), headers: { 'Authorization' => 'Bearer ABCD' }
+ send_request(headers: { 'Authorization' => 'Bearer ABCD' })
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -64,27 +88,39 @@ RSpec.describe API::Internal::Kubernetes do
end
describe 'GET /internal/kubernetes/project_info' do
+ def send_request(headers: {}, params: {})
+ get api('/internal/kubernetes/project_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
+ end
+
+ context 'not authenticated' do
+ it 'returns 401' do
+ send_request(headers: { Gitlab::Kas::INTERNAL_API_REQUEST_HEADER => '' })
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
context 'kubernetes_agent_internal_api feature flag disabled' do
before do
stub_feature_flags(kubernetes_agent_internal_api: false)
end
it 'returns 404' do
- get api('/internal/kubernetes/project_info')
+ send_request
expect(response).to have_gitlab_http_status(:not_found)
end
end
it 'returns 403 if Authorization header not sent' do
- get api('/internal/kubernetes/project_info')
+ send_request
expect(response).to have_gitlab_http_status(:forbidden)
end
context 'no such agent exists' do
it 'returns 404' do
- get api('/internal/kubernetes/project_info'), headers: { 'Authorization' => 'Bearer ABCD' }
+ send_request(headers: { 'Authorization' => 'Bearer ABCD' })
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -99,7 +135,7 @@ RSpec.describe API::Internal::Kubernetes do
let(:project) { create(:project, :public) }
it 'returns expected data', :aggregate_failures do
- get api('/internal/kubernetes/project_info'), params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }
+ send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:success)
@@ -126,7 +162,7 @@ RSpec.describe API::Internal::Kubernetes do
let(:project) { create(:project, :private) }
it 'returns 404' do
- get api('/internal/kubernetes/project_info'), params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }
+ send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -136,7 +172,7 @@ RSpec.describe API::Internal::Kubernetes do
let(:project) { create(:project, :internal) }
it 'returns 404' do
- get api('/internal/kubernetes/project_info'), params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }
+ send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -144,7 +180,7 @@ RSpec.describe API::Internal::Kubernetes do
context 'project does not exist' do
it 'returns 404' do
- get api('/internal/kubernetes/project_info'), params: { id: 0 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }
+ send_request(params: { id: 0 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:not_found)
end
diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb
index 14133731e37..cd9eae2f2ee 100644
--- a/spec/services/merge_requests/conflicts/list_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/list_service_spec.rb
@@ -30,7 +30,6 @@ RSpec.describe MergeRequests::Conflicts::ListService do
it 'returns a falsey value when one of the MR branches is missing' do
merge_request = create_merge_request('conflict-resolvable')
merge_request.project.repository.rm_branch(merge_request.author, 'conflict-resolvable')
- merge_request.clear_memoized_source_branch_exists
expect(conflicts_service(merge_request).can_be_resolved_in_ui?).to be_falsey
end
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
index 09743c20fba..5c122b4b5d6 100644
--- a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -16,8 +16,11 @@ RSpec.shared_examples 'Composer package index' do |user_type, status, add_member
subject
expect(response).to have_gitlab_http_status(status)
- expect(response).to match_response_schema('public_api/v4/packages/composer/index')
- expect(json_response).to eq presenter.root
+
+ if status == :success
+ expect(response).to match_response_schema('public_api/v4/packages/composer/index')
+ expect(json_response).to eq presenter.root
+ end
end
end
end
@@ -87,13 +90,22 @@ RSpec.shared_examples 'process Composer api request' do |user_type, status, add_
end
end
-RSpec.shared_context 'Composer auth headers' do |user_role, user_token|
+RSpec.shared_context 'Composer auth headers' do |user_role, user_token, auth_method = :token|
let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+
+ let(:headers) do
+ if user_role == :anonymous
+ {}
+ elsif auth_method == :token
+ { 'Private-Token' => token }
+ else
+ basic_auth_header(user.username, token)
+ end
+ end
end
-RSpec.shared_context 'Composer api project access' do |project_visibility_level, user_role, user_token|
- include_context 'Composer auth headers', user_role, user_token do
+RSpec.shared_context 'Composer api project access' do |project_visibility_level, user_role, user_token, auth_method|
+ include_context 'Composer auth headers', user_role, user_token, auth_method do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
end