Skip to content

Commit 7002571

Browse files
authoredMar 26, 2020
Merge pull request #80 from mgxd/ci/circle
MAINT: Add docker image/circle build
2 parents 838d385 + d550f9f commit 7002571

File tree

3 files changed

+422
-0
lines changed

3 files changed

+422
-0
lines changed
 

‎.circleci/config.yml

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
version: 2
2+
jobs:
3+
build:
4+
machine:
5+
image: circleci/classic:201808-01
6+
working_directory: /tmp/src/nitransforms
7+
environment:
8+
TZ: "/usr/share/zoneinfo/America/Los_Angeles"
9+
SCRATCH: "/scratch"
10+
11+
steps:
12+
- restore_cache:
13+
keys:
14+
- build-v1-{{ .Branch }}-{{ epoch }}
15+
- build-v1-{{ .Branch }}-
16+
- build-v1-master-
17+
- build-v1-
18+
paths:
19+
- /tmp/docker
20+
- run:
21+
name: Set-up a Docker registry
22+
command: |
23+
docker run -d -p 5000:5000 --restart=always --name=registry \
24+
-v /tmp/docker:/var/lib/registry registry:2
25+
- run:
26+
name: Pull existing images
27+
command: |
28+
set +e
29+
docker pull localhost:5000/ubuntu
30+
success=$?
31+
set -e
32+
if [[ "$success" = "0" ]]; then
33+
echo "Pulling from local registry"
34+
docker tag localhost:5000/ubuntu ubuntu:xenial-20200114
35+
docker pull localhost:5000/nitransforms
36+
docker tag localhost:5000/nitransforms nitransforms:latest
37+
else
38+
echo "Pulling from Docker Hub"
39+
docker pull ubuntu:xenial-20200114
40+
docker tag ubuntu:xenial-20200114 localhost:5000/ubuntu
41+
docker push localhost:5000/ubuntu
42+
fi
43+
- checkout
44+
- run:
45+
name: Build Docker image & push to registry
46+
no_output_timeout: 60m
47+
command: |
48+
e=1 && for i in {1..5}; do
49+
docker build --rm --cache-from=nitransforms:latest \
50+
-t nitransforms:latest \
51+
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
52+
--build-arg VCS_REF=`git rev-parse --short HEAD` . \
53+
&& e=0 && break || sleep 15
54+
done && [ "$e" -eq "0" ]
55+
docker tag nitransforms:latest localhost:5000/nitransforms
56+
docker push localhost:5000/nitransforms
57+
- run:
58+
name: Docker registry garbage collection
59+
command: |
60+
docker exec -it registry /bin/registry garbage-collect --delete-untagged \
61+
/etc/docker/registry/config.yml
62+
- save_cache:
63+
key: build-v1-{{ .Branch }}-{{ epoch }}
64+
paths:
65+
- /tmp/docker
66+
- run:
67+
name: Store FreeSurfer license file
68+
command: |
69+
mkdir -p /tmp/fslicense
70+
cd /tmp/fslicense
71+
echo "cHJpbnRmICJrcnp5c3p0b2YuZ29yZ29sZXdza2lAZ21haWwuY29tXG41MTcyXG4gKkN2dW12RVYzelRmZ1xuRlM1Si8yYzFhZ2c0RVxuIiA+IGxpY2Vuc2UudHh0Cg==" | base64 -d | sh
72+
- persist_to_workspace:
73+
root: /tmp
74+
paths:
75+
- fslicense
76+
77+
test_pytest:
78+
machine:
79+
image: circleci/classic:201808-01
80+
environment:
81+
FS_LICENSE: "/tmp/fslicense/license.txt"
82+
working_directory: /tmp/tests
83+
steps:
84+
- attach_workspace:
85+
at: /tmp
86+
- checkout:
87+
path: /tmp/src/nitransforms
88+
- run:
89+
name: Get codecov
90+
command: python -m pip install codecov
91+
- restore_cache:
92+
keys:
93+
- build-v1-{{ .Branch }}-{{ epoch }}
94+
- build-v1-{{ .Branch }}-
95+
- build-v1-master-
96+
- build-v1-
97+
- run:
98+
name: Set-up a Docker registry & pull
99+
command: |
100+
docker run -d -p 5000:5000 --restart=always --name=registry \
101+
-v /tmp/docker:/var/lib/registry registry:2
102+
docker pull localhost:5000/nitransforms
103+
docker tag localhost:5000/nitransforms nitransforms:latest
104+
- run:
105+
name: Run unit tests
106+
no_output_timeout: 2h
107+
command: |
108+
mkdir -p $PWD/artifacts $PWD/summaries
109+
sudo setfacl -d -m group:ubuntu:rwx $PWD
110+
sudo setfacl -m group:ubuntu:rwx $PWD
111+
docker run -u $( id -u ) -it --rm=false -w /src/nitransforms \
112+
-e COVERAGE_FILE=/tmp/summaries/.pytest.coverage \
113+
-v /tmp/fslicense/license.txt:/opt/freesurfer/license.txt:ro \
114+
-v ${PWD}:/tmp nitransforms:latest \
115+
pytest --junit-xml=/tmp/summaries/pytest.xml \
116+
--cov nitransforms --cov-report xml:/tmp/summaries/unittests.xml \
117+
nitransforms/
118+
- run:
119+
name: Submit unit test coverage
120+
command: |
121+
cd /tmp/src/nitransforms
122+
python -m codecov --file /tmp/tests/summaries/unittests.xml \
123+
--flags unittests -e CIRCLE_JOB
124+
- run:
125+
name: Clean up tests directory
126+
when: on_success
127+
command: |
128+
rm -rf /tmp/tests/pytest-of-root
129+
- store_artifacts:
130+
path: /tmp/tests/artifacts
131+
132+
- store_test_results:
133+
path: /tmp/tests/summaries/
134+
135+
test_packaging_and_deploy:
136+
machine:
137+
image: circleci/classic:201808-01
138+
working_directory: /tmp/src/nitransforms
139+
steps:
140+
- checkout
141+
- run: pyenv local 3.7.0
142+
- run:
143+
name: Install build depends
144+
command: python3 -m pip install "setuptools>=30.4.0" "pip>=10.0.1" "twine<2.0" docutils
145+
- run:
146+
name: Build and check
147+
command: |
148+
python3 setup.py check -r -s
149+
python3 setup.py sdist
150+
python3 -m twine check dist/*
151+
- run:
152+
name: Validate version
153+
command: |
154+
THISVERSION=$( python3 get_version.py )
155+
python3 -m pip install dist/*.tar.gz
156+
mkdir empty
157+
cd empty
158+
INSTALLED=$( python3 -c 'import nitransforms; print(nitransforms.__version__)' )
159+
test "${CIRCLE_TAG:-$THISVERSION}" == "$INSTALLED"
160+
- run:
161+
name: Upload to PyPi
162+
command: |
163+
python3 -m twine upload dist/*
164+
165+
166+
workflows:
167+
version: 2
168+
build_test_deploy:
169+
jobs:
170+
- build:
171+
filters:
172+
branches:
173+
ignore:
174+
- /docs?\/.*/
175+
tags:
176+
only: /.*/
177+
178+
- test_pytest:
179+
requires:
180+
- build
181+
filters:
182+
branches:
183+
ignore:
184+
- /docs?\/.*/
185+
- /docker\/.*/
186+
tags:
187+
only: /.*/
188+
189+
- test_packaging_and_deploy:
190+
requires:
191+
- test_pytest
192+
filters:
193+
branches:
194+
ignore: /.*/
195+
tags:
196+
only: /.*/

