kettle-dev 2.5.12 → 2.5.14
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/CHANGELOG.md +60 -1
- data/README.md +1 -1
- data/exe/kettle-changelog +25 -3
- data/exe/kettle-release +26 -2
- data/lib/kettle/dev/changelog_cli.rb +43 -1
- data/lib/kettle/dev/ci_monitor.rb +206 -7
- data/lib/kettle/dev/lockfile_reset.rb +12 -8
- data/lib/kettle/dev/pre_release_cli.rb +41 -2
- data/lib/kettle/dev/release_cli.rb +278 -20
- data/lib/kettle/dev/version.rb +1 -1
- data.tar.gz.sig +3 -3
- metadata +6 -6
- metadata.gz.sig +0 -0
|
@@ -221,7 +221,7 @@ module Kettle
|
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
def empty_registry_checksums(path)
|
|
224
|
-
|
|
224
|
+
non_registry_gems = non_registry_source_gems(path) | path_dependency_gems(path)
|
|
225
225
|
in_checksums = false
|
|
226
226
|
entries = File.readlines(path).filter_map.with_index(1) do |line, index|
|
|
227
227
|
stripped = line.strip
|
|
@@ -239,7 +239,7 @@ module Kettle
|
|
|
239
239
|
name = match[1]
|
|
240
240
|
checksum = match[3].to_s
|
|
241
241
|
next unless checksum.empty?
|
|
242
|
-
next if
|
|
242
|
+
next if non_registry_gems.include?(name)
|
|
243
243
|
|
|
244
244
|
[name, match[2], index]
|
|
245
245
|
end
|
|
@@ -282,6 +282,12 @@ module Kettle
|
|
|
282
282
|
end
|
|
283
283
|
end
|
|
284
284
|
|
|
285
|
+
def non_registry_source_gems(path)
|
|
286
|
+
lockfile_parser(path).specs.each_with_object(Set.new) do |spec, gems|
|
|
287
|
+
gems << spec.name unless registry_source?(spec.source)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
285
291
|
def path_dependency_gems(path)
|
|
286
292
|
lockfile_parser(path).dependencies.each_value.each_with_object(Set.new) do |dependency, gems|
|
|
287
293
|
source = dependency.source
|
|
@@ -289,12 +295,6 @@ module Kettle
|
|
|
289
295
|
end
|
|
290
296
|
end
|
|
291
297
|
|
|
292
|
-
def self_path_source_gems(path)
|
|
293
|
-
lockfile_parser(path).specs.each_with_object(Set.new) do |spec, gems|
|
|
294
|
-
gems << spec.name if self_path_source?(spec.source)
|
|
295
|
-
end
|
|
296
|
-
end
|
|
297
|
-
|
|
298
298
|
def display_path(path)
|
|
299
299
|
Kettle::Dev.display_path(path)
|
|
300
300
|
end
|
|
@@ -332,6 +332,10 @@ module Kettle
|
|
|
332
332
|
source.instance_of?(::Bundler::Source::Path)
|
|
333
333
|
end
|
|
334
334
|
|
|
335
|
+
def registry_source?(source)
|
|
336
|
+
source.instance_of?(::Bundler::Source::Rubygems)
|
|
337
|
+
end
|
|
338
|
+
|
|
335
339
|
def self_path_source?(source)
|
|
336
340
|
path_source?(source) && source.path.to_s == "."
|
|
337
341
|
end
|
|
@@ -10,6 +10,7 @@ require "net/http"
|
|
|
10
10
|
require "openssl"
|
|
11
11
|
require "time"
|
|
12
12
|
require_relative "cache_progress"
|
|
13
|
+
require "kettle/ndjson"
|
|
13
14
|
begin
|
|
14
15
|
require "addressable/uri"
|
|
15
16
|
rescue LoadError
|
|
@@ -271,9 +272,10 @@ module Kettle
|
|
|
271
272
|
end
|
|
272
273
|
|
|
273
274
|
# @param check_num [Integer] 1-based index to resume from
|
|
274
|
-
def initialize(check_num: 1)
|
|
275
|
+
def initialize(check_num: 1, event_recorder: nil)
|
|
275
276
|
@check_num = (check_num || 1).to_i
|
|
276
277
|
@check_num = 1 if @check_num < 1
|
|
278
|
+
@event_recorder = event_recorder
|
|
277
279
|
@image_url_cache_path = configured_image_url_cache_path
|
|
278
280
|
@refresh_image_url_cache = env_truthy?(ENV["KETTLE_IMAGE_URL_CACHE_REFRESH"])
|
|
279
281
|
@image_url_skip_patterns = configured_image_url_skip_patterns
|
|
@@ -294,7 +296,14 @@ module Kettle
|
|
|
294
296
|
checks[begin_idx..-1].each_with_index do |check, i|
|
|
295
297
|
idx = begin_idx + i + 1
|
|
296
298
|
puts "[kettle-pre-release] Running check ##{idx} of #{checks.size}"
|
|
297
|
-
check.
|
|
299
|
+
emit_pre_release_event(action: "check", status: "started", check: check_name(check), index: idx, total: checks.size)
|
|
300
|
+
begin
|
|
301
|
+
check.call
|
|
302
|
+
emit_pre_release_event(action: "check", status: "ok", check: check_name(check), index: idx, total: checks.size)
|
|
303
|
+
rescue => error
|
|
304
|
+
emit_pre_release_event(action: "check", status: "failed", check: check_name(check), index: idx, total: checks.size, reason: "#{error.class}: #{error.message}")
|
|
305
|
+
raise
|
|
306
|
+
end
|
|
298
307
|
end
|
|
299
308
|
nil
|
|
300
309
|
end
|
|
@@ -355,6 +364,7 @@ module Kettle
|
|
|
355
364
|
end
|
|
356
365
|
|
|
357
366
|
puts "[kettle-pre-release] Normalization candidates: #{total_candidates}. Files changed: #{changed.uniq.size}."
|
|
367
|
+
emit_pre_release_event(action: "markdown_urls", status: "ok", candidates: total_candidates, changed_files: changed.uniq.size)
|
|
358
368
|
nil
|
|
359
369
|
end
|
|
360
370
|
|
|
@@ -408,10 +418,12 @@ module Kettle
|
|
|
408
418
|
puts "[kettle-pre-release] Image URL checks: #{progress.cached_count} cached, #{progress.live_count} live."
|
|
409
419
|
puts "[kettle-pre-release] Skipped #{progress.skipped_count} image URL check(s)." if skipped.any?
|
|
410
420
|
if failures.any?
|
|
421
|
+
emit_pre_release_event(action: "image_links", status: "failed", urls: urls.size, cached: progress.cached_count, live: progress.live_count, skipped: progress.skipped_count, failures: failures.size)
|
|
411
422
|
warn("[kettle-pre-release] #{failures.size} image URL(s) failed validation:")
|
|
412
423
|
failures.each { |u| warn(" - #{u}") }
|
|
413
424
|
Kettle::Dev::ExitAdapter.abort("Image link validation failed")
|
|
414
425
|
else
|
|
426
|
+
emit_pre_release_event(action: "image_links", status: "ok", urls: urls.size, cached: progress.cached_count, live: progress.live_count, skipped: progress.skipped_count, failures: 0)
|
|
415
427
|
puts "[kettle-pre-release] All image links validated."
|
|
416
428
|
end
|
|
417
429
|
nil
|
|
@@ -419,6 +431,33 @@ module Kettle
|
|
|
419
431
|
|
|
420
432
|
private
|
|
421
433
|
|
|
434
|
+
def check_name(check)
|
|
435
|
+
check.name.to_s.sub(/\Acheck_/, "").sub(/!\z/, "")
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def emit_pre_release_event(action:, status:, **payload)
|
|
439
|
+
mark = case status.to_s
|
|
440
|
+
when "started"
|
|
441
|
+
">"
|
|
442
|
+
when "ok", "skipped"
|
|
443
|
+
"."
|
|
444
|
+
when "failed"
|
|
445
|
+
"!"
|
|
446
|
+
else
|
|
447
|
+
"?"
|
|
448
|
+
end
|
|
449
|
+
Kettle::Ndjson.emit_event(
|
|
450
|
+
@event_recorder,
|
|
451
|
+
"pre_release",
|
|
452
|
+
payload.merge(
|
|
453
|
+
action: action,
|
|
454
|
+
phase: "release",
|
|
455
|
+
status: status,
|
|
456
|
+
mark: mark
|
|
457
|
+
)
|
|
458
|
+
)
|
|
459
|
+
end
|
|
460
|
+
|
|
422
461
|
def image_url_cache
|
|
423
462
|
return nil if @image_url_cache_path.to_s.empty?
|
|
424
463
|
|
|
@@ -117,14 +117,15 @@ module Kettle
|
|
|
117
117
|
|
|
118
118
|
public
|
|
119
119
|
|
|
120
|
-
def initialize(start_step: 0, local_ci: false, version: nil, appraisal_task: nil, skip_steps: nil, skip_bundle_audit: nil, ci_workflows: nil, skip_remotes: nil, secrets_provider_name: nil, yes: false, **options)
|
|
120
|
+
def initialize(start_step: 0, local_ci: false, version: nil, appraisal_task: nil, skip_steps: nil, skip_bundle_audit: nil, ci_workflows: nil, skip_remotes: nil, required_remotes: nil, secrets_provider_name: nil, yes: false, **options)
|
|
121
121
|
@root = Kettle::Dev::CIHelpers.project_root
|
|
122
122
|
@git = Kettle::Dev::GitAdapter.new(@root)
|
|
123
123
|
@start_step = (start_step || 0).to_i
|
|
124
124
|
@start_step = 0 if @start_step < 0
|
|
125
125
|
@skip_steps = normalize_skip_steps(skip_steps)
|
|
126
126
|
@ci_workflows = normalize_ci_workflows(ci_workflows || ENV["K_RELEASE_CI_WORKFLOWS"])
|
|
127
|
-
@skip_remotes =
|
|
127
|
+
@skip_remotes = normalize_remote_names(skip_remotes || ENV["K_RELEASE_SKIP_REMOTES"], "skip remotes")
|
|
128
|
+
@required_remotes = normalize_required_remotes(required_remotes)
|
|
128
129
|
@local_ci = !!local_ci
|
|
129
130
|
@skip_bundle_audit = truthy_value?(skip_bundle_audit) || truthy_value?(ENV["KETTLE_DEV_SKIP_BUNDLE_AUDIT"])
|
|
130
131
|
@yes = !!yes
|
|
@@ -479,14 +480,19 @@ module Kettle
|
|
|
479
480
|
workflows.map { |workflow| workflow.match?(/\.ya?ml\z/) ? workflow : "#{workflow}.yml" }.uniq
|
|
480
481
|
end
|
|
481
482
|
|
|
482
|
-
def
|
|
483
|
+
def normalize_remote_names(value, label)
|
|
483
484
|
remotes = Array(value).flat_map { |part| part.to_s.split(",") }.map(&:strip).reject(&:empty?)
|
|
484
485
|
invalid = remotes.find { |remote| !remote.match?(/\A[A-Za-z0-9_.-]+\z/) }
|
|
485
|
-
abort("Invalid
|
|
486
|
+
abort("Invalid #{label} value #{invalid.inspect}; use comma-separated git remote names.") if invalid
|
|
486
487
|
|
|
487
488
|
remotes.uniq
|
|
488
489
|
end
|
|
489
490
|
|
|
491
|
+
def normalize_required_remotes(value)
|
|
492
|
+
remotes = normalize_remote_names(value.nil? ? ENV["K_RELEASE_REQUIRED_REMOTES"] : value, "required remotes")
|
|
493
|
+
remotes.empty? ? ["origin"] : remotes
|
|
494
|
+
end
|
|
495
|
+
|
|
490
496
|
private
|
|
491
497
|
|
|
492
498
|
def local_ci?
|
|
@@ -571,7 +577,7 @@ module Kettle
|
|
|
571
577
|
|
|
572
578
|
def run_pre_release_checks!
|
|
573
579
|
puts "Running pre-release checks via kettle-pre-release..."
|
|
574
|
-
Kettle::Dev::PreReleaseCLI.new(check_num: 1).run
|
|
580
|
+
Kettle::Dev::PreReleaseCLI.new(check_num: 1, event_recorder: @event_recorder).run
|
|
575
581
|
run_changelog!
|
|
576
582
|
end
|
|
577
583
|
|
|
@@ -590,9 +596,11 @@ module Kettle
|
|
|
590
596
|
attempts = release_lockfile_reset_attempts(stage)
|
|
591
597
|
attempts.times do |index|
|
|
592
598
|
attempt = index + 1
|
|
599
|
+
emit_release_lockfile_event(action: "reset", status: "started", stage: stage, attempt: attempt, attempts: attempts)
|
|
593
600
|
puts "Resetting release lockfiles with local path dependencies disabled #{stage} (attempt #{attempt}/#{attempts})..."
|
|
594
601
|
begin
|
|
595
602
|
lockfile_reset.reset(Kettle::Dev::LockfileReset::RELEASE_LOCKFILES_TARGET)
|
|
603
|
+
emit_release_lockfile_event(action: "reset", status: "ok", stage: stage, attempt: attempt, attempts: attempts)
|
|
596
604
|
break
|
|
597
605
|
rescue Kettle::Dev::Error => error
|
|
598
606
|
raise unless error.message.start_with?("Reset #{Kettle::Dev::LockfileReset::RELEASE_LOCKFILES_TARGET} failed validation:")
|
|
@@ -600,9 +608,26 @@ module Kettle
|
|
|
600
608
|
# Keep release-facing failures shaped like the lockfile validation
|
|
601
609
|
# guard, even when the shared reset helper is the component that
|
|
602
610
|
# detects the unrepaired lockfile.
|
|
611
|
+
emit_release_lockfile_event(
|
|
612
|
+
action: "reset",
|
|
613
|
+
status: "blocked",
|
|
614
|
+
stage: stage,
|
|
615
|
+
attempt: attempt,
|
|
616
|
+
attempts: attempts,
|
|
617
|
+
reason: error.message
|
|
618
|
+
)
|
|
603
619
|
break
|
|
604
620
|
rescue => error
|
|
605
|
-
|
|
621
|
+
retryable = retryable_release_lockfile_reset_error?(error) && attempt < attempts
|
|
622
|
+
emit_release_lockfile_event(
|
|
623
|
+
action: "reset",
|
|
624
|
+
status: retryable ? "retrying" : "failed",
|
|
625
|
+
stage: stage,
|
|
626
|
+
attempt: attempt,
|
|
627
|
+
attempts: attempts,
|
|
628
|
+
reason: error.message
|
|
629
|
+
)
|
|
630
|
+
raise unless retryable
|
|
606
631
|
|
|
607
632
|
puts "Release lockfile reset could not resolve a gem from #{RELEASE_VALIDATION_SOURCE}; waiting before retry #{attempt + 1}/#{attempts}."
|
|
608
633
|
sleep(release_availability_probe_interval)
|
|
@@ -611,7 +636,16 @@ module Kettle
|
|
|
611
636
|
|
|
612
637
|
diagnostics = release_lockfile_paths.flat_map { |path| release_lockfile_diagnostics(path) }
|
|
613
638
|
if diagnostics.empty?
|
|
639
|
+
emit_release_lockfile_event(action: "validate", status: "ok", stage: stage, count: release_lockfile_paths.length)
|
|
614
640
|
puts "Release lockfile reset complete: #{release_lockfile_paths.length} lockfile(s) checked, no diagnostics remain."
|
|
641
|
+
else
|
|
642
|
+
emit_release_lockfile_event(
|
|
643
|
+
action: "validate",
|
|
644
|
+
status: "failed",
|
|
645
|
+
stage: stage,
|
|
646
|
+
count: diagnostics.length,
|
|
647
|
+
reason: "#{diagnostics.length} diagnostic(s)"
|
|
648
|
+
)
|
|
615
649
|
end
|
|
616
650
|
@release_lockfiles_reset_for_release_tasks = true if stage == "before release task bundle installs"
|
|
617
651
|
end
|
|
@@ -758,6 +792,7 @@ module Kettle
|
|
|
758
792
|
cmd = "bundle exec kettle-changelog"
|
|
759
793
|
cmd = "#{cmd} --version #{Shellwords.escape(@version_override)}" if @version_override
|
|
760
794
|
cmd = "#{cmd} --yes" if @yes
|
|
795
|
+
cmd = "#{cmd} --events=changelog" if @event_stream
|
|
761
796
|
run_cmd!(cmd)
|
|
762
797
|
@changelog_generated_coverage = true
|
|
763
798
|
end
|
|
@@ -1010,12 +1045,24 @@ module Kettle
|
|
|
1010
1045
|
def monitor_workflows_after_push!
|
|
1011
1046
|
ensure_github_pull_request_for_ci!
|
|
1012
1047
|
keep_release_secrets_alive!("CI monitoring")
|
|
1048
|
+
restart_hint = "bundle exec kettle-release start_step=10"
|
|
1049
|
+
emit_ci_monitor_event(action: "start", status: "started", workflows: @ci_workflows, restart_hint: restart_hint)
|
|
1013
1050
|
# Use abort-on-failure CI monitor to match historical behavior and specs
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1051
|
+
begin
|
|
1052
|
+
Kettle::Dev::CIMonitor.monitor_all!(
|
|
1053
|
+
restart_hint: restart_hint,
|
|
1054
|
+
workflows: @ci_workflows,
|
|
1055
|
+
keepalive: release_secrets_configured? ? -> { keep_release_secrets_alive!("CI monitoring") } : nil,
|
|
1056
|
+
event_recorder: @event_recorder
|
|
1057
|
+
)
|
|
1058
|
+
emit_ci_monitor_event(action: "finish", status: "ok", workflows: @ci_workflows, restart_hint: restart_hint)
|
|
1059
|
+
rescue SystemExit => error
|
|
1060
|
+
emit_ci_monitor_event(action: "finish", status: "failed", workflows: @ci_workflows, restart_hint: restart_hint, reason: error.message)
|
|
1061
|
+
raise
|
|
1062
|
+
rescue => error
|
|
1063
|
+
emit_ci_monitor_event(action: "finish", status: "failed", workflows: @ci_workflows, restart_hint: restart_hint, reason: "#{error.class}: #{error.message}")
|
|
1064
|
+
raise
|
|
1065
|
+
end
|
|
1019
1066
|
end
|
|
1020
1067
|
|
|
1021
1068
|
def ensure_github_pull_request_for_ci!
|
|
@@ -1412,6 +1459,7 @@ module Kettle
|
|
|
1412
1459
|
sleep(release_availability_probe_initial_delay)
|
|
1413
1460
|
attempts.times do |index|
|
|
1414
1461
|
attempt = index + 1
|
|
1462
|
+
emit_release_probe_event(action: "availability", status: "started", candidate: candidate, attempt: attempt, attempts: attempts)
|
|
1415
1463
|
puts("Validating #{candidate.gem_name} #{candidate.version} from #{RELEASE_VALIDATION_SOURCE} (attempt #{attempt}/#{attempts})")
|
|
1416
1464
|
stdout_str = nil
|
|
1417
1465
|
stderr_str = nil
|
|
@@ -1420,10 +1468,21 @@ module Kettle
|
|
|
1420
1468
|
stdout_str, stderr_str, status = Open3.capture3(self.class.send(:command_env), Gem.ruby, script_path)
|
|
1421
1469
|
end
|
|
1422
1470
|
$stdout.print(stdout_str) unless stdout_str.to_s.empty?
|
|
1423
|
-
|
|
1471
|
+
if status.success?
|
|
1472
|
+
emit_release_probe_event(action: "availability", status: "ok", candidate: candidate, attempt: attempt, attempts: attempts)
|
|
1473
|
+
return true
|
|
1474
|
+
end
|
|
1424
1475
|
|
|
1425
1476
|
last_stderr = stderr_str
|
|
1426
1477
|
last_status = status
|
|
1478
|
+
emit_release_probe_event(
|
|
1479
|
+
action: "availability",
|
|
1480
|
+
status: (attempt < attempts) ? "retrying" : "failed",
|
|
1481
|
+
candidate: candidate,
|
|
1482
|
+
attempt: attempt,
|
|
1483
|
+
attempts: attempts,
|
|
1484
|
+
reason: "exit #{status.exitstatus}"
|
|
1485
|
+
)
|
|
1427
1486
|
sleep(release_availability_probe_interval) if attempt < attempts
|
|
1428
1487
|
end
|
|
1429
1488
|
|
|
@@ -1590,6 +1649,7 @@ module Kettle
|
|
|
1590
1649
|
name: command_event_name(command),
|
|
1591
1650
|
status: status,
|
|
1592
1651
|
reason: reason,
|
|
1652
|
+
summary: command_event_summary(command),
|
|
1593
1653
|
command: command,
|
|
1594
1654
|
changed_files: []
|
|
1595
1655
|
}
|
|
@@ -1598,7 +1658,80 @@ module Kettle
|
|
|
1598
1658
|
end
|
|
1599
1659
|
|
|
1600
1660
|
def command_event_name(command)
|
|
1601
|
-
|
|
1661
|
+
words = Shellwords.split(command.to_s)
|
|
1662
|
+
effective_words = command_event_effective_words(words)
|
|
1663
|
+
joined = effective_words.join(" ")
|
|
1664
|
+
|
|
1665
|
+
return "kettle_changelog" if joined.start_with?("bundle exec kettle-changelog")
|
|
1666
|
+
return "gem_build" if joined == "bundle exec rake build"
|
|
1667
|
+
return "gem_release" if joined == "bundle exec rake release"
|
|
1668
|
+
return "gem_push" if effective_words.first(2) == ["gem", "push"]
|
|
1669
|
+
return "gem_checksums" if effective_words.first == "bin/gem_checksums"
|
|
1670
|
+
return "bundle_lock" if effective_words.first(2) == ["bundle", "lock"]
|
|
1671
|
+
return "bin_setup" if effective_words == ["bin/setup"]
|
|
1672
|
+
return "default_task" if effective_words == ["bin/rake"]
|
|
1673
|
+
return "appraisal_generate" if effective_words == ["bin/rake", "appraisal:generate"]
|
|
1674
|
+
return "appraisal_update" if effective_words == ["bin/rake", "appraisal:update"]
|
|
1675
|
+
return "yard" if effective_words == ["bin/rake", "yard"]
|
|
1676
|
+
return "git_fetch" if effective_words.first(2) == ["git", "fetch"]
|
|
1677
|
+
return "git_pull" if effective_words.first(2) == ["git", "pull"]
|
|
1678
|
+
return "git_rebase" if effective_words.first(2) == ["git", "rebase"]
|
|
1679
|
+
return "git_merge" if effective_words.first(2) == ["git", "merge"]
|
|
1680
|
+
return "git_push" if effective_words.first(2) == ["git", "push"]
|
|
1681
|
+
|
|
1682
|
+
effective_words.first(2).join("_").gsub(/[^A-Za-z0-9_]+/, "_").sub(/_+\z/, "")
|
|
1683
|
+
end
|
|
1684
|
+
|
|
1685
|
+
def command_event_effective_words(words)
|
|
1686
|
+
words = words.dup
|
|
1687
|
+
words.shift while words.first&.match?(/\A[A-Za-z_][A-Za-z0-9_]*=/)
|
|
1688
|
+
|
|
1689
|
+
if words.first == "env"
|
|
1690
|
+
words.shift
|
|
1691
|
+
while words.any?
|
|
1692
|
+
if words.first == "-u"
|
|
1693
|
+
words.shift(2)
|
|
1694
|
+
elsif words.first&.include?("=")
|
|
1695
|
+
words.shift
|
|
1696
|
+
else
|
|
1697
|
+
break
|
|
1698
|
+
end
|
|
1699
|
+
end
|
|
1700
|
+
end
|
|
1701
|
+
|
|
1702
|
+
words
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
def command_event_summary(command)
|
|
1706
|
+
words = Shellwords.split(command.to_s)
|
|
1707
|
+
effective_words = command_event_effective_words(words)
|
|
1708
|
+
joined = effective_words.join(" ")
|
|
1709
|
+
|
|
1710
|
+
return "changelog" if joined.start_with?("bundle exec kettle-changelog")
|
|
1711
|
+
return command_event_bundle_lock_summary(words) if effective_words.first(2) == ["bundle", "lock"]
|
|
1712
|
+
return "setup" if effective_words == ["bin/setup"]
|
|
1713
|
+
return "default task" if effective_words == ["bin/rake"]
|
|
1714
|
+
return "appraisals" if effective_words == ["bin/rake", "appraisal:generate"]
|
|
1715
|
+
return "appraisals" if effective_words == ["bin/rake", "appraisal:update"]
|
|
1716
|
+
return "documentation" if effective_words == ["bin/rake", "yard"]
|
|
1717
|
+
return "build gem" if joined == "bundle exec rake build"
|
|
1718
|
+
return "publish gem" if joined == "bundle exec rake release"
|
|
1719
|
+
return "push gem" if effective_words.first(2) == ["gem", "push"]
|
|
1720
|
+
return "checksums" if effective_words.first == "bin/gem_checksums"
|
|
1721
|
+
return effective_words[2] if effective_words.first(2) == ["git", "fetch"] && effective_words[2]
|
|
1722
|
+
return effective_words[2] if effective_words.first(2) == ["git", "push"] && effective_words[2]
|
|
1723
|
+
return effective_words[2] if effective_words.first(2) == ["git", "pull"] && effective_words[2]
|
|
1724
|
+
return effective_words[2] if effective_words.first(2) == ["git", "rebase"] && effective_words[2]
|
|
1725
|
+
return effective_words[2] if effective_words.first(2) == ["git", "merge"] && effective_words[2]
|
|
1726
|
+
|
|
1727
|
+
nil
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
def command_event_bundle_lock_summary(words)
|
|
1731
|
+
gemfile = words.find { |word| word.start_with?("BUNDLE_GEMFILE=") }&.split("=", 2)&.last
|
|
1732
|
+
return "lockfile" unless gemfile
|
|
1733
|
+
|
|
1734
|
+
File.basename(gemfile)
|
|
1602
1735
|
end
|
|
1603
1736
|
|
|
1604
1737
|
def record_diagnostic(kind, message, severity:, blocking:)
|
|
@@ -1635,6 +1768,97 @@ module Kettle
|
|
|
1635
1768
|
)
|
|
1636
1769
|
end
|
|
1637
1770
|
|
|
1771
|
+
def emit_remote_parity_event(action:, remote: nil, status: nil, trunk: nil, attempt: nil, attempts: nil, required: nil, reason: nil, remotes: nil)
|
|
1772
|
+
mark = release_progress_mark(status)
|
|
1773
|
+
Kettle::Ndjson.emit_event(
|
|
1774
|
+
@event_recorder,
|
|
1775
|
+
"remote_parity",
|
|
1776
|
+
action: action,
|
|
1777
|
+
phase: "release",
|
|
1778
|
+
remote: remote,
|
|
1779
|
+
status: status,
|
|
1780
|
+
trunk: trunk,
|
|
1781
|
+
attempt: attempt,
|
|
1782
|
+
attempts: attempts,
|
|
1783
|
+
required: required,
|
|
1784
|
+
reason: reason,
|
|
1785
|
+
remotes: remotes,
|
|
1786
|
+
mark: mark
|
|
1787
|
+
)
|
|
1788
|
+
end
|
|
1789
|
+
|
|
1790
|
+
def emit_release_lockfile_event(action:, status:, stage:, attempt: nil, attempts: nil, count: nil, reason: nil)
|
|
1791
|
+
Kettle::Ndjson.emit_event(
|
|
1792
|
+
@event_recorder,
|
|
1793
|
+
"release_lockfile",
|
|
1794
|
+
action: action,
|
|
1795
|
+
phase: "release",
|
|
1796
|
+
status: status,
|
|
1797
|
+
stage: stage,
|
|
1798
|
+
attempt: attempt,
|
|
1799
|
+
attempts: attempts,
|
|
1800
|
+
count: count,
|
|
1801
|
+
reason: reason,
|
|
1802
|
+
mark: release_progress_mark(status)
|
|
1803
|
+
)
|
|
1804
|
+
end
|
|
1805
|
+
|
|
1806
|
+
def emit_release_probe_event(action:, status:, candidate:, attempt:, attempts:, reason: nil)
|
|
1807
|
+
Kettle::Ndjson.emit_event(
|
|
1808
|
+
@event_recorder,
|
|
1809
|
+
"release_probe",
|
|
1810
|
+
action: action,
|
|
1811
|
+
phase: "release",
|
|
1812
|
+
status: status,
|
|
1813
|
+
gem: candidate.gem_name,
|
|
1814
|
+
version: candidate.version,
|
|
1815
|
+
source: RELEASE_VALIDATION_SOURCE,
|
|
1816
|
+
attempt: attempt,
|
|
1817
|
+
attempts: attempts,
|
|
1818
|
+
reason: reason,
|
|
1819
|
+
mark: release_progress_mark(status)
|
|
1820
|
+
)
|
|
1821
|
+
end
|
|
1822
|
+
|
|
1823
|
+
def release_progress_mark(status)
|
|
1824
|
+
case status.to_s
|
|
1825
|
+
when "started"
|
|
1826
|
+
">"
|
|
1827
|
+
when "ok", "skipped"
|
|
1828
|
+
"."
|
|
1829
|
+
when "failed", "blocked"
|
|
1830
|
+
"!"
|
|
1831
|
+
when "retrying"
|
|
1832
|
+
">"
|
|
1833
|
+
else
|
|
1834
|
+
">"
|
|
1835
|
+
end
|
|
1836
|
+
end
|
|
1837
|
+
|
|
1838
|
+
def emit_ci_monitor_event(action:, status:, workflows:, restart_hint:, reason: nil)
|
|
1839
|
+
mark = case status.to_s
|
|
1840
|
+
when "started"
|
|
1841
|
+
">"
|
|
1842
|
+
when "ok"
|
|
1843
|
+
"."
|
|
1844
|
+
when "failed"
|
|
1845
|
+
"!"
|
|
1846
|
+
else
|
|
1847
|
+
"?"
|
|
1848
|
+
end
|
|
1849
|
+
Kettle::Ndjson.emit_event(
|
|
1850
|
+
@event_recorder,
|
|
1851
|
+
"ci_monitor",
|
|
1852
|
+
action: action,
|
|
1853
|
+
phase: "release",
|
|
1854
|
+
status: status,
|
|
1855
|
+
workflows: workflows,
|
|
1856
|
+
restart_hint: restart_hint,
|
|
1857
|
+
reason: reason,
|
|
1858
|
+
mark: mark
|
|
1859
|
+
)
|
|
1860
|
+
end
|
|
1861
|
+
|
|
1638
1862
|
def finish_release_report(status:, error:)
|
|
1639
1863
|
return if @finished_report
|
|
1640
1864
|
|
|
@@ -1798,6 +2022,10 @@ module Kettle
|
|
|
1798
2022
|
@skip_remotes.include?(name)
|
|
1799
2023
|
end
|
|
1800
2024
|
|
|
2025
|
+
def required_remote?(name)
|
|
2026
|
+
@required_remotes.include?(name)
|
|
2027
|
+
end
|
|
2028
|
+
|
|
1801
2029
|
def active_remotes
|
|
1802
2030
|
list_remotes.reject { |remote| skipped_remote?(remote) }
|
|
1803
2031
|
end
|
|
@@ -1836,26 +2064,30 @@ module Kettle
|
|
|
1836
2064
|
if has_remote?("all")
|
|
1837
2065
|
remotes = active_remotes
|
|
1838
2066
|
skipped = list_remotes.select { |remote| skipped_remote?(remote) }
|
|
2067
|
+
required = remotes.select { |remote| required_remote?(remote) }
|
|
1839
2068
|
puts "Remote 'all' detected. Fetching from active remotes and enforcing strict trunk parity..."
|
|
1840
2069
|
puts "Skipping configured remotes: #{skipped.join(", ")}" unless skipped.empty?
|
|
2070
|
+
puts "Required release parity remotes: #{required.join(", ")}" unless required.empty?
|
|
2071
|
+
fetched_remotes = []
|
|
2072
|
+
emit_remote_parity_event(action: "start", status: "started", trunk: trunk, remotes: remotes - ["all"])
|
|
1841
2073
|
remotes.each do |remote|
|
|
1842
2074
|
next if remote == "all"
|
|
1843
2075
|
|
|
1844
|
-
fetch_remote_for_parity!(remote)
|
|
2076
|
+
fetched_remotes << remote if fetch_remote_for_parity!(remote)
|
|
1845
2077
|
end
|
|
1846
2078
|
missing_from = []
|
|
1847
|
-
|
|
1848
|
-
next if r == "all"
|
|
1849
|
-
|
|
2079
|
+
fetched_remotes.each do |r|
|
|
1850
2080
|
if remote_branch_exists?(r, trunk)
|
|
1851
2081
|
_ahead, behind = ahead_behind_counts(trunk, "#{r}/#{trunk}")
|
|
1852
2082
|
missing_from << r if behind.positive?
|
|
1853
2083
|
end
|
|
1854
2084
|
end
|
|
1855
2085
|
unless missing_from.empty?
|
|
2086
|
+
emit_remote_parity_event(action: "missing", status: "failed", trunk: trunk, remotes: missing_from, reason: "missing commits")
|
|
1856
2087
|
abort("Local #{trunk} is missing commits present on: #{missing_from.join(", ")}. Please sync trunk first.")
|
|
1857
2088
|
end
|
|
1858
|
-
puts "Local #{trunk} has all commits from remotes: #{
|
|
2089
|
+
puts "Local #{trunk} has all commits from remotes: #{fetched_remotes.join(", ")}"
|
|
2090
|
+
emit_remote_parity_event(action: "ok", status: "ok", trunk: trunk, remotes: fetched_remotes)
|
|
1859
2091
|
return
|
|
1860
2092
|
end
|
|
1861
2093
|
|
|
@@ -1924,8 +2156,10 @@ module Kettle
|
|
|
1924
2156
|
attempts.times do |index|
|
|
1925
2157
|
attempt = index + 1
|
|
1926
2158
|
puts "Fetching remote '#{remote}' for release parity (attempt #{attempt}/#{attempts})"
|
|
2159
|
+
emit_remote_parity_event(action: "fetch", remote: remote, status: "started", attempt: attempt, attempts: attempts, required: required_remote?(remote))
|
|
1927
2160
|
begin
|
|
1928
2161
|
run_cmd!(command)
|
|
2162
|
+
emit_remote_parity_event(action: "fetch", remote: remote, status: "ok", attempt: attempt, attempts: attempts, required: required_remote?(remote))
|
|
1929
2163
|
return true
|
|
1930
2164
|
rescue SystemExit => error
|
|
1931
2165
|
last_error = error
|
|
@@ -1935,7 +2169,17 @@ module Kettle
|
|
|
1935
2169
|
sleep(remote_fetch_parity_interval) if attempt < attempts
|
|
1936
2170
|
end
|
|
1937
2171
|
|
|
1938
|
-
|
|
2172
|
+
detail = last_error&.message.to_s
|
|
2173
|
+
if required_remote?(remote)
|
|
2174
|
+
emit_remote_parity_event(action: "fetch", remote: remote, status: "failed", attempts: attempts, required: true, reason: detail)
|
|
2175
|
+
abort(remote_fetch_failure_message(remote, detail))
|
|
2176
|
+
end
|
|
2177
|
+
|
|
2178
|
+
message = optional_remote_fetch_failure_message(remote, detail)
|
|
2179
|
+
warn(message)
|
|
2180
|
+
emit_remote_parity_event(action: "skip", remote: remote, status: "skipped", attempts: attempts, required: false, reason: detail)
|
|
2181
|
+
record_diagnostic("release_remote_skipped", message, severity: "warning", blocking: false)
|
|
2182
|
+
false
|
|
1939
2183
|
end
|
|
1940
2184
|
|
|
1941
2185
|
def remote_fetch_parity_attempts
|
|
@@ -1948,7 +2192,7 @@ module Kettle
|
|
|
1948
2192
|
|
|
1949
2193
|
def remote_fetch_failure_message(remote, detail)
|
|
1950
2194
|
<<~MSG
|
|
1951
|
-
Unable to fetch git remote '#{remote}' during release parity checks.
|
|
2195
|
+
Unable to fetch required git remote '#{remote}' during release parity checks.
|
|
1952
2196
|
kettle-release enforces trunk parity across active remotes before publishing.
|
|
1953
2197
|
|
|
1954
2198
|
If this remote is temporarily unavailable or intentionally not part of this release,
|
|
@@ -1961,6 +2205,20 @@ module Kettle
|
|
|
1961
2205
|
MSG
|
|
1962
2206
|
end
|
|
1963
2207
|
|
|
2208
|
+
def optional_remote_fetch_failure_message(remote, detail)
|
|
2209
|
+
<<~MSG
|
|
2210
|
+
Unable to fetch optional git remote '#{remote}' during release parity checks; skipping this remote for this release.
|
|
2211
|
+
Required remotes still block release parity checks.
|
|
2212
|
+
|
|
2213
|
+
To make this remote block releases, configure:
|
|
2214
|
+
kettle-release --required-remotes #{remote}
|
|
2215
|
+
K_RELEASE_REQUIRED_REMOTES=#{remote} kettle-release
|
|
2216
|
+
|
|
2217
|
+
Original failure:
|
|
2218
|
+
#{detail}
|
|
2219
|
+
MSG
|
|
2220
|
+
end
|
|
2221
|
+
|
|
1964
2222
|
def merge_feature_into_trunk_and_push!(trunk, feature)
|
|
1965
2223
|
return if feature.nil? || feature == trunk
|
|
1966
2224
|
|
data/lib/kettle/dev/version.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "2.5.
|
|
8
|
+
VERSION = "2.5.14"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
data.tar.gz.sig
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
��'�'Qˁ ��(�NO��3��m������Ę��>-�U?4�0��ə����Qe�K��* �/#�Fc�+�MkI'��4��`rj�4�V���j�)��
|
|
2
|
+
A48~,�\���k���Bz1�@���D \=�N/�G�?�/��U��o�'L��srbȻ�����V�е�t�1K1��O,'�}O��d!�ȹ�u$8m�`ݩːS�"c��47#H^u�żx�����-�R@b!��[�����������;$
|
|
3
|
+
��_��t���֮�W��'/��h����
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-dev
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.5.
|
|
4
|
+
version: 2.5.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -80,7 +80,7 @@ dependencies:
|
|
|
80
80
|
version: '0.1'
|
|
81
81
|
- - ">="
|
|
82
82
|
- !ruby/object:Gem::Version
|
|
83
|
-
version: 0.1.
|
|
83
|
+
version: 0.1.9
|
|
84
84
|
type: :runtime
|
|
85
85
|
prerelease: false
|
|
86
86
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -90,7 +90,7 @@ dependencies:
|
|
|
90
90
|
version: '0.1'
|
|
91
91
|
- - ">="
|
|
92
92
|
- !ruby/object:Gem::Version
|
|
93
|
-
version: 0.1.
|
|
93
|
+
version: 0.1.9
|
|
94
94
|
- !ruby/object:Gem::Dependency
|
|
95
95
|
name: kettle-rb
|
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -401,10 +401,10 @@ licenses:
|
|
|
401
401
|
- AGPL-3.0-only
|
|
402
402
|
metadata:
|
|
403
403
|
homepage_uri: https://kettle-dev.galtzo.com
|
|
404
|
-
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.5.
|
|
405
|
-
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.5.
|
|
404
|
+
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.5.14
|
|
405
|
+
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.5.14/CHANGELOG.md
|
|
406
406
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
|
|
407
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.5.
|
|
407
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.5.14
|
|
408
408
|
funding_uri: https://github.com/sponsors/pboling
|
|
409
409
|
wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
|
|
410
410
|
news_uri: https://www.railsbling.com/tags/kettle-dev
|
metadata.gz.sig
CHANGED
|
Binary file
|