From 1ff41c24005ce2558fa726c5347b050e297ec73c Mon Sep 17 00:00:00 2001
From: Markus Scheidgen <markus.scheidgen@gmail.com>
Date: Fri, 21 Sep 2018 10:58:30 +0200
Subject: [PATCH] Refactored docker images.

---
 .dockerignore                         |  9 +++-
 backend.Dockerfile => Dockerfile      | 59 +++++++++++++--------------
 docs/conf.py                          | 17 +++++---
 frontend.Dockerfile => gui/Dockerfile | 11 +++--
 gui/gitinfo.sh                        |  2 +
 gui/package.json                      |  4 +-
 nomad/dependencies.py                 | 23 ++++++++---
 7 files changed, 74 insertions(+), 51 deletions(-)
 rename backend.Dockerfile => Dockerfile (53%)
 rename frontend.Dockerfile => gui/Dockerfile (83%)
 create mode 100644 gui/gitinfo.sh

diff --git a/.dockerignore b/.dockerignore
index 3118add92b..8a3681360e 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,10 +2,15 @@
 .pyenv/
 .vscode/
 .volumes/
+.git/
+.mypy_cache/
 data/
 docs/.build
-gui/node_modules/
-gui/build/
+docs/.static
+docs/*.graffle
+gui/
 infrastructure/
 __pycache__/
 *.pyc
+NOMAD.egg-info/
+.coverage
diff --git a/backend.Dockerfile b/Dockerfile
similarity index 53%
rename from backend.Dockerfile
rename to Dockerfile
index 08b6f602f8..17639ce762 100644
--- a/backend.Dockerfile
+++ b/Dockerfile
@@ -17,49 +17,48 @@
 # - nomad upload handler that initiates processing after upload
 # - nomad api
 
-# The dockerfile is multistaged to help docker with caching unnecessary steps
-# creating a base image with most requirements already installed
-FROM python:3.6-stretch as requirements
+# The dockerfile is multistaged to use a fat, more convinient build image and
+# copy only necessities to a slim final image
+
+# We use slim for the final image
+FROM python:3.6-slim as final
+
+# First, build everything in a build image
+FROM python:3.6-stretch as build
+# Make will be necessary to build the docs with sphynx
+RUN apt-get update && apt-get install -y make
 RUN mkdir /install
 WORKDIR /install
-COPY requirements.txt requirements.txt
+# We also install the -dev dependencies, to use this image for test and qa
+COPY requirements-dev.txt requirements-dev.txt
+RUN pip install -r requirements-dev.txt
 COPY requirements-dep.txt requirements-dep.txt
-RUN pip install -r requirements.txt
 RUN pip install -r requirements-dep.txt
-
-# dependency stage is used to install nomad coe projects
-FROM requirements as dependencies
-WORKDIR /install
-COPY nomad/dependencies.py nomad/dependencies.py
-COPY nomad/config.py nomad/config.py
 COPY requirements.txt requirements.txt
 RUN pip install -r requirements.txt
+# Use docker build --build-args CACHEBUST=2 to not cache this (e.g. when you know deps have changed)
+ARG CACHEBUST=1
+COPY nomad/dependencies.py /install/nomad/dependencies.py
+COPY nomad/config.py /install/nomad/config.py
 RUN python nomad/dependencies.py
+# do that after the dependencies to use docker's layer caching
+COPY . /install
+RUN pip install .
+WORKDIR /install/docs
+RUN make html
 
-# we use slim for the final image
-FROM python:3.6-slim as final
-
-# last stage is used to install the actual code, nomad user, volumes
+# Second, create a slim final image
 FROM final
-# transfer installed packages from dependency stage
-COPY --from=dependencies /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages
-COPY --from=dependencies /usr/local/bin/sphinx-build /usr/local/bin/sphinx-build
-# we also need to copy the install dir, since nomad coe deps are installed with -e
-# TODO that should be changed in production!
-COPY --from=dependencies /install /install
-
-# do stuff
-RUN apt-get update && apt-get install -y make
+# copy the sources for tests, coverage, qa, etc.
 COPY . /app
 WORKDIR /app
-
+# transfer installed packages from dependency stage
+COPY --from=build /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages
 # copy the meta-info, since it files are loaded via relative paths. TODO that should change.
-COPY --from=dependencies /install/.dependencies/nomad-meta-info /app/.dependencies/nomad-meta-info
+COPY --from=build /install/.dependencies/nomad-meta-info /app/.dependencies/nomad-meta-info
+# copy the documentation, its files will be served by the API
+COPY --from=build /install/docs/.build /app/docs/.build
 
-RUN pip install -e .
-WORKDIR /app/docs
-RUN make html
-WORKDIR /app
 RUN useradd -ms /bin/bash nomad
 RUN mkdir -p /app/.volumes/fs; chown -R nomad /app/.volumes/fs
 USER nomad
diff --git a/docs/conf.py b/docs/conf.py
index c158fabb3e..b487817678 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -15,12 +15,17 @@ import os
 import sys
 from recommonmark.transform import AutoStructify
 
-sys.path.insert(0, os.path.abspath('../nomad'))
+sys.path.insert(0, os.path.abspath('..'))
+# TODO, once the normalizers are self contained in their own gits, this should not be
+# necessary anymore
+sys.path.insert(0, os.path.abspath('../.dependencies/normalizers/stats/normalizer/normalizer-stats'))
+sys.path.insert(0, os.path.abspath('../.dependencies/normalizers/symmetry/normalizer/normalizer-symmetry'))
+sys.path.insert(0, os.path.abspath('../.dependencies/normalizers/system-type/normalizer/normalizer-system-type'))
 
 
 # -- Project information -----------------------------------------------------
 
-project = 'NOMAD-XT'
+project = 'nomad-FAIR'
 copyright = '2018, the NOMAD developers'
 author = 'the NOMAD developers'
 
@@ -141,7 +146,7 @@ latex_elements = {
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-    (master_doc, 'NOMAD-XT.tex', 'NOMAD-XT Documentation',
+    (master_doc, 'nomad-FAIR.tex', 'nomad-FAIR Documentation',
      'the NOMAD developers', 'manual'),
 ]
 
@@ -151,7 +156,7 @@ latex_documents = [
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    (master_doc, 'nomad', 'NOMAD-XT Documentation',
+    (master_doc, 'nomad', 'nomad-FAIR Documentation',
      [author], 1)
 ]
 
@@ -162,8 +167,8 @@ man_pages = [
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-    (master_doc, 'NOMAD-XT', 'NOMAD-XT Documentation',
-     author, 'NOMAD-XT', 'One line description of project.',
+    (master_doc, 'nomad-FAIR', 'nomad-FAIR Documentation',
+     author, 'nomad-FAIR', 'One line description of project.',
      'Miscellaneous'),
 ]
 
diff --git a/frontend.Dockerfile b/gui/Dockerfile
similarity index 83%
rename from frontend.Dockerfile
rename to gui/Dockerfile
index dddaab8d69..e13e34d145 100644
--- a/frontend.Dockerfile
+++ b/gui/Dockerfile
@@ -19,19 +19,18 @@
 # intended for the actual GUI container that serves the GUI.
 
 # build environment
-FROM node:latest as builder
+FROM node:latest as build
 RUN mkdir -p /nomad/app
 WORKDIR /nomad/app
 ENV PATH /nomad/app/node_modules/.bin:$PATH
-COPY gui/package.json /nomad/app/package.json
-COPY gui/yarn.lock /nomad/app/yarn.lock
+COPY package.json /nomad/app/package.json
+COPY yarn.lock /nomad/app/yarn.lock
 RUN yarn
-COPY gui /nomad/app
-COPY .git /nomad
+COPY . /nomad/app
 
 RUN yarn build
 
 # production environment
 FROM nginx:1.13.9-alpine
-COPY --from=builder /nomad/app/build /app/nomad
+COPY --from=build /nomad/app/build /app/nomad
 CMD ["nginx", "-g", "daemon off;"]
diff --git a/gui/gitinfo.sh b/gui/gitinfo.sh
new file mode 100644
index 0000000000..a4755c6c87
--- /dev/null
+++ b/gui/gitinfo.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo \"{ \\\"log\\\": \\\"$(git log -1 --oneline)\\\", \\\"ref\\\": \\\"$(git describe --all)\\\", \\\"version\\\": \\\"$(git describe)\\\" }\"  > src/gitinfo.json
\ No newline at end of file
diff --git a/gui/package.json b/gui/package.json
index 8ce96ad07c..ef0c4b41dd 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -25,8 +25,8 @@
   "scripts": {
     "metainfo": "git clone --single-branch -b nomad-xt http://gitlab.mpcdf.mpg.de/nomad-lab/nomad-meta-info.git --depth=1 public/metainfo",
     "gitinfo": "echo \"{ \\\"log\\\": \\\"$(git log -1 --oneline)\\\", \\\"ref\\\": \\\"$(git describe --all)\\\", \\\"version\\\": \\\"$(git describe)\\\" }\"  > src/gitinfo.json",
-    "start": "yarn metainfo; yarn gitinfo; react-scripts start",
-    "build": "yarn metainfo; yarn gitinfo; react-scripts build",
+    "start": "yarn metainfo; react-scripts start",
+    "build": "yarn metainfo; react-scripts build",
     "test": "react-scripts test --env=jsdom",
     "eject": "react-scripts eject"
   },
diff --git a/nomad/dependencies.py b/nomad/dependencies.py
index d684f4c446..2288728f44 100644
--- a/nomad/dependencies.py
+++ b/nomad/dependencies.py
@@ -95,10 +95,13 @@ class PythonGit():
             raise PythonGitError(
                 'Could not install (pip return code=%s)' % pipcode, repo=self)
 
-    def prepare(self) -> None:
+    def prepare(self, dev: bool = False) -> None:
         """
         Makes sure that the repository is fetched, at the right commit, and installed.
 
+        Arguments:
+            dev (bool): Indicate dev install (uses pip with -e). Default is False.
+
         Raises:
             PythonGitError: if something went wrong.
         """
@@ -132,7 +135,10 @@ class PythonGit():
 
             if os.path.exists('setup.py'):
                 _logger.info('install setup.py for %s' % self.name)
-                self._run_pip_install('-e', '.')
+                if dev:
+                    self._run_pip_install('-e', '.')
+                else:
+                    self._run_pip_install('.')
 
         except PythonGitError as e:
             raise e
@@ -185,14 +191,21 @@ dependencies = [
 dependencies_dict = {dependency.name: dependency for dependency in dependencies}
 
 
-def prepare() -> None:
+def prepare(*args, **kwargs) -> None:
     """
     Installs all dependencies from :data:`dependencies` and :data:`parsers`.
     """
     for python_git in dependencies:
-        python_git.prepare()
+        python_git.prepare(*args, **kwargs)
 
 
 if __name__ == '__main__':
+    import argparse
+
+    parser = argparse.ArgumentParser(description='Install dependencies from NOMAD-coe.')
+    parser.add_argument('--dev', help='pip install with -e', action='store_true')
+
+    args = parser.parse_args()
+
     _logger.setLevel(logging.DEBUG)
-    prepare()
+    prepare(dev=args.dev)
-- 
GitLab