<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Github on Jon Seager</title><link>https://jnsgr.uk/tags/github/</link><description>Recent content in Github on Jon Seager</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Wed, 22 May 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://jnsgr.uk/tags/github/index.xml" rel="self" type="application/rss+xml"/><item><title>Tracking Releases &amp; CI Across Software Teams and Forges</title><link>https://jnsgr.uk/2024/05/tracking-software-across-teams/</link><pubDate>Wed, 22 May 2024 00:00:00 +0000</pubDate><guid>https://jnsgr.uk/2024/05/tracking-software-across-teams/</guid><description>&lt;h2 id="introduction" class="relative group"&gt;Introduction &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#introduction" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;In my day job at Canonical, I lead the teams developing &lt;a href="https://juju.is" target="_blank" rel="noreferrer"&gt;Juju&lt;/a&gt;, and a whole host of &lt;a href="https://charmhub.io" target="_blank" rel="noreferrer"&gt;charms&lt;/a&gt;. Charms are software packages used for deploying applications on any infrastructure you have available. The packages are portable, meaning you can use our PostgreSQL operator on AWS, on Azure, on Openstack, on Google Cloud, etc. We&amp;rsquo;re building up quite the portfolio of popular open source applications across data engineering, observability, identity, telco, MLOps and more. I won&amp;rsquo;t go into detail about Juju or charms in this post, but I likely will in a future post.&lt;/p&gt;
&lt;p&gt;The important thing for this post is that I look after &amp;gt;10 software teams, who use two different software forges (Github and Launchpad), and all push artifacts into both the &lt;a href="https://snapcraft.io" target="_blank" rel="noreferrer"&gt;Snap Store&lt;/a&gt; and the &lt;a href="https://charmhub.io" target="_blank" rel="noreferrer"&gt;Charmhub&lt;/a&gt;. I wanted a way to keep track of their releases, and provide a tool that my managers could use to do the same. Put simply, I wanted a unified view of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The latest Github releases&lt;/li&gt;
&lt;li&gt;The latest Launchpad tags&lt;/li&gt;
&lt;li&gt;&lt;a href="https://snapcraft.io/docs/channels" target="_blank" rel="noreferrer"&gt;Channels&lt;/a&gt; in the &lt;a href="https://snapcraft.io" target="_blank" rel="noreferrer"&gt;Snap Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://snapcraft.io/docs/channels" target="_blank" rel="noreferrer"&gt;Channels&lt;/a&gt; in &lt;a href="https://charmhub.io" target="_blank" rel="noreferrer"&gt;Charmhub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CI status&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In particular, I wanted an easy way to be able to tie releases/commits in a forge to specific revisions in our various stores. This would enable us to troubleshoot more easily when issues are reported in customer environments by making it easier to jump to the code for the specific revision they&amp;rsquo;re running.&lt;/p&gt;
&lt;p&gt;At the time I started working on this, the effort at Canonical to build the portfolio of charms was really ramping up - and the only way of tracking which team owned which charm (between product teams, our IS department and our datacentre team) was a giant spreadsheet which was perpetually out of date (obviously!). The one thing that could be relied upon was which team owned a repo on Github or Launchpad - so my hope was to also reliably answer the question &amp;ldquo;which team owns the &lt;code&gt;foo&lt;/code&gt; charm?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;You can see the result of this effort at &lt;a href="https://releases.juju.is" target="_blank" rel="noreferrer"&gt;https://releases.juju.is&lt;/a&gt;, and a sneak peek below:&lt;/p&gt;
&lt;p&gt;&lt;a href="01.png"&gt;
&lt;figure&gt;
&lt;picture
class="mx-auto my-0 rounded-md"
&gt;
&lt;source
srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_8ec233b885900655.webp 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_5063b8f1fab85f9c.webp 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_9ac040c8220b2ee0.webp 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_5d3375656c16ccf6.webp 1320w
"
sizes="100vw"
type="image/webp"
/&gt;
&lt;img
width="1433"
height="1208"
class="mx-auto my-0 rounded-md"
alt="preview of releases.juju.is"
loading="lazy" decoding="async"
src="https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_642e9dc9a1155c8.png" srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_616246e11e81436.png 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_642e9dc9a1155c8.png 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_ad0d13e729726686.png 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/01_hu_d6b0b1a7471e3135.png 1320w
"
sizes="100vw"
/&gt;
&lt;/picture&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="prior-art" class="relative group"&gt;Prior Art &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#prior-art" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I wasn&amp;rsquo;t the first to have these problems. The idea to start tracking information in this way came from the &lt;a href="https://releases.elementary.io/" target="_blank" rel="noreferrer"&gt;elementaryOS releases tracker&lt;/a&gt;. This was a tool they built for keeping track of their various repositories, and in particular those which had seen many commits since the last release. The code for the site is available &lt;a href="https://github.com/elementary/releases" target="_blank" rel="noreferrer"&gt;on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The elementaryOS tracker uses a &lt;a href="https://github.com/elementary/releases/blob/main/release.py" target="_blank" rel="noreferrer"&gt;Python script&lt;/a&gt; to scrape the Github API, which outputs a &lt;a href="https://github.com/elementary/releases/blob/main/_data/repos.json" target="_blank" rel="noreferrer"&gt;JSON representation&lt;/a&gt; of the state of their repositories. This is then parsed during the build of a &lt;a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll" target="_blank" rel="noreferrer"&gt;Jekyll site&lt;/a&gt;, which is published on Github Pages using &lt;a href="https://github.com/elementary/releases/blob/main/.github/workflows/build.yml" target="_blank" rel="noreferrer"&gt;a Github Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This actually got me very close, and in fact my first attempt was a trivial fork of this project with a few slight modifications to the Python script. However, their tool wasn&amp;rsquo;t designed to be used across multiple distinct teams, and there are some limitations such as not rendering Markdown in the release notes.&lt;/p&gt;
&lt;p&gt;I also wanted to make some stylistic changes. I&amp;rsquo;m not particularly familiar with &lt;a href="https://jekyllrb.com/" target="_blank" rel="noreferrer"&gt;Jekyll&lt;/a&gt; and have generally used &lt;a href="https://gohugo.io" target="_blank" rel="noreferrer"&gt;Hugo&lt;/a&gt; for such tasks. I did maintain a Jekyll version for some months, but my lack of (recent) familiarity with the Ruby &amp;amp; gems ecosystem was making updates and maintenance more tedious than I liked.&lt;/p&gt;
&lt;p&gt;Another tool I came across when I was looking to solve this was &lt;a href="https://git.sr.ht/~amolith/willow" target="_blank" rel="noreferrer"&gt;willow&lt;/a&gt; by &lt;a href="https://secluded.site/" target="_blank" rel="noreferrer"&gt;Amolith&lt;/a&gt;. According to the README:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Willow helps developers, sysadmins, and homelabbers keep up with software releases across arbitrary forge platforms, including full-featured forges like GitHub, GitLab, or Forgejo as well as more minimal options like cgit or stagit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is super close - but it doesn&amp;rsquo;t have the snap/charm tracking I sought, and probably won&amp;rsquo;t do given the scope of the project.&lt;/p&gt;
&lt;h2 id="releasegen" class="relative group"&gt;&lt;code&gt;releasegen&lt;/code&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#releasegen" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;As I started to modify the Python script used in the elementaryOS release tracker, it started to get quite large, and I became a little uncomfortable with the state of it. At the time I was reintroducing myself into Go after a couple of years out, and decided that I would essentially &amp;ldquo;start again&amp;rdquo; and write a tool for my release tracker from scratch.&lt;/p&gt;
&lt;p&gt;What I came up with is &lt;a href="https://github.com/jnsgruk/releasegen" target="_blank" rel="noreferrer"&gt;&lt;code&gt;releasegen&lt;/code&gt;&lt;/a&gt;. A terribly boring and unimaginative name that I originally intended to change, but never got around to! Nevertheless, &lt;code&gt;releasegen&lt;/code&gt; solved a number of problems for me even in its first release. The first important change was support for separating releases across multiple Github organisations and teams, through the use of a simple config format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Frontend&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;github&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;org&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;acme-corp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;frontend-bots&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ignores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;some-old-project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Backend&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;github&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;org&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;acme-corp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;backend-engineers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Packaging&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;launchpad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;project-groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;acme-corp-debs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can see the config file used to generated &lt;code&gt;releases.juju.is&lt;/code&gt; &lt;a href="https://github.com/canonical/charm-eng-releases/blob/main/releasegen.yaml" target="_blank" rel="noreferrer"&gt;on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The output format is heavily inspired by that of the original Python script, but has been enriched with a few more fields over time.&lt;/p&gt;
&lt;p&gt;This tool is really quite simple: it takes a config file which points it at a combination of Github Orgs/Teams and Launchpad project groups, and outputs a big JSON file containing details of releases and associated packages. You can see an example of the output &lt;a href="https://github.com/canonical/charm-eng-releases/blob/main/data/repos.json" target="_blank" rel="noreferrer"&gt;in the &lt;code&gt;charm-eng-releases&lt;/code&gt; repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of the main reasons I chose Go in the first place was because of it&amp;rsquo;s great support for concurrency. Data from Github is gathered using the &lt;a href="https://github.com/google/go-github" target="_blank" rel="noreferrer"&gt;&lt;code&gt;google/go-github&lt;/code&gt;&lt;/a&gt; package, which provides an interface to the Github API. When I first started adding repos to the original Python version, the run time quickly grew to 10+ minutes. My intention was to spin up a goroutine per repository in &lt;code&gt;releasegen&lt;/code&gt;, but I quickly ran into secondary rate-limits. It turns out that the Github API prohibits you from making &lt;a href="https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#about-secondary-rate-limits" target="_blank" rel="noreferrer"&gt;too many concurrent requests&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For Launchpad, things are more complicated. There is &lt;a href="https://help.launchpad.net/API/Hacking" target="_blank" rel="noreferrer"&gt;an API&lt;/a&gt; but it lacks methods for grabbing tags, specific commits etc. I also wanted to avoid cloning all of the repos for which information is gathered. The Launchpad code browsing UI is based on &lt;a href="https://git.zx2c4.com/cgit/about/" target="_blank" rel="noreferrer"&gt;cgit&lt;/a&gt;, and hasn&amp;rsquo;t fundamentally changed in a &lt;strong&gt;long&lt;/strong&gt; time. For now, releasegen relies upon scraping the Launchpad web pages (using &lt;a href="https://github.com/PuerkitoBio/goquery" target="_blank" rel="noreferrer"&gt;goquery&lt;/a&gt;) to get the information it needs. This is not ideal, but has been functioning better than you might expect for around 18 months. There&amp;rsquo;s also no limit on the requests that can be made to the web frontend of Launchpad - so processing is able to be done concurrently across repos in this case.&lt;/p&gt;
&lt;p&gt;In more recent times, &lt;code&gt;releasegen&lt;/code&gt; grew the ability to read badges out of project READMEs, and use those badges to link repos to a particular store (either the Snap store or the Charmhub). Support for reading badges and parsing Github CI badges was &lt;a href="https://github.com/jnsgruk/releasegen/pull/1" target="_blank" rel="noreferrer"&gt;kindly contributed by one of my colleagues&lt;/a&gt;. I have subsequently generalised that initial implementation and added support for the Snap store too.&lt;/p&gt;
&lt;h2 id="parsing-releasegen-with-hugo" class="relative group"&gt;Parsing &lt;code&gt;releasegen&lt;/code&gt; with Hugo &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#parsing-releasegen-with-hugo" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Pleasingly, Hugo has &lt;a href="https://gohugo.io/templates/data-templates/#the-data-directory" target="_blank" rel="noreferrer"&gt;the ability&lt;/a&gt; to read data from YAML, JSON, XML, or TOML files, which can then be rendered throughout the site.&lt;/p&gt;
&lt;p&gt;Given that this tool was to be used for tracking releases from Canonical teams, I created a basic &lt;a href="https://gohugo.io/methods/page/layout/" target="_blank" rel="noreferrer"&gt;layout&lt;/a&gt; using the excellent &lt;a href="https://vanillaframework.io/" target="_blank" rel="noreferrer"&gt;Vanilla Framework&lt;/a&gt;. A layout in Hugo is nothing more than a collection of HTML/CSS/JS into which data can be rendered. The entry point for that in my project is this &lt;a href="https://github.com/canonical/charm-eng-releases/blob/main/layouts/index.html" target="_blank" rel="noreferrer"&gt;index.html&lt;/a&gt;. Here I layout the basic structure of the page, and use a number of &lt;a href="https://gohugo.io/templates/partials/" target="_blank" rel="noreferrer"&gt;partials&lt;/a&gt; to render common components across the site. There are &lt;a href="https://github.com/canonical/charm-eng-releases/blob/85fb452086419a89ccba40dcbb2e811803e4e8e2/layouts/partials/head.html#L23-L90" target="_blank" rel="noreferrer"&gt;very few adjustments&lt;/a&gt; to the standard Vanilla Framework style, and a couple of &lt;a href="https://github.com/canonical/charm-eng-releases/tree/85fb452086419a89ccba40dcbb2e811803e4e8e2/layouts/partials/js" target="_blank" rel="noreferrer"&gt;small Javascript files&lt;/a&gt; to provide tabs, modals, expanding table rows, table sorting and suchlike.&lt;/p&gt;
&lt;p&gt;Aside from the big screenshot at the start of this post, I want to highlight a couple of details in the UI. Firstly, if there is an associated artifact for a given repository (a Snap or a Charm), the row can be expanded to show details of that artifact:&lt;/p&gt;
&lt;p&gt;&lt;a href="02.png"&gt;
&lt;figure&gt;
&lt;picture
class="mx-auto my-0 rounded-md"
&gt;
&lt;source
srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/02_hu_c41b3e0d03f6a18e.webp 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/02_hu_f1dd123fbb2f9d2.webp 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/02_hu_46498fc581f6e61a.webp 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/02_hu_d38212cfcbcbd772.webp 1259w
"
sizes="100vw"
type="image/webp"
/&gt;
&lt;img
width="1259"
height="263"
class="mx-auto my-0 rounded-md"
alt="expanding table row - snap"
loading="lazy" decoding="async"
src="https://jnsgr.uk/2024/05/tracking-software-across-teams/02_hu_85b93f4980a0ebdf.png" srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/02_hu_29f757e8f7822a6b.png 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/02_hu_85b93f4980a0ebdf.png 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/02_hu_3fcae7f9641442ab.png 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/02.png 1259w
"
sizes="100vw"
/&gt;
&lt;/picture&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="03.png"&gt;
&lt;figure&gt;
&lt;picture
class="mx-auto my-0 rounded-md"
&gt;
&lt;source
srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/03_hu_b0329c594fcd0618.webp 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/03_hu_8de96637cfd3f247.webp 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/03_hu_1ce173416e17e4a4.webp 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/03_hu_9da207ef9ac534da.webp 1242w
"
sizes="100vw"
type="image/webp"
/&gt;
&lt;img
width="1242"
height="314"
class="mx-auto my-0 rounded-md"
alt="expanding table row - charm"
loading="lazy" decoding="async"
src="https://jnsgr.uk/2024/05/tracking-software-across-teams/03_hu_3183affabd62bda0.png" srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/03_hu_4688fb9ace7ae291.png 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/03_hu_3183affabd62bda0.png 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/03_hu_7aa1a7ac2b613206.png 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/03.png 1242w
"
sizes="100vw"
/&gt;
&lt;/picture&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Each individual release can also be expanded using the &amp;ldquo;eye&amp;rdquo; icon, at which point a modal will be displayed containing the release notes, complete with links:&lt;/p&gt;
&lt;p&gt;&lt;a href="04.png"&gt;
&lt;figure&gt;
&lt;picture
class="mx-auto my-0 rounded-md"
&gt;
&lt;source
srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/04_hu_9d68e77cc86da46a.webp 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/04_hu_1b546c99549996f5.webp 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/04_hu_d5ff8000a49e06ba.webp 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/04_hu_dd7e38a22fb676a4.webp 1252w
"
sizes="100vw"
type="image/webp"
/&gt;
&lt;img
width="1252"
height="938"
class="mx-auto my-0 rounded-md"
alt="release modal"
loading="lazy" decoding="async"
src="https://jnsgr.uk/2024/05/tracking-software-across-teams/04_hu_fb4c258dc8add747.png" srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/04_hu_2dd050dc49333aa5.png 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/04_hu_fb4c258dc8add747.png 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/04_hu_7e2efabe0fe56726.png 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/04.png 1252w
"
sizes="100vw"
/&gt;
&lt;/picture&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There is also some visual distinction between those repos on Github, and those on Launchpad:&lt;/p&gt;
&lt;p&gt;&lt;a href="05.png"&gt;
&lt;figure&gt;
&lt;picture
class="mx-auto my-0 rounded-md"
&gt;
&lt;source
srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/05_hu_be2e5eadb6dfdad.webp"
sizes="100vw"
type="image/webp"
/&gt;
&lt;img
width="593"
height="257"
class="mx-auto my-0 rounded-md"
alt="forge icons"
loading="lazy" decoding="async"
src="https://jnsgr.uk/2024/05/tracking-software-across-teams/05.png"
/&gt;
&lt;/picture&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="publishing-with-github-actions" class="relative group"&gt;Publishing with Github Actions &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#publishing-with-github-actions" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The site is hosted on Github Pages, and if you&amp;rsquo;ve been following along, you&amp;rsquo;ll notice that the site would need to be &amp;ldquo;regenerated&amp;rdquo; for the information to remain fresh - Hugo is ultimately a &lt;em&gt;static site generator&lt;/em&gt;. This problem is solved in my case with Github Actions. I created a workflow which is triggered every hour to dump the latest report using &lt;code&gt;releasegen&lt;/code&gt;, regenerate the Hugo site, and commit the outcome to branch of the repo which is used to serve the page. The workflow itself is pretty simple, and can be seen &lt;a href="https://github.com/canonical/charm-eng-releases/blob/main/.github/workflows/build.yml" target="_blank" rel="noreferrer"&gt;on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="06.png"&gt;
&lt;figure&gt;
&lt;picture
class="mx-auto my-0 rounded-md"
&gt;
&lt;source
srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/06_hu_cf3ea7b874b1af79.webp 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/06_hu_49839baec41b63fb.webp 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/06_hu_d1ee8d9fb0d4f989.webp 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/06_hu_5444ded37d4e6bcb.webp 1118w
"
sizes="100vw"
type="image/webp"
/&gt;
&lt;img
width="1118"
height="960"
class="mx-auto my-0 rounded-md"
alt="github actions deployment workflow"
loading="lazy" decoding="async"
src="https://jnsgr.uk/2024/05/tracking-software-across-teams/06_hu_f28192cc3679aa46.png" srcset="https://jnsgr.uk/2024/05/tracking-software-across-teams/06_hu_a730a09504d5cd8d.png 330w,https://jnsgr.uk/2024/05/tracking-software-across-teams/06_hu_f28192cc3679aa46.png 660w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/06_hu_4d0986e56324c504.png 1024w
,https://jnsgr.uk/2024/05/tracking-software-across-teams/06.png 1118w
"
sizes="100vw"
/&gt;
&lt;/picture&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="summary" class="relative group"&gt;Summary &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#summary" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;This tool scratched an itch for me. It&amp;rsquo;s a little flawed in places: it doesn&amp;rsquo;t update in real time, &lt;code&gt;releasegen&lt;/code&gt; could do with some &lt;del&gt;more&lt;/del&gt; tests, and I&amp;rsquo;m not super proud of how it parses data about Launchpad projects. That said, it&amp;rsquo;s been fairly reliable over the past 18 months, and it does present information in a pretty consistent way (though I&amp;rsquo;m no designer!). I personally think it&amp;rsquo;s a good example of how things can be automated and simplified by combining existing tools in a short space of time.&lt;/p&gt;
&lt;p&gt;I have an idea about how to generalise the processing of data, which would both remove my reliance on the Github API, and also unify the approach for gathering information about Git repos across forges (making it easier to support the likes of Codeberg and Sourcehut). I read an interesting blog recently about &lt;a href="https://mediocregopher.com/posts/git-proxy" target="_blank" rel="noreferrer"&gt;Serving a Website from a Git Repo Without Cloning It&lt;/a&gt; which implies that a lot of the information I require such as commits, tags, etc. could be gleaned directly from the git endpoint over HTTP, but I haven&amp;rsquo;t yet looked in detail.&lt;/p&gt;
&lt;p&gt;It &lt;em&gt;did&lt;/em&gt; solve the problem of keeping a big spreadsheet up to date as a means of tracking project ownership, and it&amp;rsquo;s certainly proved a useful tool for me to understand the relationship between commits, Github Releases and released revisions in our stores across teams. Some of my managers/seniors have found it really valuable, others not so much - this is generally reflected by how complete the information is for each team&amp;rsquo;s repos. Over the past few months, I&amp;rsquo;ve had a couple of teams reach out to me and ask to be added despite them not being part of my org, because they see it as a useful tool, so there&amp;rsquo;s that!&lt;/p&gt;
&lt;p&gt;This tooling was also adopted by the &lt;a href="https://snapcrafters.org" target="_blank" rel="noreferrer"&gt;Snapcrafters&lt;/a&gt; who run their own version of the &lt;a href="https://snapcrafters.org/snap-packages/" target="_blank" rel="noreferrer"&gt;dashboard&lt;/a&gt;. There are some subtle differences here - the UI in this case also displays each snap&amp;rsquo;s base (e.g. &lt;code&gt;core18&lt;/code&gt;, &lt;code&gt;core22&lt;/code&gt;, etc.). You can see the source code for that &lt;a href="https://github.com/snapcrafters/snapcrafters.org" target="_blank" rel="noreferrer"&gt;on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s all for now! If you&amp;rsquo;ve built something similar or you think I&amp;rsquo;ve missed a trick, let me know!&lt;/p&gt;</description></item></channel></rss>