Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-26 12:10:08 +00:00
parent ec3483bd18
commit c4af99d56f
85 changed files with 968 additions and 598 deletions

View File

@ -28,55 +28,6 @@ Graphql/OldTypes:
Exclude:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
- 'app/graphql/types/access_level_type.rb'
- 'app/graphql/types/admin/analytics/usage_trends/measurement_type.rb'
- 'app/graphql/types/admin/sidekiq_queues/delete_jobs_response_type.rb'
- 'app/graphql/types/alert_management/alert_status_counts_type.rb'
- 'app/graphql/types/alert_management/alert_type.rb'
- 'app/graphql/types/alert_management/integration_type.rb'
- 'app/graphql/types/award_emojis/award_emoji_type.rb'
- 'app/graphql/types/blob_viewer_type.rb'
- 'app/graphql/types/board_list_type.rb'
- 'app/graphql/types/board_type.rb'
- 'app/graphql/types/boards/board_issuable_input_base_type.rb'
- 'app/graphql/types/boards/board_issue_input_base_type.rb'
- 'app/graphql/types/boards/board_issue_input_type.rb'
- 'app/graphql/types/branch_type.rb'
- 'app/graphql/types/ci/application_setting_type.rb'
- 'app/graphql/types/ci/build_need_type.rb'
- 'app/graphql/types/ci/ci_cd_setting_type.rb'
- 'app/graphql/types/ci/config/config_type.rb'
- 'app/graphql/types/ci/config/group_type.rb'
- 'app/graphql/types/ci/config/job_type.rb'
- 'app/graphql/types/ci/config/need_type.rb'
- 'app/graphql/types/ci/config/stage_type.rb'
- 'app/graphql/types/ci/detailed_status_type.rb'
- 'app/graphql/types/ci/group_type.rb'
- 'app/graphql/types/ci/job_artifact_type.rb'
- 'app/graphql/types/ci/job_type.rb'
- 'app/graphql/types/ci/pipeline_type.rb'
- 'app/graphql/types/ci/recent_failures_type.rb'
- 'app/graphql/types/ci/runner_architecture_type.rb'
- 'app/graphql/types/ci/runner_platform_type.rb'
- 'app/graphql/types/ci/runner_setup_type.rb'
- 'app/graphql/types/ci/runner_type.rb'
- 'app/graphql/types/ci/stage_type.rb'
- 'app/graphql/types/ci/status_action_type.rb'
- 'app/graphql/types/ci/template_type.rb'
- 'app/graphql/types/ci/test_case_type.rb'
- 'app/graphql/types/ci/test_report_total_type.rb'
- 'app/graphql/types/ci/test_suite_summary_type.rb'
- 'app/graphql/types/ci/test_suite_type.rb'
- 'app/graphql/types/ci_configuration/sast/analyzers_entity_input_type.rb'
- 'app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb'
- 'app/graphql/types/ci_configuration/sast/entity_input_type.rb'
- 'app/graphql/types/ci_configuration/sast/entity_type.rb'
- 'app/graphql/types/ci_configuration/sast/options_entity_type.rb'
- 'app/graphql/types/container_expiration_policy_type.rb'
- 'app/graphql/types/container_repository_tag_type.rb'
- 'app/graphql/types/container_repository_type.rb'
- 'app/graphql/types/countable_connection_type.rb'
- 'app/graphql/types/custom_emoji_type.rb'
- 'ee/app/graphql/ee/mutations/ci/ci_cd_settings_update.rb'
- 'ee/app/graphql/ee/resolvers/issues_resolver.rb'
- 'ee/app/graphql/ee/resolvers/namespace_projects_resolver.rb'

View File

