├── .gitignore ├── .travis ├── ssh │ ├── Dockerfile │ ├── id_rsa.pub │ └── id_rsa ├── unit.sh └── integration.sh ├── tests ├── stacka │ └── docker-compose.yml ├── stackb │ └── docker-compose.yml └── script.sh ├── go.mod ├── .travis.yml ├── Dockerfile ├── trash.lock ├── vendor └── modules.txt ├── LICENSE ├── config.json ├── Makefile ├── go.sum ├── README.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | docker-volume-sshfs 3 | plugin/ 4 | rootfs 5 | -------------------------------------------------------------------------------- /.travis/ssh/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rastasheep/ubuntu-sshd 2 | COPY id_rsa.pub /root/.ssh/authorized_keys -------------------------------------------------------------------------------- /tests/stacka/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | alpine2: 4 | image: nginx:latest 5 | working_dir: /data 6 | volumes: 7 | - test-volume:/data 8 | 9 | volumes: 10 | test-volume: 11 | driver: mikebarkmin/glusterfs:next 12 | driver_opts: 13 | volname: gv0 14 | subdir: foo 15 | -------------------------------------------------------------------------------- /tests/stackb/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | alpine2: 4 | image: nginx:latest 5 | working_dir: /data 6 | volumes: 7 | - test-volume:/data 8 | 9 | volumes: 10 | test-volume: 11 | driver: mikebarkmin/glusterfs:next 12 | driver_opts: 13 | volname: gv0 14 | subdir: foo 15 | -------------------------------------------------------------------------------- /.travis/unit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | #install 7 | go get -u golang.org/x/lint/golint 8 | 9 | #script 10 | test -z "$(go vet ./... | grep -v vendor/ | tee /dev/stderr)" 11 | test -z "$(golint ./... | grep -v vendor/ | tee /dev/stderr)" 12 | test -z "$(gofmt -s -l . | grep -v vendor/ | tee /dev/stderr)" 13 | go list ./... | go test -v 14 | -------------------------------------------------------------------------------- /.travis/ssh/id_rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7BjONi2fXw9wjwYY1TB45k3GPrYaE1qdrYfnK13jNbxKv2n5h9tbkoLcjccS/PUQt/wW0PW6kln9go0fuqWhLkFXTYbqT1IOom4L8wxwq4d9USPFq+9MMHaIKKC6LUJ/evpfFAndPhWIfwith0ylT5+5Wk6W+6cEAMFb7N7TR4g6kjmDjoyjaoF3HKBB86WnZP0PGqUOSvdIroZtwsyFS+eq1z+bfHEa9t1YaXgiM2XlR1LvVESaek3ICSjN+KZH7VcrnJF3+NT/lVeNdfG3EEdbfMaqIeZoiyRGEZwwSHvfaKPck6OOk8eyt9ZFpTTZW5fd0TgAk2J9xp96KsMx/ victorvieux@Victors-MacBook-Pro.local 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mikebarkmin/docker-volume-glusterfs 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.4.16 // indirect 7 | github.com/coreos/go-systemd v0.0.0-20170731111925-d21964639418 // indirect 8 | github.com/docker/go-connections v0.3.0 // indirect 9 | github.com/docker/go-plugins-helpers v0.0.0-20170817192157-a9ef19c479cb 10 | github.com/sirupsen/logrus v1.4.1 11 | golang.org/x/net v0.0.0-20170809000501-1c05540f6879 // indirect 12 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | notifications: 3 | email: false 4 | 5 | language: go 6 | go: 1.15.x 7 | script: 8 | - go mod vendor 9 | - ./.travis/unit.sh 10 | #- language: generic 11 | # sudo: required 12 | # services: 13 | # - docker 14 | # before_install: 15 | # - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 16 | # - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 17 | # - sudo apt-get update 18 | # - sudo apt-get -y install docker-ce 19 | # env: TESTFILE=integration.sh 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.15 as builder 2 | COPY . /go/src/github.com/mikebarkmin/docker-volume-glusterfs 3 | WORKDIR /go/src/github.com/mikebarkmin/docker-volume-glusterfs 4 | RUN go mod vendor 5 | RUN go install --ldflags '-extldflags "-static"' 6 | CMD ["/go/bin/docker-volume-glusterfs"] 7 | 8 | FROM ubuntu:20.04 9 | RUN apt-get update \ 10 | && apt-get install software-properties-common -y \ 11 | && add-apt-repository ppa:gluster/glusterfs-9 \ 12 | && apt-get update \ 13 | && apt-get install glusterfs-client -y \ 14 | && apt-get purge software-properties-common -y \ 15 | && apt-get autoremove -y \ 16 | && rm -rf /var/lib/apt/lists/* 17 | COPY --from=builder /go/bin/docker-volume-glusterfs . 18 | CMD ["docker-volume-glusterfs"] 19 | 20 | -------------------------------------------------------------------------------- /trash.lock: -------------------------------------------------------------------------------- 1 | import: 2 | - package: github.com/Microsoft/go-winio 3 | version: 78439966b38d69bf38227fbf57ac8a6fee70f69a 4 | - package: github.com/sirupsen/logrus 5 | version: 181d419aa9e2223811b824e8f0b4af96f9ba9302 6 | - package: github.com/coreos/go-systemd 7 | version: d2196463941895ee908e13531a23a39feb9e1243 8 | - package: github.com/docker/go-connections 9 | version: 3ede32e2033de7505e6500d6c868c2b9ed9f169d 10 | - package: github.com/docker/go-plugins-helpers 11 | version: a9ef19c479cb60e751efa55f7f2b265776af1abf 12 | - package: golang.org/x/crypto 13 | version: b176d7def5d71bdd214203491f89843ed217f420 14 | - package: golang.org/x/net 15 | version: 1c05540f6879653db88113bc4a2b70aec4bd491f 16 | - package: golang.org/x/sys 17 | version: 9f7170bcd8e9f4d3691c06401119c46a769a1e03 18 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/Microsoft/go-winio v0.4.16 2 | ## explicit 3 | github.com/Microsoft/go-winio 4 | github.com/Microsoft/go-winio/pkg/guid 5 | # github.com/sirupsen/logrus v1.0.2-0.20170728074214-181d419aa9e2 6 | ## explicit 7 | github.com/sirupsen/logrus 8 | # github.com/coreos/go-systemd v0.0.0-20170731111925-d21964639418 9 | ## explicit 10 | github.com/coreos/go-systemd/activation 11 | # github.com/docker/go-connections v0.3.0 12 | ## explicit 13 | github.com/docker/go-connections/sockets 14 | # github.com/docker/go-plugins-helpers v0.0.0-20170817192157-a9ef19c479cb 15 | ## explicit 16 | github.com/docker/go-plugins-helpers/sdk 17 | github.com/docker/go-plugins-helpers/volume 18 | # golang.org/x/crypto v0.0.0-20170808112155-b176d7def5d7 19 | ## explicit 20 | golang.org/x/crypto/ssh/terminal 21 | # golang.org/x/net v0.0.0-20170809000501-1c05540f6879 22 | ## explicit 23 | golang.org/x/net/proxy 24 | # golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c 25 | ## explicit 26 | golang.org/x/sys/internal/unsafeheader 27 | golang.org/x/sys/unix 28 | golang.org/x/sys/windows 29 | -------------------------------------------------------------------------------- /tests/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Test for multiple volumes with same settings. 4 | # If we create multiple volumes with same settings but different names, 5 | # then there should not be a problem with mounting and unmounting 6 | # 7 | # This situation resulted in completed volume removal in older versions. 8 | 9 | docker volume rm stackb_test-volume 10 | docker volume rm stacka_test-volume 11 | 12 | cd stacka 13 | docker-compose up -d 14 | # docker-compose exec alpine2 pwd 15 | docker-compose exec alpine2 rm -rf /data/* 16 | 17 | docker-compose exec alpine2 sh -c 'echo "test" > test_data' 18 | # docker-compose exec alpine2 cat test_data 19 | # docker-compose exec alpine2 ls -l 20 | 21 | cd ../stackb 22 | 23 | docker-compose up -d # This should crash if not fixed 24 | docker-compose down 25 | docker volume rm stackb_test-volume # This should cause complete volume prune 26 | 27 | cd ../stacka 28 | docker-compose exec alpine2 sh -c 'if [ -f "test_data" ]; then echo "TEST SUCCESSFULL"; else echo "TEST FAIL"; fi;' 29 | docker-compose down -v 30 | 31 | cd ../stackb 32 | docker-compose down -v -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mike Barkmin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "glusterFS plugin for Docker", 3 | "documentation": "https://github.com/mikebarkmin/docker-volume-glusterfs", 4 | "entrypoint": ["/docker-volume-glusterfs"], 5 | "env": [ 6 | { 7 | "name": "DEBUG", 8 | "settable": ["value"], 9 | "value": "0" 10 | }, 11 | { 12 | "name": "SERVERS", 13 | "settable": ["value"], 14 | "value": "" 15 | }, 16 | { 17 | "name": "VOLNAME", 18 | "settable": ["value"], 19 | "value": "" 20 | } 21 | ], 22 | "interface": { 23 | "socket": "glusterfs.sock", 24 | "types": ["docker.volumedriver/1.0"] 25 | }, 26 | "linux": { 27 | "capabilities": ["CAP_SYS_ADMIN"], 28 | "devices": [ 29 | { 30 | "path": "/dev/fuse" 31 | } 32 | ] 33 | }, 34 | "mounts": [ 35 | { 36 | "destination": "/mnt/state", 37 | "options": ["rbind"], 38 | "name": "state", 39 | "source": "/var/lib/docker/plugins/", 40 | "settable": ["source"], 41 | "type": "bind" 42 | } 43 | ], 44 | "network": { 45 | "type": "host" 46 | }, 47 | "propagatedMount": "/mnt/volumes" 48 | } 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLUGIN_NAME = mikebarkmin/glusterfs 2 | PLUGIN_TAG ?= next 3 | 4 | all: clean rootfs create 5 | 6 | clean: 7 | @echo "### rm ./plugin" 8 | @rm -rf ./plugin 9 | 10 | rootfs: 11 | @echo "### docker build: rootfs image with docker-volume-glusterfs" 12 | @docker build -q -t ${PLUGIN_NAME}:rootfs . 13 | @echo "### create rootfs directory in ./plugin/rootfs" 14 | @mkdir -p ./plugin/rootfs 15 | @docker create --name tmp ${PLUGIN_NAME}:rootfs 16 | @docker export tmp | tar -x -C ./plugin/rootfs 17 | @echo "### copy config.json to ./plugin/" 18 | @cp config.json ./plugin/ 19 | @docker rm -vf tmp 20 | 21 | create: 22 | @echo "### remove existing plugin ${PLUGIN_NAME}:${PLUGIN_TAG} if exists" 23 | @docker plugin rm -f ${PLUGIN_NAME}:${PLUGIN_TAG} || true 24 | @echo "### create new plugin ${PLUGIN_NAME}:${PLUGIN_TAG} from ./plugin" 25 | @docker plugin create ${PLUGIN_NAME}:${PLUGIN_TAG} ./plugin 26 | 27 | enable: 28 | @echo "### enable plugin ${PLUGIN_NAME}:${PLUGIN_TAG}" 29 | @docker plugin enable ${PLUGIN_NAME}:${PLUGIN_TAG} 30 | 31 | push: clean rootfs create enable 32 | @echo "### push plugin ${PLUGIN_NAME}:${PLUGIN_TAG}" 33 | @docker plugin push ${PLUGIN_NAME}:${PLUGIN_TAG} 34 | -------------------------------------------------------------------------------- /.travis/ssh/id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAuwYzjYtn18PcI8GGNUweOZNxj62GhNana2H5ytd4zW8Sr9p+ 3 | YfbW5KC3I3HEvz1ELf8FtD1upJZ/YKNH7qloS5BV02G6k9SDqJuC/MMcKuHfVEjx 4 | avvTDB2iCigui1Cf3r6XxQJ3T4ViH8IrYdMpU+fuVpOlvunBADBW+ze00eIOpI5g 5 | 46Mo2qBdxygQfOlp2T9DxqlDkr3SK6GbcLMhUvnqtc/m3xxGvbdWGl4IjNl5UdS7 6 | 1REmnpNyAkozfimR+1XK5yRd/jU/5VXjXXxtxBHW3zGqiHmaIskRhGcMEh732ij3 7 | JOjjpPHsrfWRaU02VuX3dE4AJNifcafeirDMfwIDAQABAoIBAFiBWMg1HpFHCNiK 8 | 7p4uRWdt1SHvNmeKxXPVy8YoLezaEXfS6Fgn3g4X2FMXtcnijm9N7j8bi06sQd9T 9 | pejVlpub1GHSL99hhGS4I0bGdn/8oBlc45KDVZ64L0SrO0uGfgGFJIGA50Fkl8j7 10 | tFf4++fPLEUgXiF4sXMLbT45YIKpriE9HZf641YpR2tUGHwTnosAQdyYgiS2/pYB 11 | GhmQsKYxnSxS4yWfVrKYW4Y28YvxmTArZ7Xy2j+AtfYSK4CwVREEeEEq4nACdscL 12 | joFO2a+pth2ceuOS5al/z0OYR6zMKPLAZOJJWpaXeHn50/pHJ+09hiOOU8m1iAL+ 13 | ZRFWhKECgYEA6+RPoyWQ33J1Ms19W21YJ8oScr2VnwDxAGcdat2MLTMvkpGl/rhf 14 | OeeNcx+Cp8uwDEzdyH01X0YW/YKNKEf0uH72FWJ59Uaw2akLFQNueHYlYt3xjcdz 15 | sobAbUbncqLB8Pw664/QxQ9lehPQ5sgLKkoAFR4yPLuMHU8gDZ6nWukCgYEAyvd9 16 | ROgMjCBVlQWfhBQB5EB62I5uRPUsQwaBDV9wwxpGOwl3+xDo9egVVRVIkXsW7UK8 17 | NrQ0EMG8z3BVs/kLDTZ/GRn+ZFWt6YQ79+4TnMwSRr5iqS0j39BrjiPZBDAN0f+l 18 | YIfJSIh9YggwAiQ6fAJJlcsRjMRurwBJxqfkeycCgYBy/h2EXq+8/cL8PQg7JZav 19 | 7uYHYTwrAv55P8sraU0IS8eJH89X2PEy/RoLYPUEb1sm4+HJ9p+qDjTu6FF/rXQy 20 | 7FFyI0tosklMEggA+mdD+fRHugIjJ3PTN7VekA4L6CO7Inpmkvkm11aUqExR9Hrc 21 | 5q4bnVjIGnU3ZHcvrIPQ4QKBgQDExqDql1bi285dRjBa6tLSqjjvj1cU/+XDraCc 22 | iHVYkjaYshtijAhvsGxOKu5KLV4S9Opo1tjLjikxrCVK8R5n1vfuLCdYu0B67zr8 23 | qhLVp3vonlgk0KDBMt2z1sllp/keKY36QmtBKSL2Uh3JVbpl1AnnchM3uJHelJby 24 | RH/dbQKBgCWDV7E2Lo5QDLeuWnfzIcb5bdQRhwLI/9ff59W8msGxxd3ARIt64j3W 25 | FY1/j5hP/K/4ZVjkWebQ7scBdRfxIBj+2/AwTrRSXFAD5qrm8I/1pzyFJ8SJ/cJD 26 | SKCR8dkyJZnqp5XX9RbP/AiY1VN9VHY1RCyV+FKXQD9jpZ0IgxCZ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= 2 | github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= 3 | github.com/coreos/go-systemd v0.0.0-20170731111925-d21964639418 h1:0QH6fTJVDpblGjjozilaO++YRbnQNnTYh3yuFJHH0o8= 4 | github.com/coreos/go-systemd v0.0.0-20170731111925-d21964639418/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= 8 | github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 9 | github.com/docker/go-plugins-helpers v0.0.0-20170817192157-a9ef19c479cb h1:cjBya0EPevs0NPtO5Sr19MrJKlYVzsFIQIl9Ut9I8+I= 10 | github.com/docker/go-plugins-helpers v0.0.0-20170817192157-a9ef19c479cb/go.mod h1:LFyLie6XcDbyKGeVK6bHe+9aJTYCxWLBg5IrJZOaXKA= 11 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 12 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 13 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= 17 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 18 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 20 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 21 | golang.org/x/net v0.0.0-20170809000501-1c05540f6879 h1:0rFa7EaCGdQPmZVbo9F7MNF65b8dyzS6EUnXjs9Cllk= 22 | golang.org/x/net v0.0.0-20170809000501-1c05540f6879/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 23 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 24 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 25 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= 26 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | -------------------------------------------------------------------------------- /.travis/integration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | TAG=test 7 | 8 | # before_install 9 | #curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 10 | #sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 11 | #sudo apt-get update 12 | #sudo apt-get -y install docker-ce 13 | 14 | # install 15 | sudo docker pull rastasheep/ubuntu-sshd 16 | sudo docker pull busybox 17 | 18 | docker build -t sshd .travis/ssh 19 | #script 20 | 21 | # make the plugin 22 | sudo PLUGIN_TAG=$TAG make 23 | # enable the plugin 24 | sudo docker plugin enable mikebarkmin/glusterfs:$TAG 25 | # list plugins 26 | sudo docker plugin ls 27 | # start sshd 28 | sudo docker run -d -p 2222:22 sshd 29 | 30 | # test1: simple 31 | sudo docker volume create -d vieux/sshfs:$TAG -o sshcmd=root@localhost:/ -o port=2222 -o password=root sshvolume 32 | sudo docker run --rm -v sshvolume:/write busybox sh -c "echo hello > /write/world" 33 | sudo docker run --rm -v sshvolume:/read busybox grep -Fxq hello /read/world 34 | #sudo cat /var/lib/docker/plugins/sshfs-state.json 35 | sudo docker volume rm sshvolume 36 | 37 | # test2: allow_other 38 | sudo docker volume create -d vieux/sshfs:$TAG -o sshcmd=root@localhost:/ -o allow_other -o port=2222 -o password=root sshvolume 39 | sudo docker run --rm -v sshvolume:/write -u nobody busybox sh -c "echo hello > /write/world" 40 | docker run --rm -v sshvolume:/read -u nobody busybox grep -Fxq hello /read/world 41 | #sudo cat /var/lib/docker/plugins/sshfs-state.json 42 | sudo docker volume rm sshvolume 43 | 44 | # test3: compression 45 | sudo docker volume create -d vieux/sshfs:$TAG -o sshcmd=root@localhost:/ -o Ciphers=arcfour -o Compression=no -o port=2222 -o password=root sshvolume 46 | sudo docker run --rm -v sshvolume:/write busybox sh -c "echo hello > /write/world" 47 | sudo docker run --rm -v sshvolume:/read busybox grep -Fxq hello /read/world 48 | #sudo cat /var/lib/docker/plugins/sshfs-state.json 49 | sudo docker volume rm sshvolume 50 | 51 | # test4: source 52 | sudo docker plugin disable vieux/sshfs:$TAG 53 | sudo docker plugin set vieux/sshfs:$TAG state.source=/tmp 54 | sudo docker plugin enable vieux/sshfs:$TAG 55 | sudo docker volume create -d vieux/sshfs:$TAG -o sshcmd=root@localhost:/ -o Ciphers=arcfour -o Compression=no -o port=2222 -o password=root sshvolume 56 | sudo docker run --rm -v sshvolume:/write busybox sh -c "echo hello > /write/world" 57 | sudo docker run --rm -v sshvolume:/read busybox grep -Fxq hello /read/world 58 | #sudo cat /tmp/sshfs-state.json 59 | sudo docker volume rm sshvolume 60 | 61 | # test5: ssh key 62 | sudo docker plugin disable vieux/sshfs:$TAG 63 | sudo docker plugin set vieux/sshfs:$TAG sshkey.source=`pwd`/.travis/ssh/ 64 | sudo docker plugin enable vieux/sshfs:$TAG 65 | sudo docker volume create -d vieux/sshfs:$TAG -o sshcmd=root@localhost:/ -o port=2222 sshvolume 66 | sudo docker run --rm -v sshvolume:/write busybox sh -c "echo hello > /write/world" 67 | sudo docker run --rm -v sshvolume:/read busybox grep -Fxq hello /read/world 68 | #sudo cat /var/lib/docker/plugins/sshfs-state.json 69 | sudo docker volume rm sshvolume 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker volume plugin for GlusterFS 2 | 3 | This is a managed Docker volume plugin to allow Docker containers to access 4 | GlusterFS volumes. The GlusterFS client does not need to be installed on the 5 | host and everything is managed within the plugin. 6 | 7 | [![TravisCI](https://travis-ci.org/mikebarkmin/docker-volume-glusterfs.svg)](https://travis-ci.org/mikebarkmin/docker-volume-glusterfs) [![Go Report Card](https://goreportcard.com/badge/github.com/mikebarkmin/docker-volume-glusterfs)](https://goreportcard.com/report/github.com/mikebarkmin/docker-volume-glusterfs) ![Docker Pulls](https://img.shields.io/docker/pulls/mikebarkmin/glusterfs) ![Docker Stars](https://img.shields.io/docker/stars/mikebarkmin/glusterfs) 8 | 9 | ## Usage 10 | 11 | 1 - Install the plugin 12 | 13 | ``` 14 | docker plugin install --alias glusterfs mikebarkmin/glusterfs:latest 15 | 16 | # optional you can set a default server list and/or volume 17 | docker plugin install --alias glusterfs mikebarkmin/glusterfs SERVERS= VOLNAME= 18 | 19 | # or to enable debug 20 | docker plugin install --alias glusterfs mikebarkmin/glusterfs DEBUG=1 21 | ``` 22 | 23 | 2 - Create a volume 24 | 25 | > Make sure the **_gluster volume exists_**. 26 | > 27 | > Or the mounting of the volume will fail. 28 | 29 | ``` 30 | $ docker volume create -d glusterfs -o servers= -o volname= -o subdir= glustervolume 31 | glustervolume 32 | $ docker volume ls 33 | DRIVER VOLUME NAME 34 | glusterfs:next glustervolume 35 | ``` 36 | 37 | or if you set the defaults for the plugin, you can create a volume without any options: 38 | 39 | ``` 40 | $ docker volume create -d glusterfs glustervolume 41 | glustervolume 42 | $ docker volume ls 43 | DRIVER VOLUME NAME 44 | glusterfs:next glustervolume 45 | ``` 46 | 47 | 3 - Use the volume 48 | 49 | ``` 50 | $ docker run -it -v glustervolume: bash ls 51 | ``` 52 | 53 | ## Options 54 | 55 | - servers [required, if no default set]: A comma-separated list of servers e.g.: 192.168.2.1,192.168.1.1 56 | - volname [required, if no default set]: The name of the glusterfs volume e.g.: gv0. Needs to be defined on the glusterfs cluster. 57 | - subdir [optional, default: volume name]: The name of the subdir. Will be created, if not found. 58 | 59 | For additional options see [man mount.glusterfs](https://github.com/gluster/glusterfs/blob/release-6/doc/mount.glusterfs.8). 60 | 61 | ## Supported tags and respective `Dockerfile` links 62 | 63 | - mikebarkmin/glusterfs:latest -> mikebarkmin/glusterfs:9 64 | - [mikebarkmin/glusterfs:9](https://github.com/mikebarkmin/docker-volume-glusterfs/blob/glusterfs-9/Dockerfile) (Ubuntu 20.04) 65 | - [mikebarkmin/glusterfs:8](https://github.com/mikebarkmin/docker-volume-glusterfs/blob/glusterfs-8/Dockerfile) (Ubuntu 20.04) 66 | - [mikebarkmin/glusterfs:7](https://github.com/mikebarkmin/docker-volume-glusterfs/blob/glusterfs-7/Dockerfile) (Ubuntu 20.04) 67 | - [mikebarkmin/glusterfs:6](https://github.com/mikebarkmin/docker-volume-glusterfs/blob/glusterfs-6/Dockerfile) (Ubuntu 20.04) 68 | - [mikebarkmin/glusterfs:1.1.0](https://github.com/mikebarkmin/docker-volume-glusterfs/blob/4af73f9ba63e816958f25a2bddf5665f6c859fe9/Dockerfile) (Ubuntu 18.04) 69 | - [mikebarkmin/glusterfs:1.0.0](https://github.com/mikebarkmin/docker-volume-glusterfs/blob/4af73f9ba63e816958f25a2bddf5665f6c859fe9/Dockerfile) (Ubuntu 18.04) 70 | 71 | ## TODO 72 | 73 | - write integration tests 74 | 75 | ## LICENSE 76 | 77 | MIT 78 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "os/exec" 12 | "os/user" 13 | "path/filepath" 14 | "strconv" 15 | "strings" 16 | "sync" 17 | 18 | "github.com/docker/go-plugins-helpers/volume" 19 | "github.com/sirupsen/logrus" 20 | ) 21 | 22 | const socketAddress = "/run/docker/plugins/glusterfs.sock" 23 | 24 | type glusterfsVolume struct { 25 | connections int 26 | Name string 27 | Subdir string 28 | SubdirMountpoint string 29 | Servers []string 30 | Volname string 31 | Options []string 32 | Mountpoint string 33 | } 34 | 35 | type glusterfsDriver struct { 36 | sync.RWMutex 37 | 38 | root string 39 | statePath string 40 | volumes map[string]*glusterfsVolume 41 | defaultVolname string 42 | defaultServers string 43 | } 44 | 45 | func newGlusterfsDriver(root string, defaultServers string, defaultVolname string) (*glusterfsDriver, error) { 46 | logrus.WithField("method", "new driver").Debug(root) 47 | 48 | d := &glusterfsDriver{ 49 | root: filepath.Join(root, "volumes"), 50 | statePath: filepath.Join(root, "state", "gfs-state.json"), 51 | volumes: map[string]*glusterfsVolume{}, 52 | defaultVolname: defaultVolname, 53 | defaultServers: defaultServers, 54 | } 55 | 56 | data, err := ioutil.ReadFile(d.statePath) 57 | if err != nil { 58 | if os.IsNotExist(err) { 59 | logrus.WithField("statePath", d.statePath).Debug("no state found") 60 | } else { 61 | return nil, err 62 | } 63 | } else { 64 | if err := json.Unmarshal(data, &d.volumes); err != nil { 65 | return nil, err 66 | } 67 | } 68 | 69 | return d, nil 70 | } 71 | 72 | func (d *glusterfsDriver) saveState() { 73 | data, err := json.Marshal(d.volumes) 74 | if err != nil { 75 | logrus.WithField("statePath", d.statePath).Error(err) 76 | return 77 | } 78 | 79 | if err := ioutil.WriteFile(d.statePath, data, 0644); err != nil { 80 | logrus.WithField("savestate", d.statePath).Error(err) 81 | } 82 | } 83 | 84 | func (d *glusterfsDriver) Create(r *volume.CreateRequest) error { 85 | logrus.WithField("method", "create").Debugf("%#v", r) 86 | 87 | d.Lock() 88 | defer d.Unlock() 89 | v := &glusterfsVolume{ 90 | Subdir: r.Name, 91 | Name: r.Name, 92 | Volname: d.defaultVolname, 93 | Servers: strings.Split(d.defaultServers, ","), 94 | } 95 | 96 | for key, val := range r.Options { 97 | switch key { 98 | case "subdir": 99 | v.Subdir = val 100 | break 101 | case "volname": 102 | v.Volname = val 103 | break 104 | case "servers": 105 | v.Servers = strings.Split(val, ",") 106 | default: 107 | if val != "" { 108 | v.Options = append(v.Options, key+"="+val) 109 | } else { 110 | v.Options = append(v.Options, key) 111 | } 112 | } 113 | } 114 | 115 | if v.Subdir == "" { 116 | return logError("'subdir' option required") 117 | } 118 | 119 | if v.Volname == "" { 120 | return logError("'volname' option required") 121 | } 122 | 123 | if len(v.Servers) < 1 { 124 | return logError("'servers' option required") 125 | } 126 | 127 | v.Mountpoint = filepath.Join(d.root, fmt.Sprintf("%x/%x/%x", sha256.Sum256([]byte(v.Name)), sha256.Sum256([]byte(v.Volname)), sha256.Sum256([]byte(v.Subdir)))) 128 | 129 | d.volumes[r.Name] = v 130 | 131 | d.saveState() 132 | 133 | return nil 134 | } 135 | 136 | // https://socketloop.com/tutorials/golang-determine-if-directory-is-empty-with-os-file-readdir-function 137 | func IsDirEmpty(name string) (bool, error) { 138 | f, err := os.Open(name) 139 | if err != nil { 140 | return false, err 141 | } 142 | defer f.Close() 143 | 144 | // read in ONLY one file 145 | _, err = f.Readdir(1) 146 | 147 | // and if the file is EOF... well, the dir is empty. 148 | if err == io.EOF { 149 | return true, nil 150 | } 151 | return false, err 152 | } 153 | 154 | func (d *glusterfsDriver) Remove(r *volume.RemoveRequest) error { 155 | logrus.WithField("method", "remove").Debugf("%#v", r) 156 | 157 | d.Lock() 158 | defer d.Unlock() 159 | 160 | v, ok := d.volumes[r.Name] 161 | if !ok { 162 | return logError("volume %s not found", r.Name) 163 | } 164 | 165 | if v.connections != 0 { 166 | return logError("volume %s is currently used by a container", r.Name) 167 | } 168 | 169 | empty, err := IsDirEmpty(v.Mountpoint) 170 | 171 | if !empty || err != nil { 172 | return logError( 173 | "Directory for volume %s where the volume is mounted is not empty. "+ 174 | "This would result in complete removal of all data. Please stop all "+ 175 | "containers that mount the same volume and subdirectory and try again.", 176 | r.Name) 177 | } 178 | 179 | if err := os.RemoveAll(v.Mountpoint); err != nil { 180 | return logError(err.Error()) 181 | } 182 | delete(d.volumes, r.Name) 183 | d.saveState() 184 | return nil 185 | } 186 | 187 | func (d *glusterfsDriver) Path(r *volume.PathRequest) (*volume.PathResponse, error) { 188 | logrus.WithField("method", "path").Debugf("%#v", r) 189 | 190 | d.RLock() 191 | defer d.RUnlock() 192 | 193 | v, ok := d.volumes[r.Name] 194 | if !ok { 195 | return &volume.PathResponse{}, logError("volume %s not found", r.Name) 196 | } 197 | 198 | return &volume.PathResponse{Mountpoint: v.Mountpoint}, nil 199 | } 200 | 201 | func (d *glusterfsDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) { 202 | logrus.WithField("method", "mount").Debugf("%#v", r) 203 | 204 | d.Lock() 205 | defer d.Unlock() 206 | 207 | v, ok := d.volumes[r.Name] 208 | if !ok { 209 | return &volume.MountResponse{}, logError("volume %s not found", r.Name) 210 | } 211 | 212 | if v.connections == 0 { 213 | fi, err := os.Lstat(v.Mountpoint) 214 | if os.IsNotExist(err) { 215 | if err := os.MkdirAll(v.Mountpoint, 0755); err != nil { 216 | return &volume.MountResponse{}, logError(err.Error()) 217 | } 218 | } else if err != nil { 219 | return &volume.MountResponse{}, logError(err.Error()) 220 | } 221 | 222 | if fi != nil && !fi.IsDir() { 223 | return &volume.MountResponse{}, logError("%v already exist and it's not a directory", v.Mountpoint) 224 | } 225 | 226 | if err := d.mountVolume(v); err != nil { 227 | return &volume.MountResponse{}, logError(err.Error()) 228 | } 229 | } 230 | 231 | v.connections++ 232 | 233 | return &volume.MountResponse{Mountpoint: v.SubdirMountpoint}, nil 234 | } 235 | 236 | func (d *glusterfsDriver) Unmount(r *volume.UnmountRequest) error { 237 | logrus.WithField("method", "unmount").Debugf("%#v", r) 238 | 239 | d.Lock() 240 | defer d.Unlock() 241 | v, ok := d.volumes[r.Name] 242 | if !ok { 243 | return logError("volume %s not found", r.Name) 244 | } 245 | 246 | v.connections-- 247 | 248 | if v.connections <= 0 { 249 | if err := d.unmountVolume(v.Mountpoint); err != nil { 250 | return logError(err.Error()) 251 | } 252 | v.connections = 0 253 | } 254 | 255 | return nil 256 | } 257 | 258 | func (d *glusterfsDriver) Get(r *volume.GetRequest) (*volume.GetResponse, error) { 259 | logrus.WithField("method", "get").Debugf("%#v", r) 260 | 261 | d.Lock() 262 | defer d.Unlock() 263 | 264 | v, ok := d.volumes[r.Name] 265 | if !ok { 266 | return &volume.GetResponse{}, logError("volume %s not found", r.Name) 267 | } 268 | 269 | return &volume.GetResponse{Volume: &volume.Volume{Name: r.Name, Mountpoint: v.SubdirMountpoint}}, nil 270 | } 271 | 272 | func (d *glusterfsDriver) List() (*volume.ListResponse, error) { 273 | logrus.WithField("method", "list").Debugf("") 274 | 275 | d.Lock() 276 | defer d.Unlock() 277 | 278 | var vols []*volume.Volume 279 | for name, v := range d.volumes { 280 | vols = append(vols, &volume.Volume{Name: name, Mountpoint: v.Mountpoint}) 281 | } 282 | return &volume.ListResponse{Volumes: vols}, nil 283 | } 284 | 285 | func (d *glusterfsDriver) Capabilities() *volume.CapabilitiesResponse { 286 | logrus.WithField("method", "capabilities").Debugf("") 287 | 288 | return &volume.CapabilitiesResponse{Capabilities: volume.Capability{Scope: "local"}} 289 | } 290 | 291 | func (d *glusterfsDriver) mountVolume(v *glusterfsVolume) error { 292 | cmd := exec.Command("mount", "-t", "glusterfs") 293 | 294 | for _, option := range v.Options { 295 | cmd.Args = append(cmd.Args, "-o", option) 296 | } 297 | 298 | var servers = strings.Join(v.Servers, ",") 299 | var path = fmt.Sprintf("/%s", v.Volname) 300 | cmd.Args = append(cmd.Args, fmt.Sprintf("%s:%s", servers, path), v.Mountpoint) 301 | 302 | logrus.Debug(cmd.Args) 303 | output, err := cmd.CombinedOutput() 304 | if err != nil { 305 | return logError("glusterfs command execute failed: %v (%s)", err, output) 306 | } 307 | 308 | var subdir = filepath.Join(v.Mountpoint, v.Subdir) 309 | fi, err := os.Lstat(subdir) 310 | if os.IsNotExist(err) { 311 | if err := os.MkdirAll(subdir, 0755); err != nil { 312 | return logError(err.Error()) 313 | } 314 | } else if err != nil { 315 | return logError(err.Error()) 316 | } 317 | 318 | if fi != nil && !fi.IsDir() { 319 | return logError("subdir %v already exist and it's not a directory", subdir) 320 | } 321 | 322 | v.SubdirMountpoint = subdir 323 | 324 | return nil 325 | } 326 | 327 | func (d *glusterfsDriver) unmountVolume(target string) error { 328 | cmd := fmt.Sprintf("umount %s", target) 329 | logrus.Debug(cmd) 330 | return exec.Command("sh", "-c", cmd).Run() 331 | } 332 | 333 | func logError(format string, args ...interface{}) error { 334 | logrus.Errorf(format, args...) 335 | return fmt.Errorf(format, args...) 336 | } 337 | 338 | func main() { 339 | debug := os.Getenv("DEBUG") 340 | if ok, _ := strconv.ParseBool(debug); ok { 341 | logrus.SetLevel(logrus.DebugLevel) 342 | } 343 | 344 | d, err := newGlusterfsDriver("/mnt", os.Getenv("SERVERS"), os.Getenv("VOLNAME")) 345 | if err != nil { 346 | log.Fatal(err) 347 | } 348 | 349 | h := volume.NewHandler(d) 350 | u, _ := user.Lookup("root") 351 | gid, _ := strconv.Atoi(u.Gid) 352 | logrus.Infof("listening on %s", socketAddress) 353 | logrus.Error(h.ServeUnix(socketAddress, gid)) 354 | } 355 | --------------------------------------------------------------------------------