Skip to content

Commit

Permalink
Add alerts about unreleased changes to gems
Browse files Browse the repository at this point in the history
Alert GOV.UK teams about unreleased changes to the gems they own.

This should make us able to retire the https://github.com/alphagov/gem-release-alert repo
  • Loading branch information
MuriloDalRi committed Apr 10, 2024
1 parent 7c22110 commit 23e484a
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 0 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/gem_version_checker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: "Gem Version Checker"

on:
workflow_dispatch: {}
schedule:
- cron: '00 13 * * 3' # Runs at 13:00 UTC, Every Wednesday.

env:
SEAL_ORGANISATION: alphagov
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

jobs:
gem-version-checker:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Gem Version Checker
id: gem_version_checker
run: |
./bin/seal_runner.rb gems
File renamed without changes.
36 changes: 36 additions & 0 deletions lib/gem_version_checker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require "uri"
require "net/http"
require "json"

class GemVersionChecker
def detect_version_discrepancies(gem_name)
rubygems_version = fetch_rubygems_version(gem_name)
rubygems_version if rubygems_version && change_is_significant?(gem_name, rubygems_version)
end

def fetch_rubygems_version(gem_name)
uri = URI("https://rubygems.org/api/v1/gems/#{gem_name}.json")
res = Net::HTTP.get_response(uri)

JSON.parse(res.body)["version"] if res.is_a?(Net::HTTPSuccess)
end

def files_changed_since_tag(repo, tag)
Dir.mktmpdir do |path|
Dir.chdir(path) do
if system("git clone --recursive --depth 1 --shallow-submodules --no-single-branch https://github.com/alphagov/#{repo}.git > /dev/null 2>&1")
Dir.chdir(repo) { `git diff --name-only #{tag}`.split("\n") }
else
puts "Warning: Failed to clone #{repo}"
[]
end
end
end
end

def change_is_significant?(repo_name, previous_version)
files_changed_since_tag(repo_name, "v#{previous_version}").any? do |path|
path.start_with?("app/", "lib/") || path == "CHANGELOG.md"
end
end
end
30 changes: 30 additions & 0 deletions lib/message_builder.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "pathname"
require_relative "gem_version_checker"
require_relative "github_fetcher"
require_relative "message"

Expand All @@ -18,6 +19,8 @@ def build
build_dependapanda_message
when :ci
build_ci_message
when :gems
build_gems_message
else
build_regular_message
end
Expand Down Expand Up @@ -57,6 +60,33 @@ def build_ci_message
Message.new(ci_message, mood: "robot_face")
end

def build_gems_message
Message.new(gems_message, mood: "gem")
end

def gems_message
@alerts = all_govuk_gems.select { |gem| gem["team"] == team.channel }.map { |gem|
alert = GemVersionChecker.new.detect_version_discrepancies(gem["app_name"])
"<#{gem['links']['repo_url']}|#{gem['app_name']}> has unreleased changes since v#{alert}" if alert
}.compact

template_file = Pathname.new("#{TEMPLATE_DIR}/gem_alerts.text.erb")
ERB.new(template_file.read, trim_mode: "-").result(binding).strip
end

def all_govuk_gems
@all_govuk_gems ||= fetch_gems
end

def fetch_gems
res = Net::HTTP.get_response(URI("https://docs.publishing.service.gov.uk/gems.json"))

JSON.parse(res.body)
rescue StandardError => e
puts "Error fetching gems: #{e.message}"
[]
end

def ci_message
@repos = check_team_repos_ci.reject { |_, v| v }.keys
return nil if @repos.empty?
Expand Down
2 changes: 2 additions & 0 deletions lib/seal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def bark_at(team, mode: nil)
MessageBuilder.new(team, :ci).build if team.ci_checks
when "seal_prs"
MessageBuilder.new(team, :seal).build if team.seal_prs
when "gems"
MessageBuilder.new(team, :gems).build if team.gems
end

