1package containerizedengine 2 3import ( 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 24func (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 51func (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 108func (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 173func 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} 184