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"
2018-09-20 21:00:46 -04:00
"os"
2018-09-14 13:53:56 -04:00
"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`
2018-09-20 14:02:11 -04:00
return fmt . Errorf ( "pick the version you want to update to with --version" )
2018-03-19 18:56:51 -04:00
}
2018-09-14 13:53:56 -04:00
localMetadata , err := c . GetCurrentRuntimeMetadata ( ctx , "" )
if err == nil {
if opts . EngineImage == "" {
2018-09-20 14:02:11 -04:00
if strings . Contains ( strings . ToLower ( localMetadata . Platform ) , "community" ) {
opts . EngineImage = clitypes . CommunityEngineImage
2018-09-14 13:53:56 -04:00
} else {
2018-09-20 14:02:11 -04:00
opts . EngineImage = clitypes . EnterpriseEngineImage
2018-09-14 13:53:56 -04:00
}
}
}
if opts . EngineImage == "" {
2018-09-20 14:02:11 -04:00
return fmt . Errorf ( "unable to determine the installed engine version. Specify which engine image to update with --engine-image set to 'engine-community' or 'engine-enterprise'" )
2018-09-14 13:53:56 -04:00
}
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 {
2018-09-20 14:02:11 -04:00
fmt . Fprintf ( out , "\nNotice: you have switched to \"%s\". Refer to %s for update instructions.\n\n" , newMetadata . Platform , getReleaseNotesURL ( imageName ) )
2018-09-14 13:53:56 -04:00
}
}
2018-09-20 14:02:11 -04:00
if err := c . cclient . Install ( ctx , image , containerd . WithInstallReplace , containerd . WithInstallPath ( "/usr" ) ) ; err != nil {
2018-09-14 13:53:56 -04:00
return err
}
2018-09-20 14:02:11 -04:00
return c . WriteRuntimeMetadata ( "" , newMetadata )
2018-09-14 13:53:56 -04:00
}
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
}
2018-09-20 14:02:11 -04:00
filename := filepath . Join ( dockerRoot , runtimeMetadataName + ".json" )
2018-09-14 13:53:56 -04:00
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
}
2018-09-20 14:02:11 -04:00
// WriteRuntimeMetadata stores the metadata on the local system
func ( c * baseClient ) WriteRuntimeMetadata ( dockerRoot string , metadata * RuntimeMetadata ) error {
2018-09-14 13:53:56 -04:00
if dockerRoot == "" {
dockerRoot = defaultDockerRoot
}
2018-09-20 14:02:11 -04:00
filename := filepath . Join ( dockerRoot , runtimeMetadataName + ".json" )
2018-09-14 13:53:56 -04:00
data , err := json . Marshal ( metadata )
if err != nil {
return err
}
2018-09-20 21:00:46 -04:00
os . Remove ( filename )
2018-09-14 13:53:56 -04:00
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 )
}
2018-09-20 14:02:11 -04:00
metadataString , ok := config . Labels [ "com.docker." + runtimeMetadataName ]
2018-09-14 13:53:56 -04:00
if ! ok {
2018-09-20 14:02:11 -04:00
return nil , fmt . Errorf ( "image %s does not contain runtime metadata label %s" , image . Name ( ) , runtimeMetadataName )
2018-09-14 13:53:56 -04:00
}
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" {
2018-09-20 14:02:11 -04:00
return nil , fmt . Errorf ( "unsupported daemon image: %s\nConsult the release notes at %s for upgrade instructions" , metadata . Runtime , getReleaseNotesURL ( image . Name ( ) ) )
2018-09-14 13:53:56 -04:00
}
// 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 ) {
2018-09-20 14:02:11 -04:00
return nil , fmt . Errorf ( "local containerd is too old: %s - this engine version requires %s or newer.\nConsult the release notes at %s for upgrade instructions" ,
localVersion . Version , metadata . ContainerdMinVersion , getReleaseNotesURL ( image . Name ( ) ) )
2018-09-14 13:53:56 -04:00
}
} // If omitted on metadata, no hard dependency on containerd version beyond 18.09 baseline
// All checks look OK, proceed with update
return & metadata , nil
}
2018-09-20 14:02:11 -04:00
// getReleaseNotesURL returns a release notes url
2018-09-14 13:53:56 -04:00
// If the image name does not contain a version tag, the base release notes URL is returned
2018-09-20 14:02:11 -04:00
func getReleaseNotesURL ( imageName string ) string {
2018-09-14 13:53:56 -04:00
versionTag := ""
distributionRef , err := reference . ParseNormalizedNamed ( imageName )
if err == nil {
taggedRef , ok := distributionRef . ( reference . NamedTagged )
if ok {
versionTag = taggedRef . Tag ( )
}
}
2018-09-20 14:02:11 -04:00
return fmt . Sprintf ( "%s/%s" , clitypes . ReleaseNotePrefix , versionTag )
2018-03-19 18:56:51 -04:00
}