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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml11
-rw-r--r--.rubocop_todo/rails/save_bang.yml6
-rw-r--r--CHANGELOG.md9
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/models/ci/runner.rb7
-rw-r--r--app/services/bulk_imports/archive_extraction_service.rb4
-rw-r--r--app/services/bulk_imports/file_decompression_service.rb27
-rw-r--r--app/services/bulk_imports/file_download_service.rb28
-rw-r--r--app/views/projects/pipelines/_info.html.haml34
-rw-r--r--db/migrate/20220112205111_create_security_training_providers.rb14
-rw-r--r--db/migrate/20220113125401_create_security_trainings.rb18
-rw-r--r--db/schema_migrations/202201122051111
-rw-r--r--db/schema_migrations/202201131254011
-rw-r--r--db/structure.sql63
-rw-r--r--doc/api/graphql/reference/index.md26
-rw-r--r--doc/development/licensing.md2
-rw-r--r--doc/development/testing_guide/review_apps.md1
-rw-r--r--doc/user/admin_area/monitoring/background_migrations.md2
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_stage_table_v13_12.pngbin81442 -> 0 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_stage_table_v14_7.pngbin0 -> 242008 bytes
-rw-r--r--doc/user/group/value_stream_analytics/index.md2
-rw-r--r--lib/api/ci/runners.rb7
-rw-r--r--lib/bulk_imports/common/extractors/ndjson_extractor.rb34
-rw-r--r--lib/bulk_imports/common/pipelines/uploads_pipeline.rb14
-rw-r--r--lib/bulk_imports/ndjson_pipeline.rb2
-rw-r--r--lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb31
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml2
-rw-r--r--locale/gitlab.pot22
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb2
-rw-r--r--spec/lib/bulk_imports/common/extractors/ndjson_extractor_spec.rb6
-rw-r--r--spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb31
-rw-r--r--spec/lib/bulk_imports/projects/pipelines/project_attributes_pipeline_spec.rb27
-rw-r--r--spec/lib/gitlab/import_export/uploads_saver_spec.rb4
-rw-r--r--spec/lib/gitlab/lets_encrypt/client_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb2
-rw-r--r--spec/lib/gitlab/shard_health_cache_spec.rb6
-rw-r--r--spec/mailers/notify_spec.rb10
-rw-r--r--spec/models/ci/runner_spec.rb10
-rw-r--r--spec/requests/api/ci/runners_spec.rb12
-rw-r--r--spec/services/bulk_imports/archive_extraction_service_spec.rb6
-rw-r--r--spec/services/bulk_imports/file_decompression_service_spec.rb18
-rw-r--r--spec/services/bulk_imports/file_download_service_spec.rb32
42 files changed, 401 insertions, 137 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index af5e0ec93e0..008b62f6a0f 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -229,6 +229,9 @@
- "vendor/assets/**/*"
- "{,ee/,jh/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*"
+.controllers-patterns: &controllers-patterns
+ - "{,ee/,jh/}{app/controllers}/**/*"
+
.startup-css-patterns: &startup-css-patterns
- "{,ee/,jh/}app/assets/stylesheets/startup/**/*"
@@ -280,7 +283,7 @@
- ".dockerignore"
- "qa/**/*"
-# Code patterns + .ci-patterns + .workhorse-patterns
+# Code patterns + .ci-patterns
.code-patterns: &code-patterns
- "{package.json,yarn.lock}"
- ".browserslistrc"
@@ -1614,11 +1617,13 @@
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
- <<: *if-dot-com-gitlab-org-merge-request
+ changes: *controllers-patterns
+ - <<: *if-dot-com-gitlab-org-merge-request
+ changes: *qa-patterns
+ - <<: *if-dot-com-gitlab-org-merge-request
changes: *code-patterns
when: manual
allow_failure: true
- - <<: *if-dot-com-gitlab-org-merge-request
- changes: *qa-patterns
- <<: *if-dot-com-gitlab-org-schedule
variables:
KNAPSACK_GENERATE_REPORT: "true"
diff --git a/.rubocop_todo/rails/save_bang.yml b/.rubocop_todo/rails/save_bang.yml
index 870e085eb28..807f2d6485d 100644
--- a/.rubocop_todo/rails/save_bang.yml
+++ b/.rubocop_todo/rails/save_bang.yml
@@ -50,9 +50,3 @@ Rails/SaveBang:
- spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
- spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
- spec/lib/gitlab/import_export/uploads_manager_spec.rb
- - spec/lib/gitlab/import_export/uploads_saver_spec.rb
- - spec/lib/gitlab/import_export/wiki_restorer_spec.rb
- - spec/lib/gitlab/lets_encrypt/client_spec.rb
- - spec/lib/gitlab/middleware/go_spec.rb
- - spec/lib/gitlab/shard_health_cache_spec.rb
- - spec/mailers/notify_spec.rb
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5d48b4ceb3..1c3d38f6810 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 14.6.3 (2022-01-18)
+
+### Fixed (4 changes)
+
+- [Fix destruction of projects with pipelines](gitlab-org/gitlab@83e1616fe46b933c5b78b2d43e08463fdae4264a) ([merge request](gitlab-org/gitlab!78401))
+- [Geo: Resolve "undefined method each_batch"](gitlab-org/gitlab@a38bf23ebd0a9931ec5bb91377955824dcda39ea) ([merge request](gitlab-org/gitlab!78401)) **GitLab Enterprise Edition**
+- [Fix migration for cases with empty strings](gitlab-org/gitlab@ddda8880db35b7d48ca8e4ec8efe54954d64f41f) ([merge request](gitlab-org/gitlab!78401))
+- [Geo: adapt verification timed out query to use state table](gitlab-org/gitlab@89212752226d6c5f34830e3f4a73c5a56764ed17) ([merge request](gitlab-org/gitlab!78401)) **GitLab Enterprise Edition**
+
## 14.6.2 (2022-01-10)
No changes.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 2ca34134182..f3a0458d512 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-b119c19734ba589a079381e8390952e37994149f
+871f4415ed24c64e6a2746a4502f0bea38d0c440
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 8b2d987ed90..809c245d2b9 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -112,6 +112,13 @@ module Ci
from("(#{union_sql}) #{table_name}")
}
+ scope :belonging_to_group_and_ancestors, -> (group_id) {
+ group_self_and_ancestors_ids = ::Group.find_by(id: group_id)&.self_and_ancestor_ids
+
+ joins(:runner_namespaces)
+ .where(ci_runner_namespaces: { namespace_id: group_self_and_ancestors_ids })
+ }
+
# deprecated
# split this into: belonging_to_group & belonging_to_group_and_ancestors
scope :legacy_belonging_to_group, -> (group_id, include_ancestors: false) {
diff --git a/app/services/bulk_imports/archive_extraction_service.rb b/app/services/bulk_imports/archive_extraction_service.rb
index 9fc828b8e34..caa40d98a76 100644
--- a/app/services/bulk_imports/archive_extraction_service.rb
+++ b/app/services/bulk_imports/archive_extraction_service.rb
@@ -28,8 +28,8 @@ module BulkImports
end
def execute
- validate_filepath
validate_tmpdir
+ validate_filepath
validate_symlink
extract_archive
@@ -46,7 +46,7 @@ module BulkImports
end
def validate_tmpdir
- raise(BulkImports::Error, 'Invalid target directory') unless File.expand_path(tmpdir).start_with?(Dir.tmpdir)
+ Gitlab::Utils.check_allowed_absolute_path!(tmpdir, [Dir.tmpdir])
end
def validate_symlink
diff --git a/app/services/bulk_imports/file_decompression_service.rb b/app/services/bulk_imports/file_decompression_service.rb
index fe9017377ec..b76746b199f 100644
--- a/app/services/bulk_imports/file_decompression_service.rb
+++ b/app/services/bulk_imports/file_decompression_service.rb
@@ -1,21 +1,26 @@
# frozen_string_literal: true
+# File Decompression Service allows gzipped files decompression into tmp directory.
+#
+# @param tmpdir [String] Temp directory to store downloaded file to. Must be located under `Dir.tmpdir`.
+# @param filename [String] Name of the file to decompress.
module BulkImports
class FileDecompressionService
include Gitlab::ImportExport::CommandLineUtil
ServiceError = Class.new(StandardError)
- def initialize(dir:, filename:)
- @dir = dir
+ def initialize(tmpdir:, filename:)
+ @tmpdir = tmpdir
@filename = filename
- @filepath = File.join(@dir, @filename)
+ @filepath = File.join(@tmpdir, @filename)
@decompressed_filename = File.basename(@filename, '.gz')
- @decompressed_filepath = File.join(@dir, @decompressed_filename)
+ @decompressed_filepath = File.join(@tmpdir, @decompressed_filename)
end
def execute
- validate_dir
+ validate_tmpdir
+ validate_filepath
validate_decompressed_file_size if Feature.enabled?(:validate_import_decompressed_archive_size, default_enabled: :yaml)
validate_symlink(filepath)
@@ -33,10 +38,14 @@ module BulkImports
private
- attr_reader :dir, :filename, :filepath, :decompressed_filename, :decompressed_filepath
+ attr_reader :tmpdir, :filename, :filepath, :decompressed_filename, :decompressed_filepath
- def validate_dir
- raise(ServiceError, 'Invalid target directory') unless dir.start_with?(Dir.tmpdir)
+ def validate_filepath
+ Gitlab::Utils.check_path_traversal!(filepath)
+ end
+
+ def validate_tmpdir
+ Gitlab::Utils.check_allowed_absolute_path!(tmpdir, [Dir.tmpdir])
end
def validate_decompressed_file_size
@@ -48,7 +57,7 @@ module BulkImports
end
def decompress_file
- gunzip(dir: dir, filename: filename)
+ gunzip(dir: tmpdir, filename: filename)
end
def size_validator
diff --git a/app/services/bulk_imports/file_download_service.rb b/app/services/bulk_imports/file_download_service.rb
index d08dc72e30b..8d6ba54cd50 100644
--- a/app/services/bulk_imports/file_download_service.rb
+++ b/app/services/bulk_imports/file_download_service.rb
@@ -1,6 +1,13 @@
# frozen_string_literal: true
-# Downloads a remote file. If no filename is given, it'll use the remote filename
+# File Download Service allows remote file download into tmp directory.
+#
+# @param configuration [BulkImports::Configuration] Config object containing url and access token
+# @param relative_url [String] Relative URL to download the file from
+# @param tmpdir [String] Temp directory to store downloaded file to. Must be located under `Dir.tmpdir`.
+# @param file_size_limit [Integer] Maximum allowed file size
+# @param allowed_content_types [Array<String>] Allowed file content types
+# @param filename [String] Name of the file to download, if known. Use remote filename if none given.
module BulkImports
class FileDownloadService
ServiceError = Class.new(StandardError)
@@ -13,20 +20,21 @@ module BulkImports
def initialize(
configuration:,
relative_url:,
- dir:,
+ tmpdir:,
file_size_limit: DEFAULT_FILE_SIZE_LIMIT,
allowed_content_types: DEFAULT_ALLOWED_CONTENT_TYPES,
filename: nil)
@configuration = configuration
@relative_url = relative_url
@filename = filename
- @dir = dir
+ @tmpdir = tmpdir
@file_size_limit = file_size_limit
@allowed_content_types = allowed_content_types
end
def execute
- validate_dir
+ validate_tmpdir
+ validate_filepath
validate_url
validate_content_type
validate_content_length
@@ -40,7 +48,7 @@ module BulkImports
private
- attr_reader :configuration, :relative_url, :dir, :file_size_limit, :allowed_content_types
+ attr_reader :configuration, :relative_url, :tmpdir, :file_size_limit, :allowed_content_types
def download_file
File.open(filepath, 'wb') do |file|
@@ -76,8 +84,12 @@ module BulkImports
@headers ||= http_client.head(relative_url).headers
end
- def validate_dir
- raise(ServiceError, 'Invalid target directory') unless dir.start_with?(Dir.tmpdir)
+ def validate_filepath
+ Gitlab::Utils.check_path_traversal!(filepath)
+ end
+
+ def validate_tmpdir
+ Gitlab::Utils.check_allowed_absolute_path!(tmpdir, [Dir.tmpdir])
end
def validate_symlink
@@ -119,7 +131,7 @@ module BulkImports
end
def filepath
- @filepath ||= File.join(@dir, filename)
+ @filepath ||= File.join(@tmpdir, filename)
end
def filename
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 0bfdee088b4..13a77dbf2fd 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -22,41 +22,25 @@
.icon-container
= sprite_icon('flag')
- if @pipeline.child?
- %span.js-pipeline-child.badge.badge-pill.gl-badge.sm.badge-primary.has-tooltip{ title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
- = s_('Pipelines|Child pipeline')
- = surround '(', ')' do
- = link_to s_('Pipelines|parent'), pipeline_path(@pipeline.triggered_by_pipeline), class: 'text-white text-underline'
+ - text = sprintf(s_('Pipelines|Child pipeline (%{link_start}parent%{link_end})'), { link_start: "<a href='#{pipeline_path(@pipeline.triggered_by_pipeline)}' class='text-underline'>", link_end: "</a>"}).html_safe
+ = gl_badge_tag text, { variant: :info, size: :sm }, { class: 'js-pipeline-child has-tooltip', title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
- if @pipeline.latest?
- %span.js-pipeline-url-latest.badge.badge-pill.gl-badge.sm.badge-success.has-tooltip{ title: _("Latest pipeline for the most recent commit on this branch") }
- latest
+ = gl_badge_tag s_('Pipelines|latest'), { variant: :success, size: :sm }, { class: 'js-pipeline-url-latest has-tooltip', title: _("Latest pipeline for the most recent commit on this branch") }
- if @pipeline.merge_train_pipeline?
- %span.js-pipeline-url-train.badge.badge-pill.gl-badge.sm.badge-info.has-tooltip{ title: _("This is a merge train pipeline") }
- train
+ = gl_badge_tag s_('Pipelines|train'), { variant: :info, size: :sm }, { class: 'js-pipeline-url-train has-tooltip', title: _("This is a merge train pipeline") }
- if @pipeline.has_yaml_errors?
- %span.js-pipeline-url-yaml.badge.badge-pill.gl-badge.sm.badge-danger.has-tooltip{ title: @pipeline.yaml_errors }
- yaml invalid
+ = gl_badge_tag s_('Pipelines|yaml invalid'), { variant: :danger, size: :sm }, { class: 'js-pipeline-url-yaml has-tooltip', title: @pipeline.yaml_errors }
- if @pipeline.failure_reason?
- %span.js-pipeline-url-failure.badge.badge-pill.gl-badge.sm.badge-danger.has-tooltip{ title: @pipeline.failure_reason }
- error
+ = gl_badge_tag s_('Pipelines|error'), { variant: :danger, size: :sm }, { class: 'js-pipeline-url-failure has-tooltip', title: @pipeline.failure_reason }
- if @pipeline.auto_devops_source?
- popover_title_text = html_escape(_('This pipeline makes use of a predefined CI/CD configuration enabled by %{b_open}Auto DevOps.%{b_close}')) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe }
- popover_content_url = help_page_path('topics/autodevops/index.md')
- popover_content_text = _('Learn more about Auto DevOps')
- %a.js-pipeline-url-autodevops.badge.badge-pill.gl-badge.sm.badge-info.autodevops-badge{ href: "#", tabindex: "0", role: "button", data: { container: "body",
- toggle: "popover",
- placement: "top",
- html: "true",
- triggers: "focus",
- title: "<div class='gl-font-weight-normal gl-line-height-normal'>#{popover_title_text}</div>",
- content: "<a href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>",
- } }
- Auto DevOps
+ = gl_badge_tag s_('Pipelines|Auto DevOps'), { variant: :info, size: :sm }, { class: 'js-pipeline-url-autodevops', href: "#", tabindex: "0", role: "button", data: { container: 'body', toggle: 'popover', placement: 'top', html: 'true', triggers: 'focus', title: "<div class='gl-font-weight-normal gl-line-height-normal'>#{popover_title_text}</div>", content: "<a href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>" } }
- if @pipeline.detached_merge_request_pipeline?
- %span.js-pipeline-url-mergerequest.badge.badge-pill.gl-badge.sm.badge-info.has-tooltip{ title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.') }
- detached
+ = gl_badge_tag s_('Pipelines|detached'), { variant: :info, size: :sm }, { class: 'js-pipeline-url-mergerequest has-tooltip', title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.') }
- if @pipeline.stuck?
- %span.js-pipeline-url-stuck.badge.badge-pill.gl-badge.sm.badge-warning
- stuck
+ = gl_badge_tag s_('Pipelines|stuck'), { variant: :warning, size: :sm }, { class: 'js-pipeline-url-stuck has-tooltip' }
.well-segment.branch-info
.icon-container.commit-icon
diff --git a/db/migrate/20220112205111_create_security_training_providers.rb b/db/migrate/20220112205111_create_security_training_providers.rb
new file mode 100644
index 00000000000..afddec6a134
--- /dev/null
+++ b/db/migrate/20220112205111_create_security_training_providers.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CreateSecurityTrainingProviders < Gitlab::Database::Migration[1.0]
+ def change
+ create_table :security_training_providers do |t|
+ t.text :name, limit: 256, null: false
+ t.text :description, limit: 512
+ t.text :url, limit: 512, null: false
+ t.text :logo_url, limit: 512
+
+ t.timestamps_with_timezone null: false
+ end
+ end
+end
diff --git a/db/migrate/20220113125401_create_security_trainings.rb b/db/migrate/20220113125401_create_security_trainings.rb
new file mode 100644
index 00000000000..6924c7bd189
--- /dev/null
+++ b/db/migrate/20220113125401_create_security_trainings.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateSecurityTrainings < Gitlab::Database::Migration[1.0]
+ enable_lock_retries!
+
+ def change
+ create_table :security_trainings do |t|
+ t.references :project, null: false, foreign_key: { on_delete: :cascade }
+ t.references :provider, null: false, foreign_key: { to_table: :security_training_providers, on_delete: :cascade }
+ t.boolean :is_primary, default: false, null: false
+
+ t.timestamps_with_timezone null: false
+
+ # Guarantee that there will be only one primary per project
+ t.index :project_id, name: 'index_security_trainings_on_unique_project_id', unique: true, where: 'is_primary IS TRUE'
+ end
+ end
+end
diff --git a/db/schema_migrations/20220112205111 b/db/schema_migrations/20220112205111
new file mode 100644
index 00000000000..a2d2d42271e
--- /dev/null
+++ b/db/schema_migrations/20220112205111
@@ -0,0 +1 @@
+65d9a1d63e90dfc336d0c69503c0899259fda773bc68a330782c206ac0fc48fd \ No newline at end of file
diff --git a/db/schema_migrations/20220113125401 b/db/schema_migrations/20220113125401
new file mode 100644
index 00000000000..7241e2e29c7
--- /dev/null
+++ b/db/schema_migrations/20220113125401
@@ -0,0 +1 @@
+afe57b6b1b8b10e0e26d7f499b25adc5aef9f7c52af644d6a260f0ed3aab16d5 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 1f8ff54765f..69910954a87 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -19446,6 +19446,47 @@ CREATE SEQUENCE security_scans_id_seq
ALTER SEQUENCE security_scans_id_seq OWNED BY security_scans.id;
+CREATE TABLE security_training_providers (
+ id bigint NOT NULL,
+ name text NOT NULL,
+ description text,
+ url text NOT NULL,
+ logo_url text,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ CONSTRAINT check_544b3dc935 CHECK ((char_length(url) <= 512)),
+ CONSTRAINT check_6fe222f071 CHECK ((char_length(logo_url) <= 512)),
+ CONSTRAINT check_a8ff21ced5 CHECK ((char_length(description) <= 512)),
+ CONSTRAINT check_dae433eed6 CHECK ((char_length(name) <= 256))
+);
+
+CREATE SEQUENCE security_training_providers_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE security_training_providers_id_seq OWNED BY security_training_providers.id;
+
+CREATE TABLE security_trainings (
+ id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ provider_id bigint NOT NULL,
+ is_primary boolean DEFAULT false NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE security_trainings_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE security_trainings_id_seq OWNED BY security_trainings.id;
+
CREATE TABLE self_managed_prometheus_alert_events (
id bigint NOT NULL,
project_id bigint NOT NULL,
@@ -22074,6 +22115,10 @@ ALTER TABLE ONLY security_orchestration_policy_rule_schedules ALTER COLUMN id SE
ALTER TABLE ONLY security_scans ALTER COLUMN id SET DEFAULT nextval('security_scans_id_seq'::regclass);
+ALTER TABLE ONLY security_training_providers ALTER COLUMN id SET DEFAULT nextval('security_training_providers_id_seq'::regclass);
+
+ALTER TABLE ONLY security_trainings ALTER COLUMN id SET DEFAULT nextval('security_trainings_id_seq'::regclass);
+
ALTER TABLE ONLY self_managed_prometheus_alert_events ALTER COLUMN id SET DEFAULT nextval('self_managed_prometheus_alert_events_id_seq'::regclass);
ALTER TABLE ONLY sent_notifications ALTER COLUMN id SET DEFAULT nextval('sent_notifications_id_seq'::regclass);
@@ -24015,6 +24060,12 @@ ALTER TABLE ONLY security_orchestration_policy_rule_schedules
ALTER TABLE ONLY security_scans
ADD CONSTRAINT security_scans_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY security_training_providers
+ ADD CONSTRAINT security_training_providers_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY security_trainings
+ ADD CONSTRAINT security_trainings_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY self_managed_prometheus_alert_events
ADD CONSTRAINT self_managed_prometheus_alert_events_pkey PRIMARY KEY (id);
@@ -27486,6 +27537,12 @@ CREATE INDEX index_security_scans_on_pipeline_id ON security_scans USING btree (
CREATE INDEX index_security_scans_on_project_id ON security_scans USING btree (project_id);
+CREATE INDEX index_security_trainings_on_project_id ON security_trainings USING btree (project_id);
+
+CREATE INDEX index_security_trainings_on_provider_id ON security_trainings USING btree (provider_id);
+
+CREATE UNIQUE INDEX index_security_trainings_on_unique_project_id ON security_trainings USING btree (project_id) WHERE (is_primary IS TRUE);
+
CREATE INDEX index_self_managed_prometheus_alert_events_on_environment_id ON self_managed_prometheus_alert_events USING btree (environment_id);
CREATE INDEX index_sent_notifications_on_noteable_type_noteable_id ON sent_notifications USING btree (noteable_id) WHERE ((noteable_type)::text = 'Issue'::text);
@@ -30753,6 +30810,9 @@ ALTER TABLE ONLY required_code_owners_sections
ALTER TABLE ONLY dast_site_profiles
ADD CONSTRAINT fk_rails_83e309d69e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY security_trainings
+ ADD CONSTRAINT fk_rails_84c7951d72 FOREIGN KEY (provider_id) REFERENCES security_training_providers(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY zentao_tracker_data
ADD CONSTRAINT fk_rails_84efda7be0 FOREIGN KEY (integration_id) REFERENCES integrations(id) ON DELETE CASCADE;
@@ -31458,6 +31518,9 @@ ALTER TABLE ONLY internal_ids
ALTER TABLE ONLY issues_self_managed_prometheus_alert_events
ADD CONSTRAINT fk_rails_f7db2d72eb FOREIGN KEY (self_managed_prometheus_alert_event_id) REFERENCES self_managed_prometheus_alert_events(id) ON DELETE CASCADE;
+ALTER TABLE ONLY security_trainings
+ ADD CONSTRAINT fk_rails_f80240fae0 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY merge_requests_closing_issues
ADD CONSTRAINT fk_rails_f8540692be FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 49bfaf833f4..86d88215431 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -14024,6 +14024,18 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="projectscanexecutionpoliciesactionscantypes"></a>`actionScanTypes` | [`[SecurityReportTypeEnum!]`](#securityreporttypeenum) | Filters policies by the action scan type. Only these scan types are supported: `dast`, `secret_detection`, `cluster_image_scanning`, `container_scanning`, `sast`. |
+##### `Project.securityTrainingProviders`
+
+List of security training providers for the project.
+
+Returns [`[ProjectSecurityTraining!]`](#projectsecuritytraining).
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="projectsecuritytrainingprovidersonlyenabled"></a>`onlyEnabled` | [`Boolean`](#boolean) | Filter the list by only enabled security trainings. |
+
##### `Project.sentryDetailedError`
Detailed version of a Sentry error on the project.
@@ -14247,6 +14259,20 @@ Represents a Project Membership.
| <a id="projectpermissionsupdatewiki"></a>`updateWiki` | [`Boolean!`](#boolean) | Indicates the user can perform `update_wiki` on this resource. |
| <a id="projectpermissionsuploadfile"></a>`uploadFile` | [`Boolean!`](#boolean) | Indicates the user can perform `upload_file` on this resource. |
+### `ProjectSecurityTraining`
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="projectsecuritytrainingdescription"></a>`description` | [`String`](#string) | Description of the training provider. |
+| <a id="projectsecuritytrainingid"></a>`id` | [`GlobalID!`](#globalid) | ID of the training provider. |
+| <a id="projectsecuritytrainingisenabled"></a>`isEnabled` | [`Boolean!`](#boolean) | Represents whether the provider is enabled or not. |
+| <a id="projectsecuritytrainingisprimary"></a>`isPrimary` | [`Boolean!`](#boolean) | Represents whether the provider is set as primary or not. |
+| <a id="projectsecuritytraininglogourl"></a>`logoUrl` | [`String`](#string) | Logo URL of the provider. |
+| <a id="projectsecuritytrainingname"></a>`name` | [`String!`](#string) | Name of the training provider. |
+| <a id="projectsecuritytrainingurl"></a>`url` | [`String!`](#string) | URL of the provider. |
+
### `ProjectStatistics`
#### Fields
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index 23871bf3c68..a50c514733e 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab Licensing and Compatibility
-[GitLab Community Edition](https://gitlab.com/gitlab-org/gitlab-foss/) (CE) is licensed [under the terms of the MIT License](https://gitlab.com/gitlab-org/gitlab-foss/blob/master/LICENSE). [GitLab Enterprise Edition](https://gitlab.com/gitlab-org/gitlab/) (EE) is licensed under "[The GitLab Enterprise Edition (EE) license](https://gitlab.com/gitlab-org/gitlab/-/blob/master/LICENSE)" wherein there are more restrictions.
+[GitLab Community Edition](https://gitlab.com/gitlab-org/gitlab-foss/) (CE) is licensed [under the terms of the MIT License](https://gitlab.com/gitlab-org/gitlab-foss/blob/master/LICENSE). [GitLab Enterprise Edition](https://gitlab.com/gitlab-org/gitlab/) (EE) is licensed under "[The GitLab Enterprise Edition (EE) license](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/LICENSE)" wherein there are more restrictions.
## Automated Testing
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 4d8d0c5cdae..aef83109b9b 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -14,6 +14,7 @@ For any of the following scenarios, the `start-review-app-pipeline` job would be
- for merge requests with CI config changes
- for merge requests with frontend changes
+- for merge requests with changes to `{,ee/,jh/}{app/controllers}/**/*`
- for merge requests with QA changes
- for scheduled pipelines
- the MR has the `pipeline:run-review-app` label set
diff --git a/doc/user/admin_area/monitoring/background_migrations.md b/doc/user/admin_area/monitoring/background_migrations.md
index 66001a987a4..260a8515a1a 100644
--- a/doc/user/admin_area/monitoring/background_migrations.md
+++ b/doc/user/admin_area/monitoring/background_migrations.md
@@ -145,6 +145,8 @@ or [manually finish it](#manually-finishing-a-batched-background-migration).
### Manually finishing a batched background migration
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62634) in GitLab 14.1
+
If you need to manually finish a batched background migration due to an
error, you can run:
diff --git a/doc/user/group/value_stream_analytics/img/vsa_stage_table_v13_12.png b/doc/user/group/value_stream_analytics/img/vsa_stage_table_v13_12.png
deleted file mode 100644
index 24d485306be..00000000000
--- a/doc/user/group/value_stream_analytics/img/vsa_stage_table_v13_12.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/vsa_stage_table_v14_7.png b/doc/user/group/value_stream_analytics/img/vsa_stage_table_v14_7.png
new file mode 100644
index 00000000000..c9074cbb5ea
--- /dev/null
+++ b/doc/user/group/value_stream_analytics/img/vsa_stage_table_v14_7.png
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/index.md b/doc/user/group/value_stream_analytics/index.md
index 8e212afcb13..4663cfc8bfd 100644
--- a/doc/user/group/value_stream_analytics/index.md
+++ b/doc/user/group/value_stream_analytics/index.md
@@ -288,7 +288,7 @@ Shown metrics and charts includes:
> Sorting the stage table [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/301082) in GitLab 13.12.
-![Value stream analytics stage table](img/vsa_stage_table_v13_12.png "VSA stage table")
+![Value stream analytics stage table](img/vsa_stage_table_v14_7.png "VSA stage table")
The stage table shows a list of related workflow items for the selected stage. This can include:
diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb
index f39c139b4fa..f21782a698f 100644
--- a/lib/api/ci/runners.rb
+++ b/lib/api/ci/runners.rb
@@ -229,7 +229,12 @@ module API
use :pagination
end
get ':id/runners' do
- runners = ::Ci::Runner.legacy_belonging_to_group(user_group.id, include_ancestors: true)
+ runners = if ::Feature.enabled?(:ci_find_runners_by_ci_mirrors, user_group, default_enabled: :yaml)
+ ::Ci::Runner.belonging_to_group_and_ancestors(user_group.id)
+ else
+ ::Ci::Runner.legacy_belonging_to_group(user_group.id, include_ancestors: true)
+ end
+
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
diff --git a/lib/bulk_imports/common/extractors/ndjson_extractor.rb b/lib/bulk_imports/common/extractors/ndjson_extractor.rb
index ecd7c08bd25..04febebff8e 100644
--- a/lib/bulk_imports/common/extractors/ndjson_extractor.rb
+++ b/lib/bulk_imports/common/extractors/ndjson_extractor.rb
@@ -4,49 +4,47 @@ module BulkImports
module Common
module Extractors
class NdjsonExtractor
- include Gitlab::ImportExport::CommandLineUtil
- include Gitlab::Utils::StrongMemoize
-
def initialize(relation:)
@relation = relation
- @tmp_dir = Dir.mktmpdir
+ @tmpdir = Dir.mktmpdir
end
def extract(context)
- download_service(tmp_dir, context).execute
- decompression_service(tmp_dir).execute
- relations = ndjson_reader(tmp_dir).consume_relation('', relation)
+ download_service(context).execute
+ decompression_service.execute
+
+ records = ndjson_reader.consume_relation('', relation)
- BulkImports::Pipeline::ExtractedData.new(data: relations)
+ BulkImports::Pipeline::ExtractedData.new(data: records)
end
- def remove_tmp_dir
- FileUtils.remove_entry(tmp_dir)
+ def remove_tmpdir
+ FileUtils.remove_entry(tmpdir) if Dir.exist?(tmpdir)
end
private
- attr_reader :relation, :tmp_dir
+ attr_reader :relation, :tmpdir
def filename
- @filename ||= "#{relation}.ndjson.gz"
+ "#{relation}.ndjson.gz"
end
- def download_service(tmp_dir, context)
+ def download_service(context)
@download_service ||= BulkImports::FileDownloadService.new(
configuration: context.configuration,
relative_url: context.entity.relation_download_url_path(relation),
- dir: tmp_dir,
+ tmpdir: tmpdir,
filename: filename
)
end
- def decompression_service(tmp_dir)
- @decompression_service ||= BulkImports::FileDecompressionService.new(dir: tmp_dir, filename: filename)
+ def decompression_service
+ @decompression_service ||= BulkImports::FileDecompressionService.new(tmpdir: tmpdir, filename: filename)
end
- def ndjson_reader(tmp_dir)
- @ndjson_reader ||= Gitlab::ImportExport::Json::NdjsonReader.new(tmp_dir)
+ def ndjson_reader
+ @ndjson_reader ||= Gitlab::ImportExport::Json::NdjsonReader.new(tmpdir)
end
end
end
diff --git a/lib/bulk_imports/common/pipelines/uploads_pipeline.rb b/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
index 2ac4e533c1d..d7b9d6920ea 100644
--- a/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
+++ b/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
@@ -15,7 +15,7 @@ module BulkImports
decompression_service.execute
extraction_service.execute
- upload_file_paths = Dir.glob(File.join(tmp_dir, '**', '*'))
+ upload_file_paths = Dir.glob(File.join(tmpdir, '**', '*'))
BulkImports::Pipeline::ExtractedData.new(data: upload_file_paths)
end
@@ -37,7 +37,7 @@ module BulkImports
end
def after_run(_)
- FileUtils.remove_entry(tmp_dir) if Dir.exist?(tmp_dir)
+ FileUtils.remove_entry(tmpdir) if Dir.exist?(tmpdir)
end
private
@@ -46,17 +46,17 @@ module BulkImports
BulkImports::FileDownloadService.new(
configuration: context.configuration,
relative_url: context.entity.relation_download_url_path(relation),
- dir: tmp_dir,
+ tmpdir: tmpdir,
filename: targz_filename
)
end
def decompression_service
- BulkImports::FileDecompressionService.new(dir: tmp_dir, filename: targz_filename)
+ BulkImports::FileDecompressionService.new(tmpdir: tmpdir, filename: targz_filename)
end
def extraction_service
- BulkImports::ArchiveExtractionService.new(tmpdir: tmp_dir, filename: tar_filename)
+ BulkImports::ArchiveExtractionService.new(tmpdir: tmpdir, filename: tar_filename)
end
def relation
@@ -71,8 +71,8 @@ module BulkImports
"#{tar_filename}.gz"
end
- def tmp_dir
- @tmp_dir ||= Dir.mktmpdir('bulk_imports')
+ def tmpdir
+ @tmpdir ||= Dir.mktmpdir('bulk_imports')
end
def file_uploader
diff --git a/lib/bulk_imports/ndjson_pipeline.rb b/lib/bulk_imports/ndjson_pipeline.rb
index d5475a8b324..d85e51984df 100644
--- a/lib/bulk_imports/ndjson_pipeline.rb
+++ b/lib/bulk_imports/ndjson_pipeline.rb
@@ -68,7 +68,7 @@ module BulkImports
end
def after_run(_)
- extractor.remove_tmp_dir if extractor.respond_to?(:remove_tmp_dir)
+ extractor.remove_tmpdir if extractor.respond_to?(:remove_tmpdir)
end
def relation_class(relation_key)
diff --git a/lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb b/lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb
index 4d742225ff7..2492a023cbe 100644
--- a/lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb
+++ b/lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb
@@ -8,15 +8,16 @@ module BulkImports
transformer ::BulkImports::Common::Transformers::ProhibitedAttributesTransformer
- def extract(context)
- download_service(tmp_dir, context).execute
- decompression_service(tmp_dir).execute
+ def extract(_context)
+ download_service.execute
+ decompression_service.execute
+
project_attributes = json_decode(json_attributes)
BulkImports::Pipeline::ExtractedData.new(data: project_attributes)
end
- def transform(_, data)
+ def transform(_context, data)
subrelations = config.portable_relations_tree.keys.map(&:to_s)
Gitlab::ImportExport::AttributeCleaner.clean(
@@ -26,42 +27,42 @@ module BulkImports
).except(*subrelations)
end
- def load(_, data)
+ def load(_context, data)
portable.assign_attributes(data)
portable.reconcile_shared_runners_setting!
portable.drop_visibility_level!
portable.save!
end
- def after_run(_)
- FileUtils.remove_entry(tmp_dir)
+ def after_run(_context)
+ FileUtils.remove_entry(tmpdir) if Dir.exist?(tmpdir)
end
def json_attributes
- @json_attributes ||= File.read(File.join(tmp_dir, filename))
+ @json_attributes ||= File.read(File.join(tmpdir, filename))
end
private
- def tmp_dir
- @tmp_dir ||= Dir.mktmpdir
+ def tmpdir
+ @tmpdir ||= Dir.mktmpdir('bulk_imports')
end
def config
@config ||= BulkImports::FileTransfer.config_for(portable)
end
- def download_service(tmp_dir, context)
+ def download_service
@download_service ||= BulkImports::FileDownloadService.new(
configuration: context.configuration,
- relative_url: context.entity.relation_download_url_path(BulkImports::FileTransfer::BaseConfig::SELF_RELATION),
- dir: tmp_dir,
+ relative_url: context.entity.relation_download_url_path(BulkImports::FileTransfer::BaseConfig::SELF_RELATION),
+ tmpdir: tmpdir,
filename: compressed_filename
)
end
- def decompression_service(tmp_dir)
- @decompression_service ||= BulkImports::FileDecompressionService.new(dir: tmp_dir, filename: compressed_filename)
+ def decompression_service
+ @decompression_service ||= BulkImports::FileDecompressionService.new(tmpdir: tmpdir, filename: compressed_filename)
end
def compressed_filename
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
index f4cb49c20fc..fb5d8cfa32f 100644
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ b/lib/gitlab/database/gitlab_schemas.yml
@@ -462,6 +462,8 @@ security_findings: :gitlab_main
security_orchestration_policy_configurations: :gitlab_main
security_orchestration_policy_rule_schedules: :gitlab_main
security_scans: :gitlab_main
+security_training_providers: :gitlab_main
+security_trainings: :gitlab_main
self_managed_prometheus_alert_events: :gitlab_main
sent_notifications: :gitlab_main
sentry_issues: :gitlab_main
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ec4f91e39af..63cfffb0f2d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -26149,6 +26149,9 @@ msgstr ""
msgid "Pipelines|Are you sure you want to run this pipeline?"
msgstr ""
+msgid "Pipelines|Auto DevOps"
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -26161,7 +26164,7 @@ msgstr ""
msgid "Pipelines|CI/CD template to test and deploy your %{name} project."
msgstr ""
-msgid "Pipelines|Child pipeline"
+msgid "Pipelines|Child pipeline (%{link_start}parent%{link_end})"
msgstr ""
msgid "Pipelines|Clear runner caches"
@@ -26326,10 +26329,25 @@ msgstr ""
msgid "Pipelines|Visualize"
msgstr ""
+msgid "Pipelines|detached"
+msgstr ""
+
+msgid "Pipelines|error"
+msgstr ""
+
msgid "Pipelines|invalid"
msgstr ""
-msgid "Pipelines|parent"
+msgid "Pipelines|latest"
+msgstr ""
+
+msgid "Pipelines|stuck"
+msgstr ""
+
+msgid "Pipelines|train"
+msgstr ""
+
+msgid "Pipelines|yaml invalid"
msgstr ""
msgid "Pipeline|Actions"
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb
index be8567ee0b6..c2bd61155b1 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb
@@ -9,7 +9,7 @@ module QA
expect(project_access_token.token).not_to be_nil
project_access_token.revoke_via_ui!
- expect(page).to have_text("Revoked project access token #{project_access_token.name}!")
+ expect(page).to have_text("Revoked access token #{project_access_token.name}!")
end
after do
diff --git a/spec/lib/bulk_imports/common/extractors/ndjson_extractor_spec.rb b/spec/lib/bulk_imports/common/extractors/ndjson_extractor_spec.rb
index bd306233de8..d6e19a5fc85 100644
--- a/spec/lib/bulk_imports/common/extractors/ndjson_extractor_spec.rb
+++ b/spec/lib/bulk_imports/common/extractors/ndjson_extractor_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe BulkImports::Common::Extractors::NdjsonExtractor do
before do
allow(FileUtils).to receive(:remove_entry).with(any_args).and_call_original
- subject.instance_variable_set(:@tmp_dir, tmpdir)
+ subject.instance_variable_set(:@tmpdir, tmpdir)
end
after(:all) do
@@ -43,11 +43,11 @@ RSpec.describe BulkImports::Common::Extractors::NdjsonExtractor do
end
end
- describe '#remove_tmp_dir' do
+ describe '#remove_tmpdir' do
it 'removes tmp dir' do
expect(FileUtils).to receive(:remove_entry).with(tmpdir).once
- subject.remove_tmp_dir
+ subject.remove_tmpdir
end
end
end
diff --git a/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
index 3b5ea131d0d..9d43bb3ebfb 100644
--- a/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
@@ -3,10 +3,10 @@
require 'spec_helper'
RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline do
- let_it_be(:tmpdir) { Dir.mktmpdir }
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
+ let(:tmpdir) { Dir.mktmpdir }
let(:uploads_dir_path) { File.join(tmpdir, '72a497a02fe3ee09edae2ed06d390038') }
let(:upload_file_path) { File.join(uploads_dir_path, 'upload.txt')}
let(:tracker) { create(:bulk_import_tracker, entity: entity) }
@@ -80,10 +80,10 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline do
.with(
configuration: context.configuration,
relative_url: "/#{entity.pluralized_name}/test/export_relations/download?relation=uploads",
- dir: tmpdir,
+ tmpdir: tmpdir,
filename: 'uploads.tar.gz')
.and_return(download_service)
- expect(BulkImports::FileDecompressionService).to receive(:new).with(dir: tmpdir, filename: 'uploads.tar.gz').and_return(decompression_service)
+ expect(BulkImports::FileDecompressionService).to receive(:new).with(tmpdir: tmpdir, filename: 'uploads.tar.gz').and_return(decompression_service)
expect(BulkImports::ArchiveExtractionService).to receive(:new).with(tmpdir: tmpdir, filename: 'uploads.tar').and_return(extraction_service)
expect(download_service).to receive(:execute)
@@ -123,6 +123,31 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline do
end
end
end
+
+ describe '#after_run' do
+ before do
+ allow(Dir).to receive(:mktmpdir).with('bulk_imports').and_return(tmpdir)
+ end
+
+ it 'removes tmp dir' do
+ allow(FileUtils).to receive(:remove_entry).and_call_original
+ expect(FileUtils).to receive(:remove_entry).with(tmpdir).and_call_original
+
+ pipeline.after_run(nil)
+
+ expect(Dir.exist?(tmpdir)).to eq(false)
+ end
+
+ context 'when dir does not exist' do
+ it 'does not attempt to remove tmpdir' do
+ FileUtils.remove_entry(tmpdir)
+
+ expect(FileUtils).not_to receive(:remove_entry).with(tmpdir)
+
+ pipeline.after_run(nil)
+ end
+ end
+ end
end
context 'when importing to group' do
diff --git a/spec/lib/bulk_imports/projects/pipelines/project_attributes_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/project_attributes_pipeline_spec.rb
index 11c475318bb..df7ff5b8062 100644
--- a/spec/lib/bulk_imports/projects/pipelines/project_attributes_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/projects/pipelines/project_attributes_pipeline_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectAttributesPipeline do
subject(:pipeline) { described_class.new(context) }
before do
- allow(Dir).to receive(:mktmpdir).and_return(tmpdir)
+ allow(Dir).to receive(:mktmpdir).with('bulk_imports').and_return(tmpdir)
end
after do
@@ -95,13 +95,13 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectAttributesPipeline do
.with(
configuration: context.configuration,
relative_url: "/#{entity.pluralized_name}/#{entity.source_full_path}/export_relations/download?relation=self",
- dir: tmpdir,
+ tmpdir: tmpdir,
filename: 'self.json.gz')
.and_return(file_download_service)
expect(BulkImports::FileDecompressionService)
.to receive(:new)
- .with(dir: tmpdir, filename: 'self.json.gz')
+ .with(tmpdir: tmpdir, filename: 'self.json.gz')
.and_return(file_decompression_service)
expect(file_download_service).to receive(:execute)
@@ -156,4 +156,25 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectAttributesPipeline do
pipeline.json_attributes
end
end
+
+ describe '#after_run' do
+ it 'removes tmp dir' do
+ allow(FileUtils).to receive(:remove_entry).and_call_original
+ expect(FileUtils).to receive(:remove_entry).with(tmpdir).and_call_original
+
+ pipeline.after_run(nil)
+
+ expect(Dir.exist?(tmpdir)).to eq(false)
+ end
+
+ context 'when dir does not exist' do
+ it 'does not attempt to remove tmpdir' do
+ FileUtils.remove_entry(tmpdir)
+
+ expect(FileUtils).not_to receive(:remove_entry).with(tmpdir)
+
+ pipeline.after_run(nil)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index 8e9be209f89..bfb18c58806 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::ImportExport::UploadsSaver do
end
it 'copies the uploads to the export path' do
- saver.save
+ saver.save # rubocop:disable Rails/SaveBang
uploads = Dir.glob(File.join(shared.export_path, 'uploads/**/*')).map { |file| File.basename(file) }
@@ -54,7 +54,7 @@ RSpec.describe Gitlab::ImportExport::UploadsSaver do
end
it 'copies the uploads to the export path' do
- saver.save
+ saver.save # rubocop:disable Rails/SaveBang
uploads = Dir.glob(File.join(shared.export_path, 'uploads/**/*')).map { |file| File.basename(file) }
diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb
index f1284318687..1baf8749532 100644
--- a/spec/lib/gitlab/lets_encrypt/client_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe ::Gitlab::LetsEncrypt::Client do
context 'when private key is saved in settings' do
let!(:saved_private_key) do
key = OpenSSL::PKey::RSA.new(4096).to_pem
- Gitlab::CurrentSettings.current_application_settings.update(lets_encrypt_private_key: key)
+ Gitlab::CurrentSettings.current_application_settings.update!(lets_encrypt_private_key: key)
key
end
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index 1ef548ab29b..bc1d53b2ccb 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -100,7 +100,7 @@ RSpec.describe Gitlab::Middleware::Go do
context 'without access to the project', :sidekiq_inline do
before do
- project.team.find_member(current_user).destroy
+ project.team.find_member(current_user).destroy!
end
it_behaves_like 'unauthorized'
diff --git a/spec/lib/gitlab/shard_health_cache_spec.rb b/spec/lib/gitlab/shard_health_cache_spec.rb
index 5c47ac7e9a0..0c25cc7dab5 100644
--- a/spec/lib/gitlab/shard_health_cache_spec.rb
+++ b/spec/lib/gitlab/shard_health_cache_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
let(:shards) { %w(foo bar) }
before do
- described_class.update(shards)
+ described_class.update(shards) # rubocop:disable Rails/SaveBang
end
describe '.clear' do
@@ -24,7 +24,7 @@ RSpec.describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
it 'replaces the existing set' do
new_set = %w(test me more)
- described_class.update(new_set)
+ described_class.update(new_set) # rubocop:disable Rails/SaveBang
expect(described_class.cached_healthy_shards).to match_array(new_set)
end
@@ -36,7 +36,7 @@ RSpec.describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
end
it 'returns 0 if no shards are available' do
- described_class.update([])
+ described_class.update([]) # rubocop:disable Rails/SaveBang
expect(described_class.healthy_shard_count).to eq(0)
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 44cb18008d2..0fbdc09a206 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -213,7 +213,7 @@ RSpec.describe Notify do
subject { described_class.issue_due_email(recipient.id, issue.id) }
before do
- issue.update(due_date: Date.tomorrow)
+ issue.update!(due_date: Date.tomorrow)
end
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -1229,7 +1229,7 @@ RSpec.describe Notify do
end
context 'when a comment on an existing discussion' do
- let(:first_note) { create(model) }
+ let(:first_note) { create(model) } # rubocop:disable Rails/SaveBang
let(:note) { create(model, author: note_author, noteable: nil, in_reply_to: first_note) }
it 'contains an introduction' do
@@ -1505,7 +1505,7 @@ RSpec.describe Notify do
context 'member is not created by a user' do
before do
- group_member.update(created_by: nil)
+ group_member.update!(created_by: nil)
end
it_behaves_like 'no email is sent'
@@ -1513,7 +1513,7 @@ RSpec.describe Notify do
context 'member is a known user' do
before do
- group_member.update(user: create(:user))
+ group_member.update!(user: create(:user))
end
it_behaves_like 'no email is sent'
@@ -1737,7 +1737,7 @@ RSpec.describe Notify do
stub_config_setting(email_subject_suffix: 'A Nice Suffix')
perform_enqueued_jobs do
user.email = "new-email@mail.com"
- user.save
+ user.save!
end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 8f66978c311..6830a8daa3b 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -1438,6 +1438,16 @@ RSpec.describe Ci::Runner do
end
end
+ describe '.belonging_to_group_and_ancestors' do
+ let_it_be(:parent_group) { create(:group) }
+ let_it_be(:parent_runner) { create(:ci_runner, :group, groups: [parent_group]) }
+ let_it_be(:group) { create(:group, parent: parent_group) }
+
+ it 'returns the group runner from the parent group' do
+ expect(described_class.belonging_to_group_and_ancestors(group.id)).to contain_exactly(parent_runner)
+ end
+ end
+
describe '.belonging_to_group_or_project_descendants' do
it 'returns the specific group runners' do
group1 = create(:group)
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 6ca380a3cb9..305c0bd9df0 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -980,7 +980,7 @@ RSpec.describe API::Ci::Runners do
end
end
- describe 'GET /groups/:id/runners' do
+ shared_context 'GET /groups/:id/runners' do
context 'authorized user with maintainer privileges' do
it 'returns all runners' do
get api("/groups/#{group.id}/runners", user)
@@ -1048,6 +1048,16 @@ RSpec.describe API::Ci::Runners do
end
end
+ it_behaves_like 'GET /groups/:id/runners'
+
+ context 'when the FF ci_find_runners_by_ci_mirrors is disabled' do
+ before do
+ stub_feature_flags(ci_find_runners_by_ci_mirrors: false)
+ end
+
+ it_behaves_like 'GET /groups/:id/runners'
+ end
+
describe 'POST /projects/:id/runners' do
context 'authorized user' do
let_it_be(:project_runner2) { create(:ci_runner, :project, projects: [project2]) }
diff --git a/spec/services/bulk_imports/archive_extraction_service_spec.rb b/spec/services/bulk_imports/archive_extraction_service_spec.rb
index aa823d88010..da9df31cde9 100644
--- a/spec/services/bulk_imports/archive_extraction_service_spec.rb
+++ b/spec/services/bulk_imports/archive_extraction_service_spec.rb
@@ -34,9 +34,9 @@ RSpec.describe BulkImports::ArchiveExtractionService do
context 'when dir is not in tmpdir' do
it 'raises an error' do
- ['/etc', '/usr', '/', '/home', '', '/some/other/path', Rails.root].each do |path|
+ ['/etc', '/usr', '/', '/home', '/some/other/path', Rails.root.to_s].each do |path|
expect { described_class.new(tmpdir: path, filename: 'filename').execute }
- .to raise_error(BulkImports::Error, 'Invalid target directory')
+ .to raise_error(StandardError, "path #{path} is not allowed")
end
end
end
@@ -52,7 +52,7 @@ RSpec.describe BulkImports::ArchiveExtractionService do
context 'when filepath is being traversed' do
it 'raises an error' do
- expect { described_class.new(tmpdir: File.join(tmpdir, '../../../'), filename: 'name').execute }
+ expect { described_class.new(tmpdir: File.join(Dir.mktmpdir, 'test', '..'), filename: 'name').execute }
.to raise_error(Gitlab::Utils::PathTraversalAttackError, 'Invalid path')
end
end
diff --git a/spec/services/bulk_imports/file_decompression_service_spec.rb b/spec/services/bulk_imports/file_decompression_service_spec.rb
index 4e8f78c8243..1d6aa79a37f 100644
--- a/spec/services/bulk_imports/file_decompression_service_spec.rb
+++ b/spec/services/bulk_imports/file_decompression_service_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe BulkImports::FileDecompressionService do
FileUtils.remove_entry(tmpdir)
end
- subject { described_class.new(dir: tmpdir, filename: gz_filename) }
+ subject { described_class.new(tmpdir: tmpdir, filename: gz_filename) }
describe '#execute' do
it 'decompresses specified file' do
@@ -55,10 +55,18 @@ RSpec.describe BulkImports::FileDecompressionService do
end
context 'when dir is not in tmpdir' do
- subject { described_class.new(dir: '/etc', filename: 'filename') }
+ subject { described_class.new(tmpdir: '/etc', filename: 'filename') }
it 'raises an error' do
- expect { subject.execute }.to raise_error(described_class::ServiceError, 'Invalid target directory')
+ expect { subject.execute }.to raise_error(StandardError, 'path /etc is not allowed')
+ end
+ end
+
+ context 'when path is being traversed' do
+ subject { described_class.new(tmpdir: File.join(Dir.mktmpdir, 'test', '..'), filename: 'filename') }
+
+ it 'raises an error' do
+ expect { subject.execute }.to raise_error(Gitlab::Utils::PathTraversalAttackError, 'Invalid path')
end
end
@@ -69,7 +77,7 @@ RSpec.describe BulkImports::FileDecompressionService do
FileUtils.ln_s(File.join(tmpdir, gz_filename), symlink)
end
- subject { described_class.new(dir: tmpdir, filename: 'symlink.gz') }
+ subject { described_class.new(tmpdir: tmpdir, filename: 'symlink.gz') }
it 'raises an error and removes the file' do
expect { subject.execute }.to raise_error(described_class::ServiceError, 'Invalid file')
@@ -87,7 +95,7 @@ RSpec.describe BulkImports::FileDecompressionService do
subject.instance_variable_set(:@decompressed_filepath, symlink)
end
- subject { described_class.new(dir: tmpdir, filename: gz_filename) }
+ subject { described_class.new(tmpdir: tmpdir, filename: gz_filename) }
it 'raises an error and removes the file' do
expect { subject.execute }.to raise_error(described_class::ServiceError, 'Invalid file')
diff --git a/spec/services/bulk_imports/file_download_service_spec.rb b/spec/services/bulk_imports/file_download_service_spec.rb
index a24af9ae64d..bd664d6e996 100644
--- a/spec/services/bulk_imports/file_download_service_spec.rb
+++ b/spec/services/bulk_imports/file_download_service_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe BulkImports::FileDownloadService do
described_class.new(
configuration: config,
relative_url: '/test',
- dir: tmpdir,
+ tmpdir: tmpdir,
filename: filename,
file_size_limit: file_size_limit,
allowed_content_types: allowed_content_types
@@ -72,7 +72,7 @@ RSpec.describe BulkImports::FileDownloadService do
service = described_class.new(
configuration: double,
relative_url: '/test',
- dir: tmpdir,
+ tmpdir: tmpdir,
filename: filename,
file_size_limit: file_size_limit,
allowed_content_types: allowed_content_types
@@ -157,7 +157,7 @@ RSpec.describe BulkImports::FileDownloadService do
described_class.new(
configuration: config,
relative_url: '/test',
- dir: tmpdir,
+ tmpdir: tmpdir,
filename: 'symlink',
file_size_limit: file_size_limit,
allowed_content_types: allowed_content_types
@@ -179,7 +179,7 @@ RSpec.describe BulkImports::FileDownloadService do
described_class.new(
configuration: config,
relative_url: '/test',
- dir: '/etc',
+ tmpdir: '/etc',
filename: filename,
file_size_limit: file_size_limit,
allowed_content_types: allowed_content_types
@@ -188,8 +188,28 @@ RSpec.describe BulkImports::FileDownloadService do
it 'raises an error' do
expect { subject.execute }.to raise_error(
- described_class::ServiceError,
- 'Invalid target directory'
+ StandardError,
+ 'path /etc is not allowed'
+ )
+ end
+ end
+
+ context 'when dir path is being traversed' do
+ subject do
+ described_class.new(
+ configuration: config,
+ relative_url: '/test',
+ tmpdir: File.join(Dir.mktmpdir('bulk_imports'), 'test', '..'),
+ filename: filename,
+ file_size_limit: file_size_limit,
+ allowed_content_types: allowed_content_types
+ )
+ end
+
+ it 'raises an error' do
+ expect { subject.execute }.to raise_error(
+ Gitlab::Utils::PathTraversalAttackError,
+ 'Invalid path'
)
end
end