return if message.nil? || message.text.nil?
Expand Down
2 changes: 2 additions & 0 deletions lib/slack_poster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def assign_poster_settings
[":#{@season_symbol}angrier_seal:", "#{@season_name}Angry Seal"]
when "robot_face"
[":robot_face:", "Angry CI Robot"]
when "gem"
[":gem:", "Gem Release Robot"]
when "tea"
[":manatea:", "Tea Seal"]
when "charter"
Expand Down
3 changes: 3 additions & 0 deletions lib/team.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def initialize(
afternoon_seal_quotes: nil,
dependapanda: nil,
ci_checks: nil,
gems: nil,
compact: nil,
exclude_labels: nil,
exclude_titles: nil,
Expand All @@ -22,6 +23,7 @@ def initialize(
@afternoon_seal_quotes = (afternoon_seal_quotes.nil? ? false : afternoon_seal_quotes)
@dependapanda = (dependapanda.nil? ? false : dependapanda)
@ci_checks = (ci_checks.nil? ? false : ci_checks)
@gems = (gems.nil? ? false : gems)
@compact = (compact.nil? ? false : compact)
@quotes_days = quotes_days || []
@exclude_labels = exclude_labels || []
Expand All @@ -40,6 +42,7 @@ def initialize(
afternoon_seal_quotes
dependapanda
ci_checks
gems
compact
exclude_labels
exclude_titles
Expand Down
2 changes: 2 additions & 0 deletions lib/team_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def build_all_teams
raise "#{team_name} is a GOV.UK team and shouldn't list repos in ./config/alphagov.yml"
end
team_config["ci_checks"] = is_govuk_team?(team_name)
team_config["gems"] = is_govuk_team?(team_name)
Team.new(**apply_env(team_config))
end
end
Expand All @@ -60,6 +61,7 @@ def apply_env(config)
afternoon_seal_quotes: env["AFTERNOON_SEAL_QUOTES"] == "true" || config["afternoon_seal_quotes"],
dependapanda: env["DEPENDAPANDA"] == "true" || config["dependapanda"],
ci_checks: env["CI_CHECKS"] == "true" || config["ci_checks"],
gems: env["GEMS"] == "true" || config["gems"],
compact: env["COMPACT"] == "true" || config["compact"],
exclude_labels: env["GITHUB_EXCLUDE_LABELS"]&.split(",") || config["exclude_labels"],
exclude_titles: env["GITHUB_EXCLUDE_TITLES"]&.split(",") || config["exclude_titles"],
Expand Down
36 changes: 36 additions & 0 deletions spec/gem_version_checker_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require "./lib/gem_version_checker"

RSpec.describe GemVersionChecker do
subject(:gem_version_checker) { described_class.new }

let(:slack_poster) { instance_double(SlackPoster, send_request: nil) }

before do
allow(SlackPoster).to receive(:new).and_return(slack_poster)
end

it "fetches version number of a gem from rubygems" do
stub_rubygems_call("6.0.1")

expect(gem_version_checker.fetch_rubygems_version("example")).to eq("6.0.1")
end

it "detects when there are files changed since the last release that are built into the gem" do
stub_rubygems_call("1.2.2")
stub_files_changed_since_tag(["lib/foo.rb"])

expect(gem_version_checker.detect_version_discrepancies("example")).to match("1.2.2")
end

def stub_files_changed_since_tag(files)
allow(gem_version_checker).to receive(:files_changed_since_tag) do
files
end
end

def stub_rubygems_call(version)
repo = { "version": version }
stub_request(:get, "https://rubygems.org/api/v1/gems/example.json")
.to_return(status: 200, body: repo.to_json, headers: {})
end
end
3 changes: 3 additions & 0 deletions templates/gem_alerts.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<% @alerts.each do |alert| -%>
<%= alert %>
<% end -%>

0 comments on commit 23e484a

Please sign in to comment.