Skip to content

Commit

Permalink
ci: add architectures update script
Browse files Browse the repository at this point in the history
  • Loading branch information
ttshivers committed Oct 8, 2020
1 parent f17e9e7 commit bbccd78
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/update-architectures.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: update-architectures

on:
# Convert to schedule when done or whatever is preferred
push:
pull_request:

jobs:
update-architectures:
name: update-architectures
runs-on: ubuntu-latest

steps:
- name: Checkout the docker-node repo
uses: actions/checkout@v2
with:
path: docker-node

- name: Checkout the official-images repo
uses: actions/checkout@v2
with:
path: official-images
repository: docker-library/official-images

- name: Download bashbrew
run: |
mkdir -p ${GITHUB_WORKSPACE}/bin
wget --no-verbose -O ${GITHUB_WORKSPACE}/bin/bashbrew https://doi-janky.infosiftr.net/job/bashbrew/job/master/lastSuccessfulBuild/artifact/bashbrew-amd64
sudo chmod +x ${GITHUB_WORKSPACE}/bin/bashbrew
echo "::add-path::${GITHUB_WORKSPACE}/bin"
- name: Update architectures
uses: actions/github-script@v3
id: arch-updater
env:
BASHBREW_LIBRARY: "${{ github.workspace }}/official-images/library"
with:
script: |
const script = require(`${process.env.GITHUB_WORKSPACE}/docker-node/updateArches.js`)
return script();
- name: Open a PR
if: steps.arch-updater.outputs.result == 'true'
# TODO: open a PR
run: |
cd docker-node
git diff --exit-code
146 changes: 146 additions & 0 deletions updateArches.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
const { execFileSync } = require('child_process');
const { readFileSync, readdirSync, writeFileSync } = require('fs');
const path = require('path');

const nodeDirRegex = /^\d+$/;

// Given a name and a tag, this returns an array of architectures that it supports
const fetchImageArches = (repoTag) => execFileSync('bashbrew', [
'cat', repoTag,
], { encoding: 'utf8' }).split('\n')
.find((line) => line.startsWith('Architectures:'))
.split(':')[1]
.trim()
.split(/\s*,\s*/);

// Parses an "architectures" file into an object like:
// {
// arch1: ['variant1', 'variant2'],
// //...
// }
const parseArchitecturesFile = (file) => Object.fromEntries(
[...readFileSync(file, 'utf8').matchAll(/^(?<arch>\S+)\s+(?<variants>\S+)$/mg)]
.slice(1)
.map(({ groups: { arch, variants } }) => [arch, variants.split(',')]),
);

// Takes in an object like:
// {
// arch1: ['variant1', 'variant2'],
// // ...
// }
// and returns an object like
// {
// variant1: ['arch1', 'arch2'],
// // ...
// }
const invertObject = (obj) => Object.entries(obj)
.reduce((acc, [key, vals]) => vals.reduce((valAcc, val) => {
const { [val]: keys, ...rest } = valAcc;
return {
...rest,
[val]: keys
? [...keys, key]
: [key],
};
}, acc), {});

// Returns a list of the child directories in the given path
const getChildDirectories = (parent) => readdirSync(parent, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map(({ name }) => path.resolve(parent, name));

const getNodeVerionDirs = (base) => getChildDirectories(base)
.filter((childPath) => nodeDirRegex.test(path.basename(childPath)));

// Assume no duplicates
const areArraysEquilivant = (arches1, arches2) => arches1.length === arches2.length
&& arches1.every((arch) => arches2.includes(arch));

// Returns the paths of Dockerfiles that are at: base/*/Dockerfile
const getDockerfilesInChildDirs = (base) => getChildDirectories(base)
.map((childDir) => path.resolve(childDir, 'Dockerfile'));

// Given a path to a Dockerfile like .../14/variant/Dockerfile, this will return "variant"
const getVariantFromPath = (file) => path.dirname(file).split(path.sep).slice(-1);

const getBaseImageFromDockerfile = (file) => readFileSync(file, 'utf8')
.match(/^FROM (\S+)/m)[1];

// Given a dockerfile, this function returns an array like [variant, [arch1, arch2, ...]]
const getVariantAndArches = (dockerfile) => {
const variant = getVariantFromPath(dockerfile);
const baseImage = getBaseImageFromDockerfile(dockerfile);
const arches = fetchImageArches(baseImage);

// TODO: filter by arches node supports
return [variant, arches];
};

const getStoredVariantArches = (file) => {
const storedArchVariants = parseArchitecturesFile(file);
return invertObject(storedArchVariants);
};

const areVariantArchesEquilivant = (current, stored) => Object.keys(current).length
=== Object.keys(stored).length
&& Object.entries(current).every(
([variant, arches]) => stored[variant] && areArraysEquilivant(arches, stored[variant]),
);

const formatEntry = ([arch, variants], variantOffset) => `${arch}${' '.repeat(variantOffset - arch.length)}${variants.join(',')}`;

const sortObjectKeys = (obj) => Object.keys(obj)
.sort()
.reduce((acc, key) => ({
...acc,
[key]: obj[key]
}), {});

const storeArchitectures = (variantArches, architecturesFile) => {
const archVariants = sortObjectKeys(invertObject(variantArches));
const data = {
'bashbrew-arch': ['variants'],
...archVariants,
};

const maxKeyLength = Math.max(...Object.keys(data).map((key) => key.length));
// Variants start 2 spaces after the longest key
const variantOffset = maxKeyLength + 2;

const str = Object.entries(data)
.map((entry) => formatEntry(entry, variantOffset))
.join('\n') + '\n';

writeFileSync(architecturesFile, str);

// Just here for debugging purposes
console.log(str);
console.log('\n\n');
};

const updateNodeDirArches = (nodeDir) => {
const dockerfiles = getDockerfilesInChildDirs(nodeDir);

const currentVariantArches = Object.fromEntries(dockerfiles.map(getVariantAndArches));
const architecturesFile = path.resolve(nodeDir, 'architectures');
const storedVariantArches = getStoredVariantArches(architecturesFile);

if (areVariantArchesEquilivant(currentVariantArches, storedVariantArches)) {
console.log('Architectures up-to-date: ', nodeDir);
return false;
}

console.log('Architectures outdated: ', nodeDir);
storeArchitectures(currentVariantArches, architecturesFile);

return true;
};

const updateArchitectures = () => {
const nodeDirs = getNodeVerionDirs(__dirname);
const dirsUpdated = nodeDirs.map(updateNodeDirArches);
return dirsUpdated.some((updated) => updated);
};

module.exports = updateArchitectures;

0 comments on commit bbccd78

Please sign in to comment.