Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add update.py semi-automatic release crawler #1

Merged
merged 14 commits into from
Jan 4, 2023
74 changes: 74 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
tag_filter: &tag_filter
filters:
tags:
only: /.*/
branches:
ignore: /.*/

version: 2
jobs:
build:
machine:
image: circleci/classic:latest
steps:
- checkout
- run:
name: Build Docker image
command: |
git describe --tags --always > version
docker build -t octomike/${CIRCLE_PROJECT_REPONAME,,} .
Remi-Gau marked this conversation as resolved.
Show resolved Hide resolved
mkdir -p ${HOME}/docker
docker save "octomike/${CIRCLE_PROJECT_REPONAME,,}" > ~/docker/image.tar
no_output_timeout: 30m # MCR is a large download
- persist_to_workspace:
root: /home/circleci
paths:
- docker/image.tar
test:
machine:
image: circleci/classic:latest
steps:
- attach_workspace:
at: /tmp/workspace
- run:
name: Test Docker image
command: |
docker load -i /tmp/workspace/docker/image.tar
# figure out a better test
docker run -ti --rm --read-only --entrypoint /bin/sh octomike/${CIRCLE_PROJECT_REPONAME,,} -c 'test -d $MCRPath/runtime/glnxa64'
deploy:
docker:
- image: circleci/buildpack-deps:stretch
steps:
- attach_workspace:
at: /tmp/workspace
- setup_remote_docker
- run: docker load -i /tmp/workspace/docker/image.tar
- run:
name: Publish Docker image
command: |
if [[ -n "${CIRCLE_TAG}" ]]; then
echo "${DOCKER_PASS}" | docker login --username "${DOCKER_USER}" --password-stdin
docker tag octomike/${CIRCLE_PROJECT_REPONAME,,} octomike/${CIRCLE_PROJECT_REPONAME,,}:${CIRCLE_TAG}
docker push octomike/${CIRCLE_PROJECT_REPONAME,,}:${CIRCLE_TAG}
# for tags like 9.7.5-core, share a major tag 9.7-core as well
MAJOR_TAG=$(echo "${CIRCLE_TAG}" | sed -rn 's#([[:digit:]]+).([[:digit:]]+).([[:digit:]]+)(.*)#\1.\2\4#p')
if [[ -n "${MAJOR_TAG}" ]] ; then
docker tag octomike/${CIRCLE_PROJECT_REPONAME,,} octomike/${CIRCLE_PROJECT_REPONAME,,}:${MAJOR_TAG}
docker push octomike/${CIRCLE_PROJECT_REPONAME,,}:${MAJOR_TAG}
fi
fi
workflows:
version: 2
build-test-deploy:
jobs:
- build:
<<: *tag_filter
- test:
requires:
- build
<<: *tag_filter
- deploy:
requires:
- test
<<: *tag_filter
42 changes: 42 additions & 0 deletions Dockerfile-core.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
FROM bids/base_validator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only BIDS-related item: why not base directly on,e.g., ubuntu?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea why this is the base image. Is it containing something that BIDS-Apps expect?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't remember now but given the --skip-bids-validator flag, I assume that a BIDS-App would by default validate the BIDS input and therefore expect bids-validator to be available.

Copy link

@sappelhoff sappelhoff May 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bids/base_validator is ubuntu with bids-validator installed. But it seems to be very outdated as well: https://github.com/BIDS-Apps/base_validator/blob/master/Dockerfile

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That one could probably be based on node:current-slim

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If bids/base_validator is meant to be a base image on which to base BIDS app then node:current-slim would probably not be first choice. Even fmriprep is not based on bids/base_validator but a very specific image (ubuntu:xenial-20200114). Not sure one can impose an base, therefore bids/base_validator and bids/matlab-compiler-runtime are more examples than base images?

Copy link

@sappelhoff sappelhoff May 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure one can impose an base, therefore bids/base_validator and bids/matlab-compiler-runtime are more examples than base images?

perhaps. 🤷‍♂️ I'll ask about this in an upcoming meeting with other BIDS folks who may know.