@ -1,14 +1,14 @@
import { sanitize as dompurifySanitize, addHook } from 'dompurify';
import { getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
// Safely allow SVG <use> tags
const defaultConfig = {
// Safely allow SVG <use> tags
ADD_TAGS: ['use'],
// Prevent possible XSS attacks with data-* attributes used by @rails/ujs
// See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1421
FORBID_ATTR: ['data-remote', 'data-url', 'data-type', 'data-method'],
};
const forbiddenDataAttrs = ['data-remote', 'data-url', 'data-type', 'data-method'];
// Only icons urls from `gon` are allowed
const getAllowedIconUrls = (gon = window.gon) =>
[gon.sprite_file_icons, gon.sprite_icons].filter(Boolean);
@ -46,19 +46,10 @@ const sanitizeSvgIcon = (node) => {
removeUnsafeHref(node, 'xlink:href');
};
const sanitizeHTMLAttributes = (node) => {
forbiddenDataAttrs.forEach((attr) => {
if (node.hasAttribute(attr)) {
node.removeAttribute(attr);
}
});
};
addHook('afterSanitizeAttributes', (node) => {
if (node.tagName.toLowerCase() === 'use') {
sanitizeSvgIcon(node);
}
sanitizeHTMLAttributes(node);
});
export const sanitize = (val, config = defaultConfig) => dompurifySanitize(val, config);

View File

@ -8,20 +8,18 @@ export default {
},
inject: ['upgradePath'],
i18n: {
title: s__('SecurityConfiguration|Secure your project with Ultimate'),
title: s__('SecurityConfiguration|Secure your project'),
bodyStart: s__(
`SecurityConfiguration|GitLab Ultimate checks your application for security vulnerabilities
that may lead to unauthorized access, data leaks, and denial of service
attacks. Its features include:`,
`SecurityConfiguration|Immediately begin risk analysis and remediation with application security features. Start with SAST and Secret Detection, available to all plans. Upgrade to Ultimate to get all features, including:`,
),
bodyListItems: [
s__('SecurityConfiguration|Vulnerability details and statistics in the merge request.'),
s__('SecurityConfiguration|High-level vulnerability statistics across projects and groups.'),
s__('SecurityConfiguration|Runtime security metrics for application environments.'),
s__('SecurityConfiguration|Vulnerability details and statistics in the merge request'),
s__('SecurityConfiguration|High-level vulnerability statistics across projects and groups'),
s__('SecurityConfiguration|Runtime security metrics for application environments'),
s__(
'SecurityConfiguration|More scan types, including Container Scanning, DAST, Dependency Scanning, Fuzzing, and Licence Compliance',
),
],
bodyEnd: s__(
'SecurityConfiguration|With the information provided, you can immediately begin risk analysis and remediation within GitLab.',
),
buttonText: s__('SecurityConfiguration|Upgrade or start a free trial'),
},
};
@ -32,14 +30,14 @@ export default {
:title="$options.i18n.title"
:button-text="$options.i18n.buttonText"
:button-link="upgradePath"
variant="introduction"
v-on="$listeners"
>
<p>{{ $options.i18n.bodyStart }}</p>
<ul>
<ul class="gl-pl-6">
<li v-for="bodyListItem in $options.i18n.bodyListItems" :key="bodyListItem">
{{ bodyListItem }}
</li>
</ul>
<p>{{ $options.i18n.bodyEnd }}</p>
</gl-banner>
</template>

View File

@ -9,7 +9,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_read_build_trace!, only: [:trace, :raw]
before_action :authorize_read_build!
before_action :authorize_update_build!,
except: [:index, :show, :status, :raw, :trace, :erase]
except: [:index, :show, :status, :raw, :trace, :erase, :cancel, :unschedule]
before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
@ -93,22 +93,28 @@ class Projects::JobsController < Projects::ApplicationController
end
def cancel
return respond_422 unless @build.cancelable?
service_response = Ci::BuildCancelService.new(@build, current_user).execute
@build.cancel
if continue_params[:to]
redirect_to continue_params[:to]
if service_response.success?
destination = continue_params[:to].presence || builds_project_pipeline_path(@project, @build.pipeline.id)
redirect_to destination
elsif service_response.http_status == :forbidden
access_denied!
else
redirect_to builds_project_pipeline_path(@project, @build.pipeline.id)
head service_response.http_status
end
end
def unschedule
return respond_422 unless @build.scheduled?
service_response = Ci::BuildUnscheduleService.new(@build, current_user).execute
@build.unschedule!
redirect_to build_path(@build)
if service_response.success?
redirect_to build_path(@build)
elsif service_response.http_status == :forbidden
access_denied!
else
head service_response.http_status
end
end
def status

View File

@ -6,7 +6,7 @@ module Types
graphql_name 'AccessLevel'
description 'Represents the access level of a relationship between a User and object that it is related to'
field :integer_value, GraphQL::INT_TYPE, null: true,
field :integer_value, GraphQL::Types::Int, null: true,
description: 'Integer representation of access level.',
method: :to_i

View File

@ -14,7 +14,7 @@ module Types
field :recorded_at, Types::TimeType, null: true,
description: 'The time the measurement was recorded.'
field :count, GraphQL::INT_TYPE, null: false,
field :count, GraphQL::Types::Int, null: false,
description: 'Object count.'
field :identifier, Types::Admin::Analytics::UsageTrends::MeasurementIdentifierEnum, null: false,

View File

@ -10,17 +10,17 @@ module Types
description 'The response from the AdminSidekiqQueuesDeleteJobs mutation'
field :completed,
GraphQL::BOOLEAN_TYPE,
GraphQL::Types::Boolean,
null: true,
description: 'Whether or not the entire queue was processed in time; if not, retrying the same request is safe.'
field :deleted_jobs,
GraphQL::INT_TYPE,
GraphQL::Types::Int,
null: true,
description: 'The number of matching jobs deleted.'
field :queue_size,
GraphQL::INT_TYPE,
GraphQL::Types::Int,
null: true,
description: 'The queue size after processing.'
end

View File

@ -11,18 +11,18 @@ module Types
::AlertManagement::Alert.status_names.each do |status|
field status,
GraphQL::INT_TYPE,
GraphQL::Types::Int,
null: true,
description: "Number of alerts with status #{status.to_s.upcase} for the project"
end
field :open,
GraphQL::INT_TYPE,
GraphQL::Types::Int,
null: true,
description: 'Number of alerts with status TRIGGERED or ACKNOWLEDGED for the project.'
field :all,
GraphQL::INT_TYPE,
GraphQL::Types::Int,
null: true,
description: 'Total number of alerts for the project.'
end

View File

@ -13,12 +13,12 @@ module Types
authorize :read_alert_management_alert
field :iid,
GraphQL::ID_TYPE,
GraphQL::Types::ID,
null: false,
description: 'Internal ID of the alert.'
field :issue_iid,
GraphQL::ID_TYPE,
GraphQL::Types::ID,
null: true,
deprecated: { reason: 'Use issue field', milestone: '13.10' },
description: 'Internal ID of the GitLab issue attached to the alert.'
@ -29,12 +29,12 @@ module Types
description: 'Issue attached to the alert.'
field :title,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'Title of the alert.'
field :description,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'Description of the alert.'
@ -50,17 +50,17 @@ module Types
method: :status_name
field :service,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'Service the alert came from.'
field :monitoring_tool,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'Monitoring tool the alert came from.'
field :hosts,
[GraphQL::STRING_TYPE],
[GraphQL::Types::String],
null: true,
description: 'List of hosts the alert came from.'
@ -80,7 +80,7 @@ module Types
description: 'Environment for the alert.'
field :event_count,
GraphQL::INT_TYPE,
GraphQL::Types::Int,
null: true,
description: 'Number of events of this alert.',
method: :events
@ -106,12 +106,12 @@ module Types
description: 'Assignees of the alert.'
field :metrics_dashboard_url,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'URL for metrics embed for the alert.'
field :runbook,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'Runbook for the alert as defined in alert details.'
@ -122,7 +122,7 @@ module Types
resolver: Resolvers::TodoResolver
field :details_url,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: false,
description: 'The URL of the alert detail page.'

View File

@ -7,7 +7,7 @@ module Types
graphql_name 'AlertManagementIntegration'
field :id,
GraphQL::ID_TYPE,
GraphQL::Types::ID,
null: false,
description: 'ID of the integration.'
@ -17,27 +17,27 @@ module Types
description: 'Type of integration.'
field :name,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'Name of the integration.'
field :active,
GraphQL::BOOLEAN_TYPE,
GraphQL::Types::Boolean,
null: true,
description: 'Whether the endpoint is currently accepting alerts.'
field :token,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'Token used to authenticate alert notification requests.'
field :url,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'Endpoint which accepts alert notifications.'
field :api_url,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: true,
description: 'URL at which Prometheus metrics can be queried to populate the metrics dashboard.'

View File

@ -11,27 +11,27 @@ module Types
present_using AwardEmojiPresenter
field :name,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: false,
description: 'The emoji name.'
field :description,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: false,
description: 'The emoji description.'
field :unicode,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: false,
description: 'The emoji in Unicode.'
field :emoji,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: false,
description: 'The emoji as an icon.'
field :unicode_version,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: false,
description: 'The Unicode version for this emoji.'

View File

@ -9,30 +9,30 @@ module Types
description: 'Type of blob viewer.',
null: false
field :load_async, GraphQL::BOOLEAN_TYPE,
field :load_async, GraphQL::Types::Boolean,
description: 'Shows whether the blob content is loaded asynchronously.',
null: false
field :collapsed, GraphQL::BOOLEAN_TYPE,
field :collapsed, GraphQL::Types::Boolean,
description: 'Shows whether the blob should be displayed collapsed.',
method: :collapsed?,
null: false
field :too_large, GraphQL::BOOLEAN_TYPE,
field :too_large, GraphQL::Types::Boolean,
description: 'Shows whether the blob is too large to be displayed.',
method: :too_large?,
null: false
field :render_error, GraphQL::STRING_TYPE,
field :render_error, GraphQL::Types::String,
description: 'Error rendering the blob content.',
null: true
field :file_type, GraphQL::STRING_TYPE,
field :file_type, GraphQL::Types::String,
description: 'Content file type.',
method: :partial_name,
null: false
field :loading_partial_name, GraphQL::STRING_TYPE,
field :loading_partial_name, GraphQL::Types::String,
description: 'Loading partial name.',
null: false

View File

@ -10,19 +10,19 @@ module Types
alias_method :list, :object
field :id, GraphQL::ID_TYPE, null: false,
field :id, GraphQL::Types::ID, null: false,
description: 'ID (global ID) of the list.'
field :title, GraphQL::STRING_TYPE, null: false,
field :title, GraphQL::Types::String, null: false,
description: 'Title of the list.'
field :list_type, GraphQL::STRING_TYPE, null: false,
field :list_type, GraphQL::Types::String, null: false,
description: 'Type of the list.'
field :position, GraphQL::INT_TYPE, null: true,
field :position, GraphQL::Types::Int, null: true,
description: 'Position of list within the board.'
field :label, Types::LabelType, null: true,
description: 'Label of the list.'
field :collapsed, GraphQL::BOOLEAN_TYPE, null: true,
field :collapsed, GraphQL::Types::Boolean, null: true,
description: 'Indicates if the list is collapsed for this user.'
field :issues_count, GraphQL::INT_TYPE, null: true,
field :issues_count, GraphQL::Types::Int, null: true,
description: 'Count of issues in the list.'
field :issues, ::Types::IssueType.connection_type, null: true,

View File

@ -9,15 +9,15 @@ module Types
present_using BoardPresenter
field :id, type: GraphQL::ID_TYPE, null: false,
field :id, type: GraphQL::Types::ID, null: false,
description: 'ID (global ID) of the board.'
field :name, type: GraphQL::STRING_TYPE, null: true,
field :name, type: GraphQL::Types::String, null: true,
description: 'Name of the board.'
field :hide_backlog_list, type: GraphQL::BOOLEAN_TYPE, null: true,
field :hide_backlog_list, type: GraphQL::Types::Boolean, null: true,
description: 'Whether or not backlog list is hidden.'
field :hide_closed_list, type: GraphQL::BOOLEAN_TYPE, null: true,
field :hide_closed_list, type: GraphQL::Types::Boolean, null: true,
description: 'Whether or not closed list is hidden.'
field :created_at, Types::TimeType, null: false,
@ -33,10 +33,10 @@ module Types
resolver: Resolvers::BoardListsResolver,
extras: [:lookahead]
field :web_path, GraphQL::STRING_TYPE, null: false,
field :web_path, GraphQL::Types::String, null: false,
description: 'Web path of the board.'
field :web_url, GraphQL::STRING_TYPE, null: false,
field :web_url, GraphQL::Types::String, null: false,
description: 'Web URL of the board.'
end
end

View File

@ -4,15 +4,15 @@ module Types
module Boards
# Common arguments that we can be used to filter boards epics and issues
class BoardIssuableInputBaseType < BaseInputObject
argument :label_name, [GraphQL::STRING_TYPE, null: true],
argument :label_name, [GraphQL::Types::String, null: true],
required: false,
description: 'Filter by label name.'
argument :author_username, GraphQL::STRING_TYPE,
argument :author_username, GraphQL::Types::String,
required: false,
description: 'Filter by author username.'
argument :my_reaction_emoji, GraphQL::STRING_TYPE,
argument :my_reaction_emoji, GraphQL::Types::String,
required: false,
description: 'Filter by reaction emoji applied by the current user.'
end

View File

@ -4,19 +4,19 @@ module Types
module Boards
# rubocop: disable Graphql/AuthorizeTypes
class BoardIssueInputBaseType < BoardIssuableInputBaseType
argument :iids, [GraphQL::STRING_TYPE],
argument :iids, [GraphQL::Types::String],
required: false,
description: 'List of IIDs of issues. For example `["1", "2"]`.'
argument :milestone_title, GraphQL::STRING_TYPE,
argument :milestone_title, GraphQL::Types::String,
required: false,
description: 'Filter by milestone title.'
argument :assignee_username, [GraphQL::STRING_TYPE, null: true],
argument :assignee_username, [GraphQL::Types::String, null: true],
required: false,
description: 'Filter by assignee username.'
argument :release_tag, GraphQL::STRING_TYPE,
argument :release_tag, GraphQL::Types::String,
required: false,
description: 'Filter by release tag.'
end

View File

@ -10,7 +10,7 @@ module Types
prepare: ->(negated_args, ctx) { negated_args.to_h },
description: 'List of negated arguments.'
argument :search, GraphQL::STRING_TYPE,
argument :search, GraphQL::Types::String,
required: false,
description: 'Search query for issue title or description.'

View File

@ -6,7 +6,7 @@ module Types
graphql_name 'Branch'
field :name,
GraphQL::STRING_TYPE,
GraphQL::Types::String,
null: false,
description: 'Name of the branch.'

View File

@ -6,27 +6,27 @@ module Types
class AnalyticsType < BaseObject
graphql_name 'PipelineAnalytics'
field :week_pipelines_totals, [GraphQL::INT_TYPE], null: true,
field :week_pipelines_totals, [GraphQL::Types::Int], null: true,
description: 'Total weekly pipeline count.'
field :week_pipelines_successful, [GraphQL::INT_TYPE], null: true,
field :week_pipelines_successful, [GraphQL::Types::Int], null: true,
description: 'Total weekly successful pipeline count.'
field :week_pipelines_labels, [GraphQL::STRING_TYPE], null: true,
field :week_pipelines_labels, [GraphQL::Types::String], null: true,
description: 'Labels for the weekly pipeline count.'
field :month_pipelines_totals, [GraphQL::INT_TYPE], null: true,
field :month_pipelines_totals, [GraphQL::Types::Int], null: true,
description: 'Total monthly pipeline count.'
field :month_pipelines_successful, [GraphQL::INT_TYPE], null: true,
field :month_pipelines_successful, [GraphQL::Types::Int], null: true,
description: 'Total monthly successful pipeline count.'
field :month_pipelines_labels, [GraphQL::STRING_TYPE], null: true,
field :month_pipelines_labels, [GraphQL::Types::String], null: true,
description: 'Labels for the monthly pipeline count.'
field :year_pipelines_totals, [GraphQL::INT_TYPE], null: true,
field :year_pipelines_totals, [GraphQL::Types::Int], null: true,
description: 'Total yearly pipeline count.'
field :year_pipelines_successful, [GraphQL::INT_TYPE], null: true,
field :year_pipelines_successful, [GraphQL::Types::Int], null: true,
description: 'Total yearly successful pipeline count.'
field :year_pipelines_labels, [GraphQL::STRING_TYPE], null: true,
field :year_pipelines_labels, [GraphQL::Types::String], null: true,
description: 'Labels for the yearly pipeline count.'
field :pipeline_times_values, [GraphQL::INT_TYPE], null: true,
field :pipeline_times_values, [GraphQL::Types::Int], null: true,
description: 'Pipeline times.'
field :pipeline_times_labels, [GraphQL::STRING_TYPE], null: true,
field :pipeline_times_labels, [GraphQL::Types::String], null: true,
description: 'Pipeline times labels.'
end
end

View File

@ -7,7 +7,7 @@ module Types
authorize :read_application_setting
field :keep_latest_artifact, GraphQL::BOOLEAN_TYPE, null: true,
field :keep_latest_artifact, GraphQL::Types::Boolean, null: true,
description: 'Whether to keep the latest jobs artifacts.'
end
end

View File

@ -7,9 +7,9 @@ module Types
class BuildNeedType < BaseObject
graphql_name 'CiBuildNeed'
field :id, GraphQL::ID_TYPE, null: false,
field :id, GraphQL::Types::ID, null: false,
description: 'ID of the job we need to complete.'
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the job we need to complete.'
end
end

View File

@ -7,16 +7,16 @@ module Types
authorize :admin_project
field :merge_pipelines_enabled, GraphQL::BOOLEAN_TYPE, null: true,
field :merge_pipelines_enabled, GraphQL::Types::Boolean, null: true,
description: 'Whether merge pipelines are enabled.',
method: :merge_pipelines_enabled?
field :merge_trains_enabled, GraphQL::BOOLEAN_TYPE, null: true,
field :merge_trains_enabled, GraphQL::Types::Boolean, null: true,
description: 'Whether merge trains are enabled.',
method: :merge_trains_enabled?
field :keep_latest_artifact, GraphQL::BOOLEAN_TYPE, null: true,
field :keep_latest_artifact, GraphQL::Types::Boolean, null: true,
description: 'Whether to keep the latest builds artifacts.',
method: :keep_latest_artifacts_available?
field :job_token_scope_enabled, GraphQL::BOOLEAN_TYPE, null: true,
field :job_token_scope_enabled, GraphQL::Types::Boolean, null: true,
description: 'Indicates CI job tokens generated in this project have restricted access to resources.',
method: :job_token_scope_enabled?
field :project, Types::ProjectType, null: true,

View File

@ -7,9 +7,9 @@ module Types
class ConfigType < BaseObject
graphql_name 'CiConfig'
field :errors, [GraphQL::STRING_TYPE], null: true,
field :errors, [GraphQL::Types::String], null: true,
description: 'Linting errors.'
field :merged_yaml, GraphQL::STRING_TYPE, null: true,
field :merged_yaml, GraphQL::Types::String, null: true,
description: 'Merged CI configuration YAML.'
field :stages, Types::Ci::Config::StageType.connection_type, null: true,
description: 'Stages of the pipeline.'

View File

@ -7,11 +7,11 @@ module Types
class GroupType < BaseObject
graphql_name 'CiConfigGroup'
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the job group.'
field :jobs, Types::Ci::Config::JobType.connection_type, null: true,
description: 'Jobs in group.'
field :size, GraphQL::INT_TYPE, null: true,
field :size, GraphQL::Types::Int, null: true,
description: 'Size of the job group.'
end
end

View File

@ -7,7 +7,7 @@ module Types
class JobRestrictionType < BaseObject
graphql_name 'CiConfigJobRestriction'
field :refs, [GraphQL::STRING_TYPE], null: true,
field :refs, [GraphQL::Types::String], null: true,
description: 'The Git refs the job restriction applies to.'
end
end

View File

@ -7,32 +7,32 @@ module Types
class JobType < BaseObject
graphql_name 'CiConfigJob'
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the job.'
field :group_name, GraphQL::STRING_TYPE, null: true,
field :group_name, GraphQL::Types::String, null: true,
description: 'Name of the job group.'
field :stage, GraphQL::STRING_TYPE, null: true,
field :stage, GraphQL::Types::String, null: true,
description: 'Name of the job stage.'
field :needs, Types::Ci::Config::NeedType.connection_type, null: true,
description: 'Builds that must complete before the jobs run.'
field :allow_failure, GraphQL::BOOLEAN_TYPE, null: true,
field :allow_failure, GraphQL::Types::Boolean, null: true,
description: 'Allow job to fail.'
field :before_script, [GraphQL::STRING_TYPE], null: true,
field :before_script, [GraphQL::Types::String], null: true,
description: 'Override a set of commands that are executed before the job.'
field :script, [GraphQL::STRING_TYPE], null: true,
field :script, [GraphQL::Types::String], null: true,
description: 'Shell script that is executed by a runner.'
field :after_script, [GraphQL::STRING_TYPE], null: true,
field :after_script, [GraphQL::Types::String], null: true,
description: 'Override a set of commands that are executed after the job.'
field :when, GraphQL::STRING_TYPE, null: true,
field :when, GraphQL::Types::String, null: true,
description: 'When to run the job.',
resolver_method: :restrict_when_to_run_jobs
field :environment, GraphQL::STRING_TYPE, null: true,
field :environment, GraphQL::Types::String, null: true,
description: 'Name of an environment to which the job deploys.'
field :except, Types::Ci::Config::JobRestrictionType, null: true,
description: 'Limit when jobs are not created.'
field :only, Types::Ci::Config::JobRestrictionType, null: true,
description: 'Jobs are created when these conditions do not apply.'
field :tags, [GraphQL::STRING_TYPE], null: true,
field :tags, [GraphQL::Types::String], null: true,
description: 'List of tags that are used to select a runner.'
def restrict_when_to_run_jobs

View File

@ -7,7 +7,7 @@ module Types
class NeedType < BaseObject
graphql_name 'CiConfigNeed'
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the need.'
end
end

View File

@ -7,7 +7,7 @@ module Types
class StageType < BaseObject
graphql_name 'CiConfigStage'
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the stage.'
field :groups, Types::Ci::Config::GroupType.connection_type, null: true,
description: 'Groups of jobs for the stage.'

View File

@ -6,26 +6,26 @@ module Types
class DetailedStatusType < BaseObject
graphql_name 'DetailedStatus'
field :id, GraphQL::STRING_TYPE, null: false,
field :id, GraphQL::Types::String, null: false,
description: 'ID for a detailed status.',
extras: [:parent]
field :group, GraphQL::STRING_TYPE, null: true,
field :group, GraphQL::Types::String, null: true,
description: 'Group of the status.'
field :icon, GraphQL::STRING_TYPE, null: true,
field :icon, GraphQL::Types::String, null: true,
description: 'Icon of the status.'
field :favicon, GraphQL::STRING_TYPE, null: true,
field :favicon, GraphQL::Types::String, null: true,
description: 'Favicon of the status.'
field :details_path, GraphQL::STRING_TYPE, null: true,
field :details_path, GraphQL::Types::String, null: true,
description: 'Path of the details for the status.'
field :has_details, GraphQL::BOOLEAN_TYPE, null: true,
field :has_details, GraphQL::Types::Boolean, null: true,
description: 'Indicates if the status has further details.',
method: :has_details?
field :label, GraphQL::STRING_TYPE, null: true,
field :label, GraphQL::Types::String, null: true,
calls_gitaly: true,
description: 'Label of the status.'
field :text, GraphQL::STRING_TYPE, null: true,
field :text, GraphQL::Types::String, null: true,
description: 'Text of the status.'
field :tooltip, GraphQL::STRING_TYPE, null: true,
field :tooltip, GraphQL::Types::String, null: true,
description: 'Tooltip associated with the status.',
method: :status_tooltip
field :action, Types::Ci::StatusActionType, null: true,

View File

@ -6,11 +6,11 @@ module Types
class GroupType < BaseObject
graphql_name 'CiGroup'
field :id, GraphQL::STRING_TYPE, null: false,
field :id, GraphQL::Types::String, null: false,
description: 'ID for a group.'
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the job group.'
field :size, GraphQL::INT_TYPE, null: true,
field :size, GraphQL::Types::Int, null: true,
description: 'Size of the group.'
field :jobs, Ci::JobType.connection_type, null: true,
description: 'Jobs in group.'

View File

@ -6,7 +6,7 @@ module Types
class JobArtifactType < BaseObject
graphql_name 'CiJobArtifact'
field :download_path, GraphQL::STRING_TYPE, null: true,
field :download_path, GraphQL::Types::String, null: true,
description: "URL for downloading the artifact's file."
field :file_type, ::Types::Ci::JobArtifactFileTypeEnum, null: true,

View File

@ -14,7 +14,7 @@ module Types
description: 'ID of the job.'
field :pipeline, Types::Ci::PipelineType, null: true,
description: 'Pipeline the job belongs to.'
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the job.'
field :needs, BuildNeedType.connection_type, null: true,
description: 'References to builds that must complete before the jobs run.'
@ -24,11 +24,11 @@ module Types
description: "Status of the job."
field :stage, Types::Ci::StageType, null: true,
description: 'Stage of the job.'
field :allow_failure, ::GraphQL::BOOLEAN_TYPE, null: false,
field :allow_failure, ::GraphQL::Types::Boolean, null: false,
description: 'Whether the job is allowed to fail.'
field :duration, GraphQL::INT_TYPE, null: true,
field :duration, GraphQL::Types::Int, null: true,
description: 'Duration of the job in seconds.'
field :tags, [GraphQL::STRING_TYPE], null: true,
field :tags, [GraphQL::Types::String], null: true,
description: 'Tags for the current job.'
# Life-cycle timestamps:
@ -53,33 +53,33 @@ module Types
description: 'Detailed status of the job.'
field :artifacts, Types::Ci::JobArtifactType.connection_type, null: true,
description: 'Artifacts generated by the job.'
field :short_sha, type: GraphQL::STRING_TYPE, null: false,
field :short_sha, type: GraphQL::Types::String, null: false,
description: 'Short SHA1 ID of the commit.'
field :scheduling_type, GraphQL::STRING_TYPE, null: true,
field :scheduling_type, GraphQL::Types::String, null: true,
description: 'Type of job scheduling. Value is `dag` if the job uses the `needs` keyword, and `stage` otherwise.'
field :commit_path, GraphQL::STRING_TYPE, null: true,
field :commit_path, GraphQL::Types::String, null: true,
description: 'Path to the commit that triggered the job.'
field :ref_name, GraphQL::STRING_TYPE, null: true,
field :ref_name, GraphQL::Types::String, null: true,
description: 'Ref name of the job.'
field :ref_path, GraphQL::STRING_TYPE, null: true,
field :ref_path, GraphQL::Types::String, null: true,
description: 'Path to the ref.'
field :playable, GraphQL::BOOLEAN_TYPE, null: false, method: :playable?,
field :playable, GraphQL::Types::Boolean, null: false, method: :playable?,
description: 'Indicates the job can be played.'
field :retryable, GraphQL::BOOLEAN_TYPE, null: false, method: :retryable?,
field :retryable, GraphQL::Types::Boolean, null: false, method: :retryable?,
description: 'Indicates the job can be retried.'
field :cancelable, GraphQL::BOOLEAN_TYPE, null: false, method: :cancelable?,
field :cancelable, GraphQL::Types::Boolean, null: false, method: :cancelable?,
description: 'Indicates the job can be canceled.'
field :active, GraphQL::BOOLEAN_TYPE, null: false, method: :active?,
field :active, GraphQL::Types::Boolean, null: false, method: :active?,
description: 'Indicates the job is active.'
field :stuck, GraphQL::BOOLEAN_TYPE, null: false, method: :stuck?,
field :stuck, GraphQL::Types::Boolean, null: false, method: :stuck?,
description: 'Indicates the job is stuck.'
field :coverage, GraphQL::FLOAT_TYPE, null: true,
description: 'Coverage level of the job.'
field :created_by_tag, GraphQL::BOOLEAN_TYPE, null: false,
field :created_by_tag, GraphQL::Types::Boolean, null: false,
description: 'Whether the job was created by a tag.'
field :manual_job, GraphQL::BOOLEAN_TYPE, null: true,
field :manual_job, GraphQL::Types::Boolean, null: true,
description: 'Whether the job has a manual action.'
field :triggered, GraphQL::BOOLEAN_TYPE, null: true,
field :triggered, GraphQL::Types::Boolean, null: true,
description: 'Whether the job was triggered.'
def pipeline

View File

@ -12,25 +12,25 @@ module Types
expose_permissions Types::PermissionTypes::Ci::Pipeline
field :id, GraphQL::ID_TYPE, null: false,
field :id, GraphQL::Types::ID, null: false,
description: 'ID of the pipeline.'
field :iid, GraphQL::STRING_TYPE, null: false,
field :iid, GraphQL::Types::String, null: false,
description: 'Internal ID of the pipeline.'
field :sha, GraphQL::STRING_TYPE, null: false,
field :sha, GraphQL::Types::String, null: false,
description: "SHA of the pipeline's commit."
field :before_sha, GraphQL::STRING_TYPE, null: true,
field :before_sha, GraphQL::Types::String, null: true,
description: 'Base SHA of the source branch.'
field :complete, GraphQL::BOOLEAN_TYPE, null: false, method: :complete?,
field :complete, GraphQL::Types::Boolean, null: false, method: :complete?,
description: 'Indicates if a pipeline is complete.'
field :status, PipelineStatusEnum, null: false,
description: "Status of the pipeline (#{::Ci::Pipeline.all_state_names.compact.join(', ').upcase})"
field :warnings, GraphQL::BOOLEAN_TYPE, null: false, method: :has_warnings?,
field :warnings, GraphQL::Types::Boolean, null: false, method: :has_warnings?,
description: "Indicates if a pipeline has warnings."
field :detailed_status, Types::Ci::DetailedStatusType, null: false,
@ -39,7 +39,7 @@ module Types
field :config_source, PipelineConfigSourceEnum, null: true,
description: "Configuration source of the pipeline (#{::Enums::Ci::Pipeline.config_sources.keys.join(', ').upcase})"
field :duration, GraphQL::INT_TYPE, null: true,
field :duration, GraphQL::Types::Int, null: true,
description: 'Duration of the pipeline in seconds.'
field :queued_duration, Types::DurationType, null: true,
@ -76,12 +76,12 @@ module Types
null: true,
description: 'Pipeline user.'
field :retryable, GraphQL::BOOLEAN_TYPE,
field :retryable, GraphQL::Types::Boolean,
description: 'Specifies if a pipeline can be retried.',
method: :retryable?,
null: false
field :cancelable, GraphQL::BOOLEAN_TYPE,
field :cancelable, GraphQL::Types::Boolean,
description: 'Specifies if a pipeline can be canceled.',
method: :cancelable?,
null: false
@ -103,7 +103,7 @@ module Types
required: false,
description: 'ID of the job.'
argument :name,
type: ::GraphQL::STRING_TYPE,
type: ::GraphQL::Types::String,
required: false,
description: 'Name of the job.'
end
@ -122,19 +122,19 @@ module Types
description: 'Pipeline that triggered the pipeline.',
method: :triggered_by_pipeline
field :path, GraphQL::STRING_TYPE, null: true,
field :path, GraphQL::Types::String, null: true,
description: "Relative path to the pipeline's page."
field :commit_path, GraphQL::STRING_TYPE, null: true,
field :commit_path, GraphQL::Types::String, null: true,
description: 'Path to the commit that triggered the pipeline.'
field :project, Types::ProjectType, null: true,
description: 'Project the pipeline belongs to.'
field :active, GraphQL::BOOLEAN_TYPE, null: false, method: :active?,
field :active, GraphQL::Types::Boolean, null: false, method: :active?,
description: 'Indicates if the pipeline is active.'
field :uses_needs, GraphQL::BOOLEAN_TYPE, null: true,
field :uses_needs, GraphQL::Types::Boolean, null: true,
method: :uses_needs?,
description: 'Indicates if the pipeline has jobs with `needs` dependencies.'
@ -150,7 +150,7 @@ module Types
description: 'A specific test suite in a pipeline test report.',
resolver: Resolvers::Ci::TestSuiteResolver
field :ref, GraphQL::STRING_TYPE, null: true,
field :ref, GraphQL::Types::String, null: true,
description: 'Reference to the branch from which the pipeline was triggered.'
def detailed_status

View File

@ -9,10 +9,10 @@ module Types
connection_type_class(Types::CountableConnectionType)
field :count, GraphQL::INT_TYPE, null: true,
field :count, GraphQL::Types::Int, null: true,
description: 'Number of times the test case has failed in the past 14 days.'
field :base_branch, GraphQL::STRING_TYPE, null: true,
field :base_branch, GraphQL::Types::String, null: true,
description: 'Name of the base branch of the project.'
end
# rubocop: enable Graphql/AuthorizeTypes

View File

@ -6,9 +6,9 @@ module Types
class RunnerArchitectureType < BaseObject
graphql_name 'RunnerArchitecture'
field :name, GraphQL::STRING_TYPE, null: false,
field :name, GraphQL::Types::String, null: false,
description: 'Name of the runner platform architecture.'
field :download_location, GraphQL::STRING_TYPE, null: false,
field :download_location, GraphQL::Types::String, null: false,
description: 'Download location for the runner for the platform architecture.'
end
end

View File

@ -6,9 +6,9 @@ module Types
class RunnerPlatformType < BaseObject
graphql_name 'RunnerPlatform'
field :name, GraphQL::STRING_TYPE, null: false,
field :name, GraphQL::Types::String, null: false,
description: 'Name slug of the runner platform.'
field :human_readable_name, GraphQL::STRING_TYPE, null: false,
field :human_readable_name, GraphQL::Types::String, null: false,
description: 'Human readable name of the runner platform.'
field :architectures, Types::Ci::RunnerArchitectureType.connection_type, null: true,
description: 'Runner architectures supported for the platform.'

View File

@ -6,9 +6,9 @@ module Types
class RunnerSetupType < BaseObject
graphql_name 'RunnerSetup'
field :install_instructions, GraphQL::STRING_TYPE, null: false,
field :install_instructions, GraphQL::Types::String, null: false,
description: 'Instructions for installing the runner on the specified architecture.'
field :register_instructions, GraphQL::STRING_TYPE, null: true,
field :register_instructions, GraphQL::Types::String, null: true,
description: 'Instructions for registering the runner.'
end
end

View File

@ -12,38 +12,38 @@ module Types
field :id, ::Types::GlobalIDType[::Ci::Runner], null: false,
description: 'ID of the runner.'
field :description, GraphQL::STRING_TYPE, null: true,
field :description, GraphQL::Types::String, null: true,
description: 'Description of the runner.'
field :contacted_at, Types::TimeType, null: true,
description: 'Last contact from the runner.',
method: :contacted_at
field :maximum_timeout, GraphQL::INT_TYPE, null: true,
field :maximum_timeout, GraphQL::Types::Int, null: true,
description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
field :access_level, ::Types::Ci::RunnerAccessLevelEnum, null: false,
description: 'Access level of the runner.'
field :active, GraphQL::BOOLEAN_TYPE, null: false,
field :active, GraphQL::Types::Boolean, null: false,
description: 'Indicates the runner is allowed to receive jobs.'
field :status, ::Types::Ci::RunnerStatusEnum, null: false,
description: 'Status of the runner.'
field :version, GraphQL::STRING_TYPE, null: true,
field :version, GraphQL::Types::String, null: true,
description: 'Version of the runner.'
field :short_sha, GraphQL::STRING_TYPE, null: true,
field :short_sha, GraphQL::Types::String, null: true,
description: %q(First eight characters of the runner's token used to authenticate new job requests. Used as the runner's unique ID.)
field :revision, GraphQL::STRING_TYPE, null: true,
field :revision, GraphQL::Types::String, null: true,
description: 'Revision of the runner.'
field :locked, GraphQL::BOOLEAN_TYPE, null: true,
field :locked, GraphQL::Types::Boolean, null: true,
description: 'Indicates the runner is locked.'
field :run_untagged, GraphQL::BOOLEAN_TYPE, null: false,
field :run_untagged, GraphQL::Types::Boolean, null: false,
description: 'Indicates the runner is able to run untagged jobs.'
field :ip_address, GraphQL::STRING_TYPE, null: true,
field :ip_address, GraphQL::Types::String, null: true,
description: 'IP address of the runner.'
field :runner_type, ::Types::Ci::RunnerTypeEnum, null: false,
description: 'Type of the runner.'
field :tag_list, [GraphQL::STRING_TYPE], null: true,
field :tag_list, [GraphQL::Types::String], null: true,
description: 'Tags associated with the runner.'
field :project_count, GraphQL::INT_TYPE, null: true,
field :project_count, GraphQL::Types::Int, null: true,
description: 'Number of projects that the runner is associated with.'
field :job_count, GraphQL::INT_TYPE, null: true,
field :job_count, GraphQL::Types::Int, null: true,
description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)."
def job_count

