Skip to content
Snippets Groups Projects
Commit 40397f35 authored by Kamil Trzcińśki's avatar Kamil Trzcińśki
Browse files

Allow to make builds soft-archived.

The soft-archived builds cannot be run after some deadline time.
The intent is to aggressively recycle old builds after sometime.
parent 90473e06
No related branches found
No related tags found
No related merge requests found
Showing
with 163 additions and 37 deletions
Loading
Loading
@@ -115,6 +115,7 @@ def visible_attributes
:akismet_api_key,
:akismet_enabled,
:allow_local_requests_from_hooks_and_services,
:archive_builds_in_human_readable,
:authorized_keys_enabled,
:auto_devops_enabled,
:auto_devops_domain,
Loading
Loading
Loading
Loading
@@ -5,6 +5,7 @@ class ApplicationSetting < ActiveRecord::Base
include CacheMarkdownField
include TokenAuthenticatable
include IgnorableColumn
include ChronicDurationAttribute
 
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
Loading
Loading
@@ -45,6 +46,8 @@ class ApplicationSetting < ActiveRecord::Base
 
default_value_for :id, 1
 
chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds
validates :uuid, presence: true
 
validates :session_expire_delay,
Loading
Loading
@@ -184,6 +187,10 @@ class ApplicationSetting < ActiveRecord::Base
 
validates :user_default_internal_regex, js_regex: true, allow_nil: true
 
validates :archive_builds_in_seconds,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1.day.seconds }
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
Loading
Loading
@@ -441,6 +448,10 @@ def reset_memoized_terms
latest_terms
end
 
def archive_builds_older_than
archive_builds_in_seconds.seconds.ago if archive_builds_in_seconds
end
private
 
def ensure_uuid!
Loading
Loading
Loading
Loading
@@ -258,8 +258,24 @@ def pages_generator?
self.name == 'pages'
end
 
# degenerated build is one that cannot be run by Runner
def degenerated?
self.options.nil?
end
def degenerate!
self.update!(options: nil, yaml_variables: nil, commands: nil)
end
def archived?
return true if degenerated?
archive_builds_older_than = Gitlab::CurrentSettings.current_application_settings.archive_builds_older_than
archive_builds_older_than.present? && created_at < archive_builds_older_than
end
def playable?
action? && (manual? || scheduled? || retryable?)
action? && !archived? && (manual? || scheduled? || retryable?)
end
 
def schedulable?
Loading
Loading
@@ -287,7 +303,7 @@ def cancelable?
end
 
def retryable?
success? || failed? || canceled?
!archived? && (success? || failed? || canceled?)
end
 
def retries_count
Loading
Loading
@@ -295,7 +311,7 @@ def retries_count
end
 
def retries_max
self.options.fetch(:retry, 0).to_i
self.options.to_h.fetch(:retry, 0).to_i
end
 
def latest?
Loading
Loading
Loading
Loading
@@ -51,7 +51,8 @@ class CommitStatus < ActiveRecord::Base
missing_dependency_failure: 5,
runner_unsupported: 6,
stale_schedule: 7,
job_execution_timeout: 8
job_execution_timeout: 8,
archived_failure: 9
}
 
##
Loading
Loading
@@ -167,16 +168,18 @@ def playable?
false
end
 
# To be overridden when inherrited from
def retryable?
false
end
 
# To be overridden when inherrited from
def cancelable?
false
end
 
def archived?
false
end
def stuck?
false
end
Loading
Loading
Loading
Loading
@@ -20,12 +20,17 @@ class BuildPolicy < CommitStatusPolicy
@subject.project.branch_allows_collaboration?(@user, @subject.ref)
end
 
condition(:archived, scope: :subject) do
@subject.archived?
end
condition(:terminal, scope: :subject) do
@subject.has_terminal?
end
 
rule { protected_ref }.policy do
rule { protected_ref | archived }.policy do
prevent :update_build
prevent :update_commit_status
prevent :erase_build
end
 
Loading
Loading
Loading
Loading
@@ -2,4 +2,13 @@
 
class DeploymentPolicy < BasePolicy
delegate { @subject.project }
condition(:can_retry_deployable) do
can?(:update_build, @subject.deployable)
end
rule { ~can_retry_deployable }.policy do
prevent :create_deployment
prevent :update_deployment
end
end
Loading
Loading
@@ -10,7 +10,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
missing_dependency_failure: 'There has been a missing dependency failure',
runner_unsupported: 'Your runner is outdated, please upgrade your runner',
stale_schedule: 'Delayed job could not be executed by some reason, please try again',
job_execution_timeout: 'The script exceeded the maximum execution time set for the job'
job_execution_timeout: 'The script exceeded the maximum execution time set for the job',
archived_failure: 'The job is archived and cannot be run'
}.freeze
 
private_constant :CALLOUT_FAILURE_MESSAGES
Loading
Loading
@@ -30,6 +31,6 @@ def recoverable?
end
 
def unrecoverable?
script_failure? || missing_dependency_failure?
script_failure? || missing_dependency_failure? || archived_failure?
end
end
Loading
Loading
@@ -7,6 +7,7 @@ class JobEntity < Grape::Entity
expose :name
 
expose :started?, as: :started
expose :archived?, as: :archived
 
expose :build_path do |build|
build_path(build)
Loading
Loading
Loading
Loading
@@ -82,6 +82,11 @@ def assign_runner!(build, params)
return false
end
 
if build.archived?
build.drop!(:archived_failure)
return false
end
build.run!
true
end
Loading
Loading
Loading
Loading
@@ -41,5 +41,13 @@
The default unit is in seconds, but you can define an alternative. For example:
<code>4 mins 2 sec</code>, <code>2h42min</code>.
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
.form-group
= f.label :archive_builds_in_human_readable, 'Archive builds in', class: 'label-bold'
= f.text_field :archive_builds_in_human_readable, class: 'form-control', placeholder: 'never'
.form-text.text-muted
Set the duration when build gonna be considered old. Archived builds cannot be retried.
Make it empty to never expire builds. It has to be larger than 1 day.
The default unit is in seconds, but you can define an alternative. For example:
<code>4 mins 2 sec</code>, <code>2h42min</code>.
 
= f.submit 'Save changes', class: "btn btn-success"
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
- if can?(current_user, :create_deployment, deployment)
- tooltip = deployment.last? ? s_('Environments|Re-deploy to environment') : s_('Environments|Rollback environment')
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build has-tooltip', title: tooltip do
- if deployment.last?
Loading
Loading
Loading
Loading
@@ -61,12 +61,14 @@
%td.responsive-table-cell.build-failure{ data: { column: _("Failure")} }
= build.present.callout_failure_message
%td.responsive-table-cell.build-actions
= link_to retry_project_job_path(build.project, build, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build' do
= icon('repeat')
%tr.build-trace-row.responsive-table-border-end
%td
%td.responsive-table-cell.build-trace-container{ colspan: 4 }
%pre.build-trace.build-trace-rounded
%code.bash.js-build-output
= build_summary(build)
- if can?(current_user, :update_build, job)
= link_to retry_project_job_path(build.project, build, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build' do
= icon('repeat')
- if can?(current_user, :read_build, job)
%tr.build-trace-row.responsive-table-border-end
%td
%td.responsive-table-cell.build-trace-container{ colspan: 4 }
%pre.build-trace.build-trace-rounded
%code.bash.js-build-output
= build_summary(build)
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
---
title: Soft-archive old jobs
merge_request:
author:
type: added
# frozen_string_literal: true
class AddArchiveBuildsDurationToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column(:application_settings, :archive_builds_in_seconds, :integer, allow_null: true)
end
end
Loading
Loading
@@ -165,6 +165,7 @@
t.integer "usage_stats_set_by_user_id"
t.integer "receive_max_input_size"
t.integer "diff_max_patch_bytes", default: 102400, null: false
t.integer "archive_builds_in_seconds"
end
 
create_table "audit_events", force: :cascade do |t|
Loading
Loading
Loading
Loading
@@ -14,7 +14,8 @@ class Failed < Status::Extended
missing_dependency_failure: 'missing dependency failure',
runner_unsupported: 'unsupported runner',
stale_schedule: 'stale schedule',
job_execution_timeout: 'job execution timeout'
job_execution_timeout: 'job execution timeout',
archived_failure: 'archived failure'
}.freeze
 
private_constant :REASONS
Loading
Loading
Loading
Loading
@@ -27,6 +27,12 @@
 
pipeline factory: :ci_pipeline
 
trait :degenerated do
commands nil
options nil
yaml_variables nil
end
trait :started do
started_at 'Di 29. Okt 09:51:28 CET 2013'
end
Loading
Loading
Loading
Loading
@@ -53,10 +53,21 @@
 
it 'does show build name' do
expect(page).to have_link("#{build.name} (##{build.id})")
expect(page).to have_link('Re-deploy')
expect(page).not_to have_link('Re-deploy')
expect(page).not_to have_terminal_button
end
 
context 'when user has ability to re-deploy' do
let(:permissions) do
create(:protected_branch, :developers_can_merge,
name: build.ref, project: project)
end
it 'does show re-deploy' do
expect(page).to have_link('Re-deploy')
end
end
context 'with manual action' do
let(:action) do
create(:ci_build, :manual, pipeline: pipeline,
Loading
Loading
Loading
Loading
@@ -388,54 +388,83 @@
let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) }
 
subject { visit pipeline_failures_page }
context 'with failed build' do
before do
failed_build.trace.set('4 examples, 1 failure')
visit pipeline_failures_page
end
 
it 'shows jobs tab pane as active' do
subject
expect(page).to have_content('Failed Jobs')
expect(page).to have_css('#js-tab-failures.active')
end
 
it 'lists failed builds' do
subject
expect(page).to have_content(failed_build.name)
expect(page).to have_content(failed_build.stage)
end
 
it 'shows build failure logs' do
subject
expect(page).to have_content('4 examples, 1 failure')
end
 
it 'shows the failure reason' do
subject
expect(page).to have_content('There is an unknown failure, please try again')
end
 
it 'shows retry button for failed build' do
page.within(find('.build-failures', match: :first)) do
expect(page).to have_link('Retry')
context 'when user does not have permission to retry build' do
it 'shows retry button for failed build' do
subject
page.within(find('.build-failures', match: :first)) do
expect(page).not_to have_link('Retry')
end
end
end
end
 
context 'when missing build logs' do
before do
visit pipeline_failures_page
context 'when user does have permission to retry build' do
before do
create(:protected_branch, :developers_can_merge,
name: pipeline.ref, project: project)
end
it 'shows retry button for failed build' do
subject
page.within(find('.build-failures', match: :first)) do
expect(page).to have_link('Retry')
end
end
end
end
 
context 'when missing build logs' do
it 'shows jobs tab pane as active' do
subject
expect(page).to have_content('Failed Jobs')
expect(page).to have_css('#js-tab-failures.active')
end
 
it 'lists failed builds' do
subject
expect(page).to have_content(failed_build.name)
expect(page).to have_content(failed_build.stage)
end
 
it 'does not show trace' do
subject
expect(page).to have_content('No job trace')
end
end
Loading
Loading
@@ -448,11 +477,9 @@
end
 
context 'when accessing failed jobs page' do
before do
visit pipeline_failures_page
end
it 'fails to access the page' do
subject
expect(page).to have_title('Access Denied')
end
end
Loading
Loading
@@ -461,11 +488,11 @@
context 'without failures' do
before do
failed_build.update!(status: :success)
visit pipeline_failures_page
end
 
it 'displays the pipeline graph' do
subject
expect(current_path).to eq(pipeline_path(pipeline))
expect(page).not_to have_content('Failed Jobs')
expect(page).to have_selector('.pipeline-visualization')
Loading
Loading
Loading
Loading
@@ -9,7 +9,8 @@
"playable",
"created_at",
"updated_at",
"status"
"status",
"archived"
],
"properties": {
"id": { "type": "integer" },
Loading
Loading
@@ -27,7 +28,8 @@
"updated_at": { "type": "string" },
"status": { "$ref": "../status/ci_detailed_status.json" },
"callout_message": { "type": "string" },
"recoverable": { "type": "boolean" }
"recoverable": { "type": "boolean" },
"archived": { "type": "boolean" }
},
"additionalProperties": true
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment