diff options
Diffstat (limited to 'app/models/ci')
-rw-r--r-- | app/models/ci/build.rb | 47 | ||||
-rw-r--r-- | app/models/ci/build_report_result.rb | 4 | ||||
-rw-r--r-- | app/models/ci/group.rb | 3 | ||||
-rw-r--r-- | app/models/ci/group_variable.rb | 4 | ||||
-rw-r--r-- | app/models/ci/job_artifact.rb | 2 | ||||
-rw-r--r-- | app/models/ci/legacy_stage.rb | 73 | ||||
-rw-r--r-- | app/models/ci/pending_build.rb | 14 | ||||
-rw-r--r-- | app/models/ci/pipeline.rb | 85 | ||||
-rw-r--r-- | app/models/ci/pipeline_artifact.rb | 17 | ||||
-rw-r--r-- | app/models/ci/runner.rb | 20 | ||||
-rw-r--r-- | app/models/ci/runner_version.rb | 34 | ||||
-rw-r--r-- | app/models/ci/stage.rb | 2 | ||||
-rw-r--r-- | app/models/ci/trigger.rb | 3 | ||||
-rw-r--r-- | app/models/ci/variable.rb | 4 |
14 files changed, 143 insertions, 169 deletions
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e35198ba31f..7f9697d0424 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -2,6 +2,7 @@ module Ci class Build < Ci::Processable + prepend Ci::BulkInsertableTags include Ci::Metadatable include Ci::Contextable include TokenAuthenticatable @@ -14,8 +15,6 @@ module Ci extend ::Gitlab::Utils::Override - BuildArchivedError = Class.new(StandardError) - belongs_to :project, inverse_of: :builds belongs_to :runner belongs_to :trigger_request @@ -30,10 +29,6 @@ module Ci return_exit_code: -> (build) { build.exit_codes_defined? } }.freeze - DEFAULT_RETRIES = { - scheduler_failure: 2 - }.freeze - DEGRADATION_THRESHOLD_VARIABLE_NAME = 'DEGRADATION_THRESHOLD' RUNNERS_STATUS_CACHE_EXPIRATION = 1.minute @@ -172,7 +167,6 @@ module Ci end scope :with_artifacts_not_expired, -> { with_downloadable_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.current) } - scope :with_expired_artifacts, -> { with_downloadable_artifacts.where('artifacts_expire_at < ?', Time.current) } scope :with_pipeline_locked_artifacts, -> { joins(:pipeline).where('pipeline.locked': Ci::Pipeline.lockeds[:artifacts_locked]) } scope :last_month, -> { where('created_at > ?', Date.today - 1.month) } scope :manual_actions, -> { where(when: :manual, status: COMPLETED_STATUSES + %i[manual]) } @@ -187,13 +181,6 @@ module Ci joins(:metadata).where("ci_builds_metadata.config_options -> 'artifacts' -> 'reports' ?| array[:job_types]", job_types: job_types) end - scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) } - - scope :preload_project_and_pipeline_project, -> do - preload(Ci::Pipeline::PROJECT_ROUTE_AND_NAMESPACE_ROUTE, - pipeline: Ci::Pipeline::PROJECT_ROUTE_AND_NAMESPACE_ROUTE) - end - scope :with_coverage, -> { where.not(coverage: nil) } scope :without_coverage, -> { where(coverage: nil) } scope :with_coverage_regex, -> { where.not(coverage_regex: nil) } @@ -207,7 +194,7 @@ module Ci after_save :stick_build_if_status_changed after_create unless: :importing? do |build| - run_after_commit { BuildHooksWorker.perform_async(build.id) } + run_after_commit { BuildHooksWorker.perform_async(build) } end class << self @@ -217,10 +204,6 @@ module Ci ActiveModel::Name.new(self, nil, 'job') end - def first_pending - pending.unstarted.order('created_at ASC').first - end - def with_preloads preload(:job_artifacts_archive, :job_artifacts, :tags, project: [:namespace]) end @@ -302,7 +285,7 @@ module Ci build.run_after_commit do BuildQueueWorker.perform_async(id) - BuildHooksWorker.perform_async(id) + BuildHooksWorker.perform_async(build) end end @@ -330,7 +313,7 @@ module Ci build.run_after_commit do build.ensure_persistent_ref - BuildHooksWorker.perform_async(id) + BuildHooksWorker.perform_async(build) end end @@ -338,11 +321,7 @@ module Ci build.run_after_commit do build.run_status_commit_hooks! - if Feature.enabled?(:ci_build_finished_worker_namespace_changed, build.project) - Ci::BuildFinishedWorker.perform_async(id) - else - ::BuildFinishedWorker.perform_async(id) - end + Ci::BuildFinishedWorker.perform_async(id) end end @@ -446,10 +425,6 @@ module Ci true end - def save_tags - super unless Thread.current['ci_bulk_insert_tags'] - end - def archived? return true if degenerated? @@ -556,10 +531,6 @@ module Ci self.options.dig(:environment, :deployment_tier) if self.options end - def outdated_deployment? - success? && !deployment.try(:last?) - end - def triggered_by?(current_user) user == current_user end @@ -1162,6 +1133,14 @@ module Ci Gitlab::Utils::UsageData.track_usage_event('ci_users_executing_deployment_job', user_id) if user_id.present? && count_user_deployment? end + def track_verify_usage + Gitlab::Utils::UsageData.track_usage_event('ci_users_executing_verify_environment_job', user_id) if user_id.present? && count_user_verification? + end + + def count_user_verification? + has_environment? && environment_action == 'verify' + end + def each_report(report_types) job_artifacts_for_types(report_types).each do |report_artifact| report_artifact.each_blob do |blob| diff --git a/app/models/ci/build_report_result.rb b/app/models/ci/build_report_result.rb index 2c08fc4c8bf..b674c1b1a0e 100644 --- a/app/models/ci/build_report_result.rb +++ b/app/models/ci/build_report_result.rb @@ -39,9 +39,5 @@ module Ci def suite_error tests.dig("suite_error") end - - def tests_total - [tests_success, tests_failed, tests_errored, tests_skipped].sum - end end end diff --git a/app/models/ci/group.rb b/app/models/ci/group.rb index e5cb2026503..0105366d99b 100644 --- a/app/models/ci/group.rb +++ b/app/models/ci/group.rb @@ -50,8 +50,7 @@ module Ci def status_struct strong_memoize(:status_struct) do - Gitlab::Ci::Status::Composite - .new(@jobs, project: project) + Gitlab::Ci::Status::Composite.new(@jobs) end end diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb index 0af5533613f..e11edbda6dc 100644 --- a/app/models/ci/group_variable.rb +++ b/app/models/ci/group_variable.rb @@ -19,5 +19,9 @@ module Ci scope :unprotected, -> { where(protected: false) } scope :by_environment_scope, -> (environment_scope) { where(environment_scope: environment_scope) } scope :for_groups, ->(group_ids) { where(group_id: group_ids) } + + def audit_details + key + end end end diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 81943cfa651..ee7175a4f69 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -322,7 +322,7 @@ module Ci def expire_in=(value) self.expire_at = if value - ::Gitlab::Ci::Build::Artifacts::ExpireInParser.new(value).seconds_from_now + ::Gitlab::Ci::Build::DurationParser.new(value).seconds_from_now end end diff --git a/app/models/ci/legacy_stage.rb b/app/models/ci/legacy_stage.rb deleted file mode 100644 index ffd3d3fcd88..00000000000 --- a/app/models/ci/legacy_stage.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -module Ci - # Currently this is artificial object, constructed dynamically - # We should migrate this object to actual database record in the future - class LegacyStage - include StaticModel - include Presentable - - attr_reader :pipeline, :name - - delegate :project, to: :pipeline - - def initialize(pipeline, name:, status: nil, warnings: nil) - @pipeline = pipeline - @name = name - @status = status - # support ints and booleans - @has_warnings = ActiveRecord::Type::Boolean.new.cast(warnings) - end - - def groups - @groups ||= Ci::Group.fabricate(project, self) - end - - def to_param - name - end - - def statuses_count - @statuses_count ||= statuses.count - end - - def status - @status ||= statuses.latest.composite_status(project: project) - end - - def detailed_status(current_user) - Gitlab::Ci::Status::Stage::Factory - .new(self, current_user) - .fabricate! - end - - def latest_statuses - statuses.ordered.latest - end - - def statuses - @statuses ||= pipeline.statuses.where(stage: name) - end - - def builds - @builds ||= pipeline.builds.where(stage: name) - end - - def success? - status.to_s == 'success' - end - - def has_warnings? - # lazilly calculate the warnings - if @has_warnings.nil? - @has_warnings = statuses.latest.failed_but_allowed.any? - end - - @has_warnings - end - - def manual_playable? - %[manual scheduled skipped].include?(status.to_s) - end - end -end diff --git a/app/models/ci/pending_build.rb b/app/models/ci/pending_build.rb index d900a056242..0fa6a234a3d 100644 --- a/app/models/ci/pending_build.rb +++ b/app/models/ci/pending_build.rb @@ -30,10 +30,6 @@ module Ci self.upsert(entry.attributes.compact, returning: %w[build_id], unique_by: :build_id) end - def maintain_denormalized_data? - ::Feature.enabled?(:ci_pending_builds_maintain_denormalized_data) - end - private def args_from_build(build) @@ -43,13 +39,13 @@ module Ci build: build, project: project, protected: build.protected?, - namespace: project.namespace + namespace: project.namespace, + tag_ids: build.tags_ids, + instance_runners_enabled: shared_runners_enabled?(project) } - if maintain_denormalized_data? - args.store(:tag_ids, build.tags_ids) - args.store(:instance_runners_enabled, shared_runners_enabled?(project)) - args.store(:namespace_traversal_ids, project.namespace.traversal_ids) if group_runners_enabled?(project) + if group_runners_enabled?(project) + args.store(:namespace_traversal_ids, project.namespace.traversal_ids) end args diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 5d316906bd3..78b55680b5e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -27,8 +27,6 @@ module Ci DEFAULT_CONFIG_PATH = CONFIG_EXTENSION CANCELABLE_STATUSES = (Ci::HasStatus::CANCELABLE_STATUSES + ['manual']).freeze - BridgeStatusError = Class.new(StandardError) - paginates_per 15 sha_attribute :source_sha @@ -133,6 +131,7 @@ module Ci validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create after_create :keep_around_commits, unless: :importing? + after_find :observe_age_in_minutes, unless: :importing? use_fast_destroy :job_artifacts use_fast_destroy :build_trace_chunks @@ -241,6 +240,13 @@ module Ci pipeline.run_after_commit do unless pipeline.user&.blocked? + Gitlab::AppLogger.info( + message: "Enqueuing hooks for Pipeline #{pipeline.id}: #{pipeline.status}", + class: self.class.name, + pipeline_id: pipeline.id, + project_id: pipeline.project_id, + pipeline_status: pipeline.status) + PipelineHooksWorker.perform_async(pipeline.id) end @@ -332,8 +338,8 @@ module Ci scope :for_id, -> (id) { where(id: id) } scope :for_iid, -> (iid) { where(iid: iid) } scope :for_project, -> (project_id) { where(project_id: project_id) } - scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) } - scope :created_before_id, -> (id) { where('ci_pipelines.id < ?', id) } + scope :created_after, -> (time) { where(arel_table[:created_at].gt(time)) } + scope :created_before_id, -> (id) { where(arel_table[:id].lt(id)) } scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) } scope :with_pipeline_source, -> (source) { where(source: source) } @@ -490,40 +496,16 @@ module Ci .pluck(:stage, :stage_idx).map(&:first) end - def legacy_stage(name) - stage = Ci::LegacyStage.new(self, name: name) - stage unless stage.statuses_count == 0 - end - def ref_exists? project.repository.ref_exists?(git_ref) rescue Gitlab::Git::Repository::NoRepository false end - def legacy_stages_using_composite_status - stages = latest_statuses_ordered_by_stage.group_by(&:stage) - - stages.map do |stage_name, jobs| - composite_status = Gitlab::Ci::Status::Composite - .new(jobs) - - Ci::LegacyStage.new(self, - name: stage_name, - status: composite_status.status, - warnings: composite_status.warnings?) - end - end - def triggered_pipelines_with_preloads triggered_pipelines.preload(:source_job) end - # TODO: Remove usage of this method in templates - def legacy_stages - legacy_stages_using_composite_status - end - def valid_commit_sha if self.sha == Gitlab::Git::BLANK_SHA self.errors.add(:sha, " cant be 00000000 (branch removal)") @@ -1004,6 +986,10 @@ module Ci object_hierarchy(project_condition: :same).base_and_descendants end + def self_and_descendants_complete? + self_and_descendants.all?(&:complete?) + end + # Follow the parent-child relationships and return the top-level parent def root_ancestor return self unless child? @@ -1078,7 +1064,11 @@ module Ci end def has_reports?(reports_scope) - complete? && latest_report_builds(reports_scope).exists? + if Feature.enabled?(:mr_show_reports_immediately, project, type: :development) + latest_report_builds(reports_scope).exists? + else + complete? && latest_report_builds(reports_scope).exists? + end end def has_coverage_reports? @@ -1100,7 +1090,7 @@ module Ci end def test_reports - Gitlab::Ci::Reports::TestReports.new.tap do |test_reports| + Gitlab::Ci::Reports::TestReport.new.tap do |test_reports| latest_test_report_builds.find_each do |build| build.collect_test_reports!(test_reports) end @@ -1222,6 +1212,10 @@ module Ci Gitlab::Utils.slugify(source_ref.to_s) end + def stage(name) + stages.find_by(name: name) + end + def find_stage_by_name!(name) stages.find_by!(name: name) end @@ -1307,10 +1301,20 @@ module Ci end end - def has_expired_test_reports? - strong_memoize(:has_expired_test_reports) do - has_reports?(::Ci::JobArtifact.test_reports.expired) + def has_test_reports? + strong_memoize(:has_test_reports) do + has_reports?(::Ci::JobArtifact.test_reports) + end + end + + def age_in_minutes + return 0 unless persisted? + + unless has_attribute?(:created_at) + raise ArgumentError, 'pipeline not fully loaded' end + + (Time.current - created_at).ceil / 60 end private @@ -1363,6 +1367,21 @@ module Ci project.repository.keep_around(self.sha, self.before_sha) end + def observe_age_in_minutes + return unless age_metric_enabled? + return unless persisted? && has_attribute?(:created_at) + + ::Gitlab::Ci::Pipeline::Metrics + .pipeline_age_histogram + .observe({}, age_in_minutes) + end + + def age_metric_enabled? + ::Gitlab::SafeRequestStore.fetch(:age_metric_enabled) do + ::Feature.enabled?(:ci_pipeline_age_histogram, type: :ops) + end + end + # Without using `unscoped`, caller scope is also included into the query. # Using `unscoped` here will be redundant after Rails 6.1 def object_hierarchy(options = {}) diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb index 2284a05bcc9..cdc3d69f754 100644 --- a/app/models/ci/pipeline_artifact.rb +++ b/app/models/ci/pipeline_artifact.rb @@ -51,6 +51,23 @@ module Ci def find_by_file_type(file_type) find_by(file_type: file_type) end + + def create_or_replace_for_pipeline!(pipeline:, file_type:, file:, size:) + transaction do + pipeline.pipeline_artifacts.find_by_file_type(file_type)&.destroy! + + pipeline.pipeline_artifacts.create!( + file_type: file_type, + project_id: pipeline.project_id, + size: size, + file: file, + file_format: REPORT_TYPES[file_type], + expire_at: EXPIRATION_DATE.from_now + ) + end + rescue ActiveRecord::ActiveRecordError => err + Gitlab::ErrorTracking.track_and_raise_exception(err, { pipeline_id: pipeline.id, file_type: file_type }) + end end def present diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 61194c9b7d1..f41ad890184 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -2,6 +2,7 @@ module Ci class Runner < Ci::ApplicationRecord + prepend Ci::BulkInsertableTags include Gitlab::SQL::Pattern include RedisCacheable include ChronicDurationAttribute @@ -14,6 +15,8 @@ module Ci include Presentable include EachBatch + ignore_column :semver, remove_with: '15.3', remove_after: '2022-07-22' + add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration, expiration_enforced?: :token_expiration_enforced? enum access_level: { @@ -75,9 +78,9 @@ module Ci has_many :groups, through: :runner_namespaces, disable_joins: true has_one :last_build, -> { order('id DESC') }, class_name: 'Ci::Build' + has_one :runner_version, primary_key: :version, foreign_key: :version, class_name: 'Ci::RunnerVersion' before_save :ensure_token - before_save :update_semver, if: -> { version_changed? } scope :active, -> (value = true) { where(active: value) } scope :paused, -> { active(false) } @@ -430,7 +433,6 @@ module Ci values = values&.slice(:version, :revision, :platform, :architecture, :ip_address, :config, :executor) || {} values[:contacted_at] = Time.current values[:executor_type] = EXECUTOR_NAME_TO_TYPES.fetch(values.delete(:executor), :unknown) - values[:semver] = semver_from_version(values[:version]) cache_attributes(values) @@ -451,16 +453,6 @@ module Ci read_attribute(:contacted_at) end - def semver_from_version(version) - parsed_runner_version = ::Gitlab::VersionInfo.parse(version) - - parsed_runner_version.valid? ? parsed_runner_version.to_s : nil - end - - def update_semver - self.semver = semver_from_version(self.version) - end - def namespace_ids strong_memoize(:namespace_ids) do runner_namespaces.pluck(:namespace_id).compact @@ -484,6 +476,10 @@ module Ci private + scope :with_upgrade_status, ->(upgrade_status) do + Ci::Runner.joins(:runner_version).where(runner_version: { status: upgrade_status }) + end + EXECUTOR_NAME_TO_TYPES = { 'unknown' => :unknown, 'custom' => :custom, diff --git a/app/models/ci/runner_version.rb b/app/models/ci/runner_version.rb new file mode 100644 index 00000000000..6b2d0060c9b --- /dev/null +++ b/app/models/ci/runner_version.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Ci + class RunnerVersion < Ci::ApplicationRecord + include EachBatch + include EnumWithNil + + enum_with_nil status: { + not_processed: nil, + invalid_version: -1, + unknown: 0, + not_available: 1, + available: 2, + recommended: 3 + } + + STATUS_DESCRIPTIONS = { + invalid_version: 'Runner version is not valid.', + unknown: 'Upgrade status is unknown.', + not_available: 'Upgrade is not available for the runner.', + available: 'Upgrade is available for the runner.', + recommended: 'Upgrade is available and recommended for the runner.' + }.freeze + + # Override auto generated negative scope (from available) so the scope has expected behavior + scope :not_available, -> { where(status: :not_available) } + + # This scope returns all versions that might need recalculating. For instance, once a version is considered + # :recommended, it normally doesn't change status even if the instance is upgraded + scope :potentially_outdated, -> { where(status: [nil, :not_available, :available, :unknown]) } + + validates :version, length: { maximum: 2048 } + end +end diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 8c4e97ac840..f03d1e96a4b 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -142,7 +142,7 @@ module Ci end def latest_stage_status - statuses.latest.composite_status(project: project) || 'skipped' + statuses.latest.composite_status || 'skipped' end end end diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 5bf5ae51ec8..c4db4754c52 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -4,6 +4,9 @@ module Ci class Trigger < Ci::ApplicationRecord include Presentable include Limitable + include IgnorableColumns + + ignore_column :ref, remove_with: '15.4', remove_after: '2022-08-22' self.limit_name = 'pipeline_triggers' self.limit_scope = :project diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 1e91f248fc4..c80c2ebe69a 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -18,5 +18,9 @@ module Ci scope :unprotected, -> { where(protected: false) } scope :by_environment_scope, -> (environment_scope) { where(environment_scope: environment_scope) } + + def audit_details + key + end end end |