├── .gitignore ├── .gometalinter.json ├── .travis.yml ├── LICENSE.md ├── README.md ├── docker-zfs-plugin.service ├── docker-zfs-plugin.socket ├── go.mod ├── go.sum ├── main.go ├── make.sh └── zfs └── driver.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | bin/docker-zfs-plugin 3 | -------------------------------------------------------------------------------- /.gometalinter.json: -------------------------------------------------------------------------------- 1 | { 2 | "Linters": { 3 | "vet": { 4 | "Command": "go vet" 5 | }, 6 | "vetshadow": { 7 | "Command": "go vet -shadow -shadowstrict" 8 | } 9 | }, 10 | "Enable": [ 11 | "deadcode", 12 | "dupl", 13 | "errcheck", 14 | "gosec", 15 | "goconst", 16 | "goimports", 17 | "golint", 18 | "gotype", 19 | "gotypex", 20 | "ineffassign", 21 | "interfacer", 22 | "maligned", 23 | "megacheck", 24 | "misspell", 25 | "nakedret", 26 | "safesql", 27 | "structcheck", 28 | "unconvert", 29 | "unparam", 30 | "varcheck", 31 | "vet", 32 | "vetshadow" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.13 4 | env: 5 | global: 6 | - GOOS=linux 7 | install: 8 | - go build . 9 | script: 10 | - md5sum docker-zfs-plugin > md5.txt 11 | - sha256sum docker-zfs-plugin > sha256.txt 12 | - sha512sum docker-zfs-plugin > sha512.txt 13 | deploy: 14 | provider: releases 15 | skip_cleanup: true 16 | api_key: 17 | secure: ZM6Tp7y+wfEchMoGngiMxClRIWpv8eBA+2y8NqO61LHgsse0rS0NA+0qWtQJkq/B6ZybxvUlCRNbIZVrYGlWYO20ymXuSKsmIVmTJWT9ZF8spr9FnIpCDUse+Wr4sDjL87MA4an1rnRxPRT5Je/5PWi70AIBXFi5ik/gES+LCA6zqp2/Urm7k/Q76a4698AnXiPj3iMlGDInDOEIqjh/dKJCz1npFpm5qNbh19bZ+POvzejFLITUdrC4CaOzheM2jLTNIEpDiwSqRWl3RsO5kB7PR2+2bFKZa72dj944YV56Jqso3uUw/Sm0xL/rBI5ONJzGBpiXDYqrccTLCyE7+I1Osp2Vn7i5PYPjkvLyQESAfv7zjr3HsipKEeXBdoZpqIJeUY7963BlQCOoZ9ejNm/kld+HQ6+TaBv64wJmZdtpugPeyf/SYPrW5Em7m5nP0/LMXme2pT3AfZwAmziPFDw/FIzZoF39NIJfgiFY5oaonwY8icW7+quuobPLr7o1q5YJv8TVJGYfA/pbHKr4EX93NyUDcCC8aH3rah3I9jexkooR17oBu8zo2nM+L72JO2uO+9VTt6UQKPcMtfVQSTsgYav7Vb+vsQfdzvb9JMfIYubXWKjz/Sig7LcCLAe500jPVzTcu9+L08dCFd7znBR7dg0Rs8loqwsssn4tMr0= 18 | file: 19 | - docker-zfs-plugin 20 | - md5.txt 21 | - sha256.txt 22 | - sha512.txt 23 | on: 24 | tags: true 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Trillium Staffing 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-zfs-plugin 2 | Docker volume plugin for creating persistent volumes as a dedicated zfs dataset. 3 | 4 | # Installation 5 | 6 | Download the latest binary from github releases and place in `/usr/local/bin/`. 7 | 8 | If using a systemd based distribution, copy 9 | [docker-zfs-plugin.service](docker-zfs-plugin.service) to `/etc/systemd/system`. 10 | Then enable and start the service with `systemctl daemon-reload && systemctl 11 | enable docker-zfs-plugin.service && systemctl start docker-zfs-plugin.service`. 12 | 13 | * Usage 14 | 15 | After the plugin is running, you can interact with it through normal `docker volume` commands. 16 | 17 | Recently, support was added for passing in ZFS attributes from the `docker volume create` command: 18 | 19 | `docker volume create -d zfs -o compression=lz4 -o dedup=on --name=tank/docker-volumes/data` 20 | 21 | * Legacy 22 | 23 | The driver was refactored to allow multiple pools and fully qualified dataset names. The master branch has removed all legacy naming options and now fully qualified dataset names are required. If you still have not converted to fully qualified names, please use the latest release in the v0.4.x line until you can switch to non-legacy volume names. 24 | -------------------------------------------------------------------------------- /docker-zfs-plugin.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=zfs plugin 3 | After=zfs-mount.service zfs-import-cache.service 4 | Before=docker.service 5 | Requires=zfs-mount.service zfs-import-cache.service 6 | 7 | #Recommended condition for each pool you intend to use 8 | ConditionPathIsMountPoint=/var/lib/docker-volumes/zfs/tank 9 | 10 | 11 | [Service] 12 | ExecStart=/usr/local/bin/docker-zfs-plugin --dataset-name tank/docker-volumes 13 | 14 | [Install] 15 | WantedBy=docker.service 16 | -------------------------------------------------------------------------------- /docker-zfs-plugin.socket: -------------------------------------------------------------------------------- 1 | [Socket] 2 | ListenStream=/run/docker/plugins/zfs.sock 3 | 4 | [Install] 5 | WantedBy = sockets.target 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/TrilliumIT/docker-zfs-plugin 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.4.14 // indirect 7 | github.com/clinta/go-zfs v0.0.0-20181025145938-e5fe14d9dcb7 8 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf 9 | github.com/docker/go-connections v0.4.0 // indirect 10 | github.com/docker/go-plugins-helpers v0.0.0-20200102110956-c9a8a2d92ccc 11 | github.com/sirupsen/logrus v1.4.2 12 | github.com/urfave/cli v1.22.2 13 | golang.org/x/net v0.0.0-20200219183655-46282727080f // indirect 14 | ) 15 | 16 | replace github.com/docker/go-plugins-helpers => github.com/clinta/go-plugins-helpers v0.0.0-20200221140445-4667bb9f0ed5 // for shutdown 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= 3 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 4 | github.com/clinta/go-plugins-helpers v0.0.0-20200221140445-4667bb9f0ed5 h1:STA9F+EPT0+eLSpUYBAst5PJMgtPo1PNLqRYRyJtkK4= 5 | github.com/clinta/go-plugins-helpers v0.0.0-20200221140445-4667bb9f0ed5/go.mod h1:S7P0QAZapeYuLzFzSov/e9ehFFnX/ivIDtD4nQB7+1U= 6 | github.com/clinta/go-zfs v0.0.0-20181025145938-e5fe14d9dcb7 h1:CoyMbd4slqWuH2n1C7q4hKrm4vy21jN5vS0qwQRA0gA= 7 | github.com/clinta/go-zfs v0.0.0-20181025145938-e5fe14d9dcb7/go.mod h1:le5h468SVs24WYIObbZhWWDY1HFDlE8uM57RcfsPs8Q= 8 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= 9 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 10 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 11 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 15 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 16 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 17 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 18 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 19 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 22 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 23 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 24 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 25 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 26 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 27 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 28 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 29 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 30 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 31 | github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= 32 | github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 33 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 34 | golang.org/x/net v0.0.0-20200219183655-46282727080f h1:dB42wwhNuwPvh8f+5zZWNcU+F2Xs/B9wXXwvUCOH7r8= 35 | golang.org/x/net v0.0.0-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 36 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 38 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 39 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= 41 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 43 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 44 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 45 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | zfsdriver "github.com/TrilliumIT/docker-zfs-plugin/zfs" 14 | "github.com/coreos/go-systemd/activation" 15 | "github.com/docker/go-plugins-helpers/volume" 16 | log "github.com/sirupsen/logrus" 17 | "github.com/urfave/cli" 18 | ) 19 | 20 | const ( 21 | version = "1.0.5" 22 | shutdownTimeout = 10 * time.Second 23 | ) 24 | 25 | func main() { 26 | 27 | verbose := false 28 | app := cli.NewApp() 29 | app.Name = "docker-zfs-plugin" 30 | app.Usage = "Docker ZFS Plugin" 31 | app.Version = version 32 | app.Flags = []cli.Flag{ 33 | cli.StringSliceFlag{ 34 | Name: "dataset-name", 35 | Usage: "Name of the ZFS dataset to be used. It will be created if it doesn't exist.", 36 | }, 37 | cli.BoolFlag{ 38 | Name: "verbose", 39 | Usage: "verbose output", 40 | Destination: &verbose, 41 | }, 42 | } 43 | app.Action = Run 44 | app.Before = func(c *cli.Context) error { 45 | if verbose { 46 | log.SetLevel(log.DebugLevel) 47 | } 48 | return nil 49 | } 50 | err := app.Run(os.Args) 51 | if err != nil { 52 | panic(err) 53 | } 54 | } 55 | 56 | // Run runs the driver 57 | func Run(ctx *cli.Context) error { 58 | if ctx.String("dataset-name") == "" { 59 | return fmt.Errorf("zfs dataset name is a required field") 60 | } 61 | 62 | d, err := zfsdriver.NewZfsDriver(ctx.StringSlice("dataset-name")...) 63 | if err != nil { 64 | return err 65 | } 66 | h := volume.NewHandler(d) 67 | errCh := make(chan error) 68 | 69 | listeners, _ := activation.Listeners() // wtf coreos, this funciton never returns errors 70 | if len(listeners) > 1 { 71 | log.Warn("driver does not support multiple sockets") 72 | } 73 | if len(listeners) == 0 { 74 | log.Debug("launching volume handler.") 75 | go func() { errCh <- h.ServeUnix("zfs", 0) }() 76 | } else { 77 | l := listeners[0] 78 | log.WithField("listener", l.Addr().String()).Debug("launching volume handler") 79 | go func() { errCh <- h.Serve(l) }() 80 | } 81 | 82 | c := make(chan os.Signal) 83 | defer close(c) 84 | signal.Notify(c, os.Interrupt) 85 | signal.Notify(c, syscall.SIGTERM) 86 | 87 | select { 88 | case err = <-errCh: 89 | log.WithError(err).Error("error running handler") 90 | close(errCh) 91 | case <-c: 92 | } 93 | 94 | toCtx, toCtxCancel := context.WithTimeout(context.Background(), shutdownTimeout) 95 | defer toCtxCancel() 96 | if sErr := h.Shutdown(toCtx); sErr != nil { 97 | err = sErr 98 | log.WithError(err).Error("error shutting down handler") 99 | } 100 | 101 | if hErr := <-errCh; hErr != nil && !errors.Is(hErr, http.ErrServerClosed) { 102 | err = hErr 103 | log.WithError(err).Error("error in handler after shutdown") 104 | } 105 | 106 | return err 107 | } 108 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | check_prerequisites() { 5 | [[ "$GOPATH" == "" ]] && \ 6 | errors=(${errors[@]} "GOPATH env missing") 7 | 8 | [[ -x "$GOPATH/bin/dep" ]] || \ 9 | errors=("${errors[@]}" "dep not found in \"$GOPATH/bin/\"") 10 | 11 | if [[ "${#errors[@]}" > 0 ]]; then 12 | echo "Errors:" 13 | for error in "${errors[@]}"; do 14 | echo " $error" 15 | done 16 | return 1 17 | fi 18 | return 0 19 | } 20 | 21 | check_versions() { 22 | VERS="${LATEST_RELEASE}\n${MAIN_VER}" 23 | DKR_TAG="master" 24 | 25 | # For tagged commits 26 | if [ "$(git describe --tags)" = "$(git describe --tags --abbrev=0)" ] ; then 27 | if [ $(printf ${VERS} | uniq | wc -l) -gt 1 ] ; then 28 | echo "This is a release, all versions should match" 29 | return 1 30 | fi 31 | DKR_TAG="latest" 32 | else 33 | if [ $(printf ${VERS} | uniq | wc -l) -eq 1 ] ; then 34 | echo "Please increment the version in main.go" 35 | return 1 36 | fi 37 | if [ "$(printf ${VERS} | sort -V | tail -n 1)" != "${MAIN_VER}" ] ; then 38 | echo "Please increment the version in main.go" 39 | return 1 40 | fi 41 | fi 42 | } 43 | 44 | LATEST_RELEASE=$(git describe --tags --abbrev=0 | sed "s/^v//g") 45 | MAIN_VER=$(grep "\t*version *= " main.go | sed 's/\t*version *= //g' | sed 's/"//g') 46 | 47 | check_prerequisites || exit 1 48 | check_versions || exit 1 49 | 50 | echo "Installing Dependencies..." 51 | $GOPATH/bin/dep ensure 52 | 53 | echo "Linting..." 54 | gometalinter --vendor ./... 55 | 56 | 57 | echo "Building..." 58 | mkdir bin 2>/dev/null || true 59 | go build -o bin/docker-zfs-plugin . 60 | -------------------------------------------------------------------------------- /zfs/driver.go: -------------------------------------------------------------------------------- 1 | package zfsdriver 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/clinta/go-zfs" 8 | "github.com/docker/go-plugins-helpers/volume" 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | //ZfsDriver implements the plugin helpers volume.Driver interface for zfs 13 | type ZfsDriver struct { 14 | volume.Driver 15 | rds []*zfs.Dataset //root dataset 16 | } 17 | 18 | //NewZfsDriver returns the plugin driver object 19 | func NewZfsDriver(dss ...string) (*ZfsDriver, error) { 20 | log.Debug("Creating new ZfsDriver.") 21 | zd := &ZfsDriver{} 22 | if len(dss) < 1 { 23 | return nil, fmt.Errorf("No datasets specified") 24 | } 25 | for _, ds := range dss { 26 | if !zfs.DatasetExists(ds) { 27 | _, err := zfs.CreateDatasetRecursive(ds, make(map[string]string)) 28 | if err != nil { 29 | log.Error("Failed to create root dataset.") 30 | return nil, err 31 | } 32 | } 33 | rds, err := zfs.GetDataset(ds) 34 | if err != nil { 35 | log.Error("Failed to get root dataset.") 36 | return nil, err 37 | } 38 | zd.rds = append(zd.rds, rds) 39 | } 40 | 41 | return zd, nil 42 | } 43 | 44 | //Create creates a new zfs dataset for a volume 45 | func (zd *ZfsDriver) Create(req *volume.CreateRequest) error { 46 | log.WithField("Request", req).Debug("Create") 47 | 48 | if zfs.DatasetExists(req.Name) { 49 | return fmt.Errorf("volume already exists") 50 | } 51 | 52 | _, err := zfs.CreateDatasetRecursive(req.Name, req.Options) 53 | return err 54 | } 55 | 56 | //List returns a list of zfs volumes on this host 57 | func (zd *ZfsDriver) List() (*volume.ListResponse, error) { 58 | log.Debug("List") 59 | var vols []*volume.Volume 60 | 61 | for _, rds := range zd.rds { 62 | dsl, err := rds.DatasetList() 63 | if err != nil { 64 | return nil, err 65 | } 66 | for _, ds := range dsl { 67 | //TODO: rewrite this to utilize zd.getVolume() when 68 | //upstream go-zfs is rewritten to cache properties 69 | var mp string 70 | mp, err = ds.GetMountpoint() 71 | if err != nil { 72 | log.WithField("name", ds.Name).Error("Failed to get mountpoint from dataset") 73 | continue 74 | } 75 | vols = append(vols, &volume.Volume{Name: ds.Name, Mountpoint: mp}) 76 | } 77 | } 78 | 79 | return &volume.ListResponse{Volumes: vols}, nil 80 | } 81 | 82 | //Get returns the volume.Volume{} object for the requested volume 83 | //nolint: dupl 84 | func (zd *ZfsDriver) Get(req *volume.GetRequest) (*volume.GetResponse, error) { 85 | log.WithField("Request", req).Debug("Get") 86 | 87 | v, err := zd.getVolume(req.Name) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | return &volume.GetResponse{Volume: v}, nil 93 | } 94 | 95 | func (zd *ZfsDriver) getVolume(name string) (*volume.Volume, error) { 96 | ds, err := zfs.GetDataset(name) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | mp, err := ds.GetMountpoint() 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | ts, err := ds.GetCreation() 107 | if err != nil { 108 | log.WithError(err).Error("Failed to get creation property from zfs dataset") 109 | return &volume.Volume{Name: name, Mountpoint: mp}, nil 110 | } 111 | 112 | return &volume.Volume{Name: name, Mountpoint: mp, CreatedAt: ts.Format(time.RFC3339)}, nil 113 | } 114 | 115 | func (zd *ZfsDriver) getMP(name string) (string, error) { 116 | ds, err := zfs.GetDataset(name) 117 | if err != nil { 118 | return "", err 119 | } 120 | 121 | return ds.GetMountpoint() 122 | } 123 | 124 | //Remove destroys a zfs dataset for a volume 125 | func (zd *ZfsDriver) Remove(req *volume.RemoveRequest) error { 126 | log.WithField("Request", req).Debug("Remove") 127 | 128 | ds, err := zfs.GetDataset(req.Name) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | return ds.Destroy() 134 | } 135 | 136 | //Path returns the mountpoint of a volume 137 | //nolint: dupl 138 | func (zd *ZfsDriver) Path(req *volume.PathRequest) (*volume.PathResponse, error) { 139 | log.WithField("Request", req).Debug("Path") 140 | 141 | mp, err := zd.getMP(req.Name) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | return &volume.PathResponse{Mountpoint: mp}, nil 147 | } 148 | 149 | //Mount returns the mountpoint of the zfs volume 150 | //nolint: dupl 151 | func (zd *ZfsDriver) Mount(req *volume.MountRequest) (*volume.MountResponse, error) { 152 | log.WithField("Request", req).Debug("Mount") 153 | mp, err := zd.getMP(req.Name) 154 | if err != nil { 155 | return nil, err 156 | } 157 | 158 | return &volume.MountResponse{Mountpoint: mp}, nil 159 | } 160 | 161 | //Unmount does nothing because a zfs dataset need not be unmounted 162 | func (zd *ZfsDriver) Unmount(req *volume.UnmountRequest) error { 163 | log.WithField("Request", req).Debug("Unmount") 164 | return nil 165 | } 166 | 167 | //Capabilities sets the scope to local as this is a local only driver 168 | func (zd *ZfsDriver) Capabilities() *volume.CapabilitiesResponse { 169 | log.Debug("Capabilities") 170 | return &volume.CapabilitiesResponse{Capabilities: volume.Capability{Scope: "local"}} 171 | } 172 | --------------------------------------------------------------------------------