Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ec3483bd18
commit
c4af99d56f
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module Types
|
|||
graphql_name 'Branch'
|
||||
|
||||
field :name,
|
||||
GraphQL::STRING_TYPE,
|
||||
GraphQL::Types::String,
|
||||
null: false,
|
||||
description: 'Name of the branch.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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:)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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**.
|
||||

|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. On the left sidebar, select **Geo > Sites**.
|
||||
1. Select **New site**.
|
||||

|
||||
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.
|
||||
|
||||

|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`, () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 )}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue