From 8c623e737b2a407bbfd83839bf9583a697c262b9 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 19 Aug 2025 09:47:37 -0300 Subject: [PATCH 01/16] [GBA-9] Allow multiple plans Add support for multiple plans and pull requests in CI workflow - Introduced new migrations to add references for pull requests and plans in the database. - Updated the CI job creation process to handle multiple Bamboo plans associated with pull requests. - Enhanced logging and error handling for better traceability during CI executions. --- .../20250812101554_add_pull_requests_plans.rb | 17 +++++++ ...502_add_check_suite_multiple_bamboo_ref.rb | 23 +++++++++ .../20250812144322_add_plan_to_bamboo_ref.rb | 16 ++++++ lib/bamboo_ci/api.rb | 29 ++++++++--- lib/bamboo_ci/plan_run.rb | 5 ++ lib/bamboo_ci/stop_plan.rb | 2 +- lib/github/bamboo_ref_retriever.rb | 38 ++++++++++++++ lib/github/build/action.rb | 19 ++++--- lib/github/build/retry.rb | 2 +- lib/github/build/summary.rb | 37 +++++++------- lib/github/build_plan.rb | 50 ++++++++++++++----- lib/github/plan_execution/finished.rb | 11 ++-- lib/github/re_run/base.rb | 38 +++++++++----- lib/github/re_run/command.rb | 4 +- lib/github/re_run/comment.rb | 4 +- lib/github/update_status.rb | 10 ++-- lib/github_ci_app.rb | 1 + lib/models/bamboo_ref.rb | 20 ++++++++ lib/models/check_suite.rb | 1 + lib/models/plan.rb | 3 ++ lib/models/pull_request.rb | 3 +- lib/models/stage.rb | 13 +++-- lib/slack_bot/slack_bot.rb | 6 +-- workers/ci_job_status.rb | 8 +-- 24 files changed, 276 insertions(+), 84 deletions(-) create mode 100644 db/migrate/20250812101554_add_pull_requests_plans.rb create mode 100644 db/migrate/20250812104502_add_check_suite_multiple_bamboo_ref.rb create mode 100644 db/migrate/20250812144322_add_plan_to_bamboo_ref.rb create mode 100644 lib/github/bamboo_ref_retriever.rb create mode 100644 lib/models/bamboo_ref.rb 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..42fa6cb --- /dev/null +++ b/db/migrate/20250812101554_add_pull_requests_plans.rb @@ -0,0 +1,17 @@ +# 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 \ No newline at end of file diff --git a/db/migrate/20250812104502_add_check_suite_multiple_bamboo_ref.rb b/db/migrate/20250812104502_add_check_suite_multiple_bamboo_ref.rb new file mode 100644 index 0000000..70544d6 --- /dev/null +++ b/db/migrate/20250812104502_add_check_suite_multiple_bamboo_ref.rb @@ -0,0 +1,23 @@ +# 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 AddCheckSuiteMultipleBambooRef < ActiveRecord::Migration[6.0] + def change + create_table :bamboo_refs do |t| + t.string :bamboo_key, null: false + t.references :check_suite, null: false, foreign_key: true + + t.timestamps + end + + add_index :bamboo_refs, :bamboo_key, unique: true + end +end \ No newline at end of file diff --git a/db/migrate/20250812144322_add_plan_to_bamboo_ref.rb b/db/migrate/20250812144322_add_plan_to_bamboo_ref.rb new file mode 100644 index 0000000..bed9f03 --- /dev/null +++ b/db/migrate/20250812144322_add_plan_to_bamboo_ref.rb @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# 20250812144322_add_plan_to_bamboo_ref.rb +# Part of NetDEF CI System +# +# Copyright (c) 2025 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true +# + +class AddPlanToBambooRef < ActiveRecord::Migration[6.0] + def change + add_reference :bamboo_refs, :plan, null: false, foreign_key: true + end +end diff --git a/lib/bamboo_ci/api.rb b/lib/bamboo_ci/api.rb index aa89263..0d12275 100644 --- a/lib/bamboo_ci/api.rb +++ b/lib/bamboo_ci/api.rb @@ -29,18 +29,31 @@ def get_status(id) 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}" + resp = nil + check_suite.pull_request.plans.each do |plan| + url = "https://127.0.0.1/rest/api/latest/queue/#{plan.bamboo_ci_plan_name}" - url += custom_variables(check_suite) + url += custom_variables(check_suite) - ci_variables.each do |variable| - url += "&bamboo.variable.github_#{variable[:name]}=#{variable[:value]}" - end + ci_variables.each do |variable| + url += "&bamboo.variable.github_#{variable[:name]}=#{variable[:value]}" + end - logger(Logger::DEBUG, "Submission URL:\n #{url}") + logger(Logger::DEBUG, "Submission URL:\n #{url}") - # Fetch Request - post_request(URI(url)) + # Fetch Request + resp = post_request(URI(url)) + + json = JSON.parse(resp.body) + + logger(Logger::INFO, "BambooCi::PlanRun - Response: #{resp.code} #{resp.body} - #{plan.bamboo_ci_plan_name}") + + puts json + + @refs << { name: plan.name, key: json['buildResultKey'] } + end + + resp end def custom_variables(check_suite) diff --git a/lib/bamboo_ci/plan_run.rb b/lib/bamboo_ci/plan_run.rb index 8c9bc9a..a0784ea 100644 --- a/lib/bamboo_ci/plan_run.rb +++ b/lib/bamboo_ci/plan_run.rb @@ -36,6 +36,7 @@ def initialize(check_suite, logger_level: Logger::INFO) end def start_plan + @refs = [] @response = submit_pr_to_ci(@check_suite, @ci_variables) case @response&.code.to_i @@ -58,6 +59,10 @@ def bamboo_reference JSON.parse(@response.body)['buildResultKey'] end + def bamboo_references + @refs + end + private def success(response) diff --git a/lib/bamboo_ci/stop_plan.rb b/lib/bamboo_ci/stop_plan.rb index 54de48f..c796702 100644 --- a/lib/bamboo_ci/stop_plan.rb +++ b/lib/bamboo_ci/stop_plan.rb @@ -26,7 +26,7 @@ def self.build(bamboo_ci_ref) end def self.comment(check_suite, new_check_suite) - new_url = "https://ci1.netdef.org/browse/#{new_check_suite.bamboo_ci_ref}" + new_url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{new_check_suite.bamboo_ci_ref}" comment = "This execution was cancelled due to a new commit or `ci:rerun` (#{new_url})" add_comment_to_ci(check_suite.bamboo_ci_ref, comment) diff --git a/lib/github/bamboo_ref_retriever.rb b/lib/github/bamboo_ref_retriever.rb new file mode 100644 index 0000000..1855187 --- /dev/null +++ b/lib/github/bamboo_ref_retriever.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: BSD-2-Clause +# +# bamboo_ref_retriever.rb +# Part of NetDEF CI System +# +# Copyright (c) 2025 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true +# + +module Github + class BambooRefRetriever + def initialize(job, check_suite) + @check_suite = check_suite + @job = job + end + + def fetch + bamboo_ref = nil + @check_suite.bamboo_refs.each do |ref| + jobs = BambooCi::RunningPlan.fetch(ref.bamboo_key) + info = jobs.find { |job| job[:name] == @job.name } + + if info.present? + bamboo_ref = info + bamboo_ref[:bamboo_ci_ref] = ref.bamboo_key + break + end + end + + bamboo_ref + end + end +end + diff --git a/lib/github/build/action.rb b/lib/github/build/action.rb index bb954ae..b13c994 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 + @name = name %w[github_app.log github_build_action.log].each do |filename| @loggers << GithubLogger.instance.create(filename, logger_level) @@ -77,7 +79,7 @@ def create_jobs(rerun) if rerun next unless ci_job.stage.configuration.can_retry? - url = "https://ci1.netdef.org/browse/#{ci_job.job_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{ci_job.job_ref}" ci_job.enqueue(@github, { title: ci_job.name, summary: "Details at [#{url}](#{url})" }) else ci_job.create_check_run @@ -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,7 +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, "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) logger(Logger::INFO, "STAGE #{stage_config.github_check_run_name} #{stage.inspect} - @#{@check_suite.inspect}") @@ -148,7 +151,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, @@ -156,7 +159,7 @@ def create_stage(stage_config) status: 'queued', name: name) - url = "https://ci1.netdef.org/browse/#{stage.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "Uninitialized stage\nDetails at [#{url}](#{url})" } stage.enqueue(@github, output: output) @@ -172,7 +175,7 @@ def create_stage(stage_config) # @return [Hash] The initial output. def initial_output(ci_job) output = { title: '', summary: '' } - url = "https://ci1.netdef.org/browse/#{ci_job.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{ci_job.check_suite.bamboo_ci_ref}" output[:title] = "#{ci_job.name} summary" output[:summary] = "Details at [#{url}](#{url})" diff --git a/lib/github/build/retry.rb b/lib/github/build/retry.rb index ba0f74c..9522b3e 100644 --- a/lib/github/build/retry.rb +++ b/lib/github/build/retry.rb @@ -36,7 +36,7 @@ def enqueued_stages next if stage.nil? next if stage.success? - url = "https://ci1.netdef.org/browse/#{stage.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "Uninitialized stage\nDetails at [#{url}](#{url})" } stage.enqueue(@github, output: output) diff --git a/lib/github/build/summary.rb b/lib/github/build/summary.rb index 38b65bd..05c6eb7 100644 --- a/lib/github/build/summary.rb +++ b/lib/github/build/summary.rb @@ -57,8 +57,10 @@ def build_summary private def bamboo_info + bamboo_info = Github::BambooRefRetriever.new(@job, @job.check_suite).fetch + finish = Github::PlanExecution::Finished.new({ 'bamboo_ref' => @check_suite.bamboo_ci_ref }) - finish.fetch_build_status + finish.fetch_build_status(bamboo_info[:bamboo_ci_ref]) end def must_update_previous_stage(current_stage) @@ -90,10 +92,10 @@ def must_continue_next_stage(current_stage) next_stage = Stage - .joins(:configuration) - .where(check_suite: @check_suite) - .where(configuration: { position: current_stage.configuration.position + 1 }) - .first + .joins(:configuration) + .where(check_suite: @check_suite) + .where(configuration: { position: current_stage.configuration.position + 1 }) + .first return if next_stage.nil? or next_stage.finished? @@ -101,7 +103,7 @@ def must_continue_next_stage(current_stage) end def cancelling_next_stage(pending_stage) - url = "https://ci1.netdef.org/browse/#{pending_stage.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{pending_stage.check_suite.bamboo_ci_ref}" output = { title: "#{pending_stage.name} summary", @@ -122,7 +124,7 @@ def finished_summary(stage) end def finished_stage_summary(stage) - url = "https://ci1.netdef.org/browse/#{stage.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "#{summary_basic_output(stage)}\nDetails at [#{url}](#{url}).".force_encoding('utf-8') @@ -146,7 +148,7 @@ def finished_stage_update(stage, output) end def update_summary(stage) - url = "https://ci1.netdef.org/browse/#{@check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{@check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "#{summary_basic_output(stage)}\nDetails at [#{url}](#{url}).".force_encoding('utf-8') @@ -196,7 +198,7 @@ def in_progress_message(jobs) message = "\n\n:arrow_right: Jobs in progress: #{in_progress.size}/#{jobs.size}\n\n" message + jobs.where(status: %i[in_progress]).map do |job| - "- **#{job.name}** -> https://ci1.netdef.org/browse/#{job.job_ref}\n" + "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n" end.join("\n") end @@ -206,13 +208,13 @@ def queued_message(jobs) message = ":arrow_right: Jobs queued: #{queued.size}/#{jobs.size}\n\n" message + queued.map do |job| - "- **#{job.name}** -> https://ci1.netdef.org/browse/#{job.job_ref}\n" + "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n" end.join("\n") end def success_message(jobs) jobs.where(status: :success).map do |job| - "- **#{job.name}** -> https://ci1.netdef.org/browse/#{job.job_ref}\n" + "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n" end.join("\n") end @@ -227,7 +229,7 @@ def generate_message(name, job) failures = build_message(job) if name.downcase.match?('build') failures = checkout_message(job) if name.downcase.match?('source') - "- #{job.name} -> https://ci1.netdef.org/browse/#{job.job_ref}\n#{failures}" + "- #{job.name} -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n#{failures}" end def tests_message(job) @@ -258,12 +260,9 @@ def build_message(job) end def fetch_parent_stage - jobs = BambooCi::RunningPlan.fetch(@check_suite.bamboo_ci_ref) - info = jobs.find { |job| job[:name] == @job.name } - - stage = first_or_create_stage(info) + bamboo_info = Github::BambooRefRetriever.new(@job, @check_suite).fetch - logger(Logger::INFO, "fetch_parent_stage - stage: #{stage.inspect} info[:stage]: #{info[:stage]}") + stage = first_or_create_stage(bamboo_info) @job.update(stage: stage) @@ -272,7 +271,9 @@ def fetch_parent_stage def first_or_create_stage(info) config = StageConfiguration.find_by(bamboo_stage_name: info[:stage]) - stage = Stage.find_by(check_suite: @check_suite, name: info[:stage]) + stage = Stage.joins(check_suite: :bamboo_refs) + .where(check_suites: { id: @check_suite.id }, bamboo_refs: { bamboo_key: info[:bamboo_ci_ref] }) + .first stage = Stage.create(check_suite: @check_suite, name: info[:stage], configuration: config) if stage.nil? stage diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index de4c262..d3a59a3 100644 --- a/lib/github/build_plan.rb +++ b/lib/github/build_plan.rb @@ -55,12 +55,15 @@ def create return [422, 'Failed to save Check Suite'] end + @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 + @logger.info "New execution started with status: #{status}" return [status, 'Failed to create CI Plan'] if status != 200 # Creating CiJobs at database @@ -74,9 +77,11 @@ def fetch_pull_request return create_pull_request if @pull_request.nil? - @logger.info "Updating plan: #{fetch_plan}" + @logger.info "Updating plan: #{fetch_plan_name}" - @pull_request.update(plan: fetch_plan, branch_name: @payload.dig('pull_request', 'head', 'ref')) + @pull_request.update(plan: fetch_plan_name, branch_name: @payload.dig('pull_request', 'head', 'ref')) + + add_plans end def github_pr @@ -90,9 +95,11 @@ def create_pull_request github_pr_id: github_pr, branch_name: @payload.dig('pull_request', 'head', 'ref'), repository: @payload.dig('repository', 'full_name'), - plan: fetch_plan + plan: fetch_plan_name ) + add_plans + Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id'), pull_request: @pull_request) end @@ -103,6 +110,7 @@ def start_new_execution 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, logger_level: @logger.level) @bamboo_plan_run.ci_variables = ci_vars @bamboo_plan_run.start_plan @@ -131,7 +139,10 @@ def cancel_previous_ci_jobs end @has_previous_exec = true - BambooCi::StopPlan.build(@last_check_suite.bamboo_ci_ref) + + @last_check_suite.bamboo_refs.each do |bamboo_ref| + BambooCi::StopPlan.build(bamboo_ref.bamboo_key) + end end def create_check_suite @@ -159,17 +170,22 @@ def ci_jobs SlackBot.instance.execution_started_notification(@check_suite) - @check_suite.update(bamboo_ci_ref: @bamboo_plan_run.bamboo_reference) + @bamboo_plan_run.bamboo_references.each do |entry| + @logger.info "Creating Bamboo Reference: #{entry[:name]} - #{entry[:key]}" + plan = Plan.find_by(name: entry[:name]) + BambooRef.create(bamboo_key: entry[:key], check_suite: @check_suite, plan: plan) - jobs = BambooCi::RunningPlan.fetch(@bamboo_plan_run.bamboo_reference) + jobs = BambooCi::RunningPlan.fetch(entry[:key]) - return [422, 'Failed to fetch RunningPlan'] if jobs.nil? or jobs.empty? + @logger.info "Fetched jobs for Bamboo Reference: #{entry[:name]} - #{jobs.inspect}" + 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 + action = Github::Build::Action.new(@check_suite, @github_check, jobs, entry[:name]) + action.create_summary - @logger.info ">>> @has_previous_exec: #{@has_previous_exec}" - stop_execution_message if @has_previous_exec + @logger.info ">>> @has_previous_exec: #{@has_previous_exec}" + stop_execution_message if @has_previous_exec + end [200, 'Pull Request created'] end @@ -186,7 +202,7 @@ def ci_vars ci_vars end - def fetch_plan + def fetch_plan_name plan = Plan.find_by(github_repo_name: @payload.dig('repository', 'full_name')) return plan.bamboo_ci_plan_name unless plan.nil? @@ -194,5 +210,15 @@ def fetch_plan # Default plan 'TESTING-FRRCRAS' end + + def add_plans + return if @pull_request.nil? + + Plan.where(github_repo_name: @payload.dig('repository', 'full_name')).each do |plan| + @pull_request.plans << plan unless @pull_request.plans.include?(plan) + end + + @pull_request.save + end end end diff --git a/lib/github/plan_execution/finished.rb b/lib/github/plan_execution/finished.rb index fc1bf99..b8a5446 100644 --- a/lib/github/plan_execution/finished.rb +++ b/lib/github/plan_execution/finished.rb @@ -34,7 +34,8 @@ class Finished # # @param [Hash] payload The payload containing information about the CheckSuite. def initialize(payload) - @check_suite = CheckSuite.find_by(bamboo_ci_ref: payload['bamboo_ref']) if payload['bamboo_ref'] + @bamboo_ref = BambooRef.find_by(bamboo_key: payload['bamboo_ref']) if payload['bamboo_ref'] + @check_suite = @bamboo_ref.check_suite if @bamboo_ref @check_suite = CheckSuite.find(payload['check_suite_id']) if payload['check_suite_id'] @logger = GithubLogger.instance.create('github_plan_execution_finished.log', Logger::INFO) @hanged = payload['hanged'] || false @@ -51,7 +52,7 @@ def finished return [404, 'Check Suite not found'] if @check_suite.nil? fetch_ci_execution - build_status = fetch_build_status + build_status = fetch_build_status(@bamboo_ref.bamboo_key) @logger.info ">>> build_status: #{build_status.inspect}. Hanged? #{@hanged}" @@ -68,8 +69,8 @@ def finished # Fetches the build status from Bamboo CI. # # @return [Hash] The build status. - def fetch_build_status - get_request(URI("https://127.0.0.1/rest/api/latest/result/status/#{@check_suite.bamboo_ci_ref}")) + def fetch_build_status(bamboo_key) + get_request(URI("https://127.0.0.1/rest/api/latest/result/status/#{bamboo_key}")) end ## @@ -180,7 +181,7 @@ def update_ci_job_status(github_check, ci_job, state) # @param [CiJob] ci_job The CI job to create the message for. # @return [Hash] The output message. def create_output_message(ci_job) - url = "https://ci1.netdef.org/browse/#{ci_job.job_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{ci_job.job_ref}" { title: ci_job.name, diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index 5339a58..6c89fc0 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -65,7 +65,12 @@ def stop_and_update_previous_execution(check_suite) @last_check_suite = check_suite - BambooCi::StopPlan.build(check_suite.bamboo_ci_ref) + logger(Logger::INFO, "Stopping Bamboo Plan: #{@last_check_suite.id}") + + @last_check_suite.bamboo_refs.each do |bamboo_ref| + logger(Logger::INFO, "Stopping Bamboo Reference: #{bamboo_ref.bamboo_key}") + BambooCi::StopPlan.build(bamboo_ref.bamboo_key) + end end def cancel_previous_jobs(check_suite) @@ -74,10 +79,10 @@ 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(bamboo_plan, check_suite, plan_name) + jobs = BambooCi::RunningPlan.fetch(bamboo_plan) - 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 @@ -110,7 +115,7 @@ def start_new_execution(check_suite) Github::UserInfo.new(@payload.dig('sender', 'id'), check_suite: check_suite, audit_retry: audit_retry) - bamboo_plan_run + bamboo_plan_run.bamboo_references end def ci_vars @@ -120,17 +125,26 @@ def ci_vars ci_vars end - def ci_jobs(check_suite, bamboo_plan) - SlackBot.instance.execution_started_notification(check_suite) + def ci_jobs(check_suite, bamboo_plans) + bamboo_plans.each do |bamboo_plan| + logger(Logger::INFO, "Starting Bamboo Plan: #{bamboo_plan[:name]} - #{bamboo_plan[:key]}") + SlackBot.instance.execution_started_notification(check_suite) + + plan = Plan.find_by(name: bamboo_plan[:name]) + bamboo_ref = BambooRef.create(bamboo_key: bamboo_plan[:key], check_suite: check_suite, plan: plan) + bamboo_ref.save + + logger(Logger::INFO, "Creating Bamboo Reference: #{bamboo_ref.bamboo_key} - #{bamboo_ref.check_suite}") - check_suite.update(bamboo_ci_ref: bamboo_plan.bamboo_reference, re_run: true) + check_suite.update(bamboo_ci_ref: bamboo_plan[:key], re_run: true) - check_suite.update(cancelled_previous_check_suite: @last_check_suite) + check_suite.update(cancelled_previous_check_suite: @last_check_suite) - create_ci_jobs(bamboo_plan, check_suite) + create_ci_jobs(bamboo_plan[:key], check_suite, bamboo_plan[:name]) - CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref).each do |cs| - Github::Build::UnavailableJobs.new(cs).update(new_check_suite: 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 end diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index 7487bc8..347a66a 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -34,8 +34,8 @@ def start check_suite = create_check_suite(check_suite) - bamboo_plan = start_new_execution(check_suite) - ci_jobs(check_suite, bamboo_plan) + bamboo_plans = start_new_execution(check_suite) + ci_jobs(check_suite, bamboo_plans) [201, 'Starting re-run (command)'] end diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index daeba8a..53e4cfc 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -37,9 +37,9 @@ def start stop_previous_execution - bamboo_plan = start_new_execution(check_suite) + bamboo_plans = start_new_execution(check_suite) - ci_jobs(check_suite, bamboo_plan) + ci_jobs(check_suite, bamboo_plans) [201, 'Starting re-run (comment)'] end diff --git a/lib/github/update_status.rb b/lib/github/update_status.rb index 67a0803..0481aca 100644 --- a/lib/github/update_status.rb +++ b/lib/github/update_status.rb @@ -125,11 +125,13 @@ def insert_new_delayed_job # # @param [Integer] queue The queue number for the delayed job. def delete_and_create_delayed_job(queue) - fetch_delayed_job(queue).destroy_all + bamboo_info = Github::BambooRefRetriever.new(@job, @job.check_suite).fetch + + fetch_delayed_job(queue, bamboo_info[:bamboo_ci_ref]).destroy_all CiJobStatus .delay(run_at: DELAYED_JOB_TIMER.seconds.from_now.utc, queue: queue) - .update(@job.check_suite.id, @job.id) + .update(bamboo_info[:bamboo_ci_ref], @job.check_suite.id, @job.id) end ## @@ -137,10 +139,10 @@ 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) + def fetch_delayed_job(queue, bamboo_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- #{bamboo_ref}%") end ## diff --git a/lib/github_ci_app.rb b/lib/github_ci_app.rb index e6479d2..e99351f 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/bamboo_ref_retriever' # Helpers libs require_relative 'helpers/configuration' diff --git a/lib/models/bamboo_ref.rb b/lib/models/bamboo_ref.rb new file mode 100644 index 0000000..2870dab --- /dev/null +++ b/lib/models/bamboo_ref.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: BSD-2-Clause +# +# bamboo_ref.rb +# Part of NetDEF CI System +# +# Copyright (c) 2025 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true +# + +class BambooRef < ActiveRecord::Base + validates :bamboo_key, presence: true, uniqueness: true + validates :check_suite, presence: true + + belongs_to :check_suite + belongs_to :plan +end diff --git a/lib/models/check_suite.rb b/lib/models/check_suite.rb index 667bc56..3842c48 100644 --- a/lib/models/check_suite.rb +++ b/lib/models/check_suite.rb @@ -22,6 +22,7 @@ class CheckSuite < ActiveRecord::Base has_many :ci_jobs, dependent: :delete_all has_many :stages, dependent: :delete_all has_many :audit_retries, dependent: :delete_all + has_many :bamboo_refs, dependent: :delete_all default_scope -> { order(id: :asc) }, all_queries: true diff --git a/lib/models/plan.rb b/lib/models/plan.rb index 5a198bb..acc2206 100644 --- a/lib/models/plan.rb +++ b/lib/models/plan.rb @@ -11,4 +11,7 @@ require 'otr-activerecord' class Plan < ActiveRecord::Base + has_many :bamboo_refs, dependent: :delete_all + + belongs_to :pull_request end diff --git a/lib/models/pull_request.rb b/lib/models/pull_request.rb index 408ed51..0d8b894 100644 --- a/lib/models/pull_request.rb +++ b/lib/models/pull_request.rb @@ -18,7 +18,7 @@ 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? @@ -29,7 +29,6 @@ def current_execution?(check_suite) current_execution == check_suite end - # @return [CheckSuite] def current_execution check_suites.order(id: :asc).last end diff --git a/lib/models/stage.rb b/lib/models/stage.rb index 98f5dfd..c08abd7 100644 --- a/lib/models/stage.rb +++ b/lib/models/stage.rb @@ -33,7 +33,14 @@ def running? def previous_stage position = configuration&.position.to_i - check_suite.stages.joins(:configuration).find_by(configuration: { position: position - 1 }) + suffix = name.split(' - ', 2).last + return nil unless suffix + + check_suite.stages + .joins(:configuration) + .where(configuration: { position: position - 1 }) + .where('stages.name LIKE ?', "%#{suffix}") + .first end def finished? @@ -121,10 +128,10 @@ def output_in_progress header = ":arrow_right: Jobs in progress: #{in_progress.size}/#{jobs.size}\n\n" in_progress_jobs = jobs.where(status: :in_progress).map do |job| - "- **#{job.name}** -> https://ci1.netdef.org/browse/#{job.job_ref}\n" + "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n" end.join("\n") - url = "https://ci1.netdef.org/browse/#{check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{check_suite.bamboo_ci_ref}" { title: "#{name} summary", summary: "#{header}#{in_progress_jobs}\nDetails at [#{url}](#{url})" } end end diff --git a/lib/slack_bot/slack_bot.rb b/lib/slack_bot/slack_bot.rb index 48ad5fc..7a3e603 100644 --- a/lib/slack_bot/slack_bot.rb +++ b/lib/slack_bot/slack_bot.rb @@ -149,7 +149,7 @@ def send_stage_notification(stage, pull_request, subscription) url = "#{GitHubApp::Configuration.instance.config['slack_bot_url']}/github/user" pr_url = "https://github.com/#{pull_request.repository}/pull/#{pull_request.github_pr_id}" - bamboo_link = "https://ci1.netdef.org/browse/#{stage.check_suite.bamboo_ci_ref}" + bamboo_link = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{stage.check_suite.bamboo_ci_ref}" post_request(URI(url), machine: 'slack_bot.netdef.org', @@ -197,7 +197,7 @@ def pull_request_message(check_suite, status) pr = check_suite.pull_request pr_url = "https://github.com/#{pr.repository}/pull/#{pr.github_pr_id}" - bamboo_link = "https://ci1.netdef.org/browse/#{check_suite.bamboo_ci_ref}" + bamboo_link = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{check_suite.bamboo_ci_ref}" "PR <#{pr_url}|##{pr.github_pr_id}>. <#{bamboo_link}|#{status}> " end @@ -205,7 +205,7 @@ def pull_request_message(check_suite, status) def generate_notification_message(job, status) pr = job.check_suite.pull_request pr_url = "https://github.com/#{pr.repository}/pull/#{pr.github_pr_id}/checks?check_run_id=#{job.check_ref}" - bamboo_link = "https://ci1.netdef.org/browse/#{job.job_ref}" + bamboo_link = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}" "PR <#{pr_url}|##{pr.github_pr_id}>. <#{bamboo_link}|#{job.name} - #{status}> " end diff --git a/workers/ci_job_status.rb b/workers/ci_job_status.rb index 970244c..8c3ff69 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_ref, check_suite_id, 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: #{bamboo_ref} -> Checksuite #{check_suite_id} -> '#{ci_job_id}'") job = CiJob.find(ci_job_id) @@ -24,7 +24,9 @@ def self.update(check_suite_id, ci_job_id) @logger.info("Github::PlanExecution::Finished: '#{job.check_suite.bamboo_ci_ref}'") - finished = Github::PlanExecution::Finished.new({ 'bamboo_ref' => job.check_suite.bamboo_ci_ref }) + bamboo_info = Github::BambooRefRetriever.new(job, job.check_suite).fetch + + finished = Github::PlanExecution::Finished.new({ 'bamboo_ref' => bamboo_info[:bamboo_ci_ref] }) finished.finished end end From de15d51c544eeaad4178e494759e8ce44ab596a2 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 19 Aug 2025 14:24:21 -0300 Subject: [PATCH 02/16] [GBA-9] Allow multiple plans Update BambooRef handling to associate check_suite with existing references --- lib/github/re_run/base.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index 6c89fc0..1bae804 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -131,7 +131,8 @@ def ci_jobs(check_suite, bamboo_plans) SlackBot.instance.execution_started_notification(check_suite) plan = Plan.find_by(name: bamboo_plan[:name]) - bamboo_ref = BambooRef.create(bamboo_key: bamboo_plan[:key], check_suite: check_suite, plan: plan) + bamboo_ref = BambooRef.find_by(bamboo_key: bamboo_plan[:key], plan: plan) + bamboo_ref.check_suite = check_suite bamboo_ref.save logger(Logger::INFO, "Creating Bamboo Reference: #{bamboo_ref.bamboo_key} - #{bamboo_ref.check_suite}") From bb7be2f5d83705db1b6f23a7efa19a02d7fd9ca5 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Fri, 22 Aug 2025 07:37:36 -0300 Subject: [PATCH 03/16] [GBA-9] Allow multiple plans Refactoring relations between Plan and CheckSuite. --- Gemfile | 2 +- .../20250812101554_add_pull_requests_plans.rb | 3 +- ...502_add_check_suite_multiple_bamboo_ref.rb | 23 --- ...=> 20250822071834_add_check_suite_plan.rb} | 6 +- lib/bamboo_ci/api.rb | 29 ++-- lib/bamboo_ci/plan_run.rb | 7 +- lib/bamboo_ci/stop_plan.rb | 2 +- lib/github/bamboo_ref_retriever.rb | 38 ----- lib/github/build/action.rb | 2 - lib/github/build/plan_run.rb | 158 ++++++++++++++++++ lib/github/build/summary.rb | 37 ++-- lib/github/build_plan.rb | 132 +-------------- lib/github/plan_execution/finished.rb | 17 +- lib/github/re_run/base.rb | 62 +++---- lib/github/re_run/command.rb | 4 +- lib/github/re_run/comment.rb | 46 +++-- lib/github/update_status.rb | 19 ++- lib/github_ci_app.rb | 2 +- lib/helpers/request.rb | 2 +- lib/models/bamboo_ref.rb | 20 --- lib/models/check_suite.rb | 2 +- lib/models/plan.rb | 2 +- lib/models/pull_request.rb | 8 +- lib/models/stage.rb | 20 ++- lib/slack_bot/slack_bot.rb | 2 +- workers/ci_job_status.rb | 10 +- 26 files changed, 317 insertions(+), 338 deletions(-) delete mode 100644 db/migrate/20250812104502_add_check_suite_multiple_bamboo_ref.rb rename db/migrate/{20250812144322_add_plan_to_bamboo_ref.rb => 20250822071834_add_check_suite_plan.rb} (56%) delete mode 100644 lib/github/bamboo_ref_retriever.rb create mode 100644 lib/github/build/plan_run.rb delete mode 100644 lib/models/bamboo_ref.rb 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 index 42fa6cb..d54e4b3 100644 --- a/db/migrate/20250812101554_add_pull_requests_plans.rb +++ b/db/migrate/20250812101554_add_pull_requests_plans.rb @@ -7,11 +7,10 @@ # 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 \ No newline at end of file +end diff --git a/db/migrate/20250812104502_add_check_suite_multiple_bamboo_ref.rb b/db/migrate/20250812104502_add_check_suite_multiple_bamboo_ref.rb deleted file mode 100644 index 70544d6..0000000 --- a/db/migrate/20250812104502_add_check_suite_multiple_bamboo_ref.rb +++ /dev/null @@ -1,23 +0,0 @@ -# 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 AddCheckSuiteMultipleBambooRef < ActiveRecord::Migration[6.0] - def change - create_table :bamboo_refs do |t| - t.string :bamboo_key, null: false - t.references :check_suite, null: false, foreign_key: true - - t.timestamps - end - - add_index :bamboo_refs, :bamboo_key, unique: true - end -end \ No newline at end of file diff --git a/db/migrate/20250812144322_add_plan_to_bamboo_ref.rb b/db/migrate/20250822071834_add_check_suite_plan.rb similarity index 56% rename from db/migrate/20250812144322_add_plan_to_bamboo_ref.rb rename to db/migrate/20250822071834_add_check_suite_plan.rb index bed9f03..77fa11b 100644 --- a/db/migrate/20250812144322_add_plan_to_bamboo_ref.rb +++ b/db/migrate/20250822071834_add_check_suite_plan.rb @@ -1,6 +1,6 @@ # SPDX-License-Identifier: BSD-2-Clause # -# 20250812144322_add_plan_to_bamboo_ref.rb +# 20250822071834_add_check_suite_plan.rb # Part of NetDEF CI System # # Copyright (c) 2025 by @@ -9,8 +9,8 @@ # frozen_string_literal: true # -class AddPlanToBambooRef < ActiveRecord::Migration[6.0] +class AddCheckSuitePlan < ActiveRecord::Migration[6.0] def change - add_reference :bamboo_refs, :plan, null: false, foreign_key: true + add_reference :check_suites, :plan, foreign_key: true end end diff --git a/lib/bamboo_ci/api.rb b/lib/bamboo_ci/api.rb index 0d12275..ee9e83a 100644 --- a/lib/bamboo_ci/api.rb +++ b/lib/bamboo_ci/api.rb @@ -28,30 +28,21 @@ 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) - resp = nil - check_suite.pull_request.plans.each do |plan| - url = "https://127.0.0.1/rest/api/latest/queue/#{plan.bamboo_ci_plan_name}" + 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) + url += custom_variables(check_suite) - ci_variables.each do |variable| - url += "&bamboo.variable.github_#{variable[:name]}=#{variable[:value]}" - end - - logger(Logger::DEBUG, "Submission URL:\n #{url}") - - # Fetch Request - resp = post_request(URI(url)) - - json = JSON.parse(resp.body) + ci_variables.each do |variable| + url += "&bamboo.variable.github_#{variable[:name]}=#{variable[:value]}" + end - logger(Logger::INFO, "BambooCi::PlanRun - Response: #{resp.code} #{resp.body} - #{plan.bamboo_ci_plan_name}") + logger(Logger::DEBUG, "Submission URL:\n #{url}") - puts json + # Fetch Request + resp = post_request(URI(url)) - @refs << { name: plan.name, key: json['buildResultKey'] } - end + logger(Logger::INFO, "BambooCi::PlanRun - Response: #{resp.code} #{resp.body} - #{plan.bamboo_ci_plan_name}") resp end diff --git a/lib/bamboo_ci/plan_run.rb b/lib/bamboo_ci/plan_run.rb index a0784ea..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,15 +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 @refs = [] - @response = submit_pr_to_ci(@check_suite, @ci_variables) + @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) diff --git a/lib/bamboo_ci/stop_plan.rb b/lib/bamboo_ci/stop_plan.rb index c796702..89537b0 100644 --- a/lib/bamboo_ci/stop_plan.rb +++ b/lib/bamboo_ci/stop_plan.rb @@ -26,7 +26,7 @@ def self.build(bamboo_ci_ref) end def self.comment(check_suite, new_check_suite) - new_url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{new_check_suite.bamboo_ci_ref}" + new_url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{new_check_suite.bamboo_ci_ref}" comment = "This execution was cancelled due to a new commit or `ci:rerun` (#{new_url})" add_comment_to_ci(check_suite.bamboo_ci_ref, comment) diff --git a/lib/github/bamboo_ref_retriever.rb b/lib/github/bamboo_ref_retriever.rb deleted file mode 100644 index 1855187..0000000 --- a/lib/github/bamboo_ref_retriever.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -# SPDX-License-Identifier: BSD-2-Clause -# -# bamboo_ref_retriever.rb -# Part of NetDEF CI System -# -# Copyright (c) 2025 by -# Network Device Education Foundation, Inc. ("NetDEF") -# -# frozen_string_literal: true -# - -module Github - class BambooRefRetriever - def initialize(job, check_suite) - @check_suite = check_suite - @job = job - end - - def fetch - bamboo_ref = nil - @check_suite.bamboo_refs.each do |ref| - jobs = BambooCi::RunningPlan.fetch(ref.bamboo_key) - info = jobs.find { |job| job[:name] == @job.name } - - if info.present? - bamboo_ref = info - bamboo_ref[:bamboo_ci_ref] = ref.bamboo_key - break - end - end - - bamboo_ref - end - end -end - diff --git a/lib/github/build/action.rb b/lib/github/build/action.rb index b13c994..5c097d8 100644 --- a/lib/github/build/action.rb +++ b/lib/github/build/action.rb @@ -135,8 +135,6 @@ def create_check_run_stage(stage_config) 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) - logger(Logger::INFO, "STAGE #{stage_config.github_check_run_name} #{stage.inspect} - @#{@check_suite.inspect}") - return create_stage(stage_config) if stage.nil? return unless stage.configuration.can_retry? diff --git a/lib/github/build/plan_run.rb b/lib/github/build/plan_run.rb new file mode 100644 index 0000000..e99b121 --- /dev/null +++ b/lib/github/build/plan_run.rb @@ -0,0 +1,158 @@ +# 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 + def initialize(pull_request, payload) + @logger = Logger.new($stdout) + @logger.level = Logger::INFO + + @pull_request = pull_request + @payload = payload + end + + def build + @pull_request.plans.each do |plan| + @has_previous_exec = false + + @logger.info "Starting Plan: #{plan.name}" + + fetch_last_check_suite(plan) + + create_check_suite + + next [422, 'Failed to save Check Suite'] unless @check_suite.persisted? + + @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}" + next [status, 'Failed to create CI Plan'] if status != 200 + + ci_jobs(plan) + end + end + + private + + 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) + 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) + + @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 } }) + .where("check_suites.bamboo_ci_ref LIKE '#{plan.bamboo_ci_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 + end +end diff --git a/lib/github/build/summary.rb b/lib/github/build/summary.rb index 878c6cc..3b6378f 100644 --- a/lib/github/build/summary.rb +++ b/lib/github/build/summary.rb @@ -57,10 +57,8 @@ def build_summary private def bamboo_info - bamboo_info = Github::BambooRefRetriever.new(@job, @job.check_suite).fetch - finish = Github::PlanExecution::Finished.new({ 'bamboo_ref' => @check_suite.bamboo_ci_ref }) - finish.fetch_build_status(bamboo_info[:bamboo_ci_ref]) + finish.fetch_build_status end def must_update_previous_stage(current_stage) @@ -92,10 +90,10 @@ def must_continue_next_stage(current_stage) next_stage = Stage - .joins(:configuration) - .where(check_suite: @check_suite) - .where(configuration: { position: current_stage.configuration.position + 1 }) - .first + .joins(:configuration) + .where(check_suite: @check_suite) + .where(configuration: { position: current_stage.configuration.position + 1 }) + .first return if next_stage.nil? or next_stage.finished? @@ -103,7 +101,7 @@ def must_continue_next_stage(current_stage) end def cancelling_next_stage(pending_stage) - url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{pending_stage.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{pending_stage.check_suite.bamboo_ci_ref}" output = { title: "#{pending_stage.name} summary", @@ -124,7 +122,7 @@ def finished_summary(stage) end def finished_stage_summary(stage) - url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{stage.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "#{summary_basic_output(stage)}\nDetails at [#{url}](#{url}).".force_encoding('utf-8') @@ -148,7 +146,7 @@ def finished_stage_update(stage, output) end def update_summary(stage) - url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{@check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{@check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "#{summary_basic_output(stage)}\nDetails at [#{url}](#{url}).".force_encoding('utf-8') @@ -198,7 +196,7 @@ def in_progress_message(jobs) message = "\n\n:arrow_right: Jobs in progress: #{in_progress.size}/#{jobs.size}\n\n" message + jobs.where(status: %i[in_progress]).map do |job| - "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n" + "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{job.job_ref}\n" end.join("\n") end @@ -208,13 +206,13 @@ def queued_message(jobs) message = ":arrow_right: Jobs queued: #{queued.size}/#{jobs.size}\n\n" message + queued.map do |job| - "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n" + "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{job.job_ref}\n" end.join("\n") end def success_message(jobs) jobs.where(status: :success).map do |job| - "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n" + "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{job.job_ref}\n" end.join("\n") end @@ -229,7 +227,7 @@ def generate_message(name, job) failures = build_message(job) if name.downcase.match?('build') failures = checkout_message(job) if name.downcase.match?('source') - "- #{job.name} -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n#{failures}" + "- #{job.name} -> https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{job.job_ref}\n#{failures}" end def tests_message(job) @@ -260,9 +258,12 @@ def build_message(job) end def fetch_parent_stage - bamboo_info = Github::BambooRefRetriever.new(@job, @check_suite).fetch + jobs = BambooCi::RunningPlan.fetch(@check_suite.bamboo_ci_ref) + info = jobs.find { |job| job[:name] == @job.name } + + stage = first_or_create_stage(info) - stage = first_or_create_stage(bamboo_info) + logger(Logger::INFO, "fetch_parent_stage - stage: #{stage.inspect} info[:stage]: #{info[:stage]}") @job.update(stage: stage) @@ -271,9 +272,7 @@ def fetch_parent_stage def first_or_create_stage(info) config = StageConfiguration.find_by(bamboo_stage_name: info[:stage]) - stage = Stage.joins(check_suite: :bamboo_refs) - .where(check_suites: { id: @check_suite.id }, bamboo_refs: { bamboo_key: info[:bamboo_ci_ref] }) - .first + stage = Stage.find_by(check_suite: @check_suite, name: info[:stage]) stage = Stage.create(check_suite: @check_suite, name: info[:stage], configuration: config) if stage.nil? stage diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index d3a59a3..9f58e43 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,31 +42,9 @@ def create @logger.info 'Fetching / Creating a pull request' fetch_pull_request - # Fetch last Check Suite - fetch_last_check_suite + Github::Build::PlanRun.new(@pull_request, @payload).build - # 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 - - @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 - - @logger.info "New execution started with status: #{status}" - return [status, 'Failed to create CI Plan'] if status != 200 - - # Creating CiJobs at database - ci_jobs + @logger.info "Pull Request: #{@pull_request.id} created a new execution." end private @@ -77,9 +54,7 @@ def fetch_pull_request return create_pull_request if @pull_request.nil? - @logger.info "Updating plan: #{fetch_plan_name}" - - @pull_request.update(plan: fetch_plan_name, branch_name: @payload.dig('pull_request', 'head', 'ref')) + @pull_request.update(branch_name: @payload.dig('pull_request', 'head', 'ref')) add_plans end @@ -103,105 +78,6 @@ def create_pull_request 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) - - @logger.info "Starting a new plan" - @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 - - @last_check_suite.bamboo_refs.each do |bamboo_ref| - BambooCi::StopPlan.build(bamboo_ref.bamboo_key) - end - 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) - - @bamboo_plan_run.bamboo_references.each do |entry| - @logger.info "Creating Bamboo Reference: #{entry[:name]} - #{entry[:key]}" - plan = Plan.find_by(name: entry[:name]) - BambooRef.create(bamboo_key: entry[:key], check_suite: @check_suite, plan: plan) - - jobs = BambooCi::RunningPlan.fetch(entry[:key]) - - @logger.info "Fetched jobs for Bamboo Reference: #{entry[:name]} - #{jobs.inspect}" - return [422, 'Failed to fetch RunningPlan'] if jobs.nil? or jobs.empty? - - action = Github::Build::Action.new(@check_suite, @github_check, jobs, entry[:name]) - action.create_summary - - @logger.info ">>> @has_previous_exec: #{@has_previous_exec}" - stop_execution_message if @has_previous_exec - end - - [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 - end - def fetch_plan_name plan = Plan.find_by(github_repo_name: @payload.dig('repository', 'full_name')) @@ -214,6 +90,8 @@ def fetch_plan_name def add_plans return if @pull_request.nil? + @pull_request.plans = [] + Plan.where(github_repo_name: @payload.dig('repository', 'full_name')).each do |plan| @pull_request.plans << plan unless @pull_request.plans.include?(plan) end diff --git a/lib/github/plan_execution/finished.rb b/lib/github/plan_execution/finished.rb index b8a5446..733bfb8 100644 --- a/lib/github/plan_execution/finished.rb +++ b/lib/github/plan_execution/finished.rb @@ -34,9 +34,12 @@ class Finished # # @param [Hash] payload The payload containing information about the CheckSuite. def initialize(payload) - @bamboo_ref = BambooRef.find_by(bamboo_key: payload['bamboo_ref']) if payload['bamboo_ref'] - @check_suite = @bamboo_ref.check_suite if @bamboo_ref - @check_suite = CheckSuite.find(payload['check_suite_id']) if payload['check_suite_id'] + @check_suite = CheckSuite.where( + bamboo_ci_ref: payload['bamboo_ref'] + ).or( + CheckSuite.where(id: payload['check_suite_id']) + ).last + @logger = GithubLogger.instance.create('github_plan_execution_finished.log', Logger::INFO) @hanged = payload['hanged'] || false end @@ -52,7 +55,7 @@ def finished return [404, 'Check Suite not found'] if @check_suite.nil? fetch_ci_execution - build_status = fetch_build_status(@bamboo_ref.bamboo_key) + build_status = fetch_build_status @logger.info ">>> build_status: #{build_status.inspect}. Hanged? #{@hanged}" @@ -69,8 +72,8 @@ def finished # Fetches the build status from Bamboo CI. # # @return [Hash] The build status. - def fetch_build_status(bamboo_key) - get_request(URI("https://127.0.0.1/rest/api/latest/result/status/#{bamboo_key}")) + def fetch_build_status + get_request(URI("https://127.0.0.1/rest/api/latest/result/status/#{@check_suite.bamboo_ci_ref}")) end ## @@ -181,7 +184,7 @@ def update_ci_job_status(github_check, ci_job, state) # @param [CiJob] ci_job The CI job to create the message for. # @return [Hash] The output message. def create_output_message(ci_job) - url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{ci_job.job_ref}" + url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{ci_job.job_ref}" { title: ci_job.name, diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index 1bae804..16bcbe7 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -32,23 +32,22 @@ 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: { plan: plan, 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 @@ -65,12 +64,7 @@ def stop_and_update_previous_execution(check_suite) @last_check_suite = check_suite - logger(Logger::INFO, "Stopping Bamboo Plan: #{@last_check_suite.id}") - - @last_check_suite.bamboo_refs.each do |bamboo_ref| - logger(Logger::INFO, "Stopping Bamboo Reference: #{bamboo_ref.bamboo_key}") - BambooCi::StopPlan.build(bamboo_ref.bamboo_key) - end + BambooCi::StopPlan.build(check_suite.bamboo_ci_ref) end def cancel_previous_jobs(check_suite) @@ -79,8 +73,8 @@ def cancel_previous_jobs(check_suite) end end - def create_ci_jobs(bamboo_plan, check_suite, plan_name) - jobs = BambooCi::RunningPlan.fetch(bamboo_plan) + 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, plan_name) action.create_summary(rerun: true) @@ -101,8 +95,10 @@ def logger(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 @@ -114,8 +110,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.bamboo_references end def ci_vars @@ -125,27 +119,25 @@ def ci_vars ci_vars end - def ci_jobs(check_suite, bamboo_plans) - bamboo_plans.each do |bamboo_plan| - logger(Logger::INFO, "Starting Bamboo Plan: #{bamboo_plan[:name]} - #{bamboo_plan[:key]}") - SlackBot.instance.execution_started_notification(check_suite) + def ci_jobs(check_suite, plan) + SlackBot.instance.execution_started_notification(check_suite) - plan = Plan.find_by(name: bamboo_plan[:name]) - bamboo_ref = BambooRef.find_by(bamboo_key: bamboo_plan[:key], plan: plan) - bamboo_ref.check_suite = check_suite - bamboo_ref.save + check_suite.update(cancelled_previous_check_suite: @last_check_suite) - logger(Logger::INFO, "Creating Bamboo Reference: #{bamboo_ref.bamboo_key} - #{bamboo_ref.check_suite}") + create_ci_jobs(check_suite, plan.name) - check_suite.update(bamboo_ci_ref: bamboo_plan[:key], re_run: true) - - check_suite.update(cancelled_previous_check_suite: @last_check_suite) + update_unavailable_jobs(check_suite) + end - create_ci_jobs(bamboo_plan[:key], check_suite, bamboo_plan[:name]) + 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 - 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 + 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 diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index 347a66a..7001e7b 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -30,11 +30,11 @@ def start @github_check = Github::Check.new(check_suite) - stop_previous_execution + stop_previous_execution(plan) check_suite = create_check_suite(check_suite) - bamboo_plans = start_new_execution(check_suite) + bamboo_plans = start_new_execution(check_suite, plan) ci_jobs(check_suite, bamboo_plans) [201, 'Starting re-run (command)'] diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index 53e4cfc..64cd717 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -19,6 +19,7 @@ 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 @@ -27,24 +28,44 @@ def start logger(Logger::DEBUG, ">>> Github::ReRun::Comment - sha256: #{sha256.inspect}, payload: #{@payload.inspect}") - check_suite = sha256_or_comment? + fetch_pull_request - logger(Logger::DEBUG, ">>> Check suite: #{check_suite.inspect}") + return [404, 'Pull Request not found'] if @pull_request.nil? + return [404, 'Can not rerun a new PullRequest'] if @pull_request.check_suites.empty? - return [404, 'Failed to create a check suite'] if check_suite.nil? + confirm_and_start + + [201, 'Starting re-run (comment)'] + end + + private + def confirm_and_start github_reaction_feedback(comment_id) - stop_previous_execution + @pull_request.plans.each do |plan| + run_by_plan(plan) + end + end - bamboo_plans = start_new_execution(check_suite) + def run_by_plan(plan) + check_suite = sha256_or_comment? + logger(Logger::DEBUG, ">>> Check suite: #{check_suite.inspect}") - ci_jobs(check_suite, bamboo_plans) + return [404, 'Failed to create a check suite'] if check_suite.nil? - [201, 'Starting re-run (comment)'] + check_suite.update(plan: plan) + + stop_previous_execution(plan) + + start_new_execution(check_suite, plan) + + ci_jobs(check_suite, plan) end - private + def fetch_pull_request + @pull_request = PullRequest.find_by(github_pr_id: pr_id) + end def sha256_or_comment? fetch_old_check_suite @@ -56,10 +77,9 @@ 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) + 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? @@ -144,7 +164,9 @@ def fetch_last_commit def github_reaction_feedback(comment_id) return if comment_id.nil? - @github_check.comment_reaction_thumb_up(repo, comment_id) + github_check = Github::Check.new(@pull_request.check_suites.last) + + github_check.comment_reaction_thumb_up(repo, comment_id) end def fetch_old_check_suite(sha = sha256) @@ -161,7 +183,7 @@ def fetch_old_check_suite(sha = sha256) def create_new_check_suite CheckSuite.create( - pull_request: @old_check_suite.pull_request, + 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, diff --git a/lib/github/update_status.rb b/lib/github/update_status.rb index 0481aca..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 @@ -125,13 +128,15 @@ def insert_new_delayed_job # # @param [Integer] queue The queue number for the delayed job. def delete_and_create_delayed_job(queue) - bamboo_info = Github::BambooRefRetriever.new(@job, @job.check_suite).fetch + fetch_delayed_job(queue).destroy_all - fetch_delayed_job(queue, bamboo_info[:bamboo_ci_ref]).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(bamboo_info[:bamboo_ci_ref], @job.check_suite.id, @job.id) + .update(@check_suite.bamboo_ci_ref, @job.id) end ## @@ -139,10 +144,13 @@ 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, bamboo_ref) + 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- #{bamboo_ref}%") + .where('handler LIKE ?', "%method_name: :update\nargs:\n- #{@check_suite.bamboo_ci_ref}%") end ## @@ -181,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 e99351f..ea329e3 100644 --- a/lib/github_ci_app.rb +++ b/lib/github_ci_app.rb @@ -30,7 +30,7 @@ require_relative 'github/user_info' require_relative 'github/build/skip_old_tests' require_relative 'github/topotest_failures/retrieve_error' -require_relative 'github/bamboo_ref_retriever' +require_relative 'github/build/plan_run' # Helpers libs require_relative 'helpers/configuration' 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/bamboo_ref.rb b/lib/models/bamboo_ref.rb deleted file mode 100644 index 2870dab..0000000 --- a/lib/models/bamboo_ref.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -# SPDX-License-Identifier: BSD-2-Clause -# -# bamboo_ref.rb -# Part of NetDEF CI System -# -# Copyright (c) 2025 by -# Network Device Education Foundation, Inc. ("NetDEF") -# -# frozen_string_literal: true -# - -class BambooRef < ActiveRecord::Base - validates :bamboo_key, presence: true, uniqueness: true - validates :check_suite, presence: true - - belongs_to :check_suite - belongs_to :plan -end diff --git a/lib/models/check_suite.rb b/lib/models/check_suite.rb index 3842c48..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 @@ -22,7 +23,6 @@ class CheckSuite < ActiveRecord::Base has_many :ci_jobs, dependent: :delete_all has_many :stages, dependent: :delete_all has_many :audit_retries, dependent: :delete_all - has_many :bamboo_refs, dependent: :delete_all default_scope -> { order(id: :asc) }, all_queries: true diff --git a/lib/models/plan.rb b/lib/models/plan.rb index acc2206..588610b 100644 --- a/lib/models/plan.rb +++ b/lib/models/plan.rb @@ -11,7 +11,7 @@ require 'otr-activerecord' class Plan < ActiveRecord::Base - has_many :bamboo_refs, dependent: :delete_all + has_many :check_suites belongs_to :pull_request end diff --git a/lib/models/pull_request.rb b/lib/models/pull_request.rb index 0d8b894..48edcf1 100644 --- a/lib/models/pull_request.rb +++ b/lib/models/pull_request.rb @@ -22,15 +22,15 @@ class PullRequest < ActiveRecord::Base 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 - 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 4233a15..b0064d4 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(' - ', 2).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,7 @@ def running? def previous_stage position = configuration&.position.to_i - suffix = name.split(' - ', 2).last + return nil unless suffix check_suite.stages @@ -124,15 +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 = jobs.where(status: :in_progress).map do |job| - "- **#{job.name}** -> https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}\n" + 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://#{GitHubApp::Configuration.instance.config['ci']['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/lib/slack_bot/slack_bot.rb b/lib/slack_bot/slack_bot.rb index 7a3e603..2c12812 100644 --- a/lib/slack_bot/slack_bot.rb +++ b/lib/slack_bot/slack_bot.rb @@ -149,7 +149,7 @@ def send_stage_notification(stage, pull_request, subscription) url = "#{GitHubApp::Configuration.instance.config['slack_bot_url']}/github/user" pr_url = "https://github.com/#{pull_request.repository}/pull/#{pull_request.github_pr_id}" - bamboo_link = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{stage.check_suite.bamboo_ci_ref}" + bamboo_link = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{stage.check_suite.bamboo_ci_ref}" post_request(URI(url), machine: 'slack_bot.netdef.org', diff --git a/workers/ci_job_status.rb b/workers/ci_job_status.rb index 8c3ff69..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(bamboo_ref, 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: #{bamboo_ref} -> 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,11 +22,9 @@ def self.update(bamboo_ref, 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}'") - bamboo_info = Github::BambooRefRetriever.new(job, job.check_suite).fetch - - finished = Github::PlanExecution::Finished.new({ 'bamboo_ref' => bamboo_info[:bamboo_ci_ref] }) + finished = Github::PlanExecution::Finished.new({ 'bamboo_ref' => bamboo_ci_ref }) finished.finished end end From ea5e767879420af3a6f6812db35ae3c1bc9c7bcb Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Fri, 29 Aug 2025 15:32:34 -0300 Subject: [PATCH 04/16] [GBA-9] Allow multiple plans Rubocop --- db/migrate/20250822071834_add_check_suite_plan.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/migrate/20250822071834_add_check_suite_plan.rb b/db/migrate/20250822071834_add_check_suite_plan.rb index 77fa11b..1a29fbc 100644 --- a/db/migrate/20250822071834_add_check_suite_plan.rb +++ b/db/migrate/20250822071834_add_check_suite_plan.rb @@ -7,7 +7,6 @@ # Network Device Education Foundation, Inc. ("NetDEF") # # frozen_string_literal: true -# class AddCheckSuitePlan < ActiveRecord::Migration[6.0] def change From ae3d3732995b717002dfa6e40cb1c1d96204c54d Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Fri, 29 Aug 2025 15:41:28 -0300 Subject: [PATCH 05/16] [GBA-9] Allow multiple plans Updating unit tests --- db/schema.rb | 9 ++++++++- lib/github/build/retry.rb | 2 +- spec/factories/plan.rb | 1 + spec/factories/pull_request.rb | 2 +- spec/lib/github/build_plan_spec.rb | 2 +- spec/lib/github/re_run/command_spec.rb | 3 ++- spec/lib/github/re_run/comment_spec.rb | 2 +- 7 files changed, 15 insertions(+), 6 deletions(-) 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/github/build/retry.rb b/lib/github/build/retry.rb index 9522b3e..b592638 100644 --- a/lib/github/build/retry.rb +++ b/lib/github/build/retry.rb @@ -36,7 +36,7 @@ def enqueued_stages next if stage.nil? next if stage.success? - url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{stage.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "Uninitialized stage\nDetails at [#{url}](#{url})" } stage.enqueue(@github, output: output) 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/lib/github/build_plan_spec.rb b/spec/lib/github/build_plan_spec.rb index d2425a1..71572fe 100644 --- a/spec/lib/github/build_plan_spec.rb +++ b/spec/lib/github/build_plan_spec.rb @@ -12,7 +12,7 @@ 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 diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index d5f2748..9440601 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('') diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 044b057..7972c99 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) } From c62f90e4673cf9511d45cb5b57c3293cca25434b Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Mon, 1 Sep 2025 17:41:53 -0300 Subject: [PATCH 06/16] [GBA-9] Allow multiple plans Refactor action and plan handling to support multiple plans in CI execution --- lib/bamboo_ci/api.rb | 6 +- lib/github/build/action.rb | 13 ++-- lib/github/build/plan_run.rb | 56 +++++++++------ lib/github/build/summary.rb | 3 + lib/github/build_plan.rb | 2 - lib/github/re_run/base.rb | 2 + lib/github/re_run/command.rb | 12 ++-- lib/github/re_run/comment.rb | 10 +-- lib/models/stage.rb | 2 +- lib/slack_bot/slack_bot.rb | 4 +- spec/factories/stage.rb | 2 +- spec/lib/bamboo_ci/api_spec.rb | 5 +- spec/lib/bamboo_ci/plan_run_spec.rb | 5 +- spec/lib/github/build/action_spec.rb | 12 ++-- spec/lib/github/build/summary_spec.rb | 25 +++++-- spec/lib/github/build_plan_spec.rb | 99 ++++---------------------- spec/lib/github/re_run/comment_spec.rb | 65 +++-------------- spec/lib/github/retry/comment_spec.rb | 2 +- 18 files changed, 119 insertions(+), 206 deletions(-) diff --git a/lib/bamboo_ci/api.rb b/lib/bamboo_ci/api.rb index ee9e83a..60c9ddf 100644 --- a/lib/bamboo_ci/api.rb +++ b/lib/bamboo_ci/api.rb @@ -40,11 +40,7 @@ def submit_pr_to_ci(check_suite, plan, ci_variables) logger(Logger::DEBUG, "Submission URL:\n #{url}") # Fetch Request - resp = post_request(URI(url)) - - logger(Logger::INFO, "BambooCi::PlanRun - Response: #{resp.code} #{resp.body} - #{plan.bamboo_ci_plan_name}") - - resp + post_request(URI(url)) end def custom_variables(check_suite) diff --git a/lib/github/build/action.rb b/lib/github/build/action.rb index 5c097d8..ae73968 100644 --- a/lib/github/build/action.rb +++ b/lib/github/build/action.rb @@ -35,7 +35,7 @@ def initialize(check_suite, github, jobs, name, logger_level: Logger::INFO) @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| @@ -51,11 +51,12 @@ def initialize(check_suite, github, jobs, name, 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| + puts stage_config.github_check_run_name create_check_run_stage(stage_config) end @@ -79,7 +80,7 @@ def create_jobs(rerun) if rerun next unless ci_job.stage.configuration.can_retry? - url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{ci_job.job_ref}" + url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{ci_job.job_ref}" ci_job.enqueue(@github, { title: ci_job.name, summary: "Details at [#{url}](#{url})" }) else ci_job.create_check_run @@ -157,7 +158,7 @@ def create_stage(stage_config) status: 'queued', name: name) - url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{stage.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "Uninitialized stage\nDetails at [#{url}](#{url})" } stage.enqueue(@github, output: output) @@ -173,7 +174,7 @@ def create_stage(stage_config) # @return [Hash] The initial output. def initial_output(ci_job) output = { title: '', summary: '' } - url = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{ci_job.check_suite.bamboo_ci_ref}" + url = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{ci_job.check_suite.bamboo_ci_ref}" output[:title] = "#{ci_job.name} summary" output[:summary] = "Details at [#{url}](#{url})" diff --git a/lib/github/build/plan_run.rb b/lib/github/build/plan_run.rb index e99b121..c81641a 100644 --- a/lib/github/build/plan_run.rb +++ b/lib/github/build/plan_run.rb @@ -20,36 +20,53 @@ def initialize(pull_request, payload) end def build - @pull_request.plans.each do |plan| - @has_previous_exec = false + @status = [] - @logger.info "Starting Plan: #{plan.name}" + return [422, 'No Plans associated with this Pull Request'] if @pull_request.plans.empty? - fetch_last_check_suite(plan) + @pull_request.plans.each { |plan| create_execution_by_plan(plan) } - create_check_suite + @status + end - next [422, 'Failed to save Check Suite'] unless @check_suite.persisted? + private - @check_suite.update(plan: plan) + def create_execution_by_plan(plan) + @has_previous_exec = false - @logger.info "Check Suite created: #{@check_suite.inspect}" + @logger.info "Starting Plan: #{plan.name}" - # Stop a previous execution - Avoiding CI spam - stop_previous_execution + fetch_last_check_suite(plan) - @logger.info "Starting a new execution for Pull Request: #{@pull_request.inspect}" - # Starting a new CI run - status = start_new_execution(plan) + create_check_suite - @logger.info "New execution started with status: #{status}" - next [status, 'Failed to create CI Plan'] if status != 200 + unless @check_suite.persisted? + @status = [422, 'Failed to save Check Suite'] - ci_jobs(plan) + return end - end - private + @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' @@ -70,8 +87,6 @@ def ci_jobs(plan) end def start_new_execution(plan) - 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) @@ -122,7 +137,6 @@ def fetch_last_check_suite(plan) CheckSuite .joins(pull_request: :plans) .where(pull_request: { id: @pull_request.id, plans: { name: plan.name } }) - .where("check_suites.bamboo_ci_ref LIKE '#{plan.bamboo_ci_plan_name}%'") .last end diff --git a/lib/github/build/summary.rb b/lib/github/build/summary.rb index 3b6378f..f9fd2d4 100644 --- a/lib/github/build/summary.rb +++ b/lib/github/build/summary.rb @@ -18,6 +18,7 @@ module Github module Build class Summary def initialize(job, logger_level: Logger::INFO, agent: 'Github') + puts job.inspect @job = job.reload @check_suite = @job.check_suite @github = Github::Check.new(@check_suite) @@ -64,6 +65,8 @@ def bamboo_info def must_update_previous_stage(current_stage) previous_stage = current_stage.previous_stage + puts "previous_stage: #{previous_stage.inspect}" + return if previous_stage.nil? or !(previous_stage.in_progress? or previous_stage.queued?) logger(Logger::INFO, "must_update_previous_stage: #{previous_stage.inspect}") diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index 9f58e43..bc00258 100644 --- a/lib/github/build_plan.rb +++ b/lib/github/build_plan.rb @@ -43,8 +43,6 @@ def create fetch_pull_request Github::Build::PlanRun.new(@pull_request, @payload).build - - @logger.info "Pull Request: #{@pull_request.id} created a new execution." end private diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index 16bcbe7..8c3f348 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -124,6 +124,8 @@ def ci_jobs(check_suite, plan) check_suite.update(cancelled_previous_check_suite: @last_check_suite) + puts "Plan: #{plan.name}" + create_ci_jobs(check_suite, plan.name) update_unavailable_jobs(check_suite) diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index 7001e7b..42aa189 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -30,12 +30,16 @@ def start @github_check = Github::Check.new(check_suite) - stop_previous_execution(plan) + check_suite.pull_request.plans.each do |plan| + stop_previous_execution(plan) - check_suite = create_check_suite(check_suite) + check_suite = create_check_suite(check_suite) - bamboo_plans = start_new_execution(check_suite, plan) - ci_jobs(check_suite, bamboo_plans) + puts check_suite.re_run + + start_new_execution(check_suite, plan) + ci_jobs(check_suite, plan) + end [201, 'Starting re-run (command)'] end diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index 64cd717..289b6a5 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -30,22 +30,22 @@ def start fetch_pull_request - return [404, 'Pull Request not found'] if @pull_request.nil? - return [404, 'Can not rerun a new PullRequest'] if @pull_request.check_suites.empty? - confirm_and_start - - [201, 'Starting re-run (comment)'] end private 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? + github_reaction_feedback(comment_id) @pull_request.plans.each do |plan| run_by_plan(plan) end + + [201, 'Starting re-run (comment)'] end def run_by_plan(plan) diff --git a/lib/models/stage.rb b/lib/models/stage.rb index b0064d4..6d31889 100644 --- a/lib/models/stage.rb +++ b/lib/models/stage.rb @@ -26,7 +26,7 @@ class Stage < ActiveRecord::Base scope :next_stages, ->(current_position) { where(configuration: { position: [(current_position + 1)..] }) } def suffix - name.split(' - ', 2).last + name.split(' - ').last end def update_execution_time diff --git a/lib/slack_bot/slack_bot.rb b/lib/slack_bot/slack_bot.rb index 2c12812..1cfccfe 100644 --- a/lib/slack_bot/slack_bot.rb +++ b/lib/slack_bot/slack_bot.rb @@ -197,7 +197,7 @@ def pull_request_message(check_suite, status) pr = check_suite.pull_request pr_url = "https://github.com/#{pr.repository}/pull/#{pr.github_pr_id}" - bamboo_link = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{check_suite.bamboo_ci_ref}" + bamboo_link = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{check_suite.bamboo_ci_ref}" "PR <#{pr_url}|##{pr.github_pr_id}>. <#{bamboo_link}|#{status}> " end @@ -205,7 +205,7 @@ def pull_request_message(check_suite, status) def generate_notification_message(job, status) pr = job.check_suite.pull_request pr_url = "https://github.com/#{pr.repository}/pull/#{pr.github_pr_id}/checks?check_run_id=#{job.check_ref}" - bamboo_link = "https://#{GitHubApp::Configuration.instance.config['ci']['url']}/browse/#{job.job_ref}" + bamboo_link = "https://#{GitHubApp::Configuration.instance.ci_url}/browse/#{job.job_ref}" "PR <#{pr_url}|##{pr.github_pr_id}>. <#{bamboo_link}|#{job.name} - #{status}> " end 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..0e0e4f6 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}?" \ "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/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 71572fe..4b1b9dc 100644 --- a/spec/lib/github/build_plan_spec.rb +++ b/spec/lib/github/build_plan_spec.rb @@ -19,9 +19,13 @@ 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 @@ -86,64 +90,8 @@ 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') - 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) @@ -291,6 +214,8 @@ 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,7 +232,7 @@ 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 diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 7972c99..353e5b0 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -50,7 +50,8 @@ 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(:ci_jobs) do [ { name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }, @@ -88,7 +89,7 @@ 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 @@ -138,7 +139,7 @@ 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 @@ -207,7 +208,7 @@ 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 @@ -229,7 +230,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,7 +290,7 @@ 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 @@ -323,7 +325,7 @@ 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(bamboo_jobs) + allow(BambooCi::RunningPlan).to receive(:fetch).and_return(bamboo_jobs) end context 'when you receive an comment and does not exist a PR' do @@ -373,54 +375,5 @@ expect(check_suite_rerun).not_to be_nil end end - - context 'when can not save check_suite' do - let(:commit_sha) { Faker::Internet.uuid } - - let(:payload) do - { - 'action' => 'created', - 'comment' => { - 'body' => 'CI:rerun 000000' - }, - 'repository' => { 'full_name' => 'unit_test' }, - 'issue' => { 'number' => pull_request.github_pr_id } - } - end - - let(:pull_request_info) do - { - head: { - ref: 'master' - }, - base: { - ref: 'test', - sha: commit_sha - } - } - end - - let(:pull_request_commits) do - [ - { sha: commit_sha, date: Time.now } - ] - end - - let(:bamboo_jobs) do - [ - { name: 'test', job_ref: 'checkout-01', stage: fake_translation.bamboo_stage_name } - ] - end - - let(:fake_check_suite) { create(:check_suite, pull_request: pull_request) } - - before do - create(:plan, github_repo_name: 'unit_test') - end - - it 'must returns success' do - expect(rerun.start).to eq([404, 'Failed to create a check suite']) - end - 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 From 2ccf9e4505989f5229b04776e34210cc408b0e61 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Mon, 1 Sep 2025 18:37:52 -0300 Subject: [PATCH 07/16] [GBA-9] Allow multiple plans Refactor plan handling and clean up unused code --- lib/bamboo_ci/api.rb | 2 +- lib/github/build/action.rb | 1 - lib/github/build/skip_old_tests.rb | 1 - lib/github/build/summary.rb | 3 - lib/github/build_plan.rb | 30 +--- lib/github/re_run/base.rb | 15 +- lib/github/re_run/command.rb | 2 - lib/github/re_run/comment.rb | 34 +---- spec/lib/github/build/plan_run_spec.rb | 23 +++ spec/lib/github/re_run/comment_spec.rb | 187 +++++++++++++++++++++++++ 10 files changed, 227 insertions(+), 71 deletions(-) create mode 100644 spec/lib/github/build/plan_run_spec.rb diff --git a/lib/bamboo_ci/api.rb b/lib/bamboo_ci/api.rb index 60c9ddf..69174fa 100644 --- a/lib/bamboo_ci/api.rb +++ b/lib/bamboo_ci/api.rb @@ -40,7 +40,7 @@ def submit_pr_to_ci(check_suite, plan, ci_variables) logger(Logger::DEBUG, "Submission URL:\n #{url}") # Fetch Request - post_request(URI(url)) + post_request(URI(url.strip)) end def custom_variables(check_suite) diff --git a/lib/github/build/action.rb b/lib/github/build/action.rb index ae73968..28e8e80 100644 --- a/lib/github/build/action.rb +++ b/lib/github/build/action.rb @@ -56,7 +56,6 @@ def create_summary(rerun: false) Github::Build::SkipOldTests.new(@check_suite).skip_old_tests @stages_config.each do |stage_config| - puts stage_config.github_check_run_name create_check_run_stage(stage_config) 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/summary.rb b/lib/github/build/summary.rb index f9fd2d4..3b6378f 100644 --- a/lib/github/build/summary.rb +++ b/lib/github/build/summary.rb @@ -18,7 +18,6 @@ module Github module Build class Summary def initialize(job, logger_level: Logger::INFO, agent: 'Github') - puts job.inspect @job = job.reload @check_suite = @job.check_suite @github = Github::Check.new(@check_suite) @@ -65,8 +64,6 @@ def bamboo_info def must_update_previous_stage(current_stage) previous_stage = current_stage.previous_stage - puts "previous_stage: #{previous_stage.inspect}" - return if previous_stage.nil? or !(previous_stage.in_progress? or previous_stage.queued?) logger(Logger::INFO, "must_update_previous_stage: #{previous_stage.inspect}") diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index bc00258..d01338b 100644 --- a/lib/github/build_plan.rb +++ b/lib/github/build_plan.rb @@ -54,7 +54,7 @@ def fetch_pull_request @pull_request.update(branch_name: @payload.dig('pull_request', 'head', 'ref')) - add_plans + add_plans(@pull_request) end def github_pr @@ -67,34 +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_name + repository: @payload.dig('repository', 'full_name') ) - add_plans + add_plans(@pull_request) Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id'), pull_request: @pull_request) end - def fetch_plan_name - 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 add_plans - return if @pull_request.nil? - - @pull_request.plans = [] - - Plan.where(github_repo_name: @payload.dig('repository', 'full_name')).each do |plan| - @pull_request.plans << plan unless @pull_request.plans.include?(plan) - end - - @pull_request.save + 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/re_run/base.rb b/lib/github/re_run/base.rb index 8c3f348..d4d7608 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -36,7 +36,9 @@ def fetch_run_ci_by_pr(plan) CheckSuite .joins(pull_request: :plans) .joins(:ci_jobs) - .where(pull_request: { plan: plan, 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 @@ -80,15 +82,6 @@ def create_ci_jobs(check_suite, 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) @@ -124,8 +117,6 @@ def ci_jobs(check_suite, plan) check_suite.update(cancelled_previous_check_suite: @last_check_suite) - puts "Plan: #{plan.name}" - create_ci_jobs(check_suite, plan.name) update_unavailable_jobs(check_suite) diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index 42aa189..89b685a 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -35,8 +35,6 @@ def start check_suite = create_check_suite(check_suite) - puts check_suite.re_run - start_new_execution(check_suite, plan) ci_jobs(check_suite, plan) end diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index 289b6a5..a23d937 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -41,11 +41,13 @@ def confirm_and_start github_reaction_feedback(comment_id) + status = nil + @pull_request.plans.each do |plan| - run_by_plan(plan) + status = run_by_plan(plan) end - [201, 'Starting re-run (comment)'] + status end def run_by_plan(plan) @@ -61,6 +63,8 @@ def run_by_plan(plan) start_new_execution(check_suite, plan) ci_jobs(check_suite, plan) + + [201, 'Starting re-run (comment)'] end def fetch_pull_request @@ -114,32 +118,6 @@ def create_check_suite_by_commit(commit, pull_request, pull_request_info) ) end - 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 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..6f53a80 --- /dev/null +++ b/spec/lib/github/build/plan_run_spec.rb @@ -0,0 +1,23 @@ +# 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/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 353e5b0..4922c26 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -52,6 +52,7 @@ context 'when receives a valid command' do 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 }, @@ -69,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 }) @@ -298,6 +302,189 @@ expect(check_suite_rerun).not_to be_nil end end + + 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) } + + 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", + '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(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 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", + '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(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: '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 ##{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([404, 'Failed to create a check suite']) + end + end end describe 'alternative scenarios' do From 89d27640ddf66411c559de64fc22ef04bea8329c Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Mon, 1 Sep 2025 18:38:58 -0300 Subject: [PATCH 08/16] [GBA-9] Allow multiple plans Fix string quotes in comment_spec.rb and remove unnecessary comment in plan_run_spec.rb --- spec/lib/github/build/plan_run_spec.rb | 1 - spec/lib/github/re_run/comment_spec.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/lib/github/build/plan_run_spec.rb b/spec/lib/github/build/plan_run_spec.rb index 6f53a80..b6df448 100644 --- a/spec/lib/github/build/plan_run_spec.rb +++ b/spec/lib/github/build/plan_run_spec.rb @@ -7,7 +7,6 @@ # Network Device Education Foundation, Inc. ("NetDEF") # # frozen_string_literal: true -# describe Github::Build::PlanRun do let(:plan_run) { described_class.new(pull_request, payload) } diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 4922c26..a9fc71d 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -318,7 +318,7 @@ { 'action' => 'created', 'comment' => { - 'body' => "CI:rerun", + 'body' => 'CI:rerun', 'user' => { 'login' => 'John' } }, 'repository' => { 'full_name' => check_suite.pull_request.repository }, @@ -373,7 +373,7 @@ { 'action' => 'created', 'comment' => { - 'body' => "CI:rerun", + 'body' => 'CI:rerun', 'user' => { 'login' => 'John' } }, 'repository' => { 'full_name' => check_suite.pull_request.repository }, From 4ddfa19ad712998256a6d2ecd7999303c36d20be Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 2 Sep 2025 12:31:49 -0300 Subject: [PATCH 09/16] [GBA-9] Allow multiple plans Refactor plan execution handling and improve URL sanitization --- lib/bamboo_ci/api.rb | 4 +- lib/github/build/plan_run.rb | 151 +-------------- lib/github_ci_app.rb | 1 + spec/lib/bamboo_ci/plan_run_spec.rb | 2 +- spec/lib/github/build_plan_spec.rb | 6 +- spec/workers/create_execution_by_plan_spec.rb | 76 ++++++++ workers/create_execution_by_plan.rb | 177 ++++++++++++++++++ 7 files changed, 266 insertions(+), 151 deletions(-) create mode 100644 spec/workers/create_execution_by_plan_spec.rb create mode 100644 workers/create_execution_by_plan.rb diff --git a/lib/bamboo_ci/api.rb b/lib/bamboo_ci/api.rb index 69174fa..3cb7ec1 100644 --- a/lib/bamboo_ci/api.rb +++ b/lib/bamboo_ci/api.rb @@ -40,7 +40,7 @@ def submit_pr_to_ci(check_suite, plan, ci_variables) logger(Logger::DEBUG, "Submission URL:\n #{url}") # Fetch Request - post_request(URI(url.strip)) + 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/github/build/plan_run.rb b/lib/github/build/plan_run.rb index c81641a..1242158 100644 --- a/lib/github/build/plan_run.rb +++ b/lib/github/build/plan_run.rb @@ -11,161 +11,22 @@ module Github module Build class PlanRun + TIMER = 5 # seconds def initialize(pull_request, payload) - @logger = Logger.new($stdout) - @logger.level = Logger::INFO - @pull_request = pull_request @payload = payload end def build - @status = [] - return [422, 'No Plans associated with this Pull Request'] if @pull_request.plans.empty? - @pull_request.plans.each { |plan| create_execution_by_plan(plan) } - - @status - 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 + @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 - @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) + [200, 'Scheduled Plan Runs'] end end end diff --git a/lib/github_ci_app.rb b/lib/github_ci_app.rb index ea329e3..9cadc07 100644 --- a/lib/github_ci_app.rb +++ b/lib/github_ci_app.rb @@ -44,6 +44,7 @@ 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' # Slack libs require_relative 'slack/slack' diff --git a/spec/lib/bamboo_ci/plan_run_spec.rb b/spec/lib/bamboo_ci/plan_run_spec.rb index 0e0e4f6..afbdfb9 100644 --- a/spec/lib/bamboo_ci/plan_run_spec.rb +++ b/spec/lib/bamboo_ci/plan_run_spec.rb @@ -15,7 +15,7 @@ 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/#{plan.bamboo_ci_plan_name}?" \ + 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_plan_spec.rb b/spec/lib/github/build_plan_spec.rb index 4b1b9dc..de9b2b6 100644 --- a/spec/lib/github/build_plan_spec.rb +++ b/spec/lib/github/build_plan_spec.rb @@ -86,7 +86,7 @@ end it 'must create a PR' do - expect(build_plan.create).to eq([200, 'Pull Request created']) + expect(build_plan.create).to eq([200, 'Scheduled Plan Runs']) end end @@ -207,7 +207,7 @@ 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 @@ -236,7 +236,7 @@ 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/workers/create_execution_by_plan_spec.rb b/spec/workers/create_execution_by_plan_spec.rb new file mode 100644 index 0000000..d4b630f --- /dev/null +++ b/spec/workers/create_execution_by_plan_spec.rb @@ -0,0 +1,76 @@ +# 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 + 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 From 918e1cc8275a9599d286bfddf83107df5a85b6eb Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Wed, 3 Sep 2025 11:18:06 -0300 Subject: [PATCH 10/16] [GBA-9] Allow multiple plans Schedule plan runs with delay for command and comment executions --- lib/github/re_run/command.rb | 13 +- lib/github/re_run/comment.rb | 131 ++------------------ lib/github_ci_app.rb | 2 + spec/lib/github/re_run/comment_spec.rb | 12 +- workers/create_execution_by_command.rb | 27 +++++ workers/create_execution_by_comment.rb | 160 +++++++++++++++++++++++++ 6 files changed, 209 insertions(+), 136 deletions(-) create mode 100644 workers/create_execution_by_command.rb create mode 100644 workers/create_execution_by_comment.rb diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index 89b685a..54f3bfc 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 = 5 # seconds + def initialize(payload, logger_level: Logger::INFO) super(payload, logger_level: logger_level) @@ -31,15 +33,12 @@ def start @github_check = Github::Check.new(check_suite) check_suite.pull_request.plans.each do |plan| - stop_previous_execution(plan) - - check_suite = create_check_suite(check_suite) - - start_new_execution(check_suite, plan) - ci_jobs(check_suite, plan) + CreateExecutionByCommand + .delay(run_at: TIMER.seconds.from_now.utc, queue: 'create_execution_by_command') + .create(plan.id, check_suite.id) end - [201, 'Starting re-run (command)'] + [200, 'Scheduled Plan Runs'] end private diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index a23d937..9a88ae3 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -15,6 +15,8 @@ module Github module ReRun class Comment < Base + TIMER = 5 # seconds + def initialize(payload, logger_level: Logger::INFO) super(payload, logger_level: logger_level) @@ -26,8 +28,6 @@ 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}") - fetch_pull_request confirm_and_start @@ -41,102 +41,13 @@ def confirm_and_start github_reaction_feedback(comment_id) - status = nil - @pull_request.plans.each do |plan| - status = run_by_plan(plan) + CreateExecutionByComment + .delay(run_at: TIMER.seconds.from_now.utc, queue: 'create_execution_by_comment') + .create(@pull_request.id, @payload, plan) end - status - end - - 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 fetch_pull_request - @pull_request = PullRequest.find_by(github_pr_id: pr_id) - 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 + [200, 'Scheduled Plan Runs'] end def github_reaction_feedback(comment_id) @@ -147,34 +58,8 @@ def github_reaction_feedback(comment_id) github_check.comment_reaction_thumb_up(repo, comment_id) 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 + def fetch_pull_request + @pull_request = PullRequest.find_by(github_pr_id: pr_id) end def action? diff --git a/lib/github_ci_app.rb b/lib/github_ci_app.rb index 9cadc07..e328326 100644 --- a/lib/github_ci_app.rb +++ b/lib/github_ci_app.rb @@ -45,6 +45,8 @@ 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/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index a9fc71d..9ef40e1 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -97,7 +97,7 @@ 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 @@ -147,7 +147,7 @@ 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 @@ -218,7 +218,7 @@ 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 @@ -298,7 +298,7 @@ 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 @@ -482,7 +482,7 @@ end 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"]) end end end @@ -558,7 +558,7 @@ 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([201, 'Starting re-run (comment)']) + expect(rerun.start).to eq([200, "Scheduled Plan Runs"]) expect(check_suite_rerun).not_to be_nil end end diff --git a/workers/create_execution_by_command.rb b/workers/create_execution_by_command.rb new file mode 100644 index 0000000..a0389d6 --- /dev/null +++ b/workers/create_execution_by_command.rb @@ -0,0 +1,27 @@ +# 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) + check_suite = CheckSuite.find(check_suite_id) + plan = Plan.find(plan_id) + + return [404, 'Failed to fetch a check suite'] if check_suite.nil? + + @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 +end diff --git a/workers/create_execution_by_comment.rb b/workers/create_execution_by_comment.rb new file mode 100644 index 0000000..f2286d9 --- /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 From 784b38f8d5f9ccca6665673d40d6f98fc6a7dd93 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 9 Sep 2025 11:36:51 -0300 Subject: [PATCH 11/16] [GBA-9] Allow multiple plans Reduce TIMER value to 1 second in Command and Comment classes; refactor stage processing in finished.rb --- lib/github/plan_execution/finished.rb | 14 +++++++++----- lib/github/re_run/command.rb | 2 +- lib/github/re_run/comment.rb | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) 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/command.rb b/lib/github/re_run/command.rb index 54f3bfc..e3d4909 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -13,7 +13,7 @@ module Github module ReRun class Command < Base - TIMER = 5 # seconds + TIMER = 1 # seconds def initialize(payload, logger_level: Logger::INFO) super(payload, logger_level: logger_level) diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index 9a88ae3..670cd64 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -15,7 +15,7 @@ module Github module ReRun class Comment < Base - TIMER = 5 # seconds + TIMER = 1 # seconds def initialize(payload, logger_level: Logger::INFO) super(payload, logger_level: logger_level) From 47b7448e9d9f1bb5ce50708ad00b5998c9ba3c6c Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Wed, 10 Sep 2025 20:45:10 -0300 Subject: [PATCH 12/16] [GBA-9] Allow multiple plans Refactor command execution to schedule plans and update test expectations --- lib/github/re_run/command.rb | 12 ++++++++---- spec/lib/github/re_run/comment_spec.rb | 12 ++++++------ workers/create_execution_by_comment.rb | 6 +++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index e3d4909..5285eca 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -32,17 +32,21 @@ def start @github_check = Github::Check.new(check_suite) + suite_by_plan(check_suite) + + [200, 'Scheduled Plan Runs'] + end + + private + + 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) end - - [200, 'Scheduled Plan Runs'] end - private - def create_check_suite(check_suite) CheckSuite.create( pull_request: check_suite.pull_request, diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 9ef40e1..1ebb0f3 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -97,7 +97,7 @@ end it 'must returns success' do - expect(rerun.start).to eq([200, "Scheduled Plan Runs"]) + expect(rerun.start).to eq([200, 'Scheduled Plan Runs']) expect(check_suites.size).to eq(2) end end @@ -147,7 +147,7 @@ end it 'must returns success' do - expect(rerun.start).to eq([200, "Scheduled Plan Runs"]) + expect(rerun.start).to eq([200, 'Scheduled Plan Runs']) expect(check_suites.size).to eq(2) end end @@ -218,7 +218,7 @@ end it 'must returns success' do - expect(rerun.start).to eq([200, "Scheduled Plan Runs"]) + expect(rerun.start).to eq([200, 'Scheduled Plan Runs']) expect(check_suite_rerun).not_to be_nil end @@ -298,7 +298,7 @@ end it 'must returns success' do - expect(rerun.start).to eq([200, "Scheduled Plan Runs"]) + expect(rerun.start).to eq([200, 'Scheduled Plan Runs']) expect(check_suite_rerun).not_to be_nil end end @@ -482,7 +482,7 @@ end it 'must returns success' do - expect(rerun.start).to eq([200, "Scheduled Plan Runs"]) + expect(rerun.start).to eq([200, 'Scheduled Plan Runs']) end end end @@ -558,7 +558,7 @@ 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([200, "Scheduled Plan Runs"]) + expect(rerun.start).to eq([200, 'Scheduled Plan Runs']) expect(check_suite_rerun).not_to be_nil end end diff --git a/workers/create_execution_by_comment.rb b/workers/create_execution_by_comment.rb index f2286d9..9caaca3 100644 --- a/workers/create_execution_by_comment.rb +++ b/workers/create_execution_by_comment.rb @@ -16,7 +16,7 @@ def self.create(pull_request_id, payload, 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}" @@ -24,7 +24,7 @@ def self.create(pull_request_id, payload, plan_id) end attr_reader :status - + def initialize(pull_request_id, payload, plan) super(payload, logger_level: Logger::INFO) @@ -36,7 +36,7 @@ def initialize(pull_request_id, payload, plan) run_by_plan(plan) end - + private def run_by_plan(plan) From 6f5a728eb5259935de8d70ba35c649a871944891 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 23 Sep 2025 11:29:59 -0300 Subject: [PATCH 13/16] [GBA-9] Allow multiple plans Enhance execution creation to include payload and improve logging --- lib/github/re_run/command.rb | 14 +------- spec/lib/github/re_run/command_spec.rb | 2 +- workers/create_execution_by_command.rb | 44 +++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index 5285eca..6cdc2ab 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -43,22 +43,10 @@ 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) + .create(plan.id, check_suite.id, @payload) end 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 fetch_check_suite CheckSuite .joins(:pull_request) diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index 9440601..a05476a 100644 --- a/spec/lib/github/re_run/command_spec.rb +++ b/spec/lib/github/re_run/command_spec.rb @@ -84,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/workers/create_execution_by_command.rb b/workers/create_execution_by_command.rb index a0389d6..b3addaa 100644 --- a/workers/create_execution_by_command.rb +++ b/workers/create_execution_by_command.rb @@ -9,12 +9,25 @@ # frozen_string_literal: true class CreateExecutionByCommand < Github::ReRun::Base - def self.create(plan_id, check_suite_id) + 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) @@ -24,4 +37,33 @@ def self.create(plan_id, check_suite_id) 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 From ec5322cdb5864fde19164cf6982ec5fa10c588a8 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 28 Oct 2025 11:06:41 -0300 Subject: [PATCH 14/16] [GBA-9] Allow multiple plans Updating unit tests --- .../create_execution_by_command_spec.rb | 50 +++++++++ .../create_execution_by_comment_spec.rb | 100 ++++++++++++++++++ spec/workers/create_execution_by_plan_spec.rb | 7 ++ 3 files changed, 157 insertions(+) create mode 100644 spec/workers/create_execution_by_command_spec.rb create mode 100644 spec/workers/create_execution_by_comment_spec.rb 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..96351b0 --- /dev/null +++ b/spec/workers/create_execution_by_command_spec.rb @@ -0,0 +1,50 @@ +# 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 + + it 'instantiates and returns status' do + allow(CheckSuite).to receive(:find).with(check_suite.id).and_return(check_suite) + expect(described_class.create(plan.id, check_suite.id, payload)).to be_nil + 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..4de3198 --- /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 index d4b630f..144e40f 100644 --- a/spec/workers/create_execution_by_plan_spec.rb +++ b/spec/workers/create_execution_by_plan_spec.rb @@ -72,5 +72,12 @@ 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 From 291f52b07995c439c5dfad358124ef82c08fcce6 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 28 Oct 2025 11:44:01 -0300 Subject: [PATCH 15/16] [GBA-9] Allow multiple plans Updating unit tests --- spec/factories/check_suite.rb | 6 ++++ .../github/plan_execution/finished_spec.rb | 35 +++++++++++++++++++ .../create_execution_by_command_spec.rb | 5 --- 3 files changed, 41 insertions(+), 5 deletions(-) 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/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/workers/create_execution_by_command_spec.rb b/spec/workers/create_execution_by_command_spec.rb index 96351b0..5b84dcd 100644 --- a/spec/workers/create_execution_by_command_spec.rb +++ b/spec/workers/create_execution_by_command_spec.rb @@ -41,10 +41,5 @@ 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 - - it 'instantiates and returns status' do - allow(CheckSuite).to receive(:find).with(check_suite.id).and_return(check_suite) - expect(described_class.create(plan.id, check_suite.id, payload)).to be_nil - end end end From 6eeb44847b946effeb660dda0ba6af31147b1f4a Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 28 Oct 2025 11:44:27 -0300 Subject: [PATCH 16/16] [GBA-9] Allow multiple plans Updating unit tests --- spec/workers/create_execution_by_command_spec.rb | 2 -- spec/workers/create_execution_by_comment_spec.rb | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/workers/create_execution_by_command_spec.rb b/spec/workers/create_execution_by_command_spec.rb index 5b84dcd..e28a0fb 100644 --- a/spec/workers/create_execution_by_command_spec.rb +++ b/spec/workers/create_execution_by_command_spec.rb @@ -7,7 +7,6 @@ # Network Device Education Foundation, Inc. ("NetDEF") # # frozen_string_literal: true -# describe CreateExecutionByCommand do let(:plan) { create(:plan) } @@ -19,7 +18,6 @@ } 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)) diff --git a/spec/workers/create_execution_by_comment_spec.rb b/spec/workers/create_execution_by_comment_spec.rb index 4de3198..805a418 100644 --- a/spec/workers/create_execution_by_comment_spec.rb +++ b/spec/workers/create_execution_by_comment_spec.rb @@ -7,7 +7,6 @@ # Network Device Education Foundation, Inc. ("NetDEF") # # frozen_string_literal: true -# describe CreateExecutionByComment do let(:pull_request) { create(:pull_request) } @@ -54,7 +53,8 @@ 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)']) + allow_any_instance_of(CreateExecutionByComment).to receive(:run_by_plan).and_return([201, + 'Starting re-run (comment)']) end describe '.create' do