PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/app/models/ci/pipeline.rb

https://gitlab.com/danieljianu/gitlab-ce
Ruby | 322 lines | 247 code | 67 blank | 8 comment | 9 complexity | a91c62324e2c837c56ae74ad780b30b1 MD5 | raw file
  1. module Ci
  2. class Pipeline < ActiveRecord::Base
  3. extend Ci::Model
  4. include HasStatus
  5. include Importable
  6. self.table_name = 'ci_commits'
  7. belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
  8. belongs_to :user
  9. has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
  10. has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
  11. has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
  12. validates_presence_of :sha, unless: :importing?
  13. validates_presence_of :ref, unless: :importing?
  14. validates_presence_of :status, unless: :importing?
  15. validate :valid_commit_sha, unless: :importing?
  16. after_save :keep_around_commits, unless: :importing?
  17. delegate :stages, to: :statuses
  18. state_machine :status, initial: :created do
  19. event :enqueue do
  20. transition created: :pending
  21. transition [:success, :failed, :canceled, :skipped] => :running
  22. end
  23. event :run do
  24. transition any => :running
  25. end
  26. event :skip do
  27. transition any => :skipped
  28. end
  29. event :drop do
  30. transition any => :failed
  31. end
  32. event :succeed do
  33. transition any => :success
  34. end
  35. event :cancel do
  36. transition any => :canceled
  37. end
  38. before_transition [:created, :pending] => :running do |pipeline|
  39. pipeline.started_at = Time.now
  40. end
  41. before_transition any => [:success, :failed, :canceled] do |pipeline|
  42. pipeline.finished_at = Time.now
  43. end
  44. after_transition [:created, :pending] => :running do |pipeline|
  45. MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
  46. update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil)
  47. end
  48. after_transition any => [:success] do |pipeline|
  49. MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
  50. update_all(latest_build_finished_at: pipeline.finished_at)
  51. end
  52. before_transition do |pipeline|
  53. pipeline.update_duration
  54. end
  55. after_transition do |pipeline, transition|
  56. pipeline.execute_hooks unless transition.loopback?
  57. end
  58. end
  59. # ref can't be HEAD or SHA, can only be branch/tag name
  60. def self.latest_successful_for(ref)
  61. where(ref: ref).order(id: :desc).success.first
  62. end
  63. def self.truncate_sha(sha)
  64. sha[0...8]
  65. end
  66. def self.stages
  67. # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
  68. CommitStatus.where(pipeline: pluck(:id)).stages
  69. end
  70. def self.total_duration
  71. where.not(duration: nil).sum(:duration)
  72. end
  73. def stages_with_latest_statuses
  74. statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
  75. end
  76. def project_id
  77. project.id
  78. end
  79. def valid_commit_sha
  80. if self.sha == Gitlab::Git::BLANK_SHA
  81. self.errors.add(:sha, " cant be 00000000 (branch removal)")
  82. end
  83. end
  84. def git_author_name
  85. commit.try(:author_name)
  86. end
  87. def git_author_email
  88. commit.try(:author_email)
  89. end
  90. def git_commit_message
  91. commit.try(:message)
  92. end
  93. def git_commit_title
  94. commit.try(:title)
  95. end
  96. def short_sha
  97. Ci::Pipeline.truncate_sha(sha)
  98. end
  99. def commit
  100. @commit ||= project.commit(sha)
  101. rescue
  102. nil
  103. end
  104. def branch?
  105. !tag?
  106. end
  107. def manual_actions
  108. builds.latest.manual_actions
  109. end
  110. def retryable?
  111. builds.latest.any? do |build|
  112. build.failed? && build.retryable?
  113. end
  114. end
  115. def cancelable?
  116. builds.running_or_pending.any?
  117. end
  118. def cancel_running
  119. builds.running_or_pending.each(&:cancel)
  120. end
  121. def retry_failed(user)
  122. builds.latest.failed.select(&:retryable?).each do |build|
  123. Ci::Build.retry(build, user)
  124. end
  125. end
  126. def mark_as_processable_after_stage(stage_idx)
  127. builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
  128. end
  129. def latest?
  130. return false unless ref
  131. commit = project.commit(ref)
  132. return false unless commit
  133. commit.sha == sha
  134. end
  135. def triggered?
  136. trigger_requests.any?
  137. end
  138. def retried
  139. @retried ||= (statuses.order(id: :desc) - statuses.latest)
  140. end
  141. def coverage
  142. coverage_array = statuses.latest.map(&:coverage).compact
  143. if coverage_array.size >= 1
  144. '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
  145. end
  146. end
  147. def config_builds_attributes
  148. return [] unless config_processor
  149. config_processor.
  150. builds_for_ref(ref, tag?, trigger_requests.first).
  151. sort_by { |build| build[:stage_idx] }
  152. end
  153. def has_warnings?
  154. builds.latest.ignored.any?
  155. end
  156. def config_processor
  157. return nil unless ci_yaml_file
  158. return @config_processor if defined?(@config_processor)
  159. @config_processor ||= begin
  160. Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
  161. rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
  162. self.yaml_errors = e.message
  163. nil
  164. rescue
  165. self.yaml_errors = 'Undefined error'
  166. nil
  167. end
  168. end
  169. def ci_yaml_file
  170. return @ci_yaml_file if defined?(@ci_yaml_file)
  171. @ci_yaml_file ||= begin
  172. blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
  173. blob.load_all_data!(project.repository)
  174. blob.data
  175. rescue
  176. nil
  177. end
  178. end
  179. def environments
  180. builds.where.not(environment: nil).success.pluck(:environment).uniq
  181. end
  182. # Manually set the notes for a Ci::Pipeline
  183. # There is no ActiveRecord relation between Ci::Pipeline and notes
  184. # as they are related to a commit sha. This method helps importing
  185. # them using the +Gitlab::ImportExport::RelationFactory+ class.
  186. def notes=(notes)
  187. notes.each do |note|
  188. note[:id] = nil
  189. note[:commit_id] = sha
  190. note[:noteable_id] = self['id']
  191. note.save!
  192. end
  193. end
  194. def notes
  195. Note.for_commit_id(sha)
  196. end
  197. def process!
  198. Ci::ProcessPipelineService.new(project, user).execute(self)
  199. end
  200. def build_updated
  201. with_lock do
  202. reload
  203. case latest_builds_status
  204. when 'pending' then enqueue
  205. when 'running' then run
  206. when 'success' then succeed
  207. when 'failed' then drop
  208. when 'canceled' then cancel
  209. when 'skipped' then skip
  210. end
  211. end
  212. end
  213. def predefined_variables
  214. [
  215. { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
  216. ]
  217. end
  218. def queued_duration
  219. return unless started_at
  220. seconds = (started_at - created_at).to_i
  221. seconds unless seconds.zero?
  222. end
  223. def update_duration
  224. return unless started_at
  225. self.duration = Gitlab::Ci::PipelineDuration.from_pipeline(self)
  226. end
  227. def execute_hooks
  228. data = pipeline_data
  229. project.execute_hooks(data, :pipeline_hooks)
  230. project.execute_services(data, :pipeline_hooks)
  231. end
  232. # Merge requests for which the current pipeline is running against
  233. # the merge request's latest commit.
  234. def merge_requests
  235. @merge_requests ||=
  236. begin
  237. project.merge_requests.where(source_branch: self.ref).
  238. select { |merge_request| merge_request.pipeline.try(:id) == self.id }
  239. end
  240. end
  241. private
  242. def pipeline_data
  243. Gitlab::DataBuilder::Pipeline.build(self)
  244. end
  245. def latest_builds_status
  246. return 'failed' unless yaml_errors.blank?
  247. statuses.latest.status || 'skipped'
  248. end
  249. def keep_around_commits
  250. return unless project
  251. project.repository.keep_around(self.sha)
  252. project.repository.keep_around(self.before_sha)
  253. end
  254. end
  255. end