ecs-init/docker/dependencies.go (190 lines of code) (raw):
// Copyright 2015-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package docker
//go:generate mockgen.sh $GOPACKAGE $GOFILE
import (
"net"
"net/http"
"net/url"
"os"
"time"
"github.com/aws/amazon-ecs-agent/ecs-init/backoff"
"github.com/aws/amazon-ecs-agent/ecs-init/config"
log "github.com/cihub/seelog"
godocker "github.com/fsouza/go-dockerclient"
)
const (
// enableTaskENIDockerClientAPIVersion is the docker version at which we enable
// the ECS_ENABLE_TASK_ENI env var and set agent's "Init" field to true.
enableTaskENIDockerClientAPIVersion = "1.25"
)
var dockerAPIVersion string
type dockerclient interface {
ListImages(opts godocker.ListImagesOptions) ([]godocker.APIImages, error)
LoadImage(opts godocker.LoadImageOptions) error
Logs(opts godocker.LogsOptions) error
ListContainers(opts godocker.ListContainersOptions) ([]godocker.APIContainers, error)
RemoveContainer(opts godocker.RemoveContainerOptions) error
CreateContainer(opts godocker.CreateContainerOptions) (*godocker.Container, error)
StartContainer(id string, hostConfig *godocker.HostConfig) error
WaitContainer(id string) (int, error)
StopContainer(id string, timeout uint) error
Ping() error
}
type _dockerclient struct {
docker dockerclient
}
type dockerClientFactory interface {
NewVersionedClient(endpoint string, apiVersionString string) (dockerclient, error)
}
type godockerClientFactory struct{}
func (client godockerClientFactory) NewVersionedClient(endpoint string, apiVersionString string) (dockerclient, error) {
return godocker.NewVersionedClient(endpoint, apiVersionString)
}
// backupAPIVersions are API versions that have been deprecated on docker engine 25,
// they are only checked if none of the preferredAPIVersions are available.
// This list can be updated with time as docker engine deprecates more API versions.
func backupAPIVersions() []string {
return []string{
"1.21",
"1.22",
"1.23",
"1.24",
}
}
// preferredAPIVersions returns the preferred list of docker client API versions.
// When establishing the docker client for ecs-init to use, we will try these API version
// numbers first.
func preferredAPIVersions() []string {
return []string{
"1.25",
"1.26",
"1.27",
"1.28",
"1.29",
"1.30",
"1.31",
"1.32",
"1.33",
"1.34",
"1.35",
"1.36",
"1.37",
"1.38",
"1.39",
"1.40",
"1.41",
"1.42",
"1.43",
"1.44",
}
}
// knownAPIVersions returns all known docker API versions, in order from
// oldest to newest.
func knownAPIVersions() []string {
return append(backupAPIVersions(), preferredAPIVersions()...)
}
// dockerVersionCompare returns 0 if lhs and rhs are equal.
// returns 1 if lhs > rhs
// returns -1 if lhs < rhs
func dockerVersionCompare(lhs, rhs string) int {
if lhs == rhs {
return 0
}
var lhsI int = -1
var rhsI int = -1
for i, v := range knownAPIVersions() {
if lhs == v {
lhsI = i
}
if rhs == v {
rhsI = i
}
}
if lhsI < rhsI {
return -1
}
return 1
}
func newDockerClient(dockerClientFactory dockerClientFactory, pingBackoff backoff.Backoff) (dockerclient, error) {
var dockerClient dockerclient
var err error
allAPIVersions := append(preferredAPIVersions(), backupAPIVersions()...)
for _, apiVersion := range allAPIVersions {
dockerClient, err = newVersionedDockerClient(apiVersion, dockerClientFactory, pingBackoff)
if err == nil {
log.Infof("Successfully created docker client with API version %s", apiVersion)
dockerAPIVersion = apiVersion
break
}
log.Infof("Could not create docker client with API version %s: %s", apiVersion, err)
}
return dockerClient, err
}
func newVersionedDockerClient(apiVersion string, dockerClientFactory dockerClientFactory, pingBackoff backoff.Backoff) (dockerclient, error) {
dockerUnixSocketSourcePath, fromEnv := config.DockerUnixSocket()
if !fromEnv {
dockerUnixSocketSourcePath = "/var/run/docker.sock"
}
client, err := dockerClientFactory.NewVersionedClient(
config.UnixSocketPrefix+dockerUnixSocketSourcePath, apiVersion)
if err != nil {
return nil, err
}
for {
err = client.Ping()
if err == nil {
break
}
shouldRetry := (isNetworkError(err) || isRetryablePingError(err)) && pingBackoff.ShouldRetry()
if !shouldRetry {
break
}
backoffDuration := pingBackoff.Duration()
log.Infof("Error connecting to docker, backing off for %s, error: %s", backoffDuration, err)
time.Sleep(backoffDuration)
}
return &_dockerclient{
docker: client,
}, err
}
func (d *_dockerclient) ListImages(opts godocker.ListImagesOptions) ([]godocker.APIImages, error) {
return d.docker.ListImages(opts)
}
func (d *_dockerclient) LoadImage(opts godocker.LoadImageOptions) error {
return d.docker.LoadImage(opts)
}
func (d *_dockerclient) Logs(opts godocker.LogsOptions) error {
return d.docker.Logs(opts)
}
func (d *_dockerclient) ListContainers(opts godocker.ListContainersOptions) ([]godocker.APIContainers, error) {
return d.docker.ListContainers(opts)
}
func (d *_dockerclient) RemoveContainer(opts godocker.RemoveContainerOptions) error {
return d.docker.RemoveContainer(opts)
}
func (d *_dockerclient) CreateContainer(opts godocker.CreateContainerOptions) (*godocker.Container, error) {
return d.docker.CreateContainer(opts)
}
func (d *_dockerclient) StartContainer(id string, hostConfig *godocker.HostConfig) error {
return d.docker.StartContainer(id, hostConfig)
}
func (d *_dockerclient) WaitContainer(id string) (int, error) {
return d.docker.WaitContainer(id)
}
func (d *_dockerclient) StopContainer(id string, timeout uint) error {
return d.docker.StopContainer(id, timeout)
}
func (d *_dockerclient) Ping() error {
return d.docker.Ping()
}
type fileSystem interface {
ReadFile(filename string) ([]byte, error)
}
type _standardFS struct{}
var standardFS = &_standardFS{}
func (s *_standardFS) ReadFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}
func isNetworkError(err error) bool {
wrapped, isWrapped := err.(*url.Error)
if isWrapped {
_, ok := wrapped.Err.(*net.OpError)
return ok
}
return false
}
func isRetryablePingError(err error) bool {
godockerError, ok := err.(*godocker.Error)
if ok {
if godockerError.Status == http.StatusBadRequest {
return false
}
return godockerError.Status != http.StatusOK
}
return false
}