Skip to content

Commit 6505268

Browse files
authored
Merge pull request #1 from s4ke/master
test with exponential backoff
2 parents fe17f6e + 4e131da commit 6505268

File tree

9 files changed

+230
-63
lines changed

9 files changed

+230
-63
lines changed

.github/workflows/main.yaml

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
name: main
2-
on: [push, pull_request]
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- "*"
37
jobs:
48
test:
59
env:
@@ -8,7 +12,9 @@ jobs:
812
matrix:
913
go: [ 1.19.x ]
1014
os: [ ubuntu-latest ]
11-
name: ${{ matrix.os }}/go${{ matrix.go }}
15+
arch:
16+
- arm64
17+
- amd64
1218
runs-on: ${{ matrix.os }}
1319
steps:
1420
- uses: actions/checkout@v3
@@ -19,4 +25,20 @@ jobs:
1925
go-version: ${{ matrix.go }}
2026

2127
- run: make test
22-
28+
test_build:
29+
runs-on: ubuntu-latest
30+
strategy:
31+
matrix:
32+
arch:
33+
- arm64
34+
- amd64
35+
steps:
36+
- uses: actions/checkout@v3
37+
38+
- name: Set up QEMU
39+
uses: docker/setup-qemu-action@v2
40+
41+
- name: Set up Docker Buildx
42+
uses: docker/setup-buildx-action@v2
43+
44+
- run: make create PLUGIN_NAME=ghcr.io/${{ github.repository }} PLUGIN_TAG=$(shell git describe --tags --exact-match 2> /dev/null || echo dev)-${{ matrix.arch }}

.github/workflows/release.yml

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Production Build
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
jobs:
9+
release_build:
10+
env:
11+
LATEST_GO: 1.19.x # version used for release
12+
strategy:
13+
matrix:
14+
go: [ 1.19.x ]
15+
os: [ ubuntu-latest ]
16+
arch:
17+
- arm64
18+
- amd64
19+
name: ${{ matrix.os }}/go${{ matrix.go }} ${{ matrix.arch }}
20+
runs-on: ${{ matrix.os }}
21+
steps:
22+
- uses: actions/checkout@v3
23+
24+
- name: Setup go
25+
uses: actions/setup-go@v3
26+
with:
27+
go-version: ${{ matrix.go }}
28+
29+
- run: make test
30+
31+
- name: Set up QEMU
32+
uses: docker/setup-qemu-action@v2
33+
34+
- name: Set up Docker Buildx
35+
uses: docker/setup-buildx-action@v2
36+
37+
- name: Login to GitHub Container Registry
38+
uses: docker/login-action@v2
39+
with:
40+
registry: ghcr.io
41+
username: ${{ github.actor }}
42+
password: ${{ secrets.GITHUB_TOKEN }}
43+
44+
- name: Set env
45+
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
46+
47+
- run: |
48+
make push PLUGIN_NAME=ghcr.io/${{ github.repository }} PLUGIN_TAG=$RELEASE_VERSION-${{ matrix.arch }} ARCH=${{ matrix.arch }}

Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.19.3-alpine as builder
1+
FROM --platform=$TARGETPLATFORM golang:1.19.3-alpine as builder
22

33
ENV CGO_ENABLED=0
44

@@ -14,7 +14,7 @@ COPY . /plugin
1414
RUN go build -v
1515

1616

17-
FROM alpine
17+
FROM --platform=$TARGETPLATFORM alpine
1818

1919
RUN apk add --update ca-certificates e2fsprogs xfsprogs
2020

Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
PLUGIN_NAME = costela/docker-volume-hetzner
22
PLUGIN_TAG ?= $(shell git describe --tags --exact-match 2> /dev/null || echo dev)
3+
ARCH = amd64
34

45
all: create
56

@@ -12,7 +13,7 @@ clean:
1213
@docker container rm -vf tmp_plugin_build || true
1314