View File

@ -6,9 +6,9 @@ module Types
graphql_name 'CiStage'
authorize :read_commit_status
field :id, GraphQL::ID_TYPE, null: false,
field :id, GraphQL::Types::ID, null: false,
description: 'ID of the stage.'
field :name, type: GraphQL::STRING_TYPE, null: true,
field :name, type: GraphQL::Types::String, null: true,
description: 'Name of the stage.'
field :groups, type: Ci::GroupType.connection_type, null: true,
extras: [:lookahead],
@ -18,7 +18,7 @@ module Types
field :jobs, Ci::JobType.connection_type, null: true,
description: 'Jobs for the stage.',
method: 'latest_statuses'
field :status, GraphQL::STRING_TYPE,
field :status, GraphQL::Types::String,
null: true,
description: 'Status of the pipeline stage.'

View File

@ -5,19 +5,19 @@ module Types
class StatusActionType < BaseObject
graphql_name 'StatusAction'
field :id, GraphQL::STRING_TYPE, null: false,
field :id, GraphQL::Types::String, null: false,
description: 'ID for a status action.',
extras: [:parent]
field :button_title, GraphQL::STRING_TYPE, null: true,
field :button_title, GraphQL::Types::String, null: true,
description: 'Title for the button, for example: Retry this job.'
field :icon, GraphQL::STRING_TYPE, null: true,
field :icon, GraphQL::Types::String, null: true,
description: 'Icon used in the action button.'
field :method, GraphQL::STRING_TYPE, null: true,
field :method, GraphQL::Types::String, null: true,
description: 'Method for the action, for example: :post.',
resolver_method: :action_method
field :path, GraphQL::STRING_TYPE, null: true,
field :path, GraphQL::Types::String, null: true,
description: 'Path for the action.'
field :title, GraphQL::STRING_TYPE, null: true,
field :title, GraphQL::Types::String, null: true,
description: 'Title for the action, for example: Retry.'
def id(parent:)

