Module: StillActive::EndoflifeHelper

Extended by:
EndoflifeHelper
Included in:
EndoflifeHelper
Defined in:
lib/helpers/endoflife_helper.rb

Overview

The ecosystem-neutral support-window builder over an endoflife.date feed. Given a feed path (/api/ruby.json, /api/python.json, ...) it returns the runtime facts a per-package language ceiling is measured against: the oldest cycle still receiving security releases, the latest stable release, a grace-window flag, and the normalized cycle list. RubyHelper and PythonHelper are thin calibration wrappers that only choose the feed path, exactly as RuntimeCeilingHelper keeps the ceiling math generic and pushes the runtime specifics to the edges. RubyHelper#ruby_freshness also reuses the cycle-date parsing here so there is one home for reading this best-effort feed.

Everything degrades on bad third-party data rather than raising: the window is fetched once outside the per-package rescue, so an unguarded raise would abort the whole audit. A single malformed field must cost at most the finding it feeds, never the whole signal.

Constant Summary collapse

ENDOFLIFE_URI =
URI("https://endoflife.date/")
LATEST_STABLE_GRACE_SECONDS =

How long a newly-released runtime gets before packages are held accountable for not yet declaring support for it. Below this, a latest-not-yet ceiling is about the release calendar, not the package, so the note is suppressed.

90 * 24 * 60 * 60

Instance Method Summary collapse

Instance Method Details

#eol_reached?(value) ⇒ Boolean

true / false when the eol date is known, nil when it's absent or malformed (so callers can decide how to treat "unknown"). Routes through parse_date, so a garbled eol string degrades to nil rather than raising.

Returns:

  • (Boolean)


80
81
82
83
84
85
86
87
88
# File 'lib/helpers/endoflife_helper.rb', line 80

def eol_reached?(value)
  case value
  when true then true
  when false then false
  when String
    parsed = parse_date(value)
    parsed.nil? ? nil : parsed <= Time.now
  end
end

#parse_date(date_string) ⇒ Object

A best-effort date parse over this feed's values: nil for a missing OR malformed date, never a raise. endoflife.date is third-party data; a garbled date on one cycle must degrade that cycle, not abort the run.



63
64
65
66
67
68
69
# File 'lib/helpers/endoflife_helper.rb', line 63

def parse_date(date_string)
  return if date_string.nil?

  Time.parse(date_string)
rescue ArgumentError
  nil
end

#parse_eol(value) ⇒ Object



71
72
73
74
75
# File 'lib/helpers/endoflife_helper.rb', line 71

def parse_eol(value)
  case value
  when String then parse_date(value)
  end
end

#support_window(feed_path:) ⇒ Object

=> { oldest_supported:, latest_stable:, latest_stable_fresh:, cycles: } of Gem::Versions plus normalized EOL cycles, or nil when the feed is unavailable, empty, or every known cycle is EOL (no supported floor to compare against). latest_stable is nil when the newest cycle's latest is malformed -- see below.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/helpers/endoflife_helper.rb', line 36

def support_window(feed_path:)
  cycles = fetch_cycles(feed_path)
  return if cycles.nil? || cycles.empty?

  normalized = cycles.filter_map { |cycle| normalize_cycle(cycle) }
  supported = normalized.reject { |cycle| cycle[:eol] }
  return if supported.empty?

  # A malformed/preview `latest` on the newest cycle must not sink the whole
  # window: latest_stable feeds only the latest-not-yet NOTE, whereas the
  # EOL-forced CRITICAL depends solely on the cycles' eol flags. Degrade it to
  # nil (the note is then suppressed) rather than returning nil, which would
  # silently disable criticals too.
  latest = cycles.first["latest"]
  latest_stable = latest && Gem::Version.correct?(latest) ? Gem::Version.new(latest) : nil

  {
    oldest_supported: supported.map { |cycle| cycle[:version] }.min,
    latest_stable: latest_stable,
    latest_stable_fresh: !latest_stable.nil? && latest_stable_fresh?(cycles.first),
    cycles: normalized,
  }
end