├── .gitignore ├── .travis.yml ├── Makefile ├── README.rst ├── dockerfile └── Dockerfile ├── setup.py ├── test ├── first_test.py └── simple_app │ ├── simple_app.py │ └── vagga.yaml └── vagga_docker ├── __init__.py ├── __main__.py ├── arguments.py ├── config.py ├── main.py ├── network.py ├── runtime.py ├── settings.py └── storage.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.egg-info 3 | /venv 4 | .vagga/ 5 | .coverage 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | services: 4 | - docker 5 | 6 | python: 7 | - 3.6 8 | - 3.5 9 | - 3.4 10 | 11 | matrix: 12 | include: 13 | - os: linux 14 | 15 | - os: osx 16 | sudo: required 17 | language: generic 18 | before_install: 19 | - brew update 20 | - brew install python3 21 | - virtualenv venv -p python3 22 | - source venv/bin/activate 23 | 24 | install: 25 | - pip install . 26 | 27 | script: 28 | - pip install pytest-cov codecov 29 | - py.test --cov=./ ./ 30 | - "echo alpine-mirror: http://mirrors.gigenet.com/alpinelinux/ > ~/.vagga.yaml" 31 | - if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then cd test/simple_app && vagga _build test_simple_flask ; fi 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | tests: 2 | py.test --cov=./ ./ 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Vagga in Docker 3 | =============== 4 | 5 | :Status: PoC / `tracking issue`_ 6 | 7 | This is a prototype which brings vagga as the first-class tool to OS X and 8 | (possibly) windows through docker's layer of compatibility. 9 | 10 | Also see `Running in Gitlab`_ 11 | 12 | .. _tracking issue: https://github.com/tailhook/vagga-docker/issues/1 13 | 14 | Installation 15 | ============ 16 | 17 | Currently it's (you need python >= 3.4):: 18 | 19 | $ pip3 install git+http://github.com/tailhook/vagga-docker 20 | [ .. snip .. ] 21 | $ vagga 22 | Available commands: 23 | run 24 | 25 | .. _docker-for-mac: 26 | 27 | **WARNING:** ensure that docker that you're running is docker for mac, and 28 | not any other docker running by docker-machine or vagrant. Probably do this 29 | by running:: 30 | 31 | printenv DOCKER_HOST 32 | 33 | If this returns non-empty string run:: 34 | 35 | unset DOCKER_HOST 36 | 37 | But this only cleans environment **for current shell**. So you must fix your 38 | docker host where it is defined. 39 | 40 | For the reference, error usually looks like this:: 41 | 42 | Config not found in path "/work" 43 | 44 | (while the config actually there, basically it means docker doesn't see the 45 | volume from your host system) 46 | 47 | 48 | Short FAQ 49 | ========= 50 | 51 | **Why is it in python?** For a quick prototype. It will be integrated into 52 | vagga as soon as is proven to be useful. Or may be we leave it in python if 53 | it would be easier to install. 54 | 55 | **So should I try this version or wait it integrated in vagga?** Definitely you 56 | should. The integrated version will work the same. 57 | 58 | **Why this uses `docker run` instead of API?** We don't want to reimplement 59 | whole tty handling that is done by docker. If it is proven to be bad descision 60 | we will revisit it later. Also we use API for other things that are not 61 | running anything. 62 | 63 | **Is there any difference between this and vagga on linux?** There are two key 64 | differences: you need to export ports that you want to be accessible from the 65 | host system. And we keep files of a container filesystem inside a docker 66 | volume (`the reasons are here`__) However, you can export some part of the 67 | filesystem that is non-sensible for ownership semantics, like this: 68 | 69 | __ https://github.com/tailhook/vagga/issues/269 70 | 71 | .. code-block:: yaml 72 | 73 | containers: 74 | django: 75 | setup: 76 | - !Alpine v3.3 77 | - !Py3Install ['Django >=1.9,<1.10'] 78 | _expose-dirs: 79 | - /usr/lib/python3.5 80 | 81 | commands: 82 | run: !Command 83 | description: Start the django development server 84 | container: django 85 | _expose-ports: [8080] 86 | run: python3 manage.py runserver 87 | 88 | **Please report if you find any other differences using the tool**. Ah, but 89 | exact text of some error messages may differ, don't be too picky :) 90 | 91 | **Why `_expose-ports` and `_expose-dirs` are underscored?** This is a standard 92 | way to add extension metadata or user-defined things in vagga.yaml. We will 93 | remove the underscores as soon as integrate it into main code. Fixing 94 | underscores isn't going to be a big deal. 95 | 96 | **Will linux users add `_expose-ports` and `_expose-dirs` for me?** Frankly, 97 | currently probably now. But it's small change that probably noone will need 98 | to delete. In the future we want to apply ``seccomp`` filters to allow to bind 99 | only exposed ports on linux too. And ``expose-dirs`` will be used to filter 100 | directories that will not be optimized for disk usage, so IDE see them as a 101 | normal directory. 102 | 103 | (It's also cool project to detect ``expose-dirs`` in vagga metadata and add 104 | them to the project files automatically. But I'm not IDE guy, so I'm not sure 105 | if this is possible, or viable) 106 | 107 | **What will be changed when we integrate this into vagga?** We will move more 108 | operations from docker into host system. For example list of commands will 109 | be executed by mac os. Also ``vagga _list``, some parts of ``vagga _clean`` and 110 | so on. But we will do our best to keep semantics exactly the same. 111 | 112 | 113 | Running in Gitlab 114 | ================= 115 | 116 | To run a container on gitlab CI's docker infrastructure, 117 | use image ``tailhook/vagga:v0.8.1``, like this: 118 | 119 | .. code-block:: yaml 120 | 121 | image: tailhook/vagga:v0.8.1 122 | 123 | test: 124 | script: 125 | - vagga test 126 | 127 | Or check out `example-project `_ 128 | 129 | 130 | LICENSE 131 | ======= 132 | 133 | This project has been placed into the public domain. 134 | -------------------------------------------------------------------------------- /dockerfile/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | ADD vagga-0.8.1.tar.xz / 3 | ENV PATH=/vagga:/bin 4 | RUN ["/vagga/busybox", "mkdir", "/bin"] 5 | RUN ["/vagga/busybox", "ln", "-vsfn", "/vagga/busybox", "/bin/sh"] 6 | RUN \ 7 | /vagga/busybox ln -vsfn /vagga/busybox /bin/[ && \ 8 | /vagga/busybox ln -vsfn /vagga/busybox /bin/[[ && \ 9 | /vagga/busybox ln -vsfn /vagga/busybox /bin/acpid && \ 10 | /vagga/busybox ln -vsfn /vagga/busybox /bin/add-shell && \ 11 | /vagga/busybox ln -vsfn /vagga/busybox /bin/addgroup && \ 12 | /vagga/busybox ln -vsfn /vagga/busybox /bin/adduser && \ 13 | /vagga/busybox ln -vsfn /vagga/busybox /bin/adjtimex && \ 14 | /vagga/busybox ln -vsfn /vagga/busybox /bin/arp && \ 15 | /vagga/busybox ln -vsfn /vagga/busybox /bin/arping && \ 16 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ash && \ 17 | /vagga/busybox ln -vsfn /vagga/busybox /bin/awk && \ 18 | /vagga/busybox ln -vsfn /vagga/busybox /bin/base64 && \ 19 | /vagga/busybox ln -vsfn /vagga/busybox /bin/basename && \ 20 | /vagga/busybox ln -vsfn /vagga/busybox /bin/bbconfig && \ 21 | /vagga/busybox ln -vsfn /vagga/busybox /bin/beep && \ 22 | /vagga/busybox ln -vsfn /vagga/busybox /bin/blkdiscard && \ 23 | /vagga/busybox ln -vsfn /vagga/busybox /bin/blkid && \ 24 | /vagga/busybox ln -vsfn /vagga/busybox /bin/blockdev && \ 25 | /vagga/busybox ln -vsfn /vagga/busybox /bin/brctl && \ 26 | /vagga/busybox ln -vsfn /vagga/busybox /bin/bunzip2 && \ 27 | /vagga/busybox ln -vsfn /vagga/busybox /bin/bzcat && \ 28 | /vagga/busybox ln -vsfn /vagga/busybox /bin/bzip2 && \ 29 | /vagga/busybox ln -vsfn /vagga/busybox /bin/cal && \ 30 | /vagga/busybox ln -vsfn /vagga/busybox /bin/cat && \ 31 | /vagga/busybox ln -vsfn /vagga/busybox /bin/catv && \ 32 | /vagga/busybox ln -vsfn /vagga/busybox /bin/chgrp && \ 33 | /vagga/busybox ln -vsfn /vagga/busybox /bin/chmod && \ 34 | /vagga/busybox ln -vsfn /vagga/busybox /bin/chown && \ 35 | /vagga/busybox ln -vsfn /vagga/busybox /bin/chpasswd && \ 36 | /vagga/busybox ln -vsfn /vagga/busybox /bin/chroot && \ 37 | /vagga/busybox ln -vsfn /vagga/busybox /bin/chvt && \ 38 | /vagga/busybox ln -vsfn /vagga/busybox /bin/cksum && \ 39 | /vagga/busybox ln -vsfn /vagga/busybox /bin/clear && \ 40 | /vagga/busybox ln -vsfn /vagga/busybox /bin/cmp && \ 41 | /vagga/busybox ln -vsfn /vagga/busybox /bin/comm && \ 42 | /vagga/busybox ln -vsfn /vagga/busybox /bin/conspy && \ 43 | /vagga/busybox ln -vsfn /vagga/busybox /bin/cp && \ 44 | /vagga/busybox ln -vsfn /vagga/busybox /bin/cpio && \ 45 | /vagga/busybox ln -vsfn /vagga/busybox /bin/crond && \ 46 | /vagga/busybox ln -vsfn /vagga/busybox /bin/crontab && \ 47 | /vagga/busybox ln -vsfn /vagga/busybox /bin/cryptpw && \ 48 | /vagga/busybox ln -vsfn /vagga/busybox /bin/cut && \ 49 | /vagga/busybox ln -vsfn /vagga/busybox /bin/date && \ 50 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dc && \ 51 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dd && \ 52 | /vagga/busybox ln -vsfn /vagga/busybox /bin/deallocvt && \ 53 | /vagga/busybox ln -vsfn /vagga/busybox /bin/delgroup && \ 54 | /vagga/busybox ln -vsfn /vagga/busybox /bin/deluser && \ 55 | /vagga/busybox ln -vsfn /vagga/busybox /bin/depmod && \ 56 | /vagga/busybox ln -vsfn /vagga/busybox /bin/df && \ 57 | /vagga/busybox ln -vsfn /vagga/busybox /bin/diff && \ 58 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dirname && \ 59 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dmesg && \ 60 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dnsd && \ 61 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dnsdomainname && \ 62 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dos2unix && \ 63 | /vagga/busybox ln -vsfn /vagga/busybox /bin/du && \ 64 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dumpkmap && \ 65 | /vagga/busybox ln -vsfn /vagga/busybox /bin/dumpleases && \ 66 | /vagga/busybox ln -vsfn /vagga/busybox /bin/echo && \ 67 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ed && \ 68 | /vagga/busybox ln -vsfn /vagga/busybox /bin/egrep && \ 69 | /vagga/busybox ln -vsfn /vagga/busybox /bin/eject && \ 70 | /vagga/busybox ln -vsfn /vagga/busybox /bin/env && \ 71 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ether-wake && \ 72 | /vagga/busybox ln -vsfn /vagga/busybox /bin/expand && \ 73 | /vagga/busybox ln -vsfn /vagga/busybox /bin/expr && \ 74 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fakeidentd && \ 75 | /vagga/busybox ln -vsfn /vagga/busybox /bin/false && \ 76 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fatattr && \ 77 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fbset && \ 78 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fbsplash && \ 79 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fdflush && \ 80 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fdformat && \ 81 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fdisk && \ 82 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fgrep && \ 83 | /vagga/busybox ln -vsfn /vagga/busybox /bin/find && \ 84 | /vagga/busybox ln -vsfn /vagga/busybox /bin/findfs && \ 85 | /vagga/busybox ln -vsfn /vagga/busybox /bin/flock && \ 86 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fold && \ 87 | /vagga/busybox ln -vsfn /vagga/busybox /bin/free && \ 88 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fsck && \ 89 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fstrim && \ 90 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fsync && \ 91 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ftpd && \ 92 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ftpget && \ 93 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ftpput && \ 94 | /vagga/busybox ln -vsfn /vagga/busybox /bin/fuser && \ 95 | /vagga/busybox ln -vsfn /vagga/busybox /bin/getopt && \ 96 | /vagga/busybox ln -vsfn /vagga/busybox /bin/getty && \ 97 | /vagga/busybox ln -vsfn /vagga/busybox /bin/grep && \ 98 | /vagga/busybox ln -vsfn /vagga/busybox /bin/groups && \ 99 | /vagga/busybox ln -vsfn /vagga/busybox /bin/gunzip && \ 100 | /vagga/busybox ln -vsfn /vagga/busybox /bin/gzip && \ 101 | /vagga/busybox ln -vsfn /vagga/busybox /bin/halt && \ 102 | /vagga/busybox ln -vsfn /vagga/busybox /bin/hd && \ 103 | /vagga/busybox ln -vsfn /vagga/busybox /bin/hdparm && \ 104 | /vagga/busybox ln -vsfn /vagga/busybox /bin/head && \ 105 | /vagga/busybox ln -vsfn /vagga/busybox /bin/hexdump && \ 106 | /vagga/busybox ln -vsfn /vagga/busybox /bin/hostid && \ 107 | /vagga/busybox ln -vsfn /vagga/busybox /bin/hostname && \ 108 | /vagga/busybox ln -vsfn /vagga/busybox /bin/httpd && \ 109 | /vagga/busybox ln -vsfn /vagga/busybox /bin/hwclock && \ 110 | /vagga/busybox ln -vsfn /vagga/busybox /bin/id && \ 111 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ifconfig && \ 112 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ifdown && \ 113 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ifenslave && \ 114 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ifup && \ 115 | /vagga/busybox ln -vsfn /vagga/busybox /bin/inetd && \ 116 | /vagga/busybox ln -vsfn /vagga/busybox /bin/init && \ 117 | /vagga/busybox ln -vsfn /vagga/busybox /bin/inotifyd && \ 118 | /vagga/busybox ln -vsfn /vagga/busybox /bin/insmod && \ 119 | /vagga/busybox ln -vsfn /vagga/busybox /bin/install && \ 120 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ionice && \ 121 | /vagga/busybox ln -vsfn /vagga/busybox /bin/iostat && \ 122 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ip && \ 123 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ipaddr && \ 124 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ipcalc && \ 125 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ipcrm && \ 126 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ipcs && \ 127 | /vagga/busybox ln -vsfn /vagga/busybox /bin/iplink && \ 128 | /vagga/busybox ln -vsfn /vagga/busybox /bin/iproute && \ 129 | /vagga/busybox ln -vsfn /vagga/busybox /bin/iprule && \ 130 | /vagga/busybox ln -vsfn /vagga/busybox /bin/iptunnel && \ 131 | /vagga/busybox ln -vsfn /vagga/busybox /bin/kbd_mode && \ 132 | /vagga/busybox ln -vsfn /vagga/busybox /bin/kill && \ 133 | /vagga/busybox ln -vsfn /vagga/busybox /bin/killall && \ 134 | /vagga/busybox ln -vsfn /vagga/busybox /bin/killall5 && \ 135 | /vagga/busybox ln -vsfn /vagga/busybox /bin/klogd && \ 136 | /vagga/busybox ln -vsfn /vagga/busybox /bin/less && \ 137 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ln && \ 138 | /vagga/busybox ln -vsfn /vagga/busybox /bin/loadfont && \ 139 | /vagga/busybox ln -vsfn /vagga/busybox /bin/loadkmap && \ 140 | /vagga/busybox ln -vsfn /vagga/busybox /bin/logger && \ 141 | /vagga/busybox ln -vsfn /vagga/busybox /bin/login && \ 142 | /vagga/busybox ln -vsfn /vagga/busybox /bin/logread && \ 143 | /vagga/busybox ln -vsfn /vagga/busybox /bin/losetup && \ 144 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ls && \ 145 | /vagga/busybox ln -vsfn /vagga/busybox /bin/lsmod && \ 146 | /vagga/busybox ln -vsfn /vagga/busybox /bin/lsof && \ 147 | /vagga/busybox ln -vsfn /vagga/busybox /bin/lspci && \ 148 | /vagga/busybox ln -vsfn /vagga/busybox /bin/lsusb && \ 149 | /vagga/busybox ln -vsfn /vagga/busybox /bin/lzcat && \ 150 | /vagga/busybox ln -vsfn /vagga/busybox /bin/lzma && \ 151 | /vagga/busybox ln -vsfn /vagga/busybox /bin/lzop && \ 152 | /vagga/busybox ln -vsfn /vagga/busybox /bin/lzopcat && \ 153 | /vagga/busybox ln -vsfn /vagga/busybox /bin/makemime && \ 154 | /vagga/busybox ln -vsfn /vagga/busybox /bin/md5sum && \ 155 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mdev && \ 156 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mesg && \ 157 | /vagga/busybox ln -vsfn /vagga/busybox /bin/microcom && \ 158 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mkdir && \ 159 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mkdosfs && \ 160 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mkfifo && \ 161 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mkfs.vfat && \ 162 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mknod && \ 163 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mkpasswd && \ 164 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mkswap && \ 165 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mktemp && \ 166 | /vagga/busybox ln -vsfn /vagga/busybox /bin/modinfo && \ 167 | /vagga/busybox ln -vsfn /vagga/busybox /bin/modprobe && \ 168 | /vagga/busybox ln -vsfn /vagga/busybox /bin/more && \ 169 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mount && \ 170 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mountpoint && \ 171 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mpstat && \ 172 | /vagga/busybox ln -vsfn /vagga/busybox /bin/mv && \ 173 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nameif && \ 174 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nanddump && \ 175 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nandwrite && \ 176 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nbd-client && \ 177 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nc && \ 178 | /vagga/busybox ln -vsfn /vagga/busybox /bin/netstat && \ 179 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nice && \ 180 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nmeter && \ 181 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nohup && \ 182 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nologin && \ 183 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nsenter && \ 184 | /vagga/busybox ln -vsfn /vagga/busybox /bin/nslookup && \ 185 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ntpd && \ 186 | /vagga/busybox ln -vsfn /vagga/busybox /bin/od && \ 187 | /vagga/busybox ln -vsfn /vagga/busybox /bin/openvt && \ 188 | /vagga/busybox ln -vsfn /vagga/busybox /bin/passwd && \ 189 | /vagga/busybox ln -vsfn /vagga/busybox /bin/patch && \ 190 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pgrep && \ 191 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pidof && \ 192 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ping && \ 193 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ping6 && \ 194 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pipe_progress && \ 195 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pkill && \ 196 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pmap && \ 197 | /vagga/busybox ln -vsfn /vagga/busybox /bin/poweroff && \ 198 | /vagga/busybox ln -vsfn /vagga/busybox /bin/powertop && \ 199 | /vagga/busybox ln -vsfn /vagga/busybox /bin/printenv && \ 200 | /vagga/busybox ln -vsfn /vagga/busybox /bin/printf && \ 201 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ps && \ 202 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pscan && \ 203 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pstree && \ 204 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pwd && \ 205 | /vagga/busybox ln -vsfn /vagga/busybox /bin/pwdx && \ 206 | /vagga/busybox ln -vsfn /vagga/busybox /bin/raidautorun && \ 207 | /vagga/busybox ln -vsfn /vagga/busybox /bin/rdate && \ 208 | /vagga/busybox ln -vsfn /vagga/busybox /bin/rdev && \ 209 | /vagga/busybox ln -vsfn /vagga/busybox /bin/readahead && \ 210 | /vagga/busybox ln -vsfn /vagga/busybox /bin/readlink && \ 211 | /vagga/busybox ln -vsfn /vagga/busybox /bin/readprofile && \ 212 | /vagga/busybox ln -vsfn /vagga/busybox /bin/realpath && \ 213 | /vagga/busybox ln -vsfn /vagga/busybox /bin/reboot && \ 214 | /vagga/busybox ln -vsfn /vagga/busybox /bin/reformime && \ 215 | /vagga/busybox ln -vsfn /vagga/busybox /bin/remove-shell && \ 216 | /vagga/busybox ln -vsfn /vagga/busybox /bin/renice && \ 217 | /vagga/busybox ln -vsfn /vagga/busybox /bin/reset && \ 218 | /vagga/busybox ln -vsfn /vagga/busybox /bin/resize && \ 219 | /vagga/busybox ln -vsfn /vagga/busybox /bin/rev && \ 220 | /vagga/busybox ln -vsfn /vagga/busybox /bin/rfkill && \ 221 | /vagga/busybox ln -vsfn /vagga/busybox /bin/rm && \ 222 | /vagga/busybox ln -vsfn /vagga/busybox /bin/rmdir && \ 223 | /vagga/busybox ln -vsfn /vagga/busybox /bin/rmmod && \ 224 | /vagga/busybox ln -vsfn /vagga/busybox /bin/route && \ 225 | /vagga/busybox ln -vsfn /vagga/busybox /bin/run-parts && \ 226 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sed && \ 227 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sendmail && \ 228 | /vagga/busybox ln -vsfn /vagga/busybox /bin/seq && \ 229 | /vagga/busybox ln -vsfn /vagga/busybox /bin/setconsole && \ 230 | /vagga/busybox ln -vsfn /vagga/busybox /bin/setfont && \ 231 | /vagga/busybox ln -vsfn /vagga/busybox /bin/setkeycodes && \ 232 | /vagga/busybox ln -vsfn /vagga/busybox /bin/setlogcons && \ 233 | /vagga/busybox ln -vsfn /vagga/busybox /bin/setserial && \ 234 | /vagga/busybox ln -vsfn /vagga/busybox /bin/setsid && \ 235 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sh && \ 236 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sha1sum && \ 237 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sha256sum && \ 238 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sha3sum && \ 239 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sha512sum && \ 240 | /vagga/busybox ln -vsfn /vagga/busybox /bin/showkey && \ 241 | /vagga/busybox ln -vsfn /vagga/busybox /bin/shuf && \ 242 | /vagga/busybox ln -vsfn /vagga/busybox /bin/slattach && \ 243 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sleep && \ 244 | /vagga/busybox ln -vsfn /vagga/busybox /bin/smemcap && \ 245 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sort && \ 246 | /vagga/busybox ln -vsfn /vagga/busybox /bin/split && \ 247 | /vagga/busybox ln -vsfn /vagga/busybox /bin/stat && \ 248 | /vagga/busybox ln -vsfn /vagga/busybox /bin/strings && \ 249 | /vagga/busybox ln -vsfn /vagga/busybox /bin/stty && \ 250 | /vagga/busybox ln -vsfn /vagga/busybox /bin/su && \ 251 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sum && \ 252 | /vagga/busybox ln -vsfn /vagga/busybox /bin/swapoff && \ 253 | /vagga/busybox ln -vsfn /vagga/busybox /bin/swapon && \ 254 | /vagga/busybox ln -vsfn /vagga/busybox /bin/switch_root && \ 255 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sync && \ 256 | /vagga/busybox ln -vsfn /vagga/busybox /bin/sysctl && \ 257 | /vagga/busybox ln -vsfn /vagga/busybox /bin/syslogd && \ 258 | /vagga/busybox ln -vsfn /vagga/busybox /bin/tac && \ 259 | /vagga/busybox ln -vsfn /vagga/busybox /bin/tail && \ 260 | /vagga/busybox ln -vsfn /vagga/busybox /bin/tar && \ 261 | /vagga/busybox ln -vsfn /vagga/busybox /bin/tee && \ 262 | /vagga/busybox ln -vsfn /vagga/busybox /bin/telnet && \ 263 | /vagga/busybox ln -vsfn /vagga/busybox /bin/test && \ 264 | /vagga/busybox ln -vsfn /vagga/busybox /bin/tftp && \ 265 | /vagga/busybox ln -vsfn /vagga/busybox /bin/time && \ 266 | /vagga/busybox ln -vsfn /vagga/busybox /bin/timeout && \ 267 | /vagga/busybox ln -vsfn /vagga/busybox /bin/top && \ 268 | /vagga/busybox ln -vsfn /vagga/busybox /bin/touch && \ 269 | /vagga/busybox ln -vsfn /vagga/busybox /bin/tr && \ 270 | /vagga/busybox ln -vsfn /vagga/busybox /bin/traceroute && \ 271 | /vagga/busybox ln -vsfn /vagga/busybox /bin/traceroute6 && \ 272 | /vagga/busybox ln -vsfn /vagga/busybox /bin/true && \ 273 | /vagga/busybox ln -vsfn /vagga/busybox /bin/truncate && \ 274 | /vagga/busybox ln -vsfn /vagga/busybox /bin/tty && \ 275 | /vagga/busybox ln -vsfn /vagga/busybox /bin/ttysize && \ 276 | /vagga/busybox ln -vsfn /vagga/busybox /bin/tunctl && \ 277 | /vagga/busybox ln -vsfn /vagga/busybox /bin/udhcpc && \ 278 | /vagga/busybox ln -vsfn /vagga/busybox /bin/udhcpc6 && \ 279 | /vagga/busybox ln -vsfn /vagga/busybox /bin/udhcpd && \ 280 | /vagga/busybox ln -vsfn /vagga/busybox /bin/umount && \ 281 | /vagga/busybox ln -vsfn /vagga/busybox /bin/uname && \ 282 | /vagga/busybox ln -vsfn /vagga/busybox /bin/unexpand && \ 283 | /vagga/busybox ln -vsfn /vagga/busybox /bin/uniq && \ 284 | /vagga/busybox ln -vsfn /vagga/busybox /bin/unix2dos && \ 285 | /vagga/busybox ln -vsfn /vagga/busybox /bin/unlink && \ 286 | /vagga/busybox ln -vsfn /vagga/busybox /bin/unlzma && \ 287 | /vagga/busybox ln -vsfn /vagga/busybox /bin/unlzop && \ 288 | /vagga/busybox ln -vsfn /vagga/busybox /bin/unshare && \ 289 | /vagga/busybox ln -vsfn /vagga/busybox /bin/unxz && \ 290 | /vagga/busybox ln -vsfn /vagga/busybox /bin/unzip && \ 291 | /vagga/busybox ln -vsfn /vagga/busybox /bin/uptime && \ 292 | /vagga/busybox ln -vsfn /vagga/busybox /bin/usleep && \ 293 | /vagga/busybox ln -vsfn /vagga/busybox /bin/uudecode && \ 294 | /vagga/busybox ln -vsfn /vagga/busybox /bin/uuencode && \ 295 | /vagga/busybox ln -vsfn /vagga/busybox /bin/vconfig && \ 296 | /vagga/busybox ln -vsfn /vagga/busybox /bin/vi && \ 297 | /vagga/busybox ln -vsfn /vagga/busybox /bin/vlock && \ 298 | /vagga/busybox ln -vsfn /vagga/busybox /bin/volname && \ 299 | /vagga/busybox ln -vsfn /vagga/busybox /bin/watch && \ 300 | /vagga/busybox ln -vsfn /vagga/busybox /bin/watchdog && \ 301 | /vagga/busybox ln -vsfn /vagga/busybox /bin/wc && \ 302 | /vagga/busybox ln -vsfn /vagga/busybox /bin/wget && \ 303 | /vagga/busybox ln -vsfn /vagga/busybox /bin/which && \ 304 | /vagga/busybox ln -vsfn /vagga/busybox /bin/whoami && \ 305 | /vagga/busybox ln -vsfn /vagga/busybox /bin/whois && \ 306 | /vagga/busybox ln -vsfn /vagga/busybox /bin/xargs && \ 307 | /vagga/busybox ln -vsfn /vagga/busybox /bin/xzcat && \ 308 | /vagga/busybox ln -vsfn /vagga/busybox /bin/yes && \ 309 | /vagga/busybox ln -vsfn /vagga/busybox /bin/zcat && \ 310 | true 311 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from setuptools import setup 4 | 5 | setup(name='vagga-docker', 6 | version='0.1', 7 | description='A wrapper to run vagga in docker (easier on osx)', 8 | author='Paul Colomiets', 9 | author_email='paul@colomiets.name', 10 | url='http://github.com/tailhook/vagga-docker', 11 | packages=['vagga_docker'], 12 | install_requires=[ 13 | 'docker-py', 14 | 'PyYaml', 15 | ], 16 | entry_points = { 17 | 'console_scripts': ['vagga=vagga_docker.main:main'], 18 | }, 19 | classifiers=[ 20 | 'Development Status :: 4 - Beta', 21 | 'Programming Language :: Python :: 3', 22 | 'Programming Language :: Python :: 3.4', 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /test/first_test.py: -------------------------------------------------------------------------------- 1 | def test_true(): 2 | assert True 3 | -------------------------------------------------------------------------------- /test/simple_app/simple_app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def hello_world(): 7 | return 'Hello World!' 8 | 9 | if __name__ == '__main__': 10 | app.run(host='0.0.0.0') 11 | -------------------------------------------------------------------------------- /test/simple_app/vagga.yaml: -------------------------------------------------------------------------------- 1 | containers: 2 | test_simple_flask: 3 | setup: 4 | - !Alpine v3.5 5 | - !Py3Install 6 | - werkzeug==0.9.4 7 | - MarkupSafe==0.23 8 | - itsdangerous==0.22 9 | - jinja2==2.7.2 10 | - Flask==0.10.1 11 | commands: 12 | run: !Command 13 | description: Start the Flask development server 14 | container: test_simple_flask 15 | _expose-ports: [5000] 16 | run: python3 simple_app.py 17 | -------------------------------------------------------------------------------- /vagga_docker/__init__.py: -------------------------------------------------------------------------------- 1 | VAGGA_IMAGE = "tailhook/vagga:v0.8.1" 2 | -------------------------------------------------------------------------------- /vagga_docker/__main__.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | from .main import main 3 | main() 4 | -------------------------------------------------------------------------------- /vagga_docker/arguments.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | 4 | def parse_args(): 5 | ap = argparse.ArgumentParser( 6 | usage="vagga [options] COMMAND [ARGS...]", 7 | description=""" 8 | Runs a command in container, optionally builds container if that 9 | does not exists or outdated. Run `vagga` without arguments to see 10 | the list of commands. 11 | """) 12 | ap.add_argument("command", nargs=argparse.REMAINDER, 13 | help="A vagga command to run") 14 | ap.add_argument("-V", "--version", help="Show vagga version and exit") 15 | ap.add_argument("-E", "--env", "--environ", metavar="NAME=VALUE", 16 | help="Set environment variable for running command") 17 | ap.add_argument("-e", "--use-env", metavar="VAR", 18 | help="Propagate variable VAR into command environment") 19 | ap.add_argument("--ignore-owner-check", action="store_true", 20 | help="Ignore checking owner of the project directory") 21 | ap.add_argument("--no-prerequisites", action="store_true", 22 | help="Run only specified command(s), don't run prerequisites") 23 | ap.add_argument("--no-image-download", action="store_true", 24 | help="Do not download container image from image index.") 25 | ap.add_argument("--no-build", action="store_true", 26 | help="Do not build container even if it is out of date. \ 27 | Return error code 29 if it's out of date.") 28 | ap.add_argument("--no-version-check", action="store_true", 29 | help="Do not run versioning code, just pick whatever \ 30 | container version with the name was run last (or \ 31 | actually whatever is symlinked under \ 32 | `.vagga/container_name`). Implies `--no-build`") 33 | ap.add_argument("-m", "--run-multi", nargs="*", 34 | help="Run the following list of commands. Each without an \ 35 | arguments. When any of them fails, stop the chain. \ 36 | Basically it's the shortcut to `vagga cmd1 && vagga \ 37 | cmd2` except containers for `cmd2` are built \ 38 | beforehand, for your convenience. Also builtin commands \ 39 | (those starting with underscore) do not work with \ 40 | `vagga -m`") 41 | return ap.parse_args() 42 | -------------------------------------------------------------------------------- /vagga_docker/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import yaml 4 | 5 | 6 | class FancyLoader(yaml.Loader): 7 | pass 8 | 9 | 10 | def generic_object(loader, suffix, node): 11 | if isinstance(node, yaml.ScalarNode): 12 | constructor = loader.__class__.construct_scalar 13 | elif isinstance(node, yaml.SequenceNode): 14 | constructor = loader.__class__.construct_sequence 15 | elif isinstance(node, yaml.MappingNode): 16 | constructor = loader.__class__.construct_mapping 17 | else: 18 | raise ValueError(node) 19 | # TODO(tailhook) wrap into some object? 20 | return constructor(loader, node) 21 | 22 | 23 | yaml.add_multi_constructor('!', generic_object, Loader=FancyLoader) 24 | 25 | def load(f): 26 | return yaml.load(f, Loader=FancyLoader) 27 | 28 | 29 | def find_config(): 30 | path = pathlib.Path(os.getcwd()) 31 | suffix = pathlib.Path("") 32 | while str(path) != path.root: 33 | vagga = path / 'vagga.yaml' 34 | if vagga.exists(): 35 | return path, vagga, suffix 36 | suffix = path.parts[-1] / suffix 37 | path = path.parent 38 | raise RuntimeError("No vagga.yaml found in path {!r}".format(path)) 39 | 40 | 41 | def get_config(): 42 | dir, vagga, suffix = find_config() 43 | with vagga.open('rb') as file: 44 | data = load(file) 45 | return dir, data, suffix 46 | -------------------------------------------------------------------------------- /vagga_docker/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import logging 5 | import docker 6 | import subprocess 7 | 8 | from . import VAGGA_IMAGE 9 | from . import config 10 | from . import storage 11 | from . import runtime 12 | from . import arguments 13 | from . import settings 14 | from . import network 15 | 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | 20 | def pull_container(cli, image): 21 | try: 22 | info = cli.inspect_image(image) 23 | except docker.errors.NotFound: 24 | # Use command-line so that we don't have to reimplement progressbar 25 | subprocess.check_call(["docker", "pull", VAGGA_IMAGE]) 26 | 27 | 28 | def main(): 29 | 30 | logging.basicConfig( 31 | # TODO(tailhook) should we use RUST_LOG like in original vagga? 32 | level=os.environ.get('VAGGA_LOG', 'WARNING')) 33 | 34 | path, cfg, suffix = config.get_config() 35 | args = arguments.parse_args() 36 | 37 | setting = settings.parse_all(path) 38 | 39 | vagga = runtime.Vagga(path, cfg, args) 40 | cli = docker.from_env(assert_hostname=False) 41 | 42 | if not vagga.vagga_dir.exists(): 43 | vagga.vagga_dir.mkdir() 44 | 45 | pull_container(cli, VAGGA_IMAGE) 46 | 47 | vagga.storage_volume = storage.get_volume(vagga, cli) 48 | 49 | vagga.network_container = network.check_container(vagga, cli) 50 | 51 | setting['auto-apply-sysctl'] = True 52 | 53 | command_line = [ 54 | "docker", "run", 55 | "--volume={}:/work".format(vagga.base), 56 | "--volume={}:/work/.vagga".format(vagga.storage_volume), 57 | "--workdir=/work/{}".format(suffix), 58 | "--privileged", 59 | "--interactive", 60 | "--tty", 61 | "--rm", 62 | "--net=container:" + vagga.network_container, 63 | "--env=VAGGA_SETTINGS=" + json.dumps(setting), 64 | "--env=RUST_BACKTRACE=1", 65 | VAGGA_IMAGE, 66 | "/vagga/vagga", 67 | "--ignore-owner-check", # this is needed on linux only 68 | ] + sys.argv[1:] 69 | log.info("Docker command-line: %r", command_line) 70 | # This don't work on Windows. We may figure out a better way 71 | os.execvp("docker", command_line) 72 | 73 | 74 | -------------------------------------------------------------------------------- /vagga_docker/network.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import docker 3 | 4 | from . import VAGGA_IMAGE 5 | 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | def check_container(vagga, cli): 11 | # use storage volume as identifier for the container 12 | container_name = vagga.storage_volume 13 | 14 | ports = _find_all_exposed_ports(vagga) 15 | if not _validate_container(container_name, ports, cli): 16 | cli.create_container( 17 | name=container_name, 18 | image=VAGGA_IMAGE, 19 | command=["/vagga/busybox", "sleep", "86400000"], # 1000 days 20 | ports=list(ports), 21 | host_config=cli.create_host_config( 22 | port_bindings={x: x for x in ports}, 23 | ), 24 | ) 25 | cli.start(container_name) 26 | return container_name 27 | 28 | 29 | def _validate_container(container_name, ports, cli): 30 | try: 31 | info = cli.inspect_container(container_name) 32 | except docker.errors.NotFound: 33 | log.debug("No such container %r", container_name) 34 | return False 35 | else: 36 | extra = set() 37 | absent = set(ports) 38 | for port, value in (info['NetworkSettings']['Ports'] or {}).items(): 39 | num, kind = port.split('/') 40 | if kind != 'tcp': 41 | log.warning("Non tcp port %r", port) 42 | num = int(num) 43 | if num in ports: 44 | absent.remove(num) 45 | else: 46 | extra.add(num) 47 | if absent or extra: 48 | all_containers = cli.containers(filters=dict(status='running')) 49 | netw = 'container:' + container_name 50 | used_containers = [item['name'] 51 | for item in all_containers 52 | if item['HostConfig']['NetworkMode'] == netw] 53 | if used_containers: 54 | log.warning("Exposed ports don't match, extra %r, absent %r. " 55 | "You need to stop all containers so we can update " 56 | "exposed ports. Running containers %r", 57 | absent, extra, ', '.join(used_containers)) 58 | else: 59 | cli.remove_container(container_name, force=True) 60 | return False 61 | 62 | if info['State']['Running']: 63 | log.debug("Container is ok and running") 64 | return True 65 | else: 66 | cli.remove_container(container_name) 67 | return False 68 | 69 | 70 | def _find_all_exposed_ports(vagga): 71 | return frozenset(_exposed_ports(vagga.commands)) 72 | 73 | 74 | def _exposed_ports(commands): 75 | for cmd in commands.values(): 76 | # allow expose ports in the command 77 | yield from _get_ports(cmd) 78 | # and in children commands (for supervise) 79 | for child in cmd.get('children', {}).values(): 80 | yield from _get_ports(child) 81 | 82 | 83 | def _get_ports(cmd): 84 | ports = cmd.get('_expose-ports', []) 85 | if not isinstance(ports, list): 86 | raise RuntimeError("the `_expose-ports` setting must be a list" 87 | "of integers, got {!r} instead".format(ports)) 88 | yield from ports 89 | 90 | -------------------------------------------------------------------------------- /vagga_docker/runtime.py: -------------------------------------------------------------------------------- 1 | class Vagga(object): 2 | 3 | def __init__(self, path, config, arguments): 4 | self.base = path 5 | self.vagga_dir = path / '.vagga' 6 | self.containers = config.get('containers', {}) 7 | self.commands = config.get('commands', {}) 8 | self.arguments = arguments 9 | 10 | if arguments.command: 11 | self.run_commands = [arguments.command[0]] 12 | else: 13 | self.run_commands = arguments.run_multi or [] 14 | 15 | -------------------------------------------------------------------------------- /vagga_docker/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | import pathlib 4 | import logging 5 | import warnings 6 | 7 | 8 | log = logging.getLogger(__name__) 9 | SETTING_FILES = [ 10 | '.vagga.yaml', 11 | '.vagga/settings.yaml', 12 | '.config/vagga/settings.yaml', 13 | ] 14 | 15 | 16 | def parse_all(vagga_base): 17 | base_str = str(vagga_base) 18 | settings = {} 19 | for filename in SETTING_FILES: 20 | path = pathlib.Path(os.path.expanduser('~')) / filename 21 | if path.exists(): 22 | with path.open('rb') as f: 23 | try: 24 | data = yaml.load(f) 25 | except ValueError: 26 | print("WARNING: error loading settings from {}: {}" 27 | .format(path, data)) 28 | continue 29 | site = data.pop('site-settings', {}).get(base_str, {}) 30 | settings.update(data) 31 | settings.update(site) 32 | if settings.pop('external-volumes', None): 33 | warnings.warn("External volumes are not supported " 34 | " (defined in {})".format(path)) 35 | if settings.pop('storage-dir', None): 36 | warnings.warn("storage-dir is not supported as we use docker" 37 | " volumes for storage" 38 | " (defined in {})".format(path)) 39 | if settings.pop('cache-dir', None): 40 | warnings.warn("cache-dir is not supported yet" 41 | " (defined in {})".format(path)) 42 | log.debug("Got settings %r", settings) 43 | return settings 44 | 45 | -------------------------------------------------------------------------------- /vagga_docker/storage.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import docker 3 | 4 | log = logging.getLogger(__name__) 5 | 6 | 7 | def get_volume(vagga, dock): 8 | vol_path = vagga.vagga_dir / '.docker-volume' 9 | if vol_path.exists(): 10 | with vol_path.open('rt') as f: 11 | name = f.read().strip() 12 | try: 13 | volume_info = dock.inspect_volume(name) 14 | except docker.errors.NotFound: 15 | # we don't create volumes that should already be created, this 16 | # because we assume that something is wrong with the system, like 17 | # maybe some provider of docker volumes doesn't work, or maybe you 18 | # connect to a wrong docker 19 | raise RuntimeError("It looks like volume {!r} has been deleted " 20 | "manually. Remove `.vagga/.docker-volume` " 21 | "to fix the problem" 22 | .format(name)) 23 | else: 24 | log.debug("Volume info: %r", volume_info) 25 | return name 26 | 27 | vol_name = 'vagga-' + vagga.base.parts[-1] 28 | try: 29 | dock.inspect_volume(vol_name) 30 | except docker.errors.NotFound: 31 | pass 32 | else: 33 | # same algorithm that is used for storage-dir 34 | for i in range(100): 35 | tmpname = "{}-{}".format(vol_name, i) 36 | try: 37 | dock.inspect_volume(tmpname) 38 | except docker.errors.NotFound: 39 | vol_name = tmpname 40 | break; 41 | else: 42 | continue 43 | else: 44 | raise RuntimeError("Can't find valid name for volume {!r}, " 45 | "tried up to {!r}".format(vol_name, tmpname)) 46 | log.info("Creating volume %r", vol_name) 47 | dock.create_volume(vol_name) 48 | with vol_path.open('wt') as file: 49 | file.write(vol_name) 50 | return vol_name 51 | --------------------------------------------------------------------------------