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
"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-28 17:06:28 -04:00
"github.com/docker/cli/internal/versions"
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"
2019-04-02 11:20:21 -04:00
v1 "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-10-01 18:12:09 -04:00
authConfig * types . AuthConfig ) error {
2018-03-19 18:56:51 -04:00
2018-09-28 17:06:28 -04:00
// If the user didn't specify an image, determine the correct enterprise image to use
if opts . EngineImage == "" {
localMetadata , err := versions . GetCurrentRuntimeMetadata ( opts . RuntimeMetadataDir )
if err != nil {
return errors . Wrap ( err , "unable to determine the installed engine version. Specify which engine image to update with --engine-image" )
}
engineImage := localMetadata . EngineImage
if engineImage == clitypes . EnterpriseEngineImage || engineImage == clitypes . CommunityEngineImage {
opts . EngineImage = clitypes . EnterpriseEngineImage
} else {
// Chop off the standard prefix and retain any trailing OS specific image details
// e.g., engine-community-dm -> engine-enterprise-dm
engineImage = strings . TrimPrefix ( engineImage , clitypes . EnterpriseEngineImage )
engineImage = strings . TrimPrefix ( engineImage , clitypes . CommunityEngineImage )
opts . EngineImage = clitypes . EnterpriseEngineImage + engineImage
}
}
2018-03-19 18:56:51 -04:00
ctx = namespaces . WithNamespace ( ctx , engineNamespace )
2018-10-01 18:12:09 -04:00
return c . DoUpdate ( ctx , opts , out , authConfig )
2018-03-19 18:56:51 -04:00
}
// 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-10-01 18:12:09 -04:00
authConfig * types . AuthConfig ) error {
2018-03-19 18:56:51 -04:00
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-28 17:06:28 -04:00
var localMetadata * clitypes . RuntimeMetadata
2018-09-14 13:53:56 -04:00
if opts . EngineImage == "" {
2018-09-28 17:06:28 -04:00
var err error
localMetadata , err = versions . GetCurrentRuntimeMetadata ( opts . RuntimeMetadataDir )
if err != nil {
return errors . Wrap ( err , "unable to determine the installed engine version. Specify which engine image to update with --engine-image set to 'engine-community' or 'engine-enterprise'" )
}
opts . EngineImage = localMetadata . EngineImage
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
}
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-28 17:06:28 -04:00
return versions . WriteRuntimeMetadata ( opts . RuntimeMetadataDir , newMetadata )
2018-09-14 13:53:56 -04:00
}
// 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
2018-09-28 17:06:28 -04:00
func ( c * baseClient ) PreflightCheck ( ctx context . Context , image containerd . Image ) ( * clitypes . RuntimeMetadata , error ) {
var metadata clitypes . RuntimeMetadata
2018-09-14 13:53:56 -04:00
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-28 17:06:28 -04:00
metadataString , ok := config . Labels [ "com.docker." + clitypes . RuntimeMetadataName ]
2018-09-14 13:53:56 -04:00
if ! ok {
2018-09-28 17:06:28 -04:00
return nil , fmt . Errorf ( "image %s does not contain runtime metadata label %s" , image . Name ( ) , clitypes . 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
}