Commit e5580913 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg Committed by Kamil Trzcinski

CE implementation of multiple artifacts

See also: gitlab-org/gitlab-ce!14367
parent ccddf64f
module Ci module Ci
class Build < CommitStatus class Build < CommitStatus
prepend ArtifactMigratable
include TokenAuthenticatable include TokenAuthenticatable
include AfterCommitQueue include AfterCommitQueue
include Presentable include Presentable
...@@ -13,9 +14,14 @@ module Ci ...@@ -13,9 +14,14 @@ module Ci
has_many :sourced_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :source_job_id has_many :sourced_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :source_job_id
has_many :deployments, as: :deployable has_many :deployments, as: :deployable
has_one :last_deployment, -> { order('deployments.id DESC') }, as: :deployable, class_name: 'Deployment' has_one :last_deployment, -> { order('deployments.id DESC') }, as: :deployable, class_name: 'Deployment'
has_many :trace_sections, class_name: 'Ci::BuildTraceSection' has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :job_archive, -> () { where(file_type: Ci::JobArtifact.file_types[:archive]) }, class_name: 'Ci::JobArtifact', foreign_key: :job_id
has_one :job_metadata, -> () { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', foreign_key: :job_id
# The "environment" field for builds is a String, and is the unexpanded name # The "environment" field for builds is a String, and is the unexpanded name
def persisted_environment def persisted_environment
@persisted_environment ||= Environment.find_by( @persisted_environment ||= Environment.find_by(
...@@ -34,7 +40,9 @@ module Ci ...@@ -34,7 +40,9 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) } scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) } scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) } scope :with_artifacts, ->() do
where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)', '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id'))
end
scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :with_artifacts_stored_locally, ->() { with_artifacts.where(artifacts_file_store: [nil, ArtifactUploader::LOCAL_STORE]) } scope :with_artifacts_stored_locally, ->() { with_artifacts.where(artifacts_file_store: [nil, ArtifactUploader::LOCAL_STORE]) }
...@@ -330,18 +338,6 @@ module Ci ...@@ -330,18 +338,6 @@ module Ci
project.running_or_pending_build_count(force: true) project.running_or_pending_build_count(force: true)
end end
def artifacts?
!artifacts_expired? && artifacts_file.exists?
end
def browsable_artifacts?
artifacts_metadata?
end
def artifacts_metadata?
artifacts? && artifacts_metadata.exists?
end
def artifacts_metadata_entry(path, **options) def artifacts_metadata_entry(path, **options)
artifacts_metadata.use_file do |metadata_path| artifacts_metadata.use_file do |metadata_path|
metadata = Gitlab::Ci::Build::Artifacts::Metadata.new( metadata = Gitlab::Ci::Build::Artifacts::Metadata.new(
......
module Ci
class JobArtifact < ActiveRecord::Base
extend Gitlab::Ci::Model
belongs_to :project
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
before_save :set_size, if: :file_changed?
after_commit :remove_file!, on: :destroy
mount_uploader :file, JobArtifactUploader
enum file_type: {
archive: 1,
metadata: 2
}
def self.artifacts_size_for(project)
self.where(project: project).sum(:size)
end
def set_size
self.size = file.size
end
end
end
# Adapter class to unify the interface between mounted uploaders and the
# Ci::Artifact model
# Meant to be prepended so the interface can stay the same
module ArtifactMigratable
def artifacts_file
job_archive&.file || super
end
def artifacts_metadata
job_metadata&.file || super
end
def artifacts?
!artifacts_expired? && artifacts_file.exists?
end
def artifacts_metadata?
artifacts? && artifacts_metadata.exists?
end
def artifacts_file_changed?
job_archive&.file_changed? || super
end
def remove_artifacts_file!
if job_archive
job_archive.destroy
else
super
end
end
def remove_artifacts_metadata!
if job_metadata
job_metadata.destroy
else
super
end
end
def artifacts_size
read_attribute(:artifacts_size).to_i +
job_archive&.size.to_i + job_metadata&.size.to_i
end
end
...@@ -39,7 +39,9 @@ class ProjectStatistics < ActiveRecord::Base ...@@ -39,7 +39,9 @@ class ProjectStatistics < ActiveRecord::Base
end end
def update_build_artifacts_size def update_build_artifacts_size
self.build_artifacts_size = project.builds.sum(:artifacts_size) self.build_artifacts_size =
project.builds.sum(:artifacts_size) +
Ci::JobArtifact.artifacts_size_for(self)
end end
def update_storage_size def update_storage_size
......
class JobArtifactUploader < GitlabUploader
storage :file
def self.local_artifacts_store
Gitlab.config.artifacts.path
end
def self.artifacts_upload_path
File.join(self.local_artifacts_store, 'tmp/uploads/')
end
def initialize(artifact, _field)
@artifact = artifact
end
def size
return super if @artifact.size.nil?
@artifact.size
end
def store_dir
File.join(self.class.local_artifacts_store, default_path)
end
private
def default_path
creation_date = @artifact.created_at.utc.strftime('%Y_%m_%d')
File.join(disk_hash[0..1], disk_hash[2..3], disk_hash,
creation_date, @artifact.job_id.to_s, @artifact.id.to_s)
end
def disk_hash
@disk_hash ||= Digest::SHA2.hexdigest(@artifact.project_id.to_s)
end
end
...@@ -785,6 +785,8 @@ test: ...@@ -785,6 +785,8 @@ test:
# user: YOUR_USERNAME # user: YOUR_USERNAME
pages: pages:
path: tmp/tests/pages path: tmp/tests/pages
artifacts:
path: tmp/tests/artifacts
repositories: repositories:
storages: storages:
default: default:
......
class CreateJobArtifacts < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :ci_job_artifacts do |t|
t.belongs_to :project, null: false, index: true, foreign_key: { on_delete: :cascade }
t.integer :job_id, null: false, index: true
t.integer :size, limit: 8
t.integer :file_type, null: false, index: true
t.datetime_with_timezone :created_at, null: false
t.datetime_with_timezone :updated_at, null: false
t.string :file
t.foreign_key :ci_builds, column: :job_id, on_delete: :cascade
end
end
end
...@@ -396,6 +396,20 @@ ActiveRecord::Schema.define(version: 20171124165823) do ...@@ -396,6 +396,20 @@ ActiveRecord::Schema.define(version: 20171124165823) do
add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
create_table "ci_job_artifacts", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "job_id", null: false
t.integer "size", limit: 8
t.integer "file_type", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.string "file"
end
add_index "ci_job_artifacts", ["file_type"], name: "index_ci_job_artifacts_on_file_type", using: :btree
add_index "ci_job_artifacts", ["job_id"], name: "index_ci_job_artifacts_on_job_id", using: :btree
add_index "ci_job_artifacts", ["project_id"], name: "index_ci_job_artifacts_on_project_id", using: :btree
create_table "ci_pipeline_schedule_variables", force: :cascade do |t| create_table "ci_pipeline_schedule_variables", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.text "value" t.text "value"
...@@ -456,9 +470,9 @@ ActiveRecord::Schema.define(version: 20171124165823) do ...@@ -456,9 +470,9 @@ ActiveRecord::Schema.define(version: 20171124165823) do
t.integer "auto_canceled_by_id" t.integer "auto_canceled_by_id"
t.integer "pipeline_schedule_id" t.integer "pipeline_schedule_id"
t.integer "source" t.integer "source"
t.integer "config_source"
t.boolean "protected" t.boolean "protected"
t.integer "failure_reason" t.integer "failure_reason"
t.integer "config_source"
end end
add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
...@@ -2403,6 +2417,8 @@ ActiveRecord::Schema.define(version: 20171124165823) do ...@@ -2403,6 +2417,8 @@ ActiveRecord::Schema.define(version: 20171124165823) do
add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade
add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade
add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify
......
...@@ -1134,13 +1134,9 @@ module API ...@@ -1134,13 +1134,9 @@ module API
expose :type, :url, :username, :password expose :type, :url, :username, :password
end end
class ArtifactFile < Grape::Entity
expose :filename, :size
end
class Dependency < Grape::Entity class Dependency < Grape::Entity
expose :id, :name, :token expose :id, :name, :token
expose :artifacts_file, using: ArtifactFile, if: ->(job, _) { job.artifacts? } expose :artifacts_file, using: JobArtifactFile, if: ->(job, _) { job.artifacts? }
end end
class Response < Grape::Entity class Response < Grape::Entity
......
...@@ -215,15 +215,16 @@ module API ...@@ -215,15 +215,16 @@ module API
job = authenticate_job! job = authenticate_job!
forbidden!('Job is not running!') unless job.running? forbidden!('Job is not running!') unless job.running?
artifacts_upload_path = ArtifactUploader.artifacts_upload_path artifacts_upload_path = JobArtifactUploader.artifacts_upload_path
artifacts = uploaded_file(:file, artifacts_upload_path) artifacts = uploaded_file(:file, artifacts_upload_path)
metadata = uploaded_file(:metadata, artifacts_upload_path) metadata = uploaded_file(:metadata, artifacts_upload_path)
bad_request!('Missing artifacts file!') unless artifacts bad_request!('Missing artifacts file!') unless artifacts
file_to_large! unless artifacts.size < max_artifacts_size file_to_large! unless artifacts.size < max_artifacts_size
job.artifacts_file = artifacts job.job_artifacts.build(project: job.project, file_type: :archive, file: artifacts)
job.artifacts_metadata = metadata job.job_artifacts.build(project: job.project, file_type: :metadata, file: metadata)
job.artifacts_expire_in = params['expire_in'] || job.artifacts_expire_in = params['expire_in'] ||
Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
......
...@@ -155,38 +155,15 @@ FactoryGirl.define do ...@@ -155,38 +155,15 @@ FactoryGirl.define do
end end
trait :artifacts do trait :artifacts do
after(:create) do |build, _| after(:create) do |build|
build.artifacts_file = create(:ci_job_artifact, job: build)
fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), create(:ci_job_metadata, job: build)
'application/zip') build.reload
build.artifacts_metadata =
fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
'application/x-gzip')
build.save!
end end
end end
trait :remote_store do trait :expired do
artifacts_file_store ArtifactUploader::REMOTE_STORE artifacts_expire_at 1.minute.ago
artifacts_metadata_store ArtifactUploader::REMOTE_STORE
end
trait :artifacts_expired do
after(:create) do |build, _|
build.artifacts_file =
fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
'application/zip')
build.artifacts_metadata =
fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
'application/x-gzip')
build.artifacts_expire_at = 1.minute.ago
build.save!
end
end end
trait :with_commit do trait :with_commit do
......
include ActionDispatch::TestProcess
FactoryGirl.define do
factory :ci_job_artifact, class: Ci::JobArtifact do
job factory: :ci_build
file_type :archive
after :build do |artifact|
artifact.project ||= artifact.job.project
end
after :create do |artifact|
if artifact.archive?
artifact.file = fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
'application/zip')
artifact.save
end
end
end
factory :ci_job_metadata, parent: :ci_job_artifact do
file_type :metadata
after :create do |artifact|
artifact.file = fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
'application/x-gzip')
artifact.save
end
end
end
...@@ -304,7 +304,7 @@ describe 'Pipelines', :js do ...@@ -304,7 +304,7 @@ describe 'Pipelines', :js do
context 'with artifacts expired' do context 'with artifacts expired' do
let!(:with_artifacts_expired) do let!(:with_artifacts_expired) do
create(:ci_build, :artifacts_expired, :success, create(:ci_build, :expired, :success,
pipeline: pipeline, pipeline: pipeline,
name: 'rspec', name: 'rspec',
stage: 'test') stage: 'test')
......
...@@ -16,20 +16,22 @@ describe MigrateOldArtifacts do ...@@ -16,20 +16,22 @@ describe MigrateOldArtifacts do
end end
context 'with migratable data' do context 'with migratable data' do
let(:project1) { create(:project, ci_id: 2) } set(:project1) { create(:project, ci_id: 2) }
let(:project2) { create(:project, ci_id: 3) } set(:project2) { create(:project, ci_id: 3) }
let(:project3) { create(:project) } set(:project3) { create(:project) }
let(:pipeline1) { create(:ci_empty_pipeline, project: project1) } set(:pipeline1) { create(:ci_empty_pipeline, project: project1) }
let(:pipeline2) { create(:ci_empty_pipeline, project: project2) } set(:pipeline2) { create(:ci_empty_pipeline, project: project2) }
let(:pipeline3) { create(:ci_empty_pipeline, project: project3) } set(:pipeline3) { create(:ci_empty_pipeline, project: project3) }
let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) } let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) }
let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) } let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) }
let!(:build2) { create(:ci_build, :artifacts, pipeline: pipeline2) } let!(:build2) { create(:ci_build, pipeline: pipeline2) }
let!(:build3) { create(:ci_build, :artifacts, pipeline: pipeline3) } let!(:build3) { create(:ci_build, pipeline: pipeline3) }
before do before do
setup_builds(build2, build3)
store_artifacts_in_legacy_path(build_with_legacy_artifacts) store_artifacts_in_legacy_path(build_with_legacy_artifacts)
end end
...@@ -113,5 +115,24 @@ describe MigrateOldArtifacts do ...@@ -113,5 +115,24 @@ describe MigrateOldArtifacts do
build.project.ci_id.to_s, build.project.ci_id.to_s,
build.id.to_s) build.id.to_s)
end end
def new_legacy_path(build)
File.join(directory,
build.created_at.utc.strftime('%Y_%m'),
build.project_id.to_s,
build.id.to_s)
end
def setup_builds(*builds)
builds.each do |build|
FileUtils.mkdir_p(new_legacy_path(build))
build.update_columns(
artifacts_file: 'ci_build_artifacts.zip',
artifacts_metadata: 'ci_build_artifacts_metadata.gz')
build.reload
end
end
end end
end end
...@@ -24,6 +24,8 @@ describe Ci::Build do ...@@ -24,6 +24,8 @@ describe Ci::Build do
it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) } it { is_expected.to respond_to(:trace) }
it { is_expected.to be_a(ArtifactMigratable) }
describe 'callbacks' do describe 'callbacks' do
context 'when running after_create callback' do context 'when running after_create callback' do
it 'triggers asynchronous build hooks worker' do it 'triggers asynchronous build hooks worker' do
...@@ -131,33 +133,26 @@ describe Ci::Build do ...@@ -131,33 +133,26 @@ describe Ci::Build do
end end
describe '#artifacts?' do describe '#artifacts?' do
let(:build) { create(:ci_build, :artifacts) }
subject { build.artifacts? } subject { build.artifacts? }
context 'artifacts archive does not exist' do context 'artifacts archive does not exist' do
before do let(:build) { create(:ci_build) }
build.update_attributes(artifacts_file: nil)
end
it { is_expected.to be_falsy } it { is_expected.to be_falsy }
end end
context 'artifacts archive exists' do context 'artifacts archive exists' do
let(:build) { create(:ci_build, :artifacts) }
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
context 'is expired' do context 'is expired' do
before do let!(:build) { create(:ci_build, :artifacts, :expired) }
build.update(artifacts_expire_at: Time.now - 7.days)
end
it { is_expected.to be_falsy } it { is_expected.to be_falsy }
end end
context 'is not expired' do context 'is not expired' do
before do
build.update(artifacts_expire_at: Time.now + 7.days)
end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
end end
......
require 'spec_helper'
describe Ci::JobArtifact do
set(:artifact) { create(:ci_job_artifact) }
describe "Associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:job) }
end
it { is_expected.to respond_to(:file) }
it { is_expected.to respond_to(:created_at) }
it { is_expected.to respond_to(:updated_at) }
describe '#set_size' do
it 'sets the size' do
expect(artifact.size).to eq(106365)
end
end
describe '#file' do
subject { artifact.file }
context 'the uploader api' do
it { is_expected.to respond_to(:store_dir) }
it { is_expected.to respond_to(:cache_dir) }
it { is_expected.to respond_to(:work_dir) }
end
end
end
...@@ -133,15 +133,17 @@ describe ProjectStatistics do ...@@ -133,15 +133,17 @@ describe ProjectStatistics do
describe '#update_build_artifacts_size' do describe '#update_build_artifacts_size' do
let!(:pipeline) { create(:ci_pipeline, project: project) } let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:build1) { create(:ci_build, pipeline: pipeline, artifacts_size: 45.megabytes) } let!(:ci_build) { create(:ci_build, pipeline: pipeline, artifacts_size: 45.megabytes) }
let!(:build2) { create(:ci_build, pipeline: pipeline, artifacts_size: 56.megabytes) }
before do before do
create(:ci_build, pipeline: pipeline, artifacts_size: 56.megabytes)
create(:ci_job_artifact, project: pipeline.project, job: ci_build)
statistics.update_build_artifacts_size statistics.update_build_artifacts_size
end end
it "stores the size of related build artifacts" do it "stores the size of related build artifacts" do
expect(statistics.build_artifacts_size).to eq 101.megabytes expect(statistics.build_artifacts_size).to eq(106012541)
end end
end end
......
...@@ -948,7 +948,7 @@ describe API::Runner do ...@@ -948,7 +948,7 @@ describe API::Runner do
context 'when artifacts are being stored inside of tmp path' do context 'when artifacts are being stored inside of tmp path' do
before do before do
# by configuring this path we allow to pass temp file from any path # by configuring this path we allow to pass temp file from any path
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/') allow(JobArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
end end
context 'when job has been erased' do context 'when job has been erased' do
...@@ -1109,7 +1109,7 @@ describe API::Runner do ...@@ -1109,7 +1109,7 @@ describe API::Runner do
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
expect(stored_artifacts_size).to eq(71759) expect(stored_artifacts_size).to eq(72821)
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe PipelineSerializer do describe PipelineSerializer do
let(:user) { create(:user) } set(:user) { create(:user) }
let(:serializer) do let(:serializer) do
described_class.new(current_user: user) described_class.new(current_user: user)
...@@ -117,7 +117,8 @@ describe PipelineSerializer do ...@@ -117,7 +117,8 @@ describe PipelineSerializer do
shared_examples 'no N+1 queries' do shared_examples 'no N+1 queries' do
it 'verifies number of queries', :request_store do it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject } recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.count).to be_within(1).of(61)
expect(recorded.count).to be_within(1).of(36)
expect(recorded.cached_count).to eq(0) expect(recorded.cached_count).to eq(0)
end end
end end
......
...@@ -17,7 +17,7 @@ describe Ci::RetryBuildService do ...@@ -17,7 +17,7 @@ describe Ci::RetryBuildService do
%i[id status user token coverage trace runner artifacts_expire_at %i[id status user token coverage trace runner artifacts_expire_at
artifacts_file artifacts_metadata artifacts_size created_at artifacts_file artifacts_metadata artifacts_size created_at
updated_at started_at finished_at queued_at erased_by updated_at started_at finished_at queued_at erased_by
erased_at auto_canceled_by].freeze erased_at auto_canceled_by job_artifacts job_archive job_metadata].freeze
IGNORE_ACCESSORS = IGNORE_ACCESSORS =
%i[type lock_version target_url base_tags trace_sections %i[type lock_version target_url base_tags trace_sections
...@@ -35,7 +35,7 @@ describe Ci::RetryBuildService do ...@@ -35,7 +35,7 @@ describe Ci::RetryBuildService do
end end
let(:build) do let(:build) do
create(:ci_build, :failed, :artifacts_expired, :erased, create(:ci_build, :failed, :artifacts, :expired, :erased,
:queued, :coverage, :tags, :allowed_to_fail, :on_tag, :queued, :coverage, :tags, :allowed_to_fail, :on_tag,
:triggered, :trace, :teardown_environment, :triggered, :trace, :teardown_environment,
description: 'my-job', stage: 'test', pipeline: pipeline, description: 'my-job', stage: 'test', pipeline: pipeline,
......
...@@ -121,6 +121,7 @@ module TestEnv ...@@ -121,6 +121,7 @@ module TestEnv
FileUtils.mkdir_p(repos_path) FileUtils.mkdir_p(repos_path)
FileUtils.mkdir_p(backup_path) FileUtils.mkdir_p(backup_path)
FileUtils.mkdir_p(pages_path) FileUtils.mkdir_p(pages_path)
FileUtils.mkdir_p(artifacts_path)
end end
def clean_gitlab_test_path def clean_gitlab_test_path
...@@ -234,6 +235,10 @@ module TestEnv ...@@ -234,6 +235,10 @@ module TestEnv
Gitlab.config.pages.path Gitlab.config.pages.path
end end
def artifacts_path
Gitlab.config.artifacts.path
end
# When no cached assets exist, manually hit the root path to create them # When no cached assets exist, manually hit the root path to create them
# #
# Otherwise they'd be created by the first test, often timing out and # Otherwise they'd be created by the first test, often timing out and
......
...@@ -24,6 +24,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -24,6 +24,7 @@ describe 'gitlab:app namespace rake task' do
# We need this directory to run `gitlab:backup:create` task # We need this directory to run `gitlab:backup:create` task
FileUtils.mkdir_p('public/uploads') FileUtils.mkdir_p('public/uploads')
FileUtils.mkdir_p(Rails.root.join('tmp/tests/artifacts'))
end end
before do before do
......
...@@ -2,7 +2,7 @@ require 'rails_helper' ...@@ -2,7 +2,7 @@ require 'rails_helper'
describe ArtifactUploader do describe ArtifactUploader do
let(:store) { described_class::LOCAL_STORE } let(:store) { described_class::LOCAL_STORE }
let(:job) { create(:ci_build, artifacts_file_store: store) } set(:job) { create(:ci_build, artifacts_file_store: store) }
let(:uploader) { described_class.new(job, :artifacts_file) } let(:uploader) { described_class.new(job, :artifacts_file) }
let(:local_path) { Gitlab.config.artifacts.path } let(:local_path) { Gitlab.config.artifacts.path }
......
require 'spec_helper'
describe JobArtifactUploader do
set(:job_artifact) { create(:ci_job_artifact) }
let(:job) { job_artifact.job }
let(:uploader) { described_class.new(job_artifact, :file) }
describe '#store_dir' do
subject { uploader.store_dir }
it { is_expected.to start_with(Gitlab.config.artifacts.path) }
it { is_expected.not_to end_with("#{job.project_id}/#{job.created_at.utc.strftime('%Y_%m')}/#{job.id}") }
it { is_expected.to match(/\h{2}\/\h{2}\/\h{64}\/\d{4}_\d{1,2}_\d{1,2}\/\d+\/\d+\z/) }
end
end
...@@ -11,12 +11,8 @@ describe ExpireBuildInstanceArtifactsWorker do ...@@ -11,12 +11,8 @@ describe ExpireBuildInstanceArtifactsWorker do
end end
context 'with expired artifacts' do context 'with expired artifacts' do
let(:artifacts_expiry) { { artifacts_expire_at: Time.now - 7.days } }
context 'when associated project is valid' do context 'when associated project is valid' do
let(:build) do let(:build) { create(:ci_build, :artifacts, :expired) }
create(:ci_build, :artifacts, artifacts_expiry)
end
it 'does expire' do it 'does expire' do
expect(build.reload.artifacts_expired?).to be_truthy expect(build.reload.artifacts_expired?).to be_truthy
...@@ -26,14 +22,14 @@ describe ExpireBuildInstanceArtifactsWorker do ...@@ -26,14 +22,14 @@ describe ExpireBuildInstanceArtifactsWorker do
expect(build.reload.artifacts_file.exists?).to be_falsey expect(build.reload.artifacts_file.exists?).to be_falsey
end end
it 'does nullify artifacts_file column' do it 'does remove the job artifact record' do
expect(build.reload.artifacts_file_identifier).to be_nil expect(build.reload.job_archive).to be_nil
end end
end end
end end
context 'with not yet expired artifacts' do context 'with not yet expired artifacts' do
let(:build) do set(:build) do
create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days)
end end
...@@ -45,8 +41,8 @@ describe ExpireBuildInstanceArtifactsWorker do ...@@ -45,8 +41,8 @@ describe ExpireBuildInstanceArtifactsWorker do
expect(build.reload.artifacts_file.exists?).to be_truthy expect(build.reload.artifacts_file.exists?).to be_truthy
end end
it 'does not nullify artifacts_file column' do it 'does not remove the job artifact record' do
expect(build.reload.artifacts_file_identifier).not_to be_nil expect(build.reload.job_archive).not_to be_nil
end end
end end
...@@ -61,13 +57,13 @@ describe ExpireBuildInstanceArtifactsWorker do ...@@ -61,13 +57,13 @@ describe ExpireBuildInstanceArtifactsWorker do
expect(build.reload.artifacts_file.exists?).to be_truthy expect(build.reload.artifacts_file.exists?).to be_truthy
end end
it 'does not nullify artifacts_file column' do it 'does not remove the job artifact record' do
expect(build.reload.artifacts_file_identifier).not_to be_nil expect(build.reload.job_archive).not_to be_nil
end end
end end
context 'for expired artifacts' do context 'for expired artifacts' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now - 7.days) } let(:build) { create(:ci_build, :expired) }
it 'is still expired' do it 'is still expired' do
expect(build.reload.artifacts_expired?).to be_truthy expect(build.reload.artifacts_expired?).to be_truthy
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment