PageRenderTime 57ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/app/models/ci/build.rb

https://gitlab.com/oatberrycrunch/gitlab-ce
Ruby | 462 lines | 354 code | 78 blank | 30 comment | 42 complexity | 8f44e8a952c672c0c8610ec71e8bda04 MD5 | raw file
  1. module Ci
  2. class Build < CommitStatus
  3. belongs_to :runner, class_name: 'Ci::Runner'
  4. belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
  5. belongs_to :erased_by, class_name: 'User'
  6. serialize :options
  7. serialize :yaml_variables
  8. validates :coverage, numericality: true, allow_blank: true
  9. validates_presence_of :ref
  10. scope :unstarted, ->() { where(runner_id: nil) }
  11. scope :ignore_failures, ->() { where(allow_failure: false) }
  12. scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) }
  13. scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
  14. scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
  15. scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
  16. scope :manual_actions, ->() { where(when: :manual).relevant }
  17. mount_uploader :artifacts_file, ArtifactUploader
  18. mount_uploader :artifacts_metadata, ArtifactUploader
  19. acts_as_taggable
  20. before_save :update_artifacts_size, if: :artifacts_file_changed?
  21. before_destroy { project }
  22. after_create :execute_hooks
  23. class << self
  24. def first_pending
  25. pending.unstarted.order('created_at ASC').first
  26. end
  27. def create_from(build)
  28. new_build = build.dup
  29. new_build.status = 'pending'
  30. new_build.runner_id = nil
  31. new_build.trigger_request_id = nil
  32. new_build.save
  33. end
  34. def retry(build, user = nil)
  35. new_build = Ci::Build.new(status: 'pending')
  36. new_build.ref = build.ref
  37. new_build.tag = build.tag
  38. new_build.options = build.options
  39. new_build.commands = build.commands
  40. new_build.tag_list = build.tag_list
  41. new_build.project = build.project
  42. new_build.pipeline = build.pipeline
  43. new_build.name = build.name
  44. new_build.allow_failure = build.allow_failure
  45. new_build.stage = build.stage
  46. new_build.stage_idx = build.stage_idx
  47. new_build.trigger_request = build.trigger_request
  48. new_build.yaml_variables = build.yaml_variables
  49. new_build.when = build.when
  50. new_build.user = user
  51. new_build.environment = build.environment
  52. new_build.save
  53. MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
  54. new_build
  55. end
  56. end
  57. state_machine :status do
  58. after_transition pending: :running do |build|
  59. build.execute_hooks
  60. end
  61. after_transition any => [:success, :failed, :canceled] do |build|
  62. build.update_coverage
  63. build.execute_hooks
  64. end
  65. after_transition any => [:success] do |build|
  66. if build.environment.present?
  67. service = CreateDeploymentService.new(build.project, build.user,
  68. environment: build.environment,
  69. sha: build.sha,
  70. ref: build.ref,
  71. tag: build.tag)
  72. service.execute(build)
  73. end
  74. end
  75. end
  76. def manual?
  77. self.when == 'manual'
  78. end
  79. def other_actions
  80. pipeline.manual_actions.where.not(name: name)
  81. end
  82. def playable?
  83. project.builds_enabled? && commands.present? && manual?
  84. end
  85. def play(current_user = nil)
  86. # Try to queue a current build
  87. if self.queue
  88. self.update(user: current_user)
  89. self
  90. else
  91. # Otherwise we need to create a duplicate
  92. Ci::Build.retry(self, current_user)
  93. end
  94. end
  95. def retryable?
  96. project.builds_enabled? && commands.present? && complete?
  97. end
  98. def retried?
  99. !self.pipeline.statuses.latest.include?(self)
  100. end
  101. def depends_on_builds
  102. # Get builds of the same type
  103. latest_builds = self.pipeline.builds.latest
  104. # Return builds from previous stages
  105. latest_builds.where('stage_idx < ?', stage_idx)
  106. end
  107. def trace_html
  108. trace_with_state[:html] || ''
  109. end
  110. def trace_with_state(state = nil)
  111. trace_with_state = Ci::Ansi2html::convert(trace, state) if trace.present?
  112. trace_with_state || {}
  113. end
  114. def timeout
  115. project.build_timeout
  116. end
  117. def variables
  118. variables = predefined_variables
  119. variables += project.predefined_variables
  120. variables += pipeline.predefined_variables
  121. variables += runner.predefined_variables if runner
  122. variables += project.container_registry_variables
  123. variables += yaml_variables
  124. variables += project.secret_variables
  125. variables += trigger_request.user_variables if trigger_request
  126. variables
  127. end
  128. def merge_request
  129. merge_requests = MergeRequest.includes(:merge_request_diff)
  130. .where(source_branch: ref, source_project_id: pipeline.gl_project_id)
  131. .reorder(iid: :asc)
  132. merge_requests.find do |merge_request|
  133. merge_request.commits.any? { |ci| ci.id == pipeline.sha }
  134. end
  135. end
  136. def project_id
  137. pipeline.project_id
  138. end
  139. def project_name
  140. project.name
  141. end
  142. def repo_url
  143. auth = "gitlab-ci-token:#{token}@"
  144. project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
  145. prefix + auth
  146. end
  147. end
  148. def allow_git_fetch
  149. project.build_allow_git_fetch
  150. end
  151. def update_coverage
  152. return unless project
  153. coverage_regex = project.build_coverage_regex
  154. return unless coverage_regex
  155. coverage = extract_coverage(trace, coverage_regex)
  156. if coverage.is_a? Numeric
  157. update_attributes(coverage: coverage)
  158. end
  159. end
  160. def extract_coverage(text, regex)
  161. begin
  162. matches = text.scan(Regexp.new(regex)).last
  163. matches = matches.last if matches.kind_of?(Array)
  164. coverage = matches.gsub(/\d+(\.\d+)?/).first
  165. if coverage.present?
  166. coverage.to_f
  167. end
  168. rescue
  169. # if bad regex or something goes wrong we dont want to interrupt transition
  170. # so we just silentrly ignore error for now
  171. end
  172. end
  173. def has_trace?
  174. raw_trace.present?
  175. end
  176. def raw_trace
  177. if File.file?(path_to_trace)
  178. File.read(path_to_trace)
  179. elsif project.ci_id && File.file?(old_path_to_trace)
  180. # Temporary fix for build trace data integrity
  181. File.read(old_path_to_trace)
  182. else
  183. # backward compatibility
  184. read_attribute :trace
  185. end
  186. end
  187. def trace
  188. trace = raw_trace
  189. if project && trace.present? && project.runners_token.present?
  190. trace.gsub(project.runners_token, 'xxxxxx')
  191. else
  192. trace
  193. end
  194. end
  195. def trace_length
  196. if raw_trace
  197. raw_trace.bytesize
  198. else
  199. 0
  200. end
  201. end
  202. def trace=(trace)
  203. recreate_trace_dir
  204. File.write(path_to_trace, trace)
  205. end
  206. def recreate_trace_dir
  207. unless Dir.exist?(dir_to_trace)
  208. FileUtils.mkdir_p(dir_to_trace)
  209. end
  210. end
  211. private :recreate_trace_dir
  212. def append_trace(trace_part, offset)
  213. recreate_trace_dir
  214. File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
  215. File.open(path_to_trace, 'ab') do |f|
  216. f.write(trace_part)
  217. end
  218. end
  219. def dir_to_trace
  220. File.join(
  221. Settings.gitlab_ci.builds_path,
  222. created_at.utc.strftime("%Y_%m"),
  223. project.id.to_s
  224. )
  225. end
  226. def path_to_trace
  227. "#{dir_to_trace}/#{id}.log"
  228. end
  229. ##
  230. # Deprecated
  231. #
  232. # This is a hotfix for CI build data integrity, see #4246
  233. # Should be removed in 8.4, after CI files migration has been done.
  234. #
  235. def old_dir_to_trace
  236. File.join(
  237. Settings.gitlab_ci.builds_path,
  238. created_at.utc.strftime("%Y_%m"),
  239. project.ci_id.to_s
  240. )
  241. end
  242. ##
  243. # Deprecated
  244. #
  245. # This is a hotfix for CI build data integrity, see #4246
  246. # Should be removed in 8.4, after CI files migration has been done.
  247. #
  248. def old_path_to_trace
  249. "#{old_dir_to_trace}/#{id}.log"
  250. end
  251. ##
  252. # Deprecated
  253. #
  254. # This contains a hotfix for CI build data integrity, see #4246
  255. #
  256. # This method is used by `ArtifactUploader` to create a store_dir.
  257. # Warning: Uploader uses it after AND before file has been stored.
  258. #
  259. # This method returns old path to artifacts only if it already exists.
  260. #
  261. def artifacts_path
  262. old = File.join(created_at.utc.strftime('%Y_%m'),
  263. project.ci_id.to_s,
  264. id.to_s)
  265. old_store = File.join(ArtifactUploader.artifacts_path, old)
  266. return old if project.ci_id && File.directory?(old_store)
  267. File.join(
  268. created_at.utc.strftime('%Y_%m'),
  269. project.id.to_s,
  270. id.to_s
  271. )
  272. end
  273. def token
  274. project.runners_token
  275. end
  276. def valid_token?(token)
  277. project.valid_runners_token?(token)
  278. end
  279. def has_tags?
  280. tag_list.any?
  281. end
  282. def any_runners_online?
  283. project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
  284. end
  285. def stuck?
  286. pending? && !any_runners_online?
  287. end
  288. def execute_hooks
  289. return unless project
  290. build_data = Gitlab::BuildDataBuilder.build(self)
  291. project.execute_hooks(build_data.dup, :build_hooks)
  292. project.execute_services(build_data.dup, :build_hooks)
  293. project.running_or_pending_build_count(force: true)
  294. end
  295. def artifacts?
  296. !artifacts_expired? && artifacts_file.exists?
  297. end
  298. def artifacts_metadata?
  299. artifacts? && artifacts_metadata.exists?
  300. end
  301. def artifacts_metadata_entry(path, **options)
  302. metadata = Gitlab::Ci::Build::Artifacts::Metadata.new(
  303. artifacts_metadata.path,
  304. path,
  305. **options)
  306. metadata.to_entry
  307. end
  308. def erase_artifacts!
  309. remove_artifacts_file!
  310. remove_artifacts_metadata!
  311. save
  312. end
  313. def erase(opts = {})
  314. return false unless erasable?
  315. erase_artifacts!
  316. erase_trace!
  317. update_erased!(opts[:erased_by])
  318. end
  319. def erasable?
  320. complete? && (artifacts? || has_trace?)
  321. end
  322. def erased?
  323. !self.erased_at.nil?
  324. end
  325. def artifacts_expired?
  326. artifacts_expire_at && artifacts_expire_at < Time.now
  327. end
  328. def artifacts_expire_in
  329. artifacts_expire_at - Time.now if artifacts_expire_at
  330. end
  331. def artifacts_expire_in=(value)
  332. self.artifacts_expire_at =
  333. if value
  334. Time.now + ChronicDuration.parse(value)
  335. end
  336. end
  337. def keep_artifacts!
  338. self.update(artifacts_expire_at: nil)
  339. end
  340. def when
  341. read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
  342. end
  343. def yaml_variables
  344. read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
  345. end
  346. private
  347. def update_artifacts_size
  348. self.artifacts_size = if artifacts_file.exists?
  349. artifacts_file.size
  350. else
  351. nil
  352. end
  353. end
  354. def erase_trace!
  355. self.trace = nil
  356. end
  357. def update_erased!(user = nil)
  358. self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil)
  359. end
  360. def predefined_variables
  361. variables = [
  362. { key: 'CI', value: 'true', public: true },
  363. { key: 'GITLAB_CI', value: 'true', public: true },
  364. { key: 'CI_BUILD_ID', value: id.to_s, public: true },
  365. { key: 'CI_BUILD_TOKEN', value: token, public: false },
  366. { key: 'CI_BUILD_REF', value: sha, public: true },
  367. { key: 'CI_BUILD_BEFORE_SHA', value: before_sha, public: true },
  368. { key: 'CI_BUILD_REF_NAME', value: ref, public: true },
  369. { key: 'CI_BUILD_NAME', value: name, public: true },
  370. { key: 'CI_BUILD_STAGE', value: stage, public: true },
  371. { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
  372. { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
  373. { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }
  374. ]
  375. variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
  376. variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
  377. variables
  378. end
  379. def build_attributes_from_config
  380. return {} unless pipeline.config_processor
  381. pipeline.config_processor.build_attributes(name)
  382. end
  383. end
  384. end