diff options
Diffstat (limited to 'app/models/ci/pipeline.rb')
-rw-r--r-- | app/models/ci/pipeline.rb | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb new file mode 100644 index 00000000000..9b5b46f4928 --- /dev/null +++ b/app/models/ci/pipeline.rb @@ -0,0 +1,185 @@ +module Ci + class Pipeline < ActiveRecord::Base + extend Ci::Model + include Statuseable + + self.table_name = 'ci_commits' + + belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id + has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id + has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id + has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id + + validates_presence_of :sha + validates_presence_of :status + validate :valid_commit_sha + + # Invalidate object and save if when touched + after_touch :update_state + + def self.truncate_sha(sha) + sha[0...8] + end + + def self.stages + # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries + CommitStatus.where(pipeline: pluck(:id)).stages + end + + def project_id + project.id + end + + def valid_commit_sha + if self.sha == Gitlab::Git::BLANK_SHA + self.errors.add(:sha, " cant be 00000000 (branch removal)") + end + end + + def git_author_name + commit_data.author_name if commit_data + end + + def git_author_email + commit_data.author_email if commit_data + end + + def git_commit_message + commit_data.message if commit_data + end + + def short_sha + Ci::Pipeline.truncate_sha(sha) + end + + def commit_data + @commit ||= project.commit(sha) + rescue + nil + end + + def branch? + !tag? + end + + def retryable? + builds.latest.any? do |build| + build.failed? && build.retryable? + end + end + + def cancelable? + builds.running_or_pending.any? + end + + def cancel_running + builds.running_or_pending.each(&:cancel) + end + + def retry_failed + builds.latest.failed.select(&:retryable?).each(&:retry) + end + + def latest? + return false unless ref + commit = project.commit(ref) + return false unless commit + commit.sha == sha + end + + def triggered? + trigger_requests.any? + end + + def create_builds(user, trigger_request = nil) + return unless config_processor + config_processor.stages.any? do |stage| + CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present? + end + end + + def create_next_builds(build) + return unless config_processor + + # don't create other builds if this one is retried + latest_builds = builds.latest + return unless latest_builds.exists?(build.id) + + # get list of stages after this build + next_stages = config_processor.stages.drop_while { |stage| stage != build.stage } + next_stages.delete(build.stage) + + # get status for all prior builds + prior_builds = latest_builds.where.not(stage: next_stages) + prior_status = prior_builds.status + + # create builds for next stages based + next_stages.any? do |stage| + CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present? + end + end + + def retried + @retried ||= (statuses.order(id: :desc) - statuses.latest) + end + + def coverage + coverage_array = statuses.latest.map(&:coverage).compact + if coverage_array.size >= 1 + '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) + end + end + + def config_processor + return nil unless ci_yaml_file + return @config_processor if defined?(@config_processor) + + @config_processor ||= begin + Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) + rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e + save_yaml_error(e.message) + nil + rescue + save_yaml_error("Undefined error") + nil + end + end + + def ci_yaml_file + return @ci_yaml_file if defined?(@ci_yaml_file) + + @ci_yaml_file ||= begin + blob = project.repository.blob_at(sha, '.gitlab-ci.yml') + blob.load_all_data!(project.repository) + blob.data + rescue + nil + end + end + + def skip_ci? + git_commit_message =~ /(\[ci skip\])/ if git_commit_message + end + + private + + def update_state + statuses.reload + self.status = if yaml_errors.blank? + statuses.latest.status || 'skipped' + else + 'failed' + end + self.started_at = statuses.started_at + self.finished_at = statuses.finished_at + self.duration = statuses.latest.duration + save + end + + def save_yaml_error(error) + return if self.yaml_errors? + self.yaml_errors = error + update_state + end + end +end |