Skip to content

Commit c06d32e

Browse files
committed
Make easier to publish new versions by not having to type json
1 parent 7a679f9 commit c06d32e

4 files changed

Lines changed: 255 additions & 5 deletions

File tree

.github/workflows/publish-new-ruby-versions.yaml

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,37 @@ on:
66
ruby_versions:
77
type: string
88
required: true
9-
description: List of ruby versions to build. Should be an array ["3.3.1","3.2.4"]
9+
description: Comma or newline-separated Ruby versions (example: 3.3.1, 3.2.4)
1010
image_versions:
1111
type: string
12-
required: true
13-
description: List of image versions to build. Should be an array ["ruby-1.1.0"]
12+
required: false
13+
description: Optional comma or newline-separated image versions (example: 1.1.0, 1.2.0 or ruby-1.1.0). If empty, latest ruby-* tag is used.
1414

1515
jobs:
16+
setup:
17+
name: Normalize Inputs
18+
runs-on: ubuntu-24.04
19+
outputs:
20+
ruby_versions_json: ${{ steps.normalize.outputs.ruby_versions_json }}
21+
image_versions_json: ${{ steps.normalize.outputs.image_versions_json }}
22+
steps:
23+
- name: Checkout (GitHub)
24+
uses: actions/checkout@v6
25+
26+
- id: normalize
27+
run: |
28+
ruby bin/normalize-publish-inputs \
29+
--ruby-versions '${{ github.event.inputs.ruby_versions }}' \
30+
--image-versions '${{ github.event.inputs.image_versions }}' \
31+
--repository '${{ github.repository }}'
32+
1633
publish:
1734
name: Publish Images
35+
needs: setup
1836
uses: ./.github/workflows/publish-images-reusable.yaml
1937
with:
20-
ruby_versions_json: ${{ github.event.inputs.ruby_versions }}
21-
image_versions_json: ${{ github.event.inputs.image_versions }}
38+
ruby_versions_json: ${{ needs.setup.outputs.ruby_versions_json }}
39+
image_versions_json: ${{ needs.setup.outputs.image_versions_json }}
2240
repository_owner: ${{ github.repository_owner }}
2341
secrets:
2442
gh_token: ${{ secrets.GITHUB_TOKEN }}

