2017-10-31 13:55:35 +00:00
package docker
import (
2018-01-10 23:03:08 +00:00
"archive/tar"
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"
2017-11-30 23:16:45 +00:00
"reflect"
2017-12-01 21:06:13 +00:00
"regexp"
2017-10-31 13:55:35 +00:00
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-01-09 22:10:56 +00:00
"github.com/rancher/rke/log"
2018-01-30 18:15:14 +00:00
"github.com/rancher/types/apis/management.cattle.io/v3"
2017-11-13 21:28:38 +00:00
"github.com/sirupsen/logrus"
2017-10-31 13:55:35 +00:00
)
2018-01-30 18:15:14 +00:00
const (
DockerRegistryURL = "docker.io"
)
2017-12-01 21:06:13 +00:00
var K8sDockerVersions = map [ string ] [ ] string {
"1.8" : { "1.12.6" , "1.13.1" , "17.03.2" } ,
}
2018-01-30 18:15:14 +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 {
2018-01-15 21:01:58 +00:00
container , err := dClient . ContainerInspect ( ctx , 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 {
2018-01-15 21:01:58 +00:00
return err
}
resp , err := dClient . ContainerCreate ( ctx , imageCfg , hostCfg , nil , containerName )
if err != nil {
return fmt . Errorf ( "Failed to create [%s] container on host [%s]: %v" , containerName , hostname , err )
}
if err := dClient . ContainerStart ( ctx , resp . ID , types . ContainerStartOptions { } ) ; err != nil {
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-01-18 11:07:39 +00:00
logrus . Debugf ( "[%s] Container [%s] is already running on host [%s]" , plane , containerName , hostname )
2018-01-09 22:10:56 +00:00
isUpgradable , err := IsContainerUpgradable ( ctx , dClient , imageCfg , 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
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[%s] Starting stopped container [%s] on host [%s]" , plane , containerName , hostname )
2018-01-15 21:01:58 +00:00
if err := dClient . ContainerStart ( ctx , container . ID , types . ContainerStartOptions { } ) ; 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
}
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 {
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-01-09 22:10:56 +00:00
_ , err = CreateContiner ( 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 {
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[remove/%s] Checking if container is running on host [%s]" , containerName , hostname )
2017-11-20 18:08:50 +00:00
// not using the wrapper to check if the error is a NotFound error
2018-01-09 22:10:56 +00:00
_ , err := dClient . ContainerInspect ( ctx , 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] Stopping container on host [%s]" , containerName , hostname )
2018-01-09 22:10:56 +00:00
err = StopContainer ( ctx , dClient , hostname , containerName )
2017-11-20 18:08:50 +00:00
if err != 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 ) {
2017-11-30 23:16:45 +00:00
logrus . Debugf ( "Checking if container [%s] is running on host [%s]" , containerName , hostname )
2018-01-09 22:10:56 +00:00
containers , err := dClient . ContainerList ( ctx , types . ContainerListOptions { All : all } )
2017-10-31 13:55:35 +00:00
if err != nil {
2017-11-02 10:07:10 +00:00
return false , fmt . Errorf ( "Can't get Docker containers for host [%s]: %v" , hostname , err )
2017-10-31 13:55:35 +00:00
}
for _ , container := range containers {
if container . Names [ 0 ] == "/" + containerName {
return true , nil
}
}
return false , nil
}
2018-01-09 22:10:56 +00:00
func localImageExists ( ctx context . Context , dClient * client . Client , hostname string , containerImage string ) ( bool , error ) {
2017-12-13 00:29:24 +00:00
logrus . Debugf ( "Checking if image [%s] exists on host [%s]" , containerImage , hostname )
2018-01-09 22:10:56 +00:00
_ , _ , err := dClient . ImageInspectWithRaw ( ctx , containerImage )
2017-12-13 00:29:24 +00:00
if err != nil {
if client . IsErrNotFound ( err ) {
logrus . Debugf ( "Image [%s] does not exist on host [%s]: %v" , containerImage , hostname , err )
return false , nil
}
return false , fmt . Errorf ( "Error checking if image [%s] exists on host [%s]: %v" , containerImage , hostname , err )
}
logrus . Debugf ( "Image [%s] exists on host [%s]" , containerImage , hostname )
return true , nil
}
2018-01-30 18:15:14 +00:00
func pullImage ( ctx context . Context , dClient * client . Client , hostname string , containerImage string , prsMap map [ string ] v3 . PrivateRegistry ) error {
pullOptions := types . ImagePullOptions { }
containerNamed , err := ref . ParseNormalizedNamed ( containerImage )
if err != nil {
return err
}
regURL := ref . Domain ( containerNamed )
if pr , ok := prsMap [ regURL ] ; ok {
// We do this if we have some docker.io login information
2018-02-08 23:02:58 +00:00
regAuth , err := getRegistryAuth ( pr )
if err != nil {
return err
}
2018-01-30 18:15:14 +00:00
if pr . URL == DockerRegistryURL {
pullOptions . RegistryAuth = regAuth
} else {
// We have a registry, but it's not docker.io
// this could be public or private, ImagePull() can handle it
// if we provide a PrivilegeFunc
2018-02-08 23:02:58 +00:00
2018-01-30 18:15:14 +00:00
pullOptions . PrivilegeFunc = tryRegistryAuth ( pr )
2018-02-08 23:02:58 +00:00
pullOptions . RegistryAuth = regAuth
2018-01-30 18:15:14 +00:00
}
}
out , err := dClient . ImagePull ( ctx , containerImage , pullOptions )
2017-10-31 13:55:35 +00:00
if err != nil {
2017-11-30 23:16:45 +00:00
return fmt . Errorf ( "Can't pull Docker image [%s] for host [%s]: %v" , containerImage , hostname , err )
2017-10-31 13:55:35 +00:00
}
defer out . Close ( )
if logrus . GetLevel ( ) == logrus . DebugLevel {
io . Copy ( os . Stdout , out )
} else {
io . Copy ( ioutil . Discard , out )
}
return nil
}
2017-11-02 10:07:10 +00:00
2018-01-30 18:15:14 +00:00
func UseLocalOrPull ( ctx context . Context , dClient * client . Client , hostname string , containerImage string , plane string , prsMap map [ string ] v3 . PrivateRegistry ) error {
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[%s] Checking image [%s] on host [%s]" , plane , containerImage , hostname )
2018-01-09 22:10:56 +00:00
imageExists , err := localImageExists ( ctx , dClient , hostname , containerImage )
2017-12-13 00:29:24 +00:00
if err != nil {
return err
}
if imageExists {
2018-01-18 11:07:39 +00:00
logrus . Debugf ( "[%s] No pull necessary, image [%s] exists on host [%s]" , plane , containerImage , hostname )
2017-12-13 00:29:24 +00:00
return nil
}
2018-03-26 20:37:12 +00:00
logrus . Infof ( "[%s] Pulling image [%s] on host [%s]" , plane , containerImage , hostname )
2018-01-30 18:15:14 +00:00
if err := pullImage ( ctx , dClient , hostname , containerImage , prsMap ) ; err != nil {
2017-12-13 00:29:24 +00:00
return err
}
2018-01-09 22:10:56 +00:00
log . Infof ( ctx , "[%s] Successfully pulled image [%s] on host [%s]" , plane , containerImage , hostname )
2017-12-13 00:29:24 +00:00
return nil
}
2018-01-09 22:10:56 +00:00
func RemoveContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) error {
err := dClient . ContainerRemove ( ctx , containerName , types . ContainerRemoveOptions { } )
2017-11-02 10:07:10 +00:00
if err != nil {
2017-11-30 23:16:45 +00:00
return fmt . Errorf ( "Can't remove Docker container [%s] for host [%s]: %v" , containerName , hostname , err )
2017-11-02 10:07:10 +00:00
}
return nil
}
2017-11-15 02:54:26 +00:00
2018-01-09 22:10:56 +00:00
func StopContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) error {
err := dClient . ContainerStop ( ctx , containerName , nil )
2017-11-15 02:54:26 +00:00
if err != nil {
2017-11-30 23:16:45 +00:00
return fmt . Errorf ( "Can't stop Docker container [%s] for host [%s]: %v" , containerName , hostname , err )
2017-11-15 02:54:26 +00:00
}
return nil
}
2018-01-09 22:10:56 +00:00
func RenameContainer ( ctx context . Context , dClient * client . Client , hostname string , oldContainerName string , newContainerName string ) error {
err := dClient . ContainerRename ( ctx , oldContainerName , newContainerName )
2017-11-15 02:54:26 +00:00
if err != nil {
2017-11-30 23:16:45 +00:00
return fmt . Errorf ( "Can't rename Docker container [%s] for host [%s]: %v" , oldContainerName , hostname , err )
2017-11-15 02:54:26 +00:00
}
return nil
}
2018-01-09 22:10:56 +00:00
func StartContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) error {
if err := dClient . ContainerStart ( ctx , containerName , types . ContainerStartOptions { } ) ; 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-15 02:54:26 +00:00
}
return nil
}
2018-01-09 22:10:56 +00:00
func CreateContiner ( ctx context . Context , dClient * client . Client , hostname string , containerName string , imageCfg * container . Config , hostCfg * container . HostConfig ) ( container . ContainerCreateCreatedBody , error ) {
created , err := dClient . ContainerCreate ( ctx , imageCfg , hostCfg , nil , containerName )
2017-11-15 02:54:26 +00:00
if err != nil {
2017-11-30 23:16:45 +00:00
return container . ContainerCreateCreatedBody { } , fmt . Errorf ( "Failed to create [%s] container on host [%s]: %v" , containerName , hostname , err )
2017-11-15 02:54:26 +00:00
}
return created , nil
}
2018-01-09 22:10:56 +00:00
func InspectContainer ( ctx context . Context , dClient * client . Client , hostname string , containerName string ) ( types . ContainerJSON , error ) {
inspection , err := dClient . ContainerInspect ( ctx , containerName )
2017-11-15 02:54:26 +00:00
if err != nil {
2017-11-30 23:16:45 +00:00
return types . ContainerJSON { } , fmt . Errorf ( "Failed to inspect [%s] container on host [%s]: %v" , containerName , hostname , err )
2017-11-15 02:54:26 +00:00
}
return inspection , nil
}
2018-01-09 22:10:56 +00:00
func StopRenameContainer ( ctx context . Context , dClient * client . Client , hostname string , oldContainerName string , newContainerName string ) error {
if err := StopContainer ( ctx , dClient , hostname , oldContainerName ) ; err != nil {
2017-11-15 02:54:26 +00:00
return err
}
2018-01-09 22:10:56 +00:00
if err := WaitForContainer ( ctx , dClient , oldContainerName ) ; err != nil {
2017-11-15 02:54:26 +00:00
return nil
}
2018-01-09 22:10:56 +00:00
err := RenameContainer ( ctx , dClient , hostname , oldContainerName , newContainerName )
2017-11-15 02:54:26 +00:00
return err
}
2018-01-09 22:10:56 +00:00
func WaitForContainer ( ctx context . Context , dClient * client . Client , containerName string ) error {
statusCh , errCh := dClient . ContainerWait ( ctx , containerName , container . WaitConditionNotRunning )
2017-11-15 02:54:26 +00:00
select {
case err := <- errCh :
if err != nil {
return fmt . Errorf ( "Error wating for container [%s]: %v" , containerName , err )
}
case <- statusCh :
}
return nil
}
2017-11-28 11:26:15 +00:00
2018-01-09 22:10:56 +00:00
func IsContainerUpgradable ( ctx context . Context , dClient * client . Client , imageCfg * container . Config , containerName string , hostname string , plane string ) ( bool , error ) {
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-01-31 23:56:12 +00:00
if containerInspect . Config . Image != imageCfg . Image ||
2018-03-29 23:43:02 +00:00
! reflect . DeepEqual ( containerInspect . Config . Entrypoint , imageCfg . Entrypoint ) ||
2018-02-06 23:07:09 +00:00
! reflect . DeepEqual ( containerInspect . Config . Cmd , imageCfg . Cmd ) {
2017-11-30 23:16:45 +00:00
logrus . Debugf ( "[%s] Container [%s] is eligible for updgrade on host [%s]" , plane , containerName , hostname )
return true , nil
2017-11-28 11:26:15 +00:00
}
2017-11-30 23:16:45 +00:00
logrus . Debugf ( "[%s] Container [%s] is not eligible for updgrade on host [%s]" , plane , containerName , hostname )
return false , nil
2017-11-28 11:26:15 +00:00
}
2017-12-01 21:06:13 +00:00
func IsSupportedDockerVersion ( info types . Info , K8sVersion string ) ( bool , error ) {
// Docker versions are not semver compliant since stable/edge version (17.03 and higher) so we need to check if the reported ServerVersion starts with a compatible version
for _ , DockerVersion := range K8sDockerVersions [ K8sVersion ] {
DockerVersionRegexp := regexp . MustCompile ( "^" + DockerVersion )
if DockerVersionRegexp . MatchString ( info . ServerVersion ) {
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 ) {
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
func ReadContainerLogs ( ctx context . Context , dClient * client . Client , containerName string ) ( io . ReadCloser , error ) {
return dClient . ContainerLogs ( ctx , containerName , types . ContainerLogsOptions { ShowStdout : true } )
}
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 ) {
authConfig := types . AuthConfig {
Username : pr . User ,
Password : pr . Password ,
}
encodedJSON , err := json . Marshal ( authConfig )
if err != nil {
return "" , err
}
return base64 . URLEncoding . EncodeToString ( encodedJSON ) , nil
}