From 927e7d436f41a0ffb474cac9f8d79d6d43154b08 Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Sat, 13 Apr 2019 15:23:48 -0400 Subject: [PATCH 1/5] increase time for mount and attach to 300s --- driver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver.go b/driver.go index 44443a7..ff83560 100644 --- a/driver.go +++ b/driver.go @@ -262,7 +262,7 @@ func (driver *linodeVolumeDriver) Mount(req *volume.MountRequest) (*volume.Mount // else... linode already attached to current host // wait for kernel to have block device available - if err := waitForDeviceFileExists(linVol.FilesystemPath, 180); err != nil { + if err := waitForDeviceFileExists(linVol.FilesystemPath, 300); err != nil { return nil, err } @@ -376,7 +376,7 @@ func attachAndWait(api *linodego.Client, volumeID int, linodeID int) error { return fmt.Errorf("Error attaching volume(%d) to linode(%d): %s", volumeID, linodeID, err) } - if _, err := api.WaitForVolumeLinodeID(context.Background(), volumeID, &linodeID, 180); err != nil { + if _, err := api.WaitForVolumeLinodeID(context.Background(), volumeID, &linodeID, 300); err != nil { return fmt.Errorf("Error waiting for attachment of volume(%d) to linode(%d): %s", volumeID, linodeID, err) } return nil From 088bb3d41c4f10e1f19d0ef318ae9c1b8c2888bf Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Sat, 13 Apr 2019 18:12:22 -0400 Subject: [PATCH 2/5] remove the region argument --- README.md | 13 +++++-------- driver.go | 40 +++++++++++++++++++++++++++++----------- main.go | 20 ++++---------------- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index f7355d3..c8c579d 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,14 @@ The plugin can also be configured (or reconfigured) in multiple steps. docker plugin install --alias linode linode/docker-volume-linode docker plugin disable linode docker plugin set linode linode-token= -docker plugin set linode linode-region= docker plugin set linode linode-label= docker plugin enable linode ``` - \: You will need a Linode APIv4 Personal Access Token. Get one here: . The API Token must have Read/Write permission for Volumes and Linodes. -- \: `us-east`, `us-central`, `us-west`, `eu-west`, `eu-central`, `ap-south`, `ap-northeast`. [Some Linode regions do not have Block Storage Volume support](https://www.linode.com/community/questions/344/when-will-block-storage-be-available-in-my-datacenter), such as: `us-southeast` and `ap-northeast-1a`. -- \: The label given to the host Linode Control Panel. -- For a complete list of regions: https://api.linode.com/v4/regions -- For all options see "Driver Options" section +- \: The label given to the host Linode Control Panel. Defaults to the system hostname. + [Some Linode regions do not have Block Storage Volume support](https://www.linode.com/community/questions/344/when-will-block-storage-be-available-in-my-datacenter), such as: `us-southeast` and `ap-northeast-1a`. For a complete list of regions: https://api.linode.com/v4/regions +- For all options see [Driver Options](#Driver-Options) section ### Docker Swarm @@ -103,7 +101,6 @@ my-test-volume-50 | --- | --- | | linode-token | **Required** The Linode APIv4 [Personal Access Token](https://cloud.linode.com/profile/tokens) | linode-label | The Linode Label to attach block storage volumes to (defaults to the system hostname) | -| linode-region | The Linode region to create volumes in (inferred if using linode-label, defaults to us-west) | | socket-file | Sets the socket file/address (defaults to /run/docker/plugins/linode.sock) | | socket-gid | Sets the socket GID (defaults to 0) | | mount-root | Sets the root directory for volume mounts (default /mnt) | @@ -119,7 +116,7 @@ Options can be set once for all future uses with [`docker plugin set`](https://d ### Run the driver ```sh -docker-volume-linode --linode-token= --linode-region= --linode-label= +docker-volume-linode --linode-token= --linode-label= ``` ### Debugging @@ -135,7 +132,7 @@ docker plugin set docker-volume-linode LOG_LEVEL=debug #### Enable Debug Level in manual installation ```sh -docker-volume-linode --linode-token=<...> --linode-region=<...> --linode-label=<...> --log-level=debug +docker-volume-linode --linode-token=<...> --linode-label=<...> --log-level=debug ``` ## Development diff --git a/driver.go b/driver.go index ff83560..8ed5127 100644 --- a/driver.go +++ b/driver.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "strconv" + "strings" "sync" "github.com/docker/docker/api/types/filters" @@ -29,15 +30,18 @@ type linodeVolumeDriver struct { linodeAPIPtr *linodego.Client } -// Constructor -func newLinodeVolumeDriver(region string, linodeLabel string, linodeToken string) linodeVolumeDriver { +// Constructor +func newLinodeVolumeDriver(linodeLabel string, linodeToken string) linodeVolumeDriver { driver := linodeVolumeDriver{ linodeToken: linodeToken, - region: region, linodeLabel: linodeLabel, mutex: &sync.Mutex{}, } + if _, err := driver.linodeAPI(); err != nil { + log.Fatalf("Could not initialize Linode API: %s", err) + } + return driver } @@ -50,7 +54,20 @@ func (driver *linodeVolumeDriver) linodeAPI() (*linodego.Client, error) { return driver.linodeAPIPtr, nil } - tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: *linodeTokenParamPtr}) + driver.linodeAPIPtr = setupLinodeAPI(*linodeTokenParamPtr) + + if driver.instanceID == 0 { + if err := driver.determineLinodeID(); err != nil { + driver.linodeAPIPtr = nil + return nil, err + } + } + + return driver.linodeAPIPtr, nil +} + +func setupLinodeAPI(token string) *linodego.Client { + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) oauth2Client := &http.Client{ Transport: &oauth2.Transport{ Source: tokenSource, @@ -60,14 +77,15 @@ func (driver *linodeVolumeDriver) linodeAPI() (*linodego.Client, error) { api := linodego.NewClient(oauth2Client) ua := fmt.Sprintf("docker-volume-linode/%s linodego/%s", VERSION, linodego.Version) api.SetUserAgent(ua) + return &api +} - driver.linodeAPIPtr = &api - +func (driver *linodeVolumeDriver) determineLinodeID() error { if driver.linodeLabel == "" { var hostnameErr error driver.linodeLabel, hostnameErr = os.Hostname() if hostnameErr != nil { - return nil, fmt.Errorf("Could not determine hostname: %s", hostnameErr) + return fmt.Errorf("Could not determine hostname: %s", hostnameErr) } } @@ -76,17 +94,16 @@ func (driver *linodeVolumeDriver) linodeAPI() (*linodego.Client, error) { linodes, lErr := driver.linodeAPIPtr.ListInstances(context.Background(), listOpts) if lErr != nil { - return nil, fmt.Errorf("Could not determine Linode instance ID from Linode label %s due to error: %s", driver.linodeLabel, lErr) + return fmt.Errorf("Could not determine Linode instance ID from Linode label %s due to error: %s", driver.linodeLabel, lErr) } else if len(linodes) != 1 { - return nil, fmt.Errorf("Could not determine Linode instance ID from Linode label %s", driver.linodeLabel) + return fmt.Errorf("Could not determine Linode instance ID from Linode label %s", driver.linodeLabel) } driver.instanceID = linodes[0].ID if driver.region == "" { driver.region = linodes[0].Region } - - return driver.linodeAPIPtr, nil + return nil } // Get implementation @@ -159,6 +176,7 @@ func (driver *linodeVolumeDriver) Create(req *volume.CreateRequest) error { defer driver.mutex.Unlock() var size int + if sizeOpt, ok := req.Options["size"]; ok { s, err := strconv.Atoi(sizeOpt) if err != nil { diff --git a/main.go b/main.go index 7096b24..5467679 100644 --- a/main.go +++ b/main.go @@ -28,15 +28,13 @@ var ( socketAddressParamPtr = cfgString("socket-file", DefaultSocketAddress, "Sets the socket file/address.") mountRootParamPtr = cfgString("mount-root", MountRoot, "Sets the root directory for volume mounts.") linodeTokenParamPtr = cfgString("linode-token", "", "Required Personal Access Token generated in Linode Console.") - linodeRegionParamPtr = cfgString("linode-region", "", "Required linode region.") - linodeLabelParamPtr = cfgString("linode-label", "", "Sets the Linode instance label.") - logLevelPtr = cfgString("log-level", "info", "Sets log level debug,info,warn,error") + linodeLabelParamPtr = cfgString("linode-label", "", "Sets the Linode Instance Label (defaults to the OS HOSTNAME)") + logLevelPtr = cfgString("log-level", "info", "Sets log level: debug,info,warn,error") ) func main() { // flag.Parse() - // log.SetOutput(os.Stdout) level, err := log.ParseLevel(*logLevelPtr) @@ -49,26 +47,16 @@ func main() { // check required parameters (token, region and label) if *linodeTokenParamPtr == "" { - log.Error("linode-token is required.") - } - - if *linodeRegionParamPtr == "" { - log.Error("linode-region is required.") - } - - if *linodeLabelParamPtr == "" { - log.Error("linode-label is required.") + log.Fatal("linode-token is required.") } MountRoot = *mountRootParamPtr - // log.Debugf("linode-token: %s", *linodeTokenParamPtr) - log.Debugf("linode-region: %s", *linodeRegionParamPtr) log.Debugf("linode-label: %s", *linodeLabelParamPtr) // Driver instance - driver := newLinodeVolumeDriver(*linodeRegionParamPtr, *linodeLabelParamPtr, *linodeTokenParamPtr) + driver := newLinodeVolumeDriver(*linodeLabelParamPtr, *linodeTokenParamPtr) // Attach Driver to docker handler := volume.NewHandler(&driver) From be8f94b4eb7bd4615e550defa0cf76d98bd23ed6 Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Sat, 13 Apr 2019 18:13:06 -0400 Subject: [PATCH 3/5] add filesystem and delete-on-remove volume create options --- README.md | 8 +++++++- driver.go | 38 ++++++++++++++++++++++++++++++++++---- fs_utils_linux.go | 8 ++------ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c8c579d..43c4ec3 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,13 @@ my-test-volume #### Create Options -This driver offers `size` as [driver specific option](https://docs.docker.com/engine/reference/commandline/volume_create/#driver-specific-options). The `size` option specifies the size (in GB) of the volume to be created. Volumes must be at least 10GB in size, so the default is 10GB. +The driver offers [driver specific volume create options](https://docs.docker.com/engine/reference/commandline/volume_create/#driver-specific-options): + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `size` | int | `10` | the size (in GB) of the volume to be created. Volumes must be at least 10GB in size, so the default is 10GB. +| `filesystem` | string | `ext4` | the filesystem argument for `mkfs` when formating the new (raw) volume +| `delete-on-remove` | bool | `false`| if the Linode volume should be deleted when removed ```sh $ docker volume create -o size=50 -d linode my-test-volume-50 diff --git a/driver.go b/driver.go index 8ed5127..79e4ddd 100644 --- a/driver.go +++ b/driver.go @@ -30,6 +30,9 @@ type linodeVolumeDriver struct { linodeAPIPtr *linodego.Client } +const ( + fsTagPrefix = "docker-volume-filesystem-" +) // Constructor func newLinodeVolumeDriver(linodeLabel string, linodeToken string) linodeVolumeDriver { @@ -191,6 +194,20 @@ func (driver *linodeVolumeDriver) Create(req *volume.CreateRequest) error { Size: size, } + if fsOpt, ok := req.Options["filesystem"]; ok { + createOpts.Tags = append(createOpts.Tags, fsTagPrefix+fsOpt) + } + + if deleteOpt, ok := req.Options["delete-on-remove"]; ok { + b, err := strconv.ParseBool(deleteOpt) + if err != nil { + return fmt.Errorf("Invalid delete-on-remove argument") + } + if b { + createOpts.Tags = append(createOpts.Tags, "docker-volume-delete-on-remove") + } + } + if _, err := api.CreateVolume(context.Background(), createOpts); err != nil { return fmt.Errorf("Create(%s) Failed: %s", req.Name, err) } @@ -221,10 +238,16 @@ func (driver *linodeVolumeDriver) Remove(req *volume.RemoveRequest) error { return err } - // Send Delete request - if err := api.DeleteVolume(context.Background(), linVol.ID); err != nil { - return err + // Optionally send Delete request + for _, t := range linVol.Tags { + if t == "docker-volume-delete-on-remove" { + if err := api.DeleteVolume(context.Background(), linVol.ID); err != nil { + return err + } + break + } } + return nil } @@ -287,7 +310,14 @@ func (driver *linodeVolumeDriver) Mount(req *volume.MountRequest) (*volume.Mount // Format block device if no FS found if GetFSType(linVol.FilesystemPath) == "" { log.Infof("Formatting device:%s;", linVol.FilesystemPath) - if err := Format(linVol.FilesystemPath); err != nil { + filesystem := "ext4" + for _, tag := range linVol.Tags { + if strings.HasPrefix(tag, fsTagPrefix) { + filesystem = tag[len(fsTagPrefix):] + break + } + } + if err := Format(linVol.FilesystemPath, filesystem); err != nil { return nil, err } } diff --git a/fs_utils_linux.go b/fs_utils_linux.go index 75decdc..9dfd556 100644 --- a/fs_utils_linux.go +++ b/fs_utils_linux.go @@ -8,13 +8,9 @@ import ( log "github.com/sirupsen/logrus" ) -const ( - formatFSType = "ext4" -) - // Format calls mke2fs on path -func Format(path string) error { - cmd := exec.Command("mke2fs", "-t", formatFSType, path) +func Format(path string, formatFSType string) error { + cmd := exec.Command("mkfs", "-t", formatFSType, path) stdOutAndErr, err := cmd.CombinedOutput() log.Debugf("Mke2fs Output:\n%s", stdOutAndErr) return err From 5745cf56405aa3e9a33d8da683421947983f2a99 Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Sat, 13 Apr 2019 23:38:00 +0000 Subject: [PATCH 4/5] support btrfs, xfs, ext* --- Dockerfile | 2 +- README.md | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index aaddcbe..d32a204 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,5 +11,5 @@ CMD ["/go/bin/docker-volume-linode"] FROM alpine COPY --from=builder /go/bin/docker-volume-linode . -RUN apk update && apk add ca-certificates e2fsprogs +RUN apk update && apk add ca-certificates e2fsprogs xfsprogs btrfs-progs util-linux CMD ["./docker-volume-linode"] diff --git a/README.md b/README.md index 43c4ec3..2f2d599 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ $ docker volume create -d linode my-test-volume my-test-volume ``` +If a named volume already exists on the Linode account and it is in the same region of the Linode, it will be reattached if possible. A Linode Volume can be attached to a single Linode at a time. + #### Create Options The driver offers [driver specific volume create options](https://docs.docker.com/engine/reference/commandline/volume_create/#driver-specific-options): @@ -61,7 +63,7 @@ The driver offers [driver specific volume create options](https://docs.docker.co | Option | Type | Default | Description | | --- | --- | --- | --- | | `size` | int | `10` | the size (in GB) of the volume to be created. Volumes must be at least 10GB in size, so the default is 10GB. -| `filesystem` | string | `ext4` | the filesystem argument for `mkfs` when formating the new (raw) volume +| `filesystem` | string | `ext4` | the filesystem argument for `mkfs` when formating the new (raw) volume (xfs, btrfs, ext4) | `delete-on-remove` | bool | `false`| if the Linode volume should be deleted when removed ```sh @@ -75,6 +77,13 @@ Volumes can also be created and attached from `docker run`: docker run -it --rm --mount volume-driver=linode,source=test-vol,destination=/test,volume-opt=size=25 alpine ``` +Multiple create options can be supplied: + +```sh +docker run -it --rm --mount volume-driver=linode,source=test-vol,destination=/test,volume-opt=size=25,volume-opt=filesystem=btrfs,volume-opt=delete-on-remove=true alpine +``` + + ### List Volumes ```sh From b3a537816c11a77ac3ed4044cd30c78857359213 Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Sun, 14 Apr 2019 00:13:35 +0000 Subject: [PATCH 5/5] remove TEST_REGION from the Makefile --- Makefile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index ead1d7f..ec741bb 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,6 @@ DOCKER_PASSWORD ?= xxxxx # Test Arguments TEST_TOKEN ?= xyz -TEST_REGION ?= xyz TEST_LABEL ?= xyz GOPATH=$(shell go env GOPATH) @@ -82,16 +81,15 @@ test-use-volume: docker run --rm -i -v test-volume-default-size:/mnt busybox test -f /mnt/abc.txt || false test-pre-check: - @if [ "${TEST_TOKEN}" = "xyz" ] || [ "${TEST_REGION}" = "xyz" ] || [ "${TEST_LABEL}" = "xyz" ] ; then \ + @if [ "${TEST_TOKEN}" = "xyz" ] || [ "${TEST_LABEL}" = "xyz" ] ; then \ echo -en "#############################\nYou must set TEST_* Variables\n#############################\n"; exit 1; fi test-setup: - @docker plugin set $(PLUGIN_NAME) LINODE_TOKEN=${TEST_TOKEN} LINODE_REGION=${TEST_REGION} LINODE_LABEL=${TEST_LABEL} - docker plugin enable $(PLUGIN_NAME) + @docker plugin set $(PLUGIN_NAME) LINODE_TOKEN=${TEST_TOKEN} LINODE_LABEL=${TEST_LABEL} + docker plugin enable $(PLUGIN_NAME) check: # Tools - GO111MODULE=off go get -u github.com/tsenart/deadcode GO111MODULE=off go get -u github.com/kisielk/errcheck GO111MODULE=off go get -u golang.org/x/lint/golint