From 69a373f436b911b790e0324f7fb4b91c09521b80 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Mon, 13 Jul 2020 16:29:13 -0400 Subject: [PATCH 1/9] WIP: POC to improve coverage report feature --- app/models/ci/pipeline.rb | 9 +++++++ app/models/ci/pipeline_report_processor.rb | 12 ++++++++++ .../ci/generate_coverage_reports_service.rb | 21 ++++++++++++---- .../ci/pipeline_process_report_service.rb | 14 +++++++++++ app/uploaders/ci/processed_report_uploader.rb | 19 +++++++++++++++ app/workers/all_queues.yml | 8 +++++++ .../ci/pipeline_process_report_worker.rb | 14 +++++++++++ ..._create_pipeline_report_processor_table.rb | 24 +++++++++++++++++++ db/structure.sql | 24 +++++++++++++++++++ lib/gitlab/ci/reports/coverage_reports.rb | 4 ++++ 10 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 app/models/ci/pipeline_report_processor.rb create mode 100644 app/services/ci/pipeline_process_report_service.rb create mode 100644 app/uploaders/ci/processed_report_uploader.rb create mode 100644 app/workers/ci/pipeline_process_report_worker.rb create mode 100644 db/migrate/20200713200740_create_pipeline_report_processor_table.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 76af1b49ddcd6d..004cb7d3a95dd1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -83,6 +83,7 @@ class Pipeline < ApplicationRecord has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id has_many :latest_builds_report_results, through: :latest_builds, source: :report_results + has_one :processed_report, class_name: 'Ci::PipelineReportProcessor', inverse_of: :pipeline accepts_nested_attributes_for :variables, reject_if: :persisted? @@ -221,6 +222,10 @@ class Pipeline < ApplicationRecord AutoMergeProcessWorker.perform_async(merge_request.id) end + if pipeline.has_coverage_reports? + ::Ci::PipelineProcessReportWorker.perform_async(pipeline.id) + end + if pipeline.auto_devops_source? self.class.auto_devops_pipelines_completed_total.increment(status: pipeline.status) end @@ -910,6 +915,10 @@ def has_reports?(reports_scope) complete? && latest_report_builds(reports_scope).exists? end + def has_coverage_reports? + self.has_reports?(Ci::JobArtifact.coverage_reports) + end + def test_report_summary Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results) end diff --git a/app/models/ci/pipeline_report_processor.rb b/app/models/ci/pipeline_report_processor.rb new file mode 100644 index 00000000000000..e039d236a0c4c4 --- /dev/null +++ b/app/models/ci/pipeline_report_processor.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Ci + class PipelineReportProcessor < ApplicationRecord + include ObjectStorage::BackgroundMove + extend Gitlab::Ci::Model + + belongs_to :pipeline, inverse_of: :processed_report + + mount_uploader :coverage_report, Ci::ProcessedReportUploader + end +end diff --git a/app/services/ci/generate_coverage_reports_service.rb b/app/services/ci/generate_coverage_reports_service.rb index ebd1eaf0bade17..6c3b9c29447ee3 100644 --- a/app/services/ci/generate_coverage_reports_service.rb +++ b/app/services/ci/generate_coverage_reports_service.rb @@ -9,11 +9,14 @@ module Ci class GenerateCoverageReportsService < CompareReportsBaseService def execute(base_pipeline, head_pipeline) merge_request = MergeRequest.find_by_id(params[:id]) - { - status: :parsed, - key: key(base_pipeline, head_pipeline), - data: head_pipeline.coverage_reports.pick(merge_request.new_paths) - } + head_pipeline.processed_report.coverage_report.open do |file| + raw_coverage = Gitlab::Json.parse(file.read) + { + status: :parsed, + key: key(base_pipeline, head_pipeline), + data: { files: pick(raw_coverage, merge_request.new_paths) } + } + end rescue => e Gitlab::ErrorTracking.track_exception(e, project_id: project.id) { @@ -26,5 +29,13 @@ def execute(base_pipeline, head_pipeline) def latest?(base_pipeline, head_pipeline, data) data&.fetch(:key, nil) == key(base_pipeline, head_pipeline) end + + private + + def pick(files, keys) + files.select do |key| + keys.include?(key) + end + end end end diff --git a/app/services/ci/pipeline_process_report_service.rb b/app/services/ci/pipeline_process_report_service.rb new file mode 100644 index 00000000000000..78b183e83443bb --- /dev/null +++ b/app/services/ci/pipeline_process_report_service.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Ci + class PipelineProcessReportService + def execute(pipeline) + return unless Feature.enabled?(:coverage_report_view, pipeline.project) + return unless pipeline.has_coverage_reports? + + report = pipeline.create_processed_report + report_file = CarrierWaveStringFile.new(pipeline.coverage_reports.to_json) + report.update(coverage_report: report_file) + end + end +end diff --git a/app/uploaders/ci/processed_report_uploader.rb b/app/uploaders/ci/processed_report_uploader.rb new file mode 100644 index 00000000000000..ae7164df702f59 --- /dev/null +++ b/app/uploaders/ci/processed_report_uploader.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Ci + class ProcessedReportUploader < GitlabUploader + include ObjectStorage::Concern + + storage_options Gitlab.config.artifacts + + alias_method :upload, :model + + def filename + "report-#{model.pipeline_id}-#{model.id}" + end + + def store_dir + File.join(model.model_name.plural, "pipeline-#{model.pipeline_id}") + end + end +end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 3d243cfd00c3bd..5189f2d54cbac0 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -891,6 +891,14 @@ :weight: 1 :idempotent: true :tags: [] +- :name: pipeline_background:ci_pipeline_process_report + :feature_category: :continuous_integration + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: pipeline_background:ci_ref_delete_unlock_artifacts :feature_category: :continuous_integration :has_external_dependencies: diff --git a/app/workers/ci/pipeline_process_report_worker.rb b/app/workers/ci/pipeline_process_report_worker.rb new file mode 100644 index 00000000000000..9067efb6ec51d3 --- /dev/null +++ b/app/workers/ci/pipeline_process_report_worker.rb @@ -0,0 +1,14 @@ +module Ci + class PipelineProcessReportWorker + include ApplicationWorker + include PipelineBackgroundQueue + + idempotent! + + def perform(pipeline_id) + Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline| + Ci::PipelineProcessReportService.new.execute(pipeline) + end + end + end +end diff --git a/db/migrate/20200713200740_create_pipeline_report_processor_table.rb b/db/migrate/20200713200740_create_pipeline_report_processor_table.rb new file mode 100644 index 00000000000000..74dda5238c9e8a --- /dev/null +++ b/db/migrate/20200713200740_create_pipeline_report_processor_table.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class CreatePipelineReportProcessorTable < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + unless table_exists?(:ci_pipeline_report_processors) + create_table :ci_pipeline_report_processors do |t| + t.bigint :pipeline_id, null: false, index: true, foreign_key: { to_table: :ci_pipelines, on_delete: :cascade } + t.text :coverage_report, null: false + end + end + + add_text_limit :ci_pipeline_report_processors, :coverage_report, 255 + end + + def down + drop_table :ci_pipeline_report_processors + end +end diff --git a/db/structure.sql b/db/structure.sql index e1c40897c4ef73..adc2f6bc0e8913 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -10009,6 +10009,22 @@ CREATE SEQUENCE public.ci_pipeline_messages_id_seq ALTER SEQUENCE public.ci_pipeline_messages_id_seq OWNED BY public.ci_pipeline_messages.id; +CREATE TABLE public.ci_pipeline_report_processors ( + id bigint NOT NULL, + pipeline_id bigint NOT NULL, + coverage_report text NOT NULL, + CONSTRAINT check_5a3861dba9 CHECK ((char_length(coverage_report) <= 255)) +); + +CREATE SEQUENCE public.ci_pipeline_report_processors_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE public.ci_pipeline_report_processors_id_seq OWNED BY public.ci_pipeline_report_processors.id; + CREATE TABLE public.ci_pipeline_schedule_variables ( id integer NOT NULL, key character varying NOT NULL, @@ -16604,6 +16620,8 @@ ALTER TABLE ONLY public.ci_pipeline_chat_data ALTER COLUMN id SET DEFAULT nextva ALTER TABLE ONLY public.ci_pipeline_messages ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_messages_id_seq'::regclass); +ALTER TABLE ONLY public.ci_pipeline_report_processors ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_report_processors_id_seq'::regclass); + ALTER TABLE ONLY public.ci_pipeline_schedule_variables ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_schedule_variables_id_seq'::regclass); ALTER TABLE ONLY public.ci_pipeline_schedules ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_schedules_id_seq'::regclass); @@ -17539,6 +17557,9 @@ ALTER TABLE ONLY public.ci_pipeline_chat_data ALTER TABLE ONLY public.ci_pipeline_messages ADD CONSTRAINT ci_pipeline_messages_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.ci_pipeline_report_processors + ADD CONSTRAINT ci_pipeline_report_processors_pkey PRIMARY KEY (id); + ALTER TABLE ONLY public.ci_pipeline_schedule_variables ADD CONSTRAINT ci_pipeline_schedule_variables_pkey PRIMARY KEY (id); @@ -19000,6 +19021,8 @@ CREATE UNIQUE INDEX index_ci_pipeline_chat_data_on_pipeline_id ON public.ci_pipe CREATE INDEX index_ci_pipeline_messages_on_pipeline_id ON public.ci_pipeline_messages USING btree (pipeline_id); +CREATE INDEX index_ci_pipeline_report_processors_on_pipeline_id ON public.ci_pipeline_report_processors USING btree (pipeline_id); + CREATE UNIQUE INDEX index_ci_pipeline_schedule_variables_on_schedule_id_and_key ON public.ci_pipeline_schedule_variables USING btree (pipeline_schedule_id, key); CREATE INDEX index_ci_pipeline_schedules_on_next_run_at_and_active ON public.ci_pipeline_schedules USING btree (next_run_at, active); @@ -23990,6 +24013,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200713071042 20200713141854 20200713152443 +20200713200740 20200714075739 20200715124210 20200715135130 diff --git a/lib/gitlab/ci/reports/coverage_reports.rb b/lib/gitlab/ci/reports/coverage_reports.rb index 31afb636d2f39d..2e616ddc0b4d88 100644 --- a/lib/gitlab/ci/reports/coverage_reports.rb +++ b/lib/gitlab/ci/reports/coverage_reports.rb @@ -18,6 +18,10 @@ def pick(keys) { files: coverage_files } end + def to_json + ::Gitlab::Json.generate(files) + end + def add_file(name, line_coverage) if files[name].present? line_coverage.each { |line, hits| combine_lines(name, line, hits) } -- GitLab From 4d76a8e68e95e4c3419e7e45543b74d18996b907 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Wed, 22 Jul 2020 12:14:29 -0400 Subject: [PATCH 2/9] Introduce PipelineArtifact --- app/models/ci/pipeline.rb | 2 +- app/models/ci/pipeline_artifact.rb | 31 +++++++++ app/models/ci/pipeline_report_processor.rb | 12 ---- app/models/project.rb | 1 + ..._create_pipeline_report_processor_table.rb | 24 ------- ...00722152956_create_ci_pipeline_artifact.rb | 30 +++++++++ db/structure.sql | 63 ++++++++++++------- 7 files changed, 102 insertions(+), 61 deletions(-) create mode 100644 app/models/ci/pipeline_artifact.rb delete mode 100644 app/models/ci/pipeline_report_processor.rb delete mode 100644 db/migrate/20200713200740_create_pipeline_report_processor_table.rb create mode 100644 db/migrate/20200722152956_create_ci_pipeline_artifact.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 004cb7d3a95dd1..a973de1e5e9b11 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -83,7 +83,7 @@ class Pipeline < ApplicationRecord has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id has_many :latest_builds_report_results, through: :latest_builds, source: :report_results - has_one :processed_report, class_name: 'Ci::PipelineReportProcessor', inverse_of: :pipeline + has_many :pipeline_artifacts, class_name: 'Ci::PipelineArtifact', inverse_of: :pipeline accepts_nested_attributes_for :variables, reject_if: :persisted? diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb new file mode 100644 index 00000000000000..9c37bef36736dd --- /dev/null +++ b/app/models/ci/pipeline_artifact.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# This class is being used to persist generated report common logic for creating new controllers in a pipeline context + +module Ci + class PipelineArtifact < ApplicationRecord + include ObjectStorage::BackgroundMove + extend Gitlab::Ci::Model + + NotSupportedAdapterError = Class.new(StandardError) + + belongs_to :project, class_name: "Project", inverse_of: :pipeline_artifacts + belongs_to :pipeline, class_name: "Ci::Pipeline", inverse_of: :pipeline_artifacts + + # mount_uploader :file, Ci::PipelineUploader + + enum file_type: { + coverate_report: 1, + } + + enum file_format: { + raw: 1, + zip: 2, + gzip: 3 + }, _suffix: true + + REPORT_TYPES = { + coverage: :raw + } + end +end diff --git a/app/models/ci/pipeline_report_processor.rb b/app/models/ci/pipeline_report_processor.rb deleted file mode 100644 index e039d236a0c4c4..00000000000000 --- a/app/models/ci/pipeline_report_processor.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Ci - class PipelineReportProcessor < ApplicationRecord - include ObjectStorage::BackgroundMove - extend Gitlab::Ci::Model - - belongs_to :pipeline, inverse_of: :processed_report - - mount_uploader :coverage_report, Ci::ProcessedReportUploader - end -end diff --git a/app/models/project.rb b/app/models/project.rb index 92d2c85e99ab66..7a6e3e7356c403 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -301,6 +301,7 @@ class Project < ApplicationRecord has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks has_many :build_report_results, class_name: 'Ci::BuildReportResult', inverse_of: :project has_many :job_artifacts, class_name: 'Ci::JobArtifact' + has_many :pipeline_artifacts, class_name: 'Ci::PipelineArtifact', inverse_of: :project has_many :runner_projects, class_name: 'Ci::RunnerProject', inverse_of: :project has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :variables, class_name: 'Ci::Variable' diff --git a/db/migrate/20200713200740_create_pipeline_report_processor_table.rb b/db/migrate/20200713200740_create_pipeline_report_processor_table.rb deleted file mode 100644 index 74dda5238c9e8a..00000000000000 --- a/db/migrate/20200713200740_create_pipeline_report_processor_table.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -class CreatePipelineReportProcessorTable < ActiveRecord::Migration[6.0] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - def up - unless table_exists?(:ci_pipeline_report_processors) - create_table :ci_pipeline_report_processors do |t| - t.bigint :pipeline_id, null: false, index: true, foreign_key: { to_table: :ci_pipelines, on_delete: :cascade } - t.text :coverage_report, null: false - end - end - - add_text_limit :ci_pipeline_report_processors, :coverage_report, 255 - end - - def down - drop_table :ci_pipeline_report_processors - end -end diff --git a/db/migrate/20200722152956_create_ci_pipeline_artifact.rb b/db/migrate/20200722152956_create_ci_pipeline_artifact.rb new file mode 100644 index 00000000000000..ca66db63f6e49b --- /dev/null +++ b/db/migrate/20200722152956_create_ci_pipeline_artifact.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class CreateCiPipelineArtifact < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + unless table_exists?(:ci_pipeline_artifacts) + create_table :ci_pipeline_artifacts do |t| + t.timestamps_with_timezone + t.references :pipeline, foreign_key: { to_table: :ci_pipelines, on_delete: :cascade }, index: true, null: false + t.references :project, foreign_key: { on_delete: :cascade }, index: true, null: false + t.integer :file_type, null: false + t.integer :size, null: false + t.integer :file_store, null: false, default: 1 + t.integer :file_format, null: false + t.text :file, null: false + end + end + + add_text_limit :ci_pipeline_artifacts, :file, 255 + end + + def down + drop_table(:ci_pipeline_artifacts) + end +end diff --git a/db/structure.sql b/db/structure.sql index adc2f6bc0e8913..79f46886b369ed 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9976,6 +9976,29 @@ CREATE SEQUENCE public.ci_job_variables_id_seq ALTER SEQUENCE public.ci_job_variables_id_seq OWNED BY public.ci_job_variables.id; +CREATE TABLE public.ci_pipeline_artifacts ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + pipeline_id bigint NOT NULL, + project_id bigint NOT NULL, + file_type integer NOT NULL, + size integer NOT NULL, + file_store integer DEFAULT 1 NOT NULL, + file_format integer NOT NULL, + file text NOT NULL, + CONSTRAINT check_191b5850ec CHECK ((char_length(file) <= 255)) +); + +CREATE SEQUENCE public.ci_pipeline_artifacts_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE public.ci_pipeline_artifacts_id_seq OWNED BY public.ci_pipeline_artifacts.id; + CREATE TABLE public.ci_pipeline_chat_data ( id bigint NOT NULL, pipeline_id integer NOT NULL, @@ -10009,22 +10032,6 @@ CREATE SEQUENCE public.ci_pipeline_messages_id_seq ALTER SEQUENCE public.ci_pipeline_messages_id_seq OWNED BY public.ci_pipeline_messages.id; -CREATE TABLE public.ci_pipeline_report_processors ( - id bigint NOT NULL, - pipeline_id bigint NOT NULL, - coverage_report text NOT NULL, - CONSTRAINT check_5a3861dba9 CHECK ((char_length(coverage_report) <= 255)) -); - -CREATE SEQUENCE public.ci_pipeline_report_processors_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - -ALTER SEQUENCE public.ci_pipeline_report_processors_id_seq OWNED BY public.ci_pipeline_report_processors.id; - CREATE TABLE public.ci_pipeline_schedule_variables ( id integer NOT NULL, key character varying NOT NULL, @@ -16616,12 +16623,12 @@ ALTER TABLE ONLY public.ci_job_artifacts ALTER COLUMN id SET DEFAULT nextval('pu ALTER TABLE ONLY public.ci_job_variables ALTER COLUMN id SET DEFAULT nextval('public.ci_job_variables_id_seq'::regclass); +ALTER TABLE ONLY public.ci_pipeline_artifacts ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_artifacts_id_seq'::regclass); + ALTER TABLE ONLY public.ci_pipeline_chat_data ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_chat_data_id_seq'::regclass); ALTER TABLE ONLY public.ci_pipeline_messages ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_messages_id_seq'::regclass); -ALTER TABLE ONLY public.ci_pipeline_report_processors ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_report_processors_id_seq'::regclass); - ALTER TABLE ONLY public.ci_pipeline_schedule_variables ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_schedule_variables_id_seq'::regclass); ALTER TABLE ONLY public.ci_pipeline_schedules ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_schedules_id_seq'::regclass); @@ -17551,15 +17558,15 @@ ALTER TABLE ONLY public.ci_job_artifacts ALTER TABLE ONLY public.ci_job_variables ADD CONSTRAINT ci_job_variables_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.ci_pipeline_artifacts + ADD CONSTRAINT ci_pipeline_artifacts_pkey PRIMARY KEY (id); + ALTER TABLE ONLY public.ci_pipeline_chat_data ADD CONSTRAINT ci_pipeline_chat_data_pkey PRIMARY KEY (id); ALTER TABLE ONLY public.ci_pipeline_messages ADD CONSTRAINT ci_pipeline_messages_pkey PRIMARY KEY (id); -ALTER TABLE ONLY public.ci_pipeline_report_processors - ADD CONSTRAINT ci_pipeline_report_processors_pkey PRIMARY KEY (id); - ALTER TABLE ONLY public.ci_pipeline_schedule_variables ADD CONSTRAINT ci_pipeline_schedule_variables_pkey PRIMARY KEY (id); @@ -19015,14 +19022,16 @@ CREATE INDEX index_ci_job_variables_on_job_id ON public.ci_job_variables USING b CREATE UNIQUE INDEX index_ci_job_variables_on_key_and_job_id ON public.ci_job_variables USING btree (key, job_id); +CREATE INDEX index_ci_pipeline_artifacts_on_pipeline_id ON public.ci_pipeline_artifacts USING btree (pipeline_id); + +CREATE INDEX index_ci_pipeline_artifacts_on_project_id ON public.ci_pipeline_artifacts USING btree (project_id); + CREATE INDEX index_ci_pipeline_chat_data_on_chat_name_id ON public.ci_pipeline_chat_data USING btree (chat_name_id); CREATE UNIQUE INDEX index_ci_pipeline_chat_data_on_pipeline_id ON public.ci_pipeline_chat_data USING btree (pipeline_id); CREATE INDEX index_ci_pipeline_messages_on_pipeline_id ON public.ci_pipeline_messages USING btree (pipeline_id); -CREATE INDEX index_ci_pipeline_report_processors_on_pipeline_id ON public.ci_pipeline_report_processors USING btree (pipeline_id); - CREATE UNIQUE INDEX index_ci_pipeline_schedule_variables_on_schedule_id_and_key ON public.ci_pipeline_schedule_variables USING btree (pipeline_schedule_id, key); CREATE INDEX index_ci_pipeline_schedules_on_next_run_at_and_active ON public.ci_pipeline_schedules USING btree (next_run_at, active); @@ -22016,6 +22025,9 @@ ALTER TABLE ONLY public.vulnerability_feedback ALTER TABLE ONLY public.user_custom_attributes ADD CONSTRAINT fk_rails_47b91868a8 FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; +ALTER TABLE ONLY public.ci_pipeline_artifacts + ADD CONSTRAINT fk_rails_4a70390ca6 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE; + ALTER TABLE ONLY public.group_deletion_schedules ADD CONSTRAINT fk_rails_4b8c694a6c FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE; @@ -22472,6 +22484,9 @@ ALTER TABLE ONLY public.resource_milestone_events ALTER TABLE ONLY public.term_agreements ADD CONSTRAINT fk_rails_a88721bcdf FOREIGN KEY (term_id) REFERENCES public.application_setting_terms(id); +ALTER TABLE ONLY public.ci_pipeline_artifacts + ADD CONSTRAINT fk_rails_a9e811a466 FOREIGN KEY (pipeline_id) REFERENCES public.ci_pipelines(id) ON DELETE CASCADE; + ALTER TABLE ONLY public.merge_request_user_mentions ADD CONSTRAINT fk_rails_aa1b2961b1 FOREIGN KEY (merge_request_id) REFERENCES public.merge_requests(id) ON DELETE CASCADE; @@ -24013,7 +24028,6 @@ COPY "schema_migrations" (version) FROM STDIN; 20200713071042 20200713141854 20200713152443 -20200713200740 20200714075739 20200715124210 20200715135130 @@ -24025,5 +24039,6 @@ COPY "schema_migrations" (version) FROM STDIN; 20200718040200 20200718040300 20200720154123 +20200722152956 \. -- GitLab From 4c16f6d2a3f1c230d7808c171e9a1967635503d8 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Wed, 22 Jul 2020 13:51:20 -0400 Subject: [PATCH 3/9] Add PipelineArtifactUploader --- app/models/ci/pipeline_artifact.rb | 2 +- .../ci/pipeline_artifact_uploader.rb | 14 ++++++++++++++ app/uploaders/ci/processed_report_uploader.rb | 19 ------------------- app/uploaders/job_artifact_uploader.rb | 6 ------ 4 files changed, 15 insertions(+), 26 deletions(-) create mode 100644 app/uploaders/ci/pipeline_artifact_uploader.rb delete mode 100644 app/uploaders/ci/processed_report_uploader.rb diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb index 9c37bef36736dd..04d4c976784734 100644 --- a/app/models/ci/pipeline_artifact.rb +++ b/app/models/ci/pipeline_artifact.rb @@ -12,7 +12,7 @@ class PipelineArtifact < ApplicationRecord belongs_to :project, class_name: "Project", inverse_of: :pipeline_artifacts belongs_to :pipeline, class_name: "Ci::Pipeline", inverse_of: :pipeline_artifacts - # mount_uploader :file, Ci::PipelineUploader + mount_uploader :file, Ci::PipelineArtifactUploader enum file_type: { coverate_report: 1, diff --git a/app/uploaders/ci/pipeline_artifact_uploader.rb b/app/uploaders/ci/pipeline_artifact_uploader.rb new file mode 100644 index 00000000000000..8db175f6b7928e --- /dev/null +++ b/app/uploaders/ci/pipeline_artifact_uploader.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Ci + class PipelineArtifactUploader < JobArtifactUploader + def store_dir + super + end + + def hashed_path + File.join(disk_hash[0..1], disk_hash[2..3], disk_hash, + model.created_at.utc.strftime('%Y_%m_%d'), model.pipeline_id.to_s, model.id.to_s) + end + end +end diff --git a/app/uploaders/ci/processed_report_uploader.rb b/app/uploaders/ci/processed_report_uploader.rb deleted file mode 100644 index ae7164df702f59..00000000000000 --- a/app/uploaders/ci/processed_report_uploader.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Ci - class ProcessedReportUploader < GitlabUploader - include ObjectStorage::Concern - - storage_options Gitlab.config.artifacts - - alias_method :upload, :model - - def filename - "report-#{model.pipeline_id}-#{model.id}" - end - - def store_dir - File.join(model.model_name.plural, "pipeline-#{model.pipeline_id}") - end - end -end diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index 400f0b3dcc6ff2..53d04bdc1c7bab 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -21,8 +21,6 @@ def store_dir dynamic_segment end - private - def dynamic_segment raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id @@ -43,8 +41,4 @@ def hashed_path def legacy_path File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.job_id.to_s) end - - def disk_hash - @disk_hash ||= Digest::SHA2.hexdigest(model.project_id.to_s) - end end -- GitLab From 0cc7536a8249999acebbd8b55aa1f51aa7ad5098 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Wed, 22 Jul 2020 16:28:46 -0400 Subject: [PATCH 4/9] Rename service --- app/models/ci/pipeline.rb | 2 +- app/models/ci/pipeline_artifact.rb | 14 +++++++------- app/services/ci/pipeline_artifact_service.rb | 18 ++++++++++++++++++ .../ci/pipeline_process_report_service.rb | 14 -------------- app/uploaders/job_artifact_uploader.rb | 4 ++++ app/workers/all_queues.yml | 6 +++--- ...t_worker.rb => pipeline_artifact_worker.rb} | 4 ++-- ...200722152956_create_ci_pipeline_artifact.rb | 4 ++-- db/structure.sql | 6 +++--- 9 files changed, 40 insertions(+), 32 deletions(-) create mode 100644 app/services/ci/pipeline_artifact_service.rb delete mode 100644 app/services/ci/pipeline_process_report_service.rb rename app/workers/ci/{pipeline_process_report_worker.rb => pipeline_artifact_worker.rb} (68%) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index a973de1e5e9b11..29b6ceebdc1a43 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -223,7 +223,7 @@ class Pipeline < ApplicationRecord end if pipeline.has_coverage_reports? - ::Ci::PipelineProcessReportWorker.perform_async(pipeline.id) + ::Ci::PipelineArtifactWorker.perform_async(pipeline.id) end if pipeline.auto_devops_source? diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb index 04d4c976784734..bb6a47628893fb 100644 --- a/app/models/ci/pipeline_artifact.rb +++ b/app/models/ci/pipeline_artifact.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# This class is being used to persist generated report common logic for creating new controllers in a pipeline context +# This class is being used to persist generated report in a pipeline context module Ci class PipelineArtifact < ApplicationRecord @@ -15,17 +15,17 @@ class PipelineArtifact < ApplicationRecord mount_uploader :file, Ci::PipelineArtifactUploader enum file_type: { - coverate_report: 1, + coverage_report: 1, } - enum file_format: { - raw: 1, - zip: 2, - gzip: 3 - }, _suffix: true + enum file_format: REPORT_TYPES = { coverage: :raw } + + def hashed_path? + true + end end end diff --git a/app/services/ci/pipeline_artifact_service.rb b/app/services/ci/pipeline_artifact_service.rb new file mode 100644 index 00000000000000..fe89f11e384589 --- /dev/null +++ b/app/services/ci/pipeline_artifact_service.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Ci + class PipelineArtifactService + def execute(pipeline) + return unless Feature.enabled?(:coverage_report_view, pipeline.project) + return unless pipeline.has_coverage_reports? + + report = pipeline.pipeline_artifacts.create!( + project_id: pipeline.project_id, + file_type: 1, + size: pipeline.job_artifacts.coverage_reports.first.size, + file_format: 3, + file: CarrierWaveStringFile.new(pipeline.coverage_reports.to_json) + ) + end + end +end diff --git a/app/services/ci/pipeline_process_report_service.rb b/app/services/ci/pipeline_process_report_service.rb deleted file mode 100644 index 78b183e83443bb..00000000000000 --- a/app/services/ci/pipeline_process_report_service.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Ci - class PipelineProcessReportService - def execute(pipeline) - return unless Feature.enabled?(:coverage_report_view, pipeline.project) - return unless pipeline.has_coverage_reports? - - report = pipeline.create_processed_report - report_file = CarrierWaveStringFile.new(pipeline.coverage_reports.to_json) - report.update(coverage_report: report_file) - end - end -end diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index 53d04bdc1c7bab..0c09e266172b93 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -41,4 +41,8 @@ def hashed_path def legacy_path File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.job_id.to_s) end + + def disk_hash + @disk_hash ||= Digest::SHA2.hexdigest(model.project_id.to_s) + end end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 5189f2d54cbac0..b49b9543c67048 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -883,7 +883,7 @@ :weight: 1 :idempotent: true :tags: [] -- :name: pipeline_background:ci_pipeline_success_unlock_artifacts +- :name: pipeline_background:ci_pipeline_artifact :feature_category: :continuous_integration :has_external_dependencies: :urgency: :low @@ -891,9 +891,9 @@ :weight: 1 :idempotent: true :tags: [] -- :name: pipeline_background:ci_pipeline_process_report +- :name: pipeline_background:ci_pipeline_success_unlock_artifacts :feature_category: :continuous_integration - :has_external_dependencies: + :has_external_dependencies: :urgency: :low :resource_boundary: :unknown :weight: 1 diff --git a/app/workers/ci/pipeline_process_report_worker.rb b/app/workers/ci/pipeline_artifact_worker.rb similarity index 68% rename from app/workers/ci/pipeline_process_report_worker.rb rename to app/workers/ci/pipeline_artifact_worker.rb index 9067efb6ec51d3..80ea4785003796 100644 --- a/app/workers/ci/pipeline_process_report_worker.rb +++ b/app/workers/ci/pipeline_artifact_worker.rb @@ -1,5 +1,5 @@ module Ci - class PipelineProcessReportWorker + class PipelineArtifactWorker include ApplicationWorker include PipelineBackgroundQueue @@ -7,7 +7,7 @@ class PipelineProcessReportWorker def perform(pipeline_id) Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline| - Ci::PipelineProcessReportService.new.execute(pipeline) + Ci::PipelineArtifactService.new.execute(pipeline) end end end diff --git a/db/migrate/20200722152956_create_ci_pipeline_artifact.rb b/db/migrate/20200722152956_create_ci_pipeline_artifact.rb index ca66db63f6e49b..5d0e548711a817 100644 --- a/db/migrate/20200722152956_create_ci_pipeline_artifact.rb +++ b/db/migrate/20200722152956_create_ci_pipeline_artifact.rb @@ -10,14 +10,14 @@ class CreateCiPipelineArtifact < ActiveRecord::Migration[6.0] def up unless table_exists?(:ci_pipeline_artifacts) create_table :ci_pipeline_artifacts do |t| - t.timestamps_with_timezone t.references :pipeline, foreign_key: { to_table: :ci_pipelines, on_delete: :cascade }, index: true, null: false t.references :project, foreign_key: { on_delete: :cascade }, index: true, null: false + t.timestamps_with_timezone t.integer :file_type, null: false t.integer :size, null: false t.integer :file_store, null: false, default: 1 t.integer :file_format, null: false - t.text :file, null: false + t.text :file end end diff --git a/db/structure.sql b/db/structure.sql index 79f46886b369ed..9e264b8ab14fab 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9978,15 +9978,15 @@ ALTER SEQUENCE public.ci_job_variables_id_seq OWNED BY public.ci_job_variables.i CREATE TABLE public.ci_pipeline_artifacts ( id bigint NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, pipeline_id bigint NOT NULL, project_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, file_type integer NOT NULL, size integer NOT NULL, file_store integer DEFAULT 1 NOT NULL, file_format integer NOT NULL, - file text NOT NULL, + file text, CONSTRAINT check_191b5850ec CHECK ((char_length(file) <= 255)) ); -- GitLab From c88ca972ea76dab0977c48a22927dd2348e4ee1c Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Wed, 22 Jul 2020 17:02:59 -0400 Subject: [PATCH 5/9] Makes POC working again --- app/models/ci/pipeline_artifact.rb | 6 +++--- app/services/ci/generate_coverage_reports_service.rb | 2 +- app/services/ci/pipeline_artifact_service.rb | 2 +- app/uploaders/ci/pipeline_artifact_uploader.rb | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb index bb6a47628893fb..6b3d73c2e022ab 100644 --- a/app/models/ci/pipeline_artifact.rb +++ b/app/models/ci/pipeline_artifact.rb @@ -7,8 +7,6 @@ class PipelineArtifact < ApplicationRecord include ObjectStorage::BackgroundMove extend Gitlab::Ci::Model - NotSupportedAdapterError = Class.new(StandardError) - belongs_to :project, class_name: "Project", inverse_of: :pipeline_artifacts belongs_to :pipeline, class_name: "Ci::Pipeline", inverse_of: :pipeline_artifacts @@ -18,7 +16,9 @@ class PipelineArtifact < ApplicationRecord coverage_report: 1, } - enum file_format: + enum file_format: { + json: 1 + }, _suffix: true REPORT_TYPES = { coverage: :raw diff --git a/app/services/ci/generate_coverage_reports_service.rb b/app/services/ci/generate_coverage_reports_service.rb index 6c3b9c29447ee3..ede8ee8d53afcb 100644 --- a/app/services/ci/generate_coverage_reports_service.rb +++ b/app/services/ci/generate_coverage_reports_service.rb @@ -9,7 +9,7 @@ module Ci class GenerateCoverageReportsService < CompareReportsBaseService def execute(base_pipeline, head_pipeline) merge_request = MergeRequest.find_by_id(params[:id]) - head_pipeline.processed_report.coverage_report.open do |file| + head_pipeline.pipeline_artifacts.first.file.open do |file| raw_coverage = Gitlab::Json.parse(file.read) { status: :parsed, diff --git a/app/services/ci/pipeline_artifact_service.rb b/app/services/ci/pipeline_artifact_service.rb index fe89f11e384589..03583bf69df9b0 100644 --- a/app/services/ci/pipeline_artifact_service.rb +++ b/app/services/ci/pipeline_artifact_service.rb @@ -10,7 +10,7 @@ def execute(pipeline) project_id: pipeline.project_id, file_type: 1, size: pipeline.job_artifacts.coverage_reports.first.size, - file_format: 3, + file_format: 1, file: CarrierWaveStringFile.new(pipeline.coverage_reports.to_json) ) end diff --git a/app/uploaders/ci/pipeline_artifact_uploader.rb b/app/uploaders/ci/pipeline_artifact_uploader.rb index 8db175f6b7928e..cc0ebd04a43d08 100644 --- a/app/uploaders/ci/pipeline_artifact_uploader.rb +++ b/app/uploaders/ci/pipeline_artifact_uploader.rb @@ -2,11 +2,11 @@ module Ci class PipelineArtifactUploader < JobArtifactUploader + alias_method :upload, :model + def store_dir - super - end + raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id - def hashed_path File.join(disk_hash[0..1], disk_hash[2..3], disk_hash, model.created_at.utc.strftime('%Y_%m_%d'), model.pipeline_id.to_s, model.id.to_s) end -- GitLab From 8b3ab6275376b73c4f737a993e1879fe19b2e69a Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Thu, 23 Jul 2020 14:32:04 -0400 Subject: [PATCH 6/9] Add smallint and simplify service --- app/models/ci/pipeline_artifact.rb | 5 ++--- app/services/ci/pipeline_artifact_service.rb | 5 ++++- db/migrate/20200722152956_create_ci_pipeline_artifact.rb | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb index 6b3d73c2e022ab..87542c5e795a97 100644 --- a/app/models/ci/pipeline_artifact.rb +++ b/app/models/ci/pipeline_artifact.rb @@ -20,9 +20,8 @@ class PipelineArtifact < ApplicationRecord json: 1 }, _suffix: true - REPORT_TYPES = { - coverage: :raw - } + validates :pipeline, :project, presence: true + validates :file_store, inclusion: { in: [1, 2] } def hashed_path? true diff --git a/app/services/ci/pipeline_artifact_service.rb b/app/services/ci/pipeline_artifact_service.rb index 03583bf69df9b0..723d397eb2aee5 100644 --- a/app/services/ci/pipeline_artifact_service.rb +++ b/app/services/ci/pipeline_artifact_service.rb @@ -6,11 +6,14 @@ def execute(pipeline) return unless Feature.enabled?(:coverage_report_view, pipeline.project) return unless pipeline.has_coverage_reports? + coverage_report = pipeline.job_artifacts.coverage_reports.first + report = pipeline.pipeline_artifacts.create!( project_id: pipeline.project_id, file_type: 1, - size: pipeline.job_artifacts.coverage_reports.first.size, file_format: 1, + file_store: coverage_report.file_store, + size: coverage_report.size, file: CarrierWaveStringFile.new(pipeline.coverage_reports.to_json) ) end diff --git a/db/migrate/20200722152956_create_ci_pipeline_artifact.rb b/db/migrate/20200722152956_create_ci_pipeline_artifact.rb index 5d0e548711a817..147543ca89addd 100644 --- a/db/migrate/20200722152956_create_ci_pipeline_artifact.rb +++ b/db/migrate/20200722152956_create_ci_pipeline_artifact.rb @@ -13,10 +13,10 @@ def up t.references :pipeline, foreign_key: { to_table: :ci_pipelines, on_delete: :cascade }, index: true, null: false t.references :project, foreign_key: { on_delete: :cascade }, index: true, null: false t.timestamps_with_timezone - t.integer :file_type, null: false + t.integer :file_type, null: false, limit: 2 t.integer :size, null: false - t.integer :file_store, null: false, default: 1 - t.integer :file_format, null: false + t.integer :file_store, null: false, limit: 2 + t.integer :file_format, null: false, limit: 2 t.text :file end end -- GitLab From 39066c1f3af29de2b26f78df9a3211e1d8d35c91 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Thu, 23 Jul 2020 14:47:41 -0400 Subject: [PATCH 7/9] Fix filename --- app/uploaders/ci/pipeline_artifact_uploader.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/uploaders/ci/pipeline_artifact_uploader.rb b/app/uploaders/ci/pipeline_artifact_uploader.rb index cc0ebd04a43d08..5c644cab2dc72c 100644 --- a/app/uploaders/ci/pipeline_artifact_uploader.rb +++ b/app/uploaders/ci/pipeline_artifact_uploader.rb @@ -4,6 +4,10 @@ module Ci class PipelineArtifactUploader < JobArtifactUploader alias_method :upload, :model + def filename + "#{model.file_type}-#{model.pipeline_id}" + end + def store_dir raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id -- GitLab From 39c2d34b22e6da7354f4132842c021ec3f5e4651 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Thu, 23 Jul 2020 15:09:56 -0400 Subject: [PATCH 8/9] refacto uploader --- app/uploaders/ci/pipeline_artifact_uploader.rb | 4 ---- app/uploaders/job_artifact_uploader.rb | 10 ++++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/uploaders/ci/pipeline_artifact_uploader.rb b/app/uploaders/ci/pipeline_artifact_uploader.rb index 5c644cab2dc72c..334f942e0e362d 100644 --- a/app/uploaders/ci/pipeline_artifact_uploader.rb +++ b/app/uploaders/ci/pipeline_artifact_uploader.rb @@ -2,15 +2,11 @@ module Ci class PipelineArtifactUploader < JobArtifactUploader - alias_method :upload, :model - def filename "#{model.file_type}-#{model.pipeline_id}" end def store_dir - raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id - File.join(disk_hash[0..1], disk_hash[2..3], disk_hash, model.created_at.utc.strftime('%Y_%m_%d'), model.pipeline_id.to_s, model.id.to_s) end diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index 0c09e266172b93..04eec8527e54c3 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -21,6 +21,12 @@ def store_dir dynamic_segment end + def disk_hash + @disk_hash ||= Digest::SHA2.hexdigest(model.project_id.to_s) + end + + private + def dynamic_segment raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id @@ -41,8 +47,4 @@ def hashed_path def legacy_path File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.job_id.to_s) end - - def disk_hash - @disk_hash ||= Digest::SHA2.hexdigest(model.project_id.to_s) - end end -- GitLab From feddc1b75e0c8b55621f5e15b81ea67eb5f4b819 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Thu, 23 Jul 2020 15:17:15 -0400 Subject: [PATCH 9/9] refacto uploader --- app/uploaders/job_artifact_uploader.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index 04eec8527e54c3..400f0b3dcc6ff2 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -21,10 +21,6 @@ def store_dir dynamic_segment end - def disk_hash - @disk_hash ||= Digest::SHA2.hexdigest(model.project_id.to_s) - end - private def dynamic_segment @@ -47,4 +43,8 @@ def hashed_path def legacy_path File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.job_id.to_s) end + + def disk_hash + @disk_hash ||= Digest::SHA2.hexdigest(model.project_id.to_s) + end end -- GitLab