2018-03-19 18:56:51 -04:00
package containerizedengine
import (
"context"
2018-09-14 13:53:56 -04:00
"encoding/json"
2018-03-19 18:56:51 -04:00
"fmt"
2018-09-14 13:53:56 -04:00
"io/ioutil"
"path/filepath"
"strings"
2018-03-19 18:56:51 -04:00
2018-09-13 12:32:19 -04:00
"github.com/containerd/containerd"
2018-09-14 13:53:56 -04:00
"github.com/containerd/containerd/content"
2018-03-19 18:56:51 -04:00
"github.com/containerd/containerd/errdefs"
2018-09-14 13:53:56 -04:00
"github.com/containerd/containerd/images"
2018-03-19 18:56:51 -04:00
"github.com/containerd/containerd/namespaces"
2018-09-11 08:46:30 -04:00
clitypes "github.com/docker/cli/types"
2018-09-14 13:53:56 -04:00
"github.com/docker/distribution/reference"
2018-03-19 18:56:51 -04:00
"github.com/docker/docker/api/types"
2018-09-14 13:53:56 -04:00
ver "github.com/hashicorp/go-version"
"github.com/opencontainers/image-spec/specs-go/v1"
2018-03-19 18:56:51 -04:00
"github.com/pkg/errors"
)
// ActivateEngine will switch the image from the CE to EE image
2018-09-11 08:46:30 -04:00
func ( c * baseClient ) ActivateEngine ( ctx context . Context , opts clitypes . EngineInitOptions , out clitypes . OutStream ,
2018-03-19 18:56:51 -04:00
authConfig * types . AuthConfig , healthfn func ( context . Context ) error ) error {
ctx = namespaces . WithNamespace ( ctx , engineNamespace )
return c . DoUpdate ( ctx , opts , out , authConfig , healthfn )
}
// DoUpdate performs the underlying engine update
2018-09-11 08:46:30 -04:00
func ( c * baseClient ) DoUpdate ( ctx context . Context , opts clitypes . EngineInitOptions , out clitypes . OutStream ,
2018-03-19 18:56:51 -04:00
authConfig * types . AuthConfig , healthfn func ( context . Context ) error ) error {
ctx = namespaces . WithNamespace ( ctx , engineNamespace )
if opts . EngineVersion == "" {
// TODO - Future enhancement: This could be improved to be
// smart about figuring out the latest patch rev for the
// current engine version and automatically apply it so users
// could stay in sync by simply having a scheduled
// `docker engine update`
return fmt . Errorf ( "please pick the version you want to update to" )
}
2018-09-14 13:53:56 -04:00
localMetadata , err := c . GetCurrentRuntimeMetadata ( ctx , "" )
if err == nil {
if opts . EngineImage == "" {
if strings . Contains ( strings . ToLower ( localMetadata . Platform ) , "enterprise" ) {
opts . EngineImage = "engine-enterprise"
} else {
opts . EngineImage = "engine-community"
}
}
}
if opts . EngineImage == "" {
return fmt . Errorf ( "please pick the engine image to update with (engine-community or engine-enterprise)" )
}
2018-03-19 18:56:51 -04:00
imageName := fmt . Sprintf ( "%s/%s:%s" , opts . RegistryPrefix , opts . EngineImage , opts . EngineVersion )
// Look for desired image
image , err := c . cclient . GetImage ( ctx , imageName )
if err != nil {
if errdefs . IsNotFound ( err ) {
image , err = c . pullWithAuth ( ctx , imageName , out , authConfig )
if err != nil {
return errors . Wrapf ( err , "unable to pull image %s" , imageName )
}
} else {
return errors . Wrapf ( err , "unable to check for image %s" , imageName )
}
}
2018-09-14 13:53:56 -04:00
// Make sure we're safe to proceed
newMetadata , err := c . PreflightCheck ( ctx , image )
if err != nil {
return err
}
// Grab current metadata for comparison purposes
if localMetadata != nil {
if localMetadata . Platform != newMetadata . Platform {
fmt . Fprintf ( out , "\nNotice: you have switched to \"%s\". Please refer to %s for update instructions.\n\n" , newMetadata . Platform , c . GetReleaseNotesURL ( imageName ) )
}
}
err = c . cclient . Install ( ctx , image , containerd . WithInstallReplace , containerd . WithInstallPath ( "/usr" ) )
if err != nil {
return err
}
return c . WriteRuntimeMetadata ( ctx , "" , newMetadata )
}
var defaultDockerRoot = "/var/lib/docker"
// GetCurrentRuntimeMetadata loads the current daemon runtime metadata information from the local host
func ( c * baseClient ) GetCurrentRuntimeMetadata ( _ context . Context , dockerRoot string ) ( * RuntimeMetadata , error ) {
if dockerRoot == "" {
dockerRoot = defaultDockerRoot
}
filename := filepath . Join ( dockerRoot , RuntimeMetadataName + ".json" )
data , err := ioutil . ReadFile ( filename )
if err != nil {
return nil , err
}
var res RuntimeMetadata
err = json . Unmarshal ( data , & res )
if err != nil {
return nil , errors . Wrapf ( err , "malformed runtime metadata file %s" , filename )
}
return & res , nil
}
func ( c * baseClient ) WriteRuntimeMetadata ( _ context . Context , dockerRoot string , metadata * RuntimeMetadata ) error {
if dockerRoot == "" {
dockerRoot = defaultDockerRoot
}
filename := filepath . Join ( dockerRoot , RuntimeMetadataName + ".json" )
data , err := json . Marshal ( metadata )
if err != nil {
return err
}
return ioutil . WriteFile ( filename , data , 0644 )
}
// PreflightCheck verifies the specified image is compatible with the local system before proceeding to update/activate
// If things look good, the RuntimeMetadata for the new image is returned and can be written out to the host
func ( c * baseClient ) PreflightCheck ( ctx context . Context , image containerd . Image ) ( * RuntimeMetadata , error ) {
var metadata RuntimeMetadata
ic , err := image . Config ( ctx )
if err != nil {
return nil , err
}
var (
ociimage v1 . Image
config v1 . ImageConfig
)
switch ic . MediaType {
case v1 . MediaTypeImageConfig , images . MediaTypeDockerSchema2Config :
p , err := content . ReadBlob ( ctx , image . ContentStore ( ) , ic )
if err != nil {
return nil , err
}
if err := json . Unmarshal ( p , & ociimage ) ; err != nil {
return nil , err
}
config = ociimage . Config
default :
return nil , fmt . Errorf ( "unknown image %s config media type %s" , image . Name ( ) , ic . MediaType )
}
metadataString , ok := config . Labels [ RuntimeMetadataName ]
if ! ok {
return nil , fmt . Errorf ( "image %s does not contain runtime metadata label %s" , image . Name ( ) , RuntimeMetadataName )
}
err = json . Unmarshal ( [ ] byte ( metadataString ) , & metadata )
if err != nil {
return nil , errors . Wrapf ( err , "malformed runtime metadata file in %s" , image . Name ( ) )
}
// Current CLI only supports host install runtime
if metadata . Runtime != "host_install" {
return nil , fmt . Errorf ( "unsupported runtime: %s\nPlease consult the release notes at %s for upgrade instructions" , metadata . Runtime , c . GetReleaseNotesURL ( image . Name ( ) ) )
}
// Verify local containerd is new enough
localVersion , err := c . cclient . Version ( ctx )
if err != nil {
return nil , err
}
if metadata . ContainerdMinVersion != "" {
lv , err := ver . NewVersion ( localVersion . Version )
if err != nil {
return nil , err
}
mv , err := ver . NewVersion ( metadata . ContainerdMinVersion )
if err != nil {
return nil , err
}
if lv . LessThan ( mv ) {
return nil , fmt . Errorf ( "local containerd is too old: %s - this engine version requires %s or newer.\nPlease consult the release notes at %s for upgrade instructions" ,
localVersion . Version , metadata . ContainerdMinVersion , c . GetReleaseNotesURL ( image . Name ( ) ) )
}
} // If omitted on metadata, no hard dependency on containerd version beyond 18.09 baseline
// All checks look OK, proceed with update
return & metadata , nil
}
// GetReleaseNotesURL returns a release notes url
// If the image name does not contain a version tag, the base release notes URL is returned
func ( c * baseClient ) GetReleaseNotesURL ( imageName string ) string {
versionTag := ""
distributionRef , err := reference . ParseNormalizedNamed ( imageName )
if err == nil {
taggedRef , ok := distributionRef . ( reference . NamedTagged )
if ok {
versionTag = taggedRef . Tag ( )
}
}
return fmt . Sprintf ( "%s/%s" , ReleaseNotePrefix , versionTag )
2018-03-19 18:56:51 -04:00
}