View File

@ -7,9 +7,9 @@ module Types
graphql_name 'CiTemplate'
description 'GitLab CI/CD configuration template.'
field :name, GraphQL::STRING_TYPE, null: false,
field :name, GraphQL::Types::String, null: false,
description: 'Name of the CI template.'
field :content, GraphQL::STRING_TYPE, null: false,
field :content, GraphQL::Types::String, null: false,
description: 'Contents of the CI template.'
end
end

View File

@ -12,25 +12,25 @@ module Types
field :status, Types::Ci::TestCaseStatusEnum, null: true,
description: "Status of the test case (#{::Gitlab::Ci::Reports::TestCase::STATUS_TYPES.join(', ')})."
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the test case.'
field :classname, GraphQL::STRING_TYPE, null: true,
field :classname, GraphQL::Types::String, null: true,
description: 'Classname of the test case.'
field :execution_time, GraphQL::FLOAT_TYPE, null: true,
description: 'Test case execution time in seconds.'
field :file, GraphQL::STRING_TYPE, null: true,
field :file, GraphQL::Types::String, null: true,
description: 'Path to the file of the test case.'
field :attachment_url, GraphQL::STRING_TYPE, null: true,
field :attachment_url, GraphQL::Types::String, null: true,
description: 'URL of the test case attachment file.'
field :system_output, GraphQL::STRING_TYPE, null: true,
field :system_output, GraphQL::Types::String, null: true,
description: 'System output of the test case.'
field :stack_trace, GraphQL::STRING_TYPE, null: true,
field :stack_trace, GraphQL::Types::String, null: true,
description: 'Stack trace of the test case.'
field :recent_failures, Types::Ci::RecentFailuresType, null: true,

View File

@ -10,22 +10,22 @@ module Types
field :time, GraphQL::FLOAT_TYPE, null: true,
description: 'Total duration of the tests.'
field :count, GraphQL::INT_TYPE, null: true,
field :count, GraphQL::Types::Int, null: true,
description: 'Total number of the test cases.'
field :success, GraphQL::INT_TYPE, null: true,
field :success, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that succeeded.'
field :failed, GraphQL::INT_TYPE, null: true,
field :failed, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that failed.'
field :skipped, GraphQL::INT_TYPE, null: true,
field :skipped, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that were skipped.'
field :error, GraphQL::INT_TYPE, null: true,
field :error, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that had an error.'
field :suite_error, GraphQL::STRING_TYPE, null: true,
field :suite_error, GraphQL::Types::String, null: true,
description: 'Test suite error message.'
end
# rubocop: enable Graphql/AuthorizeTypes

View File

@ -9,31 +9,31 @@ module Types
connection_type_class(Types::CountableConnectionType)
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the test suite.'
field :total_time, GraphQL::FLOAT_TYPE, null: true,
description: 'Total duration of the tests in the test suite.'
field :total_count, GraphQL::INT_TYPE, null: true,
field :total_count, GraphQL::Types::Int, null: true,
description: 'Total number of the test cases in the test suite.'
field :success_count, GraphQL::INT_TYPE, null: true,
field :success_count, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that succeeded in the test suite.'
field :failed_count, GraphQL::INT_TYPE, null: true,
field :failed_count, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that failed in the test suite.'
field :skipped_count, GraphQL::INT_TYPE, null: true,
field :skipped_count, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that were skipped in the test suite.'
field :error_count, GraphQL::INT_TYPE, null: true,
field :error_count, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that had an error.'
field :suite_error, GraphQL::STRING_TYPE, null: true,
field :suite_error, GraphQL::Types::String, null: true,
description: 'Test suite error message.'
field :build_ids, [GraphQL::ID_TYPE], null: true,
field :build_ids, [GraphQL::Types::ID], null: true,
description: 'IDs of the builds used to run the test suite.'
end
# rubocop: enable Graphql/AuthorizeTypes

View File

@ -9,28 +9,28 @@ module Types
connection_type_class(Types::CountableConnectionType)
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the test suite.'
field :total_time, GraphQL::FLOAT_TYPE, null: true,
description: 'Total duration of the tests in the test suite.'
field :total_count, GraphQL::INT_TYPE, null: true,
field :total_count, GraphQL::Types::Int, null: true,
description: 'Total number of the test cases in the test suite.'
field :success_count, GraphQL::INT_TYPE, null: true,
field :success_count, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that succeeded in the test suite.'
field :failed_count, GraphQL::INT_TYPE, null: true,
field :failed_count, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that failed in the test suite.'
field :skipped_count, GraphQL::INT_TYPE, null: true,
field :skipped_count, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that were skipped in the test suite.'
field :error_count, GraphQL::INT_TYPE, null: true,
field :error_count, GraphQL::Types::Int, null: true,
description: 'Total number of test cases that had an error.'
field :suite_error, GraphQL::STRING_TYPE, null: true,
field :suite_error, GraphQL::Types::String, null: true,
description: 'Test suite error message.'
field :test_cases, Types::Ci::TestCaseType.connection_type, null: true,

View File

@ -7,10 +7,10 @@ module Types
graphql_name 'SastCiConfigurationAnalyzersEntityInput'
description 'Represents the analyzers entity in SAST CI configuration'
argument :name, GraphQL::STRING_TYPE, required: true,
argument :name, GraphQL::Types::String, required: true,
description: 'Name of analyzer.'
argument :enabled, GraphQL::BOOLEAN_TYPE, required: true,
argument :enabled, GraphQL::Types::Boolean, required: true,
description: 'State of the analyzer.'
argument :variables, [::Types::CiConfiguration::Sast::EntityInputType],

View File

@ -8,16 +8,16 @@ module Types
graphql_name 'SastCiConfigurationAnalyzersEntity'
description 'Represents an analyzer entity in SAST CI configuration'
field :name, GraphQL::STRING_TYPE, null: true,
field :name, GraphQL::Types::String, null: true,
description: 'Name of the analyzer.'
field :label, GraphQL::STRING_TYPE, null: true,
field :label, GraphQL::Types::String, null: true,
description: 'Analyzer label used in the config UI.'
field :enabled, GraphQL::BOOLEAN_TYPE, null: true,
field :enabled, GraphQL::Types::Boolean, null: true,
description: 'Indicates whether an analyzer is enabled.'
field :description, GraphQL::STRING_TYPE, null: true,
field :description, GraphQL::Types::String, null: true,
description: 'Analyzer description that is displayed on the form.'
field :variables, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true,

View File

@ -7,13 +7,13 @@ module Types
graphql_name 'SastCiConfigurationEntityInput'
description 'Represents an entity in SAST CI configuration'
argument :field, GraphQL::STRING_TYPE, required: true,
argument :field, GraphQL::Types::String, required: true,
description: 'CI keyword of entity.'
argument :default_value, GraphQL::STRING_TYPE, required: true,
argument :default_value, GraphQL::Types::String, required: true,
description: 'Default value that is used if value is empty.'
argument :value, GraphQL::STRING_TYPE, required: true,
argument :value, GraphQL::Types::String, required: true,
description: 'Current value of the entity.'
end
end

View File

@ -8,25 +8,25 @@ module Types
graphql_name 'SastCiConfigurationEntity'
description 'Represents an entity in SAST CI configuration'
field :field, GraphQL::STRING_TYPE, null: true,
field :field, GraphQL::Types::String, null: true,
description: 'CI keyword of entity.'
field :label, GraphQL::STRING_TYPE, null: true,
field :label, GraphQL::Types::String, null: true,
description: 'Label for entity used in the form.'
field :type, GraphQL::STRING_TYPE, null: true,
field :type, GraphQL::Types::String, null: true,
description: 'Type of the field value.'
field :options, ::Types::CiConfiguration::Sast::OptionsEntityType.connection_type, null: true,
description: 'Different possible values of the field.'
field :default_value, GraphQL::STRING_TYPE, null: true,
field :default_value, GraphQL::Types::String, null: true,
description: 'Default value that is used if value is empty.'
field :description, GraphQL::STRING_TYPE, null: true,
field :description, GraphQL::Types::String, null: true,
description: 'Entity description that is displayed on the form.'
field :value, GraphQL::STRING_TYPE, null: true,
field :value, GraphQL::Types::String, null: true,
description: 'Current value of the entity.'
field :size, ::Types::CiConfiguration::Sast::UiComponentSizeEnum, null: true,

View File

@ -8,10 +8,10 @@ module Types
graphql_name 'SastCiConfigurationOptionsEntity'
description 'Represents an entity for options in SAST CI configuration'
field :label, GraphQL::STRING_TYPE, null: true,
field :label, GraphQL::Types::String, null: true,
description: 'Label of option entity.'
field :value, GraphQL::STRING_TYPE, null: true,
field :value, GraphQL::Types::String, null: true,
description: 'Value of option entity.'
end
end

View File

@ -4,15 +4,15 @@ module Types
class CommitActionType < BaseInputObject
argument :action, type: Types::CommitActionModeEnum, required: true,
description: 'The action to perform, create, delete, move, update, chmod.'
argument :file_path, type: GraphQL::STRING_TYPE, required: true,
argument :file_path, type: GraphQL::Types::String, required: true,
description: 'Full path to the file.'
argument :content, type: GraphQL::STRING_TYPE, required: false,
argument :content, type: GraphQL::Types::String, required: false,
description: 'Content of the file.'
argument :previous_path, type: GraphQL::STRING_TYPE, required: false,
argument :previous_path, type: GraphQL::Types::String, required: false,
description: 'Original full path to the file being moved.'
argument :last_commit_id, type: GraphQL::STRING_TYPE, required: false,
argument :last_commit_id, type: GraphQL::Types::String, required: false,
description: 'Last known file commit ID.'
argument :execute_filemode, type: GraphQL::BOOLEAN_TYPE, required: false,
argument :execute_filemode, type: GraphQL::Types::Boolean, required: false,
description: 'Enables/disables the execute flag on the file.'
argument :encoding, type: Types::CommitEncodingEnum, required: false,
description: 'Encoding of the file. Default is text.'

View File

@ -8,31 +8,31 @@ module Types
present_using CommitPresenter
field :id, type: GraphQL::ID_TYPE, null: false,
field :id, type: GraphQL::Types::ID, null: false,
description: 'ID (global ID) of the commit.'
field :sha, type: GraphQL::STRING_TYPE, null: false,
field :sha, type: GraphQL::Types::String, null: false,
description: 'SHA1 ID of the commit.'
field :short_id, type: GraphQL::STRING_TYPE, null: false,
field :short_id, type: GraphQL::Types::String, null: false,
description: 'Short SHA1 ID of the commit.'
field :title, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
field :title, type: GraphQL::Types::String, null: true, calls_gitaly: true,
description: 'Title of the commit message.'
markdown_field :title_html, null: true
field :description, type: GraphQL::STRING_TYPE, null: true,
field :description, type: GraphQL::Types::String, null: true,
description: 'Description of the commit message.'
markdown_field :description_html, null: true
field :message, type: GraphQL::STRING_TYPE, null: true,
field :message, type: GraphQL::Types::String, null: true,
description: 'Raw commit message.'
field :authored_date, type: Types::TimeType, null: true,
description: 'Timestamp of when the commit was authored.'
field :web_url, type: GraphQL::STRING_TYPE, null: false,
field :web_url, type: GraphQL::Types::String, null: false,
description: 'Web URL of the commit.'
field :web_path, type: GraphQL::STRING_TYPE, null: false,
field :web_path, type: GraphQL::Types::String, null: false,
description: 'Web path of the commit.'
field :signature_html, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
field :signature_html, type: GraphQL::Types::String, null: true, calls_gitaly: true,
description: 'Rendered HTML of the commit signature.'
field :author_name, type: GraphQL::STRING_TYPE, null: true,
field :author_name, type: GraphQL::Types::String, null: true,
description: 'Commit authors name.'
field :author_gravatar, type: GraphQL::STRING_TYPE, null: true,
field :author_gravatar, type: GraphQL::Types::String, null: true,
description: 'Commit authors gravatar.'
# models/commit lazy loads the author by email

