github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/manifest/push.go (about) 1 package manifest 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 9 "github.com/docker/cli/cli" 10 "github.com/docker/cli/cli/command" 11 "github.com/docker/cli/cli/manifest/types" 12 registryclient "github.com/docker/cli/cli/registry/client" 13 "github.com/docker/distribution" 14 "github.com/docker/distribution/manifest/manifestlist" 15 "github.com/docker/distribution/manifest/schema2" 16 "github.com/docker/distribution/reference" 17 "github.com/docker/docker/registry" 18 "github.com/pkg/errors" 19 "github.com/spf13/cobra" 20 ) 21 22 type pushOpts struct { 23 insecure bool 24 purge bool 25 target string 26 } 27 28 type mountRequest struct { 29 ref reference.Named 30 manifest types.ImageManifest 31 } 32 33 type manifestBlob struct { 34 canonical reference.Canonical 35 os string 36 } 37 38 type pushRequest struct { 39 targetRef reference.Named 40 list *manifestlist.DeserializedManifestList 41 mountRequests []mountRequest 42 manifestBlobs []manifestBlob 43 insecure bool 44 } 45 46 func newPushListCommand(dockerCli command.Cli) *cobra.Command { 47 opts := pushOpts{} 48 49 cmd := &cobra.Command{ 50 Use: "push [OPTIONS] MANIFEST_LIST", 51 Short: "Push a manifest list to a repository", 52 Args: cli.ExactArgs(1), 53 RunE: func(cmd *cobra.Command, args []string) error { 54 opts.target = args[0] 55 return runPush(dockerCli, opts) 56 }, 57 } 58 59 flags := cmd.Flags() 60 flags.BoolVarP(&opts.purge, "purge", "p", false, "Remove the local manifest list after push") 61 flags.BoolVar(&opts.insecure, "insecure", false, "Allow push to an insecure registry") 62 return cmd 63 } 64 65 func runPush(dockerCli command.Cli, opts pushOpts) error { 66 67 targetRef, err := normalizeReference(opts.target) 68 if err != nil { 69 return err 70 } 71 72 manifests, err := dockerCli.ManifestStore().GetList(targetRef) 73 if err != nil { 74 return err 75 } 76 if len(manifests) == 0 { 77 return errors.Errorf("%s not found", targetRef) 78 } 79 80 pushRequest, err := buildPushRequest(manifests, targetRef, opts.insecure) 81 if err != nil { 82 return err 83 } 84 85 ctx := context.Background() 86 if err := pushList(ctx, dockerCli, pushRequest); err != nil { 87 return err 88 } 89 if opts.purge { 90 return dockerCli.ManifestStore().Remove(targetRef) 91 } 92 return nil 93 } 94 95 func buildPushRequest(manifests []types.ImageManifest, targetRef reference.Named, insecure bool) (pushRequest, error) { 96 req := pushRequest{targetRef: targetRef, insecure: insecure} 97 98 var err error 99 req.list, err = buildManifestList(manifests, targetRef) 100 if err != nil { 101 return req, err 102 } 103 104 targetRepo, err := registry.ParseRepositoryInfo(targetRef) 105 if err != nil { 106 return req, err 107 } 108 targetRepoName, err := registryclient.RepoNameForReference(targetRepo.Name) 109 if err != nil { 110 return req, err 111 } 112 113 for _, imageManifest := range manifests { 114 manifestRepoName, err := registryclient.RepoNameForReference(imageManifest.Ref) 115 if err != nil { 116 return req, err 117 } 118 119 repoName, _ := reference.WithName(manifestRepoName) 120 if repoName.Name() != targetRepoName { 121 blobs, err := buildBlobRequestList(imageManifest, repoName) 122 if err != nil { 123 return req, err 124 } 125 req.manifestBlobs = append(req.manifestBlobs, blobs...) 126 127 manifestPush, err := buildPutManifestRequest(imageManifest, targetRef) 128 if err != nil { 129 return req, err 130 } 131 req.mountRequests = append(req.mountRequests, manifestPush) 132 } 133 } 134 return req, nil 135 } 136 137 func buildManifestList(manifests []types.ImageManifest, targetRef reference.Named) (*manifestlist.DeserializedManifestList, error) { 138 targetRepoInfo, err := registry.ParseRepositoryInfo(targetRef) 139 if err != nil { 140 return nil, err 141 } 142 143 descriptors := []manifestlist.ManifestDescriptor{} 144 for _, imageManifest := range manifests { 145 if imageManifest.Descriptor.Platform == nil || 146 imageManifest.Descriptor.Platform.Architecture == "" || 147 imageManifest.Descriptor.Platform.OS == "" { 148 return nil, errors.Errorf( 149 "manifest %s must have an OS and Architecture to be pushed to a registry", imageManifest.Ref) 150 } 151 descriptor, err := buildManifestDescriptor(targetRepoInfo, imageManifest) 152 if err != nil { 153 return nil, err 154 } 155 descriptors = append(descriptors, descriptor) 156 } 157 158 return manifestlist.FromDescriptors(descriptors) 159 } 160 161 func buildManifestDescriptor(targetRepo *registry.RepositoryInfo, imageManifest types.ImageManifest) (manifestlist.ManifestDescriptor, error) { 162 repoInfo, err := registry.ParseRepositoryInfo(imageManifest.Ref) 163 if err != nil { 164 return manifestlist.ManifestDescriptor{}, err 165 } 166 167 manifestRepoHostname := reference.Domain(repoInfo.Name) 168 targetRepoHostname := reference.Domain(targetRepo.Name) 169 if manifestRepoHostname != targetRepoHostname { 170 return manifestlist.ManifestDescriptor{}, errors.Errorf("cannot use source images from a different registry than the target image: %s != %s", manifestRepoHostname, targetRepoHostname) 171 } 172 173 manifest := manifestlist.ManifestDescriptor{ 174 Descriptor: distribution.Descriptor{ 175 Digest: imageManifest.Descriptor.Digest, 176 Size: imageManifest.Descriptor.Size, 177 MediaType: imageManifest.Descriptor.MediaType, 178 }, 179 } 180 181 platform := types.PlatformSpecFromOCI(imageManifest.Descriptor.Platform) 182 if platform != nil { 183 manifest.Platform = *platform 184 } 185 186 if err = manifest.Descriptor.Digest.Validate(); err != nil { 187 return manifestlist.ManifestDescriptor{}, errors.Wrapf(err, 188 "digest parse of image %q failed", imageManifest.Ref) 189 } 190 191 return manifest, nil 192 } 193 194 func buildBlobRequestList(imageManifest types.ImageManifest, repoName reference.Named) ([]manifestBlob, error) { 195 var blobReqs []manifestBlob 196 197 for _, blobDigest := range imageManifest.Blobs() { 198 canonical, err := reference.WithDigest(repoName, blobDigest) 199 if err != nil { 200 return nil, err 201 } 202 var os string 203 if imageManifest.Descriptor.Platform != nil { 204 os = imageManifest.Descriptor.Platform.OS 205 } 206 blobReqs = append(blobReqs, manifestBlob{canonical: canonical, os: os}) 207 } 208 return blobReqs, nil 209 } 210 211 // nolint: interfacer 212 func buildPutManifestRequest(imageManifest types.ImageManifest, targetRef reference.Named) (mountRequest, error) { 213 refWithoutTag, err := reference.WithName(targetRef.Name()) 214 if err != nil { 215 return mountRequest{}, err 216 } 217 mountRef, err := reference.WithDigest(refWithoutTag, imageManifest.Descriptor.Digest) 218 if err != nil { 219 return mountRequest{}, err 220 } 221 222 // This indentation has to be added to ensure sha parity with the registry 223 v2ManifestBytes, err := json.MarshalIndent(imageManifest.SchemaV2Manifest, "", " ") 224 if err != nil { 225 return mountRequest{}, err 226 } 227 // indent only the DeserializedManifest portion of this, in order to maintain parity with the registry 228 // and not alter the sha 229 var v2Manifest schema2.DeserializedManifest 230 if err = v2Manifest.UnmarshalJSON(v2ManifestBytes); err != nil { 231 return mountRequest{}, err 232 } 233 imageManifest.SchemaV2Manifest = &v2Manifest 234 235 return mountRequest{ref: mountRef, manifest: imageManifest}, err 236 } 237 238 func pushList(ctx context.Context, dockerCli command.Cli, req pushRequest) error { 239 rclient := dockerCli.RegistryClient(req.insecure) 240 241 if err := mountBlobs(ctx, rclient, req.targetRef, req.manifestBlobs); err != nil { 242 return err 243 } 244 if err := pushReferences(ctx, dockerCli.Out(), rclient, req.mountRequests); err != nil { 245 return err 246 } 247 dgst, err := rclient.PutManifest(ctx, req.targetRef, req.list) 248 if err != nil { 249 return err 250 } 251 252 fmt.Fprintln(dockerCli.Out(), dgst.String()) 253 return nil 254 } 255 256 func pushReferences(ctx context.Context, out io.Writer, client registryclient.RegistryClient, mounts []mountRequest) error { 257 for _, mount := range mounts { 258 newDigest, err := client.PutManifest(ctx, mount.ref, mount.manifest) 259 if err != nil { 260 return err 261 } 262 fmt.Fprintf(out, "Pushed ref %s with digest: %s\n", mount.ref, newDigest) 263 } 264 return nil 265 } 266 267 func mountBlobs(ctx context.Context, client registryclient.RegistryClient, ref reference.Named, blobs []manifestBlob) error { 268 for _, blob := range blobs { 269 err := client.MountBlob(ctx, blob.canonical, ref) 270 switch err.(type) { 271 case nil: 272 case registryclient.ErrBlobCreated: 273 if blob.os != "windows" { 274 return fmt.Errorf("error mounting %s to %s", blob.canonical, ref) 275 } 276 default: 277 return err 278 } 279 } 280 return nil 281 }