IHaskell/ihaskell-notebook

fix-permission not enough for installing extra-snapshot packages

Closed this issue · 12 comments

The source of the problem is that the build lock files are owned by root in recent Docker images such as crosscompass/ihaskell-notebook:fb96a81230df (currently latest) on DockerHub.

In order to fix this, I think your Dockerfile should apply fix-permission to /opt/IHaskell as well as $STACK_ROOT or /opt/stack after everything has been built and registered. For some reasons, this does not seem to be the case now.

Assuming the user sticks to global project rather than local stack.yaml file, (FYI, using local stack.yaml shares the same problem), the user needs to edit global project stack.yaml to list newly intsalled extra-snapshot package in extra-deps. Otherwise, it is not visible from stack therefore not visible in notebooks.

The problem is when you touch the stack.yaml file you need to re-build all previously built project-local packages under /opt/IHaskell and things like ghc-parser which depend on them. There seems to be additional timestamp check that didn't exist in older stack versions, which invalidates all project-local builds. This is itself bit annoying, but not a big bottleneck because it uses previously built results (not actually re-compiling all the Haskell source) and just register them again to the package database. If this succeeds I think it is tolerable (in terms of compile time, not quite sure for image size bloating when trying to build on top of your image), although not ideal.

However, I was not able to re-build packages like those in /opt/IHaskell inside the running Docker image as a user, because few build lock file permissions are root owned.

To go around the problem, what I did is something like this, unfortunately bloating image more than size twice. (FYI, tree-view and data-partition are non-snapshot packages and global-project.stack.yaml is same as the global-porject stack.yaml in you Docker image but with those additional non-snapshot packages appended to extra-deps)

FROM crosscompass/ihaskell-notebook:fb96a81230df

USER root
RUN jupyter labextension install @jupyterlab/katex-extension
RUN mkdir /home/$NB_USER/picalc
COPY *.ipynb /home/$NB_USER/picalc/
RUN fix-permissions /home/$NB_USER/picalc
COPY global-project.stack.yaml $STACK_ROOT/global-project/stack.yaml
RUN stack install unbound-generics uglymemo lens tree-view data-partition \
 && stack build $STACK_ARGS ihaskell \
 && stack build $STACK_ARGS ghc-parser \
 && stack build $STACK_ARGS ipython-kernel \
 ## ihaskell-display
 && stack build $STACK_ARGS ihaskell-aeson \
 && stack build $STACK_ARGS ihaskell-blaze \
 && stack build $STACK_ARGS ihaskell-charts \
 && stack build $STACK_ARGS ihaskell-diagrams \
 && stack build $STACK_ARGS ihaskell-gnuplot \
 && stack build $STACK_ARGS ihaskell-graphviz \
 && stack build $STACK_ARGS ihaskell-hatex \
 && stack build $STACK_ARGS ihaskell-juicypixels \
# && stack build $STACK_ARGS ihaskell-magic \
# && stack build $STACK_ARGS ihaskell-plot \
# && stack build $STACK_ARGS ihaskell-rlangqq \
# && stack build $STACK_ARGS ihaskell-static-canvas \
# Skip install of ihaskell-widgets, they don't work.
# See https://github.com/gibiansky/IHaskell/issues/870
# && stack build $STACK_ARGS ihaskell-widgets \
 && stack build $STACK_ARGS hvega \
 && stack build $STACK_ARGS ihaskell-hvega \
 && fix-permissions $STACK_ROOT \
 && chown --recursive $NB_USER:users $STACK_ROOT \
 && chown --recursive $NB_USER:users /opt/IHaskell \
 && chown --recursive $NB_USER:users /opt/hvega

ENV JUPYTER_ENABLE_LAB=yes

I think it might work with fix-permission rather than chown, but have not tested that yet.

P.S.> fix-permission instead of chown does the work

instead of those four lines above

&& fix-permissions $STACK_ROOT
&& chown --recursive $NB_USER:users $STACK_ROOT
&& chown --recursive $NB_USER:users /opt/IHaskell
&& chown --recursive $NB_USER:users /opt/hvega

these three lines below does the job with much less image size

&& fix-permissions $STACK_ROOT/global-project
&& fix-permissions /opt/IHaskell
&& fix-permissions /opt/hvega

Thanks for the bug report!

We definitely want to be able to change the global-project/stack.yaml and then build more packages.

I would be happy to review a PR from you for this, if you think you know a good fix. I will investigate in the meanwhile.

