Module: Kettle::Dev::CIMonitor
- Defined in:
- lib/kettle/dev/ci_monitor.rb
Overview
CIMonitor centralizes CI monitoring logic (GitHub Actions and GitLab pipelines)
so it can be reused by both kettle-release and Rake tasks (e.g., ci:act).
Public API is intentionally small and based on environment/project introspection
via CIHelpers, matching the behavior historically implemented in ReleaseCLI.
Class Method Summary collapse
-
.abort(msg) ⇒ Object
Abort helper (delegates through ExitAdapter so specs can trap exits).
-
.github_remote_candidates ⇒ Object
-
.gitlab_remote_candidates ⇒ Object
-
.monitor_all!(restart_hint: "bundle exec kettle-release start_step=10") ⇒ void
Monitor both GitHub and GitLab CI for the current project/branch.
-
.monitor_github_internal!(restart_hint:) ⇒ Object
– internals –.
-
.monitor_gitlab!(restart_hint: "bundle exec rake ci:act") ⇒ Boolean
Monitor only the GitLab pipeline for current project/branch.
-
.monitor_gitlab_internal!(restart_hint:) ⇒ Object
-
.parse_github_owner_repo(url) ⇒ Object
-
.preferred_github_remote ⇒ Object
-
.remote_url(name) ⇒ Object
-
.remotes_with_urls ⇒ Object
– tiny wrappers around GitAdapter-like helpers used by ReleaseCLI –.
Class Method Details
.abort(msg) ⇒ Object
Abort helper (delegates through ExitAdapter so specs can trap exits)
24 25 26 |
# File 'lib/kettle/dev/ci_monitor.rb', line 24 def abort(msg) Kettle::Dev::ExitAdapter.abort(msg) end |
.github_remote_candidates ⇒ Object
169 170 171 |
# File 'lib/kettle/dev/ci_monitor.rb', line 169 def github_remote_candidates remotes_with_urls.select { |n, u| u.include?("github.com") }.keys end |
.gitlab_remote_candidates ⇒ Object
174 175 176 |
# File 'lib/kettle/dev/ci_monitor.rb', line 174 def gitlab_remote_candidates remotes_with_urls.select { |n, u| u.include?("gitlab.com") }.keys end |
.monitor_all!(restart_hint: "bundle exec kettle-release start_step=10") ⇒ void
This method returns an undefined value.
Monitor both GitHub and GitLab CI for the current project/branch.
This mirrors ReleaseCLI behavior.
34 35 36 37 38 39 |
# File 'lib/kettle/dev/ci_monitor.rb', line 34 def monitor_all!(restart_hint: "bundle exec kettle-release start_step=10") checks_any = false checks_any |= monitor_github_internal!(restart_hint: restart_hint) checks_any |= monitor_gitlab_internal!(restart_hint: restart_hint) abort("CI configuration not detected (GitHub or GitLab). Ensure CI is configured and remotes point to the correct hosts.") unless checks_any end |
.monitor_github_internal!(restart_hint:) ⇒ Object
– internals –
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/kettle/dev/ci_monitor.rb', line 52 def monitor_github_internal!(restart_hint:) root = Kettle::Dev::CIHelpers.project_root workflows = Kettle::Dev::CIHelpers.workflows_list(root) gh_remote = preferred_github_remote return false unless gh_remote && !workflows.empty? branch = Kettle::Dev::CIHelpers.current_branch abort("Could not determine current branch for CI checks.") unless branch url = remote_url(gh_remote) owner, repo = parse_github_owner_repo(url) return false unless owner && repo total = workflows.size abort("No GitHub workflows found under .github/workflows; aborting.") if total.zero? passed = {} puts "Ensuring GitHub Actions workflows pass on #{branch} (#{owner}/#{repo}) via remote '#{gh_remote}'" = if defined?(ProgressBar) ProgressBar.create(title: "CI", total: total, format: "%t %b %c/%C", length: 30) end # Small initial delay to allow GitHub to register the newly pushed commit and enqueue workflows. # Configurable via K_RELEASE_CI_INITIAL_SLEEP (seconds); defaults to 3s. begin initial_sleep = begin Integer(ENV["K_RELEASE_CI_INITIAL_SLEEP"]) rescue nil end end sleep((initial_sleep && initial_sleep >= 0) ? initial_sleep : 3) idx = 0 loop do wf = workflows[idx] run = Kettle::Dev::CIHelpers.latest_run(owner: owner, repo: repo, workflow_file: wf, branch: branch) if run if Kettle::Dev::CIHelpers.success?(run) unless passed[wf] passed[wf] = true &.increment end elsif Kettle::Dev::CIHelpers.failed?(run) puts wf_url = run["html_url"] || "https://github.com/#{owner}/#{repo}/actions/workflows/#{wf}" abort("Workflow failed: #{wf} -> #{wf_url} Fix the workflow, then restart this tool from CI validation with: #{restart_hint}") end end break if passed.size == total idx = (idx + 1) % total sleep(1) end &.finish unless &.finished? puts "\nAll GitHub workflows passing (#{passed.size}/#{total})." true end |
.monitor_gitlab!(restart_hint: "bundle exec rake ci:act") ⇒ Boolean
Monitor only the GitLab pipeline for current project/branch.
Used by ci:act after running ‘act’.
46 47 48 |
# File 'lib/kettle/dev/ci_monitor.rb', line 46 def monitor_gitlab!(restart_hint: "bundle exec rake ci:act") monitor_gitlab_internal!(restart_hint: restart_hint) end |
.monitor_gitlab_internal!(restart_hint:) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/kettle/dev/ci_monitor.rb', line 109 def monitor_gitlab_internal!(restart_hint:) root = Kettle::Dev::CIHelpers.project_root gitlab_ci = File.exist?(File.join(root, ".gitlab-ci.yml")) gl_remote = gitlab_remote_candidates.first return false unless gitlab_ci && gl_remote branch = Kettle::Dev::CIHelpers.current_branch abort("Could not determine current branch for CI checks.") unless branch owner, repo = Kettle::Dev::CIHelpers.repo_info_gitlab return false unless owner && repo puts "Ensuring GitLab pipeline passes on #{branch} (#{owner}/#{repo}) via remote '#{gl_remote}'" = if defined?(ProgressBar) ProgressBar.create(title: "CI", total: 1, format: "%t %b %c/%C", length: 30) end loop do pipe = Kettle::Dev::CIHelpers.gitlab_latest_pipeline(owner: owner, repo: repo, branch: branch) if pipe if Kettle::Dev::CIHelpers.gitlab_success?(pipe) &.increment unless &.finished? break elsif Kettle::Dev::CIHelpers.gitlab_failed?(pipe) # Special-case: if failure is due to exhausted minutes/insufficient quota, treat as unknown and continue reason = (pipe["failure_reason"] || "").to_s if reason =~ /insufficient|quota|minute/i puts "\nGitLab reports pipeline cannot run due to quota/minutes exhaustion. Result is unknown; continuing." &.finish unless &.finished? break else puts url = pipe["web_url"] || "https://gitlab.com/#{owner}/#{repo}/-/pipelines" abort("Pipeline failed: #{url} Fix the pipeline, then restart this tool from CI validation with: #{restart_hint}") end elsif pipe["status"] == "blocked" # Blocked pipeline (e.g., awaiting approvals) — treat as unknown and continue puts "\nGitLab pipeline is blocked. Result is unknown; continuing." &.finish unless &.finished? break end end sleep(1) end &.finish unless &.finished? puts "\nGitLab pipeline passing." true end |
.parse_github_owner_repo(url) ⇒ Object
189 190 191 192 193 194 195 196 197 198 |
# File 'lib/kettle/dev/ci_monitor.rb', line 189 def parse_github_owner_repo(url) return [nil, nil] unless url if url =~ %r{git@github.com:(.+?)/(.+?)(\.git)?$} [Regexp.last_match(1), Regexp.last_match(2).sub(/\.git\z/, "")] elsif url =~ %r{https://github.com/(.+?)/(.+?)(\.git)?$} [Regexp.last_match(1), Regexp.last_match(2).sub(/\.git\z/, "")] else [nil, nil] end end |
.preferred_github_remote ⇒ Object
179 180 181 182 183 184 185 186 |
# File 'lib/kettle/dev/ci_monitor.rb', line 179 def preferred_github_remote cands = github_remote_candidates return if cands.empty? explicit = cands.find { |n| n == "github" } || cands.find { |n| n == "gh" } return explicit if explicit return "origin" if cands.include?("origin") cands.first end |
.remote_url(name) ⇒ Object
164 165 166 |
# File 'lib/kettle/dev/ci_monitor.rb', line 164 def remote_url(name) Kettle::Dev::GitAdapter.new.remote_url(name) end |
.remotes_with_urls ⇒ Object
– tiny wrappers around GitAdapter-like helpers used by ReleaseCLI –
159 160 161 |
# File 'lib/kettle/dev/ci_monitor.rb', line 159 def remotes_with_urls Kettle::Dev::GitAdapter.new.remotes_with_urls end |