summaryrefslogtreecommitdiff
path: root/pkg/domain/infra/abi/images.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/domain/infra/abi/images.go')
-rw-r--r--pkg/domain/infra/abi/images.go291
1 files changed, 216 insertions, 75 deletions
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 7c63276e5..d8af4d339 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -4,14 +4,22 @@ import (
"context"
"fmt"
"io"
+ "io/ioutil"
+ "net/url"
"os"
+ "path/filepath"
+ "strconv"
"strings"
+ "github.com/containers/libpod/pkg/rootless"
+
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/docker"
dockerarchive "github.com/containers/image/v5/docker/archive"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
+ "github.com/containers/image/v5/signature"
+ "github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod/define"
@@ -19,14 +27,17 @@ import (
libpodImage "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/domain/entities"
domainUtils "github.com/containers/libpod/pkg/domain/utils"
+ "github.com/containers/libpod/pkg/trust"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
- "github.com/hashicorp/go-multierror"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
+// SignatureStoreDir defines default directory to store signatures
+const SignatureStoreDir = "/var/lib/containers/sigstore"
+
func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) {
_, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
if err != nil && errors.Cause(err) != define.ErrNoSuchImage {
@@ -36,7 +47,11 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
}
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
- results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter)
+ return ir.pruneImagesHelper(ctx, opts.All, opts.Filter)
+}
+
+func (ir *ImageEngine) pruneImagesHelper(ctx context.Context, all bool, filters []string) (*entities.ImagePruneReport, error) {
+ results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, all, filters)
if err != nil {
return nil, err
}
@@ -106,19 +121,18 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
}
var registryCreds *types.DockerAuthConfig
- if options.Credentials != "" {
- creds, err := util.ParseRegistryCreds(options.Credentials)
- if err != nil {
- return nil, err
+ if len(options.Username) > 0 && len(options.Password) > 0 {
+ registryCreds = &types.DockerAuthConfig{
+ Username: options.Username,
+ Password: options.Password,
}
- registryCreds = creds
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,
OSChoice: options.OverrideOS,
ArchitectureChoice: options.OverrideArch,
- DockerInsecureSkipTLSVerify: options.TLSVerify,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
if !options.AllTags {
@@ -211,17 +225,16 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
}
var registryCreds *types.DockerAuthConfig
- if options.Credentials != "" {
- creds, err := util.ParseRegistryCreds(options.Credentials)
- if err != nil {
- return err
+ if len(options.Username) > 0 && len(options.Password) > 0 {
+ registryCreds = &types.DockerAuthConfig{
+ Username: options.Username,
+ Password: options.Password,
}
- registryCreds = creds
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,
- DockerInsecureSkipTLSVerify: options.TLSVerify,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
signOptions := image.SigningOptions{
@@ -370,7 +383,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
Filter: *filter,
Limit: opts.Limit,
NoTrunc: opts.NoTrunc,
- InsecureSkipTLSVerify: opts.TLSVerify,
+ InsecureSkipTLSVerify: opts.SkipTLSVerify,
}
searchResults, err := image.SearchImages(term, searchOpts)
@@ -419,8 +432,10 @@ func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.
return &entities.ImageTreeReport{Tree: results}, nil
}
-// Remove removes one or more images from local storage.
-func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, finalError error) {
+// removeErrorsToExitCode returns an exit code for the specified slice of
+// image-removal errors. The error codes are set according to the documented
+// behaviour in the Podman man pages.
+func removeErrorsToExitCode(rmErrors []error) int {
var (
// noSuchImageErrors indicates that at least one image was not found.
noSuchImageErrors bool
@@ -430,57 +445,53 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
// otherErrors indicates that at least one error other than the two
// above occured.
otherErrors bool
- // deleteError is a multierror to conveniently collect errors during
- // removal. We really want to delete as many images as possible and not
- // error out immediately.
- deleteError *multierror.Error
)
- report = &entities.ImageRemoveReport{}
+ if len(rmErrors) == 0 {
+ return 0
+ }
- // Set the removalCode and the error after all work is done.
- defer func() {
- switch {
- // 2
- case inUseErrors:
- // One of the specified images has child images or is
- // being used by a container.
- report.ExitCode = 2
- // 1
- case noSuchImageErrors && !(otherErrors || inUseErrors):
- // One of the specified images did not exist, and no other
- // failures.
- report.ExitCode = 1
- // 0
+ for _, e := range rmErrors {
+ switch errors.Cause(e) {
+ case define.ErrNoSuchImage:
+ noSuchImageErrors = true
+ case define.ErrImageInUse, storage.ErrImageUsedByContainer:
+ inUseErrors = true
default:
- // Nothing to do.
- }
- if deleteError != nil {
- // go-multierror has a trailing new line which we need to remove to normalize the string.
- finalError = deleteError.ErrorOrNil()
- finalError = errors.New(strings.TrimSpace(finalError.Error()))
+ otherErrors = true
}
+ }
+
+ switch {
+ case inUseErrors:
+ // One of the specified images has child images or is
+ // being used by a container.
+ return 2
+ case noSuchImageErrors && !(otherErrors || inUseErrors):
+ // One of the specified images did not exist, and no other
+ // failures.
+ return 1
+ default:
+ return 125
+ }
+}
+
+// Remove removes one or more images from local storage.
+func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) {
+ report = &entities.ImageRemoveReport{}
+
+ // Set the exit code at very end.
+ defer func() {
+ report.ExitCode = removeErrorsToExitCode(rmErrors)
}()
// deleteImage is an anonymous function to conveniently delete an image
// without having to pass all local data around.
deleteImage := func(img *image.Image) error {
results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
- switch errors.Cause(err) {
- case nil:
- break
- case storage.ErrImageUsedByContainer:
- inUseErrors = true // Important for exit codes in Podman.
- return errors.New(
- 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()))
- case define.ErrImageInUse:
- inUseErrors = true
- return err
- default:
- otherErrors = true // Important for exit codes in Podman.
+ if err != nil {
return err
}
-
report.Deleted = append(report.Deleted, results.Deleted)
report.Untagged = append(report.Untagged, results.Untagged...)
return nil
@@ -493,9 +504,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
for {
storageImages, err := ir.Libpod.ImageRuntime().GetRWImages()
if err != nil {
- deleteError = multierror.Append(deleteError,
- errors.Wrapf(err, "unable to query local images"))
- otherErrors = true // Important for exit codes in Podman.
+ rmErrors = append(rmErrors, err)
return
}
// No images (left) to remove, so we're done.
@@ -504,9 +513,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
}
// Prevent infinity loops by making a delete-progress check.
if previousImages == len(storageImages) {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError,
- errors.New("unable to delete all images, check errors and re-run image removal if needed"))
+ rmErrors = append(rmErrors, errors.New("unable to delete all images, check errors and re-run image removal if needed"))
break
}
previousImages = len(storageImages)
@@ -514,15 +521,15 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
for _, img := range storageImages {
isParent, err := img.IsParent(ctx)
if err != nil {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
+ continue
}
// Skip parent images.
if isParent {
continue
}
if err := deleteImage(img); err != nil {
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
}
}
}
@@ -533,21 +540,13 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
// Delete only the specified images.
for _, id := range images {
img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
- switch errors.Cause(err) {
- case nil:
- break
- case image.ErrNoSuchImage:
- noSuchImageErrors = true // Important for exit codes in Podman.
- fallthrough
- default:
- deleteError = multierror.Append(deleteError, err)
+ if err != nil {
+ rmErrors = append(rmErrors, err)
continue
}
-
err = deleteImage(img)
if err != nil {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
}
}
@@ -560,3 +559,145 @@ func (ir *ImageEngine) Shutdown(_ context.Context) {
_ = ir.Libpod.Shutdown(false)
})
}
+
+func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) {
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerCertPath: options.CertDir,
+ }
+
+ mech, err := signature.NewGPGSigningMechanism()
+ if err != nil {
+ return nil, errors.Wrap(err, "error initializing GPG")
+ }
+ defer mech.Close()
+ if err := mech.SupportsSigning(); err != nil {
+ return nil, errors.Wrap(err, "signing is not supported")
+ }
+ sc := ir.Libpod.SystemContext()
+ sc.DockerCertPath = options.CertDir
+
+ systemRegistriesDirPath := trust.RegistriesDirPath(sc)
+ registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error reading registry configuration")
+ }
+
+ for _, signimage := range names {
+ var sigStoreDir string
+ srcRef, err := alltransports.ParseImageName(signimage)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing image name")
+ }
+ rawSource, err := srcRef.NewImageSource(ctx, sc)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting image source")
+ }
+ err = rawSource.Close()
+ if err != nil {
+ logrus.Errorf("unable to close new image source %q", err)
+ }
+ getManifest, _, err := rawSource.GetManifest(ctx, nil)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting getManifest")
+ }
+ dockerReference := rawSource.Reference().DockerReference()
+ if dockerReference == nil {
+ return nil, errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference()))
+ }
+
+ // create the signstore file
+ rtc, err := ir.Libpod.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+ newImage, err := ir.Libpod.ImageRuntime().New(ctx, signimage, rtc.Engine.SignaturePolicyPath, "", os.Stderr, &dockerRegistryOptions, image.SigningOptions{SignBy: options.SignBy}, nil, util.PullImageMissing)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error pulling image %s", signimage)
+ }
+ if sigStoreDir == "" {
+ if rootless.IsRootless() {
+ sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore")
+ } else {
+ registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs)
+ if registryInfo != nil {
+ if sigStoreDir = registryInfo.SigStoreStaging; sigStoreDir == "" {
+ sigStoreDir = registryInfo.SigStore
+
+ }
+ }
+ }
+ }
+ sigStoreDir, err = isValidSigStoreDir(sigStoreDir)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreDir)
+ }
+ repos, err := newImage.RepoDigests()
+ if err != nil {
+ return nil, errors.Wrapf(err, "error calculating repo digests for %s", signimage)
+ }
+ if len(repos) == 0 {
+ logrus.Errorf("no repodigests associated with the image %s", signimage)
+ continue
+ }
+
+ // create signature
+ newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error creating new signature")
+ }
+
+ trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0])
+ sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1))
+ if err := os.MkdirAll(sigStoreDir, 0751); err != nil {
+ // The directory is allowed to exist
+ if !os.IsExist(err) {
+ logrus.Errorf("error creating directory %s: %s", sigStoreDir, err)
+ continue
+ }
+ }
+ sigFilename, err := getSigFilename(sigStoreDir)
+ if err != nil {
+ logrus.Errorf("error creating sigstore file: %v", err)
+ continue
+ }
+ err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644)
+ if err != nil {
+ logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String())
+ continue
+ }
+ }
+ return nil, nil
+}
+
+func getSigFilename(sigStoreDirPath string) (string, error) {
+ sigFileSuffix := 1
+ sigFiles, err := ioutil.ReadDir(sigStoreDirPath)
+ if err != nil {
+ return "", err
+ }
+ sigFilenames := make(map[string]bool)
+ for _, file := range sigFiles {
+ sigFilenames[file.Name()] = true
+ }
+ for {
+ sigFilename := "signature-" + strconv.Itoa(sigFileSuffix)
+ if _, exists := sigFilenames[sigFilename]; !exists {
+ return sigFilename, nil
+ }
+ sigFileSuffix++
+ }
+}
+
+func isValidSigStoreDir(sigStoreDir string) (string, error) {
+ writeURIs := map[string]bool{"file": true}
+ url, err := url.Parse(sigStoreDir)
+ if err != nil {
+ return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir)
+ }
+ _, exists := writeURIs[url.Scheme]
+ if !exists {
+ return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir)
+ }
+ sigStoreDir = url.Path
+ return sigStoreDir, nil
+}