Dockerfile should apply fix-permission to /opt/IHaskell as well as $STACK_ROOT or /opt/stack after everything has been built and registered. For some reasons, this does not seem to be the case now.

fix-permission sets the setgid bit so that all files and subdirectories inherit group ownership. However, it “does not affect group ID of the files that are created elsewhere and moved to the directory in question.”

Have you tried running fix-permission after you copy over your new stack.yaml and before you stack install, like this?

COPY global-project.stack.yaml $STACK_ROOT/global-project/stack.yaml
RUN fix-permissions $STACK_ROOT/global-project

Could I see your repository, including your global-project.stack.yaml file? Can you push it to Github?

I tried adding some more extra-deps: to the /opt/stack/global-project/stack.yaml from inside the JupyterLab interface then building the extra-deps: with stack and it worked.

I strongly suspect that if you fix-permissions after COPY global-project.stack.yaml, as I suggested above, then that will solve your problem.

Could I see your repository, including your global-project.stack.yaml file? Can you push it to Github?

https://github.com/kyagrd/ihaskell-picalc/blob/master/dockerdev/global-project.stack.yaml

Here I added lines 41 and 42

Have you tried running fix-permission after you copy over your new stack.yaml and before you stack install, like this?

COPY global-project.stack.yaml $STACK_ROOT/global-project/stack.yaml
RUN fix-permissions $STACK_ROOT/global-project

This does not solve the problem because the file permission error also comes from /opt/IHaskell or /opt/hvega where one needs to rebuild local packages after editing the global project stack.yaml file.

What I think would solve the problem is to make sure that there are no files left behind as root only writable without being fix-permission in $STACK_ROOT/global-proejct, /opt/IHaskell, and /opt/hvega (maybe there are more directories to fix but for me making sure those three directory are recursivley fix-permissioned does the job)

Let me try reproduce the error message to point you to the exact file name that caused those errors, and come back here.

Just to make sure that we are on the same page.

What I mean by fail to do it "as a user in the running Docker image" is when I try to install the package via JupyterLab Terminal or via shell-script command within IHaskell notebook. Editing global-project stack.yaml file is already possible in your DockerHub image. Permission of global proejct stack.yaml file is already sane. It's some other few build-lock files that are lingering as root write only is causing the problem.

There aren't any problems with docker scripting of course, because one could just invoke root user privilege in a docker script.

Have you tried running fix-permission after you copy over your new stack.yaml and before you stack install, like this?

COPY global-project.stack.yaml $STACK_ROOT/global-project/stack.yaml
RUN fix-permissions $STACK_ROOT/global-project

This does not solve the problem because the file permission error also comes from /opt/IHaskell or /opt/hvega where one needs to rebuild local packages after editing the global project stack.yaml file.

What I think would solve the problem is to make sure that there are no files left behind as root only writable without being fix-permission in $STACK_ROOT/global-proejct, /opt/IHaskell, and /opt/hvega (maybe there are more directories to fix but for me making sure those three directory are recursivley fix-permissioned does the job)

Let me try reproduce the error message to point you to the exact file name that caused those errors, and come back here.

Here is the entire run of a JupterLab Termial from the running image of crosscompass/ihaskell-notebook:fb96a81230df

jovyan@7707cf3d4825:~$ ls
ihaskell_examples  pwd  work
jovyan@7707cf3d4825:~$ cd /opt/
bin/      conda/    ghc/      hvega/    IHaskell/ stack/    
jovyan@7707cf3d4825:~$ cd /opt/stack/
jovyan@7707cf3d4825:/opt/stack$ ls
config.yaml  global-project  pantry  programs  setup-exe-cache  setup-exe-src  snapshots  stack.sqlite3  stack.sqlite3.pantry-write-lock
jovyan@7707cf3d4825:/opt/stack$ cd global-project/
jovyan@7707cf3d4825:/opt/stack/global-project$ ls
stack.yaml  stack.yaml.lock
jovyan@7707cf3d4825:/opt/stack/global-project$ ls -al
total 20
drwsrwsr-x 1 jovyan users 4096 Apr 27 07:04 .
drwsrwsr-x 1 root   users 4096 Apr 27 09:33 ..
drwsrwsr-x 1 root   users 4096 Apr 27 09:35 .stack-work
-rw-rw-r-- 1 jovyan users 2014 Apr 27 06:58 stack.yaml
-rw-rw---- 1 root   users 2113 Apr 27 07:04 stack.yaml.lock
jovyan@7707cf3d4825:/opt/stack/global-project$ cat stack.yaml
# Stack global project /opt/stack/global-project/stack.yaml in the Docker image.
# https://docs.haskellstack.org/en/stable/yaml_configuration/#yaml-configuration