View File

@ -10,7 +10,7 @@ module Types
field :created_at, Types::TimeType, null: false, description: 'Timestamp of when the container expiration policy was created.'
field :updated_at, Types::TimeType, null: false, description: 'Timestamp of when the container expiration policy was updated.'
field :enabled, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates whether this container expiration policy is enabled.'
field :enabled, GraphQL::Types::Boolean, null: false, description: 'Indicates whether this container expiration policy is enabled.'
field :older_than, Types::ContainerExpirationPolicyOlderThanEnum, null: true, description: 'Tags older that this will expire.'
field :cadence, Types::ContainerExpirationPolicyCadenceEnum, null: false, description: 'This container expiration policy schedule.'
field :keep_n, Types::ContainerExpirationPolicyKeepEnum, null: true, description: 'Number of tags to retain.'

View File

@ -8,15 +8,15 @@ module Types
authorize :read_container_image
field :name, GraphQL::STRING_TYPE, null: false, description: 'Name of the tag.'
field :path, GraphQL::STRING_TYPE, null: false, description: 'Path of the tag.'
field :location, GraphQL::STRING_TYPE, null: false, description: 'URL of the tag.'
field :digest, GraphQL::STRING_TYPE, null: true, description: 'Digest of the tag.'
field :revision, GraphQL::STRING_TYPE, null: true, description: 'Revision of the tag.'
field :short_revision, GraphQL::STRING_TYPE, null: true, description: 'Short revision of the tag.'
field :name, GraphQL::Types::String, null: false, description: 'Name of the tag.'
field :path, GraphQL::Types::String, null: false, description: 'Path of the tag.'
field :location, GraphQL::Types::String, null: false, description: 'URL of the tag.'
field :digest, GraphQL::Types::String, null: true, description: 'Digest of the tag.'
field :revision, GraphQL::Types::String, null: true, description: 'Revision of the tag.'
field :short_revision, GraphQL::Types::String, null: true, description: 'Short revision of the tag.'
field :total_size, GraphQL::Types::BigInt, null: true, description: 'The size of the tag.'
field :created_at, Types::TimeType, null: true, description: 'Timestamp when the tag was created.'
field :can_delete, GraphQL::BOOLEAN_TYPE, null: false, description: 'Can the current user delete this tag.'
field :can_delete, GraphQL::Types::Boolean, null: false, description: 'Can the current user delete this tag.'
def can_delete
Ability.allowed?(current_user, :destroy_container_image, object)

View File

@ -8,17 +8,17 @@ module Types
authorize :read_container_image
field :id, GraphQL::ID_TYPE, null: false, description: 'ID of the container repository.'
field :name, GraphQL::STRING_TYPE, null: false, description: 'Name of the container repository.'
field :path, GraphQL::STRING_TYPE, null: false, description: 'Path of the container repository.'
field :location, GraphQL::STRING_TYPE, null: false, description: 'URL of the container repository.'
field :id, GraphQL::Types::ID, null: false, description: 'ID of the container repository.'
field :name, GraphQL::Types::String, null: false, description: 'Name of the container repository.'
field :path, GraphQL::Types::String, null: false, description: 'Path of the container repository.'
field :location, GraphQL::Types::String, null: false, description: 'URL of the container repository.'
field :created_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was created.'
field :updated_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was updated.'
field :expiration_policy_started_at, Types::TimeType, null: true, description: 'Timestamp when the cleanup done by the expiration policy was started on the container repository.'
field :expiration_policy_cleanup_status, Types::ContainerRepositoryCleanupStatusEnum, null: true, description: 'The tags cleanup status for the container repository.'
field :status, Types::ContainerRepositoryStatusEnum, null: true, description: 'Status of the container repository.'
field :tags_count, GraphQL::INT_TYPE, null: false, description: 'Number of tags associated with this image.'
field :can_delete, GraphQL::BOOLEAN_TYPE, null: false, description: 'Can the current user delete the container repository.'
field :tags_count, GraphQL::Types::Int, null: false, description: 'Number of tags associated with this image.'
field :can_delete, GraphQL::Types::Boolean, null: false, description: 'Can the current user delete the container repository.'
field :project, Types::ProjectType, null: false, description: 'Project of the container registry.'
def can_delete

View File

@ -3,7 +3,7 @@
module Types
# rubocop: disable Graphql/AuthorizeTypes
class CountableConnectionType < GraphQL::Types::Relay::BaseConnection
field :count, GraphQL::INT_TYPE, null: false,
field :count, GraphQL::Types::Int, null: false,
description: 'Total count of collection.'
def count

View File

@ -11,16 +11,16 @@ module Types
null: false,
description: 'The ID of the emoji.'
field :name, GraphQL::STRING_TYPE,
field :name, GraphQL::Types::String,
null: false,
description: 'The name of the emoji.'
field :url, GraphQL::STRING_TYPE,
field :url, GraphQL::Types::String,
null: false,
method: :file,
description: 'The link to file of the emoji.'
field :external, GraphQL::BOOLEAN_TYPE,
field :external, GraphQL::Types::Boolean,
null: false,
description: 'Whether the emoji is an external link.'
end

View File

@ -90,6 +90,17 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:container_registry_access_level, value)
end
# TODO: Remove this method after we drop support for project create/edit APIs to set the
# container_registry_enabled attribute. They can instead set the container_registry_access_level
# attribute.
def container_registry_enabled=(value)
write_feature_attribute_boolean(:container_registry_access_level, value)
# TODO: Remove this when we remove the projects.container_registry_enabled
# column. https://gitlab.com/gitlab-org/gitlab/-/issues/335425
super
end
private
def write_feature_attribute_boolean(field, value)

View File

@ -465,10 +465,34 @@ class Namespace < ApplicationRecord
end
def refresh_access_of_projects_invited_groups
Group
.joins(project_group_links: :project)
.where(projects: { namespace_id: id })
.find_each(&:refresh_members_authorized_projects)
if Feature.enabled?(:specialized_worker_for_group_lock_update_auth_recalculation)
Project
.where(namespace_id: id)
.joins(:project_group_links)
.distinct
.find_each do |project|
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project.id)
end
# Until we compare the inconsistency rates of the new specialized worker and
# the old approach, we still run AuthorizedProjectsWorker
# but with some delay and lower urgency as a safety net.
Group
.joins(project_group_links: :project)
.where(projects: { namespace_id: id })
.distinct
.find_each do |group|
group.refresh_members_authorized_projects(
blocking: false,
priority: UserProjectAccessChangedService::LOW_PRIORITY
)
end
else
Group
.joins(project_group_links: :project)
.where(projects: { namespace_id: id })
.find_each(&:refresh_members_authorized_projects)
end
end
def nesting_level_allowed

View File

@ -76,7 +76,6 @@ class Project < ApplicationRecord
default_value_for :packages_enabled, true
default_value_for :archived, false
default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) do
Repository.pick_storage_shard
end
@ -98,9 +97,6 @@ class Project < ApplicationRecord
before_save :ensure_runners_token
# https://api.rubyonrails.org/v6.0.3.4/classes/ActiveRecord/AttributeMethods/Dirty.html#method-i-will_save_change_to_attribute-3F
before_update :set_container_registry_access_level, if: :will_save_change_to_container_registry_enabled?
after_save :update_project_statistics, if: :saved_change_to_namespace_id?
after_save :create_import_state, if: ->(project) { project.import? && project.import_state.nil? }
@ -1184,6 +1180,15 @@ class Project < ApplicationRecord
import_type == 'gitea'
end
def github_import?
import_type == 'github'
end
def github_enterprise_import?
github_import? &&
URI.parse(import_url).host != URI.parse(Octokit::Default::API_ENDPOINT).host
end
def has_remote_mirror?
remote_mirror_available? && remote_mirrors.enabled.exists?
end
@ -2659,20 +2664,6 @@ class Project < ApplicationRecord
private
def set_container_registry_access_level
# changes_to_save = { 'container_registry_enabled' => [value_before_update, value_after_update] }
value = changes_to_save['container_registry_enabled'][1]
access_level =
if value
ProjectFeature::ENABLED
else
ProjectFeature::DISABLED
end
project_feature.update!(container_registry_access_level: access_level)
end
def find_integration(integrations, name)
integrations.find { _1.to_param == name }
end

View File

