2017-10-31 13:55:35 +00:00
package docker
import (
2018-01-10 23:03:08 +00:00
"archive/tar"
2018-07-18 20:44:55 +00:00
"bytes"
2017-10-31 13:55:35 +00:00
"context"
2018-01-30 18:15:14 +00:00
"encoding/base64"
"encoding/json"
2017-10-31 13:55:35 +00:00
"fmt"
"io"
"io/ioutil"
"os"
2018-04-04 20:55:26 +00:00
"strings"
2018-06-28 23:56:15 +00:00
"time"
2017-10-31 13:55:35 +00:00
2020-10-05 03:15:57 +00:00
"github.com/rancher/rke/util"
2018-04-04 20:55:26 +00:00
"github.com/coreos/go-semver/semver"
2018-01-30 18:15:14 +00:00
ref "github.com/docker/distribution/reference"
2017-10-31 13:55:35 +00:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
2017-11-02 10:07:10 +00:00
"github.com/docker/docker/client"
2018-07-18 20:44:55 +00:00
"github.com/docker/docker/pkg/stdcopy"
2018-01-09 22:10:56 +00:00
"github.com/rancher/rke/log"
2019-07-15 17:58:09 +00:00
"github.com/rancher/rke/metadata"
2020-07-11 16:24:19 +00:00
v3 "github.com/rancher/rke/types"
2017-11-13 21:28:38 +00:00
"github.com/sirupsen/logrus"
2018-04-02 11:01:51 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-10-31 13:55:35 +00:00
)
2018-01-30 18:15:14 +00:00
const (
DockerRegistryURL = "docker.io"
2018-09-20 16:55:29 +00:00
// RestartTimeout in seconds
2018-08-20 04:37:04 +00:00
RestartTimeout = 5
2018-09-20 16:55:29 +00:00
// StopTimeout in seconds
2018-10-01 17:58:29 +00:00
StopTimeout = 5
2019-06-11 09:39:45 +00:00
// RetryCount is the amount of retries for Docker operations
RetryCount = 3
2021-01-22 17:35:13 +00:00
// WaitTimeout in seconds
WaitTimeout = 300
// WaitTimeoutContextKey name
WaitTimeoutContextKey = "wait_timeout"
2018-01-30 18:15:14 +00:00
)
2018-08-29 00:23:41 +00:00
type dockerConfig struct {
2020-10-05 03:15:57 +00:00
Auths map [ string ] authConfig ` json:"auths,omitempty" `
CredHelpers map [ string ] string ` json:"credHelpers,omitempty" `
2018-08-29 00:23:41 +00:00
}
type authConfig types . AuthConfig
2019-11-06 00:50:01 +00:00
func DoRunContainer ( ctx context . Context , dClient * client . Client , imageCfg * container . Config , hostCfg * container . HostConfig ,
containerName string , hostname string , plane string , prsMap map [ string ] v3 . PrivateRegistry ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
2019-11-06 00:50:01 +00:00
return fmt . Errorf ( "[%s] Failed to run container: docker client is nil for container [%s] on host [%s]" ,
plane , containerName , hostname )
2019-01-25 01:42:01 +00:00
}
2019-06-11 09:39:45 +00:00
container , err := InspectContainer ( ctx , dClient , hostname , containerName )
2017-10-31 13:55:35 +00:00
if err != nil {
2018-01-15 21:01:58 +00:00
if ! client . IsErrNotFound ( err ) {
return err
}
2018-01-30 18:15:14 +00:00
if err := UseLocalOrPull ( ctx , dClient , hostname , imageCfg . Image , plane , prsMap ) ; err != nil {
2019-06-11 09:39:45 +00:00
return fmt . Errorf ( "Failed to pull image [%s] on host [%s]: %v" , imageCfg . Image , hostname , err )
2018-01-15 21:01:58 +00:00
}
2019-06-11 09:39:45 +00:00
_ , err := CreateContainer ( ctx , dClient , hostname , containerName , imageCfg , hostCfg )
2018-01-15 21:01:58 +00:00
if err != nil {
return fmt . Errorf ( "Failed to create [%s] container on host [%s]: %v" , containerName , hostname , err )
}
2019-06-11 09:39:45 +00:00
if err := StartContainer ( ctx , dClient , hostname , containerName ) ; err != nil {
2018-01-15 21:01:58 +00:00
return fmt . Errorf ( "Failed to start [%s] container on host [%s]: %v" , containerName , hostname , err )
}
log . Infof ( ctx , "[%s] Successfully started [%s] container on host [%s]" , plane , containerName , hostname )
return nil
2017-10-31 13:55:35 +00:00
}
2018-01-15 21:01:58 +00:00
// Check for upgrades
if container . State . Running {
2018-06-28 23:56:15 +00:00
// check if container is in a restarting loop
if container . State . Restarting {
logrus . Debugf ( "[%s] Container [%s] is in a restarting loop [%s]" , plane , containerName , hostname )
2019-06-11 09:39:45 +00:00
err = RestartContainer ( ctx , dClient , hostname , containerName )
if err != nil {
return err
2018-06-28 23:56:15 +00:00
}
}
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[%s] Container [%s] is already running on host [%s]" , plane , containerName , hostname )
2018-07-18 19:51:46 +00:00
isUpgradable , err := IsContainerUpgradable ( ctx , dClient , imageCfg , hostCfg , containerName , hostname , plane )
2017-11-28 11:26:15 +00:00
if err != nil {
return err
}
if isUpgradable {
2018-01-30 18:15:14 +00:00
return DoRollingUpdateContainer ( ctx , dClient , imageCfg , hostCfg , containerName , hostname , plane , prsMap )
2017-11-28 11:26:15 +00:00
}
2017-10-31 13:55:35 +00:00
return nil
}
2017-11-28 11:26:15 +00:00
2018-01-15 21:01:58 +00:00
// start if not running
2019-06-11 09:39:45 +00:00
logrus . Infof ( "[%s] Starting stopped container [%s] on host [%s]" , plane , containerName , hostname )
if err := StartContainer ( ctx , dClient , hostname , containerName ) ; err != nil {
2017-11-30 23:16:45 +00:00
return fmt . Errorf ( "Failed to start [%s] container on host [%s]: %v" , containerName , hostname , err )
2017-10-31 13:55:35 +00:00
}
2018-01-09 22:10:56 +00:00
log . Infof ( ctx , "[%s] Successfully started [%s] container on host [%s]" , plane , containerName , hostname )
2017-10-31 13:55:35 +00:00
return nil
}
2019-04-21 20:39:08 +00:00
func DoRunOnetimeContainer ( ctx context . Context , dClient * client . Client , imageCfg * container . Config , hostCfg * container . HostConfig , containerName string , hostname string , plane string , prsMap map [ string ] v3 . PrivateRegistry ) error {
if dClient == nil {
return fmt . Errorf ( "[%s] Failed to run container: docker client is nil for container [%s] on host [%s]" , plane , containerName , hostname )
}
2019-06-11 09:39:45 +00:00
_ , err := InspectContainer ( ctx , dClient , hostname , containerName )
2019-04-21 20:39:08 +00:00
if err != nil {
if ! client . IsErrNotFound ( err ) {
return err
}
if err := UseLocalOrPull ( ctx , dClient , hostname , imageCfg . Image , plane , prsMap ) ; err != nil {
return err
}
2019-06-11 09:39:45 +00:00
_ , err := CreateContainer ( ctx , dClient , hostname , containerName , imageCfg , hostCfg )
2019-04-21 20:39:08 +00:00
if err != nil {
return fmt . Errorf ( "Failed to create [%s] container on host [%s]: %v" , containerName , hostname , err )
}
2019-06-11 09:39:45 +00:00
if err := StartContainer ( ctx , dClient , hostname , containerName ) ; err != nil {
2019-04-21 20:39:08 +00:00
return fmt . Errorf ( "Failed to start [%s] container on host [%s]: %v" , containerName , hostname , err )
}
log . Infof ( ctx , "Successfully started [%s] container on host [%s]" , containerName , hostname )
log . Infof ( ctx , "Waiting for [%s] container to exit on host [%s]" , containerName , hostname )
exitCode , err := WaitForContainer ( ctx , dClient , hostname , containerName )
if err != nil {
return fmt . Errorf ( "Container [%s] did not complete in time on host [%s]" , containerName , hostname )
}
if exitCode != 0 {
stderr , stdout , err := GetContainerLogsStdoutStderr ( ctx , dClient , containerName , "1" , false )
if err != nil {
return fmt . Errorf ( "Unable to retrieve logs for container [%s] on host [%s]: %v" , containerName , hostname , err )
}
stderr = strings . TrimSuffix ( stderr , "\n" )
stdout = strings . TrimSuffix ( stdout , "\n" )
return fmt . Errorf ( "Container [%s] exited with non-zero exit code [%d] on host [%s]: stdout: %s, stderr: %s" , containerName , exitCode , hostname , stdout , stderr )
}
return nil
}
return err
}
2020-08-14 16:42:37 +00:00
func DoCopyToContainer ( ctx context . Context , dClient * client . Client , plane , containerName , hostname , destinationDir string , tarFile io . Reader ) error {
if dClient == nil {
return fmt . Errorf ( "[%s] Failed to run container: docker client is nil for container [%s] on host [%s]" , plane , containerName , hostname )
}
// Need container.ID for CopyToContainer function
container , err := InspectContainer ( ctx , dClient , hostname , containerName )
if err != nil {
return fmt . Errorf ( "Could not inspect container [%s] on host [%s]: %s" , containerName , hostname , err )
}
err = dClient . CopyToContainer ( ctx , container . ID , destinationDir , tarFile , types . CopyToContainerOptions { } )
if err != nil {
return fmt . Errorf ( "Error while copying file to container [%s] on host [%s]: %v" , containerName , hostname , err )
}
return nil
}
2019-04-21 20:39:08 +00:00
2018-01-30 18:15:14 +00:00
func DoRollingUpdateContainer ( ctx context . Context , dClient * client . Client , imageCfg * container . Config , hostCfg * container . HostConfig , containerName , hostname , plane string , prsMap map [ string ] v3 . PrivateRegistry ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "[%s] Failed rolling update of container: docker client is nil for container [%s] on host [%s]" , plane , containerName , hostname )
}
2017-11-30 23:16:45 +00:00
logrus . Debugf ( "[%s] Checking for deployed [%s]" , plane , containerName )
2018-01-09 22:10:56 +00:00
isRunning , err := IsContainerRunning ( ctx , dClient , hostname , containerName , false )
2017-11-17 00:45:51 +00:00
if err != nil {
return err
}
if ! isRunning {
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[%s] Container %s is not running on host [%s]" , plane , containerName , hostname )
2017-11-17 00:45:51 +00:00
return nil
}
2018-01-30 18:15:14 +00:00
err = UseLocalOrPull ( ctx , dClient , hostname , imageCfg . Image , plane , prsMap )
2017-11-28 11:26:15 +00:00
if err != nil {
return err
}
2017-11-17 00:45:51 +00:00
logrus . Debugf ( "[%s] Stopping old container" , plane )
oldContainerName := "old-" + containerName
2018-01-09 22:10:56 +00:00
if err := StopRenameContainer ( ctx , dClient , hostname , containerName , oldContainerName ) ; err != nil {
2017-11-17 00:45:51 +00:00
return err
}
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[%s] Successfully stopped old container %s on host [%s]" , plane , containerName , hostname )
2018-04-24 12:44:17 +00:00
_ , err = CreateContainer ( ctx , dClient , hostname , containerName , imageCfg , hostCfg )
2017-11-17 00:45:51 +00:00
if err != nil {
2017-11-30 23:16:45 +00:00
return fmt . Errorf ( "Failed to create [%s] container on host [%s]: %v" , containerName , hostname , err )
2017-11-17 00:45:51 +00:00
}
2018-01-09 22:10:56 +00:00
if err := StartContainer ( ctx , dClient , hostname , containerName ) ; err != nil {
2017-11-30 23:16:45 +00:00
return fmt . Errorf ( "Failed to start [%s] container on host [%s]: %v" , containerName , hostname , err )
2017-11-17 00:45:51 +00:00
}
2018-01-09 22:10:56 +00:00
log . Infof ( ctx , "[%s] Successfully updated [%s] container on host [%s]" , plane , containerName , hostname )
2017-11-17 00:45:51 +00:00
logrus . Debugf ( "[%s] Removing old container" , plane )
2018-01-09 22:10:56 +00:00
err = RemoveContainer ( ctx , dClient , hostname , oldContainerName )
2017-11-17 00:45:51 +00:00
return err
}
2018-01-09 22:10:56 +00:00
func DoRemoveContainer ( ctx context . Context , dClient * client . Client , containerName , hostname string ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "Failed to remove container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[remove/%s] Checking if container is running on host [%s]" , containerName , hostname )
2019-06-11 09:39:45 +00:00
_ , err := InspectContainer ( ctx , dClient , hostname , containerName )
2017-11-20 18:08:50 +00:00
if err != nil {
if client . IsErrNotFound ( err ) {
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[remove/%s] Container doesn't exist on host [%s]" , containerName , hostname )
2017-11-20 18:08:50 +00:00
return nil
}
return err
}
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[remove/%s] Removing container on host [%s]" , containerName , hostname )
2018-01-09 22:10:56 +00:00
err = RemoveContainer ( ctx , dClient , hostname , containerName )
2017-11-20 18:08:50 +00:00
if err != nil {
return err
}
2018-01-09 22:10:56 +00:00
log . Infof ( ctx , "[remove/%s] Successfully removed container on host [%s]" , containerName , hostname )
2017-11-20 18:08:50 +00:00
return nil
}
2018-01-09 22:10:56 +00:00
func IsContainerRunning ( ctx context . Context , dClient * client . Client , hostname string , containerName string , all bool ) ( bool , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return false , fmt . Errorf ( "Failed to check if container is running: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2019-06-11 09:39:45 +00:00
var containers [ ] types . Container
var err error
for i := 1 ; i <= RetryCount ; i ++ {
logrus . Infof ( "Checking if container [%s] is running on host [%s], try #%d" , containerName , hostname , i )
containers , err = dClient . ContainerList ( ctx , types . ContainerListOptions { All : all } )
if err != nil {
logrus . Warnf ( "Error checking if container [%s] is running on host [%s]: %v" , containerName , hostname , err )
continue
}
break
}
2017-10-31 13:55:35 +00:00
if err != nil {
2019-06-11 09:39:45 +00:00
return false , fmt . Errorf ( "Error checking if container [%s] is running on host [%s]: %v" , containerName , hostname , err )
2017-10-31 13:55:35 +00:00
}
for _ , container := range containers {
2019-02-27 21:44:13 +00:00
if len ( container . Names ) != 0 && container . Names [ 0 ] == "/" + containerName {
2017-10-31 13:55:35 +00:00
return true , nil
}
}
return false , nil
}
2019-10-30 22:46:56 +00:00
func localImageExists ( ctx context . Context , dClient * client . Client , hostname string , containerImage string ) error {
2019-06-11 09:39:45 +00:00
var err error
for i := 1 ; i <= RetryCount ; i ++ {
2019-10-30 22:46:56 +00:00
logrus . Debugf ( "Checking if image [%s] exists on host [%s], try #%d" , containerImage , hostname , i )
2019-06-11 09:39:45 +00:00
_ , _ , err = dClient . ImageInspectWithRaw ( ctx , containerImage )
if err != nil {
if client . IsErrNotFound ( err ) {
2019-10-30 22:46:56 +00:00
logrus . Debugf ( "Image [%s] does not exist on host [%s]: %v" , containerImage , hostname , err )
return err
2019-06-11 09:39:45 +00:00
}
2019-10-30 22:46:56 +00:00
logrus . Debugf ( "Error checking if image [%s] exists on host [%s]: %v" , containerImage , hostname , err )
2019-06-11 09:39:45 +00:00
continue
2017-12-13 00:29:24 +00:00
}
2019-06-11 09:39:45 +00:00
logrus . Infof ( "Image [%s] exists on host [%s]" , containerImage , hostname )
2019-10-30 22:46:56 +00:00
return nil
2017-12-13 00:29:24 +00:00
}
2019-10-30 22:46:56 +00:00
return fmt . Errorf ( "Error checking if image [%s] exists on host [%s]: %v" , containerImage , hostname , err )
2017-12-13 00:29:24 +00:00
}
2019-11-06 00:50:01 +00:00
func pullImage ( ctx context . Context , dClient * client . Client , hostname string , containerImage string ,
prsMap map [ string ] v3 . PrivateRegistry ) error {
2019-06-11 09:39:45 +00:00
var out io . ReadCloser
var err error
2018-01-30 18:15:14 +00:00
pullOptions := types . ImagePullOptions { }
2018-04-03 20:18:51 +00:00
regAuth , prURL , err := GetImageRegistryConfig ( containerImage , prsMap )
2018-01-30 18:15:14 +00:00
if err != nil {
return err
}
2018-04-03 20:18:51 +00:00
if regAuth != "" && prURL == DockerRegistryURL {
pullOptions . PrivilegeFunc = tryRegistryAuth ( prsMap [ prURL ] )
2018-01-30 18:15:14 +00:00
}
2018-04-03 20:18:51 +00:00
pullOptions . RegistryAuth = regAuth
2018-01-30 18:15:14 +00:00
2019-06-11 09:39:45 +00:00
// Retry up to RetryCount times to pull image
for i := 1 ; i <= RetryCount ; i ++ {
logrus . Infof ( "Pulling image [%s] on host [%s], try #%d" , containerImage , hostname , i )
out , err = dClient . ImagePull ( ctx , containerImage , pullOptions )
if err != nil {
logrus . Warnf ( "Can't pull Docker image [%s] on host [%s]: %v" , containerImage , hostname , err )
continue
}
defer out . Close ( )
2020-03-05 18:42:05 +00:00
if logrus . GetLevel ( ) == logrus . TraceLevel {
2019-06-11 09:39:45 +00:00
io . Copy ( os . Stdout , out )
} else {
io . Copy ( ioutil . Discard , out )
}
return nil
2017-10-31 13:55:35 +00:00
}
2019-06-11 09:39:45 +00:00
// If the for loop does not return, return the error
return err
2017-10-31 13:55:35 +00:00
}
2017-11-02 10:07:10 +00:00
2019-11-06 00:50:01 +00:00
func UseLocalOrPull ( ctx context . Context , dClient * client . Client , hostname string , containerImage string , plane string ,
prsMap map [ string ] v3 . PrivateRegistry ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "[%s] Failed to use local image or pull: docker client is nil for container [%s] on host [%s]" , plane , containerImage , hostname )
}
2019-06-11 09:39:45 +00:00
var err error
// Retry up to RetryCount times to see if image exists
for i := 1 ; i <= RetryCount ; i ++ {
// Increasing wait time on retry, but not on the first two try
if i > 2 {
time . Sleep ( time . Duration ( i ) * time . Second )
}
2019-10-30 22:46:56 +00:00
if err = localImageExists ( ctx , dClient , hostname , containerImage ) ; err == nil {
// Return if image exists to prevent pulling
return nil
2019-06-11 09:39:45 +00:00
}
2019-10-30 22:46:56 +00:00
// If error, log and retry
if ! client . IsErrNotFound ( err ) {
logrus . Debugf ( "[%s] %v" , plane , err )
continue
2019-06-11 09:39:45 +00:00
}
2019-10-30 22:46:56 +00:00
// Try pulling when not found and if error, log and retry
2019-06-11 09:39:45 +00:00
err = pullImage ( ctx , dClient , hostname , containerImage , prsMap )
if err != nil {
2019-10-30 22:46:56 +00:00
logrus . Debugf ( "[%s] Can't pull Docker image [%s] on host [%s]: %v" , plane , containerImage , hostname , err )
2019-06-11 09:39:45 +00:00
continue
}
2017-12-13 00:29:24 +00:00
}
2019-06-11 09:39:45 +00:00
// If the for loop does not return, return the error
2019-10-30 22:46:56 +00:00
if err != nil {
// Although error should be logged in the caller stack, logging the final error here just in case. Mostly
// because error logging was reduced in other places
logrus . Warnf ( "[%s] Can't pull Docker image [%s] on host [%s]: %v" , plane , containerImage , hostname , err )
}
2019-06-11 09:39:45 +00:00
return err
2017-12-13 00:29:24 +00:00
}
2018-01-09 22:10:56 +00:00
func RemoveContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "Failed to remove container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2019-06-11 09:39:45 +00:00
var err error
// Retry up to RetryCount times to see if image exists
for i := 1 ; i <= RetryCount ; i ++ {
logrus . Infof ( "Removing container [%s] on host [%s], try #%d" , containerName , hostname , i )
err = dClient . ContainerRemove ( ctx , containerName , types . ContainerRemoveOptions { Force : true , RemoveVolumes : true } )
if err != nil {
logrus . Warningf ( "Can't remove Docker container [%s] for host [%s]: %v" , containerName , hostname , err )
continue
}
return nil
2017-11-02 10:07:10 +00:00
}
2019-06-11 09:39:45 +00:00
return err
2017-11-02 10:07:10 +00:00
}
2017-11-15 02:54:26 +00:00
2018-08-20 04:37:04 +00:00
func RestartContainer ( ctx context . Context , dClient * client . Client , hostname , containerName string ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "Failed to restart container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2019-06-11 09:39:45 +00:00
var err error
2018-08-20 04:37:04 +00:00
restartTimeout := RestartTimeout * time . Second
2019-06-11 09:39:45 +00:00
// Retry up to RetryCount times to see if image exists
for i := 1 ; i <= RetryCount ; i ++ {
logrus . Infof ( "Restarting container [%s] on host [%s], try #%d" , containerName , hostname , i )
err = dClient . ContainerRestart ( ctx , containerName , & restartTimeout )
if err != nil {
logrus . Warningf ( "Can't restart Docker container [%s] for host [%s]: %v" , containerName , hostname , err )
continue
}
return nil
2018-08-20 04:37:04 +00:00
}
2019-06-11 09:39:45 +00:00
return err
2018-08-20 04:37:04 +00:00
}
2018-01-09 22:10:56 +00:00
func StopContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "Failed to stop container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2019-06-11 09:39:45 +00:00
var err error
2018-09-20 16:55:29 +00:00
// define the stop timeout
stopTimeoutDuration := StopTimeout * time . Second
2019-06-11 09:39:45 +00:00
// Retry up to RetryCount times to see if image exists
for i := 1 ; i <= RetryCount ; i ++ {
logrus . Infof ( "Stopping container [%s] on host [%s] with stopTimeoutDuration [%s], try #%d" , containerName , hostname , stopTimeoutDuration , i )
2020-02-20 18:21:51 +00:00
err = dClient . ContainerStop ( ctx , containerName , & stopTimeoutDuration )
2019-06-11 09:39:45 +00:00
if err != nil {
logrus . Warningf ( "Can't stop Docker container [%s] for host [%s]: %v" , containerName , hostname , err )
continue
}
return nil
2017-11-15 02:54:26 +00:00
}
2019-06-11 09:39:45 +00:00
return err
2017-11-15 02:54:26 +00:00
}
2018-01-09 22:10:56 +00:00
func RenameContainer ( ctx context . Context , dClient * client . Client , hostname string , oldContainerName string , newContainerName string ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "Failed to rename container: docker client is nil for container [%s] on host [%s]" , oldContainerName , hostname )
}
2019-06-11 09:39:45 +00:00
var err error
// Retry up to RetryCount times to see if image exists
for i := 1 ; i <= RetryCount ; i ++ {
logrus . Infof ( "Renaming container [%s] to [%s] on host [%s], try #%d" , oldContainerName , newContainerName , hostname , i )
err = dClient . ContainerRename ( ctx , oldContainerName , newContainerName )
if err != nil {
logrus . Warningf ( "Can't rename Docker container [%s] to [%s] for host [%s]: %v" , oldContainerName , newContainerName , hostname , err )
continue
}
return nil
2017-11-15 02:54:26 +00:00
}
2019-06-11 09:39:45 +00:00
return err
2017-11-15 02:54:26 +00:00
}
2018-01-09 22:10:56 +00:00
func StartContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "Failed to start container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2019-06-11 09:39:45 +00:00
var err error
// Retry up to RetryCount times to see if image exists
for i := 1 ; i <= RetryCount ; i ++ {
logrus . Infof ( "Starting container [%s] on host [%s], try #%d" , containerName , hostname , i )
err = dClient . ContainerStart ( ctx , containerName , types . ContainerStartOptions { } )
if err != nil {
2019-11-11 11:27:38 +00:00
if strings . Contains ( err . Error ( ) , "bind: address already in use" ) {
return err
}
2019-06-11 09:39:45 +00:00
logrus . Warningf ( "Can't start Docker container [%s] on host [%s]: %v" , containerName , hostname , err )
continue
}
return nil
2017-11-15 02:54:26 +00:00
}
2019-06-11 09:39:45 +00:00
return err
2017-11-15 02:54:26 +00:00
}
2018-04-24 12:44:17 +00:00
func CreateContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string , imageCfg * container . Config , hostCfg * container . HostConfig ) ( container . ContainerCreateCreatedBody , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return container . ContainerCreateCreatedBody { } , fmt . Errorf ( "Failed to create container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2020-02-20 18:21:51 +00:00
var created container . ContainerCreateCreatedBody
2019-06-11 09:39:45 +00:00
var err error
// Retry up to RetryCount times to see if image exists
for i := 1 ; i <= RetryCount ; i ++ {
2021-04-29 21:07:38 +00:00
created , err = dClient . ContainerCreate ( ctx , imageCfg , hostCfg , nil , nil , containerName )
2019-06-11 09:39:45 +00:00
if err != nil {
logrus . Warningf ( "Failed to create Docker container [%s] on host [%s]: %v" , containerName , hostname , err )
continue
}
return created , nil
2017-11-15 02:54:26 +00:00
}
2019-06-11 09:39:45 +00:00
return container . ContainerCreateCreatedBody { } , fmt . Errorf ( "Failed to create Docker container [%s] on host [%s]: %v" , containerName , hostname , err )
2017-11-15 02:54:26 +00:00
}
2018-01-09 22:10:56 +00:00
func InspectContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) ( types . ContainerJSON , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return types . ContainerJSON { } , fmt . Errorf ( "Failed to inspect container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2020-02-20 18:21:51 +00:00
var inspection types . ContainerJSON
2019-06-11 09:39:45 +00:00
var err error
// Retry up to RetryCount times to see if image exists
for i := 1 ; i <= RetryCount ; i ++ {
2020-02-20 18:21:51 +00:00
inspection , err = dClient . ContainerInspect ( ctx , containerName )
2019-06-11 09:39:45 +00:00
if err != nil {
if client . IsErrNotFound ( err ) {
return types . ContainerJSON { } , err
}
logrus . Warningf ( "Failed to inspect Docker container [%s] on host [%s]: %v" , containerName , hostname , err )
continue
}
return inspection , nil
2017-11-15 02:54:26 +00:00
}
2019-06-11 09:39:45 +00:00
return types . ContainerJSON { } , fmt . Errorf ( "Failed to inspect Docker container [%s] on host [%s]: %v" , containerName , hostname , err )
2017-11-15 02:54:26 +00:00
}
2018-01-09 22:10:56 +00:00
func StopRenameContainer ( ctx context . Context , dClient * client . Client , hostname string , oldContainerName string , newContainerName string ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "Failed to stop and rename container: docker client is nil for container [%s] on host [%s]" , oldContainerName , hostname )
}
2018-04-27 18:38:59 +00:00
// make sure we don't have an old old-container from a previous broken update
exists , err := IsContainerRunning ( ctx , dClient , hostname , newContainerName , true )
if err != nil {
return err
}
if exists {
if err := RemoveContainer ( ctx , dClient , hostname , newContainerName ) ; err != nil {
return err
}
}
2018-01-09 22:10:56 +00:00
if err := StopContainer ( ctx , dClient , hostname , oldContainerName ) ; err != nil {
2017-11-15 02:54:26 +00:00
return err
}
2018-05-09 17:39:19 +00:00
if _ , err := WaitForContainer ( ctx , dClient , hostname , oldContainerName ) ; err != nil {
2019-04-21 20:39:08 +00:00
return err
2017-11-15 02:54:26 +00:00
}
2018-04-27 18:38:59 +00:00
return RenameContainer ( ctx , dClient , hostname , oldContainerName , newContainerName )
2017-11-15 02:54:26 +00:00
}
2018-05-09 17:39:19 +00:00
func WaitForContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) ( int64 , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return 1 , fmt . Errorf ( "Failed waiting for container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2021-01-22 17:35:13 +00:00
// Set containerTimeout value from context or default. Especially for transferring snapshots
containerTimeout := WaitTimeout
if v , ok := ctx . Value ( WaitTimeoutContextKey ) . ( int ) ; ok && v > 0 {
containerTimeout = v
}
for retries := 0 ; retries < containerTimeout ; retries ++ {
2019-04-21 20:39:08 +00:00
log . Infof ( ctx , "Waiting for [%s] container to exit on host [%s]" , containerName , hostname )
2019-06-11 09:39:45 +00:00
container , err := InspectContainer ( ctx , dClient , hostname , containerName )
2017-11-15 02:54:26 +00:00
if err != nil {
2019-04-21 20:39:08 +00:00
return 1 , fmt . Errorf ( "Could not inspect container [%s] on host [%s]: %s" , containerName , hostname , err )
}
if container . State . Running {
2020-03-30 19:16:47 +00:00
stderr , stdout , err := GetContainerLogsStdoutStderr ( ctx , dClient , containerName , "1" , false )
if err != nil {
logrus . Warnf ( "Failed to get container logs from container [%s] on host [%s]: %v" , containerName , hostname , err )
}
log . Infof ( ctx , "Container [%s] is still running on host [%s]: stderr: [%s], stdout: [%s]" , containerName , hostname , stderr , stdout )
2019-04-21 20:39:08 +00:00
time . Sleep ( 1 * time . Second )
continue
2017-11-15 02:54:26 +00:00
}
2019-04-21 20:39:08 +00:00
logrus . Debugf ( "Exit code for [%s] container on host [%s] is [%d]" , containerName , hostname , int64 ( container . State . ExitCode ) )
return int64 ( container . State . ExitCode ) , nil
2017-11-15 02:54:26 +00:00
}
2020-03-30 19:16:47 +00:00
stderr , stdout , err := GetContainerLogsStdoutStderr ( ctx , dClient , containerName , "1" , false )
if err != nil {
logrus . Warnf ( "Failed to get container logs from container [%s] on host [%s]" , containerName , hostname )
}
return 1 , fmt . Errorf ( "Container [%s] did not exit in time on host [%s]: stderr: [%s], stdout: [%s]" , containerName , hostname , stderr , stdout )
2017-11-15 02:54:26 +00:00
}
2017-11-28 11:26:15 +00:00
2018-07-18 19:51:46 +00:00
func IsContainerUpgradable ( ctx context . Context , dClient * client . Client , imageCfg * container . Config , hostCfg * container . HostConfig , containerName string , hostname string , plane string ) ( bool , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return false , fmt . Errorf ( "[%s] Failed checking if container is upgradable: docker client is nil for container [%s] on host [%s]" , plane , containerName , hostname )
}
2017-11-30 23:16:45 +00:00
logrus . Debugf ( "[%s] Checking if container [%s] is eligible for upgrade on host [%s]" , plane , containerName , hostname )
2017-11-28 11:26:15 +00:00
// this should be moved to a higher layer.
2018-01-09 22:10:56 +00:00
containerInspect , err := InspectContainer ( ctx , dClient , hostname , containerName )
2017-11-28 11:26:15 +00:00
if err != nil {
return false , err
}
2018-07-25 14:18:11 +00:00
// image inspect to compare the env correctly
imageInspect , _ , err := dClient . ImageInspectWithRaw ( ctx , imageCfg . Image )
if err != nil {
2018-07-31 22:40:10 +00:00
if ! client . IsErrNotFound ( err ) {
return false , err
}
logrus . Debugf ( "[%s] Container [%s] is eligible for upgrade on host [%s]" , plane , containerName , hostname )
return true , nil
2018-07-25 14:18:11 +00:00
}
2018-01-31 23:56:12 +00:00
if containerInspect . Config . Image != imageCfg . Image ||
2018-04-02 11:01:51 +00:00
! sliceEqualsIgnoreOrder ( containerInspect . Config . Entrypoint , imageCfg . Entrypoint ) ||
2018-06-13 02:10:48 +00:00
! sliceEqualsIgnoreOrder ( containerInspect . Config . Cmd , imageCfg . Cmd ) ||
2018-07-25 14:18:11 +00:00
! isContainerEnvChanged ( containerInspect . Config . Env , imageCfg . Env , imageInspect . Config . Env ) ||
2019-08-14 10:53:32 +00:00
! sliceEqualsIgnoreOrder ( containerInspect . HostConfig . Binds , hostCfg . Binds ) ||
2019-09-03 15:40:31 +00:00
! securityOptsliceEqualsIgnoreOrder ( containerInspect . HostConfig . SecurityOpt , hostCfg . SecurityOpt ) {
2018-04-24 12:44:17 +00:00
logrus . Debugf ( "[%s] Container [%s] is eligible for upgrade on host [%s]" , plane , containerName , hostname )
2017-11-30 23:16:45 +00:00
return true , nil
2017-11-28 11:26:15 +00:00
}
2018-04-24 12:44:17 +00:00
logrus . Debugf ( "[%s] Container [%s] is not eligible for upgrade on host [%s]" , plane , containerName , hostname )
2017-11-30 23:16:45 +00:00
return false , nil
2017-11-28 11:26:15 +00:00
}
2017-12-01 21:06:13 +00:00
2018-04-02 11:01:51 +00:00
func sliceEqualsIgnoreOrder ( left , right [ ] string ) bool {
2019-08-14 10:53:32 +00:00
if equal := sets . NewString ( left ... ) . Equal ( sets . NewString ( right ... ) ) ; ! equal {
logrus . Debugf ( "slice is not equal, showing data in new value which is not in old value: %v" , sets . NewString ( right ... ) . Difference ( sets . NewString ( left ... ) ) )
2019-09-03 15:40:31 +00:00
logrus . Debugf ( "slice is not equal, showing data in old value which is not in new value: %v" , sets . NewString ( left ... ) . Difference ( sets . NewString ( right ... ) ) )
return false
}
return true
}
func securityOptsliceEqualsIgnoreOrder ( left , right [ ] string ) bool {
if equal := sets . NewString ( left ... ) . Equal ( sets . NewString ( right ... ) ) ; ! equal {
logrus . Debugf ( "slice is not equal, showing data in new value which is not in old value: %v" , sets . NewString ( right ... ) . Difference ( sets . NewString ( left ... ) ) )
diff := sets . NewString ( left ... ) . Difference ( sets . NewString ( right ... ) )
logrus . Debugf ( "slice is not equal, showing data in old value which is not in new value: %v" , diff )
// Docker sets label=disable automatically on all non labeled containers with will result in a false diff between spec and the actual running container
// If the diff matches the disable label exactly, we still report true as being equal
if equal := sets . NewString ( [ ] string { "label=disable" } ... ) . Equal ( diff ) ; equal {
logrus . Debugf ( "returning equal as true because diff matches the automatically added disable label for SELinux which can be ignored: %v" , diff )
return true
}
2019-08-14 10:53:32 +00:00
return false
}
return true
2018-04-02 11:01:51 +00:00
}
2017-12-01 21:06:13 +00:00
func IsSupportedDockerVersion ( info types . Info , K8sVersion string ) ( bool , error ) {
2018-04-04 20:55:26 +00:00
dockerVersion , err := semver . NewVersion ( info . ServerVersion )
if err != nil {
return false , err
}
2019-07-15 17:58:09 +00:00
for _ , DockerVersion := range metadata . K8sVersionToDockerVersions [ K8sVersion ] {
2018-04-04 20:55:26 +00:00
supportedDockerVersion , err := convertToSemver ( DockerVersion )
if err != nil {
return false , err
}
if dockerVersion . Major == supportedDockerVersion . Major && dockerVersion . Minor == supportedDockerVersion . Minor {
2017-12-01 21:06:13 +00:00
return true , nil
}
}
return false , nil
}
2018-01-10 23:03:08 +00:00
func ReadFileFromContainer ( ctx context . Context , dClient * client . Client , hostname , container , filePath string ) ( string , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return "" , fmt . Errorf ( "Failed reading file from container: docker client is nil for container [%s] on host [%s]" , container , hostname )
}
2018-01-10 23:03:08 +00:00
reader , _ , err := dClient . CopyFromContainer ( ctx , container , filePath )
if err != nil {
return "" , fmt . Errorf ( "Failed to copy file [%s] from container [%s] on host [%s]: %v" , filePath , container , hostname , err )
}
defer reader . Close ( )
tarReader := tar . NewReader ( reader )
if _ , err := tarReader . Next ( ) ; err != nil {
return "" , err
}
file , err := ioutil . ReadAll ( tarReader )
if err != nil {
return "" , err
}
return string ( file ) , nil
}
2018-01-16 18:29:09 +00:00
2018-05-21 18:11:11 +00:00
func ReadContainerLogs ( ctx context . Context , dClient * client . Client , containerName string , follow bool , tail string ) ( io . ReadCloser , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return nil , fmt . Errorf ( "Failed reading container logs: docker client is nil for container [%s]" , containerName )
}
2020-02-20 18:21:51 +00:00
var logs io . ReadCloser
2019-06-11 09:39:45 +00:00
var err error
for i := 1 ; i <= RetryCount ; i ++ {
2020-02-20 18:21:51 +00:00
logs , err = dClient . ContainerLogs ( ctx , containerName , types . ContainerLogsOptions { Follow : follow , ShowStdout : true , ShowStderr : true , Timestamps : false , Tail : tail } )
2019-06-11 09:39:45 +00:00
if err != nil {
logrus . Warnf ( "Can't read container logs for container [%s]: %v" , containerName , err )
continue
}
return logs , nil
}
return nil , err
2018-01-16 18:29:09 +00:00
}
2018-01-30 18:15:14 +00:00
2018-11-24 10:18:24 +00:00
func GetContainerLogsStdoutStderr ( ctx context . Context , dClient * client . Client , containerName , tail string , follow bool ) ( string , string , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return "" , "" , fmt . Errorf ( "Failed to get container logs stdout and stderr: docker client is nil for container [%s]" , containerName )
}
2018-07-18 20:44:55 +00:00
var containerStderr bytes . Buffer
var containerStdout bytes . Buffer
2018-11-24 10:18:24 +00:00
var containerErrLog , containerStdLog string
2018-07-21 07:14:45 +00:00
clogs , logserr := ReadContainerLogs ( ctx , dClient , containerName , follow , tail )
2019-07-23 20:59:41 +00:00
if logserr != nil || clogs == nil {
2018-10-17 18:55:33 +00:00
logrus . Debugf ( "logserr: %v" , logserr )
2018-11-24 10:18:24 +00:00
return containerErrLog , containerStdLog , fmt . Errorf ( "Failed to get gather logs from container [%s]: %v" , containerName , logserr )
2018-07-18 20:44:55 +00:00
}
defer clogs . Close ( )
stdcopy . StdCopy ( & containerStdout , & containerStderr , clogs )
2018-11-24 10:18:24 +00:00
containerErrLog = containerStderr . String ( )
containerStdLog = containerStdout . String ( )
return containerErrLog , containerStdLog , nil
2018-07-18 20:44:55 +00:00
}
2018-01-30 18:15:14 +00:00
func tryRegistryAuth ( pr v3 . PrivateRegistry ) types . RequestPrivilegeFunc {
return func ( ) ( string , error ) {
return getRegistryAuth ( pr )
}
}
func getRegistryAuth ( pr v3 . PrivateRegistry ) ( string , error ) {
2020-10-05 03:15:57 +00:00
var authConfig types . AuthConfig
var err error
2021-08-18 05:36:25 +00:00
if len ( pr . User ) == 0 && len ( pr . Password ) == 0 && pr . ECRCredentialPlugin != nil {
authConfig , err = util . ECRCredentialPlugin ( pr . ECRCredentialPlugin , pr . URL )
if err != nil {
return "" , err
2020-10-05 03:15:57 +00:00
}
} else {
authConfig = types . AuthConfig {
Username : pr . User ,
Password : pr . Password ,
}
2018-01-30 18:15:14 +00:00
}
2020-10-05 03:15:57 +00:00
2018-01-30 18:15:14 +00:00
encodedJSON , err := json . Marshal ( authConfig )
if err != nil {
return "" , err
}
return base64 . URLEncoding . EncodeToString ( encodedJSON ) , nil
}
2018-04-03 20:18:51 +00:00
func GetImageRegistryConfig ( image string , prsMap map [ string ] v3 . PrivateRegistry ) ( string , string , error ) {
2019-11-06 00:50:01 +00:00
/ *
Image can be passed as
- Example1 : repo . com / foo / bar / rancher / rke - tools : v0 .1 .51
or
- Example2 : repo . com / rancher / rke - tools : v0 .1 .51 // image2
or
- rancher / rke - tools
Where the repo can be :
- repo . com
or
- repo . com / foo / bar
When checking for the repo presence in prsMap , the following repo will be found :
- Example1 : repo . com / foo / bar
- Exmaple2 : repo . com
* /
2018-04-03 20:18:51 +00:00
namedImage , err := ref . ParseNormalizedNamed ( image )
if err != nil {
return "" , "" , err
}
2019-11-06 00:50:01 +00:00
if len ( prsMap ) == 0 {
return "" , "" , nil
}
2018-04-03 20:18:51 +00:00
regURL := ref . Domain ( namedImage )
2019-11-06 00:50:01 +00:00
regPath := ref . Path ( namedImage )
splitPath := strings . Split ( regPath , "/" )
if len ( splitPath ) > 2 {
splitPath = splitPath [ : len ( splitPath ) - 2 ]
regPath = strings . Join ( splitPath , "/" )
regURL = fmt . Sprintf ( "%s/%s" , regURL , regPath )
}
2018-04-03 20:18:51 +00:00
if pr , ok := prsMap [ regURL ] ; ok {
2019-11-06 00:50:01 +00:00
logrus . Debugf ( "Found regURL %v" , regURL )
2018-04-03 20:18:51 +00:00
// We do this if we have some docker.io login information
regAuth , err := getRegistryAuth ( pr )
return regAuth , pr . URL , err
}
2019-11-06 00:50:01 +00:00
2018-04-03 20:18:51 +00:00
return "" , "" , nil
}
2018-04-04 20:55:26 +00:00
func convertToSemver ( version string ) ( * semver . Version , error ) {
compVersion := strings . SplitN ( version , "." , 3 )
if len ( compVersion ) != 3 {
return nil , fmt . Errorf ( "The default version is not correct" )
}
compVersion [ 2 ] = "0"
return semver . NewVersion ( strings . Join ( compVersion , "." ) )
}
2018-06-13 02:10:48 +00:00
2018-07-25 14:18:11 +00:00
func isContainerEnvChanged ( containerEnv , imageConfigEnv , dockerfileEnv [ ] string ) bool {
2018-06-13 02:10:48 +00:00
// remove PATH env from the container env
2018-07-25 14:18:11 +00:00
allImageEnv := append ( imageConfigEnv , dockerfileEnv ... )
return sliceEqualsIgnoreOrder ( allImageEnv , containerEnv )
2018-06-13 02:10:48 +00:00
}
2018-08-29 00:23:41 +00:00
func GetKubeletDockerConfig ( prsMap map [ string ] v3 . PrivateRegistry ) ( string , error ) {
auths := map [ string ] authConfig { }
2020-10-05 03:15:57 +00:00
credHelper := make ( map [ string ] string )
2018-08-29 00:23:41 +00:00
for url , pr := range prsMap {
2021-08-18 05:36:25 +00:00
if pr . ECRCredentialPlugin != nil {
credHelper [ pr . URL ] = "ecr-login"
2020-10-05 03:15:57 +00:00
} else {
auth := base64 . StdEncoding . EncodeToString ( [ ] byte ( fmt . Sprintf ( "%s:%s" , pr . User , pr . Password ) ) )
auths [ url ] = authConfig { Auth : auth }
}
2018-08-29 00:23:41 +00:00
}
2020-10-05 03:15:57 +00:00
cfg , err := json . Marshal ( dockerConfig { auths , credHelper } )
2018-08-29 00:23:41 +00:00
if err != nil {
return "" , err
}
return string ( cfg ) , nil
}
2018-08-20 04:37:04 +00:00
func DoRestartContainer ( ctx context . Context , dClient * client . Client , containerName , hostname string ) error {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return fmt . Errorf ( "Failed to restart container: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2018-08-20 04:37:04 +00:00
logrus . Debugf ( "[restart/%s] Checking if container is running on host [%s]" , containerName , hostname )
2019-06-11 09:39:45 +00:00
_ , err := InspectContainer ( ctx , dClient , hostname , containerName )
2018-08-20 04:37:04 +00:00
if err != nil {
if client . IsErrNotFound ( err ) {
logrus . Debugf ( "[restart/%s] Container doesn't exist on host [%s]" , containerName , hostname )
return nil
}
return err
}
logrus . Debugf ( "[restart/%s] Restarting container on host [%s]" , containerName , hostname )
err = RestartContainer ( ctx , dClient , hostname , containerName )
if err != nil {
return err
}
log . Infof ( ctx , "[restart/%s] Successfully restarted container on host [%s]" , containerName , hostname )
return nil
}
2019-01-24 22:56:51 +00:00
func GetContainerOutput ( ctx context . Context , dClient * client . Client , containerName , hostname string ) ( int64 , string , string , error ) {
2019-01-25 01:42:01 +00:00
if dClient == nil {
return 1 , "" , "" , fmt . Errorf ( "Failed to get container output: docker client is nil for container [%s] on host [%s]" , containerName , hostname )
}
2019-01-24 22:56:51 +00:00
status , err := WaitForContainer ( ctx , dClient , hostname , containerName )
if err != nil {
return 1 , "" , "" , err
}
stderr , stdout , err := GetContainerLogsStdoutStderr ( ctx , dClient , containerName , "1" , false )
if err != nil {
return 1 , "" , "" , err
}
return status , stdout , stderr , nil
}