lib/release_tools/pipeline_tracer/pipeline.rb (132 lines of code) (raw):

# frozen_string_literal: true module ReleaseTools module PipelineTracer # Helper class for the PipelineTracer module class Pipeline include ::SemanticLogger::Loggable PIPELINE_URL_REGEX = %r{https://(?<instance>dev\.gitlab\.org|ops\.gitlab\.net|gitlab\.com)/(?<project>.+)/-/pipelines/(?<pipeline_id>\d+)} FINISHED_STATUSES = %w[success failed canceled].freeze # Jobs that should be ignored when calculating end_time of the pipeline. # This is used to ignore the metrics:trace-pipeline job since the job runs much later than # the rest of the pipeline and should not count towards the duration of the pipeline. IGNORED_JOBS_FOR_END_TIME_CALCULATION = %w[metrics:trace-pipeline].freeze attr_reader :gitlab_instance, :project, :id # @param [string] pipeline_url def self.from_url(pipeline_url) match = PIPELINE_URL_REGEX.match(pipeline_url) new(match[:instance], match[:project], match[:pipeline_id]) end # @param [string] gitlab_instance Ex: 'ops.gitlab.net', 'dev.gitlab.org', 'gitlab.com' # @param [string] project Ex: 'gitlab-org/release/tools' # @param [string, integer] pipeline_id Ex: '12345' or 12345 def initialize(gitlab_instance, project, pipeline_id) @gitlab_instance = gitlab_instance @project = project @id = pipeline_id end def details @details ||= Retriable.with_context(:api) do client.pipeline(project, id) end end def bridge_jobs @bridge_jobs ||= Retriable.with_context(:api) do client.pipeline_bridges(project, id) end end def jobs @jobs ||= Retriable.with_context(:api) do client.pipeline_jobs(project, id, { include_retried: true, per_page: 100 }) end end def url "https://#{gitlab_instance}/#{project}/-/pipelines/#{id}" end def start_time return @start_time if defined?(@start_time) logger.info( 'Start time of pipeline', pipeline_url: url, started_at: details.started_at, created_at: details.created_at ) @start_time ||= details.started_at || details.created_at end def end_time @end_time ||= if valid_finished_at? logger.info( 'Has valid finished_at', pipeline_url: url, finished_at: details.finished_at, updated_at: details.updated_at ) details.finished_at || details.updated_at else calculate_end_time end end def real_time_duration Time.parse(end_time) - Time.parse(start_time) end def root_attributes { user: details.user.username, web_url: details.web_url, created_at: details.created_at, updated_at: details.updated_at, ref: details.ref, duration: details.duration || 0, queued_duration: details.queued_duration || 0, status: details.status, started_at: details.started_at, finished_at: details.finished_at || 0 } end def valid_finished_at? FINISHED_STATUSES.include?(details.status) end def deploy_environment deploy_env = pipeline_variables.detect { |v| v.key == 'DEPLOY_ENVIRONMENT' } return unless deploy_env deploy_env.value end def client @client ||= case gitlab_instance when 'dev.gitlab.org' GitlabDevClient when 'ops.gitlab.net' GitlabOpsClient when 'gitlab.com' GitlabClient else raise "Unknown Gitlab instance: #{gitlab_instance}" end end private # The end_time of the pipeline should be the finished_at value of the last job to complete. def calculate_end_time last_job_end_time = last_job_finished_at last_bridge_job_end_time = last_bridge_job_finished_at logger.info( 'Calculating end time', pipeline_url: url, last_job_finished_at: last_job_end_time&.iso8601, last_bridge_job_finished_at: last_bridge_job_end_time&.iso8601 ) [last_job_end_time, last_bridge_job_end_time].compact.max&.iso8601 end def last_job_finished_at all_finished_at = jobs.auto_paginate.collect do |job| next if IGNORED_JOBS_FOR_END_TIME_CALCULATION.include?(job.name) next unless job.finished_at Time.parse(job.finished_at) end all_finished_at.compact.max end def last_bridge_job_finished_at bridge_jobs .auto_paginate .filter_map { |job| Time.parse(job.finished_at) if job.finished_at } .max end def pipeline_variables client.pipeline_variables(project, id) end end end end