github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/internal/containerizedengine/update.go (about)

     1  package containerizedengine
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/containerd/containerd"
    10  	"github.com/containerd/containerd/content"
    11  	"github.com/containerd/containerd/errdefs"
    12  	"github.com/containerd/containerd/images"
    13  	"github.com/containerd/containerd/namespaces"
    14  	"github.com/docker/cli/internal/versions"
    15  	clitypes "github.com/docker/cli/types"
    16  	"github.com/docker/distribution/reference"
    17  	"github.com/docker/docker/api/types"
    18  	ver "github.com/hashicorp/go-version"
    19  	"github.com/opencontainers/image-spec/specs-go/v1"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  // ActivateEngine will switch the image from the CE to EE image
    24  func (c *baseClient) ActivateEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
    25  	authConfig *types.AuthConfig) error {
    26  
    27  	// If the user didn't specify an image, determine the correct enterprise image to use
    28  	if opts.EngineImage == "" {
    29  		localMetadata, err := versions.GetCurrentRuntimeMetadata(opts.RuntimeMetadataDir)
    30  		if err != nil {
    31  			return errors.Wrap(err, "unable to determine the installed engine version. Specify which engine image to update with --engine-image")
    32  		}
    33  
    34  		engineImage := localMetadata.EngineImage
    35  		if engineImage == clitypes.EnterpriseEngineImage || engineImage == clitypes.CommunityEngineImage {
    36  			opts.EngineImage = clitypes.EnterpriseEngineImage
    37  		} else {
    38  			// Chop off the standard prefix and retain any trailing OS specific image details
    39  			// e.g., engine-community-dm -> engine-enterprise-dm
    40  			engineImage = strings.TrimPrefix(engineImage, clitypes.EnterpriseEngineImage)
    41  			engineImage = strings.TrimPrefix(engineImage, clitypes.CommunityEngineImage)
    42  			opts.EngineImage = clitypes.EnterpriseEngineImage + engineImage
    43  		}
    44  	}
    45  
    46  	ctx = namespaces.WithNamespace(ctx, engineNamespace)
    47  	return c.DoUpdate(ctx, opts, out, authConfig)
    48  }
    49  
    50  // DoUpdate performs the underlying engine update
    51  func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
    52  	authConfig *types.AuthConfig) error {
    53  
    54  	ctx = namespaces.WithNamespace(ctx, engineNamespace)
    55  	if opts.EngineVersion == "" {
    56  		// TODO - Future enhancement: This could be improved to be
    57  		// smart about figuring out the latest patch rev for the
    58  		// current engine version and automatically apply it so users
    59  		// could stay in sync by simply having a scheduled
    60  		// `docker engine update`
    61  		return fmt.Errorf("pick the version you want to update to with --version")
    62  	}
    63  	var localMetadata *clitypes.RuntimeMetadata
    64  	if opts.EngineImage == "" {
    65  		var err error
    66  		localMetadata, err = versions.GetCurrentRuntimeMetadata(opts.RuntimeMetadataDir)
    67  		if err != nil {
    68  			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'")
    69  		}
    70  		opts.EngineImage = localMetadata.EngineImage
    71  	}
    72  
    73  	imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
    74  
    75  	// Look for desired image
    76  	image, err := c.cclient.GetImage(ctx, imageName)
    77  	if err != nil {
    78  		if errdefs.IsNotFound(err) {
    79  			image, err = c.pullWithAuth(ctx, imageName, out, authConfig)
    80  			if err != nil {
    81  				return errors.Wrapf(err, "unable to pull image %s", imageName)
    82  			}
    83  		} else {
    84  			return errors.Wrapf(err, "unable to check for image %s", imageName)
    85  		}
    86  	}
    87  
    88  	// Make sure we're safe to proceed
    89  	newMetadata, err := c.PreflightCheck(ctx, image)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	if localMetadata != nil {
    94  		if localMetadata.Platform != newMetadata.Platform {
    95  			fmt.Fprintf(out, "\nNotice: you have switched to \"%s\".  Refer to %s for update instructions.\n\n", newMetadata.Platform, getReleaseNotesURL(imageName))
    96  		}
    97  	}
    98  
    99  	if err := c.cclient.Install(ctx, image, containerd.WithInstallReplace, containerd.WithInstallPath("/usr")); err != nil {
   100  		return err
   101  	}
   102  
   103  	return versions.WriteRuntimeMetadata(opts.RuntimeMetadataDir, newMetadata)
   104  }
   105  
   106  // PreflightCheck verifies the specified image is compatible with the local system before proceeding to update/activate
   107  // If things look good, the RuntimeMetadata for the new image is returned and can be written out to the host
   108  func (c *baseClient) PreflightCheck(ctx context.Context, image containerd.Image) (*clitypes.RuntimeMetadata, error) {
   109  	var metadata clitypes.RuntimeMetadata
   110  	ic, err := image.Config(ctx)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	var (
   115  		ociimage v1.Image
   116  		config   v1.ImageConfig
   117  	)
   118  	switch ic.MediaType {
   119  	case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
   120  		p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  
   125  		if err := json.Unmarshal(p, &ociimage); err != nil {
   126  			return nil, err
   127  		}
   128  		config = ociimage.Config
   129  	default:
   130  		return nil, fmt.Errorf("unknown image %s config media type %s", image.Name(), ic.MediaType)
   131  	}
   132  
   133  	metadataString, ok := config.Labels["com.docker."+clitypes.RuntimeMetadataName]
   134  	if !ok {
   135  		return nil, fmt.Errorf("image %s does not contain runtime metadata label %s", image.Name(), clitypes.RuntimeMetadataName)
   136  	}
   137  	err = json.Unmarshal([]byte(metadataString), &metadata)
   138  	if err != nil {
   139  		return nil, errors.Wrapf(err, "malformed runtime metadata file in %s", image.Name())
   140  	}
   141  
   142  	// Current CLI only supports host install runtime
   143  	if metadata.Runtime != "host_install" {
   144  		return nil, fmt.Errorf("unsupported daemon image: %s\nConsult the release notes at %s for upgrade instructions", metadata.Runtime, getReleaseNotesURL(image.Name()))
   145  	}
   146  
   147  	// Verify local containerd is new enough
   148  	localVersion, err := c.cclient.Version(ctx)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	if metadata.ContainerdMinVersion != "" {
   153  		lv, err := ver.NewVersion(localVersion.Version)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  		mv, err := ver.NewVersion(metadata.ContainerdMinVersion)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		if lv.LessThan(mv) {
   162  			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",
   163  				localVersion.Version, metadata.ContainerdMinVersion, getReleaseNotesURL(image.Name()))
   164  		}
   165  	} // If omitted on metadata, no hard dependency on containerd version beyond 18.09 baseline
   166  
   167  	// All checks look OK, proceed with update
   168  	return &metadata, nil
   169  }
   170  
   171  // getReleaseNotesURL returns a release notes url
   172  // If the image name does not contain a version tag, the base release notes URL is returned
   173  func getReleaseNotesURL(imageName string) string {
   174  	versionTag := ""
   175  	distributionRef, err := reference.ParseNormalizedNamed(imageName)
   176  	if err == nil {
   177  		taggedRef, ok := distributionRef.(reference.NamedTagged)
   178  		if ok {
   179  			versionTag = taggedRef.Tag()
   180  		}
   181  	}
   182  	return fmt.Sprintf("%s/%s", clitypes.ReleaseNotePrefix, versionTag)
   183  }