kettle-dev 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.env.local.example +14 -1
- data/.envrc +2 -2
- data/.git-hooks/commit-msg +22 -16
- data/.git-hooks/prepare-commit-msg.example +19 -0
- data/.github/workflows/coverage.yml +2 -2
- data/.junie/guidelines.md +4 -0
- data/.opencollective.yml.example +3 -0
- data/CHANGELOG.md +31 -1
- data/CONTRIBUTING.md +29 -0
- data/FUNDING.md +2 -2
- data/README.md +148 -38
- data/README.md.example +7 -7
- data/Rakefile.example +1 -1
- data/exe/kettle-changelog +7 -2
- data/exe/kettle-commit-msg +20 -5
- data/exe/kettle-dev-setup +2 -1
- data/exe/kettle-dvcs +7 -2
- data/exe/kettle-pre-release +66 -0
- data/exe/kettle-readme-backers +7 -2
- data/exe/kettle-release +9 -2
- data/lib/kettle/dev/changelog_cli.rb +4 -5
- data/lib/kettle/dev/ci_helpers.rb +9 -5
- data/lib/kettle/dev/ci_monitor.rb +229 -8
- data/lib/kettle/dev/gem_spec_reader.rb +105 -39
- data/lib/kettle/dev/git_adapter.rb +6 -3
- data/lib/kettle/dev/git_commit_footer.rb +5 -2
- data/lib/kettle/dev/pre_release_cli.rb +248 -0
- data/lib/kettle/dev/readme_backers.rb +4 -2
- data/lib/kettle/dev/release_cli.rb +27 -17
- data/lib/kettle/dev/tasks/ci_task.rb +112 -22
- data/lib/kettle/dev/tasks/install_task.rb +23 -17
- data/lib/kettle/dev/tasks/template_task.rb +64 -23
- data/lib/kettle/dev/template_helpers.rb +44 -31
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev.rb +5 -0
- data/sig/kettle/dev/ci_monitor.rbs +6 -0
- data/sig/kettle/dev/gem_spec_reader.rbs +8 -5
- data/sig/kettle/dev/pre_release_cli.rbs +20 -0
- data/sig/kettle/dev/template_helpers.rbs +2 -0
- data/sig/kettle/dev.rbs +1 -0
- data.tar.gz.sig +0 -0
- metadata +30 -4
- metadata.gz.sig +0 -0
data/exe/kettle-commit-msg
CHANGED
@@ -5,8 +5,15 @@
|
|
5
5
|
|
6
6
|
# Immediate, unbuffered output
|
7
7
|
$stdout.sync = true
|
8
|
-
|
9
|
-
|
8
|
+
$stderr.sync = true
|
9
|
+
|
10
|
+
# Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
|
11
|
+
# Ensure RubyGems is available for 'require' lookups
|
12
|
+
begin
|
13
|
+
require "rubygems"
|
14
|
+
rescue LoadError
|
15
|
+
# Older Rubies always have rubygems; continue anyway
|
16
|
+
end
|
10
17
|
|
11
18
|
script_basename = File.basename(__FILE__)
|
12
19
|
|
@@ -14,11 +21,18 @@ begin
|
|
14
21
|
# Standard library
|
15
22
|
require "erb"
|
16
23
|
|
17
|
-
|
24
|
+
begin
|
25
|
+
require "kettle/dev"
|
26
|
+
rescue LoadError
|
27
|
+
# Fallback: allow running from a checkout without bundler by adding ./lib to the load path
|
28
|
+
repo_lib = File.expand_path("../lib", __dir__)
|
29
|
+
$LOAD_PATH.unshift(repo_lib) unless $LOAD_PATH.include?(repo_lib)
|
30
|
+
require "kettle/dev"
|
31
|
+
end
|
18
32
|
puts "== #{script_basename} v#{Kettle::Dev::Version::VERSION} =="
|
19
33
|
rescue LoadError => e
|
20
34
|
warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
|
21
|
-
warn("Hint:
|
35
|
+
warn("Hint: Install the kettle-dev gem (`gem install kettle-dev`) or add it to your Gemfile. Bundler is not required for this script.")
|
22
36
|
exit(1)
|
23
37
|
end
|
24
38
|
|
@@ -28,7 +42,8 @@ end
|
|
28
42
|
BRANCH_RULES = {
|
29
43
|
"jira" => /^(?<story_type>(hotfix)|(bug)|(feature)|(candy))\/(?<story_id>\d{8,})-.+\Z/,
|
30
44
|
}
|
31
|
-
|
45
|
+
# Ruby 2.3 compatibility: String#casecmp? is not available; use casecmp.zero?
|
46
|
+
BRANCH_RULE_TYPE = (validate = ENV.fetch("GIT_HOOK_BRANCH_VALIDATE", "false")) && !(validate.respond_to?(:casecmp?) ? validate.casecmp?("false") : (validate.casecmp("false").zero?)) && validate
|
32
47
|
|
33
48
|
# Called the "JIRA pattern", because of traditional use with JIRA.
|
34
49
|
if (branch_rule = BRANCH_RULES[BRANCH_RULE_TYPE])
|
data/exe/kettle-dev-setup
CHANGED
@@ -71,6 +71,7 @@ module Kettle
|
|
71
71
|
opts.on("--allowed=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "allowed=#{v}" }
|
72
72
|
opts.on("--force", "Pass through to kettle:dev:install") { @passthrough << "force=true" }
|
73
73
|
opts.on("--hook_templates=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "hook_templates=#{v}" }
|
74
|
+
opts.on("--only=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "only=#{v}" }
|
74
75
|
opts.on("-h", "--help", "Show help") do
|
75
76
|
puts opts
|
76
77
|
exit(0)
|
@@ -165,7 +166,7 @@ module Kettle
|
|
165
166
|
modified = target.dup
|
166
167
|
wanted.each do |gem_name, desired_line|
|
167
168
|
# Replace existing add_development_dependency for this gem, else append near the end
|
168
|
-
if
|
169
|
+
if /add_development_dependency\s*\(\s*["']#{Regexp.escape(gem_name)}["']/ =~ modified
|
169
170
|
modified = modified.gsub(/^[^\n]*add_development_dependency\s*\(\s*["']#{Regexp.escape(gem_name)}["'][^\n]*\)$/) do |_|
|
170
171
|
desired_line
|
171
172
|
end
|
data/exe/kettle-dvcs
CHANGED
@@ -11,8 +11,13 @@
|
|
11
11
|
$stdout.sync = true
|
12
12
|
$stderr.sync = true
|
13
13
|
|
14
|
-
#
|
15
|
-
require
|
14
|
+
# Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
|
15
|
+
# Ensure RubyGems is available for 'require' lookups
|
16
|
+
begin
|
17
|
+
require "rubygems"
|
18
|
+
rescue LoadError
|
19
|
+
# Older Rubies always have rubygems; continue anyway
|
20
|
+
end
|
16
21
|
|
17
22
|
script_basename = File.basename(__FILE__)
|
18
23
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# vim: set syntax=ruby
|
5
|
+
|
6
|
+
# kettle-pre-release: Run pre-release checks to catch avoidable mistakes.
|
7
|
+
# - Structured as a sequence of checks that can be resumed via check_num
|
8
|
+
# - Initial check: validate all image URLs referenced by Markdown files resolve (HTTP HEAD)
|
9
|
+
|
10
|
+
require "optparse"
|
11
|
+
|
12
|
+
# Immediate, unbuffered output
|
13
|
+
$stdout.sync = true
|
14
|
+
$stderr.sync = true
|
15
|
+
|
16
|
+
# Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
|
17
|
+
# Ensure RubyGems is available for 'require' lookups
|
18
|
+
begin
|
19
|
+
require "rubygems"
|
20
|
+
rescue LoadError
|
21
|
+
# Older Rubies always have rubygems; continue anyway
|
22
|
+
end
|
23
|
+
|
24
|
+
script_basename = File.basename(__FILE__)
|
25
|
+
|
26
|
+
begin
|
27
|
+
require "kettle/dev"
|
28
|
+
puts "== #{script_basename} v#{Kettle::Dev::Version::VERSION} =="
|
29
|
+
rescue StandardError => e
|
30
|
+
warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
|
31
|
+
warn("Hint: Ensure the host project has kettle-dev as a dependency and run bundle install.")
|
32
|
+
exit(1)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Option parsing
|
36
|
+
options = {
|
37
|
+
check_num: 1,
|
38
|
+
}
|
39
|
+
|
40
|
+
parser = OptionParser.new do |opts|
|
41
|
+
opts.banner = "Usage: kettle-pre-release [options]"
|
42
|
+
opts.on("--check-num N", Integer, "Start from check number N (1-based)") { |v| options[:check_num] = v }
|
43
|
+
opts.on("-cN", Integer, "Alias for --check-num N") { |v| options[:check_num] = v }
|
44
|
+
opts.on("-h", "--help", "Show this help") do
|
45
|
+
puts opts
|
46
|
+
puts
|
47
|
+
puts "Checks:"
|
48
|
+
puts " 1) Validate Markdown image links (HTTP HEAD)"
|
49
|
+
exit(0)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
parser.parse!(ARGV)
|
55
|
+
rescue OptionParser::ParseError => e
|
56
|
+
warn("#{script_basename}: #{e.message}")
|
57
|
+
puts parser
|
58
|
+
exit(2)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Execute
|
62
|
+
# Build and run CLI in the same style as kettle-release
|
63
|
+
cli = Kettle::Dev::PreReleaseCLI.new(check_num: options[:check_num])
|
64
|
+
cli.run
|
65
|
+
|
66
|
+
puts "== #{script_basename} complete =="
|
data/exe/kettle-readme-backers
CHANGED
@@ -5,8 +5,13 @@
|
|
5
5
|
|
6
6
|
# Immediate, unbuffered output
|
7
7
|
$stdout.sync = true
|
8
|
-
#
|
9
|
-
require
|
8
|
+
# Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
|
9
|
+
# Ensure RubyGems is available for 'require' lookups
|
10
|
+
begin
|
11
|
+
require "rubygems"
|
12
|
+
rescue LoadError
|
13
|
+
# Older Rubies always have rubygems; continue anyway
|
14
|
+
end
|
10
15
|
|
11
16
|
script_basename = File.basename(__FILE__)
|
12
17
|
|
data/exe/kettle-release
CHANGED
@@ -16,8 +16,15 @@
|
|
16
16
|
|
17
17
|
# Immediate, unbuffered output
|
18
18
|
$stdout.sync = true
|
19
|
-
|
20
|
-
|
19
|
+
$stderr.sync = true
|
20
|
+
|
21
|
+
# Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
|
22
|
+
# Ensure RubyGems is available for 'require' lookups
|
23
|
+
begin
|
24
|
+
require "rubygems"
|
25
|
+
rescue LoadError
|
26
|
+
# Older Rubies always have rubygems; continue anyway
|
27
|
+
end
|
21
28
|
|
22
29
|
script_basename = File.basename(__FILE__)
|
23
30
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Kettle
|
4
4
|
module Dev
|
5
5
|
class ChangelogCLI
|
6
|
+
UNRELEASED_SECTION_HEADING = "[Unreleased]:"
|
6
7
|
def initialize
|
7
8
|
@root = Kettle::Dev::CIHelpers.project_root
|
8
9
|
@changelog_path = File.join(@root, "CHANGELOG.md")
|
@@ -70,7 +71,7 @@ module Kettle
|
|
70
71
|
if after && !after.empty?
|
71
72
|
# Split 'after' by lines so we can locate the first link-ref to Unreleased
|
72
73
|
after_lines = after.lines
|
73
|
-
unreleased_ref_idx = after_lines.index { |l| l.start_with?(
|
74
|
+
unreleased_ref_idx = after_lines.index { |l| l.start_with?(UNRELEASED_SECTION_HEADING) }
|
74
75
|
if unreleased_ref_idx
|
75
76
|
# Keep all content prior to the link-ref (older releases and interspersed refs)
|
76
77
|
preserved_body = after_lines[0...unreleased_ref_idx].join
|
@@ -96,8 +97,6 @@ module Kettle
|
|
96
97
|
|
97
98
|
def abort(msg)
|
98
99
|
Kettle::Dev::ExitAdapter.abort(msg)
|
99
|
-
rescue NameError
|
100
|
-
Kernel.abort(msg)
|
101
100
|
end
|
102
101
|
|
103
102
|
def detect_version
|
@@ -260,7 +259,7 @@ module Kettle
|
|
260
259
|
# Identify the true start of the footer reference block: the line with the [Unreleased] link-ref.
|
261
260
|
# Do NOT assume the first link-ref after the Unreleased heading starts the footer, because
|
262
261
|
# some changelogs contain interspersed link-refs within section bodies.
|
263
|
-
unreleased_ref_idx = lines.index { |l| l.start_with?(
|
262
|
+
unreleased_ref_idx = lines.index { |l| l.start_with?(UNRELEASED_SECTION_HEADING) }
|
264
263
|
first_ref = if unreleased_ref_idx
|
265
264
|
unreleased_ref_idx
|
266
265
|
else
|
@@ -274,7 +273,7 @@ module Kettle
|
|
274
273
|
# Update an existing Unreleased ref only if it appears after Unreleased heading; otherwise append
|
275
274
|
idx = nil
|
276
275
|
lines.each_with_index do |l, i|
|
277
|
-
if l.start_with?(
|
276
|
+
if l.start_with?(UNRELEASED_SECTION_HEADING) && i >= first_ref
|
278
277
|
idx = i
|
279
278
|
break
|
280
279
|
end
|
@@ -115,7 +115,8 @@ module Kettle
|
|
115
115
|
"html_url" => run["html_url"],
|
116
116
|
"id" => run["id"],
|
117
117
|
}
|
118
|
-
rescue StandardError
|
118
|
+
rescue StandardError => e
|
119
|
+
Kettle::Dev.debug_error(e, __method__)
|
119
120
|
nil
|
120
121
|
end
|
121
122
|
|
@@ -185,8 +186,9 @@ module Kettle
|
|
185
186
|
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
|
186
187
|
return unless res.is_a?(Net::HTTPSuccess)
|
187
188
|
data = JSON.parse(res.body)
|
188
|
-
|
189
|
-
|
189
|
+
return unless data.is_a?(Array)
|
190
|
+
pipe = data.first
|
191
|
+
return unless pipe.is_a?(Hash)
|
190
192
|
# Attempt to enrich with failure_reason by querying the single pipeline endpoint
|
191
193
|
begin
|
192
194
|
if pipe["id"]
|
@@ -202,7 +204,8 @@ module Kettle
|
|
202
204
|
pipe["web_url"] = det["web_url"] if det["web_url"]
|
203
205
|
end
|
204
206
|
end
|
205
|
-
rescue StandardError
|
207
|
+
rescue StandardError => e
|
208
|
+
Kettle::Dev.debug_error(e, __method__)
|
206
209
|
# ignore enrichment errors; fall back to basic fields
|
207
210
|
end
|
208
211
|
{
|
@@ -211,7 +214,8 @@ module Kettle
|
|
211
214
|
"id" => pipe["id"],
|
212
215
|
"failure_reason" => pipe["failure_reason"],
|
213
216
|
}
|
214
|
-
rescue StandardError
|
217
|
+
rescue StandardError => e
|
218
|
+
Kettle::Dev.debug_error(e, __method__)
|
215
219
|
nil
|
216
220
|
end
|
217
221
|
|
@@ -21,8 +21,28 @@ module Kettle
|
|
21
21
|
end
|
22
22
|
module_function :abort
|
23
23
|
|
24
|
+
# Small helper to map CI run status/conclusion to an emoji.
|
25
|
+
# Reused by ci:act and release summary.
|
26
|
+
# @param status [String, nil]
|
27
|
+
# @param conclusion [String, nil]
|
28
|
+
# @return [String]
|
29
|
+
def status_emoji(status, conclusion)
|
30
|
+
case status.to_s
|
31
|
+
when "queued" then "⏳️"
|
32
|
+
when "in_progress", "running" then "👟"
|
33
|
+
when "completed"
|
34
|
+
(conclusion.to_s == "success") ? "✅" : "🍅"
|
35
|
+
else
|
36
|
+
# Some APIs report only a final state string like "success"/"failed"
|
37
|
+
return "✅" if conclusion.to_s == "success" || status.to_s == "success"
|
38
|
+
return "🍅" if conclusion.to_s == "failure" || status.to_s == "failed"
|
39
|
+
"⏳️"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
module_function :status_emoji
|
43
|
+
|
24
44
|
# Monitor both GitHub and GitLab CI for the current project/branch.
|
25
|
-
# This mirrors ReleaseCLI behavior.
|
45
|
+
# This mirrors ReleaseCLI behavior and aborts on first failure.
|
26
46
|
#
|
27
47
|
# @param restart_hint [String] guidance command shown on failure
|
28
48
|
# @return [void]
|
@@ -33,16 +53,217 @@ module Kettle
|
|
33
53
|
abort("CI configuration not detected (GitHub or GitLab). Ensure CI is configured and remotes point to the correct hosts.") unless checks_any
|
34
54
|
end
|
35
55
|
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# @param restart_hint [String]
|
40
|
-
# @return [Boolean]
|
41
|
-
def monitor_gitlab!(restart_hint: "bundle exec
|
56
|
+
# Public wrapper to monitor GitLab pipeline with abort-on-failure semantics.
|
57
|
+
# Matches RBS and call sites expecting ::monitor_gitlab!
|
58
|
+
# Returns false when GitLab is not configured for this repo/branch.
|
59
|
+
# @param restart_hint [String]
|
60
|
+
# @return [Boolean]
|
61
|
+
def monitor_gitlab!(restart_hint: "bundle exec kettle-release start_step=10")
|
42
62
|
monitor_gitlab_internal!(restart_hint: restart_hint)
|
43
63
|
end
|
64
|
+
module_function :monitor_gitlab!
|
65
|
+
|
66
|
+
# Non-aborting collection across GH and GL, returning a compact results hash.
|
67
|
+
# Results format:
|
68
|
+
# {
|
69
|
+
# github: [ {workflow: "file.yml", status: "completed", conclusion: "success"|"failure"|nil, url: String} ],
|
70
|
+
# gitlab: { status: "success"|"failed"|"blocked"|"unknown"|nil, url: String }
|
71
|
+
# }
|
72
|
+
# @return [Hash]
|
73
|
+
def collect_all
|
74
|
+
results = {github: [], gitlab: nil}
|
75
|
+
begin
|
76
|
+
gh = collect_github
|
77
|
+
results[:github] = gh if gh
|
78
|
+
rescue StandardError => e
|
79
|
+
Kettle::Dev.debug_error(e, __method__)
|
80
|
+
end
|
81
|
+
begin
|
82
|
+
gl = collect_gitlab
|
83
|
+
results[:gitlab] = gl if gl
|
84
|
+
rescue StandardError => e
|
85
|
+
Kettle::Dev.debug_error(e, __method__)
|
86
|
+
end
|
87
|
+
results
|
88
|
+
end
|
89
|
+
module_function :collect_all
|
90
|
+
|
91
|
+
# Print a concise summary like ci:act and return whether everything is green.
|
92
|
+
# @param results [Hash]
|
93
|
+
# @return [Boolean] true when all checks passed or were unknown, false when any failed
|
94
|
+
def summarize_results(results)
|
95
|
+
all_ok = true
|
96
|
+
gh_items = results[:github] || []
|
97
|
+
unless gh_items.empty?
|
98
|
+
puts "GitHub Actions:"
|
99
|
+
gh_items.each do |it|
|
100
|
+
emoji = status_emoji(it[:status], it[:conclusion])
|
101
|
+
details = [it[:status], it[:conclusion]].compact.join("/")
|
102
|
+
wf = it[:workflow]
|
103
|
+
puts " - #{wf}: #{emoji} (#{details}) #{it[:url] ? "-> #{it[:url]}" : ""}"
|
104
|
+
all_ok &&= (it[:conclusion] == "success")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
gl = results[:gitlab]
|
108
|
+
if gl
|
109
|
+
status = if gl[:status] == "success"
|
110
|
+
"success"
|
111
|
+
else
|
112
|
+
((gl[:status] == "failed") ? "failure" : nil)
|
113
|
+
end
|
114
|
+
emoji = status_emoji(gl[:status], status)
|
115
|
+
details = gl[:status].to_s
|
116
|
+
puts "GitLab Pipeline: #{emoji} (#{details}) #{gl[:url] ? "-> #{gl[:url]}" : ""}"
|
117
|
+
all_ok &&= (gl[:status] != "failed")
|
118
|
+
end
|
119
|
+
all_ok
|
120
|
+
end
|
121
|
+
module_function :summarize_results
|
122
|
+
|
123
|
+
# Prompt user to continue or quit when failures are present; otherwise return.
|
124
|
+
# Designed for kettle-release.
|
125
|
+
# @param restart_hint [String]
|
126
|
+
# @return [void]
|
127
|
+
def monitor_and_prompt_for_release!(restart_hint: "bundle exec kettle-release start_step=10")
|
128
|
+
results = collect_all
|
129
|
+
any_checks = !(results[:github].nil? || results[:github].empty?) || !!results[:gitlab]
|
130
|
+
abort("CI configuration not detected (GitHub or GitLab). Ensure CI is configured and remotes point to the correct hosts.") unless any_checks
|
131
|
+
|
132
|
+
ok = summarize_results(results)
|
133
|
+
return if ok
|
134
|
+
|
135
|
+
# Non-interactive environments default to quitting unless explicitly allowed
|
136
|
+
env_val = ENV.fetch("K_RELEASE_CI_CONTINUE", "false")
|
137
|
+
non_interactive_continue = !!(Kettle::Dev::ENV_TRUE_RE =~ env_val)
|
138
|
+
if !$stdin.tty?
|
139
|
+
abort("CI checks reported failures. Fix and restart from CI validation (#{restart_hint}).") unless non_interactive_continue
|
140
|
+
puts "CI checks reported failures, but continuing due to K_RELEASE_CI_CONTINUE=true."
|
141
|
+
return
|
142
|
+
end
|
143
|
+
|
144
|
+
# Prompt exactly once; avoid repeated printing in case of unexpected input buffering.
|
145
|
+
# Accept c/continue to proceed or q/quit to abort. Any other input defaults to quit with a message.
|
146
|
+
print("One or more CI checks failed. (c)ontinue or (q)uit? ")
|
147
|
+
ans = Kettle::Dev::InputAdapter.gets
|
148
|
+
if ans.nil?
|
149
|
+
abort("Aborting (no input available). Fix CI, then restart with: #{restart_hint}")
|
150
|
+
end
|
151
|
+
ans = ans.strip.downcase
|
152
|
+
if ans == "c" || ans == "continue"
|
153
|
+
puts "Continuing release despite CI failures."
|
154
|
+
elsif ans == "q" || ans == "quit"
|
155
|
+
abort("Aborting per user choice. Fix CI, then restart with: #{restart_hint}")
|
156
|
+
else
|
157
|
+
abort("Unrecognized input '#{ans}'. Aborting. Fix CI, then restart with: #{restart_hint}")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
module_function :monitor_and_prompt_for_release!
|
161
|
+
|
162
|
+
# --- Collectors ---
|
163
|
+
def collect_github
|
164
|
+
root = Kettle::Dev::CIHelpers.project_root
|
165
|
+
workflows = Kettle::Dev::CIHelpers.workflows_list(root)
|
166
|
+
gh_remote = preferred_github_remote
|
167
|
+
return unless gh_remote && !workflows.empty?
|
168
|
+
|
169
|
+
branch = Kettle::Dev::CIHelpers.current_branch
|
170
|
+
abort("Could not determine current branch for CI checks.") unless branch
|
171
|
+
|
172
|
+
url = remote_url(gh_remote)
|
173
|
+
owner, repo = parse_github_owner_repo(url)
|
174
|
+
return unless owner && repo
|
175
|
+
|
176
|
+
total = workflows.size
|
177
|
+
return [] if total.zero?
|
178
|
+
|
179
|
+
puts "Checking GitHub Actions workflows on #{branch} (#{owner}/#{repo}) via remote '#{gh_remote}'"
|
180
|
+
pbar = if defined?(ProgressBar)
|
181
|
+
ProgressBar.create(title: "GHA", total: total, format: "%t %b %c/%C", length: 30)
|
182
|
+
end
|
183
|
+
# Initial sleep same as aborting path
|
184
|
+
begin
|
185
|
+
initial_sleep = Integer(ENV["K_RELEASE_CI_INITIAL_SLEEP"])
|
186
|
+
rescue
|
187
|
+
initial_sleep = nil
|
188
|
+
end
|
189
|
+
sleep((initial_sleep && initial_sleep >= 0) ? initial_sleep : 3)
|
190
|
+
|
191
|
+
results = {}
|
192
|
+
idx = 0
|
193
|
+
loop do
|
194
|
+
wf = workflows[idx]
|
195
|
+
run = Kettle::Dev::CIHelpers.latest_run(owner: owner, repo: repo, workflow_file: wf, branch: branch)
|
196
|
+
if run
|
197
|
+
if Kettle::Dev::CIHelpers.success?(run)
|
198
|
+
unless results[wf]
|
199
|
+
status = run["status"] || "completed"
|
200
|
+
conclusion = run["conclusion"] || "success"
|
201
|
+
results[wf] = {workflow: wf, status: status, conclusion: conclusion, url: run["html_url"]}
|
202
|
+
pbar&.increment
|
203
|
+
end
|
204
|
+
elsif Kettle::Dev::CIHelpers.failed?(run)
|
205
|
+
unless results[wf]
|
206
|
+
results[wf] = {workflow: wf, status: run["status"], conclusion: run["conclusion"] || "failure", url: run["html_url"] || "https://github.com/#{owner}/#{repo}/actions/workflows/#{wf}"}
|
207
|
+
pbar&.increment
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
break if results.size == total
|
212
|
+
idx = (idx + 1) % total
|
213
|
+
sleep(1)
|
214
|
+
end
|
215
|
+
pbar&.finish unless pbar&.finished?
|
216
|
+
results.values
|
217
|
+
end
|
218
|
+
module_function :collect_github
|
219
|
+
|
220
|
+
def collect_gitlab
|
221
|
+
root = Kettle::Dev::CIHelpers.project_root
|
222
|
+
gitlab_ci = File.exist?(File.join(root, ".gitlab-ci.yml"))
|
223
|
+
gl_remote = gitlab_remote_candidates.first
|
224
|
+
return unless gitlab_ci && gl_remote
|
225
|
+
|
226
|
+
branch = Kettle::Dev::CIHelpers.current_branch
|
227
|
+
abort("Could not determine current branch for CI checks.") unless branch
|
228
|
+
|
229
|
+
owner, repo = Kettle::Dev::CIHelpers.repo_info_gitlab
|
230
|
+
return unless owner && repo
|
231
|
+
|
232
|
+
puts "Checking GitLab pipeline on #{branch} (#{owner}/#{repo}) via remote '#{gl_remote}'"
|
233
|
+
pbar = if defined?(ProgressBar)
|
234
|
+
ProgressBar.create(title: "GL", total: 1, format: "%t %b %c/%C", length: 30)
|
235
|
+
end
|
236
|
+
result = {status: "unknown", url: nil}
|
237
|
+
loop do
|
238
|
+
pipe = Kettle::Dev::CIHelpers.gitlab_latest_pipeline(owner: owner, repo: repo, branch: branch)
|
239
|
+
if pipe
|
240
|
+
result[:url] ||= pipe["web_url"] || "https://gitlab.com/#{owner}/#{repo}/-/pipelines"
|
241
|
+
if Kettle::Dev::CIHelpers.gitlab_success?(pipe)
|
242
|
+
result[:status] = "success"
|
243
|
+
pbar&.increment unless pbar&.finished?
|
244
|
+
elsif Kettle::Dev::CIHelpers.gitlab_failed?(pipe)
|
245
|
+
reason = (pipe["failure_reason"] || "").to_s
|
246
|
+
if reason =~ /insufficient|quota|minute/i
|
247
|
+
result[:status] = "unknown"
|
248
|
+
pbar&.finish unless pbar&.finished?
|
249
|
+
else
|
250
|
+
result[:status] = "failed"
|
251
|
+
pbar&.increment unless pbar&.finished?
|
252
|
+
end
|
253
|
+
elsif pipe["status"] == "blocked"
|
254
|
+
result[:status] = "blocked"
|
255
|
+
pbar&.finish unless pbar&.finished?
|
256
|
+
end
|
257
|
+
break
|
258
|
+
end
|
259
|
+
sleep(1)
|
260
|
+
end
|
261
|
+
pbar&.finish unless pbar&.finished?
|
262
|
+
result
|
263
|
+
end
|
264
|
+
module_function :collect_gitlab
|
44
265
|
|
45
|
-
# -- internals --
|
266
|
+
# -- internals (abort-on-failure legacy paths used elsewhere) --
|
46
267
|
|
47
268
|
def monitor_github_internal!(restart_hint:)
|
48
269
|
root = Kettle::Dev::CIHelpers.project_root
|