github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/domain/infra/abi/manifest.go (about) 1 package abi 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "os" 8 "strings" 9 10 "github.com/containers/common/libimage" 11 cp "github.com/containers/image/v5/copy" 12 "github.com/containers/image/v5/manifest" 13 "github.com/containers/image/v5/pkg/shortnames" 14 "github.com/containers/image/v5/transports" 15 "github.com/containers/image/v5/transports/alltransports" 16 "github.com/hanks177/podman/v4/pkg/domain/entities" 17 "github.com/containers/storage" 18 "github.com/opencontainers/go-digest" 19 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" 20 "github.com/pkg/errors" 21 "github.com/sirupsen/logrus" 22 ) 23 24 // ManifestCreate implements logic for creating manifest lists via ImageEngine 25 func (ir *ImageEngine) ManifestCreate(ctx context.Context, name string, images []string, opts entities.ManifestCreateOptions) (string, error) { 26 if len(name) == 0 { 27 return "", errors.New("no name specified for creating a manifest list") 28 } 29 30 manifestList, err := ir.Libpod.LibimageRuntime().CreateManifestList(name) 31 if err != nil { 32 return "", err 33 } 34 35 addOptions := &libimage.ManifestListAddOptions{All: opts.All} 36 for _, image := range images { 37 if _, err := manifestList.Add(ctx, image, addOptions); err != nil { 38 return "", err 39 } 40 } 41 42 return manifestList.ID(), nil 43 } 44 45 // ManifestExists checks if a manifest list with the given name exists in local storage 46 func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entities.BoolReport, error) { 47 _, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) 48 if err != nil { 49 if errors.Cause(err) == storage.ErrImageUnknown { 50 return &entities.BoolReport{Value: false}, nil 51 } 52 return nil, err 53 } 54 55 return &entities.BoolReport{Value: true}, nil 56 } 57 58 // ManifestInspect returns the content of a manifest list or image 59 func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) { 60 // NOTE: we have to do a bit of a limbo here as `podman manifest 61 // inspect foo` wants to do a remote-inspect of foo iff "foo" in the 62 // containers storage is an ordinary image but not a manifest list. 63 64 manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) 65 if err != nil { 66 switch errors.Cause(err) { 67 // Do a remote inspect if there's no local image or if the 68 // local image is not a manifest list. 69 case storage.ErrImageUnknown, libimage.ErrNotAManifestList: 70 return ir.remoteManifestInspect(ctx, name) 71 72 default: 73 return nil, err 74 } 75 } 76 77 schema2List, err := manifestList.Inspect() 78 if err != nil { 79 return nil, err 80 } 81 82 rawSchema2List, err := json.Marshal(schema2List) 83 if err != nil { 84 return nil, err 85 } 86 87 var b bytes.Buffer 88 if err := json.Indent(&b, rawSchema2List, "", " "); err != nil { 89 return nil, errors.Wrapf(err, "error rendering manifest %s for display", name) 90 } 91 return b.Bytes(), nil 92 } 93 94 // inspect a remote manifest list. 95 func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ([]byte, error) { 96 sys := ir.Libpod.SystemContext() 97 98 resolved, err := shortnames.Resolve(sys, name) 99 if err != nil { 100 return nil, err 101 } 102 103 var ( 104 latestErr error 105 result []byte 106 manType string 107 b bytes.Buffer 108 ) 109 appendErr := func(e error) { 110 if latestErr == nil { 111 latestErr = e 112 } else { 113 // FIXME should we use multierror package instead? 114 115 // we want the new line here so ignore the linter 116 //nolint:revive 117 latestErr = errors.Wrapf(latestErr, "tried %v\n", e) 118 } 119 } 120 121 for _, candidate := range resolved.PullCandidates { 122 ref, err := alltransports.ParseImageName("docker://" + candidate.Value.String()) 123 if err != nil { 124 return nil, err 125 } 126 src, err := ref.NewImageSource(ctx, sys) 127 if err != nil { 128 appendErr(errors.Wrapf(err, "reading image %q", transports.ImageName(ref))) 129 continue 130 } 131 defer src.Close() 132 133 manifestBytes, manifestType, err := src.GetManifest(ctx, nil) 134 if err != nil { 135 appendErr(errors.Wrapf(err, "loading manifest %q", transports.ImageName(ref))) 136 continue 137 } 138 139 result = manifestBytes 140 manType = manifestType 141 break 142 } 143 144 if len(result) == 0 && latestErr != nil { 145 return nil, latestErr 146 } 147 148 switch manType { 149 case manifest.DockerV2Schema2MediaType: 150 logrus.Warnf("The manifest type %s is not a manifest list but a single image.", manType) 151 schema2Manifest, err := manifest.Schema2FromManifest(result) 152 if err != nil { 153 return nil, errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(result), manType) 154 } 155 if result, err = schema2Manifest.Serialize(); err != nil { 156 return nil, err 157 } 158 default: 159 listBlob, err := manifest.ListFromBlob(result, manType) 160 if err != nil { 161 return nil, errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(result), manType) 162 } 163 list, err := listBlob.ConvertToMIMEType(manifest.DockerV2ListMediaType) 164 if err != nil { 165 return nil, err 166 } 167 if result, err = list.Serialize(); err != nil { 168 return nil, err 169 } 170 } 171 172 if err = json.Indent(&b, result, "", " "); err != nil { 173 return nil, errors.Wrapf(err, "error rendering manifest %s for display", name) 174 } 175 return b.Bytes(), nil 176 } 177 178 // ManifestAdd adds images to the manifest list 179 func (ir *ImageEngine) ManifestAdd(ctx context.Context, name string, images []string, opts entities.ManifestAddOptions) (string, error) { 180 if len(images) < 1 { 181 return "", errors.New("manifest add requires at least one image") 182 } 183 184 manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) 185 if err != nil { 186 return "", err 187 } 188 189 addOptions := &libimage.ManifestListAddOptions{ 190 All: opts.All, 191 AuthFilePath: opts.Authfile, 192 CertDirPath: opts.CertDir, 193 InsecureSkipTLSVerify: opts.SkipTLSVerify, 194 Username: opts.Username, 195 Password: opts.Password, 196 } 197 198 for _, image := range images { 199 instanceDigest, err := manifestList.Add(ctx, image, addOptions) 200 if err != nil { 201 return "", err 202 } 203 204 annotateOptions := &libimage.ManifestListAnnotateOptions{ 205 Architecture: opts.Arch, 206 Features: opts.Features, 207 OS: opts.OS, 208 OSVersion: opts.OSVersion, 209 Variant: opts.Variant, 210 } 211 if len(opts.Annotation) != 0 { 212 annotations := make(map[string]string) 213 for _, annotationSpec := range opts.Annotation { 214 spec := strings.SplitN(annotationSpec, "=", 2) 215 if len(spec) != 2 { 216 return "", errors.Errorf("no value given for annotation %q", spec[0]) 217 } 218 annotations[spec[0]] = spec[1] 219 } 220 annotateOptions.Annotations = annotations 221 } 222 223 if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil { 224 return "", err 225 } 226 } 227 return manifestList.ID(), nil 228 } 229 230 // ManifestAnnotate updates an entry of the manifest list 231 func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, image string, opts entities.ManifestAnnotateOptions) (string, error) { 232 instanceDigest, err := digest.Parse(image) 233 if err != nil { 234 return "", errors.Errorf(`invalid image digest "%s": %v`, image, err) 235 } 236 237 manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) 238 if err != nil { 239 return "", err 240 } 241 242 annotateOptions := &libimage.ManifestListAnnotateOptions{ 243 Architecture: opts.Arch, 244 Features: opts.Features, 245 OS: opts.OS, 246 OSVersion: opts.OSVersion, 247 Variant: opts.Variant, 248 } 249 if len(opts.Annotation) != 0 { 250 annotations := make(map[string]string) 251 for _, annotationSpec := range opts.Annotation { 252 spec := strings.SplitN(annotationSpec, "=", 2) 253 if len(spec) != 2 { 254 return "", errors.Errorf("no value given for annotation %q", spec[0]) 255 } 256 annotations[spec[0]] = spec[1] 257 } 258 annotateOptions.Annotations = annotations 259 } 260 261 if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil { 262 return "", err 263 } 264 265 return manifestList.ID(), nil 266 } 267 268 // ManifestRemoveDigest removes specified digest from the specified manifest list 269 func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name, image string) (string, error) { 270 instanceDigest, err := digest.Parse(image) 271 if err != nil { 272 return "", errors.Errorf(`invalid image digest "%s": %v`, image, err) 273 } 274 275 manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) 276 if err != nil { 277 return "", err 278 } 279 280 if err := manifestList.RemoveInstance(instanceDigest); err != nil { 281 return "", err 282 } 283 284 return manifestList.ID(), nil 285 } 286 287 // ManifestRm removes the specified manifest list from storage 288 func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string) (report *entities.ImageRemoveReport, rmErrors []error) { 289 return ir.Remove(ctx, names, entities.ImageRemoveOptions{LookupManifest: true}) 290 } 291 292 // ManifestPush pushes a manifest list or image index to the destination 293 func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) { 294 manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) 295 if err != nil { 296 return "", errors.Wrapf(err, "error retrieving local image from image name %s", name) 297 } 298 299 var manifestType string 300 if opts.Format != "" { 301 switch opts.Format { 302 case "oci": 303 manifestType = imgspecv1.MediaTypeImageManifest 304 case "v2s2", "docker": 305 manifestType = manifest.DockerV2Schema2MediaType 306 default: 307 return "", errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format) 308 } 309 } 310 311 pushOptions := &libimage.ManifestListPushOptions{} 312 pushOptions.AuthFilePath = opts.Authfile 313 pushOptions.CertDirPath = opts.CertDir 314 pushOptions.Username = opts.Username 315 pushOptions.Password = opts.Password 316 pushOptions.ImageListSelection = cp.CopySpecificImages 317 pushOptions.ManifestMIMEType = manifestType 318 pushOptions.RemoveSignatures = opts.RemoveSignatures 319 pushOptions.SignBy = opts.SignBy 320 pushOptions.InsecureSkipTLSVerify = opts.SkipTLSVerify 321 322 if opts.All { 323 pushOptions.ImageListSelection = cp.CopyAllImages 324 } 325 if !opts.Quiet { 326 pushOptions.Writer = os.Stderr 327 } 328 329 manDigest, err := manifestList.Push(ctx, destination, pushOptions) 330 if err != nil { 331 return "", err 332 } 333 334 if opts.Rm { 335 if _, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, []string{manifestList.ID()}, nil); len(rmErrors) > 0 { 336 return "", errors.Wrap(rmErrors[0], "error removing manifest after push") 337 } 338 } 339 340 return manDigest.String(), err 341 }