‎Dockerfile

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
FROM ubuntu:xenial-20200114
2+
3+
# Pre-cache neurodebian key
4+
COPY docker/files/neurodebian.gpg /usr/local/etc/neurodebian.gpg
5+
6+
# Prepare environment
7+
RUN apt-get update && \
8+
apt-get install -y --no-install-recommends \
9+
curl \
10+
bzip2 \
11+
ca-certificates \
12+
xvfb \
13+
build-essential \
14+
autoconf \
15+
libtool \
16+
pkg-config \
17+
git && \
18+
curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
19+
apt-get install -y --no-install-recommends \
20+
nodejs && \
21+
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
22+
23+
# Installing freesurfer
24+
RUN curl -sSL https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/6.0.1/freesurfer-Linux-centos6_x86_64-stable-pub-v6.0.1.tar.gz | tar zxv --no-same-owner -C /opt \
25+
--exclude='freesurfer/diffusion' \
26+
--exclude='freesurfer/docs' \
27+
--exclude='freesurfer/fsfast' \
28+
--exclude='freesurfer/lib/cuda' \
29+
--exclude='freesurfer/lib/qt' \
30+
--exclude='freesurfer/matlab' \
31+
--exclude='freesurfer/mni/share/man' \
32+
--exclude='freesurfer/subjects/fsaverage_sym' \
33+
--exclude='freesurfer/subjects/fsaverage3' \
34+
--exclude='freesurfer/subjects/fsaverage4' \
35+
--exclude='freesurfer/subjects/cvs_avg35' \
36+
--exclude='freesurfer/subjects/cvs_avg35_inMNI152' \
37+
--exclude='freesurfer/subjects/bert' \
38+
--exclude='freesurfer/subjects/lh.EC_average' \
39+
--exclude='freesurfer/subjects/rh.EC_average' \
40+
--exclude='freesurfer/subjects/sample-*.mgz' \
41+
--exclude='freesurfer/subjects/V1_average' \
42+
--exclude='freesurfer/trctrain'
43+
44+
ENV FSL_DIR="/usr/share/fsl/5.0" \
45+
OS="Linux" \
46+
FS_OVERRIDE=0 \
47+
FIX_VERTEX_AREA="" \
48+
FSF_OUTPUT_FORMAT="nii.gz" \
49+
FREESURFER_HOME="/opt/freesurfer"
50+
ENV SUBJECTS_DIR="$FREESURFER_HOME/subjects" \
51+
FUNCTIONALS_DIR="$FREESURFER_HOME/sessions" \
52+
MNI_DIR="$FREESURFER_HOME/mni" \
53+
LOCAL_DIR="$FREESURFER_HOME/local" \
54+
MINC_BIN_DIR="$FREESURFER_HOME/mni/bin" \
55+
MINC_LIB_DIR="$FREESURFER_HOME/mni/lib" \
56+
MNI_DATAPATH="$FREESURFER_HOME/mni/data"
57+
ENV PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \
58+
MNI_PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \
59+
PATH="$FREESURFER_HOME/bin:$FSFAST_HOME/bin:$FREESURFER_HOME/tktools:$MINC_BIN_DIR:$PATH"
60+
61+
# Installing Neurodebian packages (FSL, AFNI, git)
62+
RUN curl -sSL "http://neuro.debian.net/lists/$( lsb_release -c | cut -f2 ).us-ca.full" >> /etc/apt/sources.list.d/neurodebian.sources.list && \
63+
apt-key add /usr/local/etc/neurodebian.gpg && \
64+
(apt-key adv --refresh-keys --keyserver hkp://ha.pool.sks-keyservers.net 0xA5D32F012649A5A9 || true)
65+
66+
RUN apt-get update && \
67+
apt-get install -y --no-install-recommends \
68+
fsl-core=5.0.9-5~nd16.04+1 \
69+
fsl-mni152-templates=5.0.7-2 \
70+
afni=16.2.07~dfsg.1-5~nd16.04+1 \
71+
convert3d \
72+
connectome-workbench=1.3.2-2~nd16.04+1 \
73+
git-annex-standalone && \
74+
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
75+
76+
ENV FSLDIR="/usr/share/fsl/5.0" \
77+
FSLOUTPUTTYPE="NIFTI_GZ" \
78+
FSLMULTIFILEQUIT="TRUE" \
79+
POSSUMDIR="/usr/share/fsl/5.0" \
80+
LD_LIBRARY_PATH="/usr/lib/fsl/5.0:$LD_LIBRARY_PATH" \
81+
FSLTCLSH="/usr/bin/tclsh" \
82+
FSLWISH="/usr/bin/wish" \
83+
AFNI_MODELPATH="/usr/lib/afni/models" \
84+
AFNI_IMSAVE_WARNINGS="NO" \
85+
AFNI_TTATLAS_DATASET="/usr/share/afni/atlases" \
86+
AFNI_PLUGINPATH="/usr/lib/afni/plugins"
87+
ENV PATH="/usr/lib/fsl/5.0:/usr/lib/afni/bin:$PATH"
88+
89+
# Installing ANTs 2.2.0 (NeuroDocker build)
90+
ENV ANTSPATH=/usr/lib/ants
91+
RUN mkdir -p $ANTSPATH && \
92+
curl -sSL "https://dl.dropbox.com/s/2f4sui1z6lcgyek/ANTs-Linux-centos5_x86_64-v2.2.0-0740f91.tar.gz" \
93+
| tar -xzC $ANTSPATH --strip-components 1
94+
ENV PATH=$ANTSPATH:$PATH
95+
96+
# Installing and setting up miniconda
97+
RUN curl -sSLO https://repo.continuum.io/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh && \
98+
bash Miniconda3-4.5.11-Linux-x86_64.sh -b -p /usr/local/miniconda && \
99+
rm Miniconda3-4.5.11-Linux-x86_64.sh
100+
101+
# Set CPATH for packages relying on compiled libs (e.g. indexed_gzip)
102+
ENV PATH="/usr/local/miniconda/bin:$PATH" \
103+
CPATH="/usr/local/miniconda/include/:$CPATH" \
104+
LANG="C.UTF-8" \
105+
LC_ALL="C.UTF-8" \
106+
PYTHONNOUSERSITE=1
107+
108+
# Installing precomputed python packages
109+
RUN conda install -y python=3.7.1 \
110+
pip=19.1 \
111+
mkl=2018.0.3 \
112+
mkl-service \
113+
numpy=1.15.4 \
114+
scipy=1.1.0 \
115+
libxml2=2.9.8 \
116+
libxslt=1.1.32 \
117+
zlib; sync && \
118+
chmod -R a+rX /usr/local/miniconda; sync && \
119+
chmod +x /usr/local/miniconda/bin/*; sync && \
120+
conda build purge-all; sync && \
121+
conda clean -tipsy && sync
122+
123+
# Unless otherwise specified each process should only use one thread - nipype
124+
# will handle parallelization
125+
ENV MKL_NUM_THREADS=1 \
126+
OMP_NUM_THREADS=1
127+
128+
# Create a shared $HOME directory
129+
RUN useradd -m -s /bin/bash -G users neuro
130+
WORKDIR /home/neuro
131+
ENV HOME="/home/neuro"
132+
133+
# Install package
134+
COPY . /src/nitransforms
135+
ARG VERSION
136+
# Force static versioning within container
137+
RUN echo "${VERSION}" > /src/nitransforms/nitransforms/VERSION && \
138+
echo "include nitransforms/VERSION" >> /src/nitransforms/MANIFEST.in && \
139+
pip install --no-cache-dir "/src/nitransforms[all]"
140+
141+
RUN find $HOME -type d -exec chmod go=u {} + && \
142+
find $HOME -type f -exec chmod go=u {} +
143+
144+
145+
RUN ldconfig
146+
WORKDIR /tmp/
147+
148+
ARG BUILD_DATE
149+
ARG VCS_REF
150+
ARG VERSION
151+
LABEL org.label-schema.build-date=$BUILD_DATE \
152+
org.label-schema.name="nitransforms" \
153+
org.label-schema.vcs-url="https://github.com/poldracklab/nitransforms" \
154+
org.label-schema.version=$VERSION \
155+
org.label-schema.schema-version="1.0"

‎docker/files/neurodebian.gpg

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
-----BEGIN PGP PUBLIC KEY BLOCK-----
2+
Version: GnuPG v1
3+
4+
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
70+
=fAJZ
71+
-----END PGP PUBLIC KEY BLOCK-----

0 commit comments

Comments
 (0)
Please sign in to comment.