2017-10-31 13:55:35 +00:00
package docker
import (
"context"
"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
"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"
2017-11-13 21:28:38 +00:00
"github.com/sirupsen/logrus"
2017-10-31 13:55:35 +00:00
)
2017-12-01 21:06:13 +00:00
var K8sDockerVersions = map [ string ] [ ] string {
"1.8" : { "1.12.6" , "1.13.1" , "17.03.2" } ,
}
2017-11-02 10:07:10 +00:00
func DoRunContainer ( dClient * client . Client , imageCfg * container . Config , hostCfg * container . HostConfig , containerName string , hostname string , plane string ) error {
2017-12-08 23:05:55 +00:00
isRunning , err := IsContainerRunning ( dClient , hostname , containerName , false )
2017-10-31 13:55:35 +00:00
if err != nil {
return err
}
if isRunning {
2017-11-30 23:16:45 +00:00
logrus . Infof ( "[%s] Container [%s] is already running on host [%s]" , plane , containerName , hostname )
2017-11-28 11:26:15 +00:00
isUpgradable , err := IsContainerUpgradable ( dClient , imageCfg , containerName , hostname , plane )
if err != nil {
return err
}
if isUpgradable {
return DoRollingUpdateContainer ( dClient , imageCfg , hostCfg , containerName , hostname , plane )
}
2017-10-31 13:55:35 +00:00
return nil
}
2017-11-28 11:26:15 +00:00
logrus . Infof ( "[%s] Pulling Image on host [%s]" , plane , hostname )
2017-11-02 10:07:10 +00:00
err = PullImage ( dClient , hostname , imageCfg . Image )
2017-10-31 13:55:35 +00:00
if err != nil {
return err
}
2017-11-30 23:16:45 +00:00
logrus . Infof ( "[%s] Successfully pulled [%s] image on host [%s]" , plane , containerName , hostname )
2017-11-02 10:07:10 +00:00
resp , err := dClient . ContainerCreate ( context . Background ( ) , imageCfg , hostCfg , nil , containerName )
2017-10-31 13:55:35 +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-10-31 13:55:35 +00:00
}
2017-11-02 10:07:10 +00:00
if err := dClient . ContainerStart ( context . Background ( ) , resp . 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
}
2017-11-30 23:16:45 +00:00
logrus . Debugf ( "[%s] Successfully started [%s] container: [%s]" , plane , containerName , resp . ID )
logrus . Infof ( "[%s] Successfully started [%s] container on host [%s]" , plane , containerName , hostname )
2017-10-31 13:55:35 +00:00
return nil
}
2017-11-17 00:45:51 +00:00
func DoRollingUpdateContainer ( dClient * client . Client , imageCfg * container . Config , hostCfg * container . HostConfig , containerName , hostname , plane string ) error {
2017-11-30 23:16:45 +00:00
logrus . Debugf ( "[%s] Checking for deployed [%s]" , plane , containerName )
2017-12-08 23:05:55 +00:00
isRunning , err := IsContainerRunning ( dClient , hostname , containerName , false )
2017-11-17 00:45:51 +00:00
if err != nil {
return err
}
if ! isRunning {
logrus . Infof ( "[%s] Container %s is not running on host [%s]" , plane , containerName , hostname )
return nil
}
2017-11-28 11:26:15 +00:00
logrus . Infof ( "[%s] Pulling Image on host [%s]" , plane , hostname )
err = PullImage ( dClient , hostname , imageCfg . Image )
if err != nil {
return err
}
2017-11-30 23:16:45 +00:00
logrus . Infof ( "[%s] Successfully pulled [%s] image on host [%s]" , plane , containerName , hostname )
2017-11-17 00:45:51 +00:00
logrus . Debugf ( "[%s] Stopping old container" , plane )
oldContainerName := "old-" + containerName
if err := StopRenameContainer ( dClient , hostname , containerName , oldContainerName ) ; err != nil {
return err
}
logrus . Infof ( "[%s] Successfully stopped old container %s on host [%s]" , plane , containerName , hostname )
_ , err = CreateContiner ( dClient , hostname , containerName , imageCfg , hostCfg )
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
}
if err := StartContainer ( 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
}
2017-11-30 23:16:45 +00:00
logrus . Infof ( "[%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 )
err = RemoveContainer ( dClient , hostname , oldContainerName )
return err
}
2017-11-20 18:08:50 +00:00
func DoRemoveContainer ( dClient * client . Client , containerName , hostname string ) error {
2017-11-28 11:26:15 +00:00
logrus . Infof ( "[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
_ , err := dClient . ContainerInspect ( context . Background ( ) , containerName )
if err != nil {
if client . IsErrNotFound ( err ) {
2017-11-28 11:26:15 +00:00
logrus . Infof ( "[remove/%s] Container doesn't exist on host [%s]" , containerName , hostname )
2017-11-20 18:08:50 +00:00
return nil
}
return err
}
2017-11-28 11:26:15 +00:00
logrus . Infof ( "[remove/%s] Stopping container on host [%s]" , containerName , hostname )
2017-11-20 18:08:50 +00:00
err = StopContainer ( dClient , hostname , containerName )
if err != nil {
return err
}
2017-11-28 11:26:15 +00:00
logrus . Infof ( "[remove/%s] Removing container on host [%s]" , containerName , hostname )
2017-11-20 18:08:50 +00:00
err = RemoveContainer ( dClient , hostname , containerName )
if err != nil {
return err
}
2017-12-09 11:53:38 +00:00
logrus . Infof ( "[remove/%s] Successfully removed container on host [%s]" , containerName , hostname )
2017-11-20 18:08:50 +00:00
return nil
}
2017-12-08 23:05:55 +00:00
func IsContainerRunning ( 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 )
2017-12-08 23:05:55 +00:00
containers , err := dClient . ContainerList ( context . Background ( ) , 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
}
2017-11-02 10:07:10 +00:00
func PullImage ( dClient * client . Client , hostname string , containerImage string ) error {
out , err := dClient . ImagePull ( context . Background ( ) , containerImage , types . ImagePullOptions { } )
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
func RemoveContainer ( dClient * client . Client , hostname string , containerName string ) error {
err := dClient . ContainerRemove ( context . Background ( ) , containerName , types . ContainerRemoveOptions { } )
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
func StopContainer ( dClient * client . Client , hostname string , containerName string ) error {
err := dClient . ContainerStop ( context . Background ( ) , containerName , nil )
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
}
func RenameContainer ( dClient * client . Client , hostname string , oldContainerName string , newContainerName string ) error {
err := dClient . ContainerRename ( context . Background ( ) , oldContainerName , newContainerName )
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
}
func StartContainer ( dClient * client . Client , hostname string , containerName string ) error {
if err := dClient . ContainerStart ( context . Background ( ) , 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
}
func CreateContiner ( dClient * client . Client , hostname string , containerName string , imageCfg * container . Config , hostCfg * container . HostConfig ) ( container . ContainerCreateCreatedBody , error ) {
created , err := dClient . ContainerCreate ( context . Background ( ) , imageCfg , hostCfg , nil , containerName )
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
}
func InspectContainer ( dClient * client . Client , hostname string , containerName string ) ( types . ContainerJSON , error ) {
inspection , err := dClient . ContainerInspect ( context . Background ( ) , containerName )
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
}
func StopRenameContainer ( dClient * client . Client , hostname string , oldContainerName string , newContainerName string ) error {
if err := StopContainer ( dClient , hostname , oldContainerName ) ; err != nil {
return err
}
if err := WaitForContainer ( dClient , oldContainerName ) ; err != nil {
return nil
}
err := RenameContainer ( dClient , hostname , oldContainerName , newContainerName )
return err
}
func WaitForContainer ( dClient * client . Client , containerName string ) error {
statusCh , errCh := dClient . ContainerWait ( context . Background ( ) , containerName , container . WaitConditionNotRunning )
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
func IsContainerUpgradable ( 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.
containerInspect , err := InspectContainer ( dClient , hostname , containerName )
if err != nil {
return false , err
}
2017-11-30 23:16:45 +00:00
if containerInspect . Config . Image != imageCfg . Image || ! reflect . DeepEqual ( containerInspect . Config . Cmd , imageCfg . Cmd ) {
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
}