diff --git a/Gemfile b/Gemfile
index 442d185..c62c672 100644
--- a/Gemfile
+++ b/Gemfile
@@ -10,7 +10,7 @@
source 'https://rubygems.org'
-ruby '3.1.2'
+ruby '>= 3.1.2'
# Token
gem 'jwt'
diff --git a/db/migrate/20250812101554_add_pull_requests_plans.rb b/db/migrate/20250812101554_add_pull_requests_plans.rb
new file mode 100644
index 0000000..d54e4b3
--- /dev/null
+++ b/db/migrate/20250812101554_add_pull_requests_plans.rb
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# 20250812101554_add_pull_requests_plans.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+class AddPullRequestsPlans < ActiveRecord::Migration[6.0]
+ def change
+ add_reference :plans, :pull_request, foreign_key: true
+ add_column :plans, :name, :string, null: false, default: ''
+ end
+end
diff --git a/db/migrate/20250822071834_add_check_suite_plan.rb b/db/migrate/20250822071834_add_check_suite_plan.rb
new file mode 100644
index 0000000..1a29fbc
--- /dev/null
+++ b/db/migrate/20250822071834_add_check_suite_plan.rb
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# 20250822071834_add_check_suite_plan.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+class AddCheckSuitePlan < ActiveRecord::Migration[6.0]
+ def change
+ add_reference :check_suites, :plan, foreign_key: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cf40164..882e693 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2025_04_16_153222) do
+ActiveRecord::Schema[7.2].define(version: 2025_08_22_071834) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -60,8 +60,10 @@
t.bigint "github_user_id"
t.bigint "stopped_in_stage_id"
t.bigint "cancelled_previous_check_suite_id"
+ t.bigint "plan_id"
t.index ["cancelled_previous_check_suite_id"], name: "index_check_suites_on_cancelled_previous_check_suite_id"
t.index ["github_user_id"], name: "index_check_suites_on_github_user_id"
+ t.index ["plan_id"], name: "index_check_suites_on_plan_id"
t.index ["pull_request_id"], name: "index_check_suites_on_pull_request_id"
t.index ["stopped_in_stage_id"], name: "index_check_suites_on_stopped_in_stage_id"
end
@@ -130,7 +132,10 @@
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "check_suite_id"
+ t.bigint "pull_request_id"
+ t.string "name", default: "", null: false
t.index ["check_suite_id"], name: "index_plans_on_check_suite_id"
+ t.index ["pull_request_id"], name: "index_plans_on_pull_request_id"
end
create_table "pull_request_subscriptions", force: :cascade do |t|
@@ -195,12 +200,14 @@
add_foreign_key "audit_retries", "github_users"
add_foreign_key "check_suites", "check_suites", column: "cancelled_previous_check_suite_id"
add_foreign_key "check_suites", "github_users"
+ add_foreign_key "check_suites", "plans"
add_foreign_key "check_suites", "pull_requests"
add_foreign_key "check_suites", "stages", column: "stopped_in_stage_id"
add_foreign_key "ci_jobs", "check_suites"
add_foreign_key "ci_jobs", "stages"
add_foreign_key "github_users", "organizations"
add_foreign_key "plans", "check_suites"
+ add_foreign_key "plans", "pull_requests"
add_foreign_key "pull_request_subscriptions", "pull_requests"
add_foreign_key "pull_requests", "github_users"
add_foreign_key "stages", "check_suites"
diff --git a/lib/bamboo_ci/api.rb b/lib/bamboo_ci/api.rb
index aa89263..3cb7ec1 100644
--- a/lib/bamboo_ci/api.rb
+++ b/lib/bamboo_ci/api.rb
@@ -28,8 +28,8 @@ def get_status(id)
get_request(URI("https://127.0.0.1/rest/api/latest/result/#{id}?expand=stages.stage.results,artifacts"))
end
- def submit_pr_to_ci(check_suite, ci_variables)
- url = "https://127.0.0.1/rest/api/latest/queue/#{check_suite.pull_request.plan}"
+ def submit_pr_to_ci(check_suite, plan, ci_variables)
+ url = "https://127.0.0.1/rest/api/latest/queue/#{plan.bamboo_ci_plan_name}"
url += custom_variables(check_suite)
@@ -40,7 +40,7 @@ def submit_pr_to_ci(check_suite, ci_variables)
logger(Logger::DEBUG, "Submission URL:\n #{url}")
# Fetch Request
- post_request(URI(url))
+ post_request(URI(url.delete(' ')))
end
def custom_variables(check_suite)
@@ -58,7 +58,7 @@ def add_comment_to_ci(key, comment)
logger(Logger::DEBUG, "Comment Submission URL:\n #{url}")
# Fetch Request
- post_request(URI(url), body: "#{comment}")
+ post_request(URI(url.delete(' ')), body: "#{comment}")
end
def logger(severity, message)
diff --git a/lib/bamboo_ci/plan_run.rb b/lib/bamboo_ci/plan_run.rb
index 8c9bc9a..5bb0d30 100644
--- a/lib/bamboo_ci/plan_run.rb
+++ b/lib/bamboo_ci/plan_run.rb
@@ -22,7 +22,7 @@ class PlanRun
attr_reader :ci_key
attr_accessor :checks_run, :ci_variables
- def initialize(check_suite, logger_level: Logger::INFO)
+ def initialize(check_suite, plan, logger_level: Logger::INFO)
@logger_manager = []
@logger_level = logger_level
@@ -32,14 +32,18 @@ def initialize(check_suite, logger_level: Logger::INFO)
logger(Logger::INFO, "BambooCi::PlanRun - CheckSuite: #{check_suite.inspect}")
@check_suite = check_suite
+ @plan = plan
@ci_variables = []
end
def start_plan
- @response = submit_pr_to_ci(@check_suite, @ci_variables)
+ @refs = []
+ @response = submit_pr_to_ci(@check_suite, @plan, @ci_variables)
case @response&.code.to_i
when 200, 201
+ @check_suite.update(bamboo_ci_ref: JSON.parse(@response.body)['buildResultKey'])
+
success(@response)
when 400..500
failed(@response)
@@ -58,6 +62,10 @@ def bamboo_reference
JSON.parse(@response.body)['buildResultKey']
end
+ def bamboo_references
+ @refs
+ end
+
private
def success(response)
diff --git a/lib/github/build/action.rb b/lib/github/build/action.rb
index 1557ad5..28e8e80 100644
--- a/lib/github/build/action.rb
+++ b/lib/github/build/action.rb
@@ -26,15 +26,17 @@ class Action
# Initializes the Action class with the given parameters.
#
# @param [CheckSuite] check_suite The CheckSuite to handle.
- # @param [Github] github The Github instance to use.
+ # @param [Github::Check] github The Github::Check instance to use.
# @param [Array] jobs The jobs to create for the CheckSuite.
+ # @param [String] Stage Plan name.
# @param [Integer] logger_level The logging level to use (default: Logger::INFO).
- def initialize(check_suite, github, jobs, logger_level: Logger::INFO)
+ def initialize(check_suite, github, jobs, name, logger_level: Logger::INFO)
@check_suite = check_suite
@github = github
@jobs = jobs
@loggers = []
- @stages = StageConfiguration.all
+ @stages_config = StageConfiguration.all
+ @name = name
%w[github_app.log github_build_action.log].each do |filename|
@loggers << GithubLogger.instance.create(filename, logger_level)
@@ -49,11 +51,11 @@ def initialize(check_suite, github, jobs, logger_level: Logger::INFO)
#
# @param [Boolean] rerun Indicates if the jobs should be rerun (default: false).
def create_summary(rerun: false)
- logger(Logger::INFO, "SUMMARY #{@stages.inspect}")
+ logger(Logger::INFO, "SUMMARY #{@stages_config.inspect}")
Github::Build::SkipOldTests.new(@check_suite).skip_old_tests
- @stages.each do |stage_config|
+ @stages_config.each do |stage_config|
create_check_run_stage(stage_config)
end
@@ -118,7 +120,7 @@ def create_ci_job(job)
return if stage_config.nil?
- stage = Stage.find_by(check_suite: @check_suite, name: stage_config.github_check_run_name)
+ stage = Stage.find_by(check_suite: @check_suite, name: "#{stage_config.github_check_run_name} - #{@name}")
logger(Logger::INFO, "create_jobs - #{job.inspect} -> #{stage.inspect}")
@@ -130,9 +132,8 @@ def create_ci_job(job)
#
# @param [StageConfiguration] stage_config The stage configuration.
def create_check_run_stage(stage_config)
- stage = Stage.find_by(name: stage_config.github_check_run_name, check_suite_id: @check_suite.id)
-
- logger(Logger::INFO, "STAGE #{stage_config.github_check_run_name} #{stage.inspect} - @#{@check_suite.inspect}")
+ logger(Logger::INFO, "create_check_run_stage - #{stage_config.github_check_run_name} - #{@name}")
+ stage = Stage.find_by(name: "#{stage_config.github_check_run_name} - #{@name}", check_suite_id: @check_suite.id)
return create_stage(stage_config) if stage.nil?
return unless stage.configuration.can_retry?
@@ -148,7 +149,7 @@ def create_check_run_stage(stage_config)
# @param [StageConfiguration] stage_config The stage configuration.
# @return [Stage] The created stage.
def create_stage(stage_config)
- name = stage_config.github_check_run_name
+ name = "#{stage_config.github_check_run_name} - #{@name}"
stage =
Stage.create(check_suite: @check_suite,
diff --git a/lib/github/build/plan_run.rb b/lib/github/build/plan_run.rb
new file mode 100644
index 0000000..1242158
--- /dev/null
+++ b/lib/github/build/plan_run.rb
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# plan_run.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+module Github
+ module Build
+ class PlanRun
+ TIMER = 5 # seconds
+ def initialize(pull_request, payload)
+ @pull_request = pull_request
+ @payload = payload
+ end
+
+ def build
+ return [422, 'No Plans associated with this Pull Request'] if @pull_request.plans.empty?
+
+ @pull_request.plans.each do |plan|
+ CreateExecutionByPlan
+ .delay(run_at: TIMER.seconds.from_now.utc, queue: 'create_execution_by_plan')
+ .create(@pull_request.id, @payload, plan)
+ end
+
+ [200, 'Scheduled Plan Runs']
+ end
+ end
+ end
+end
diff --git a/lib/github/build/skip_old_tests.rb b/lib/github/build/skip_old_tests.rb
index 19efdc3..f90fa47 100644
--- a/lib/github/build/skip_old_tests.rb
+++ b/lib/github/build/skip_old_tests.rb
@@ -35,7 +35,6 @@ def skipping_old_test(check_run)
return if check_run[:app][:name] != 'NetDEF CI Hook' or @stages.include?(check_run[:name])
@logger.info("Skipping old test suite: #{check_run[:name]}")
- puts("Skipping old test suite: #{check_run[:name]}")
message = 'Old test suite, skipping...'
@github.skipped(check_run[:id], { title: "#{check_run[:name]} summary", summary: message })
diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb
index de4c262..d01338b 100644
--- a/lib/github/build_plan.rb
+++ b/lib/github/build_plan.rb
@@ -23,7 +23,6 @@ class BuildPlan
def initialize(payload, logger_level: Logger::INFO)
@logger = Logger.new($stdout)
@logger.level = logger_level
- @has_previous_exec = false
@payload = payload
@@ -43,28 +42,7 @@ def create
@logger.info 'Fetching / Creating a pull request'
fetch_pull_request
- # Fetch last Check Suite
- fetch_last_check_suite
-
- # Create a Check Suite
- create_check_suite
-
- # Check if could save the Check Suite at database
- unless @check_suite.persisted?
- @logger.error "Failed to save CheckSuite: #{@check_suite.errors.inspect}"
- return [422, 'Failed to save Check Suite']
- end
-
- # Stop a previous execution - Avoiding CI spam
- stop_previous_execution
-
- # Starting a new CI run
- status = start_new_execution
-
- return [status, 'Failed to create CI Plan'] if status != 200
-
- # Creating CiJobs at database
- ci_jobs
+ Github::Build::PlanRun.new(@pull_request, @payload).build
end
private
@@ -74,9 +52,9 @@ def fetch_pull_request
return create_pull_request if @pull_request.nil?
- @logger.info "Updating plan: #{fetch_plan}"
+ @pull_request.update(branch_name: @payload.dig('pull_request', 'head', 'ref'))
- @pull_request.update(plan: fetch_plan, branch_name: @payload.dig('pull_request', 'head', 'ref'))
+ add_plans(@pull_request)
end
def github_pr
@@ -89,110 +67,18 @@ def create_pull_request
author: @payload.dig('pull_request', 'user', 'login'),
github_pr_id: github_pr,
branch_name: @payload.dig('pull_request', 'head', 'ref'),
- repository: @payload.dig('repository', 'full_name'),
- plan: fetch_plan
+ repository: @payload.dig('repository', 'full_name')
)
- Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id'), pull_request: @pull_request)
- end
-
- def start_new_execution
- create_pull_request if @pull_request.nil?
-
- @check_suite.pull_request = @pull_request
-
- Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id'), check_suite: @check_suite)
+ add_plans(@pull_request)
- @bamboo_plan_run = BambooCi::PlanRun.new(@check_suite, logger_level: @logger.level)
- @bamboo_plan_run.ci_variables = ci_vars
- @bamboo_plan_run.start_plan
- end
-
- def stop_previous_execution
- return if @last_check_suite.nil? or @last_check_suite.finished?
-
- @logger.info 'Stopping previous execution'
- @logger.info @last_check_suite.inspect
- @logger.info @check_suite.inspect
-
- cancel_previous_ci_jobs
- end
-
- def cancel_previous_ci_jobs
- @last_check_suite.ci_jobs.where(status: %w[queued in_progress]).each do |ci_job|
- @logger.warn("Cancelling Job #{ci_job.inspect}")
- ci_job.cancelled(@github_check)
- end
-
- @last_check_suite.update(stopped_in_stage: @last_check_suite.stages.where(status: :in_progress).last)
-
- @last_check_suite.stages.where(status: %w[queued in_progress]).each do |stage|
- stage.cancelled(@github_check)
- end
-
- @has_previous_exec = true
- BambooCi::StopPlan.build(@last_check_suite.bamboo_ci_ref)
- end
-
- def create_check_suite
- @logger.info 'Creating a check suite'
- @check_suite =
- CheckSuite.create(
- pull_request: @pull_request,
- author: @payload.dig('pull_request', 'user', 'login'),
- commit_sha_ref: @payload.dig('pull_request', 'head', 'sha'),
- work_branch: @payload.dig('pull_request', 'head', 'ref'),
- base_sha_ref: @payload.dig('pull_request', 'base', 'sha'),
- merge_branch: @payload.dig('pull_request', 'base', 'ref')
- )
-
- @logger.info 'Creating GitHub Check API'
- @github_check = Github::Check.new(@check_suite)
- end
-
- def fetch_last_check_suite
- @last_check_suite = @pull_request.check_suites.last
- end
-
- def ci_jobs
- @logger.info 'Creating GitHub Check'
-
- SlackBot.instance.execution_started_notification(@check_suite)
-
- @check_suite.update(bamboo_ci_ref: @bamboo_plan_run.bamboo_reference)
-
- jobs = BambooCi::RunningPlan.fetch(@bamboo_plan_run.bamboo_reference)
-
- return [422, 'Failed to fetch RunningPlan'] if jobs.nil? or jobs.empty?
-
- action = Github::Build::Action.new(@check_suite, @github_check, jobs)
- action.create_summary
-
- @logger.info ">>> @has_previous_exec: #{@has_previous_exec}"
- stop_execution_message if @has_previous_exec
-
- [200, 'Pull Request created']
- end
-
- def stop_execution_message
- @check_suite.update(cancelled_previous_check_suite_id: @last_check_suite.id)
- BambooCi::StopPlan.comment(@last_check_suite, @check_suite)
- end
-
- def ci_vars
- ci_vars = []
- ci_vars << { value: @github_check.signature, name: 'signature_secret' }
-
- ci_vars
+ Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id'), pull_request: @pull_request)
end
- def fetch_plan
- plan = Plan.find_by(github_repo_name: @payload.dig('repository', 'full_name'))
-
- return plan.bamboo_ci_plan_name unless plan.nil?
-
- # Default plan
- 'TESTING-FRRCRAS'
+ def add_plans(pull_request)
+ plans = Plan.where(github_repo_name: @payload.dig('repository', 'full_name'))
+ pull_request.plans = plans
+ pull_request.save
end
end
end
diff --git a/lib/github/plan_execution/finished.rb b/lib/github/plan_execution/finished.rb
index 733bfb8..7fbebf4 100644
--- a/lib/github/plan_execution/finished.rb
+++ b/lib/github/plan_execution/finished.rb
@@ -234,12 +234,16 @@ def check_stages
@logger.info ">>> @result: #{@result.inspect}"
return if @result.nil? or @result.empty? or @result['status-code']&.between?(400, 500)
- @result.dig('stages', 'stage').each do |stage|
- stage.dig('results', 'result').each do |result|
- ci_job = CiJob.find_by(job_ref: result['buildResultKey'], check_suite_id: @check_suite.id)
+ @result.dig('stages', 'stage')&.each do |stage|
+ check_stage(stage, github_check)
+ end
+ end
+
+ def check_stage(stage, github_check)
+ stage.dig('results', 'result')&.each do |result|
+ ci_job = CiJob.find_by(job_ref: result['buildResultKey'], check_suite_id: @check_suite.id)
- update_stage_status(ci_job, result, github_check)
- end
+ update_stage_status(ci_job, result, github_check)
end
end
diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb
index 5339a58..d4d7608 100644
--- a/lib/github/re_run/base.rb
+++ b/lib/github/re_run/base.rb
@@ -32,23 +32,24 @@ def initialize(payload, logger_level: Logger::INFO)
private
- def fetch_run_ci_by_pr
+ def fetch_run_ci_by_pr(plan)
CheckSuite
- .joins(:pull_request)
+ .joins(pull_request: :plans)
.joins(:ci_jobs)
- .where(pull_request: { github_pr_id: pr_id, repository: repo }, ci_jobs: { status: 1 })
+ .where(pull_request: { plans: { id: plan.id },
+ github_pr_id: pr_id, repository: repo },
+ ci_jobs: { status: 1 })
.uniq
end
- def stop_previous_execution
- return if fetch_run_ci_by_pr.empty?
+ def stop_previous_execution(plan)
+ return if fetch_run_ci_by_pr(plan).empty?
logger(Logger::INFO, 'Stopping previous execution')
- logger(Logger::INFO, fetch_run_ci_by_pr.inspect)
@last_check_suite = nil
- fetch_run_ci_by_pr.each do |check_suite|
+ fetch_run_ci_by_pr(plan).each do |check_suite|
stop_and_update_previous_execution(check_suite)
end
end
@@ -74,30 +75,23 @@ def cancel_previous_jobs(check_suite)
end
end
- def create_ci_jobs(bamboo_plan, check_suite)
- jobs = BambooCi::RunningPlan.fetch(bamboo_plan.bamboo_reference)
+ def create_ci_jobs(check_suite, plan_name)
+ jobs = BambooCi::RunningPlan.fetch(check_suite.bamboo_ci_ref)
- action = Github::Build::Action.new(check_suite, @github_check, jobs)
+ action = Github::Build::Action.new(check_suite, @github_check, jobs, plan_name)
action.create_summary(rerun: true)
end
- def fetch_plan
- plan = Plan.find_by_github_repo_name(@payload.dig('repository', 'full_name'))
-
- return plan.bamboo_ci_plan_name unless plan.nil?
-
- # Default plan
- 'TESTING-FRRCRAS'
- end
-
def logger(severity, message)
@logger_manager.each do |logger_object|
logger_object.add(severity, message)
end
end
- def start_new_execution(check_suite)
- bamboo_plan_run = BambooCi::PlanRun.new(check_suite, logger_level: @logger_level)
+ def start_new_execution(check_suite, plan)
+ cleanup(check_suite)
+
+ bamboo_plan_run = BambooCi::PlanRun.new(check_suite, plan, logger_level: @logger_level)
bamboo_plan_run.ci_variables = ci_vars
bamboo_plan_run.start_plan
@@ -109,8 +103,6 @@ def start_new_execution(check_suite)
retry_type: 'full')
Github::UserInfo.new(@payload.dig('sender', 'id'), check_suite: check_suite, audit_retry: audit_retry)
-
- bamboo_plan_run
end
def ci_vars
@@ -120,20 +112,28 @@ def ci_vars
ci_vars
end
- def ci_jobs(check_suite, bamboo_plan)
+ def ci_jobs(check_suite, plan)
SlackBot.instance.execution_started_notification(check_suite)
- check_suite.update(bamboo_ci_ref: bamboo_plan.bamboo_reference, re_run: true)
-
check_suite.update(cancelled_previous_check_suite: @last_check_suite)
- create_ci_jobs(bamboo_plan, check_suite)
+ create_ci_jobs(check_suite, plan.name)
+
+ update_unavailable_jobs(check_suite)
+ end
+ def update_unavailable_jobs(check_suite)
CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref).each do |cs|
Github::Build::UnavailableJobs.new(cs).update(new_check_suite: check_suite)
end
end
+ def cleanup(check_suite)
+ check_suite.pull_request.check_suites.each do |suite|
+ Delayed::Job.where('handler LIKE ?', "%method_name: :timeout\nargs:\n- #{suite.id}%")
+ end
+ end
+
def action
@payload.dig('comment', 'body')
end
diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb
index 7487bc8..6cdc2ab 100644
--- a/lib/github/re_run/command.rb
+++ b/lib/github/re_run/command.rb
@@ -13,6 +13,8 @@
module Github
module ReRun
class Command < Base
+ TIMER = 1 # seconds
+
def initialize(payload, logger_level: Logger::INFO)
super(payload, logger_level: logger_level)
@@ -30,28 +32,19 @@ def start
@github_check = Github::Check.new(check_suite)
- stop_previous_execution
-
- check_suite = create_check_suite(check_suite)
-
- bamboo_plan = start_new_execution(check_suite)
- ci_jobs(check_suite, bamboo_plan)
+ suite_by_plan(check_suite)
- [201, 'Starting re-run (command)']
+ [200, 'Scheduled Plan Runs']
end
private
- def create_check_suite(check_suite)
- CheckSuite.create(
- pull_request: check_suite.pull_request,
- author: check_suite.author,
- commit_sha_ref: check_suite.commit_sha_ref,
- work_branch: check_suite.work_branch,
- base_sha_ref: check_suite.base_sha_ref,
- merge_branch: check_suite.merge_branch,
- re_run: true
- )
+ def suite_by_plan(check_suite)
+ check_suite.pull_request.plans.each do |plan|
+ CreateExecutionByCommand
+ .delay(run_at: TIMER.seconds.from_now.utc, queue: 'create_execution_by_command')
+ .create(plan.id, check_suite.id, @payload)
+ end
end
def fetch_check_suite
diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb
index daeba8a..670cd64 100644
--- a/lib/github/re_run/comment.rb
+++ b/lib/github/re_run/comment.rb
@@ -15,166 +15,51 @@
module Github
module ReRun
class Comment < Base
+ TIMER = 1 # seconds
+
def initialize(payload, logger_level: Logger::INFO)
super(payload, logger_level: logger_level)
@logger_manager << GithubLogger.instance.create('github_rerun_comment.log', logger_level)
+ @logger_manager << Logger.new($stdout)
end
def start
return [422, 'Payload can not be blank'] if @payload.nil? or @payload.empty?
return [404, 'Action not found'] unless action?
- logger(Logger::DEBUG, ">>> Github::ReRun::Comment - sha256: #{sha256.inspect}, payload: #{@payload.inspect}")
-
- check_suite = sha256_or_comment?
-
- logger(Logger::DEBUG, ">>> Check suite: #{check_suite.inspect}")
-
- return [404, 'Failed to create a check suite'] if check_suite.nil?
-
- github_reaction_feedback(comment_id)
-
- stop_previous_execution
-
- bamboo_plan = start_new_execution(check_suite)
+ fetch_pull_request
- ci_jobs(check_suite, bamboo_plan)
-
- [201, 'Starting re-run (comment)']
+ confirm_and_start
end
private
- def sha256_or_comment?
- fetch_old_check_suite
-
- @old_check_suite.nil? ? comment_flow : sha256_flow
- end
-
- def comment_flow
- commit = fetch_last_commit_or_sha256
- github_check = fetch_github_check
- pull_request_info = github_check.pull_request_info(pr_id, repo)
- pull_request = fetch_or_create_pr(pull_request_info)
-
- fetch_old_check_suite(commit[:sha])
- check_suite = create_check_suite_by_commit(commit, pull_request, pull_request_info)
- logger(Logger::INFO, "CheckSuite errors: #{check_suite.inspect}")
- return nil unless check_suite.persisted?
-
- @github_check = Github::Check.new(check_suite)
-
- check_suite
- end
-
- # Fetches the GitHub check associated with the pull request.
- #
- # This method finds the pull request by its GitHub PR ID and then retrieves
- # the last check suite associated with that pull request. It then initializes
- # a new `Github::Check` object with the last check suite.
- #
- # @return [Github::Check] the GitHub check associated with the pull request.
- #
- # @raise [ActiveRecord::RecordNotFound] if the pull request is not found.
- def fetch_github_check
- pull_request = PullRequest.find_by(github_pr_id: pr_id)
- Github::Check.new(pull_request.check_suites.last)
- end
-
- def create_check_suite_by_commit(commit, pull_request, pull_request_info)
- CheckSuite.create(
- pull_request: pull_request,
- author: @payload.dig('comment', 'user', 'login'),
- commit_sha_ref: commit[:sha],
- work_branch: pull_request_info.dig(:head, :ref),
- base_sha_ref: pull_request_info.dig(:base, :sha),
- merge_branch: pull_request_info.dig(:base, :ref),
- re_run: true
- )
- end
+ def confirm_and_start
+ return [404, 'Pull Request not found'] if @pull_request.nil?
+ return [404, 'Can not rerun a new PullRequest'] if @pull_request.check_suites.empty?
- def fetch_or_create_pr(pull_request_info)
- last_check_suite = CheckSuite
- .joins(:pull_request)
- .where(pull_request: { github_pr_id: pr_id, repository: repo })
- .last
-
- return last_check_suite.pull_request unless last_check_suite.nil?
-
- pull_request = create_pull_request(pull_request_info)
-
- logger(Logger::DEBUG, ">>> Created a new pull request: #{pull_request}")
- logger(Logger::ERROR, "Error: #{pull_request.errors.inspect}") unless pull_request.persisted?
-
- pull_request
- end
-
- def create_pull_request(pull_request_info)
- PullRequest.create(
- author: @payload.dig('issue', 'user', 'login'),
- github_pr_id: pr_id,
- branch_name: pull_request_info.dig(:head, :ref),
- repository: repo,
- plan: fetch_plan
- )
- end
-
- def sha256_flow
- @github_check = Github::Check.new(@old_check_suite)
- create_new_check_suite
- end
-
- # The behaviour will be the following: It will fetch the last commit if it has
- # received a comment and only fetch a commit if the command starts with ci:rerrun #.
- # If there is any other character before the # it will be considered a comment.
- def fetch_last_commit_or_sha256
- pull_request_commit = Github::Parsers::PullRequestCommit.new(repo, pr_id)
- commit = pull_request_commit.find_by_sha(sha256)
-
- return commit if commit and action.match(/ci:rerun\s+#/i)
+ github_reaction_feedback(comment_id)
- fetch_last_commit
- end
+ @pull_request.plans.each do |plan|
+ CreateExecutionByComment
+ .delay(run_at: TIMER.seconds.from_now.utc, queue: 'create_execution_by_comment')
+ .create(@pull_request.id, @payload, plan)
+ end
- def fetch_last_commit
- Github::Parsers::PullRequestCommit.new(repo, pr_id).last_commit_in_pr
+ [200, 'Scheduled Plan Runs']
end
def github_reaction_feedback(comment_id)
return if comment_id.nil?
- @github_check.comment_reaction_thumb_up(repo, comment_id)
- end
-
- def fetch_old_check_suite(sha = sha256)
- return if sha.nil?
+ github_check = Github::Check.new(@pull_request.check_suites.last)
- logger(Logger::DEBUG, ">>> fetch_old_check_suite SHA: #{sha}")
-
- @old_check_suite =
- CheckSuite
- .joins(:pull_request)
- .where('commit_sha_ref ILIKE ? AND pull_requests.repository = ?', "#{sha}%", repo)
- .last
- end
-
- def create_new_check_suite
- CheckSuite.create(
- pull_request: @old_check_suite.pull_request,
- author: @old_check_suite.author,
- commit_sha_ref: @old_check_suite.commit_sha_ref,
- work_branch: @old_check_suite.work_branch,
- base_sha_ref: @old_check_suite.base_sha_ref,
- merge_branch: @old_check_suite.merge_branch,
- re_run: true
- )
+ github_check.comment_reaction_thumb_up(repo, comment_id)
end
- def sha256
- return nil unless action.downcase.match? 'ci:rerun #'
-
- action.downcase.split('#').last
+ def fetch_pull_request
+ @pull_request = PullRequest.find_by(github_pr_id: pr_id)
end
def action?
diff --git a/lib/github/update_status.rb b/lib/github/update_status.rb
index 67a0803..1799677 100644
--- a/lib/github/update_status.rb
+++ b/lib/github/update_status.rb
@@ -50,6 +50,7 @@ def initialize(payload)
#
# @return [Array] An array containing the status code and message.
def update
+ logger(Logger::INFO, "Updating status for job: #{@reference} with status: #{@status}")
return job_not_found if @job.nil?
return [304, 'Not Modified'] if @job.queued? and @status != 'in_progress' and @job.name != 'Checkout Code'
return [304, 'Not Modified'] if @job.in_progress? and !%w[success failure].include? @status
@@ -117,6 +118,8 @@ def update_status
def insert_new_delayed_job
queue = @job.check_suite.pull_request.github_pr_id % 10
+ logger(Logger::INFO, "Inserting new delayed job for queue: #{queue} to update job #{@job.id}")
+
delete_and_create_delayed_job(queue)
end
@@ -127,9 +130,13 @@ def insert_new_delayed_job
def delete_and_create_delayed_job(queue)
fetch_delayed_job(queue).destroy_all
+ logger(Logger::INFO,
+ "Inserting new delayed job for queue: #{queue} to update job #{@job.id} " \
+ "and bamboo_ci_ref: #{@check_suite.bamboo_ci_ref}")
+
CiJobStatus
.delay(run_at: DELAYED_JOB_TIMER.seconds.from_now.utc, queue: queue)
- .update(@job.check_suite.id, @job.id)
+ .update(@check_suite.bamboo_ci_ref, @job.id)
end
##
@@ -138,9 +145,12 @@ def delete_and_create_delayed_job(queue)
# @param [Integer] queue The queue number for the delayed job.
# @return [ActiveRecord::Relation] The relation containing the delayed jobs.
def fetch_delayed_job(queue)
+ logger(Logger::INFO,
+ "Removing old delayed job for queue: #{queue} and bamboo_ci_ref: #{@check_suite.bamboo_ci_ref}")
+
Delayed::Job
.where(queue: queue)
- .where('handler LIKE ?', "%method_name: :update\nargs:\n- #{@check_suite.id}%")
+ .where('handler LIKE ?', "%method_name: :update\nargs:\n- #{@check_suite.bamboo_ci_ref}%")
end
##
@@ -179,6 +189,7 @@ def logger_initializer
else
GithubLogger.instance.create("pr#{@job.check_suite.pull_request.github_pr_id}.log", Logger::INFO)
end
+ @loggers << Logger.new($stdout)
end
end
end
diff --git a/lib/github_ci_app.rb b/lib/github_ci_app.rb
index e6479d2..e328326 100644
--- a/lib/github_ci_app.rb
+++ b/lib/github_ci_app.rb
@@ -30,6 +30,7 @@
require_relative 'github/user_info'
require_relative 'github/build/skip_old_tests'
require_relative 'github/topotest_failures/retrieve_error'
+require_relative 'github/build/plan_run'
# Helpers libs
require_relative 'helpers/configuration'
@@ -43,6 +44,9 @@
require_relative '../workers/timeout_execution'
require_relative '../workers/ci_job_fetch_topotest_failures'
require_relative '../workers/slack_username2_id'
+require_relative '../workers/create_execution_by_plan'
+require_relative '../workers/create_execution_by_comment'
+require_relative '../workers/create_execution_by_command'
# Slack libs
require_relative 'slack/slack'
diff --git a/lib/helpers/request.rb b/lib/helpers/request.rb
index 55bb1e1..5fbba62 100644
--- a/lib/helpers/request.rb
+++ b/lib/helpers/request.rb
@@ -56,7 +56,7 @@ def delete_request(uri, machine: 'ci1.netdef.org')
# Fetch Request
resp = http.request(req)
- logger(Logger::DEBUG, resp)
+ logger(Logger::INFO, resp)
resp
end
diff --git a/lib/models/check_suite.rb b/lib/models/check_suite.rb
index 667bc56..d7593f6 100644
--- a/lib/models/check_suite.rb
+++ b/lib/models/check_suite.rb
@@ -15,6 +15,7 @@ class CheckSuite < ActiveRecord::Base
validates :commit_sha_ref, presence: true
belongs_to :pull_request
+ belongs_to :plan
belongs_to :stopped_in_stage, class_name: 'Stage', optional: true
belongs_to :cancelled_previous_check_suite, class_name: 'CheckSuite', optional: true
diff --git a/lib/models/plan.rb b/lib/models/plan.rb
index 5a198bb..588610b 100644
--- a/lib/models/plan.rb
+++ b/lib/models/plan.rb
@@ -11,4 +11,7 @@
require 'otr-activerecord'
class Plan < ActiveRecord::Base
+ has_many :check_suites
+
+ belongs_to :pull_request
end
diff --git a/lib/models/pull_request.rb b/lib/models/pull_request.rb
index 408ed51..48edcf1 100644
--- a/lib/models/pull_request.rb
+++ b/lib/models/pull_request.rb
@@ -18,20 +18,19 @@ class PullRequest < ActiveRecord::Base
has_many :check_suites, dependent: :delete_all
has_many :pull_request_subscriptions, dependent: :delete_all
-
+ has_many :plans
def finished?
return true if check_suites.nil? or check_suites.empty?
- current_execution.finished?
+ current_execution_by_plan(plan).finished?
end
def current_execution?(check_suite)
- current_execution == check_suite
+ current_execution_by_plan(check_suite.plan) == check_suite
end
- # @return [CheckSuite]
- def current_execution
- check_suites.order(id: :asc).last
+ def current_execution_by_plan(plan_obj)
+ check_suites.where(plan: plan_obj).order(id: :asc).last
end
def self.unique_repository_names
diff --git a/lib/models/stage.rb b/lib/models/stage.rb
index 8384e73..6d31889 100644
--- a/lib/models/stage.rb
+++ b/lib/models/stage.rb
@@ -18,6 +18,17 @@ class Stage < ActiveRecord::Base
default_scope -> { order(id: :asc) }, all_queries: true
+ scope :related_stages, lambda { |check_suite, suffix|
+ where('stages.name LIKE ?', "%#{suffix}").where(check_suite: check_suite)
+ }
+
+ scope :next_stage, ->(current_position) { where(configuration: { position: current_position + 1 }) }
+ scope :next_stages, ->(current_position) { where(configuration: { position: [(current_position + 1)..] }) }
+
+ def suffix
+ name.split(' - ').last
+ end
+
def update_execution_time
started = audit_statuses.find_by(status: :in_progress)
finished = audit_statuses.find_by(status: %i[success failure])
@@ -33,7 +44,14 @@ def running?
def previous_stage
position = configuration&.position.to_i
- check_suite.stages.joins(:configuration).find_by(configuration: { position: position - 1 })
+
+ return nil unless suffix
+
+ check_suite.stages
+ .joins(:configuration)
+ .where(configuration: { position: position - 1 })
+ .where('stages.name LIKE ?', "%#{suffix}")
+ .first
end
def finished?
@@ -117,13 +135,14 @@ def github_stage_full_name(name)
end
def output_in_progress
- url = GitHubApp::Configuration.instance.ci_url
in_progress = jobs.where(status: :in_progress)
header = ":arrow_right: Jobs in progress: #{in_progress.size}/#{jobs.size}\n\n"
- in_progress_jobs = mount_in_progress_jobs(jobs)
+ in_progress_jobs = in_progress.map do |job|
+ "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{job.job_ref}\n"
+ end.join("\n")
- url = "https://#{url}/browse/#{check_suite.bamboo_ci_ref}"
+ url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{check_suite.bamboo_ci_ref}"
{ title: "#{name} summary", summary: "#{header}#{in_progress_jobs}\nDetails at [#{url}](#{url})" }
end
diff --git a/spec/factories/check_suite.rb b/spec/factories/check_suite.rb
index b368e2b..c52eff5 100644
--- a/spec/factories/check_suite.rb
+++ b/spec/factories/check_suite.rb
@@ -45,5 +45,11 @@
create(:stage, check_suite: check_suite)
end
end
+
+ trait :with_stages_and_jobs do
+ after(:create) do |check_suite|
+ create(:stage, :with_job, check_suite: check_suite)
+ end
+ end
end
end
diff --git a/spec/factories/plan.rb b/spec/factories/plan.rb
index b892989..b12b687 100644
--- a/spec/factories/plan.rb
+++ b/spec/factories/plan.rb
@@ -10,6 +10,7 @@
FactoryBot.define do
factory :plan do
+ name { Faker::App.name }
bamboo_ci_plan_name { Faker::App.name }
github_repo_name { Faker::App.name }
end
diff --git a/spec/factories/pull_request.rb b/spec/factories/pull_request.rb
index 71e8777..98419a1 100644
--- a/spec/factories/pull_request.rb
+++ b/spec/factories/pull_request.rb
@@ -14,7 +14,7 @@
github_pr_id { 1 }
branch_name { Faker::App.name }
repository { 'Unit/Test' }
- plan { Faker::Alphanumeric.alpha(number: 10) }
+ plans { [create(:plan)] }
trait :with_check_suite do
after(:create) do |pr|
diff --git a/spec/factories/stage.rb b/spec/factories/stage.rb
index ad46085..6b0fbaf 100644
--- a/spec/factories/stage.rb
+++ b/spec/factories/stage.rb
@@ -14,7 +14,7 @@
status { 0 }
check_ref { Faker::Alphanumeric.alphanumeric(number: 18, min_alpha: 3, min_numeric: 3) }
- configuration { create(:stage_configuration, github_check_run_name: name) }
+ configuration { create(:stage_configuration, github_check_run_name: name.split(' - ').first) }
trait :failure do
status { :failure }
diff --git a/spec/lib/bamboo_ci/api_spec.rb b/spec/lib/bamboo_ci/api_spec.rb
index 11e4f28..186cabc 100644
--- a/spec/lib/bamboo_ci/api_spec.rb
+++ b/spec/lib/bamboo_ci/api_spec.rb
@@ -74,9 +74,10 @@ def initialize
let(:id) { 1 }
let(:status) { 200 }
let(:check_suite) { create(:check_suite) }
+ let(:plan) { check_suite.pull_request.plans.last }
let(:url) do
- "https://127.0.0.1/rest/api/latest/queue/#{check_suite.pull_request.plan}" \
+ "https://127.0.0.1/rest/api/latest/queue/#{plan.bamboo_ci_plan_name}" \
"#{custom_variables}#{ci_variables_parsed}"
end
@@ -104,7 +105,7 @@ def initialize
end
it 'must returns success' do
- expect(dummy.submit_pr_to_ci(check_suite, ci_variables).code.to_i).to eq(status)
+ expect(dummy.submit_pr_to_ci(check_suite, plan, ci_variables).code.to_i).to eq(status)
end
end
diff --git a/spec/lib/bamboo_ci/plan_run_spec.rb b/spec/lib/bamboo_ci/plan_run_spec.rb
index 5077844..afbdfb9 100644
--- a/spec/lib/bamboo_ci/plan_run_spec.rb
+++ b/spec/lib/bamboo_ci/plan_run_spec.rb
@@ -9,12 +9,13 @@
# frozen_string_literal: true
describe BambooCi::PlanRun do
- let(:plan_run) { described_class.new(check_suite) }
+ let(:plan) { check_suite.pull_request.plans.last }
+ let(:plan_run) { described_class.new(check_suite, plan) }
before do
allow(Netrc).to receive(:read).and_return({ 'ci1.netdef.org' => %w[user password] })
- stub_request(:post, "https://127.0.0.1/rest/api/latest/queue/#{check_suite.pull_request.plan}?" \
+ stub_request(:post, "https://127.0.0.1/rest/api/latest/queue/#{plan.bamboo_ci_plan_name.delete(' ')}?" \
"bamboo.variable.github_base_sha=#{check_suite.base_sha_ref}" \
"&bamboo.variable.github_branch=#{check_suite.merge_branch}&" \
"bamboo.variable.github_merge_sha=#{check_suite.commit_sha_ref}&" \
diff --git a/spec/lib/github/build/action_spec.rb b/spec/lib/github/build/action_spec.rb
index d4b412c..02dfc54 100644
--- a/spec/lib/github/build/action_spec.rb
+++ b/spec/lib/github/build/action_spec.rb
@@ -9,11 +9,11 @@
# frozen_string_literal: true
describe Github::Build::Action do
- let(:action) { described_class.new(check_suite, fake_github_check, jobs) }
+ let(:action) { described_class.new(check_suite, fake_github_check, jobs, 'Tato') }
let(:fake_client) { Octokit::Client.new }
let(:fake_github_check) { Github::Check.new(nil) }
let(:check_suite) { create(:check_suite) }
- let(:stage) { create(:stage, check_suite: check_suite) }
+ let(:stage) { create(:stage, name: 'Build - Tato', check_suite: check_suite) }
let(:jobs) do
[
{
@@ -163,7 +163,8 @@
before do
stage.configuration.update(start_in_progress: true)
- described_class.new(check_suite_new, fake_github_check, jobs).create_summary(rerun: false)
+ described_class.new(check_suite_new, fake_github_check, jobs,
+ check_suite_new.pull_request.plans.last.name).create_summary(rerun: false)
end
it 'must not change' do
@@ -177,11 +178,12 @@
before do
stage.configuration.update(start_in_progress: true)
- described_class.new(check_suite_new, fake_github_check, [ci_job]).create_summary(rerun: false)
+ described_class.new(check_suite_new, fake_github_check, [ci_job],
+ check_suite_new.pull_request.plans.last.name).create_summary(rerun: false)
end
it 'must not change' do
- expect { described_class.new(check_suite_new, fake_github_check, [ci_job]).create_summary(rerun: false) }
+ expect { described_class.new(check_suite_new, fake_github_check, [ci_job], '').create_summary(rerun: false) }
.not_to raise_error
end
end
diff --git a/spec/lib/github/build/plan_run_spec.rb b/spec/lib/github/build/plan_run_spec.rb
new file mode 100644
index 0000000..b6df448
--- /dev/null
+++ b/spec/lib/github/build/plan_run_spec.rb
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# plan_run_spec.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+describe Github::Build::PlanRun do
+ let(:plan_run) { described_class.new(pull_request, payload) }
+
+ context 'when receives an invalid pull request' do
+ let(:pull_request) { create(:pull_request, plans: []) }
+ let(:payload) { {} }
+
+ it 'must return 422' do
+ expect(plan_run.build).to eq([422, 'No Plans associated with this Pull Request'])
+ end
+ end
+end
diff --git a/spec/lib/github/build/summary_spec.rb b/spec/lib/github/build/summary_spec.rb
index 333e641..14c38c7 100644
--- a/spec/lib/github/build/summary_spec.rb
+++ b/spec/lib/github/build/summary_spec.rb
@@ -126,8 +126,12 @@
context 'when the tests stage finished unsuccessfully' do
let(:first_stage_config) { create(:stage_configuration, position: 1) }
let(:second_stage_config) { create(:stage_configuration, position: 2) }
- let(:first_stage) { create(:stage, configuration: first_stage_config, check_suite: check_suite) }
- let(:second_stage) { create(:stage, configuration: second_stage_config, check_suite: check_suite) }
+ let(:first_stage) do
+ create(:stage, name: 'Coding - Ajax', configuration: first_stage_config, check_suite: check_suite)
+ end
+ let(:second_stage) do
+ create(:stage, name: 'Tests - Ajax', configuration: second_stage_config, check_suite: check_suite)
+ end
let(:ci_job2) { create(:ci_job, :success, check_suite: check_suite, stage: first_stage) }
let(:ci_job) { create(:ci_job, :failure, check_suite: check_suite, stage: second_stage) }
@@ -146,8 +150,12 @@
context 'when the tests stage finished unsuccessfully and build_message returns null' do
let(:first_stage_config) { create(:stage_configuration, position: 1) }
let(:second_stage_config) { create(:stage_configuration, position: 2) }
- let(:first_stage) { create(:stage, configuration: first_stage_config, check_suite: check_suite) }
- let(:second_stage) { create(:stage, name: 'Build', configuration: second_stage_config, check_suite: check_suite) }
+ let(:first_stage) do
+ create(:stage, name: 'Code - Tato', configuration: first_stage_config, check_suite: check_suite)
+ end
+ let(:second_stage) do
+ create(:stage, name: 'Build - Tato', configuration: second_stage_config, check_suite: check_suite)
+ end
let(:ci_job2) { create(:ci_job, :success, check_suite: check_suite, stage: first_stage) }
let(:ci_job) { create(:ci_job, :failure, name: 'Ubuntu Build', check_suite: check_suite, stage: second_stage) }
@@ -159,6 +167,7 @@
end
it 'must update stage' do
+ puts ci_job.inspect
summary.build_summary
expect(ci_job.stage.reload.status).to eq('failure')
expect(ci_job2.stage.reload.status).to eq('success')
@@ -168,8 +177,12 @@
context 'when the tests stage finished unsuccessfully and build_message returns errorlog' do
let(:first_stage_config) { create(:stage_configuration, position: 1) }
let(:second_stage_config) { create(:stage_configuration, position: 2) }
- let(:first_stage) { create(:stage, configuration: first_stage_config, check_suite: check_suite) }
- let(:second_stage) { create(:stage, name: 'Build', configuration: second_stage_config, check_suite: check_suite) }
+ let(:first_stage) do
+ create(:stage, name: 'Coding - Ajax', configuration: first_stage_config, check_suite: check_suite)
+ end
+ let(:second_stage) do
+ create(:stage, name: 'Build - Ajax', configuration: second_stage_config, check_suite: check_suite)
+ end
let(:ci_job2) { create(:ci_job, :success, check_suite: check_suite, stage: first_stage) }
let(:ci_job) { create(:ci_job, :failure, name: 'Ubuntu Build', check_suite: check_suite, stage: second_stage) }
diff --git a/spec/lib/github/build_plan_spec.rb b/spec/lib/github/build_plan_spec.rb
index d2425a1..de9b2b6 100644
--- a/spec/lib/github/build_plan_spec.rb
+++ b/spec/lib/github/build_plan_spec.rb
@@ -12,16 +12,20 @@
let(:build_plan) { described_class.new(payload) }
let(:fake_client) { Octokit::Client.new }
let(:fake_github_check) { Github::Check.new(nil) }
- let(:fake_plan_run) { BambooCi::PlanRun.new(nil) }
+ let(:fake_plan_run) { BambooCi::PlanRun.new(nil, pull_request.plans.last) }
let(:fake_check_run) { create(:check_suite) }
before do
allow(File).to receive(:read).and_return('')
allow(OpenSSL::PKey::RSA).to receive(:new).and_return(OpenSSL::PKey::RSA.new(2048))
allow(TimeoutExecution).to receive_message_chain(:delay, :timeout).and_return(true)
+ allow(GitHubApp::Configuration).to receive(:new).and_return(GitHubApp::Configuration.instance)
end
describe 'Valid commands' do
+ let!(:plan) { create(:plan, github_repo_name: repo) }
+
+ let(:pull_request) { create(:pull_request, github_pr_id: pr_number, repository: repo, author: author) }
let(:pr_number) { rand(1_000_000) }
let(:repo) { 'UnitTest/repo' }
let(:fake_translation) { create(:stage_configuration) }
@@ -68,7 +72,7 @@
allow(fake_github_check).to receive(:fetch_username).and_return({})
allow(fake_github_check).to receive(:check_runs_for_ref).and_return({})
- allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs)
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return(ci_jobs)
end
context 'when action is opened' do
@@ -82,68 +86,12 @@
end
it 'must create a PR' do
- expect(build_plan.create).to eq([200, 'Pull Request created'])
- end
- end
-
- context 'when action is opened and check created_objects' do
- let(:action) { 'opened' }
- let(:author) { 'Johnny Silverhand' }
- let(:pull_request) { PullRequest.last }
- let(:check_suite) { pull_request.check_suites.last }
- let(:ci_job) { check_suite.ci_jobs.find_by(name: 'First Test') }
- let(:ci_jobs) do
- [{ name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }]
- end
- let(:plan) { create(:plan, github_repo_name: repo) }
-
- before do
- plan
- build_plan.create
- end
-
- it 'must create all objects' do
- expect(pull_request.author).to eq(author)
- expect(pull_request.github_pr_id.to_i).to eq(pr_number)
- expect(pull_request.check_suites.to_a).not_to eq([])
- expect(check_suite.author).to eq(author)
- expect(ci_job.name).to eq('First Test')
- expect(ci_job.job_ref).to eq('UNIT-TEST-FIRST-1')
- end
- end
-
- context 'when commit and has a previous CI jobs' do
- let(:action) { 'opened' }
- let(:pull_request) { create(:pull_request, github_pr_id: pr_number, repository: repo) }
- let(:previous_check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) }
- let(:previous_ci_job) { previous_check_suite.reload.ci_jobs.last }
- let(:check_suite) { pull_request.reload.check_suites.last }
- let(:author) { 'Johnny Silverhand' }
- let(:ci_jobs) do
- [{ name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }]
- end
- let(:new_pull_request) { PullRequest.last }
-
- before do
- previous_check_suite
-
- allow(BambooCi::StopPlan).to receive(:build)
- allow(BambooCi::StopPlan).to receive(:comment)
- allow(fake_github_check).to receive(:cancelled)
-
- build_plan.create
- end
-
- it 'must create a new check_suite' do
- expect(pull_request.author).to eq(new_pull_request.author)
- expect(check_suite.author).to eq(author)
- expect(previous_ci_job.status).to eq('cancelled')
+ expect(build_plan.create).to eq([200, 'Scheduled Plan Runs'])
end
end
context 'when commit and has a previous CI jobs running' do
let(:action) { 'opened' }
- let(:pull_request) { create(:pull_request, github_pr_id: pr_number, repository: repo) }
let(:previous_check_suite) { create(:check_suite, :with_running_success_ci_jobs, pull_request: pull_request) }
let(:previous_ci_job) { previous_check_suite.reload.ci_jobs.last }
let(:check_suite) { pull_request.reload.check_suites.last }
@@ -188,6 +136,8 @@
end
describe 'Invalid commands' do
+ let!(:plan) { create(:plan, github_repo_name: repo) }
+
let(:pr_number) { 0 }
let(:repo) { 'unit-test/xxx' }
let(:payload) do
@@ -234,38 +184,11 @@
end
end
- context 'when check suite does not persisted at database' do
- let(:author) { nil }
- let(:action) { 'synchronize' }
- let(:check_suite) { create(:check_suite) }
-
- before do
- allow(Octokit::Client).to receive(:new).and_return(fake_client)
- allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
- allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
-
- allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run)
- allow(fake_plan_run).to receive(:start_plan).and_return(200)
- allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1')
-
- allow(Github::Check).to receive(:new).and_return(fake_github_check)
- allow(fake_github_check).to receive(:create).and_return(fake_check_run)
- allow(fake_github_check).to receive(:in_progress).and_return(fake_check_run)
- allow(fake_github_check).to receive(:queued).and_return(fake_check_run)
- allow(fake_github_check).to receive(:fetch_username).and_return({})
-
- allow(CheckSuite).to receive(:create).and_return(check_suite)
- allow(check_suite).to receive(:persisted?).and_return(false)
- end
-
- it 'must returns an error' do
- expect(build_plan.create).to eq([422, 'Failed to save Check Suite'])
- end
- end
-
context 'when failed to start CI' do
let(:author) { 'Jonny Rocket' }
let(:action) { 'synchronize' }
+ let(:check_suite) { create(:check_suite, pull_request: pull_request) }
+ let(:pull_request) { create(:pull_request) }
before do
allow(Octokit::Client).to receive(:new).and_return(fake_client)
@@ -284,13 +207,15 @@
end
it 'must returns an error' do
- expect(build_plan.create).to eq([400, 'Failed to create CI Plan'])
+ expect(build_plan.create).to eq([200, 'Scheduled Plan Runs'])
end
end
context 'when failed to fetch the running plan' do
let(:author) { 'Jonny Rocket' }
let(:action) { 'synchronize' }
+ let(:check_suite) { create(:check_suite, pull_request: pull_request) }
+ let(:pull_request) { create(:pull_request, author: author) }
before do
allow(Octokit::Client).to receive(:new).and_return(fake_client)
@@ -307,11 +232,11 @@
allow(fake_github_check).to receive(:queued).and_return(fake_check_run)
allow(fake_github_check).to receive(:fetch_username).and_return({})
- allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return([])
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return([])
end
it 'must returns an error' do
- expect(build_plan.create).to eq([422, 'Failed to fetch RunningPlan'])
+ expect(build_plan.create).to eq([200, 'Scheduled Plan Runs'])
end
end
end
diff --git a/spec/lib/github/plan_execution/finished_spec.rb b/spec/lib/github/plan_execution/finished_spec.rb
index 12aad0a..66c09d7 100644
--- a/spec/lib/github/plan_execution/finished_spec.rb
+++ b/spec/lib/github/plan_execution/finished_spec.rb
@@ -319,5 +319,40 @@
expect(pla_exec.finished).to eq([200, 'Still running'])
end
end
+
+ context 'when ci_job.job_ref is nil and not current execution' do
+ let(:status) { 200 }
+ let(:ci_job) { check_suite.ci_jobs.last }
+ let(:check_suite) { create(:check_suite, :with_stages_and_jobs, :with_running_ci_jobs) }
+ let(:payload) { { 'bamboo_ref' => check_suite.bamboo_ci_ref } }
+ let(:summary) { double(build_summary: nil) }
+ let(:body) do
+ {
+ 'stages' => {
+ 'stage' => [
+ {
+ 'results' => {
+ 'result' => [ci_job]
+ }
+ }
+ ]
+ }
+ }
+ end
+
+ before do
+ allow(ci_job).to receive(:enqueue)
+ allow(check_suite).to receive(:ci_jobs).and_return([ci_job])
+ allow(Github::Check).to receive(:new).and_return(fake_github_check)
+ allow(check_suite.pull_request).to receive(:current_execution?).and_return(false)
+ allow(Github::Build::Summary).to receive(:new).and_return(summary)
+ allow(summary).to receive(:build_summary)
+ allow_any_instance_of(PullRequest).to receive(:current_execution?).and_return(false)
+ end
+
+ it 'enqueues the ci_job and returns false' do
+ expect(pla_exec.finished).to eq([200, 'Finished'])
+ end
+ end
end
end
diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb
index d5f2748..a05476a 100644
--- a/spec/lib/github/re_run/command_spec.rb
+++ b/spec/lib/github/re_run/command_spec.rb
@@ -9,10 +9,11 @@
# frozen_string_literal: true
describe Github::ReRun::Command do
+ let(:pull_request) { create(:pull_request) }
let(:rerun) { described_class.new(payload) }
let(:fake_client) { Octokit::Client.new }
let(:fake_github_check) { Github::Check.new(nil) }
- let(:fake_plan_run) { BambooCi::PlanRun.new(nil) }
+ let(:fake_plan_run) { BambooCi::PlanRun.new(nil, pull_request.plans.last) }
before do
allow(File).to receive(:read).and_return('')
@@ -83,7 +84,7 @@
end
it 'must returns success' do
- expect(rerun.start).to eq([201, 'Starting re-run (command)'])
+ expect(rerun.start).to eq([200, 'Scheduled Plan Runs'])
expect(check_suites.size).to eq(2)
expect(check_suites.first.re_run).to be_falsey
expect(check_suites.last.re_run).to be_truthy
diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb
index 044b057..1ebb0f3 100644
--- a/spec/lib/github/re_run/comment_spec.rb
+++ b/spec/lib/github/re_run/comment_spec.rb
@@ -12,7 +12,7 @@
let(:rerun) { described_class.new(payload) }
let(:fake_client) { Octokit::Client.new }
let(:fake_github_check) { Github::Check.new(nil) }
- let(:fake_plan_run) { BambooCi::PlanRun.new(nil) }
+ let(:fake_plan_run) { BambooCi::PlanRun.new(nil, pull_request.plans.last) }
let(:fake_unavailable) { Github::Build::UnavailableJobs.new(nil) }
let!(:pull_request) { create(:pull_request, :with_check_suite, id: 1) }
@@ -50,7 +50,9 @@
let(:fake_translation) { create(:stage_configuration) }
context 'when receives a valid command' do
- let(:check_suite) { create(:check_suite, :with_running_ci_jobs) }
+ let(:pull_request) { create(:pull_request, github_pr_id: 22, repository: 'test') }
+ let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) }
+ let(:previous_check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) }
let(:ci_jobs) do
[
{ name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name },
@@ -68,6 +70,9 @@
let(:check_suites) { CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref) }
before do
+ previous_check_suite
+ check_suite
+
allow(Octokit::Client).to receive(:new).and_return(fake_client)
allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
@@ -88,11 +93,11 @@
allow(fake_plan_run).to receive(:bamboo_reference).and_return('CHK-01')
allow(BambooCi::StopPlan).to receive(:build)
- allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs)
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return(ci_jobs)
end
it 'must returns success' do
- expect(rerun.start).to eq([201, 'Starting re-run (comment)'])
+ expect(rerun.start).to eq([200, 'Scheduled Plan Runs'])
expect(check_suites.size).to eq(2)
end
end
@@ -138,11 +143,11 @@
allow(fake_plan_run).to receive(:bamboo_reference).and_return('CHK-01')
allow(BambooCi::StopPlan).to receive(:build)
- allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs)
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return(ci_jobs)
end
it 'must returns success' do
- expect(rerun.start).to eq([201, 'Starting re-run (comment)'])
+ expect(rerun.start).to eq([200, 'Scheduled Plan Runs'])
expect(check_suites.size).to eq(2)
end
end
@@ -207,13 +212,13 @@
allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1')
allow(BambooCi::StopPlan).to receive(:build)
- allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs)
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return(ci_jobs)
another_check_suite
end
it 'must returns success' do
- expect(rerun.start).to eq([201, 'Starting re-run (comment)'])
+ expect(rerun.start).to eq([200, 'Scheduled Plan Runs'])
expect(check_suite_rerun).not_to be_nil
end
@@ -229,7 +234,8 @@
end
context 'when you receive an comment' do
- let(:check_suite) { create(:check_suite, :with_running_ci_jobs) }
+ let(:pull_request) { create(:pull_request, github_pr_id: 12, repository: 'test') }
+ let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) }
let(:check_suite_rerun) { CheckSuite.find_by(commit_sha_ref: check_suite.commit_sha_ref, re_run: true) }
let(:ci_jobs) do
@@ -288,56 +294,90 @@
allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1')
allow(BambooCi::StopPlan).to receive(:build)
- allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs)
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return(ci_jobs)
end
it 'must returns success' do
- expect(rerun.start).to eq([201, 'Starting re-run (comment)'])
+ expect(rerun.start).to eq([200, 'Scheduled Plan Runs'])
expect(check_suite_rerun).not_to be_nil
end
end
- end
- describe 'alternative scenarios' do
- let(:fake_client) { Octokit::Client.new }
- let(:fake_github_check) { Github::Check.new(nil) }
- let(:fake_translation) { create(:stage_configuration) }
+ context 'when receives an invalid pull request' do
+ let(:pull_request) { create(:pull_request, github_pr_id: 12, repository: 'test') }
+ let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) }
+ let(:check_suite_rerun) { CheckSuite.find_by(commit_sha_ref: check_suite.commit_sha_ref, re_run: true) }
- before do
- allow(Octokit::Client).to receive(:new).and_return(fake_client)
- allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
- allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
- allow(fake_client).to receive(:pull_request_commits).and_return(pull_request_commits, [])
+ let(:ci_jobs) do
+ [
+ { name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }
+ ]
+ end
- allow(Github::Check).to receive(:new).and_return(fake_github_check)
- allow(fake_github_check).to receive(:create).and_return(fake_check_suite)
- allow(fake_github_check).to receive(:add_comment)
- allow(fake_github_check).to receive(:cancelled)
- allow(fake_github_check).to receive(:queued)
- allow(fake_github_check).to receive(:pull_request_info).and_return(pull_request_info)
- allow(fake_github_check).to receive(:fetch_username).and_return({})
- allow(fake_github_check).to receive(:check_runs_for_ref).and_return({})
+ let(:payload) do
+ {
+ 'action' => 'created',
+ 'comment' => {
+ 'body' => 'CI:rerun',
+ 'user' => { 'login' => 'John' }
+ },
+ 'repository' => { 'full_name' => check_suite.pull_request.repository },
+ 'issue' => { 'number' => check_suite.pull_request.github_pr_id }
+ }
+ end
- allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run)
- allow(fake_plan_run).to receive(:start_plan).and_return(200)
- allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1')
+ let(:pull_request_info) do
+ {
+ head: {
+ ref: 'master'
+ },
+ base: {
+ ref: 'test',
+ sha: check_suite.base_sha_ref
+ }
+ }
+ end
- allow(BambooCi::StopPlan).to receive(:build)
- allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(bamboo_jobs)
+ let(:pull_request_commits) do
+ [
+ { sha: check_suite.commit_sha_ref, date: Time.now }
+ ]
+ end
+
+ before do
+ allow(Octokit::Client).to receive(:new).and_return(fake_client)
+ allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
+ allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
+ allow(fake_client).to receive(:pull_request_commits).and_return(pull_request_commits, [])
+
+ allow(PullRequest).to receive(:find_by).and_return(nil)
+ end
+
+ it 'must returns failure' do
+ expect(rerun.start).to eq([404, 'Pull Request not found'])
+ end
end
- context 'when you receive an comment and does not exist a PR' do
- let(:commit_sha) { Faker::Internet.uuid }
+ context 'when receives a valid pull request but without check_suites' do
+ let(:pull_request) { create(:pull_request, github_pr_id: 12, repository: 'test') }
+ let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) }
+ let(:check_suite_rerun) { CheckSuite.find_by(commit_sha_ref: check_suite.commit_sha_ref, re_run: true) }
+
+ let(:ci_jobs) do
+ [
+ { name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }
+ ]
+ end
let(:payload) do
{
'action' => 'created',
'comment' => {
- 'body' => 'CI:rerun 000000',
+ 'body' => 'CI:rerun',
'user' => { 'login' => 'John' }
},
- 'repository' => { 'full_name' => 'unit_test' },
- 'issue' => { 'number' => pull_request.github_pr_id }
+ 'repository' => { 'full_name' => check_suite.pull_request.repository },
+ 'issue' => { 'number' => check_suite.pull_request.github_pr_id }
}
end
@@ -348,40 +388,142 @@
},
base: {
ref: 'test',
- sha: commit_sha
+ sha: check_suite.base_sha_ref
}
}
end
let(:pull_request_commits) do
[
- { sha: commit_sha, date: Time.now }
+ { sha: check_suite.commit_sha_ref, date: Time.now }
]
end
- let(:bamboo_jobs) do
+ before do
+ allow(Octokit::Client).to receive(:new).and_return(fake_client)
+ allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
+ allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
+ allow(fake_client).to receive(:pull_request_commits).and_return(pull_request_commits, [])
+
+ allow(PullRequest).to receive(:find_by).and_return(pull_request)
+ allow(pull_request).to receive(:check_suites).and_return([])
+ end
+
+ it 'must returns failure' do
+ expect(rerun.start).to eq([404, 'Can not rerun a new PullRequest'])
+ end
+ end
+
+ context 'when you receive an comment' do
+ let(:pull_request) { create(:pull_request, github_pr_id: 12, repository: 'test') }
+ let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) }
+ let(:check_suite_rerun) { CheckSuite.find_by(commit_sha_ref: check_suite.commit_sha_ref, re_run: true) }
+
+ let(:ci_jobs) do
[
- { name: 'test', job_ref: 'checkout-01', stage: fake_translation.bamboo_stage_name }
+ { name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }
]
end
- let(:fake_check_suite) { create(:check_suite, pull_request: pull_request) }
- let(:check_suite_rerun) { CheckSuite.find_by(commit_sha_ref: commit_sha, re_run: true) }
+ let(:payload) do
+ {
+ 'action' => 'created',
+ 'comment' => {
+ 'body' => "CI:rerun 000000 ##{check_suite.commit_sha_ref}",
+ 'user' => { 'login' => 'John' }
+ },
+ 'repository' => { 'full_name' => check_suite.pull_request.repository },
+ 'issue' => { 'number' => check_suite.pull_request.github_pr_id }
+ }
+ end
+
+ let(:pull_request_info) do
+ {
+ head: {
+ ref: 'master'
+ },
+ base: {
+ ref: 'test',
+ sha: check_suite.base_sha_ref
+ }
+ }
+ end
+
+ let(:pull_request_commits) do
+ [
+ { sha: check_suite.commit_sha_ref, date: Time.now }
+ ]
+ end
+
+ before do
+ allow(Octokit::Client).to receive(:new).and_return(fake_client)
+ allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
+ allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
+ allow(fake_client).to receive(:pull_request_commits).and_return(pull_request_commits, [])
+
+ allow(Github::Check).to receive(:new).and_return(fake_github_check)
+ allow(fake_github_check).to receive(:create).and_return(check_suite)
+ allow(fake_github_check).to receive(:add_comment)
+ allow(fake_github_check).to receive(:cancelled)
+ allow(fake_github_check).to receive(:queued)
+ allow(fake_github_check).to receive(:pull_request_info).and_return(pull_request_info)
+ allow(fake_github_check).to receive(:fetch_username).and_return({})
+ allow(fake_github_check).to receive(:check_runs_for_ref).and_return({})
+
+ allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run)
+ allow(fake_plan_run).to receive(:start_plan).and_return(200)
+ allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1')
+
+ allow(BambooCi::StopPlan).to receive(:build)
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return(ci_jobs)
+
+ allow(CheckSuite).to receive(:create).and_return(check_suite)
+ allow(check_suite).to receive(:persisted?).and_return(false)
+ end
it 'must returns success' do
- expect(rerun.start).to eq([201, 'Starting re-run (comment)'])
- expect(check_suite_rerun).not_to be_nil
+ expect(rerun.start).to eq([200, 'Scheduled Plan Runs'])
end
end
+ end
+
+ describe 'alternative scenarios' do
+ let(:fake_client) { Octokit::Client.new }
+ let(:fake_github_check) { Github::Check.new(nil) }
+ let(:fake_translation) { create(:stage_configuration) }
+
+ before do
+ allow(Octokit::Client).to receive(:new).and_return(fake_client)
+ allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
+ allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
+ allow(fake_client).to receive(:pull_request_commits).and_return(pull_request_commits, [])
- context 'when can not save check_suite' do
+ allow(Github::Check).to receive(:new).and_return(fake_github_check)
+ allow(fake_github_check).to receive(:create).and_return(fake_check_suite)
+ allow(fake_github_check).to receive(:add_comment)
+ allow(fake_github_check).to receive(:cancelled)
+ allow(fake_github_check).to receive(:queued)
+ allow(fake_github_check).to receive(:pull_request_info).and_return(pull_request_info)
+ allow(fake_github_check).to receive(:fetch_username).and_return({})
+ allow(fake_github_check).to receive(:check_runs_for_ref).and_return({})
+
+ allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run)
+ allow(fake_plan_run).to receive(:start_plan).and_return(200)
+ allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1')
+
+ allow(BambooCi::StopPlan).to receive(:build)
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return(bamboo_jobs)
+ end
+
+ context 'when you receive an comment and does not exist a PR' do
let(:commit_sha) { Faker::Internet.uuid }
let(:payload) do
{
'action' => 'created',
'comment' => {
- 'body' => 'CI:rerun 000000'
+ 'body' => 'CI:rerun 000000',
+ 'user' => { 'login' => 'John' }
},
'repository' => { 'full_name' => 'unit_test' },
'issue' => { 'number' => pull_request.github_pr_id }
@@ -413,13 +555,11 @@
end
let(:fake_check_suite) { create(:check_suite, pull_request: pull_request) }
-
- before do
- create(:plan, github_repo_name: 'unit_test')
- end
+ let(:check_suite_rerun) { CheckSuite.find_by(commit_sha_ref: commit_sha, re_run: true) }
it 'must returns success' do
- expect(rerun.start).to eq([404, 'Failed to create a check suite'])
+ expect(rerun.start).to eq([200, 'Scheduled Plan Runs'])
+ expect(check_suite_rerun).not_to be_nil
end
end
end
diff --git a/spec/lib/github/retry/comment_spec.rb b/spec/lib/github/retry/comment_spec.rb
index 2347551..5f2f348 100644
--- a/spec/lib/github/retry/comment_spec.rb
+++ b/spec/lib/github/retry/comment_spec.rb
@@ -12,7 +12,7 @@
let(:github_retry) { described_class.new(payload) }
let(:fake_client) { Octokit::Client.new }
let(:fake_github_check) { Github::Check.new(nil) }
- let(:fake_plan_run) { BambooCi::PlanRun.new(nil) }
+ let(:fake_plan_run) { BambooCi::PlanRun.new(nil, check_suite.pull_request.plans.last) }
let(:fake_unavailable) { Github::Build::UnavailableJobs.new(nil) }
before do
diff --git a/spec/workers/create_execution_by_command_spec.rb b/spec/workers/create_execution_by_command_spec.rb
new file mode 100644
index 0000000..e28a0fb
--- /dev/null
+++ b/spec/workers/create_execution_by_command_spec.rb
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# create_execution_by_command_spec.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+describe CreateExecutionByCommand do
+ let(:plan) { create(:plan) }
+ let(:pull_request) { create(:pull_request, plan: plan) }
+ let(:check_suite) { create(:check_suite, pull_request: pull_request) }
+ let(:payload) do
+ {
+ 'sender' => { 'login' => 'user', 'id' => 123, 'type' => 'User' }
+ }
+ end
+
+ before do
+ allow(Plan).to receive(:find).with(plan.id).and_return(plan)
+ allow(GithubLogger).to receive_message_chain(:instance, :create).and_return(Logger.new($stdout))
+ allow(Logger).to receive(:new).and_return(Logger.new($stdout))
+ allow(Github::Check).to receive(:new)
+ allow_any_instance_of(CreateExecutionByCommand).to receive(:stop_previous_execution)
+ allow_any_instance_of(CreateExecutionByCommand).to receive(:ci_jobs)
+ allow_any_instance_of(CreateExecutionByCommand).to receive(:cleanup)
+ bamboo_plan_run_double = double('BambooCi::PlanRun')
+ allow(bamboo_plan_run_double).to receive(:ci_variables=)
+ allow(bamboo_plan_run_double).to receive(:start_plan)
+ allow(BambooCi::PlanRun).to receive(:new).and_return(bamboo_plan_run_double)
+ allow(AuditRetry).to receive(:create)
+ allow(Github::UserInfo).to receive(:new)
+ end
+
+ describe '.create' do
+ it 'returns [404, "Failed to fetch a check suite"] if check_suite is nil' do
+ allow(CheckSuite).to receive(:find).with(999).and_return(nil)
+ expect(described_class.create(plan.id, 999, payload)).to eq([404, 'Failed to fetch a check suite'])
+ end
+ end
+end
diff --git a/spec/workers/create_execution_by_comment_spec.rb b/spec/workers/create_execution_by_comment_spec.rb
new file mode 100644
index 0000000..805a418
--- /dev/null
+++ b/spec/workers/create_execution_by_comment_spec.rb
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# create_execution_by_comment_spec.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+describe CreateExecutionByComment do
+ let(:pull_request) { create(:pull_request) }
+ let(:plan) { create(:plan) }
+ let(:payload) do
+ {
+ 'comment' => { 'body' => 'ci:rerun #123456', 'user' => { 'login' => 'user' } },
+ 'action' => 'created'
+ }
+ end
+
+ let(:fake_client) { Octokit::Client.new }
+ let(:fake_github_check) { Github::Check.new(nil) }
+ let(:fake_plan_run) { BambooCi::PlanRun.new(nil, pull_request.plans.last) }
+ let(:fake_check_run) { create(:check_suite) }
+ let(:fake_action) { double('Github::Build::Action') }
+
+ before do
+ allow(File).to receive(:read).and_return('')
+ allow(OpenSSL::PKey::RSA).to receive(:new).and_return(OpenSSL::PKey::RSA.new(2048))
+ allow(TimeoutExecution).to receive_message_chain(:delay, :timeout).and_return(true)
+ allow(GitHubApp::Configuration).to receive(:new).and_return(GitHubApp::Configuration.instance)
+
+ allow(Octokit::Client).to receive(:new).and_return(fake_client)
+ allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
+ allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
+
+ allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run)
+ allow(fake_plan_run).to receive(:start_plan).and_return(200)
+ allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-FIRST-1')
+ allow(fake_plan_run).to receive(:bamboo_reference).and_return('CHECKOUT-1')
+
+ allow(Github::Check).to receive(:new).and_return(fake_github_check)
+ allow(fake_github_check).to receive(:create).and_return(fake_check_run)
+ allow(fake_github_check).to receive(:in_progress).and_return(fake_check_run)
+ allow(fake_github_check).to receive(:queued).and_return(fake_check_run)
+ allow(fake_github_check).to receive(:fetch_username).and_return({})
+ allow(fake_github_check).to receive(:fetch_username).and_return({})
+ allow(fake_github_check).to receive(:check_runs_for_ref).and_return({})
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return({ job: '1' })
+ allow(Github::Build::Action).to receive(:new).and_return(fake_action)
+ allow(fake_action).to receive(:create_summary)
+
+ allow(GithubLogger).to receive_message_chain(:instance, :create).and_return(Logger.new($stdout))
+ allow(Logger).to receive(:new).and_return(Logger.new($stdout))
+ allow(PullRequest).to receive(:find).and_return(pull_request)
+ allow_any_instance_of(CreateExecutionByComment).to receive(:run_by_plan).and_return([201,
+ 'Starting re-run (comment)'])
+ end
+
+ describe '.create' do
+ it 'returns [422, "Plan not found"] if plan is nil' do
+ expect(described_class.create(pull_request.id, payload, nil)).to eq([422, 'Plan not found'])
+ end
+ end
+
+ describe '#fetch_last_commit_or_sha256' do
+ it 'returns commit if commit exists and action matches ci:rerun # pattern' do
+ instance = described_class.allocate
+ # Corrige erro de @payload nil
+ instance.instance_variable_set(:@payload, { 'repository' => { 'full_name' => 'repo/name' } })
+ allow(instance).to receive(:action).and_return('ci:rerun #123456')
+ commit = double('commit')
+ allow(Github::Parsers::PullRequestCommit).to receive_message_chain(:new, :find_by_sha).and_return(commit)
+ expect(instance.send(:fetch_last_commit_or_sha256)).to eq(commit)
+ end
+ end
+
+ describe '#action?' do
+ it 'returns true when action matches ci:rerun and payload action is created' do
+ instance = described_class.allocate
+ instance.instance_variable_set(:@payload, { 'action' => 'created' })
+ allow(instance).to receive(:action).and_return('ci:rerun')
+ expect(instance.send(:action?)).to be true
+ end
+
+ it 'returns false when action does not match ci:rerun' do
+ instance = described_class.allocate
+ instance.instance_variable_set(:@payload, { 'action' => 'created' })
+ allow(instance).to receive(:action).and_return('other')
+ expect(instance.send(:action?)).to be false
+ end
+
+ it 'returns false when payload action is not created' do
+ instance = described_class.allocate
+ instance.instance_variable_set(:@payload, { 'action' => 'edited' })
+ allow(instance).to receive(:action).and_return('ci:rerun')
+ expect(instance.send(:action?)).to be false
+ end
+ end
+end
diff --git a/spec/workers/create_execution_by_plan_spec.rb b/spec/workers/create_execution_by_plan_spec.rb
new file mode 100644
index 0000000..144e40f
--- /dev/null
+++ b/spec/workers/create_execution_by_plan_spec.rb
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# create_execution_by_plan_spec.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+describe CreateExecutionByPlan do
+ let(:pull_request) { create(:pull_request, id: 25) }
+ let(:payload) do
+ {
+ 'pull_request' => {
+ 'user' => { 'login' => 'user', 'id' => 123 },
+ 'head' => { 'sha' => 'abc123', 'ref' => 'feature' },
+ 'base' => { 'sha' => 'def456', 'ref' => 'main' }
+ }
+ }
+ end
+
+ let(:fake_client) { Octokit::Client.new }
+ let(:fake_github_check) { Github::Check.new(nil) }
+ let(:fake_plan_run) { BambooCi::PlanRun.new(nil, pull_request.plans.last) }
+ let(:fake_check_run) { create(:check_suite) }
+ let(:fake_action) { double('Github::Build::Action') }
+
+ before do
+ allow(File).to receive(:read).and_return('')
+ allow(OpenSSL::PKey::RSA).to receive(:new).and_return(OpenSSL::PKey::RSA.new(2048))
+ allow(TimeoutExecution).to receive_message_chain(:delay, :timeout).and_return(true)
+ allow(GitHubApp::Configuration).to receive(:new).and_return(GitHubApp::Configuration.instance)
+
+ allow(Octokit::Client).to receive(:new).and_return(fake_client)
+ allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }])
+ allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 })
+
+ allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run)
+ allow(fake_plan_run).to receive(:start_plan).and_return(200)
+ allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-FIRST-1')
+ allow(fake_plan_run).to receive(:bamboo_reference).and_return('CHECKOUT-1')
+
+ allow(Github::Check).to receive(:new).and_return(fake_github_check)
+ allow(fake_github_check).to receive(:create).and_return(fake_check_run)
+ allow(fake_github_check).to receive(:in_progress).and_return(fake_check_run)
+ allow(fake_github_check).to receive(:queued).and_return(fake_check_run)
+ allow(fake_github_check).to receive(:fetch_username).and_return({})
+ allow(fake_github_check).to receive(:fetch_username).and_return({})
+ allow(fake_github_check).to receive(:check_runs_for_ref).and_return({})
+ allow(BambooCi::RunningPlan).to receive(:fetch).and_return({ job: '1' })
+ allow(Github::Build::Action).to receive(:new).and_return(fake_action)
+ allow(fake_action).to receive(:create_summary)
+ end
+
+ describe '.create' do
+ let(:fake_suite) { create(:check_suite) }
+ it 'returns [422, "Plan not found"]' do
+ allow(Plan).to receive(:find_by).and_return(nil)
+ expect(described_class.create(pull_request.id, payload, 999)).to eq([422, 'Plan not found'])
+ end
+
+ it 'returns [422, "Failed to save Check Suite"]' do
+ allow(CheckSuite).to receive(:create).and_return(fake_suite)
+ allow(fake_suite).to receive(:persisted?).and_return(false)
+
+ expect(described_class.create(pull_request.id, payload,
+ pull_request.plans.last.id)).to eq([422, 'Failed to save Check Suite'])
+ end
+
+ it 'must create the execution' do
+ result = described_class.create(pull_request.id, payload, pull_request.plans.last.id)
+ expect(result).to eq([200, 'Pull Request created'])
+ end
+
+ context 'when plan does not exists' do
+ it 'returns [422, "Plan not found"]' do
+ allow(Plan).to receive(:find_by).and_return(nil)
+ expect(described_class.create(pull_request.id, payload, 999)).to eq([422, 'Plan not found'])
+ end
+ end
+ end
+end
diff --git a/workers/ci_job_status.rb b/workers/ci_job_status.rb
index 970244c..76d01aa 100644
--- a/workers/ci_job_status.rb
+++ b/workers/ci_job_status.rb
@@ -11,9 +11,9 @@
require_relative '../config/setup'
class CiJobStatus
- def self.update(check_suite_id, ci_job_id)
+ def self.update(bamboo_ci_ref, ci_job_id)
@logger = GithubLogger.instance.create('ci_job_status.log', Logger::INFO)
- @logger.info("CiJobStatus::Update: Checksuite #{check_suite_id} -> '#{ci_job_id}'")
+ @logger.info("CiJobStatus::Update: Checksuite #{bamboo_ci_ref} -> '#{ci_job_id}'")
job = CiJob.find(ci_job_id)
@@ -22,9 +22,9 @@ def self.update(check_suite_id, ci_job_id)
return unless job.finished?
- @logger.info("Github::PlanExecution::Finished: '#{job.check_suite.bamboo_ci_ref}'")
+ @logger.info("Github::PlanExecution::Finished: '#{bamboo_ci_ref}'")
- finished = Github::PlanExecution::Finished.new({ 'bamboo_ref' => job.check_suite.bamboo_ci_ref })
+ finished = Github::PlanExecution::Finished.new({ 'bamboo_ref' => bamboo_ci_ref })
finished.finished
end
end
diff --git a/workers/create_execution_by_command.rb b/workers/create_execution_by_command.rb
new file mode 100644
index 0000000..b3addaa
--- /dev/null
+++ b/workers/create_execution_by_command.rb
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# create_execution_by_command.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+class CreateExecutionByCommand < Github::ReRun::Base
+ def self.create(plan_id, check_suite_id, payload)
+ check_suite = CheckSuite.find(check_suite_id)
+ plan = Plan.find(plan_id)
+
+ return [404, 'Failed to fetch a check suite'] if check_suite.nil?
+
+ instance = new(plan, check_suite, payload)
+
+ instance.status
+ end
+
+ attr_reader :status
+
+ def initialize(plan, check_suite, payload)
+ super(payload, logger_level: Logger::INFO)
+
+ @logger_manager << GithubLogger.instance.create('github_rerun_command.log', Logger::INFO)
+ @logger_manager << Logger.new($stdout)
+
+ @github_check = Github::Check.new(check_suite)
+
+ stop_previous_execution(plan)
+
+ check_suite = create_check_suite(check_suite)
+
+ start_new_execution(check_suite, plan)
+ ci_jobs(check_suite, plan)
+ end
+
+ def create_check_suite(check_suite)
+ CheckSuite.create(
+ pull_request: check_suite.pull_request,
+ author: check_suite.author,
+ commit_sha_ref: check_suite.commit_sha_ref,
+ work_branch: check_suite.work_branch,
+ base_sha_ref: check_suite.base_sha_ref,
+ merge_branch: check_suite.merge_branch,
+ re_run: true
+ )
+ end
+
+ def start_new_execution(check_suite, plan)
+ cleanup(check_suite)
+
+ bamboo_plan_run = BambooCi::PlanRun.new(check_suite, plan, logger_level: @logger_level)
+ bamboo_plan_run.ci_variables = ci_vars
+ bamboo_plan_run.start_plan
+
+ audit_retry =
+ AuditRetry.create(check_suite: check_suite,
+ github_username: @payload.dig('sender', 'login'),
+ github_id: @payload.dig('sender', 'id'),
+ github_type: @payload.dig('sender', 'type'),
+ retry_type: 'full')
+
+ Github::UserInfo.new(@payload.dig('sender', 'id'), check_suite: check_suite, audit_retry: audit_retry)
+ end
+end
diff --git a/workers/create_execution_by_comment.rb b/workers/create_execution_by_comment.rb
new file mode 100644
index 0000000..9caaca3
--- /dev/null
+++ b/workers/create_execution_by_comment.rb
@@ -0,0 +1,160 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# create_execution_by_comment.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+class CreateExecutionByComment < Github::ReRun::Base
+ def self.create(pull_request_id, payload, plan_id)
+ logger = GithubLogger.instance.create('github_app.log', Logger::INFO)
+ plan = Plan.find_by(id: plan_id)
+
+ return [422, 'Plan not found'] if plan.nil?
+
+ instance = new(pull_request_id, payload, plan)
+
+ logger.info "CreateExecutionByComment: Plan '#{plan.name}' for Pull Request ID: #{pull_request_id} with " \
+ "status: #{instance.status.inspect}"
+
+ instance.status
+ end
+
+ attr_reader :status
+
+ def initialize(pull_request_id, payload, plan)
+ super(payload, logger_level: Logger::INFO)
+
+ @logger_manager << GithubLogger.instance.create('github_rerun_comment.log', Logger::INFO)
+ @logger_manager << Logger.new($stdout)
+
+ @pull_request = PullRequest.find(pull_request_id)
+ @status = []
+
+ run_by_plan(plan)
+ end
+
+ private
+
+ def run_by_plan(plan)
+ check_suite = sha256_or_comment?
+ logger(Logger::DEBUG, ">>> Check suite: #{check_suite.inspect}")
+
+ return [404, 'Failed to create a check suite'] if check_suite.nil?
+
+ check_suite.update(plan: plan)
+
+ stop_previous_execution(plan)
+
+ start_new_execution(check_suite, plan)
+
+ ci_jobs(check_suite, plan)
+
+ [201, 'Starting re-run (comment)']
+ end
+
+ def sha256_or_comment?
+ fetch_old_check_suite
+
+ @old_check_suite.nil? ? comment_flow : sha256_flow
+ end
+
+ def comment_flow
+ commit = fetch_last_commit_or_sha256
+ github_check = fetch_github_check
+ pull_request_info = github_check.pull_request_info(pr_id, repo)
+
+ fetch_old_check_suite(commit[:sha])
+ check_suite = create_check_suite_by_commit(commit, @pull_request, pull_request_info)
+ logger(Logger::INFO, "CheckSuite errors: #{check_suite.inspect}")
+ return nil unless check_suite.persisted?
+
+ @github_check = Github::Check.new(check_suite)
+
+ check_suite
+ end
+
+ # Fetches the GitHub check associated with the pull request.
+ #
+ # This method finds the pull request by its GitHub PR ID and then retrieves
+ # the last check suite associated with that pull request. It then initializes
+ # a new `Github::Check` object with the last check suite.
+ #
+ # @return [Github::Check] the GitHub check associated with the pull request.
+ #
+ # @raise [ActiveRecord::RecordNotFound] if the pull request is not found.
+ def fetch_github_check
+ pull_request = PullRequest.find_by(github_pr_id: pr_id)
+ Github::Check.new(pull_request.check_suites.last)
+ end
+
+ def create_check_suite_by_commit(commit, pull_request, pull_request_info)
+ CheckSuite.create(
+ pull_request: pull_request,
+ author: @payload.dig('comment', 'user', 'login'),
+ commit_sha_ref: commit[:sha],
+ work_branch: pull_request_info.dig(:head, :ref),
+ base_sha_ref: pull_request_info.dig(:base, :sha),
+ merge_branch: pull_request_info.dig(:base, :ref),
+ re_run: true
+ )
+ end
+
+ def sha256_flow
+ @github_check = Github::Check.new(@old_check_suite)
+ create_new_check_suite
+ end
+
+ # The behaviour will be the following: It will fetch the last commit if it has
+ # received a comment and only fetch a commit if the command starts with ci:rerrun #.
+ # If there is any other character before the # it will be considered a comment.
+ def fetch_last_commit_or_sha256
+ pull_request_commit = Github::Parsers::PullRequestCommit.new(repo, pr_id)
+ commit = pull_request_commit.find_by_sha(sha256)
+
+ return commit if commit and action.match(/ci:rerun\s+#/i)
+
+ fetch_last_commit
+ end
+
+ def fetch_last_commit
+ Github::Parsers::PullRequestCommit.new(repo, pr_id).last_commit_in_pr
+ end
+
+ def fetch_old_check_suite(sha = sha256)
+ return if sha.nil?
+
+ logger(Logger::DEBUG, ">>> fetch_old_check_suite SHA: #{sha}")
+
+ @old_check_suite =
+ CheckSuite
+ .joins(:pull_request)
+ .where('commit_sha_ref ILIKE ? AND pull_requests.repository = ?', "#{sha}%", repo)
+ .last
+ end
+
+ def create_new_check_suite
+ CheckSuite.create(
+ pull_request: @pull_request,
+ author: @old_check_suite.author,
+ commit_sha_ref: @old_check_suite.commit_sha_ref,
+ work_branch: @old_check_suite.work_branch,
+ base_sha_ref: @old_check_suite.base_sha_ref,
+ merge_branch: @old_check_suite.merge_branch,
+ re_run: true
+ )
+ end
+
+ def sha256
+ return nil unless action.downcase.match? 'ci:rerun #'
+
+ action.downcase.split('#').last
+ end
+
+ def action?
+ action.to_s.downcase.match? 'ci:rerun' and @payload['action'] == 'created'
+ end
+end
diff --git a/workers/create_execution_by_plan.rb b/workers/create_execution_by_plan.rb
new file mode 100644
index 0000000..09f7a1d
--- /dev/null
+++ b/workers/create_execution_by_plan.rb
@@ -0,0 +1,177 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# create_execution_by_plan.rb
+# Part of NetDEF CI System
+#
+# Copyright (c) 2025 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# frozen_string_literal: true
+
+class CreateExecutionByPlan
+ def self.create(pull_request_id, payload, plan_id)
+ logger = GithubLogger.instance.create('github_app.log', Logger::INFO)
+ plan = Plan.find_by(id: plan_id)
+
+ return [422, 'Plan not found'] if plan.nil?
+
+ instance = new(pull_request_id, payload, plan_id)
+
+ logger.info "CreateExecutionByPlan: Plan '#{plan.name}' for Pull Request ID: #{pull_request_id} with " \
+ "status: #{instance.status.inspect}"
+
+ instance.status
+ end
+
+ attr_reader :status
+
+ def initialize(pull_request_id, payload, plan_id)
+ @logger = Logger.new($stdout)
+ @logger.level = Logger::INFO
+
+ @pull_request = PullRequest.find(pull_request_id)
+ @payload = payload
+ @status = []
+
+ create_execution_by_plan(Plan.find_by(id: plan_id))
+ end
+
+ private
+
+ def create_execution_by_plan(plan)
+ @has_previous_exec = false
+
+ @logger.info "Starting Plan: #{plan.name}"
+
+ fetch_last_check_suite(plan)
+
+ create_check_suite
+
+ unless @check_suite.persisted?
+ @status = [422, 'Failed to save Check Suite']
+
+ return
+ end
+
+ @check_suite.update(plan: plan)
+
+ @logger.info "Check Suite created: #{@check_suite.inspect}"
+
+ # Stop a previous execution - Avoiding CI spam
+ stop_previous_execution
+
+ @logger.info "Starting a new execution for Pull Request: #{@pull_request.inspect}"
+ # Starting a new CI run
+ status = start_new_execution(plan)
+
+ @logger.info "New execution started with status: #{status}"
+
+ if status != 200
+ @status = [status, 'Failed to create CI Plan']
+
+ return
+ end
+
+ @status = ci_jobs(plan)
+ end
+
+ def ci_jobs(plan)
+ @logger.info 'Creating GitHub Check'
+
+ SlackBot.instance.execution_started_notification(@check_suite)
+
+ jobs = BambooCi::RunningPlan.fetch(@check_suite.bamboo_ci_ref)
+
+ return [422, 'Failed to fetch RunningPlan'] if jobs.nil? or jobs.empty?
+
+ action = Github::Build::Action.new(@check_suite, @github_check, jobs, plan.name)
+ action.create_summary
+
+ @logger.info ">>> @has_previous_exec: #{@has_previous_exec}"
+ stop_execution_message if @has_previous_exec
+
+ [200, 'Pull Request created']
+ end
+
+ def start_new_execution(plan)
+ @check_suite.pull_request = @pull_request
+
+ Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id'), check_suite: @check_suite)
+
+ @logger.info 'Starting a new plan'
+ @bamboo_plan_run = BambooCi::PlanRun.new(@check_suite, plan, logger_level: @logger.level)
+ @bamboo_plan_run.ci_variables = ci_vars
+ @bamboo_plan_run.start_plan
+ end
+
+ def stop_previous_execution
+ return if @last_check_suite.nil? or @last_check_suite.finished?
+
+ @logger.info 'Stopping previous execution'
+ @logger.info @last_check_suite.inspect
+ @logger.info @check_suite.inspect
+
+ cancel_previous_ci_jobs
+ end
+
+ def cancel_previous_ci_jobs
+ mark_as_cancelled_jobs
+
+ @last_check_suite.update(stopped_in_stage: @last_check_suite.stages.where(status: :in_progress).last)
+
+ mark_as_cancelled_stages
+
+ @has_previous_exec = true
+
+ BambooCi::StopPlan.build(@last_check_suite.bamboo_ci_ref)
+ end
+
+ def mark_as_cancelled_jobs
+ @last_check_suite.ci_jobs.where(status: %w[queued in_progress]).each do |ci_job|
+ @logger.warn("Cancelling Job #{ci_job.inspect}")
+ ci_job.cancelled(@github_check)
+ end
+ end
+
+ def mark_as_cancelled_stages
+ @last_check_suite.stages.where(status: %w[queued in_progress]).each do |stage|
+ stage.cancelled(@github_check)
+ end
+ end
+
+ def fetch_last_check_suite(plan)
+ @last_check_suite =
+ CheckSuite
+ .joins(pull_request: :plans)
+ .where(pull_request: { id: @pull_request.id, plans: { name: plan.name } })
+ .last
+ end
+
+ def create_check_suite
+ @logger.info 'Creating a check suite'
+ @check_suite =
+ CheckSuite.create(
+ pull_request: @pull_request,
+ author: @payload.dig('pull_request', 'user', 'login'),
+ commit_sha_ref: @payload.dig('pull_request', 'head', 'sha'),
+ work_branch: @payload.dig('pull_request', 'head', 'ref'),
+ base_sha_ref: @payload.dig('pull_request', 'base', 'sha'),
+ merge_branch: @payload.dig('pull_request', 'base', 'ref')
+ )
+
+ @logger.info 'Creating GitHub Check API'
+ @github_check = Github::Check.new(@check_suite)
+ end
+
+ def ci_vars
+ ci_vars = []
+ ci_vars << { value: @github_check.signature, name: 'signature_secret' }
+
+ ci_vars
+ end
+
+ def stop_execution_message
+ @check_suite.update(cancelled_previous_check_suite_id: @last_check_suite.id)
+ BambooCi::StopPlan.comment(@last_check_suite, @check_suite)
+ end
+end