Skip to content

Commit

Permalink
Merge pull request #25 from displague/feature/no-require-region
Browse files Browse the repository at this point in the history
Address a few issues
  • Loading branch information
displague authored Apr 14, 2019
2 parents 069d82b + b3a5378 commit 2f1cfcf
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 54 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
8 changes: 3 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ DOCKER_PASSWORD ?= xxxxx

# Test Arguments
TEST_TOKEN ?= xyz
TEST_REGION ?= xyz
TEST_LABEL ?= xyz

GOPATH=$(shell go env GOPATH)
Expand Down Expand Up @@ -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
Expand Down
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<linode token>
docker plugin set linode linode-region=<linode region>
docker plugin set linode linode-label=<linode label>
docker plugin enable linode
```

- \<linode token\>: You will need a Linode APIv4 Personal Access Token. Get one here: <https://developers.linode.com/api/v4#section/Personal-Access-Token>. The API Token must have Read/Write permission for Volumes and Linodes.
- \<linode regions\>: `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`.
- \<linode label\>: 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
- \<linode label\>: 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

Expand All @@ -56,9 +54,17 @@ $ 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

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 (xfs, btrfs, ext4)
| `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
Expand All @@ -71,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
Expand Down Expand Up @@ -103,7 +116,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) |
Expand All @@ -119,7 +131,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=<token from linode console> --linode-region=<linode region> --linode-label=<linode label>
docker-volume-linode --linode-token=<token from linode console> --linode-label=<linode label>
```

### Debugging
Expand All @@ -135,7 +147,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
Expand Down
82 changes: 65 additions & 17 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"os"
"strconv"
"strings"
"sync"

"github.com/docker/docker/api/types/filters"
Expand All @@ -29,15 +30,21 @@ type linodeVolumeDriver struct {
linodeAPIPtr *linodego.Client
}

// Constructor
func newLinodeVolumeDriver(region string, linodeLabel string, linodeToken string) linodeVolumeDriver {
const (
fsTagPrefix = "docker-volume-filesystem-"
)

// 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
}

Expand All @@ -50,7 +57,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,
Expand All @@ -60,14 +80,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)
}
}

Expand All @@ -76,17 +97,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
Expand Down Expand Up @@ -159,6 +179,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 {
Expand All @@ -173,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)
}
Expand Down Expand Up @@ -203,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
}

Expand Down Expand Up @@ -262,14 +303,21 @@ 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
}

// 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
}
}
Expand Down Expand Up @@ -376,7 +424,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
Expand Down
8 changes: 2 additions & 6 deletions fs_utils_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 4 additions & 16 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 2f1cfcf

Please sign in to comment.