# All the IHaskell packages are listed as extra-deps rather than packages,
# because we never want to build anything automatically, we always want to
# select exactly what we build for the IHaskell notebook environment.
# For example, `stack ghci` tries to load every package listed in `packages`,
# and we don't want that behavior. Several of these packages are unbuildable
# at the time of this writing. See the Dockerfile for the list of packages
# which are pre-built into the Docker image.
#
# To make an notebook project with custom `stack.yaml`, copy this `stack.yaml`
# file into the project directory that has the `.ipynb` notebook file and
# then make changes to the copied `stack.yaml`.

packages: []
extra-deps:
- /opt/IHaskell
- /opt/IHaskell/ipython-kernel
- /opt/IHaskell/ghc-parser
- /opt/IHaskell/ihaskell-display/ihaskell-aeson
- /opt/IHaskell/ihaskell-display/ihaskell-blaze
- /opt/IHaskell/ihaskell-display/ihaskell-charts
- /opt/IHaskell/ihaskell-display/ihaskell-diagrams
- /opt/IHaskell/ihaskell-display/ihaskell-gnuplot
- /opt/IHaskell/ihaskell-display/ihaskell-graphviz
- /opt/IHaskell/ihaskell-display/ihaskell-hatex
- /opt/IHaskell/ihaskell-display/ihaskell-juicypixels
- /opt/IHaskell/ihaskell-display/ihaskell-magic
- /opt/IHaskell/ihaskell-display/ihaskell-plot
- /opt/IHaskell/ihaskell-display/ihaskell-rlangqq
- /opt/IHaskell/ihaskell-display/ihaskell-static-canvas
- /opt/IHaskell/ihaskell-display/ihaskell-widgets
- /opt/hvega/hvega
- /opt/hvega/ihaskell-hvega
#
# dependencies needed for ihaskell-diagrams but not in Stackage snapshot
- diagrams-cairo-1.4.1.1
- cairo-0.13.8.0
- pango-0.13.8.0
- glib-0.13.8.0
- gtk2hs-buildtools-0.13.8.0
#
# dependences needed for ihaskell-charts but not in Stackage snapshot
- Chart-cairo-1.9.3

# Resolver copied from IHaskell stack.yaml, see the Dockerfile.
resolver: lts-14.27
jovyan@7707cf3d4825:/opt/stack/global-project$ cat > stack.yaml
# Stack global project /opt/stack/global-project/stack.yaml in the Docker image.
# https://docs.haskellstack.org/en/stable/yaml_configuration/#yaml-configuration

# All the IHaskell packages are listed as extra-deps rather than packages,
# because we never want to build anything automatically, we always want to
# select exactly what we build for the IHaskell notebook environment.
# For example, `stack ghci` tries to load every package listed in `packages`,
# and we don't want that behavior. Several of these packages are unbuildable
# at the time of this writing. See the Dockerfile for the list of packages
# which are pre-built into the Docker image.
#
# To make an notebook project with custom `stack.yaml`, copy this `stack.yaml`
# file into the project directory that has the `.ipynb` notebook file and
# then make changes to the copied `stack.yaml`.

packages: []
extra-deps:
- /opt/IHaskell
- /opt/IHaskell/ipython-kernel
- /opt/IHaskell/ghc-parser
- /opt/IHaskell/ihaskell-display/ihaskell-aeson
- /opt/IHaskell/ihaskell-display/ihaskell-blaze
- /opt/IHaskell/ihaskell-display/ihaskell-charts
- /opt/IHaskell/ihaskell-display/ihaskell-diagrams
- /opt/IHaskell/ihaskell-display/ihaskell-gnuplot
- /opt/IHaskell/ihaskell-display/ihaskell-graphviz
- /opt/IHaskell/ihaskell-display/ihaskell-hatex
- /opt/IHaskell/ihaskell-display/ihaskell-juicypixels
- /opt/IHaskell/ihaskell-display/ihaskell-magic
- /opt/IHaskell/ihaskell-display/ihaskell-plot
- /opt/IHaskell/ihaskell-display/ihaskell-rlangqq
- /opt/IHaskell/ihaskell-display/ihaskell-static-canvas
- /opt/IHaskell/ihaskell-display/ihaskell-widgets
- /opt/hvega/hvega
- /opt/hvega/ihaskell-hvega
#
# dependencies needed for ihaskell-diagrams but not in Stackage snapshot
- diagrams-cairo-1.4.1.1
- cairo-0.13.8.0
- pango-0.13.8.0
- glib-0.13.8.0
- gtk2hs-buildtools-0.13.8.0
#
# dependences needed for ihaskell-charts but not in Stackage snapshot
- Chart-cairo-1.9.3
- tree-view-0.5