@ -2,6 +2,7 @@
class ProjectFeature < ApplicationRecord
include Featurable
extend Gitlab::ConfigHelper
# When updating this array, make sure to update rubocop/cop/gitlab/feature_available_usage.rb as well.
FEATURES = %i[
@ -48,8 +49,6 @@ class ProjectFeature < ApplicationRecord
end
end
before_create :set_container_registry_access_level
# Default scopes force us to unscope here since a service may need to check
# permissions for a project in pending_delete
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
@ -80,6 +79,14 @@ class ProjectFeature < ApplicationRecord
end
end
default_value_for(:container_registry_access_level, allows_nil: false) do |feature|
if gitlab_config_features.container_registry
ENABLED
else
DISABLED
end
end
def public_pages?
return true unless Gitlab.config.pages.access_control
@ -94,15 +101,6 @@ class ProjectFeature < ApplicationRecord
private
def set_container_registry_access_level
self.container_registry_access_level =
if project&.read_attribute(:container_registry_enabled)
ENABLED
else
DISABLED
end
end
# Validates builds and merge requests access level
# which cannot be higher than repository access level
def repository_children_level

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Ci
class BuildCancelService
def initialize(build, user)
@build = build
@user = user
end
def execute
return forbidden unless allowed?
return unprocessable_entity unless build.cancelable?
build.cancel
ServiceResponse.success(payload: build)
end
private
attr_reader :build, :user
def allowed?
user.can?(:update_build, build)
end
def forbidden
ServiceResponse.error(message: 'Forbidden', http_status: :forbidden)
end
def unprocessable_entity
ServiceResponse.error(message: 'Unprocessable entity', http_status: :unprocessable_entity)
end
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Ci
class BuildUnscheduleService
def initialize(build, user)
@build = build
@user = user
end
def execute
return forbidden unless allowed?
return unprocessable_entity unless build.scheduled?
build.unschedule!
ServiceResponse.success(payload: build)
end
private
attr_reader :build, :user
def allowed?
user.can?(:update_build, build)
end
def forbidden
ServiceResponse.error(message: 'Forbidden', http_status: :forbidden)
end
def unprocessable_entity
ServiceResponse.error(message: 'Unprocessable entity', http_status: :unprocessable_entity)
end
end
end

View File

@ -63,16 +63,23 @@ module ContainerExpirationPolicies
def container_repository
strong_memoize(:container_repository) do
ContainerRepository.transaction do
# We need a lock to prevent two workers from picking up the same row
container_repository = next_container_repository
repository = next_container_repository
container_repository&.tap(&:cleanup_ongoing!)
repository&.tap do |repo|
log_info(
project_id: repo.project_id,
container_repository_id: repo.id
)
repo.cleanup_ongoing!
end
end
end
end
def next_container_repository
# rubocop: disable CodeReuse/ActiveRecord
# We need a lock to prevent two workers from picking up the same row
next_one_requiring = ContainerRepository.requiring_cleanup
.order(:expiration_policy_cleanup_status, :expiration_policy_started_at)
.limit(1)

View File

@ -0,0 +1,8 @@
---
name: specialized_worker_for_group_lock_update_auth_recalculation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66525
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336592
milestone: '14.2'
type: development
group: group::access
default_enabled: false

View File

@ -7,35 +7,35 @@ type: howto
# Geo configuration **(PREMIUM SELF)**
## Configuring a new **secondary** node
## Configuring a new **secondary** site
NOTE:
This is the final step in setting up a **secondary** Geo node. Stages of the
This is the final step in setting up a **secondary** Geo site. Stages of the
setup process must be completed in the documented order.
Before attempting the steps in this stage, [complete all prior stages](../setup/index.md#using-omnibus-gitlab).
The basic steps of configuring a **secondary** node are to:
The basic steps of configuring a **secondary** site are to:
- Replicate required configurations between the **primary** node and the **secondary** nodes.
- Configure a tracking database on each **secondary** node.
- Start GitLab on each **secondary** node.
- Replicate required configurations between the **primary** site and the **secondary** sites.
- Configure a tracking database on each **secondary** site.
- Start GitLab on each **secondary** site.
You are encouraged to first read through all the steps before executing them
in your testing/production environment.
NOTE:
**Do not** set up any custom authentication for the **secondary** nodes. This is handled by the **primary** node.
**Do not** set up any custom authentication for the **secondary** sites. This is handled by the **primary** site.
Any change that requires access to the **Admin Area** needs to be done in the
**primary** node because the **secondary** node is a read-only replica.
**primary** site because the **secondary** site is a read-only replica.
### Step 1. Manually replicate secret GitLab values
GitLab stores a number of secret values in the `/etc/gitlab/gitlab-secrets.json`
file which *must* be the same on all nodes. Until there is
a means of automatically replicating these between nodes (see [issue #3789](https://gitlab.com/gitlab-org/gitlab/-/issues/3789)),
they must be manually replicated to the **secondary** node.
file which *must* be the same on all of a site's nodes. Until there is
a means of automatically replicating these between sites (see [issue #3789](https://gitlab.com/gitlab-org/gitlab/-/issues/3789)),
they must be manually replicated to **all nodes of the secondary site**.
1. SSH into the **primary** node, and execute the command below:
1. SSH into a **Rails node on your primary** site, and execute the command below:
```shell
sudo cat /etc/gitlab/gitlab-secrets.json
@ -43,7 +43,7 @@ they must be manually replicated to the **secondary** node.
This displays the secrets that need to be replicated, in JSON format.
1. SSH into the **secondary** node and login as the `root` user:
1. SSH **into each node on your secondary Geo site** and login as the `root` user:
```shell
sudo -i
@ -55,7 +55,7 @@ they must be manually replicated to the **secondary** node.
mv /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json.`date +%F`
```
1. Copy `/etc/gitlab/gitlab-secrets.json` from the **primary** node to the **secondary** node, or
1. Copy `/etc/gitlab/gitlab-secrets.json` from the **Rails node on your primary** site to **each node on your secondary** site, or
copy-and-paste the file contents between nodes:
```shell
@ -72,28 +72,28 @@ they must be manually replicated to the **secondary** node.
chmod 0600 /etc/gitlab/gitlab-secrets.json
```
1. Reconfigure the **secondary** node for the change to take effect:
1. Reconfigure **each Rails, Sidekiq and Gitaly nodes on your secondary** site for the change to take effect:
```shell
gitlab-ctl reconfigure
gitlab-ctl restart
```
### Step 2. Manually replicate the **primary** node's SSH host keys
### Step 2. Manually replicate the **primary** site's SSH host keys
GitLab integrates with the system-installed SSH daemon, designating a user
(typically named `git`) through which all access requests are handled.
In a [Disaster Recovery](../disaster_recovery/index.md) situation, GitLab system
administrators promote a **secondary** node to the **primary** node. DNS records for the
**primary** domain should also be updated to point to the new **primary** node
(previously a **secondary** node). Doing so avoids the need to update Git remotes and API URLs.
administrators promote a **secondary** site to the **primary** site. DNS records for the
**primary** domain should also be updated to point to the new **primary** site
(previously a **secondary** site). Doing so avoids the need to update Git remotes and API URLs.
This causes all SSH requests to the newly promoted **primary** node to
This causes all SSH requests to the newly promoted **primary** site to
fail due to SSH host key mismatch. To prevent this, the primary SSH host
keys must be manually replicated to the **secondary** node.
keys must be manually replicated to the **secondary** site.
1. SSH into the **secondary** node and login as the `root` user:
1. SSH into **each node on your secondary** site and login as the `root` user:
```shell
sudo -i
@ -105,34 +105,34 @@ keys must be manually replicated to the **secondary** node.
find /etc/ssh -iname ssh_host_* -exec cp {} {}.backup.`date +%F` \;
```
1. Copy OpenSSH host keys from the **primary** node:
1. Copy OpenSSH host keys from the **primary** site:
If you can access your **primary** node using the **root** user:
If you can access one of the **nodes on your primary** site serving SSH traffic (usually, the main GitLab Rails application nodes) using the **root** user:
```shell
# Run this from the secondary node, change `<primary_node_fqdn>` for the IP or FQDN of the server
# Run this from the secondary site, change `<primary_site_fqdn>` for the IP or FQDN of the server
scp root@<primary_node_fqdn>:/etc/ssh/ssh_host_*_key* /etc/ssh
```
If you only have access through a user with `sudo` privileges:
```shell
# Run this from your primary node:
# Run this from the node on your primary site:
sudo tar --transform 's/.*\///g' -zcvf ~/geo-host-key.tar.gz /etc/ssh/ssh_host_*_key*
# Run this from your secondary node:
scp <user_with_sudo>@<primary_node_fqdn>:geo-host-key.tar.gz .
# Run this on each node on your secondary site:
scp <user_with_sudo>@<primary_site_fqdn>:geo-host-key.tar.gz .
tar zxvf ~/geo-host-key.tar.gz -C /etc/ssh
```
1. On your **secondary** node, ensure the file permissions are correct:
1. On **each node on your secondary** site, ensure the file permissions are correct:
```shell
chown root:root /etc/ssh/ssh_host_*_key*
chmod 0600 /etc/ssh/ssh_host_*_key*
```
1. To verify key fingerprint matches, execute the following command on both nodes:
1. To verify key fingerprint matches, execute the following command on both primary and secondary nodes on each site:
```shell
for file in /etc/ssh/ssh_host_*_key; do ssh-keygen -lf $file; done
@ -160,7 +160,7 @@ keys must be manually replicated to the **secondary** node.
NOTE:
The output for private keys and public keys command should generate the same fingerprint.
1. Restart `sshd` on your **secondary** node:
1. Restart `sshd` on **each node on your secondary** site:
```shell
# Debian or Ubuntu installations
@ -175,31 +175,31 @@ keys must be manually replicated to the **secondary** node.
SSH into your GitLab **secondary** server in a new terminal. If you are unable to connect,
verify the permissions are correct according to the previous steps.
### Step 3. Add the **secondary** node
### Step 3. Add the **secondary** site
1. SSH into your GitLab **secondary** server and login as root:
1. SSH into **each Rails and Sidekiq node on your secondary** site and login as root:
```shell
sudo -i
```
1. Edit `/etc/gitlab/gitlab.rb` and add a **unique** name for your node. You need this in the next steps:
1. Edit `/etc/gitlab/gitlab.rb` and add a **unique** name for your site. You need this in the next steps:
```ruby
# The unique identifier for the Geo node.
gitlab_rails['geo_node_name'] = '<node_name_here>'
# The unique identifier for the Geo site.
gitlab_rails['geo_node_name'] = '<site_name_here>'
```
1. Reconfigure the **secondary** node for the change to take effect:
1. Reconfigure **each Rails and Sidekiq node on your secondary** site for the change to take effect:
```shell
gitlab-ctl reconfigure
```
1. On the top bar of the primary node, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Geo > Nodes**.
1. Select **Add site**.
![Add secondary node](img/adding_a_secondary_node_v13_3.png)
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Geo > Sites**.
1. Select **New site**.
![Add secondary site](img/adding_a_secondary_node_v13_3.png)
1. Fill in **Name** with the `gitlab_rails['geo_node_name']` in
`/etc/gitlab/gitlab.rb`. These values must always match *exactly*, character
for character.
@ -207,10 +207,10 @@ keys must be manually replicated to the **secondary** node.
values must always match, but it doesn't matter if one ends with a `/` and
the other doesn't.
1. Optionally, choose which groups or storage shards should be replicated by the
**secondary** node. Leave blank to replicate all. Read more in
**secondary** site. Leave blank to replicate all. Read more in
[selective synchronization](#selective-synchronization).
1. Select **Add node** to add the **secondary** node.
1. SSH into your GitLab **secondary** server and restart the services:
1. Select **Add site** to add the **secondary** site.
1. SSH into **each Rails, and Sidekiq node on your secondary** site and restart the services:
```shell
gitlab-ctl restart
@ -222,56 +222,56 @@ keys must be manually replicated to the **secondary** node.
gitlab-rake gitlab:geo:check
```
1. SSH into your **primary** server and login as root to verify the
**secondary** node is reachable or there are any common issue with your Geo setup:
1. SSH into a **Rails or Sidekiq server on your primary** site and login as root to verify the
**secondary** site is reachable or there are any common issue with your Geo setup:
```shell
gitlab-rake gitlab:geo:check
```
Once added to the Geo administration page and restarted, the **secondary** node automatically starts
replicating missing data from the **primary** node in a process known as **backfill**.
Meanwhile, the **primary** node starts to notify each **secondary** node of any changes, so
that the **secondary** node can act on those notifications immediately.
Once added to the Geo administration page and restarted, the **secondary** site automatically starts
replicating missing data from the **primary** site in a process known as **backfill**.
Meanwhile, the **primary** site starts to notify each **secondary** site of any changes, so
that the **secondary** site can act on those notifications immediately.
Be sure the _secondary_ node is running and accessible. You can sign in to the
_secondary_ node with the same credentials as were used with the _primary_ node.
Be sure the _secondary_ site is running and accessible. You can sign in to the
_secondary_ site with the same credentials as were used with the _primary_ site.
### Step 4. (Optional) Configuring the **secondary** node to trust the **primary** node
### Step 4. (Optional) Configuring the **secondary** site to trust the **primary** site
You can safely skip this step if your **primary** node uses a CA-issued HTTPS certificate.
You can safely skip this step if your **primary** site uses a CA-issued HTTPS certificate.
If your **primary** node is using a self-signed certificate for *HTTPS* support, you
need to add that certificate to the **secondary** node's trust store. Retrieve the
certificate from the **primary** node and follow
If your **primary** site is using a self-signed certificate for *HTTPS* support, you
need to add that certificate to the **secondary** site's trust store. Retrieve the
certificate from the **primary** site and follow
[these instructions](https://docs.gitlab.com/omnibus/settings/ssl.html)
on the **secondary** node.
on the **secondary** site.
### Step 5. Enable Git access over HTTP/HTTPS
Geo synchronizes repositories over HTTP/HTTPS, and therefore requires this clone
method to be enabled. This is enabled by default, but if converting an existing node to Geo it should be checked:
method to be enabled. This is enabled by default, but if converting an existing site to Geo it should be checked:
On the **primary** node:
On the **primary** site:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Settings > General**.
1. Expand **Visibility and access controls**.
1. Ensure "Enabled Git access protocols" is set to either "Both SSH and HTTP(S)" or "Only HTTP(S)".
### Step 6. Verify proper functioning of the **secondary** node
### Step 6. Verify proper functioning of the **secondary** site
You can sign in to the **secondary** node with the same credentials you used with
the **primary** node. After you sign in:
You can sign in to the **secondary** site with the same credentials you used with
the **primary** site. After you sign in:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Geo > Nodes**.
1. Verify that it's correctly identified as a **secondary** Geo node, and that
1. On the left sidebar, select **Geo > Sites**.
1. Verify that it's correctly identified as a **secondary** Geo site, and that
Geo is enabled.
The initial replication, or 'backfill', is probably still in progress. You
can monitor the synchronization process on each Geo node from the **primary**
node's **Geo Nodes** dashboard in your browser.
can monitor the synchronization process on each Geo site from the **primary**
site's **Geo Sites** dashboard in your browser.
![Geo dashboard](img/geo_node_dashboard_v14_0.png)
@ -286,10 +286,10 @@ The two most obvious issues that can become apparent in the dashboard are:
- You are using a custom certificate or custom CA (see the [troubleshooting document](troubleshooting.md)).
- The instance is firewalled (check your firewall rules).
Disabling a **secondary** node stops the synchronization process.
Please note that disabling a **secondary** site stops the synchronization process.
If `git_data_dirs` is customized on the **primary** node for multiple
repository shards you must duplicate the same configuration on each **secondary** node.
Please note that if `git_data_dirs` is customized on the **primary** site for multiple
repository shards you must duplicate the same configuration on each **secondary** site.
Point your users to the [Using a Geo Site guide](usage.md).
@ -304,7 +304,7 @@ Currently, this is what is synced:
## Selective synchronization
Geo supports selective synchronization, which allows administrators to choose
which projects should be synchronized by **secondary** nodes.
which projects should be synchronized by **secondary** sites.
A subset of projects can be chosen, either by group or by storage shard. The
former is ideal for replicating data belonging to a subset of users, while the
latter is more suited to progressively rolling out Geo to a large GitLab
@ -312,22 +312,22 @@ instance.
It is important to note that selective synchronization:
1. Does not restrict permissions from **secondary** nodes.
1. Does not hide project metadata from **secondary** nodes.
1. Does not restrict permissions from **secondary** sites.
1. Does not hide project metadata from **secondary** sites.
- Since Geo currently relies on PostgreSQL replication, all project metadata
gets replicated to **secondary** nodes, but repositories that have not been
gets replicated to **secondary** sites, but repositories that have not been
selected are empty.
1. Does not reduce the number of events generated for the Geo event log.
- The **primary** node generates events as long as any **secondary** nodes are present.
Selective synchronization restrictions are implemented on the **secondary** nodes,
not the **primary** node.
- The **primary** site generates events as long as any **secondary** sites are present.
Selective synchronization restrictions are implemented on the **secondary** sites,
not the **primary** site.
### Git operations on unreplicated repositories
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2562) in GitLab 12.10 for HTTP(S) and in GitLab 13.0 for SSH.
Git clone, pull, and push operations over HTTP(S) and SSH are supported for repositories that
exist on the **primary** node but not on **secondary** nodes. This situation can occur
exist on the **primary** site but not on **secondary** sites. This situation can occur
when:
- Selective synchronization does not include the project attached to the repository.
@ -335,7 +335,7 @@ when:
## Upgrading Geo
See the [updating the Geo nodes document](updating_the_geo_nodes.md).
See the [updating the Geo sites document](updating_the_geo_nodes.md).
## Troubleshooting

View File

@ -31,7 +31,7 @@ For example:
- Before:
```haml
= gitlab_ui_form_for @group do |f|
= form_for @group do |f|
.form-group.gl-mb-3
.gl-form-checkbox.custom-control.custom-checkbox
= f.check_box :prevent_sharing_groups_outside_hierarchy, disabled: !can_change_prevent_sharing_groups_outside_hierarchy?(@group), class: 'custom-control-input'

View File

@ -183,6 +183,9 @@ perform:
tries to find the user based on the GitHub user ID, while the second query
is used to find the user using their GitHub Email address.
To avoid mismatching users, the search by GitHub user ID is not done when importing from GitHub
Enterprise.
Because this process is quite expensive we cache the result of these lookups in
Redis. For every user looked up we store three keys:

View File

@ -148,39 +148,44 @@ To use a custom project template on the **New project** page:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/26388) in GitLab 10.5.
When you create a new repository locally, instead of manually creating a new project in GitLab
and then [cloning the repository](../../gitlab-basics/start-using-git.md#clone-a-repository)
locally, you can directly push it to GitLab to create the new project, all without leaving
your terminal. If you have access rights to the associated namespace, GitLab
automatically creates a new project under that GitLab namespace with its visibility
set to Private by default (you can later change it in the [project's settings](../../public_access/public_access.md#change-project-visibility)).
When you create a new repository locally, you don't have to sign in to the GitLab
interface to create a project and
[clone its repository](../../gitlab-basics/start-using-git.md#clone-a-repository).
You can directly push your new repository to GitLab, which creates your new project
without leaving your terminal.
This can be done by using either SSH or HTTPS:
To push a new project:
```shell
## Git push using SSH
git push --set-upstream git@gitlab.example.com:namespace/nonexistent-project.git master
1. Identify the [namespace](../group/index.md#namespaces) you want to add the new
project to, as you need this information in a future step. To determine if you have
permission to create new projects in a namespace, view the group's page in a
web browser and confirm the page displays a **New project** button.
## Git push using HTTPS
git push --set-upstream https://gitlab.example.com/namespace/nonexistent-project.git master
```
NOTE:
As project creation permissions can have many factors, contact your
GitLab administrator if you're unsure.
You can pass the flag `--tags` to the `git push` command to export existing repository tags.
1. If you want to push using SSH, ensure you have [created a SSH key](../../ssh/README.md) and
[added it to your GitLab account](../../ssh/index.md#add-an-ssh-key-to-your-gitlab-account).
1. Push with one of the following methods. Replace `gitlab.example.com` with the
domain name of the machine that hosts your Git repository, `namespace` with the name of
your namespace, and `myproject` with the name of your new project:
- To push with SSH: `git push --set-upstream git@gitlab.example.com:namespace/myproject.git master`
- To push with HTTPS: `git push --set-upstream https://gitlab.example.com/namespace/myproject.git master`
Optional: to export existing repository tags, append the `--tags` flag to your `git push` command.
1. When the push completes, GitLab displays a message:
Once the push finishes successfully, a remote message indicates
the command to set the remote and the URL to the new project:
```plaintext
remote: The private project namespace/myproject was created.
```
```plaintext
remote:
remote: The private project namespace/nonexistent-project was created.
remote:
remote: To configure the remote, run:
remote: git remote add origin https://gitlab.example.com/namespace/nonexistent-project.git
remote:
remote: To view the project, visit:
remote: https://gitlab.example.com/namespace/nonexistent-project
remote:
```
1. (Optional) To configure the remote, alter the command
`git remote add origin https://gitlab.example.com/namespace/myproject.git`
to match your namespace and project names.
You can view your new project at `https://gitlab.example.com/namespace/myproject`.
Your project's visibility is set to **Private** by default, but you can change it
in your [project's settings](../../public_access/public_access.md#change-project-visibility)).
## Fork a project

View File

@ -120,10 +120,18 @@ module Gitlab
read_id_from_cache(ID_FOR_EMAIL_CACHE_KEY % email)
end
# Queries and caches the GitLab user ID for a GitHub user ID, if one was
# found.
# If importing from github.com, queries and caches the GitLab user ID for
# a GitHub user ID, if one was found.
#
# When importing from Github Enterprise, do not query user by Github ID
# since we only have users' Github ID from github.com.
def id_for_github_id(id)
gitlab_id = query_id_for_github_id(id) || nil
gitlab_id =
if project.github_enterprise_import?
nil
else
query_id_for_github_id(id)
end
Gitlab::Cache::Import::Caching.write(ID_CACHE_KEY % id, gitlab_id)
end

View File

@ -28964,10 +28964,10 @@ msgstr ""
msgid "SecurityConfiguration|Feature documentation for %{featureName}"
msgstr ""
msgid "SecurityConfiguration|GitLab Ultimate checks your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of service attacks. Its features include:"
msgid "SecurityConfiguration|High-level vulnerability statistics across projects and groups"
msgstr ""
msgid "SecurityConfiguration|High-level vulnerability statistics across projects and groups."
msgid "SecurityConfiguration|Immediately begin risk analysis and remediation with application security features. Start with SAST and Secret Detection, available to all plans. Upgrade to Ultimate to get all features, including:"
msgstr ""
msgid "SecurityConfiguration|Manage"
@ -28982,6 +28982,9 @@ msgstr ""
msgid "SecurityConfiguration|More information"
msgstr ""
msgid "SecurityConfiguration|More scan types, including Container Scanning, DAST, Dependency Scanning, Fuzzing, and Licence Compliance"
msgstr ""
msgid "SecurityConfiguration|Not enabled"
msgstr ""
@ -28991,7 +28994,7 @@ msgstr ""
msgid "SecurityConfiguration|Quickly enable all continuous testing and compliance tools by enabling %{linkStart}Auto DevOps%{linkEnd}"
msgstr ""
msgid "SecurityConfiguration|Runtime security metrics for application environments."
msgid "SecurityConfiguration|Runtime security metrics for application environments"
msgstr ""
msgid "SecurityConfiguration|SAST Analyzers"
@ -29000,7 +29003,7 @@ msgstr ""
msgid "SecurityConfiguration|SAST Configuration"
msgstr ""
msgid "SecurityConfiguration|Secure your project with Ultimate"
msgid "SecurityConfiguration|Secure your project"
msgstr ""
msgid "SecurityConfiguration|Security Control"
@ -29027,10 +29030,7 @@ msgstr ""
msgid "SecurityConfiguration|View history"
msgstr ""
msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request."
msgstr ""
msgid "SecurityConfiguration|With the information provided, you can immediately begin risk analysis and remediation within GitLab."
msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request"
msgstr ""
msgid "SecurityConfiguration|You can quickly enable all security scanning tools by enabling %{linkStart}Auto DevOps%{linkEnd}."

View File

@ -868,64 +868,85 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
describe 'POST cancel' do
before do
project.add_developer(user)
sign_in(user)
end
context 'when continue url is present' do
let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
context 'when continue to is a safe url' do
let(:url) { '/test' }
before do
post_cancel(continue: { to: url })
end
it 'redirects to the continue url' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(url)
end
it 'transits to canceled' do
expect(job.reload).to be_canceled
end
end
context 'when continue to is not a safe url' do
let(:url) { 'http://example.com' }
it 'raises an error' do
expect { cancel_with_redirect(url) }.to raise_error
end
end
end
context 'when continue url is not present' do
context 'when user is authorized to cancel the build' do
before do
project.add_developer(user)
sign_in(user)
end
context 'when continue url is present' do
let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
context 'when continue to is a safe url' do
let(:url) { '/test' }
before do
post_cancel(continue: { to: url })
end
it 'redirects to the continue url' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(url)
end
it 'transits to canceled' do
expect(job.reload).to be_canceled
end
end
context 'when continue to is not a safe url' do
let(:url) { 'http://example.com' }
it 'raises an error' do
expect { cancel_with_redirect(url) }.to raise_error
end
end
end
context 'when continue url is not present' do
before do
post_cancel
end
context 'when job is cancelable' do
let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
it 'redirects to the builds page' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(builds_namespace_project_pipeline_path(id: pipeline.id))
end
it 'transits to canceled' do
expect(job.reload).to be_canceled
end
end
context 'when job is not cancelable' do
let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
it 'returns unprocessable_entity' do
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
end
end
context 'when user is not authorized to cancel the build' do
let!(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
before do
project.add_reporter(user)
sign_in(user)
post_cancel
end
context 'when job is cancelable' do
let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
it 'redirects to the builds page' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(builds_namespace_project_pipeline_path(id: pipeline.id))
end
it 'transits to canceled' do
expect(job.reload).to be_canceled
end
it 'responds with not_found' do
expect(response).to have_gitlab_http_status(:not_found)
end
context 'when job is not cancelable' do
let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
it 'returns unprocessable_entity' do
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
it 'does not transit to canceled' do
expect(job.reload).not_to be_canceled
end
end
@ -938,43 +959,60 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
describe 'POST unschedule' do
before do
project.add_developer(user)
create(:protected_branch, :developers_can_merge,
name: 'master', project: project)
sign_in(user)
post_unschedule
create(:protected_branch, :developers_can_merge, name: 'master', project: project)
end
context 'when job is scheduled' do
context 'when user is authorized to unschedule the build' do
before do
project.add_developer(user)
sign_in(user)
post_unschedule
end
context 'when job is scheduled' do
let(:job) { create(:ci_build, :scheduled, pipeline: pipeline) }
it 'redirects to the unscheduled job page' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(namespace_project_job_path(id: job.id))
end
it 'transits to manual' do
expect(job.reload).to be_manual
end
end
context 'when job is not scheduled' do
let(:job) { create(:ci_build, pipeline: pipeline) }
it 'renders unprocessable_entity' do
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
end
context 'when user is not authorized to unschedule the build' do
let(:job) { create(:ci_build, :scheduled, pipeline: pipeline) }
it 'redirects to the unscheduled job page' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(namespace_project_job_path(id: job.id))
before do
project.add_reporter(user)
sign_in(user)
post_unschedule
end
it 'transits to manual' do
expect(job.reload).to be_manual
it 'responds with not_found' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when job is not scheduled' do
let(:job) { create(:ci_build, pipeline: pipeline) }
it 'renders unprocessable_entity' do
expect(response).to have_gitlab_http_status(:unprocessable_entity)
it 'does not transit to scheduled' do
expect(job.reload).not_to be_manual
end
end
def post_unschedule
post :unschedule, params: {
namespace_id: project.namespace,
project_id: project,
id: job.id
}
post :unschedule, params: { namespace_id: project.namespace, project_id: project, id: job.id }
end
end

View File

@ -34,6 +34,7 @@ FactoryBot.define do
end
metrics_dashboard_access_level { ProjectFeature::PRIVATE }
operations_access_level { ProjectFeature::ENABLED }
container_registry_access_level { ProjectFeature::ENABLED }
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
@ -70,6 +71,17 @@ FactoryBot.define do
}
project.build_project_feature(hash)
# This is not included in the `hash` above because the default_value_for in
# the ProjectFeature model overrides the value set by `build_project_feature` when
# evaluator.container_registry_access_level == ProjectFeature::DISABLED.
#
# This is because the default_value_for gem uses the <column>_changed? method
# to determine if the default value should be applied. For new records,
# <column>_changed? returns false if the value of the column is the same as
# the database default.
# See https://github.com/FooBarWidget/default_value_for/blob/release-3.4.0/lib/default_value_for.rb#L158.
project.project_feature.container_registry_access_level = evaluator.container_registry_access_level
end
after(:create) do |project, evaluator|
@ -344,6 +356,9 @@ FactoryBot.define do
trait(:analytics_enabled) { analytics_access_level { ProjectFeature::ENABLED } }
trait(:analytics_disabled) { analytics_access_level { ProjectFeature::DISABLED } }
trait(:analytics_private) { analytics_access_level { ProjectFeature::PRIVATE } }
trait(:container_registry_enabled) { container_registry_access_level { ProjectFeature::ENABLED } }
trait(:container_registry_disabled) { container_registry_access_level { ProjectFeature::DISABLED } }
trait(:container_registry_private) { container_registry_access_level { ProjectFeature::PRIVATE } }
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops

View File

@ -43,11 +43,11 @@ describe('UpgradeBanner component', () => {
it('renders the list of benefits', () => {
const wrapperText = wrapper.text();
expect(wrapperText).toContain('GitLab Ultimate checks your application');
expect(wrapperText).toContain('Immediately begin risk analysis and remediation');
expect(wrapperText).toContain('statistics in the merge request');
expect(wrapperText).toContain('statistics across projects');
expect(wrapperText).toContain('Runtime security metrics');
expect(wrapperText).toContain('risk analysis and remediation');
expect(wrapperText).toContain('More scan types, including Container Scanning,');
});
it(`re-emits GlBanner's close event`, () => {

View File

@ -17,7 +17,7 @@ RSpec.describe Types::BaseField do
end
it 'defaults to 1' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true)
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true)
expect(field.to_graphql.complexity).to eq 1
end
@ -25,7 +25,7 @@ RSpec.describe Types::BaseField do
describe '#base_complexity' do
context 'with no gitaly calls' do
it 'defaults to 1' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true)
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true)
expect(field.base_complexity).to eq 1
end
@ -33,7 +33,7 @@ RSpec.describe Types::BaseField do
context 'with a gitaly call' do
it 'adds 1 to the default value' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true)
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, calls_gitaly: true)
expect(field.base_complexity).to eq 2
end
@ -41,14 +41,14 @@ RSpec.describe Types::BaseField do
end
it 'has specified value' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, complexity: 12)
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12)
expect(field.to_graphql.complexity).to eq 12
end
context 'when field has a resolver' do
context 'when a valid complexity is already set' do
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, complexity: 2, max_page_size: 100, null: true) }
let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: resolver, complexity: 2, max_page_size: 100, null: true) }
it 'uses this complexity' do
expect(field.to_graphql.complexity).to eq 2
@ -56,7 +56,7 @@ RSpec.describe Types::BaseField do
end
context 'and is a connection' do
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, max_page_size: 100, null: true) }
let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: resolver, max_page_size: 100, null: true) }
it 'sets complexity depending on arguments for resolvers' do
expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4
@ -71,7 +71,7 @@ RSpec.describe Types::BaseField do
context 'and is not a connection' do
it 'sets complexity as normal' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, max_page_size: 100, null: true)
expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2
expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2
@ -82,8 +82,8 @@ RSpec.describe Types::BaseField do
context 'calls_gitaly' do
context 'for fields with a resolver' do
it 'adds 1 if true' do
with_gitaly_field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, null: true, calls_gitaly: true)
without_gitaly_field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, null: true)
with_gitaly_field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, null: true, calls_gitaly: true)
without_gitaly_field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, null: true)
base_result = without_gitaly_field.to_graphql.complexity.call({}, {}, 2)
expect(with_gitaly_field.to_graphql.complexity.call({}, {}, 2)).to eq base_result + 1
@ -92,28 +92,28 @@ RSpec.describe Types::BaseField do
context 'for fields without a resolver' do
it 'adds 1 if true' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true)
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, calls_gitaly: true)
expect(field.to_graphql.complexity).to eq 2
end
end
it 'defaults to false' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true)
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true)
expect(field.base_complexity).to eq Types::BaseField::DEFAULT_COMPLEXITY
end
context 'with declared constant complexity value' do
it 'has complexity set to that constant' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, complexity: 12)
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12)
expect(field.to_graphql.complexity).to eq 12
end
it 'does not raise an error even with Gitaly calls' do
allow(Gitlab::GitalyClient).to receive(:get_request_count).and_return([0, 1])
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, complexity: 12)
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12)
expect(field.to_graphql.complexity).to eq 12
end
@ -123,7 +123,7 @@ RSpec.describe Types::BaseField do
describe '#visible?' do
context 'and has a feature_flag' do
let(:flag) { :test_feature }
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false) }
let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, feature_flag: flag, null: false) }
let(:context) { {} }
before do
@ -156,7 +156,7 @@ RSpec.describe Types::BaseField do
describe '#description' do
context 'feature flag given' do
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false, description: 'Test description.') }
let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, feature_flag: flag, null: false, description: 'Test description.') }
let(:flag) { :test_flag }
it 'prepends the description' do
@ -211,7 +211,7 @@ RSpec.describe Types::BaseField do
include_examples 'Gitlab-style deprecations' do
def subject(args = {})
base_args = { name: 'test', type: GraphQL::STRING_TYPE, null: true }
base_args = { name: 'test', type: GraphQL::Types::String, null: true }
described_class.new(**base_args.merge(args))
end

