Skip to content

Commit 10864b5

Browse files
committed
add docker client version negotiation
1 parent 3c34170 commit 10864b5

File tree

11 files changed

+138
-37
lines changed

11 files changed

+138
-37
lines changed

goseg/config/urbit.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"fmt"
99
"groundseg/defaults"
10+
"groundseg/dockerclient"
1011
"groundseg/structs"
1112
"io/ioutil"
1213
"os"
@@ -16,7 +17,6 @@ import (
1617

1718
"github.com/docker/docker/api/types"
1819
"github.com/docker/docker/api/types/filters"
19-
"github.com/docker/docker/client"
2020
)
2121

2222
var (
@@ -133,7 +133,7 @@ func getImageTagByContainerName(containerName string) (string, error) {
133133
ctx := context.Background()
134134

135135
// Create a new Docker client
136-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
136+
cli, err := dockerclient.New()
137137
if err != nil {
138138
return "", fmt.Errorf("failed to create docker client: %w", err)
139139
}

goseg/docker/docker.go

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"groundseg/config"
8+
"groundseg/dockerclient"
89
"groundseg/structs"
910
"io"
1011
"io/ioutil"
@@ -42,7 +43,7 @@ func init() {
4243
if err = killContainerUsingPort(80); err != nil {
4344
zap.L().Error(fmt.Sprintf("Couldn't stop container on port 80: %v", err))
4445
}
45-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
46+
cli, err := dockerclient.New()
4647
if err != nil {
4748
zap.L().Error(fmt.Sprintf("Error creating Docker client: %v", err))
4849
return
@@ -63,7 +64,7 @@ func init() {
6364
func killContainerUsingPort(n uint16) error {
6465
// Initialize Docker client
6566
ctx := context.Background()
66-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
67+
cli, err := dockerclient.New()
6768
if err != nil {
6869
return err
6970
}
@@ -156,7 +157,7 @@ func updateDocker() {
156157
// return the container status of a slice of ships
157158
func GetShipStatus(patps []string) (map[string]string, error) {
158159
statuses := make(map[string]string)
159-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
160+
cli, err := dockerclient.New()
160161
if err != nil {
161162
errmsg := fmt.Sprintf("Error getting Docker info: %v", err)
162163
zap.L().Error(errmsg)
@@ -195,7 +196,7 @@ func GetShipStatus(patps []string) (map[string]string, error) {
195196

196197
func GetContainerRunningStatus(containerName string) (string, error) {
197198
var status string
198-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
199+
cli, err := dockerclient.New()
199200
if err != nil {
200201
}
201202
defer cli.Close()
@@ -217,7 +218,7 @@ func GetContainerRunningStatus(containerName string) (string, error) {
217218

218219
// return the name of a container's network
219220
func GetContainerNetwork(name string) (string, error) {
220-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
221+
cli, err := dockerclient.New()
221222
if err != nil {
222223
return "", err
223224
}
@@ -234,7 +235,7 @@ func GetContainerNetwork(name string) (string, error) {
234235

235236
// creates a volume by name
236237
func CreateVolume(name string) error {
237-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
238+
cli, err := dockerclient.New()
238239
if err != nil {
239240
errmsg := fmt.Errorf("Failed to create docker client: %v : %v", name, err)
240241
return errmsg
@@ -254,7 +255,7 @@ func CreateVolume(name string) error {
254255

255256
// deletes a volume by its name
256257
func DeleteVolume(name string) error {
257-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
258+
cli, err := dockerclient.New()
258259
if err != nil {
259260
errmsg := fmt.Errorf("Failed to create docker client: %v : %v", name, err)
260261
return errmsg
@@ -272,7 +273,7 @@ func DeleteVolume(name string) error {
272273

273274
// deletes a container by its name
274275
func DeleteContainer(name string) error {
275-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
276+
cli, err := dockerclient.New()
276277
if err != nil {
277278
errmsg := fmt.Errorf("Failed to create docker client: %v : %v", name, err)
278279
return errmsg
@@ -291,7 +292,7 @@ func DeleteContainer(name string) error {
291292

292293
// Write a file to a specific location in a volume
293294
func WriteFileToVolume(name string, file string, content string) error {
294-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
295+
cli, err := dockerclient.New()
295296
if err != nil {
296297
errmsg := fmt.Errorf("Failed to create docker client: %v : %v", name, err)
297298
return errmsg
@@ -381,7 +382,7 @@ func StartContainer(containerName string, containerType string) (structs.Contain
381382
existingContainer, _ := FindContainer(containerName)
382383

383384
ctx := context.Background()
384-
cli, err := client.NewClientWithOpts(client.FromEnv)
385+
cli, err := dockerclient.New()
385386
if err != nil {
386387
return containerState, err
387388
}
@@ -525,7 +526,7 @@ func CreateContainer(containerName string, containerType string) (structs.Contai
525526
return containerState, err
526527
}
527528
ctx := context.Background()
528-
cli, err := client.NewClientWithOpts(client.FromEnv)
529+
cli, err := dockerclient.New()
529530
if err != nil {
530531
return containerState, err
531532
}
@@ -604,7 +605,7 @@ func GetLatestContainerInfo(containerType string) (map[string]string, error) {
604605
// stop a container with the name
605606
func StopContainerByName(containerName string) error {
606607
ctx := context.Background()
607-
cli, err := client.NewClientWithOpts(client.FromEnv)
608+
cli, err := dockerclient.New()
608609
if err != nil {
609610
return err
610611
}
@@ -633,7 +634,7 @@ func StopContainerByName(containerName string) error {
633634
// pull the image if it doesn't exist locally
634635
func PullImageIfNotExist(desiredImage string, imageInfo map[string]string) (bool, error) {
635636
ctx := context.Background()
636-
cli, err := client.NewClientWithOpts(client.FromEnv)
637+
cli, err := dockerclient.New()
637638
if err != nil {
638639
return false, err
639640
}
@@ -660,7 +661,7 @@ func PullImageIfNotExist(desiredImage string, imageInfo map[string]string) (bool
660661

661662
// looks for a container with the given name and returns it, or nil if not found
662663
func FindContainer(containerName string) (*types.Container, error) {
663-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
664+
cli, err := dockerclient.New()
664665
if err != nil {
665666
return nil, err
666667
}
@@ -698,7 +699,7 @@ func DockerPoller() {
698699

699700
// execute command
700701
func ExecDockerCommand(containerName string, cmd []string) (string, error) {
701-
cli, err := client.NewClientWithOpts(client.FromEnv)
702+
cli, err := dockerclient.New()
702703
if err != nil {
703704
return "", err
704705
}
@@ -758,7 +759,7 @@ func GetContainerIDByName(ctx context.Context, cli *client.Client, name string)
758759
// restart a running container
759760
func RestartContainer(name string) error {
760761
ctx := context.Background()
761-
cli, err := client.NewClientWithOpts(client.FromEnv)
762+
cli, err := dockerclient.New()
762763
if err != nil {
763764
return fmt.Errorf("Couldn't create client: %v", err)
764765
}
@@ -787,7 +788,7 @@ func contains(slice []string, str string) bool {
787788
}
788789

789790
func volumeExists(volumeName string) (bool, error) {
790-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
791+
cli, err := dockerclient.New()
791792
if err != nil {
792793
return false, fmt.Errorf("Failed to create client: %v", err)
793794
}
@@ -805,7 +806,7 @@ func volumeExists(volumeName string) (bool, error) {
805806
}
806807

807808
func addOrGetNetwork(networkName string) (string, error) {
808-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
809+
cli, err := dockerclient.New()
809810
if err != nil {
810811
return "", fmt.Errorf("Failed to create client: %v", err)
811812
}

goseg/docker/netdata.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import (
44
"context"
55
"fmt"
66
"groundseg/config"
7+
"groundseg/dockerclient"
78
"io/ioutil"
89
"os"
910
"path/filepath"
1011

1112
"github.com/docker/docker/api/types"
1213
"github.com/docker/docker/api/types/container"
13-
"github.com/docker/docker/client"
1414
"github.com/docker/go-connections/nat"
1515
"go.uber.org/zap"
1616
)
@@ -142,7 +142,7 @@ func writeNDConfToFile(filePath string, content string) error {
142142
// write ND conf to volume
143143
func copyNDFileToVolume(filePath string, targetPath string, volumeName string) error {
144144
ctx := context.Background()
145-
cli, err := client.NewClientWithOpts(client.FromEnv)
145+
cli, err := dockerclient.New()
146146
if err != nil {
147147
return err
148148
}

goseg/docker/stats.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"groundseg/dockerclient"
78
"groundseg/structs"
89
"io/ioutil"
910
"os"
1011
"path/filepath"
1112
"time"
1213

1314
"github.com/docker/docker/api/types"
14-
"github.com/docker/docker/client"
1515
"go.uber.org/zap"
1616
)
1717

@@ -53,7 +53,7 @@ func ForceUpdateContainerStats(name string) structs.ContainerStats {
5353

5454
// getMemoryUsage retrieves the memory usage of a container.
5555
func getMemoryUsage(containerID string) uint64 {
56-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
56+
cli, err := dockerclient.New()
5757
if err != nil {
5858
zap.L().Error(fmt.Sprintf("Failed to create Docker client: ", err))
5959
return 0
@@ -85,7 +85,7 @@ func getMemoryUsage(containerID string) uint64 {
8585

8686
// getDiskUsage calculates the disk usage by totaling the space used by volumes of the container.
8787
func getDiskUsage(containerID string) int64 {
88-
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
88+
cli, err := dockerclient.New()
8989
if err != nil {
9090
zap.L().Error(fmt.Sprintf("Failed to create Docker client: ", err))
9191
return 0

goseg/docker/wireguard.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/base64"
66
"fmt"
77
"groundseg/config"
8+
"groundseg/dockerclient"
89
"io/ioutil"
910
"os"
1011
"path/filepath"
@@ -14,7 +15,6 @@ import (
1415
"github.com/docker/docker/api/types"
1516
"github.com/docker/docker/api/types/container"
1617
"github.com/docker/docker/api/types/mount"
17-
"github.com/docker/docker/client"
1818
"go.uber.org/zap"
1919
// "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
2020
)
@@ -146,7 +146,7 @@ func writeWgConfToFile(filePath string, content string) error {
146146
// write wg conf to volume
147147
func copyWGFileToVolume(filePath string, targetPath string, volumeName string) error {
148148
ctx := context.Background()
149-
cli, err := client.NewClientWithOpts(client.FromEnv)
149+
cli, err := dockerclient.New()
150150
if err != nil {
151151
return err
152152
}

goseg/dockerclient/client.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package dockerclient
2+
3+
import (
4+
"context"
5+
"sync"
6+
"time"
7+
8+
"github.com/docker/docker/api"
9+
"github.com/docker/docker/api/types/versions"
10+
"github.com/docker/docker/client"
11+
"go.uber.org/zap"
12+
)
13+
14+
const pingTimeout = 5 * time.Second
15+
16+
var (
17+
versionMu sync.RWMutex
18+
cachedVersion string
19+
versionDetected bool
20+
)
21+
22+
// New returns a Docker client that pings the daemon once to discover the API
23+
// version the server expects. Subsequent calls reuse the cached version so we
24+
// don't renegotiate every time we touch the Docker daemon.
25+
func New(extraOpts ...client.Opt) (*client.Client, error) {
26+
apiVersion, err := getAPIVersion()
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
opts := []client.Opt{client.FromEnv}
32+
if apiVersion == "" {
33+
opts = append(opts, client.WithAPIVersionNegotiation())
34+
} else {
35+
opts = append(opts, client.WithVersion(apiVersion))
36+
}
37+
opts = append(opts, extraOpts...)
38+
return client.NewClientWithOpts(opts...)
39+
}
40+
41+
func getAPIVersion() (string, error) {
42+
versionMu.RLock()
43+
if versionDetected {
44+
defer versionMu.RUnlock()
45+
return cachedVersion, nil
46+
}
47+
versionMu.RUnlock()
48+
49+
versionMu.Lock()
50+
defer versionMu.Unlock()
51+
if versionDetected {
52+
return cachedVersion, nil
53+
}
54+
55+
version, err := detectAPIVersion()
56+
if err != nil {
57+
return "", err
58+
}
59+
cachedVersion = version
60+
versionDetected = true
61+
return cachedVersion, nil
62+
}
63+
64+
func detectAPIVersion() (string, error) {
65+
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
66+
if err != nil {
67+
return "", err
68+
}
69+
defer cli.Close()
70+
71+
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
72+
defer cancel()
73+
74+
ping, err := cli.Ping(ctx)
75+
if err != nil {
76+
zap.L().Warn("Failed to ping Docker daemon while discovering API version; falling back to negotiation", zap.Error(err))
77+
return "", nil
78+
}
79+
if ping.APIVersion == "" {
80+
zap.L().Warn("Docker daemon did not report an API version; falling back to negotiation")
81+
return "", nil
82+
}
83+
84+
clientVersion := api.DefaultVersion
85+
switch {
86+
case versions.GreaterThan(ping.APIVersion, clientVersion):
87+
zap.L().Info("Docker daemon requires newer API version than library default",
88+
zap.String("client_api_version", clientVersion),
89+
zap.String("daemon_api_version", ping.APIVersion))
90+
case versions.LessThan(ping.APIVersion, clientVersion):
91+
zap.L().Debug("Docker daemon API version is older than client default",
92+
zap.String("client_api_version", clientVersion),
93+
zap.String("daemon_api_version", ping.APIVersion))
94+
default:
95+
zap.L().Debug("Docker daemon API version matches client default",
96+
zap.String("api_version", clientVersion))
97+
}
98+
99+
return ping.APIVersion, nil
100+
}

0 commit comments

Comments
 (0)