{
"info": {
"author": "Christophe Combelles",
"author_email": "ccomb@anybox.fr",
"bugtrack_url": null,
"classifiers": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: Apache Software License",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Topic :: System :: Clustering"
],
"description": ".. image:: https://travis-ci.org/anybox/buttervolume.svg?branch=master\n :target: https://travis-ci.org/anybox/buttervolume\n :alt: Travis state\n\n\nBTRFS Volume plugin for Docker\n==============================\n\nThis package provides a Docker volume plugin that creates a BTRFS subvolume for\neach container volume.\n\nPlease note this is **not** a BTRFS storage driver for Docker, but a plugin to\nmanage volumes. It means you can use any storage driver, such as AUFS, this is\nan independant topic.\n\n.. contents::\n\n\nIntroduction\n************\n\n`BTRFS `_ is a next-generation copy-on-write\nfilesystem with subvolume and snapshot support. A BTRFS `subvolume\n`_ can be\nseen as an independant file namespace that can live in a directory and can be\nmounted as a filesystem and snapshotted individually.\n\nOn the other hand, `Docker volumes\n`_ are commonly used\nto store persistent data of stateful containers, such as a MySQL/PostgreSQL\ndatabase or an upload directory of a CMS. By default, Docker volumes are just\nlocal directories in the host filesystem. A number of `Volume plugins\n`_\nalready exist for various storage backends, including distributed filesystems,\nbut small clusters often can't afford to deploy a distributed filesystem.\n\nWe believe BTRFS subvolumes are a powerful and lightweight storage solution for\nDocker volumes, allowing fast and easy replication (and backup) across several\nnodes of a small cluster.\n\nPrerequisites\n*************\n\nMake sure the directory ``/var/lib/buttervolume/`` is living in a BTRFS\nfilesystem. It can be a BTRFS mountpoint or a BTRFS subvolume or both.\n\nYou should also create the directories for the config and ssh on the host::\n\n sudo mkdir /var/lib/buttervolume/{config,ssh}\n\n\nBuild and run\n*************\n\nIf you don't want to be a contributor but just run the plugin, jump to the next section.\n\nYou first need to create a root filesystem for the plugin, using the provided Dockerfile::\n\n git clone https://github.com/anybox/buttervolume\n cd buttervolume/docker\n ./rebuild_rootfs.sh\n\nThen you can create the plugin::\n\n docker plugin create anybox/buttervolume .\n\nNow you can enable the plugin, which should start buttervolume in the plugin\ncontainer::\n\n docker plugin enable anybox/buttervolume\n\nYou can check it is responding by running a buttervolume command::\n\n alias drunc=\"sudo docker-runc --root /run/docker/plugins/runtime-root/plugins.moby/\"\n alias buttervolume=\"drunc exec -t `drunc list|tail -n+2|awk '{print $1}'` buttervolume\"\n sudo buttervolume scheduled\n\nYou can also locally install and run the plugin with::\n\n pyvenv venv\n ./venv/bin/python setup.py develop\n sudo ./venv/bin/buttervolume run\n\n\nInstall and run\n***************\n\nIf the plugin is already pushed to the image repository, you can install it with::\n\n docker plugin install anybox/buttervolume\n\nCheck it is running::\n\n docker plugin ls\n\nDefine useful aliases::\n\n alias drunc=\"sudo docker-runc --root /run/docker/plugins/runtime-root/plugins.moby/\"\n alias buttervolume=\"drunc exec -t `drunc list|tail -n+2|awk '{print $1}'` buttervolume\"\n\nAnd try a buttervolume command::\n\n buttervolume scheduled\n\nOr create a volume with the driver. Note that the name of the driver is the\nname of the plugin::\n\n docker volume create -d anybox/buttervolume:latest myvolume\n\nUpgrade\n*******\n\nYou must force disable it before reinstalling it (as explained in the docker documentation)::\n\n docker plugin disable -f anybox/buttervolume\n docker plugin rm -f anybox/buttervolume\n docker plugin install anybox/buttervolume\n\n\nConfigure\n*********\n\nYou can configure the following variables:\n\n * ``DRIVERNAME``: the full name of the driver (with the tag)\n * ``VOLUMES_PATH``: the path were the BTRFS volumes are located\n * ``SNAPSHOTS_PATH``: the path were the BTRFS snapshots are located\n * ``TEST_REMOTE_PATH``: the path during unit tests were the remote BTRFS snapshots are located\n * ``SCHEDULE``: the path of the scheduler configuration\n * ``RUNPATH``: the path of the docker run directory (/run/docker)\n * ``SOCKET``: the path of the unix socket were buttervolume listen\n * ``TIMER``: the number of seconds between two runs of the scheduler\n * ``DTFORMAT``: the format of the datetime in the logs\n * ``LOGLEVEL``: the Python log level (INFO, DEBUG, etc.)\n\nThe configuration can be done in this order of priority:\n\n #. from an environment variable prefixed with ``BUTTERVOLUME_`` (ex: ``BUTTERVOLUME_TIMER=120``)\n #. from the [DEFAULT] section of the ``/etc/buttervolume/config.ini`` file\n inside the container or ``/var/lib/buttervolume/config/config.ini`` on the\n host\n\nExample of ``config.ini`` file::\n\n [DEFAULT]\n TIMER = 120\n\nIf none of this is configured, the following default values are used:\n\n * ``DRIVERNAME = anybox/buttervolume:latest``\n * ``VOLUMES_PATH = /var/lib/buttervolume/volumes/``\n * ``SNAPSHOTS_PATH = /var/lib/buttervolume/snapshots/``\n * ``TEST_REMOTE_PATH = /var/lib/buttervolume/received/``\n * ``SCHEDULE = /etc/buttervolume/schedule.csv``\n * ``RUNPATH = /run/docker``\n * ``SOCKET = $RUNPATH/plugins/btrfs.sock`` # only if run manually\n * ``TIMER = 60``\n * ``DTFORMAT = %Y-%m-%dT%H:%M:%S.%f``\n * ``LOGLEVEL = INFO``\n\n\nUsage\n*****\n\nRunning the plugin\n------------------\n\nThe normal way to run it is as a new-style Docker Plugin as described above in\nthe \"Install and run\" section, which will start it automatically. This will\ncreate a ``/run/docker/plugins//btrfs.sock`` file to be used by the\nDocker daemon. The ```` is the unique identifier of the `runc/OCI`\ncontainer running it. This means you can probably run several versions of the\nplugin simultaneously but this is currently not recommended unless you keep in\nmind the volumes and snapshots are in the same place for the different\nversions. Otherwise you can configure a different path for the volumes and\nsnapshots of each different versions using the ``config.ini`` file.\n\nThen the name of the volume driver is the name of the plugin::\n\n docker volume create -d anybox/buttervolume:latest myvolume\n\nor::\n\n docker create --volume-driver=anybox/buttervolume:latest\n\nHowever if you installed it locally as a Python distribution, you can also\nstart it manually with::\n\n sudo buttervolume run\n\nIn this case it will create a unix socket in ``/run/docker/plugins/btrfs.sock``\nfor use by Docker with the legacy plugin system. Then the name of the volume\ndriver is the name of the socket file::\n\n docker volume create -d btrfs myvolume\n\nor::\n\n docker create --volume-driver=btrfs\n\nWhen started, the plugin will also start its own scheduler to run periodic jobs\n(such as a snapshot, replication, purge or synchronization)\n\n\nCreating and deleting volumes\n-----------------------------\n\nOnce the plugin is running, whenever you create a container you can specify the\nvolume driver with ``docker create --volume-driver=btrfs --name \n``. You can also manually create a BTRFS volume with ``docker volume\ncreate -d btrfs``. It also works with docker-compose, by specifying the\n``btrfs`` driver in the ``volumes`` section of the compose file.\n\nWhen you delete the volume with ``docker rm -v `` or ``docker volume\nrm ``, the BTRFS subvolume is deleted. If you snapshotted the volume\nelsewhere in the meantime, the snapshots won't be deleted.\n\n\nManaging volumes and snapshots\n------------------------------\n\nWhen buttervolume is installed, it provides a command line tool\n``buttervolume``, with the following subcommands::\n\n run Run the plugin in foreground\n snapshot Snapshot a volume\n snapshots List snapshots\n schedule (un)Schedule a snapshot, replication or purge\n scheduled List scheduled actions\n restore Restore a snapshot (optionally to a different volume)\n clone Clone a volume as new volume\n send Send a snapshot to another host\n sync Synchronise a volume from a remote host volume\n rm Delete a snapshot\n purge Purge old snapshot using a purge pattern\n\n\nCreate a snapshot\n-----------------\n\nYou can create a readonly snapshot of the volume with::\n\n buttervolume snapshot \n\nThe volumes are currently expected to live in ``/var/lib/buttervolume/volumes`` and\nthe snapshot will be created in ``/var/lib/docker/snapshots``, by appending the\ndatetime to the name of the volume, separated with ``@``.\n\n\nList the snapshots\n------------------\n\nYou can list all the snapshots::\n\n buttervolume snapshots\n\nor just the snapshots corresponding to a volume with::\n\n buttervolume snapshots \n\n```` is the name of the volume, not the full path. It is expected\nto live in ``/var/lib/buttervolume/volumes``.\n\n\nRestore a snapshot\n------------------\n\nYou can restore a snapshot as a volume. The current volume will first\nbe snapshotted, deleted, then replaced with the snapshot. If you provide a\nvolume name instead of a snapshot, the **latest snapshot** is restored. So no\ndata is lost if you do something wrong. Please take care of stopping the\ncontainer before restoring a snapshot::\n\n buttervolume restore \n\n```` is the name of the snapshot, not the full path. It is expected\nto live in ``/var/lib/docker/snapshots``.\n\nBy default, the volume name corresponds to the volume the snapshot was created\nfrom. But you can optionally restore the snapshot to a different volume name by\nadding the target as the second argument::\n\n buttervolume restore \n\n\nClone a volume\n------------------\n\nYou can clone a volume as a new volume. The current volume will be cloned\nas a new volume name given as parameter. Please take care of stopping the\ncontainer before clonning a volume::\n\n buttervolume clone \n\n```` is the name of the volume to be cloned, not the full path. It is expected\nto live in ``/var/lib/buttervolume/volumes``.\n```` is the name of the new volume to be created as clone of previous one,\nnot the full path. It is expected to be created in ``/var/lib/buttervolume/volumes``.\n\n\nDelete a snapshot\n-----------------\n\nYou can delete a snapshot with::\n\n buttervolume rm \n\n```` is the name of the snapshot, not the full path. It is expected\nto live in ``/var/lib/docker/snapshots``.\n\n\nReplicate a snapshot to another host\n------------------------------------\n\nYou can incrementally send snapshots to another host, so that data is\nreplicated to several machines, allowing to quickly move a stateful docker\ncontainer to another host. The first snapshot is first sent as a whole, then\nthe next snapshots are used to only send the difference between the current one\nand the previous one. This allows to replicate snapshots very often without\nconsuming a lot of bandwith or disk space::\n\n buttervolume send \n\n```` is the name of the snapshot, not the full path. It is expected\nto live in ``/var/lib/docker/snapshots`` and is replicated to the same path on\nthe remote host.\n\n\n```` is the hostname or IP address of the remote host. The snapshot is\ncurrently sent using BTRFS send/receive through ssh. This requires that ssh\nkeys be present and already authorized on the target host (under ``/var/lib/buttervolume/ssh``), and that the\n``StrictHostKeyChecking no`` option be enabled in ``/var/lib/buttervolume/ssh/config`` on local host.\n\nPlease note you have to restart you docker daemons each time you change ssh configuration.\n\n\nSynchronize a volume from another host volume\n---------------------------------------------\n\nYou can receive data from a remote volume, so in case there is a volume on\nthe remote host with the **same name**, it will get new and most recent data\nfrom the distantant volume and replace in the local volume. Before running the\n``rsync`` command a snapshot is made on the locale machine to manage recovery::\n\n buttervolume sync [][...]\n\nThe intent is to synchronize a volume between multi hosts on running\ncontainers, so you should schedule that action on each nodes from all remote\nhosts.\n\n.. note::\n\n As we are pulling data from multiple hosts we never remove data, consider\n removing scheduled actions before removing data on each hosts.\n\n.. warning::\n\n Make sure your application is able to handle such synchronisation\n\n\nPurge old snapshots\n-------------------\n\nYou can purge old snapshot corresponding to the specified volume, using a retention pattern::\n\n buttervolume purge \n\nIf you're unsure whether you retention pattern is correct, you can run the\npurge with the ``--dryrun`` option, to inspect what snapshots would be deleted,\nwithout deleting them::\n\n buttervolume purge --dryrun \n\n```` is the name of the volume, not the full path. It is expected\nto live in ``/var/lib/buttervolume/volumes``.\n\n```` is the snapshot retention pattern. It is a semicolon-separated\nlist of time length specifiers with a unit. Units can be ``m`` for minutes,\n``h`` for hours, ``d`` for days, ``w`` for weeks, ``y`` for years. The pattern\nshould have at least 2 items.\n\nHere are a few examples of retention patterns:\n\n- ``4h:1d:2w:2y``\n Keep all snapshots in the last four hours, then keep only one snapshot\n every four hours during the first day, then one snapshot per day during\n the first two weeks, then one snapshot every two weeks during the first\n two years, then delete everything after two years.\n\n- ``4h:1w``\n keep all snapshots during the last four hours, then one snapshot every\n four hours during the first week, then delete older snapshots.\n\n- ``2h:2h``\n keep all snapshots during the last two hours, then delete older snapshots.\n\n\nSchedule a job\n--------------\n\nYou can schedule a periodic job, such as a snapshot, a replication, a\nsynchronization or a purge. The schedule it self is stored in\n``/etc/buttervolume/schedule.csv``.\n\n**Schedule a snapshot** of a volume every 60 minutes::\n\n buttervolume schedule snapshot 60 \n\nRemove the same schedule by specifying a timer of 0 min::\n\n buttervolume schedule snapshot 0 \n\n**Schedule a replication** of volume ``foovolume`` to ``remote_host``::\n\n buttervolume schedule replicate:remote_host 3600 foovolume\n\nRemove the same schedule::\n\n buttervolume schedule replicate:remote_host 0 foovolume\n\n**Schedule a purge** every hour of the snapshots of volume ``foovolume``, but\nkeep all the snapshots in the last 4 hours, then only one snapshot every 4\nhours during the first week, then one snapshot every week during one year, then\ndelete all snapshots after one year::\n\n buttervolume schedule purge:4h:1w:1y 60 foovolume\n\nRemove the same schedule::\n\n buttervolume schedule purge:4h:1w:1y 0 foovolume\n\nUsing the right combination of snapshot schedule timer, purge schedule timer\nand purge retention pattern, you can create you own backup strategy, from the\nsimplest ones to more elaborate ones. A common one is the following::\n\n buttervolume schedule snapshot 1440 \n buttervolume schedule purge:1d:4w:1y 1440 \n\nIt should create a snapshot every day, then purge snapshots everydays while\nkeeping all snapshots in the last 24h, then one snapshot per day during one\nmonth, then one snapshot per month during only one year.\n\n**Schedule a syncrhonization** of volume ``foovolume`` from ``remote_host1``\nabd ``remote_host2``::\n\n buttervolume schedule synchronize:remote_host1,remote_host2 60 foovolume\n\nRemove the same schedule::\n\n buttervolume schedule synchronize:remote_host1,remote_host2 0 foovolume\n\n\nList scheduled jobs\n-------------------\n\nYou can list all the scheduled job with::\n\n buttervolume scheduled\n\nIt will display the schedule in the same format used for adding the schedule,\nwhich is convenient to remove an existing schedule or add a similar one.\n\n\nCopy-on-write\n-------------\n\nCopy-On-Write is disabled by default.\n\nWhy disabling copy-on-write? If your docker volume stores databases such as\nPostgreSQL or MariaDB, the copy-on-write feature may hurt performance a lot.\nThe good news is that disabling copy-on-write does not prevent from doing\nsnaphots, so we get the best of both world: good performances with the ability\nto do snapshots.\n\n\nTest\n****\n\nIf your volumes directory is a BTRFS partition or volume, tests can be run\nwith::\n\n sudo SSH_PORT=22 python3 setup.py test\n\n22 being the port of your running ssh server with authorized key,\nor using and testing the docker image (with python >= 3.5)::\n\n docker build -t anybox/buttervolume docker/\n sudo docker run -it --rm --privileged \\\n -v /var/lib/docker:/var/lib/docker \\\n -v \"$PWD\":/usr/src/buttervolume \\\n -w /usr/src/buttervolume \\\n anybox/buttervolume test\n\nIf you have no BTRFS partitions or volumes you can setup a virtual partition\nin a file as follows (tested on Debian 8):\n\nSetup BTRFS virtual partition::\n\n sudo qemu-img create /var/lib/docker/btrfs.img 10G\n sudo mkfs.btrfs /var/lib/docker/btrfs.img\n\n.. note::\n\n you can ignore the error, in fact the new FS is formatted\n\nMount the partition somewhere temporarily to create 3 new BTRFS subvolumes::\n\n sudo -s\n mkdir /tmp/btrfs_mount_point\n mount -o loop /var/lib/docker/btrfs.img /tmp/btrfs_mount_point/\n btrfs subvolume create /tmp/btrfs_mount_point/snapshots\n btrfs subvolume create /tmp/btrfs_mount_point/volumes\n btrfs subvolume create /tmp/btrfs_mount_point/received\n umount /tmp/btrfs_mount_point/\n rm -r /tmp/btrfs_mount_point/\n\nStop docker, create required mount point and restart docker::\n\n systemctl stop docker\n mkdir -p /var/lib/buttervolume/volumes\n mkdir -p /var/lib/docker/snapshots\n mkdir -p /var/lib/docker/received\n mount -o loop,subvol=volumes /var/lib/docker/btrfs.img /var/lib/buttervolume/volumes\n mount -o loop,subvol=snapshots /var/lib/docker/btrfs.img /var/lib/buttervolume/snapshots\n mount -o loop,subvol=received /var/lib/docker/btrfs.img /var/lib/buttervolume/received\n systemctl start docker\n\nOnce you are done with your test, you can unmount those volumes and you will\nfind back your previous docker volumes::\n\n\n systemctl stop docker\n umount /var/lib/buttervolume/volumes\n umount /var/lib/docker/snapshots\n umount /var/lib/docker/received\n systemctl start docker\n rm /var/lib/docker/btrfs.img\n\n\nMigrate to version 3\n********************\n\nIf you're currently using Buttervolume 1.x or 2.0 in production, you must\ncarefully follow the guidelines below to migrate to version 3.\n\nFirst copy the ssh and config files and disable the scheduler::\n\n sudo -s\n docker cp buttervolume_plugin_1:/etc/buttervolume /var/lib/buttervolume/config\n docker cp buttervolume_plugin_1:/root/.ssh /var/lib/buttervolume/ssh\n mv /var/lib/buttervolume/config/schedule.csv /var/lib/buttervolume/config/schedule.csv.disabled\n\nThen stop all your containers, excepted buttervolume\n\nNow snapshot and delete all your volumes::\n\n volumes=$(docker volume ls -f driver=btrfs --format \"{{.Name}}\")\n # or: # volumes=$(docker volume ls -f driver=btrfs|tail -n+2|awk '{print $2}')\n echo $volumes\n for v in $volumes; do docker exec buttervolume_plugin_1 buttervolume snapshot $v; done\n for v in $volumes; do docker volume rm $v; done\n\nThen stop the buttervolume container, **remove the old btrfs.sock file**, and\nrestart docker::\n\n docker stop buttervolume_plugin_1\n docker rm -v buttervolume_plugin_1\n rm /run/docker/plugins/btrfs.sock\n systemctl stop docker\n\nIf you were using Buttervolume 1.x, you must move your snapshots to the new location::\n\n mkdir /var/lib/buttervolume/snapshots\n cd /var/lib/docker/snapshots\n for i in *; do btrfs subvolume snapshot -r $i /var/lib/buttervolume/snapshots/$i; done\n\nRestore /var/lib/docker/volumes as the original folder::\n\n cd /var/lib/docker\n mkdir volumes.new\n mv volumes/* volumes.new/\n umount volumes # if this was a mounted btrfs subvolume\n mv volumes.new/* volumes/\n rmdir volumes.new\n systemctl start docker\n\nChange your volume configurations (in your compose files) to use the new\n``anybox/buttervolume:latest`` driver name instead of ``btrfs``\n\nThen start the new buttervolume 3.x as a managed plugin and check it is started::\n\n docker plugin install anybox/buttervolume:latest\n docker plugin ls\n\nThen recreate all your volumes with the new driver and restore them from the snapshots::\n\n for v in $volumes; do docker volume create -d anybox/buttervolume:latest $v; done\n alias drunc=\"sudo docker-runc --root /run/docker/plugins/runtime-root/plugins.moby/\"\n alias buttervolume=\"drunc exec -t `drunc list|tail -n+2|awk '{print $1}'` buttervolume\"\n # WARNING : check the the volume you will restore are the correct ones\n for v in $volumes; do buttervolume restore $v; done\n\nThen restart your containers, check they are ok with the correct data.\n\nReenable the schedule::\n\n mv /var/lib/buttervolume/config/schedule.csv.disabled /var/lib/buttervolume/config/schedule.csv\n\nCredits\n*******\n\n- Christophe Combelles\n- Pierre Verkest\n- Marcelo Ochoa\n\n\nCHANGELOG\n=========\n\n3.7 (2018-12-13)\n****************\n\n- unpinned urllib3\n\n3.6 (2018-12-11)\n****************\n\n- fixed zombie sshd processes inside the plugin\n- minor documentation change\n\n3.5 (2018-06-07)\n****************\n\n- improved documentation\n\n3.4 (2018-04-27)\n****************\n\n- fix rights at startup so that ssh works\n\n3.3 (2018-04-27)\n****************\n\n- Fixed a bug preventing a start in certain conditions\n\n3.2 (2018-04-27)\n****************\n\n- Fixed the socket path for startup\n\n3.1 (2018-04-27)\n****************\n\n- Fixed a declaration in Python 3.6\n- Automatically detects the btrfs.sock path\n- Made the runpath and drivername configurable\n\n3.0 (2018-04-24)\n****************\n\n- Now use the docker *managed plugin* system\n- Stop the scheduler before shutdown to avoid a 5s timeout\n- Improved logging\n- Improved the migration doc from version 1 or 2\n\n2.0 (2018-03-24)\n****************\n\n- BREAKING CHANGE: Please read the migration path from version 1 to version 2:\n BTRFS volumes and snapshots are now stored by default in different directories under ``/var/lib/buttervolume``\n- Configuration possible through environment variables or a ``config.ini`` file\n- implemented ``VolumeDriver.Capabilities`` and just return ``'local'``\n- other minor fixes and improvements\n\n1.4 (2018-02-01)\n****************\n\n- Add clone command\n- replace sync by `btrfs filesystem sync`\n\n1.3.1 (2017-10-22)\n******************\n\n- fixed packaging (missing README)\n\n1.3 (2017-07-30)\n****************\n\n- fixed the cli for the restore command\n\n1.2 (2017-07-16)\n****************\n\n- fixed the purge algorithm\n\n1.1 (2017-07-13)\n****************\n\n- allow to restore a snapshot to a different volume name\n\n1.0 (2017-05-24)\n****************\n\n- initial release, used in production\n\n",
"description_content_type": "",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "https://github.com/anybox/buttervolume",
"keywords": "",
"license": "Apache License, Version 2.0",
"maintainer": "",
"maintainer_email": "",
"name": "buttervolume",
"package_url": "https://pypi.org/project/buttervolume/",
"platform": "",
"project_url": "https://pypi.org/project/buttervolume/",
"project_urls": {
"Homepage": "https://github.com/anybox/buttervolume"
},
"release_url": "https://pypi.org/project/buttervolume/3.7/",
"requires_dist": null,
"requires_python": "",
"summary": "Docker plugin to manage Docker Volumes as BTRFS subvolumes",
"version": "3.7"
},
"last_serial": 4594751,
"releases": {
"1.0": [
{
"comment_text": "",
"digests": {
"md5": "dd0343294d567c3f9e01202a75261ce7",
"sha256": "9c37009ee677a8065bfc1b323d8b6f1d828bd1607f4b578431f0f3096ef3b625"
},
"downloads": -1,
"filename": "buttervolume-1.0.tar.gz",
"has_sig": false,
"md5_digest": "dd0343294d567c3f9e01202a75261ce7",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 16378,
"upload_time": "2017-05-24T15:21:08",
"url": "https://files.pythonhosted.org/packages/7f/aa/665ec886a81bf29a6f825d4becc28b2d519ae7d61c114e5d998a3520578f/buttervolume-1.0.tar.gz"
}
],
"1.1": [
{
"comment_text": "",
"digests": {
"md5": "b99da7b9fe4bd51343694c9c02798b0b",
"sha256": "d2b7837376a4c53402bebfe68f98b73c8f268a51ce5a978fb8e1cab344f4912a"
},
"downloads": -1,
"filename": "buttervolume-1.1.tar.gz",
"has_sig": false,
"md5_digest": "b99da7b9fe4bd51343694c9c02798b0b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 16678,
"upload_time": "2017-07-13T20:41:13",
"url": "https://files.pythonhosted.org/packages/23/ab/ce313de8c84a39db78277135c4d491c361f04b88b512c22f206bc7b8fe43/buttervolume-1.1.tar.gz"
}
],
"1.2": [
{
"comment_text": "",
"digests": {
"md5": "35e180a11e2ca4b70f5f42c323d70c77",
"sha256": "109e2ec40b784782aabf56841e8a7a2ae2a85c5fcc4f80b8c8830004a7b7187f"
},
"downloads": -1,
"filename": "buttervolume-1.2.tar.gz",
"has_sig": false,
"md5_digest": "35e180a11e2ca4b70f5f42c323d70c77",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 16754,
"upload_time": "2017-07-16T20:20:23",
"url": "https://files.pythonhosted.org/packages/ff/b8/d73299fd42cc3804dcfaa23386f6714879dac5e4b56d1b6dc33773e97f01/buttervolume-1.2.tar.gz"
}
],
"1.3": [
{
"comment_text": "",
"digests": {
"md5": "9345f3644c44458ab34784dfac705d71",
"sha256": "34b8cbe3730ec4ab4a9edcfa7a958f90d880c5b20b6484c02434bfc5a8e30a86"
},
"downloads": -1,
"filename": "buttervolume-1.3.tar.gz",
"has_sig": false,
"md5_digest": "9345f3644c44458ab34784dfac705d71",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 16805,
"upload_time": "2017-07-30T15:56:51",
"url": "https://files.pythonhosted.org/packages/6a/9c/d9aca61e6a61c8f14ae78e7b7dd086506a64de1aeaaffffbb7511a19a2ad/buttervolume-1.3.tar.gz"
}
],
"1.3.1": [
{
"comment_text": "",
"digests": {
"md5": "96f758e99863f35fd74383c02b1ca853",
"sha256": "32cf9722035b8416632e42050a2e8421e4a72b9fcff44832d78cefc2bae0281d"
},
"downloads": -1,
"filename": "buttervolume-1.3.1.tar.gz",
"has_sig": false,
"md5_digest": "96f758e99863f35fd74383c02b1ca853",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 17031,
"upload_time": "2017-10-22T17:29:51",
"url": "https://files.pythonhosted.org/packages/1f/77/a30238b6898c8c0f4338b99182153c4a062bb0b23e6831ba049e3768f9bb/buttervolume-1.3.1.tar.gz"
}
],
"2.0.0": [
{
"comment_text": "",
"digests": {
"md5": "490e49cf9db6a0fb489937caf8703339",
"sha256": "9aed2b359ecf1e3d0454ba1cae168666c83148bfc803be0843883e2a16066136"
},
"downloads": -1,
"filename": "buttervolume-2.0.0.tar.gz",
"has_sig": false,
"md5_digest": "490e49cf9db6a0fb489937caf8703339",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 19409,
"upload_time": "2018-03-24T22:00:54",
"url": "https://files.pythonhosted.org/packages/7f/c2/cf78400ebd9563cd7778ed769513f99fe784cdf4504d6c0edabbd00a45c7/buttervolume-2.0.0.tar.gz"
}
],
"3.0": [
{
"comment_text": "",
"digests": {
"md5": "bd71cb9c060e3067127cb0e07da6bbbd",
"sha256": "958bb8233d8608f43dfa7c02a60f8b6160008a108590f60a93b299d995624a63"
},
"downloads": -1,
"filename": "buttervolume-3.0.tar.gz",
"has_sig": false,
"md5_digest": "bd71cb9c060e3067127cb0e07da6bbbd",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 20899,
"upload_time": "2018-04-23T22:56:02",
"url": "https://files.pythonhosted.org/packages/ae/68/c1dadffc4199adc5fca14ab0c9e9f9f2c78ab6dd4ba56203eef853542f7a/buttervolume-3.0.tar.gz"
}
],
"3.1": [
{
"comment_text": "",
"digests": {
"md5": "b4bcfed40a65782b7050f8a5e24df466",
"sha256": "17c5651ed265ec4175222dc2b9baa68d44305016970330ca88f241f67689c6ae"
},
"downloads": -1,
"filename": "buttervolume-3.1.tar.gz",
"has_sig": false,
"md5_digest": "b4bcfed40a65782b7050f8a5e24df466",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 22378,
"upload_time": "2018-04-27T14:13:02",
"url": "https://files.pythonhosted.org/packages/38/43/20a05276dc0dc4c2371530617495d80f6d004dcace37d6aab7de35e964f7/buttervolume-3.1.tar.gz"
}
],
"3.2": [
{
"comment_text": "",
"digests": {
"md5": "7f157e1fba689884dcae8faaa0ee3e22",
"sha256": "e9e203e5c3d6d87c98cb05e6ed5f9b526bb0cc31e068d7247270ee6bd4d831b9"
},
"downloads": -1,
"filename": "buttervolume-3.2.tar.gz",
"has_sig": false,
"md5_digest": "7f157e1fba689884dcae8faaa0ee3e22",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 22464,
"upload_time": "2018-04-27T14:40:09",
"url": "https://files.pythonhosted.org/packages/5e/fc/31d478aab3d4a68f993d566b9b53fd01fb601aa3ace6c92613cb1972ba72/buttervolume-3.2.tar.gz"
}
],
"3.3": [
{
"comment_text": "",
"digests": {
"md5": "33a9919745b45bdfa411b7d8d7ec65f6",
"sha256": "903284d34ab14b892de128135a1a2d7e9740f37c5c077d0da6601d6dd31e6551"
},
"downloads": -1,
"filename": "buttervolume-3.3.tar.gz",
"has_sig": false,
"md5_digest": "33a9919745b45bdfa411b7d8d7ec65f6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 22514,
"upload_time": "2018-04-27T16:19:34",
"url": "https://files.pythonhosted.org/packages/c9/5a/eb2359e87a482a7091a689e6672b8049371db172999c2c2aa0c679998c09/buttervolume-3.3.tar.gz"
}
],
"3.4": [
{
"comment_text": "",
"digests": {
"md5": "fd018e14546770fd05d3c33ae28797e7",
"sha256": "762e72ea69694ec403c09812237e0b0be3942da290e23b9382fff0d8084572d2"
},
"downloads": -1,
"filename": "buttervolume-3.4.tar.gz",
"has_sig": false,
"md5_digest": "fd018e14546770fd05d3c33ae28797e7",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 23001,
"upload_time": "2018-04-27T18:34:33",
"url": "https://files.pythonhosted.org/packages/12/5a/9c6baa275231a1faed14b8db51638cbab801e4f6cc999f4287a73ab8eda6/buttervolume-3.4.tar.gz"
}
],
"3.5": [
{
"comment_text": "",
"digests": {
"md5": "adf4eff92f57d5f0df9e37dec24bc432",
"sha256": "b93dcc0b513c3c2818f7984e1be41987b17e7d946cb4a09c87b2e4e91af8d5dd"
},
"downloads": -1,
"filename": "buttervolume-3.5.tar.gz",
"has_sig": false,
"md5_digest": "adf4eff92f57d5f0df9e37dec24bc432",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 25425,
"upload_time": "2018-06-07T19:13:07",
"url": "https://files.pythonhosted.org/packages/7f/56/dc02f6563f26bfb7bf4abfa3e89f90e53d7f2b86e851e973b577e7222771/buttervolume-3.5.tar.gz"
}
],
"3.6": [
{
"comment_text": "",
"digests": {
"md5": "f8152cc8ae1fc89ac637beadb40576b9",
"sha256": "8ff6f4b281f118206c6213ed32f527b91f8b05ab4d17b7f0625d2c7e4058bd8f"
},
"downloads": -1,
"filename": "buttervolume-3.6.tar.gz",
"has_sig": false,
"md5_digest": "f8152cc8ae1fc89ac637beadb40576b9",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 25830,
"upload_time": "2018-12-12T13:48:29",
"url": "https://files.pythonhosted.org/packages/b4/b6/dfe5a00e5cdfd97e91568107fb8d41a20edb65aa81dcd6d2756d05fa3093/buttervolume-3.6.tar.gz"
}
],
"3.7": [
{
"comment_text": "",
"digests": {
"md5": "300ce99d6035fee8b8375568e13a0178",
"sha256": "0ae6fcde41c07d93e8bd2eb8137ba94c07f300ce849037452270adb5233b3e43"
},
"downloads": -1,
"filename": "buttervolume-3.7.tar.gz",
"has_sig": false,
"md5_digest": "300ce99d6035fee8b8375568e13a0178",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 25866,
"upload_time": "2018-12-13T15:10:20",
"url": "https://files.pythonhosted.org/packages/0e/5f/b68fae345aeedeeac5260493d9205a5af3147c6e6ef6ad961708b3af7106/buttervolume-3.7.tar.gz"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "300ce99d6035fee8b8375568e13a0178",
"sha256": "0ae6fcde41c07d93e8bd2eb8137ba94c07f300ce849037452270adb5233b3e43"
},
"downloads": -1,
"filename": "buttervolume-3.7.tar.gz",
"has_sig": false,
"md5_digest": "300ce99d6035fee8b8375568e13a0178",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 25866,
"upload_time": "2018-12-13T15:10:20",
"url": "https://files.pythonhosted.org/packages/0e/5f/b68fae345aeedeeac5260493d9205a5af3147c6e6ef6ad961708b3af7106/buttervolume-3.7.tar.gz"
}
]
}