View File

@ -3,7 +3,14 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:project) { create(:project) }
let(:project) do
create(
:project,
import_type: 'github',
import_url: 'https://api.github.com/user/repo'
)
end
let(:client) { double(:client) }
let(:finder) { described_class.new(project, client) }
@ -263,6 +270,26 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
finder.id_for_github_id(id)
end
context 'when importing from github enterprise' do
let(:project) do
create(
:project,
import_type: 'github',
import_url: 'https://othergithub.net/user/repo'
)
end
it 'does not look up the user by external id' do
expect(finder).not_to receive(:query_id_for_github_id)
expect(Gitlab::Cache::Import::Caching)
.to receive(:write)
.with(described_class::ID_CACHE_KEY % id, nil)
finder.id_for_github_id(id)
end
end
end
describe '#id_for_github_email' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ::Gitlab::SubscriptionPortal, skip: Gitlab.jh? do
RSpec.describe ::Gitlab::SubscriptionPortal do
using RSpec::Parameterized::TableSyntax
where(:method_name, :test, :development, :result) do

View File

@ -323,7 +323,7 @@ RSpec.describe ContainerRepository do
context 'with a subgroup' do
let_it_be(:test_group) { create(:group) }
let_it_be(:another_project) { create(:project, path: 'test', group: test_group) }
let_it_be(:project3) { create(:project, path: 'test3', group: test_group, container_registry_enabled: false) }
let_it_be(:project3) { create(:project, :container_registry_disabled, path: 'test3', group: test_group) }
let_it_be(:another_repository) do
create(:container_repository, name: 'my_image', project: another_project)