1415
rootfs: clean
15-
docker image build -t ${PLUGIN_NAME}:rootfs .
16+
docker image build --platform=linux/${ARCH} -t ${PLUGIN_NAME}:rootfs .
1617
mkdir -p ./plugin/rootfs
1718
docker container create --name tmp_plugin_build ${PLUGIN_NAME}:rootfs
1819
docker container export tmp_plugin_build | tar -x -C ./plugin/rootfs

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
[![Go Report Card](https://goreportcard.com/badge/github.com/costela/docker-volume-hetzner)](https://goreportcard.com/report/github.com/costela/docker-volume-hetzner)
22
![tests](https://github.com/costela/docker-volume-hetzner/actions/workflows/main.yaml/badge.svg)
3-
[![Docker Hub Version](https://img.shields.io/badge/dynamic/json.svg?label=hub&url=https%3A%2F%2Findex.docker.io%2Fv1%2Frepositories%2Fcostela%2Fdocker-volume-hetzner%2Ftags&query=%24[-1:].name&colorB=green)](https://hub.docker.com/r/costela/docker-volume-hetzner)
43

54
# Docker Volume Plugin for Hetzner Cloud
65

@@ -12,7 +11,7 @@ This plugin manages docker volumes using Hetzner Cloud's volumes.
1211

1312
To install the plugin, run the following command:
1413
```shell
15-
$ docker plugin install --alias hetzner costela/docker-volume-hetzner
14+
$ docker plugin install --alias hetzner ghcr.io/costela/docker-volume-hetzner:...-amd64
1615
```
1716

1817
When using Docker Swarm, this should be done on all nodes in the cluster.

driver.go

+100-10
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,45 @@ package main
33
import (
44
"context"
55
"fmt"
6+
"math"
67
"os"
78
"strconv"
89
"strings"
910
"time"
1011

1112
"github.com/docker/docker/pkg/mount"
1213
"github.com/hashicorp/go-multierror"
14+
"github.com/hetznercloud/hcloud-go/v2/hcloud"
1315
"github.com/sirupsen/logrus"
1416

1517
"github.com/docker/go-plugins-helpers/volume"
16-
"github.com/hetznercloud/hcloud-go/hcloud"
1718
)
1819

1920
// used in methods that take &bools
2021
var trueVar = true
2122
var falseVar = false
2223

2324
type hetznerDriver struct {
24-
client hetznerClienter
25+
client hetznerClienter
26+
nextTry time.Time
27+
failuresInARow int
28+
}
29+
30+
func (hd *hetznerDriver) checkBackoff() error {
31+
if time.Now().Before(hd.nextTry) {
32+
return fmt.Errorf("last failure too recent; waiting a bit before retrying")
33+
}
34+
return nil
35+
}
36+
37+
func (hd *hetznerDriver) handleBackoff(err error) {
38+
if err == nil {
39+
hd.failuresInARow = 0
40+
}
41+
if err != nil {
42+
hd.nextTry = time.Now().Add(time.Duration(int64(math.Pow(2, float64(hd.failuresInARow)))) * time.Second)
43+
hd.failuresInARow++
44+
}
2545
}
2646

2747
func newHetznerDriver() *hetznerDriver {
@@ -36,7 +56,7 @@ func (hd *hetznerDriver) Capabilities() *volume.CapabilitiesResponse {
3656
}
3757
}
3858

39-
func (hd *hetznerDriver) Create(req *volume.CreateRequest) error {
59+
func (hd *hetznerDriver) createInternal(req *volume.CreateRequest) error {
4060
validateOptions(req.Name, req.Options)
4161

4262
prefixedName := prefixName(req.Name)
@@ -61,7 +81,7 @@ func (hd *hetznerDriver) Create(req *volume.CreateRequest) error {
6181
}
6282
switch f := getOption("fstype", req.Options); f {
6383
case "xfs", "ext4":
64-
opts.Format = hcloud.String(f)
84+
opts.Format = hcloud.Ptr(f)
6585
}
6686

6787
resp, _, err := hd.client.Volume().Create(context.Background(), opts)
@@ -118,7 +138,17 @@ func (hd *hetznerDriver) Create(req *volume.CreateRequest) error {
118138
return nil
119139
}
120140

121-
func (hd *hetznerDriver) List() (*volume.ListResponse, error) {
141+
func (hd *hetznerDriver) Create(req *volume.CreateRequest) error {
142+
if err := hd.checkBackoff(); err != nil {
143+
return err
144+
}
145+
146+
err := hd.createInternal(req)
147+
hd.handleBackoff(err)
148+
return err
149+
}
150+
151+
func (hd *hetznerDriver) listInternal() (*volume.ListResponse, error) {
122152
logrus.Infof("got list request")
123153

124154
vols, err := hd.client.Volume().All(context.Background())
@@ -150,7 +180,17 @@ func (hd *hetznerDriver) List() (*volume.ListResponse, error) {
150180
return &resp, nil
151181
}
152182

153-
func (hd *hetznerDriver) Get(req *volume.GetRequest) (*volume.GetResponse, error) {
183+
func (hd *hetznerDriver) List() (*volume.ListResponse, error) {
184+
if err := hd.checkBackoff(); err != nil {
185+
return nil, err
186+
}
187+
188+
resp, err := hd.listInternal()
189+
hd.handleBackoff(err)
190+
return resp, err
191+
}
192+
193+
func (hd *hetznerDriver) getInternal(req *volume.GetRequest) (*volume.GetResponse, error) {
154194
prefixedName := prefixName(req.Name)
155195

156196
logrus.Infof("fetching information for volume %q", prefixedName)
@@ -186,7 +226,17 @@ func (hd *hetznerDriver) Get(req *volume.GetRequest) (*volume.GetResponse, error
186226
return &resp, nil
187227
}
188228

189-
func (hd *hetznerDriver) Remove(req *volume.RemoveRequest) error {
229+
func (hd *hetznerDriver) Get(req *volume.GetRequest) (*volume.GetResponse, error) {
230+
if err := hd.checkBackoff(); err != nil {
231+
return nil, err
232+
}
233+
234+
resp, err := hd.getInternal(req)
235+
hd.handleBackoff(err)
236+
return resp, err
237+
}
238+
239+
func (hd *hetznerDriver) removeInternal(req *volume.RemoveRequest) error {
190240
prefixedName := prefixName(req.Name)
191241

192242
logrus.Infof("starting volume removal for %q", prefixedName)
@@ -228,7 +278,17 @@ func (hd *hetznerDriver) Remove(req *volume.RemoveRequest) error {
228278
return nil
229279
}
230280

231-
func (hd *hetznerDriver) Path(req *volume.PathRequest) (*volume.PathResponse, error) {
281+
func (hd *hetznerDriver) Remove(req *volume.RemoveRequest) error {
282+
if err := hd.checkBackoff(); err != nil {
283+
return err
284+
}
285+
286+
err := hd.removeInternal(req)
287+
hd.handleBackoff(err)
288+
return err
289+
}
290+
291+
func (hd *hetznerDriver) pathInternal(req *volume.PathRequest) (*volume.PathResponse, error) {
232292
prefixedName := prefixName(req.Name)
233293

234294
logrus.Infof("got path request for volume %q", prefixedName)
@@ -241,7 +301,17 @@ func (hd *hetznerDriver) Path(req *volume.PathRequest) (*volume.PathResponse, er
241301
return &volume.PathResponse{Mountpoint: resp.Volume.Mountpoint}, nil
242302
}
243303

244-
func (hd *hetznerDriver) Mount(req *volume.MountRequest) (*volume.MountResponse, error) {
304+
func (hd *hetznerDriver) Path(req *volume.PathRequest) (*volume.PathResponse, error) {
305+
if err := hd.checkBackoff(); err != nil {
306+
return nil, err
307+
}
308+
309+
resp, err := hd.pathInternal(req)
310+
hd.handleBackoff(err)
311+
return resp, err
312+
}
313+
314+
func (hd *hetznerDriver) mountInternal(req *volume.MountRequest) (*volume.MountResponse, error) {
245315
prefixedName := prefixName(req.Name)
246316

247317
logrus.Infof("received mount request for %q as %q", prefixedName, req.ID)
@@ -313,7 +383,17 @@ func (hd *hetznerDriver) Mount(req *volume.MountRequest) (*volume.MountResponse,
313383
return &volume.MountResponse{Mountpoint: mountpoint}, nil
314384
}
315385

316-
func (hd *hetznerDriver) Unmount(req *volume.UnmountRequest) error {
386+
func (hd *hetznerDriver) Mount(req *volume.MountRequest) (*volume.MountResponse, error) {
387+
if err := hd.checkBackoff(); err != nil {
388+
return nil, err
389+
}
390+
391+
resp, err := hd.mountInternal(req)
392+
hd.handleBackoff(err)
393+
return resp, err
394+
}
395+
396+
func (hd *hetznerDriver) unmountInternal(req *volume.UnmountRequest) error {
317397
prefixedName := prefixName(req.Name)
318398

319399
logrus.Infof("received unmount request for %q as %q", prefixedName, req.ID)
@@ -357,6 +437,16 @@ func (hd *hetznerDriver) Unmount(req *volume.UnmountRequest) error {
357437
return nil
358438
}
359439

440+
func (hd *hetznerDriver) Unmount(req *volume.UnmountRequest) error {
441+
if err := hd.checkBackoff(); err != nil {
442+
return err
443+
}
444+
445+
err := hd.unmountInternal(req)
446+
hd.handleBackoff(err)
447+
return err
448+
}
449+
360450
func (hd *hetznerDriver) getServerForLocalhost() (*hcloud.Server, error) {
361451
hostname, err := os.Hostname()
362452
if err != nil {

driver_testwrapper.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package main
33
import (
44
"context"
55

6-
"github.com/hetznercloud/hcloud-go/hcloud"
6+
"github.com/hetznercloud/hcloud-go/v2/hcloud"
77
)
88

99
// these types wrap hcloud.Client to make mocking easier
@@ -24,7 +24,7 @@ type hetznerVolumeClienter interface {
2424
}
2525

2626
type hetznerServerClienter interface {
27-
GetByID(ctx context.Context, id int) (*hcloud.Server, *hcloud.Response, error)
27+
GetByID(ctx context.Context, id int64) (*hcloud.Server, *hcloud.Response, error)
2828
GetByName(ctx context.Context, name string) (*hcloud.Server, *hcloud.Response, error)
2929
}
3030

0 commit comments

Comments
 (0)