puppet_metadata 5.3.0 → 6.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd19aa83607d45feded94b42519f1d8b31e19ca4806d36b5adf6031e74f7a709
4
- data.tar.gz: c88d61173b62fc78a4f40f85d84add61933071b5f7d66000b702dd2ac3b4e6c1
3
+ metadata.gz: bbf0cec07a466deb7e97d1e4c20b75087243573b954c8350c605141f534fe1b9
4
+ data.tar.gz: 3522efe76946069d526bd87ac9023da1b054b0676d4cc0c0b3ac1f476f32c979
5
5
  SHA512:
6
- metadata.gz: 725d91a9e780c4cb4249f62e76beb055c64847bf11352e5c8be776139ec8cb266cca6fc3ec2927b81628f2ee8e758a3985e80d2487b6da5e53946478f2e56ac0
7
- data.tar.gz: 50ecf56b078348a0b60356b8eebc0b645bb945ae7cab4c09601d5e42e5a544ffaba7d8b84cc0214567297c73254a77a9485f90e5dbf4b8d3c610bc8fbfc43500
6
+ metadata.gz: 6149d30f3403cbf37f17ccb6d132ce15c86eae5e4d3de16d2dbc96362d5b3e82751e229919fe39afc7fa06528873cfea974ec123aed9165ab7f6f3531c205212
7
+ data.tar.gz: 8d759313fbb484738c39984190d0756712ba0aab483a7f909bebfebc629f9c9b80cf69695bb8690a0c3f23dc3eaa3654f6c80a7cb05b46215d4e89be6494f339
data/README.md CHANGED
@@ -8,11 +8,127 @@
8
8
  [![RubyGem Downloads](https://img.shields.io/gem/dt/puppet_metadata.svg)](https://rubygems.org/gems/puppet_metadata)
9
9
  [![Donated by Ewoud Kohl van Wijngaarden](https://img.shields.io/badge/donated%20by-Ewoud%20Kohl%20van%20Wijngaarden-fb7047.svg)](#transfer-notice)
10
10
 
11
- The gem intends to provide an abstraction over Puppet's metadata.json file. Its API allow easy iteration over its illogical data structures.
11
+ The gem intends to provide an abstraction over Puppet's metadata.json file.
12
+ Its API allow easy iteration over its illogical data structures.
13
+
14
+ - [puppet\_metadata](#puppet_metadata)
15
+ - [New CLI interface in 6.0.0](#new-cli-interface-in-600)
16
+ - [Manage OS versions in metadata.json](#manage-os-versions-in-metadatajson)
17
+ - [List supported OS versions](#list-supported-os-versions)
18
+ - [Add missing supported OS versions](#add-missing-supported-os-versions)
19
+ - [Remove EOL OS versions](#remove-eol-os-versions)
20
+ - [Generating Github Actions outputs](#generating-github-actions-outputs)
21
+ - [Work with the API](#work-with-the-api)
22
+ - [List all supported operating systems](#list-all-supported-operating-systems)
23
+ - [List supported major puppet versions](#list-supported-major-puppet-versions)
24
+ - [Check if an operating systems is supported](#check-if-an-operating-systems-is-supported)
25
+ - [Get all versions for an Operating System that are not EoL](#get-all-versions-for-an-operating-system-that-are-not-eol)
26
+ - [Get all versions for an Operating System that are not EoL after a certain date](#get-all-versions-for-an-operating-system-that-are-not-eol-after-a-certain-date)
27
+ - [Updating OS EOL dates](#updating-os-eol-dates)
28
+ - [Adding new operating systems](#adding-new-operating-systems)
29
+ - [List supported setfiles](#list-supported-setfiles)
30
+ - [Transfer Notice](#transfer-notice)
31
+ - [License](#license)
32
+ - [Release information](#release-information)
33
+
34
+ ## New CLI interface in 6.0.0
35
+
36
+ Version 6.0.0 introduces a new CLI interface, in `bin/puppet-metadata`.
37
+ It provides a new way of handling default CLI options, like the path to the metadata.json.
38
+
39
+ ```
40
+ $ bundle exec bin/puppet-metadata --help
41
+ Usage: puppet-metadata [options] <action> [options]
42
+ --filename METADATA Metadata filename
43
+
44
+ ACTIONS
45
+ os-versions Manage operating system versions in metadata.json
46
+ setfiles Show the various setfiles supported by the metadata
47
+
48
+ See 'puppet-metadata ACTION --help' for more information on a specific action.
49
+ ```
50
+
51
+ `--filename ` is optional.
52
+ If ommitted, a metadata.json in the current directory will be parsed.
53
+
54
+ Each action is implemented as a file in `lib/puppet_metadata/command/*rb` and automatically loaded via `lib/puppet_metadata/command.rb`.
55
+
56
+ ## Manage OS versions in metadata.json
57
+
58
+ The `os-versions` command provides a unified interface to view, add, and remove operating system versions in the metadata.json.
59
+
60
+ ### List supported OS versions
61
+
62
+ By default, `os-versions` shows which OS versions in your metadata.json are still supported and which are EOL:
63
+
64
+ ```
65
+ $ bundle exec puppet-metadata os-versions
66
+ module-name supports these non-EOL operating system versions:
67
+ AlmaLinux: 8, 9
68
+ CentOS: 9
69
+ Debian: 11, 12
70
+ OracleLinux: 8, 9, 10
71
+ RedHat: 8, 9
72
+ Rocky: 8, 9
73
+ Ubuntu: 22.04, 24.04
74
+
75
+ module-name supports these EOL operating system versions:
76
+ Fedora: 40
77
+ Ubuntu: 20.04
78
+ ```
79
+
80
+ You can filter to a specific OS:
81
+
82
+ ```
83
+ $ bundle exec puppet-metadata os-versions --os Ubuntu
84
+ module-name supports these non-EOL operating system versions:
85
+ Ubuntu: 22.04, 24.04
86
+
87
+ module-name supports these EOL operating system versions:
88
+ Ubuntu: 20.04
89
+ ```
90
+
91
+ ### Add missing supported OS versions
92
+
93
+ Use `--add-missing` to automatically add all non-EOL OS versions to metadata.json:
94
+
95
+ ```
96
+ $ bundle exec puppet-metadata os-versions --add-missing
97
+ Added support:
98
+ CentOS => 10
99
+ Debian => 13
100
+ ```
101
+
102
+ These OSes are exceptions (to align with [beaker-hostgenerator](https://github.com/voxpupuli/beaker-hostgenerator) support):
103
+
104
+ - For SLES, only major versions are added.
105
+ - For Ubuntu, only LTS versions are added.
106
+
107
+ ### Remove EOL OS versions
108
+
109
+ Use `--remove-eol` to automatically remove all EOL OS versions from metadata.json:
110
+
111
+ ```
112
+ $ bundle exec puppet-metadata os-versions --remove-eol
113
+ Removed EOL operating systems:
114
+ CentOS => 7, 8
115
+ Debian => 9, 10
116
+ Ubuntu => 20.04
117
+ ```
118
+
119
+ You can preview changes without modifying metadata.json using `--noop`:
120
+
121
+ ```
122
+ $ bundle exec puppet-metadata os-versions --add-missing --noop
123
+ [NOOP] Would add support:
124
+ CentOS => 10
125
+ Debian => 13
126
+ ```
12
127
 
13
128
  ## Generating Github Actions outputs
14
129
 
15
- To get outputs [usable in Github Actions](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions), there is the `metadata2gha` command available. This generates based on metadata.json, such as [Beaker](https://github.com/voxpupuli/beaker) setfiles, Puppet major versions and a Puppet unit test matrix.
130
+ To get outputs [usable in Github Actions](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions), there is the `metadata2gha` command available.
131
+ This generates based on metadata.json, such as [Beaker](https://github.com/voxpupuli/beaker) setfiles, Puppet major versions and a Puppet unit test matrix.
16
132
 
17
133
  ```console
18
134
  $ metadata2gha
@@ -71,39 +187,6 @@ Beaker test matrix formatted for readability
71
187
  ]
72
188
  ```
73
189
 
74
- It is possible to specify the path to metadata.json and customize the setfiles. For example, to ensure the setfiles use FQDNs and apply the [systemd PIDFile workaround under docker](https://github.com/docker/for-linux/issues/835). This either means either using an older image (CentOS 7, Ubuntu 16.04) or skipping (CentOS 8).
75
-
76
- ```console
77
- $ metadata2gha --use-fqdn --pidfile-workaround true /path/to/metadata.json
78
- ```
79
-
80
- This results in the following JSON data
81
- ```json
82
- [
83
- {
84
- "name": "Puppet 7 - CentOS 7",
85
- "env": {
86
- "BEAKER_PUPPET_COLLECTION": "puppet7",
87
- "BEAKER_SETFILE": "centos7-64{hostname=centos7-64-puppet7.example.com,image=centos:7.6.1810}"
88
- }
89
- },
90
- {
91
- "name": "Puppet 7 - Debian 12",
92
- "env": {
93
- "BEAKER_PUPPET_COLLECTION": "puppet7",
94
- "BEAKER_SETFILE": "debian12-64{hostname=debian12-64-puppet7.example.com}"
95
- }
96
- },
97
- {
98
- "name": "Puppet 7 - Ubuntu 22.04",
99
- "env": {
100
- "BEAKER_PUPPET_COLLECTION": "puppet7",
101
- "BEAKER_SETFILE": "ubuntu2204-64{hostname=ubuntu2204-64-puppet7.example.com}"
102
- }
103
- }
104
- ]
105
- ```
106
-
107
190
  If you need custom hostname or multiple hosts in your integration tests this could be achived by using the --beaker-hosts option
108
191
 
109
192
  Option argument is 'HOSTNAME:ROLES;HOSTNAME:..;..' where
@@ -177,6 +260,75 @@ This results in the following JSON data
177
260
  ]
178
261
  ```
179
262
 
263
+ We can also limit the matrix to a specific collection:
264
+
265
+ ```console
266
+ $ metadata2gha --collection openvox8
267
+ ```
268
+
269
+ ```
270
+ [
271
+ {
272
+ "name": "OpenVox 8 - AlmaLinux 8",
273
+ "env": {
274
+ "BEAKER_PUPPET_COLLECTION": "openvox8",
275
+ "BEAKER_SETFILE": "almalinux8-64{hostname=almalinux8-64-openvox8}"
276
+ }
277
+ },
278
+ {
279
+ "name": "OpenVox 8 - AlmaLinux 9",
280
+ "env": {
281
+ "BEAKER_PUPPET_COLLECTION": "openvox8",
282
+ "BEAKER_SETFILE": "almalinux9-64{hostname=almalinux9-64-openvox8}"
283
+ }
284
+ },
285
+ {
286
+ "name": "OpenVox 8 - CentOS 9",
287
+ "env": {
288
+ "BEAKER_PUPPET_COLLECTION": "openvox8",
289
+ "BEAKER_SETFILE": "centos9-64{hostname=centos9-64-openvox8}"
290
+ }
291
+ },
292
+ {
293
+ "name": "OpenVox 8 - Debian 11",
294
+ "env": {
295
+ "BEAKER_PUPPET_COLLECTION": "openvox8",
296
+ "BEAKER_SETFILE": "debian11-64{hostname=debian11-64-openvox8}"
297
+ }
298
+ }
299
+ ]
300
+ ```
301
+
302
+ There is also the special collection `staging`.
303
+ This will generate a matrix for all OS releases with AIO packages.
304
+ The idea is to test unreleased packages.
305
+
306
+ ```
307
+ [
308
+ {
309
+ "name": "OpenVox 8 - AlmaLinux 8",
310
+ "env": {
311
+ "BEAKER_PUPPET_COLLECTION": "staging",
312
+ "BEAKER_SETFILE": "almalinux8-64{hostname=almalinux8-64-staging}"
313
+ }
314
+ },
315
+ {
316
+ "name": "OpenVox 8 - AlmaLinux 9",
317
+ "env": {
318
+ "BEAKER_PUPPET_COLLECTION": "staging",
319
+ "BEAKER_SETFILE": "almalinux9-64{hostname=almalinux9-64-staging}"
320
+ }
321
+ },
322
+ {
323
+ "name": "OpenVox 8 - CentOS 9",
324
+ "env": {
325
+ "BEAKER_PUPPET_COLLECTION": "staging",
326
+ "BEAKER_SETFILE": "centos9-64{hostname=centos9-64-staging}"
327
+ }
328
+ }
329
+ ]
330
+ ```
331
+
180
332
  ## Work with the API
181
333
 
182
334
  The API can be initialised like this:
@@ -212,6 +364,60 @@ The metadata object has several different methods that we can call
212
364
  [7] pry(main)>
213
365
  ```
214
366
 
367
+ ### Get all versions for an Operating System that are not EoL
368
+
369
+ ```
370
+ [1] pry(main)> require 'puppet_metadata'
371
+ => true
372
+ [2] pry(main)> PuppetMetadata::OperatingSystem.supported_releases('RedHat')
373
+ => ["8", "9", "10"]
374
+ [3] pry(main)> PuppetMetadata::OperatingSystem.supported_releases('windows')
375
+ => []
376
+ [4] pry(main)>
377
+ ```
378
+
379
+ **For Operating systems without any known releases, an empty array is returned.**
380
+
381
+ ### Get all versions for an Operating System that are not EoL after a certain date
382
+
383
+ ```
384
+ [1] pry(main)> require 'puppet_metadata'
385
+ => true
386
+ [2] pry(main)> PuppetMetadata::OperatingSystem.supported_releases('CentOS', Date.parse('2025-04-15'))
387
+ => ["9", "10"]
388
+ [3] pry(main)>
389
+ ```
390
+
391
+ CentOS 8 and older aren't listed.
392
+ 8 is EoL since 2024-05-31.
393
+
394
+ ## Updating OS EOL dates
395
+
396
+ The EOL dates for operating systems are stored in `data/eol_dates.json` and are automatically updated weekly via GitHub Actions using data from [endoflife.date](https://endoflife.date/).
397
+
398
+ - For Amazon Linux, this is security support, not standard support.
399
+ - For Debian, this is extended life cycle, not standard support.
400
+ - For CentOS, this is security support, not active support.
401
+ - For OracleLinux, this is basic support, not extended support.
402
+ - For RedHat, this is maintenance support, not extended life cycle.
403
+ - For Rocky, this is security support, not active support.
404
+ - For SLES, this is general support, not long term service pack support.
405
+
406
+ To manually update the EOL dates:
407
+
408
+ ```bash
409
+ ./bin/update_eol_dates
410
+ ```
411
+
412
+ ### Adding new operating systems
413
+
414
+ To add a new operating system to the EOL tracking:
415
+
416
+ 1. Add an entry to the `OS_MAPPING` hash in `bin/update_eol_dates`
417
+ 2. Map it to the corresponding [endoflife.date product identifier](https://endoflife.date/docs/api/)
418
+ 3. Run `./bin/update_eol_dates` to fetch the data
419
+ 4. If the OS requires special handling (like Amazon Linux which uses multiple API endpoints), add a custom handler function
420
+
215
421
  ## List supported setfiles
216
422
 
217
423
  When running beaker on the CLI, you can specify a specific setfile. `puppet_metadata` provides `bin/setfiles` to list all setfiles:
data/bin/metadata2gha CHANGED
@@ -3,10 +3,7 @@ require 'optparse'
3
3
  require 'json'
4
4
  require 'puppet_metadata'
5
5
 
6
- PidfileWorkaround = Object.new
7
-
8
6
  options = {
9
- beaker_pidfile_workaround: false,
10
7
  domain: nil,
11
8
  minimum_major_puppet_version: nil,
12
9
  beaker_fact: nil,
@@ -14,20 +11,9 @@ options = {
14
11
  }
15
12
 
16
13
  OptionParser.new do |opts|
17
- opts.accept(PidfileWorkaround) do |value|
18
- case value
19
- when 'true'
20
- true
21
- when 'false'
22
- false
23
- else
24
- value.split(',')
25
- end
26
- end
27
-
28
14
  opts.banner = "Usage: #{$0} [options] metadata"
29
15
 
30
- opts.on('--pidfile-workaround VALUE', 'Generate the systemd PIDFile workaround to work around a docker bug', PidfileWorkaround) { |opt| options[:beaker_pidfile_workaround] = opt }
16
+ opts.on('-c', '--collection VALUE', 'Limit GitHub jobs to only test this specific collection') { |opt| options[:collection] = opt }
31
17
  opts.on('-d', '--domain VALUE', 'the domain for the box, only used when --use-fqdn is set to true') { |opt| options[:domain] = opt }
32
18
  opts.on('--minimum-major-puppet-version VERSION', "Don't create actions for Puppet versions less than this major version") { |opt| options[:minimum_major_puppet_version] = opt }
33
19
  opts.on('--beaker-facter FACT:LABEL:VALUES', 'Expand the matrix based on a fact. Separate values using commas') do |opt|
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # optparse subcommands inspired by https://gist.github.com/rkumar/445735
5
+
6
+ require 'optparse'
7
+ require 'optparse/date'
8
+
9
+ require 'puppet_metadata'
10
+
11
+ def main
12
+ subcommands = PuppetMetadata::BaseCommand.commands
13
+
14
+ options = {}
15
+
16
+ parsers = subcommands.transform_values { |cls| cls.parser(options) }
17
+
18
+ global = OptionParser.new do |opts|
19
+ opts.banner = "Usage: #{opts.program_name} [options] <action> [options]"
20
+ opts.on('--filename METADATA', 'Metadata filename') do |value|
21
+ options[:filename] = value
22
+ end
23
+
24
+ opts.separator ''
25
+ opts.separator 'ACTIONS'
26
+ width = subcommands.keys.max { |command, _parser| command.length }.length
27
+ parsers.each do |command, parser|
28
+ # TODO: positional argument
29
+ parser.banner = "Usage: #{opts.program_name} #{command} [options]"
30
+ opts.separator " #{command.ljust(width + 4)}#{parser.program_name}"
31
+ end
32
+ opts.separator ''
33
+ opts.separator "See '#{opts.program_name} ACTION --help' for more information on a specific action."
34
+ end
35
+
36
+ begin
37
+ global.order!
38
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
39
+ warn e.cause ? "#{e}: #{e.cause}" : e
40
+ warn ''
41
+ global.help_exit
42
+ end
43
+ unless (command = ARGV.shift)
44
+ puts global
45
+ exit 1
46
+ end
47
+ if (parser = parsers[command])
48
+ begin
49
+ arguments = parser.parse!
50
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
51
+ warn e.cause ? "#{e}: #{e.cause}" : e
52
+ warn ''
53
+ parser.help_exit
54
+ end
55
+
56
+ command = subcommands[command].new(arguments, options)
57
+ command.run
58
+ else
59
+ puts global
60
+ exit 1
61
+ end
62
+ end
63
+
64
+ main
data/bin/setfiles CHANGED
@@ -16,13 +16,14 @@ rescue StandardError => e
16
16
  end
17
17
 
18
18
  options = {
19
- beaker_pidfile_workaround: false,
20
19
  domain: 'example.com',
21
20
  minimum_major_puppet_version: nil,
22
21
  beaker_fact: nil,
23
22
  beaker_hosts: nil,
24
23
  }
25
24
 
25
+ warn 'Command deprecated - call puppet-metadata setfiles instead'
26
+
26
27
  metadata.github_actions(options).outputs[:puppet_beaker_test_matrix].each do |os|
27
28
  puts "BEAKER_SETFILE=\"#{os[:env]['BEAKER_SETFILE']}\""
28
29
  end
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This script updates the EOL dates data file using the endoflife.date API
5
+ # It can be run manually or via GitHub Actions automation
6
+
7
+ require 'json'
8
+ require 'net/http'
9
+ require 'uri'
10
+ require 'date'
11
+
12
+ # Mapping between internal OS names and endoflife.date identifiers; only OSes listed here will be updated
13
+ OS_MAPPING = {
14
+ 'AlmaLinux' => 'almalinux',
15
+ 'Amazon' => 'amazon-linux',
16
+ 'CentOS' => 'centos',
17
+ 'Debian' => 'debian',
18
+ 'OracleLinux' => 'oracle-linux',
19
+ 'Fedora' => 'fedora',
20
+ 'FreeBSD' => 'freebsd',
21
+ 'RedHat' => 'rhel',
22
+ 'Rocky' => 'rocky-linux',
23
+ 'Scientific' => nil, # Not in endoflife.date
24
+ 'SLES' => 'sles',
25
+ 'Ubuntu' => 'ubuntu',
26
+ }.freeze
27
+
28
+ def fetch_eol_data(product)
29
+ uri = URI("https://endoflife.date/api/#{product}.json")
30
+ response = Net::HTTP.get_response(uri)
31
+
32
+ unless response.is_a?(Net::HTTPSuccess)
33
+ warn "Failed to fetch data for #{product}: #{response.code} #{response.message}"
34
+ return nil
35
+ end
36
+
37
+ JSON.parse(response.body)
38
+ rescue StandardError => e
39
+ warn "Error fetching data for #{product}: #{e.message}"
40
+ nil
41
+ end
42
+
43
+ def parse_eol_date(eol_value)
44
+ # endoflife.date returns different formats:
45
+ # - Date string: "2024-06-30"
46
+ # - Boolean false: not yet EOL
47
+ # - Boolean true: EOL date unknown
48
+ case eol_value
49
+ when String
50
+ # Validate it's a proper date
51
+ Date.parse(eol_value)
52
+ eol_value
53
+ when false, 'false', true, 'true'
54
+ # false: Not yet EOL
55
+ # true: EOL date unknown - keep existing data or skip
56
+ nil
57
+ end
58
+ rescue Date::Error
59
+ nil
60
+ end
61
+
62
+ def update_os_data(os_name, product_id, current_data)
63
+ return current_data unless product_id
64
+
65
+ puts "Fetching data for #{os_name} (#{product_id})..."
66
+ api_data = fetch_eol_data(product_id)
67
+ return current_data unless api_data
68
+
69
+ updated_data = {}
70
+
71
+ api_data.each do |cycle|
72
+ # The API returns an array of cycles, each with:
73
+ # - cycle: version number
74
+ # - eol: end of life date or boolean
75
+ version = cycle['cycle'].to_s
76
+ eol_value = if os_name == 'Debian' && cycle.key?('extendedSupport')
77
+ cycle['extendedSupport']
78
+ else
79
+ cycle['eol']
80
+ end
81
+
82
+ eol = parse_eol_date(eol_value)
83
+
84
+ if eol
85
+ updated_data[version] = eol
86
+ puts " #{version}: #{eol}"
87
+ elsif eol_value == false || cycle['eol'] == false
88
+ # Track versions that aren't EOL yet
89
+ updated_data[version] = nil
90
+ puts " #{version}: not yet EOL"
91
+ end
92
+ end
93
+
94
+ current_data[os_name]&.each do |version, date|
95
+ unless updated_data.key?(version)
96
+ puts " Preserving #{version}: #{date || 'nil'} (not in API)"
97
+ updated_data[version] = date
98
+ end
99
+ end
100
+
101
+ updated_data
102
+ end
103
+
104
+ def handle_amazon_linux(current_data)
105
+ puts 'Fetching data for Amazon Linux...'
106
+
107
+ updated_data = {}
108
+
109
+ al_data = fetch_eol_data('amazon-linux')
110
+ al_data&.each do |cycle|
111
+ version = cycle['cycle'].to_s
112
+ eol = parse_eol_date(cycle['eol'])
113
+
114
+ # Map version "2" to "2.0" for compatibility with existing metadata entries
115
+ version = '2.0' if version == '2'
116
+
117
+ if eol
118
+ updated_data[version] = eol
119
+ puts " #{version}: #{eol}"
120
+ elsif cycle['eol'] == false
121
+ updated_data[version] = nil
122
+ puts " #{version}: not yet EOL"
123
+ end
124
+ end
125
+
126
+ current_data['Amazon']&.each do |version, date|
127
+ unless updated_data.key?(version)
128
+ puts " Preserving #{version}: #{date || 'nil'} (not in API)"
129
+ updated_data[version] = date
130
+ end
131
+ end
132
+
133
+ updated_data
134
+ end
135
+
136
+ def handle_centos(current_data)
137
+ puts 'Fetching data for CentOS...'
138
+
139
+ updated_data = {}
140
+
141
+ centos_data = fetch_eol_data('centos')
142
+ centos_data&.each do |cycle|
143
+ version = cycle['cycle'].to_s
144
+ eol = parse_eol_date(cycle['eol'])
145
+
146
+ if eol
147
+ updated_data[version] = eol
148
+ puts " #{version}: #{eol}"
149
+ elsif cycle['eol'] == false
150
+ updated_data[version] = nil
151
+ puts " #{version}: not yet EOL"
152
+ end
153
+ end
154
+
155
+ # Fetch CentOS Stream versions after CentOS. Stream takes precedence for overlapping versions.
156
+ stream_data = fetch_eol_data('centos-stream')
157
+ stream_data&.each do |cycle|
158
+ version = cycle['cycle'].to_s
159
+ eol = parse_eol_date(cycle['eol'])
160
+
161
+ if eol
162
+ updated_data[version] = eol
163
+ puts " #{version}: #{eol}"
164
+ elsif cycle['eol'] == false
165
+ updated_data[version] = nil
166
+ puts " #{version}: not yet EOL"
167
+ end
168
+ end
169
+
170
+ current_data['CentOS']&.each do |version, date|
171
+ unless updated_data.key?(version)
172
+ puts " Preserving #{version}: #{date || 'nil'} (not in API)"
173
+ updated_data[version] = date
174
+ end
175
+ end
176
+
177
+ updated_data
178
+ end
179
+
180
+ def handle_ubuntu(current_data)
181
+ puts 'Fetching data for Ubuntu...'
182
+
183
+ updated_data = {}
184
+
185
+ ubuntu_data = fetch_eol_data('ubuntu')
186
+ ubuntu_data&.each do |cycle|
187
+ version = cycle['cycle'].to_s
188
+ eol = parse_eol_date(cycle['eol'])
189
+
190
+ if eol
191
+ updated_data[version] = eol
192
+ puts " #{version}: #{eol}"
193
+ elsif cycle['eol'] == false
194
+ updated_data[version] = nil
195
+ puts " #{version}: not yet EOL"
196
+ end
197
+ end
198
+
199
+ current_data['Ubuntu']&.each do |version, date|
200
+ next if updated_data.key?(version)
201
+
202
+ puts " Preserving #{version}: #{date || 'nil'} (not in API)"
203
+ updated_data[version] = date
204
+ end
205
+
206
+ updated_data
207
+ end
208
+
209
+ def main
210
+ data_file = File.expand_path('../data/eol_dates.json', __dir__)
211
+
212
+ # Load current data
213
+ current_data = JSON.parse(File.read(data_file))
214
+
215
+ # Update each OS
216
+ updated_data = {}
217
+
218
+ OS_MAPPING.each do |os_name, product_id|
219
+ if os_name == 'Amazon'
220
+ updated_data[os_name] = handle_amazon_linux(current_data)
221
+ elsif os_name == 'CentOS'
222
+ updated_data[os_name] = handle_centos(current_data)
223
+ elsif os_name == 'Ubuntu'
224
+ updated_data[os_name] = handle_ubuntu(current_data)
225
+ elsif product_id
226
+ updated_data[os_name] = update_os_data(os_name, product_id, current_data)
227
+ else
228
+ puts "Preserving #{os_name} (not in endoflife.date API)"
229
+ updated_data[os_name] = current_data[os_name]
230
+ end
231
+ end
232
+
233
+ # Sort each OS's versions by EOL date (latest EOL first, then nulls)
234
+ sorted_data = {}
235
+ updated_data.each do |os_name, versions|
236
+ sorted_versions = versions.sort do |a, b|
237
+ version_a, eol_a = a
238
+ version_b, eol_b = b
239
+
240
+ # Handle nil values (not yet EOL) - they go first
241
+ return -1 if eol_a.nil? && !eol_b.nil?
242
+ return 1 if !eol_a.nil? && eol_b.nil?
243
+
244
+ if eol_a == eol_b
245
+ # Same EOL date (or both nil) - sort by version number descending
246
+ Gem::Version.new(version_b) <=> Gem::Version.new(version_a)
247
+ else
248
+ # Different EOL dates - sort by date descending (later date first)
249
+ (eol_b || '0000-00-00') <=> (eol_a || '0000-00-00')
250
+ end
251
+ end.to_h
252
+ sorted_data[os_name] = sorted_versions
253
+ end
254
+
255
+ File.write(data_file, "#{JSON.pretty_generate(sorted_data)}\n")
256
+ puts "\nUpdated #{data_file}"
257
+
258
+ if current_data == updated_data
259
+ puts 'No changes detected.'
260
+ exit 0
261
+ else
262
+ puts 'Changes detected!'
263
+ exit 1 # Exit with 1 to signal changes (useful for CI)
264
+ end
265
+ end
266
+
267
+ main if __FILE__ == $0