bin/normalize-publish-inputs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require "bundler/setup"
5+
require "optparse"
6+
require_relative "../lib/commands/publish_input_normalizer"
7+
8+
options = {
9+
ruby_versions: nil,
10+
image_versions: "",
11+
repository: ENV["GITHUB_REPOSITORY"]
12+
}
13+
14+
parser = OptionParser.new do |opts|
15+
opts.banner = "Usage: ruby bin/normalize-publish-inputs --ruby-versions LIST [--image-versions LIST] [--repository OWNER/REPO]"
16+
17+
opts.on("--ruby-versions VALUE", "Comma/newline-separated Ruby versions") do |value|
18+
options[:ruby_versions] = value
19+
end
20+
21+
opts.on("--image-versions VALUE", "Optional comma/newline-separated image versions") do |value|
22+
options[:image_versions] = value
23+
end
24+
25+
opts.on("--repository VALUE", "Repository in OWNER/REPO format (defaults to GITHUB_REPOSITORY)") do |value|
26+
options[:repository] = value
27+
end
28+
29+
opts.on("-h", "--help", "Show this help") do
30+
puts opts
31+
exit 0
32+
end
33+
end
34+
35+
begin
36+
parser.parse!
37+
38+
unless options[:ruby_versions] && !options[:ruby_versions].strip.empty?
39+
warn "--ruby-versions is required"
40+
warn parser
41+
exit 1
42+
end
43+
44+
result = Commands::PublishInputNormalizer.call(
45+
ruby_versions_input: options[:ruby_versions],
46+
image_versions_input: options[:image_versions],
47+
repository: options[:repository]
48+
)
49+
50+
if ENV["GITHUB_OUTPUT"]
51+
File.open(ENV["GITHUB_OUTPUT"], "a") do |f|
52+
f.puts "ruby_versions_json=#{result[:ruby_versions_json]}"
53+
f.puts "image_versions_json=#{result[:image_versions_json]}"
54+
end
55+
else
56+
puts JSON.pretty_generate({
57+
ruby_versions_json: result[:ruby_versions_json],
58+
image_versions_json: result[:image_versions_json]
59+
})
60+
end
61+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
62+
warn e.message
63+
warn parser
64+
exit 1
65+
rescue Commands::PublishInputNormalizer::Error => e
66+
warn e.message
67+
exit 1
68+
end
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
require "open3"
5+
require "rubygems"
6+
7+
module Commands
8+
class PublishInputNormalizer
9+
class Error < StandardError; end
10+
11+
class << self
12+
def call(ruby_versions_input:, image_versions_input:, repository:, latest_tag_fetcher: nil)
13+
new(
14+
ruby_versions_input: ruby_versions_input,
15+
image_versions_input: image_versions_input,
16+
repository: repository,
17+
latest_tag_fetcher: latest_tag_fetcher
18+
).call
19+
end
20+
end
21+
22+
attr_reader :ruby_versions_input, :image_versions_input, :repository, :latest_tag_fetcher
23+
24+
def initialize(ruby_versions_input:, image_versions_input:, repository:, latest_tag_fetcher: nil)
25+
@ruby_versions_input = ruby_versions_input.to_s
26+
@image_versions_input = image_versions_input.to_s
27+
@repository = repository.to_s
28+
@latest_tag_fetcher = latest_tag_fetcher || method(:fetch_latest_image_tag)
29+
end
30+
31+
def call
32+
ruby_versions = normalize_list(ruby_versions_input)
33+
raise Error, "Ruby versions input is empty" if ruby_versions.empty?
34+
35+
images_source = image_versions_input
36+
if blank_list?(images_source)
37+
images_source = latest_tag_fetcher.call(repository)
38+
end
39+
40+
image_versions = normalize_image_versions(normalize_list(images_source))
41+
raise Error, "Image versions input is empty" if image_versions.empty?
42+
43+
{
44+
ruby_versions: ruby_versions,
45+
image_versions: image_versions,
46+
ruby_versions_json: JSON.generate(ruby_versions),
47+
image_versions_json: JSON.generate(image_versions)
48+
}
49+
end
50+
51+
private
52+
53+
def blank_list?(value)
54+
value.to_s.gsub(/[\s,]/, "").empty?
55+
end
56+
57+
def normalize_list(value)
58+
value
59+
.to_s
60+
.split(/[\n,]/)
61+
.map(&:strip)
62+
.reject(&:empty?)
63+
end
64+
65+
def normalize_image_versions(versions)
66+
versions.map { |version| version.start_with?("ruby-") ? version : "ruby-#{version}" }
67+
end
68+
69+
def fetch_latest_image_tag(repo)
70+
raise Error, "Repository is required to discover latest image tag" if repo.empty?
71+
72+
stdout, status = Open3.capture2(
73+
"git",
74+
"ls-remote",
75+
"--tags",
76+
"--refs",
77+
"https://github.com/#{repo}.git",
78+
"refs/tags/ruby-*"
79+
)
80+
81+
unless status.success?
82+
raise Error, "Unable to resolve latest ruby-* image tag"
83+
end
84+
85+
tags = stdout.lines.map do |line|
86+
ref = line.split[1]
87+
ref&.sub("refs/tags/", "")
88+
end.compact
89+
90+
latest = tags.max_by { |tag| version_for(tag) }
91+
raise Error, "Unable to resolve latest ruby-* image tag" unless latest
92+
93+
latest
94+
end
95+
96+
def version_for(tag)
97+
raw = tag.sub(/^ruby-/, "")
98+
Gem::Version.new(raw)
99+
rescue ArgumentError
100+
Gem::Version.new("0")
101+
end
102+
end
103+
end
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require "minitest/autorun"
5+
require_relative "../../lib/commands/publish_input_normalizer"
6+
7+
class Commands::PublishInputNormalizerTest < Minitest::Test
8+
def test_normalizes_comma_and_newline_lists
9+
result = Commands::PublishInputNormalizer.call(
10+
ruby_versions_input: "3.3.1, 3.2.4\n3.1.9",
11+
image_versions_input: "1.1.0, ruby-1.2.0",
12+
repository: "rails/devcontainer"
13+
)
14+
15+
assert_equal ["3.3.1", "3.2.4", "3.1.9"], result[:ruby_versions]
16+
assert_equal ["ruby-1.1.0", "ruby-1.2.0"], result[:image_versions]
17+
assert_equal '["3.3.1","3.2.4","3.1.9"]', result[:ruby_versions_json]
18+
assert_equal '["ruby-1.1.0","ruby-1.2.0"]', result[:image_versions_json]
19+
end
20+
21+
def test_uses_latest_image_tag_when_image_versions_blank
22+
fetcher = ->(_repo) { "ruby-2.0.0" }
23+
24+
result = Commands::PublishInputNormalizer.call(
25+
ruby_versions_input: "3.3.1",
26+
image_versions_input: " , \n",
27+
repository: "rails/devcontainer",
28+
latest_tag_fetcher: fetcher
29+
)
30+
31+
assert_equal ["ruby-2.0.0"], result[:image_versions]
32+
assert_equal '["ruby-2.0.0"]', result[:image_versions_json]
33+
end
34+
35+
def test_raises_for_empty_ruby_versions
36+
error = assert_raises(Commands::PublishInputNormalizer::Error) do
37+
Commands::PublishInputNormalizer.call(
38+
ruby_versions_input: " , \n",
39+
image_versions_input: "1.1.0",
40+
repository: "rails/devcontainer"
41+
)
42+
end
43+
44+
assert_match(/Ruby versions input is empty/, error.message)
45+
end
46+
47+
def test_raises_when_latest_tag_is_missing
48+
fetcher = ->(_repo) { nil }
49+
50+
error = assert_raises(Commands::PublishInputNormalizer::Error) do
51+
Commands::PublishInputNormalizer.call(
52+
ruby_versions_input: "3.3.1",
53+
image_versions_input: "",
54+
repository: "rails/devcontainer",
55+
latest_tag_fetcher: fetcher
56+
)
57+
end
58+
59+
assert_match(/Image versions input is empty/, error.message)
60+
end
61+
end

0 commit comments

Comments
 (0)