# Update system
RUN apt-get -qq update && apt-get -qq install -y \
unzip \
xorg \
wget && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Install MATLAB MCR
ENV MATLAB_VERSION %%MATLAB_VERSION%%
RUN mkdir /opt/mcr_install && \
mkdir /opt/mcr && \
wget --quiet -P /opt/mcr_install %%MCR_LINK%% && \
unzip -q /opt/mcr_install/*${MATLAB_VERSION}*.zip -d /opt/mcr_install && \
cd /opt/mcr_install && mkdir save && \
for f in $(grep -E '(xml|enc)$' productdata/1000.txt) ; do cp --parents archives/$f save/ ; done && \
for f in $(grep -E '(xml|enc)$' productdata/35000.txt) ; do cp --parents archives/$f save/ ; done && \
for f in $(grep -E '(xml|enc)$' productdata/35010.txt) ; do cp --parents archives/$f save/ ; done && \
rm -rf archives && mv save/archives . && rmdir save && \
/opt/mcr_install/install -destinationFolder /opt/mcr -agreeToLicense yes -mode silent && \
rm -rf /opt/mcr_install /tmp/* && \
rm -rf /opt/mcr/*/cefclient && \
rm -rf /opt/mcr/*/mcr/toolbox/matlab/maps && \
rm -rf /opt/mcr/*/java/jarext && \
rm -rf /opt/mcr/*/toolbox/matlab/system/editor && \
rm -rf /opt/mcr/*/toolbox/matlab/codetools && \
rm -rf /opt/mcr/*/toolbox/matlab/datatools && \
rm -rf /opt/mcr/*/toolbox/matlab/codeanalysis && \
rm -rf /opt/mcr/*/toolbox/shared/dastudio && \
rm -rf /opt/mcr/*/toolbox/shared/mlreportgen && \
rm -rf /opt/mcr/*/sys/java/jre/glnxa64/jre/lib/ext/jfxrt.jar && \
rm -rf /opt/mcr/*/sys/java/jre/glnxa64/jre/lib/amd64/libjfxwebkit.so && \
rm -rf /opt/mcr/*/bin/glnxa64/libQt* && \
rm -rf /opt/mcr/*/bin/glnxa64/qtwebengine && \
rm -rf /opt/mcr/*/bin/glnxa64/cef_resources

# Configure environment
ENV MCR_VERSION %%MCR_VERSION%%
ENV LD_LIBRARY_PATH /opt/mcr/${MCR_VERSION}/runtime/glnxa64:/opt/mcr/${MCR_VERSION}/bin/glnxa64:/opt/mcr/${MCR_VERSION}/sys/os/glnxa64:/opt/mcr/${MCR_VERSION}/sys/opengl/lib/glnxa64
ENV MCR_INHIBIT_CTF_LOCK 1
ENV MCRPath /opt/mcr/${MCR_VERSION}
Remi-Gau marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 5 additions & 5 deletions Dockerfile → Dockerfile-full.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ RUN apt-get -qq update && apt-get -qq install -y \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Install MATLAB MCR
ENV MATLAB_VERSION R2016b
ENV MATLAB_VERSION %%MATLAB_VERSION%%
RUN mkdir /opt/mcr_install && \
mkdir /opt/mcr && \
wget --quiet -P /opt/mcr_install http://www.mathworks.com/supportfiles/downloads/${MATLAB_VERSION}/deployment_files/${MATLAB_VERSION}/installers/glnxa64/MCR_${MATLAB_VERSION}_glnxa64_installer.zip && \
unzip -q /opt/mcr_install/MCR_${MATLAB_VERSION}_glnxa64_installer.zip -d /opt/mcr_install && \
wget --quiet -P /opt/mcr_install %%MCR_LINK%% && \
unzip -q /opt/mcr_install/*.zip -d /opt/mcr_install && \
/opt/mcr_install/install -destinationFolder /opt/mcr -agreeToLicense yes -mode silent && \
rm -rf /opt/mcr_install /tmp/*

# Configure environment
ENV MCR_VERSION v91
ENV MCR_VERSION %%MCR_VERSION%%
ENV LD_LIBRARY_PATH /opt/mcr/${MCR_VERSION}/runtime/glnxa64:/opt/mcr/${MCR_VERSION}/bin/glnxa64:/opt/mcr/${MCR_VERSION}/sys/os/glnxa64:/opt/mcr/${MCR_VERSION}/sys/opengl/lib/glnxa64
ENV MCR_INHIBIT_CTF_LOCK 1
ENV MCRPath /opt/mcr/${MCR_VERSION}
ENV MCRPath /opt/mcr/${MCR_VERSION}
116 changes: 116 additions & 0 deletions update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""
Dirty website parser to generate Dockerfiles for Matlab's MCR releases.

Each Matlab release name (R2020a etc) gets a branch that contains a single
Dockerfile and each release version (9.8.0 or 9.8.5 for 9.8 Update 5) becomes
a tag in that branch. Each variant gets a new commit as well.
So a sample history for R2020a could look something like this:

+ [R2020a] <9.8.1-core> Auto-Update
+ <9.8.1> Auto-Update
+ Merged master
| \
| + Update in templates
| /
+ <9.8.0-core> Auto-Update
+ <9.8.0> Auto-Update
+ <master> Import

(Circle)CI should then fire a docker build and push for every tag only. Shared
tags for the major version (e.g. 9.8 always pointing to the latest 9.8 tag are
done in Circle CI to avoid duplicate builds).

Update.py should be run often enough to catch individual matlab releae updates.
Copy link

@sappelhoff sappelhoff May 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Update.py should be run often enough to catch individual matlab releae updates.
Update.py should be run often enough to catch individual matlab release updates.

I don't get the "semi updating" process yet. Does it mean a potential maintainer regularly runs update.py from their machine and then commits and pushes any changes?

Are tags generated automatically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now update.py is simply generating git tags in separate branches and afterwards outputs necessary git commands to trigger circleCI actually building new images based on git tags.

Example output would be:

Adding R2020a/9.8.0
Adding R2020a/9.8.0-core
Adding R2019b/9.7.5
Adding R2019b/9.7.5-core
Adding R2019a/9.6.8
Adding R2019a/9.6.8-core
Adding R2018b/9.5.0
Adding R2018b/9.5.0-core
Adding R2018a/9.4.0
Adding R2018a/9.4.0-core
New tags have been added, verify and update to git with:
git push --all
git push origin 9.4.0-core
git push origin 9.4.0
git push origin 9.5.0-core
git push origin 9.5.0
git push origin 9.6.8-core
git push origin 9.6.8
git push origin 9.7.5-core
git push origin 9.7.5
git push origin 9.8.0-core
git push origin 9.8.0

Those last git push lines can easily be automated away of course but my experience with Matlab tells me that it will still require minor changes once in a while, so this semi-automated process boils down to:

  1. run update.py
  2. inspect and potentially fix new releases (minor changes in the URL probably)
  3. git push all the things

That shouldn't take more than 5 minutes every other month or so.

"""

import re
from subprocess import DEVNULL, run
from urllib import request

from packaging import version
from bs4 import BeautifulSoup

REL_URL = 'https://www.mathworks.com/products/compiler/matlab-runtime.html'
VER_LIMIT = '9.3' # release URLs get weird before that..

def call(cmd, split=True):
if split:
cmd = cmd.split()
process = run(cmd, stdout=DEVNULL, stderr=DEVNULL)
return process.returncode == 0


with request.urlopen(REL_URL) as res:
if res.status != 200:
raise RuntimeError('Could not open matlab release URL')
html = res.read()

soup = BeautifulSoup(html, 'html.parser')
ver_re = re.compile(r'(R2\d{3}.) \((\d\.\d)\)')
rel_re = re.compile(r'Release/(\d+)/')

dockers = []
for row in soup.find_all('table')[0].find_all('tr'):
tds = row.find_all('td')
if len(tds) >= 4:
name = tds[0].text
match = ver_re.match(name)
if not match:
continue
mcr_name, mcr_ver = match.groups()
if version.parse(mcr_ver) <= version.parse(VER_LIMIT):
continue
try:
link = tds[2].a.get('href')
except (KeyError, ValueError):
raise RuntimeError('Error parsing matlab release page')
if 'glnxa64' not in link:
raise RuntimeError('Error parsing matlab release page link')
match = rel_re.search(link)
if match:
mcr_ver = '{}.{}'.format(mcr_ver, match.groups()[0])
dockers.append((mcr_name, mcr_ver, link))


variants = [
('Dockerfile-full.template', ''),
('Dockerfile-core.template', '-core')
]
new_tags = []

for docker in dockers:
mcr_name, mcr_ver, link = docker
if len(mcr_ver.split('.')) == 2:
mcr_ver = mcr_ver + '.0'
mcr_ver_maj = '.'.join(mcr_ver.split('.')[0:2])
mcr_ver_dir = 'v{}'.format(mcr_ver_maj.replace('.', ''))
if not call('git checkout {}'.format(mcr_name)):
call('git checkout -b {}'.format(mcr_name))
for (template, suffix) in variants:
tag = '{}{}'.format(mcr_ver, suffix)
if call('git rev-parse --verify {}'.format(tag)):
print('Skipping {}/{}, already present'.format(mcr_name, tag))
continue
print('Adding {}/{}'.format(mcr_name, tag))
if not call('git merge master'):
raise RuntimeError('Merging master failed, will not continue')
with open(template) as f:
lines = f.read()
lines = lines.replace('%%MATLAB_VERSION%%', mcr_name)
lines = lines.replace('%%MCR_VERSION%%', mcr_ver_dir)
lines = lines.replace('%%MCR_LINK%%', link)
with open('Dockerfile', 'w+') as f2:
f2.write(lines)
call('git add Dockerfile')
# Tag X.Y.Z[-variant] - see circle CI for shared tag X.Y[-variant]
call(['git', 'commit', '-m', 'Auto-Update'], split=False)
call('git tag {}'.format(tag))
new_tags.append(tag)
call('git checkout master')

if new_tags:
print('New tags have been added, verify and update to git with:')
print('git push --all')
for tag in reversed(new_tags):
print('git push origin {}'.format(tag))