# Resolver copied from IHaskell stack.yaml, see the Dockerfile.
resolver: lts-14.27
jovyan@7707cf3d4825:~$ stack install tree-view
tree-view> configure
tree-view> Configuring tree-view-0.5...
tree-view> build
tree-view> Preprocessing library for tree-view-0.5..
tree-view> Building library for tree-view-0.5..
tree-view> [1 of 1] Compiling Data.Tree.View
tree-view> copy/register
tree-view> Installing library in /opt/stack/snapshots/x86_64-linux/4fd42ae9a557848c60380cbbcb7b203aafa3c3921e6f2fe8e4dce7dfdd0d6f70/8.6.5/lib/x86_64-linux-ghc-8.6.5/tree-view-0.5-EvRQw2klzjNK3yCPTFplie
tree-view> Registering library for tree-view-0.5..
jovyan@7707cf3d4825:~$ stack exec ghc-pkg -- list # locally built packages like ihaskell-display is now hidden
/opt/stack/programs/x86_64-linux/ghc-8.6.5/lib/ghc-8.6.5/package.conf.d
    Cabal-2.4.0.1
    array-0.5.3.0
    base-4.12.0.0
    binary-0.8.6.0
    bytestring-0.10.8.2
    containers-0.6.0.1
    deepseq-1.4.4.0
    directory-1.3.3.0
    filepath-1.4.2.1
    ghc-8.6.5
    ghc-boot-8.6.5
    ghc-boot-th-8.6.5
    ghc-compact-0.1.0.0
    ghc-heap-8.6.5
    ghc-prim-0.5.3
    ghci-8.6.5
    haskeline-0.7.4.3
    hpc-0.6.0.3
    integer-gmp-1.0.2.0
    libiserv-8.6.3
    mtl-2.2.2
    parsec-3.1.13.0
    pretty-1.1.3.6
    process-1.6.5.0
    rts-1.0
    stm-2.5.0.0
    template-haskell-2.14.0.0
    terminfo-0.4.1.2
    text-1.2.3.1
    time-1.8.0.2
    transformers-0.5.6.2
    unix-2.7.2.2
    xhtml-3000.2.2.1
/opt/stack/snapshots/x86_64-linux/4fd42ae9a557848c60380cbbcb7b203aafa3c3921e6f2fe8e4dce7dfdd0d6f70/8.6.5/pkgdb
    tree-view-0.5
/opt/stack/global-project/.stack-work/install/x86_64-linux/4fd42ae9a557848c60380cbbcb7b203aafa3c3921e6f2fe8e4dce7dfdd0d6f70/8.6.5/pkgdb
    (no packages)
