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
78 changes: 78 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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
# tag should always be X.Y.Z[-variant]
docker tag octomike/${CIRCLE_PROJECT_REPONAME,,} octomike/${CIRCLE_PROJECT_REPONAME,,}:${CIRCLE_TAG}
docker push octomike/${CIRCLE_PROJECT_REPONAME,,}:${CIRCLE_TAG}
# also publish tag for the corresponding matlab release version, which is the name of the current branch
docker tag octomike/${CIRCLE_PROJECT_REPONAME,,} octomike/${CIRCLE_PROJECT_REPONAME,,}:${CIRCLE_BRANCH}
docker push octomike/${CIRCLE_PROJECT_REPONAME,,}:${CIRCLE_BRANCH}
# update major tag X.Y[-variant] to the latest in this branch
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 release updates.
"""

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))