github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/domain/infra/abi/images.go (about) 1 // +build ABISupport 2 3 package abi 4 5 import ( 6 "context" 7 "fmt" 8 "io" 9 "os" 10 "strings" 11 12 "github.com/containers/common/pkg/config" 13 "github.com/containers/image/v5/docker" 14 dockerarchive "github.com/containers/image/v5/docker/archive" 15 "github.com/containers/image/v5/docker/reference" 16 "github.com/containers/image/v5/manifest" 17 "github.com/containers/image/v5/transports/alltransports" 18 "github.com/containers/image/v5/types" 19 "github.com/containers/libpod/libpod/define" 20 "github.com/containers/libpod/libpod/image" 21 libpodImage "github.com/containers/libpod/libpod/image" 22 "github.com/containers/libpod/pkg/domain/entities" 23 domainUtils "github.com/containers/libpod/pkg/domain/utils" 24 "github.com/containers/libpod/pkg/util" 25 "github.com/containers/storage" 26 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" 27 "github.com/pkg/errors" 28 "github.com/sirupsen/logrus" 29 ) 30 31 func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) { 32 _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) 33 if err != nil && errors.Cause(err) != define.ErrNoSuchImage { 34 return nil, err 35 } 36 return &entities.BoolReport{Value: err == nil}, nil 37 } 38 39 func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { 40 report := entities.ImageDeleteReport{} 41 42 if opts.All { 43 var previousTargets []*libpodImage.Image 44 repeatRun: 45 targets, err := ir.Libpod.ImageRuntime().GetRWImages() 46 if err != nil { 47 return &report, errors.Wrapf(err, "unable to query local images") 48 } 49 if len(targets) == 0 { 50 return &report, nil 51 } 52 if len(targets) > 0 && len(targets) == len(previousTargets) { 53 return &report, errors.New("unable to delete all images; re-run the rmi command again.") 54 } 55 previousTargets = targets 56 57 for _, img := range targets { 58 isParent, err := img.IsParent(ctx) 59 if err != nil { 60 return &report, err 61 } 62 if isParent { 63 continue 64 } 65 err = ir.deleteImage(ctx, img, opts, report) 66 report.Errors = append(report.Errors, err) 67 } 68 if len(previousTargets) != 1 { 69 goto repeatRun 70 } 71 return &report, nil 72 } 73 74 for _, id := range nameOrId { 75 image, err := ir.Libpod.ImageRuntime().NewFromLocal(id) 76 if err != nil { 77 return nil, err 78 } 79 80 err = ir.deleteImage(ctx, image, opts, report) 81 if err != nil { 82 return &report, err 83 } 84 } 85 return &report, nil 86 } 87 88 func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, opts entities.ImageDeleteOptions, report entities.ImageDeleteReport) error { 89 results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) 90 switch errors.Cause(err) { 91 case nil: 92 break 93 case storage.ErrImageUsedByContainer: 94 report.ImageInUse = errors.New( 95 fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())) 96 return nil 97 case libpodImage.ErrNoSuchImage: 98 report.ImageNotFound = err 99 return nil 100 default: 101 return err 102 } 103 104 report.Deleted = append(report.Deleted, results.Deleted) 105 report.Untagged = append(report.Untagged, results.Untagged...) 106 return nil 107 } 108 109 func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { 110 results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) 111 if err != nil { 112 return nil, err 113 } 114 115 report := entities.ImagePruneReport{ 116 Report: entities.Report{ 117 Id: results, 118 Err: nil, 119 }, 120 Size: 0, 121 } 122 return &report, nil 123 } 124 125 func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) { 126 image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) 127 if err != nil { 128 return nil, err 129 } 130 results, err := image.History(ctx) 131 if err != nil { 132 return nil, err 133 } 134 135 history := entities.ImageHistoryReport{ 136 Layers: make([]entities.ImageHistoryLayer, len(results)), 137 } 138 139 for i, layer := range results { 140 history.Layers[i] = ToDomainHistoryLayer(layer) 141 } 142 return &history, nil 143 } 144 145 func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer { 146 l := entities.ImageHistoryLayer{} 147 l.ID = layer.ID 148 l.Created = *layer.Created 149 l.CreatedBy = layer.CreatedBy 150 copy(l.Tags, layer.Tags) 151 l.Size = layer.Size 152 l.Comment = layer.Comment 153 return l 154 } 155 156 func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { 157 var writer io.Writer 158 if !options.Quiet { 159 writer = os.Stderr 160 } 161 162 dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) 163 imageRef, err := alltransports.ParseImageName(rawImage) 164 if err != nil { 165 imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage)) 166 if err != nil { 167 return nil, errors.Errorf("invalid image reference %q", rawImage) 168 } 169 } 170 171 // Special-case for docker-archive which allows multiple tags. 172 if imageRef.Transport().Name() == dockerarchive.Transport.Name() { 173 newImage, err := ir.Libpod.ImageRuntime().LoadFromArchiveReference(ctx, imageRef, options.SignaturePolicy, writer) 174 if err != nil { 175 return nil, errors.Wrapf(err, "error pulling image %q", rawImage) 176 } 177 return &entities.ImagePullReport{Images: []string{newImage[0].ID()}}, nil 178 } 179 180 var registryCreds *types.DockerAuthConfig 181 if options.Credentials != "" { 182 creds, err := util.ParseRegistryCreds(options.Credentials) 183 if err != nil { 184 return nil, err 185 } 186 registryCreds = creds 187 } 188 dockerRegistryOptions := image.DockerRegistryOptions{ 189 DockerRegistryCreds: registryCreds, 190 DockerCertPath: options.CertDir, 191 OSChoice: options.OverrideOS, 192 ArchitectureChoice: options.OverrideArch, 193 DockerInsecureSkipTLSVerify: options.TLSVerify, 194 } 195 196 if !options.AllTags { 197 newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) 198 if err != nil { 199 return nil, errors.Wrapf(err, "error pulling image %q", rawImage) 200 } 201 return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil 202 } 203 204 // --all-tags requires the docker transport 205 if imageRef.Transport().Name() != docker.Transport.Name() { 206 return nil, errors.New("--all-tags requires docker transport") 207 } 208 209 // Trim the docker-transport prefix. 210 rawImage = strings.TrimPrefix(rawImage, docker.Transport.Name()) 211 212 // all-tags doesn't work with a tagged reference, so let's check early 213 namedRef, err := reference.Parse(rawImage) 214 if err != nil { 215 return nil, errors.Wrapf(err, "error parsing %q", rawImage) 216 } 217 if _, isTagged := namedRef.(reference.Tagged); isTagged { 218 return nil, errors.New("--all-tags requires a reference without a tag") 219 220 } 221 222 systemContext := image.GetSystemContext("", options.Authfile, false) 223 tags, err := docker.GetRepositoryTags(ctx, systemContext, imageRef) 224 if err != nil { 225 return nil, errors.Wrapf(err, "error getting repository tags") 226 } 227 228 var foundIDs []string 229 for _, tag := range tags { 230 name := rawImage + ":" + tag 231 newImage, err := ir.Libpod.ImageRuntime().New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) 232 if err != nil { 233 logrus.Errorf("error pulling image %q", name) 234 continue 235 } 236 foundIDs = append(foundIDs, newImage.ID()) 237 } 238 239 if len(tags) != len(foundIDs) { 240 return nil, errors.Errorf("error pulling image %q", rawImage) 241 } 242 return &entities.ImagePullReport{Images: foundIDs}, nil 243 } 244 245 func (ir *ImageEngine) Inspect(ctx context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) { 246 report := entities.ImageInspectReport{ 247 Errors: make(map[string]error), 248 } 249 250 for _, id := range names { 251 img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) 252 if err != nil { 253 report.Errors[id] = err 254 continue 255 } 256 257 results, err := img.Inspect(ctx) 258 if err != nil { 259 report.Errors[id] = err 260 continue 261 } 262 263 cookedResults := entities.ImageData{} 264 _ = domainUtils.DeepCopy(&cookedResults, results) 265 report.Images = append(report.Images, &cookedResults) 266 } 267 return &report, nil 268 } 269 270 func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { 271 var writer io.Writer 272 if !options.Quiet { 273 writer = os.Stderr 274 } 275 276 var manifestType string 277 switch options.Format { 278 case "": 279 // Default 280 case "oci": 281 manifestType = imgspecv1.MediaTypeImageManifest 282 case "v2s1": 283 manifestType = manifest.DockerV2Schema1SignedMediaType 284 case "v2s2", "docker": 285 manifestType = manifest.DockerV2Schema2MediaType 286 default: 287 return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format) 288 } 289 290 var registryCreds *types.DockerAuthConfig 291 if options.Credentials != "" { 292 creds, err := util.ParseRegistryCreds(options.Credentials) 293 if err != nil { 294 return err 295 } 296 registryCreds = creds 297 } 298 dockerRegistryOptions := image.DockerRegistryOptions{ 299 DockerRegistryCreds: registryCreds, 300 DockerCertPath: options.CertDir, 301 DockerInsecureSkipTLSVerify: options.TLSVerify, 302 } 303 304 signOptions := image.SigningOptions{ 305 RemoveSignatures: options.RemoveSignatures, 306 SignBy: options.SignBy, 307 } 308 309 newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(source) 310 if err != nil { 311 return err 312 } 313 314 return newImage.PushImageToHeuristicDestination( 315 ctx, 316 destination, 317 manifestType, 318 options.Authfile, 319 options.DigestFile, 320 options.SignaturePolicy, 321 writer, 322 options.Compress, 323 signOptions, 324 &dockerRegistryOptions, 325 nil) 326 } 327 328 // func (r *imageRuntime) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { 329 // image, err := r.libpod.ImageEngine().NewFromLocal(nameOrId) 330 // if err != nil { 331 // return nil, err 332 // } 333 // 334 // results, err := r.libpod.RemoveImage(ctx, image, opts.Force) 335 // if err != nil { 336 // return nil, err 337 // } 338 // 339 // report := entities.ImageDeleteReport{} 340 // if err := domainUtils.DeepCopy(&report, results); err != nil { 341 // return nil, err 342 // } 343 // return &report, nil 344 // } 345 // 346 // func (r *imageRuntime) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { 347 // // TODO: map FilterOptions 348 // id, err := r.libpod.ImageEngine().PruneImages(ctx, opts.All, []string{}) 349 // if err != nil { 350 // return nil, err 351 // } 352 // 353 // // TODO: Determine Size 354 // report := entities.ImagePruneReport{} 355 // copy(report.Report.Id, id) 356 // return &report, nil 357 // } 358 359 func (ir *ImageEngine) Tag(ctx context.Context, nameOrId string, tags []string, options entities.ImageTagOptions) error { 360 newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) 361 if err != nil { 362 return err 363 } 364 for _, tag := range tags { 365 if err := newImage.TagImage(tag); err != nil { 366 return err 367 } 368 } 369 return nil 370 } 371 372 func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string, options entities.ImageUntagOptions) error { 373 newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) 374 if err != nil { 375 return err 376 } 377 for _, tag := range tags { 378 if err := newImage.UntagImage(tag); err != nil { 379 return err 380 } 381 } 382 return nil 383 } 384 385 func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { 386 var ( 387 writer io.Writer 388 ) 389 if !opts.Quiet { 390 writer = os.Stderr 391 } 392 name, err := ir.Libpod.LoadImage(ctx, opts.Name, opts.Input, writer, opts.SignaturePolicy) 393 if err != nil { 394 return nil, err 395 } 396 newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name) 397 if err != nil { 398 return nil, errors.Wrap(err, "image loaded but no additional tags were created") 399 } 400 if len(opts.Name) > 0 { 401 if err := newImage.TagImage(fmt.Sprintf("%s:%s", opts.Name, opts.Tag)); err != nil { 402 return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName) 403 } 404 } 405 return &entities.ImageLoadReport{Name: name}, nil 406 } 407 408 func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { 409 id, err := ir.Libpod.Import(ctx, opts.Source, opts.Reference, opts.Changes, opts.Message, opts.Quiet) 410 if err != nil { 411 return nil, err 412 } 413 return &entities.ImageImportReport{Id: id}, nil 414 } 415 416 func (ir *ImageEngine) Save(ctx context.Context, nameOrId string, tags []string, options entities.ImageSaveOptions) error { 417 newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) 418 if err != nil { 419 return err 420 } 421 return newImage.Save(ctx, nameOrId, options.Format, options.Output, tags, options.Quiet, options.Compress) 422 } 423 424 func (ir *ImageEngine) Diff(_ context.Context, nameOrId string, _ entities.DiffOptions) (*entities.DiffReport, error) { 425 changes, err := ir.Libpod.GetDiff("", nameOrId) 426 if err != nil { 427 return nil, err 428 } 429 return &entities.DiffReport{Changes: changes}, nil 430 } 431 432 func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { 433 filter, err := image.ParseSearchFilter(opts.Filters) 434 if err != nil { 435 return nil, err 436 } 437 438 searchOpts := image.SearchOptions{ 439 Authfile: opts.Authfile, 440 Filter: *filter, 441 Limit: opts.Limit, 442 NoTrunc: opts.NoTrunc, 443 InsecureSkipTLSVerify: opts.TLSVerify, 444 } 445 446 searchResults, err := image.SearchImages(term, searchOpts) 447 if err != nil { 448 return nil, err 449 } 450 451 // Convert from image.SearchResults to entities.ImageSearchReport. We don't 452 // want to leak any low-level packages into the remote client, which 453 // requires converting. 454 reports := make([]entities.ImageSearchReport, len(searchResults)) 455 for i := range searchResults { 456 reports[i].Index = searchResults[i].Index 457 reports[i].Name = searchResults[i].Name 458 reports[i].Description = searchResults[i].Index 459 reports[i].Stars = searchResults[i].Stars 460 reports[i].Official = searchResults[i].Official 461 reports[i].Automated = searchResults[i].Automated 462 } 463 464 return reports, nil 465 } 466 467 // GetConfig returns a copy of the configuration used by the runtime 468 func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) { 469 return ir.Libpod.GetConfig() 470 }