View File

@ -1152,6 +1152,68 @@ RSpec.describe Namespace do
end
end
context 'refreshing project access on updating share_with_group_lock' do
let(:group) { create(:group, share_with_group_lock: false) }
let(:project) { create(:project, :private, group: group) }
let_it_be(:shared_with_group_one) { create(:group) }
let_it_be(:shared_with_group_two) { create(:group) }
let_it_be(:group_one_user) { create(:user) }
let_it_be(:group_two_user) { create(:user) }
subject(:execute_update) { group.update!(share_with_group_lock: true) }
before do
shared_with_group_one.add_developer(group_one_user)
shared_with_group_two.add_developer(group_two_user)
create(:project_group_link, group: shared_with_group_one, project: project)
create(:project_group_link, group: shared_with_group_two, project: project)
end
it 'calls AuthorizedProjectUpdate::ProjectRecalculateWorker to update project authorizations' do
expect(AuthorizedProjectUpdate::ProjectRecalculateWorker)
.to receive(:perform_async).with(project.id).once
execute_update
end
it 'updates authorizations leading to users from shared groups losing access', :sidekiq_inline do
expect { execute_update }
.to change { group_one_user.authorized_projects.include?(project) }.from(true).to(false)
.and change { group_two_user.authorized_projects.include?(project) }.from(true).to(false)
end
it 'calls AuthorizedProjectUpdate::UserRefreshFromReplicaWorker with a delay to update project authorizations' do
expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
receive(:bulk_perform_in)
.with(1.hour,
[[group_one_user.id]],
batch_delay: 30.seconds, batch_size: 100)
)
expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
receive(:bulk_perform_in)
.with(1.hour,
[[group_two_user.id]],
batch_delay: 30.seconds, batch_size: 100)
)
execute_update
end
context 'when the feature flag `specialized_worker_for_group_lock_update_auth_recalculation` is disabled' do
before do
stub_feature_flags(specialized_worker_for_group_lock_update_auth_recalculation: false)
end
it 'refreshes the permissions of the members of the old and new namespace' do
expect { execute_update }
.to change { group_one_user.authorized_projects.include?(project) }.from(true).to(false)
.and change { group_two_user.authorized_projects.include?(project) }.from(true).to(false)
end
end
end
describe '#share_with_group_lock with subgroups' do
context 'when creating a subgroup' do
let(:subgroup) { create(:group, parent: root_group )}

View File

@ -189,27 +189,33 @@ RSpec.describe ProjectFeature do
end
describe 'container_registry_access_level' do
context 'when the project is created with container_registry_enabled false' do
it 'creates project with DISABLED container_registry_access_level' do
project = create(:project, container_registry_enabled: false)
context 'with default value' do
let(:project) { Project.new }
expect(project.project_feature.container_registry_access_level).to eq(described_class::DISABLED)
context 'when the default is false' do
it 'creates project_feature with `disabled` container_registry_access_level' do
stub_config_setting(default_projects_features: { container_registry: false })
expect(project.project_feature.container_registry_access_level).to eq(described_class::DISABLED)
end
end
end
context 'when the project is created with container_registry_enabled true' do
it 'creates project with ENABLED container_registry_access_level' do
project = create(:project, container_registry_enabled: true)
context 'when the default is true' do
before do
stub_config_setting(default_projects_features: { container_registry: true })
end
expect(project.project_feature.container_registry_access_level).to eq(described_class::ENABLED)
it 'creates project_feature with `enabled` container_registry_access_level' do
expect(project.project_feature.container_registry_access_level).to eq(described_class::ENABLED)
end
end
end
context 'when the project is created with container_registry_enabled nil' do
it 'creates project with DISABLED container_registry_access_level' do
project = create(:project, container_registry_enabled: nil)
context 'when the default is nil' do
it 'creates project_feature with `disabled` container_registry_access_level' do
stub_config_setting(default_projects_features: { container_registry: nil })
expect(project.project_feature.container_registry_access_level).to eq(described_class::DISABLED)
expect(project.project_feature.container_registry_access_level).to eq(described_class::DISABLED)
end
end
end
end

View File

@ -2407,7 +2407,7 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '#set_container_registry_access_level' do
describe '#container_registry_enabled=' do
let_it_be_with_reload(:project) { create(:project) }
it 'updates project_feature', :aggregate_failures do
@ -2872,6 +2872,36 @@ RSpec.describe Project, factory_default: :keep do
it { expect(project.import?).to be true }
end
describe '#github_import?' do
let_it_be(:project) { build(:project, import_type: 'github') }
it { expect(project.github_import?).to be true }
end
describe '#github_enterprise_import?' do
let_it_be(:github_com_project) do
build(
:project,
import_type: 'github',
import_url: 'https://api.github.com/user/repo'
)
end
let_it_be(:github_enterprise_project) do
build(
:project,
import_type: 'github',
import_url: 'https://othergithub.net/user/repo'
)
end
it { expect(github_com_project.github_import?).to be true }
it { expect(github_com_project.github_enterprise_import?).to be false }
it { expect(github_enterprise_project.github_import?).to be true }
it { expect(github_enterprise_project.github_enterprise_import?).to be true }
end
describe '#remove_import_data' do
let(:import_data) { ProjectImportData.new(data: { 'test' => 'some data' }) }

View File

@ -307,7 +307,7 @@ RSpec.describe ProjectTeam do
it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
context 'but share_with_group_lock is true' do
context 'but share_with_group_lock is true', :sidekiq_inline do
before do
project.namespace.update!(share_with_group_lock: true)
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::BuildCancelService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
describe '#execute' do
subject(:execute) { described_class.new(build, user).execute }
context 'when user is authorized to cancel the build' do
before do
project.add_maintainer(user)
end
context 'when build is cancelable' do
let!(:build) { create(:ci_build, :cancelable, pipeline: pipeline) }
it 'transits build to canceled', :aggregate_failures do
response = execute
expect(response).to be_success
expect(response.payload.reload).to be_canceled
end
end
context 'when build is not cancelable' do
let!(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
it 'responds with unprocessable entity', :aggregate_failures do
response = execute
expect(response).to be_error
expect(response.http_status).to eq(:unprocessable_entity)
end
end
end
context 'when user is not authorized to cancel the build' do
let!(:build) { create(:ci_build, :cancelable, pipeline: pipeline) }
it 'responds with forbidden', :aggregate_failures do
response = execute
expect(response).to be_error
expect(response.http_status).to eq(:forbidden)
end
end
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::BuildUnscheduleService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
describe '#execute' do
subject(:execute) { described_class.new(build, user).execute }
context 'when user is authorized to unschedule the build' do
before do
project.add_maintainer(user)
end
context 'when build is scheduled' do
let!(:build) { create(:ci_build, :scheduled, pipeline: pipeline) }
it 'transits build to manual' do
response = execute
expect(response).to be_success
expect(response.payload.reload).to be_manual
end
end
context 'when build is not scheduled' do
let!(:build) { create(:ci_build, pipeline: pipeline) }
it 'responds with unprocessable entity', :aggregate_failures do
response = execute
expect(response).to be_error
expect(response.http_status).to eq(:unprocessable_entity)
end
end
end
context 'when user is not authorized to unschedule the build' do
let!(:build) { create(:ci_build, :scheduled, pipeline: pipeline) }
it 'responds with forbidden', :aggregate_failures do
response = execute
expect(response).to be_error
expect(response.http_status).to eq(:forbidden)
end
end
end
end

View File

@ -25,6 +25,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(ContainerExpirationPolicies::CleanupService)
.to receive(:new).with(repository).and_return(double(execute: service_response))
expect_log_extra_metadata(service_response: service_response)
expect_log_info(project_id: project.id, container_repository_id: repository.id)
subject
end
@ -35,6 +36,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(ContainerExpirationPolicies::CleanupService)
.to receive(:new).with(repository).and_return(double(execute: service_response))
expect_log_extra_metadata(service_response: service_response, cleanup_status: :unfinished)
expect_log_info(project_id: project.id, container_repository_id: repository.id)
subject
end
@ -45,6 +47,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(ContainerExpirationPolicies::CleanupService)
.to receive(:new).with(repository).and_return(double(execute: service_response))
expect_log_extra_metadata(service_response: service_response, cleanup_status: :unfinished, truncated: true)
expect_log_info(project_id: project.id, container_repository_id: repository.id)
subject
end
@ -65,6 +68,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(ContainerExpirationPolicies::CleanupService)
.to receive(:new).with(repository).and_return(double(execute: service_response))
expect_log_extra_metadata(service_response: service_response, cleanup_status: :unfinished, truncated: truncated)
expect_log_info(project_id: project.id, container_repository_id: repository.id)
subject
end
@ -78,6 +82,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(ContainerExpirationPolicies::CleanupService)
.to receive(:new).with(repository).and_return(double(execute: service_response))
expect_log_extra_metadata(service_response: service_response, cleanup_status: :error)
expect_log_info(project_id: project.id, container_repository_id: repository.id)
subject
end
@ -361,6 +366,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(ContainerExpirationPolicies::CleanupService)
.to receive(:new).with(repository).and_return(double(execute: service_response))
expect_log_extra_metadata(service_response: service_response)
expect_log_info(project_id: project.id, container_repository_id: repository.id)
subject
end
@ -396,6 +402,11 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_error_message, service_response.message)
end
end
def expect_log_info(structure)
expect(worker.logger)
.to receive(:info).with(worker.structured_payload(structure))
end
end
describe '#remaining_work_count' do
@ -446,6 +457,12 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
end
it { is_expected.to eq(0) }
it 'does not log a selected container' do
expect(worker).not_to receive(:log_info)
subject
end
end
end