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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/graphql/types/environment_type.rb6
-rw-r--r--app/graphql/types/permission_types/environment.rb11
-rw-r--r--app/services/merge_requests/create_service.rb2
-rw-r--r--doc/api/graphql/reference/index.md11
-rw-r--r--lib/gitlab/graphql/expose_permissions.rb8
-rw-r--r--qa/qa/resource/bulk_import_group.rb2
-rw-r--r--qa/qa/resource/issuable.rb3
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb93
-rw-r--r--qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb4
-rw-r--r--scripts/lib/glfm/update_specification.rb2
-rw-r--r--spec/graphql/types/environment_type_spec.rb1
-rw-r--r--spec/graphql/types/permission_types/environment_spec.rb15
-rw-r--r--spec/requests/api/graphql/project/environments_spec.rb54
13 files changed, 166 insertions, 46 deletions
diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb
index dd2286d333d..2249fa49f06 100644
--- a/app/graphql/types/environment_type.rb
+++ b/app/graphql/types/environment_type.rb
@@ -9,6 +9,12 @@ module Types
authorize :read_environment
+ expose_permissions Types::PermissionTypes::Environment,
+ description: 'Permissions for the current user on the resource. '\
+ 'This field can only be resolved for one environment in any single request.' do
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+ end
+
field :name, GraphQL::Types::String, null: false,
description: 'Human-readable name of the environment.'
diff --git a/app/graphql/types/permission_types/environment.rb b/app/graphql/types/permission_types/environment.rb
new file mode 100644
index 00000000000..59c9fce64e5
--- /dev/null
+++ b/app/graphql/types/permission_types/environment.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class Environment < BasePermissionType
+ graphql_name 'EnvironmentPermissions'
+
+ abilities :update_environment, :destroy_environment, :stop_environment
+ end
+ end
+end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 04d08f257f1..8fa80dc3513 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -39,7 +39,7 @@ module MergeRequests
# open while the Gitaly RPC waits. To avoid an idle in transaction
# timeout, we do this before we attempt to save the merge request.
- if Feature.enabled?(:async_merge_request_diff_creation, current_user)
+ if Feature.enabled?(:async_merge_request_diff_creation, merge_request.target_project)
merge_request.skip_ensure_merge_request_diff = true
else
merge_request.eager_fetch_ref!
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 79202e500b7..e1fe6a0c1bf 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -12375,6 +12375,7 @@ Describes where code is deployed for a project.
| <a id="environmentstate"></a>`state` | [`String!`](#string) | State of the environment, for example: available/stopped. |
| <a id="environmenttier"></a>`tier` | [`DeploymentTier`](#deploymenttier) | Deployment tier of the environment. |
| <a id="environmentupdatedat"></a>`updatedAt` | [`Time`](#time) | When the environment was updated. |
+| <a id="environmentuserpermissions"></a>`userPermissions` | [`EnvironmentPermissions!`](#environmentpermissions) | Permissions for the current user on the resource. This field can only be resolved for one environment in any single request. |
#### Fields with arguments
@@ -12419,6 +12420,16 @@ Returns [`MetricsDashboard`](#metricsdashboard).
| ---- | ---- | ----------- |
| <a id="environmentmetricsdashboardpath"></a>`path` | [`String!`](#string) | Path to a file which defines a metrics dashboard eg: `"config/prometheus/common_metrics.yml"`. |
+### `EnvironmentPermissions`
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="environmentpermissionsdestroyenvironment"></a>`destroyEnvironment` | [`Boolean!`](#boolean) | Indicates the user can perform `destroy_environment` on this resource. |
+| <a id="environmentpermissionsstopenvironment"></a>`stopEnvironment` | [`Boolean!`](#boolean) | Indicates the user can perform `stop_environment` on this resource. |
+| <a id="environmentpermissionsupdateenvironment"></a>`updateEnvironment` | [`Boolean!`](#boolean) | Indicates the user can perform `update_environment` on this resource. |
+
### `Epic`
Represents an epic.
diff --git a/lib/gitlab/graphql/expose_permissions.rb b/lib/gitlab/graphql/expose_permissions.rb
index ab9ed354673..070d3c188a4 100644
--- a/lib/gitlab/graphql/expose_permissions.rb
+++ b/lib/gitlab/graphql/expose_permissions.rb
@@ -5,11 +5,15 @@ module Gitlab
module ExposePermissions
extend ActiveSupport::Concern
prepended do
- def self.expose_permissions(permission_type, description: 'Permissions for the current user on the resource')
+ def self.expose_permissions(
+ permission_type,
+ description: 'Permissions for the current user on the resource',
+ &block)
field :user_permissions, permission_type,
description: description,
null: false,
- method: :itself
+ method: :itself,
+ &block
end
end
end
diff --git a/qa/qa/resource/bulk_import_group.rb b/qa/qa/resource/bulk_import_group.rb
index 2daf9684bbc..19ad5f1faf2 100644
--- a/qa/qa/resource/bulk_import_group.rb
+++ b/qa/qa/resource/bulk_import_group.rb
@@ -93,7 +93,7 @@ module QA
# override transformation only for /bulk_imports endpoint which doesn't have web_url in response and
# ignore others so import_id is not overwritten incorrectly
- api_resource[:web_url] = "#{source_gitlab_address}/#{full_path}"
+ api_resource[:web_url] = "#{QA::Runtime::Scenario.gitlab_address}/#{full_path}"
api_resource[:import_id] = api_resource[:id]
api_resource
end
diff --git a/qa/qa/resource/issuable.rb b/qa/qa/resource/issuable.rb
index 6ebdaac8298..5aee27b46d4 100644
--- a/qa/qa/resource/issuable.rb
+++ b/qa/qa/resource/issuable.rb
@@ -3,6 +3,8 @@
module QA
module Resource
class Issuable < Base
+ using Rainbow
+
# Commentes (notes) path
#
# @return [String]
@@ -14,6 +16,7 @@ module QA
#
# @return [Array]
def comments(auto_paginate: false, attempts: 0)
+ Runtime::Logger.debug("Fetching comments for #{self.class.name.black.bg(:white)} with path '#{api_get_path}'")
return parse_body(api_get_from(api_comments_path)) unless auto_paginate
auto_paginated_response(
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
index 985edf3efc1..a7fe9c7630e 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
@@ -13,13 +13,7 @@ module QA
let!(:source_gitlab_address) { ENV["QA_LARGE_IMPORT_SOURCE_URL"] || "https://gitlab.com" }
let!(:gitlab_source_group) { ENV["QA_LARGE_IMPORT_GROUP"] || "gitlab-migration-large-import-test" }
let!(:gitlab_source_project) { ENV["QA_LARGE_IMPORT_REPO"] || "migration-test-project" }
-
- let!(:import_wait_duration) do
- {
- max_duration: (ENV["QA_LARGE_IMPORT_DURATION"] || 3600).to_i,
- sleep_interval: 30
- }
- end
+ let!(:import_wait_duration) { { max_duration: (ENV["QA_LARGE_IMPORT_DURATION"] || 3600).to_i, sleep_interval: 30 } }
let!(:source_admin_user) { "no-op" }
let!(:source_admin_api_client) do
@@ -56,8 +50,8 @@ module QA
let(:source_labels) { source_project.labels(auto_paginate: true).map { |l| l.except(:id) } }
let(:source_milestones) { source_project.milestones(auto_paginate: true).map { |ms| ms.except(:id, :web_url, :project_id) } }
let(:source_pipelines) { source_project.pipelines(auto_paginate: true).map { |pp| pp.except(:id, :web_url, :project_id) } }
- let(:source_mrs) { fetch_mrs(source_project, source_api_client) }
- let(:source_issues) { fetch_issues(source_project, source_api_client) }
+ let(:source_mrs) { fetch_mrs(source_project, source_api_client, transform_urls: true) }
+ let(:source_issues) { fetch_issues(source_project, source_api_client, transform_urls: true) }
# Imported objects
#
@@ -66,12 +60,10 @@ module QA
let(:commits) { imported_project.commits(auto_paginate: true).map { |c| c[:id] } }
let(:labels) { imported_project.labels(auto_paginate: true).map { |l| l.except(:id) } }
let(:milestones) { imported_project.milestones(auto_paginate: true).map { |ms| ms.except(:id, :web_url, :project_id) } }
- let(:pipelines) { imported_project.pipelines.map { |pp| pp.except(:id, :web_url, :project_id) } }
+ let(:pipelines) { imported_project.pipelines(auto_paginate: true).map { |pp| pp.except(:id, :web_url, :project_id) } }
let(:mrs) { fetch_mrs(imported_project, api_client) }
let(:issues) { fetch_issues(imported_project, api_client) }
- let(:import_failures) { imported_group.import_details.sum([]) { |details| details[:failures] } }
-
before do
Runtime::Feature.enable(:bulk_import_projects) unless Runtime::Feature.enabled?(:bulk_import_projects)
end
@@ -139,10 +131,7 @@ module QA
# wait for import to finish and save import time
logger.info("== Waiting for import to be finished ==")
- expect { imported_group.import_status }.not_to eventually_eq("started").within(import_wait_duration)
- # finished status actually means success, don't wait for finished status explicitly
- # because test would wait full duration if returned status is "failed"
- expect(imported_group.import_status).to eq("finished")
+ expect_group_import_finished_successfully
@import_time = Time.now - start
@@ -305,11 +294,12 @@ module QA
#
# @param [QA::Resource::Project]
# @param [Runtime::API::Client] client
+ # @param [Boolean] transform_urls
# @return [Hash]
- def fetch_mrs(project, client)
+ def fetch_mrs(project, client, transform_urls: false)
imported_mrs = project.merge_requests(auto_paginate: true, attempts: 2)
- Parallel.map(imported_mrs, in_threads: 4) do |mr|
+ Parallel.map(imported_mrs, in_threads: 6) do |mr|
resource = Resource::MergeRequest.init do |resource|
resource.project = project
resource.iid = mr[:iid]
@@ -319,11 +309,11 @@ module QA
[mr[:iid], {
url: mr[:web_url],
title: mr[:title],
- body: sanitize_description(mr[:description]) || '',
+ body: sanitize_description(mr[:description], transform_urls) || '',
state: mr[:state],
comments: resource
.comments(auto_paginate: true, attempts: 2)
- .map { |c| sanitize_comment(c[:body]) }
+ .map { |c| sanitize_comment(c[:body], transform_urls) }
}]
end.to_h
end
@@ -332,11 +322,12 @@ module QA
#
# @param [QA::Resource::Project]
# @param [Runtime::API::Client] client
+ # @param [Boolean] transform_urls
# @return [Hash]
- def fetch_issues(project, client)
+ def fetch_issues(project, client, transform_urls: false)
imported_issues = project.issues(auto_paginate: true, attempts: 2)
- Parallel.map(imported_issues, in_threads: 4) do |issue|
+ Parallel.map(imported_issues, in_threads: 6) do |issue|
resource = Resource::Issue.init do |issue_resource|
issue_resource.project = project
issue_resource.iid = issue[:iid]
@@ -347,42 +338,66 @@ module QA
url: issue[:web_url],
title: issue[:title],
state: issue[:state],
- body: sanitize_description(issue[:description]) || '',
+ body: sanitize_description(issue[:description], transform_urls) || '',
comments: resource
.comments(auto_paginate: true, attempts: 2)
- .map { |c| sanitize_comment(c[:body]) }
+ .map { |c| sanitize_comment(c[:body], transform_urls) }
}]
end.to_h
end
- # Importer user mention pattern
+ # Remove added postfixes and transform urls
#
- # @return [Regex]
- def created_by_pattern
- @created_by_pattern ||= /\n\n \*By #{importer_username_pattern} on \S+ \(imported from GitLab\)\*/
+ # Source urls need to be replaced with target urls for comparison to work
+ #
+ # @param [String] body
+ # @param [Boolean] transform_urls
+ # @return [String]
+ def sanitize_comment(body, transform_urls)
+ comment = body&.gsub(created_by_pattern, "")
+ return comment unless transform_urls
+
+ comment&.gsub(source_project_url, imported_project_url)
end
- # Username of importer user for removal from comments and descriptions
+ # Remove added postfixes and transform urls
+ #
+ # Source urls need to be replaced with target urls for comparison to work
#
+ # @param [String] body
+ # @param [Boolean] transform_urls
# @return [String]
- def importer_username_pattern
- @importer_username_pattern ||= ENV['QA_LARGE_IMPORT_USER_PATTERN'] || "(gitlab-migration|GitLab QA Bot)"
+ def sanitize_description(body, transform_urls)
+ description = body&.gsub(created_by_pattern, "")
+ return description unless transform_urls
+
+ description&.gsub(source_project_url, imported_project_url)
end
- # Remove added prefixes from comments
+ # Following objects are memoized via instance variables due to Parallel having some type of issue calling
+ # helpers defined via rspec let method
+
+ # Importer user mention pattern
+ #
+ # @return [Regex]
+ def created_by_pattern
+ @created_by_pattern ||= /\n\n \*By .+ on \S+ \(imported from GitLab\)\*/
+ end
+
+ # Source project url
#
- # @param [String] body
# @return [String]
- def sanitize_comment(body)
- body&.gsub(created_by_pattern, "")
+ def source_project_url
+ @source_group_url ||= "#{source_gitlab_address}/#{source_project.full_path}"
end
- # Remove created by prefix from descripion
+ # Imported project url
+ #
+ # This needs to be constructed manually because it is called before project import finishes
#
- # @param [String] body
# @return [String]
- def sanitize_description(body)
- body&.gsub(created_by_pattern, "")
+ def imported_project_url
+ @imported_group_url ||= "#{Runtime::Scenario.gitlab_address}/#{imported_group.full_path}/#{source_project.path}"
end
# Save json as file
diff --git a/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb
index f9df6e805c0..6075efd348b 100644
--- a/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb
+++ b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb
@@ -79,13 +79,13 @@ module QA
imported_group # trigger import
status = nil
- Support::Retrier.retry_until(**import_wait_duration, message: "Import did not complete") do
+ Support::Retrier.retry_until(**import_wait_duration, raise_on_failure: false) do
status = imported_group.import_status
%w[finished failed].include?(status)
end
# finished status means success, all other statuses are considered to fail the test
- expect(status).to eq('finished')
+ expect(status).to eq('finished'), "Expected import to finish successfully, but status was: #{status}"
end
before do
diff --git a/scripts/lib/glfm/update_specification.rb b/scripts/lib/glfm/update_specification.rb
index 1a66928bae4..ef6f24d5a77 100644
--- a/scripts/lib/glfm/update_specification.rb
+++ b/scripts/lib/glfm/update_specification.rb
@@ -306,7 +306,7 @@ module Glfm
end
def write_spec_html(spec_html_string)
- output("Writing #{GLFM_SPEC_TXT_PATH}...")
+ output("Writing #{GLFM_SPEC_HTML_PATH}...")
FileUtils.mkdir_p(Pathname.new(GLFM_SPEC_HTML_PATH).dirname)
write_file(GLFM_SPEC_HTML_PATH, "#{spec_html_string}\n")
end
diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb
index 2605beac95a..a891a5c285b 100644
--- a/spec/graphql/types/environment_type_spec.rb
+++ b/spec/graphql/types/environment_type_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Environment'] do
specify { expect(described_class.graphql_name).to eq('Environment') }
+ specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Environment) }
it 'includes the expected fields' do
expected_fields = %w[
diff --git a/spec/graphql/types/permission_types/environment_spec.rb b/spec/graphql/types/permission_types/environment_spec.rb
new file mode 100644
index 00000000000..944699c972a
--- /dev/null
+++ b/spec/graphql/types/permission_types/environment_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::PermissionTypes::Environment, feature_category: :continuous_delivery do
+ it do
+ expected_permissions = [
+ :update_environment, :destroy_environment, :stop_environment
+ ]
+
+ expected_permissions.each do |permission|
+ expect(described_class).to have_graphql_field(permission)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/environments_spec.rb b/spec/requests/api/graphql/project/environments_spec.rb
index e5b6aebbf2c..a86edde2ef3 100644
--- a/spec/requests/api/graphql/project/environments_spec.rb
+++ b/spec/requests/api/graphql/project/environments_spec.rb
@@ -47,6 +47,60 @@ RSpec.describe 'Project Environments query' do
expect(environment_data['environmentType']).to eq(production.environment_type)
end
+ describe 'user permissions' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{production.name}") {
+ userPermissions {
+ updateEnvironment
+ destroyEnvironment
+ stopEnvironment
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns user permissions of the environment', :aggregate_failures do
+ subject
+
+ permission_data = graphql_data.dig('project', 'environment', 'userPermissions')
+ expect(permission_data['updateEnvironment']).to eq(true)
+ expect(permission_data['destroyEnvironment']).to eq(false)
+ expect(permission_data['stopEnvironment']).to eq(true)
+ end
+
+ context 'when fetching user permissions for multiple environments' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environments {
+ nodes {
+ userPermissions {
+ updateEnvironment
+ destroyEnvironment
+ stopEnvironment
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'limits the result', :aggregate_failures do
+ subject
+
+ expect_graphql_errors_to_include('"userPermissions" field can be requested only ' \
+ 'for 1 Environment(s) at a time.')
+ end
+ end
+ end
+
describe 'last deployments of environments' do
::Deployment.statuses.each do |status, _|
let_it_be(:"production_#{status}_deployment") do