Skip to content

Commit

Permalink
Automagic build
Browse files Browse the repository at this point in the history
  • Loading branch information
mattiapenati committed Sep 1, 2023
1 parent 421121d commit 2450645
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 0 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/dirty-work.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: It's too hard to keep everything updated by hand

on:
push:
branches:
- 'main'

env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
list-missing-versions:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- name: List versions
run: deno run -A ./ci/list_versions_to_build.ts
169 changes: 169 additions & 0 deletions ci/list_versions_to_build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import "https://deno.land/std@0.201.0/dotenv/load.ts";
import { Octokit } from "https://esm.sh/octokit@3.1.0?dts";

interface Version {
readonly major: number;
readonly minor: number;
readonly patch: number;
}

function versionCmp(a: Version, b: Version) {
// lexicographic comparison
const aa = a.major * 1_000_000 + a.minor * 1_000 + a.patch;
const bb = b.major * 1_000_000 + b.minor * 1_000 + b.patch;
return (aa < bb) ? -1 : ((aa > bb) ? 1 : 0);
}

const dockerBaseUrl = "https://hub.docker.com";

async function dockerLogin(
options: { username: string; password: string },
): Promise<string> {
interface Response {
token: string;
}

const url = `${dockerBaseUrl}/v2/users/login`;
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(options),
});

if (response.status !== 200) {
throw new Error("failed to login");
}

return response.json().then((data: Response) => data.token);
}

async function dockerListTags(
token: string,
{ namespace, repository }: {
namespace: string;
repository: string;
},
) {
interface Response {
count: number;
results: {
name: string;
}[];
}

let allNames: string[] = [];
const page = 0;
for (;;) {
const pageSize = 100;
const url = (page && page >= 0)
? `${dockerBaseUrl}/v2/namespaces/${namespace}/repositories/${repository}/tags?page=${page}&page_size=${pageSize}`
: `${dockerBaseUrl}/v2/namespaces/${namespace}/repositories/${repository}/tags?page_size=${pageSize}`;
const response = await fetch(url, {
headers: { "Authorization": `Bearer ${token}` },
});

if (response.status !== 200) {
throw new Error("failed to download list of tags");
}

const body: Response = await response.json();
allNames = allNames.concat(body.results.map((r) => r.name));

if (allNames.length < body.count) {
break;
}
}

// filter only valid triplet
const validVersionRegExp = /^(\d+)\.(\d+)\.(\d+)$/;
return allNames
.map((name) => {
const matches = name.match(validVersionRegExp);
if (matches) {
const version = {
major: Number.parseInt(matches[1]),
minor: Number.parseInt(matches[2]),
patch: Number.parseInt(matches[3]),
};
return version;
}
})
.filter((x): x is Version => x !== undefined)
.sort(versionCmp);
}

async function githubListContainerTags(token: string, { packageName }: {
packageName: string;
}) {
const octokit = new Octokit({ auth: token });

const api = octokit.rest.packages
.getAllPackageVersionsForPackageOwnedByAuthenticatedUser;
const packageVersion = octokit.paginate.iterator(api, {
package_type: "container",
package_name: packageName,
per_page: 100,
});

let allTags: string[] = [];
for await (const { data: packages } of packageVersion) {
for (const { metadata: packageMetadata } of packages) {
const tags = packageMetadata.container.tags as string[];
if (tags.length > 0) {
allTags = allTags.concat(tags);
}
}
}

// filter only valid triplet
const validTagRegExp = /^(\d+)\.(\d+)\.(\d+)$/;
return allTags
.map((name) => {
const matches = name.match(validTagRegExp);
if (matches) {
const version = {
major: Number.parseInt(matches[1]),
minor: Number.parseInt(matches[2]),
patch: Number.parseInt(matches[3]),
};
return version;
}
})
.filter((x): x is Version => x !== undefined)
.sort(versionCmp);
}

async function main() {
// fetch list of all docker alpine images
const dockerUsername = Deno.env.get("DOCKER_USERNAME") as string;
const dockerPassword = Deno.env.get("DOCKER_PASSWORD") as string;
const dockerToken = await dockerLogin({
username: dockerUsername,
password: dockerPassword,
});
const allAlpineTags = await dockerListTags(dockerToken, {
namespace: "library",
repository: "alpine",
});

// fetch list of all package tags
const githubToken = Deno.env.get("GITHUB_TOKEN") as string;
const allPackageTags = await githubListContainerTags(githubToken, {
packageName: "mock-vsftpd",
});

const latestPackageVersion = allPackageTags[allPackageTags.length - 1];

const newestAlpineTags = allAlpineTags.filter((alpineTag) =>
versionCmp(latestPackageVersion, alpineTag) < 0
);

const value = JSON.stringify(newestAlpineTags.map((v) => `${v.major}.${v.minor}.${v.patch}`));
const name = "tags";
console.log(`${name}=${value}`);

// workaroung for octokit
Deno.exit(0);
}

main();

0 comments on commit 2450645

Please sign in to comment.