Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c82f9ecc2a
commit
814b3eca14
|
|
@ -40,6 +40,7 @@ RSpec/SpecFilePathFormat:
|
|||
- 'spec/services/ci/create_pipeline_service/environment_spec.rb'
|
||||
- 'spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb'
|
||||
- 'spec/services/ci/create_pipeline_service/include_spec.rb'
|
||||
- 'spec/services/ci/create_pipeline_service/inputs_spec.rb'
|
||||
- 'spec/services/ci/create_pipeline_service/limit_active_jobs_spec.rb'
|
||||
- 'spec/services/ci/create_pipeline_service/logger_spec.rb'
|
||||
- 'spec/services/ci/create_pipeline_service/merge_requests_spec.rb'
|
||||
|
|
|
|||
|
|
@ -349,7 +349,6 @@ SidekiqLoadBalancing/WorkerDataConsistency:
|
|||
- 'ee/app/workers/search/elastic/metrics_update_cron_worker.rb'
|
||||
- 'ee/app/workers/search/index_curation_worker.rb'
|
||||
- 'ee/app/workers/search/zoekt/namespace_indexer_worker.rb'
|
||||
- 'ee/app/workers/search/zoekt/namespace_initial_indexing_worker.rb'
|
||||
- 'ee/app/workers/search/zoekt/scheduling_worker.rb'
|
||||
- 'ee/app/workers/security/create_orchestration_policy_worker.rb'
|
||||
- 'ee/app/workers/security/orchestration_policy_rule_schedule_worker.rb'
|
||||
|
|
|
|||
|
|
@ -153,11 +153,13 @@ export default {
|
|||
</gl-alert>
|
||||
<gl-form-group
|
||||
:label="__('Name')"
|
||||
label-for="comment-template-name-input"
|
||||
:state="isNameValid"
|
||||
:invalid-feedback="__('Please enter a name for the comment template.')"
|
||||
data-testid="comment-template-name-form-group"
|
||||
>
|
||||
<gl-form-input
|
||||
id="comment-template-name-input"
|
||||
v-model="updateCommentTemplate.name"
|
||||
:placeholder="__('Enter a name for your comment template')"
|
||||
data-testid="comment-template-name-input"
|
||||
|
|
|
|||
|
|
@ -20,12 +20,23 @@ export default {
|
|||
<template>
|
||||
<multi-step-form-template :title="option.title" :current-step="2" :steps-total="2">
|
||||
<template #next>
|
||||
<gl-button category="primary" variant="confirm" :disabled="true">
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
:disabled="true"
|
||||
data-testid="create-project-button"
|
||||
@click="$emit('create-project')"
|
||||
>
|
||||
{{ __('Create project') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
<template #back>
|
||||
<gl-button category="primary" variant="default" @click="$emit('back')">
|
||||
<gl-button
|
||||
category="primary"
|
||||
data-testid="create-project-back-button"
|
||||
variant="default"
|
||||
@click="$emit('back')"
|
||||
>
|
||||
{{ __('Go back') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,22 @@ export default {
|
|||
<template>
|
||||
<multi-step-form-template :title="option.title" :current-step="2" :steps-total="2">
|
||||
<template #next>
|
||||
<gl-button category="primary" variant="confirm" :disabled="true">
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
:disabled="true"
|
||||
data-testid="create-cicd-project-button"
|
||||
>
|
||||
{{ __('Create project') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
<template #back>
|
||||
<gl-button category="primary" variant="default" @click="$emit('back')">
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="default"
|
||||
data-testid="create-cicd-project-back-button"
|
||||
@click="$emit('back')"
|
||||
>
|
||||
{{ __('Go back') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -18,14 +18,24 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<multi-step-form-template :title="option.title" :current-step="2">
|
||||
<multi-step-form-template :title="option.title" :current-step="2" :steps-total="2">
|
||||
<template #next>
|
||||
<gl-button category="primary" variant="confirm" :disabled="true">
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
:disabled="true"
|
||||
data-testid="import-project-next-button"
|
||||
>
|
||||
{{ __('Next step') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
<template #back>
|
||||
<gl-button category="primary" variant="default" @click="$emit('back')">
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="default"
|
||||
data-testid="import-project-back-button"
|
||||
@click="$emit('back')"
|
||||
>
|
||||
{{ __('Go back') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,22 @@ export default {
|
|||
<template>
|
||||
<multi-step-form-template :title="option.title" :current-step="2" :steps-total="3">
|
||||
<template #next>
|
||||
<gl-button category="primary" variant="confirm" :disabled="true">
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
:disabled="true"
|
||||
data-testid="template-project-next-button"
|
||||
>
|
||||
{{ __('Next step') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
<template #back>
|
||||
<gl-button category="primary" variant="default" @click="$emit('back')">
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="default"
|
||||
data-testid="template-project-back-button"
|
||||
@click="$emit('back')"
|
||||
>
|
||||
{{ __('Go back') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -485,10 +485,7 @@ module Ci
|
|||
end
|
||||
|
||||
def archived?
|
||||
return true if degenerated?
|
||||
|
||||
archive_builds_older_than = Gitlab::CurrentSettings.current_application_settings.archive_builds_older_than
|
||||
archive_builds_older_than.present? && created_at < archive_builds_older_than
|
||||
degenerated? || super
|
||||
end
|
||||
|
||||
def playable?
|
||||
|
|
|
|||
|
|
@ -714,9 +714,16 @@ module Ci
|
|||
end
|
||||
|
||||
def retryable?
|
||||
return false if archived?
|
||||
|
||||
retryable_builds.any?
|
||||
end
|
||||
|
||||
def archived?
|
||||
archive_builds_older_than = Gitlab::CurrentSettings.current_application_settings.archive_builds_older_than
|
||||
archive_builds_older_than.present? && created_at < archive_builds_older_than
|
||||
end
|
||||
|
||||
def cancelable?
|
||||
cancelable_statuses.any? && internal_pipeline?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ class CommitStatus < Ci::ApplicationRecord
|
|||
end
|
||||
|
||||
def archived?
|
||||
false
|
||||
pipeline.archived?
|
||||
end
|
||||
|
||||
def stuck?
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ module Ci
|
|||
can?(:read_build, @subject.project)
|
||||
end
|
||||
|
||||
condition(:archived, scope: :subject) do
|
||||
@subject.archived?
|
||||
end
|
||||
|
||||
# Allow reading builds for external pipelines regardless of whether CI/CD is disabled
|
||||
overrides :read_build
|
||||
rule { project_allows_read_build | (external_pipeline & can?(:reporter_access)) }.policy do
|
||||
|
|
@ -37,7 +41,7 @@ module Ci
|
|||
prevent :read_pipeline
|
||||
end
|
||||
|
||||
rule { protected_ref }.policy do
|
||||
rule { archived | protected_ref }.policy do
|
||||
prevent :update_pipeline
|
||||
prevent :cancel_pipeline
|
||||
end
|
||||
|
|
|
|||
|
|
@ -63,7 +63,12 @@ module Ci
|
|||
#
|
||||
# @return [Ci::Pipeline] The created Ci::Pipeline object.
|
||||
# rubocop: disable Metrics/ParameterLists, Metrics/AbcSize
|
||||
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, bridge: nil, **options, &block)
|
||||
def execute(
|
||||
source,
|
||||
ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil,
|
||||
external_pull_request: nil, bridge: nil, inputs: {},
|
||||
**options, &block
|
||||
)
|
||||
@logger = build_logger
|
||||
@command_logger = Gitlab::Ci::Pipeline::CommandLogger.new
|
||||
@pipeline = Ci::Pipeline.new
|
||||
|
|
@ -93,6 +98,7 @@ module Ci
|
|||
bridge: bridge,
|
||||
logger: @logger,
|
||||
partition_id: params[:partition_id],
|
||||
inputs: ::Feature.enabled?(:ci_inputs_for_pipelines, project) ? inputs : {},
|
||||
**extra_options(**options))
|
||||
|
||||
@pipeline.readonly! if command.readonly?
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ module Ci
|
|||
end
|
||||
|
||||
def execute(&transition)
|
||||
return forbidden unless allowed?
|
||||
|
||||
transition ||= ->(job) { job.enqueue! }
|
||||
|
||||
Gitlab::OptimisticLocking.retry_lock(job, name: 'ci_enqueue_job') do |job|
|
||||
|
|
@ -24,5 +26,15 @@ module Ci
|
|||
|
||||
job
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allowed?
|
||||
::Ability.allowed?(current_user, :update_pipeline, job.pipeline)
|
||||
end
|
||||
|
||||
def forbidden
|
||||
ServiceResponse.error(message: 'Forbidden', reason: :forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
module Ci
|
||||
module Pipelines
|
||||
class UpdateMetadataService
|
||||
def initialize(pipeline, params)
|
||||
def initialize(pipeline, current_user:, params: {})
|
||||
@pipeline = pipeline
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
return forbidden unless allowed?
|
||||
|
||||
metadata = pipeline.pipeline_metadata
|
||||
|
||||
metadata = pipeline.build_pipeline_metadata(project: pipeline.project) if metadata.nil?
|
||||
|
||||
params[:name] = params[:name].strip if params.key?(:name)
|
||||
|
||||
if metadata.update(params)
|
||||
|
|
@ -25,7 +26,15 @@ module Ci
|
|||
|
||||
private
|
||||
|
||||
attr_reader :pipeline, :params
|
||||
attr_reader :pipeline, :current_user, :params
|
||||
|
||||
def allowed?
|
||||
::Ability.allowed?(current_user, :update_pipeline, pipeline)
|
||||
end
|
||||
|
||||
def forbidden
|
||||
ServiceResponse.error(message: 'Forbidden', reason: :forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ module Ci
|
|||
|
||||
pipeline.ensure_scheduling_type!
|
||||
|
||||
builds_relation(pipeline).find_each do |build|
|
||||
next unless can_be_retried?(build)
|
||||
builds_relation(pipeline).find_each do |job|
|
||||
next unless can_be_retried?(job)
|
||||
|
||||
Ci::RetryJobService.new(project, current_user).clone!(build)
|
||||
Ci::RetryJobService.new(project, current_user).clone!(job)
|
||||
end
|
||||
|
||||
pipeline.processables.latest.skipped.find_each do |skipped|
|
||||
retry_optimistic_lock(skipped, name: 'ci_retry_pipeline') { |build| build.process(current_user) }
|
||||
retry_optimistic_lock(skipped, name: 'ci_retry_pipeline') { |job| job.process(current_user) }
|
||||
end
|
||||
|
||||
pipeline.reset_source_bridge!(current_user)
|
||||
|
|
@ -47,8 +47,8 @@ module Ci
|
|||
pipeline.retryable_builds.preload_needs
|
||||
end
|
||||
|
||||
def can_be_retried?(build)
|
||||
can?(current_user, :update_build, build)
|
||||
def can_be_retried?(job)
|
||||
can?(current_user, :update_build, job) && job.retryable?
|
||||
end
|
||||
|
||||
def start_pipeline(pipeline)
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@
|
|||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :notes_create_limit, _('Maximum requests per minute'), class: 'label-bold'
|
||||
= f.label :notes_create_limit, _('Maximum requests per minute')
|
||||
= f.number_field :notes_create_limit, class: 'form-control gl-form-input'
|
||||
.form-group
|
||||
= f.label :notes_create_limit_allowlist, _('Users to exclude from the rate limit'), class: 'label-bold'
|
||||
= f.text_area :notes_create_limit_allowlist_raw, class: 'form-control gl-form-input', rows: 5, aria: { describedBy: 'note-create-limits-allowlist-field-description' }
|
||||
= f.label :notes_create_limit_allowlist_raw, _('Users to exclude from the rate limit')
|
||||
= f.text_area :notes_create_limit_allowlist_raw, class: 'form-control gl-form-input', rows: 5
|
||||
.form-text.gl-text-subtle{ id: 'note-create-limits-allowlist-field-description' }
|
||||
= _('List of users who are allowed to exceed the rate limit. Example: username1, username2')
|
||||
|
||||
|
||||
= f.submit _('Save changes'), pajamas_button: true
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@
|
|||
_('Enable rate limiting for requests to the specified paths'),
|
||||
help_text: _('Helps reduce request volume for protected paths.')
|
||||
.form-group
|
||||
= f.label :throttle_protected_paths_requests_per_period, _('Maximum requests per period per user'), class: 'label-bold'
|
||||
= f.label :throttle_protected_paths_requests_per_period, _('Maximum requests per period per user')
|
||||
= f.number_field :throttle_protected_paths_requests_per_period, class: 'form-control gl-form-input'
|
||||
.form-group
|
||||
= f.label :throttle_protected_paths_period_in_seconds, _('Rate limit period (in seconds)'), class: 'label-bold'
|
||||
= f.label :throttle_protected_paths_period_in_seconds, _('Rate limit period (in seconds)')
|
||||
= f.number_field :throttle_protected_paths_period_in_seconds, class: 'form-control gl-form-input'
|
||||
.form-group
|
||||
= f.label :protected_paths, class: 'label-bold' do
|
||||
= f.label :protected_paths do
|
||||
= _('Paths with rate limiting for POST requests')
|
||||
= f.text_area :protected_paths_raw, placeholder: '/users/sign_in,/users/password', class: 'form-control gl-form-input', rows: 10
|
||||
.form-group
|
||||
= f.label :protected_paths_for_get_request, class: 'label-bold' do
|
||||
= f.label :protected_paths_for_get_request_raw do
|
||||
= _('Paths with rate limiting for GET requests')
|
||||
= f.text_area :protected_paths_for_get_request_raw, class: 'form-control gl-form-input', rows: 10
|
||||
%span.form-text.gl-text-subtle
|
||||
|
|
|
|||
|
|
@ -3,20 +3,19 @@
|
|||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :search_rate_limit, _('Maximum number of requests per minute for an authenticated user'), class: 'label-bold'
|
||||
= f.label :search_rate_limit, _('Maximum number of requests per minute for an authenticated user')
|
||||
.form-text.gl-text-subtle
|
||||
= _("Set this number to 0 to disable the limit.")
|
||||
= f.number_field :search_rate_limit, class: 'form-control gl-form-input'
|
||||
|
||||
.form-group
|
||||
= f.label :search_rate_limit_unauthenticated, _('Maximum number of requests per minute for an unauthenticated IP address'), class: 'label-bold'
|
||||
= f.label :search_rate_limit_unauthenticated, _('Maximum number of requests per minute for an unauthenticated IP address')
|
||||
= f.number_field :search_rate_limit_unauthenticated, class: 'form-control gl-form-input'
|
||||
|
||||
.form-group
|
||||
= f.label :search_rate_limit_allowlist, _('Users to exclude from the rate limit'), class: 'label-bold'
|
||||
= f.text_area :search_rate_limit_allowlist_raw, class: 'form-control gl-form-input', rows: 5, aria: { describedBy: 'search-rate-limit-allowlist-field-description' }
|
||||
= f.label :search_rate_limit_allowlist_raw, _('Users to exclude from the rate limit')
|
||||
= f.text_area :search_rate_limit_allowlist_raw, class: 'form-control gl-form-input', rows: 5
|
||||
.form-text.gl-text-subtle{ id: 'search-rate-limit-allowlist-field-description' }
|
||||
= _('List of users who are allowed to exceed the rate limit. Example: username1, username2')
|
||||
|
||||
|
||||
= f.submit _('Save changes'), pajamas_button: true
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
= render ::Layouts::PageHeadingComponent.new(_('Projects')) do |c|
|
||||
- c.with_actions do
|
||||
= render Pajamas::ButtonComponent.new(href: starred_explore_projects_path, variant: :link, button_options: { class: 'gl-m-2' }) do
|
||||
= render Pajamas::ButtonComponent.new(href: starred_explore_projects_path, variant: :confirm, category: :tertiary) do
|
||||
= _("Explore projects")
|
||||
|
||||
- if current_user.can_create_project?
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
.gl-mb-5
|
||||
= render Pajamas::CardComponent.new do |c|
|
||||
- c.with_body do
|
||||
%p.gl-mt-0.gl-mb-3.gl-font-bold
|
||||
%h3.gl-mt-0.gl-mt-0.gl-mb-3.gl-font-bold.gl-leading-reset.gl-text-base.gl-text-default
|
||||
= _("Can't scan the code?")
|
||||
%p.gl-mt-0.gl-mb-3
|
||||
= _("To add the entry manually, provide the following details to the application on your phone.")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: ci_inputs_for_pipelines
|
||||
feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/16321
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182125
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/520290
|
||||
milestone: '17.10'
|
||||
group: group::pipeline authoring
|
||||
type: wip
|
||||
default_enabled: false
|
||||
|
|
@ -825,8 +825,6 @@
|
|||
- 1
|
||||
- - search_zoekt_namespace_indexer
|
||||
- 1
|
||||
- - search_zoekt_namespace_initial_indexing
|
||||
- 1
|
||||
- - search_zoekt_node_with_negative_unclaimed_storage_event
|
||||
- 1
|
||||
- - search_zoekt_orphaned_index_event
|
||||
|
|
|
|||
|
|
@ -4278,20 +4278,6 @@ CREATE TABLE loose_foreign_keys_deleted_records (
|
|||
)
|
||||
PARTITION BY LIST (partition);
|
||||
|
||||
CREATE TABLE merge_request_diff_commits_b5377a7a34 (
|
||||
authored_date timestamp without time zone,
|
||||
committed_date timestamp without time zone,
|
||||
sha bytea NOT NULL,
|
||||
message text,
|
||||
trailers jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
commit_author_id bigint,
|
||||
committer_id bigint,
|
||||
merge_request_diff_id bigint NOT NULL,
|
||||
relative_order integer NOT NULL,
|
||||
project_id bigint
|
||||
)
|
||||
PARTITION BY RANGE (merge_request_diff_id);
|
||||
|
||||
CREATE TABLE p_batched_git_ref_updates_deletions (
|
||||
id bigint NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
|
|
@ -16026,6 +16012,20 @@ CREATE TABLE merge_request_diff_commits (
|
|||
committer_id bigint
|
||||
);
|
||||
|
||||
CREATE TABLE merge_request_diff_commits_b5377a7a34 (
|
||||
authored_date timestamp without time zone,
|
||||
committed_date timestamp without time zone,
|
||||
sha bytea NOT NULL,
|
||||
message text,
|
||||
trailers jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
commit_author_id bigint,
|
||||
committer_id bigint,
|
||||
merge_request_diff_id bigint NOT NULL,
|
||||
relative_order integer NOT NULL,
|
||||
project_id bigint
|
||||
)
|
||||
PARTITION BY RANGE (merge_request_diff_id);
|
||||
|
||||
CREATE TABLE merge_request_diff_details (
|
||||
merge_request_diff_id bigint NOT NULL,
|
||||
verification_retry_at timestamp with time zone,
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ After developer tools have been enabled, obtain a session cookie as follows:
|
|||
1. Select the `results?request_id=<some-request-id>` request on the left hand side.
|
||||
1. The session cookie is displayed under the `Request Headers` section of the `Headers` panel. Right-click on the cookie value and select `Copy value`.
|
||||
|
||||

|
||||

|
||||
|
||||
You have the value of the session cookie copied to your clipboard, for example:
|
||||
|
||||
|
|
@ -191,11 +191,11 @@ You can then view the database details for this request:
|
|||
|
||||
1. Paste the `x-request-id` value into the `request details` field of the [performance bar](../monitoring/performance/performance_bar.md) and press <kbd>Enter/Return</kbd>. This example uses the `x-request-id` value `01FGN8P881GF2E5J91JYA338Y3`, returned by the above response:
|
||||
|
||||

|
||||

|
||||
|
||||
1. A new request is inserted into the `Request Selector` dropdown list on the right-hand side of the Performance Bar. Select the new request to view the metrics of the API request:
|
||||
|
||||

|
||||

|
||||
|
||||
<!-- vale gitlab_base.Substitutions = NO -->
|
||||
1. Select the `pg` link in the Progress Bar to view the database queries executed by the API request:
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ administrators can allow top-level group Owners to create service accounts.
|
|||
{{< details >}}
|
||||
|
||||
- Tier: Ultimate
|
||||
- Offering: GitLab Self-Managed
|
||||
- Offering: GitLab Self-Managed, GitLab Dedicated
|
||||
|
||||
{{< /details >}}
|
||||
|
||||
|
|
@ -330,6 +330,7 @@ administrators can allow top-level group Owners to create service accounts.
|
|||
|
||||
The availability of the extended maximum allowable lifetime limit is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
The feature flag is not available on GitLab Dedicated.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
@ -359,6 +360,7 @@ After a lifetime for SSH keys is set, GitLab:
|
|||
- Requires users to set an expiration date that is no later than the allowed lifetime on new SSH keys. The maximum allowed lifetime is:
|
||||
- 365 days by default.
|
||||
- 400 days, if you enable the `buffered_token_expiration_limit` feature flag.
|
||||
This extended limit is not available on GitLab Dedicated.
|
||||
- Applies the lifetime restriction to existing SSH keys. Keys with no expiry or a lifetime
|
||||
greater than the maximum immediately become invalid.
|
||||
|
||||
|
|
@ -373,7 +375,7 @@ When a user's SSH key becomes invalid they can delete and re-add the same key ag
|
|||
{{< details >}}
|
||||
|
||||
- Tier: Ultimate
|
||||
- Offering: GitLab Self-Managed
|
||||
- Offering: GitLab Self-Managed, GitLab Dedicated
|
||||
|
||||
{{< /details >}}
|
||||
|
||||
|
|
@ -387,6 +389,7 @@ When a user's SSH key becomes invalid they can delete and re-add the same key ag
|
|||
|
||||
The availability of the extended maximum allowable lifetime limit is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
The feature flag is not available on GitLab Dedicated.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
@ -397,11 +400,13 @@ This lifetime is not a requirement, and can be set to any value greater than 0 a
|
|||
|
||||
- 365 days by default.
|
||||
- 400 days, if you enable the `buffered_token_expiration_limit` feature flag.
|
||||
This extended limit is not available on GitLab Dedicated.
|
||||
|
||||
If this setting is left blank, the default allowable lifetime of access tokens is:
|
||||
|
||||
- 365 days by default.
|
||||
- 400 days, if you enable the `buffered_token_expiration_limit` feature flag.
|
||||
This extended limit is not available on GitLab Dedicated.
|
||||
|
||||
Access tokens are the only tokens needed for programmatic access to GitLab.
|
||||
However, organizations with security requirements may want to enforce more protection by
|
||||
|
|
|
|||
|
|
@ -315,3 +315,12 @@ You can configure this in either of the following ways:
|
|||
```
|
||||
|
||||
This configuration ensures the AI Gateway can properly cache HuggingFace models while respecting OpenShift's security constraints. The exact directory you choose may depend on your specific OpenShift configuration and security policies.
|
||||
|
||||
### Self-signed certificate error
|
||||
|
||||
A `[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain` error is logged by the AI gateway
|
||||
when the gateway tries to connect to a GitLab instance using either a certificate signed by a custom certificate authority (CA), or a self-signed certificate:
|
||||
|
||||
- The use of custom CA certificates in the Helm chart configuration when deploying the AI gateway is not supported. For more information, see [issue 3](https://gitlab.com/gitlab-org/charts/ai-gateway-helm-chart/-/issues/3). Use the [workaround](https://gitlab.com/gitlab-org/charts/ai-gateway-helm-chart/-/issues/3#workaround) detailed in this issue.
|
||||
|
||||
- The use of a self-signed certificate by the GitLab instance is not supported. For more information, see [issue 799](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/issues/799).
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ To create a Jira Cloud API token:
|
|||
|
||||
To copy the API token, select **Copy**.
|
||||
|
||||
## Migrate from Jira Server to Jira Cloud
|
||||
## Migrate from one Jira site to another
|
||||
|
||||
{{< history >}}
|
||||
|
||||
|
|
@ -243,17 +243,10 @@ To copy the API token, select **Copy**.
|
|||
|
||||
{{< /history >}}
|
||||
|
||||
To migrate from Jira Server to Jira Cloud in GitLab and maintain your Jira issues integration:
|
||||
To migrate from one Jira site to another in GitLab and maintain your Jira issues integration:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Jira issues**.
|
||||
1. In **Web URL**, enter the new Jira site URL (for example, `https://myjirasite.atlassian.net`).
|
||||
1. In **Email or username**, enter the email registered on your Jira profile.
|
||||
1. [Create a Jira Cloud API token](#create-a-jira-cloud-api-token), and copy the token value.
|
||||
1. In **API token or password**, paste the API token value.
|
||||
1. Optional. Select **Test settings**.
|
||||
1. Select **Save changes**.
|
||||
1. Follow the steps in [configure the integration](#configure-the-integration).
|
||||
1. Enter the new Jira site URL (for example, `https://myjirasite.atlassian.net`).
|
||||
|
||||
To update existing Jira issue references in GitLab to use the new Jira site URL, you must
|
||||
[invalidate the Markdown cache](../../administration/invalidate_markdown_cache.md#invalidate-the-cache).
|
||||
|
|
|
|||
|
|
@ -710,7 +710,7 @@ group(fullPath: "your-group-path") {
|
|||
Similarly, to request metrics for a project, run:
|
||||
|
||||
```graphl
|
||||
project(fullPath: "your-group-path") {
|
||||
project(fullPath: "your-project-path") {
|
||||
valueStreams {
|
||||
nodes {
|
||||
id
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ Prerequisites:
|
|||
The `CODEOWNERS` file defines who is responsible for code in a GitLab project.
|
||||
Its purpose is to:
|
||||
|
||||
- Automatically assign reviewers to merge requests based on the files changed.
|
||||
- Define Code Owners for specific files and directories.
|
||||
- Enforce approval requirements for protected branches.
|
||||
- Communicate code ownership in a project.
|
||||
|
||||
|
|
|
|||
|
|
@ -291,7 +291,9 @@ module API
|
|||
put ':id/pipelines/:pipeline_id/metadata', urgency: :low, feature_category: :continuous_integration do
|
||||
authorize! :update_pipeline, pipeline
|
||||
|
||||
response = ::Ci::Pipelines::UpdateMetadataService.new(pipeline, params.slice(:name)).execute
|
||||
response = ::Ci::Pipelines::UpdateMetadataService
|
||||
.new(pipeline, current_user: current_user, params: params.slice(:name))
|
||||
.execute
|
||||
|
||||
if response.success?
|
||||
present response.payload, with: Entities::Ci::PipelineWithMetadata
|
||||
|
|
|
|||
|
|
@ -22,7 +22,11 @@ module Gitlab
|
|||
attr_reader :root, :context, :source_ref_path, :source, :logger, :inject_edge_stages, :pipeline_policy_context
|
||||
|
||||
# rubocop: disable Metrics/ParameterLists
|
||||
def initialize(config, project: nil, pipeline: nil, sha: nil, ref: nil, user: nil, parent_pipeline: nil, source: nil, pipeline_config: nil, logger: nil, inject_edge_stages: true, pipeline_policy_context: nil)
|
||||
def initialize(
|
||||
config,
|
||||
project: nil, pipeline: nil, sha: nil, ref: nil, user: nil, parent_pipeline: nil, source: nil,
|
||||
pipeline_config: nil, logger: nil, inject_edge_stages: true, pipeline_policy_context: nil, inputs: {}
|
||||
)
|
||||
@logger = logger || ::Gitlab::Ci::Pipeline::Logger.new(project: project)
|
||||
@source_ref_path = pipeline&.source_ref_path
|
||||
@project = project
|
||||
|
|
@ -41,7 +45,7 @@ module Gitlab
|
|||
|
||||
Gitlab::Ci::Config::FeatureFlags.with_actor(project) do
|
||||
@config = self.logger.instrument(:config_expand, once: true) do
|
||||
expand_config(config)
|
||||
expand_config(config, inputs)
|
||||
end
|
||||
|
||||
@root = self.logger.instrument(:config_root, once: true) do
|
||||
|
|
@ -148,8 +152,8 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def expand_config(config)
|
||||
build_config(config)
|
||||
def expand_config(config, inputs)
|
||||
build_config(config, inputs)
|
||||
|
||||
rescue Gitlab::Config::Loader::Yaml::DataTooLargeError => e
|
||||
track_and_raise_for_dev_exception(e)
|
||||
|
|
@ -158,11 +162,18 @@ module Gitlab
|
|||
rescue Gitlab::Ci::Config::External::Context::TimeoutError => e
|
||||
track_and_raise_for_dev_exception(e)
|
||||
raise Config::ConfigError, TIMEOUT_MESSAGE
|
||||
|
||||
rescue Gitlab::Ci::Config::Yaml::LoadError => e
|
||||
raise Config::ConfigError, e.message
|
||||
end
|
||||
|
||||
def build_config(config)
|
||||
def build_config(config, inputs)
|
||||
initial_config = logger.instrument(:config_yaml_load, once: true) do
|
||||
Config::Yaml.load!(config)
|
||||
if inputs.present?
|
||||
Config::Yaml.load_with_inputs!(config, inputs, @context.variables)
|
||||
else
|
||||
Config::Yaml.load!(config)
|
||||
end
|
||||
end
|
||||
|
||||
initial_config = logger.instrument(:config_external_process, once: true) do
|
||||
|
|
|
|||
|
|
@ -15,6 +15,15 @@ module Gitlab
|
|||
result.content
|
||||
end
|
||||
end
|
||||
|
||||
def load_with_inputs!(content, inputs, variables)
|
||||
Loader.new(content, inputs: inputs, variables: variables).load.then do |result|
|
||||
raise result.error_class, result.error if !result.valid? && result.error_class.present?
|
||||
raise LoadError, result.error unless result.valid?
|
||||
|
||||
result.content
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module Gitlab
|
|||
:chat_data, :allow_mirror_update, :bridge, :content, :dry_run, :linting, :logger, :pipeline_policy_context,
|
||||
# These attributes are set by Chains during processing:
|
||||
:config_content, :yaml_processor_result, :workflow_rules_result, :pipeline_seed,
|
||||
:pipeline_config, :partition_id,
|
||||
:pipeline_config, :partition_id, :inputs,
|
||||
keyword_init: true
|
||||
) do
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ module Gitlab
|
|||
pipeline_source: @command.source, pipeline_source_bridge: @command.bridge,
|
||||
triggered_for_branch: @pipeline.branch?,
|
||||
ref: @pipeline.ref,
|
||||
pipeline_policy_context: @command.pipeline_policy_context
|
||||
pipeline_policy_context: @command.pipeline_policy_context,
|
||||
inputs: @command.inputs
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ module Gitlab
|
|||
user: current_user,
|
||||
parent_pipeline: parent_pipeline,
|
||||
pipeline_config: @command.pipeline_config,
|
||||
logger: logger
|
||||
logger: logger,
|
||||
inputs: @command.pipeline_config.inputs_for_pipeline_creation
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ module Gitlab
|
|||
|
||||
FALLBACK_POLICY_SOURCE = ProjectConfig::SecurityPolicyDefault
|
||||
|
||||
def initialize(
|
||||
def initialize( # rubocop:disable Metrics/ParameterLists -- we need all these parameters
|
||||
project:, sha:, custom_content: nil, pipeline_source: nil, pipeline_source_bridge: nil,
|
||||
triggered_for_branch: nil, ref: nil, pipeline_policy_context: nil)
|
||||
triggered_for_branch: nil, ref: nil, pipeline_policy_context: nil, inputs: nil)
|
||||
|
||||
unless pipeline_policy_context&.applying_config_override?
|
||||
@config = find_source(project: project,
|
||||
|
|
@ -35,7 +35,8 @@ module Gitlab
|
|||
pipeline_source: pipeline_source,
|
||||
pipeline_source_bridge: pipeline_source_bridge,
|
||||
triggered_for_branch: triggered_for_branch,
|
||||
ref: ref
|
||||
ref: ref,
|
||||
inputs: inputs
|
||||
)
|
||||
|
||||
return if @config
|
||||
|
|
@ -52,7 +53,7 @@ module Gitlab
|
|||
@config = fallback_config if fallback_config.exists?
|
||||
end
|
||||
|
||||
delegate :content, :source, :url, to: :@config, allow_nil: true
|
||||
delegate :content, :source, :url, :inputs_for_pipeline_creation, to: :@config, allow_nil: true
|
||||
delegate :internal_include_prepended?, to: :@config
|
||||
|
||||
def exists?
|
||||
|
|
@ -62,7 +63,8 @@ module Gitlab
|
|||
private
|
||||
|
||||
def find_source(
|
||||
project:, sha:, custom_content:, pipeline_source:, pipeline_source_bridge:, triggered_for_branch:, ref:)
|
||||
project:, sha:, custom_content:, pipeline_source:, pipeline_source_bridge:, triggered_for_branch:, ref:,
|
||||
inputs:)
|
||||
STANDARD_SOURCES.each do |source|
|
||||
source_config = source.new(project: project,
|
||||
sha: sha,
|
||||
|
|
@ -70,7 +72,8 @@ module Gitlab
|
|||
pipeline_source: pipeline_source,
|
||||
pipeline_source_bridge: pipeline_source_bridge,
|
||||
triggered_for_branch: triggered_for_branch,
|
||||
ref: ref
|
||||
ref: ref,
|
||||
inputs: inputs
|
||||
)
|
||||
|
||||
return source_config if source_config.exists?
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ module Gitlab
|
|||
next unless project&.auto_devops_enabled?
|
||||
|
||||
template = Gitlab::Template::GitlabCiYmlTemplate.find(template_name)
|
||||
YAML.dump('include' => [{ 'template' => template.full_name }])
|
||||
ci_yaml_include({ 'template' => template.full_name })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -76,10 +76,6 @@ module Gitlab
|
|||
def remote_config_path?
|
||||
URI::DEFAULT_PARSER.make_regexp(%w[http https]).match?(ci_config_path)
|
||||
end
|
||||
|
||||
def ci_yaml_include(config)
|
||||
YAML.dump('include' => [config])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module Gitlab
|
|||
|
||||
def initialize(
|
||||
project:, sha:, custom_content: nil, pipeline_source: nil, pipeline_source_bridge: nil,
|
||||
triggered_for_branch: false, ref: nil)
|
||||
triggered_for_branch: false, ref: nil, inputs: {})
|
||||
@project = project
|
||||
@sha = sha
|
||||
@custom_content = custom_content
|
||||
|
|
@ -16,6 +16,7 @@ module Gitlab
|
|||
@pipeline_source_bridge = pipeline_source_bridge
|
||||
@triggered_for_branch = triggered_for_branch
|
||||
@ref = ref
|
||||
@inputs = inputs
|
||||
end
|
||||
|
||||
def exists?
|
||||
|
|
@ -41,14 +42,27 @@ module Gitlab
|
|||
nil
|
||||
end
|
||||
|
||||
def inputs_for_pipeline_creation
|
||||
# For internal_include_prepended?, we already pass the inputs to the `include` statement.
|
||||
internal_include_prepended? ? {} : inputs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :sha, :custom_content, :pipeline_source, :pipeline_source_bridge, :triggered_for_branch,
|
||||
:ref
|
||||
:ref, :inputs
|
||||
|
||||
def ci_config_path
|
||||
@ci_config_path ||= project.ci_config_path_or_default
|
||||
end
|
||||
|
||||
def ci_yaml_include(config)
|
||||
YAML.dump('include' => [config.merge(include_inputs)])
|
||||
end
|
||||
|
||||
def include_inputs
|
||||
{ 'inputs' => inputs }.compact_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import BlankProjectForm from '~/projects/new_v2/components/blank_project_form.vue';
|
||||
|
||||
describe('Blank Project Form', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
option: {
|
||||
title: 'Import project',
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMountExtended(BlankProjectForm, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
const findMultiStepFormTemplate = () => wrapper.findComponent(MultiStepFormTemplate);
|
||||
const findCreateButton = () => wrapper.findByTestId('create-project-button');
|
||||
const findBackButton = () => wrapper.findByTestId('create-project-back-button');
|
||||
|
||||
it('passes the correct props to MultiStepFormTemplate', () => {
|
||||
expect(findMultiStepFormTemplate().props()).toMatchObject({
|
||||
title: defaultProps.option.title,
|
||||
currentStep: 2,
|
||||
stepsTotal: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the option to Create Project as disabled', () => {
|
||||
expect(findCreateButton().text()).toBe('Create project');
|
||||
expect(findCreateButton().props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it(`emits the "back" event when the back button is clicked`, () => {
|
||||
findBackButton().vm.$emit('click');
|
||||
expect(wrapper.emitted('back')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import CICDProjectForm from '~/projects/new_v2/components/ci_cd_project_form.vue';
|
||||
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
|
||||
|
||||
describe('CI/CD Project Form', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
option: {
|
||||
title: 'Import project',
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMountExtended(CICDProjectForm, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
const findMultiStepFormTemplate = () => wrapper.findComponent(MultiStepFormTemplate);
|
||||
const findCreateButton = () => wrapper.findByTestId('create-cicd-project-button');
|
||||
const findBackButton = () => wrapper.findByTestId('create-cicd-project-back-button');
|
||||
|
||||
it('passes the correct props to MultiStepFormTemplate', () => {
|
||||
expect(findMultiStepFormTemplate().props()).toMatchObject({
|
||||
title: defaultProps.option.title,
|
||||
currentStep: 2,
|
||||
stepsTotal: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the option to Create Project', () => {
|
||||
expect(findCreateButton().text()).toBe('Create project');
|
||||
});
|
||||
|
||||
it(`emits the "back" event when the back button is clicked`, () => {
|
||||
findBackButton().vm.$emit('click');
|
||||
expect(wrapper.emitted('back')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import ImportProjectForm from '~/projects/new_v2/components/import_project_form.vue';
|
||||
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
|
||||
|
||||
describe('Import Project Form', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
option: {
|
||||
title: 'Import project',
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMountExtended(ImportProjectForm, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
const findMultiStepFormTemplate = () => wrapper.findComponent(MultiStepFormTemplate);
|
||||
const findNextButton = () => wrapper.findByTestId('import-project-next-button');
|
||||
const findBackButton = () => wrapper.findByTestId('import-project-back-button');
|
||||
|
||||
it('passes the correct props to MultiStepFormTemplate', () => {
|
||||
expect(findMultiStepFormTemplate().props()).toMatchObject({
|
||||
title: defaultProps.option.title,
|
||||
currentStep: 2,
|
||||
stepsTotal: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the option to move to Next Step', () => {
|
||||
expect(findNextButton().text()).toBe('Next step');
|
||||
});
|
||||
|
||||
it(`emits the "back" event when the back button is clicked`, () => {
|
||||
findBackButton().vm.$emit('click');
|
||||
expect(wrapper.emitted('back')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import TemplateProjectForm from '~/projects/new_v2/components/template_project_form.vue';
|
||||
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
|
||||
|
||||
describe('Template Project Form', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
option: {
|
||||
title: 'Template project',
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMountExtended(TemplateProjectForm, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findMultiStepFormTemplate = () => wrapper.findComponent(MultiStepFormTemplate);
|
||||
const findNextButton = () => wrapper.findByTestId('template-project-next-button');
|
||||
const findBackButton = () => wrapper.findByTestId('template-project-back-button');
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('passes the correct props to MultiStepFormTemplate', () => {
|
||||
expect(findMultiStepFormTemplate().props()).toMatchObject({
|
||||
title: defaultProps.option.title,
|
||||
currentStep: 2,
|
||||
stepsTotal: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the option to move to Next Step', () => {
|
||||
expect(findNextButton().text()).toBe('Next step');
|
||||
});
|
||||
|
||||
it(`emits the "back" event when the back button is clicked`, () => {
|
||||
findBackButton().vm.$emit('click');
|
||||
expect(wrapper.emitted('back')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -5,18 +5,13 @@ import SingleChoiceSelector from '~/vue_shared/components/single_choice_selector
|
|||
describe('SingleChoice', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultPropsData = {
|
||||
checked: 'option',
|
||||
};
|
||||
|
||||
function createComponent({ propsData = {} } = {}) {
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(SingleChoiceSelector, {
|
||||
propsData: {
|
||||
...defaultPropsData,
|
||||
...propsData,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
|
||||
|
||||
|
|
@ -25,4 +20,10 @@ describe('SingleChoice', () => {
|
|||
|
||||
expect(findRadioGroup().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('passes the checked prop to GlFormRadioGroup', () => {
|
||||
const checkedValue = 'test-option';
|
||||
createComponent({ checked: checkedValue });
|
||||
expect(findRadioGroup().attributes('checked')).toBe(checkedValue);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Config::Yaml, feature_category: :pipeline_composition do
|
||||
let(:yaml) do
|
||||
<<~YAML
|
||||
image: 'image:1.0'
|
||||
texts:
|
||||
nested_key: 'value1'
|
||||
more_text:
|
||||
more_nested_key: 'value2'
|
||||
YAML
|
||||
end
|
||||
|
||||
describe '.load!' do
|
||||
let(:yaml) do
|
||||
<<~YAML
|
||||
image: 'image:1.0'
|
||||
texts:
|
||||
nested_key: 'value1'
|
||||
more_text:
|
||||
more_nested_key: 'value2'
|
||||
YAML
|
||||
end
|
||||
|
||||
subject(:config) { described_class.load!(yaml) }
|
||||
|
||||
it 'loads a YAML file' do
|
||||
|
|
@ -37,4 +37,67 @@ RSpec.describe Gitlab::Ci::Config::Yaml, feature_category: :pipeline_composition
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.load_with_inputs!' do
|
||||
let(:yaml) do
|
||||
<<~YAML
|
||||
spec:
|
||||
inputs:
|
||||
compiler:
|
||||
default: gcc
|
||||
optimization_level:
|
||||
type: number
|
||||
---
|
||||
|
||||
test:
|
||||
script:
|
||||
- echo "with compiler $[[ inputs.compiler | expand_vars ]] and level $[[ inputs.optimization_level ]]"
|
||||
YAML
|
||||
end
|
||||
|
||||
let(:inputs) do
|
||||
{ compiler: 'g++', optimization_level: 1 }
|
||||
end
|
||||
|
||||
let(:variables) do
|
||||
[{ key: 'COMPILER', value: 'c++' }]
|
||||
end
|
||||
|
||||
subject(:config) { described_class.load_with_inputs!(yaml, inputs, variables) }
|
||||
|
||||
it 'loads a YAML file' do
|
||||
expect(config).to eq(
|
||||
test: {
|
||||
script: ['echo "with compiler g++ and level 1"']
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
context 'when using a variable in the input value' do
|
||||
let(:inputs) do
|
||||
{ compiler: '$COMPILER', optimization_level: 2 }
|
||||
end
|
||||
|
||||
it 'loads the YAML config file, expands the variable and interpolates the input(s)' do
|
||||
expect(config).to eq(
|
||||
test: {
|
||||
script: ['echo "with compiler c++ and level 2"']
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given invalid input values' do
|
||||
let(:inputs) do
|
||||
{ compiler: 5, optimization_level: 'a string' }
|
||||
end
|
||||
|
||||
it 'raises error' do
|
||||
expect { config }.to raise_error(
|
||||
::Gitlab::Ci::Config::Yaml::LoadError,
|
||||
'`compiler` input: provided value is not a string, `optimization_level` input: provided value is not a number'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -970,6 +970,66 @@ RSpec.describe Gitlab::Ci::Config, feature_category: :pipeline_composition do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when config accepts inputs' do
|
||||
let_it_be(:complex_inputs_example_path) { 'spec/lib/gitlab/ci/config/yaml/fixtures/complex-included-ci.yml' }
|
||||
let_it_be(:complex_inputs_example_yaml) { File.read(Rails.root.join(complex_inputs_example_path)) }
|
||||
|
||||
let(:inputs) { nil }
|
||||
|
||||
subject(:config) do
|
||||
described_class.new(complex_inputs_example_yaml, inputs: inputs)
|
||||
end
|
||||
|
||||
context 'when not passing required inputs' do
|
||||
it 'raises Config::ConfigError' do
|
||||
expect { config }.to raise_error(
|
||||
described_class::ConfigError,
|
||||
/required value has not been provided/
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passing required inputs' do
|
||||
let(:inputs) do
|
||||
{
|
||||
deploy_strategy: 'blue-green',
|
||||
job_stage: 'deploy',
|
||||
test_script: ['echo "test"'],
|
||||
test_rules: [
|
||||
{ if: '$CI_PIPELINE_SOURCE == "push"' }
|
||||
],
|
||||
test_framework: '$TEST_FRAMEWORK'
|
||||
}
|
||||
end
|
||||
|
||||
let(:result) do
|
||||
{
|
||||
"my-job-build": a_hash_including(stage: 'deploy'),
|
||||
"my-job-test": a_hash_including(
|
||||
stage: 'deploy',
|
||||
script: [
|
||||
'echo "Testing with $TEST_FRAMEWORK"',
|
||||
'if [ false == true ]; then echo "Coverage is enabled"; fi'
|
||||
]
|
||||
),
|
||||
"my-job-test-2": a_hash_including(
|
||||
stage: 'deploy',
|
||||
script: ['echo "test"']
|
||||
),
|
||||
"my-job-deploy": a_hash_including(
|
||||
stage: 'deploy',
|
||||
script: ['echo "Deploying to staging using blue-green strategy"']
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns valid result' do
|
||||
expect(config.valid?).to be_truthy
|
||||
expect(config.to_hash).to match(a_hash_including(result))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#workflow_rules' do
|
||||
subject(:workflow_rules) { config.workflow_rules }
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: :
|
|||
let(:pipeline) { build(:ci_pipeline, project: project) }
|
||||
let(:content) { nil }
|
||||
let(:source) { :push }
|
||||
let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project, content: content, source: source) }
|
||||
let(:inputs) { {} }
|
||||
let(:command) do
|
||||
Gitlab::Ci::Pipeline::Chain::Command.new(project: project, content: content, source: source, inputs: inputs)
|
||||
end
|
||||
|
||||
subject { described_class.new(pipeline, command) }
|
||||
|
||||
|
|
@ -145,6 +148,28 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: :
|
|||
expect(command.config_content).to eq(config_content_result)
|
||||
expect(command.pipeline_config.internal_include_prepended?).to eq(true)
|
||||
end
|
||||
|
||||
context 'when passing inputs' do
|
||||
let(:inputs) { { 'foo' => 'bar' } }
|
||||
let(:config_content_result) do
|
||||
<<~CICONFIG
|
||||
---
|
||||
include:
|
||||
- local: ".gitlab-ci.yml"
|
||||
inputs:
|
||||
foo: bar
|
||||
CICONFIG
|
||||
end
|
||||
|
||||
it 'builds the config with the inputs' do
|
||||
subject.perform!
|
||||
|
||||
expect(pipeline.config_source).to eq 'repository_source'
|
||||
expect(pipeline.pipeline_config.content).to eq(config_content_result)
|
||||
expect(command.config_content).to eq(config_content_result)
|
||||
expect(command.pipeline_config.internal_include_prepended?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when config is the Auto-Devops template' do
|
||||
|
|
@ -190,6 +215,19 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: :
|
|||
expect(command.config_content).to eq(content)
|
||||
expect(command.pipeline_config.internal_include_prepended?).to eq(false)
|
||||
end
|
||||
|
||||
context 'when passing inputs' do
|
||||
let(:inputs) { { 'foo' => 'bar' } }
|
||||
|
||||
it 'uses the parameter content with inputs' do
|
||||
subject.perform!
|
||||
|
||||
expect(pipeline.config_source).to eq 'parameter_source'
|
||||
expect(pipeline.pipeline_config.content).to eq(content)
|
||||
expect(command.config_content).to eq(content)
|
||||
expect(command.pipeline_config.internal_include_prepended?).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when config is not defined anywhere' do
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ RSpec.describe Gitlab::Ci::ProjectConfig::ProjectSetting, feature_category: :pip
|
|||
let(:sha) { project.repository.head_commit.sha }
|
||||
let(:files) { { 'README.md' => 'hello' } }
|
||||
let(:config_path) { nil }
|
||||
let(:inputs) { nil }
|
||||
|
||||
before do
|
||||
project.ci_config_path = config_path
|
||||
end
|
||||
|
||||
subject(:config) do
|
||||
described_class.new(project: project, sha: sha)
|
||||
described_class.new(project: project, sha: sha, inputs: inputs)
|
||||
end
|
||||
|
||||
describe '#content' do
|
||||
|
|
@ -31,6 +32,21 @@ RSpec.describe Gitlab::Ci::ProjectConfig::ProjectSetting, feature_category: :pip
|
|||
let(:files) { { '.gitlab-ci.yml' => 'content' } }
|
||||
|
||||
it { is_expected.to eq(config_content_result) }
|
||||
|
||||
context 'when passing inputs' do
|
||||
let(:inputs) { { 'foo' => 'bar' } }
|
||||
let(:config_content_result) do
|
||||
<<~CICONFIG
|
||||
---
|
||||
include:
|
||||
- local: ".gitlab-ci.yml"
|
||||
inputs:
|
||||
foo: bar
|
||||
CICONFIG
|
||||
end
|
||||
|
||||
it { is_expected.to eq(config_content_result) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with external config' do
|
||||
|
|
@ -46,6 +62,22 @@ RSpec.describe Gitlab::Ci::ProjectConfig::ProjectSetting, feature_category: :pip
|
|||
end
|
||||
|
||||
it { is_expected.to eq(config_content_result) }
|
||||
|
||||
context 'when passing inputs' do
|
||||
let(:inputs) { { 'foo' => 'bar' } }
|
||||
let(:config_content_result) do
|
||||
<<~CICONFIG
|
||||
---
|
||||
include:
|
||||
- project: another-group/another-project
|
||||
file: path/to/.gitlab-ci.yml
|
||||
inputs:
|
||||
foo: bar
|
||||
CICONFIG
|
||||
end
|
||||
|
||||
it { is_expected.to eq(config_content_result) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with remote config' do
|
||||
|
|
@ -59,6 +91,21 @@ RSpec.describe Gitlab::Ci::ProjectConfig::ProjectSetting, feature_category: :pip
|
|||
end
|
||||
|
||||
it { is_expected.to eq(config_content_result) }
|
||||
|
||||
context 'when passing inputs' do
|
||||
let(:inputs) { { 'foo' => 'bar' } }
|
||||
let(:config_content_result) do
|
||||
<<~CICONFIG
|
||||
---
|
||||
include:
|
||||
- remote: #{config_path}
|
||||
inputs:
|
||||
foo: bar
|
||||
CICONFIG
|
||||
end
|
||||
|
||||
it { is_expected.to eq(config_content_result) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when file is not in repository' do
|
||||
|
|
@ -103,4 +150,12 @@ RSpec.describe Gitlab::Ci::ProjectConfig::ProjectSetting, feature_category: :pip
|
|||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
describe '#inputs_for_pipeline_creation' do
|
||||
let(:inputs) { { 'foo' => 'bar' } }
|
||||
|
||||
subject(:inputs_for_pipeline_creation) { config.inputs_for_pipeline_creation }
|
||||
|
||||
it { is_expected.to eq({}) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::ProjectConfig::Source, feature_category: :continuous_integration do
|
||||
RSpec.describe Gitlab::Ci::ProjectConfig::Source, feature_category: :pipeline_composition do
|
||||
let_it_be(:custom_config_class) { Class.new(described_class) }
|
||||
let_it_be(:project) { build_stubbed(:project) }
|
||||
let_it_be(:sha) { '123456' }
|
||||
let_it_be(:inputs) { {} }
|
||||
|
||||
subject(:custom_config) { custom_config_class.new(project: project, sha: sha) }
|
||||
subject(:custom_config) { custom_config_class.new(project: project, sha: sha, inputs: inputs) }
|
||||
|
||||
describe '#content' do
|
||||
subject(:content) { custom_config.content }
|
||||
|
|
@ -26,4 +27,12 @@ RSpec.describe Gitlab::Ci::ProjectConfig::Source, feature_category: :continuous_
|
|||
|
||||
it { expect(internal_include_prepended).to eq(false) }
|
||||
end
|
||||
|
||||
describe '#inputs_for_pipeline_creation' do
|
||||
let(:inputs) { { 'foo' => 'bar' } }
|
||||
|
||||
subject(:inputs_for_pipeline_creation) { custom_config.inputs_for_pipeline_creation }
|
||||
|
||||
it { expect(inputs_for_pipeline_creation).to eq(inputs) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig, feature_category: :pipeline_compositio
|
|||
let(:bridge) { nil }
|
||||
let(:triggered_for_branch) { true }
|
||||
let(:ref) { 'master' }
|
||||
let(:inputs) { nil }
|
||||
|
||||
subject(:config) do
|
||||
described_class.new(
|
||||
|
|
@ -19,7 +20,8 @@ RSpec.describe Gitlab::Ci::ProjectConfig, feature_category: :pipeline_compositio
|
|||
pipeline_source: source,
|
||||
pipeline_source_bridge: bridge,
|
||||
triggered_for_branch: triggered_for_branch,
|
||||
ref: ref
|
||||
ref: ref,
|
||||
inputs: inputs
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -141,6 +143,25 @@ RSpec.describe Gitlab::Ci::ProjectConfig, feature_category: :pipeline_compositio
|
|||
expect(config.content).to eq(config_content_result)
|
||||
expect(config.url).to eq("localhost/#{project.full_path}//.gitlab-ci.yml")
|
||||
end
|
||||
|
||||
context 'when passing inputs' do
|
||||
let(:inputs) { { 'foo' => 'bar' } }
|
||||
let(:config_content_result) do
|
||||
<<~CICONFIG
|
||||
---
|
||||
include:
|
||||
- local: ".gitlab-ci.yml"
|
||||
inputs:
|
||||
foo: bar
|
||||
CICONFIG
|
||||
end
|
||||
|
||||
it 'returns root config with inputs' do
|
||||
expect(config.source).to eq(:repository_source)
|
||||
expect(config.content).to eq(config_content_result)
|
||||
expect(config.url).to eq("localhost/#{project.full_path}//.gitlab-ci.yml")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when config is the Auto-Devops template' do
|
||||
|
|
|
|||
|
|
@ -4991,14 +4991,18 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
|
|||
end
|
||||
|
||||
describe '#archived?' do
|
||||
before do
|
||||
pipeline.update!(created_at: 1.day.ago)
|
||||
end
|
||||
|
||||
context 'when build is degenerated' do
|
||||
subject { create(:ci_build, :degenerated, pipeline: pipeline) }
|
||||
subject { build_stubbed(:ci_build, :degenerated, pipeline: pipeline) }
|
||||
|
||||
it { is_expected.to be_archived }
|
||||
end
|
||||
|
||||
context 'for old build' do
|
||||
subject { create(:ci_build, created_at: 1.day.ago, pipeline: pipeline) }
|
||||
context 'for old pipelines' do
|
||||
subject { build_stubbed(:ci_build, pipeline: pipeline) }
|
||||
|
||||
context 'when archive_builds_in is set' do
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -1426,7 +1426,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
|
|||
describe '#retryable?' do
|
||||
subject { pipeline.retryable? }
|
||||
|
||||
let_it_be(:pipeline) { create(:ci_empty_pipeline, :created, project: project) }
|
||||
let_it_be(:pipeline) { create(:ci_empty_pipeline, :created, project: project, created_at: 1.day.ago) }
|
||||
|
||||
context 'no failed builds' do
|
||||
before do
|
||||
|
|
@ -1445,6 +1445,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
|
|||
it 'is retryable' do
|
||||
is_expected.to be_truthy
|
||||
end
|
||||
|
||||
context 'with archived pipelines' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -6114,4 +6122,24 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#archived?' do
|
||||
subject { build_stubbed(:ci_pipeline, created_at: 1.day.ago, project: project) }
|
||||
|
||||
context 'when archive_builds_in is set' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it { is_expected.to be_archived }
|
||||
end
|
||||
|
||||
context 'when archive_builds_in is not set' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: nil)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_archived }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe CommitStatus, feature_category: :continuous_integration do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
let_it_be(:pipeline) do
|
||||
let_it_be_with_reload(:pipeline) do
|
||||
create(:ci_pipeline, project: project, sha: project.commit.id)
|
||||
end
|
||||
|
||||
|
|
@ -1046,4 +1046,28 @@ RSpec.describe CommitStatus, feature_category: :continuous_integration do
|
|||
|
||||
it_behaves_like 'having enum with nil value'
|
||||
end
|
||||
|
||||
describe '#archived?' do
|
||||
before do
|
||||
pipeline.update!(created_at: 1.day.ago)
|
||||
end
|
||||
|
||||
subject { build_stubbed(:commit_status, pipeline: pipeline) }
|
||||
|
||||
context 'when archive_builds_in is set' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it { is_expected.to be_archived }
|
||||
end
|
||||
|
||||
context 'when archive_builds_in is not set' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: nil)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_archived }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe Ci::PipelinePolicy, :models, :request_store, :use_clean_rails_redis_caching, feature_category: :continuous_integration do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be_with_reload(:project) { create(:project, :repository, developers: user) }
|
||||
let_it_be_with_reload(:pipeline) { create(:ci_empty_pipeline, project: project) }
|
||||
let_it_be_with_reload(:pipeline) { create(:ci_empty_pipeline, project: project, created_at: 1.day.ago) }
|
||||
|
||||
subject(:policy) do
|
||||
described_class.new(user, pipeline)
|
||||
|
|
@ -53,6 +53,26 @@ RSpec.describe Ci::PipelinePolicy, :models, :request_store, :use_clean_rails_red
|
|||
end
|
||||
end
|
||||
|
||||
describe 'archived rules' do
|
||||
context 'when archive_builds_in is set' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_allowed(:update_pipeline) }
|
||||
it { is_expected.not_to be_allowed(:cancel_pipeline) }
|
||||
end
|
||||
|
||||
context 'when archive_builds_in is not set' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: nil)
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed(:update_pipeline) }
|
||||
it { is_expected.to be_allowed(:cancel_pipeline) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when maintainer is allowed to push to pipeline branch' do
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
|
|
@ -62,6 +82,15 @@ RSpec.describe Ci::PipelinePolicy, :models, :request_store, :use_clean_rails_red
|
|||
|
||||
it { is_expected.to be_allowed(:update_pipeline) }
|
||||
it { is_expected.to be_allowed(:cancel_pipeline) }
|
||||
|
||||
context 'and the pipeline is archived' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_allowed(:update_pipeline) }
|
||||
it { is_expected.not_to be_allowed(:cancel_pipeline) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have access to internal CI' do
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ RSpec.describe 'Update of an existing issue', feature_category: :team_planning d
|
|||
end
|
||||
|
||||
context 'setting labels' do
|
||||
before do
|
||||
allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(102)
|
||||
end
|
||||
|
||||
let(:mutation) do
|
||||
graphql_mutation(:update_issue, input_params) do
|
||||
<<~QL
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur
|
|||
|
||||
context 'when the current user does not have permission to add assignees' do
|
||||
let(:current_user) { create(:user) }
|
||||
let(:db_query_limit) { 29 }
|
||||
let(:db_query_limit) { 31 }
|
||||
|
||||
it 'does not change the assignees' do
|
||||
project.add_guest(current_user)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ RSpec.describe API::ProjectImport, :aggregate_failures, feature_category: :impor
|
|||
it 'executes a limited number of queries', :use_clean_rails_redis_caching do
|
||||
control = ActiveRecord::QueryRecorder.new { perform_archive_upload }
|
||||
|
||||
expect(control.count).to be <= 126
|
||||
expect(control.count).to be <= 127
|
||||
end
|
||||
|
||||
it 'schedules an import using a namespace' do
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::BuildUnscheduleService, feature_category: :continuous_integration do
|
||||
RSpec.describe Ci::BuildUnscheduleService, :aggregate_failures, feature_category: :continuous_integration do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project, created_at: 1.day.ago) }
|
||||
|
||||
describe '#execute' do
|
||||
subject(:execute) { described_class.new(build, user).execute }
|
||||
|
|
@ -24,12 +24,25 @@ RSpec.describe Ci::BuildUnscheduleService, feature_category: :continuous_integra
|
|||
expect(response).to be_success
|
||||
expect(response.payload.reload).to be_manual
|
||||
end
|
||||
|
||||
context 'when the pipeline is archived' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it 'responds with forbidden' do
|
||||
response = execute
|
||||
|
||||
expect(response).to be_error
|
||||
expect(response.http_status).to eq(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when build is not scheduled' do
|
||||
let!(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
|
||||
it 'responds with unprocessable entity', :aggregate_failures do
|
||||
it 'responds with unprocessable entity' do
|
||||
response = execute
|
||||
|
||||
expect(response).to be_error
|
||||
|
|
@ -41,7 +54,7 @@ RSpec.describe Ci::BuildUnscheduleService, feature_category: :continuous_integra
|
|||
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
|
||||
it 'responds with forbidden' do
|
||||
response = execute
|
||||
|
||||
expect(response).to be_error
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ RSpec.describe Ci::CreateCommitStatusService, :clean_gitlab_redis_cache, feature
|
|||
let(:params) { { state: 'pending' } }
|
||||
let(:job) { response.payload[:job] }
|
||||
|
||||
%w[pending running success failed canceled skipped].each do |status|
|
||||
context "for #{status}" do
|
||||
let(:params) { { state: status } }
|
||||
context 'when pipeline for sha does not exists' do
|
||||
%w[pending running success failed canceled skipped].each do |status|
|
||||
context "for #{status}" do
|
||||
let(:params) { { state: status } }
|
||||
|
||||
context 'when pipeline for sha does not exists' do
|
||||
it 'creates commit status and sets pipeline iid' do
|
||||
expect(response).to be_success
|
||||
expect(job.sha).to eq(commit.id)
|
||||
|
|
@ -41,6 +41,37 @@ RSpec.describe Ci::CreateCommitStatusService, :clean_gitlab_redis_cache, feature
|
|||
end
|
||||
end
|
||||
|
||||
context 'when pipeline for sha already exists' do
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, created_at: 1.day.ago) }
|
||||
|
||||
%w[pending running success failed canceled skipped].each do |status|
|
||||
context "for #{status}" do
|
||||
let(:params) { { state: status } }
|
||||
|
||||
it 'creates commit status on the pipeline' do
|
||||
expect(response).to be_success
|
||||
expect(job.sha).to eq(commit.id)
|
||||
expect(job.status).to eq(status)
|
||||
expect(job.name).to eq('default')
|
||||
expect(job.ref).not_to be_empty
|
||||
expect(job.pipeline_id).to eq(pipeline.id)
|
||||
end
|
||||
|
||||
context 'when the pipeline is archived' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it 'returns an error' do
|
||||
expect(response).to be_error
|
||||
expect(response.http_status).to eq(:forbidden)
|
||||
expect(response.message).to eq('403 Forbidden')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status transitions from pending' do
|
||||
before do
|
||||
execute_service(state: 'pending')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_composition do
|
||||
include RepoHelpers
|
||||
|
||||
context 'for inputs' do
|
||||
let_it_be(:project) { create(:project, :small_repo) }
|
||||
let_it_be(:user) { project.first_owner }
|
||||
|
||||
let(:ref) { 'refs/heads/master' }
|
||||
let(:source) { :push }
|
||||
let(:content) { nil }
|
||||
let(:inputs) { {} }
|
||||
|
||||
let_it_be(:complex_inputs_example_path) { 'spec/lib/gitlab/ci/config/yaml/fixtures/complex-included-ci.yml' }
|
||||
let_it_be(:complex_inputs_example_yaml) { File.read(Rails.root.join(complex_inputs_example_path)) }
|
||||
|
||||
let(:project_ci_config_path) { nil } # default: .gitlab-ci.yml
|
||||
|
||||
let(:service) { described_class.new(project, user, { ref: ref }) }
|
||||
|
||||
subject(:execute) { service.execute(source, content: content, inputs: inputs) }
|
||||
|
||||
before do
|
||||
project.ci_config_path = project_ci_config_path
|
||||
end
|
||||
|
||||
shared_examples 'returning errors' do
|
||||
it 'returns errors' do
|
||||
response = execute
|
||||
pipeline = response.payload
|
||||
|
||||
expect(response).to be_error
|
||||
|
||||
errors = pipeline.error_messages.map(&:content)
|
||||
|
||||
expect(errors.size).to eq(1)
|
||||
expected_errors.each do |error|
|
||||
expect(errors.first).to include(error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'testing invalid and valid cases' do
|
||||
context 'when passing no inputs' do
|
||||
let(:expected_errors) do
|
||||
[
|
||||
'`deploy_strategy` input: required value has not been provided',
|
||||
'`job_stage` input: required value has not been provided',
|
||||
'`test_script` input: required value has not been provided'
|
||||
]
|
||||
end
|
||||
|
||||
it_behaves_like 'returning errors'
|
||||
end
|
||||
|
||||
context 'when passing invalid inputs' do
|
||||
let(:inputs) do
|
||||
{
|
||||
deploy_strategy: 'manual',
|
||||
job_stage: 'deploy',
|
||||
test_script: 'echo "test"'
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_errors) do
|
||||
[
|
||||
'`deploy_strategy` input: `manual` cannot be used because it is not in the list of allowed options',
|
||||
'`test_script` input: provided value is not an array'
|
||||
]
|
||||
end
|
||||
|
||||
it_behaves_like 'returning errors'
|
||||
end
|
||||
|
||||
context 'when passing valid inputs' do
|
||||
let(:inputs) do
|
||||
{
|
||||
deploy_strategy: 'blue-green',
|
||||
job_stage: 'deploy',
|
||||
test_script: ['echo "test"'],
|
||||
test_rules: [
|
||||
{ if: '$CI_PIPELINE_SOURCE == "push"' }
|
||||
],
|
||||
test_framework: '$TEST_FRAMEWORK'
|
||||
}
|
||||
end
|
||||
|
||||
before_all do
|
||||
create(:ci_variable, project: project, key: 'TEST_FRAMEWORK', value: 'rspec')
|
||||
end
|
||||
|
||||
it 'creates a pipeline with correct jobs' do
|
||||
response = execute
|
||||
pipeline = response.payload
|
||||
|
||||
expect(response).to be_success
|
||||
expect(pipeline).to be_created_successfully
|
||||
|
||||
expect(pipeline.builds.map(&:name)).to contain_exactly(
|
||||
'my-job-build 1/2', 'my-job-build 2/2', 'my-job-test', 'my-job-test-2', 'my-job-deploy'
|
||||
)
|
||||
expect(pipeline.builds.map(&:stage)).to contain_exactly('deploy', 'deploy', 'deploy', 'deploy', 'deploy')
|
||||
|
||||
my_job_test = pipeline.builds.find { |build| build.name == 'my-job-test' }
|
||||
expect(my_job_test.options[:script]).to eq([
|
||||
'echo "Testing with rspec"',
|
||||
'if [ false == true ]; then echo "Coverage is enabled"; fi'
|
||||
])
|
||||
|
||||
my_job_test_2 = pipeline.builds.find { |build| build.name == 'my-job-test-2' }
|
||||
expect(my_job_test_2.options[:script]).to eq(['echo "test"'])
|
||||
|
||||
my_job_deploy = pipeline.builds.find { |build| build.name == 'my-job-deploy' }
|
||||
expect(my_job_deploy.options[:script]).to eq(['echo "Deploying to staging using blue-green strategy"'])
|
||||
end
|
||||
|
||||
context 'when the FF ci_inputs_for_pipelines is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_inputs_for_pipelines: false)
|
||||
end
|
||||
|
||||
let(:expected_errors) do
|
||||
[
|
||||
'`deploy_strategy` input: required value has not been provided',
|
||||
'`job_stage` input: required value has not been provided',
|
||||
'`test_script` input: required value has not been provided'
|
||||
]
|
||||
end
|
||||
|
||||
it_behaves_like 'returning errors'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project CI config is in the current repository file' do
|
||||
let(:project_files) do
|
||||
{ '.gitlab-ci.yml' => complex_inputs_example_yaml }
|
||||
end
|
||||
|
||||
around do |example|
|
||||
create_and_delete_files(project, project_files) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'testing invalid and valid cases'
|
||||
end
|
||||
|
||||
context 'when the project CI config is in another project repository file' do
|
||||
let_it_be(:project_2) do
|
||||
create(:project, :custom_repo, files: { 'a_config_file.yml' => complex_inputs_example_yaml })
|
||||
end
|
||||
|
||||
let(:project_ci_config_path) { "a_config_file.yml@#{project_2.full_path}:#{project_2.default_branch}" }
|
||||
|
||||
before_all do
|
||||
project_2.add_developer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'testing invalid and valid cases'
|
||||
end
|
||||
|
||||
context 'when the project CI config is a remote file' do
|
||||
let(:project_ci_config_path) { 'https://gitlab.example.com/something/.gitlab-ci.yml' }
|
||||
|
||||
before do
|
||||
stub_request(:get, project_ci_config_path)
|
||||
.to_return(status: 200, body: complex_inputs_example_yaml)
|
||||
end
|
||||
|
||||
it_behaves_like 'testing invalid and valid cases'
|
||||
end
|
||||
|
||||
context 'when the CI config is passed as content' do
|
||||
let(:content) { complex_inputs_example_yaml }
|
||||
|
||||
it_behaves_like 'testing invalid and valid cases'
|
||||
end
|
||||
|
||||
context 'when the CI config does not have spec:inputs' do
|
||||
let(:content) do
|
||||
<<~YAML
|
||||
job:
|
||||
script: echo "hello"
|
||||
YAML
|
||||
end
|
||||
|
||||
context 'when passing inputs' do
|
||||
let(:inputs) do
|
||||
{ deploy_strategy: 'blue-green' }
|
||||
end
|
||||
|
||||
let(:expected_errors) do
|
||||
['Given inputs not defined in the `spec` section of the included configuration file']
|
||||
end
|
||||
|
||||
it_behaves_like 'returning errors'
|
||||
|
||||
context 'when the FF ci_inputs_for_pipelines is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_inputs_for_pipelines: false)
|
||||
end
|
||||
|
||||
it 'creates a pipeline' do
|
||||
response = execute
|
||||
pipeline = response.payload
|
||||
|
||||
expect(response).to be_success
|
||||
expect(pipeline).to be_created_successfully
|
||||
|
||||
expect(pipeline.builds.map(&:name)).to contain_exactly('job')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,9 +4,9 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Ci::EnqueueJobService, '#execute', feature_category: :continuous_integration do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:user) { create(:user, developer_of: project) }
|
||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
|
||||
let_it_be(:user) { create(:user, maintainer_of: project) }
|
||||
let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project, created_at: 1.day.ago) }
|
||||
let_it_be_with_reload(:build) { create(:ci_build, :manual, pipeline: pipeline) }
|
||||
|
||||
let(:service) do
|
||||
described_class.new(build, current_user: user)
|
||||
|
|
@ -107,4 +107,28 @@ RSpec.describe Ci::EnqueueJobService, '#execute', feature_category: :continuous_
|
|||
expect(build.job_variables.map(&:key)).to contain_exactly('third', 'fourth')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the pipeline is archived' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it 'responds with forbidden' do
|
||||
response = execute
|
||||
|
||||
expect(response).to be_error
|
||||
expect(response.reason).to eq(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not authorized' do
|
||||
let_it_be(:user) { create(:user, developer_of: project) }
|
||||
|
||||
it 'responds with forbidden' do
|
||||
response = execute
|
||||
|
||||
expect(response).to be_error
|
||||
expect(response.reason).to eq(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,32 +3,64 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::Pipelines::UpdateMetadataService, feature_category: :continuous_integration do
|
||||
subject(:execute) { described_class.new(pipeline, { name: name }).execute }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project, created_at: 1.day.ago) }
|
||||
|
||||
let(:params) { { name: name } }
|
||||
let(:name) { 'Some random pipeline name' }
|
||||
|
||||
context 'when pipeline has no name' do
|
||||
let(:pipeline) { create(:ci_pipeline) }
|
||||
subject(:execute) { described_class.new(pipeline, current_user: user, params: params).execute }
|
||||
|
||||
it 'updates the name' do
|
||||
expect { execute }.to change { pipeline.reload.name }.to(name)
|
||||
describe '#execute' do
|
||||
context 'when user is authorized' do
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'when pipeline has no name' do
|
||||
it 'updates the name' do
|
||||
expect { execute }.to change { pipeline.reload.name }.to(name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline has a name' do
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project, name: 'Some other name') }
|
||||
|
||||
it 'updates the name' do
|
||||
expect { execute }.to change { pipeline.reload.name }.to(name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when new name is too long' do
|
||||
let(:name) { 'a' * 256 }
|
||||
|
||||
it 'does not update the name' do
|
||||
expect { execute }.not_to change { pipeline.reload.name }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the pipeline is archived' do
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it 'responds with forbidden' do
|
||||
response = execute
|
||||
|
||||
expect(response).to be_error
|
||||
expect(response.reason).to eq(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline has a name' do
|
||||
let(:pipeline) { create(:ci_pipeline, name: 'Some other name') }
|
||||
context 'when user is not authorized' do
|
||||
it 'responds with forbidden' do
|
||||
response = execute
|
||||
|
||||
it 'updates the name' do
|
||||
expect { execute }.to change { pipeline.reload.name }.to(name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when new name is too long' do
|
||||
let(:pipeline) { create(:ci_pipeline) }
|
||||
let(:name) { 'a' * 256 }
|
||||
|
||||
it 'does not update the name' do
|
||||
expect { execute }.not_to change { pipeline.reload.name }
|
||||
expect(response).to be_error
|
||||
expect(response.reason).to eq(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,20 +3,17 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::PlayBridgeService, '#execute', feature_category: :continuous_integration do
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
let(:downstream_project) { create(:project) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:downstream_project) { create(:project) }
|
||||
let_it_be(:user) { create(:user, maintainer_of: [project, downstream_project]) }
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
|
||||
let(:bridge) { create(:ci_bridge, :playable, pipeline: pipeline, downstream: downstream_project) }
|
||||
let(:instance) { described_class.new(project, user) }
|
||||
|
||||
subject(:execute_service) { instance.execute(bridge) }
|
||||
|
||||
context 'when user can run the bridge' do
|
||||
before do
|
||||
allow(instance).to receive(:can?).with(user, :play_job, bridge).and_return(true)
|
||||
end
|
||||
|
||||
it 'marks the bridge pending' do
|
||||
execute_service
|
||||
|
||||
|
|
@ -57,9 +54,7 @@ RSpec.describe Ci::PlayBridgeService, '#execute', feature_category: :continuous_
|
|||
end
|
||||
|
||||
context 'when user can not run the bridge' do
|
||||
before do
|
||||
allow(instance).to receive(:can?).with(user, :play_job, bridge).and_return(false)
|
||||
end
|
||||
let_it_be(:user) { create(:user, developer_of: project) }
|
||||
|
||||
it 'allows user with developer role to play a bridge' do
|
||||
expect { execute_service }.to raise_error Gitlab::Access::AccessDeniedError
|
||||
|
|
|
|||
|
|
@ -392,6 +392,22 @@ RSpec.describe Ci::RetryPipelineService, '#execute', feature_category: :continuo
|
|||
expect(pipeline.reload).not_to be_running
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the pipeline is archived' do
|
||||
let(:pipeline) { create(:ci_pipeline, project: project, created_at: 1.day.ago) }
|
||||
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it 'returns an error' do
|
||||
response = service.execute(pipeline)
|
||||
|
||||
expect(response.http_status).to eq(:forbidden)
|
||||
expect(response.errors).to include('403 Forbidden')
|
||||
expect(pipeline.reload).not_to be_running
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not allowed to retry pipeline' do
|
||||
|
|
|
|||
|
|
@ -2,17 +2,19 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::RunScheduledBuildService, feature_category: :continuous_integration do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
RSpec.describe Ci::RunScheduledBuildService, :aggregate_failures, feature_category: :continuous_integration do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project, created_at: 1.day.ago) }
|
||||
|
||||
subject(:execute_service) { described_class.new(build).execute }
|
||||
|
||||
context 'when user can update build' do
|
||||
before do
|
||||
before_all do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
before do
|
||||
create(:protected_branch, :developers_can_merge, name: pipeline.ref, project: project)
|
||||
end
|
||||
|
||||
|
|
@ -59,6 +61,20 @@ RSpec.describe Ci::RunScheduledBuildService, feature_category: :continuous_integ
|
|||
expect(build).to be_created
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the pipeline is archived' do
|
||||
let(:build) { create(:ci_build, :scheduled, user: user, project: project, pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
stub_application_setting(archive_builds_in_seconds: 3600)
|
||||
end
|
||||
|
||||
it 'can not run the build' do
|
||||
expect { execute_service }.to raise_error(Gitlab::Access::AccessDeniedError)
|
||||
|
||||
expect(build).to be_scheduled
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can not update build' do
|
||||
|
|
|
|||
Loading…
Reference in New Issue