jovyan@7707cf3d4825:~$ stack build ihaskell-graphviz # let's try to re-build one I need
alex                             > using precompiled package
base-compat                      > using precompiled package
base64-bytestring                > using precompiled package
base-orphans                     > using precompiled package
basement                         > using precompiled package
blaze-builder                    > using precompiled package
cereal                           > using precompiled package
clock                            > using precompiled package
cmdargs                          > using precompiled package                                                 
colour                           > using precompiled package                                                 
cryptohash-md5                   > using precompiled package                                    
cryptohash-sha1                  > using precompiled package                           
data-default-class               > using precompiled package                                  
dlist                            > using precompiled package                                              
cereal-text                      > using precompiled package                                                   
exceptions                       > using precompiled package                                                        
happy                            > using precompiled package                                                        
ghc-lib-parser                   > using precompiled package                                                
ansi-terminal                    > using precompiled package                                                        
hashable                         > using precompiled package                                                                 
haskeline                        > using precompiled package                                                                 
cookie                           > using precompiled package                                                
data-default-instances-containers> using precompiled package                                               
data-default-instances-dlist     > using precompiled package                                                                                            
haskell-src-exts                 > using precompiled package                                                                                                  
hourglass                        > using precompiled package                                                                                                  
hscolour                         > using precompiled package                                                                                             
async                            > using precompiled package                                                                                        
case-insensitive                 > using precompiled package                                                                                            
integer-logarithms               > using precompiled package                                                                                            
memory                           > using precompiled package                                                                                    
mime-types                       > using precompiled package                                                                     
network                          > using precompiled package                                                           
network-info                     > using precompiled package                                                     
old-locale                       > using precompiled package                                                       
parsec                           > using precompiled package                                                       
http-types                       > using precompiled package                                                
polyparse                        > using precompiled package                                         
asn1-types                       > using precompiled package                                             
cryptonite                       > using precompiled package                                             
pem                              > using precompiled package                                             
primitive                        > using precompiled package                                         
data-default-instances-old-locale> using precompiled package                                                             
Cabal                            > using precompiled package                                                             
network-uri                      > using precompiled package                                                             
old-time                         > using precompiled package                                                             
asn1-encoding                    > using precompiled package                                                               
random                           > using precompiled package                                                               
refact                           > using precompiled package                                                           
scientific                       > using precompiled package                                                               
data-default                     > using precompiled package                                                               
entropy                          > using precompiled package                                            
ghc-paths                        > using precompiled package                                            
cpphs                            > using precompiled package                                          
asn1-parse                       > using precompiled package                                       
semigroups                       > using precompiled package                                        
socks                            > using precompiled package                                        
attoparsec                       > using precompiled package                                       
split                            > using precompiled package                                       
strict                           > using precompiled package                               
syb                              > using precompiled package                               
/opt/IHaskell/ghc-parser/.stack-work/dist/x86_64-linux/Cabal-2.4.0.1/build-lock: openFd: permission denied (Permission denied)
Progress 62/108
jovyan@7707cf3d4825:~$ ls -al /opt/IHaskell/ghc-parser/.stack-work/dist/x86_64-linux/Cabal-2.4.0.1/
total 212
drwxr-sr-x 5 root users   4096 Apr 27 08:16 .
drwxr-sr-x 3 root users   4096 Apr 27 08:16 ..
drwxr-xr-x 4 root users   4096 Apr 27 08:16 build
-rw-r--r-- 1 root users      0 Apr 27 08:16 build-lock
drwxr-sr-x 2 root users   4096 Apr 27 08:16 package.conf.inplace
-rw-r--r-- 1 root users 191416 Apr 27 08:16 setup-config
drwxr-sr-x 3 root users   4096 Apr 27 08:16 stack-build-caches
-rw------- 1 root users     35 Apr 27 06:58 stack-cabal-mod

As you can see build-lock is read only for users group and only writable by root

I don't have a PR yet but I have a pretty concrete suggestion.

https://github.com/jamesdbrock/ihaskell-notebook/blob/fb96a81230dfa4a870c3a0f3200e2d6aa0bab4d4/Dockerfile#L152

I think appending \ and then these two lines after the line above

&& fix-permissions /opt/IHaskell
&& fix-permissions /opt/hvega

should solve the problem. This is place where all the builds related to IHaskell and hvega are done. That might get rid of lingering lock-files of root write only in the build directories of those packages.

If I get a chance to really get my hands on to test this and it works out (and it does not bloat image size), then I might do a pull request and hopefully we can close this issue :)

I couldn't reproduce what you're seeing.

I always see build-lock group-writable in my image.

jovyan@6b44936fd5b5:/opt/IHaskell/ghc-parser/.stack-work/dist/x86_64-linux/Cabal-2.4.0.1$ ll
total 224
drwsrwsr-x 1 root   users   4096 Jun 15 06:45 ./
drwsrwsr-x 1 root   users   4096 Jun 15 04:59 ../
drwsrwsr-x 1 root   users   4096 Jun 15 06:45 build/
-rw-rw-r-- 1 root   users      0 Jun 15 04:59 build-lock

But I pushed your suggestion to master anyway, since I'm sure it won't hurt. Please tell me if it helps you.

https://github.com/jamesdbrock/ihaskell-notebook/blob/7d8e5c9a71dd06ed4c59fbff831fa3915aed8907/Dockerfile#L151

(I'm having a bit of difficulty building the new master on Dockerhub for some some reason. It builds on my machine.)

Ok, the new image with your suggestions is built on Dockerhub now.

https://hub.docker.com/repository/docker/crosscompass/ihaskell-notebook/tags?page=1

Ok, the new image with your suggestions is built on Dockerhub now.

https://hub.docker.com/repository/docker/crosscompass/ihaskell-notebook/tags?page=1

Thanks! Re-building locally installed packages like ihaskell-* inside running Docker image as non-root now works!!