diff options
229 files changed, 8995 insertions, 7810 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index e56ba9086..a011c9af5 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -24,13 +24,13 @@ env: #### #### Cache-image names to test with (double-quotes around names are critical) #### - FEDORA_NAME: "fedora-34beta" + FEDORA_NAME: "fedora-34" PRIOR_FEDORA_NAME: "fedora-33" UBUNTU_NAME: "ubuntu-2104" PRIOR_UBUNTU_NAME: "ubuntu-2010" # Google-cloud VM Images - IMAGE_SUFFIX: "c6731272010596352" + IMAGE_SUFFIX: "c6032583541653504" FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "ubuntu-${IMAGE_SUFFIX}" diff --git a/.github/workflows/multi-arch-build.yaml b/.github/workflows/multi-arch-build.yaml index e4ab88544..0f8a3df7e 100644 --- a/.github/workflows/multi-arch-build.yaml +++ b/.github/workflows/multi-arch-build.yaml @@ -1,3 +1,8 @@ +--- + +# Please see contrib/podmanimage/README.md for details on the intentions +# of this workflow. + name: build multi-arch images on: @@ -19,6 +24,8 @@ jobs: # build several images (upstream, testing, stable) in parallel strategy: + # By default, failure of one matrix item cancels all others + fail-fast: false matrix: # Builds are located under contrib/podmanimage/<source> directory source: @@ -54,8 +61,10 @@ jobs: push: true tags: localhost:5000/podman/${{ matrix.source }} - # Simple verification that container works + grab version number + # Simple verification that stable images work, and + # also grab version number use in forming the FQIN. - name: amd64 container sniff test + if: matrix.source == 'stable' id: sniff_test run: | VERSION_OUTPUT="$(docker run localhost:5000/podman/${{ matrix.source }} \ @@ -65,69 +74,69 @@ jobs: test -n "$VERSION" echo "::set-output name=version::${VERSION}" - # Generate image FQINs, labels, check whether to push - - name: Generate image information - id: image_info + - name: Generate podman reg. image FQIN(s) + id: podman_reg run: | - VERSION='v${{ steps.sniff_test.outputs.version }}' - # workaround vim syntax-hilighting bug: ' if [[ "${{ matrix.source }}" == 'stable' ]]; then - # quay.io/podman/stable:vX.X.X + # The `podman version` in image just built + VERSION='v${{ steps.sniff_test.outputs.version }}' + # workaround vim syntax-highlight bug: ' + # Image tags previously pushed to quay ALLTAGS=$(skopeo list-tags \ docker://$PODMAN_QUAY_REGISTRY/stable | \ jq -r '.Tags[]') - PUSH="false" - if ! fgrep -qx "$VERSION" <<<"$ALLTAGS"; then - PUSH="true" - fi - FQIN="$PODMAN_QUAY_REGISTRY/stable:$VERSION" - # Only push if version tag does not exist - if [[ "$PUSH" == "true" ]]; then - echo "Will push $FQIN" - echo "::set-output name=podman_push::true" - echo "::set-output name=podman_fqin::${FQIN}" - else - echo "Not pushing, $FQIN already exists." + # New image? Push quay.io/podman/stable:vX.X.X and :latest + if ! fgrep -qx "$VERSION" <<<"$ALLTAGS"; then + # Assume version-tag is also the most up to date (i.e. "latest") + FQIN="$PODMAN_QUAY_REGISTRY/stable:$VERSION,$PODMAN_QUAY_REGISTRY/stable:latest" + else # Not a new version-tagged image + # Assume other contents changed, so this is the "new" latest. + FQIN="$PODMAN_QUAY_REGISTRY/stable:latest" fi - - # quay.io/containers/podman:vX.X.X - unset ALLTAGS + elif [[ "${{ matrix.source }}" == 'testing' ]]; then + # Assume some contents changed, always push latest testing. + FQIN="$PODMAN_QUAY_REGISTRY/testing:latest" + elif [[ "${{ matrix.source }}" == 'upstream' ]]; then + # Assume some contents changed, always push latest upstream. + FQIN="$PODMAN_QUAY_REGISTRY/upstream:latest" + else + echo "::error::Unknown matrix item '${{ matrix.source }}'" + exit 1 + fi + echo "::warning::Pushing $FQIN" + echo "::set-output name=fqin::${FQIN}" + echo '::set-output name=push::true' + + # This is substantially the same as the above step, except the + # $CONTAINERS_QUAY_REGISTRY is used and the "testing" + # flavor is never pushed. + - name: Generate containers reg. image FQIN(s) + if: matrix.source != 'testing' + id: containers_reg + run: | + if [[ "${{ matrix.source }}" == 'stable' ]]; then + VERSION='v${{ steps.sniff_test.outputs.version }}' + # workaround vim syntax-highlight bug: ' ALLTAGS=$(skopeo list-tags \ docker://$CONTAINERS_QUAY_REGISTRY/podman | \ jq -r '.Tags[]') - PUSH="false" - if ! fgrep -qx "$VERSION" <<<"$ALLTAGS"; then - PUSH="true" - fi - FQIN="$CONTAINERS_QUAY_REGISTRY/podman:$VERSION" - # Only push if version tag does not exist - if [[ "$PUSH" == "true" ]]; then - echo "Will push $FQIN" - echo "::set-output name=containers_push::true" - echo "::set-output name=containers_fqin::$FQIN" - else - echo "Not pushing, $FQIN already exists." + # New image? Push quay.io/containers/podman:vX.X.X and :latest + if ! fgrep -qx "$VERSION" <<<"$ALLTAGS"; then + FQIN="$CONTAINERS_QUAY_REGISTRY/podman:$VERSION,$CONTAINERS_QUAY_REGISTRY/podman:latest" + else # Not a new version-tagged image, but contents may be updated + FQIN="$CONTAINERS_QUAY_REGISTRY/podman:latest" fi - elif [[ "${{ matrix.source }}" == 'testing' ]]; then - P_FQIN="$PODMAN_QUAY_REGISTRY/testing:master" - echo "Will push $P_FQIN" - echo "::set-output name=podman_fqin::${P_FQIN}" - echo '::set-output name=podman_push::true' elif [[ "${{ matrix.source }}" == 'upstream' ]]; then - P_FQIN="$PODMAN_QUAY_REGISTRY/upstream:master" - C_FQIN="$CONTAINERS_QUAY_REGISTRY/podman:master" - echo "Will push $P_FQIN and $C_FQIN" - echo "::set-output name=podman_fqin::${P_FQIN}" - echo "::set-output name=containers_fqin::${C_FQIN}" - # Always push 'master' tag - echo '::set-output name=podman_push::true' - echo '::set-output name=containers_push::true' + FQIN="$CONTAINERS_QUAY_REGISTRY/podman:latest" else - echo "::error ::Unknown matrix value ${{ matrix.source }}" + echo "::error::Unknown matrix item '${{ matrix.source }}'" exit 1 fi + echo "::warning::Pushing $FQIN" + echo "::set-output name=fqin::${FQIN}" + echo '::set-output name=push::true' - name: Define LABELS multi-line env. var. value run: | @@ -153,7 +162,7 @@ jobs: # Push to 'podman' Quay repo for stable, testing. and upstream - name: Login to 'podman' Quay registry uses: docker/login-action@v1 - if: ${{ steps.image_info.outputs.podman_push == 'true' }} + if: steps.podman_reg.outputs.push == 'true' with: registry: ${{ env.PODMAN_QUAY_REGISTRY }} # N/B: Secrets are not passed to workflows that are triggered @@ -163,7 +172,7 @@ jobs: - name: Push images to 'podman' Quay uses: docker/build-push-action@v2 - if: ${{ steps.image_info.outputs.podman_push == 'true' }} + if: steps.podman_reg.outputs.push == 'true' with: cache-from: type=registry,ref=localhost:5000/podman/${{ matrix.source }} cache-to: type=inline @@ -171,13 +180,13 @@ jobs: file: ./contrib/podmanimage/${{ matrix.source }}/Dockerfile platforms: ${{ env.PLATFORMS }} push: true - tags: ${{ steps.image_info.outputs.podman_fqin }} + tags: ${{ steps.podman_reg.outputs.fqin }} labels: | ${{ env.LABELS }} # Push to 'containers' Quay repo only stable podman - name: Login to 'containers' Quay registry - if: ${{ steps.image_info.outputs.containers_push == 'true' }} + if: steps.containers_reg.outputs.push == 'true' uses: docker/login-action@v1 with: registry: ${{ env.CONTAINERS_QUAY_REGISTRY}} @@ -185,7 +194,7 @@ jobs: password: ${{ secrets.CONTAINERS_QUAY_PASSWORD }} - name: Push images to 'containers' Quay - if: ${{ steps.image_info.outputs.containers_push == 'true' }} + if: steps.containers_reg.outputs.push == 'true' uses: docker/build-push-action@v2 with: cache-from: type=registry,ref=localhost:5000/podman/${{ matrix.source }} @@ -194,6 +203,6 @@ jobs: file: ./contrib/podmanimage/${{ matrix.source }}/Dockerfile platforms: ${{ env.PLATFORMS }} push: true - tags: ${{ steps.image_info.outputs.containers_fqin }} + tags: ${{ steps.containers_reg.outputs.fqin }} labels: | ${{ env.LABELS }} diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go index 818cd0bbd..dcef1a151 100644 --- a/cmd/podman/common/createparse.go +++ b/cmd/podman/common/createparse.go @@ -1,7 +1,7 @@ package common import ( - "github.com/containers/podman/v3/pkg/util" + "github.com/containers/common/pkg/config" "github.com/pkg/errors" ) @@ -13,7 +13,7 @@ func (c *ContainerCLIOpts) validate() error { return errors.Errorf(`the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"`) } - if _, err := util.ValidatePullType(c.Pull); err != nil { + if _, err := config.ParsePullPolicy(c.Pull); err != nil { return err } diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 3f495e19b..f06869c4e 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -8,15 +8,15 @@ import ( "strings" "github.com/containers/common/pkg/config" - "github.com/containers/image/v5/storage" + storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" - "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" + "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -238,6 +238,8 @@ func createInit(c *cobra.Command) error { return nil } +// TODO: we should let the backend take care of the pull policy (which it +// does!). The code below is at risk of causing regression and code divergence. func pullImage(imageName string) (string, error) { pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull) if err != nil { @@ -252,7 +254,7 @@ func pullImage(imageName string) (string, error) { // Assume we specified a local image without the explicit storage transport. fallthrough - case imageRef.Transport().Name() == storage.Transport.Name(): + case imageRef.Transport().Name() == storageTransport.Transport.Name(): br, err := registry.ImageEngine().Exists(registry.GetContext(), imageName) if err != nil { return "", err @@ -272,15 +274,15 @@ func pullImage(imageName string) (string, error) { } } - if pullPolicy != config.PullImageAlways { + if pullPolicy != config.PullPolicyAlways { logrus.Info("--platform --arch and --os causes the pull policy to be \"always\"") - pullPolicy = config.PullImageAlways + pullPolicy = config.PullPolicyAlways } } - if imageMissing || pullPolicy == config.PullImageAlways { - if pullPolicy == config.PullImageNever { - return "", errors.Wrapf(define.ErrNoSuchImage, "unable to find a name and tag match for %s in repotags", imageName) + if imageMissing || pullPolicy == config.PullPolicyAlways { + if pullPolicy == config.PullPolicyNever { + return "", errors.Wrap(storage.ErrImageUnknown, imageName) } pullReport, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{ Authfile: cliVals.Authfile, diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 9b358db74..8d62dc12f 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -57,6 +57,8 @@ func startFlags(cmd *cobra.Command) { flags.BoolVarP(&startOptions.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") flags.BoolVar(&startOptions.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") + flags.BoolVar(&startOptions.All, "all", false, "Start all containers regardless of their state or configuration") + if registry.IsRemote() { _ = flags.MarkHidden("sig-proxy") } @@ -79,7 +81,7 @@ func init() { } func validateStart(cmd *cobra.Command, args []string) error { - if len(args) == 0 && !startOptions.Latest { + if len(args) == 0 && !startOptions.Latest && !startOptions.All { return errors.New("start requires at least one argument") } if len(args) > 0 && startOptions.Latest { @@ -88,6 +90,12 @@ func validateStart(cmd *cobra.Command, args []string) error { if len(args) > 1 && startOptions.Attach { return errors.Errorf("you cannot start and attach multiple containers at once") } + if (len(args) > 0 || startOptions.Latest) && startOptions.All { + return errors.Errorf("either start all containers or the container(s) provided in the arguments") + } + if startOptions.Attach && startOptions.All { + return errors.Errorf("you cannot start and attach all containers at once") + } return nil } diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go index 6333512d9..c192669a9 100644 --- a/cmd/podman/images/trust_set.go +++ b/cmd/podman/images/trust_set.go @@ -1,10 +1,12 @@ package images import ( + "net/url" + "regexp" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" @@ -53,7 +55,7 @@ File(s) must exist before using this command`) func setTrust(cmd *cobra.Command, args []string) error { validTrustTypes := []string{"accept", "insecureAcceptAnything", "reject", "signedBy"} - valid, err := image.IsValidImageURI(args[0]) + valid, err := isValidImageURI(args[0]) if err != nil || !valid { return err } @@ -63,3 +65,23 @@ func setTrust(cmd *cobra.Command, args []string) error { } return registry.ImageEngine().SetTrust(registry.Context(), args, setOptions) } + +// isValidImageURI checks if image name has valid format +func isValidImageURI(imguri string) (bool, error) { + uri := "http://" + imguri + u, err := url.Parse(uri) + if err != nil { + return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + } + reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`) + ret := reg.FindAllString(u.Host, -1) + if len(ret) == 0 { + return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + } + reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`) + ret = reg.FindAllString(u.Fragment, -1) + if len(ret) == 0 { + return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + } + return true, nil +} diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go index 82e155909..2499dc2e8 100644 --- a/cmd/podman/manifest/add.go +++ b/cmd/podman/manifest/add.go @@ -94,6 +94,8 @@ func add(cmd *cobra.Command, args []string) error { return err } + // FIXME: (@vrothberg) this interface confuses me a lot. Why are they + // not two arguments? manifestAddOpts.Images = []string{args[1], args[0]} if manifestAddOpts.CredentialsCLI != "" { diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 30d6d86f0..fe382bdfb 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -2,6 +2,7 @@ package pods import ( "fmt" + "net" "os" "github.com/containers/common/pkg/auth" @@ -27,6 +28,7 @@ type playKubeOptionsWrapper struct { } var ( + macs []string // https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/ defaultSeccompRoot = "/var/lib/kubelet/seccomp" kubeOptions = playKubeOptionsWrapper{} @@ -61,6 +63,10 @@ func init() { flags.StringVar(&kubeOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") _ = kubeCmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + staticMACFlagName := "mac-address" + flags.StringSliceVar(&macs, staticMACFlagName, nil, "Static MAC addresses to assign to the pods") + _ = kubeCmd.RegisterFlagCompletionFunc(staticMACFlagName, completion.AutocompleteNone) + networkFlagName := "network" flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) @@ -128,6 +134,15 @@ func kube(cmd *cobra.Command, args []string) error { if yamlfile == "-" { yamlfile = "/dev/stdin" } + + for _, mac := range macs { + m, err := net.ParseMAC(mac) + if err != nil { + return err + } + kubeOptions.StaticMACs = append(kubeOptions.StaticMACs, m) + } + report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), yamlfile, kubeOptions.PlayKubeOptions) if err != nil { return err diff --git a/contrib/cirrus/pr-should-include-tests b/contrib/cirrus/pr-should-include-tests index 9ccac17a3..392136fdb 100755 --- a/contrib/cirrus/pr-should-include-tests +++ b/contrib/cirrus/pr-should-include-tests @@ -39,6 +39,7 @@ filtered_changes=$(git diff --name-status $base $head | fgrep -vx go.mod | fgrep -vx go.sum | egrep -v '^[^/]+\.md$' | + egrep -v '^.github' | egrep -v '^contrib/' | egrep -v '^docs/' | egrep -v '^hack/' | diff --git a/contrib/podmanimage/README.md b/contrib/podmanimage/README.md index 7641f6c7e..32590d185 100644 --- a/contrib/podmanimage/README.md +++ b/contrib/podmanimage/README.md @@ -16,11 +16,24 @@ default to `/`. The container images are: - * quay.io/containers/podman - This image is built using the latest stable version of Podman in a Fedora based container. Built with [podmanimage/stable/Dockerfile](stable/Dockerfile). - * quay.io/podman/stable - This image is built using the latest stable version of Podman in a Fedora based container. Built with [podmanimage/stable/Dockerfile](stable/Dockerfile). - * quay.io/podman/upstream - This image is built using the latest code found in this GitHub repository. When someone creates a commit and pushes it, the image is created. Due to that the image changes frequently and is not guaranteed to be stable. Built with [podmanimage/upstream/Dockerfile](upstream/Dockerfile). - * quay.io/podman/testing - This image is built using the latest version of Podman that is or was in updates testing for Fedora. At times this may be the same as the stable image. This container image will primarily be used by the development teams for verification testing when a new package is created. Built with [podmanimage/testing/Dockerfile](testing/Dockerfile). - * quay.io/podman/stable:version - This image is built manually using a Fedora based container. An RPM is first pulled from the [Fedora Updates System](https://bodhi.fedoraproject.org/) and the image is built from there. For more details, see the Containerfile used to build it, [podmanimage/stable/manual/Containerfile](stable/manual/Containerfile). + * `quay.io/containers/podman:<version>` and `quay.io/podman/stable:<version>` - + These images are built when a new podman version becomes available in + Fedora. These images are intended to be unchanging and stable, they will + never be updated by automation once they've been pushed. For build + details, see the configuration used to build it, + [podmanimage/stable/Dockerfile](stable/Dockerfile). + * `quay.io/containers/podman:latest` and `quay.io/podman/stable:latest` - + Built daily using the same Containerfile as above. The podman version + will remain the "latest" available in Fedora, however the other image + contents may vary compared to the version-tagged images. + * `quay.io/podman/testing:latest` - This image is built daily, using the + latest version of Podman that was in the Fedora `updates-testing` repository. + The image is Built with [podmanimage/testing/Dockerfile](testing/Dockerfile). + * `quay.io/podman/upstream:latest` - This image is built daily using the latest + code found in this GitHub repository. Due to the image changing frequently, + it's not guaranteed to be stable or even executable. The image is built with + [podmanimage/upstream/Dockerfile](upstream/Dockerfile). + ## Sample Usage diff --git a/contrib/podmanimage/stable/manual/Containerfile b/contrib/podmanimage/stable/manual/Containerfile deleted file mode 100644 index fb4d1adc0..000000000 --- a/contrib/podmanimage/stable/manual/Containerfile +++ /dev/null @@ -1,36 +0,0 @@ -# stable/manual/Containerfile -# -# Build a Podman container image from the latest -# stable version of Podman on the Fedora Updates System. -# https://bodhi.fedoraproject.org/updates/?search=podman -# This image can be used to create a secured container -# that runs safely with privileges within the container. -# This Containerfile builds version 1.7.0, the version and -# the RPM name would need to be adjusted before a run as -# appropriate. -# -# To use, first copy an rpm file from bodhi to `/root/tmp` -# and then run: -# 'podman build -f ./Containerfile -t quay.io/podman/stable:v1.7.0 .' -# -# Once complete run: -# `podman push quay.io/stable:v1.7.0 docker://quay.io/podman/stable:v1.7.0` -# -# Start Build Process using the latest Fedora -FROM registry.fedoraproject.org/fedora:latest - -# Don't include container-selinux and remove -# directories used by dnf that are just taking -# up space. -# -COPY /tmp/podman-1.7.0-3.fc30.x86_64.rpm /tmp -RUN yum -y install /tmp/podman-1.7.0-3.fc30.x86_64.rpm fuse-overlayfs --exclude container-selinux; rm -rf /var/cache /var/log/dnf* /var/log/yum.* /tmp/podman*.rpm - -ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ - -# chmod containers.conf and adjust storage.conf to enable Fuse storage. -RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock - - -ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/docs/README.md b/docs/README.md index 83f5c79a3..a00b8f39c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,7 @@ link on that page. | ------------------------------------ | --------------------------- | | docs/remote-docs.sh | Read the docs/source/markdown files and format for each platform | | docs/links-to-html.lua | pandoc filter to do aliases for html files | +| docs/use-pagetitle.lua | pandoc filter to set html document title | ## API Reference diff --git a/docs/remote-docs.sh b/docs/remote-docs.sh index 939c7264c..b1682e9cd 100755 --- a/docs/remote-docs.sh +++ b/docs/remote-docs.sh @@ -80,7 +80,10 @@ function html_fn() { local link=$(sed -e 's?.so man1/\(.*\)?\1?' <$dir/links/${file%.md}) markdown=$dir/$link.md fi - pandoc --ascii --lua-filter=docs/links-to-html.lua -o $TARGET/${file%%.*}.html $markdown + pandoc --ascii --standalone \ + --lua-filter=docs/links-to-html.lua \ + --lua-filter=docs/use-pagetitle.lua \ + -o $TARGET/${file%%.*}.html $markdown } # Run 'podman help' (possibly against a subcommand, e.g. 'podman help image') diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index 791e2d907..9fc4ffb5b 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -381,12 +381,6 @@ BUILDAH\_LAYERS environment variable. `export BUILDAH_LAYERS=true` Log output which would be sent to standard output and standard error to the specified file instead of to standard output and standard error. -#### **\-\-loglevel**=*number* - -Adjust the logging level up or down. Valid option values range from -2 to 3, -with 3 being roughly equivalent to using the global *--debug* option, and -values below 0 omitting even error messages which accompany fatal errors. - #### **\-\-manifest** "manifest" Name of the manifest list to which the image will be added. Creates the manifest list @@ -490,6 +484,18 @@ commands specified by the **RUN** instruction. Note: You can also override the default runtime by setting the BUILDAH\_RUNTIME environment variable. `export BUILDAH_RUNTIME=/usr/local/bin/runc` + +#### **\-\-secret**=**id=id,src=path** + +Pass secret information to be used in the Containerfile for building images +in a safe way that will not end up stored in the final image, or be seen in other stages. +The secret will be mounted in the container at the default location of `/run/secrets/id`. + +To later use the secret, use the --mount flag in a `RUN` instruction within a `Containerfile`: + +`RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret` + + #### **\-\-security-opt**=*option* Security Options diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 1074c27f8..ab2019139 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -70,6 +70,10 @@ Assign a static ip address to the pod. This option can be specified several time Set logging driver for all created containers. +#### **\-\-mac-address**=*MAC address* + +Assign a static mac address to the pod. This option can be specified several times when play kube creates more than one pod. + #### **\-\-network**=*networks*, **\-\-net** A comma-separated list of the names of CNI networks the pod should join. diff --git a/docs/source/markdown/podman-start.1.md b/docs/source/markdown/podman-start.1.md index 1822eab34..626e6e368 100644 --- a/docs/source/markdown/podman-start.1.md +++ b/docs/source/markdown/podman-start.1.md @@ -38,6 +38,10 @@ to run containers such as CRI-O, the last started container could be from either Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true* when attaching, *false* otherwise. +#### **\-\-all** + +Start all the containers created by Podman, default is only running containers. + ## EXAMPLE podman start mywebserver diff --git a/docs/use-pagetitle.lua b/docs/use-pagetitle.lua new file mode 100644 index 000000000..ebc92641d --- /dev/null +++ b/docs/use-pagetitle.lua @@ -0,0 +1,14 @@ +local List = require("pandoc.List") + +function Meta(m) + -- Use pagetitle instead of title (prevents pandoc inserting a <H1> title) + m.pagetitle = m.title + m.title = nil + + if m.pagetitle ~= nil and m.pagetitle.t == "MetaInlines" then + -- Add suffix to match the Sphinx HTML documentation + List.extend(m.pagetitle, {pandoc.Str" \u{2014} Podman documentation"}) + end + + return m +end @@ -11,8 +11,8 @@ require ( github.com/container-orchestrated-devices/container-device-interface v0.0.0-20210325223243-f99e8b6c10b9 github.com/containernetworking/cni v0.8.1 github.com/containernetworking/plugins v0.9.1 - github.com/containers/buildah v1.20.1-0.20210402144408-36a37402d0c8 - github.com/containers/common v0.37.0 + github.com/containers/buildah v1.20.2-0.20210504130217-903dc56408ac + github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.11.1 github.com/containers/ocicrypt v1.1.1 @@ -58,7 +58,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 - github.com/uber/jaeger-client-go v2.27.0+incompatible + github.com/uber/jaeger-client-go v2.28.0+incompatible github.com/vbauerster/mpb/v6 v6.0.3 github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 go.etcd.io/bbolt v1.3.5 @@ -192,31 +192,24 @@ github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHV github.com/containernetworking/plugins v0.8.7/go.mod h1:R7lXeZaBzpfqapcAbHRW8/CYwm0dHzbz0XEjofx0uB0= github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9r2Quag7HMLV8= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/buildah v1.20.1-0.20210402144408-36a37402d0c8 h1:RlqbDlfE3+qrq4bNTZG7NVPqCDzfZrgE/yicu0VAykQ= -github.com/containers/buildah v1.20.1-0.20210402144408-36a37402d0c8/go.mod h1:iowyscoAC5jwNDhs3c5CLGdBZ9FJk5UOoN2I5TdmXFs= -github.com/containers/common v0.35.4/go.mod h1:rMzxgD7nMGw++cEbsp+NZv0UJO4rgXbm7F7IbJPTwIE= -github.com/containers/common v0.37.0 h1:RRyR8FITTJXfrF7J9KXKSplywY4zsXoA2kuQXMaUaNo= -github.com/containers/common v0.37.0/go.mod h1:dgbJcccCPTmncqxhma56+XW+6d5VzqGF6jtkMHyu3v0= +github.com/containers/buildah v1.20.2-0.20210504130217-903dc56408ac h1:rPQTF+1lz+F4uTZgfk2pwqGcEEg9mPSWK58UncsqsrA= +github.com/containers/buildah v1.20.2-0.20210504130217-903dc56408ac/go.mod h1:0hqcxPCNk/lit/SwBQoXXymCbp2LUa07U0cwrn/T1c0= +github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce h1:e7VNmGqwfUQkw+D5bms262x1HYqxfN9/+t5SoaFnwTk= +github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce/go.mod h1:JjU+yvzIGyx8ZsY8nyf7snzs4VSNh1eIaYsqoSKBoRw= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= -github.com/containers/image/v5 v5.10.5/go.mod h1:SgIbWEedCNBbn2FI5cH0/jed1Ecy2s8XK5zTxvJTzII= github.com/containers/image/v5 v5.11.1 h1:mNybUvU6zXUwcMsQaa3n+Idsru5pV+GE7k4oRuPzYi0= github.com/containers/image/v5 v5.11.1/go.mod h1:HC9lhJ/Nz5v3w/5Co7H431kLlgzlVlOC+auD/er3OqE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQj8jcy0EVG6g= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1 h1:prL8l9w3ntVqXvNH1CiNn5ENjcCnr38JqpSyvKKB4GI= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/psgo v1.5.2 h1:3aoozst/GIwsrr/5jnFy3FrJay98uujPCu9lTuSZ/Cw= github.com/containers/psgo v1.5.2/go.mod h1:2ubh0SsreMZjSXW1Hif58JrEcFudQyIy9EzPUWfawVU= github.com/containers/storage v1.23.5/go.mod h1:ha26Q6ngehFNhf3AWoXldvAvwI4jFe3ETQAf/CeZPyM= -github.com/containers/storage v1.24.8/go.mod h1:YC+2pY8SkfEAcZkwycxYbpK8EiRbx5soPPwz9dxe4IQ= -github.com/containers/storage v1.28.0/go.mod h1:ixAwO7Bj31cigqPEG7aCz+PYmxkDxbIFdUFioYdxbzI= -github.com/containers/storage v1.28.1/go.mod h1:5bwiMh2LkrN3AWIfDFMH7A/xbVNLcve+oeXYvHvW8cc= github.com/containers/storage v1.29.0/go.mod h1:u84RU4CCufGeJBNTRNwMB+FoE+AiFeFw4SsMoqAOeCM= -github.com/containers/storage v1.30.0/go.mod h1:M/xn0pg6ReYFrLtWl5YELI/a4Xjq+Z3e5GJxQrJCcDI= github.com/containers/storage v1.30.1 h1:+87sZDoUp0uNsP45dWypHTWTEoy0eNDgFYjTU1XIRVQ= github.com/containers/storage v1.30.1/go.mod h1:NDJkiwxnSHD1Is+4DGcyR3SIEYSDOa0xnAW+uGQFx9E= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -264,13 +257,14 @@ github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1 h1:j6vGfla github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1/go.mod h1:QS1XzqZLcDniNYrN7EZefq3wIyb/M2WmJbql4ZKoc1Q= github.com/digitalocean/go-qemu v0.0.0-20210209191958-152a1535e49f h1:N2HvbwONtcvzegFxOAgGt15JsajIk5QzY3j5X3VzFDI= github.com/digitalocean/go-qemu v0.0.0-20210209191958-152a1535e49f/go.mod h1:IetBE52JfFxK46p2n2Rqm+p5Gx1gpu2hRHsrbnPOWZQ= +github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWhkNRq8= +github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v17.12.0-ce-rc1.0.20201020191947-73dc6a680cdd+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible h1:Yu2uGErhwEoOT/OxAFe+/SiJCqRLs+pgcS5XKrDXnG4= github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= @@ -473,10 +467,12 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/insomniacslk/dhcp v0.0.0-20210120172423-cc9239ac6294/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI= -github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 h1:rw3IAne6CDuVFlZbPOkA7bhxlqawFh7RJJ+CejfMaxE= -github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee h1:PAXLXk1heNZ5yokbMBpVLZQxo43wCZxRwl00mX+dd44= +github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jamescun/tuntap v0.0.0-20190712092105-cb1fb277045c/go.mod h1:zzwpsgcYhzzIP5WyF8g9ivCv38cY9uAV9Gu0m3lThhE= +github.com/jinzhu/copier v0.3.0 h1:P5zN9OYSxmtzZmwgcVmt5Iu8egfP53BGMPAFgEksKPI= +github.com/jinzhu/copier v0.3.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -501,11 +497,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.12.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= @@ -540,7 +532,6 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -619,7 +610,6 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= -github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= @@ -664,8 +654,8 @@ github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwy github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/openshift/imagebuilder v1.2.0 h1:uoZFjJICLlTMjlAL/UG2PA2kM8RjAsVflGfHJK7MMDk= -github.com/openshift/imagebuilder v1.2.0/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= +github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656 h1:WaxyNFpmIDu4i6so9r6LVFIbSaXqsj8oitMitt86ae4= +github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -792,9 +782,8 @@ github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY= -github.com/uber/jaeger-client-go v2.27.0+incompatible h1:6WVONolFJiB8Vx9bq4z9ddyV/SXSpfvvtb7Yl/TGHiE= -github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/uber/jaeger-client-go v2.28.0+incompatible h1:G4QSBfvPKvg5ZM2j9MrJFdfI5iSljY/WnJqOGFao6HI= +github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -804,7 +793,6 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE= github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= -github.com/vbauerster/mpb/v5 v5.4.0/go.mod h1:fi4wVo7BVQ22QcvFObm+VwliQXlV1eBT8JDaKXR4JGI= github.com/vbauerster/mpb/v6 v6.0.3 h1:j+twHHhSUe8aXWaT/27E98G5cSBeqEuJSVCMjmLg0PI= github.com/vbauerster/mpb/v6 v6.0.3/go.mod h1:5luBx4rDLWxpA4t6I5sdeeQuZhqDxc+wr5Nqf35+tnM= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= @@ -859,11 +847,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -952,7 +938,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= @@ -1027,7 +1012,6 @@ golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1036,7 +1020,6 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/libpod/container_commit.go b/libpod/container_commit.go index 22da0c566..c1dd42942 100644 --- a/libpod/container_commit.go +++ b/libpod/container_commit.go @@ -6,12 +6,11 @@ import ( "strings" "github.com/containers/buildah" - "github.com/containers/buildah/util" + "github.com/containers/common/libimage" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/libpod/image" libpodutil "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -32,11 +31,7 @@ type ContainerCommitOptions struct { // Commit commits the changes between a container and its image, creating a new // image -func (c *Container) Commit(ctx context.Context, destImage string, options ContainerCommitOptions) (*image.Image, error) { - var ( - imageRef types.ImageReference - ) - +func (c *Container) Commit(ctx context.Context, destImage string, options ContainerCommitOptions) (*libimage.Image, error) { if c.config.Rootfs != "" { return nil, errors.Errorf("cannot commit a container that uses an exploded rootfs") } @@ -61,7 +56,6 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai }() } - sc := image.GetSystemContext(options.SignaturePolicyPath, "", false) builderOptions := buildah.ImportOptions{ Container: c.ID(), SignaturePolicyPath: options.SignaturePolicyPath, @@ -69,7 +63,7 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai commitOptions := buildah.CommitOptions{ SignaturePolicyPath: options.SignaturePolicyPath, ReportWriter: options.ReportWriter, - SystemContext: sc, + SystemContext: c.runtime.imageContext, PreferredManifestType: options.PreferredManifestType, } importBuilder, err := buildah.ImportBuilder(ctx, c.runtime.store, builderOptions) @@ -191,20 +185,28 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai importBuilder.SetOnBuild(onbuild) } - candidates, _, _, err := util.ResolveName(destImage, "", sc, c.runtime.store) - if err != nil { - return nil, errors.Wrapf(err, "error resolving name %q", destImage) - } - if len(candidates) > 0 { - imageRef, err = is.Transport.ParseStoreReference(c.runtime.store, candidates[0]) + var commitRef types.ImageReference + if destImage != "" { + // Now resolve the name. + resolvedImageName, err := c.runtime.LibimageRuntime().ResolveName(destImage) + if err != nil { + return nil, err + } + + imageRef, err := is.Transport.ParseStoreReference(c.runtime.store, resolvedImageName) if err != nil { return nil, errors.Wrapf(err, "error parsing target image name %q", destImage) } + commitRef = imageRef } - id, _, _, err := importBuilder.Commit(ctx, imageRef, commitOptions) + id, _, _, err := importBuilder.Commit(ctx, commitRef, commitOptions) if err != nil { return nil, err } defer c.newContainerEvent(events.Commit) - return c.runtime.imageRuntime.NewFromLocal(id) + img, _, err := c.runtime.libimageRuntime.LookupImage(id, nil) + if err != nil { + return nil, err + } + return img, nil } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 80c00a622..132012404 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1842,7 +1842,7 @@ func (c *Container) cleanup(ctx context.Context) error { // Unmount image volumes for _, v := range c.config.ImageVolumes { - img, err := c.runtime.ImageRuntime().NewFromLocal(v.Source) + img, _, err := c.runtime.LibimageRuntime().LookupImage(v.Source, nil) if err != nil { if lastError == nil { lastError = err diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index f4762b5ff..f87e845cb 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -465,11 +465,11 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { // Add image volumes as overlay mounts for _, volume := range c.config.ImageVolumes { // Mount the specified image. - img, err := c.runtime.ImageRuntime().NewFromLocal(volume.Source) + img, _, err := c.runtime.LibimageRuntime().LookupImage(volume.Source, nil) if err != nil { return nil, errors.Wrapf(err, "error creating image volume %q:%q", volume.Source, volume.Dest) } - mountPoint, err := img.Mount(nil, "") + mountPoint, err := img.Mount(ctx, nil, "") if err != nil { return nil, errors.Wrapf(err, "error mounting image volume %q:%q", volume.Source, volume.Dest) } @@ -2224,8 +2224,19 @@ func (c *Container) getOCICgroupPath() (string, error) { } cgroupManager := c.CgroupManager() switch { - case (rootless.IsRootless() && (cgroupManager == config.CgroupfsCgroupsManager || !unified)) || c.config.NoCgroups: + case c.config.NoCgroups: return "", nil + case (rootless.IsRootless() && (cgroupManager == config.CgroupfsCgroupsManager || !unified)): + if c.config.CgroupParent == CgroupfsDefaultCgroupParent { + // old versions of podman were setting the CgroupParent to CgroupfsDefaultCgroupParent + // by default. Avoid breaking these versions and check whether the cgroup parent is + // set to the default and in this case enable the old behavior. It should not be a real + // problem because the default CgroupParent is usually owned by root so rootless users + // cannot access it. + // This check might be lifted in a future version of Podman. + return "", nil + } + return c.config.CgroupParent, nil case c.config.CgroupsMode == cgroupSplit: if c.config.CgroupParent != "" { return c.config.CgroupParent, nil diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 8d943099b..64c652eec 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -12,15 +12,6 @@ var ( // ErrNoSuchPod indicates the requested pod does not exist ErrNoSuchPod = errors.New("no such pod") - // ErrNoSuchImage indicates the requested image does not exist - ErrNoSuchImage = errors.New("no such image") - - // ErrMultipleImages found multiple name and tag matches - ErrMultipleImages = errors.New("found multiple name and tag matches") - - // ErrNoSuchTag indicates the requested image tag does not exist - ErrNoSuchTag = errors.New("no such tag") - // ErrNoSuchVolume indicates the requested volume does not exist ErrNoSuchVolume = errors.New("no such volume") @@ -174,9 +165,6 @@ var ( // killed, preventing normal operation. ErrConmonDead = errors.New("conmon process killed") - // ErrImageInUse indicates the requested operation failed because the image was in use - ErrImageInUse = errors.New("image is being used") - // ErrNetworkOnPodContainer indicates the user wishes to alter network attributes on a container // in a pod. This cannot be done as the infra container has all the network information ErrNetworkOnPodContainer = errors.New("network cannot be configured when it is shared with a pod") diff --git a/libpod/diff.go b/libpod/diff.go index df1acf4bb..6ce8d809a 100644 --- a/libpod/diff.go +++ b/libpod/diff.go @@ -1,6 +1,7 @@ package libpod import ( + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod/layers" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" @@ -49,7 +50,7 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) { // If the id matches a layer, the top layer id is returned func (r *Runtime) getLayerID(id string) (string, error) { var toLayer string - toImage, err := r.imageRuntime.NewFromLocal(id) + toImage, _, err := r.libimageRuntime.LookupImage(id, &libimage.LookupImageOptions{IgnorePlatform: true}) if err == nil { return toImage.TopLayer(), nil } diff --git a/libpod/image/config.go b/libpod/image/config.go deleted file mode 100644 index efd83d343..000000000 --- a/libpod/image/config.go +++ /dev/null @@ -1,14 +0,0 @@ -package image - -const ( - // LatestTag describes the tag used to refer to the latest version - // of an image - LatestTag = "latest" -) - -// ImageDeleteResponse is the response for removing an image from storage and containers -// what was untagged vs actually removed -type ImageDeleteResponse struct { //nolint - Untagged []string `json:"untagged"` - Deleted string `json:"deleted"` -} diff --git a/libpod/image/df.go b/libpod/image/df.go deleted file mode 100644 index 231d28df4..000000000 --- a/libpod/image/df.go +++ /dev/null @@ -1,126 +0,0 @@ -package image - -import ( - "context" - "time" - - "github.com/containers/image/v5/docker/reference" -) - -// DiskUsageStat gives disk-usage statistics for a specific image. -type DiskUsageStat struct { - // ID of the image. - ID string - // Repository of the first recorded name of the image. - Repository string - // Tag of the first recorded name of the image. - Tag string - // Created is the creation time of the image. - Created time.Time - // SharedSize is the amount of space shared with another image. - SharedSize uint64 - // UniqueSize is the amount of space used only by this image. - UniqueSize uint64 - // Size is the total size of the image (i.e., the sum of the shared and - // unique size). - Size uint64 - // Number of containers using the image. - Containers int -} - -// DiskUsage returns disk-usage statistics for the specified slice of images. -func (ir *Runtime) DiskUsage(ctx context.Context, images []*Image) ([]DiskUsageStat, error) { - stats := make([]DiskUsageStat, len(images)) - - // Build a layerTree to quickly compute (and cache!) parent/child - // relations. - tree, err := ir.layerTree() - if err != nil { - return nil, err - } - - // Calculate the stats for each image. - for i, img := range images { - stat, err := diskUsageForImage(ctx, img, tree) - if err != nil { - return nil, err - } - stats[i] = *stat - } - - return stats, nil -} - -// diskUsageForImage returns the disk-usage statistics for the specified image. -func diskUsageForImage(ctx context.Context, image *Image, tree *layerTree) (*DiskUsageStat, error) { - stat := DiskUsageStat{ - ID: image.ID(), - Created: image.Created(), - } - - // Repository and tag. - var name, repository, tag string - for _, n := range image.Names() { - if len(n) > 0 { - name = n - break - } - } - if len(name) > 0 { - named, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - repository = named.Name() - if tagged, isTagged := named.(reference.NamedTagged); isTagged { - tag = tagged.Tag() - } - } else { - repository = "<none>" - tag = "<none>" - } - stat.Repository = repository - stat.Tag = tag - - // Shared, unique and total size. - parent, err := tree.parent(ctx, image) - if err != nil { - return nil, err - } - childIDs, err := tree.children(ctx, image, false) - if err != nil { - return nil, err - } - // Optimistically set unique size to the full size of the image. - size, err := image.Size(ctx) - if err != nil { - return nil, err - } - stat.UniqueSize = *size - - if len(childIDs) > 0 { - // If we have children, we share everything. - stat.SharedSize = stat.UniqueSize - stat.UniqueSize = 0 - } else if parent != nil { - // If we have no children but a parent, remove the parent - // (shared) size from the unique one. - size, err := parent.Size(ctx) - if err != nil { - return nil, err - } - stat.UniqueSize -= *size - stat.SharedSize = *size - } - - stat.Size = stat.SharedSize + stat.UniqueSize - - // Number of containers using the image. - containers, err := image.Containers() - if err != nil { - return nil, err - } - stat.Containers = len(containers) - - return &stat, nil -} diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go deleted file mode 100644 index d95234e3d..000000000 --- a/libpod/image/docker_registry_options.go +++ /dev/null @@ -1,75 +0,0 @@ -package image - -import ( - "fmt" - - "github.com/containers/buildah/pkg/parse" - "github.com/containers/image/v5/docker/reference" - "github.com/containers/image/v5/types" - podmanVersion "github.com/containers/podman/v3/version" -) - -// DockerRegistryOptions encapsulates settings that affect how we connect or -// authenticate to a remote registry. -type DockerRegistryOptions struct { - // DockerRegistryCreds is the user name and password to supply in case - // we need to pull an image from a registry, and it requires us to - // authenticate. - DockerRegistryCreds *types.DockerAuthConfig - // DockerCertPath is the location of a directory containing CA - // certificates which will be used to verify the registry's certificate - // (all files with names ending in ".crt"), and possibly client - // certificates and private keys (pairs of files with the same name, - // except for ".cert" and ".key" suffixes). - DockerCertPath string - // DockerInsecureSkipTLSVerify turns off verification of TLS - // certificates and allows connecting to registries without encryption - // - or forces it on even if registries.conf has the registry configured as insecure. - DockerInsecureSkipTLSVerify types.OptionalBool - // If not "", overrides the use of platform.GOOS when choosing an image or verifying OS match. - OSChoice string - // If not "", overrides the use of platform.GOARCH when choosing an image or verifying architecture match. - ArchitectureChoice string - // If not "", overrides_VARIANT_ instead of the running architecture variant for choosing images. - VariantChoice string - // RegistriesConfPath can be used to override the default path of registries.conf. - RegistriesConfPath string -} - -// GetSystemContext constructs a new system context from a parent context. the values in the DockerRegistryOptions, and other parameters. -func (o DockerRegistryOptions) GetSystemContext(parent *types.SystemContext, additionalDockerArchiveTags []reference.NamedTagged) *types.SystemContext { - sc := &types.SystemContext{ - DockerAuthConfig: o.DockerRegistryCreds, - DockerCertPath: o.DockerCertPath, - DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify, - DockerArchiveAdditionalTags: additionalDockerArchiveTags, - OSChoice: o.OSChoice, - ArchitectureChoice: o.ArchitectureChoice, - VariantChoice: o.VariantChoice, - BigFilesTemporaryDir: parse.GetTempDir(), - } - if parent != nil { - sc.SignaturePolicyPath = parent.SignaturePolicyPath - sc.AuthFilePath = parent.AuthFilePath - sc.DirForceCompress = parent.DirForceCompress - sc.DockerRegistryUserAgent = parent.DockerRegistryUserAgent - sc.OSChoice = parent.OSChoice - sc.ArchitectureChoice = parent.ArchitectureChoice - sc.BlobInfoCacheDir = parent.BlobInfoCacheDir - } - return sc -} - -// GetSystemContext Constructs a new containers/image/types.SystemContext{} struct from the given signaturePolicy path -func GetSystemContext(signaturePolicyPath, authFilePath string, forceCompress bool) *types.SystemContext { - sc := &types.SystemContext{} - if signaturePolicyPath != "" { - sc.SignaturePolicyPath = signaturePolicyPath - } - sc.AuthFilePath = authFilePath - sc.DirForceCompress = forceCompress - sc.DockerRegistryUserAgent = fmt.Sprintf("libpod/%s", podmanVersion.Version) - sc.BigFilesTemporaryDir = parse.GetTempDir() - - return sc -} diff --git a/libpod/image/errors.go b/libpod/image/errors.go deleted file mode 100644 index 49f841bf4..000000000 --- a/libpod/image/errors.go +++ /dev/null @@ -1,16 +0,0 @@ -package image - -import ( - "github.com/containers/podman/v3/libpod/define" -) - -var ( - // ErrNoSuchCtr indicates the requested container does not exist - ErrNoSuchCtr = define.ErrNoSuchCtr - // ErrNoSuchPod indicates the requested pod does not exist - ErrNoSuchPod = define.ErrNoSuchPod - // ErrNoSuchImage indicates the requested image does not exist - ErrNoSuchImage = define.ErrNoSuchImage - // ErrNoSuchTag indicates the requested image tag does not exist - ErrNoSuchTag = define.ErrNoSuchTag -) diff --git a/libpod/image/filters.go b/libpod/image/filters.go deleted file mode 100644 index d316c6956..000000000 --- a/libpod/image/filters.go +++ /dev/null @@ -1,196 +0,0 @@ -package image - -import ( - "context" - "fmt" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/containers/podman/v3/pkg/inspect" - "github.com/containers/podman/v3/pkg/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// ResultFilter is a mock function for image filtering -type ResultFilter func(*Image) bool - -// Filter is a function to determine whether an image is included in -// command output. Images to be outputted are tested using the function. A true -// return will include the image, a false return will exclude it. -type Filter func(*Image, *inspect.ImageData) bool - -// CreatedBeforeFilter allows you to filter on images created before -// the given time.Time -func CreatedBeforeFilter(createTime time.Time) ResultFilter { - return func(i *Image) bool { - return i.Created().Before(createTime) - } -} - -// IntermediateFilter returns filter for intermediate images (i.e., images -// with children and no tags). -func (ir *Runtime) IntermediateFilter(ctx context.Context, images []*Image) (ResultFilter, error) { - tree, err := ir.layerTree() - if err != nil { - return nil, err - } - return func(i *Image) bool { - if len(i.Names()) > 0 { - return true - } - children, err := tree.children(ctx, i, false) - if err != nil { - logrus.Error(err.Error()) - return false - } - return len(children) == 0 - }, nil -} - -// CreatedAfterFilter allows you to filter on images created after -// the given time.Time -func CreatedAfterFilter(createTime time.Time) ResultFilter { - return func(i *Image) bool { - return i.Created().After(createTime) - } -} - -// DanglingFilter allows you to filter images for dangling images -func DanglingFilter(danglingImages bool) ResultFilter { - return func(i *Image) bool { - if danglingImages { - return i.Dangling() - } - return !i.Dangling() - } -} - -// ReadOnlyFilter allows you to filter images based on read/only and read/write -func ReadOnlyFilter(readOnly bool) ResultFilter { - return func(i *Image) bool { - if readOnly { - return i.IsReadOnly() - } - return !i.IsReadOnly() - } -} - -// LabelFilter allows you to filter by images labels key and/or value -func LabelFilter(ctx context.Context, filter string) ResultFilter { - // We need to handle both label=key and label=key=value - return func(i *Image) bool { - labels, err := i.Labels(ctx) - if err != nil { - return false - } - return util.MatchLabelFilters([]string{filter}, labels) - } -} - -// ReferenceFilter allows you to filter by image name -// Replacing all '/' with '|' so that filepath.Match() can work -// '|' character is not valid in image name, so this is safe -func ReferenceFilter(ctx context.Context, referenceFilter string) ResultFilter { - filter := fmt.Sprintf("*%s*", referenceFilter) - filter = strings.Replace(filter, "/", "|", -1) - return func(i *Image) bool { - if len(referenceFilter) < 1 { - return true - } - for _, name := range i.Names() { - newName := strings.Replace(name, "/", "|", -1) - match, err := filepath.Match(filter, newName) - if err != nil { - logrus.Errorf("failed to match %s and %s, %q", name, referenceFilter, err) - } - if match { - return true - } - } - return false - } -} - -// IDFilter allows you to filter by image Id -func IDFilter(idFilter string) ResultFilter { - return func(i *Image) bool { - return i.ID() == idFilter - } -} - -// OutputImageFilter allows you to filter by an a specific image name -func OutputImageFilter(userImage *Image) ResultFilter { - return func(i *Image) bool { - return userImage.ID() == i.ID() - } -} - -// FilterImages filters images using a set of predefined filter funcs -func FilterImages(images []*Image, filters []ResultFilter) []*Image { - var filteredImages []*Image - for _, image := range images { - include := true - for _, filter := range filters { - include = include && filter(image) - } - if include { - filteredImages = append(filteredImages, image) - } - } - return filteredImages -} - -// createFilterFuncs returns an array of filter functions based on the user inputs -// and is later used to filter images for output -func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilter, error) { - var filterFuncs []ResultFilter - ctx := context.Background() - for _, filter := range filters { - splitFilter := strings.SplitN(filter, "=", 2) - if len(splitFilter) < 2 { - return nil, errors.Errorf("invalid filter syntax %s", filter) - } - switch splitFilter[0] { - case "before": - before, err := ir.NewFromLocal(splitFilter[1]) - if err != nil { - return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) - } - filterFuncs = append(filterFuncs, CreatedBeforeFilter(before.Created())) - case "since", "after": - after, err := ir.NewFromLocal(splitFilter[1]) - if err != nil { - return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) - } - filterFuncs = append(filterFuncs, CreatedAfterFilter(after.Created())) - case "readonly": - readonly, err := strconv.ParseBool(splitFilter[1]) - if err != nil { - return nil, errors.Wrapf(err, "invalid filter readonly=%s", splitFilter[1]) - } - filterFuncs = append(filterFuncs, ReadOnlyFilter(readonly)) - case "dangling": - danglingImages, err := strconv.ParseBool(splitFilter[1]) - if err != nil { - return nil, errors.Wrapf(err, "invalid filter dangling=%s", splitFilter[1]) - } - filterFuncs = append(filterFuncs, DanglingFilter(danglingImages)) - case "label": - labelFilter := strings.Join(splitFilter[1:], "=") - filterFuncs = append(filterFuncs, LabelFilter(ctx, labelFilter)) - case "reference": - filterFuncs = append(filterFuncs, ReferenceFilter(ctx, splitFilter[1])) - case "id": - filterFuncs = append(filterFuncs, IDFilter(splitFilter[1])) - default: - return nil, errors.Errorf("invalid filter %s ", splitFilter[0]) - } - } - if img != nil { - filterFuncs = append(filterFuncs, OutputImageFilter(img)) - } - return filterFuncs, nil -} diff --git a/libpod/image/image.go b/libpod/image/image.go deleted file mode 100644 index 3c9fb3a37..000000000 --- a/libpod/image/image.go +++ /dev/null @@ -1,1858 +0,0 @@ -package image - -import ( - "context" - "encoding/json" - stderrors "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strings" - "syscall" - "time" - - "github.com/containers/common/pkg/retry" - cp "github.com/containers/image/v5/copy" - "github.com/containers/image/v5/directory" - dockerarchive "github.com/containers/image/v5/docker/archive" - "github.com/containers/image/v5/docker/reference" - "github.com/containers/image/v5/image" - "github.com/containers/image/v5/manifest" - ociarchive "github.com/containers/image/v5/oci/archive" - "github.com/containers/image/v5/oci/layout" - "github.com/containers/image/v5/pkg/shortnames" - is "github.com/containers/image/v5/storage" - "github.com/containers/image/v5/tarball" - "github.com/containers/image/v5/transports" - "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/driver" - "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/pkg/inspect" - "github.com/containers/podman/v3/pkg/registries" - "github.com/containers/podman/v3/pkg/util" - "github.com/containers/storage" - digest "github.com/opencontainers/go-digest" - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// Image is the primary struct for dealing with images -// It is still very much a work in progress -type Image struct { - // Adding these two structs for now but will cull when we near - // completion of this library. - imgRef types.Image - imgSrcRef types.ImageSource - inspect.ImageData - inspect.ImageResult - inspectInfo *types.ImageInspectInfo - InputName string - image *storage.Image - imageruntime *Runtime -} - -// Runtime contains the store -type Runtime struct { - store storage.Store - SignaturePolicyPath string - EventsLogFilePath string - EventsLogger string - Eventer events.Eventer -} - -// InfoImage keep information of Image along with all associated layers -type InfoImage struct { - // ID of image - ID string - // Tags of image - Tags []string - // Layers stores all layers of image. - Layers []LayerInfo -} - -const maxRetry = 3 - -// ImageFilter is a function to determine whether a image is included -// in command output. Images to be outputted are tested using the function. -// A true return will include the image, a false return will exclude it. -type ImageFilter func(*Image) bool //nolint - -// ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store -var ErrRepoTagNotFound = stderrors.New("unable to match user input to any specific repotag") - -// ErrImageIsBareList is the error returned when the image is just a list or index -var ErrImageIsBareList = stderrors.New("image contains a manifest list or image index, but no runnable image") - -// NewImageRuntimeFromStore creates an ImageRuntime based on a provided store -func NewImageRuntimeFromStore(store storage.Store) *Runtime { - return &Runtime{ - store: store, - } -} - -// NewImageRuntimeFromOptions creates an Image Runtime including the store given -// store options -func NewImageRuntimeFromOptions(options storage.StoreOptions) (*Runtime, error) { - store, err := setStore(options) - if err != nil { - return nil, err - } - return NewImageRuntimeFromStore(store), nil -} - -func setStore(options storage.StoreOptions) (storage.Store, error) { - store, err := storage.GetStore(options) - if err != nil { - return nil, err - } - is.Transport.SetStore(store) - return store, nil -} - -// newImage creates a new image object given an "input name" and a storage.Image -func (ir *Runtime) newImage(inputName string, img *storage.Image) *Image { - return &Image{ - InputName: inputName, - imageruntime: ir, - image: img, - } -} - -// newFromStorage creates a new image object from a storage.Image. Its "input name" will be its ID. -func (ir *Runtime) newFromStorage(img *storage.Image) *Image { - return ir.newImage(img.ID, img) -} - -// NewFromLocal creates a new image object that is intended -// to only deal with local images already in the store (or -// its aliases) -func (ir *Runtime) NewFromLocal(name string) (*Image, error) { - updatedInputName, localImage, err := ir.getLocalImage(name) - if err != nil { - return nil, err - } - return ir.newImage(updatedInputName, localImage), nil -} - -// New creates a new image object where the image could be local -// or remote -func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, label *string, pullType util.PullType, progress chan types.ProgressProperties) (*Image, error) { - // We don't know if the image is local or not ... check local first - if pullType != util.PullImageAlways { - newImage, err := ir.NewFromLocal(name) - if err == nil { - return newImage, nil - } else if pullType == util.PullImageNever { - return nil, err - } - } - - // The image is not local - if signaturePolicyPath == "" { - signaturePolicyPath = ir.SignaturePolicyPath - } - imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, &retry.RetryOptions{MaxRetry: maxRetry}, label, progress) - if err != nil { - return nil, err - } - - newImage, err := ir.NewFromLocal(imageName[0]) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving local image after pulling %s", name) - } - return newImage, nil -} - -// SaveImages stores one more images in a multi-image archive. -// Note that only `docker-archive` supports storing multiple -// image. -func (ir *Runtime) SaveImages(ctx context.Context, namesOrIDs []string, format string, outputFile string, quiet, removeSignatures bool) (finalErr error) { - if format != DockerArchive { - return errors.Errorf("multi-image archives are only supported in in the %q format", DockerArchive) - } - - sys := GetSystemContext("", "", false) - - archWriter, err := dockerarchive.NewWriter(sys, outputFile) - if err != nil { - return err - } - defer func() { - err := archWriter.Close() - if err == nil { - return - } - if finalErr == nil { - finalErr = err - return - } - finalErr = errors.Wrap(finalErr, err.Error()) - }() - - // Decide whether c/image's progress bars should use stderr or stdout. - // Use stderr in case we need to be quiet or if the output is set to - // stdout. If the output is set of stdout, any log message there would - // corrupt the tarfile. - writer := os.Stdout - if quiet { - writer = os.Stderr - } - - // extend an image with additional tags - type imageData struct { - *Image - tags []reference.NamedTagged - } - - // Look up the images (and their tags) in the local storage. - imageMap := make(map[string]*imageData) // to group tags for an image - imageQueue := []string{} // to preserve relative image order - for _, nameOrID := range namesOrIDs { - // Look up the name or ID in the local image storage. - localImage, err := ir.NewFromLocal(nameOrID) - if err != nil { - return err - } - id := localImage.ID() - - iData, exists := imageMap[id] - if !exists { - imageQueue = append(imageQueue, id) - iData = &imageData{Image: localImage} - imageMap[id] = iData - } - - // Unless we referred to an ID, add the input as a tag. - if !strings.HasPrefix(id, nameOrID) { - tag, err := NormalizedTag(nameOrID) - if err != nil { - return err - } - refTagged, isTagged := tag.(reference.NamedTagged) - if isTagged { - iData.tags = append(iData.tags, refTagged) - } - } - } - - policyContext, err := getPolicyContext(sys) - if err != nil { - return err - } - defer func() { - if err := policyContext.Destroy(); err != nil { - logrus.Errorf("failed to destroy policy context: %q", err) - } - }() - - // Now copy the images one-by-one. - for _, id := range imageQueue { - dest, err := archWriter.NewReference(nil) - if err != nil { - return err - } - - img := imageMap[id] - copyOptions := getCopyOptions(sys, writer, nil, nil, SigningOptions{RemoveSignatures: removeSignatures}, "", img.tags) - copyOptions.DestinationCtx.SystemRegistriesConfPath = registries.SystemRegistriesConfPath() - - // For copying, we need a source reference that we can create - // from the image. - src, err := is.Transport.NewStoreReference(img.imageruntime.store, nil, id) - if err != nil { - return errors.Wrapf(err, "error getting source imageReference for %q", img.InputName) - } - _, err = cp.Image(ctx, policyContext, dest, src, copyOptions) - if err != nil { - return err - } - } - - return nil -} - -// LoadAllImagesFromDockerArchive loads all images from the docker archive that -// fileName points to. -func (ir *Runtime) LoadAllImagesFromDockerArchive(ctx context.Context, fileName string, signaturePolicyPath string, writer io.Writer) ([]*Image, error) { - if signaturePolicyPath == "" { - signaturePolicyPath = ir.SignaturePolicyPath - } - - sc := GetSystemContext(signaturePolicyPath, "", false) - reader, err := dockerarchive.NewReader(sc, fileName) - if err != nil { - return nil, err - } - - defer func() { - if err := reader.Close(); err != nil { - logrus.Errorf(err.Error()) - } - }() - - refLists, err := reader.List() - if err != nil { - return nil, err - } - - refPairs := []pullRefPair{} - for _, refList := range refLists { - for _, ref := range refList { - pairs, err := ir.getPullRefPairsFromDockerArchiveReference(ctx, reader, ref, sc) - if err != nil { - return nil, err - } - refPairs = append(refPairs, pairs...) - } - } - - goal := pullGoal{ - pullAllPairs: true, - refPairs: refPairs, - } - - defer goal.cleanUp() - imageNames, err := ir.doPullImage(ctx, sc, goal, writer, SigningOptions{}, &DockerRegistryOptions{}, &retry.RetryOptions{}, nil, nil) - if err != nil { - return nil, err - } - - newImages := make([]*Image, 0, len(imageNames)) - for _, name := range imageNames { - newImage, err := ir.NewFromLocal(name) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving local image after pulling %s", name) - } - newImages = append(newImages, newImage) - } - ir.newImageEvent(events.LoadFromArchive, "") - return newImages, nil -} - -// LoadFromArchiveReference creates a new image object for images pulled from a tar archive and the like (podman load) -// This function is needed because it is possible for a tar archive to have multiple tags for one image -func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*Image, error) { - if signaturePolicyPath == "" { - signaturePolicyPath = ir.SignaturePolicyPath - } - - imageNames, err := ir.pullImageFromReference(ctx, srcRef, writer, "", signaturePolicyPath, SigningOptions{}, &DockerRegistryOptions{}, &retry.RetryOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "unable to pull %s", transports.ImageName(srcRef)) - } - - newImages := make([]*Image, 0, len(imageNames)) - for _, name := range imageNames { - newImage, err := ir.NewFromLocal(name) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving local image after pulling %s", name) - } - newImages = append(newImages, newImage) - } - ir.newImageEvent(events.LoadFromArchive, "") - return newImages, nil -} - -// Shutdown closes down the storage and require a bool arg as to -// whether it should do so forcibly. -func (ir *Runtime) Shutdown(force bool) error { - _, err := ir.store.Shutdown(force) - return err -} - -// GetImagesWithFilters gets images with a series of filters applied -func (ir *Runtime) GetImagesWithFilters(filters []string) ([]*Image, error) { - filterFuncs, err := ir.createFilterFuncs(filters, nil) - if err != nil { - return nil, err - } - images, err := ir.GetImages() - if err != nil { - return nil, err - } - return FilterImages(images, filterFuncs), nil -} - -func (i *Image) reloadImage() error { - newImage, err := i.imageruntime.getImage(i.ID()) - if err != nil { - return errors.Wrapf(err, "unable to reload image") - } - i.image = newImage - return nil -} - -// stringSha256 strips sha256 from user input -func stripSha256(name string) string { - if strings.HasPrefix(name, "sha256:") && len(name) > 7 { - return name[7:] - } - return name -} - -// getLocalImage resolves an unknown input describing an image and -// returns an updated input name, and a storage.Image, or an error. It is used by NewFromLocal. -func (ir *Runtime) getLocalImage(inputName string) (string, *storage.Image, error) { - imageError := fmt.Sprintf("unable to find '%s' in local storage", inputName) - if inputName == "" { - return "", nil, errors.Errorf("input name is blank") - } - - // Check if the input name has a transport and if so strip it - dest, err := alltransports.ParseImageName(inputName) - if err == nil && dest.DockerReference() != nil { - inputName = dest.DockerReference().String() - } - - // Early check for fully-qualified images and (short) IDs. - img, err := ir.store.Image(stripSha256(inputName)) - if err == nil { - return inputName, img, nil - } - - // Note that it's crucial to first decompose the image and check if - // it's a fully-qualified one or a "short name". The latter requires - // some normalization with search registries and the - // "localhost/prefix". - decomposedImage, err := decompose(inputName) - if err != nil { - // We may have a storage reference. We can't parse it to a - // reference before. Otherwise, we'd normalize "alpine" to - // "docker.io/library/alpine:latest" which would break the - // order in which we should query local images below. - if ref, err := is.Transport.ParseStoreReference(ir.store, inputName); err == nil { - img, err = is.Transport.GetStoreImage(ir.store, ref) - if err == nil { - return inputName, img, nil - } - } - return "", nil, err - } - - // The specified image is fully qualified, so it doesn't exist in the - // storage. - if decomposedImage.hasRegistry { - // However ... we may still need to normalize to docker.io: - // `docker.io/foo` -> `docker.io/library/foo` - if ref, err := is.Transport.ParseStoreReference(ir.store, inputName); err == nil { - img, err = is.Transport.GetStoreImage(ir.store, ref) - if err == nil { - return inputName, img, nil - } - } - return "", nil, errors.Wrapf(ErrNoSuchImage, imageError) - } - - sys := &types.SystemContext{ - SystemRegistriesConfPath: registries.SystemRegistriesConfPath(), - } - - candidates, err := shortnames.ResolveLocally(sys, inputName) - if err != nil { - return "", nil, err - } - - for _, candidate := range candidates { - img, err := ir.store.Image(candidate.String()) - if err == nil { - return candidate.String(), img, nil - } - } - - // Backwards compat: normalize to docker.io as some users may very well - // rely on that. - ref, err := is.Transport.ParseStoreReference(ir.store, inputName) - if err == nil { - img, err = is.Transport.GetStoreImage(ir.store, ref) - if err == nil { - return inputName, img, nil - } - } - - // Last resort: look at the repotags of all images and try to find a - // match. - images, err := ir.GetImages() - if err != nil { - return "", nil, err - } - - decomposedImage, err = decompose(inputName) - if err != nil { - return "", nil, err - } - repoImage, err := findImageInRepotags(decomposedImage, images) - if err == nil { - return inputName, repoImage, nil - } - - return "", nil, err -} - -// ID returns the image ID as a string -func (i *Image) ID() string { - return i.image.ID -} - -// IsReadOnly returns whether the image ID comes from a local store -func (i *Image) IsReadOnly() bool { - return i.image.ReadOnly -} - -// Digest returns the image's digest -func (i *Image) Digest() digest.Digest { - return i.image.Digest -} - -// Digests returns the image's digests -func (i *Image) Digests() []digest.Digest { - return i.image.Digests -} - -// GetManifest returns the image's manifest as a byte array -// and manifest type as a string. -func (i *Image) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { - imgSrcRef, err := i.toImageSourceRef(ctx) - if err != nil { - return nil, "", err - } - return imgSrcRef.GetManifest(ctx, instanceDigest) -} - -// Manifest returns the image's manifest as a byte array -// and manifest type as a string. -func (i *Image) Manifest(ctx context.Context) ([]byte, string, error) { - imgRef, err := i.toImageRef(ctx) - if err != nil { - return nil, "", err - } - return imgRef.Manifest(ctx) -} - -// Names returns a string array of names associated with the image, which may be a mixture of tags and digests -func (i *Image) Names() []string { - return i.image.Names -} - -// NamesHistory returns a string array of names previously associated with the -// image, which may be a mixture of tags and digests -func (i *Image) NamesHistory() []string { - if len(i.image.Names) > 0 && len(i.image.NamesHistory) > 0 && - // We compare the latest (time-referenced) tags for equality and skip - // it in the history if they match to not display them twice. We have - // to compare like this, because `i.image.Names` (latest last) gets - // appended on retag, whereas `i.image.NamesHistory` gets prepended - // (latest first) - i.image.Names[len(i.image.Names)-1] == i.image.NamesHistory[0] { - return i.image.NamesHistory[1:] - } - return i.image.NamesHistory -} - -// RepoTags returns a string array of repotags associated with the image -func (i *Image) RepoTags() ([]string, error) { - var repoTags []string - for _, name := range i.Names() { - named, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - if tagged, isTagged := named.(reference.NamedTagged); isTagged { - repoTags = append(repoTags, tagged.String()) - } - } - return repoTags, nil -} - -// RepoDigests returns a string array of repodigests associated with the image -func (i *Image) RepoDigests() ([]string, error) { - var repoDigests []string - added := make(map[string]struct{}) - - for _, name := range i.Names() { - for _, imageDigest := range append(i.Digests(), i.Digest()) { - if imageDigest == "" { - continue - } - - named, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - - canonical, err := reference.WithDigest(reference.TrimNamed(named), imageDigest) - if err != nil { - return nil, err - } - - if _, alreadyInList := added[canonical.String()]; !alreadyInList { - repoDigests = append(repoDigests, canonical.String()) - added[canonical.String()] = struct{}{} - } - } - } - sort.Strings(repoDigests) - return repoDigests, nil -} - -// Created returns the time the image was created -func (i *Image) Created() time.Time { - return i.image.Created -} - -// TopLayer returns the top layer id as a string -func (i *Image) TopLayer() string { - return i.image.TopLayer -} - -// Remove an image; container removal for the image must be done -// outside the context of images -// TODO: the force param does nothing as of now. Need to move container -// handling logic here eventually. -func (i *Image) Remove(ctx context.Context, force bool) error { - parent, err := i.GetParent(ctx) - if err != nil { - logrus.Warnf("error determining parent of image: %v, ignoring the error", err) - parent = nil - } - if _, err := i.imageruntime.store.DeleteImage(i.ID(), true); err != nil { - return err - } - i.newImageEvent(events.Remove) - for parent != nil { - nextParent, err := parent.GetParent(ctx) - if err != nil { - return err - } - children, err := parent.GetChildren(ctx) - if err != nil { - return err - } - // Do not remove if image is a base image and is not untagged, or if - // the image has more children. - if len(children) > 0 || len(parent.Names()) > 0 { - return nil - } - id := parent.ID() - if _, err := i.imageruntime.store.DeleteImage(id, true); err != nil { - logrus.Debugf("unable to remove intermediate image %q: %v", id, err) - } else { - fmt.Println(id) - } - parent = nextParent - } - return nil -} - -// getImage retrieves an image matching the given name or hash from system -// storage -// If no matching image can be found, an error is returned -func (ir *Runtime) getImage(image string) (*storage.Image, error) { - var img *storage.Image - ref, err := is.Transport.ParseStoreReference(ir.store, image) - if err == nil { - img, err = is.Transport.GetStoreImage(ir.store, ref) - } - if err != nil { - img2, err2 := ir.store.Image(image) - if err2 != nil { - if ref == nil { - return nil, errors.Wrapf(err, "error parsing reference to image %q", image) - } - return nil, errors.Wrapf(err, "unable to locate image %q", image) - } - img = img2 - } - return img, nil -} - -func (ir *Runtime) ImageNames(id string) ([]string, error) { - myImage, err := ir.getImage(id) - if err != nil { - return nil, errors.Wrapf(err, "error getting image %s ", id) - } - return myImage.Names, nil -} - -// GetImages retrieves all images present in storage -func (ir *Runtime) GetImages() ([]*Image, error) { - return ir.getImages(false) -} - -// GetRWImages retrieves all read/write images present in storage -func (ir *Runtime) GetRWImages() ([]*Image, error) { - return ir.getImages(true) -} - -// getImages retrieves all images present in storage -func (ir *Runtime) getImages(rwOnly bool) ([]*Image, error) { - images, err := ir.store.Images() - if err != nil { - return nil, err - } - newImages := []*Image{} - for _, i := range images { - if rwOnly && i.ReadOnly { - continue - } - // iterating over these, be careful to not iterate on the literal - // pointer. - image := i - img := ir.newFromStorage(&image) - newImages = append(newImages, img) - } - return newImages, nil -} - -// getImageDigest creates an image object and uses the hex value of the digest as the image ID -// for parsing the store reference -func getImageDigest(ctx context.Context, src types.ImageReference, sc *types.SystemContext) (string, error) { - newImg, err := src.NewImage(ctx, sc) - if err != nil { - return "", err - } - defer func() { - if err := newImg.Close(); err != nil { - logrus.Errorf("failed to close image: %q", err) - } - }() - imageDigest := newImg.ConfigInfo().Digest - if err = imageDigest.Validate(); err != nil { - return "", errors.Wrapf(err, "error getting config info") - } - return "@" + imageDigest.Hex(), nil -} - -// NormalizedTag returns the canonical version of tag for use in Image.Names() -func NormalizedTag(tag string) (reference.Named, error) { - decomposedTag, err := decompose(tag) - if err != nil { - return nil, err - } - // If the input doesn't specify a registry, set the registry to localhost - var ref reference.Named - if !decomposedTag.hasRegistry { - ref, err = decomposedTag.referenceWithRegistry(DefaultLocalRegistry) - if err != nil { - return nil, err - } - } else { - ref, err = decomposedTag.normalizedReference() - if err != nil { - return nil, err - } - } - // If the input does not have a tag, we need to add one (latest) - ref = reference.TagNameOnly(ref) - return ref, nil -} - -// TagImage adds a tag to the given image -func (i *Image) TagImage(tag string) error { - if err := i.reloadImage(); err != nil { - return err - } - ref, err := NormalizedTag(tag) - if err != nil { - return err - } - tags := i.Names() - if util.StringInSlice(ref.String(), tags) { - return nil - } - tags = append(tags, ref.String()) - if err := i.imageruntime.store.SetNames(i.ID(), tags); err != nil { - return err - } - if err := i.reloadImage(); err != nil { - return err - } - i.newImageEvent(events.Tag) - return nil -} - -// UntagImage removes the specified tag from the image. -// If the tag does not exist, ErrNoSuchTag is returned. -func (i *Image) UntagImage(tag string) error { - if err := i.reloadImage(); err != nil { - return err - } - - // Normalize the tag as we do with TagImage. - ref, err := NormalizedTag(tag) - if err != nil { - return err - } - tag = ref.String() - - var newTags []string - tags := i.Names() - if !util.StringInSlice(tag, tags) { - return errors.Wrapf(ErrNoSuchTag, "%q", tag) - } - for _, t := range tags { - if tag != t { - newTags = append(newTags, t) - } - } - if err := i.imageruntime.store.SetNames(i.ID(), newTags); err != nil { - return err - } - if err := i.reloadImage(); err != nil { - return err - } - i.newImageEvent(events.Untag) - return nil -} - -// PushImageToHeuristicDestination pushes the given image to "destination", which is heuristically parsed. -// Use PushImageToReference if the destination is known precisely. -func (i *Image) PushImageToHeuristicDestination(ctx context.Context, destination, manifestMIMEType, authFile, digestFile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged, progress chan types.ProgressProperties) error { - if destination == "" { - return errors.Wrapf(syscall.EINVAL, "destination image name must be specified") - } - - // Get the destination Image Reference - dest, err := alltransports.ParseImageName(destination) - if err != nil { - if hasTransport(destination) { - return errors.Wrapf(err, "error getting destination imageReference for %q", destination) - } - // Try adding the images default transport - destination2 := DefaultTransport + destination - dest, err = alltransports.ParseImageName(destination2) - if err != nil { - return err - } - } - return i.PushImageToReference(ctx, dest, manifestMIMEType, authFile, digestFile, signaturePolicyPath, writer, forceCompress, signingOptions, dockerRegistryOptions, additionalDockerArchiveTags, progress) -} - -// PushImageToReference pushes the given image to a location described by the given path -func (i *Image) PushImageToReference(ctx context.Context, dest types.ImageReference, manifestMIMEType, authFile, digestFile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged, progress chan types.ProgressProperties) error { - sc := GetSystemContext(signaturePolicyPath, authFile, forceCompress) - sc.BlobInfoCacheDir = filepath.Join(i.imageruntime.store.GraphRoot(), "cache") - - policyContext, err := getPolicyContext(sc) - if err != nil { - return err - } - defer func() { - if err := policyContext.Destroy(); err != nil { - logrus.Errorf("failed to destroy policy context: %q", err) - } - }() - - // Look up the source image, expecting it to be in local storage - src, err := is.Transport.ParseStoreReference(i.imageruntime.store, i.ID()) - if err != nil { - return errors.Wrapf(err, "error getting source imageReference for %q", i.InputName) - } - copyOptions := getCopyOptions(sc, writer, nil, dockerRegistryOptions, signingOptions, manifestMIMEType, additionalDockerArchiveTags) - copyOptions.DestinationCtx.SystemRegistriesConfPath = registries.SystemRegistriesConfPath() // FIXME: Set this more globally. Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place. - if progress != nil { - copyOptions.Progress = progress - copyOptions.ProgressInterval = time.Second - } - // Copy the image to the remote destination - manifestBytes, err := cp.Image(ctx, policyContext, dest, src, copyOptions) - if err != nil { - return errors.Wrapf(err, "error copying image to the remote destination") - } - digest, err := manifest.Digest(manifestBytes) - if err != nil { - return errors.Wrapf(err, "error computing digest of manifest of new image %q", transports.ImageName(dest)) - } - - logrus.Debugf("Successfully pushed %s with digest %s", transports.ImageName(dest), digest.String()) - - if digestFile != "" { - if err = ioutil.WriteFile(digestFile, []byte(digest.String()), 0644); err != nil { - return errors.Wrapf(err, "failed to write digest to file %q", digestFile) - } - } - i.newImageEvent(events.Push) - return nil -} - -// MatchesID returns a bool based on if the input id -// matches the image's id -// TODO: This isn't used anywhere, so remove it -func (i *Image) MatchesID(id string) bool { - return strings.HasPrefix(i.ID(), id) -} - -// ToImageRef returns an image reference type from an image -// TODO: Hopefully we can remove this exported function for mheon -func (i *Image) ToImageRef(ctx context.Context) (types.Image, error) { - return i.toImageRef(ctx) -} - -// toImageSourceRef returns an ImageSource Reference type from an image -func (i *Image) toImageSourceRef(ctx context.Context) (types.ImageSource, error) { - if i == nil { - return nil, errors.Errorf("cannot convert nil image to image source reference") - } - if i.imgSrcRef == nil { - ref, err := is.Transport.ParseStoreReference(i.imageruntime.store, "@"+i.ID()) - if err != nil { - return nil, errors.Wrapf(err, "error parsing reference to image %q", i.ID()) - } - imgSrcRef, err := ref.NewImageSource(ctx, nil) - if err != nil { - return nil, errors.Wrapf(err, "error reading image %q as image source", i.ID()) - } - i.imgSrcRef = imgSrcRef - } - return i.imgSrcRef, nil -} - -//Size returns the size of the image -func (i *Image) Size(ctx context.Context) (*uint64, error) { - sum, err := i.imageruntime.store.ImageSize(i.ID()) - if err == nil && sum >= 0 { - usum := uint64(sum) - return &usum, nil - } - return nil, errors.Wrap(err, "unable to determine size") -} - -// toImageRef returns an Image Reference type from an image -func (i *Image) toImageRef(ctx context.Context) (types.Image, error) { - if i == nil { - return nil, errors.Errorf("cannot convert nil image to image reference") - } - imgSrcRef, err := i.toImageSourceRef(ctx) - if err != nil { - return nil, err - } - if i.imgRef == nil { - systemContext := &types.SystemContext{} - unparsedDefaultInstance := image.UnparsedInstance(imgSrcRef, nil) - imgRef, err := image.FromUnparsedImage(ctx, systemContext, unparsedDefaultInstance) - if err != nil { - // check for a "tried-to-treat-a-bare-list-like-a-runnable-image" problem, else - // return info about the not-a-bare-list runnable image part of this storage.Image - if manifestBytes, manifestType, err2 := imgSrcRef.GetManifest(ctx, nil); err2 == nil { - if manifest.MIMETypeIsMultiImage(manifestType) { - if list, err3 := manifest.ListFromBlob(manifestBytes, manifestType); err3 == nil { - switch manifestType { - case ociv1.MediaTypeImageIndex: - err = errors.Wrapf(ErrImageIsBareList, "%q is an image index", i.InputName) - case manifest.DockerV2ListMediaType: - err = errors.Wrapf(ErrImageIsBareList, "%q is a manifest list", i.InputName) - default: - err = errors.Wrapf(ErrImageIsBareList, "%q", i.InputName) - } - for _, instanceDigest := range list.Instances() { - instance := instanceDigest - unparsedInstance := image.UnparsedInstance(imgSrcRef, &instance) - if imgRef2, err4 := image.FromUnparsedImage(ctx, systemContext, unparsedInstance); err4 == nil { - imgRef = imgRef2 - err = nil - break - } - } - } - } - } - if err != nil { - return nil, errors.Wrapf(err, "error reading image %q as image", i.ID()) - } - } - i.imgRef = imgRef - } - return i.imgRef, nil -} - -// DriverData gets the driver data from the store on a layer -func (i *Image) DriverData() (*define.DriverData, error) { - return driver.GetDriverData(i.imageruntime.store, i.TopLayer()) -} - -// Layer returns the image's top layer -func (i *Image) Layer() (*storage.Layer, error) { - return i.imageruntime.store.Layer(i.image.TopLayer) -} - -// History contains the history information of an image -type History struct { - ID string `json:"id"` - Created *time.Time `json:"created"` - CreatedBy string `json:"createdBy"` - Size int64 `json:"size"` - Comment string `json:"comment"` - Tags []string `json:"tags"` -} - -// History gets the history of an image and the IDs of images that are part of -// its history -func (i *Image) History(ctx context.Context) ([]*History, error) { - img, err := i.toImageRef(ctx) - if err != nil { - if errors.Cause(err) == ErrImageIsBareList { - return nil, nil - } - return nil, err - } - oci, err := img.OCIConfig(ctx) - if err != nil { - return nil, err - } - - // Build a mapping from top-layer to image ID. - images, err := i.imageruntime.GetImages() - if err != nil { - return nil, err - } - topLayerMap := make(map[string]string) - for _, image := range images { - if _, exists := topLayerMap[image.TopLayer()]; !exists { - topLayerMap[image.TopLayer()] = image.ID() - } - } - - var allHistory []*History - var layer *storage.Layer - - // Check if we have an actual top layer to prevent lookup errors. - if i.TopLayer() != "" { - layer, err = i.imageruntime.store.Layer(i.TopLayer()) - if err != nil { - return nil, err - } - } - - // Iterate in reverse order over the history entries, and lookup the - // corresponding image ID, size and get the next later if needed. - numHistories := len(oci.History) - 1 - for x := numHistories; x >= 0; x-- { - var size int64 - - id := "<missing>" - if x == numHistories { - id = i.ID() - } - if layer != nil { - if !oci.History[x].EmptyLayer { - size = layer.UncompressedSize - } - if imageID, exists := topLayerMap[layer.ID]; exists { - id = imageID - // Delete the entry to avoid reusing it for following history items. - delete(topLayerMap, layer.ID) - } - } - h := History{ - ID: id, - Created: oci.History[x].Created, - CreatedBy: oci.History[x].CreatedBy, - Size: size, - Comment: oci.History[x].Comment, - } - if layer != nil { - h.Tags = layer.Names - } - allHistory = append(allHistory, &h) - - if layer != nil && layer.Parent != "" && !oci.History[x].EmptyLayer { - layer, err = i.imageruntime.store.Layer(layer.Parent) - if err != nil { - return nil, err - } - } - } - - return allHistory, nil -} - -// Dangling returns a bool if the image is "dangling" -func (i *Image) Dangling() bool { - return len(i.Names()) == 0 -} - -// User returns the image's user -func (i *Image) User(ctx context.Context) (string, error) { - imgInspect, err := i.inspect(ctx, false) - if err != nil { - return "", err - } - return imgInspect.Config.User, nil -} - -// StopSignal returns the image's StopSignal -func (i *Image) StopSignal(ctx context.Context) (string, error) { - imgInspect, err := i.inspect(ctx, false) - if err != nil { - return "", err - } - return imgInspect.Config.StopSignal, nil -} - -// WorkingDir returns the image's WorkingDir -func (i *Image) WorkingDir(ctx context.Context) (string, error) { - imgInspect, err := i.inspect(ctx, false) - if err != nil { - return "", err - } - return imgInspect.Config.WorkingDir, nil -} - -// Cmd returns the image's cmd -func (i *Image) Cmd(ctx context.Context) ([]string, error) { - imgInspect, err := i.inspect(ctx, false) - if err != nil { - return nil, err - } - return imgInspect.Config.Cmd, nil -} - -// Entrypoint returns the image's entrypoint -func (i *Image) Entrypoint(ctx context.Context) ([]string, error) { - imgInspect, err := i.inspect(ctx, false) - if err != nil { - return nil, err - } - return imgInspect.Config.Entrypoint, nil -} - -// Env returns the image's env -func (i *Image) Env(ctx context.Context) ([]string, error) { - imgInspect, err := i.imageInspectInfo(ctx) - if err != nil { - return nil, err - } - return imgInspect.Env, nil -} - -// Labels returns the image's labels -func (i *Image) Labels(ctx context.Context) (map[string]string, error) { - imgInspect, err := i.imageInspectInfo(ctx) - if err != nil { - return nil, err - } - return imgInspect.Labels, nil -} - -// GetLabel Returns a case-insensitive match of a given label -func (i *Image) GetLabel(ctx context.Context, label string) (string, error) { - labels, err := i.Labels(ctx) - if err != nil { - return "", err - } - - for k, v := range labels { - if strings.EqualFold(k, label) { - return v, nil - } - } - return "", nil -} - -// Annotations returns the annotations of an image -func (i *Image) Annotations(ctx context.Context) (map[string]string, error) { - imageManifest, manifestType, err := i.Manifest(ctx) - if err != nil { - imageManifest, manifestType, err = i.GetManifest(ctx, nil) - if err != nil { - return nil, err - } - } - annotations := make(map[string]string) - if manifestType == ociv1.MediaTypeImageManifest { - var m ociv1.Manifest - if err := json.Unmarshal(imageManifest, &m); err == nil { - for k, v := range m.Annotations { - annotations[k] = v - } - } - } - return annotations, nil -} - -// ociv1Image converts an image to an imgref and then returns its config blob -// converted to an ociv1 image type -func (i *Image) ociv1Image(ctx context.Context) (*ociv1.Image, error) { - imgRef, err := i.toImageRef(ctx) - if err != nil { - return nil, err - } - return imgRef.OCIConfig(ctx) -} - -func (i *Image) imageInspectInfo(ctx context.Context) (*types.ImageInspectInfo, error) { - if i.inspectInfo == nil { - ic, err := i.toImageRef(ctx) - if err != nil { - return nil, err - } - imgInspect, err := ic.Inspect(ctx) - if err != nil { - return nil, err - } - i.inspectInfo = imgInspect - } - return i.inspectInfo, nil -} - -func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.ImageData, error) { - ociv1Img, err := i.ociv1Image(ctx) - if err != nil { - ociv1Img = &ociv1.Image{} - } - info, err := i.imageInspectInfo(ctx) - if err != nil { - info = &types.ImageInspectInfo{} - } - annotations, err := i.Annotations(ctx) - if err != nil { - return nil, err - } - - size := int64(-1) - if calculateSize { - if usize, err := i.Size(ctx); err == nil { - size = int64(*usize) - } - } - - parent, err := i.ParentID(ctx) - if err != nil { - return nil, err - } - - repoTags, err := i.RepoTags() - if err != nil { - return nil, err - } - - repoDigests, err := i.RepoDigests() - if err != nil { - return nil, err - } - - driver, err := i.DriverData() - if err != nil { - return nil, err - } - - _, manifestType, err := i.GetManifest(ctx, nil) - if err != nil { - return nil, errors.Wrapf(err, "unable to determine manifest type") - } - comment, err := i.Comment(ctx, manifestType) - if err != nil { - return nil, err - } - - data := &inspect.ImageData{ - ID: i.ID(), - Parent: parent, - RepoTags: repoTags, - RepoDigests: repoDigests, - Comment: comment, - Created: ociv1Img.Created, - Author: ociv1Img.Author, - Architecture: ociv1Img.Architecture, - Os: ociv1Img.OS, - Config: &ociv1Img.Config, - Version: info.DockerVersion, - Size: size, - // This is good enough for now, but has to be - // replaced later with correct calculation logic - VirtualSize: size, - Annotations: annotations, - Digest: i.Digest(), - Labels: info.Labels, - RootFS: &inspect.RootFS{ - Type: ociv1Img.RootFS.Type, - Layers: ociv1Img.RootFS.DiffIDs, - }, - GraphDriver: driver, - ManifestType: manifestType, - User: ociv1Img.Config.User, - History: ociv1Img.History, - NamesHistory: i.NamesHistory(), - } - if manifestType == manifest.DockerV2Schema2MediaType { - hc, err := i.GetHealthCheck(ctx) - if err != nil { - return nil, err - } - if hc != nil { - data.HealthCheck = hc - } - } - return data, nil -} - -// Inspect returns an image's inspect data -func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { - return i.inspect(ctx, true) -} - -// InspectNoSize returns an image's inspect data without calculating the size for the image -func (i *Image) InspectNoSize(ctx context.Context) (*inspect.ImageData, error) { - return i.inspect(ctx, false) -} - -// Import imports and image into the store and returns an image -func (ir *Runtime) Import(ctx context.Context, path, reference string, writer io.Writer, signingOptions SigningOptions, imageConfig ociv1.Image) (*Image, error) { - src, err := tarball.Transport.ParseReference(path) - if err != nil { - return nil, errors.Wrapf(err, "error parsing image name %q", path) - } - - updater, ok := src.(tarball.ConfigUpdater) - if !ok { - return nil, errors.Wrapf(err, "unexpected type, a tarball reference should implement tarball.ConfigUpdater") - } - - annotations := make(map[string]string) - - // config ociv1.Image - err = updater.ConfigUpdate(imageConfig, annotations) - if err != nil { - return nil, errors.Wrapf(err, "error updating image config") - } - - sc := GetSystemContext(ir.SignaturePolicyPath, "", false) - - // if reference not given, get the image digest - if reference == "" { - reference, err = getImageDigest(ctx, src, sc) - if err != nil { - return nil, err - } - } - policyContext, err := getPolicyContext(sc) - if err != nil { - return nil, err - } - defer func() { - if err := policyContext.Destroy(); err != nil { - logrus.Errorf("failed to destroy policy context: %q", err) - } - }() - copyOptions := getCopyOptions(sc, writer, nil, nil, signingOptions, "", nil) - dest, err := is.Transport.ParseStoreReference(ir.store, reference) - if err != nil { - return nil, errors.Wrapf(err, "error getting image reference for %q", reference) - } - _, err = cp.Image(ctx, policyContext, dest, src, copyOptions) - if err != nil { - return nil, err - } - newImage, err := ir.NewFromLocal(reference) - if err == nil { - newImage.newImageEvent(events.Import) - } - return newImage, err -} - -// MatchRepoTag takes a string and tries to match it against an -// image's repotags -func (i *Image) MatchRepoTag(input string) (string, error) { - results := make(map[int][]string) - var maxCount int - // first check if we have an exact match with the input - if util.StringInSlice(input, i.Names()) { - return input, nil - } - // next check if we are missing the tag - dcImage, err := decompose(input) - if err != nil { - return "", err - } - imageRegistry, imageName, imageSuspiciousTagValueForSearch := dcImage.suspiciousRefNameTagValuesForSearch() - for _, repoName := range i.Names() { - count := 0 - dcRepoName, err := decompose(repoName) - if err != nil { - return "", err - } - repoNameRegistry, repoNameName, repoNameSuspiciousTagValueForSearch := dcRepoName.suspiciousRefNameTagValuesForSearch() - if repoNameRegistry == imageRegistry && imageRegistry != "" { - count++ - } - if repoNameName == imageName && imageName != "" { - count++ - } else if splitString(repoNameName) == splitString(imageName) { - count++ - } - if repoNameSuspiciousTagValueForSearch == imageSuspiciousTagValueForSearch { - count++ - } - results[count] = append(results[count], repoName) - if count > maxCount { - maxCount = count - } - } - if maxCount == 0 { - return "", ErrRepoTagNotFound - } - if len(results[maxCount]) > 1 { - return "", errors.Errorf("user input matched multiple repotags for the image") - } - return results[maxCount][0], nil -} - -// splitString splits input string by / and returns the last array item -func splitString(input string) string { - split := strings.Split(input, "/") - return split[len(split)-1] -} - -// IsParent goes through the layers in the store and checks if i.TopLayer is -// the parent of any other layer in store. Double check that image with that -// layer exists as well. -func (i *Image) IsParent(ctx context.Context) (bool, error) { - children, err := i.getChildren(ctx, false) - if err != nil { - if errors.Cause(err) == ErrImageIsBareList { - return false, nil - } - return false, err - } - return len(children) > 0, nil -} - -// historiesMatch returns the number of entries in the histories which have the -// same contents -func historiesMatch(a, b []ociv1.History) int { - i := 0 - for i < len(a) && i < len(b) { - if a[i].Created != nil && b[i].Created == nil { - return i - } - if a[i].Created == nil && b[i].Created != nil { - return i - } - if a[i].Created != nil && b[i].Created != nil { - if !a[i].Created.Equal(*(b[i].Created)) { - return i - } - } - if a[i].CreatedBy != b[i].CreatedBy { - return i - } - if a[i].Author != b[i].Author { - return i - } - if a[i].Comment != b[i].Comment { - return i - } - if a[i].EmptyLayer != b[i].EmptyLayer { - return i - } - i++ - } - return i -} - -// areParentAndChild checks diff ID and history in the two images and return -// true if the second should be considered to be directly based on the first -func areParentAndChild(parent, child *ociv1.Image) bool { - // the child and candidate parent should share all of the - // candidate parent's diff IDs, which together would have - // controlled which layers were used - - // Both, child and parent, may be nil when the storage is left in an - // incoherent state. Issue #7444 describes such a case when a build - // has been killed. - if child == nil || parent == nil { - return false - } - - if len(parent.RootFS.DiffIDs) > len(child.RootFS.DiffIDs) { - return false - } - childUsesCandidateDiffs := true - for i := range parent.RootFS.DiffIDs { - if child.RootFS.DiffIDs[i] != parent.RootFS.DiffIDs[i] { - childUsesCandidateDiffs = false - break - } - } - if !childUsesCandidateDiffs { - return false - } - // the child should have the same history as the parent, plus - // one more entry - if len(parent.History)+1 != len(child.History) { - return false - } - if historiesMatch(parent.History, child.History) != len(parent.History) { - return false - } - return true -} - -// GetParent returns the image ID of the parent. Return nil if a parent is not found. -func (i *Image) GetParent(ctx context.Context) (*Image, error) { - tree, err := i.imageruntime.layerTree() - if err != nil { - return nil, err - } - return tree.parent(ctx, i) -} - -// ParentID returns the image ID of the parent. Return empty string if a parent is not found. -func (i *Image) ParentID(ctx context.Context) (string, error) { - parent, err := i.GetParent(ctx) - if err == nil && parent != nil { - return parent.ID(), nil - } - return "", err -} - -// GetChildren returns a list of the imageIDs that depend on the image -func (i *Image) GetChildren(ctx context.Context) ([]string, error) { - children, err := i.getChildren(ctx, true) - if err != nil { - if errors.Cause(err) == ErrImageIsBareList { - return nil, nil - } - return nil, err - } - return children, nil -} - -// getChildren returns a list of imageIDs that depend on the image. If all is -// false, only the first child image is returned. -func (i *Image) getChildren(ctx context.Context, all bool) ([]string, error) { - tree, err := i.imageruntime.layerTree() - if err != nil { - return nil, err - } - - return tree.children(ctx, i, all) -} - -// InputIsID returns a bool if the user input for an image -// is the image's partial or full id -func (i *Image) InputIsID() bool { - return strings.HasPrefix(i.ID(), i.InputName) -} - -// Containers a list of container IDs associated with the image -func (i *Image) Containers() ([]string, error) { - containers, err := i.imageruntime.store.Containers() - if err != nil { - return nil, err - } - var imageContainers []string - for _, c := range containers { - if c.ImageID == i.ID() { - imageContainers = append(imageContainers, c.ID) - } - } - return imageContainers, err -} - -// Comment returns the Comment for an image depending on its ManifestType -func (i *Image) Comment(ctx context.Context, manifestType string) (string, error) { - if manifestType == manifest.DockerV2Schema2MediaType { - imgRef, err := i.toImageRef(ctx) - if err != nil { - return "", errors.Wrapf(err, "unable to create image reference from image") - } - blob, err := imgRef.ConfigBlob(ctx) - if err != nil { - return "", errors.Wrapf(err, "unable to get config blob from image") - } - b := manifest.Schema2Image{} - if err := json.Unmarshal(blob, &b); err != nil { - return "", err - } - return b.Comment, nil - } - ociv1Img, err := i.ociv1Image(ctx) - if err != nil { - if errors.Cause(err) == ErrImageIsBareList { - return "", nil - } - return "", err - } - if len(ociv1Img.History) > 0 { - return ociv1Img.History[0].Comment, nil - } - return "", nil -} - -// Save writes a container image to the filesystem -func (i *Image) Save(ctx context.Context, source, format, output string, moreTags []string, quiet, compress, removeSignatures bool) error { - var ( - writer io.Writer - destRef types.ImageReference - manifestType string - err error - ) - - if quiet { - writer = os.Stderr - } - switch format { - case "oci-archive": - destImageName := imageNameForSaveDestination(i, source) - destRef, err = ociarchive.NewReference(output, destImageName) // destImageName may be "" - if err != nil { - return errors.Wrapf(err, "error getting OCI archive ImageReference for (%q, %q)", output, destImageName) - } - case "oci-dir": - destImageName := imageNameForSaveDestination(i, source) - destRef, err = layout.NewReference(output, destImageName) // destImageName may be "" - if err != nil { - return errors.Wrapf(err, "error getting the OCI directory ImageReference for (%q, %q)", output, destImageName) - } - manifestType = ociv1.MediaTypeImageManifest - case "docker-dir": - destRef, err = directory.NewReference(output) - if err != nil { - return errors.Wrapf(err, "error getting directory ImageReference for %q", output) - } - manifestType = manifest.DockerV2Schema2MediaType - case "docker-archive", "": - destImageName := imageNameForSaveDestination(i, source) - ref, err := dockerArchiveDstReference(destImageName) - if err != nil { - return err - } - destRef, err = dockerarchive.NewReference(output, ref) - if err != nil { - return errors.Wrapf(err, "error getting Docker archive ImageReference for %s:%v", output, ref) - } - default: - return errors.Errorf("unknown format option %q", format) - } - // supports saving multiple tags to the same tar archive - var additionaltags []reference.NamedTagged - if len(moreTags) > 0 { - additionaltags, err = GetAdditionalTags(moreTags) - if err != nil { - return err - } - } - if err := i.PushImageToReference(ctx, destRef, manifestType, "", "", "", writer, compress, SigningOptions{RemoveSignatures: removeSignatures}, &DockerRegistryOptions{}, additionaltags, nil); err != nil { - return errors.Wrapf(err, "unable to save %q", source) - } - i.newImageEvent(events.Save) - return nil -} - -// dockerArchiveDestReference returns a NamedTagged reference for a tagged image and nil for untagged image. -func dockerArchiveDstReference(normalizedInput string) (reference.NamedTagged, error) { - if normalizedInput == "" { - return nil, nil - } - ref, err := reference.ParseNormalizedNamed(normalizedInput) - if err != nil { - return nil, errors.Wrapf(err, "docker-archive parsing reference %s", normalizedInput) - } - ref = reference.TagNameOnly(ref) - namedTagged, isTagged := ref.(reference.NamedTagged) - if !isTagged { - namedTagged = nil - } - return namedTagged, nil -} - -// GetConfigBlob returns a schema2image. If the image is not a schema2, then -// it will return an error -func (i *Image) GetConfigBlob(ctx context.Context) (*manifest.Schema2Image, error) { - imageRef, err := i.toImageRef(ctx) - if err != nil { - return nil, err - } - b, err := imageRef.ConfigBlob(ctx) - if err != nil { - return nil, errors.Wrapf(err, "unable to get config blob for %s", i.ID()) - } - blob := manifest.Schema2Image{} - if err := json.Unmarshal(b, &blob); err != nil { - return nil, errors.Wrapf(err, "unable to parse image blob for %s", i.ID()) - } - return &blob, nil -} - -// GetHealthCheck returns a HealthConfig for an image. This function only works with -// schema2 images. -func (i *Image) GetHealthCheck(ctx context.Context) (*manifest.Schema2HealthConfig, error) { - configBlob, err := i.GetConfigBlob(ctx) - if err != nil { - return nil, err - } - return configBlob.ContainerConfig.Healthcheck, nil -} - -// newImageEvent creates a new event based on an image -func (ir *Runtime) newImageEvent(status events.Status, name string) { - e := events.NewEvent(status) - e.Type = events.Image - e.Name = name - if err := ir.Eventer.Write(e); err != nil { - logrus.Infof("unable to write event to %s", ir.EventsLogFilePath) - } -} - -// newImageEvent creates a new event based on an image -func (i *Image) newImageEvent(status events.Status) { - e := events.NewEvent(status) - e.ID = i.ID() - e.Type = events.Image - if len(i.Names()) > 0 { - e.Name = i.Names()[0] - } - if err := i.imageruntime.Eventer.Write(e); err != nil { - logrus.Infof("unable to write event to %s", i.imageruntime.EventsLogFilePath) - } -} - -// Mount mounts a image's filesystem on the host -// The path where the image has been mounted is returned -func (i *Image) Mount(options []string, mountLabel string) (string, error) { - defer i.newImageEvent(events.Mount) - return i.mount(options, mountLabel) -} - -// Unmount unmounts a image's filesystem on the host -func (i *Image) Unmount(force bool) error { - defer i.newImageEvent(events.Unmount) - return i.unmount(force) -} - -// Mounted returns whether the image is mounted and the path it is mounted -// at (if it is mounted). -// If the image is not mounted, no error is returned, and the mountpoint -// will be set to "". -func (i *Image) Mounted() (bool, string, error) { - mountedTimes, err := i.imageruntime.store.Mounted(i.TopLayer()) - if err != nil { - return false, "", err - } - - if mountedTimes > 0 { - layer, err := i.imageruntime.store.Layer(i.TopLayer()) - if err != nil { - return false, "", err - } - return true, layer.MountPoint, nil - } - - return false, "", nil -} - -// mount mounts the container's root filesystem -func (i *Image) mount(options []string, mountLabel string) (string, error) { - mountPoint, err := i.imageruntime.store.MountImage(i.ID(), options, mountLabel) - if err != nil { - return "", errors.Wrapf(err, "error mounting storage for image %s", i.ID()) - } - mountPoint, err = filepath.EvalSymlinks(mountPoint) - if err != nil { - return "", errors.Wrapf(err, "error resolving storage path for image %s", i.ID()) - } - return mountPoint, nil -} - -// unmount unmounts the image's root filesystem -func (i *Image) unmount(force bool) error { - // Also unmount storage - if _, err := i.imageruntime.store.UnmountImage(i.ID(), force); err != nil { - return errors.Wrapf(err, "error unmounting image %s root filesystem", i.ID()) - } - - return nil -} - -// LayerInfo keeps information of single layer -type LayerInfo struct { - // Layer ID - ID string - // Parent ID of current layer. - ParentID string - // ChildID of current layer. - // there can be multiple children in case of fork - ChildID []string - // RepoTag will have image repo names, if layer is top layer of image - RepoTags []string - // Size stores Uncompressed size of layer. - Size int64 -} - -// GetLayersMapWithImageInfo returns map of image-layers, with associated information like RepoTags, parent and list of child layers. -func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, error) { - // TODO: evaluate if we can reuse `layerTree` here. - - // Memory allocated to store map of layers with key LayerID. - // Map will build dependency chain with ParentID and ChildID(s) - layerInfoMap := make(map[string]*LayerInfo) - - // scan all layers & fill size and parent id for each layer in layerInfoMap - layers, err := imageruntime.store.Layers() - if err != nil { - return nil, err - } - for _, layer := range layers { - _, ok := layerInfoMap[layer.ID] - if !ok { - layerInfoMap[layer.ID] = &LayerInfo{ - ID: layer.ID, - Size: layer.UncompressedSize, - ParentID: layer.Parent, - } - } else { - return nil, fmt.Errorf("detected multiple layers with the same ID %q", layer.ID) - } - } - - // scan all layers & add all childid's for each layers to layerInfo - for _, layer := range layers { - _, ok := layerInfoMap[layer.ID] - if ok { - if layer.Parent != "" { - layerInfoMap[layer.Parent].ChildID = append(layerInfoMap[layer.Parent].ChildID, layer.ID) - } - } else { - return nil, fmt.Errorf("lookup error: layer-id %s, not found", layer.ID) - } - } - - // Add the Repo Tags to Top layer of each image. - imgs, err := imageruntime.store.Images() - if err != nil { - return nil, err - } - layerInfoMap[""] = &LayerInfo{} - for _, img := range imgs { - e, ok := layerInfoMap[img.TopLayer] - if !ok { - return nil, fmt.Errorf("top-layer for image %s not found local store", img.ID) - } - e.RepoTags = append(e.RepoTags, img.Names...) - } - return layerInfoMap, nil -} - -// BuildImageHierarchyMap stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo -// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) -func BuildImageHierarchyMap(imageInfo *InfoImage, layerMap map[string]*LayerInfo, layerID string) error { - if layerID == "" { - return nil - } - ll, ok := layerMap[layerID] - if !ok { - return fmt.Errorf("lookup error: layerid %s not found", layerID) - } - if err := BuildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil { - return err - } - - imageInfo.Layers = append(imageInfo.Layers, *ll) - return nil -} diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go deleted file mode 100644 index 2b42d6394..000000000 --- a/libpod/image/image_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package image - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "testing" - - "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/pkg/util" - podmanVersion "github.com/containers/podman/v3/version" - "github.com/containers/storage" - "github.com/containers/storage/pkg/reexec" - "github.com/opencontainers/go-digest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - bbNames = []string{"docker.io/library/busybox:latest", "docker.io/library/busybox", "docker.io/busybox:latest", "docker.io/busybox", "busybox:latest", "busybox"} - bbGlibcNames = []string{"docker.io/library/busybox:glibc", "docker.io/busybox:glibc", "busybox:glibc"} -) - -type localImageTest struct { - fqname, taggedName string - img *Image - names []string -} - -// make a temporary directory for the runtime -func mkWorkDir() (string, error) { - return ioutil.TempDir("", "podman-test") -} - -// shutdown the runtime and clean behind it -func cleanup(workdir string, ir *Runtime) { - if err := ir.Shutdown(false); err != nil { - fmt.Println(err) - os.Exit(1) - } - err := os.RemoveAll(workdir) - if err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func makeLocalMatrix(b, bg *Image) []localImageTest { - var l []localImageTest - // busybox - busybox := localImageTest{ - fqname: "docker.io/library/busybox:latest", - taggedName: "bb:latest", - } - busybox.img = b - busybox.names = b.Names() - busybox.names = append(busybox.names, []string{"bb:latest", "bb", b.ID(), b.ID()[0:7], fmt.Sprintf("busybox@%s", b.Digest())}...) - - // busybox-glibc - busyboxGlibc := localImageTest{ - fqname: "docker.io/library/busybox:glibc", - taggedName: "bb:glibc", - } - - busyboxGlibc.img = bg - busyboxGlibc.names = bbGlibcNames - - l = append(l, busybox, busyboxGlibc) - return l -} - -func TestMain(m *testing.M) { - if reexec.Init() { - return - } - os.Exit(m.Run()) -} - -// TestImage_NewFromLocal tests finding the image locally by various names, -// tags, and aliases -func TestImage_NewFromLocal(t *testing.T) { - if os.Geteuid() != 0 { // containers/storage requires root access - t.Skipf("Test not running as root") - } - - workdir, err := mkWorkDir() - assert.NoError(t, err) - so := storage.StoreOptions{ - RunRoot: workdir, - GraphRoot: workdir, - } - writer := os.Stdout - - // Need images to be present for this test - ir, err := NewImageRuntimeFromOptions(so) - assert.NoError(t, err) - defer cleanup(workdir, ir) - - ir.Eventer = events.NewNullEventer() - bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing, nil) - assert.NoError(t, err) - bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing, nil) - assert.NoError(t, err) - - tm := makeLocalMatrix(bb, bbglibc) - for _, image := range tm { - // tag our images - err = image.img.TagImage(image.taggedName) - assert.NoError(t, err) - for _, name := range image.names { - newImage, err := ir.NewFromLocal(name) - require.NoError(t, err) - assert.Equal(t, newImage.ID(), image.img.ID()) - } - } -} - -// TestImage_New tests pulling the image by various names, tags, and from -// different registries -func TestImage_New(t *testing.T) { - if os.Geteuid() != 0 { // containers/storage requires root access - t.Skipf("Test not running as root") - } - - var names []string - workdir, err := mkWorkDir() - assert.NoError(t, err) - so := storage.StoreOptions{ - RunRoot: workdir, - GraphRoot: workdir, - } - ir, err := NewImageRuntimeFromOptions(so) - assert.NoError(t, err) - defer cleanup(workdir, ir) - - ir.Eventer = events.NewNullEventer() - // Build the list of pull names - names = append(names, bbNames...) - writer := os.Stdout - - opts := DockerRegistryOptions{ - RegistriesConfPath: "testdata/registries.conf", - } - // Iterate over the names and delete the image - // after the pull - for _, img := range names { - newImage, err := ir.New(context.Background(), img, "", "", writer, &opts, SigningOptions{}, nil, util.PullImageMissing, nil) - require.NoError(t, err, img) - assert.NotEqual(t, newImage.ID(), "") - err = newImage.Remove(context.Background(), false) - assert.NoError(t, err) - } -} - -// TestImage_MatchRepoTag tests the various inputs we need to match -// against an image's reponames -func TestImage_MatchRepoTag(t *testing.T) { - if os.Geteuid() != 0 { // containers/storage requires root access - t.Skipf("Test not running as root") - } - - //Set up - workdir, err := mkWorkDir() - assert.NoError(t, err) - so := storage.StoreOptions{ - RunRoot: workdir, - GraphRoot: workdir, - } - ir, err := NewImageRuntimeFromOptions(so) - require.NoError(t, err) - defer cleanup(workdir, ir) - - opts := DockerRegistryOptions{ - RegistriesConfPath: "testdata/registries.conf", - } - ir.Eventer = events.NewNullEventer() - newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, &opts, SigningOptions{}, nil, util.PullImageMissing, nil) - require.NoError(t, err) - err = newImage.TagImage("foo:latest") - require.NoError(t, err) - err = newImage.TagImage("foo:bar") - require.NoError(t, err) - - // Tests start here. - for _, name := range bbNames { - repoTag, err := newImage.MatchRepoTag(name) - assert.NoError(t, err) - assert.Equal(t, "docker.io/library/busybox:latest", repoTag) - } - - // Test against tagged images of busybox - - // foo should resolve to foo:latest - repoTag, err := newImage.MatchRepoTag("foo") - require.NoError(t, err) - assert.Equal(t, "localhost/foo:latest", repoTag) - - // foo:bar should resolve to foo:bar - repoTag, err = newImage.MatchRepoTag("foo:bar") - require.NoError(t, err) - assert.Equal(t, "localhost/foo:bar", repoTag) -} - -// TestImage_RepoDigests tests RepoDigest generation. -func TestImage_RepoDigests(t *testing.T) { - dgst, err := digest.Parse("sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc") - require.NoError(t, err) - - for _, tt := range []struct { - name string - names []string - expected []string - }{ - { - name: "empty", - names: []string{}, - expected: nil, - }, - { - name: "tagged", - names: []string{"docker.io/library/busybox:latest"}, - expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, - }, - { - name: "digest", - names: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, - expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, - }, - } { - test := tt - t.Run(test.name, func(t *testing.T) { - image := &Image{ - image: &storage.Image{ - Names: test.names, - Digest: dgst, - }, - } - actual, err := image.RepoDigests() - require.NoError(t, err) - assert.Equal(t, test.expected, actual) - - image = &Image{ - image: &storage.Image{ - Names: test.names, - Digests: []digest.Digest{dgst}, - }, - } - actual, err = image.RepoDigests() - require.NoError(t, err) - assert.Equal(t, test.expected, actual) - }) - } -} - -// Test_splitString tests the splitString function in image that -// takes input and splits on / and returns the last array item -func Test_splitString(t *testing.T) { - assert.Equal(t, splitString("foo/bar"), "bar") - assert.Equal(t, splitString("a/foo/bar"), "bar") - assert.Equal(t, splitString("bar"), "bar") -} - -// Test_stripSha256 tests test the stripSha256 function which removes -// the prefix "sha256:" from a string if it is present -func Test_stripSha256(t *testing.T) { - assert.Equal(t, stripSha256(""), "") - assert.Equal(t, stripSha256("test1"), "test1") - assert.Equal(t, stripSha256("sha256:9110ae7f579f35ee0c3938696f23fe0f5fbe641738ea52eb83c2df7e9995fa17"), "9110ae7f579f35ee0c3938696f23fe0f5fbe641738ea52eb83c2df7e9995fa17") - assert.Equal(t, stripSha256("sha256:9110ae7f"), "9110ae7f") - assert.Equal(t, stripSha256("sha256:"), "sha256:") - assert.Equal(t, stripSha256("sha256:a"), "a") -} - -func TestNormalizedTag(t *testing.T) { - const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - - for _, c := range []struct{ input, expected string }{ - {"#", ""}, // Clearly invalid - {"example.com/busybox", "example.com/busybox:latest"}, // Qualified name-only - {"example.com/busybox:notlatest", "example.com/busybox:notlatest"}, // Qualified name:tag - {"example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix}, // Qualified name@digest; FIXME? Should we allow tagging with a digest at all? - {"example.com/busybox:notlatest" + digestSuffix, "example.com/busybox:notlatest" + digestSuffix}, // Qualified name:tag@digest - {"busybox:latest", "localhost/busybox:latest"}, // Unqualified name-only - {"ns/busybox:latest", "localhost/ns/busybox:latest"}, // Unqualified with a dot-less namespace - {"docker.io/busybox:latest", "docker.io/library/busybox:latest"}, // docker.io without /library/ - } { - res, err := NormalizedTag(c.input) - if c.expected == "" { - assert.Error(t, err, c.input) - } else { - assert.NoError(t, err, c.input) - assert.Equal(t, c.expected, res.String()) - } - } -} - -func TestGetSystemContext(t *testing.T) { - sc := GetSystemContext("", "", false) - assert.Equal(t, sc.SignaturePolicyPath, "") - assert.Equal(t, sc.AuthFilePath, "") - assert.Equal(t, sc.DirForceCompress, false) - assert.Equal(t, sc.DockerRegistryUserAgent, fmt.Sprintf("libpod/%s", podmanVersion.Version)) - assert.Equal(t, sc.BigFilesTemporaryDir, "/var/tmp") - - oldtmpdir := os.Getenv("TMPDIR") - os.Setenv("TMPDIR", "/mnt") - sc = GetSystemContext("/tmp/foo", "/tmp/bar", true) - assert.Equal(t, sc.SignaturePolicyPath, "/tmp/foo") - assert.Equal(t, sc.AuthFilePath, "/tmp/bar") - assert.Equal(t, sc.DirForceCompress, true) - assert.Equal(t, sc.BigFilesTemporaryDir, "/mnt") - if oldtmpdir != "" { - os.Setenv("TMPDIR", oldtmpdir) - } else { - os.Unsetenv("TMPDIR") - } -} diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go deleted file mode 100644 index 1ae3693c9..000000000 --- a/libpod/image/manifests.go +++ /dev/null @@ -1,209 +0,0 @@ -package image - -import ( - "context" - "fmt" - - "github.com/containers/buildah/manifests" - "github.com/containers/image/v5/docker" - "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - "github.com/opencontainers/go-digest" -) - -// Options for adding a manifest -// swagger:model ManifestAddOpts -type ManifestAddOpts struct { - All bool `json:"all"` - Annotation map[string]string `json:"annotation"` - Arch string `json:"arch"` - Features []string `json:"features"` - Images []string `json:"images"` - OS string `json:"os"` - OSVersion string `json:"os_version"` - Variant string `json:"variant"` -} - -// ManifestAnnotateOptions defines the options for -// manifest annotate -type ManifestAnnotateOpts struct { - Annotation map[string]string `json:"annotation"` - Arch string `json:"arch"` - Features []string `json:"features"` - OS string `json:"os"` - OSFeatures []string `json:"os_feature"` - OSVersion string `json:"os_version"` - Variant string `json:"variant"` -} - -// InspectManifest returns a dockerized version of the manifest list -func (i *Image) InspectManifest() (*manifest.Schema2List, error) { - list, err := i.getManifestList() - if err != nil { - return nil, err - } - return list.Docker(), nil -} - -// ExistsManifest checks if a manifest list exists -func (i *Image) ExistsManifest() (bool, error) { - _, err := i.getManifestList() - if err != nil { - return false, err - } - return true, nil -} - -// RemoveManifest removes the given digest from the manifest list. -func (i *Image) RemoveManifest(d digest.Digest) (string, error) { - list, err := i.getManifestList() - if err != nil { - return "", err - } - if err := list.Remove(d); err != nil { - return "", err - } - return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "") -} - -// getManifestList is a helper to obtain a manifest list -func (i *Image) getManifestList() (manifests.List, error) { - _, list, err := manifests.LoadFromImage(i.imageruntime.store, i.ID()) - return list, err -} - -// CreateManifestList creates a new manifest list and can optionally add given images -// to the list -func CreateManifestList(rt *Runtime, systemContext types.SystemContext, names []string, imgs []string, all bool) (string, error) { - list := manifests.Create() - opts := ManifestAddOpts{Images: names, All: all} - for _, img := range imgs { - ref, err := alltransports.ParseImageName(img) - if err != nil { - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) - ref, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, img)) - if err != nil { - return "", err - } - } - list, err = addManifestToList(ref, list, systemContext, opts) - if err != nil { - return "", err - } - } - return list.SaveToImage(rt.store, "", names, manifest.DockerV2ListMediaType) -} - -func addManifestToList(ref types.ImageReference, list manifests.List, systemContext types.SystemContext, opts ManifestAddOpts) (manifests.List, error) { - d, err := list.Add(context.Background(), &systemContext, ref, opts.All) - if err != nil { - return nil, err - } - if opts.OS != "" { - if err := list.SetOS(d, opts.OS); err != nil { - return nil, err - } - } - if len(opts.OSVersion) > 0 { - if err := list.SetOSVersion(d, opts.OSVersion); err != nil { - return nil, err - } - } - if len(opts.Features) > 0 { - if err := list.SetFeatures(d, opts.Features); err != nil { - return nil, err - } - } - if len(opts.Arch) > 0 { - if err := list.SetArchitecture(d, opts.Arch); err != nil { - return nil, err - } - } - if len(opts.Variant) > 0 { - if err := list.SetVariant(d, opts.Variant); err != nil { - return nil, err - } - } - if len(opts.Annotation) > 0 { - if err := list.SetAnnotations(&d, opts.Annotation); err != nil { - return nil, err - } - } - return list, err -} - -// AddManifest adds a manifest to a given manifest list. -func (i *Image) AddManifest(systemContext types.SystemContext, opts ManifestAddOpts) (string, error) { - ref, err := alltransports.ParseImageName(opts.Images[0]) - if err != nil { - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) - ref, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, opts.Images[0])) - if err != nil { - return "", err - } - } - list, err := i.getManifestList() - if err != nil { - return "", err - } - list, err = addManifestToList(ref, list, systemContext, opts) - if err != nil { - return "", err - } - return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "") -} - -// PushManifest pushes a manifest to a destination -func (i *Image) PushManifest(dest types.ImageReference, opts manifests.PushOptions) (digest.Digest, error) { - list, err := i.getManifestList() - if err != nil { - return "", err - } - _, d, err := list.Push(context.Background(), dest, opts) - return d, err -} - -// AnnotateManifest updates an image configuration of a manifest list. -func (i *Image) AnnotateManifest(systemContext types.SystemContext, d digest.Digest, opts ManifestAnnotateOpts) (string, error) { - list, err := i.getManifestList() - if err != nil { - return "", err - } - if len(opts.OS) > 0 { - if err := list.SetOS(d, opts.OS); err != nil { - return "", err - } - } - if len(opts.OSVersion) > 0 { - if err := list.SetOSVersion(d, opts.OSVersion); err != nil { - return "", err - } - } - if len(opts.Features) > 0 { - if err := list.SetFeatures(d, opts.Features); err != nil { - return "", err - } - } - if len(opts.OSFeatures) > 0 { - if err := list.SetOSFeatures(d, opts.OSFeatures); err != nil { - return "", err - } - } - if len(opts.Arch) > 0 { - if err := list.SetArchitecture(d, opts.Arch); err != nil { - return "", err - } - } - if len(opts.Variant) > 0 { - if err := list.SetVariant(d, opts.Variant); err != nil { - return "", err - } - } - if len(opts.Annotation) > 0 { - if err := list.SetAnnotations(&d, opts.Annotation); err != nil { - return "", err - } - } - return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "") -} diff --git a/libpod/image/parts.go b/libpod/image/parts.go deleted file mode 100644 index 08421320c..000000000 --- a/libpod/image/parts.go +++ /dev/null @@ -1,104 +0,0 @@ -package image - -import ( - "strings" - - "github.com/containers/image/v5/docker/reference" - "github.com/pkg/errors" -) - -// imageParts describes the parts of an image's name -type imageParts struct { - unnormalizedRef reference.Named // WARNING: Did not go through docker.io[/library] normalization - hasRegistry bool -} - -// Registries must contain a ":" or a "." or be localhost; this helper exists for users of reference.Parse. -// For inputs that should use the docker.io[/library] normalization, use reference.ParseNormalizedNamed instead. -func isRegistry(name string) bool { - return strings.ContainsAny(name, ".:") || name == "localhost" -} - -// GetImageBaseName uses decompose and string splits to obtain the base -// name of an image. Doing this here because it beats changing the -// imageParts struct names to be exported as well. -func GetImageBaseName(input string) (string, error) { - decomposedImage, err := decompose(input) - if err != nil { - return "", err - } - splitImageName := strings.Split(decomposedImage.unnormalizedRef.Name(), "/") - return splitImageName[len(splitImageName)-1], nil -} - -// decompose breaks an input name into an imageParts description -func decompose(input string) (imageParts, error) { - imgRef, err := reference.Parse(input) - if err != nil { - return imageParts{}, err - } - unnormalizedNamed := imgRef.(reference.Named) - // ip.unnormalizedRef, because it uses reference.Parse and not reference.ParseNormalizedNamed, - // does not use the standard heuristics for domains vs. namespaces/repos, so we need to check - // explicitly. - hasRegistry := isRegistry(reference.Domain(unnormalizedNamed)) - return imageParts{ - unnormalizedRef: unnormalizedNamed, - hasRegistry: hasRegistry, - }, nil -} - -// suspiciousRefNameTagValuesForSearch returns a "tag" value used in a previous implementation. -// This exists only to preserve existing behavior in heuristic code; it’s dubious that that behavior is correct, -// especially for the tag value. -func (ip *imageParts) suspiciousRefNameTagValuesForSearch() (string, string, string) { - registry := reference.Domain(ip.unnormalizedRef) - imageName := reference.Path(ip.unnormalizedRef) - // ip.unnormalizedRef, because it uses reference.Parse and not reference.ParseNormalizedNamed, - // does not use the standard heuristics for domains vs. namespaces/repos. - if registry != "" && !isRegistry(registry) { - imageName = registry + "/" + imageName - registry = "" - } - - var tag string - if tagged, isTagged := ip.unnormalizedRef.(reference.NamedTagged); isTagged { - tag = tagged.Tag() - } else if _, hasDigest := ip.unnormalizedRef.(reference.Digested); hasDigest { - tag = "none" - } else { - tag = LatestTag - } - return registry, imageName, tag -} - -// referenceWithRegistry returns a (normalized) reference.Named composed of ip (with !ip.hasRegistry) -// qualified with registry. -func (ip *imageParts) referenceWithRegistry(registry string) (reference.Named, error) { - if ip.hasRegistry { - return nil, errors.Errorf("internal error: referenceWithRegistry called on imageParts with a registry (%#v)", *ip) - } - // We could build a reference.WithName+WithTag/WithDigest here, but we need to round-trip via a string - // and a ParseNormalizedNamed anyway to get the right normalization of docker.io/library, so - // just use a string directly. - qualified := registry + "/" + ip.unnormalizedRef.String() - ref, err := reference.ParseNormalizedNamed(qualified) - if err != nil { - return nil, errors.Wrapf(err, "error normalizing registry+unqualified reference %#v", qualified) - } - return ref, nil -} - -// normalizedReference returns a (normalized) reference for ip (with ip.hasRegistry) -func (ip *imageParts) normalizedReference() (reference.Named, error) { - if !ip.hasRegistry { - return nil, errors.Errorf("internal error: normalizedReference called on imageParts without a registry (%#v)", *ip) - } - // We need to round-trip via a string to get the right normalization of docker.io/library - s := ip.unnormalizedRef.String() - ref, err := reference.ParseNormalizedNamed(s) - if err != nil { // Should never happen - return nil, errors.Wrapf(err, "error normalizing qualified reference %#v", s) - } - return ref, nil -} diff --git a/libpod/image/parts_test.go b/libpod/image/parts_test.go deleted file mode 100644 index 726e55e86..000000000 --- a/libpod/image/parts_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package image - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDecompose(t *testing.T) { - const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - - for _, c := range []struct { - input string - registry, name, suspiciousTagValueForSearch string - hasRegistry bool - }{ - {"#", "", "", "", false}, // Entirely invalid input - { // Fully qualified docker.io, name-only input - "docker.io/library/busybox", "docker.io", "library/busybox", "latest", true, - }, - { // Fully qualified example.com, name-only input - "example.com/ns/busybox", "example.com", "ns/busybox", "latest", true, - }, - { // Unqualified single-name input - "busybox", "", "busybox", "latest", false, - }, - { // Unqualified namespaced input - "ns/busybox", "", "ns/busybox", "latest", false, - }, - { // name:tag - "example.com/ns/busybox:notlatest", "example.com", "ns/busybox", "notlatest", true, - }, - { // name@digest - // FIXME? .suspiciousTagValueForSearch == "none" - "example.com/ns/busybox" + digestSuffix, "example.com", "ns/busybox", "none", true, - }, - { // name:tag@digest - "example.com/ns/busybox:notlatest" + digestSuffix, "example.com", "ns/busybox", "notlatest", true, - }, - } { - parts, err := decompose(c.input) - if c.name == "" { - assert.Error(t, err, c.input) - } else { - assert.NoError(t, err, c.input) - registry, name, suspiciousTagValueForSearch := parts.suspiciousRefNameTagValuesForSearch() - assert.Equal(t, c.registry, registry, c.input) - assert.Equal(t, c.name, name, c.input) - assert.Equal(t, c.suspiciousTagValueForSearch, suspiciousTagValueForSearch, c.input) - assert.Equal(t, c.hasRegistry, parts.hasRegistry, c.input) - } - } -} - -func TestImagePartsReferenceWithRegistry(t *testing.T) { - const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - - for _, c := range []struct { - input string - withDocker, withNonDocker string - }{ - {"example.com/ns/busybox", "", ""}, // Fully-qualified input is invalid. - {"busybox", "docker.io/library/busybox", "example.com/busybox"}, // Single-name input - {"ns/busybox", "docker.io/ns/busybox", "example.com/ns/busybox"}, // Namespaced input - {"ns/busybox:notlatest", "docker.io/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}, // name:tag - {"ns/busybox" + digestSuffix, "docker.io/ns/busybox" + digestSuffix, "example.com/ns/busybox" + digestSuffix}, // name@digest - { // name:tag@digest - "ns/busybox:notlatest" + digestSuffix, - "docker.io/ns/busybox:notlatest" + digestSuffix, "example.com/ns/busybox:notlatest" + digestSuffix, - }, - } { - parts, err := decompose(c.input) - require.NoError(t, err) - if c.withDocker == "" { - _, err := parts.referenceWithRegistry("docker.io") - assert.Error(t, err, c.input) - _, err = parts.referenceWithRegistry("example.com") - assert.Error(t, err, c.input) - } else { - ref, err := parts.referenceWithRegistry("docker.io") - require.NoError(t, err, c.input) - assert.Equal(t, c.withDocker, ref.String()) - ref, err = parts.referenceWithRegistry("example.com") - require.NoError(t, err, c.input) - assert.Equal(t, c.withNonDocker, ref.String()) - } - } - - // Invalid registry value - parts, err := decompose("busybox") - require.NoError(t, err) - _, err = parts.referenceWithRegistry("invalid@domain") - assert.Error(t, err) -} - -func TestImagePartsNormalizedReference(t *testing.T) { - const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - - for _, c := range []struct{ input, expected string }{ - {"busybox", ""}, // Unqualified input is invalid - {"docker.io/busybox", "docker.io/library/busybox"}, // docker.io single-name - {"example.com/busybox", "example.com/busybox"}, // example.com single-name - {"docker.io/ns/busybox", "docker.io/ns/busybox"}, // docker.io namespaced - {"example.com/ns/busybox", "example.com/ns/busybox"}, // example.com namespaced - {"example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}, // name:tag - {"example.com/ns/busybox" + digestSuffix, "example.com/ns/busybox" + digestSuffix}, // name@digest - { // name:tag@digest - "example.com/ns/busybox:notlatest" + digestSuffix, "example.com/ns/busybox:notlatest" + digestSuffix, - }, - } { - parts, err := decompose(c.input) - require.NoError(t, err) - if c.expected == "" { - _, err := parts.normalizedReference() - assert.Error(t, err, c.input) - } else { - ref, err := parts.normalizedReference() - require.NoError(t, err, c.input) - assert.Equal(t, c.expected, ref.String()) - } - } -} diff --git a/libpod/image/prune.go b/libpod/image/prune.go deleted file mode 100644 index e0480d3d1..000000000 --- a/libpod/image/prune.go +++ /dev/null @@ -1,164 +0,0 @@ -package image - -import ( - "context" - "strconv" - "strings" - - "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/pkg/domain/entities/reports" - "github.com/containers/podman/v3/pkg/util" - "github.com/containers/storage" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) { - switch filter { - case "label": - return func(i *Image) bool { - labels, err := i.Labels(context.Background()) - if err != nil { - return false - } - return util.MatchLabelFilters([]string{filterValue}, labels) - }, nil - - case "until": - until, err := util.ComputeUntilTimestamp([]string{filterValue}) - if err != nil { - return nil, err - } - return func(i *Image) bool { - if !until.IsZero() && i.Created().Before(until) { - return true - } - return false - }, nil - case "dangling": - danglingImages, err := strconv.ParseBool(filterValue) - if err != nil { - return nil, errors.Wrapf(err, "invalid filter dangling=%s", filterValue) - } - return ImageFilter(DanglingFilter(danglingImages)), nil - } - return nil, nil -} - -// GetPruneImages returns a slice of images that have no names/unused -func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []ImageFilter) ([]*Image, error) { - var ( - pruneImages []*Image - ) - - allImages, err := ir.GetRWImages() - if err != nil { - return nil, err - } - - tree, err := ir.layerTree() - if err != nil { - return nil, err - } - - for _, i := range allImages { - // filter the images based on this. - for _, filterFunc := range filterFuncs { - if !filterFunc(i) { - continue - } - } - - if all { - containers, err := i.Containers() - if err != nil { - return nil, err - } - if len(containers) < 1 { - pruneImages = append(pruneImages, i) - continue - } - } - - // skip the cache (i.e., with parent) and intermediate (i.e., - // with children) images - intermediate, err := tree.hasChildrenAndParent(ctx, i) - if err != nil { - return nil, err - } - if intermediate { - continue - } - - if i.Dangling() { - pruneImages = append(pruneImages, i) - } - } - return pruneImages, nil -} - -// PruneImages prunes dangling and optionally all unused images from the local -// image store -func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]*reports.PruneReport, error) { - preports := make([]*reports.PruneReport, 0) - filterFuncs := make([]ImageFilter, 0, len(filter)) - for _, f := range filter { - filterSplit := strings.SplitN(f, "=", 2) - if len(filterSplit) < 2 { - return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) - } - - generatedFunc, err := generatePruneFilterFuncs(filterSplit[0], filterSplit[1]) - if err != nil { - return nil, errors.Wrapf(err, "invalid filter") - } - filterFuncs = append(filterFuncs, generatedFunc) - } - - prev := 0 - for { - toPrune, err := ir.GetPruneImages(ctx, all, filterFuncs) - if err != nil { - return nil, errors.Wrap(err, "unable to get images to prune") - } - numImages := len(toPrune) - if numImages == 0 || numImages == prev { - // If there's nothing left to do, return. - break - } - prev = numImages - for _, img := range toPrune { - repotags, err := img.RepoTags() - if err != nil { - return nil, err - } - nameOrID := img.ID() - s, err := img.Size(ctx) - imgSize := uint64(0) - if err != nil { - logrus.Warnf("Failed to collect image size for: %s, %s", nameOrID, err) - } else { - imgSize = *s - } - if err := img.Remove(ctx, false); err != nil { - if errors.Cause(err) == storage.ErrImageUsedByContainer { - logrus.Warnf("Failed to prune image %s as it is in use: %v.\nA container associated with containers/storage (e.g., Buildah, CRI-O, etc.) maybe associated with this image.\nUsing the rmi command with the --force option will remove the container and image, but may cause failures for other dependent systems.", img.ID(), err) - continue - } - return nil, errors.Wrap(err, "failed to prune image") - } - defer img.newImageEvent(events.Prune) - - if len(repotags) > 0 { - nameOrID = repotags[0] - } - - preports = append(preports, &reports.PruneReport{ - Id: nameOrID, - Err: nil, - Size: uint64(imgSize), - }) - } - } - return preports, nil -} diff --git a/libpod/image/pull.go b/libpod/image/pull.go deleted file mode 100644 index 6517fbd07..000000000 --- a/libpod/image/pull.go +++ /dev/null @@ -1,437 +0,0 @@ -package image - -import ( - "context" - "fmt" - "io" - "path/filepath" - "strings" - "time" - - "github.com/containers/common/pkg/retry" - cp "github.com/containers/image/v5/copy" - "github.com/containers/image/v5/directory" - "github.com/containers/image/v5/docker" - dockerarchive "github.com/containers/image/v5/docker/archive" - ociarchive "github.com/containers/image/v5/oci/archive" - oci "github.com/containers/image/v5/oci/layout" - "github.com/containers/image/v5/pkg/shortnames" - is "github.com/containers/image/v5/storage" - "github.com/containers/image/v5/transports" - "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/pkg/errorhandling" - "github.com/containers/podman/v3/pkg/registries" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -var ( - // DockerArchive is the transport we prepend to an image name - // when saving to docker-archive - DockerArchive = dockerarchive.Transport.Name() - // OCIArchive is the transport we prepend to an image name - // when saving to oci-archive - OCIArchive = ociarchive.Transport.Name() - // DirTransport is the transport for pushing and pulling - // images to and from a directory - DirTransport = directory.Transport.Name() - // DockerTransport is the transport for docker registries - DockerTransport = docker.Transport.Name() - // OCIDirTransport is the transport for pushing and pulling - // images to and from a directory containing an OCI image - OCIDirTransport = oci.Transport.Name() - // AtomicTransport is the transport for atomic registries - AtomicTransport = "atomic" - // DefaultTransport is a prefix that we apply to an image name - // NOTE: This is a string prefix, not actually a transport name usable for transports.Get(); - // and because syntaxes of image names are transport-dependent, the prefix is not really interchangeable; - // each user implicitly assumes the appended string is a Docker-like reference. - DefaultTransport = DockerTransport + "://" - // DefaultLocalRegistry is the default local registry for local image operations - // Remote pulls will still use defined registries - DefaultLocalRegistry = "localhost" -) - -// pullRefPair records a pair of prepared image references to pull. -type pullRefPair struct { - image string - srcRef types.ImageReference - dstRef types.ImageReference - resolvedShortname *shortnames.PullCandidate // if set, must be recorded after successful pull -} - -// cleanUpFunc is a function prototype for clean-up functions. -type cleanUpFunc func() error - -// pullGoal represents the prepared image references and decided behavior to be executed by imagePull -type pullGoal struct { - refPairs []pullRefPair - pullAllPairs bool // Pull all refPairs instead of stopping on first success. - cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader) - shortName string // Set when pulling a short name - resolved *shortnames.Resolved // Set when pulling a short name -} - -// cleanUp invokes all cleanUpFuncs. Certain resources may not be available -// anymore. Errors are logged. -func (p *pullGoal) cleanUp() { - for _, f := range p.cleanUpFuncs { - if err := f(); err != nil { - logrus.Error(err.Error()) - } - } -} - -// singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair. -func singlePullRefPairGoal(rp pullRefPair) *pullGoal { - return &pullGoal{ - refPairs: []pullRefPair{rp}, - pullAllPairs: false, // Does not really make a difference. - } -} - -func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) (pullRefPair, error) { - decomposedDest, err := decompose(destName) - if err == nil && !decomposedDest.hasRegistry { - // If the image doesn't have a registry, set it as the default repo - ref, err := decomposedDest.referenceWithRegistry(DefaultLocalRegistry) - if err != nil { - return pullRefPair{}, err - } - destName = ref.String() - } - - reference := destName - if srcRef.DockerReference() != nil { - reference = srcRef.DockerReference().String() - } - destRef, err := is.Transport.ParseStoreReference(ir.store, reference) - if err != nil { - return pullRefPair{}, errors.Wrapf(err, "error parsing dest reference name %#v", destName) - } - return pullRefPair{ - image: destName, - srcRef: srcRef, - dstRef: destRef, - }, nil -} - -// getSinglePullRefPairGoal calls getPullRefPair with the specified parameters, and returns a single-pair goal for the return value. -func (ir *Runtime) getSinglePullRefPairGoal(srcRef types.ImageReference, destName string) (*pullGoal, error) { - rp, err := ir.getPullRefPair(srcRef, destName) - if err != nil { - return nil, err - } - return singlePullRefPairGoal(rp), nil -} - -// getPullRefPairsFromDockerArchiveReference returns a slice of pullRefPairs -// for the specified docker reference and the corresponding archive.Reader. -func (ir *Runtime) getPullRefPairsFromDockerArchiveReference(ctx context.Context, reader *dockerarchive.Reader, ref types.ImageReference, sc *types.SystemContext) ([]pullRefPair, error) { - destNames, err := reader.ManifestTagsForReference(ref) - if err != nil { - return nil, err - } - - if len(destNames) == 0 { - destName, err := getImageDigest(ctx, ref, sc) - if err != nil { - return nil, err - } - destNames = append(destNames, destName) - } else { - for i := range destNames { - ref, err := NormalizedTag(destNames[i]) - if err != nil { - return nil, err - } - destNames[i] = ref.String() - } - } - - refPairs := []pullRefPair{} - for _, destName := range destNames { - destRef, err := is.Transport.ParseStoreReference(ir.store, destName) - if err != nil { - return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName) - } - pair := pullRefPair{ - image: destName, - srcRef: ref, - dstRef: destRef, - } - refPairs = append(refPairs, pair) - } - - return refPairs, nil -} - -// pullGoalFromImageReference returns a pull goal for a single ImageReference, depending on the used transport. -// Note that callers are responsible for invoking (*pullGoal).cleanUp() to clean up possibly open resources. -func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (*pullGoal, error) { - // supports pulling from docker-archive, oci, and registries - switch srcRef.Transport().Name() { - case DockerArchive: - reader, readerRef, err := dockerarchive.NewReaderForReference(sc, srcRef) - if err != nil { - return nil, err - } - - pairs, err := ir.getPullRefPairsFromDockerArchiveReference(ctx, reader, readerRef, sc) - if err != nil { - // No need to defer for a single error path. - if err := reader.Close(); err != nil { - logrus.Error(err.Error()) - } - return nil, err - } - - return &pullGoal{ - pullAllPairs: true, - refPairs: pairs, - cleanUpFuncs: []cleanUpFunc{reader.Close}, - }, nil - - case OCIArchive: - // retrieve the manifest from index.json to access the image name - manifest, err := ociarchive.LoadManifestDescriptor(srcRef) - if err != nil { - return nil, errors.Wrapf(err, "error loading manifest for %q", srcRef) - } - var dest string - if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { - // If the input image has no image.ref.name, we need to feed it a dest anyways - // use the hex of the digest - dest, err = getImageDigest(ctx, srcRef, sc) - if err != nil { - return nil, errors.Wrapf(err, "error getting image digest; image reference not found") - } - } else { - dest = manifest.Annotations["org.opencontainers.image.ref.name"] - } - return ir.getSinglePullRefPairGoal(srcRef, dest) - - case DirTransport: - image := toLocalImageName(srcRef.StringWithinTransport()) - return ir.getSinglePullRefPairGoal(srcRef, image) - - case OCIDirTransport: - split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2) - image := toLocalImageName(split[0]) - return ir.getSinglePullRefPairGoal(srcRef, image) - - default: - return ir.getSinglePullRefPairGoal(srcRef, imgName) - } -} - -// toLocalImageName converts an image name into a 'localhost/' prefixed one -func toLocalImageName(imageName string) string { - return fmt.Sprintf( - "%s/%s", - DefaultLocalRegistry, - strings.TrimLeft(imageName, "/"), - ) -} - -// pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries. -// Use pullImageFromReference if the source is known precisely. -func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string, progress chan types.ProgressProperties) ([]string, error) { - var goal *pullGoal - sc := GetSystemContext(signaturePolicyPath, authfile, false) - if dockerOptions != nil { - sc.OSChoice = dockerOptions.OSChoice - sc.ArchitectureChoice = dockerOptions.ArchitectureChoice - sc.VariantChoice = dockerOptions.VariantChoice - sc.SystemRegistriesConfPath = dockerOptions.RegistriesConfPath - } - if signaturePolicyPath == "" { - sc.SignaturePolicyPath = ir.SignaturePolicyPath - } - sc.BlobInfoCacheDir = filepath.Join(ir.store.GraphRoot(), "cache") - srcRef, err := alltransports.ParseImageName(inputName) - if err != nil { - // We might be pulling with an unqualified image reference in which case - // we need to make sure that we're not using any other transport. - srcTransport := alltransports.TransportFromImageName(inputName) - if srcTransport != nil && srcTransport.Name() != DockerTransport { - return nil, err - } - goal, err = ir.pullGoalFromPossiblyUnqualifiedName(sc, writer, inputName) - if err != nil { - return nil, errors.Wrap(err, "error getting default registries to try") - } - } else { - goal, err = ir.pullGoalFromImageReference(ctx, srcRef, inputName, sc) - if err != nil { - return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName) - } - } - defer goal.cleanUp() - return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, label, progress) -} - -// pullImageFromReference pulls an image from a types.imageReference. -func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions) ([]string, error) { - sc := GetSystemContext(signaturePolicyPath, authfile, false) - if dockerOptions != nil { - sc.OSChoice = dockerOptions.OSChoice - sc.ArchitectureChoice = dockerOptions.ArchitectureChoice - sc.VariantChoice = dockerOptions.VariantChoice - } - goal, err := ir.pullGoalFromImageReference(ctx, srcRef, transports.ImageName(srcRef), sc) - if err != nil { - return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef)) - } - defer goal.cleanUp() - return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, nil, nil) -} - -func cleanErrorMessage(err error) string { - errMessage := strings.TrimPrefix(errors.Cause(err).Error(), "errors:\n") - errMessage = strings.Split(errMessage, "\n")[0] - return fmt.Sprintf(" %s\n", errMessage) -} - -// doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead. -func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string, progress chan types.ProgressProperties) ([]string, error) { - policyContext, err := getPolicyContext(sc) - if err != nil { - return nil, err - } - defer func() { - if err := policyContext.Destroy(); err != nil { - logrus.Errorf("failed to destroy policy context: %q", err) - } - }() - - var systemRegistriesConfPath string - if dockerOptions != nil && dockerOptions.RegistriesConfPath != "" { - systemRegistriesConfPath = dockerOptions.RegistriesConfPath - } else { - systemRegistriesConfPath = registries.SystemRegistriesConfPath() - } - - var ( - images []string - pullErrors []error - ) - - for _, imageInfo := range goal.refPairs { - copyOptions := getCopyOptions(sc, writer, dockerOptions, nil, signingOptions, "", nil) - copyOptions.SourceCtx.SystemRegistriesConfPath = systemRegistriesConfPath // FIXME: Set this more globally. Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place. - if progress != nil { - copyOptions.Progress = progress - copyOptions.ProgressInterval = time.Second - } - // Print the following statement only when pulling from a docker or atomic registry - if writer != nil && (imageInfo.srcRef.Transport().Name() == DockerTransport || imageInfo.srcRef.Transport().Name() == AtomicTransport) { - if _, err := io.WriteString(writer, fmt.Sprintf("Trying to pull %s...\n", imageInfo.image)); err != nil { - return nil, err - } - } - // If the label is not nil, check if the label exists and if not, return err - if label != nil { - if err := checkRemoteImageForLabel(ctx, *label, imageInfo, sc); err != nil { - return nil, err - } - } - imageInfo := imageInfo - if err = retry.RetryIfNecessary(ctx, func() error { - _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) - return err - }, retryOptions); err != nil { - pullErrors = append(pullErrors, err) - logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) - if writer != nil { - _, _ = io.WriteString(writer, cleanErrorMessage(err)) - } - } else { - if imageInfo.resolvedShortname != nil { - if err := imageInfo.resolvedShortname.Record(); err != nil { - logrus.Errorf("Error recording short-name alias %q: %v", imageInfo.resolvedShortname.Value.String(), err) - } - } - if !goal.pullAllPairs { - ir.newImageEvent(events.Pull, "") - return []string{imageInfo.image}, nil - } - images = append(images, imageInfo.image) - } - } - // If no image was found, we should handle. Lets be nicer to the user - // and see if we can figure out why. - if len(images) == 0 { - if goal.resolved != nil { - return nil, goal.resolved.FormatPullErrors(pullErrors) - } - return nil, errorhandling.JoinErrors(pullErrors) - } - - ir.newImageEvent(events.Pull, images[0]) - return images, nil -} - -// pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible -// image references to try pulling in combination with the registries.conf file as well -func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(sys *types.SystemContext, writer io.Writer, inputName string) (*pullGoal, error) { - if sys == nil { - sys = &types.SystemContext{} - } - - resolved, err := shortnames.Resolve(sys, inputName) - if err != nil { - return nil, err - } - - if desc := resolved.Description(); len(desc) > 0 { - logrus.Debug(desc) - if writer != nil { - if _, err := writer.Write([]byte(desc + "\n")); err != nil { - return nil, err - } - } - } - - refPairs := []pullRefPair{} - for i, candidate := range resolved.PullCandidates { - srcRef, err := docker.NewReference(candidate.Value) - if err != nil { - return nil, err - } - ps, err := ir.getPullRefPair(srcRef, candidate.Value.String()) - if err != nil { - return nil, err - } - ps.resolvedShortname = &resolved.PullCandidates[i] - refPairs = append(refPairs, ps) - } - return &pullGoal{ - refPairs: refPairs, - pullAllPairs: false, - shortName: inputName, - resolved: resolved, - }, nil -} - -// checkRemoteImageForLabel checks if the remote image has a specific label. if the label exists, we -// return nil, else we return an error -func checkRemoteImageForLabel(ctx context.Context, label string, imageInfo pullRefPair, sc *types.SystemContext) error { - labelImage, err := imageInfo.srcRef.NewImage(ctx, sc) - if err != nil { - return err - } - remoteInspect, err := labelImage.Inspect(ctx) - if err != nil { - return err - } - // Labels are case insensitive; so we iterate instead of simple lookup - for k := range remoteInspect.Labels { - if strings.EqualFold(label, k) { - return nil - } - } - return errors.Errorf("%s has no label %s in %q", imageInfo.image, label, remoteInspect.Labels) -} diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go deleted file mode 100644 index d2930451c..000000000 --- a/libpod/image/pull_test.go +++ /dev/null @@ -1,394 +0,0 @@ -package image - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/containers/image/v5/transports" - "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - "github.com/containers/storage" - "github.com/containers/storage/pkg/idtools" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// newTestRuntime returns a *Runtime implementation and a cleanup function which the caller is expected to call. -func newTestRuntime(t *testing.T) (*Runtime, func()) { - wd, err := ioutil.TempDir("", "testStorageRuntime") - require.NoError(t, err) - err = os.MkdirAll(wd, 0700) - require.NoError(t, err) - - store, err := storage.GetStore(storage.StoreOptions{ - RunRoot: filepath.Join(wd, "run"), - GraphRoot: filepath.Join(wd, "root"), - GraphDriverName: "vfs", - GraphDriverOptions: []string{}, - UIDMap: []idtools.IDMap{{ - ContainerID: 0, - HostID: os.Getuid(), - Size: 1, - }}, - GIDMap: []idtools.IDMap{{ - ContainerID: 0, - HostID: os.Getgid(), - Size: 1, - }}, - }) - require.NoError(t, err) - - ir := NewImageRuntimeFromStore(store) - cleanup := func() { _ = os.RemoveAll(wd) } - return ir, cleanup -} - -// storageReferenceWithoutLocation returns ref.StringWithinTransport(), -// stripping the [store-specification] prefix from containers/image/storage reference format. -func storageReferenceWithoutLocation(ref types.ImageReference) string { - res := ref.StringWithinTransport() - if res[0] == '[' { - closeIndex := strings.IndexRune(res, ']') - if closeIndex > 0 { - res = res[closeIndex+1:] - } - } - return res -} - -func TestGetPullRefPair(t *testing.T) { - const imageID = "@0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - - ir, cleanup := newTestRuntime(t) - defer cleanup() - - for _, c := range []struct{ srcName, destName, expectedImage, expectedDstName string }{ - // == Source does not have a Docker reference (as is the case for docker-archive:, oci-archive, dir:); destination formats: - { // registry/name, no tag: - "dir:/dev/this-does-not-exist", "example.com/from-directory", - "example.com/from-directory", "example.com/from-directory:latest", - }, - { // name, no registry, no tag: - "dir:/dev/this-does-not-exist", "from-directory", - "localhost/from-directory", "localhost/from-directory:latest", - }, - { // registry/name:tag : - "dir:/dev/this-does-not-exist", "example.com/from-directory:notlatest", - "example.com/from-directory:notlatest", "example.com/from-directory:notlatest", - }, - { // name:tag, no registry: - "dir:/dev/this-does-not-exist", "from-directory:notlatest", - "localhost/from-directory:notlatest", "localhost/from-directory:notlatest", - }, - { // name@digest, no registry: - "dir:/dev/this-does-not-exist", "from-directory" + digestSuffix, - "localhost/from-directory" + digestSuffix, "localhost/from-directory" + digestSuffix, - }, - { // registry/name@digest: - "dir:/dev/this-does-not-exist", "example.com/from-directory" + digestSuffix, - "example.com/from-directory" + digestSuffix, "example.com/from-directory" + digestSuffix, - }, - { // ns/name:tag, no registry: - "dir:/dev/this-does-not-exist", "ns/from-directory:notlatest", - "localhost/ns/from-directory:notlatest", "localhost/ns/from-directory:notlatest", - }, - { // containers-storage image ID - "dir:/dev/this-does-not-exist", imageID, - imageID, imageID, - }, - // == Source does have a Docker reference. - // In that case getPullListFromRef uses the full transport:name input as a destName, - // which would be invalid in the returned dstName - but dstName is derived from the source, so it does not really matter _so_ much. - // Note that unlike real-world use we use different :source and :destination to verify the data flow in more detail. - { // registry/name:tag - "docker://example.com/busybox:source", "docker://example.com/busybox:destination", - "docker://example.com/busybox:destination", "example.com/busybox:source", - }, - { // Implied docker.io/library and :latest - "docker://busybox", "docker://busybox:destination", - "docker://busybox:destination", "docker.io/library/busybox:latest", - }, - // == Invalid destination format. - {"tarball:/dev/null", "tarball:/dev/null", "", ""}, - } { - testDescription := fmt.Sprintf("%#v %#v", c.srcName, c.destName) - srcRef, err := alltransports.ParseImageName(c.srcName) - require.NoError(t, err, testDescription) - - res, err := ir.getPullRefPair(srcRef, c.destName) - if c.expectedDstName == "" { - assert.Error(t, err, testDescription) - } else { - require.NoError(t, err, testDescription) - assert.Equal(t, c.expectedImage, res.image, testDescription) - assert.Equal(t, srcRef, res.srcRef, testDescription) - assert.Equal(t, c.expectedDstName, storageReferenceWithoutLocation(res.dstRef), testDescription) - } - } -} - -func TestPullGoalFromImageReference(t *testing.T) { - ir, cleanup := newTestRuntime(t) - defer cleanup() - - type expected struct{ image, dstName string } - for _, c := range []struct { - srcName string - expected []expected - expectedPullAllPairs bool - }{ - // == docker-archive: - {"docker-archive:/dev/this-does-not-exist", nil, false}, // Input does not exist. - {"docker-archive:/dev/null", nil, false}, // Input exists but does not contain a manifest. - // FIXME: The implementation has extra code for len(manifest) == 0?! That will fail in getImageDigest anyway. - { // RepoTags is empty - "docker-archive:testdata/docker-unnamed.tar.xz", - []expected{{"@ec9293436c2e66da44edb9efb8d41f6b13baf62283ebe846468bc992d76d7951", "@ec9293436c2e66da44edb9efb8d41f6b13baf62283ebe846468bc992d76d7951"}}, - true, - }, - { // RepoTags is a [docker.io/library/]name:latest, normalized to the short format. - "docker-archive:testdata/docker-name-only.tar.xz", - []expected{{"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"}}, - true, - }, - { // RepoTags is a registry/name:latest - "docker-archive:testdata/docker-registry-name.tar.xz", - []expected{{"example.com/empty:latest", "example.com/empty:latest"}}, - true, - }, - { // RepoTags has multiple items for a single image - "docker-archive:testdata/docker-two-names.tar.xz", - []expected{ - {"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"}, - {"example.com/empty:latest", "example.com/empty:latest"}, - }, - true, - }, - { // Reference image by name in multi-image archive - "docker-archive:testdata/docker-two-images.tar.xz:example.com/empty:latest", - []expected{ - {"example.com/empty:latest", "example.com/empty:latest"}, - }, - true, - }, - { // Reference image by name in multi-image archive - "docker-archive:testdata/docker-two-images.tar.xz:example.com/empty/but:different", - []expected{ - {"example.com/empty/but:different", "example.com/empty/but:different"}, - }, - true, - }, - { // Reference image by index in multi-image archive - "docker-archive:testdata/docker-two-images.tar.xz:@0", - []expected{ - {"example.com/empty:latest", "example.com/empty:latest"}, - }, - true, - }, - { // Reference image by index in multi-image archive - "docker-archive:testdata/docker-two-images.tar.xz:@1", - []expected{ - {"example.com/empty/but:different", "example.com/empty/but:different"}, - }, - true, - }, - { // Reference entire multi-image archive must fail (more than one manifest) - "docker-archive:testdata/docker-two-images.tar.xz", - []expected{}, - true, - }, - - // == oci-archive: - {"oci-archive:/dev/this-does-not-exist", nil, false}, // Input does not exist. - {"oci-archive:/dev/null", nil, false}, // Input exists but does not contain a manifest. - // FIXME: The remaining tests are commented out for now, because oci-archive: does not work unprivileged. - // { // No name annotation - // "oci-archive:testdata/oci-unnamed.tar.gz", - // []expected{{"@5c8aca8137ac47e84c69ae93ce650ce967917cc001ba7aad5494073fac75b8b6", "@5c8aca8137ac47e84c69ae93ce650ce967917cc001ba7aad5494073fac75b8b6"}}, - // false, - // }, - // { // Name is a name:latest (no normalization is defined). - // "oci-archive:testdata/oci-name-only.tar.gz", - // []expected{{"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"}}, - // false, - // }, - // { // Name is a registry/name:latest - // "oci-archive:testdata/oci-registry-name.tar.gz", - // []expected{{"example.com/empty:latest", "example.com/empty:latest"}}, - // false, - // }, - // // Name exists, but is an invalid Docker reference; such names will fail when creating dstReference. - // {"oci-archive:testdata/oci-non-docker-name.tar.gz", nil, false}, - // Maybe test support of two images in a single archive? It should be transparently handled by adding a reference to srcRef. - - // == dir: - { // Absolute path - "dir:/dev/this-does-not-exist", - []expected{{"localhost/dev/this-does-not-exist", "localhost/dev/this-does-not-exist:latest"}}, - false, - }, - { // Relative path, single element. - "dir:this-does-not-exist", - []expected{{"localhost/this-does-not-exist", "localhost/this-does-not-exist:latest"}}, - false, - }, - { // Relative path, multiple elements. - "dir:testdata/this-does-not-exist", - []expected{{"localhost/testdata/this-does-not-exist", "localhost/testdata/this-does-not-exist:latest"}}, - false, - }, - - // == Others, notably: - // === docker:// (has ImageReference.DockerReference) - { // Fully-specified input - "docker://docker.io/library/busybox:latest", - []expected{{"docker://docker.io/library/busybox:latest", "docker.io/library/busybox:latest"}}, - false, - }, - { // Minimal form of the input - "docker://busybox", - []expected{{"docker://busybox", "docker.io/library/busybox:latest"}}, - false, - }, - - // === tarball: (as an example of what happens when ImageReference.DockerReference is nil). - // FIXME? This tries to parse "tarball:/dev/null" as a storageReference, and fails. - // (This is NOT an API promise that the results will continue to be this way.) - {"tarball:/dev/null", nil, false}, - } { - srcRef, err := alltransports.ParseImageName(c.srcName) - require.NoError(t, err, c.srcName) - - res, err := ir.pullGoalFromImageReference(context.Background(), srcRef, c.srcName, nil) - if len(c.expected) == 0 { - assert.Error(t, err, c.srcName) - } else { - require.NoError(t, err, c.srcName) - require.Len(t, res.refPairs, len(c.expected), c.srcName) - for i, e := range c.expected { - testDescription := fmt.Sprintf("%s #%d", c.srcName, i) - assert.Equal(t, e.image, res.refPairs[i].image, testDescription) - assert.Equal(t, transports.ImageName(srcRef), transports.ImageName(res.refPairs[i].srcRef), testDescription) - assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription) - } - assert.Equal(t, c.expectedPullAllPairs, res.pullAllPairs, c.srcName) - } - } -} - -const registriesConfWithSearch = `unqualified-search-registries = ['example.com', 'docker.io']` - -func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { - const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - type pullRefStrings struct{ image, srcRef, dstName string } // pullRefPair with string data only - - registriesConf, err := ioutil.TempFile("", "TestPullGoalFromPossiblyUnqualifiedName") - require.NoError(t, err) - defer registriesConf.Close() - defer os.Remove(registriesConf.Name()) - - err = ioutil.WriteFile(registriesConf.Name(), []byte(registriesConfWithSearch), 0600) - require.NoError(t, err) - - ir, cleanup := newTestRuntime(t) - defer cleanup() - - sc := GetSystemContext("", "", false) - - aliasesConf, err := ioutil.TempFile("", "short-name-aliases.conf") - require.NoError(t, err) - defer aliasesConf.Close() - defer os.Remove(aliasesConf.Name()) - sc.UserShortNameAliasConfPath = aliasesConf.Name() - sc.SystemRegistriesConfPath = registriesConf.Name() - - // Make sure to not sure the system's registries.conf.d - dir, err := ioutil.TempDir("", "example") - require.NoError(t, err) - sc.SystemRegistriesConfDirPath = dir - defer os.RemoveAll(dir) // clean up - - for _, c := range []struct { - input string - expected []pullRefStrings - }{ - {"#", nil}, // Clearly invalid. - { // Fully-explicit docker.io, name-only. - "docker.io/library/busybox", - // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, - }, - { // docker.io with implied /library/, name-only. - "docker.io/busybox", - // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, - }, - { // Qualified example.com, name-only. - "example.com/ns/busybox", - []pullRefStrings{{"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}}, - }, - { // Qualified example.com, name:tag. - "example.com/ns/busybox:notlatest", - []pullRefStrings{{"example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}}, - }, - { // Qualified example.com, name@digest. - "example.com/ns/busybox" + digestSuffix, - []pullRefStrings{{"example.com/ns/busybox" + digestSuffix, "docker://example.com/ns/busybox" + digestSuffix, - "example.com/ns/busybox" + digestSuffix}}, - }, - // Qualified example.com, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input. - {"example.com/ns/busybox:notlatest" + digestSuffix, nil}, - { // Unqualified, single-name, name-only - "busybox", - []pullRefStrings{ - {"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"}, - // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}, - }, - }, - { // Unqualified, namespaced, name-only - "ns/busybox", - []pullRefStrings{ - {"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}, - }, - }, - { // Unqualified, name:tag - "busybox:notlatest", - []pullRefStrings{ - {"example.com/busybox:notlatest", "docker://example.com/busybox:notlatest", "example.com/busybox:notlatest"}, - // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/library/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"}, - }, - }, - { // Unqualified, name@digest - "busybox" + digestSuffix, - []pullRefStrings{ - {"example.com/busybox" + digestSuffix, "docker://example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix}, - // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/library/busybox" + digestSuffix, "docker://busybox" + digestSuffix, "docker.io/library/busybox" + digestSuffix}, - }, - }, - // Unqualified, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input. - {"busybox:notlatest" + digestSuffix, nil}, - } { - res, err := ir.pullGoalFromPossiblyUnqualifiedName(sc, nil, c.input) - if len(c.expected) == 0 { - assert.Error(t, err, c.input) - } else { - assert.NoError(t, err, c.input) - for i, e := range c.expected { - testDescription := fmt.Sprintf("%s #%d (%v)", c.input, i, res.refPairs) - assert.Equal(t, e.image, res.refPairs[i].image, testDescription) - assert.Equal(t, e.srcRef, transports.ImageName(res.refPairs[i].srcRef), testDescription) - assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription) - } - assert.False(t, res.pullAllPairs, c.input) - } - } -} diff --git a/libpod/image/signing_options.go b/libpod/image/signing_options.go deleted file mode 100644 index f310da749..000000000 --- a/libpod/image/signing_options.go +++ /dev/null @@ -1,10 +0,0 @@ -package image - -// SigningOptions encapsulates settings that control whether or not we strip or -// add signatures to images when writing them. -type SigningOptions struct { - // RemoveSignatures directs us to remove any signatures which are already present. - RemoveSignatures bool - // SignBy is a key identifier of some kind, indicating that a signature should be generated using the specified private key and stored with the image. - SignBy string -} diff --git a/libpod/image/tree.go b/libpod/image/tree.go deleted file mode 100644 index c7c69462f..000000000 --- a/libpod/image/tree.go +++ /dev/null @@ -1,138 +0,0 @@ -package image - -import ( - "context" - "fmt" - "strings" - - "github.com/docker/go-units" - "github.com/pkg/errors" -) - -const ( - middleItem = "├── " - continueItem = "│ " - lastItem = "└── " -) - -type tree struct { - img *Image - imageInfo *InfoImage - layerInfo map[string]*LayerInfo - sb *strings.Builder -} - -// GenerateTree creates an image tree string representation for displaying it -// to the user. -func (i *Image) GenerateTree(whatRequires bool) (string, error) { - // Fetch map of image-layers, which is used for printing output. - layerInfo, err := GetLayersMapWithImageInfo(i.imageruntime) - if err != nil { - return "", errors.Wrapf(err, "error while retrieving layers of image %q", i.InputName) - } - - // Create an imageInfo and fill the image and layer info - imageInfo := &InfoImage{ - ID: i.ID(), - Tags: i.Names(), - } - - if err := BuildImageHierarchyMap(imageInfo, layerInfo, i.TopLayer()); err != nil { - return "", err - } - sb := &strings.Builder{} - tree := &tree{i, imageInfo, layerInfo, sb} - if err := tree.print(whatRequires); err != nil { - return "", err - } - return tree.string(), nil -} - -func (t *tree) string() string { - return t.sb.String() -} - -func (t *tree) print(whatRequires bool) error { - size, err := t.img.Size(context.Background()) - if err != nil { - return err - } - - fmt.Fprintf(t.sb, "Image ID: %s\n", t.imageInfo.ID[:12]) - fmt.Fprintf(t.sb, "Tags: %s\n", t.imageInfo.Tags) - fmt.Fprintf(t.sb, "Size: %v\n", units.HumanSizeWithPrecision(float64(*size), 4)) - if t.img.TopLayer() != "" { - fmt.Fprintf(t.sb, "Image Layers\n") - } else { - fmt.Fprintf(t.sb, "No Image Layers\n") - } - - if !whatRequires { - // fill imageInfo with layers associated with image. - // the layers will be filled such that - // (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) - // Build output from imageInfo into buffer - t.printImageHierarchy(t.imageInfo) - } else { - // fill imageInfo with layers associated with image. - // the layers will be filled such that - // (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End) - // (Forks)... intermediate Child Layer(s) -> Child Top Layer(End) - return t.printImageChildren(t.layerInfo, t.img.TopLayer(), "", true) - } - return nil -} - -// Stores all children layers which are created using given Image. -// Layers are stored as follows -// (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End) -// (Forks)... intermediate Child Layer(s) -> Child Top Layer(End) -func (t *tree) printImageChildren(layerMap map[string]*LayerInfo, layerID string, prefix string, last bool) error { - if layerID == "" { - return nil - } - ll, ok := layerMap[layerID] - if !ok { - return fmt.Errorf("lookup error: layerid %s, not found", layerID) - } - fmt.Fprint(t.sb, prefix) - - //initialize intend with middleItem to reduce middleItem checks. - intend := middleItem - if !last { - // add continueItem i.e. '|' for next iteration prefix - prefix += continueItem - } else if len(ll.ChildID) > 1 || len(ll.ChildID) == 0 { - // The above condition ensure, alignment happens for node, which has more then 1 children. - // If node is last in printing hierarchy, it should not be printed as middleItem i.e. ├── - intend = lastItem - prefix += " " - } - - var tags string - if len(ll.RepoTags) > 0 { - tags = fmt.Sprintf(" Top Layer of: %s", ll.RepoTags) - } - fmt.Fprintf(t.sb, "%sID: %s Size: %7v%s\n", intend, ll.ID[:12], units.HumanSizeWithPrecision(float64(ll.Size), 4), tags) - for count, childID := range ll.ChildID { - if err := t.printImageChildren(layerMap, childID, prefix, count == len(ll.ChildID)-1); err != nil { - return err - } - } - return nil -} - -// prints the layers info of image -func (t *tree) printImageHierarchy(imageInfo *InfoImage) { - for count, l := range imageInfo.Layers { - var tags string - intend := middleItem - if len(l.RepoTags) > 0 { - tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags) - } - if count == len(imageInfo.Layers)-1 { - intend = lastItem - } - fmt.Fprintf(t.sb, "%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags) - } -} diff --git a/libpod/image/utils.go b/libpod/image/utils.go deleted file mode 100644 index dfe35c017..000000000 --- a/libpod/image/utils.go +++ /dev/null @@ -1,182 +0,0 @@ -package image - -import ( - "fmt" - "io" - "net/url" - "regexp" - "strings" - - cp "github.com/containers/image/v5/copy" - "github.com/containers/image/v5/docker/reference" - "github.com/containers/image/v5/signature" - "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/libpod/define" - "github.com/containers/storage" - "github.com/pkg/errors" -) - -// findImageInRepotags takes an imageParts struct and searches images' repotags for -// a match on name:tag -func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, error) { - _, searchName, searchSuspiciousTagValueForSearch := search.suspiciousRefNameTagValuesForSearch() - type Candidate struct { - name string - image *Image - } - var candidates []Candidate - for _, image := range images { - for _, name := range image.Names() { - d, err := decompose(name) - // if we get an error, ignore and keep going - if err != nil { - continue - } - _, dName, dSuspiciousTagValueForSearch := d.suspiciousRefNameTagValuesForSearch() - if dSuspiciousTagValueForSearch != searchSuspiciousTagValueForSearch { - continue - } - if dName == searchName || strings.HasSuffix(dName, "/"+searchName) { - candidates = append(candidates, Candidate{ - name: name, - image: image, - }) - } - } - } - if len(candidates) == 0 { - return nil, errors.Wrapf(define.ErrNoSuchImage, "unable to find a name and tag match for %s in repotags", searchName) - } - - // If more then one candidate and the candidates all have same name - // and only one is read/write return it. - // Otherwise return error with the list of candidates - if len(candidates) > 1 { - var ( - rwImage *Image - rwImageCnt int - ) - names := make(map[string]bool) - for _, c := range candidates { - names[c.name] = true - if !c.image.IsReadOnly() { - rwImageCnt++ - rwImage = c.image - } - } - // If only one name used and have read/write image return it - if len(names) == 1 && rwImageCnt == 1 { - return rwImage.image, nil - } - keys := []string{} - for k := range names { - keys = append(keys, k) - } - if rwImageCnt > 1 { - return nil, errors.Wrapf(define.ErrMultipleImages, "found multiple read/write images %s", strings.Join(keys, ",")) - } - return nil, errors.Wrapf(define.ErrMultipleImages, "found multiple read/only images %s", strings.Join(keys, ",")) - } - return candidates[0].image.image, nil -} - -// getCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters, inheriting some from sc. -func getCopyOptions(sc *types.SystemContext, reportWriter io.Writer, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, manifestType string, additionalDockerArchiveTags []reference.NamedTagged) *cp.Options { - if srcDockerRegistry == nil { - srcDockerRegistry = &DockerRegistryOptions{} - } - if destDockerRegistry == nil { - destDockerRegistry = &DockerRegistryOptions{} - } - srcContext := srcDockerRegistry.GetSystemContext(sc, additionalDockerArchiveTags) - destContext := destDockerRegistry.GetSystemContext(sc, additionalDockerArchiveTags) - return &cp.Options{ - RemoveSignatures: signing.RemoveSignatures, - SignBy: signing.SignBy, - ReportWriter: reportWriter, - SourceCtx: srcContext, - DestinationCtx: destContext, - ForceManifestMIMEType: manifestType, - } -} - -// getPolicyContext sets up, initializes and returns a new context for the specified policy -func getPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error) { - policy, err := signature.DefaultPolicy(ctx) - if err != nil { - return nil, err - } - - policyContext, err := signature.NewPolicyContext(policy) - if err != nil { - return nil, err - } - return policyContext, nil -} - -// hasTransport determines if the image string contains '://', returns bool -func hasTransport(image string) bool { - return strings.Contains(image, "://") -} - -// GetAdditionalTags returns a list of reference.NamedTagged for the -// additional tags given in images -func GetAdditionalTags(images []string) ([]reference.NamedTagged, error) { - var allTags []reference.NamedTagged - for _, img := range images { - ref, err := reference.ParseNormalizedNamed(img) - if err != nil { - return nil, errors.Wrapf(err, "error parsing additional tags") - } - refTagged, isTagged := ref.(reference.NamedTagged) - if isTagged { - allTags = append(allTags, refTagged) - } - } - return allTags, nil -} - -// IsValidImageURI checks if image name has valid format -func IsValidImageURI(imguri string) (bool, error) { - uri := "http://" + imguri - u, err := url.Parse(uri) - if err != nil { - return false, errors.Wrapf(err, "invalid image uri: %s", imguri) - } - reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`) - ret := reg.FindAllString(u.Host, -1) - if len(ret) == 0 { - return false, errors.Wrapf(err, "invalid image uri: %s", imguri) - } - reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`) - ret = reg.FindAllString(u.Fragment, -1) - if len(ret) == 0 { - return false, errors.Wrapf(err, "invalid image uri: %s", imguri) - } - return true, nil -} - -// imageNameForSaveDestination returns a Docker-like reference appropriate for saving img, -// which the user referred to as imgUserInput; or an empty string, if there is no appropriate -// reference. -func imageNameForSaveDestination(img *Image, imgUserInput string) string { - if strings.Contains(img.ID(), imgUserInput) { - return "" - } - - prepend := "" - localRegistryPrefix := fmt.Sprintf("%s/", DefaultLocalRegistry) - if !strings.HasPrefix(imgUserInput, localRegistryPrefix) { - // we need to check if localhost was added to the image name in NewFromLocal - for _, name := range img.Names() { - // If the user is saving an image in the localhost registry, getLocalImage need - // a name that matches the format localhost/<tag1>:<tag2> or localhost/<tag>:latest to correctly - // set up the manifest and save. - if strings.HasPrefix(name, localRegistryPrefix) && (strings.HasSuffix(name, imgUserInput) || strings.HasSuffix(name, fmt.Sprintf("%s:latest", imgUserInput))) { - prepend = localRegistryPrefix - break - } - } - } - return fmt.Sprintf("%s%s", prepend, imgUserInput) -} diff --git a/libpod/network/config.go b/libpod/network/config.go index 294e23509..ac4478602 100644 --- a/libpod/network/config.go +++ b/libpod/network/config.go @@ -149,7 +149,18 @@ type DNSNameConfig struct { Capabilities map[string]bool `json:"capabilities"` } +// PodmanMachineConfig enables port handling on the host OS +type PodmanMachineConfig struct { + PluginType string `json:"type"` + Capabilities map[string]bool `json:"capabilities"` +} + // Bytes outputs the configuration as []byte func (d DNSNameConfig) Bytes() ([]byte, error) { return json.MarshalIndent(d, "", "\t") } + +// Bytes outputs the configuration as []byte +func (p PodmanMachineConfig) Bytes() ([]byte, error) { + return json.MarshalIndent(p, "", "\t") +} diff --git a/libpod/network/create.go b/libpod/network/create.go index 4fe9b445f..aca8150b5 100644 --- a/libpod/network/create.go +++ b/libpod/network/create.go @@ -231,6 +231,10 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon plugins = append(plugins, NewDNSNamePlugin(DefaultPodmanDomainName)) } } + // Add the podman-machine CNI plugin if we are in a machine + if runtimeConfig.MachineEnabled() { // check if we are in a machine vm + plugins = append(plugins, NewPodmanMachinePlugin()) + } ncList["plugins"] = plugins b, err := json.MarshalIndent(ncList, "", " ") if err != nil { diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index 08816f2bd..d2031df6d 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -293,3 +293,12 @@ func getCreatedTimestamp(config *config.Config, netconf *libcni.NetworkConfigLis created := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) // nolint: unconvert return &created, nil } + +func NewPodmanMachinePlugin() PodmanMachineConfig { + caps := make(map[string]bool, 1) + caps["portMappings"] = true + return PodmanMachineConfig{ + PluginType: "podman-machine", + Capabilities: caps, + } +} diff --git a/libpod/reset.go b/libpod/reset.go index 4199e9b76..8e753e845 100644 --- a/libpod/reset.go +++ b/libpod/reset.go @@ -6,7 +6,9 @@ import ( "os" "path/filepath" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod/define" + "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" @@ -49,20 +51,11 @@ func (r *Runtime) Reset(ctx context.Context) error { logrus.Errorf("Error stopping pause process: %v", err) } - ir := r.ImageRuntime() - images, err := ir.GetImages() - if err != nil { - return err + rmiOptions := &libimage.RemoveImagesOptions{Filters: []string{"readonly=false"}} + if _, rmiErrors := r.LibimageRuntime().RemoveImages(ctx, nil, rmiOptions); rmiErrors != nil { + return errorhandling.JoinErrors(rmiErrors) } - for _, i := range images { - if err := i.Remove(ctx, true); err != nil { - if errors.Cause(err) == define.ErrNoSuchImage { - continue - } - logrus.Errorf("Error removing image %s: %v", i.ID(), err) - } - } volumes, err := r.state.AllVolumes() if err != nil { return err diff --git a/libpod/runtime.go b/libpod/runtime.go index 3518ed25a..80fe92b54 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -14,13 +14,13 @@ import ( "sync" "syscall" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/pkg/sysregistriesv2" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/libpod/lock" "github.com/containers/podman/v3/libpod/plugin" "github.com/containers/podman/v3/libpod/shutdown" @@ -76,7 +76,7 @@ type Runtime struct { runtimeFlags []string netPlugin ocicni.CNIPlugin conmonPath string - imageRuntime *image.Runtime + libimageRuntime *libimage.Runtime lockManager lock.Manager // doRenumber indicates that the runtime should perform a lock renumber @@ -371,9 +371,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { return err } runtime.eventer = eventer - if runtime.imageRuntime != nil { - runtime.imageRuntime.Eventer = eventer - } + // TODO: events for libimage // Set up containers/image if runtime.imageContext == nil { @@ -836,21 +834,21 @@ func (r *Runtime) configureStore() error { // images r.storageService = getStorageService(r.store) - ir := image.NewImageRuntimeFromStore(r.store) - ir.SignaturePolicyPath = r.config.Engine.SignaturePolicyPath - ir.EventsLogFilePath = r.config.Engine.EventsLogFilePath - ir.EventsLogger = r.config.Engine.EventsLogger - - r.imageRuntime = ir + runtimeOptions := &libimage.RuntimeOptions{ + SystemContext: r.imageContext, + } + libimageRuntime, err := libimage.RuntimeFromStore(store, runtimeOptions) + if err != nil { + return err + } + r.libimageRuntime = libimageRuntime return nil } -// ImageRuntime returns the imageruntime for image operations. -// If WithNoStore() was used, no image runtime will be available, and this -// function will return nil. -func (r *Runtime) ImageRuntime() *image.Runtime { - return r.imageRuntime +// LibimageRuntime ... to allow for a step-by-step migration to libimage. +func (r *Runtime) LibimageRuntime() *libimage.Runtime { + return r.libimageRuntime } // SystemContext returns the imagecontext diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 0acf88cbc..328f47c12 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -296,7 +296,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai return nil, errors.Wrapf(define.ErrInternal, "pod %s cgroup is not set", pod.ID()) } ctr.config.CgroupParent = podCgroup - } else { + } else if !rootless.IsRootless() { ctr.config.CgroupParent = CgroupfsDefaultCgroupParent } } else if strings.HasSuffix(path.Base(ctr.config.CgroupParent), ".slice") { diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 2b101c01f..a42f9a365 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -2,158 +2,50 @@ package libpod import ( "context" - "fmt" "io" "io/ioutil" - "net/http" - "net/url" "os" buildahDefine "github.com/containers/buildah/define" "github.com/containers/buildah/imagebuildah" - "github.com/containers/image/v5/directory" + "github.com/containers/common/libimage" "github.com/containers/image/v5/docker/reference" - ociarchive "github.com/containers/image/v5/oci/archive" - "github.com/containers/image/v5/oci/layout" - "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/util" - "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" - - dockerarchive "github.com/containers/image/v5/docker/archive" - v1 "github.com/opencontainers/image-spec/specs-go/v1" ) // Runtime API -// RemoveImage deletes an image from local storage -// Images being used by running containers can only be removed if force=true -func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) (*image.ImageDeleteResponse, error) { - response := image.ImageDeleteResponse{} - r.lock.Lock() - defer r.lock.Unlock() - - if !r.valid { - return nil, define.ErrRuntimeStopped - } +// RemoveContainersForImageCallback returns a callback that can be used in +// `libimage`. When forcefully removing images, containers using the image +// should be removed as well. The callback allows for more graceful removal as +// we can use the libpod-internal removal logic. +func (r *Runtime) RemoveContainersForImageCallback(ctx context.Context) libimage.RemoveContainerFunc { + return func(imageID string) error { + r.lock.Lock() + defer r.lock.Unlock() - // Get all containers, filter to only those using the image, and remove those containers - ctrs, err := r.state.AllContainers() - if err != nil { - return nil, err - } - imageCtrs := []*Container{} - for _, ctr := range ctrs { - if ctr.config.RootfsImageID == img.ID() { - imageCtrs = append(imageCtrs, ctr) + if !r.valid { + return define.ErrRuntimeStopped } - } - if len(imageCtrs) > 0 && (len(img.Names()) <= 1 || (force && img.InputIsID())) { - if force { - for _, ctr := range imageCtrs { + ctrs, err := r.state.AllContainers() + if err != nil { + return err + } + for _, ctr := range ctrs { + if ctr.config.RootfsImageID == imageID { if err := r.removeContainer(ctx, ctr, true, false, false); err != nil { - return nil, errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID()) + return errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", imageID, ctr.ID()) } } - } else { - return nil, errors.Wrapf(define.ErrImageInUse, "could not remove image %s as it is being used by %d containers", img.ID(), len(imageCtrs)) } + // Note that `libimage` will take care of removing any leftover + // containers from the storage. + return nil } - - hasChildren, err := img.IsParent(ctx) - if err != nil { - logrus.Warnf("error determining if an image is a parent: %v, ignoring the error", err) - hasChildren = false - } - - if (len(img.Names()) > 1 && !img.InputIsID()) || hasChildren { - // If the image has multiple reponames, we do not technically delete - // the image. we figure out which repotag the user is trying to refer - // to and untag it. - repoName, err := img.MatchRepoTag(img.InputName) - if hasChildren && errors.Cause(err) == image.ErrRepoTagNotFound { - return nil, errors.Wrapf(define.ErrImageInUse, - "unable to delete %q (cannot be forced) - image has dependent child images", img.ID()) - } - if err != nil { - return nil, err - } - if err := img.UntagImage(repoName); err != nil { - return nil, err - } - response.Untagged = append(response.Untagged, repoName) - return &response, nil - } else if len(img.Names()) > 1 && img.InputIsID() && !force { - // If the user requests to delete an image by ID and the image has multiple - // reponames and no force is applied, we error out. - return nil, errors.Wrapf(define.ErrImageInUse, - "unable to delete %s (must force) - image is referred to in multiple tags", img.ID()) - } - err = img.Remove(ctx, force) - if err != nil && errors.Cause(err) == storage.ErrImageUsedByContainer { - if errStorage := r.rmStorageContainers(force, img); errStorage == nil { - // Containers associated with the image should be deleted now, - // let's try removing the image again. - err = img.Remove(ctx, force) - } else { - err = errStorage - } - } - response.Untagged = append(response.Untagged, img.Names()...) - response.Deleted = img.ID() - return &response, err -} - -// Remove containers that are in storage rather than Podman. -func (r *Runtime) rmStorageContainers(force bool, image *image.Image) error { - ctrIDs, err := storageContainers(image.ID(), r.store) - if err != nil { - return errors.Wrapf(err, "error getting containers for image %q", image.ID()) - } - - if len(ctrIDs) > 0 && !force { - return storage.ErrImageUsedByContainer - } - - if len(ctrIDs) > 0 && force { - if err = removeStorageContainers(ctrIDs, r.store); err != nil { - return errors.Wrapf(err, "error removing containers %v for image %q", ctrIDs, image.ID()) - } - } - return nil -} - -// Returns a list of storage containers associated with the given ImageReference -func storageContainers(imageID string, store storage.Store) ([]string, error) { - ctrIDs := []string{} - containers, err := store.Containers() - if err != nil { - return nil, err - } - for _, ctr := range containers { - if ctr.ImageID == imageID { - ctrIDs = append(ctrIDs, ctr.ID) - } - } - return ctrIDs, nil -} - -// Removes the containers passed in the array. -func removeStorageContainers(ctrIDs []string, store storage.Store) error { - for _, ctrID := range ctrIDs { - if _, err := store.Unmount(ctrID, true); err != nil { - return errors.Wrapf(err, "could not unmount container %q to remove it", ctrID) - } - - if err := store.DeleteContainer(ctrID); err != nil { - return errors.Wrapf(err, "could not remove container %q", ctrID) - } - } - return nil } // newBuildEvent creates a new event based on completion of a built image @@ -177,89 +69,6 @@ func (r *Runtime) Build(ctx context.Context, options buildahDefine.BuildOptions, return id, ref, err } -// Import is called as an intermediary to the image library Import -func (r *Runtime) Import(ctx context.Context, source, reference, signaturePolicyPath string, changes []string, history string, quiet bool) (string, error) { - var ( - writer io.Writer - err error - ) - - ic := v1.ImageConfig{} - if len(changes) > 0 { - config, err := util.GetImageConfig(changes) - if err != nil { - return "", errors.Wrapf(err, "error adding config changes to image %q", source) - } - ic = config.ImageConfig - } - - hist := []v1.History{ - {Comment: history}, - } - - config := v1.Image{ - Config: ic, - History: hist, - } - - writer = nil - if !quiet { - writer = os.Stderr - } - - // if source is a url, download it and save to a temp file - u, err := url.ParseRequestURI(source) - if err == nil && u.Scheme != "" { - file, err := downloadFromURL(source) - if err != nil { - return "", err - } - defer os.Remove(file) - source = file - } - // if it's stdin, buffer it, too - if source == "-" { - file, err := DownloadFromFile(os.Stdin) - if err != nil { - return "", err - } - defer os.Remove(file) - source = file - } - - r.imageRuntime.SignaturePolicyPath = signaturePolicyPath - newImage, err := r.imageRuntime.Import(ctx, source, reference, writer, image.SigningOptions{}, config) - if err != nil { - return "", err - } - return newImage.ID(), nil -} - -// downloadFromURL downloads an image in the format "https:/example.com/myimage.tar" -// and temporarily saves in it $TMPDIR/importxyz, which is deleted after the image is imported -func downloadFromURL(source string) (string, error) { - fmt.Printf("Downloading from %q\n", source) - - outFile, err := ioutil.TempFile(util.Tmpdir(), "import") - if err != nil { - return "", errors.Wrap(err, "error creating file") - } - defer outFile.Close() - - response, err := http.Get(source) - if err != nil { - return "", errors.Wrapf(err, "error downloading %q", source) - } - defer response.Body.Close() - - _, err = io.Copy(outFile, response.Body) - if err != nil { - return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name()) - } - - return outFile.Name(), nil -} - // DownloadFromFile reads all of the content from the reader and temporarily // saves in it $TMPDIR/importxyz, which is deleted after the image is imported func DownloadFromFile(reader *os.File) (string, error) { @@ -278,79 +87,3 @@ func DownloadFromFile(reader *os.File) (string, error) { return outFile.Name(), nil } - -// LoadImage loads a container image into local storage -func (r *Runtime) LoadImage(ctx context.Context, inputFile string, writer io.Writer, signaturePolicy string) (string, error) { - if newImages, err := r.LoadAllImageFromArchive(ctx, writer, inputFile, signaturePolicy); err == nil { - return newImages, nil - } - - return r.LoadImageFromSingleImageArchive(ctx, writer, inputFile, signaturePolicy) -} - -// LoadAllImageFromArchive loads all images from the archive of multi-image that inputFile points to. -func (r *Runtime) LoadAllImageFromArchive(ctx context.Context, writer io.Writer, inputFile, signaturePolicy string) (string, error) { - newImages, err := r.ImageRuntime().LoadAllImagesFromDockerArchive(ctx, inputFile, signaturePolicy, writer) - if err == nil { - return getImageNames(newImages), nil - } - return "", err -} - -// LoadImageFromSingleImageArchive load image from the archive of single image that inputFile points to. -func (r *Runtime) LoadImageFromSingleImageArchive(ctx context.Context, writer io.Writer, inputFile, signaturePolicy string) (string, error) { - var saveErr error - for _, referenceFn := range []func() (types.ImageReference, error){ - func() (types.ImageReference, error) { - return dockerarchive.ParseReference(inputFile) - }, - func() (types.ImageReference, error) { - return ociarchive.NewReference(inputFile, "") - }, - func() (types.ImageReference, error) { - return directory.NewReference(inputFile) - }, - func() (types.ImageReference, error) { - return layout.NewReference(inputFile, "") - }, - func() (types.ImageReference, error) { - // This item needs to be last to break out of loop and report meaningful error message - return nil, - errors.New("payload does not match any of the supported image formats (oci-archive, oci-dir, docker-archive, docker-dir)") - }, - } { - src, err := referenceFn() - if err != nil { - saveErr = err - continue - } - - newImages, err := r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer) - if err == nil { - return getImageNames(newImages), nil - } - saveErr = err - } - return "", errors.Wrapf(saveErr, "error pulling image") -} - -// RemoveImageFromStorage goes directly to storage and attempts to remove -// the specified image. This is dangerous and should only be done if libpod -// reports that image is not known. This call is useful if you have a corrupted -// image that was never fully added to the libpod database. -func (r *Runtime) RemoveImageFromStorage(id string) error { - _, err := r.store.DeleteImage(id, true) - return err -} - -func getImageNames(images []*image.Image) string { - var names string - for i := range images { - if i == 0 { - names = images[i].InputName - } else { - names += ", " + images[i].InputName - } - } - return names -} diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index 1ae375ed9..c20153c8d 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -6,8 +6,8 @@ import ( "context" "strings" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/util" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -212,20 +212,23 @@ func (r *Runtime) createInfraContainer(ctx context.Context, p *Pod) (*Container, return nil, define.ErrRuntimeStopped } - img := p.config.InfraContainer.InfraImage - if img == "" { - img = r.config.Engine.InfraImage + imageName := p.config.InfraContainer.InfraImage + if imageName == "" { + imageName = r.config.Engine.InfraImage } - newImage, err := r.ImageRuntime().New(ctx, img, "", "", nil, nil, image.SigningOptions{}, nil, util.PullImageMissing, nil) + + pulledImages, err := r.LibimageRuntime().Pull(ctx, imageName, config.PullPolicyMissing, nil) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error pulling infra-container image") } - data, err := newImage.InspectNoSize(ctx) + newImage := pulledImages[0] + data, err := newImage.Inspect(ctx, false) if err != nil { return nil, err } - imageName := "none" + + imageName = "none" if len(newImage.Names()) > 0 { imageName = newImage.Names()[0] } diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index d97a4d3bd..263d64a7b 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -22,6 +22,7 @@ import ( "github.com/containers/podman/v3/pkg/util" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" "github.com/docker/go-units" "github.com/gorilla/schema" @@ -526,6 +527,10 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, if err := json.Unmarshal(n, &networkSettings); err != nil { return nil, err } + // do not report null instead use an empty map + if networkSettings.Networks == nil { + networkSettings.Networks = map[string]*network.EndpointSettings{} + } c := types.ContainerJSON{ ContainerJSONBase: &cb, diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 93934f1de..162a98135 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -6,12 +6,12 @@ import ( "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" "github.com/containers/podman/v3/pkg/specgen" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -50,14 +50,14 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } - newImage, err := runtime.ImageRuntime().NewFromLocal(body.Config.Image) + newImage, resolvedName, err := runtime.LibimageRuntime().LookupImage(body.Config.Image, nil) if err != nil { - if errors.Cause(err) == define.ErrNoSuchImage { + if errors.Cause(err) == storage.ErrImageUnknown { utils.Error(w, "No such image", http.StatusNotFound, err) return } - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error looking up image")) return } @@ -71,7 +71,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { imgNameOrID := newImage.ID() // if the img had multi names with the same sha256 ID, should use the InputName, not the ID if len(newImage.Names()) > 1 { - imageRef, err := utils.ParseDockerReference(newImage.InputName) + imageRef, err := utils.ParseDockerReference(resolvedName) if err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index e5caa9ea5..0b9367a17 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -4,23 +4,24 @@ import ( "context" "encoding/json" "fmt" - "io" "io/ioutil" "net/http" "os" "strings" "github.com/containers/buildah" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - image2 "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/channel" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/util" + "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -47,26 +48,35 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { // 500 server runtime := r.Context().Value("runtime").(*libpod.Runtime) - name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) - return - } tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) return } defer os.Remove(tmpfile.Name()) - if err := tmpfile.Close(); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + + name := utils.GetName(r) + imageEngine := abi.ImageEngine{Libpod: runtime} + + saveOptions := entities.ImageSaveOptions{ + Format: "docker-archive", + Output: tmpfile.Name(), + } + + if err := imageEngine.Save(r.Context(), name, nil, saveOptions); err != nil { + if errors.Cause(err) == storage.ErrImageUnknown { + utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) + return + } + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) return } - if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false, true); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) + + if err := tmpfile.Close(); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) return } + rdr, err := os.Open(tmpfile.Name()) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) @@ -105,7 +115,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + sc := runtime.SystemContext() tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, @@ -180,20 +190,13 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) } } - iid, err := runtime.Import(r.Context(), source, "", "", query.Changes, "", false) + + imageEngine := abi.ImageEngine{Libpod: runtime} + report, err := imageEngine.Import(r.Context(), entities.ImageImportOptions{Source: source, Changes: query.Changes}) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) return } - tmpfile, err := ioutil.TempFile("", "fromsrc.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) - return - } - if err := tmpfile.Close(); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) - return - } // Success utils.WriteResponse(w, http.StatusOK, struct { Status string `json:"status"` @@ -201,9 +204,9 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { ProgressDetail map[string]string `json:"progressDetail"` Id string `json:"id"` // nolint }{ - Status: iid, + Status: report.Id, ProgressDetail: map[string]string{}, - Id: iid, + Id: report.Id, }) } @@ -235,36 +238,34 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { } defer auth.RemoveAuthfile(authfile) - registryOpts := image2.DockerRegistryOptions{DockerRegistryCreds: authConf} - if sys := runtime.SystemContext(); sys != nil { - registryOpts.DockerCertPath = sys.DockerCertPath + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = authfile + if authConf != nil { + pullOptions.Username = authConf.Username + pullOptions.Password = authConf.Password + pullOptions.IdentityToken = authConf.IdentityToken } + pullOptions.Writer = os.Stderr // allows for debugging on the server stderr := channel.NewWriter(make(chan []byte)) defer stderr.Close() progress := make(chan types.ProgressProperties) + pullOptions.Progress = progress var img string runCtx, cancel := context.WithCancel(context.Background()) go func() { defer cancel() - - newImage, err := runtime.ImageRuntime().New( - runCtx, - fromImage, - "", // signature policy - authfile, - nil, // writer - ®istryOpts, - image2.SigningOptions{}, - nil, // label - util.PullImageAlways, - progress) + pulledImages, err := runtime.LibimageRuntime().Pull(runCtx, fromImage, config.PullPolicyAlways, pullOptions) if err != nil { stderr.Write([]byte(err.Error() + "\n")) } else { - img = newImage.ID() + if len(pulledImages) == 0 { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.New("internal error: no images pulled")) + return + } + img = pulledImages[0].ID() } }() @@ -347,7 +348,7 @@ func GetImage(w http.ResponseWriter, r *http.Request) { if err != nil { // Here we need to fiddle with the error message because docker-py is looking for "No // such image" to determine on how to raise the correct exception. - errMsg := strings.ReplaceAll(err.Error(), "no such image", "No such image") + errMsg := strings.ReplaceAll(err.Error(), "image not known", "No such image") utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Errorf("failed to find image %s: %s", name, errMsg)) return } @@ -379,13 +380,14 @@ func GetImages(w http.ResponseWriter, r *http.Request) { func LoadImages(w http.ResponseWriter, r *http.Request) { // TODO this is basically wrong + // TODO ... improve these ^ messages to something useful decoder := r.Context().Value("decoder").(*schema.Decoder) runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { - Changes map[string]string `json:"changes"` - Message string `json:"message"` - Quiet bool `json:"quiet"` + Changes map[string]string `json:"changes"` // Ignored + Message string `json:"message"` // Ignored + Quiet bool `json:"quiet"` // Ignored }{ // This is where you can override the golang default value for one of fields } @@ -395,10 +397,8 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { return } - var ( - err error - writer io.Writer - ) + // First write the body to a temporary file that we can later attempt + // to load. f, err := ioutil.TempFile("", "api_load.tar") if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) @@ -414,15 +414,25 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) return } - id, err := runtime.LoadImage(r.Context(), f.Name(), writer, "") + + imageEngine := abi.ImageEngine{Libpod: runtime} + + loadOptions := entities.ImageLoadOptions{Input: f.Name()} + loadReport, err := imageEngine.Load(r.Context(), loadOptions) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) return } + + if len(loadReport.Names) != 1 { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Errorf("%d instead of 1 were loaded", len(loadReport.Names))) + return + } + utils.WriteResponse(w, http.StatusOK, struct { Stream string `json:"stream"` }{ - Stream: fmt.Sprintf("Loaded image: %s\n", id), + Stream: fmt.Sprintf("Loaded image: %s\n", loadReport.Names[0]), }) } @@ -453,10 +463,15 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) return } - if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false, true); err != nil { + + imageEngine := abi.ImageEngine{Libpod: runtime} + + saveOptions := entities.ImageSaveOptions{Output: tmpfile.Name()} + if err := imageEngine.Save(r.Context(), images[0], images[1:], saveOptions); err != nil { utils.InternalServerError(w, err) return } + rdr, err := os.Open(tmpfile.Name()) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go index a02ed179c..ea596890f 100644 --- a/pkg/api/handlers/compat/images_history.go +++ b/pkg/api/handlers/compat/images_history.go @@ -3,6 +3,7 @@ package compat import ( "net/http" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" @@ -13,7 +14,8 @@ func HistoryImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) if err != nil { utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) return diff --git a/pkg/api/handlers/compat/images_prune.go b/pkg/api/handlers/compat/images_prune.go index ddf559ec6..bbbfb5577 100644 --- a/pkg/api/handlers/compat/images_prune.go +++ b/pkg/api/handlers/compat/images_prune.go @@ -8,6 +8,8 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" + "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/domain/infra/abi" "github.com/containers/podman/v3/pkg/util" "github.com/docker/docker/api/types" "github.com/pkg/errors" @@ -30,7 +32,11 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } - imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) + + imageEngine := abi.ImageEngine{Libpod: runtime} + + pruneOptions := entities.ImagePruneOptions{Filter: filters} + imagePruneReports, err := imageEngine.Prune(r.Context(), pruneOptions) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go index e89558a86..390f25caf 100644 --- a/pkg/api/handlers/compat/images_remove.go +++ b/pkg/api/handlers/compat/images_remove.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -41,7 +41,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { report, rmerrors := imageEngine.Remove(r.Context(), []string{name}, options) if len(rmerrors) > 0 && rmerrors[0] != nil { err := rmerrors[0] - if errors.Cause(err) == define.ErrNoSuchImage { + if errors.Cause(err) == storage.ErrImageUnknown { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return } diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go index 18974f424..13a3693fa 100644 --- a/pkg/api/handlers/compat/images_search.go +++ b/pkg/api/handlers/compat/images_search.go @@ -6,11 +6,11 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -64,7 +64,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { } if !utils.IsLibpodRequest(r) { if len(reports) == 0 { - utils.ImageNotFound(w, query.Term, define.ErrNoSuchImage) + utils.ImageNotFound(w, query.Term, storage.ErrImageUnknown) return } } diff --git a/pkg/api/handlers/compat/images_tag.go b/pkg/api/handlers/compat/images_tag.go index 0d0c204f3..8d256f4fa 100644 --- a/pkg/api/handlers/compat/images_tag.go +++ b/pkg/api/handlers/compat/images_tag.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/pkg/errors" @@ -14,11 +15,14 @@ func TagImage(w http.ResponseWriter, r *http.Request) { // /v1.xx/images/(name)/tag name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) + + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) if err != nil { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return } + tag := "latest" if len(r.Form.Get("tag")) > 0 { tag = r.Form.Get("tag") @@ -29,7 +33,7 @@ func TagImage(w http.ResponseWriter, r *http.Request) { } repo := r.Form.Get("repo") tagName := fmt.Sprintf("%s:%s", repo, tag) - if err := newImage.TagImage(tagName); err != nil { + if err := newImage.Tag(tagName); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 92882cc40..a90408bfd 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -11,11 +11,12 @@ import ( "strings" "github.com/containers/buildah" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" @@ -24,6 +25,7 @@ import ( "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/podman/v3/pkg/util" utils2 "github.com/containers/podman/v3/utils" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -74,7 +76,7 @@ func ImageTree(w http.ResponseWriter, r *http.Request) { options := entities.ImageTreeOptions{WhatRequires: query.WhatRequires} report, err := ir.Tree(r.Context(), name, options) if err != nil { - if errors.Cause(err) == define.ErrNoSuchImage { + if errors.Cause(err) == storage.ErrImageUnknown { utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) return } @@ -91,7 +93,7 @@ func GetImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) return } - inspect, err := newImage.Inspect(r.Context()) + inspect, err := newImage.Inspect(r.Context(), true) if err != nil { utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID)) return @@ -100,22 +102,44 @@ func GetImage(w http.ResponseWriter, r *http.Request) { } func GetImages(w http.ResponseWriter, r *http.Request) { - images, err := utils.GetImages(w, r) + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + query := struct { + All bool + Digests bool + Filter string // Docker 1.24 compatibility + }{ + // This is where you can override the golang default value for one of fields + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + if _, found := r.URL.Query()["digests"]; found && query.Digests { + utils.UnSupportedParameter("digests") + return + } + + filterList, err := filters.FiltersFromRequest(r) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } - var summaries = make([]*entities.ImageSummary, len(images)) - for j, img := range images { - is, err := handlers.ImageToImageSummary(img) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) - return - } - // libpod has additional fields that we need to populate. - is.ReadOnly = img.IsReadOnly() - summaries[j] = is + if !utils.IsLibpodRequest(r) && len(query.Filter) > 0 { // Docker 1.24 compatibility + filterList = append(filterList, "reference="+query.Filter) } + + imageEngine := abi.ImageEngine{Libpod: runtime} + + listOptions := entities.ImageListOptions{All: query.All, Filter: filterList} + summaries, err := imageEngine.List(r.Context(), listOptions) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, summaries) } @@ -135,7 +159,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { if dErr := decoder.Decode(&query, r.URL.Query()); dErr != nil || err != nil { utils.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + errors. + Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } @@ -156,7 +181,13 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { } } - imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) + imageEngine := abi.ImageEngine{Libpod: runtime} + + pruneOptions := entities.ImagePruneOptions{ + All: query.All, + Filter: libpodFilters, + } + imagePruneReports, err := imageEngine.Prune(r.Context(), pruneOptions) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return @@ -183,11 +214,13 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { return } name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { + + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + if _, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions); err != nil { utils.ImageNotFound(w, name, err) return } + switch query.Format { case define.OCIArchive, define.V2s2Archive: tmpfile, err := ioutil.TempFile("", "api.tar") @@ -211,7 +244,15 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) return } - if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress, true); err != nil { + + imageEngine := abi.ImageEngine{Libpod: runtime} + + saveOptions := entities.ImageSaveOptions{ + Compress: query.Compress, + Format: query.Format, + Output: output, + } + if err := imageEngine.Save(r.Context(), name, nil, saveOptions); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } @@ -347,12 +388,15 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { return } - loadedImage, err := runtime.LoadImage(context.Background(), tmpfile.Name(), os.Stderr, "") + imageEngine := abi.ImageEngine{Libpod: runtime} + + loadOptions := entities.ImageLoadOptions{Input: tmpfile.Name()} + loadReport, err := imageEngine.Load(r.Context(), loadOptions) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image")) return } - utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Names: strings.Split(loadedImage, ",")}) + utils.WriteResponse(w, http.StatusOK, loadReport) } func ImagesImport(w http.ResponseWriter, r *http.Request) { @@ -392,13 +436,21 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { tmpfile.Close() source = tmpfile.Name() } - importedImage, err := runtime.Import(context.Background(), source, query.Reference, "", query.Changes, query.Message, true) + + imageEngine := abi.ImageEngine{Libpod: runtime} + importOptions := entities.ImageImportOptions{ + Changes: query.Changes, + Message: query.Message, + Reference: query.Reference, + Source: source, + } + report, err := imageEngine.Import(r.Context(), importOptions) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) return } - utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage}) + utils.WriteResponse(w, http.StatusOK, report) } // PushImage is the handler for the compat http endpoint for pushing images. @@ -497,7 +549,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config")) return } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + sc := runtime.SystemContext() tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, @@ -579,7 +631,7 @@ func UntagImage(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) if err := imageEngine.Untag(r.Context(), name, tags, opts); err != nil { - if errors.Cause(err) == define.ErrNoSuchImage { + if errors.Cause(err) == storage.ErrImageUnknown { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) } else { utils.Error(w, "failed to untag", http.StatusInternalServerError, err) diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go index e2e4b53b4..7545ba235 100644 --- a/pkg/api/handlers/libpod/images_pull.go +++ b/pkg/api/handlers/libpod/images_pull.go @@ -3,20 +3,16 @@ package libpod import ( "context" "encoding/json" - "fmt" "net/http" - "strings" - "github.com/containers/image/v5/docker" - "github.com/containers/image/v5/docker/reference" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/channel" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -51,28 +47,23 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { return } - imageRef, err := utils.ParseDockerReference(query.Reference) - if err != nil { + // Make sure that the reference has no transport or the docker one. + if _, err := utils.ParseDockerReference(query.Reference); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } - // Trim the docker-transport prefix. - rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name())) + pullOptions := &libimage.PullOptions{} + pullOptions.AllTags = query.AllTags + pullOptions.Architecture = query.Arch + pullOptions.OS = query.OS + pullOptions.Variant = query.Variant - // all-tags doesn't work with a tagged reference, so let's check early - namedRef, err := reference.Parse(rawImage) - if err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(err, "error parsing reference %q", rawImage)) - return - } - if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Errorf("reference %q must not have a tag for all-tags", rawImage)) - return + if _, found := r.URL.Query()["tlsVerify"]; found { + pullOptions.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) } + // Do the auth dance. authConf, authfile, key, err := auth.GetCredentials(r) if err != nil { utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) @@ -80,71 +71,25 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { } defer auth.RemoveAuthfile(authfile) - // Setup the registry options - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: authConf, - OSChoice: query.OS, - ArchitectureChoice: query.Arch, - VariantChoice: query.Variant, - } - if _, found := r.URL.Query()["tlsVerify"]; found { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) - } - - sys := runtime.SystemContext() - if sys == nil { - sys = image.GetSystemContext("", authfile, false) - } - dockerRegistryOptions.DockerCertPath = sys.DockerCertPath - sys.DockerAuthConfig = authConf - - // Prepare the images we want to pull - imagesToPull := []string{} - imageName := namedRef.String() - - if !query.AllTags { - imagesToPull = append(imagesToPull, imageName) - } else { - tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef) - if err != nil { - utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags")) - return - } - for _, tag := range tags { - imagesToPull = append(imagesToPull, fmt.Sprintf("%s:%s", imageName, tag)) - } + pullOptions.AuthFilePath = authfile + if authConf != nil { + pullOptions.Username = authConf.Username + pullOptions.Password = authConf.Password + pullOptions.IdentityToken = authConf.IdentityToken } writer := channel.NewWriter(make(chan []byte)) defer writer.Close() - stderr := channel.NewWriter(make(chan []byte)) - defer stderr.Close() + pullOptions.Writer = writer - images := make([]string, 0, len(imagesToPull)) + var pulledImages []*libimage.Image + var pullError error runCtx, cancel := context.WithCancel(context.Background()) - go func(imgs []string) { + go func() { defer cancel() - // Finally pull the images - for _, img := range imgs { - newImage, err := runtime.ImageRuntime().New( - runCtx, - img, - "", - authfile, - writer, - &dockerRegistryOptions, - image.SigningOptions{}, - nil, - util.PullImageAlways, - nil) - if err != nil { - stderr.Write([]byte(err.Error() + "\n")) - } else { - images = append(images, newImage.ID()) - } - } - }(imagesToPull) + pulledImages, pullError = runtime.LibimageRuntime().Pull(runCtx, query.Reference, config.PullPolicyAlways, pullOptions) + }() flush := func() { if flusher, ok := w.(http.Flusher); ok { @@ -158,45 +103,32 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.SetEscapeHTML(true) - var failed bool -loop: // break out of for/select infinite loop for { var report entities.ImagePullReport select { - case e := <-writer.Chan(): - report.Stream = string(e) - if err := enc.Encode(report); err != nil { - stderr.Write([]byte(err.Error())) - } - flush() - case e := <-stderr.Chan(): - failed = true - report.Error = string(e) + case s := <-writer.Chan(): + report.Stream = string(s) if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to json encode error %q", err.Error()) + logrus.Warnf("Failed to encode json: %v", err) } flush() case <-runCtx.Done(): - if !failed { - // Send all image id's pulled in 'images' stanza - report.Images = images - if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to json encode error %q", err.Error()) - } - - report.Images = nil + for _, image := range pulledImages { + report.Images = append(report.Images, image.ID()) // Pull last ID from list and publish in 'id' stanza. This maintains previous API contract - report.ID = images[len(images)-1] - if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to json encode error %q", err.Error()) - } - - flush() + report.ID = image.ID() + } + if pullError != nil { + report.Error = pullError.Error() + } + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to encode json: %v", err) } - break loop // break out of for/select infinite loop + flush() + return case <-r.Context().Done(): // Client has closed connection - break loop // break out of for/select infinite loop + return } } } diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 6a491ae48..f21eb2e80 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -9,7 +9,6 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" @@ -45,13 +44,10 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) { } } - rtc, err := runtime.GetConfig() - if err != nil { - utils.InternalServerError(w, err) - return - } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) - manID, err := image.CreateManifestList(runtime.ImageRuntime(), *sc, query.Name, query.Image, query.All) + imageEngine := abi.ImageEngine{Libpod: runtime} + + createOptions := entities.ManifestCreateOptions{All: query.All} + manID, err := imageEngine.ManifestCreate(r.Context(), query.Name, query.Image, createOptions) if err != nil { utils.InternalServerError(w, err) return @@ -64,8 +60,8 @@ func ExistsManifest(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) - ic := abi.ImageEngine{Libpod: runtime} - report, err := ic.ManifestExists(r.Context(), name) + imageEngine := abi.ImageEngine{Libpod: runtime} + report, err := imageEngine.ManifestExists(r.Context(), name) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return @@ -80,45 +76,46 @@ func ExistsManifest(w http.ResponseWriter, r *http.Request) { func ManifestInspect(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) + imageEngine := abi.ImageEngine{Libpod: runtime} - inspectReport, inspectError := imageEngine.ManifestInspect(r.Context(), name) - if inspectError != nil { - utils.Error(w, "Something went wrong.", http.StatusNotFound, inspectError) + rawManifest, err := imageEngine.ManifestInspect(r.Context(), name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusNotFound, err) return } - var list manifest.Schema2List - if err := json.Unmarshal(inspectReport, &list); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Unmarshal()")) + var schema2List manifest.Schema2List + if err := json.Unmarshal(rawManifest, &schema2List); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } - if list.Manifests == nil { - list.Manifests = make([]manifest.Schema2ManifestDescriptor, 0) - } - utils.WriteResponse(w, http.StatusOK, &list) + utils.WriteResponse(w, http.StatusOK, schema2List) } func ManifestAdd(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) - var manifestInput image.ManifestAddOpts - if err := json.NewDecoder(r.Body).Decode(&manifestInput); err != nil { + var addOptions entities.ManifestAddOptions + if err := json.NewDecoder(r.Body).Decode(&addOptions); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } + name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.ImageNotFound(w, name, err) + if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { + utils.Error(w, "Something went wrong.", http.StatusNotFound, err) return } - rtc, err := runtime.GetConfig() - if err != nil { - utils.InternalServerError(w, err) - return + + // FIXME: we really need to clean up the manifest API. Swagger states + // the arguments were strings not string slices. The use of string + // slices, mixing lists and images is incredibly confusing. + if len(addOptions.Images) == 1 { + addOptions.Images = append(addOptions.Images, name) } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) - newID, err := newImage.AddManifest(*sc, manifestInput) + + imageEngine := abi.ImageEngine{Libpod: runtime} + newID, err := imageEngine.ManifestAdd(r.Context(), addOptions) if err != nil { utils.InternalServerError(w, err) return @@ -140,9 +137,9 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - newImage, err := runtime.ImageRuntime().NewFromLocal(name) + manifestList, err := runtime.LibimageRuntime().LookupManifestList(name) if err != nil { - utils.ImageNotFound(w, name, err) + utils.Error(w, "Something went wrong.", http.StatusNotFound, err) return } d, err := digest.Parse(query.Digest) @@ -150,13 +147,13 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) { utils.Error(w, "invalid digest", http.StatusBadRequest, err) return } - newID, err := newImage.RemoveManifest(d) - if err != nil { + if err := manifestList.RemoveInstance(d); err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID}) + utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: manifestList.ID()}) } + func ManifestPush(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index 96f572a8b..90332924c 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -21,11 +21,12 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - Network string `schema:"network"` - TLSVerify bool `schema:"tlsVerify"` - LogDriver string `schema:"logDriver"` - Start bool `schema:"start"` - StaticIPs []string `schema:"staticIPs"` + Network string `schema:"network"` + TLSVerify bool `schema:"tlsVerify"` + LogDriver string `schema:"logDriver"` + Start bool `schema:"start"` + StaticIPs []string `schema:"staticIPs"` + StaticMACs []string `schema:"staticMACs"` }{ TLSVerify: true, Start: true, @@ -48,6 +49,17 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { staticIPs = append(staticIPs, ip) } + staticMACs := make([]net.HardwareAddr, 0, len(query.StaticMACs)) + for _, macString := range query.StaticMACs { + mac, err := net.ParseMAC(macString) + if err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + err) + return + } + staticMACs = append(staticMACs, mac) + } + // Fetch the K8s YAML file from the body, and copy it to a temp file. tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml") if err != nil { @@ -78,13 +90,14 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { containerEngine := abi.ContainerEngine{Libpod: runtime} options := entities.PlayKubeOptions{ - Authfile: authfile, - Username: username, - Password: password, - Network: query.Network, - Quiet: true, - LogDriver: query.LogDriver, - StaticIPs: staticIPs, + Authfile: authfile, + Username: username, + Password: password, + Network: query.Network, + Quiet: true, + LogDriver: query.LogDriver, + StaticIPs: staticIPs, + StaticMACs: staticMACs, } if _, found := r.URL.Query()["tlsVerify"]; found { options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) diff --git a/pkg/api/handlers/libpod/system.go b/pkg/api/handlers/libpod/system.go index 2b4cef1bb..bca92a4af 100644 --- a/pkg/api/handlers/libpod/system.go +++ b/pkg/api/handlers/libpod/system.go @@ -4,21 +4,19 @@ import ( "net/http" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/pkg/api/handlers/compat" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/podman/v3/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" ) // SystemPrune removes unused data func SystemPrune(w http.ResponseWriter, r *http.Request) { - var ( - decoder = r.Context().Value("decoder").(*schema.Decoder) - runtime = r.Context().Value("runtime").(*libpod.Runtime) - systemPruneReport = new(entities.SystemPruneReport) - ) + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + query := struct { All bool `schema:"all"` Volumes bool `schema:"volumes"` @@ -29,39 +27,27 @@ func SystemPrune(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - - podPruneReport, err := PodPruneHelper(r) + filterMap, err := util.PrepareFilters(r) if err != nil { - utils.InternalServerError(w, err) + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - systemPruneReport.PodPruneReport = podPruneReport - // We could parallelize this, should we? - containerPruneReports, err := compat.PruneContainersHelper(r, nil) - if err != nil { - utils.InternalServerError(w, err) - return - } - systemPruneReport.ContainerPruneReports = containerPruneReports + containerEngine := abi.ContainerEngine{Libpod: runtime} - imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, nil) + pruneOptions := entities.SystemPruneOptions{ + All: query.All, + Volume: query.Volumes, + Filters: *filterMap, + } + report, err := containerEngine.SystemPrune(r.Context(), pruneOptions) if err != nil { utils.InternalServerError(w, err) return } - systemPruneReport.ImagePruneReports = imagePruneReports - - if query.Volumes { - volumePruneReports, err := pruneVolumesHelper(r) - if err != nil { - utils.InternalServerError(w, err) - return - } - systemPruneReport.VolumePruneReports = volumePruneReports - } - utils.WriteResponse(w, http.StatusOK, systemPruneReport) + utils.WriteResponse(w, http.StatusOK, report) } func DiskUsage(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go index 384e06cac..ef3d12df8 100644 --- a/pkg/api/handlers/swagger/swagger.go +++ b/pkg/api/handlers/swagger/swagger.go @@ -2,7 +2,6 @@ package swagger import ( "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/inspect" @@ -66,7 +65,10 @@ type swagLibpodPlayKubeResponse struct { // swagger:response DocsImageDeleteResponse type swagImageDeleteResponse struct { // in:body - Body []image.ImageDeleteResponse + Body []struct { + Untagged []string `json:"untagged"` + Deleted string `json:"deleted"` + } } // Search results @@ -74,7 +76,20 @@ type swagImageDeleteResponse struct { type swagSearchResponse struct { // in:body Body struct { - image.SearchResult + // Index is the image index (e.g., "docker.io" or "quay.io") + Index string + // Name is the canonical name of the image (e.g., "docker.io/library/alpine"). + Name string + // Description of the image. + Description string + // Stars is the number of stars of the image. + Stars int + // Official indicates if it's an official image. + Official string + // Automated indicates if the image was created by an automated build. + Automated string + // Tag is the image tag + Tag string } } diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 736203171..52d7633af 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -2,12 +2,9 @@ package handlers import ( "context" - "encoding/json" - "fmt" "time" - "github.com/containers/image/v5/manifest" - libpodImage "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/pkg/domain/entities" docker "github.com/docker/docker/api/types" dockerContainer "github.com/docker/docker/api/types/container" @@ -173,8 +170,8 @@ type ExecStartConfig struct { Tty bool `json:"Tty"` } -func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) { - imageData, err := l.Inspect(context.TODO()) +func ImageToImageSummary(l *libimage.Image) (*entities.ImageSummary, error) { + imageData, err := l.Inspect(context.TODO(), true) if err != nil { return nil, errors.Wrapf(err, "failed to obtain summary for image %s", l.ID()) } @@ -197,17 +194,17 @@ func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) { Labels: imageData.Labels, Containers: containerCount, ReadOnly: l.IsReadOnly(), - Dangling: l.Dangling(), + Dangling: l.IsDangling(), Names: l.Names(), Digest: string(imageData.Digest), - ConfigDigest: string(l.ConfigDigest), + ConfigDigest: "", // TODO: libpod/image didn't set it but libimage should History: imageData.NamesHistory, } return &is, nil } -func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageInspect, error) { - info, err := l.Inspect(context.Background()) +func ImageDataToImageInspect(ctx context.Context, l *libimage.Image) (*ImageInspect, error) { + info, err := l.Inspect(context.Background(), true) if err != nil { return nil, err } @@ -216,37 +213,17 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI return nil, err } - // TODO the rest of these still need wiring! + // TODO: many fields in Config still need wiring config := dockerContainer.Config{ - // Hostname: "", - // Domainname: "", - User: info.User, - // AttachStdin: false, - // AttachStdout: false, - // AttachStderr: false, + User: info.User, ExposedPorts: ports, - // Tty: false, - // OpenStdin: false, - // StdinOnce: false, - Env: info.Config.Env, - Cmd: info.Config.Cmd, - // Healthcheck: l.ImageData.HealthCheck, - // ArgsEscaped: false, - // Image: "", - Volumes: info.Config.Volumes, - WorkingDir: info.Config.WorkingDir, - Entrypoint: info.Config.Entrypoint, - // NetworkDisabled: false, - // MacAddress: "", - // OnBuild: info.Config.OnBuild, - Labels: info.Labels, - StopSignal: info.Config.StopSignal, - // StopTimeout: nil, - // Shell: nil, - } - ic, err := l.ToImageRef(ctx) - if err != nil { - return nil, err + Env: info.Config.Env, + Cmd: info.Config.Cmd, + Volumes: info.Config.Volumes, + WorkingDir: info.Config.WorkingDir, + Entrypoint: info.Config.Entrypoint, + Labels: info.Labels, + StopSignal: info.Config.StopSignal, } rootfs := docker.RootFS{} @@ -257,6 +234,11 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI rootfs.Layers = append(rootfs.Layers, string(layer)) } } + + graphDriver := docker.GraphDriverData{ + Name: info.GraphDriver.Name, + Data: info.GraphDriver.Data, + } dockerImageInspect := docker.ImageInspect{ Architecture: info.Architecture, Author: info.Author, @@ -264,8 +246,8 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI Config: &config, Created: l.Created().Format(time.RFC3339Nano), DockerVersion: info.Version, - GraphDriver: docker.GraphDriverData{}, - ID: fmt.Sprintf("sha256:%s", l.ID()), + GraphDriver: graphDriver, + ID: "sha256:" + l.ID(), Metadata: docker.ImageMetadata{}, Os: info.Os, OsVersion: info.Version, @@ -277,33 +259,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI Variant: "", VirtualSize: info.VirtualSize, } - bi := ic.ConfigInfo() - // For docker images, we need to get the Container id and config - // and populate the image with it. - if bi.MediaType == manifest.DockerV2Schema2ConfigMediaType { - d := manifest.Schema2Image{} - b, err := ic.ConfigBlob(ctx) - if err != nil { - return nil, err - } - if err := json.Unmarshal(b, &d); err != nil { - return nil, err - } - // populate the Container id into the image - dockerImageInspect.Container = d.Container - containerConfig := dockerContainer.Config{} - configBytes, err := json.Marshal(d.ContainerConfig) - if err != nil { - return nil, err - } - if err := json.Unmarshal(configBytes, &containerConfig); err != nil { - return nil, err - } - // populate the Container config in the image - dockerImageInspect.ContainerConfig = &containerConfig - // populate parent - dockerImageInspect.Parent = d.Parent.String() - } + // TODO: consider filling the container config. return &ImageInspect{dockerImageInspect}, nil } diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go index d22ad414f..4a8005bfd 100644 --- a/pkg/api/handlers/utils/errors.go +++ b/pkg/api/handlers/utils/errors.go @@ -6,6 +6,7 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/errorhandling" + "github.com/containers/storage" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) @@ -49,7 +50,7 @@ func ContainerNotFound(w http.ResponseWriter, name string, err error) { } func ImageNotFound(w http.ResponseWriter, name string, err error) { - if errors.Cause(err) != define.ErrNoSuchImage { + if errors.Cause(err) != storage.ErrImageUnknown { InternalServerError(w, err) } msg := fmt.Sprintf("No such image: %s", name) diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index da3c9e985..2662cd368 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -3,15 +3,14 @@ package utils import ( "fmt" "net/http" - "strings" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" - "github.com/containers/podman/v3/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -54,7 +53,7 @@ func ParseStorageReference(name string) (types.ImageReference, error) { // GetImages is a common function used to get images for libpod and other compatibility // mechanisms -func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { +func GetImages(w http.ResponseWriter, r *http.Request) ([]*libimage.Image, error) { decoder := r.Context().Value("decoder").(*schema.Decoder) runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { @@ -65,56 +64,37 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { // This is where you can override the golang default value for one of fields } - filterMap, err := util.PrepareFilters(r) - if err != nil { - return nil, err - } - if err := decoder.Decode(&query, r.URL.Query()); err != nil { return nil, err } - var filters = []string{} if _, found := r.URL.Query()["digests"]; found && query.Digests { UnSupportedParameter("digests") } - var images []*image.Image - queryFilters := *filterMap + filterList, err := filters.FiltersFromRequest(r) + if err != nil { + return nil, err + } if !IsLibpodRequest(r) && len(query.Filter) > 0 { // Docker 1.24 compatibility - if queryFilters == nil { - queryFilters = make(map[string][]string) - } - queryFilters["reference"] = append(queryFilters["reference"], query.Filter) + filterList = append(filterList, "reference="+query.Filter) } - if len(queryFilters) > 0 { - for k, v := range queryFilters { - filters = append(filters, fmt.Sprintf("%s=%s", k, strings.Join(v, "="))) - } - images, err = runtime.ImageRuntime().GetImagesWithFilters(filters) - if err != nil { - return images, err - } - } else { - images, err = runtime.ImageRuntime().GetImages() - if err != nil { - return images, err - } - } - if query.All { - return images, nil + if !query.All { + // Filter intermediate images unless we want to list *all*. + // NOTE: it's a positive filter, so `intermediate=false` means + // to display non-intermediate images. + filterList = append(filterList, "intermediate=false") } + listOptions := &libimage.ListImagesOptions{Filters: filterList} + return runtime.LibimageRuntime().ListImages(r.Context(), nil, listOptions) +} - filter, err := runtime.ImageRuntime().IntermediateFilter(r.Context(), images) +func GetImage(r *http.Request, name string) (*libimage.Image, error) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + image, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) if err != nil { return nil, err } - images = image.FilterImages(images, []image.ResultFilter{filter}) - - return images, nil -} - -func GetImage(r *http.Request, name string) (*image.Image, error) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - return runtime.ImageRuntime().NewFromLocal(name) + return image, err } diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go index da37abb70..c51301aa8 100644 --- a/pkg/api/server/register_play.go +++ b/pkg/api/server/register_play.go @@ -40,6 +40,12 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error { // description: Static IPs used for the pods. // items: // type: string + // - in: query + // name: staticMACs + // type: array + // description: Static MACs used for the pods. + // items: + // type: string // - in: body // name: request // description: Kubernetes YAML file. diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go index e4b43109f..0a13e7e74 100644 --- a/pkg/autoupdate/autoupdate.go +++ b/pkg/autoupdate/autoupdate.go @@ -5,16 +5,16 @@ import ( "os" "sort" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/systemd" systemdDefine "github.com/containers/podman/v3/pkg/systemd/define" - "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -126,12 +126,15 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { return nil, errs } - // Create a map from `image ID -> *image.Image` for image lookups. - imagesSlice, err := runtime.ImageRuntime().GetImages() + // Create a map from `image ID -> *libimage.Image` for image lookups. + listOptions := &libimage.ListImagesOptions{ + Filters: []string{"readonly=false"}, + } + imagesSlice, err := runtime.LibimageRuntime().ListImages(context.Background(), nil, listOptions) if err != nil { return nil, []error{err} } - imageMap := make(map[string]*image.Image) + imageMap := make(map[string]*libimage.Image) for i := range imagesSlice { imageMap[imagesSlice[i].ID()] = imagesSlice[i] } @@ -190,7 +193,7 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { errs = append(errs, errors.Errorf("error locally auto-updating container %q: raw-image name is empty", cid)) } // This avoids restarting containers unnecessarily. - needsUpdate, err := newerLocalImageAvailable(image, rawImageName) + needsUpdate, err := newerLocalImageAvailable(runtime, image, rawImageName) if err != nil { errs = append(errs, errors.Wrapf(err, "error locally auto-updating container %q: image check for %q failed", cid, rawImageName)) continue @@ -288,13 +291,13 @@ func readAuthenticationPath(ctr *libpod.Container, options Options) { // newerRemoteImageAvailable returns true if there corresponding image on the remote // registry is newer. -func newerRemoteImageAvailable(runtime *libpod.Runtime, img *image.Image, origName string, options Options) (bool, error) { +func newerRemoteImageAvailable(runtime *libpod.Runtime, img *libimage.Image, origName string, options Options) (bool, error) { remoteRef, err := docker.ParseReference("//" + origName) if err != nil { return false, err } - data, err := img.Inspect(context.Background()) + data, err := img.Inspect(context.Background(), false) if err != nil { return false, err } @@ -326,13 +329,8 @@ func newerRemoteImageAvailable(runtime *libpod.Runtime, img *image.Image, origNa } // newerLocalImageAvailable returns true if the container and local image have different digests -func newerLocalImageAvailable(img *image.Image, rawImageName string) (bool, error) { - rt, err := libpod.NewRuntime(context.TODO()) - if err != nil { - return false, err - } - - localImg, err := rt.ImageRuntime().NewFromLocal(rawImageName) +func newerLocalImageAvailable(runtime *libpod.Runtime, img *libimage.Image, rawImageName string) (bool, error) { + localImg, _, err := runtime.LibimageRuntime().LookupImage(rawImageName, nil) if err != nil { return false, err } @@ -345,31 +343,14 @@ func newerLocalImageAvailable(img *image.Image, rawImageName string) (bool, erro } // updateImage pulls the specified image. -func updateImage(runtime *libpod.Runtime, name string, options Options) (*image.Image, error) { - sys := runtime.SystemContext() - registryOpts := image.DockerRegistryOptions{} - signaturePolicyPath := "" - - if sys != nil { - registryOpts.OSChoice = sys.OSChoice - registryOpts.ArchitectureChoice = sys.OSChoice - registryOpts.DockerCertPath = sys.DockerCertPath - signaturePolicyPath = sys.SignaturePolicyPath - } +func updateImage(runtime *libpod.Runtime, name string, options Options) (*libimage.Image, error) { + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.Writer = os.Stderr - newImage, err := runtime.ImageRuntime().New(context.Background(), - docker.Transport.Name()+"://"+name, - signaturePolicyPath, - options.Authfile, - os.Stderr, - ®istryOpts, - image.SigningOptions{}, - nil, - util.PullImageAlways, - nil, - ) + pulledImages, err := runtime.LibimageRuntime().Pull(context.Background(), name, config.PullPolicyAlways, pullOptions) if err != nil { return nil, err } - return newImage, nil + return pulledImages[0], nil } diff --git a/pkg/bindings/images/pull.go b/pkg/bindings/images/pull.go index f4da2d521..9780c3bff 100644 --- a/pkg/bindings/images/pull.go +++ b/pkg/bindings/images/pull.go @@ -3,7 +3,6 @@ package images import ( "context" "encoding/json" - "errors" "fmt" "io" "io/ioutil" @@ -15,6 +14,7 @@ import ( "github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" ) // Pull is the binding for libpod's v2 endpoints for pulling images. Note that @@ -91,7 +91,7 @@ func Pull(ctx context.Context, rawImage string, options *PullOptions) ([]string, images = report.Images case report.ID != "": default: - return images, errors.New("failed to parse pull results stream, unexpected input") + return images, errors.Errorf("failed to parse pull results stream, unexpected input: %v", report) } } return images, mErr diff --git a/pkg/bindings/play/types.go b/pkg/bindings/play/types.go index 6598ec3c2..52a72c7b6 100644 --- a/pkg/bindings/play/types.go +++ b/pkg/bindings/play/types.go @@ -27,6 +27,8 @@ type KubeOptions struct { SeccompProfileRoot *string // StaticIPs - Static IP address used by the pod(s). StaticIPs *[]net.IP + // StaticMACs - Static MAC address used by the pod(s). + StaticMACs *[]net.HardwareAddr // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. ConfigMaps *[]string // LogDriver for the container. For example: journald diff --git a/pkg/bindings/play/types_kube_options.go b/pkg/bindings/play/types_kube_options.go index a1786f553..4cc7d6f21 100644 --- a/pkg/bindings/play/types_kube_options.go +++ b/pkg/bindings/play/types_kube_options.go @@ -181,6 +181,22 @@ func (o *KubeOptions) GetStaticIPs() []net.IP { return *o.StaticIPs } +// WithStaticMACs +func (o *KubeOptions) WithStaticMACs(value []net.HardwareAddr) *KubeOptions { + v := &value + o.StaticMACs = v + return o +} + +// GetStaticMACs +func (o *KubeOptions) GetStaticMACs() []net.HardwareAddr { + var staticMACs []net.HardwareAddr + if o.StaticMACs == nil { + return staticMACs + } + return *o.StaticMACs +} + // WithConfigMaps func (o *KubeOptions) WithConfigMaps(value []string) *KubeOptions { v := &value diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 688bf049f..ff8f72c85 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -9,7 +9,6 @@ import ( "github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/bindings/containers" "github.com/containers/podman/v3/pkg/bindings/images" - dreports "github.com/containers/podman/v3/pkg/domain/entities/reports" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -116,7 +115,7 @@ var _ = Describe("Podman images", func() { // Removing the image "alpine" where force = true options := new(images.RemoveOptions).WithForce(true) response, errs = images.Remove(bt.conn, []string{alpine.shortName}, options) - Expect(len(errs)).To(BeZero()) + Expect(errs).To(BeNil()) // To be extra sure, check if the previously created container // is gone as well. _, err = containers.Inspect(bt.conn, "top", nil) @@ -346,7 +345,6 @@ var _ = Describe("Podman images", func() { results, err := images.Prune(bt.conn, options) Expect(err).NotTo(HaveOccurred()) Expect(len(results)).To(BeNumerically(">", 0)) - Expect(dreports.PruneReportsIds(results)).To(ContainElement("docker.io/library/alpine:latest")) }) // TODO: we really need to extent to pull tests once we have a more sophisticated CI. diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index 68e9d9301..aecc5db81 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -83,8 +83,6 @@ var _ = Describe("Podman system", func() { Expect(len(systemPruneResponse.ContainerPruneReports)).To(Equal(1)) Expect(len(systemPruneResponse.ImagePruneReports)). To(BeNumerically(">", 0)) - Expect(reports.PruneReportsIds(systemPruneResponse.ImagePruneReports)). - To(ContainElement("docker.io/library/alpine:latest")) Expect(len(systemPruneResponse.VolumePruneReports)).To(Equal(0)) }) @@ -193,13 +191,8 @@ var _ = Describe("Podman system", func() { systemPruneResponse, err := system.Prune(bt.conn, options) Expect(err).To(BeNil()) Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(0)) - // TODO fix system filter handling so all components can handle filters - // This check **should** be "Equal(0)" since we are passing label - // filters however the Prune function doesn't seem to pass filters - // to each component. - Expect(len(systemPruneResponse.ContainerPruneReports)).To(Equal(1)) - Expect(len(systemPruneResponse.ImagePruneReports)). - To(BeNumerically(">", 0)) + Expect(len(systemPruneResponse.ContainerPruneReports)).To(Equal(0)) + Expect(len(systemPruneResponse.ImagePruneReports)).To(Equal(0)) // Alpine image should not be pruned as used by running container Expect(reports.PruneReportsIds(systemPruneResponse.ImagePruneReports)). ToNot(ContainElement("docker.io/library/alpine:latest")) diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go index 77a993128..7a8f71c66 100644 --- a/pkg/checkpoint/checkpoint_restore.go +++ b/pkg/checkpoint/checkpoint_restore.go @@ -6,11 +6,11 @@ import ( "os" metadata "github.com/checkpoint-restore/checkpointctl/lib" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/errorhandling" - "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage/pkg/archive" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -62,19 +62,19 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt } // Load config.dump from temporary directory - config := new(libpod.ContainerConfig) - if _, err = metadata.ReadJSONFile(config, dir, metadata.ConfigDumpFile); err != nil { + ctrConfig := new(libpod.ContainerConfig) + if _, err = metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil { return nil, err } // This should not happen as checkpoints with these options are not exported. - if len(config.Dependencies) > 0 { + if len(ctrConfig.Dependencies) > 0 { return nil, errors.Errorf("Cannot import checkpoints of containers with dependencies") } // Volumes included in the checkpoint should not exist if !restoreOptions.IgnoreVolumes { - for _, vol := range config.NamedVolumes { + for _, vol := range ctrConfig.NamedVolumes { exists, err := runtime.HasVolume(vol.Name) if err != nil { return nil, err @@ -85,33 +85,24 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt } } - ctrID := config.ID + ctrID := ctrConfig.ID newName := false // Check if the restored container gets a new name if restoreOptions.Name != "" { - config.ID = "" - config.Name = restoreOptions.Name + ctrConfig.ID = "" + ctrConfig.Name = restoreOptions.Name newName = true } - ctrName := config.Name - - // The code to load the images is copied from create.go - // In create.go this only set if '--quiet' does not exist. - writer := os.Stderr - rtc, err := runtime.GetConfig() - if err != nil { - return nil, err - } - - _, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.Engine.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, nil, util.PullImageMissing, nil) - if err != nil { + pullOptions := &libimage.PullOptions{} + pullOptions.Writer = os.Stderr + if _, err := runtime.LibimageRuntime().Pull(ctx, ctrConfig.RootfsImageName, config.PullPolicyMissing, pullOptions); err != nil { return nil, err } // Now create a new container from the just loaded information - container, err := runtime.RestoreContainer(ctx, dumpSpec, config) + container, err := runtime.RestoreContainer(ctx, dumpSpec, ctrConfig) if err != nil { return nil, err } @@ -122,6 +113,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt } containerConfig := container.Config() + ctrName := ctrConfig.Name if containerConfig.Name != ctrName { return nil, errors.Errorf("Name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName) } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 7d074f89d..4707ced85 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -265,6 +265,7 @@ type ContainerExistsOptions struct { // ContainerStartOptions describes the val from the // CLI needed to start a container type ContainerStartOptions struct { + All bool Attach bool DetachKeys string Interactive bool diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go index 6a645e20b..3f89e4d30 100644 --- a/pkg/domain/entities/manifest.go +++ b/pkg/domain/entities/manifest.go @@ -8,6 +8,7 @@ type ManifestCreateOptions struct { All bool `schema:"all"` } +// swagger:model ManifestAddOpts type ManifestAddOptions struct { All bool `json:"all" schema:"all"` Annotation []string `json:"annotation" schema:"annotation"` diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index c69bb0867..89dfc08e9 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -30,6 +30,8 @@ type PlayKubeOptions struct { SeccompProfileRoot string // StaticIPs - Static IP address used by the pod(s). StaticIPs []net.IP + // StaticMACs - Static MAC address used by the pod(s). + StaticMACs []net.HardwareAddr // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. ConfigMaps []string // LogDriver for the container. For example: journald diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go index 55a6a1b14..62f5401cc 100644 --- a/pkg/domain/entities/volumes.go +++ b/pkg/domain/entities/volumes.go @@ -4,6 +4,8 @@ import ( "net/url" "github.com/containers/podman/v3/libpod/define" + docker_api_types "github.com/docker/docker/api/types" + docker_api_types_volume "github.com/docker/docker/api/types/volume" ) // Volume volume @@ -91,42 +93,6 @@ type VolumeConfigResponse struct { define.InspectVolumeData } -// VolumeInfo Volume list response -// swagger:model VolumeInfo -type VolumeInfo struct { - - // Date/Time the volume was created. - CreatedAt string `json:"CreatedAt,omitempty"` - - // Name of the volume driver used by the volume. Only supports local driver - // Required: true - Driver string `json:"Driver"` - - // User-defined key/value metadata. - // Always included - Labels map[string]string `json:"Labels"` - - // Mount path of the volume on the host. - // Required: true - Mountpoint string `json:"Mountpoint"` - - // Name of the volume. - // Required: true - Name string `json:"Name"` - - // The driver specific options used when creating the volume. - // Required: true - Options map[string]string `json:"Options"` - - // The level at which the volume exists. - // Libpod does not implement volume scoping, and this is provided solely for - // Docker compatibility. The value is only "local". - // Required: true - Scope string `json:"Scope"` - - // TODO: We don't include the volume `Status` for now -} - type VolumeRmOptions struct { All bool Force bool @@ -158,7 +124,7 @@ type VolumeListReport struct { // VolumeListBody Volume list response // swagger:model VolumeListBody type VolumeListBody struct { - Volumes []*VolumeInfo + Volumes []docker_api_types_volume.VolumeListOKBody } // Volume list response @@ -191,7 +157,7 @@ type SwagDockerVolumeInfoResponse struct { type SwagDockerVolumePruneResponse struct { // in:body Body struct { - // docker_api_types.VolumesPruneReport + docker_api_types.VolumesPruneReport } } diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 6f8845f10..ef3ccab0c 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -15,7 +15,6 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/libpod/logs" "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/checkpoint" @@ -23,6 +22,7 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities/reports" dfilters "github.com/containers/podman/v3/pkg/domain/filters" "github.com/containers/podman/v3/pkg/domain/infra/abi/terminal" + "github.com/containers/podman/v3/pkg/errorhandling" parallelctr "github.com/containers/podman/v3/pkg/parallel/ctr" "github.com/containers/podman/v3/pkg/ps" "github.com/containers/podman/v3/pkg/rootless" @@ -438,7 +438,8 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, default: return nil, errors.Errorf("unrecognized image format %q", options.Format) } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + + sc := ic.Libpod.SystemContext() coptions := buildah.CommitOptions{ SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: options.Writer, @@ -693,14 +694,17 @@ func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID s func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { reports := []*entities.ContainerStartReport{} var exitCode = define.ExecErrorCodeGeneric - ctrs, rawInputs, err := getContainersAndInputByContext(false, options.Latest, namesOrIds, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } // There can only be one container if attach was used for i := range ctrs { ctr := ctrs[i] - rawInput := rawInputs[i] + rawInput := ctr.ID() + if !options.All { + rawInput = rawInputs[i] + } ctrState, err := ctr.State() if err != nil { return nil, err @@ -996,14 +1000,9 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st if options.RemoveImage { _, imageName := ctr.Image() - ctrImage, err := ic.Libpod.ImageRuntime().NewFromLocal(imageName) - if err != nil { - report.RmiErr = err - reports = append(reports, &report) - continue - } - _, err = ic.Libpod.RemoveImage(ctx, ctrImage, false) - report.RmiErr = err + imageEngine := ImageEngine{Libpod: ic.Libpod} + _, rmErrors := imageEngine.Remove(ctx, []string{imageName}, entities.ImageRemoveOptions{}) + report.RmiErr = errorhandling.JoinErrors(rmErrors) } reports = append(reports, &report) diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go index 2cabab988..199ae43ad 100644 --- a/pkg/domain/infra/abi/containers_runlabel.go +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -7,8 +7,9 @@ import ( "path/filepath" "strings" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" envLib "github.com/containers/podman/v3/pkg/env" "github.com/containers/podman/v3/utils" @@ -18,21 +19,48 @@ import ( ) func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, imageRef string, args []string, options entities.ContainerRunlabelOptions) error { - // First, get the image and pull it if needed. - img, err := ic.runlabelImage(ctx, label, imageRef, options) + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.Credentials = options.Credentials + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + + pullPolicy := config.PullPolicyNever + if options.Pull { + pullPolicy = config.PullPolicyMissing + } + if !options.Quiet { + pullOptions.Writer = os.Stderr + } + + pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, imageRef, pullPolicy, pullOptions) if err != nil { return err } + + if len(pulledImages) != 1 { + return errors.Errorf("internal error: expected an image to be pulled (or an error)") + } + // Extract the runlabel from the image. - runlabel, err := img.GetLabel(ctx, label) + labels, err := pulledImages[0].Labels(ctx) if err != nil { return err } + + var runlabel string + for k, v := range labels { + if strings.EqualFold(k, label) { + runlabel = v + break + } + } if runlabel == "" { return errors.Errorf("cannot find the value of label: %s in image: %s", label, imageRef) } - cmd, env, err := generateRunlabelCommand(runlabel, img, args, options) + cmd, env, err := generateRunlabelCommand(runlabel, pulledImages[0], imageRef, args, options) if err != nil { return err } @@ -76,36 +104,9 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } -// runlabelImage returns an image based on the specified image AND options. -func (ic *ContainerEngine) runlabelImage(ctx context.Context, label string, imageRef string, options entities.ContainerRunlabelOptions) (*image.Image, error) { - // First, look up the image locally. If we get an error and requested - // to pull, fallthrough and pull it. - img, err := ic.Libpod.ImageRuntime().NewFromLocal(imageRef) - switch { - case err == nil: - return img, nil - case !options.Pull: - return nil, err - default: - // Fallthrough and pull! - } - - pullOptions := entities.ImagePullOptions{ - Quiet: options.Quiet, - CertDir: options.CertDir, - SkipTLSVerify: options.SkipTLSVerify, - SignaturePolicy: options.SignaturePolicy, - Authfile: options.Authfile, - } - if _, err := pull(ctx, ic.Libpod.ImageRuntime(), imageRef, pullOptions, &label); err != nil { - return nil, err - } - return ic.Libpod.ImageRuntime().NewFromLocal(imageRef) -} - // generateRunlabelCommand generates the to-be-executed command as a string // slice along with a base environment. -func generateRunlabelCommand(runlabel string, img *image.Image, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) { +func generateRunlabelCommand(runlabel string, img *libimage.Image, inputName string, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) { var ( err error name, imageName string @@ -113,24 +114,25 @@ func generateRunlabelCommand(runlabel string, img *image.Image, args []string, o cmd []string ) - // TODO: How do we get global opts as done in v1? - // Extract the imageName (or ID). - imgNames := img.Names() + imgNames := img.NamesHistory() if len(imgNames) == 0 { imageName = img.ID() } else { + // The newest name is the first entry in the `NamesHistory` + // slice. imageName = imgNames[0] } // Use the user-specified name or extract one from the image. - if options.Name != "" { - name = options.Name - } else { - name, err = image.GetImageBaseName(imageName) - if err != nil { - return nil, nil, err + name = options.Name + if name == "" { + normalize := imageName + if !strings.HasPrefix(img.ID(), inputName) { + normalize = inputName } + splitImageName := strings.Split(normalize, "/") + name = splitImageName[len(splitImageName)-1] } // Append the user-specified arguments to the runlabel (command). diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 84c7ebecd..0364b00a3 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -3,15 +3,14 @@ package abi import ( "context" "fmt" - "io" "io/ioutil" "net/url" "os" "path" "path/filepath" "strconv" - "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" @@ -19,14 +18,11 @@ import ( "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/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities/reports" domainUtils "github.com/containers/podman/v3/pkg/domain/utils" + "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/podman/v3/pkg/rootless" - "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" dockerRef "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" @@ -36,31 +32,84 @@ import ( ) func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) { - _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + exists, err := ir.Libpod.LibimageRuntime().Exists(nameOrID) if err != nil { - if errors.Cause(err) == define.ErrMultipleImages { - return &entities.BoolReport{Value: true}, nil - } - if errors.Cause(err) != define.ErrNoSuchImage { - return nil, err - } + return nil, err } - return &entities.BoolReport{Value: err == nil}, nil + return &entities.BoolReport{Value: exists}, nil } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) { - reports, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) - if err != nil { - return nil, err + // NOTE: the terms "dangling" and "intermediate" are not used + // consistently across our code base. In libimage, "dangling" means + // that an image has no tags. "intermediate" means that an image is + // dangling and that no other image depends on it (i.e., has no + // children). + // + // While pruning usually refers to "dangling" images, it has always + // removed "intermediate" ones. + defaultOptions := &libimage.RemoveImagesOptions{ + Filters: append(opts.Filter, "intermediate=true", "containers=false", "readonly=false"), + WithSize: true, + } + + // `image prune --all` means to *also* remove images which are not in + // use by any container. Since image filters are chained, we need to + // do two look ups since the default ones are a subset of all. + unusedOptions := &libimage.RemoveImagesOptions{ + Filters: append(opts.Filter, "containers=false", "readonly=false"), + WithSize: true, + } + + var pruneReports []*reports.PruneReport + + // Now prune all images until we converge. + numPreviouslyRemovedImages := 1 + for { + removedDefault, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, defaultOptions) + if rmErrors != nil { + return nil, errorhandling.JoinErrors(rmErrors) + } + removedUnused, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, unusedOptions) + if rmErrors != nil { + return nil, errorhandling.JoinErrors(rmErrors) + } + + for _, rmReport := range append(removedDefault, removedUnused...) { + r := *rmReport + pruneReports = append(pruneReports, &reports.PruneReport{ + Id: r.ID, + Size: uint64(r.Size), + }) + } + + numRemovedImages := len(removedDefault) + len(removedUnused) + if numRemovedImages+numPreviouslyRemovedImages == 0 { + break + } + numPreviouslyRemovedImages = numRemovedImages } - return reports, err + + return pruneReports, nil +} + +func toDomainHistoryLayer(layer *libimage.ImageHistory) entities.ImageHistoryLayer { + l := entities.ImageHistoryLayer{} + l.ID = layer.ID + l.Created = *layer.Created + l.CreatedBy = layer.CreatedBy + copy(l.Tags, layer.Tags) + l.Size = layer.Size + l.Comment = layer.Comment + return l } func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) { - image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return nil, err } + results, err := image.History(ctx) if err != nil { return nil, err @@ -70,17 +119,17 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entiti Layers: make([]entities.ImageHistoryLayer, len(results)), } - for i, layer := range results { - history.Layers[i] = ToDomainHistoryLayer(layer) + for i := range results { + history.Layers[i] = toDomainHistoryLayer(&results[i]) } return &history, nil } func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entities.ImageMountOptions) ([]*entities.ImageMountReport, error) { - var ( - images []*image.Image - err error - ) + if opts.All && len(nameOrIDs) > 0 { + return nil, errors.Errorf("cannot mix --all with images") + } + if os.Geteuid() != 0 { if driver := ir.Libpod.StorageConfig().GraphDriverName; driver != "vfs" { // Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part @@ -96,219 +145,129 @@ func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entit os.Exit(ret) } } + + listImagesOptions := &libimage.ListImagesOptions{} if opts.All { - allImages, err := ir.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - for _, img := range allImages { - if !img.IsReadOnly() { - images = append(images, img) - } - } - } else { - for _, i := range nameOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) - if err != nil { - return nil, err - } - images = append(images, img) - } + listImagesOptions.Filters = []string{"readonly=false"} } - reports := make([]*entities.ImageMountReport, 0, len(images)) - for _, img := range images { - report := entities.ImageMountReport{Id: img.ID()} - if img.IsReadOnly() { - report.Err = errors.Errorf("mounting readonly %s image not supported", img.ID()) - } else { - report.Path, report.Err = img.Mount([]string{}, "") - } - reports = append(reports, &report) - } - if len(reports) > 0 { - return reports, nil - } - - images, err = ir.Libpod.ImageRuntime().GetImages() + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nameOrIDs, listImagesOptions) if err != nil { return nil, err } + + mountReports := []*entities.ImageMountReport{} + listMountsOnly := !opts.All && len(nameOrIDs) == 0 for _, i := range images { - mounted, path, err := i.Mounted() - if err != nil { - if errors.Cause(err) == storage.ErrLayerUnknown { - continue - } - return nil, err - } - if mounted { - tags, err := i.RepoTags() + // TODO: the .Err fields are not used. This pre-dates the + // libimage migration but should be addressed at some point. + // A quick glimpse at cmd/podman/image/mount.go suggests that + // the errors needed to be handled there as well. + var mountPoint string + var err error + if listMountsOnly { + // We're only looking for mounted images. + mountPoint, err = i.Mountpoint() if err != nil { return nil, err } - reports = append(reports, &entities.ImageMountReport{ - Id: i.ID(), - Name: string(i.Digest()), - Repositories: tags, - Path: path, - }) - } - } - return reports, nil -} - -func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) { - var images []*image.Image - - if options.All { - allImages, err := ir.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - for _, img := range allImages { - if !img.IsReadOnly() { - images = append(images, img) + // Not mounted, so skip. + if mountPoint == "" { + continue } - } - } else { - for _, i := range nameOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) + } else { + mountPoint, err = i.Mount(ctx, nil, "") if err != nil { return nil, err } - images = append(images, img) } - } - reports := []*entities.ImageUnmountReport{} - for _, img := range images { - report := entities.ImageUnmountReport{Id: img.ID()} - mounted, _, err := img.Mounted() + tags, err := i.RepoTags() if err != nil { - // Errors will be caught in Unmount call below - // Default assumption to mounted - mounted = true - } - if !mounted { - continue - } - if err := img.Unmount(options.Force); err != nil { - if options.All && errors.Cause(err) == storage.ErrLayerNotMounted { - logrus.Debugf("Error umounting image %s, storage.ErrLayerNotMounted", img.ID()) - continue - } - report.Err = errors.Wrapf(err, "error unmounting image %s", img.ID()) + return nil, err } - reports = append(reports, &report) + mountReports = append(mountReports, &entities.ImageMountReport{ + Id: i.ID(), + Name: string(i.Digest()), + Repositories: tags, + Path: mountPoint, + }) } - return reports, nil + return mountReports, nil } -func ToDomainHistoryLayer(layer *image.History) entities.ImageHistoryLayer { - l := entities.ImageHistoryLayer{} - l.ID = layer.ID - l.Created = *layer.Created - l.CreatedBy = layer.CreatedBy - copy(l.Tags, layer.Tags) - l.Size = layer.Size - l.Comment = layer.Comment - return l -} - -func pull(ctx context.Context, runtime *image.Runtime, rawImage string, options entities.ImagePullOptions, label *string) (*entities.ImagePullReport, error) { - var writer io.Writer - if !options.Quiet { - writer = os.Stderr - } - - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) - imageRef, err := alltransports.ParseImageName(rawImage) - if err != nil { - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage)) - if err != nil { - return nil, errors.Wrapf(err, "invalid image reference %q", rawImage) - } +func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) { + if options.All && len(nameOrIDs) > 0 { + return nil, errors.Errorf("cannot mix --all with images") } - var registryCreds *types.DockerAuthConfig - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } + listImagesOptions := &libimage.ListImagesOptions{} + if options.All { + listImagesOptions.Filters = []string{"readonly=false"} } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - OSChoice: options.OS, - ArchitectureChoice: options.Arch, - VariantChoice: options.Variant, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nameOrIDs, listImagesOptions) + if err != nil { + return nil, err } - if !options.AllTags { - newImage, err := runtime.New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, label, options.PullPolicy, nil) + unmountReports := []*entities.ImageUnmountReport{} + for _, image := range images { + r := &entities.ImageUnmountReport{Id: image.ID()} + mountPoint, err := image.Mountpoint() if err != nil { - return nil, err + r.Err = err + unmountReports = append(unmountReports, r) + continue } - return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil - } - - // --all-tags requires the docker transport - if imageRef.Transport().Name() != docker.Transport.Name() { - return nil, errors.New("--all-tags requires docker transport") + if mountPoint == "" { + // Skip if the image wasn't mounted. + continue + } + r.Err = image.Unmount(options.Force) + unmountReports = append(unmountReports, r) } + return unmountReports, nil +} - // Trim the docker-transport prefix. - rawImage = strings.TrimPrefix(rawImage, docker.Transport.Name()) +func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { + pullOptions := &libimage.PullOptions{AllTags: options.AllTags} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.Username = options.Username + pullOptions.Password = options.Password + pullOptions.Architecture = options.Arch + pullOptions.OS = options.OS + pullOptions.Variant = options.Variant + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify - // all-tags doesn't work with a tagged reference, so let's check early - namedRef, err := reference.Parse(rawImage) - if err != nil { - return nil, errors.Wrapf(err, "error parsing %q", rawImage) - } - if _, isTagged := namedRef.(reference.Tagged); isTagged { - return nil, errors.New("--all-tags requires a reference without a tag") + if !options.Quiet { + pullOptions.Writer = os.Stderr } - systemContext := image.GetSystemContext("", options.Authfile, false) - tags, err := docker.GetRepositoryTags(ctx, systemContext, imageRef) + pulledImages, err := ir.Libpod.LibimageRuntime().Pull(ctx, rawImage, options.PullPolicy, pullOptions) if err != nil { - return nil, errors.Wrapf(err, "error getting repository tags") + return nil, err } - foundIDs := []string{} - for _, tag := range tags { - name := rawImage + ":" + tag - newImage, err := runtime.New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways, nil) - if err != nil { - logrus.Errorf("error pulling image %q", name) - continue - } - foundIDs = append(foundIDs, newImage.ID()) + pulledIDs := make([]string, len(pulledImages)) + for i := range pulledImages { + pulledIDs[i] = pulledImages[i].ID() } - if len(tags) != len(foundIDs) { - return nil, err - } - return &entities.ImagePullReport{Images: foundIDs}, nil -} - -func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { - return pull(ctx, ir.Libpod.ImageRuntime(), rawImage, options, nil) + return &entities.ImagePullReport{Images: pulledIDs}, nil } func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, []error, error) { reports := []*entities.ImageInspectReport{} errs := []error{} for _, i := range namesOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) + img, _, err := ir.Libpod.LibimageRuntime().LookupImage(i, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { // This is probably a no such image, treat as nonfatal. errs = append(errs, err) continue } - result, err := img.Inspect(ctx) + result, err := img.Inspect(ctx, true) if err != nil { // This is more likely to be fatal. return nil, nil, err @@ -323,11 +282,6 @@ func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts en } func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { - var writer io.Writer - if !options.Quiet { - writer = os.Stderr - } - var manifestType string switch options.Format { case "": @@ -342,58 +296,56 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format) } - var registryCreds *types.DockerAuthConfig - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, - } + pushOptions := &libimage.PushOptions{} + pushOptions.AuthFilePath = options.Authfile + pushOptions.CertDirPath = options.CertDir + pushOptions.DirForceCompress = options.Compress + pushOptions.Username = options.Username + pushOptions.Password = options.Password + pushOptions.ManifestMIMEType = manifestType + pushOptions.RemoveSignatures = options.RemoveSignatures + pushOptions.SignBy = options.SignBy + pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify - signOptions := image.SigningOptions{ - RemoveSignatures: options.RemoveSignatures, - SignBy: options.SignBy, + if !options.Quiet { + pushOptions.Writer = os.Stderr } - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(source) - if err != nil { - return err - } + pushedManifestBytes, pushError := ir.Libpod.LibimageRuntime().Push(ctx, source, destination, pushOptions) + if pushError == nil { + if options.DigestFile != "" { + manifestDigest, err := manifest.Digest(pushedManifestBytes) + if err != nil { + return err + } - err = newImage.PushImageToHeuristicDestination( - ctx, - destination, - manifestType, - options.Authfile, - options.DigestFile, - options.SignaturePolicy, - writer, - options.Compress, - signOptions, - &dockerRegistryOptions, - nil, - options.Progress) - if err != nil && errors.Cause(err) != storage.ErrImageUnknown { + if err := ioutil.WriteFile(options.DigestFile, []byte(manifestDigest.String()), 0644); err != nil { + return err + } + } + return nil + } + // If the image could not be found, we may be referring to a manifest + // list but could not find a matching image instance in the local + // containers storage. In that case, fall back and attempt to push the + // (entire) manifest. + if errors.Cause(pushError) == storage.ErrImageUnknown { // Image might be a manifest list so attempt a manifest push - if _, manifestErr := ir.ManifestPush(ctx, source, destination, options); manifestErr == nil { + _, manifestErr := ir.ManifestPush(ctx, source, destination, options) + if manifestErr == nil { return nil } } - return err + return pushError } func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error { - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return err } for _, tag := range tags { - if err := newImage.TagImage(tag); err != nil { + if err := image.Tag(tag); err != nil { return err } } @@ -401,54 +353,71 @@ func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, } func (ir *ImageEngine) Untag(ctx context.Context, nameOrID string, tags []string, options entities.ImageUntagOptions) error { - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return err } // If only one arg is provided, all names are to be untagged if len(tags) == 0 { - tags = newImage.Names() + tags = image.Names() } for _, tag := range tags { - if err := newImage.UntagImage(tag); err != nil { + if err := image.Untag(tag); err != nil { return err } } return nil } -func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { - var ( - writer io.Writer - ) - if !opts.Quiet { - writer = os.Stderr - } - name, err := ir.Libpod.LoadImage(ctx, opts.Input, writer, opts.SignaturePolicy) - if err != nil { - return nil, err +func (ir *ImageEngine) Load(ctx context.Context, options entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { + loadOptions := &libimage.LoadOptions{} + loadOptions.SignaturePolicyPath = options.SignaturePolicy + if !options.Quiet { + loadOptions.Writer = os.Stderr } - return &entities.ImageLoadReport{Names: strings.Split(name, ",")}, nil -} -func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { - id, err := ir.Libpod.Import(ctx, opts.Source, opts.Reference, opts.SignaturePolicy, opts.Changes, opts.Message, opts.Quiet) + loadedImages, err := ir.Libpod.LibimageRuntime().Load(ctx, options.Input, loadOptions) if err != nil { return nil, err } - return &entities.ImageImportReport{Id: id}, nil + return &entities.ImageLoadReport{Names: loadedImages}, nil } func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error { + saveOptions := &libimage.SaveOptions{} + saveOptions.DirForceCompress = options.Compress + saveOptions.RemoveSignatures = options.RemoveSignatures + + if !options.Quiet { + saveOptions.Writer = os.Stderr + } + + names := []string{nameOrID} if options.MultiImageArchive { - nameOrIDs := append([]string{nameOrID}, tags...) - return ir.Libpod.ImageRuntime().SaveImages(ctx, nameOrIDs, options.Format, options.Output, options.Quiet, true) + names = append(names, tags...) + } else { + saveOptions.AdditionalTags = tags } - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + return ir.Libpod.LibimageRuntime().Save(ctx, names, options.Format, options.Output, saveOptions) +} + +func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportOptions) (*entities.ImageImportReport, error) { + importOptions := &libimage.ImportOptions{} + importOptions.Changes = options.Changes + importOptions.CommitMessage = options.Message + importOptions.Tag = options.Reference + importOptions.SignaturePolicyPath = options.SignaturePolicy + + if !options.Quiet { + importOptions.Writer = os.Stderr + } + + imageID, err := ir.Libpod.LibimageRuntime().Import(ctx, options.Source, importOptions) if err != nil { - return err + return nil, err } - return newImage.Save(ctx, nameOrID, options.Format, options.Output, tags, options.Quiet, options.Compress, true) + + return &entities.ImageImportReport{Id: imageID}, nil } func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) { @@ -460,12 +429,12 @@ func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffO } func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { - filter, err := image.ParseSearchFilter(opts.Filters) + filter, err := libimage.ParseSearchFilter(opts.Filters) if err != nil { return nil, err } - searchOpts := image.SearchOptions{ + searchOptions := &libimage.SearchOptions{ Authfile: opts.Authfile, Filter: *filter, Limit: opts.Limit, @@ -474,7 +443,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im ListTags: opts.ListTags, } - searchResults, err := image.SearchImages(term, searchOpts) + searchResults, err := ir.Libpod.LibimageRuntime().Search(ctx, term, searchOptions) if err != nil { return nil, err } @@ -510,15 +479,15 @@ func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts } func (ir *ImageEngine) Tree(ctx context.Context, nameOrID string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return nil, err } - results, err := img.GenerateTree(opts.WhatRequires) + tree, err := image.Tree(opts.WhatRequires) if err != nil { return nil, err } - return &entities.ImageTreeReport{Tree: results}, nil + return &entities.ImageTreeReport{Tree: tree}, nil } // removeErrorsToExitCode returns an exit code for the specified slice of @@ -542,9 +511,9 @@ func removeErrorsToExitCode(rmErrors []error) int { for _, e := range rmErrors { switch errors.Cause(e) { - case define.ErrNoSuchImage: + case storage.ErrImageUnknown, storage.ErrLayerUnknown: noSuchImageErrors = true - case define.ErrImageInUse, storage.ErrImageUsedByContainer: + case storage.ErrImageUsedByContainer: inUseErrors = true default: otherErrors = true @@ -574,84 +543,25 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie 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: - // Removal worked, so let's report it. - report.Deleted = append(report.Deleted, results.Deleted) - report.Untagged = append(report.Untagged, results.Untagged...) - return nil - case storage.ErrImageUnknown, storage.ErrLayerUnknown: - // The image must have been removed already (see #6510) - // or the storage is corrupted (see #9617). - report.Deleted = append(report.Deleted, img.ID()) - report.Untagged = append(report.Untagged, img.ID()) - return nil - default: - // Fatal error. - return err - } + libimageOptions := &libimage.RemoveImagesOptions{} + libimageOptions.Filters = []string{"readonly=false"} + libimageOptions.Force = opts.Force + if !opts.All { + libimageOptions.Filters = append(libimageOptions.Filters, "intermediate=false") } + libimageOptions.RemoveContainerFunc = ir.Libpod.RemoveContainersForImageCallback(ctx) - // Delete all images from the local storage. - if opts.All { - previousImages := 0 - // Remove all images one-by-one. - for { - storageImages, err := ir.Libpod.ImageRuntime().GetRWImages() - if err != nil { - rmErrors = append(rmErrors, err) - return - } - // No images (left) to remove, so we're done. - if len(storageImages) == 0 { - return - } - // Prevent infinity loops by making a delete-progress check. - if previousImages == len(storageImages) { - rmErrors = append(rmErrors, errors.New("unable to delete all images, check errors and re-run image removal if needed")) - break - } - previousImages = len(storageImages) - // Delete all "leaves" (i.e., images without child images). - for _, img := range storageImages { - isParent, err := img.IsParent(ctx) - if err != nil { - logrus.Warnf("%v, ignoring the error", err) - isParent = false - } - // Skip parent images. - if isParent { - continue - } - if err := deleteImage(img); err != nil { - rmErrors = append(rmErrors, err) - } - } - } - - return - } + libimageReport, libimageErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, images, libimageOptions) - // Delete only the specified images. - for _, id := range images { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) - if err != nil { - // attempt to remove image from storage - if forceErr := ir.Libpod.RemoveImageFromStorage(id); forceErr == nil { - continue - } - rmErrors = append(rmErrors, err) - continue - } - err = deleteImage(img) - if err != nil { - rmErrors = append(rmErrors, err) + for _, r := range libimageReport { + if r.Removed { + report.Deleted = append(report.Deleted, r.ID) } + report.Untagged = append(report.Untagged, r.Untagged...) } + + rmErrors = libimageErrors + return //nolint } diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 3b8aabeb7..b0e947991 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -3,23 +3,25 @@ package abi import ( "context" - libpodImage "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" ) func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { - images, err := ir.Libpod.ImageRuntime().GetImagesWithFilters(opts.Filter) - if err != nil { - return nil, err + listImagesOptions := &libimage.ListImagesOptions{ + Filters: opts.Filter, } - if !opts.All { - filter, err := ir.Libpod.ImageRuntime().IntermediateFilter(ctx, images) - if err != nil { - return nil, err - } - images = libpodImage.FilterImages(images, []libpodImage.ResultFilter{filter}) + // Filter intermediate images unless we want to list *all*. + // NOTE: it's a positive filter, so `intermediate=false` means + // to display non-intermediate images. + listImagesOptions.Filters = append(listImagesOptions.Filters, "intermediate=false") + } + + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nil, listImagesOptions) + if err != nil { + return nil, err } summaries := []*entities.ImageSummary{} @@ -30,24 +32,21 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) } e := entities.ImageSummary{ - ID: img.ID(), - ConfigDigest: string(img.ConfigDigest), - Created: img.Created().Unix(), - Dangling: img.Dangling(), - Digest: string(img.Digest()), - RepoDigests: digests, - History: img.NamesHistory(), - Names: img.Names(), - ReadOnly: img.IsReadOnly(), - SharedSize: 0, - RepoTags: img.Names(), // may include tags and digests + ID: img.ID(), + // ConfigDigest: string(img.ConfigDigest), + Created: img.Created().Unix(), + Dangling: img.IsDangling(), + Digest: string(img.Digest()), + RepoDigests: digests, + History: img.NamesHistory(), + Names: img.Names(), + ReadOnly: img.IsReadOnly(), + SharedSize: 0, + RepoTags: img.Names(), // may include tags and digests } e.Labels, err = img.Labels(ctx) if err != nil { - // Ignore empty manifest lists. - if errors.Cause(err) != libpodImage.ErrImageIsBareList { - return nil, errors.Wrapf(err, "error retrieving label for image %q: you may need to remove the image to resolve the error", img.ID()) - } + return nil, errors.Wrapf(err, "error retrieving label for image %q: you may need to remove the image to resolve the error", img.ID()) } ctnrs, err := img.Containers() @@ -56,20 +55,22 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) } e.Containers = len(ctnrs) - sz, err := img.Size(ctx) + sz, err := img.Size() if err != nil { return nil, errors.Wrapf(err, "error retrieving size of image %q: you may need to remove the image to resolve the error", img.ID()) } - e.Size = int64(*sz) + e.Size = sz // This is good enough for now, but has to be // replaced later with correct calculation logic - e.VirtualSize = int64(*sz) + e.VirtualSize = sz - parent, err := img.ParentID(ctx) + parent, err := img.Parent(ctx) if err != nil { return nil, errors.Wrapf(err, "error retrieving parent of image %q: you may need to remove the image to resolve the error", img.ID()) } - e.ParentId = parent + if parent != nil { + e.ParentId = parent.ID() + } summaries = append(summaries, &e) } diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 8e3c50fac..f932cf21d 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -4,21 +4,17 @@ import ( "bytes" "context" "encoding/json" - "fmt" "os" "strings" - "github.com/containers/buildah/manifests" - buildahManifests "github.com/containers/buildah/pkg/manifests" - buildahUtil "github.com/containers/buildah/util" + "github.com/containers/common/libimage" cp "github.com/containers/image/v5/copy" - "github.com/containers/image/v5/docker" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - libpodImage "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/storage" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -26,51 +22,103 @@ import ( ) // ManifestCreate implements logic for creating manifest lists via ImageEngine -func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) { - fullNames, err := buildahUtil.ExpandNames(names, "", ir.Libpod.SystemContext(), ir.Libpod.GetStore()) - if err != nil { - return "", errors.Wrapf(err, "error encountered while expanding image name %q", names) +func (ir *ImageEngine) ManifestCreate(ctx context.Context, names []string, images []string, opts entities.ManifestCreateOptions) (string, error) { + // FIXME: change the interface of manifest create `names []string` -> + // `name string`. + if len(names) == 0 { + return "", errors.New("no name specified for creating a manifest list") } - imageID, err := libpodImage.CreateManifestList(ir.Libpod.ImageRuntime(), *ir.Libpod.SystemContext(), fullNames, images, opts.All) + name := names[0] + + manifestList, err := ir.Libpod.LibimageRuntime().CreateManifestList(name) if err != nil { - return imageID, err + return "", err } - return imageID, err + + addOptions := &libimage.ManifestListAddOptions{All: opts.All} + for _, image := range images { + if _, err := manifestList.Add(ctx, image, addOptions); err != nil { + return "", err + } + } + + return manifestList.ID(), nil } // ManifestExists checks if a manifest list with the given name exists in local storage func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entities.BoolReport, error) { - if image, err := ir.Libpod.ImageRuntime().NewFromLocal(name); err == nil { - exists, err := image.ExistsManifest() - if err != nil && errors.Cause(err) != buildahManifests.ErrManifestTypeNotSupported { - return nil, err + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(name, &libimage.LookupImageOptions{IgnorePlatform: true}) + if err != nil { + if errors.Cause(err) == storage.ErrImageUnknown { + return &entities.BoolReport{Value: false}, nil } - return &entities.BoolReport{Value: exists}, nil + return nil, err + } + + isManifestList, err := image.IsManifestList(ctx) + if err != nil { + return nil, err } - return &entities.BoolReport{Value: false}, nil + return &entities.BoolReport{Value: isManifestList}, nil } // ManifestInspect returns the content of a manifest list or image func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) { - if newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name); err == nil { - // return the manifest in local storage - if list, err := newImage.InspectManifest(); err == nil { - buf, err := json.MarshalIndent(list, "", " ") - if err != nil { - return buf, errors.Wrapf(err, "error rendering manifest %s for display", name) - } - return buf, nil - // no return if local image is not a list of images type - // continue on getting valid manifest through remote service - } else if errors.Cause(err) != buildahManifests.ErrManifestTypeNotSupported { - return nil, errors.Wrapf(err, "loading manifest %q", name) + // NOTE: we have to do a bit of a limbo here as `podman manifest + // inspect foo` wants to do a remote-inspect of foo iff "foo" in the + // containers storage is an ordinary image but not a manifest list. + + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(name, lookupOptions) + if err != nil { + // If the image doesn't exist, do a remote inspect. + if errors.Cause(err) == storage.ErrImageUnknown { + return ir.remoteManifestInspect(ctx, name) } + return nil, err + } + + isManifestList, err := image.IsManifestList(ctx) + if err != nil { + return nil, err + } + + // If the image isn't a manifest list, do a remote inspect. + if !isManifestList { + return ir.remoteManifestInspect(ctx, name) } - sc := ir.Libpod.SystemContext() - refs, err := buildahUtil.ResolveNameToReferences(ir.Libpod.GetStore(), sc, name) + + manifestList, err := image.ToManifestList() if err != nil { return nil, err } + + schema2List, err := manifestList.Inspect() + if err != nil { + return nil, err + } + + rawSchema2List, err := json.Marshal(schema2List) + if err != nil { + return nil, err + } + + var b bytes.Buffer + if err := json.Indent(&b, rawSchema2List, "", " "); err != nil { + return nil, errors.Wrapf(err, "error rendering manifest %s for display", name) + } + return b.Bytes(), nil +} + +// inspect a remote manifest list. +func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ([]byte, error) { + sys := ir.Libpod.SystemContext() + + resolved, err := shortnames.Resolve(sys, name) + if err != nil { + return nil, err + } + var ( latestErr error result []byte @@ -84,8 +132,13 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte latestErr = errors.Wrapf(latestErr, "tried %v\n", e) } } - for _, ref := range refs { - src, err := ref.NewImageSource(ctx, sc) + + for _, candidate := range resolved.PullCandidates { + ref, err := alltransports.ParseImageName("docker://" + candidate.Value.String()) + if err != nil { + return nil, err + } + src, err := ref.NewImageSource(ctx, sys) if err != nil { appendErr(errors.Wrapf(err, "reading image %q", transports.ImageName(ref))) continue @@ -102,6 +155,7 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte manType = manifestType break } + if len(result) == 0 && latestErr != nil { return nil, latestErr } @@ -138,29 +192,41 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte // ManifestAdd adds images to the manifest list func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) { - imageSpec := opts.Images[0] - listImageSpec := opts.Images[1] - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) - _, err := alltransports.ParseImageName(imageSpec) + // FIXME: the name options below are *mandatory* arguments and should + // be reflected as such in the signature. + + if len(opts.Images) < 2 { + return "", errors.New("manifest add requires two images") + } + + imageName := opts.Images[0] + listName := opts.Images[1] + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) if err != nil { - _, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, imageSpec)) - if err != nil { - return "", errors.Errorf("invalid image reference %q", imageSpec) - } + return "", err } - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec) + + addOptions := &libimage.ManifestListAddOptions{ + All: opts.All, + AuthFilePath: opts.Authfile, + CertDirPath: opts.CertDir, + InsecureSkipTLSVerify: opts.SkipTLSVerify, + Username: opts.Username, + Password: opts.Password, + } + + instanceDigest, err := manifestList.Add(ctx, imageName, addOptions) if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", listImageSpec) + return "", err } - manifestAddOpts := libpodImage.ManifestAddOpts{ - All: opts.All, - Arch: opts.Arch, - Features: opts.Features, - Images: opts.Images, - OS: opts.OS, - OSVersion: opts.OSVersion, - Variant: opts.Variant, + annotateOptions := &libimage.ManifestListAnnotateOptions{ + Architecture: opts.Arch, + Features: opts.Features, + OS: opts.OS, + OSVersion: opts.OSVersion, + Variant: opts.Variant, } if len(opts.Annotation) != 0 { annotations := make(map[string]string) @@ -171,51 +237,44 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd } annotations[spec[0]] = spec[1] } - manifestAddOpts.Annotation = annotations - } - - // Set the system context. - sys := ir.Libpod.SystemContext() - if sys != nil { - sys = &types.SystemContext{} + annotateOptions.Annotations = annotations } - sys.AuthFilePath = opts.Authfile - sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify - sys.DockerCertPath = opts.CertDir - if opts.Username != "" && opts.Password != "" { - sys.DockerAuthConfig = &types.DockerAuthConfig{ - Username: opts.Username, - Password: opts.Password, - } + if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil { + return "", err } - listID, err := listImage.AddManifest(*sys, manifestAddOpts) - if err != nil { - return listID, err - } - return listID, nil + return manifestList.ID(), nil } // ManifestAnnotate updates an entry of the manifest list func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) { - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0]) - if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", names[0]) + // FIXME: the `names` are *mandatory* arguments and should be + // reflected as such in the signature. + + if len(names) < 2 { + return "", errors.New("manifest annotate requires two names") } - digest, err := digest.Parse(names[1]) + + listName := names[0] + instanceDigest, err := digest.Parse(names[1]) if err != nil { return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err) } - manifestAnnotateOpts := libpodImage.ManifestAnnotateOpts{ - Arch: opts.Arch, - Features: opts.Features, - OS: opts.OS, - OSFeatures: opts.OSFeatures, - OSVersion: opts.OSVersion, - Variant: opts.Variant, + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) + if err != nil { + return "", err + } + + annotateOptions := &libimage.ManifestListAnnotateOptions{ + Architecture: opts.Arch, + Features: opts.Features, + OS: opts.OS, + OSVersion: opts.OSVersion, + Variant: opts.Variant, } - if len(opts.Annotation) > 0 { + if len(opts.Annotation) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.Annotation { spec := strings.SplitN(annotationSpec, "=", 2) @@ -224,48 +283,49 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opt } annotations[spec[0]] = spec[1] } - manifestAnnotateOpts.Annotation = annotations + annotateOptions.Annotations = annotations } - updatedListID, err := listImage.AnnotateManifest(*ir.Libpod.SystemContext(), digest, manifestAnnotateOpts) - if err == nil { - return fmt.Sprintf("%s: %s", updatedListID, digest.String()), nil + + if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil { + return "", err } - return "", err + + return manifestList.ID(), nil } // ManifestRemove removes specified digest from the specified manifest list func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) { + // FIXME: the `names` are *mandatory* arguments and should be + // reflected as such in the signature. + + if len(names) < 2 { + return "", errors.New("manifest remove requires two names") + } + + listName := names[0] instanceDigest, err := digest.Parse(names[1]) if err != nil { return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err) } - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0]) + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", names[0]) + return "", err } - updatedListID, err := listImage.RemoveManifest(instanceDigest) - if err == nil { - return fmt.Sprintf("%s :%s\n", updatedListID, instanceDigest.String()), nil + + if err := manifestList.RemoveInstance(instanceDigest); err != nil { + return "", err } - return "", err + + return manifestList.ID(), nil } // ManifestPush pushes a manifest list or image index to the destination func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) { - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name) + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) if err != nil { return "", errors.Wrapf(err, "error retrieving local image from image name %s", name) } - dest, err := alltransports.ParseImageName(destination) - if err != nil { - oldErr := err - // Try adding the images default transport - destination2 := libpodImage.DefaultTransport + destination - dest, err = alltransports.ParseImageName(destination2) - if err != nil { - return "", oldErr - } - } var manifestType string if opts.Format != "" { @@ -279,40 +339,33 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin } } - // Set the system context. - sys := ir.Libpod.SystemContext() - if sys == nil { - sys = new(types.SystemContext) - } - sys.AuthFilePath = opts.Authfile - sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify - sys.DockerCertPath = opts.CertDir - - if opts.Username != "" && opts.Password != "" { - sys.DockerAuthConfig = &types.DockerAuthConfig{ - Username: opts.Username, - Password: opts.Password, - } - } + pushOptions := &libimage.ManifestListPushOptions{} + pushOptions.AuthFilePath = opts.Authfile + pushOptions.CertDirPath = opts.CertDir + pushOptions.Username = opts.Username + pushOptions.Password = opts.Password + pushOptions.ImageListSelection = cp.CopySpecificImages + pushOptions.ManifestMIMEType = manifestType + pushOptions.RemoveSignatures = opts.RemoveSignatures + pushOptions.SignBy = opts.SignBy - options := manifests.PushOptions{ - Store: ir.Libpod.GetStore(), - SystemContext: sys, - ImageListSelection: cp.CopySpecificImages, - Instances: nil, - RemoveSignatures: opts.RemoveSignatures, - SignBy: opts.SignBy, - ManifestType: manifestType, - } if opts.All { - options.ImageListSelection = cp.CopyAllImages + pushOptions.ImageListSelection = cp.CopyAllImages } if !opts.Quiet { - options.ReportWriter = os.Stderr + pushOptions.Writer = os.Stderr } - manDigest, err := listImage.PushManifest(dest, options) - if err == nil && opts.Rm { - _, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true) + + manDigest, err := manifestList.Push(ctx, destination, pushOptions) + if err != nil { + return "", err + } + + if opts.Rm { + if _, err := ir.Libpod.GetStore().DeleteImage(manifestList.ID(), true); err != nil { + return "", errors.Wrap(err, "error removing manifest after push") + } } + return manDigest.String(), err } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 6ddd4a042..64e7f208c 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -10,17 +10,17 @@ import ( "strconv" "strings" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen/generate" "github.com/containers/podman/v3/pkg/specgen/generate/kube" "github.com/containers/podman/v3/pkg/util" - "github.com/docker/distribution/reference" "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -154,10 +154,9 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) { var ( - registryCreds *types.DockerAuthConfig - writer io.Writer - playKubePod entities.PlayKubePod - report entities.PlayKubeReport + writer io.Writer + playKubePod entities.PlayKubePod + report entities.PlayKubeReport ) // Create the secret manager before hand @@ -199,11 +198,17 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } if len(options.StaticIPs) > *ipIndex { p.StaticIP = &options.StaticIPs[*ipIndex] - *ipIndex++ } else if len(options.StaticIPs) > 0 { - // only warn if the user has set at least one ip ip + // only warn if the user has set at least one ip logrus.Warn("No more static ips left using a random one") } + if len(options.StaticMACs) > *ipIndex { + p.StaticMAC = &options.StaticMACs[*ipIndex] + } else if len(options.StaticIPs) > 0 { + // only warn if the user has set at least one mac + logrus.Warn("No more static macs left using a random one") + } + *ipIndex++ // Create the Pod pod, err := generate.MakePod(p, ic.Libpod) @@ -220,19 +225,6 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY writer = os.Stderr } - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } - } - - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, - } - volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes) if err != nil { return nil, err @@ -273,35 +265,36 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers)) for _, container := range podYAML.Spec.Containers { - pullPolicy := util.PullImageMissing + // NOTE: set the pull policy to "newer". This will cover cases + // where the "latest" tag requires a pull and will also + // transparently handle "localhost/" prefixed files which *may* + // refer to a locally built image OR an image running a + // registry on localhost. + pullPolicy := config.PullPolicyNewer if len(container.ImagePullPolicy) > 0 { - pullPolicy, err = util.ValidatePullType(string(container.ImagePullPolicy)) + pullPolicy, err = config.ParsePullPolicy(string(container.ImagePullPolicy)) if err != nil { return nil, err } } - named, err := reference.ParseNormalizedNamed(container.Image) - if err != nil { - return nil, errors.Wrapf(err, "Failed to parse image %q", container.Image) - } - // In kube, if the image is tagged with latest, it should always pull - // but if the domain is localhost, that means the image was built locally - // so do not attempt a pull. - if tagged, isTagged := named.(reference.NamedTagged); isTagged { - if tagged.Tag() == image.LatestTag && reference.Domain(named) != image.DefaultLocalRegistry { - pullPolicy = util.PullImageAlways - } - } - // This ensures the image is the image store - newImage, err := ic.Libpod.ImageRuntime().New(ctx, container.Image, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullPolicy, nil) + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.Writer = writer + pullOptions.Username = options.Username + pullOptions.Password = options.Password + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + + pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions) if err != nil { return nil, err } specgenOpts := kube.CtrSpecGenOptions{ Container: container, - Image: newImage, + Image: pulledImages[0], Volumes: volumes, PodID: pod.ID(), PodName: podName, diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 9bba0fa6c..ebe59e871 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -164,7 +164,10 @@ func movePauseProcessToScope(r *libpod.Runtime) error { // SystemPrune removes unused data from the system. Pruning pods, containers, volumes and images. func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) { var systemPruneReport = new(entities.SystemPruneReport) - var filters []string + filters := []string{} + for k, v := range options.Filters { + filters = append(filters, fmt.Sprintf("%s=%s", k, v[0])) + } reclaimedSpace := (uint64)(0) found := true for found { @@ -188,10 +191,12 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys } reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(containerPruneReports) systemPruneReport.ContainerPruneReports = append(systemPruneReport.ContainerPruneReports, containerPruneReports...) - for k, v := range options.Filters { - filters = append(filters, fmt.Sprintf("%s=%s", k, v[0])) + imagePruneOptions := entities.ImagePruneOptions{ + All: options.All, + Filter: filters, } - imagePruneReports, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, filters) + imageEngine := ImageEngine{Libpod: ic.Libpod} + imagePruneReports, err := imageEngine.Prune(ctx, imagePruneOptions) reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(imagePruneReports) if err != nil { @@ -225,13 +230,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System dfImages = []*entities.SystemDfImageReport{} ) - // Compute disk-usage stats for all local images. - imgs, err := ic.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - - imageStats, err := ic.Libpod.ImageRuntime().DiskUsage(ctx, imgs) + imageStats, err := ic.Libpod.LibimageRuntime().DiskUsage(ctx) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 4545d266b..aa26825e0 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -506,7 +506,7 @@ func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { reports := []*entities.ContainerStartReport{} var exitCode = define.ExecErrorCodeGeneric - ctrs, err := getContainersByContext(ic.ClientCtx, false, false, namesOrIds) + ctrs, err := getContainersByContext(ic.ClientCtx, options.All, false, namesOrIds) if err != nil { return nil, err } @@ -514,9 +514,13 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri // There can only be one container if attach was used for i, ctr := range ctrs { name := ctr.ID + rawInput := ctr.ID + if !options.All { + rawInput = namesOrIds[i] + } report := entities.ContainerStartReport{ Id: name, - RawInput: namesOrIds[i], + RawInput: rawInput, ExitCode: exitCode, } ctrRunning := ctr.State == define.ContainerStateRunning.String() @@ -598,9 +602,9 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri reports = append(reports, &report) continue } + report.ExitCode = 0 + reports = append(reports, &report) } - report.ExitCode = 0 - reports = append(reports, &report) } return reports, nil } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 90b6e104b..3fd9a755d 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -8,10 +8,10 @@ import ( "strings" "time" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/libpod/image" images "github.com/containers/podman/v3/pkg/bindings/images" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities/reports" @@ -311,7 +311,7 @@ func (ir *ImageEngine) Diff(ctx context.Context, nameOrID string, _ entities.Dif func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { mappedFilters := make(map[string][]string) - filters, err := image.ParseSearchFilter(opts.Filters) + filters, err := libimage.ParseSearchFilter(opts.Filters) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go index e52e1a1f7..e66ff0308 100644 --- a/pkg/domain/infra/tunnel/play.go +++ b/pkg/domain/infra/tunnel/play.go @@ -11,7 +11,8 @@ import ( func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { options := new(play.KubeOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password) options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps) - options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot).WithStaticIPs(opts.StaticIPs) + options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot) + options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { options.WithSkipTLSVerify(s == types.OptionalBoolTrue) diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go index 9dc545ebb..9b1740006 100644 --- a/pkg/errorhandling/errorhandling.go +++ b/pkg/errorhandling/errorhandling.go @@ -33,6 +33,9 @@ func JoinErrors(errs []error) error { // ErrorsToString converts the slice of errors into a slice of corresponding // error messages. func ErrorsToStrings(errs []error) []string { + if len(errs) == 0 { + return nil + } strErrs := make([]string, len(errs)) for i := range errs { strErrs[i] = errs[i].Error() @@ -43,6 +46,9 @@ func ErrorsToStrings(errs []error) []string { // StringsToErrors converts a slice of error messages into a slice of // corresponding errors. func StringsToErrors(strErrs []string) []error { + if len(strErrs) == 0 { + return nil + } errs := make([]error, len(strErrs)) for i := range strErrs { errs[i] = errors.New(strErrs[i]) diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index b31978638..0b76636de 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" @@ -257,12 +258,13 @@ func ListStorageContainer(rt *libpod.Runtime, ctr storage.Container, opts entiti imageName := "" if ctr.ImageID != "" { - names, err := rt.ImageRuntime().ImageNames(ctr.ImageID) + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + image, _, err := rt.LibimageRuntime().LookupImage(ctr.ImageID, lookupOptions) if err != nil { return ps, err } - if len(names) > 0 { - imageName = names[0] + if len(image.NamesHistory()) > 0 { + imageName = image.NamesHistory()[0] } } else if buildahCtr { imageName = "scratch" diff --git a/pkg/specgen/config_unsupported.go b/pkg/specgen/config_unsupported.go index 3d89e49f8..70a60ac47 100644 --- a/pkg/specgen/config_unsupported.go +++ b/pkg/specgen/config_unsupported.go @@ -3,11 +3,11 @@ package specgen import ( - "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) -func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { +func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *libimage.Image) (*spec.LinuxSeccomp, error) { return nil, errors.New("function not supported on non-linux OS's") } diff --git a/pkg/specgen/generate/config_linux_cgo.go b/pkg/specgen/generate/config_linux_cgo.go index 41f03d5b6..6ffbf69c1 100644 --- a/pkg/specgen/generate/config_linux_cgo.go +++ b/pkg/specgen/generate/config_linux_cgo.go @@ -6,8 +6,8 @@ import ( "context" "io/ioutil" + "github.com/containers/common/libimage" goSeccomp "github.com/containers/common/pkg/seccomp" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/seccomp" "github.com/containers/podman/v3/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -15,7 +15,7 @@ import ( "github.com/sirupsen/logrus" ) -func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *libimage.Image) (*spec.LinuxSeccomp, error) { var seccompConfig *spec.LinuxSeccomp var err error scp, err := seccomp.LookupPolicy(s.SeccompPolicy) diff --git a/pkg/specgen/generate/config_linux_nocgo.go b/pkg/specgen/generate/config_linux_nocgo.go index 0867988b6..4a1880b74 100644 --- a/pkg/specgen/generate/config_linux_nocgo.go +++ b/pkg/specgen/generate/config_linux_nocgo.go @@ -5,11 +5,11 @@ package generate import ( "errors" - "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" ) -func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *libimage.Image) (*spec.LinuxSeccomp, error) { return nil, errors.New("not implemented") } diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 3d20ed8ff..d00e51e82 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -5,90 +5,45 @@ import ( "os" "strings" - "github.com/containers/image/v5/manifest" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" ann "github.com/containers/podman/v3/pkg/annotations" envLib "github.com/containers/podman/v3/pkg/env" "github.com/containers/podman/v3/pkg/signal" "github.com/containers/podman/v3/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) // Fill any missing parts of the spec generator (e.g. from the image). // Returns a set of warnings or any fatal error that occurred. func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) ([]string, error) { - var ( - newImage *image.Image - err error - ) - // Only add image configuration if we have an image + var newImage *libimage.Image + var inspectData *libimage.ImageData + var err error if s.Image != "" { - newImage, err = r.ImageRuntime().NewFromLocal(s.Image) + newImage, _, err = r.LibimageRuntime().LookupImage(s.Image, nil) if err != nil { return nil, err } - _, mediaType, err := newImage.Manifest(ctx) + inspectData, err = newImage.Inspect(ctx, false) if err != nil { - if errors.Cause(err) != image.ErrImageIsBareList { - return nil, err - } - // if err is not runnable image - // use the local store image with repo@digest matches with the list, if exists - manifestByte, manifestType, err := newImage.GetManifest(ctx, nil) - if err != nil { - return nil, err - } - list, err := manifest.ListFromBlob(manifestByte, manifestType) - if err != nil { - return nil, err - } - images, err := r.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - findLocal := false - listDigest, err := list.ChooseInstance(r.SystemContext()) - if err != nil { - return nil, err - } - for _, img := range images { - for _, imageDigest := range img.Digests() { - if imageDigest == listDigest { - newImage = img - s.Image = img.ID() - mediaType = manifestType - findLocal = true - logrus.Debug("image contains manifest list, using image from local storage") - break - } - } - } - if !findLocal { - return nil, image.ErrImageIsBareList - } + return nil, err } - if s.HealthConfig == nil && mediaType == manifest.DockerV2Schema2MediaType { - s.HealthConfig, err = newImage.GetHealthCheck(ctx) - if err != nil { - return nil, err - } + if s.HealthConfig == nil { + // NOTE: the health check is only set for Docker images + // but inspect will take care of it. + s.HealthConfig = inspectData.HealthCheck } // Image stop signal if s.StopSignal == nil { - stopSignal, err := newImage.StopSignal(ctx) - if err != nil { - return nil, err - } - if stopSignal != "" { - sig, err := signal.ParseSignalNameOrNumber(stopSignal) + if inspectData.Config.StopSignal != "" { + sig, err := signal.ParseSignalNameOrNumber(inspectData.Config.StopSignal) if err != nil { return nil, err } @@ -113,15 +68,10 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat var envs map[string]string // Image Environment defaults - if newImage != nil { + if inspectData != nil { // Image envs from the image if they don't exist // already, overriding the default environments - imageEnvs, err := newImage.Env(ctx) - if err != nil { - return nil, err - } - - envs, err = envLib.ParseSlice(imageEnvs) + envs, err = envLib.ParseSlice(inspectData.Config.Env) if err != nil { return nil, errors.Wrap(err, "Env fields from image failed to parse") } @@ -175,11 +125,7 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } // Add annotations from the image - imgAnnotations, err := newImage.Annotations(ctx) - if err != nil { - return nil, err - } - for k, v := range imgAnnotations { + for k, v := range inspectData.Annotations { annotations[k] = v } } @@ -221,11 +167,8 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.SeccompProfilePath = p } - if len(s.User) == 0 && newImage != nil { - s.User, err = newImage.User(ctx) - if err != nil { - return nil, err - } + if len(s.User) == 0 && inspectData != nil { + s.User = inspectData.Config.User } if err := finishThrottleDevices(s); err != nil { return nil, err diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 277435ef1..01f939022 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -7,9 +7,9 @@ import ( "strings" cdi "github.com/container-orchestrated-devices/container-device-interface/pkg" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage/types" @@ -86,11 +86,18 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener options = append(options, libpod.WithCreateCommand(s.ContainerCreateCommand)) } - var newImage *image.Image + var newImage *libimage.Image + var imageData *libimage.ImageData if s.Rootfs != "" { options = append(options, libpod.WithRootFS(s.Rootfs)) } else { - newImage, err = rt.ImageRuntime().NewFromLocal(s.Image) + var resolvedImageName string + newImage, resolvedImageName, err = rt.LibimageRuntime().LookupImage(s.Image, nil) + if err != nil { + return nil, err + } + + imageData, err = newImage.Inspect(ctx, false) if err != nil { return nil, err } @@ -98,15 +105,14 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener // image. Otherwise, it must have been an ID where we're // defaulting to the first name or an empty one if no names are // present. - imgName := newImage.InputName - if s.Image == newImage.InputName && strings.HasPrefix(newImage.ID(), s.Image) { + if strings.HasPrefix(newImage.ID(), resolvedImageName) { names := newImage.Names() if len(names) > 0 { - imgName = names[0] + resolvedImageName = names[0] } } - options = append(options, libpod.WithRootFSFromImage(newImage.ID(), imgName, s.RawImageName)) + options = append(options, libpod.WithRootFSFromImage(newImage.ID(), resolvedImageName, s.RawImageName)) } if err := s.Validate(); err != nil { return nil, errors.Wrap(err, "invalid config provided") @@ -117,12 +123,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, err } - command, err := makeCommand(ctx, s, newImage, rtc) + command, err := makeCommand(ctx, s, imageData, rtc) if err != nil { return nil, err } - opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, newImage, command) + opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command) if err != nil { return nil, err } @@ -176,7 +182,7 @@ func extractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption { return options } -func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, img *image.Image, command []string) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -202,11 +208,8 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. case "false": break case "", "true": - if len(command) == 0 { - command, err = img.Cmd(ctx) - if err != nil { - return nil, err - } + if len(command) == 0 && imageData != nil { + command = imageData.Config.Cmd } if len(command) > 0 { @@ -308,13 +311,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. } // If the user did not specify a workdir on the CLI, let's extract it // from the image. - if s.WorkDir == "" && img != nil { + if s.WorkDir == "" && imageData != nil { options = append(options, libpod.WithCreateWorkingDir()) - wd, err := img.WorkingDir(ctx) - if err != nil { - return nil, err - } - s.WorkDir = wd + s.WorkDir = imageData.Config.WorkingDir } if s.WorkDir == "" { s.WorkDir = "/" @@ -367,7 +366,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := namespaceOptions(ctx, s, rt, pod, img) + namespaceOptions, err := namespaceOptions(ctx, s, rt, pod, imageData) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 1347ed1e0..73c1c31ba 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -7,9 +7,9 @@ import ( "net" "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/parse" "github.com/containers/common/pkg/secrets" - "github.com/containers/podman/v3/libpod/image" ann "github.com/containers/podman/v3/pkg/annotations" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" @@ -79,7 +79,7 @@ type CtrSpecGenOptions struct { // Container as read from the pod yaml Container v1.Container // Image available to use (pulled or found local) - Image *image.Image + Image *libimage.Image // Volumes for all containers Volumes map[string]*KubeVolume // PodID of the parent pod @@ -165,7 +165,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener // TODO: We don't understand why specgen does not take of this, but // integration tests clearly pointed out that it was required. - imageData, err := opts.Image.Inspect(ctx) + imageData, err := opts.Image.Inspect(ctx, false) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index b52e8d100..278f35c22 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -6,10 +6,10 @@ import ( "os" "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" @@ -79,7 +79,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) // joining a pod. // TODO: Consider grouping options that are not directly attached to a namespace // elsewhere. -func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, img *image.Image) ([]libpod.CtrCreateOption, error) { +func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, imageData *libimage.ImageData) ([]libpod.CtrCreateOption, error) { toReturn := []libpod.CtrCreateOption{} // If pod is not nil, get infra container. @@ -234,7 +234,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. } toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr)) case specgen.Slirp: - portMappings, err := createPortMappings(ctx, s, img) + portMappings, err := createPortMappings(ctx, s, imageData) if err != nil { return nil, err } @@ -246,7 +246,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. case specgen.Private: fallthrough case specgen.Bridge: - portMappings, err := createPortMappings(ctx, s, img) + portMappings, err := createPortMappings(ctx, s, imageData) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 4eae09a5e..bf8d44ed6 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -5,10 +5,10 @@ import ( "path" "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/specgen" @@ -95,16 +95,12 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { } // Produce the final command for the container. -func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image, rtc *config.Config) ([]string, error) { +func makeCommand(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData, rtc *config.Config) ([]string, error) { finalCommand := []string{} entrypoint := s.Entrypoint - if entrypoint == nil && img != nil { - newEntry, err := img.Entrypoint(ctx) - if err != nil { - return nil, err - } - entrypoint = newEntry + if entrypoint == nil && imageData != nil { + entrypoint = imageData.Config.Entrypoint } // Don't append the entrypoint if it is [""] @@ -115,12 +111,8 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image // Only use image command if the user did not manually set an // entrypoint. command := s.Command - if len(command) == 0 && img != nil && len(s.Entrypoint) == 0 { - newCmd, err := img.Cmd(ctx) - if err != nil { - return nil, err - } - command = newCmd + if len(command) == 0 && imageData != nil && len(s.Entrypoint) == 0 { + command = imageData.Config.Cmd } finalCommand = append(finalCommand, command...) @@ -182,7 +174,7 @@ func getCGroupPermissons(unmask []string) string { } // SpecGenToOCI returns the base configuration for the container. -func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) { +func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *libimage.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) { cgroupPerm := getCGroupPermissons(s.Unmask) g, err := generate.New("linux") diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index 678e36a70..6832664a7 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -6,9 +6,9 @@ import ( "strconv" "strings" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/utils" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" @@ -253,7 +253,7 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, } // Make final port mappings for the container -func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *image.Image) ([]ocicni.PortMapping, error) { +func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]ocicni.PortMapping, error) { finalMappings, containerPortValidate, hostPortValidate, err := parsePortMapping(s.PortMappings) if err != nil { return nil, err @@ -262,7 +262,7 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *imag // If not publishing exposed ports, or if we are publishing and there is // nothing to publish - then just return the port mappings we've made so // far. - if !s.PublishExposedPorts || (len(s.Expose) == 0 && img == nil) { + if !s.PublishExposedPorts || (len(s.Expose) == 0 && imageData == nil) { return finalMappings, nil } @@ -273,12 +273,8 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *imag for k, v := range s.Expose { expose[k] = v } - if img != nil { - inspect, err := img.InspectNoSize(ctx) - if err != nil { - return nil, errors.Wrapf(err, "error inspecting image to get exposed ports") - } - for imgExpose := range inspect.Config.ExposedPorts { + if imageData != nil { + for imgExpose := range imageData.Config.ExposedPorts { // Expose format is portNumber[/protocol] splitExpose := strings.SplitN(imgExpose, "/", 2) num, err := strconv.Atoi(splitExpose[0]) diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index e0e4a47a4..a12cc09e2 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -3,12 +3,12 @@ package generate import ( "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/capabilities" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" "github.com/opencontainers/runtime-tools/generate" @@ -80,7 +80,7 @@ func setupApparmor(s *specgen.SpecGenerator, rtc *config.Config, g *generate.Gen return nil } -func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image, rtc *config.Config) error { +func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *libimage.Image, rtc *config.Config) error { var ( caplist []string err error diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index 8066834f7..13f336594 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -8,10 +8,10 @@ import ( "path/filepath" "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -24,7 +24,7 @@ var ( ) // Produce final mounts and named volumes for a container -func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *image.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) { +func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *libimage.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) { // Get image volumes baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s) if err != nil { @@ -173,7 +173,7 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru } // Get image volumes from the given image -func getImageVolumes(ctx context.Context, img *image.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { +func getImageVolumes(ctx context.Context, img *libimage.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { mounts := make(map[string]spec.Mount) volumes := make(map[string]*specgen.NamedVolume) @@ -184,7 +184,7 @@ func getImageVolumes(ctx context.Context, img *image.Image, s *specgen.SpecGener return mounts, volumes, nil } - inspect, err := img.InspectNoSize(ctx) + inspect, err := img.Inspect(ctx, false) if err != nil { return nil, nil, errors.Wrapf(err, "error inspecting image to get image volumes") } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 622fbde99..60aa64ac1 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -554,23 +554,6 @@ func OpenExclusiveFile(path string) (*os.File, error) { return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) } -type PullType = config.PullPolicy - -var ( - // PullImageAlways always try to pull new image when create or run - PullImageAlways = config.PullImageAlways - // PullImageMissing pulls image if it is not locally - PullImageMissing = config.PullImageMissing - // PullImageNever will never pull new image - PullImageNever = config.PullImageNever -) - -// ValidatePullType check if the pullType from CLI is valid and returns the valid enum type -// if the value from CLI is invalid returns the error -func ValidatePullType(pullType string) (PullType, error) { - return config.ValidatePullPolicy(pullType) -} - // ExitCode reads the error message when failing to executing container process // and then returns 0 if no error, 126 if command does not exist, or 127 for // all other errors diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 58b2dff0a..23dd374d6 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -205,10 +205,15 @@ t GET containers/$cid/json 200 \ t POST containers/create Image=$IMAGE Entrypoint='["top"]' 201 \ .Id~[0-9a-f]\\{64\\} cid_top=$(jq -r '.Id' <<<"$output") +network_expect="{}" +if root; then + network_expect='.podman.NetworkID=podman' +fi t GET containers/${cid_top}/json 200 \ .Config.Entrypoint[0]="top" \ .Config.Cmd='[]' \ .Path="top" + .NetworkSettings.Networks="$network_expect" t POST containers/${cid_top}/start 204 # make sure the container is running t GET containers/${cid_top}/json 200 \ @@ -258,7 +263,7 @@ cid=$(jq -r '.Id' <<<"$output") t GET containers/$cid/json 200 \ .Image=${MultiTagName} t DELETE containers/$cid 204 -t DELETE images/${MultiTagName}?force=true 200 +t DELETE images/${MultiTagName} 200 # vim: filetype=sh # Test Volumes field adds an anonymous volume diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py index 3b089e2f2..f66e2b120 100644 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ b/test/apiv2/rest_api/test_rest_v2_0_0.py @@ -614,7 +614,11 @@ class TestApi(unittest.TestCase): # FIXME need method to determine which image is going to be "pruned" to fix test # TODO should handler be recursive when deleting images? # self.assertIn(img["Id"], prune_payload["ImagesDeleted"][1]["Deleted"]) - self.assertIsNotNone(prune_payload["ImagesDeleted"][1]["Deleted"]) + + # FIXME (@vrothberg): I commented this line out during the `libimage` migration. + # It doesn't make sense to report anything to be deleted if the reclaimed space + # is zero. I think the test needs some rewrite. + # self.assertIsNotNone(prune_payload["ImagesDeleted"][1]["Deleted"]) def test_status_compat(self): r = requests.post( diff --git a/test/buildah-bud/apply-podman-deltas b/test/buildah-bud/apply-podman-deltas index 9f6f38190..ecdb9430c 100755 --- a/test/buildah-bud/apply-podman-deltas +++ b/test/buildah-bud/apply-podman-deltas @@ -137,7 +137,8 @@ skip "podman requires a directory, not a Dockerfile" \ # ...or due to Ed's laziness skip "Too much effort to spin up a local registry" \ - "bud with encrypted FROM image" + "bud with encrypted FROM image" \ + "bud --authfile" # ...or due to a fundamental arg-parsing difference between buildah and podman # which we could and perhaps should fix in the buildah repo via: @@ -146,6 +147,14 @@ skip "Too much effort to spin up a local registry" \ skip "FIXME FIXME FIXME: argument-order incompatible with podman" \ "bud-squash-hardlinks" +skip "FIXME FIXME FIXME we'll figure these out later" \ + "bud-multi-stage-nocache-nocommit" \ + "bud with --cgroup-parent" + +# see https://github.com/containers/podman/pull/10147#issuecomment-832503633 +skip "FIXME FIXME FIXME podman save/load has been fixed (but not yet used in Buildah CI)" \ + "bud with --layers and --no-cache flags" + ############################################################################### # BEGIN tests which are skipped due to actual podman bugs. skip "FIXME: podman #9915" \ diff --git a/test/buildah-bud/buildah-tests.diff b/test/buildah-bud/buildah-tests.diff index bba737848..6cda37723 100644 --- a/test/buildah-bud/buildah-tests.diff +++ b/test/buildah-bud/buildah-tests.diff @@ -1,27 +1,18 @@ -From b948e99cb6cb4765987711e8d8948841f6d3f7e2 Mon Sep 17 00:00:00 2001 +From a51192239fafdb59f26c9ddaab1ca9fcac2bb664 Mon Sep 17 00:00:00 2001 From: Ed Santiago <santiago@redhat.com> Date: Tue, 9 Feb 2021 17:28:05 -0700 Subject: [PATCH] tweaks for running buildah tests under podman Signed-off-by: Ed Santiago <santiago@redhat.com> --- - tests/helpers.bash | 28 ++++++++++++++++++++++++---- - 1 file changed, 24 insertions(+), 4 deletions(-) + tests/helpers.bash | 26 +++++++++++++++++++++++--- + 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/helpers.bash b/tests/helpers.bash -index 99c290af..c5572840 100644 +index 4dc3a7dbda13..003575f48cec 100644 --- a/tests/helpers.bash +++ b/tests/helpers.bash -@@ -70,7 +70,7 @@ function _prefetch() { - mkdir -p ${_BUILDAH_IMAGE_CACHEDIR} - fi - -- local _podman_opts="--root ${TESTDIR}/root --storage-driver ${STORAGE_DRIVER}" -+ local _podman_opts="--root ${TESTDIR}/root --runroot ${TESTDIR}/runroot --storage-driver ${STORAGE_DRIVER}" - - for img in "$@"; do - echo "# [checking for: $img]" >&2 -@@ -138,15 +138,35 @@ function run_buildah() { +@@ -140,15 +140,35 @@ function run_buildah() { --retry) retry=3; shift;; # retry network flakes esac @@ -54,11 +45,11 @@ index 99c290af..c5572840 100644 # stdout is only emitted upon error; this echo is to help a debugger - echo "\$ $BUILDAH_BINARY $*" -- run timeout --foreground --kill=10 $BUILDAH_TIMEOUT ${BUILDAH_BINARY} --registries-conf ${TESTSDIR}/registries.conf --root ${TESTDIR}/root --runroot ${TESTDIR}/runroot --storage-driver ${STORAGE_DRIVER} "$@" +- run timeout --foreground --kill=10 $BUILDAH_TIMEOUT ${BUILDAH_BINARY} ${REGISTRY_OPTS} ${ROOTDIR_OPTS} "$@" + echo "\$ $cmd_basename $*" + run timeout --foreground --kill=10 $BUILDAH_TIMEOUT ${podman_or_buildah} --registries-conf ${TESTSDIR}/registries.conf --root ${TESTDIR}/root --runroot ${TESTDIR}/runroot --storage-driver ${STORAGE_DRIVER} "$@" # without "quotes", multiple lines are glommed together into one if [ -n "$output" ]; then echo "$output" -- -2.30.2 +2.31.1 diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 9ae56d7ce..8530d3dd3 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -320,7 +320,7 @@ func (p *PodmanTestIntegration) createArtifact(image string) { fmt.Printf("Caching %s at %s...", image, destName) if _, err := os.Stat(destName); os.IsNotExist(err) { pull := p.PodmanNoCache([]string{"pull", image}) - pull.Wait(240) + pull.Wait(440) Expect(pull.ExitCode()).To(Equal(0)) save := p.PodmanNoCache([]string{"save", "-o", destName, image}) diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index 267f18b0a..3bd75a8f2 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -286,7 +286,7 @@ var _ = Describe("Podman load", func() { }) It("podman load multi-image archive", func() { - result := podmanTest.Podman([]string{"load", "-i", "./testdata/image/docker-two-images.tar.xz"}) + result := podmanTest.Podman([]string{"load", "-i", "./testdata/docker-two-images.tar.xz"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) Expect(result.LineInOutputContains("example.com/empty:latest")).To(BeTrue()) diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index f89da4c05..836fbe1ee 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -1717,7 +1717,7 @@ spec: } }) - It("podman play kube --ip", func() { + It("podman play kube --ip and --mac-address", func() { var i, numReplicas int32 numReplicas = 3 deployment := getDeployment(withReplicas(numReplicas)) @@ -1735,6 +1735,10 @@ spec: for _, ip := range ips { playArgs = append(playArgs, "--ip", ip) } + macs := []string{"e8:d8:82:c9:80:40", "e8:d8:82:c9:80:50", "e8:d8:82:c9:80:60"} + for _, mac := range macs { + playArgs = append(playArgs, "--mac-address", mac) + } kube := podmanTest.Podman(append(playArgs, kubeYaml)) kube.WaitWithDefaultTimeout() @@ -1747,6 +1751,13 @@ spec: Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(Equal(ips[i])) } + + for i = 0; i < numReplicas; i++ { + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[i]), "--format", "{{ .NetworkSettings.Networks." + net + ".MacAddress }}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + Expect(inspect.OutputToString()).To(Equal(macs[i])) + } }) It("podman play kube test with network portbindings", func() { diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index cbe38fc76..38f893a43 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -104,8 +104,9 @@ var _ = Describe("Podman prune", func() { after := podmanTest.Podman([]string{"images", "-a"}) after.WaitWithDefaultTimeout() Expect(none.ExitCode()).To(Equal(0)) + // Check if all "dangling" images were pruned. hasNoneAfter, _ := after.GrepString("<none>") - Expect(hasNoneAfter).To(BeTrue()) + Expect(hasNoneAfter).To(BeFalse()) Expect(len(after.OutputToStringArray()) > 1).To(BeTrue()) }) @@ -135,12 +136,18 @@ var _ = Describe("Podman prune", func() { It("podman image prune unused images", func() { podmanTest.AddImageToRWStore(ALPINE) podmanTest.AddImageToRWStore(BB) + + images := podmanTest.Podman([]string{"images", "-a"}) + images.WaitWithDefaultTimeout() + Expect(images.ExitCode()).To(Equal(0)) + prune := podmanTest.Podman([]string{"image", "prune", "-af"}) prune.WaitWithDefaultTimeout() Expect(prune.ExitCode()).To(Equal(0)) - images := podmanTest.Podman([]string{"images", "-aq"}) + images = podmanTest.Podman([]string{"images", "-aq"}) images.WaitWithDefaultTimeout() + Expect(images.ExitCode()).To(Equal(0)) // all images are unused, so they all should be deleted! Expect(len(images.OutputToStringArray())).To(Equal(len(CACHE_IMAGES))) }) diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index 5308548f1..c60ad9487 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -86,7 +86,7 @@ var _ = Describe("Podman pull", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"rmi", "testdigest_v2s2:none"}) + session = podmanTest.Podman([]string{"rmi", "testdigest_v2s2"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) }) @@ -256,7 +256,7 @@ var _ = Describe("Podman pull", func() { // Pulling a multi-image archive without further specifying // which image _must_ error out. Pulling is restricted to one // image. - session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/image/docker-two-images.tar.xz")}) + session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/docker-two-images.tar.xz")}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(125)) expectedError := "Unexpected tar manifest.json: expected 1 item, got 2" @@ -265,31 +265,31 @@ var _ = Describe("Podman pull", func() { // Now pull _one_ image from a multi-image archive via the name // and index syntax. - session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/image/docker-two-images.tar.xz:@0")}) + session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/docker-two-images.tar.xz:@0")}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/image/docker-two-images.tar.xz:example.com/empty:latest")}) + session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/docker-two-images.tar.xz:example.com/empty:latest")}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/image/docker-two-images.tar.xz:@1")}) + session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/docker-two-images.tar.xz:@1")}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/image/docker-two-images.tar.xz:example.com/empty/but:different")}) + session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/docker-two-images.tar.xz:example.com/empty/but:different")}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) // Now check for some errors. - session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/image/docker-two-images.tar.xz:foo.com/does/not/exist:latest")}) + session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/docker-two-images.tar.xz:foo.com/does/not/exist:latest")}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(125)) expectedError = "Tag \"foo.com/does/not/exist:latest\" not found" found, _ = session.ErrorGrepString(expectedError) Expect(found).To(Equal(true)) - session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/image/docker-two-images.tar.xz:@2")}) + session = podmanTest.Podman([]string{"pull", fmt.Sprintf("docker-archive:./testdata/docker-two-images.tar.xz:@2")}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(125)) expectedError = "Invalid source index @2, only 2 manifest items available" diff --git a/test/e2e/run_cgroup_parent_test.go b/test/e2e/run_cgroup_parent_test.go index d68b1bb5f..1df4c4033 100644 --- a/test/e2e/run_cgroup_parent_test.go +++ b/test/e2e/run_cgroup_parent_test.go @@ -1,7 +1,10 @@ package integration import ( + "fmt" "os" + "path/filepath" + "strings" . "github.com/containers/podman/v3/test/utils" . "github.com/onsi/ginkgo" @@ -58,6 +61,38 @@ var _ = Describe("Podman run with --cgroup-parent", func() { Expect(ok).To(BeTrue()) }) + Specify("always honor --cgroup-parent", func() { + SkipIfCgroupV1("test not supported in cgroups v1") + if Containerized() || podmanTest.CgroupManager == "cgroupfs" { + Skip("Requires Systemd cgroup manager support") + } + if IsRemote() { + Skip("Not supported for remote") + } + + run := podmanTest.Podman([]string{"run", "-d", "--cgroupns=host", fedoraMinimal, "sleep", "100"}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + cid := run.OutputToString() + + exec := podmanTest.Podman([]string{"exec", cid, "cat", "/proc/self/cgroup"}) + exec.WaitWithDefaultTimeout() + Expect(exec.ExitCode()).To(Equal(0)) + + cgroup := filepath.Dir(strings.TrimRight(strings.Replace(exec.OutputToString(), "0::", "", -1), "\n")) + + run = podmanTest.Podman([]string{"--cgroup-manager=cgroupfs", "run", "-d", fmt.Sprintf("--cgroup-parent=%s", cgroup), fedoraMinimal, "sleep", "100"}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + exec = podmanTest.Podman([]string{"exec", cid, "cat", "/proc/self/cgroup"}) + exec.WaitWithDefaultTimeout() + Expect(exec.ExitCode()).To(Equal(0)) + cgroupEffective := filepath.Dir(strings.TrimRight(strings.Replace(exec.OutputToString(), "0::", "", -1), "\n")) + + Expect(cgroupEffective).To(Equal(cgroup)) + }) + Specify("valid --cgroup-parent using slice", func() { if Containerized() || podmanTest.CgroupManager == "cgroupfs" { Skip("Requires Systemd cgroup manager support") diff --git a/libpod/image/testdata/docker-name-only.tar.xz b/test/e2e/testdata/docker-name-only.tar.xz Binary files differindex 0cad9f108..0cad9f108 100644 --- a/libpod/image/testdata/docker-name-only.tar.xz +++ b/test/e2e/testdata/docker-name-only.tar.xz diff --git a/libpod/image/testdata/docker-registry-name.tar.xz b/test/e2e/testdata/docker-registry-name.tar.xz Binary files differindex 181816c2e..181816c2e 100644 --- a/libpod/image/testdata/docker-registry-name.tar.xz +++ b/test/e2e/testdata/docker-registry-name.tar.xz diff --git a/libpod/image/testdata/docker-two-images.tar.xz b/test/e2e/testdata/docker-two-images.tar.xz Binary files differindex 148d8a86b..148d8a86b 100644 --- a/libpod/image/testdata/docker-two-images.tar.xz +++ b/test/e2e/testdata/docker-two-images.tar.xz diff --git a/libpod/image/testdata/docker-two-names.tar.xz b/test/e2e/testdata/docker-two-names.tar.xz Binary files differindex 07fbc479c..07fbc479c 100644 --- a/libpod/image/testdata/docker-two-names.tar.xz +++ b/test/e2e/testdata/docker-two-names.tar.xz diff --git a/libpod/image/testdata/docker-unnamed.tar.xz b/test/e2e/testdata/docker-unnamed.tar.xz Binary files differindex ba6ea1bae..ba6ea1bae 100644 --- a/libpod/image/testdata/docker-unnamed.tar.xz +++ b/test/e2e/testdata/docker-unnamed.tar.xz diff --git a/test/e2e/testdata/image b/test/e2e/testdata/image deleted file mode 120000 index a9e67bf9a..000000000 --- a/test/e2e/testdata/image +++ /dev/null @@ -1 +0,0 @@ -../../../libpod/image/testdata/
\ No newline at end of file diff --git a/libpod/image/testdata/oci-name-only.tar.gz b/test/e2e/testdata/oci-name-only.tar.gz Binary files differindex 57bc07564..57bc07564 100644 --- a/libpod/image/testdata/oci-name-only.tar.gz +++ b/test/e2e/testdata/oci-name-only.tar.gz diff --git a/libpod/image/testdata/oci-non-docker-name.tar.gz b/test/e2e/testdata/oci-non-docker-name.tar.gz Binary files differindex 5ffc0eabd..5ffc0eabd 100644 --- a/libpod/image/testdata/oci-non-docker-name.tar.gz +++ b/test/e2e/testdata/oci-non-docker-name.tar.gz diff --git a/libpod/image/testdata/oci-registry-name.tar.gz b/test/e2e/testdata/oci-registry-name.tar.gz Binary files differindex e6df87339..e6df87339 100644 --- a/libpod/image/testdata/oci-registry-name.tar.gz +++ b/test/e2e/testdata/oci-registry-name.tar.gz diff --git a/libpod/image/testdata/oci-unnamed.tar.gz b/test/e2e/testdata/oci-unnamed.tar.gz Binary files differindex de445fdf8..de445fdf8 100644 --- a/libpod/image/testdata/oci-unnamed.tar.gz +++ b/test/e2e/testdata/oci-unnamed.tar.gz diff --git a/libpod/image/testdata/registries.conf b/test/e2e/testdata/registries.conf index 16622a1ac..16622a1ac 100644 --- a/libpod/image/testdata/registries.conf +++ b/test/e2e/testdata/registries.conf diff --git a/test/e2e/tree_test.go b/test/e2e/tree_test.go index 184b99dfb..33c69554b 100644 --- a/test/e2e/tree_test.go +++ b/test/e2e/tree_test.go @@ -34,8 +34,7 @@ var _ = Describe("Podman image tree", func() { }) It("podman image tree", func() { - SkipIfRemote("Does not work on remote client") - Skip("don't understand why this fails") + SkipIfRemote("podman-image-tree is not supported for remote clients") podmanTest.AddImageToRWStore(cirros) dockerfile := `FROM quay.io/libpod/cirros:latest RUN mkdir hello diff --git a/test/system/005-info.bats b/test/system/005-info.bats index ed341dd17..83d79221a 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -67,8 +67,7 @@ store.imageStore.number | 1 # RHEL or CentOS 8. # FIXME: what does 'CentOS 8' even mean? What is $VERSION_ID in CentOS? - run_podman info --format '{{.Host.OCIRuntime.Name}}' - is "$output" "runc" "$osname only supports OCI Runtime = runc" + is "$(podman_runtime)" "runc" "$osname only supports OCI Runtime = runc" else skip "only applicable on RHEL, this is $osname" fi diff --git a/test/system/010-images.bats b/test/system/010-images.bats index e7c88408e..bda331e6b 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -64,7 +64,7 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z run_podman commit my-container my-test-image run_podman images my-test-image --format '{{ .History }}' - is "$output" "" "Image has empty history to begin with" + is "$output" "localhost/my-test-image:latest" "image history with initial name" # Generate two randomish tags; 'tr' because they must be all lower-case rand_name1="test-image-history-$(random_string 10 | tr A-Z a-z)" @@ -74,13 +74,13 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z run_podman tag my-test-image $rand_name1 run_podman rmi $rand_name1 run_podman images my-test-image --format '{{ .History }}' - is "$output" "localhost/${rand_name1}:latest" "image history after one tag" + is "$output" "localhost/my-test-image:latest, localhost/${rand_name1}:latest" "image history after one tag" # Repeat with second tag. Now both tags should be in history run_podman tag my-test-image $rand_name2 run_podman rmi $rand_name2 run_podman images my-test-image --format '{{ .History }}' - is "$output" "localhost/${rand_name2}:latest, localhost/${rand_name1}:latest" \ + is "$output" "localhost/my-test-image:latest, localhost/${rand_name2}:latest, localhost/${rand_name1}:latest" \ "image history after two tags" run_podman rmi my-test-image diff --git a/test/system/020-tag.bats b/test/system/020-tag.bats index 1f5eede39..945781afd 100644 --- a/test/system/020-tag.bats +++ b/test/system/020-tag.bats @@ -29,7 +29,7 @@ function _tag_and_check() { # Test error case. run_podman 125 untag $IMAGE registry.com/foo:bar - is "$output" "Error: \"registry.com/foo:bar\": no such tag" + is "$output" "Error: registry.com/foo:bar: tag not known" } @test "podman untag all" { diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 2b83fa56e..9a136ff13 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -142,7 +142,7 @@ echo $rand | 0 | $rand NONLOCAL_IMAGE="$PODMAN_NONLOCAL_IMAGE_FQN" run_podman 125 run --pull=never $NONLOCAL_IMAGE true - is "$output" "Error: unable to find a name and tag match for $NONLOCAL_IMAGE in repotags: no such image" "--pull=never [with image not present]: error" + is "$output" "Error: $NONLOCAL_IMAGE: image not known" "--pull=never [with image not present]: error" run_podman run --pull=missing $NONLOCAL_IMAGE true is "$output" "Trying to pull .*" "--pull=missing [with image NOT PRESENT]: fetches" @@ -153,13 +153,11 @@ echo $rand | 0 | $rand run_podman run --pull=always $NONLOCAL_IMAGE true is "$output" "Trying to pull .*" "--pull=always [with image PRESENT]: re-fetches" - # Very weird corner case fixed by #7770: 'podman run foo' will run 'myfoo' - # if it exists, because the string 'foo' appears in 'myfoo'. This test - # covers that, as well as making sure that our testimage (which is always - # tagged :YYYYMMDD, never :latest) doesn't match either. - run_podman tag $IMAGE my${PODMAN_TEST_IMAGE_NAME}:latest - run_podman 125 run --pull=never $PODMAN_TEST_IMAGE_NAME true - is "$output" "Error: unable to find a name and tag match for $PODMAN_TEST_IMAGE_NAME in repotags: no such image" \ + # NOTE: older version of podman would match "foo" against "myfoo". That + # behaviour was changed with introduction of `containers/common/libimage` + # which will only match at repository boundaries (/). + run_podman 125 run --pull=never my$PODMAN_TEST_IMAGE_NAME true + is "$output" "Error: my$PODMAN_TEST_IMAGE_NAME: image not known" \ "podman run --pull=never with shortname (and implicit :latest)" # ...but if we add a :latest tag (without 'my'), it should now work @@ -169,7 +167,7 @@ echo $rand | 0 | $rand "podman run --pull=never, with shortname, succeeds if img is present" run_podman rm -a - run_podman rmi $NONLOCAL_IMAGE {my,}${PODMAN_TEST_IMAGE_NAME}:latest + run_podman rmi $NONLOCAL_IMAGE ${PODMAN_TEST_IMAGE_NAME}:latest } # 'run --rmi' deletes the image in the end unless it's used by another container @@ -243,7 +241,7 @@ echo $rand | 0 | $rand # Save it as a tar archive run_podman commit myc myi archive=$PODMAN_TMPDIR/archive.tar - run_podman save myi -o $archive + run_podman save --quiet myi -o $archive is "$output" "" "podman save" # Clean up image and container from container storage... diff --git a/test/system/045-start.bats b/test/system/045-start.bats new file mode 100644 index 000000000..ff818e51d --- /dev/null +++ b/test/system/045-start.bats @@ -0,0 +1,43 @@ +#!/usr/bin/env bats -*- bats -*- + +load helpers + +@test "podman start --all - start all containers" { + # Run a bunch of short-lived containers, with different --restart settings + run_podman run -d $IMAGE /bin/true + cid_none_implicit="$output" + run_podman run -d --restart=no $IMAGE /bin/false + cid_none_explicit="$output" + run_podman run -d --restart=on-failure $IMAGE /bin/true + cid_on_failure="$output" + + # Run one longer-lived one. + run_podman run -d --restart=always $IMAGE sleep 20 + cid_always="$output" + + run_podman wait $cid_none_implicit $cid_none_explicit $cid_on_failure + + run_podman start --all + is "$output" ".*$cid_none_implicit" "started: container with no --restart" + is "$output" ".*$cid_none_explicit" "started: container with --restart=no" + is "$output" ".*$cid_on_failure" "started: container with --restart=on-failure" + if [[ $output =~ $cid_always ]]; then + die "podman start --all restarted a running container" + fi + + run_podman rm $cid_none_implicit $cid_none_explicit $cid_on_failure + run_podman stop -t 1 $cid_always + run_podman rm $cid_always +} + +@test "podman start --all with incompatible options" { + expected="Error: either start all containers or the container(s) provided in the arguments" + run_podman 125 start --all 12333 + is "$output" "$expected" "start --all, with args, throws error" + if ! is_remote; then + run_podman 125 start --all --latest + is "$output" "$expected" "podman start --all --latest" + fi +} + +# vim: filetype=sh diff --git a/test/system/060-mount.bats b/test/system/060-mount.bats index f04f34bf6..63a93e13b 100644 --- a/test/system/060-mount.bats +++ b/test/system/060-mount.bats @@ -70,7 +70,7 @@ load helpers is "$output" "" "podman image umount: does not re-umount" run_podman 125 image umount no-such-container - is "$output" "Error: unable to find a name and tag match for no-such-container in repotags: no such image" \ + is "$output" "Error: no-such-container: image not known" \ "error message from image umount no-such-container" run_podman image mount diff --git a/test/system/070-build.bats b/test/system/070-build.bats index 6ae78de2e..a2c8ae588 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -393,9 +393,9 @@ Labels.$label_name | $label_value "image tree: third line" is "${lines[3]}" "Image Layers" \ "image tree: fourth line" - is "${lines[4]}" "... ID: [0-9a-f]\{12\} Size: .* Top Layer of: \[$IMAGE]" \ + is "${lines[4]}" ".* ID: [0-9a-f]\{12\} Size: .* Top Layer of: \[localhost/build_test:latest]" \ "image tree: first layer line" - is "${lines[-1]}" "... ID: [0-9a-f]\{12\} Size: .* Top Layer of: \[localhost/build_test:latest]" \ + is "${lines[-1]}" ".* ID: [0-9a-f]\{12\} Size: .* Top Layer of: \[$IMAGE]" \ "image tree: last layer line" # FIXME: 'image tree --whatrequires' does not work via remote @@ -553,6 +553,7 @@ STEP 2: RUN echo x${random2}y x${random2}y${remote_extra} STEP 3: COMMIT build_test${remote_extra} --> [0-9a-f]\{11\} +Successfully tagged localhost/build_test:latest [0-9a-f]\{64\} a${random3}z" @@ -698,7 +699,7 @@ EOF # we're happy. if ! is_remote; then is "$output" \ - ".* pull policy is .never. but .* could not be found locally" \ + ".*Error: error creating build container: quay.io/libpod/nosuchimage:nosuchtag: image not known" \ "--pull-never fails with expected error message" fi } diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index 98992f973..9a852db89 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -123,8 +123,7 @@ EOF # ARGH. Unfortunately, runc (used for cgroups v1) produces a different error local expect_rc=126 local expect_msg='.* OCI permission denied.*' - run_podman info --format '{{ .Host.OCIRuntime.Path }}' - if expr "$output" : ".*/runc"; then + if [[ $(podman_runtime) = "runc" ]]; then expect_rc=1 expect_msg='.* exec user process caused.*permission denied' fi diff --git a/test/system/170-run-userns.bats b/test/system/170-run-userns.bats index 2dc5b078f..eb6c4e259 100644 --- a/test/system/170-run-userns.bats +++ b/test/system/170-run-userns.bats @@ -6,22 +6,31 @@ load helpers +function _require_crun() { + runtime=$(podman_runtime) + if [[ $runtime != "crun" ]]; then + skip "runtime is $runtime; keep-groups requires crun" + fi +} + @test "podman --group-add keep-groups while in a userns" { - skip_if_rootless "choot is not allowed in rootless mode" + skip_if_rootless "chroot is not allowed in rootless mode" skip_if_remote "--group-add keep-groups not supported in remote mode" + _require_crun run chroot --groups 1234 / ${PODMAN} run --uidmap 0:200000:5000 --group-add keep-groups $IMAGE id is "$output" ".*65534(nobody)" "Check group leaked into user namespace" } @test "podman --group-add keep-groups while not in a userns" { - skip_if_rootless "choot is not allowed in rootless mode" + skip_if_rootless "chroot is not allowed in rootless mode" skip_if_remote "--group-add keep-groups not supported in remote mode" + _require_crun run chroot --groups 1234,5678 / ${PODMAN} run --group-add keep-groups $IMAGE id is "$output" ".*1234" "Check group leaked into container" } @test "podman --group-add without keep-groups while in a userns" { - skip_if_rootless "choot is not allowed in rootless mode" + skip_if_rootless "chroot is not allowed in rootless mode" skip_if_remote "--group-add keep-groups not supported in remote mode" run chroot --groups 1234,5678 / ${PODMAN} run --uidmap 0:200000:5000 --group-add 457 $IMAGE id is "$output" ".*457" "Check group leaked into container" diff --git a/test/system/260-sdnotify.bats b/test/system/260-sdnotify.bats index 8bf49eb1d..acb30de47 100644 --- a/test/system/260-sdnotify.bats +++ b/test/system/260-sdnotify.bats @@ -17,9 +17,9 @@ function setup() { # sdnotify fails with runc 1.0.0-3-dev2 on Ubuntu. Let's just # assume that we work only with crun, nothing else. - run_podman info --format '{{ .Host.OCIRuntime.Name }}' - if [[ "$output" != "crun" ]]; then - skip "this test only works with crun, not '$output'" + runtime=$(podman_runtime) + if [[ "$runtime" != "crun" ]]; then + skip "this test only works with crun, not $runtime" fi basic_setup diff --git a/test/system/410-selinux.bats b/test/system/410-selinux.bats index 8a690fb48..95233c1e6 100644 --- a/test/system/410-selinux.bats +++ b/test/system/410-selinux.bats @@ -51,18 +51,13 @@ function check_label() { } @test "podman selinux: pid=host" { - # FIXME FIXME FIXME: Remove these lines once all VMs have >= 2.146.0 - # (this is ugly, but better than an unconditional skip) - skip_if_no_selinux + # FIXME this test fails when run rootless with runc: + # Error: container_linux.go:367: starting container process caused: process_linux.go:495: container init caused: readonly path /proc/asound: operation not permitted: OCI permission denied if is_rootless; then - if [ -x /usr/bin/rpm ]; then - cs_version=$(rpm -q --qf '%{version}' container-selinux) - else - # SELinux not enabled on Ubuntu, so we should never get here - die "WHOA! SELinux enabled, but no /usr/bin/rpm!" - fi + runtime=$(podman_runtime) + test "$runtime" == "crun" \ + || skip "runtime is $runtime; this test requires crun" fi - # FIXME FIXME FIXME: delete up to here, leaving just check_label check_label "--pid=host" "spc_t" } @@ -185,10 +180,18 @@ function check_label() { @test "podman with nonexistent labels" { skip_if_no_selinux + # runc and crun emit different diagnostics + runtime=$(podman_runtime) + case "$runtime" in + crun) expect="\`/proc/thread-self/attr/exec\`: OCI runtime error: unable to assign security attribute" ;; + runc) expect="OCI runtime error: .*: failed to set /proc/self/attr/keycreate on procfs" ;; + *) skip "Unknown runtime '$runtime'";; + esac + # The '.*' in the error below is for dealing with podman-remote, which # includes "error preparing container <sha> for attach" in output. run_podman 126 run --security-opt label=type:foo.bar $IMAGE true - is "$output" "Error.*: \`/proc/thread-self/attr/exec\`: OCI runtime error: unable to assign security attribute" "useful diagnostic" + is "$output" "Error.*: $expect" "podman emits useful diagnostic on failure" } @test "podman selinux: check relabel" { diff --git a/test/system/build-testimage b/test/system/build-testimage index aac08e307..3e5b982ce 100755 --- a/test/system/build-testimage +++ b/test/system/build-testimage @@ -78,7 +78,7 @@ podman rmi -f testimage &> /dev/null || true # and because Dan says arch emulation is not currently working on podman # (no further details). # Arch emulation on Fedora requires the qemu-user-static package. -for arch in amd64 ppc64le s390x;do +for arch in amd64 arm64v8 ppc64le s390x;do ${BUILDAH} bud \ --arch=$arch \ --build-arg ARCH=$arch \ @@ -106,9 +106,9 @@ ${BUILDAH} manifest push --all ${remote_tag} docker://${remote_tag} # As of 2021-02-24 it is simply busybox, because it is super small, # but it's complicated because of multiarch: # -# img=quay.io/libpod/testimage:00000001 +# img=quay.io/libpod/testimage:0000000<current+1> # buildah manifest create $img -# for arch in amd64 ppc64le s390x;do +# for arch in amd64 arm64v8 ppc64le s390x;do # buildah pull --arch $arch docker.io/$arch/busybox:1.32.0 # buildah manifest add $img docker.io/$arch/busybox:1.32.0 # done diff --git a/test/system/helpers.bash b/test/system/helpers.bash index b9eacfd0b..e0c208f57 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -7,14 +7,14 @@ PODMAN=${PODMAN:-podman} PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"} PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"} PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"testimage"} -PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"20210223"} +PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"20210427"} PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" PODMAN_TEST_IMAGE_ID= # Remote image that we *DO NOT* fetch or keep by default; used for testing pull # This changed from 0 to 1 on 2021-02-24 due to multiarch considerations; it # should change only very rarely. -PODMAN_NONLOCAL_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:00000001" +PODMAN_NONLOCAL_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:00000002" # Because who wants to spell that out each time? IMAGE=$PODMAN_TEST_IMAGE_FQN @@ -35,6 +35,23 @@ fi # That way individual tests can override with their own setup/teardown, # while retaining the ability to include these if they so desire. +# Some CI systems set this to runc, overriding the default crun. +# Although it would be more elegant to override options in run_podman(), +# we instead override $PODMAN itself because some tests (170-run-userns) +# have to invoke $PODMAN directly. +if [[ -n $OCI_RUNTIME ]]; then + if [[ -z $CONTAINERS_CONF ]]; then + # FIXME: BATS provides no mechanism for end-of-run cleanup[1]; how + # can we avoid leaving this file behind when we finish? + # [1] https://github.com/bats-core/bats-core/issues/39 + export CONTAINERS_CONF=$(mktemp --tmpdir=${BATS_TMPDIR:-/tmp} podman-bats-XXXXXXX.containers.conf) + cat >$CONTAINERS_CONF <<EOF +[engine] +runtime="$OCI_RUNTIME" +EOF + fi +fi + # Setup helper: establish a test environment with exactly the images needed function basic_setup() { # Clean up all containers @@ -284,6 +301,16 @@ function is_cgroupsv2() { test "$cgroup_type" = "cgroup2fs" } +# Returns the OCI runtime *basename* (typically crun or runc). Much as we'd +# love to cache this result, we probably shouldn't. +function podman_runtime() { + # This function is intended to be used as '$(podman_runtime)', i.e. + # our caller wants our output. run_podman() messes with output because + # it emits the command invocation to stdout, hence the redirection. + run_podman info --format '{{ .Host.OCIRuntime.Name }}' >/dev/null + basename "${output:-[null]}" +} + # rhbz#1895105: rootless journald is unavailable except to users in # certain magic groups; which our testuser account does not belong to # (intentional: that is the RHEL default, so that's the setup we test). diff --git a/troubleshooting.md b/troubleshooting.md index 1e21edab4..e320f20e7 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -495,10 +495,10 @@ $ podman unshare cat /proc/self/uid_map Reference [subuid](http://man7.org/linux/man-pages/man5/subuid.5.html) and [subgid](http://man7.org/linux/man-pages/man5/subgid.5.html) man pages for more detail. -### 20) Passed-in device can't be accessed in rootless container +### 20) Passed-in devices or files can't be accessed in rootless container -As a non-root user you have group access rights to a device that you want to -pass into a rootless container with `--device=...`. +As a non-root user you have group access rights to a device or files that you +want to pass into a rootless container with `--device=...` or `--volume=...` #### Symptom @@ -507,9 +507,9 @@ Any access inside the container is rejected with "Permission denied". #### Solution The runtime uses `setgroups(2)` hence the process looses all additional groups -the non-root user has. If you use the `crun` runtime, 0.10.4 or newer, -then you can enable a workaround by adding `--annotation io.crun.keep_original_groups=1` -to the `podman` command line. +the non-root user has. Use the `--group-add keep-groups` flag to pass the +user's supplementary group access into the container. Currently only available +with the `crun` OCI runtime. ### 21) A rootless container running in detached mode is closed at logout diff --git a/vendor/github.com/containers/buildah/.cirrus.yml b/vendor/github.com/containers/buildah/.cirrus.yml index 32c711be8..e62c14863 100644 --- a/vendor/github.com/containers/buildah/.cirrus.yml +++ b/vendor/github.com/containers/buildah/.cirrus.yml @@ -26,12 +26,12 @@ env: # GCE project where images live IMAGE_PROJECT: "libpod-218412" # See https://github.com/containers/podman/blob/master/contrib/cirrus/README.md#test_build_cache_images_task-task - FEDORA_NAME: "fedora-33" - PRIOR_FEDORA_NAME: "fedora-32" - UBUNTU_NAME: "ubuntu-2010" - PRIOR_UBUNTU_NAME: "ubuntu-2004" + FEDORA_NAME: "fedora-34" + PRIOR_FEDORA_NAME: "fedora-33" + UBUNTU_NAME: "ubuntu-2104" + PRIOR_UBUNTU_NAME: "ubuntu-2010" - IMAGE_SUFFIX: "c6102133168668672" + IMAGE_SUFFIX: "c6032583541653504" FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "ubuntu-${IMAGE_SUFFIX}" @@ -133,14 +133,20 @@ vendor_task: unit_task: - name: "Unit tests" + name: 'Unit tests w/ $STORAGE_DRIVER' alias: unit depends_on: - smoke - vendor - timeout_in: 45m + timeout_in: 50m + + matrix: + - env: + STORAGE_DRIVER: 'vfs' + - env: + STORAGE_DRIVER: 'overlay' setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' build_script: '${SCRIPT_BASE}/build.sh |& ${_TIMESTAMP}' @@ -149,13 +155,9 @@ unit_task: binary_artifacts: path: ./bin/* - env: - matrix: - STORAGE_DRIVER: 'vfs' - STORAGE_DRIVER: 'overlay' conformance_task: - name: "Docker Build Conformance" + name: 'Build Conformance w/ $STORAGE_DRIVER' alias: conformance depends_on: @@ -166,13 +168,15 @@ conformance_task: timeout_in: 25m + matrix: + - env: + STORAGE_DRIVER: 'vfs' + - env: + STORAGE_DRIVER: 'overlay' + setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' conformance_test_script: '${SCRIPT_BASE}/test.sh conformance |& ${_TIMESTAMP}' - env: - matrix: - STORAGE_DRIVER: 'vfs' - STORAGE_DRIVER: 'overlay' # Confirm cross-compile ALL archetectures on a Mac OS-X VM. cross_build_task: @@ -208,6 +212,9 @@ static_build_task: memory: 12 disk: 200 + env: + NIX_FQIN: "docker.io/nixos/nix:latest" + init_script: | set -ex setenforce 0 @@ -223,8 +230,16 @@ static_build_task: set -ex mkdir -p .cache mv .cache /nix - if [[ -z $(ls -A /nix) ]]; then podman run --rm --privileged -ti -v /:/mnt nixos/nix cp -rfT /nix /mnt/nix; fi - podman run --rm --privileged -ti -v /nix:/nix -v ${PWD}:${PWD} -w ${PWD} nixos/nix nix --print-build-logs --option cores 8 --option max-jobs 8 build --file nix/ + if [[ -z $(ls -A /nix) ]]; then + podman run --rm --privileged -i -v /:/mnt \ + $NIX_FQIN \ + cp -rfT /nix /mnt/nix + fi + podman run --rm --privileged -i -v /nix:/nix \ + -v ${PWD}:${PWD} -w ${PWD} \ + $NIX_FQIN \ + nix --print-build-logs --option cores 8 \ + --option max-jobs 8 build --file nix/ binaries_artifacts: path: "result/bin/buildah" @@ -235,25 +250,47 @@ static_build_task: integration_task: - name: "Integration $DISTRO_NV" + name: "Integration $DISTRO_NV w/ $STORAGE_DRIVER" alias: integration depends_on: - unit matrix: + # VFS + - env: + DISTRO_NV: "${FEDORA_NAME}" + IMAGE_NAME: "${FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + - env: + DISTRO_NV: "${PRIOR_FEDORA_NAME}" + IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + - env: + DISTRO_NV: "${UBUNTU_NAME}" + IMAGE_NAME: "${UBUNTU_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + - env: + DISTRO_NV: "${PRIOR_UBUNTU_NAME}" + IMAGE_NAME: "${PRIOR_UBUNTU_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + # OVERLAY - env: DISTRO_NV: "${FEDORA_NAME}" IMAGE_NAME: "${FEDORA_CACHE_IMAGE_NAME}" - # - env: - # DISTRO_NV: "${PRIOR_FEDORA_NAME}" - # IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + - env: + DISTRO_NV: "${PRIOR_FEDORA_NAME}" + IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' - env: DISTRO_NV: "${UBUNTU_NAME}" IMAGE_NAME: "${UBUNTU_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' - env: DISTRO_NV: "${PRIOR_UBUNTU_NAME}" IMAGE_NAME: "${PRIOR_UBUNTU_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' gce_instance: image_name: "$IMAGE_NAME" @@ -276,10 +313,6 @@ integration_task: package_versions_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh packages' golang_version_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh golang' - env: - matrix: - STORAGE_DRIVER: 'vfs' - STORAGE_DRIVER: 'overlay' in_podman_task: name: "Containerized Integration" diff --git a/vendor/github.com/containers/buildah/Makefile b/vendor/github.com/containers/buildah/Makefile index 9ff59df55..2a54d73c1 100644 --- a/vendor/github.com/containers/buildah/Makefile +++ b/vendor/github.com/containers/buildah/Makefile @@ -51,8 +51,11 @@ all: bin/buildah bin/imgtype docs # Update nix/nixpkgs.json its latest stable commit .PHONY: nixpkgs nixpkgs: - @nix run -f channel:nixos-20.09 nix-prefetch-git -c nix-prefetch-git \ - --no-deepClone https://github.com/nixos/nixpkgs > nix/nixpkgs.json + @nix run \ + -f channel:nixos-20.09 nix-prefetch-git \ + -c nix-prefetch-git \ + --no-deepClone \ + https://github.com/nixos/nixpkgs refs/heads/nixos-20.09 > nix/nixpkgs.json # Build statically linked binary .PHONY: static @@ -161,7 +164,7 @@ tests/testreport/testreport: tests/testreport/testreport.go .PHONY: test-unit test-unit: tests/testreport/testreport - $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover -race $(shell $(GO) list ./... | grep -v vendor | grep -v tests | grep -v cmd) -timeout 40m + $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover -race $(shell $(GO) list ./... | grep -v vendor | grep -v tests | grep -v cmd) -timeout 45m tmp=$(shell mktemp -d) ; \ mkdir -p $$tmp/root $$tmp/runroot; \ $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover -race ./cmd/buildah -args --root $$tmp/root --runroot $$tmp/runroot --storage-driver vfs --signature-policy $(shell pwd)/tests/policy.json --registries-conf $(shell pwd)/tests/registries.conf diff --git a/vendor/github.com/containers/buildah/add.go b/vendor/github.com/containers/buildah/add.go index e81e35c30..0a77e9f9d 100644 --- a/vendor/github.com/containers/buildah/add.go +++ b/vendor/github.com/containers/buildah/add.go @@ -224,7 +224,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption } localSourceStats, err = copier.Stat(contextDir, contextDir, statOptions, localSources) if err != nil { - return errors.Wrapf(err, "error checking on sources %v under %q", localSources, contextDir) + return errors.Wrapf(err, "checking on sources under %q", contextDir) } } numLocalSourceItems := 0 @@ -238,10 +238,10 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { errorText = fmt.Sprintf("possible escaping context directory error: %s", errorText) } - return errors.Errorf("error checking on source %v under %q: %v", localSourceStat.Glob, contextDir, errorText) + return errors.Errorf("checking on sources under %q: %v", contextDir, errorText) } if len(localSourceStat.Globbed) == 0 { - return errors.Wrapf(syscall.ENOENT, "error checking on source %v under %q: no glob matches", localSourceStat.Glob, contextDir) + return errors.Wrapf(syscall.ENOENT, "checking source under %q: no glob matches", contextDir) } numLocalSourceItems += len(localSourceStat.Globbed) } @@ -433,7 +433,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption } } if localSourceStat == nil { - return errors.Errorf("internal error: should have statted %s, but we didn't?", src) + continue } // Iterate through every item that matched the glob. diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index b0ddd0f72..771165d43 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -357,6 +357,9 @@ type ImportFromImageOptions struct { // NewBuilder creates a new build container. func NewBuilder(ctx context.Context, store storage.Store, options BuilderOptions) (*Builder, error) { + if options.CommonBuildOpts == nil { + options.CommonBuildOpts = &CommonBuildOptions{} + } return newBuilder(ctx, store, options) } diff --git a/vendor/github.com/containers/buildah/changelog.txt b/vendor/github.com/containers/buildah/changelog.txt index 74929da78..7d31c854d 100644 --- a/vendor/github.com/containers/buildah/changelog.txt +++ b/vendor/github.com/containers/buildah/changelog.txt @@ -1,3 +1,34 @@ +- Changelog for v1.20.1 (2021-04-13) + * Run container with isolation type set at 'from' + * bats helpers.bash - minor refactoring + * Bump containers/storage vendor to v1.29.0 + * build(deps): bump github.com/onsi/ginkgo from 1.16.0 to 1.16.1 + * Cirrus: Update VMs w/ F34beta + * CLI add/copy: add a --from option + * build(deps): bump github.com/onsi/ginkgo from 1.15.2 to 1.16.0 + * Add authentication system tests for 'commit' and 'bud' + * fix local image lookup for custom platform + * Double-check existence of OCI runtimes + * Cirrus: Make use of shared get_ci_vm container + * Add system tests of "buildah run" + * Update nix pin with `make nixpkgs` + * Remove some stuttering on returns errors + * Setup alias for --tty to --terminal + * Add conformance tests for COPY /... + * Put a few more minutes on the clock for the CI conformance test + * Add a conformance test for COPY --from $symlink + * Add conformance tests for COPY "" + * Check for symlink in builtin volume + * Sort all mounts by destination directory + * System-test cleanup + * Export parse.Platform string to be used by podman-remote + * blobcache: fix sequencing error + * build(deps): bump github.com/containers/common from 0.35.3 to 0.35.4 + * Fix URL in demos/buildah_multi_stage.sh + * Add a few system tests + * [NO TESTS NEEDED] Use --recurse-modules when building git context + * Bump to v1.20.1-dev + - Changelog for v1.20.0 (2021-03-25) * vendor in containers/storage v1.28.1 * build(deps): bump github.com/containers/common from 0.35.2 to 0.35.3 diff --git a/vendor/github.com/containers/buildah/commit.go b/vendor/github.com/containers/buildah/commit.go index f588c8043..139355517 100644 --- a/vendor/github.com/containers/buildah/commit.go +++ b/vendor/github.com/containers/buildah/commit.go @@ -3,16 +3,15 @@ package buildah import ( "context" "encoding/json" - "fmt" "io" "io/ioutil" "os" "strings" "time" - "github.com/containers/buildah/manifests" "github.com/containers/buildah/pkg/blobcache" "github.com/containers/buildah/util" + "github.com/containers/common/libimage/manifests" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" @@ -104,59 +103,6 @@ type CommitOptions struct { OciEncryptLayers *[]int } -// PushOptions can be used to alter how an image is copied somewhere. -type PushOptions struct { - // Compression specifies the type of compression which is applied to - // layer blobs. The default is to not use compression, but - // archive.Gzip is recommended. - Compression archive.Compression - // SignaturePolicyPath specifies an override location for the signature - // policy which should be used for verifying the new image as it is - // being written. Except in specific circumstances, no value should be - // specified, indicating that the shared, system-wide default policy - // should be used. - SignaturePolicyPath string - // ReportWriter is an io.Writer which will be used to log the writing - // of the new image. - ReportWriter io.Writer - // Store is the local storage store which holds the source image. - Store storage.Store - // github.com/containers/image/types SystemContext to hold credentials - // and other authentication/authorization information. - SystemContext *types.SystemContext - // ManifestType is the format to use when saving the image using the 'dir' transport - // possible options are oci, v2s1, and v2s2 - ManifestType string - // BlobDirectory is the name of a directory in which we'll look for - // prebuilt copies of layer blobs that we might otherwise need to - // regenerate from on-disk layers, substituting them in the list of - // blobs to copy whenever possible. - BlobDirectory string - // Quiet is a boolean value that determines if minimal output to - // the user will be displayed, this is best used for logging. - // The default is false. - Quiet bool - // SignBy is the fingerprint of a GPG key to use for signing the image. - SignBy string - // RemoveSignatures causes any existing signatures for the image to be - // discarded for the pushed copy. - RemoveSignatures bool - // MaxRetries is the maximum number of attempts we'll make to push any - // one image to the external registry if the first attempt fails. - MaxRetries int - // RetryDelay is how long to wait before retrying a push attempt. - RetryDelay time.Duration - // OciEncryptConfig when non-nil indicates that an image should be encrypted. - // The encryption options is derived from the construction of EncryptConfig object. - OciEncryptConfig *encconfig.EncryptConfig - // OciEncryptLayers represents the list of layers to encrypt. - // If nil, don't encrypt any layers. - // If non-nil and len==0, denotes encrypt all layers. - // integers in the slice represent 0-indexed layer indices, with support for negative - // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. - OciEncryptLayers *[]int -} - var ( // storageAllowedPolicyScopes overrides the policy for local storage // to ensure that we can read images from it. @@ -239,7 +185,7 @@ func (b *Builder) addManifest(ctx context.Context, manifestName string, imageSpe } } - names, err := util.ExpandNames([]string{manifestName}, "", systemContext, b.store) + names, err := util.ExpandNames([]string{manifestName}, systemContext, b.store) if err != nil { return "", errors.Wrapf(err, "error encountered while expanding image name %q", manifestName) } @@ -341,30 +287,6 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options systemContext.OCIInsecureSkipTLSVerify = true systemContext.DockerDaemonInsecureSkipTLSVerify = true } - if len(options.AdditionalTags) > 0 { - names, err := util.ExpandNames(options.AdditionalTags, "", systemContext, b.store) - if err != nil { - return imgID, nil, "", err - } - for _, name := range names { - additionalDest, err := docker.Transport.ParseReference(name) - if err != nil { - return imgID, nil, "", errors.Wrapf(err, "error parsing image name %q as an image reference", name) - } - insecure, err := checkRegistrySourcesAllows("commit to", additionalDest) - if err != nil { - return imgID, nil, "", err - } - if insecure { - if systemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse { - return imgID, nil, "", errors.Errorf("can't require tls verification on an insecured registry") - } - systemContext.DockerInsecureSkipTLSVerify = types.OptionalBoolTrue - systemContext.OCIInsecureSkipTLSVerify = true - systemContext.DockerDaemonInsecureSkipTLSVerify = true - } - } - } logrus.Debugf("committing image with reference %q is allowed by policy", transports.ImageName(dest)) // Check if the base image is already in the destination and it's some kind of local @@ -495,97 +417,3 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options } return imgID, ref, manifestDigest, nil } - -// Push copies the contents of the image to a new location. -func Push(ctx context.Context, image string, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) { - systemContext := getSystemContext(options.Store, options.SystemContext, options.SignaturePolicyPath) - - if options.Quiet { - options.ReportWriter = nil // Turns off logging output - } - blocked, err := isReferenceBlocked(dest, systemContext) - if err != nil { - return nil, "", errors.Wrapf(err, "error checking if pushing to registry for %q is blocked", transports.ImageName(dest)) - } - if blocked { - return nil, "", errors.Errorf("push access to registry for %q is blocked by configuration", transports.ImageName(dest)) - } - - // Load the system signing policy. - pushPolicy, err := signature.DefaultPolicy(systemContext) - if err != nil { - return nil, "", errors.Wrapf(err, "error obtaining default signature policy") - } - // Override the settings for local storage to make sure that we can always read the source "image". - pushPolicy.Transports[is.Transport.Name()] = storageAllowedPolicyScopes - - policyContext, err := signature.NewPolicyContext(pushPolicy) - if err != nil { - return nil, "", errors.Wrapf(err, "error creating new signature policy context") - } - defer func() { - if err2 := policyContext.Destroy(); err2 != nil { - logrus.Debugf("error destroying signature policy context: %v", err2) - } - }() - - // Look up the image. - src, _, err := util.FindImage(options.Store, "", systemContext, image) - if err != nil { - return nil, "", err - } - maybeCachedSrc := src - if options.BlobDirectory != "" { - compress := types.PreserveOriginal - if options.Compression != archive.Uncompressed { - compress = types.Compress - } - cache, err := blobcache.NewBlobCache(src, options.BlobDirectory, compress) - if err != nil { - return nil, "", errors.Wrapf(err, "error wrapping image reference %q in blob cache at %q", transports.ImageName(src), options.BlobDirectory) - } - maybeCachedSrc = cache - } - - // Check if the push is blocked by $BUILDER_REGISTRY_SOURCES. - insecure, err := checkRegistrySourcesAllows("push to", dest) - if err != nil { - return nil, "", err - } - if insecure { - if systemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse { - return nil, "", errors.Errorf("can't require tls verification on an insecured registry") - } - systemContext.DockerInsecureSkipTLSVerify = types.OptionalBoolTrue - systemContext.OCIInsecureSkipTLSVerify = true - systemContext.DockerDaemonInsecureSkipTLSVerify = true - } - logrus.Debugf("pushing image to reference %q is allowed by policy", transports.ImageName(dest)) - - // Copy everything. - switch options.Compression { - case archive.Uncompressed: - systemContext.OCIAcceptUncompressedLayers = true - case archive.Gzip: - systemContext.DirForceCompress = true - } - var manifestBytes []byte - if manifestBytes, err = retryCopyImage(ctx, policyContext, dest, maybeCachedSrc, dest, getCopyOptions(options.Store, options.ReportWriter, nil, systemContext, options.ManifestType, options.RemoveSignatures, options.SignBy, options.OciEncryptLayers, options.OciEncryptConfig, nil), options.MaxRetries, options.RetryDelay); err != nil { - return nil, "", errors.Wrapf(err, "error copying layers and metadata from %q to %q", transports.ImageName(maybeCachedSrc), transports.ImageName(dest)) - } - if options.ReportWriter != nil { - fmt.Fprintf(options.ReportWriter, "") - } - manifestDigest, err := manifest.Digest(manifestBytes) - if err != nil { - return nil, "", errors.Wrapf(err, "error computing digest of manifest of new image %q", transports.ImageName(dest)) - } - var ref reference.Canonical - if name := dest.DockerReference(); name != nil { - ref, err = reference.WithDigest(name, manifestDigest) - if err != nil { - logrus.Warnf("error generating canonical reference with name %q and digest %s: %v", name, manifestDigest.String(), err) - } - } - return ref, manifestDigest, nil -} diff --git a/vendor/github.com/containers/buildah/copier/copier.go b/vendor/github.com/containers/buildah/copier/copier.go index a37d4635e..8f6821c31 100644 --- a/vendor/github.com/containers/buildah/copier/copier.go +++ b/vendor/github.com/containers/buildah/copier/copier.go @@ -70,12 +70,13 @@ func isArchivePath(path string) bool { type requestType string const ( - requestEval requestType = "EVAL" - requestStat requestType = "STAT" - requestGet requestType = "GET" - requestPut requestType = "PUT" - requestMkdir requestType = "MKDIR" - requestQuit requestType = "QUIT" + requestEval requestType = "EVAL" + requestStat requestType = "STAT" + requestGet requestType = "GET" + requestPut requestType = "PUT" + requestMkdir requestType = "MKDIR" + requestRemove requestType = "REMOVE" + requestQuit requestType = "QUIT" ) // Request encodes a single request. @@ -88,10 +89,11 @@ type request struct { preservedDirectory string Globs []string `json:",omitempty"` // used by stat, get preservedGlobs []string - StatOptions StatOptions `json:",omitempty"` - GetOptions GetOptions `json:",omitempty"` - PutOptions PutOptions `json:",omitempty"` - MkdirOptions MkdirOptions `json:",omitempty"` + StatOptions StatOptions `json:",omitempty"` + GetOptions GetOptions `json:",omitempty"` + PutOptions PutOptions `json:",omitempty"` + MkdirOptions MkdirOptions `json:",omitempty"` + RemoveOptions RemoveOptions `json:",omitempty"` } func (req *request) Excludes() []string { @@ -106,6 +108,8 @@ func (req *request) Excludes() []string { return nil case requestMkdir: return nil + case requestRemove: + return nil case requestQuit: return nil default: @@ -125,6 +129,8 @@ func (req *request) UIDMap() []idtools.IDMap { return req.PutOptions.UIDMap case requestMkdir: return req.MkdirOptions.UIDMap + case requestRemove: + return nil case requestQuit: return nil default: @@ -144,6 +150,8 @@ func (req *request) GIDMap() []idtools.IDMap { return req.PutOptions.GIDMap case requestMkdir: return req.MkdirOptions.GIDMap + case requestRemove: + return nil case requestQuit: return nil default: @@ -153,12 +161,13 @@ func (req *request) GIDMap() []idtools.IDMap { // Response encodes a single response. type response struct { - Error string `json:",omitempty"` - Stat statResponse - Eval evalResponse - Get getResponse - Put putResponse - Mkdir mkdirResponse + Error string `json:",omitempty"` + Stat statResponse `json:",omitempty"` + Eval evalResponse `json:",omitempty"` + Get getResponse `json:",omitempty"` + Put putResponse `json:",omitempty"` + Mkdir mkdirResponse `json:",omitempty"` + Remove removeResponse `json:",omitempty"` } // statResponse encodes a response for a single Stat request. @@ -205,6 +214,10 @@ type putResponse struct { type mkdirResponse struct { } +// removeResponse encodes a response for a single Remove request. +type removeResponse struct { +} + // EvalOptions controls parts of Eval()'s behavior. type EvalOptions struct { } @@ -285,6 +298,7 @@ type GetOptions struct { Rename map[string]string // rename items with the specified names, or under the specified names NoDerefSymlinks bool // don't follow symlinks when globs match them IgnoreUnreadable bool // ignore errors reading items, instead of returning an error + NoCrossDevice bool // if a subdirectory is a mountpoint with a different device number, include it but skip its contents } // Get produces an archive containing items that match the specified glob @@ -396,6 +410,36 @@ func Mkdir(root string, directory string, options MkdirOptions) error { return nil } +// RemoveOptions controls parts of Remove()'s behavior. +type RemoveOptions struct { + All bool // if Directory is a directory, remove its contents as well +} + +// Remove removes the specified directory or item, traversing any intermediate +// symbolic links. +// If the root directory is not specified, the current root directory is used. +// If root is specified and the current OS supports it, and the calling process +// has the necessary privileges, the remove() is performed in a chrooted context. +// If the item to remove is specified as an absolute path, it should either be +// in the root directory or in a subdirectory of the root directory. Otherwise, +// the directory is treated as a path relative to the root directory. +func Remove(root string, item string, options RemoveOptions) error { + req := request{ + Request: requestRemove, + Root: root, + Directory: item, + RemoveOptions: options, + } + resp, err := copier(nil, nil, req) + if err != nil { + return err + } + if resp.Error != "" { + return errors.New(resp.Error) + } + return nil +} + // cleanerReldirectory resolves relative path candidate lexically, attempting // to ensure that when joined as a subdirectory of another directory, it does // not reference anything outside of that other directory. @@ -819,6 +863,9 @@ func copierHandler(bulkReader io.Reader, bulkWriter io.Writer, req request) (*re return copierHandlerPut(bulkReader, req, idMappings) case requestMkdir: return copierHandlerMkdir(req, idMappings) + case requestRemove: + resp := copierHandlerRemove(req) + return resp, nil, nil case requestQuit: return nil, nil, nil } @@ -859,7 +906,7 @@ func pathIsExcluded(root, path string, pm *fileutils.PatternMatcher) (string, bo // it is not expected to be. // This helps us approximate chrooted behavior on systems and in test cases // where chroot isn't available. -func resolvePath(root, path string, pm *fileutils.PatternMatcher) (string, error) { +func resolvePath(root, path string, evaluateFinalComponent bool, pm *fileutils.PatternMatcher) (string, error) { rel, err := convertToRelSubdirectory(root, path) if err != nil { return "", errors.Errorf("error making path %q relative to %q", path, root) @@ -876,7 +923,7 @@ func resolvePath(root, path string, pm *fileutils.PatternMatcher) (string, error } excluded = excluded || thisExcluded if !excluded { - if target, err := os.Readlink(filepath.Join(workingPath, components[0])); err == nil { + if target, err := os.Readlink(filepath.Join(workingPath, components[0])); err == nil && !(len(components) == 1 && !evaluateFinalComponent) { followed++ if followed > maxLoopsFollowed { return "", &os.PathError{ @@ -922,7 +969,7 @@ func copierHandlerEval(req request) *response { errorResponse := func(fmtspec string, args ...interface{}) *response { return &response{Error: fmt.Sprintf(fmtspec, args...), Eval: evalResponse{}} } - resolvedTarget, err := resolvePath(req.Root, req.Directory, nil) + resolvedTarget, err := resolvePath(req.Root, req.Directory, true, nil) if err != nil { return errorResponse("copier: eval: error resolving %q: %v", req.Directory, err) } @@ -941,11 +988,13 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response { s := StatsForGlob{ Glob: req.preservedGlobs[i], } - stats = append(stats, &s) // glob this pattern globMatched, err := filepath.Glob(glob) if err != nil { s.Error = fmt.Sprintf("copier: stat: %q while matching glob pattern %q", err.Error(), glob) + } + + if len(globMatched) == 0 && strings.ContainsAny(glob, "*?[") { continue } // collect the matches @@ -1001,7 +1050,7 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response { // could be a relative link) and in the context // of the chroot result.ImmediateTarget = immediateTarget - resolvedTarget, err := resolvePath(req.Root, globbed, pm) + resolvedTarget, err := resolvePath(req.Root, globbed, true, pm) if err != nil { return errorResponse("copier: stat: error resolving %q: %v", globbed, err) } @@ -1032,6 +1081,14 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response { s.Results = nil s.Error = fmt.Sprintf("copier: stat: %q: %v", glob, syscall.ENOENT) } + stats = append(stats, &s) + } + // no matches -> error + if len(stats) == 0 { + s := StatsForGlob{ + Error: fmt.Sprintf("copier: stat: %q: %v", req.Globs, syscall.ENOENT), + } + stats = append(stats, &s) } return &response{Stat: statResponse{Globs: stats}} } @@ -1072,6 +1129,10 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa if len(queue) == 0 { return errorResponse("copier: get: globs %v matched nothing (%d filtered out): %v", req.Globs, globMatchedCount, syscall.ENOENT) } + topInfo, err := os.Stat(req.Directory) + if err != nil { + return errorResponse("copier: get: error reading info about directory %q: %v", req.Directory, err) + } cb := func() error { tw := tar.NewWriter(bulkWriter) defer tw.Close() @@ -1168,14 +1229,22 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa } symlinkTarget = target } + // if it's a directory and we're staying on one device, and it's on a + // different device than the one we started from, skip its contents + var ok error + if info.Mode().IsDir() && req.GetOptions.NoCrossDevice { + if !sameDevice(topInfo, info) { + ok = filepath.SkipDir + } + } // add the item to the outgoing tar stream if err := copierHandlerGetOne(info, symlinkTarget, rel, path, options, tw, hardlinkChecker, idMappings); err != nil { if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) { - return nil + return ok } return err } - return nil + return ok } // walk the directory tree, checking/adding items individually if err := filepath.Walk(item, walkfn); err != nil { @@ -1463,7 +1532,7 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM } return n, nil } - targetDirectory, err := resolvePath(req.Root, req.Directory, nil) + targetDirectory, err := resolvePath(req.Root, req.Directory, true, nil) if err != nil { return errorResponse("copier: put: error resolving %q: %v", req.Directory, err) } @@ -1568,7 +1637,7 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM if req.PutOptions.Rename != nil { hdr.Linkname = handleRename(req.PutOptions.Rename, hdr.Linkname) } - if linkTarget, err = resolvePath(targetDirectory, filepath.Join(req.Root, filepath.FromSlash(hdr.Linkname)), nil); err != nil { + if linkTarget, err = resolvePath(targetDirectory, filepath.Join(req.Root, filepath.FromSlash(hdr.Linkname)), true, nil); err != nil { return errors.Errorf("error resolving hardlink target path %q under root %q", hdr.Linkname, req.Root) } if err = os.Link(linkTarget, path); err != nil && os.IsExist(err) { @@ -1742,7 +1811,7 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response, dirUID, dirGID = hostDirPair.UID, hostDirPair.GID } - directory, err := resolvePath(req.Root, req.Directory, nil) + directory, err := resolvePath(req.Root, req.Directory, true, nil) if err != nil { return errorResponse("copier: mkdir: error resolving %q: %v", req.Directory, err) } @@ -1772,3 +1841,22 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response, return &response{Error: "", Mkdir: mkdirResponse{}}, nil, nil } + +func copierHandlerRemove(req request) *response { + errorResponse := func(fmtspec string, args ...interface{}) *response { + return &response{Error: fmt.Sprintf(fmtspec, args...), Remove: removeResponse{}} + } + resolvedTarget, err := resolvePath(req.Root, req.Directory, false, nil) + if err != nil { + return errorResponse("copier: remove: %v", err) + } + if req.RemoveOptions.All { + err = os.RemoveAll(resolvedTarget) + } else { + err = os.Remove(resolvedTarget) + } + if err != nil { + return errorResponse("copier: remove %q: %v", req.Directory, err) + } + return &response{Error: "", Remove: removeResponse{}} +} diff --git a/vendor/github.com/containers/buildah/copier/syscall_unix.go b/vendor/github.com/containers/buildah/copier/syscall_unix.go index aa40f327c..9fc8fece3 100644 --- a/vendor/github.com/containers/buildah/copier/syscall_unix.go +++ b/vendor/github.com/containers/buildah/copier/syscall_unix.go @@ -4,6 +4,7 @@ package copier import ( "os" + "syscall" "time" "github.com/pkg/errors" @@ -73,6 +74,21 @@ func lutimes(isSymlink bool, path string, atime, mtime time.Time) error { return unix.Lutimes(path, []unix.Timeval{unix.NsecToTimeval(atime.UnixNano()), unix.NsecToTimeval(mtime.UnixNano())}) } +// sameDevice returns true unless we're sure that they're not on the same device +func sameDevice(a, b os.FileInfo) bool { + aSys := a.Sys() + bSys := b.Sys() + if aSys == nil || bSys == nil { + return true + } + au, aok := aSys.(*syscall.Stat_t) + bu, bok := bSys.(*syscall.Stat_t) + if !aok || !bok { + return true + } + return au.Dev == bu.Dev +} + const ( testModeMask = int64(os.ModePerm) testIgnoreSymlinkDates = false diff --git a/vendor/github.com/containers/buildah/copier/syscall_windows.go b/vendor/github.com/containers/buildah/copier/syscall_windows.go index be50d473d..3a88d2d3e 100644 --- a/vendor/github.com/containers/buildah/copier/syscall_windows.go +++ b/vendor/github.com/containers/buildah/copier/syscall_windows.go @@ -77,6 +77,11 @@ func lutimes(isSymlink bool, path string, atime, mtime time.Time) error { return windows.UtimesNano(path, []windows.Timespec{windows.NsecToTimespec(atime.UnixNano()), windows.NsecToTimespec(mtime.UnixNano())}) } +// sameDevice returns true since we can't be sure that they're not on the same device +func sameDevice(a, b os.FileInfo) bool { + return true +} + const ( testModeMask = int64(0600) testIgnoreSymlinkDates = true diff --git a/vendor/github.com/containers/buildah/define/build.go b/vendor/github.com/containers/buildah/define/build.go index 635626a64..dd49c47c1 100644 --- a/vendor/github.com/containers/buildah/define/build.go +++ b/vendor/github.com/containers/buildah/define/build.go @@ -69,6 +69,8 @@ type CommonBuildOptions struct { Ulimit []string // Volumes to bind mount into the container Volumes []string + // Secrets are the available secrets to use in a build + Secrets []string } // BuildOptions can be used to alter how an image is built. diff --git a/vendor/github.com/containers/buildah/define/types.go b/vendor/github.com/containers/buildah/define/types.go index 6d4809cc0..45e85e138 100644 --- a/vendor/github.com/containers/buildah/define/types.go +++ b/vendor/github.com/containers/buildah/define/types.go @@ -28,7 +28,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.20.1-dev" + Version = "1.20.2-dev" // DefaultRuntime if containers.conf fails. DefaultRuntime = "runc" diff --git a/vendor/github.com/containers/buildah/go.mod b/vendor/github.com/containers/buildah/go.mod index 075bdfb01..047c0aeba 100644 --- a/vendor/github.com/containers/buildah/go.mod +++ b/vendor/github.com/containers/buildah/go.mod @@ -4,19 +4,19 @@ go 1.12 require ( github.com/containernetworking/cni v0.8.1 - github.com/containers/common v0.35.4 - github.com/containers/image/v5 v5.10.5 - github.com/containers/ocicrypt v1.1.0 - github.com/containers/storage v1.28.1 + github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce + github.com/containers/image/v5 v5.11.1 + github.com/containers/ocicrypt v1.1.1 + github.com/containers/storage v1.30.1 github.com/docker/distribution v2.7.1+incompatible github.com/docker/go-units v0.4.0 github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316 github.com/fsouza/go-dockerclient v1.7.2 github.com/ghodss/yaml v1.0.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 // indirect + github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee // indirect github.com/mattn/go-shellwords v1.0.11 - github.com/onsi/ginkgo v1.15.2 + github.com/onsi/ginkgo v1.16.1 github.com/onsi/gomega v1.11.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 @@ -24,9 +24,8 @@ require ( github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d github.com/opencontainers/runtime-tools v0.9.0 github.com/opencontainers/selinux v1.8.0 - github.com/openshift/imagebuilder v1.2.0 + github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656 github.com/pkg/errors v0.9.1 - github.com/prometheus/procfs v0.6.0 // indirect github.com/seccomp/libseccomp-golang v0.9.2-0.20200616122406-847368b35ebf github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.1.3 @@ -34,9 +33,9 @@ require ( github.com/stretchr/testify v1.7.0 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 go.etcd.io/bbolt v1.3.5 - golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a - golang.org/x/sys v0.0.0-20210216224549-f992740a1bac + golang.org/x/sys v0.0.0-20210324051608-47abb6519492 k8s.io/klog v1.0.0 // indirect ) diff --git a/vendor/github.com/containers/buildah/go.sum b/vendor/github.com/containers/buildah/go.sum index 6a48853ac..232a8aac1 100644 --- a/vendor/github.com/containers/buildah/go.sum +++ b/vendor/github.com/containers/buildah/go.sum @@ -53,9 +53,11 @@ github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3h github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15 h1:Aof83YILRs2Vx3GhHqlvvfyx1asRJKMFIMeVlHsZKtI= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16 h1:8/auA4LFIZFTGrqfKhGBSXwM6/4X1fHa/xniyEHu8ac= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -97,7 +99,6 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -106,24 +107,26 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102 h1:Qf4HiqfvmB7zS6scsmNgTLmByHbq8n9RTF39v+TzP7A= github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68 h1:hkGVFjz+plgr5UfxZUTPFbUFIF/Km6/s+RVRIRHLrrY= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -131,9 +134,12 @@ github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1 h1:IK6yirB4X7wpKyFSikWiT++nZsyIxGAAgNEv3fEGuls= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4 h1:zjz4MOAOFgdBlwid2nNUlJ3YLpVi/97L36lfMYJex60= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -145,12 +151,17 @@ github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= @@ -160,26 +171,26 @@ github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kw github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1 h1:7zpDnQ3T3s4ucOuJ/ZCLrYBxzkg0AELFfII3Epo9TmI= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containers/common v0.35.4 h1:szyWRncsHkBwCVpu1dkEOXUjkwCetlfcLmKJTwo1Sp8= -github.com/containers/common v0.35.4/go.mod h1:rMzxgD7nMGw++cEbsp+NZv0UJO4rgXbm7F7IbJPTwIE= -github.com/containers/image/v5 v5.10.5 h1:VK1UbsZMzjdw5Xqr3Im9h4iOqHWU0naFs+I78kavc7I= -github.com/containers/image/v5 v5.10.5/go.mod h1:SgIbWEedCNBbn2FI5cH0/jed1Ecy2s8XK5zTxvJTzII= +github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce h1:e7VNmGqwfUQkw+D5bms262x1HYqxfN9/+t5SoaFnwTk= +github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce/go.mod h1:JjU+yvzIGyx8ZsY8nyf7snzs4VSNh1eIaYsqoSKBoRw= +github.com/containers/image/v5 v5.11.1 h1:mNybUvU6zXUwcMsQaa3n+Idsru5pV+GE7k4oRuPzYi0= +github.com/containers/image/v5 v5.11.1/go.mod h1:HC9lhJ/Nz5v3w/5Co7H431kLlgzlVlOC+auD/er3OqE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQj8jcy0EVG6g= -github.com/containers/ocicrypt v1.1.0 h1:A6UzSUFMla92uxO43O6lm86i7evMGjTY7wTKB2DyGPY= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/storage v1.24.8/go.mod h1:YC+2pY8SkfEAcZkwycxYbpK8EiRbx5soPPwz9dxe4IQ= -github.com/containers/storage v1.28.0 h1:lA/9i9BIjfmIRxCI8GuzasYHmU4IUXVcfZZiDceD0Eg= -github.com/containers/storage v1.28.0/go.mod h1:ixAwO7Bj31cigqPEG7aCz+PYmxkDxbIFdUFioYdxbzI= -github.com/containers/storage v1.28.1 h1:axYBD+c0N0YkHelDoqzdLQXfY3fgb8pqIMsRHqUNGts= -github.com/containers/storage v1.28.1/go.mod h1:5bwiMh2LkrN3AWIfDFMH7A/xbVNLcve+oeXYvHvW8cc= +github.com/containers/ocicrypt v1.1.1 h1:prL8l9w3ntVqXvNH1CiNn5ENjcCnr38JqpSyvKKB4GI= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/storage v1.29.0/go.mod h1:u84RU4CCufGeJBNTRNwMB+FoE+AiFeFw4SsMoqAOeCM= +github.com/containers/storage v1.30.1 h1:+87sZDoUp0uNsP45dWypHTWTEoy0eNDgFYjTU1XIRVQ= +github.com/containers/storage v1.30.1/go.mod h1:NDJkiwxnSHD1Is+4DGcyR3SIEYSDOa0xnAW+uGQFx9E= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= @@ -212,13 +223,14 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWhkNRq8= +github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v17.12.0-ce-rc1.0.20201020191947-73dc6a680cdd+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible h1:Yu2uGErhwEoOT/OxAFe+/SiJCqRLs+pgcS5XKrDXnG4= github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= @@ -279,6 +291,7 @@ github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8 github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= @@ -321,6 +334,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -333,6 +348,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM= +github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -373,7 +390,6 @@ github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjh github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -398,9 +414,11 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 h1:rw3IAne6CDuVFlZbPOkA7bhxlqawFh7RJJ+CejfMaxE= -github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee h1:PAXLXk1heNZ5yokbMBpVLZQxo43wCZxRwl00mX+dd44= +github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jinzhu/copier v0.3.0 h1:P5zN9OYSxmtzZmwgcVmt5Iu8egfP53BGMPAFgEksKPI= +github.com/jinzhu/copier v0.3.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -418,15 +436,15 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.12 h1:famVnQVu7QwryBN4jNseQdUKES71ZAOnB6UQQJPZvqk= -github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= +github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= @@ -437,6 +455,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -450,10 +469,9 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw= github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -462,7 +480,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible h1:aKW/4cBs+yK6gpqU3K/oIwk9Q/XICqd3zOX/UFuvqmk= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -476,7 +493,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM= github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= -github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= @@ -490,7 +506,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mtrmac/gpgme v0.1.2 h1:dNOmvYmsrakgW7LcgiprD0yfRuQQe8/C8F6Z+zogO3s= github.com/mtrmac/gpgme v0.1.2/go.mod h1:GYYHnGSuS7HK3zVS2n3y73y0okK/BeKzwnn5jgiVFNI= @@ -511,8 +526,8 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.15.2 h1:l77YT15o814C2qVL47NOyjV/6RbaP7kKdrvZnxQ3Org= -github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= +github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= +github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -534,25 +549,22 @@ github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5X github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc91/go.mod h1:3Sm6Dt7OT8z88EbdQqqcRN2oCT54jbi72tT/HqgflT8= github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d h1:pNa8metDkwZjb9g4T8s+krQ+HRgZAkqnXml+wNir/+s= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK5zsQavY8NPMkU= github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/openshift/imagebuilder v1.2.0 h1:uoZFjJICLlTMjlAL/UG2PA2kM8RjAsVflGfHJK7MMDk= -github.com/openshift/imagebuilder v1.2.0/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= +github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656 h1:WaxyNFpmIDu4i6so9r6LVFIbSaXqsj8oitMitt86ae4= +github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -599,11 +611,13 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -647,6 +661,7 @@ github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRci github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -660,8 +675,6 @@ github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmD github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I= -github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -670,8 +683,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE= github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= -github.com/vbauerster/mpb/v5 v5.4.0 h1:n8JPunifvQvh6P1D1HAl2Ur9YcmKT1tpoUuiea5mlmg= -github.com/vbauerster/mpb/v5 v5.4.0/go.mod h1:fi4wVo7BVQ22QcvFObm+VwliQXlV1eBT8JDaKXR4JGI= +github.com/vbauerster/mpb/v6 v6.0.3 h1:j+twHHhSUe8aXWaT/27E98G5cSBeqEuJSVCMjmLg0PI= +github.com/vbauerster/mpb/v6 v6.0.3/go.mod h1:5luBx4rDLWxpA4t6I5sdeeQuZhqDxc+wr5Nqf35+tnM= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= @@ -720,12 +733,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -791,8 +803,9 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -804,9 +817,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -855,8 +866,8 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -868,13 +879,12 @@ golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210216224549-f992740a1bac h1:9glrpwtNjBYgRpb67AZJKHfzj1stG/8BL5H7In2oTC4= golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/vendor/github.com/containers/buildah/image.go b/vendor/github.com/containers/buildah/image.go index 51d18232a..92e0c3e8e 100644 --- a/vendor/github.com/containers/buildah/image.go +++ b/vendor/github.com/containers/buildah/image.go @@ -295,7 +295,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System if src == nil { err2 := os.RemoveAll(path) if err2 != nil { - logrus.Errorf("error removing layer blob directory %q: %v", path, err) + logrus.Errorf("error removing layer blob directory: %v", err) } } }() diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index 062752274..62e656271 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -165,11 +165,22 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B } func warnOnUnsetBuildArgs(node *parser.Node, args map[string]string) { + argFound := make(map[string]bool) for _, child := range node.Children { switch strings.ToUpper(child.Value) { case "ARG": argName := child.Next.Value - if _, ok := args[argName]; !strings.Contains(argName, "=") && !ok { + if strings.Contains(argName, "=") { + res := strings.Split(argName, "=") + if res[1] != "" { + argFound[res[0]] = true + } + } + argHasValue := true + if !strings.Contains(argName, "=") { + argHasValue = argFound[argName] + } + if _, ok := args[argName]; !argHasValue && !ok { logrus.Warnf("missing %q build argument. Try adding %q to the command line", argName, fmt.Sprintf("--build-arg %s=<VALUE>", argName)) } default: diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_linux.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_linux.go deleted file mode 100644 index 4dd49130d..000000000 --- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_linux.go +++ /dev/null @@ -1,151 +0,0 @@ -package imagebuildah - -import ( - "flag" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/containers/storage/pkg/reexec" - "github.com/pkg/errors" - "golang.org/x/sys/unix" -) - -const ( - symlinkChrootedCommand = "chrootsymlinks-resolve" - maxSymlinksResolved = 40 -) - -func init() { - reexec.Register(symlinkChrootedCommand, resolveChrootedSymlinks) -} - -// resolveSymlink uses a child subprocess to resolve any symlinks in filename -// in the context of rootdir. -func resolveSymlink(rootdir, filename string) (string, error) { - // The child process expects a chroot and one path that - // will be consulted relative to the chroot directory and evaluated - // for any symbolic links present. - cmd := reexec.Command(symlinkChrootedCommand, rootdir, filename) - output, err := cmd.CombinedOutput() - if err != nil { - return "", errors.Wrapf(err, string(output)) - } - - // Hand back the resolved symlink, will be filename if a symlink is not found - return string(output), nil -} - -// main() for resolveSymlink()'s subprocess. -func resolveChrootedSymlinks() { - status := 0 - flag.Parse() - if len(flag.Args()) < 2 { - fmt.Fprintf(os.Stderr, "%s needs two arguments\n", symlinkChrootedCommand) - os.Exit(1) - } - // Our first parameter is the directory to chroot into. - if err := unix.Chdir(flag.Arg(0)); err != nil { - fmt.Fprintf(os.Stderr, "chdir(): %v\n", err) - os.Exit(1) - } - if err := unix.Chroot(flag.Arg(0)); err != nil { - fmt.Fprintf(os.Stderr, "chroot(): %v\n", err) - os.Exit(1) - } - - // Our second parameter is the path name to evaluate for symbolic links - symLink, err := getSymbolicLink(flag.Arg(1)) - if err != nil { - fmt.Fprintf(os.Stderr, "error getting symbolic links: %v\n", err) - os.Exit(1) - } - if _, err := os.Stdout.WriteString(symLink); err != nil { - fmt.Fprintf(os.Stderr, "error writing string to stdout: %v\n", err) - os.Exit(1) - } - os.Exit(status) -} - -// getSymbolic link goes through each part of the path and continues resolving symlinks as they appear. -// Returns what the whole target path for what "path" resolves to. -func getSymbolicLink(path string) (string, error) { - var ( - symPath string - symLinksResolved int - ) - // Splitting path as we need to resolve each part of the path at a time - splitPath := strings.Split(path, "/") - if splitPath[0] == "" { - splitPath = splitPath[1:] - symPath = "/" - } - for _, p := range splitPath { - // If we have resolved 40 symlinks, that means something is terribly wrong - // will return an error and exit - if symLinksResolved >= maxSymlinksResolved { - return "", errors.Errorf("have resolved %q symlinks, something is terribly wrong!", maxSymlinksResolved) - } - symPath = filepath.Join(symPath, p) - isSymlink, resolvedPath, err := hasSymlink(symPath) - if err != nil { - return "", err - } - // if isSymlink is true, check if resolvedPath is potentially another symlink - // keep doing this till resolvedPath is not a symlink and isSymlink is false - for isSymlink { - // Need to keep track of number of symlinks resolved - // Will also return an error if the symlink points to itself as that will exceed maxSymlinksResolved - if symLinksResolved >= maxSymlinksResolved { - return "", errors.Errorf("have resolved %q symlinks, something is terribly wrong!", maxSymlinksResolved) - } - isSymlink, resolvedPath, err = hasSymlink(resolvedPath) - if err != nil { - return "", err - } - symLinksResolved++ - } - // Assign resolvedPath to symPath. The next part of the loop will append the next part of the original path - // and continue resolving - symPath = resolvedPath - symLinksResolved++ - } - return symPath, nil -} - -// hasSymlink returns true and the target if path is symlink -// otherwise it returns false and path -func hasSymlink(path string) (bool, string, error) { - info, err := os.Lstat(path) - if err != nil { - if os.IsNotExist(err) { - if err = os.MkdirAll(path, 0755); err != nil { - return false, "", err - } - info, err = os.Lstat(path) - if err != nil { - return false, "", err - } - } else { - return false, path, err - } - } - - // Return false and path as path if not a symlink - if info.Mode()&os.ModeSymlink != os.ModeSymlink { - return false, path, nil - } - - // Read the symlink to get what it points to - targetDir, err := os.Readlink(path) - if err != nil { - return false, "", err - } - // if the symlink points to a relative path, prepend the path till now to the resolved path - if !filepath.IsAbs(targetDir) { - targetDir = filepath.Join(filepath.Dir(path), targetDir) - } - // run filepath.Clean to remove the ".." from relative paths - return true, filepath.Clean(targetDir), nil -} diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_unsupported.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_unsupported.go deleted file mode 100644 index 2cec4fe21..000000000 --- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink_unsupported.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build !linux - -package imagebuildah - -import "github.com/pkg/errors" - -func resolveSymlink(rootdir, filename string) (string, error) { - return "", errors.New("function not supported on non-linux systems") -} - -func resolveModifiedTime(rootdir, filename, historyTime string) (bool, error) { - return false, errors.New("function not supported on non-linux systems") -} diff --git a/vendor/github.com/containers/buildah/imagebuildah/executor.go b/vendor/github.com/containers/buildah/imagebuildah/executor.go index b7b339961..fc4753e35 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/executor.go @@ -16,10 +16,12 @@ import ( "github.com/containers/buildah/define" "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" is "github.com/containers/image/v5/storage" + storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" @@ -117,6 +119,7 @@ type Executor struct { imageInfoCache map[string]imageTypeAndHistoryAndDiffIDs fromOverride string manifest string + secrets map[string]string } type imageTypeAndHistoryAndDiffIDs struct { @@ -164,6 +167,11 @@ func NewExecutor(store storage.Store, options define.BuildOptions, mainNode *par transientMounts = append([]Mount{Mount(mount)}, transientMounts...) } + secrets, err := parse.Secrets(options.CommonBuildOpts.Secrets) + if err != nil { + return nil, err + } + jobs := 1 if options.Jobs != nil { jobs = *options.Jobs @@ -234,6 +242,7 @@ func NewExecutor(store storage.Store, options define.BuildOptions, mainNode *par imageInfoCache: make(map[string]imageTypeAndHistoryAndDiffIDs), fromOverride: options.From, manifest: options.Manifest, + secrets: secrets, } if exec.err == nil { exec.err = os.Stderr @@ -301,22 +310,23 @@ func (b *Executor) startStage(ctx context.Context, stage *imagebuilder.Stage, st // resolveNameToImageRef creates a types.ImageReference for the output name in local storage func (b *Executor) resolveNameToImageRef(output string) (types.ImageReference, error) { - imageRef, err := alltransports.ParseImageName(output) + if imageRef, err := alltransports.ParseImageName(output); err == nil { + return imageRef, nil + } + runtime, err := libimage.RuntimeFromStore(b.store, &libimage.RuntimeOptions{SystemContext: b.systemContext}) if err != nil { - candidates, _, _, err := util.ResolveName(output, "", b.systemContext, b.store) - if err != nil { - return nil, errors.Wrapf(err, "error parsing target image name %q", output) - } - if len(candidates) == 0 { - return nil, errors.Errorf("error parsing target image name %q", output) - } - imageRef2, err2 := is.Transport.ParseStoreReference(b.store, candidates[0]) - if err2 != nil { - return nil, errors.Wrapf(err, "error parsing target image name %q", output) - } - return imageRef2, nil + return nil, err } - return imageRef, nil + resolved, err := runtime.ResolveName(output) + if err != nil { + return nil, err + } + imageRef, err := storageTransport.Transport.ParseStoreReference(b.store, resolved) + if err == nil { + return imageRef, nil + } + + return imageRef, err } // waitForStage waits for an entry to be added to terminatedStage indicating @@ -661,19 +671,31 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image fmt.Fprintf(b.out, "[Warning] one or more build args were not consumed: %v\n", unusedList) } - if len(b.additionalTags) > 0 { - if dest, err := b.resolveNameToImageRef(b.output); err == nil { - switch dest.Transport().Name() { - case is.Transport.Name(): - img, err := is.Transport.GetStoreImage(b.store, dest) - if err != nil { - return imageID, ref, errors.Wrapf(err, "error locating just-written image %q", transports.ImageName(dest)) - } + // Add additional tags and print image names recorded in storage + if dest, err := b.resolveNameToImageRef(b.output); err == nil { + switch dest.Transport().Name() { + case is.Transport.Name(): + img, err := is.Transport.GetStoreImage(b.store, dest) + if err != nil { + return imageID, ref, errors.Wrapf(err, "error locating just-written image %q", transports.ImageName(dest)) + } + if len(b.additionalTags) > 0 { if err = util.AddImageNames(b.store, "", b.systemContext, img, b.additionalTags); err != nil { return imageID, ref, errors.Wrapf(err, "error setting image names to %v", append(img.Names, b.additionalTags...)) } logrus.Debugf("assigned names %v to image %q", img.Names, img.ID) - default: + } + // Report back the caller the tags applied, if any. + img, err = is.Transport.GetStoreImage(b.store, dest) + if err != nil { + return imageID, ref, errors.Wrapf(err, "error locating just-written image %q", transports.ImageName(dest)) + } + for _, name := range img.Names { + fmt.Fprintf(b.out, "Successfully tagged %s\n", name) + } + + default: + if len(b.additionalTags) > 0 { logrus.Warnf("don't know how to add tags to images stored in %q transport", dest.Transport().Name()) } } diff --git a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go index ff9abdda8..f1bee9366 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go @@ -24,7 +24,7 @@ import ( "github.com/containers/image/v5/transports" "github.com/containers/image/v5/types" "github.com/containers/storage" - "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/chrootarchive" docker "github.com/fsouza/go-dockerclient" digest "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -81,12 +81,12 @@ func (s *StageExecutor) Preserve(path string) error { // This path is already a subdirectory of a volume path that // we're already preserving, so there's nothing new to be done // except ensure that it exists. - archivedPath := filepath.Join(s.mountPoint, path) - if err := os.MkdirAll(archivedPath, 0755); err != nil { + createdDirPerms := os.FileMode(0755) + if err := copier.Mkdir(s.mountPoint, filepath.Join(s.mountPoint, path), copier.MkdirOptions{ChmodNew: &createdDirPerms}); err != nil { return errors.Wrapf(err, "error ensuring volume path exists") } if err := s.volumeCacheInvalidate(path); err != nil { - return errors.Wrapf(err, "error ensuring volume path %q is preserved", archivedPath) + return errors.Wrapf(err, "error ensuring volume path %q is preserved", filepath.Join(s.mountPoint, path)) } return nil } @@ -102,16 +102,24 @@ func (s *StageExecutor) Preserve(path string) error { // Try and resolve the symlink (if one exists) // Set archivedPath and path based on whether a symlink is found or not - if symLink, err := resolveSymlink(s.mountPoint, path); err == nil { - archivedPath = filepath.Join(s.mountPoint, symLink) - path = symLink + if evaluated, err := copier.Eval(s.mountPoint, filepath.Join(s.mountPoint, path), copier.EvalOptions{}); err == nil { + symLink, err := filepath.Rel(s.mountPoint, evaluated) + if err != nil { + return errors.Wrapf(err, "making evaluated path %q relative to %q", evaluated, s.mountPoint) + } + if strings.HasPrefix(symLink, ".."+string(os.PathSeparator)) { + return errors.Errorf("evaluated path %q was not below %q", evaluated, s.mountPoint) + } + archivedPath = evaluated + path = string(os.PathSeparator) + symLink } else { - return errors.Wrapf(err, "error reading symbolic link to %q", path) + return errors.Wrapf(err, "error evaluating path %q", path) } st, err := os.Stat(archivedPath) if os.IsNotExist(err) { - if err = os.MkdirAll(archivedPath, 0755); err != nil { + createdDirPerms := os.FileMode(0755) + if err = copier.Mkdir(s.mountPoint, archivedPath, copier.MkdirOptions{ChmodNew: &createdDirPerms}); err != nil { return errors.Wrapf(err, "error ensuring volume path exists") } st, err = os.Stat(archivedPath) @@ -178,64 +186,85 @@ func (s *StageExecutor) volumeCacheInvalidate(path string) error { return err } archivedPath := filepath.Join(s.mountPoint, cachedPath) - logrus.Debugf("invalidated volume cache for %q from %q", archivedPath, s.volumeCache[cachedPath]) - delete(s.volumeCache, cachedPath) + logrus.Debugf("invalidated volume cache %q for %q from %q", archivedPath, path, s.volumeCache[cachedPath]) } return nil } // Save the contents of each of the executor's list of volumes for which we // don't already have a cache file. -func (s *StageExecutor) volumeCacheSaveVFS() error { +func (s *StageExecutor) volumeCacheSaveVFS() (mounts []specs.Mount, err error) { for cachedPath, cacheFile := range s.volumeCache { - archivedPath := filepath.Join(s.mountPoint, cachedPath) - _, err := os.Stat(cacheFile) + archivedPath, err := copier.Eval(s.mountPoint, filepath.Join(s.mountPoint, cachedPath), copier.EvalOptions{}) + if err != nil { + return nil, errors.Wrapf(err, "error evaluating volume path") + } + relativePath, err := filepath.Rel(s.mountPoint, archivedPath) + if err != nil { + return nil, errors.Wrapf(err, "error converting %q into a path relative to %q", archivedPath, s.mountPoint) + } + if strings.HasPrefix(relativePath, ".."+string(os.PathSeparator)) { + return nil, errors.Errorf("error converting %q into a path relative to %q", archivedPath, s.mountPoint) + } + _, err = os.Stat(cacheFile) if err == nil { logrus.Debugf("contents of volume %q are already cached in %q", archivedPath, cacheFile) continue } if !os.IsNotExist(err) { - return err + return nil, err } - if err := os.MkdirAll(archivedPath, 0755); err != nil { - return errors.Wrapf(err, "error ensuring volume path exists") + createdDirPerms := os.FileMode(0755) + if err := copier.Mkdir(s.mountPoint, archivedPath, copier.MkdirOptions{ChmodNew: &createdDirPerms}); err != nil { + return nil, errors.Wrapf(err, "error ensuring volume path exists") } logrus.Debugf("caching contents of volume %q in %q", archivedPath, cacheFile) cache, err := os.Create(cacheFile) if err != nil { - return err + return nil, err } defer cache.Close() - rc, err := archive.Tar(archivedPath, archive.Uncompressed) + rc, err := chrootarchive.Tar(archivedPath, nil, s.mountPoint) if err != nil { - return errors.Wrapf(err, "error archiving %q", archivedPath) + return nil, errors.Wrapf(err, "error archiving %q", archivedPath) } defer rc.Close() _, err = io.Copy(cache, rc) if err != nil { - return errors.Wrapf(err, "error archiving %q to %q", archivedPath, cacheFile) + return nil, errors.Wrapf(err, "error archiving %q to %q", archivedPath, cacheFile) + } + mount := specs.Mount{ + Source: archivedPath, + Destination: string(os.PathSeparator) + relativePath, + Type: "bind", + Options: []string{"private"}, } + mounts = append(mounts, mount) } - return nil + return nil, nil } // Restore the contents of each of the executor's list of volumes. func (s *StageExecutor) volumeCacheRestoreVFS() (err error) { for cachedPath, cacheFile := range s.volumeCache { - archivedPath := filepath.Join(s.mountPoint, cachedPath) + archivedPath, err := copier.Eval(s.mountPoint, filepath.Join(s.mountPoint, cachedPath), copier.EvalOptions{}) + if err != nil { + return errors.Wrapf(err, "error evaluating volume path") + } logrus.Debugf("restoring contents of volume %q from %q", archivedPath, cacheFile) cache, err := os.Open(cacheFile) if err != nil { return err } defer cache.Close() - if err := os.RemoveAll(archivedPath); err != nil { + if err := copier.Remove(s.mountPoint, archivedPath, copier.RemoveOptions{All: true}); err != nil { return err } - if err := os.MkdirAll(archivedPath, 0755); err != nil { + createdDirPerms := os.FileMode(0755) + if err := copier.Mkdir(s.mountPoint, archivedPath, copier.MkdirOptions{ChmodNew: &createdDirPerms}); err != nil { return err } - err = archive.Untar(cache, archivedPath, nil) + err = chrootarchive.Untar(cache, archivedPath, nil) if err != nil { return errors.Wrapf(err, "error extracting archive at %q", archivedPath) } @@ -264,6 +293,10 @@ func (s *StageExecutor) volumeCacheRestoreVFS() (err error) { // don't already have a cache file. func (s *StageExecutor) volumeCacheSaveOverlay() (mounts []specs.Mount, err error) { for cachedPath := range s.volumeCache { + err = copier.Mkdir(s.mountPoint, filepath.Join(s.mountPoint, cachedPath), copier.MkdirOptions{}) + if err != nil { + return nil, errors.Wrapf(err, "ensuring volume exists") + } volumePath := filepath.Join(s.mountPoint, cachedPath) mount := specs.Mount{ Source: volumePath, @@ -287,7 +320,7 @@ func (s *StageExecutor) volumeCacheSave() (mounts []specs.Mount, err error) { case "overlay": return s.volumeCacheSaveOverlay() } - return nil, s.volumeCacheSaveVFS() + return s.volumeCacheSaveVFS() } // Reset the contents of each of the executor's list of volumes. @@ -372,7 +405,7 @@ func (s *StageExecutor) Copy(excludes []string, copies ...imagebuilder.Copy) err StripSetgidBit: stripSetgid, } if err := s.builder.Add(copy.Dest, copy.Download, options, sources...); err != nil { - return errors.Wrapf(err, "error adding sources %v", sources) + return err } } return nil @@ -411,6 +444,8 @@ func (s *StageExecutor) Run(run imagebuilder.Run, config docker.Config) error { Quiet: s.executor.quiet, NamespaceOptions: s.executor.namespaceOptions, Terminal: buildah.WithoutTerminal, + Secrets: s.executor.secrets, + RunMounts: run.Mounts, } if config.NetworkDisabled { options.ConfigureNetwork = buildah.NetworkDisabled diff --git a/vendor/github.com/containers/buildah/install.md b/vendor/github.com/containers/buildah/install.md index 90e844c3e..4dc362911 100644 --- a/vendor/github.com/containers/buildah/install.md +++ b/vendor/github.com/containers/buildah/install.md @@ -4,19 +4,6 @@ ## Installing packaged versions of buildah -#### [Amazon Linux 2](https://aws.amazon.com/amazon-linux-2/) - -The [Kubic project](https://build.opensuse.org/project/show/devel:kubic:libcontainers:stable) -provides updated packages for CentOS 7 which can be used unmodified on Amazon Linux 2. - -```bash -cd /etc/yum.repos.d/ -sudo wget https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/CentOS_7/devel:kubic:libcontainers:stable.repo -sudo yum -y install yum-plugin-copr -sudo yum -y copr enable lsm5/container-selinux -sudo yum -y install buildah -``` - ### [Arch Linux](https://www.archlinux.org) ```bash @@ -34,26 +21,28 @@ sudo yum -y install buildah ``` The [Kubic project](https://build.opensuse.org/project/show/devel:kubic:libcontainers:stable) -provides updated packages for CentOS 7, 8 and Stream. +provides updated packages for CentOS 8 and CentOS 8 Stream. ```bash -# CentOS 7 -sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/CentOS_7/devel:kubic:libcontainers:stable.repo -sudo yum -y install buildah - # CentOS 8 sudo dnf -y module disable container-tools sudo dnf -y install 'dnf-command(copr)' sudo dnf -y copr enable rhcontainerbot/container-selinux sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/CentOS_8/devel:kubic:libcontainers:stable.repo -sudo dnf -y install buildah +# OPTIONAL FOR RUNC USERS: crun will be installed by default. Install runc first if you prefer runc +sudo dnf -y --refresh install runc +# Install Buildah +sudo dnf -y --refresh install buildah -# CentOS Stream +# CentOS 8 Stream sudo dnf -y module disable container-tools sudo dnf -y install 'dnf-command(copr)' sudo dnf -y copr enable rhcontainerbot/container-selinux sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/CentOS_8_Stream/devel:kubic:libcontainers:stable.repo -sudo dnf -y install buildah +# OPTIONAL FOR RUNC USERS: crun will be installed by default. Install runc first if you prefer runc +sudo dnf -y --refresh install runc +# Install Buildah +sudo dnf -y --refresh install buildah ``` @@ -69,36 +58,6 @@ sudo apt-get update sudo apt-get -y install buildah ``` -If you would prefer newer (though not as well-tested) packages, -the [Kubic project](https://build.opensuse.org/package/show/devel:kubic:libcontainers:stable/buildah) -provides packages for Debian 10 and newer. The packages in Kubic project repos are more frequently -updated than the one in Debian's official repositories, due to how Debian works. -The build sources for the Kubic packages can be found [here](https://gitlab.com/rhcontainerbot/buildah/-/tree/debian/debian). - -CAUTION: On Debian 11 and newer, including Testing and Sid/Unstable, we highly recommend you use Buildah, Podman and Skopeo ONLY from EITHER the Kubic repo -OR the official Debian repos. Mixing and matching may lead to unpredictable situations including installation conflicts. - -```bash -# Debian 10 -echo 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_10/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list -curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_10/Release.key | sudo apt-key add - -sudo apt-get update -sudo apt-get -y install buildah - -# Debian Testing -echo 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_Testing/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list -curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_Testing/Release.key | sudo apt-key add - -sudo apt-get update -sudo apt-get -y install buildah - -# Debian Sid/Unstable -echo 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_Unstable/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list -curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_Unstable/Release.key | sudo apt-key add - -sudo apt-get update -sudo apt-get -y install buildah -``` - - ### [Fedora](https://www.fedoraproject.org) @@ -143,21 +102,6 @@ sudo subscription-manager repos --enable=rhel-7-server-extras-rpms sudo yum -y install buildah ``` -#### [Raspberry Pi OS armhf (ex Raspbian)](https://www.raspberrypi.org/downloads/raspberry-pi-os/) - -The [Kubic project](https://build.opensuse.org/package/show/devel:kubic:libcontainers:stable/buildah) provides -packages for Raspbian 10. - -```bash -# Raspbian 10 -echo 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Raspbian_10/ /' | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list -curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Raspbian_10/Release.key | sudo apt-key add - -sudo apt-get update -qq -sudo apt-get -qq -y install buildah -``` - -The build sources for the Kubic packages can be found [here](https://gitlab.com/rhcontainerbot/buildah/-/tree/debian/debian). - #### [Raspberry Pi OS arm64 (beta)](https://downloads.raspberrypi.org/raspios_arm64/images/) Raspberry Pi OS use the standard Debian's repositories, @@ -185,7 +129,7 @@ sudo apt-get -y install buildah If you would prefer newer (though not as well-tested) packages, the [Kubic project](https://build.opensuse.org/package/show/devel:kubic:libcontainers:stable/buildah) -provides packages for active Ubuntu releases 18.04 and newer (it should also work with direct derivatives like Pop!\_OS). +provides packages for active Ubuntu releases 20.04 and newer (it should also work with direct derivatives like Pop!\_OS). The packages in Kubic project repos are more frequently updated than the one in Ubuntu's official repositories, due to how Debian/Ubuntu works. Checkout the Kubic project page for a list of supported Ubuntu version and architecture combinations. The build sources for the Kubic packages can be found [here](https://gitlab.com/rhcontainerbot/buildah/-/tree/debian/debian). diff --git a/vendor/github.com/containers/buildah/new.go b/vendor/github.com/containers/buildah/new.go index f29af1f5d..0293e4abd 100644 --- a/vendor/github.com/containers/buildah/new.go +++ b/vendor/github.com/containers/buildah/new.go @@ -4,18 +4,15 @@ import ( "context" "fmt" "math/rand" - "runtime" "strings" "github.com/containers/buildah/define" - "github.com/containers/buildah/util" - "github.com/containers/image/v5/docker" + "github.com/containers/buildah/pkg/blobcache" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/pkg/shortnames" - is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" - "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/storage" digest "github.com/opencontainers/go-digest" @@ -30,29 +27,6 @@ const ( BaseImageFakeName = imagebuilder.NoBaseImageSpecifier ) -func pullAndFindImage(ctx context.Context, store storage.Store, srcRef types.ImageReference, options BuilderOptions, sc *types.SystemContext) (*storage.Image, types.ImageReference, error) { - pullOptions := PullOptions{ - ReportWriter: options.ReportWriter, - Store: store, - SystemContext: options.SystemContext, - BlobDirectory: options.BlobDirectory, - MaxRetries: options.MaxPullRetries, - RetryDelay: options.PullRetryDelay, - OciDecryptConfig: options.OciDecryptConfig, - } - ref, err := pullImage(ctx, store, srcRef, pullOptions, sc) - if err != nil { - logrus.Debugf("error pulling image %q: %v", transports.ImageName(srcRef), err) - return nil, nil, err - } - img, err := is.Transport.GetStoreImage(store, ref) - if err != nil { - logrus.Debugf("error reading pulled image %q: %v", transports.ImageName(srcRef), err) - return nil, nil, errors.Wrapf(err, "error locating image %q in local storage", transports.ImageName(ref)) - } - return img, ref, nil -} - func getImageName(name string, img *storage.Image) string { imageName := name if len(img.Names) > 0 { @@ -105,187 +79,6 @@ func newContainerIDMappingOptions(idmapOptions *define.IDMappingOptions) storage return options } -func resolveLocalImage(systemContext *types.SystemContext, store storage.Store, options BuilderOptions) (types.ImageReference, string, string, *storage.Image, error) { - candidates, _, _, err := util.ResolveName(options.FromImage, options.Registry, systemContext, store) - if err != nil { - return nil, "", "", nil, errors.Wrapf(err, "error resolving local image %q", options.FromImage) - } - for _, imageName := range candidates { - img, err := store.Image(imageName) - if err != nil { - if errors.Cause(err) == storage.ErrImageUnknown { - continue - } - return nil, "", "", nil, err - } - ref, err := is.Transport.ParseStoreReference(store, img.ID) - if err != nil { - return nil, "", "", nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID) - } - return ref, ref.Transport().Name(), imageName, img, nil - } - - return nil, "", "", nil, nil -} - -func imageMatch(ctx context.Context, ref types.ImageReference, systemContext *types.SystemContext) bool { - img, err := ref.NewImage(ctx, systemContext) - if err != nil { - logrus.Warnf("Failed to create newImage in imageMatch: %v", err) - return false - } - defer img.Close() - data, err := img.Inspect(ctx) - if err != nil { - logrus.Warnf("Failed to inspect img %s: %v", ref, err) - return false - } - os := systemContext.OSChoice - if os == "" { - os = runtime.GOOS - } - arch := systemContext.ArchitectureChoice - if arch == "" { - arch = runtime.GOARCH - } - if os == data.Os && arch == data.Architecture { - if systemContext.VariantChoice == "" || systemContext.VariantChoice == data.Variant { - return true - } - } - return false -} - -func resolveImage(ctx context.Context, systemContext *types.SystemContext, store storage.Store, options BuilderOptions) (types.ImageReference, string, *storage.Image, error) { - if systemContext == nil { - systemContext = &types.SystemContext{} - } - - fromImage := options.FromImage - // If the image name includes a transport we can use it as it. Special - // treatment for docker references which are subject to pull policies - // that we're handling below. - srcRef, err := alltransports.ParseImageName(options.FromImage) - if err == nil { - if srcRef.Transport().Name() == docker.Transport.Name() { - fromImage = srcRef.DockerReference().String() - } else { - pulledImg, pulledReference, err := pullAndFindImage(ctx, store, srcRef, options, systemContext) - return pulledReference, srcRef.Transport().Name(), pulledImg, err - } - } - - localImageRef, _, localImageName, localImage, err := resolveLocalImage(systemContext, store, options) - if err != nil { - return nil, "", nil, err - } - - // If we could resolve the image locally, check if it was clearly - // referring to a local image, either by ID or digest. In that case, - // we don't need to perform a remote lookup. - if localImage != nil && (strings.HasPrefix(localImage.ID, options.FromImage) || strings.HasPrefix(options.FromImage, "sha256:")) { - return localImageRef, localImageRef.Transport().Name(), localImage, nil - } - - if options.PullPolicy == define.PullNever || options.PullPolicy == define.PullIfMissing { - if localImage != nil && imageMatch(ctx, localImageRef, systemContext) { - return localImageRef, localImageRef.Transport().Name(), localImage, nil - } - if options.PullPolicy == define.PullNever { - return nil, "", nil, errors.Errorf("pull policy is %q but %q could not be found locally", "never", options.FromImage) - } - } - - // If we found a local image, we must use it's name. - // See #2904. - if localImageRef != nil { - fromImage = localImageName - } - - resolved, err := shortnames.Resolve(systemContext, fromImage) - if err != nil { - return nil, "", nil, err - } - - // Print the image-resolution description unless we're looking for a - // new image and already found a local image. In many cases, the - // description will be more confusing than helpful (e.g., `buildah from - // localImage`). - if desc := resolved.Description(); len(desc) > 0 { - logrus.Debug(desc) - if !(options.PullPolicy == define.PullIfNewer && localImage != nil) { - if options.ReportWriter != nil { - if _, err := options.ReportWriter.Write([]byte(desc + "\n")); err != nil { - return nil, "", nil, err - } - } - } - } - - var pullErrors []error - for _, pullCandidate := range resolved.PullCandidates { - ref, err := docker.NewReference(pullCandidate.Value) - if err != nil { - return nil, "", nil, err - } - - // We're tasked to pull a "newer" image. If there's no local - // image, we have no base for comparison, so we'll pull the - // first available image. - // - // If there's a local image, the `pullCandidate` is considered - // to be newer if its time stamp differs from the local one. - // Otherwise, we don't pull and skip it. - if options.PullPolicy == define.PullIfNewer && localImage != nil { - remoteImage, err := ref.NewImage(ctx, systemContext) - if err != nil { - logrus.Debugf("unable to remote-inspect image %q: %v", pullCandidate.Value.String(), err) - pullErrors = append(pullErrors, err) - continue - } - defer remoteImage.Close() - - remoteData, err := remoteImage.Inspect(ctx) - if err != nil { - logrus.Debugf("unable to remote-inspect image %q: %v", pullCandidate.Value.String(), err) - pullErrors = append(pullErrors, err) - continue - } - - // FIXME: we should compare image digests not time stamps. - // Comparing time stamps is flawed. Be aware that fixing - // it may entail non-trivial changes to the tests. Please - // refer to https://github.com/containers/buildah/issues/2779 - // for more. - if localImage.Created.Equal(*remoteData.Created) { - continue - } - } - - pulledImg, pulledReference, err := pullAndFindImage(ctx, store, ref, options, systemContext) - if err != nil { - logrus.Debugf("unable to pull and read image %q: %v", pullCandidate.Value.String(), err) - pullErrors = append(pullErrors, err) - continue - } - - // Make sure to record the short-name alias if necessary. - if err = pullCandidate.Record(); err != nil { - return nil, "", nil, err - } - - return pulledReference, "", pulledImg, nil - } - - // If we were looking for a newer image but could not find one, return - // the local image if present. - if options.PullPolicy == define.PullIfNewer && localImage != nil { - return localImageRef, localImageRef.Transport().Name(), localImage, nil - } - - return nil, "", nil, resolved.FormatPullErrors(pullErrors) -} - func containerNameExist(name string, containers []storage.Container) bool { for _, container := range containers { for _, cname := range container.Names { @@ -313,6 +106,7 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions img *storage.Image err error ) + if options.FromImage == BaseImageFakeName { options.FromImage = "" } @@ -320,11 +114,45 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions systemContext := getSystemContext(store, options.SystemContext, options.SignaturePolicyPath) if options.FromImage != "" && options.FromImage != "scratch" { - ref, _, img, err = resolveImage(ctx, systemContext, store, options) + imageRuntime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) + if err != nil { + return nil, err + } + + pullPolicy, err := config.ParsePullPolicy(options.PullPolicy.String()) if err != nil { return nil, err } + + // Note: options.Format does *not* relate to the image we're + // about to pull (see tests/digests.bats). So we're not + // forcing a MIMEType in the pullOptions below. + pullOptions := libimage.PullOptions{} + pullOptions.RetryDelay = &options.PullRetryDelay + pullOptions.OciDecryptConfig = options.OciDecryptConfig + pullOptions.SignaturePolicyPath = options.SignaturePolicyPath + pullOptions.Writer = options.ReportWriter + + maxRetries := uint(options.MaxPullRetries) + pullOptions.MaxRetries = &maxRetries + + if options.BlobDirectory != "" { + pullOptions.DestinationLookupReferenceFunc = blobcache.CacheLookupReferenceFunc(options.BlobDirectory, types.PreserveOriginal) + } + + pulledImages, err := imageRuntime.Pull(ctx, options.FromImage, pullPolicy, &pullOptions) + if err != nil { + return nil, err + } + if len(pulledImages) > 0 { + img = pulledImages[0].StorageImage() + ref, err = pulledImages[0].StorageReference() + if err != nil { + return nil, err + } + } } + imageSpec := options.FromImage imageID := "" imageDigest := "" diff --git a/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go b/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go index f3876cd13..8dadec130 100644 --- a/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go +++ b/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go @@ -10,6 +10,7 @@ import ( "sync" "github.com/containers/buildah/docker" + "github.com/containers/common/libimage" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" @@ -82,6 +83,21 @@ func makeFilename(blobSum digest.Digest, isConfig bool) string { return blobSum.String() } +// CacheLookupReferenceFunc wraps a BlobCache into a +// libimage.LookupReferenceFunc to allow for using a BlobCache during +// image-copy operations. +func CacheLookupReferenceFunc(directory string, compress types.LayerCompression) libimage.LookupReferenceFunc { + // NOTE: this prevents us from moving BlobCache around and generalizes + // the libimage API. + return func(ref types.ImageReference) (types.ImageReference, error) { + ref, err := NewBlobCache(ref, directory, compress) + if err != nil { + return nil, errors.Wrapf(err, "error using blobcache %q", directory) + } + return ref, nil + } +} + // NewBlobCache creates a new blob cache that wraps an image reference. Any blobs which are // written to the destination image created from the resulting reference will also be stored // as-is to the specified directory or a temporary directory. The cache directory's contents @@ -141,7 +157,7 @@ func (r *blobCacheReference) HasBlob(blobinfo types.BlobInfo) (bool, int64, erro return true, fileInfo.Size(), nil } if !os.IsNotExist(err) { - return false, -1, errors.Wrapf(err, "error checking size of %q", filename) + return false, -1, errors.Wrap(err, "checking size") } } @@ -155,7 +171,7 @@ func (r *blobCacheReference) Directory() string { func (r *blobCacheReference) ClearCache() error { f, err := os.Open(r.directory) if err != nil { - return errors.Wrapf(err, "error opening directory %q", r.directory) + return errors.WithStack(err) } defer f.Close() names, err := f.Readdirnames(-1) @@ -165,7 +181,7 @@ func (r *blobCacheReference) ClearCache() error { for _, name := range names { pathname := filepath.Join(r.directory, name) if err = os.RemoveAll(pathname); err != nil { - return errors.Wrapf(err, "error removing %q while clearing cache for %q", pathname, transports.ImageName(r)) + return errors.Wrapf(err, "clearing cache for %q", transports.ImageName(r)) } } return nil @@ -216,7 +232,7 @@ func (s *blobCacheSource) GetManifest(ctx context.Context, instanceDigest *diges } if !os.IsNotExist(err) { s.cacheErrors++ - return nil, "", errors.Wrapf(err, "error checking for manifest file %q", filename) + return nil, "", errors.Wrap(err, "checking for manifest file") } } s.cacheMisses++ @@ -246,7 +262,7 @@ func (s *blobCacheSource) GetBlob(ctx context.Context, blobinfo types.BlobInfo, s.mu.Lock() s.cacheErrors++ s.mu.Unlock() - return nil, -1, errors.Wrapf(err, "error checking for cache file %q", filepath.Join(s.reference.directory, filename)) + return nil, -1, errors.Wrap(err, "checking for cache") } } } diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index 9c3c8cfe0..6e59dbe64 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -64,7 +64,6 @@ type BudResults struct { Iidfile string Label []string Logfile string - Loglevel int Manifest string NoCache bool Timestamp int64 @@ -75,6 +74,7 @@ type BudResults struct { Rm bool Runtime string RuntimeFlags []string + Secrets []string SignaturePolicy string SignBy string Squash bool @@ -191,7 +191,10 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.IntVar(&flags.Jobs, "jobs", 1, "how many stages to run in parallel") fs.StringArrayVar(&flags.Label, "label", []string{}, "Set metadata for an image (default [])") fs.StringVar(&flags.Logfile, "logfile", "", "log to `file` instead of stdout/stderr") - fs.IntVar(&flags.Loglevel, "loglevel", 0, "adjust logging level (range from -2 to 3)") + fs.Int("loglevel", 0, "NO LONGER USED, flag ignored, and hidden") + if err := fs.MarkHidden("loglevel"); err != nil { + panic(fmt.Sprintf("error marking the loglevel flag as hidden: %v", err)) + } fs.BoolVar(&flags.LogRusage, "log-rusage", false, "log resource usage at each build step") if err := fs.MarkHidden("log-rusage"); err != nil { panic(fmt.Sprintf("error marking the log-rusage flag as hidden: %v", err)) @@ -207,6 +210,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.BoolVar(&flags.Rm, "rm", true, "Remove intermediate containers after a successful build") // "runtime" definition moved to avoid name collision in podman build. Defined in cmd/buildah/bud.go. fs.StringSliceVar(&flags.RuntimeFlags, "runtime-flag", []string{}, "add global flags for the container runtime") + fs.StringArrayVar(&flags.Secrets, "secret", []string{}, "secret file to expose to the build") fs.StringVar(&flags.SignBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") fs.StringVar(&flags.SignaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") if err := fs.MarkHidden("signature-policy"); err != nil { @@ -240,11 +244,11 @@ func GetBudFlagsCompletions() commonComp.FlagCompletions { flagCompletion["jobs"] = commonComp.AutocompleteNone flagCompletion["label"] = commonComp.AutocompleteNone flagCompletion["logfile"] = commonComp.AutocompleteDefault - flagCompletion["loglevel"] = commonComp.AutocompleteDefault flagCompletion["manifest"] = commonComp.AutocompleteDefault flagCompletion["os"] = commonComp.AutocompleteNone flagCompletion["platform"] = commonComp.AutocompleteNone flagCompletion["runtime-flag"] = commonComp.AutocompleteNone + flagCompletion["secret"] = commonComp.AutocompleteNone flagCompletion["sign-by"] = commonComp.AutocompleteNone flagCompletion["signature-policy"] = commonComp.AutocompleteNone flagCompletion["tag"] = commonComp.AutocompleteNone @@ -403,6 +407,8 @@ func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { name = "os" case "purge": name = "rm" + case "tty": + name = "terminal" } return pflag.NormalizedName(name) } diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go index 462561983..d1b8955bb 100644 --- a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go +++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go @@ -174,15 +174,15 @@ func recreate(contentDir string) error { if os.IsNotExist(err) { return nil } - return errors.Wrapf(err, "failed to stat overlay upper %s directory", contentDir) + return errors.Wrap(err, "failed to stat overlay upper directory") } if err := os.RemoveAll(contentDir); err != nil { - return errors.Wrapf(err, "failed to cleanup overlay %s directory", contentDir) + return errors.WithStack(err) } if err := idtools.MkdirAllAs(contentDir, os.FileMode(st.Mode()), int(st.UID()), int(st.GID())); err != nil { - return errors.Wrapf(err, "failed to create the overlay %s directory", contentDir) + return errors.Wrap(err, "failed to create overlay directory") } return nil } @@ -208,7 +208,7 @@ func CleanupContent(containerDir string) (Err error) { if os.IsNotExist(err) { return nil } - return errors.Wrapf(err, "read directory") + return errors.Wrap(err, "read directory") } for _, f := range files { dir := filepath.Join(contentDir, f.Name()) @@ -218,7 +218,7 @@ func CleanupContent(containerDir string) (Err error) { } if err := os.RemoveAll(contentDir); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "failed to cleanup overlay %s directory", contentDir) + return errors.Wrap(err, "failed to cleanup overlay directory") } return nil } diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go index 2ae07efe9..462ac212e 100644 --- a/vendor/github.com/containers/buildah/pkg/parse/parse.go +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -125,6 +125,8 @@ func CommonBuildOptions(c *cobra.Command) (*define.CommonBuildOptions, error) { ulimit, _ = c.Flags().GetStringSlice("ulimit") } + secrets, _ := c.Flags().GetStringArray("secret") + commonOpts := &define.CommonBuildOptions{ AddHost: addHost, CPUPeriod: cpuPeriod, @@ -142,6 +144,7 @@ func CommonBuildOptions(c *cobra.Command) (*define.CommonBuildOptions, error) { ShmSize: c.Flag("shm-size").Value.String(), Ulimit: ulimit, Volumes: volumes, + Secrets: secrets, } securityOpts, _ := c.Flags().GetStringArray("security-opt") if err := parseSecurityOpts(securityOpts, commonOpts); err != nil { @@ -178,11 +181,11 @@ func parseSecurityOpts(securityOpts []string, commonOpts *define.CommonBuildOpti commonOpts.SeccompProfilePath = SeccompOverridePath } else { if !os.IsNotExist(err) { - return errors.Wrapf(err, "can't check if %q exists", SeccompOverridePath) + return errors.WithStack(err) } if _, err := os.Stat(SeccompDefaultPath); err != nil { if !os.IsNotExist(err) { - return errors.Wrapf(err, "can't check if %q exists", SeccompDefaultPath) + return errors.WithStack(err) } } else { commonOpts.SeccompProfilePath = SeccompDefaultPath @@ -454,7 +457,7 @@ func ValidateVolumeHostDir(hostDir string) error { } if filepath.IsAbs(hostDir) { if _, err := os.Stat(hostDir); err != nil { - return errors.Wrapf(err, "error checking path %q", hostDir) + return errors.WithStack(err) } } // If hostDir is not an absolute path, that means the user wants to create a @@ -468,7 +471,7 @@ func validateVolumeMountHostDir(hostDir string) error { return errors.Errorf("invalid host path, must be an absolute path %q", hostDir) } if _, err := os.Stat(hostDir); err != nil { - return errors.Wrapf(err, "error checking path %q", hostDir) + return errors.WithStack(err) } return nil } @@ -587,6 +590,14 @@ func SystemContextFromOptions(c *cobra.Command) (*types.SystemContext, error) { ctx.OCIInsecureSkipTLSVerify = !tlsVerify ctx.DockerDaemonInsecureSkipTLSVerify = !tlsVerify } + disableCompression, err := c.Flags().GetBool("disable-compression") + if err == nil { + if disableCompression { + ctx.OCIAcceptUncompressedLayers = true + } else { + ctx.DirForceCompress = true + } + } creds, err := c.Flags().GetString("creds") if err == nil && c.Flag("creds").Changed { var err error @@ -832,7 +843,7 @@ func IDMappingOptions(c *cobra.Command, isolation define.Isolation) (usernsOptio default: how = strings.TrimPrefix(how, "ns:") if _, err := os.Stat(how); err != nil { - return nil, nil, errors.Wrapf(err, "error checking for %s namespace at %q", string(specs.UserNamespace), how) + return nil, nil, errors.Wrapf(err, "checking %s namespace", string(specs.UserNamespace)) } logrus.Debugf("setting %q namespace to %q", string(specs.UserNamespace), how) usernsOption.Path = how @@ -922,7 +933,7 @@ func NamespaceOptions(c *cobra.Command) (namespaceOptions define.NamespaceOption } how = strings.TrimPrefix(how, "ns:") if _, err := os.Stat(how); err != nil { - return nil, define.NetworkDefault, errors.Wrapf(err, "error checking for %s namespace", what) + return nil, define.NetworkDefault, errors.Wrapf(err, "checking %s namespace", what) } policy = define.NetworkEnabled logrus.Debugf("setting %q namespace to %q", what, how) @@ -1043,3 +1054,37 @@ func GetTempDir() string { } return "/var/tmp" } + +// Secrets parses the --secret flag +func Secrets(secrets []string) (map[string]string, error) { + parsed := make(map[string]string) + invalidSyntax := errors.Errorf("incorrect secret flag format: should be --secret id=foo,src=bar") + for _, secret := range secrets { + split := strings.Split(secret, ",") + if len(split) > 2 { + return nil, invalidSyntax + } + if len(split) == 2 { + id := strings.Split(split[0], "=") + src := strings.Split(split[1], "=") + if len(split) == 2 && strings.ToLower(id[0]) == "id" && strings.ToLower(src[0]) == "src" { + fullPath, err := filepath.Abs(src[1]) + if err != nil { + return nil, err + } + _, err = os.Stat(fullPath) + if err == nil { + parsed[id[1]] = fullPath + } + if err != nil { + return nil, errors.Wrap(err, "could not parse secrets") + } + } else { + return nil, invalidSyntax + } + } else { + return nil, invalidSyntax + } + } + return parsed, nil +} diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go index 04eac5821..7149ac986 100644 --- a/vendor/github.com/containers/buildah/pull.go +++ b/vendor/github.com/containers/buildah/pull.go @@ -3,28 +3,16 @@ package buildah import ( "context" "io" - "strings" "time" "github.com/containers/buildah/define" "github.com/containers/buildah/pkg/blobcache" - "github.com/containers/image/v5/directory" - "github.com/containers/image/v5/docker" - dockerarchive "github.com/containers/image/v5/docker/archive" - "github.com/containers/image/v5/docker/reference" - tarfile "github.com/containers/image/v5/docker/tarfile" - ociarchive "github.com/containers/image/v5/oci/archive" - oci "github.com/containers/image/v5/oci/layout" - "github.com/containers/image/v5/signature" - is "github.com/containers/image/v5/storage" - "github.com/containers/image/v5/transports" - "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/types" encconfig "github.com/containers/ocicrypt/config" "github.com/containers/storage" - multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // PullOptions can be used to alter how an image is copied in from somewhere. @@ -65,258 +53,44 @@ type PullOptions struct { PullPolicy define.PullPolicy } -func localImageNameForReference(ctx context.Context, store storage.Store, srcRef types.ImageReference) (string, error) { - if srcRef == nil { - return "", errors.Errorf("reference to image is empty") - } - var name string - switch srcRef.Transport().Name() { - case dockerarchive.Transport.Name(): - file := srcRef.StringWithinTransport() - tarSource, err := tarfile.NewSourceFromFile(file) - if err != nil { - return "", errors.Wrapf(err, "error opening tarfile %q as a source image", file) - } - defer tarSource.Close() - manifest, err := tarSource.LoadTarManifest() - if err != nil { - return "", errors.Errorf("error retrieving manifest.json from tarfile %q: %v", file, err) - } - // to pull the first image stored in the tar file - if len(manifest) == 0 { - // use the hex of the digest if no manifest is found - name, err = getImageDigest(ctx, srcRef, nil) - if err != nil { - return "", err - } - } else { - if len(manifest[0].RepoTags) > 0 { - name = manifest[0].RepoTags[0] - } else { - // If the input image has no repotags, we need to feed it a dest anyways - name, err = getImageDigest(ctx, srcRef, nil) - if err != nil { - return "", err - } - } - } - case ociarchive.Transport.Name(): - // retrieve the manifest from index.json to access the image name - manifest, err := ociarchive.LoadManifestDescriptor(srcRef) - if err != nil { - return "", errors.Wrapf(err, "error loading manifest for %q", transports.ImageName(srcRef)) - } - // if index.json has no reference name, compute the image digest instead - if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { - name, err = getImageDigest(ctx, srcRef, nil) - if err != nil { - return "", err - } - } else { - name = manifest.Annotations["org.opencontainers.image.ref.name"] - } - case directory.Transport.Name(): - // supports pull from a directory - name = toLocalImageName(srcRef.StringWithinTransport()) - case oci.Transport.Name(): - // supports pull from a directory - split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2) - name = toLocalImageName(split[0]) - default: - ref := srcRef.DockerReference() - if ref == nil { - name = srcRef.StringWithinTransport() - _, err := is.Transport.ParseStoreReference(store, name) - if err == nil { - return name, nil - } - logrus.Debugf("error parsing local storage reference %q: %v", name, err) - if strings.LastIndex(name, "/") != -1 { - name = name[strings.LastIndex(name, "/")+1:] - _, err = is.Transport.ParseStoreReference(store, name) - if err == nil { - return name, errors.Wrapf(err, "error parsing local storage reference %q", name) - } - } - return "", errors.Errorf("reference to image %q is not a named reference", transports.ImageName(srcRef)) - } - - if named, ok := ref.(reference.Named); ok { - name = named.Name() - if namedTagged, ok := ref.(reference.NamedTagged); ok { - name = name + ":" + namedTagged.Tag() - } - if canonical, ok := ref.(reference.Canonical); ok { - name = name + "@" + canonical.Digest().String() - } - } - } - - if _, err := is.Transport.ParseStoreReference(store, name); err != nil { - return "", errors.Wrapf(err, "error parsing computed local image name %q", name) - } - return name, nil -} - // Pull copies the contents of the image from somewhere else to local storage. Returns the // ID of the local image or an error. func Pull(ctx context.Context, imageName string, options PullOptions) (imageID string, err error) { - systemContext := getSystemContext(options.Store, options.SystemContext, options.SignaturePolicyPath) - - boptions := BuilderOptions{ - FromImage: imageName, - SignaturePolicyPath: options.SignaturePolicyPath, - SystemContext: systemContext, - BlobDirectory: options.BlobDirectory, - ReportWriter: options.ReportWriter, - MaxPullRetries: options.MaxRetries, - PullRetryDelay: options.RetryDelay, - OciDecryptConfig: options.OciDecryptConfig, - PullPolicy: options.PullPolicy, - } - - if !options.AllTags { - _, _, img, err := resolveImage(ctx, systemContext, options.Store, boptions) - if err != nil { - return "", err - } - return img.ID, nil - } - - srcRef, err := alltransports.ParseImageName(imageName) - if err == nil && srcRef.Transport().Name() != docker.Transport.Name() { - return "", errors.New("Non-docker transport is not supported, for --all-tags pulling") - } - - storageRef, _, _, err := resolveImage(ctx, systemContext, options.Store, boptions) - if err != nil { - return "", err - } - - var errs *multierror.Error - repo := reference.TrimNamed(storageRef.DockerReference()) - dockerRef, err := docker.NewReference(reference.TagNameOnly(storageRef.DockerReference())) - if err != nil { - return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", storageRef.DockerReference().String()) - } - tags, err := docker.GetRepositoryTags(ctx, systemContext, dockerRef) - if err != nil { - return "", errors.Wrapf(err, "error getting repository tags") - } - for _, tag := range tags { - tagged, err := reference.WithTag(repo, tag) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - taggedRef, err := docker.NewReference(tagged) - if err != nil { - return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", tagged.String()) - } - if options.ReportWriter != nil { - if _, err := options.ReportWriter.Write([]byte("Pulling " + tagged.String() + "\n")); err != nil { - return "", errors.Wrapf(err, "error writing pull report") - } - } - ref, err := pullImage(ctx, options.Store, taggedRef, options, systemContext) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - taggedImg, err := is.Transport.GetStoreImage(options.Store, ref) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - imageID = taggedImg.ID - } - - return imageID, errs.ErrorOrNil() -} + libimageOptions := &libimage.PullOptions{} + libimageOptions.SignaturePolicyPath = options.SignaturePolicyPath + libimageOptions.Writer = options.ReportWriter + libimageOptions.RemoveSignatures = options.RemoveSignatures + libimageOptions.OciDecryptConfig = options.OciDecryptConfig + libimageOptions.AllTags = options.AllTags + libimageOptions.RetryDelay = &options.RetryDelay -func pullImage(ctx context.Context, store storage.Store, srcRef types.ImageReference, options PullOptions, sc *types.SystemContext) (types.ImageReference, error) { - blocked, err := isReferenceBlocked(srcRef, sc) - if err != nil { - return nil, errors.Wrapf(err, "error checking if pulling from registry for %q is blocked", transports.ImageName(srcRef)) - } - if blocked { - return nil, errors.Errorf("pull access to registry for %q is blocked by configuration", transports.ImageName(srcRef)) - } - insecure, err := checkRegistrySourcesAllows("pull from", srcRef) - if err != nil { - return nil, err - } - if insecure { - if sc.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse { - return nil, errors.Errorf("can't require tls verification on an insecured registry") - } - sc.DockerInsecureSkipTLSVerify = types.OptionalBoolTrue - sc.OCIInsecureSkipTLSVerify = true - sc.DockerDaemonInsecureSkipTLSVerify = true - } - - destName, err := localImageNameForReference(ctx, store, srcRef) - if err != nil { - return nil, errors.Wrapf(err, "error computing local image name for %q", transports.ImageName(srcRef)) - } - if destName == "" { - return nil, errors.Errorf("error computing local image name for %q", transports.ImageName(srcRef)) + if options.MaxRetries > 0 { + retries := uint(options.MaxRetries) + libimageOptions.MaxRetries = &retries } - destRef, err := is.Transport.ParseStoreReference(store, destName) - if err != nil { - return nil, errors.Wrapf(err, "error parsing image name %q", destName) - } - var maybeCachedDestRef = types.ImageReference(destRef) if options.BlobDirectory != "" { - cachedRef, err := blobcache.NewBlobCache(destRef, options.BlobDirectory, types.PreserveOriginal) - if err != nil { - return nil, errors.Wrapf(err, "error wrapping image reference %q in blob cache at %q", transports.ImageName(destRef), options.BlobDirectory) - } - maybeCachedDestRef = cachedRef + libimageOptions.DestinationLookupReferenceFunc = blobcache.CacheLookupReferenceFunc(options.BlobDirectory, types.PreserveOriginal) } - policy, err := signature.DefaultPolicy(sc) + pullPolicy, err := config.ParsePullPolicy(options.PullPolicy.String()) if err != nil { - return nil, errors.Wrapf(err, "error obtaining default signature policy") + return "", err } - policyContext, err := signature.NewPolicyContext(policy) + runtime, err := libimage.RuntimeFromStore(options.Store, &libimage.RuntimeOptions{SystemContext: options.SystemContext}) if err != nil { - return nil, errors.Wrapf(err, "error creating new signature policy context") - } - - defer func() { - if err2 := policyContext.Destroy(); err2 != nil { - logrus.Debugf("error destroying signature policy context: %v", err2) - } - }() - - logrus.Debugf("copying %q to %q", transports.ImageName(srcRef), destName) - if _, err := retryCopyImage(ctx, policyContext, maybeCachedDestRef, srcRef, srcRef, getCopyOptions(store, options.ReportWriter, sc, sc, "", options.RemoveSignatures, "", nil, nil, options.OciDecryptConfig), options.MaxRetries, options.RetryDelay); err != nil { - logrus.Debugf("error copying src image [%q] to dest image [%q] err: %v", transports.ImageName(srcRef), destName, err) - return nil, err + return "", err } - return destRef, nil -} -// getImageDigest creates an image object and uses the hex value of the digest as the image ID -// for parsing the store reference -func getImageDigest(ctx context.Context, src types.ImageReference, sc *types.SystemContext) (string, error) { - newImg, err := src.NewImage(ctx, sc) + pulledImages, err := runtime.Pull(context.Background(), imageName, pullPolicy, libimageOptions) if err != nil { - return "", errors.Wrapf(err, "error opening image %q for reading", transports.ImageName(src)) + return "", err } - defer newImg.Close() - digest := newImg.ConfigInfo().Digest - if err = digest.Validate(); err != nil { - return "", errors.Wrapf(err, "error getting config info from image %q", transports.ImageName(src)) + if len(pulledImages) == 0 { + return "", errors.Errorf("internal error pulling %s: no image pulled and no error", imageName) } - return "@" + digest.Hex(), nil -} -// toLocalImageName converts an image name into a 'localhost/' prefixed one -func toLocalImageName(imageName string) string { - return "localhost/" + strings.TrimLeft(imageName, "/") + return pulledImages[0].ID(), nil } diff --git a/vendor/github.com/containers/buildah/push.go b/vendor/github.com/containers/buildah/push.go new file mode 100644 index 000000000..692dfccd4 --- /dev/null +++ b/vendor/github.com/containers/buildah/push.go @@ -0,0 +1,126 @@ +package buildah + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/containers/buildah/pkg/blobcache" + "github.com/containers/common/libimage" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" + encconfig "github.com/containers/ocicrypt/config" + "github.com/containers/storage" + "github.com/containers/storage/pkg/archive" + digest "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// PushOptions can be used to alter how an image is copied somewhere. +type PushOptions struct { + // Compression specifies the type of compression which is applied to + // layer blobs. The default is to not use compression, but + // archive.Gzip is recommended. + Compression archive.Compression + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string + // ReportWriter is an io.Writer which will be used to log the writing + // of the new image. + ReportWriter io.Writer + // Store is the local storage store which holds the source image. + Store storage.Store + // github.com/containers/image/types SystemContext to hold credentials + // and other authentication/authorization information. + SystemContext *types.SystemContext + // ManifestType is the format to use when saving the image using the 'dir' transport + // possible options are oci, v2s1, and v2s2 + ManifestType string + // BlobDirectory is the name of a directory in which we'll look for + // prebuilt copies of layer blobs that we might otherwise need to + // regenerate from on-disk layers, substituting them in the list of + // blobs to copy whenever possible. + BlobDirectory string + // Quiet is a boolean value that determines if minimal output to + // the user will be displayed, this is best used for logging. + // The default is false. + Quiet bool + // SignBy is the fingerprint of a GPG key to use for signing the image. + SignBy string + // RemoveSignatures causes any existing signatures for the image to be + // discarded for the pushed copy. + RemoveSignatures bool + // MaxRetries is the maximum number of attempts we'll make to push any + // one image to the external registry if the first attempt fails. + MaxRetries int + // RetryDelay is how long to wait before retrying a push attempt. + RetryDelay time.Duration + // OciEncryptConfig when non-nil indicates that an image should be encrypted. + // The encryption options is derived from the construction of EncryptConfig object. + OciEncryptConfig *encconfig.EncryptConfig + // OciEncryptLayers represents the list of layers to encrypt. + // If nil, don't encrypt any layers. + // If non-nil and len==0, denotes encrypt all layers. + // integers in the slice represent 0-indexed layer indices, with support for negativ + // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. + OciEncryptLayers *[]int +} + +// Push copies the contents of the image to a new location. +func Push(ctx context.Context, image string, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) { + libimageOptions := &libimage.PushOptions{} + libimageOptions.SignaturePolicyPath = options.SignaturePolicyPath + libimageOptions.Writer = options.ReportWriter + libimageOptions.ManifestMIMEType = options.ManifestType + libimageOptions.SignBy = options.SignBy + libimageOptions.RemoveSignatures = options.RemoveSignatures + libimageOptions.RetryDelay = &options.RetryDelay + libimageOptions.OciEncryptConfig = options.OciEncryptConfig + libimageOptions.OciEncryptLayers = options.OciEncryptLayers + libimageOptions.PolicyAllowStorage = true + + if options.Quiet { + libimageOptions.Writer = nil + } + + if options.BlobDirectory != "" { + compress := types.PreserveOriginal + if options.Compression == archive.Gzip { + compress = types.Compress + } + libimageOptions.SourceLookupReferenceFunc = blobcache.CacheLookupReferenceFunc(options.BlobDirectory, compress) + } + + runtime, err := libimage.RuntimeFromStore(options.Store, &libimage.RuntimeOptions{SystemContext: options.SystemContext}) + if err != nil { + return nil, "", err + } + + destString := fmt.Sprintf("%s:%s", dest.Transport().Name(), dest.StringWithinTransport()) + manifestBytes, err := runtime.Push(ctx, image, destString, libimageOptions) + if err != nil { + return nil, "", err + } + + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return nil, "", errors.Wrapf(err, "error computing digest of manifest of new image %q", transports.ImageName(dest)) + } + + var ref reference.Canonical + if name := dest.DockerReference(); name != nil { + ref, err = reference.WithDigest(name, manifestDigest) + if err != nil { + logrus.Warnf("error generating canonical reference with name %q and digest %s: %v", name, manifestDigest.String(), err) + } + } + + return ref, manifestDigest, nil +} diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go index 876850403..efffd1f5f 100644 --- a/vendor/github.com/containers/buildah/run.go +++ b/vendor/github.com/containers/buildah/run.go @@ -134,4 +134,9 @@ type RunOptions struct { DropCapabilities []string // Devices are the additional devices to add to the containers Devices define.ContainerDevices + // Secrets are the available secrets to use in a RUN + Secrets map[string]string + // RunMounts are mounts for this run. RunMounts for this run + // will not show up in subsequent runs. + RunMounts []string } diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index 6356d2602..005607792 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -246,10 +246,17 @@ rootless=%d bindFiles["/run/.containerenv"] = containerenvPath } - err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, namespaceOptions) + runMountTargets, err := b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, namespaceOptions, options.Secrets, options.RunMounts) if err != nil { return errors.Wrapf(err, "error resolving mountpoints for container %q", b.ContainerID) } + + defer func() { + if err := cleanupRunMounts(runMountTargets, mountPoint); err != nil { + logrus.Errorf("unabe to cleanup run mounts %v", err) + } + }() + defer b.cleanupTempVolumes() if options.CNIConfigDir == "" { @@ -341,16 +348,16 @@ func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, builtin // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. for _, volume := range builtinVolumes { - subdir := digest.Canonical.FromString(volume).Hex() - volumePath := filepath.Join(containerDir, "buildah-volumes", subdir) - srcPath := filepath.Join(mountPoint, volume) + volumePath := filepath.Join(containerDir, "buildah-volumes", digest.Canonical.FromString(volume).Hex()) initializeVolume := false - // If we need to, initialize the volume path's initial contents. + // If we need to, create the directory that we'll use to hold + // the volume contents. If we do need to create it, then we'll + // need to populate it, too, so make a note of that. if _, err := os.Stat(volumePath); err != nil { if !os.IsNotExist(err) { return nil, err } - logrus.Debugf("setting up built-in volume at %q", volumePath) + logrus.Debugf("setting up built-in volume path at %q for %q", volumePath, volume) if err = os.MkdirAll(volumePath, 0755); err != nil { return nil, err } @@ -359,28 +366,25 @@ func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, builtin } initializeVolume = true } - // Check if srcPath is a symlink - stat, err := os.Lstat(srcPath) - // If srcPath is a symlink, follow the link and ensure the destination exists - if err == nil && stat != nil && (stat.Mode()&os.ModeSymlink != 0) { - srcPath, err = copier.Eval(mountPoint, volume, copier.EvalOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "evaluating symlink %q", srcPath) - } - // Stat the destination of the evaluated symlink - stat, err = os.Stat(srcPath) + // Make sure the volume exists in the rootfs and read its attributes. + createDirPerms := os.FileMode(0755) + err := copier.Mkdir(mountPoint, filepath.Join(mountPoint, volume), copier.MkdirOptions{ + ChownNew: &hostOwner, + ChmodNew: &createDirPerms, + }) + if err != nil { + return nil, errors.Wrapf(err, "ensuring volume path %q", filepath.Join(mountPoint, volume)) } + srcPath, err := copier.Eval(mountPoint, filepath.Join(mountPoint, volume), copier.EvalOptions{}) if err != nil { - if !os.IsNotExist(err) { - return nil, err - } - if err = idtools.MkdirAllAndChownNew(srcPath, 0755, hostOwner); err != nil { - return nil, err - } - if stat, err = os.Stat(srcPath); err != nil { - return nil, err - } + return nil, errors.Wrapf(err, "evaluating path %q", srcPath) + } + stat, err := os.Stat(srcPath) + if err != nil && !os.IsNotExist(err) { + return nil, err } + // If we need to populate the mounted volume's contents with + // content from the rootfs, set it up now. if initializeVolume { if err = os.Chmod(volumePath, stat.Mode().Perm()); err != nil { return nil, err @@ -388,6 +392,7 @@ func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, builtin if err = os.Chown(volumePath, int(stat.Sys().(*syscall.Stat_t).Uid), int(stat.Sys().(*syscall.Stat_t).Gid)); err != nil { return nil, err } + logrus.Debugf("populating directory %q for volume %q using contents of %q", volumePath, volume, srcPath) if err = extractWithTar(mountPoint, srcPath, volumePath); err != nil && !os.IsNotExist(errors.Cause(err)) { return nil, errors.Wrapf(err, "error populating directory %q for volume %q using contents of %q", volumePath, volume, srcPath) } @@ -403,7 +408,7 @@ func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, builtin return mounts, nil } -func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions define.NamespaceOptions) error { +func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions define.NamespaceOptions, secrets map[string]string, runFileMounts []string) (runMountTargets []string, err error) { // Start building a new list of mounts. var mounts []specs.Mount haveMount := func(destination string) bool { @@ -497,39 +502,45 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st // After this point we need to know the per-container persistent storage directory. cdir, err := b.store.ContainerDirectory(b.ContainerID) if err != nil { - return errors.Wrapf(err, "error determining work directory for container %q", b.ContainerID) + return nil, errors.Wrapf(err, "error determining work directory for container %q", b.ContainerID) } // Figure out which UID and GID to tell the subscriptions package to use // for files that it creates. rootUID, rootGID, err := util.GetHostRootIDs(spec) if err != nil { - return err + return nil, err } // Get the list of subscriptions mounts. - secretMounts := subscriptions.MountsWithUIDGID(b.MountLabel, cdir, b.DefaultMountsFilePath, mountPoint, int(rootUID), int(rootGID), unshare.IsRootless(), false) + subscriptionMounts := subscriptions.MountsWithUIDGID(b.MountLabel, cdir, b.DefaultMountsFilePath, mountPoint, int(rootUID), int(rootGID), unshare.IsRootless(), false) + + // Get the list of mounts that are just for this Run() call. + runMounts, runTargets, err := runSetupRunMounts(runFileMounts, secrets, b.MountLabel, cdir, spec.Linux.UIDMappings, spec.Linux.GIDMappings) + if err != nil { + return nil, err + } // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, builtinVolumes, int(rootUID), int(rootGID)) if err != nil { - return err + return nil, err } // Get host UID and GID of the container process. processUID, processGID, err := util.GetHostIDs(spec.Linux.UIDMappings, spec.Linux.GIDMappings, spec.Process.User.UID, spec.Process.User.GID) if err != nil { - return err + return nil, err } // Get the list of explicitly-specified volume mounts. volumes, err := b.runSetupVolumeMounts(spec.Linux.MountLabel, volumeMounts, optionMounts, int(rootUID), int(rootGID), int(processUID), int(processGID)) if err != nil { - return err + return nil, err } - allMounts := util.SortMounts(append(append(append(append(append(volumes, builtins...), secretMounts...), bindFileMounts...), specMounts...), sysfsMount...)) + allMounts := util.SortMounts(append(append(append(append(append(append(volumes, builtins...), runMounts...), subscriptionMounts...), bindFileMounts...), specMounts...), sysfsMount...)) // Add them all, in the preferred order, except where they conflict with something that was previously added. for _, mount := range allMounts { if haveMount(mount.Destination) { @@ -542,7 +553,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st // Set the list in the spec. spec.Mounts = mounts - return nil + return runTargets, nil } // addNetworkConfig copies files from host and sets them up to bind mount into container @@ -818,7 +829,7 @@ func runUsingRuntime(isolation define.Isolation, options RunOptions, configureNe logrus.Debugf("Running %q", create.Args) err = create.Run() if err != nil { - return 1, errors.Wrapf(err, "error creating container for %v: %s", pargs, runCollectOutput(errorFds, closeBeforeReadingErrorFds)) + return 1, errors.Wrapf(err, "error from %s creating container for %v: %s", runtime, pargs, runCollectOutput(errorFds, closeBeforeReadingErrorFds)) } defer func() { err2 := del.Run() @@ -826,7 +837,7 @@ func runUsingRuntime(isolation define.Isolation, options RunOptions, configureNe if err == nil { err = errors.Wrapf(err2, "error deleting container") } else { - logrus.Infof("error deleting container: %v", err2) + logrus.Infof("error from %s deleting container: %v", runtime, err2) } } }() @@ -879,13 +890,13 @@ func runUsingRuntime(isolation define.Isolation, options RunOptions, configureNe logrus.Debugf("Running %q", start.Args) err = start.Run() if err != nil { - return 1, errors.Wrapf(err, "error starting container") + return 1, errors.Wrapf(err, "error from %s starting container", runtime) } stopped := false defer func() { if !stopped { if err2 := kill.Run(); err2 != nil { - logrus.Infof("error stopping container: %v", err2) + logrus.Infof("error from %s stopping container: %v", runtime, err2) } } }() @@ -900,10 +911,10 @@ func runUsingRuntime(isolation define.Isolation, options RunOptions, configureNe stat.Stderr = os.Stderr stateOutput, err := stat.Output() if err != nil { - return 1, errors.Wrapf(err, "error reading container state (got output: %q)", string(stateOutput)) + return 1, errors.Wrapf(err, "error reading container state from %s (got output: %q)", runtime, string(stateOutput)) } if err = json.Unmarshal(stateOutput, &state); err != nil { - return 1, errors.Wrapf(err, "error parsing container state %q", string(stateOutput)) + return 1, errors.Wrapf(err, "error parsing container state %q from %s", string(stateOutput), runtime) } switch state.Status { case "running": @@ -2248,3 +2259,149 @@ type runUsingRuntimeSubprocOptions struct { func init() { reexec.Register(runUsingRuntimeCommand, runUsingRuntimeMain) } + +// runSetupRunMounts sets up mounts that exist only in this RUN, not in subsequent runs +func runSetupRunMounts(mounts []string, secrets map[string]string, mountlabel string, containerWorkingDir string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping) ([]spec.Mount, []string, error) { + mountTargets := make([]string, 0, 10) + finalMounts := make([]specs.Mount, 0, len(mounts)) + for _, mount := range mounts { + arr := strings.SplitN(mount, ",", 2) + if len(arr) < 2 { + return nil, nil, errors.New("invalid mount syntax") + } + + kv := strings.Split(arr[0], "=") + if len(kv) != 2 || kv[0] != "type" { + return nil, nil, errors.New("invalid mount type") + } + + tokens := strings.Split(arr[1], ",") + // For now, we only support type secret. + switch kv[1] { + case "secret": + mount, err := getSecretMount(tokens, secrets, mountlabel, containerWorkingDir, uidmap, gidmap) + if err != nil { + return nil, nil, err + } + if mount != nil { + finalMounts = append(finalMounts, *mount) + mountTargets = append(mountTargets, mount.Destination) + + } + default: + return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) + } + } + return finalMounts, mountTargets, nil +} + +func getSecretMount(tokens []string, secrets map[string]string, mountlabel string, containerWorkingDir string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping) (*spec.Mount, error) { + errInvalidSyntax := errors.New("secret should have syntax id=id[,target=path,required=bool,mode=uint,uid=uint,gid=uint") + + var err error + var id, target string + var required bool + var uid, gid uint32 + var mode uint32 = 400 + for _, val := range tokens { + kv := strings.SplitN(val, "=", 2) + switch kv[0] { + case "id": + id = kv[1] + case "target": + target = kv[1] + case "required": + required, err = strconv.ParseBool(kv[1]) + if err != nil { + return nil, errInvalidSyntax + } + case "mode": + mode64, err := strconv.ParseUint(kv[1], 8, 32) + if err != nil { + return nil, errInvalidSyntax + } + mode = uint32(mode64) + case "uid": + uid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, errInvalidSyntax + } + uid = uint32(uid64) + case "gid": + gid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, errInvalidSyntax + } + gid = uint32(gid64) + default: + return nil, errInvalidSyntax + } + } + + if id == "" { + return nil, errInvalidSyntax + } + // Default location for secretis is /run/secrets/id + if target == "" { + target = "/run/secrets/" + id + } + + src, ok := secrets[id] + if !ok { + if required { + return nil, errors.Errorf("secret required but no secret with id %s found", id) + } + return nil, nil + } + + // Copy secrets to container working dir, since we need to chmod, chown and relabel it + // for the container user and we don't want to mess with the original file + ctrFileOnHost := filepath.Join(containerWorkingDir, "secrets", id) + _, err = os.Stat(ctrFileOnHost) + if os.IsNotExist(err) { + data, err := ioutil.ReadFile(src) + if err != nil { + return nil, err + } + if err := os.MkdirAll(filepath.Dir(ctrFileOnHost), 0644); err != nil { + return nil, err + } + if err := ioutil.WriteFile(ctrFileOnHost, data, 0644); err != nil { + return nil, err + } + } + + if err := label.Relabel(ctrFileOnHost, mountlabel, false); err != nil { + return nil, err + } + hostUID, hostGID, err := util.GetHostIDs(uidmap, gidmap, uid, gid) + if err != nil { + return nil, err + } + if err := os.Lchown(ctrFileOnHost, int(hostUID), int(hostGID)); err != nil { + return nil, err + } + if err := os.Chmod(ctrFileOnHost, os.FileMode(mode)); err != nil { + return nil, err + } + newMount := specs.Mount{ + Destination: target, + Type: "bind", + Source: ctrFileOnHost, + Options: []string{"bind", "rprivate", "ro"}, + } + return &newMount, nil +} + +func cleanupRunMounts(paths []string, mountpoint string) error { + opts := copier.RemoveOptions{ + All: true, + } + for _, path := range paths { + err := copier.Remove(mountpoint, path, opts) + if err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index b3fae6003..3b22a3943 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -5,7 +5,6 @@ import ( "io" "net/url" "os" - "path" "path/filepath" "sort" "strings" @@ -13,12 +12,12 @@ import ( "syscall" "github.com/containers/buildah/define" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/signature" - is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/storage" @@ -46,7 +45,7 @@ var ( } ) -// ResolveName checks if name is a valid image name, and if that name doesn't +// resolveName checks if name is a valid image name, and if that name doesn't // include a domain portion, returns a list of the names which it might // correspond to in the set of configured registries, the transport used to // pull the image, and a boolean which is true iff @@ -59,7 +58,7 @@ var ( // // NOTE: The "list of search registries is empty" check does not count blocked registries, // and neither the implied "localhost" nor a possible firstRegistry are counted -func ResolveName(name string, firstRegistry string, sc *types.SystemContext, store storage.Store) ([]string, string, bool, error) { +func resolveName(name string, sc *types.SystemContext, store storage.Store) ([]string, string, bool, error) { if name == "" { return nil, "", false, nil } @@ -112,16 +111,6 @@ func ResolveName(name string, firstRegistry string, sc *types.SystemContext, sto searchRegistriesAreEmpty := len(registries) == 0 var candidates []string - // Set the first registry if requested. - if firstRegistry != "" && firstRegistry != "localhost" { - middle := "" - if prefix, ok := RegistryDefaultPathPrefix[firstRegistry]; ok && !strings.ContainsRune(name, '/') { - middle = prefix - } - candidate := path.Join(firstRegistry, middle, name) - candidates = append(candidates, candidate) - } - // Local short-name resolution. namedCandidates, err := shortnames.ResolveLocally(sc, name) if err != nil { @@ -144,11 +133,11 @@ func StartsWithValidTransport(name string) bool { // the fully expanded result, including a tag. Names which don't include a registry // name will be marked for the most-preferred registry (i.e., the first one in our // configuration). -func ExpandNames(names []string, firstRegistry string, systemContext *types.SystemContext, store storage.Store) ([]string, error) { +func ExpandNames(names []string, systemContext *types.SystemContext, store storage.Store) ([]string, error) { expanded := make([]string, 0, len(names)) for _, n := range names { var name reference.Named - nameList, _, _, err := ResolveName(n, firstRegistry, systemContext, store) + nameList, _, _, err := resolveName(n, systemContext, store) if err != nil { return nil, errors.Wrapf(err, "error parsing name %q", n) } @@ -172,45 +161,34 @@ func ExpandNames(names []string, firstRegistry string, systemContext *types.Syst } // FindImage locates the locally-stored image which corresponds to a given name. +// Please note that the `firstRegistry` argument has been deprecated and has no +// effect anymore. func FindImage(store storage.Store, firstRegistry string, systemContext *types.SystemContext, image string) (types.ImageReference, *storage.Image, error) { - var ref types.ImageReference - var img *storage.Image - var err error - names, _, _, err := ResolveName(image, firstRegistry, systemContext, store) + runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { - return nil, nil, errors.Wrapf(err, "error parsing name %q", image) + return nil, nil, err } - for _, name := range names { - ref, err = is.Transport.ParseStoreReference(store, name) - if err != nil { - logrus.Debugf("error parsing reference to image %q: %v", name, err) - continue - } - img, err = is.Transport.GetStoreImage(store, ref) - if err != nil { - img2, err2 := store.Image(name) - if err2 != nil { - logrus.Debugf("error locating image %q: %v", name, err2) - continue - } - img = img2 - } - break + + localImage, _, err := runtime.LookupImage(image, &libimage.LookupImageOptions{IgnorePlatform: true}) + if err != nil { + return nil, nil, err } - if ref == nil || img == nil { - return nil, nil, errors.Wrapf(err, "error locating image with name %q (%v)", image, names) + ref, err := localImage.StorageReference() + if err != nil { + return nil, nil, err } - return ref, img, nil + + return ref, localImage.StorageImage(), nil } -// ResolveNameToReferences tries to create a list of possible references +// resolveNameToReferences tries to create a list of possible references // (including their transports) from the provided image name. func ResolveNameToReferences( store storage.Store, systemContext *types.SystemContext, image string, ) (refs []types.ImageReference, err error) { - names, transport, _, err := ResolveName(image, "", systemContext, store) + names, transport, _, err := resolveName(image, systemContext, store) if err != nil { return nil, errors.Wrapf(err, "error parsing name %q", image) } @@ -233,16 +211,26 @@ func ResolveNameToReferences( return refs, nil } -// AddImageNames adds the specified names to the specified image. +// AddImageNames adds the specified names to the specified image. Please note +// that the `firstRegistry` argument has been deprecated and has no effect +// anymore. func AddImageNames(store storage.Store, firstRegistry string, systemContext *types.SystemContext, image *storage.Image, addNames []string) error { - names, err := ExpandNames(addNames, firstRegistry, systemContext, store) + runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } - err = store.SetNames(image.ID, append(image.Names, names...)) + + localImage, _, err := runtime.LookupImage(image.ID, nil) if err != nil { - return errors.Wrapf(err, "error adding names (%v) to image %q", names, image.ID) + return err } + + for _, tag := range addNames { + if err := localImage.Tag(tag); err != nil { + return errors.Wrapf(err, "error tagging image %s", image.ID) + } + } + return nil } @@ -275,11 +263,6 @@ func Runtime() string { return runtime } - // Need to switch default until runc supports cgroups v2 - if unified, _ := IsCgroup2UnifiedMode(); unified { - return "crun" - } - conf, err := config.Default() if err != nil { logrus.Warnf("Error loading container config when searching for local runtime: %v", err) diff --git a/vendor/github.com/containers/common/libimage/copier.go b/vendor/github.com/containers/common/libimage/copier.go new file mode 100644 index 000000000..34cc0d45d --- /dev/null +++ b/vendor/github.com/containers/common/libimage/copier.go @@ -0,0 +1,427 @@ +package libimage + +import ( + "context" + "encoding/json" + "io" + "os" + "strings" + "time" + + "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/retry" + "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/signature" + storageTransport "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/types" + encconfig "github.com/containers/ocicrypt/config" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + defaultMaxRetries = 3 + defaultRetryDelay = time.Second +) + +// LookupReferenceFunc return an image reference based on the specified one. +// This can be used to pass custom blob caches to the copy operation. +type LookupReferenceFunc func(ref types.ImageReference) (types.ImageReference, error) + +// CopyOptions allow for customizing image-copy operations. +type CopyOptions struct { + // If set, will be used for copying the image. Fields below may + // override certain settings. + SystemContext *types.SystemContext + // Allows for customizing the source reference lookup. This can be + // used to use custom blob caches. + SourceLookupReferenceFunc LookupReferenceFunc + // Allows for customizing the destination reference lookup. This can + // be used to use custom blob caches. + DestinationLookupReferenceFunc LookupReferenceFunc + + // containers-auth.json(5) file to use when authenticating against + // container registries. + AuthFilePath string + // Custom path to a blob-info cache. + BlobInfoCacheDirPath string + // Path to the certificates directory. + CertDirPath string + // Force layer compression when copying to a `dir` transport destination. + DirForceCompress bool + // Allow contacting registries over HTTP, or HTTPS with failed TLS + // verification. Note that this does not affect other TLS connections. + InsecureSkipTLSVerify types.OptionalBool + // Maximum number of retries with exponential backoff when facing + // transient network errors. A reasonable default is used if not set. + // Default 3. + MaxRetries *uint + // RetryDelay used for the exponential back off of MaxRetries. + // Default 1 time.Scond. + RetryDelay *time.Duration + // ManifestMIMEType is the desired media type the image will be + // converted to if needed. Note that it must contain the exact MIME + // types. Short forms (e.g., oci, v2s2) used by some tools are not + // supported. + ManifestMIMEType string + // If OciEncryptConfig is non-nil, it indicates that an image should be + // encrypted. The encryption options is derived from the construction + // of EncryptConfig object. Note: During initial encryption process of + // a layer, the resultant digest is not known during creation, so + // newDigestingReader has to be set with validateDigest = false + OciEncryptConfig *encconfig.EncryptConfig + // OciEncryptLayers represents the list of layers to encrypt. If nil, + // don't encrypt any layers. If non-nil and len==0, denotes encrypt + // all layers. integers in the slice represent 0-indexed layer + // indices, with support for negative indexing. i.e. 0 is the first + // layer, -1 is the last (top-most) layer. + OciEncryptLayers *[]int + // OciDecryptConfig contains the config that can be used to decrypt an + // image if it is encrypted if non-nil. If nil, it does not attempt to + // decrypt an image. + OciDecryptConfig *encconfig.DecryptConfig + // Reported to when ProgressInterval has arrived for a single + // artifact+offset. + Progress chan types.ProgressProperties + // If set, allow using the storage transport even if it's disabled by + // the specified SignaturePolicyPath. + PolicyAllowStorage bool + // SignaturePolicyPath to overwrite the default one. + SignaturePolicyPath string + // If non-empty, asks for a signature to be added during the copy, and + // specifies a key ID. + SignBy string + // Remove any pre-existing signatures. SignBy will still add a new + // signature. + RemoveSignatures bool + // Writer is used to display copy information including progress bars. + Writer io.Writer + + // ----- platform ----------------------------------------------------- + + // Architecture to use for choosing images. + Architecture string + // OS to use for choosing images. + OS string + // Variant to use when choosing images. + Variant string + + // ----- credentials -------------------------------------------------- + + // Username to use when authenticating at a container registry. + Username string + // Password to use when authenticating at a container registry. + Password string + // Credentials is an alternative way to specify credentials in format + // "username[:password]". Cannot be used in combination with + // Username/Password. + Credentials string + // IdentityToken is used to authenticate the user and get + // an access token for the registry. + IdentityToken string `json:"identitytoken,omitempty"` + + // ----- internal ----------------------------------------------------- + + // Additional tags when creating or copying a docker-archive. + dockerArchiveAdditionalTags []reference.NamedTagged +} + +// copier is an internal helper to conveniently copy images. +type copier struct { + imageCopyOptions copy.Options + retryOptions retry.RetryOptions + systemContext *types.SystemContext + policyContext *signature.PolicyContext + + sourceLookup LookupReferenceFunc + destinationLookup LookupReferenceFunc +} + +var ( + // storageAllowedPolicyScopes overrides the policy for local storage + // to ensure that we can read images from it. + storageAllowedPolicyScopes = signature.PolicyTransportScopes{ + "": []signature.PolicyRequirement{ + signature.NewPRInsecureAcceptAnything(), + }, + } +) + +// getDockerAuthConfig extracts a docker auth config from the CopyOptions. Returns +// nil if no credentials are set. +func (options *CopyOptions) getDockerAuthConfig() (*types.DockerAuthConfig, error) { + authConf := &types.DockerAuthConfig{IdentityToken: options.IdentityToken} + + if options.Username != "" { + if options.Credentials != "" { + return nil, errors.New("username/password cannot be used with credentials") + } + authConf.Username = options.Username + authConf.Password = options.Password + return authConf, nil + } + + if options.Credentials != "" { + split := strings.SplitN(options.Credentials, ":", 2) + switch len(split) { + case 1: + authConf.Username = split[0] + default: + authConf.Username = split[0] + authConf.Password = split[1] + } + return authConf, nil + } + + // We should return nil unless a token was set. That's especially + // useful for Podman's remote API. + if options.IdentityToken != "" { + return authConf, nil + } + + return nil, nil +} + +// newCopier creates a copier. Note that fields in options *may* overwrite the +// counterparts of the specified system context. Please make sure to call +// `(*copier).close()`. +func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) { + c := copier{} + c.systemContext = r.systemContextCopy() + + if options.SourceLookupReferenceFunc != nil { + c.sourceLookup = options.SourceLookupReferenceFunc + } + + if options.DestinationLookupReferenceFunc != nil { + c.destinationLookup = options.DestinationLookupReferenceFunc + } + + if options.InsecureSkipTLSVerify != types.OptionalBoolUndefined { + c.systemContext.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify + c.systemContext.OCIInsecureSkipTLSVerify = options.InsecureSkipTLSVerify == types.OptionalBoolTrue + c.systemContext.DockerDaemonInsecureSkipTLSVerify = options.InsecureSkipTLSVerify == types.OptionalBoolTrue + } + + c.systemContext.DirForceCompress = c.systemContext.DirForceCompress || options.DirForceCompress + + if options.AuthFilePath != "" { + c.systemContext.AuthFilePath = options.AuthFilePath + } + + c.systemContext.DockerArchiveAdditionalTags = options.dockerArchiveAdditionalTags + + if options.Architecture != "" { + c.systemContext.ArchitectureChoice = options.Architecture + } + if options.OS != "" { + c.systemContext.OSChoice = options.OS + } + if options.Variant != "" { + c.systemContext.VariantChoice = options.Variant + } + + if options.SignaturePolicyPath != "" { + c.systemContext.SignaturePolicyPath = options.SignaturePolicyPath + } + + dockerAuthConfig, err := options.getDockerAuthConfig() + if err != nil { + return nil, err + } + if dockerAuthConfig != nil { + c.systemContext.DockerAuthConfig = dockerAuthConfig + } + + if options.BlobInfoCacheDirPath != "" { + c.systemContext.BlobInfoCacheDir = options.BlobInfoCacheDirPath + } + + if options.CertDirPath != "" { + c.systemContext.DockerCertPath = options.CertDirPath + } + + policy, err := signature.DefaultPolicy(c.systemContext) + if err != nil { + return nil, err + } + + // Buildah compatibility: even if the policy denies _all_ transports, + // Buildah still wants the storage to be accessible. + if options.PolicyAllowStorage { + policy.Transports[storageTransport.Transport.Name()] = storageAllowedPolicyScopes + } + + policyContext, err := signature.NewPolicyContext(policy) + if err != nil { + return nil, err + } + + c.policyContext = policyContext + + c.retryOptions.MaxRetry = defaultMaxRetries + if options.MaxRetries != nil { + c.retryOptions.MaxRetry = int(*options.MaxRetries) + } + c.retryOptions.Delay = defaultRetryDelay + if options.RetryDelay != nil { + c.retryOptions.Delay = *options.RetryDelay + } + + c.imageCopyOptions.Progress = options.Progress + if c.imageCopyOptions.Progress != nil { + c.imageCopyOptions.ProgressInterval = time.Second + } + + c.imageCopyOptions.ForceManifestMIMEType = options.ManifestMIMEType + c.imageCopyOptions.SourceCtx = c.systemContext + c.imageCopyOptions.DestinationCtx = c.systemContext + c.imageCopyOptions.OciEncryptConfig = options.OciEncryptConfig + c.imageCopyOptions.OciEncryptLayers = options.OciEncryptLayers + c.imageCopyOptions.OciDecryptConfig = options.OciDecryptConfig + c.imageCopyOptions.RemoveSignatures = options.RemoveSignatures + c.imageCopyOptions.SignBy = options.SignBy + c.imageCopyOptions.ReportWriter = options.Writer + + defaultContainerConfig, err := config.Default() + if err != nil { + logrus.Warnf("failed to get container config for copy options: %v", err) + } else { + c.imageCopyOptions.MaxParallelDownloads = defaultContainerConfig.Engine.ImageParallelCopies + } + + return &c, nil +} + +// close open resources. +func (c *copier) close() error { + return c.policyContext.Destroy() +} + +// copy the source to the destination. Returns the bytes of the copied +// manifest which may be used for digest computation. +func (c *copier) copy(ctx context.Context, source, destination types.ImageReference) ([]byte, error) { + logrus.Debugf("Copying source image %s to destination image %s", source.StringWithinTransport(), destination.StringWithinTransport()) + + var err error + + if c.sourceLookup != nil { + source, err = c.sourceLookup(source) + if err != nil { + return nil, err + } + } + + if c.destinationLookup != nil { + destination, err = c.destinationLookup(destination) + if err != nil { + return nil, err + } + } + + // Buildah compat: used when running in OpenShift. + sourceInsecure, err := checkRegistrySourcesAllows(source) + if err != nil { + return nil, err + } + destinationInsecure, err := checkRegistrySourcesAllows(destination) + if err != nil { + return nil, err + } + + // Sanity checks for Buildah. + if sourceInsecure != nil && *sourceInsecure { + if c.systemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse { + return nil, errors.Errorf("can't require tls verification on an insecured registry") + } + } + if destinationInsecure != nil && *destinationInsecure { + if c.systemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse { + return nil, errors.Errorf("can't require tls verification on an insecured registry") + } + } + + var copiedManifest []byte + f := func() error { + opts := c.imageCopyOptions + if sourceInsecure != nil { + value := types.NewOptionalBool(*sourceInsecure) + opts.SourceCtx.DockerInsecureSkipTLSVerify = value + } + if destinationInsecure != nil { + value := types.NewOptionalBool(*destinationInsecure) + opts.DestinationCtx.DockerInsecureSkipTLSVerify = value + } + + var err error + copiedManifest, err = copy.Image(ctx, c.policyContext, destination, source, &opts) + return err + } + return copiedManifest, retry.RetryIfNecessary(ctx, f, &c.retryOptions) +} + +// checkRegistrySourcesAllows checks the $BUILD_REGISTRY_SOURCES environment +// variable, if it's set. The contents are expected to be a JSON-encoded +// github.com/openshift/api/config/v1.Image, set by an OpenShift build +// controller that arranged for us to be run in a container. +// +// If set, the insecure return value indicates whether the registry is set to +// be insecure. +// +// NOTE: this functionality is required by Buildah. +func checkRegistrySourcesAllows(dest types.ImageReference) (insecure *bool, err error) { + registrySources, ok := os.LookupEnv("BUILD_REGISTRY_SOURCES") + if !ok || registrySources == "" { + return nil, nil + } + + logrus.Debugf("BUILD_REGISTRY_SOURCES set %q", registrySources) + + dref := dest.DockerReference() + if dref == nil || reference.Domain(dref) == "" { + return nil, nil + } + + // Use local struct instead of github.com/openshift/api/config/v1 RegistrySources + var sources struct { + InsecureRegistries []string `json:"insecureRegistries,omitempty"` + BlockedRegistries []string `json:"blockedRegistries,omitempty"` + AllowedRegistries []string `json:"allowedRegistries,omitempty"` + } + if err := json.Unmarshal([]byte(registrySources), &sources); err != nil { + return nil, errors.Wrapf(err, "error parsing $BUILD_REGISTRY_SOURCES (%q) as JSON", registrySources) + } + blocked := false + if len(sources.BlockedRegistries) > 0 { + for _, blockedDomain := range sources.BlockedRegistries { + if blockedDomain == reference.Domain(dref) { + blocked = true + } + } + } + if blocked { + return nil, errors.Errorf("registry %q denied by policy: it is in the blocked registries list (%s)", reference.Domain(dref), registrySources) + } + allowed := true + if len(sources.AllowedRegistries) > 0 { + allowed = false + for _, allowedDomain := range sources.AllowedRegistries { + if allowedDomain == reference.Domain(dref) { + allowed = true + } + } + } + if !allowed { + return nil, errors.Errorf("registry %q denied by policy: not in allowed registries list (%s)", reference.Domain(dref), registrySources) + } + + for _, inseureDomain := range sources.InsecureRegistries { + if inseureDomain == reference.Domain(dref) { + insecure := true + return &insecure, nil + } + } + + return nil, nil +} diff --git a/vendor/github.com/containers/common/libimage/disk_usage.go b/vendor/github.com/containers/common/libimage/disk_usage.go new file mode 100644 index 000000000..edfd095a0 --- /dev/null +++ b/vendor/github.com/containers/common/libimage/disk_usage.go @@ -0,0 +1,126 @@ +package libimage + +import ( + "context" + "time" +) + +// ImageDiskUsage reports the total size of an image. That is the size +type ImageDiskUsage struct { + // Number of containers using the image. + Containers int + // ID of the image. + ID string + // Repository of the image. + Repository string + // Tag of the image. + Tag string + // Created time stamp. + Created time.Time + // The amount of space that an image shares with another one (i.e. their common data). + SharedSize int64 + // The the amount of space that is only used by a given image. + UniqueSize int64 + // Sum of shared an unique size. + Size int64 +} + +// DiskUsage calculates the disk usage for each image in the local containers +// storage. Note that a single image may yield multiple usage reports, one for +// each repository tag. +func (r *Runtime) DiskUsage(ctx context.Context) ([]ImageDiskUsage, error) { + layerTree, err := r.layerTree() + if err != nil { + return nil, err + } + + images, err := r.ListImages(ctx, nil, nil) + if err != nil { + return nil, err + } + + var allUsages []ImageDiskUsage + for _, image := range images { + usages, err := diskUsageForImage(ctx, image, layerTree) + if err != nil { + return nil, err + } + allUsages = append(allUsages, usages...) + } + return allUsages, err +} + +// diskUsageForImage returns the disk-usage baseistics for the specified image. +func diskUsageForImage(ctx context.Context, image *Image, tree *layerTree) ([]ImageDiskUsage, error) { + base := ImageDiskUsage{ + ID: image.ID(), + Created: image.Created(), + Repository: "<none>", + Tag: "<none>", + } + + // Shared, unique and total size. + parent, err := tree.parent(ctx, image) + if err != nil { + return nil, err + } + childIDs, err := tree.children(ctx, image, false) + if err != nil { + return nil, err + } + + // Optimistically set unique size to the full size of the image. + size, err := image.Size() + if err != nil { + return nil, err + } + base.UniqueSize = size + + if len(childIDs) > 0 { + // If we have children, we share everything. + base.SharedSize = base.UniqueSize + base.UniqueSize = 0 + } else if parent != nil { + // If we have no children but a parent, remove the parent + // (shared) size from the unique one. + size, err := parent.Size() + if err != nil { + return nil, err + } + base.UniqueSize -= size + base.SharedSize = size + } + + base.Size = base.SharedSize + base.UniqueSize + + // Number of containers using the image. + containers, err := image.Containers() + if err != nil { + return nil, err + } + base.Containers = len(containers) + + repoTags, err := image.NamedRepoTags() + if err != nil { + return nil, err + } + + if len(repoTags) == 0 { + return []ImageDiskUsage{base}, nil + } + + pairs, err := ToNameTagPairs(repoTags) + if err != nil { + return nil, err + } + + results := make([]ImageDiskUsage, len(pairs)) + for i, pair := range pairs { + res := base + res.Repository = pair.Name + res.Tag = pair.Tag + results[i] = res + } + + return results, nil +} diff --git a/vendor/github.com/containers/common/libimage/download.go b/vendor/github.com/containers/common/libimage/download.go new file mode 100644 index 000000000..5ea11f084 --- /dev/null +++ b/vendor/github.com/containers/common/libimage/download.go @@ -0,0 +1,46 @@ +package libimage + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/pkg/errors" +) + +// tmpdir returns a path to a temporary directory. +func (r *Runtime) tmpdir() string { + tmpdir := os.Getenv("TMPDIR") + if tmpdir == "" { + tmpdir = "/var/tmp" + } + + return tmpdir +} + +// downloadFromURL downloads an image in the format "https:/example.com/myimage.tar" +// and temporarily saves in it $TMPDIR/importxyz, which is deleted after the image is imported +func (r *Runtime) downloadFromURL(source string) (string, error) { + fmt.Printf("Downloading from %q\n", source) + + outFile, err := ioutil.TempFile(r.tmpdir(), "import") + if err != nil { + return "", errors.Wrap(err, "error creating file") + } + defer outFile.Close() + + response, err := http.Get(source) // nolint:noctx + if err != nil { + return "", errors.Wrapf(err, "error downloading %q", source) + } + defer response.Body.Close() + + _, err = io.Copy(outFile, response.Body) + if err != nil { + return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name()) + } + + return outFile.Name(), nil +} diff --git a/vendor/github.com/containers/common/libimage/events.go b/vendor/github.com/containers/common/libimage/events.go new file mode 100644 index 000000000..bca736c7b --- /dev/null +++ b/vendor/github.com/containers/common/libimage/events.go @@ -0,0 +1,43 @@ +package libimage + +import "time" + +// EventType indicates the type of an event. Currrently, there is only one +// supported type for container image but we may add more (e.g., for manifest +// lists) in the future. +type EventType int + +const ( + // EventTypeUnknow is an unitialized EventType. + EventTypeUnknown EventType = iota + // EventTypeImagePull represents an image pull. + EventTypeImagePull + // EventTypeImagePush represents an image push. + EventTypeImagePush + // EventTypeImageRemove represents an image removal. + EventTypeImageRemove + // EventTypeImageLoad represents an image being loaded. + EventTypeImageLoad + // EventTypeImageSave represents an image being saved. + EventTypeImageSave + // EventTypeImageTag represents an image being tagged. + EventTypeImageTag + // EventTypeImageUntag represents an image being untagged. + EventTypeImageUntag + // EventTypeImageMount represents an image being mounted. + EventTypeImageMount + // EventTypeImageUnmounted represents an image being unmounted. + EventTypeImageUnmount +) + +// Event represents an event such an image pull or image tag. +type Event struct { + // ID of the object (e.g., image ID). + ID string + // Name of the object (e.g., image name "quay.io/containers/podman:latest") + Name string + // Time of the event. + Time time.Time + // Type of the event. + Type EventType +} diff --git a/vendor/github.com/containers/common/libimage/filters.go b/vendor/github.com/containers/common/libimage/filters.go new file mode 100644 index 000000000..eae18fd9c --- /dev/null +++ b/vendor/github.com/containers/common/libimage/filters.go @@ -0,0 +1,228 @@ +package libimage + +import ( + "context" + "fmt" + "path/filepath" + "strconv" + "strings" + "time" + + filtersPkg "github.com/containers/common/pkg/filters" + "github.com/containers/common/pkg/timetype" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// filterFunc is a prototype for a positive image filter. Returning `true` +// indicates that the image matches the criteria. +type filterFunc func(*Image) (bool, error) + +// filterImages returns a slice of images which are passing all specified +// filters. +func filterImages(images []*Image, filters []filterFunc) ([]*Image, error) { + if len(filters) == 0 { + return images, nil + } + result := []*Image{} + for i := range images { + include := true + var err error + for _, filter := range filters { + include, err = filter(images[i]) + if err != nil { + return nil, err + } + if !include { + break + } + } + if include { + result = append(result, images[i]) + } + } + return result, nil +} + +// compileImageFilters creates `filterFunc`s for the specified filters. The +// required format is `key=value` with the following supported keys: +// after, since, before, containers, dangling, id, label, readonly, reference, intermediate +func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]filterFunc, error) { + logrus.Tracef("Parsing image filters %s", filters) + + filterFuncs := []filterFunc{} + for _, filter := range filters { + var key, value string + split := strings.SplitN(filter, "=", 2) + if len(split) != 2 { + return nil, errors.Errorf("invalid image filter %q: must be in the format %q", filter, "filter=value") + } + + key = split[0] + value = split[1] + switch key { + + case "after", "since": + img, _, err := r.LookupImage(value, nil) + if err != nil { + return nil, errors.Wrapf(err, "could not find local image for filter %q", filter) + } + filterFuncs = append(filterFuncs, filterAfter(img.Created())) + + case "before": + img, _, err := r.LookupImage(value, nil) + if err != nil { + return nil, errors.Wrapf(err, "could not find local image for filter %q", filter) + } + filterFuncs = append(filterFuncs, filterBefore(img.Created())) + + case "containers": + containers, err := strconv.ParseBool(value) + if err != nil { + return nil, errors.Wrapf(err, "non-boolean value %q for dangling filter", value) + } + filterFuncs = append(filterFuncs, filterContainers(containers)) + + case "dangling": + dangling, err := strconv.ParseBool(value) + if err != nil { + return nil, errors.Wrapf(err, "non-boolean value %q for dangling filter", value) + } + filterFuncs = append(filterFuncs, filterDangling(dangling)) + + case "id": + filterFuncs = append(filterFuncs, filterID(value)) + + case "intermediate": + intermediate, err := strconv.ParseBool(value) + if err != nil { + return nil, errors.Wrapf(err, "non-boolean value %q for intermediate filter", value) + } + filterFuncs = append(filterFuncs, filterIntermediate(ctx, intermediate)) + + case "label": + filterFuncs = append(filterFuncs, filterLabel(ctx, value)) + + case "readonly": + readOnly, err := strconv.ParseBool(value) + if err != nil { + return nil, errors.Wrapf(err, "non-boolean value %q for readonly filter", value) + } + filterFuncs = append(filterFuncs, filterReadOnly(readOnly)) + + case "reference": + filterFuncs = append(filterFuncs, filterReference(value)) + + case "until": + ts, err := timetype.GetTimestamp(value, time.Now()) + if err != nil { + return nil, err + } + seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0) + if err != nil { + return nil, err + } + until := time.Unix(seconds, nanoseconds) + filterFuncs = append(filterFuncs, filterBefore(until)) + + default: + return nil, errors.Errorf("unsupported image filter %q", key) + } + } + + return filterFuncs, nil +} + +// filterReference creates a reference filter for matching the specified value. +func filterReference(value string) filterFunc { + // Replacing all '/' with '|' so that filepath.Match() can work '|' + // character is not valid in image name, so this is safe. + // + // TODO: this has been copied from Podman and requires some more review + // and especially tests. + filter := fmt.Sprintf("*%s*", value) + filter = strings.ReplaceAll(filter, "/", "|") + return func(img *Image) (bool, error) { + if len(value) < 1 { + return true, nil + } + for _, name := range img.Names() { + newName := strings.ReplaceAll(name, "/", "|") + match, _ := filepath.Match(filter, newName) + if match { + return true, nil + } + } + return false, nil + } +} + +// filterLabel creates a label for matching the specified value. +func filterLabel(ctx context.Context, value string) filterFunc { + return func(img *Image) (bool, error) { + labels, err := img.Labels(ctx) + if err != nil { + return false, err + } + return filtersPkg.MatchLabelFilters([]string{value}, labels), nil + } +} + +// filterAfter creates an after filter for matching the specified value. +func filterAfter(value time.Time) filterFunc { + return func(img *Image) (bool, error) { + return img.Created().After(value), nil + } +} + +// filterBefore creates a before filter for matching the specified value. +func filterBefore(value time.Time) filterFunc { + return func(img *Image) (bool, error) { + return img.Created().Before(value), nil + } +} + +// filterReadOnly creates a readonly filter for matching the specified value. +func filterReadOnly(value bool) filterFunc { + return func(img *Image) (bool, error) { + return img.IsReadOnly() == value, nil + } +} + +// filterContainers creates a container filter for matching the specified value. +func filterContainers(value bool) filterFunc { + return func(img *Image) (bool, error) { + ctrs, err := img.Containers() + if err != nil { + return false, err + } + return (len(ctrs) > 0) == value, nil + } +} + +// filterDangling creates a dangling filter for matching the specified value. +func filterDangling(value bool) filterFunc { + return func(img *Image) (bool, error) { + return img.IsDangling() == value, nil + } +} + +// filterID creates an image-ID filter for matching the specified value. +func filterID(value string) filterFunc { + return func(img *Image) (bool, error) { + return img.ID() == value, nil + } +} + +// filterIntermediate creates an intermediate filter for images. An image is +// considered to be an intermediate image if it is dangling (i.e., no tags) and +// has no children (i.e., no other image depends on it). +func filterIntermediate(ctx context.Context, value bool) filterFunc { + return func(img *Image) (bool, error) { + isIntermediate, err := img.IsIntermediate(ctx) + if err != nil { + return false, err + } + return isIntermediate == value, nil + } +} diff --git a/vendor/github.com/containers/common/libimage/history.go b/vendor/github.com/containers/common/libimage/history.go new file mode 100644 index 000000000..b63fe696b --- /dev/null +++ b/vendor/github.com/containers/common/libimage/history.go @@ -0,0 +1,80 @@ +package libimage + +import ( + "context" + "time" + + "github.com/containers/storage" +) + +// ImageHistory contains the history information of an image. +type ImageHistory struct { + ID string `json:"id"` + Created *time.Time `json:"created"` + CreatedBy string `json:"createdBy"` + Size int64 `json:"size"` + Comment string `json:"comment"` + Tags []string `json:"tags"` +} + +// History computes the image history of the image including all of its parents. +func (i *Image) History(ctx context.Context) ([]ImageHistory, error) { + ociImage, err := i.toOCI(ctx) + if err != nil { + return nil, err + } + + layerTree, err := i.runtime.layerTree() + if err != nil { + return nil, err + } + + var allHistory []ImageHistory + var layer *storage.Layer + if i.TopLayer() != "" { + layer, err = i.runtime.store.Layer(i.TopLayer()) + if err != nil { + return nil, err + } + } + + // Iterate in reverse order over the history entries, and lookup the + // corresponding image ID, size and get the next later if needed. + numHistories := len(ociImage.History) - 1 + usedIDs := make(map[string]bool) // prevents assigning images IDs more than once + for x := numHistories; x >= 0; x-- { + history := ImageHistory{ + ID: "<missing>", // may be overridden below + Created: ociImage.History[x].Created, + CreatedBy: ociImage.History[x].CreatedBy, + Comment: ociImage.History[x].Comment, + } + + if layer != nil { + history.Tags = layer.Names + if !ociImage.History[x].EmptyLayer { + history.Size = layer.UncompressedSize + } + // Query the layer tree if it's the top layer of an + // image. + node := layerTree.node(layer.ID) + if len(node.images) > 0 { + id := node.images[0].ID() // always use the first one + if _, used := usedIDs[id]; !used { + history.ID = id + usedIDs[id] = true + } + } + if layer.Parent != "" && !ociImage.History[x].EmptyLayer { + layer, err = i.runtime.store.Layer(layer.Parent) + if err != nil { + return nil, err + } + } + } + + allHistory = append(allHistory, history) + } + + return allHistory, nil +} diff --git a/vendor/github.com/containers/common/libimage/image.go b/vendor/github.com/containers/common/libimage/image.go new file mode 100644 index 000000000..4728565bb --- /dev/null +++ b/vendor/github.com/containers/common/libimage/image.go @@ -0,0 +1,802 @@ +package libimage + +import ( + "context" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + storageTransport "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + "github.com/hashicorp/go-multierror" + "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// Image represents an image in the containers storage and allows for further +// operations and data manipulation. +type Image struct { + // Backwards pointer to the runtime. + runtime *Runtime + + // Counterpart in the local containers storage. + storageImage *storage.Image + + // Image reference to the containers storage. + storageReference types.ImageReference + + // All fields in the below structure are cached. They may be cleared + // at any time. When adding a new field, please make sure to clear + // it in `(*Image).reload()`. + cached struct { + // Image source. Cached for performance reasons. + imageSource types.ImageSource + // Inspect data we get from containers/image. + partialInspectData *types.ImageInspectInfo + // Fully assembled image data. + completeInspectData *ImageData + // Corresponding OCI image. + ociv1Image *ociv1.Image + } +} + +// reload the image and pessimitically clear all cached data. +func (i *Image) reload() error { + logrus.Tracef("Reloading image %s", i.ID()) + img, err := i.runtime.store.Image(i.ID()) + if err != nil { + return errors.Wrap(err, "error reloading image") + } + i.storageImage = img + i.cached.imageSource = nil + i.cached.partialInspectData = nil + i.cached.completeInspectData = nil + i.cached.ociv1Image = nil + return nil +} + +// Names returns associated names with the image which may be a mix of tags and +// digests. +func (i *Image) Names() []string { + return i.storageImage.Names +} + +// StorageImage returns the underlying storage.Image. +func (i *Image) StorageImage() *storage.Image { + return i.storageImage +} + +// NamesHistory returns a string array of names previously associated with the +// image, which may be a mixture of tags and digests. +func (i *Image) NamesHistory() []string { + return i.storageImage.NamesHistory +} + +// ID returns the ID of the image. +func (i *Image) ID() string { + return i.storageImage.ID +} + +// Digest is a digest value that we can use to locate the image, if one was +// specified at creation-time. +func (i *Image) Digest() digest.Digest { + return i.storageImage.Digest +} + +// Digests is a list of digest values of the image's manifests, and possibly a +// manually-specified value, that we can use to locate the image. If Digest is +// set, its value is also in this list. +func (i *Image) Digests() []digest.Digest { + return i.storageImage.Digests +} + +// IsReadOnly returns whether the image is set read only. +func (i *Image) IsReadOnly() bool { + return i.storageImage.ReadOnly +} + +// IsDangling returns true if the image is dangling. An image is considered +// dangling if no names are associated with it in the containers storage. +func (i *Image) IsDangling() bool { + return len(i.Names()) == 0 +} + +// IsIntermediate returns true if the image is an intermediate image, that is +// a dangling image without children. +func (i *Image) IsIntermediate(ctx context.Context) (bool, error) { + // If the image has tags, it's not an intermediate one. + if !i.IsDangling() { + return false, nil + } + children, err := i.getChildren(ctx, false) + if err != nil { + return false, err + } + // No tags, no children -> intermediate! + return len(children) != 0, nil +} + +// Created returns the time the image was created. +func (i *Image) Created() time.Time { + return i.storageImage.Created +} + +// Labels returns the label of the image. +func (i *Image) Labels(ctx context.Context) (map[string]string, error) { + data, err := i.inspectInfo(ctx) + if err != nil { + isManifestList, listErr := i.IsManifestList(ctx) + if listErr != nil { + err = errors.Wrapf(err, "fallback error checking whether image is a manifest list: %v", err) + } else if isManifestList { + logrus.Debugf("Ignoring error: cannot return labels for manifest list or image index %s", i.ID()) + return nil, nil + } + return nil, err + } + + return data.Labels, nil +} + +// TopLayer returns the top layer id as a string +func (i *Image) TopLayer() string { + return i.storageImage.TopLayer +} + +// Parent returns the parent image or nil if there is none +func (i *Image) Parent(ctx context.Context) (*Image, error) { + tree, err := i.runtime.layerTree() + if err != nil { + return nil, err + } + return tree.parent(ctx, i) +} + +// HasChildren returns indicates if the image has children. +func (i *Image) HasChildren(ctx context.Context) (bool, error) { + children, err := i.getChildren(ctx, false) + if err != nil { + return false, err + } + return len(children) > 0, nil +} + +// Children returns the image's children. +func (i *Image) Children(ctx context.Context) ([]*Image, error) { + children, err := i.getChildren(ctx, true) + if err != nil { + return nil, err + } + return children, nil +} + +// getChildren returns a list of imageIDs that depend on the image. If all is +// false, only the first child image is returned. +func (i *Image) getChildren(ctx context.Context, all bool) ([]*Image, error) { + tree, err := i.runtime.layerTree() + if err != nil { + return nil, err + } + + return tree.children(ctx, i, all) +} + +// Containers returns a list of containers using the image. +func (i *Image) Containers() ([]string, error) { + var containerIDs []string + containers, err := i.runtime.store.Containers() + if err != nil { + return nil, err + } + imageID := i.ID() + for i := range containers { + if containers[i].ImageID == imageID { + containerIDs = append(containerIDs, containers[i].ID) + } + } + return containerIDs, nil +} + +// removeContainers removes all containers using the image. +func (i *Image) removeContainers(fn RemoveContainerFunc) error { + // Execute the custom removal func if specified. + if fn != nil { + logrus.Debugf("Removing containers of image %s with custom removal function", i.ID()) + if err := fn(i.ID()); err != nil { + return err + } + } + + containers, err := i.Containers() + if err != nil { + return err + } + + logrus.Debugf("Removing containers of image %s from the local containers storage", i.ID()) + var multiE error + for _, cID := range containers { + if err := i.runtime.store.DeleteContainer(cID); err != nil { + // If the container does not exist anymore, we're good. + if errors.Cause(err) != storage.ErrContainerUnknown { + multiE = multierror.Append(multiE, err) + } + } + } + + return multiE +} + +// RemoveContainerFunc allows for customizing the removal of containers using +// an image specified by imageID. +type RemoveContainerFunc func(imageID string) error + +// RemoveImagesReport is the assembled data from removing *one* image. +type RemoveImageReport struct { + // ID of the image. + ID string + // Image was removed. + Removed bool + // Size of the removed image. Only set when explicitly requested in + // RemoveImagesOptions. + Size int64 + // The untagged tags. + Untagged []string +} + +// remove removes the image along with all dangling parent images that no other +// image depends on. The image must not be set read-only and not be used by +// containers. +// +// If the image is used by containers return storage.ErrImageUsedByContainer. +// Use force to remove these containers. +// +// NOTE: the rmMap is used to assemble image-removal data across multiple +// invocations of this function. The recursive nature requires some +// bookkeeping to make sure that all data is aggregated correctly. +// +// This function is internal. Users of libimage should always use +// `(*Runtime).RemoveImages()`. +func (i *Image) remove(ctx context.Context, rmMap map[string]*RemoveImageReport, referencedBy string, options *RemoveImagesOptions) error { + // If referencedBy is empty, the image is considered to be removed via + // `image remove --all` which alters the logic below. + + // The removal logic below is complex. There is a number of rules + // inherited from Podman and Buildah (and Docker). This function + // should be the *only* place to extend the removal logic so we keep it + // sealed in one place. Make sure to add verbose comments to leave + // some breadcrumbs for future readers. + logrus.Debugf("Removing image %s", i.ID()) + + if i.IsReadOnly() { + return errors.Errorf("cannot remove read-only image %q", i.ID()) + } + + // Check if already visisted this image. + report, exists := rmMap[i.ID()] + if exists { + // If the image has already been removed, we're done. + if report.Removed { + return nil + } + } else { + report = &RemoveImageReport{ID: i.ID()} + rmMap[i.ID()] = report + } + + // The image may have already been (partially) removed, so we need to + // have a closer look at the errors. On top, image removal should be + // tolerant toward corrupted images. + handleError := func(err error) error { + switch errors.Cause(err) { + case storage.ErrImageUnknown, storage.ErrNotAnImage, storage.ErrLayerUnknown: + // The image or layers of the image may already + // have been removed in which case we consider + // the image to be removed. + return nil + default: + return err + } + } + + // Calculate the size if requested. `podman-image-prune` likes to + // report the regained size. + if options.WithSize { + size, err := i.Size() + if handleError(err) != nil { + return err + } + report.Size = size + } + + skipRemove := false + numNames := len(i.Names()) + + // NOTE: the `numNames == 1` check is not only a performance + // optimization but also preserves exiting Podman/Docker behaviour. + // If image "foo" is used by a container and has only this tag/name, + // an `rmi foo` will not untag "foo" but instead attempt to remove the + // entire image. If there's a container using "foo", we should get an + // error. + if options.Force || referencedBy == "" || numNames == 1 { + // DO NOTHING, the image will be removed + } else { + byID := strings.HasPrefix(i.ID(), referencedBy) + byDigest := strings.HasPrefix(referencedBy, "sha256:") + if byID && numNames > 1 { + return errors.Errorf("unable to delete image %q by ID with more than one tag (%s): please force removal", i.ID(), i.Names()) + } else if byDigest && numNames > 1 { + // FIXME - Docker will remove the digest but containers storage + // does not support that yet, so our hands are tied. + return errors.Errorf("unable to delete image %q by digest with more than one tag (%s): please force removal", i.ID(), i.Names()) + } + + // Only try to untag if we know it's not an ID or digest. + if !byID && !byDigest { + if err := i.Untag(referencedBy); handleError(err) != nil { + return err + } + report.Untagged = append(report.Untagged, referencedBy) + + // If there's still tags left, we cannot delete it. + skipRemove = len(i.Names()) > 0 + } + } + + if skipRemove { + return nil + } + + // Perform the actual removal. First, remove containers if needed. + if options.Force { + if err := i.removeContainers(options.RemoveContainerFunc); err != nil { + return err + } + } + + // Podman/Docker compat: we only report an image as removed if it has + // no children. Otherwise, the data is effectively still present in the + // storage despite the image being removed. + hasChildren, err := i.HasChildren(ctx) + if err != nil { + // We must be tolerant toward corrupted images. + // See containers/podman commit fd9dd7065d44. + logrus.Warnf("error determining if an image is a parent: %v, ignoring the error", err) + hasChildren = false + } + + // If there's a dangling parent that no other image depends on, remove + // it recursively. + parent, err := i.Parent(ctx) + if err != nil { + // We must be tolerant toward corrupted images. + // See containers/podman commit fd9dd7065d44. + logrus.Warnf("error determining parent of image: %v, ignoring the error", err) + parent = nil + } + + if _, err := i.runtime.store.DeleteImage(i.ID(), true); handleError(err) != nil { + return err + } + report.Untagged = append(report.Untagged, i.Names()...) + + if !hasChildren { + report.Removed = true + } + + // Check if can remove the parent image. + if parent == nil { + return nil + } + + if !parent.IsDangling() { + return nil + } + + // If the image has siblings, we don't remove the parent. + hasSiblings, err := parent.HasChildren(ctx) + if err != nil { + // See Podman commit fd9dd7065d44: we need to + // be tolerant toward corrupted images. + logrus.Warnf("error determining if an image is a parent: %v, ignoring the error", err) + hasSiblings = false + } + if hasSiblings { + return nil + } + + // Recurse into removing the parent. + return parent.remove(ctx, rmMap, "", options) +} + +// Tag the image with the specified name and store it in the local containers +// storage. The name is normalized according to the rules of NormalizeName. +func (i *Image) Tag(name string) error { + ref, err := NormalizeName(name) + if err != nil { + return errors.Wrapf(err, "error normalizing name %q", name) + } + + logrus.Debugf("Tagging image %s with %q", i.ID(), ref.String()) + + newNames := append(i.Names(), ref.String()) + if err := i.runtime.store.SetNames(i.ID(), newNames); err != nil { + return err + } + + return i.reload() +} + +// to have some symmetry with the errors from containers/storage. +var errTagUnknown = errors.New("tag not known") + +// TODO (@vrothberg) - `docker rmi sha256:` will remove the digest from the +// image. However, that's something containers storage does not support. +var errUntagDigest = errors.New("untag by digest not supported") + +// Untag the image with the specified name and make the change persistent in +// the local containers storage. The name is normalized according to the rules +// of NormalizeName. +func (i *Image) Untag(name string) error { + if strings.HasPrefix(name, "sha256:") { + return errors.Wrap(errUntagDigest, name) + } + + ref, err := NormalizeName(name) + if err != nil { + return errors.Wrapf(err, "error normalizing name %q", name) + } + name = ref.String() + + logrus.Debugf("Untagging %q from image %s", ref.String(), i.ID()) + + removedName := false + newNames := []string{} + for _, n := range i.Names() { + if n == name { + removedName = true + continue + } + newNames = append(newNames, n) + } + + if !removedName { + return errors.Wrap(errTagUnknown, name) + } + + if err := i.runtime.store.SetNames(i.ID(), newNames); err != nil { + return err + } + + return i.reload() +} + +// RepoTags returns a string slice of repotags associated with the image. +func (i *Image) RepoTags() ([]string, error) { + namedTagged, err := i.NamedTaggedRepoTags() + if err != nil { + return nil, err + } + repoTags := make([]string, len(namedTagged)) + for i := range namedTagged { + repoTags[i] = namedTagged[i].String() + } + return repoTags, nil +} + +// NamedTaggedRepoTags returns the repotags associated with the image as a +// slice of reference.NamedTagged. +func (i *Image) NamedTaggedRepoTags() ([]reference.NamedTagged, error) { + var repoTags []reference.NamedTagged + for _, name := range i.Names() { + parsed, err := reference.Parse(name) + if err != nil { + return nil, err + } + named, isNamed := parsed.(reference.Named) + if !isNamed { + continue + } + tagged, isTagged := named.(reference.NamedTagged) + if !isTagged { + continue + } + repoTags = append(repoTags, tagged) + } + return repoTags, nil +} + +// NamedRepoTags returns the repotags associated with the image as a +// slice of reference.Named. +func (i *Image) NamedRepoTags() ([]reference.Named, error) { + var repoTags []reference.Named + for _, name := range i.Names() { + parsed, err := reference.Parse(name) + if err != nil { + return nil, err + } + if named, isNamed := parsed.(reference.Named); isNamed { + repoTags = append(repoTags, named) + } + } + return repoTags, nil +} + +// inRepoTags looks for the specified name/tag pair in the image's repo tags. +// Note that tag may be empty. +func (i *Image) inRepoTags(name, tag string) (reference.Named, error) { + repoTags, err := i.NamedRepoTags() + if err != nil { + return nil, err + } + + pairs, err := ToNameTagPairs(repoTags) + if err != nil { + return nil, err + } + + for _, pair := range pairs { + if tag != "" && tag != pair.Tag { + continue + } + if !strings.HasSuffix(pair.Name, name) { + continue + } + if len(pair.Name) == len(name) { // full match + return pair.named, nil + } + if pair.Name[len(pair.Name)-len(name)-1] == '/' { // matches at repo + return pair.named, nil + } + } + + return nil, nil +} + +// RepoDigests returns a string array of repodigests associated with the image. +func (i *Image) RepoDigests() ([]string, error) { + repoDigests := []string{} + added := make(map[string]struct{}) + + for _, name := range i.Names() { + for _, imageDigest := range append(i.Digests(), i.Digest()) { + if imageDigest == "" { + continue + } + + named, err := reference.ParseNormalizedNamed(name) + if err != nil { + return nil, err + } + + canonical, err := reference.WithDigest(reference.TrimNamed(named), imageDigest) + if err != nil { + return nil, err + } + + if _, alreadyInList := added[canonical.String()]; !alreadyInList { + repoDigests = append(repoDigests, canonical.String()) + added[canonical.String()] = struct{}{} + } + } + } + sort.Strings(repoDigests) + return repoDigests, nil +} + +// Mount the image with the specified mount options and label, both of which +// are directly passed down to the containers storage. Returns the fully +// evaluated path to the mount point. +func (i *Image) Mount(ctx context.Context, mountOptions []string, mountLabel string) (string, error) { + mountPoint, err := i.runtime.store.MountImage(i.ID(), mountOptions, mountLabel) + if err != nil { + return "", err + } + mountPoint, err = filepath.EvalSymlinks(mountPoint) + if err != nil { + return "", err + } + logrus.Debugf("Mounted image %s at %q", i.ID(), mountPoint) + return mountPoint, nil +} + +// Mountpoint returns the path to image's mount point. The path is empty if +// the image is not mounted. +func (i *Image) Mountpoint() (string, error) { + mountedTimes, err := i.runtime.store.Mounted(i.TopLayer()) + if err != nil || mountedTimes == 0 { + if errors.Cause(err) == storage.ErrLayerUnknown { + // Can happen, Podman did it, but there's no + // explanation why. + err = nil + } + return "", err + } + + layer, err := i.runtime.store.Layer(i.TopLayer()) + if err != nil { + return "", err + } + + mountPoint, err := filepath.EvalSymlinks(layer.MountPoint) + if err != nil { + return "", err + } + + return mountPoint, nil +} + +// Unmount the image. Use force to ignore the reference counter and forcefully +// unmount. +func (i *Image) Unmount(force bool) error { + logrus.Debugf("Unmounted image %s", i.ID()) + _, err := i.runtime.store.UnmountImage(i.ID(), force) + return err +} + +// MountPoint returns the fully-evaluated mount point of the image. If the +// image isn't mounted, an empty string is returned. +func (i *Image) MountPoint() (string, error) { + counter, err := i.runtime.store.Mounted(i.TopLayer()) + if err != nil { + return "", err + } + + if counter == 0 { + return "", nil + } + + layer, err := i.runtime.store.Layer(i.TopLayer()) + if err != nil { + return "", err + } + return filepath.EvalSymlinks(layer.MountPoint) +} + +// Size computes the size of the image layers and associated data. +func (i *Image) Size() (int64, error) { + return i.runtime.store.ImageSize(i.ID()) +} + +// HasDifferentDigest returns true if the image specified by `remoteRef` has a +// different digest than the local one. This check can be useful to check for +// updates on remote registries. +func (i *Image) HasDifferentDigest(ctx context.Context, remoteRef types.ImageReference) (bool, error) { + // We need to account for the arch that the image uses. It seems + // common on ARM to tweak this option to pull the correct image. See + // github.com/containers/podman/issues/6613. + inspectInfo, err := i.inspectInfo(ctx) + if err != nil { + return false, err + } + + sys := i.runtime.systemContextCopy() + sys.ArchitectureChoice = inspectInfo.Architecture + // OS and variant may not be set, so let's check to avoid accidental + // overrides of the runtime settings. + if inspectInfo.Os != "" { + sys.OSChoice = inspectInfo.Os + } + if inspectInfo.Variant != "" { + sys.VariantChoice = inspectInfo.Variant + } + + remoteImg, err := remoteRef.NewImage(ctx, sys) + if err != nil { + return false, err + } + + rawManifest, _, err := remoteImg.Manifest(ctx) + if err != nil { + return false, err + } + + remoteDigest, err := manifest.Digest(rawManifest) + if err != nil { + return false, err + } + + return i.Digest().String() != remoteDigest.String(), nil +} + +// driverData gets the driver data from the store on a layer +func (i *Image) driverData() (*DriverData, error) { + store := i.runtime.store + layerID := i.TopLayer() + driver, err := store.GraphDriver() + if err != nil { + return nil, err + } + metaData, err := driver.Metadata(layerID) + if err != nil { + return nil, err + } + if mountTimes, err := store.Mounted(layerID); mountTimes == 0 || err != nil { + delete(metaData, "MergedDir") + } + return &DriverData{ + Name: driver.String(), + Data: metaData, + }, nil +} + +// StorageReference returns the image's reference to the containers storage +// using the image ID. +func (i *Image) StorageReference() (types.ImageReference, error) { + if i.storageReference != nil { + return i.storageReference, nil + } + ref, err := storageTransport.Transport.ParseStoreReference(i.runtime.store, "@"+i.ID()) + if err != nil { + return nil, err + } + i.storageReference = ref + return ref, nil +} + +// source returns the possibly cached image reference. +func (i *Image) source(ctx context.Context) (types.ImageSource, error) { + if i.cached.imageSource != nil { + return i.cached.imageSource, nil + } + ref, err := i.StorageReference() + if err != nil { + return nil, err + } + src, err := ref.NewImageSource(ctx, i.runtime.systemContextCopy()) + if err != nil { + return nil, err + } + i.cached.imageSource = src + return src, nil +} + +// rawConfigBlob returns the image's config as a raw byte slice. Users need to +// unmarshal it to the corresponding type (OCI, Docker v2s{1,2}) +func (i *Image) rawConfigBlob(ctx context.Context) ([]byte, error) { + ref, err := i.StorageReference() + if err != nil { + return nil, err + } + + imageCloser, err := ref.NewImage(ctx, i.runtime.systemContextCopy()) + if err != nil { + return nil, err + } + defer imageCloser.Close() + + return imageCloser.ConfigBlob(ctx) +} + +// Manifest returns the raw data and the MIME type of the image's manifest. +func (i *Image) Manifest(ctx context.Context) (rawManifest []byte, mimeType string, err error) { + src, err := i.source(ctx) + if err != nil { + return nil, "", err + } + return src.GetManifest(ctx, nil) +} + +// getImageDigest creates an image object and uses the hex value of the digest as the image ID +// for parsing the store reference +func getImageDigest(ctx context.Context, src types.ImageReference, sys *types.SystemContext) (string, error) { + newImg, err := src.NewImage(ctx, sys) + if err != nil { + return "", err + } + defer func() { + if err := newImg.Close(); err != nil { + logrus.Errorf("failed to close image: %q", err) + } + }() + imageDigest := newImg.ConfigInfo().Digest + if err = imageDigest.Validate(); err != nil { + return "", errors.Wrapf(err, "error getting config info") + } + return "@" + imageDigest.Hex(), nil +} diff --git a/vendor/github.com/containers/common/libimage/image_config.go b/vendor/github.com/containers/common/libimage/image_config.go new file mode 100644 index 000000000..b57121182 --- /dev/null +++ b/vendor/github.com/containers/common/libimage/image_config.go @@ -0,0 +1,242 @@ +package libimage + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strconv" + "strings" + + "github.com/containers/common/pkg/signal" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +// ImageConfig is a wrapper around the OCIv1 Image Configuration struct exported +// by containers/image, but containing additional fields that are not supported +// by OCIv1 (but are by Docker v2) - notably OnBuild. +type ImageConfig struct { + ociv1.ImageConfig + OnBuild []string +} + +// ImageConfigFromChanges produces a v1.ImageConfig from the --change flag that +// is accepted by several Podman commands. It accepts a (limited subset) of +// Dockerfile instructions. +// Valid changes are: +// * USER +// * EXPOSE +// * ENV +// * ENTRYPOINT +// * CMD +// * VOLUME +// * WORKDIR +// * LABEL +// * STOPSIGNAL +// * ONBUILD +func ImageConfigFromChanges(changes []string) (*ImageConfig, error) { // nolint:gocyclo + config := &ImageConfig{} + + for _, change := range changes { + // First, let's assume proper Dockerfile format - space + // separator between instruction and value + split := strings.SplitN(change, " ", 2) + + if len(split) != 2 { + split = strings.SplitN(change, "=", 2) + if len(split) != 2 { + return nil, errors.Errorf("invalid change %q - must be formatted as KEY VALUE", change) + } + } + + outerKey := strings.ToUpper(strings.TrimSpace(split[0])) + value := strings.TrimSpace(split[1]) + switch outerKey { + case "USER": + // Assume literal contents are the user. + if value == "" { + return nil, errors.Errorf("invalid change %q - must provide a value to USER", change) + } + config.User = value + case "EXPOSE": + // EXPOSE is either [portnum] or + // [portnum]/[proto] + // Protocol must be "tcp" or "udp" + splitPort := strings.Split(value, "/") + if len(splitPort) > 2 { + return nil, errors.Errorf("invalid change %q - EXPOSE port must be formatted as PORT[/PROTO]", change) + } + portNum, err := strconv.Atoi(splitPort[0]) + if err != nil { + return nil, errors.Wrapf(err, "invalid change %q - EXPOSE port must be an integer", change) + } + if portNum > 65535 || portNum <= 0 { + return nil, errors.Errorf("invalid change %q - EXPOSE port must be a valid port number", change) + } + proto := "tcp" + if len(splitPort) > 1 { + testProto := strings.ToLower(splitPort[1]) + switch testProto { + case "tcp", "udp": + proto = testProto + default: + return nil, errors.Errorf("invalid change %q - EXPOSE protocol must be TCP or UDP", change) + } + } + if config.ExposedPorts == nil { + config.ExposedPorts = make(map[string]struct{}) + } + config.ExposedPorts[fmt.Sprintf("%d/%s", portNum, proto)] = struct{}{} + case "ENV": + // Format is either: + // ENV key=value + // ENV key=value key=value ... + // ENV key value + // Both keys and values can be surrounded by quotes to group them. + // For now: we only support key=value + // We will attempt to strip quotation marks if present. + + var ( + key, val string + ) + + splitEnv := strings.SplitN(value, "=", 2) + key = splitEnv[0] + // We do need a key + if key == "" { + return nil, errors.Errorf("invalid change %q - ENV must have at least one argument", change) + } + // Perfectly valid to not have a value + if len(splitEnv) == 2 { + val = splitEnv[1] + } + + if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) { + key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`) + } + if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { + val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`) + } + config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, val)) + case "ENTRYPOINT": + // Two valid forms. + // First, JSON array. + // Second, not a JSON array - we interpret this as an + // argument to `sh -c`, unless empty, in which case we + // just use a blank entrypoint. + testUnmarshal := []string{} + if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { + // It ain't valid JSON, so assume it's an + // argument to sh -c if not empty. + if value != "" { + config.Entrypoint = []string{"/bin/sh", "-c", value} + } else { + config.Entrypoint = []string{} + } + } else { + // Valid JSON + config.Entrypoint = testUnmarshal + } + case "CMD": + // Same valid forms as entrypoint. + // However, where ENTRYPOINT assumes that 'ENTRYPOINT ' + // means no entrypoint, CMD assumes it is 'sh -c' with + // no third argument. + testUnmarshal := []string{} + if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { + // It ain't valid JSON, so assume it's an + // argument to sh -c. + // Only include volume if it's not "" + config.Cmd = []string{"/bin/sh", "-c"} + if value != "" { + config.Cmd = append(config.Cmd, value) + } + } else { + // Valid JSON + config.Cmd = testUnmarshal + } + case "VOLUME": + // Either a JSON array or a set of space-separated + // paths. + // Acts rather similar to ENTRYPOINT and CMD, but always + // appends rather than replacing, and no sh -c prepend. + testUnmarshal := []string{} + if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { + // Not valid JSON, so split on spaces + testUnmarshal = strings.Split(value, " ") + } + if len(testUnmarshal) == 0 { + return nil, errors.Errorf("invalid change %q - must provide at least one argument to VOLUME", change) + } + for _, vol := range testUnmarshal { + if vol == "" { + return nil, errors.Errorf("invalid change %q - VOLUME paths must not be empty", change) + } + if config.Volumes == nil { + config.Volumes = make(map[string]struct{}) + } + config.Volumes[vol] = struct{}{} + } + case "WORKDIR": + // This can be passed multiple times. + // Each successive invocation is treated as relative to + // the previous one - so WORKDIR /A, WORKDIR b, + // WORKDIR c results in /A/b/c + // Just need to check it's not empty... + if value == "" { + return nil, errors.Errorf("invalid change %q - must provide a non-empty WORKDIR", change) + } + config.WorkingDir = filepath.Join(config.WorkingDir, value) + case "LABEL": + // Same general idea as ENV, but we no longer allow " " + // as a separator. + // We didn't do that for ENV either, so nice and easy. + // Potentially problematic: LABEL might theoretically + // allow an = in the key? If people really do this, we + // may need to investigate more advanced parsing. + var ( + key, val string + ) + + splitLabel := strings.SplitN(value, "=", 2) + // Unlike ENV, LABEL must have a value + if len(splitLabel) != 2 { + return nil, errors.Errorf("invalid change %q - LABEL must be formatted key=value", change) + } + key = splitLabel[0] + val = splitLabel[1] + + if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) { + key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`) + } + if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { + val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`) + } + // Check key after we strip quotations + if key == "" { + return nil, errors.Errorf("invalid change %q - LABEL must have a non-empty key", change) + } + if config.Labels == nil { + config.Labels = make(map[string]string) + } + config.Labels[key] = val + case "STOPSIGNAL": + // Check the provided signal for validity. + killSignal, err := signal.ParseSignal(value) + if err != nil { + return nil, errors.Wrapf(err, "invalid change %q - KILLSIGNAL must be given a valid signal", change) + } + config.StopSignal = fmt.Sprintf("%d", killSignal) + case "ONBUILD": + // Onbuild always appends. + if value == "" { + return nil, errors.Errorf("invalid change %q - ONBUILD must be given an argument", change) + } + config.OnBuild = append(config.OnBuild, value) + default: + return nil, errors.Errorf("invalid change %q - invalid instruction %s", change, outerKey) + } + } + + return config, nil +} diff --git a/vendor/github.com/containers/common/libimage/image_tree.go b/vendor/github.com/containers/common/libimage/image_tree.go new file mode 100644 index 000000000..6583a7007 --- /dev/null +++ b/vendor/github.com/containers/common/libimage/image_tree.go @@ -0,0 +1,96 @@ +package libimage + +import ( + "fmt" + "strings" + + "github.com/disiqueira/gotree/v3" + "github.com/docker/go-units" +) + +// Tree generates a tree for the specified image and its layers. Use +// `traverseChildren` to traverse the layers of all children. By default, only +// layers of the image are printed. +func (i *Image) Tree(traverseChildren bool) (string, error) { + // NOTE: a string builder prevents us from copying to much data around + // and compile the string when and where needed. + sb := &strings.Builder{} + + // First print the pretty header for the target image. + size, err := i.Size() + if err != nil { + return "", err + } + repoTags, err := i.RepoTags() + if err != nil { + return "", err + } + + fmt.Fprintf(sb, "Image ID: %s\n", i.ID()[:12]) + fmt.Fprintf(sb, "Tags: %s\n", repoTags) + fmt.Fprintf(sb, "Size: %v\n", units.HumanSizeWithPrecision(float64(size), 4)) + if i.TopLayer() != "" { + fmt.Fprintf(sb, "Image Layers") + } else { + fmt.Fprintf(sb, "No Image Layers") + } + + tree := gotree.New(sb.String()) + + layerTree, err := i.runtime.layerTree() + if err != nil { + return "", err + } + + imageNode := layerTree.node(i.TopLayer()) + + // Traverse the entire tree down to all children. + if traverseChildren { + if err := imageTreeTraverseChildren(imageNode, tree); err != nil { + return "", err + } + } else { + // Walk all layers of the image and assemlbe their data. + for parentNode := imageNode; parentNode != nil; parentNode = parentNode.parent { + if parentNode.layer == nil { + break // we're done + } + var tags string + repoTags, err := parentNode.repoTags() + if err != nil { + return "", err + } + if len(repoTags) > 0 { + tags = fmt.Sprintf(" Top Layer of: %s", repoTags) + } + tree.Add(fmt.Sprintf("ID: %s Size: %7v%s", parentNode.layer.ID[:12], units.HumanSizeWithPrecision(float64(parentNode.layer.UncompressedSize), 4), tags)) + } + } + + return tree.Print(), nil +} + +func imageTreeTraverseChildren(node *layerNode, parent gotree.Tree) error { + var tags string + repoTags, err := node.repoTags() + if err != nil { + return err + } + if len(repoTags) > 0 { + tags = fmt.Sprintf(" Top Layer of: %s", repoTags) + } + + newNode := parent.Add(fmt.Sprintf("ID: %s Size: %7v%s", node.layer.ID[:12], units.HumanSizeWithPrecision(float64(node.layer.UncompressedSize), 4), tags)) + + if len(node.children) <= 1 { + newNode = parent + } + for i := range node.children { + child := node.children[i] + if err := imageTreeTraverseChildren(child, newNode); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/containers/common/libimage/import.go b/vendor/github.com/containers/common/libimage/import.go new file mode 100644 index 000000000..4cce4c9ca --- /dev/null +++ b/vendor/github.com/containers/common/libimage/import.go @@ -0,0 +1,108 @@ +package libimage + +import ( + "context" + "net/url" + "os" + + storageTransport "github.com/containers/image/v5/storage" + tarballTransport "github.com/containers/image/v5/tarball" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// ImportOptions allow for customizing image imports. +type ImportOptions struct { + CopyOptions + + // Apply the specified changes to the created image. Please refer to + // `ImageConfigFromChanges` for supported change instructions. + Changes []string + // Set the commit message as a comment to created image's history. + CommitMessage string + // Tag the imported image with this value. + Tag string +} + +// Import imports a custom tarball at the specified path. Returns the name of +// the imported image. +func (r *Runtime) Import(ctx context.Context, path string, options *ImportOptions) (string, error) { + logrus.Debugf("Importing image from %q", path) + + if options == nil { + options = &ImportOptions{} + } + + ic := v1.ImageConfig{} + if len(options.Changes) > 0 { + config, err := ImageConfigFromChanges(options.Changes) + if err != nil { + return "", err + } + ic = config.ImageConfig + } + + hist := []v1.History{ + {Comment: options.CommitMessage}, + } + + config := v1.Image{ + Config: ic, + History: hist, + } + + u, err := url.ParseRequestURI(path) + if err == nil && u.Scheme != "" { + // If source is a URL, download the file. + file, err := r.downloadFromURL(path) + if err != nil { + return "", err + } + defer os.Remove(file) + path = file + } else if path == "-" { + // "-" special cases stdin + path = os.Stdin.Name() + } + + srcRef, err := tarballTransport.Transport.ParseReference(path) + if err != nil { + return "", err + } + + updater, ok := srcRef.(tarballTransport.ConfigUpdater) + if !ok { + return "", errors.New("unexpected type, a tarball reference should implement tarball.ConfigUpdater") + } + annotations := make(map[string]string) + if err := updater.ConfigUpdate(config, annotations); err != nil { + return "", err + } + + name := options.Tag + if name == "" { + name, err = getImageDigest(ctx, srcRef, r.systemContextCopy()) + if err != nil { + return "", err + } + name = "sha256:" + name[1:] // strip leading "@" + } + + destRef, err := storageTransport.Transport.ParseStoreReference(r.store, name) + if err != nil { + return "", err + } + + c, err := r.newCopier(&options.CopyOptions) + if err != nil { + return "", err + } + defer c.close() + + if _, err := c.copy(ctx, srcRef, destRef); err != nil { + return "", err + } + + return name, nil +} diff --git a/vendor/github.com/containers/common/libimage/inspect.go b/vendor/github.com/containers/common/libimage/inspect.go new file mode 100644 index 000000000..349709155 --- /dev/null +++ b/vendor/github.com/containers/common/libimage/inspect.go @@ -0,0 +1,206 @@ +package libimage + +import ( + "context" + "encoding/json" + "time" + + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" +) + +// ImageData contains the inspected data of an image. +type ImageData struct { + ID string `json:"Id"` + Digest digest.Digest `json:"Digest"` + RepoTags []string `json:"RepoTags"` + RepoDigests []string `json:"RepoDigests"` + Parent string `json:"Parent"` + Comment string `json:"Comment"` + Created *time.Time `json:"Created"` + Config *ociv1.ImageConfig `json:"Config"` + Version string `json:"Version"` + Author string `json:"Author"` + Architecture string `json:"Architecture"` + Os string `json:"Os"` + Size int64 `json:"Size"` + VirtualSize int64 `json:"VirtualSize"` + GraphDriver *DriverData `json:"GraphDriver"` + RootFS *RootFS `json:"RootFS"` + Labels map[string]string `json:"Labels"` + Annotations map[string]string `json:"Annotations"` + ManifestType string `json:"ManifestType"` + User string `json:"User"` + History []ociv1.History `json:"History"` + NamesHistory []string `json:"NamesHistory"` + HealthCheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"` +} + +// DriverData includes data on the storage driver of the image. +type DriverData struct { + Name string `json:"Name"` + Data map[string]string `json:"Data"` +} + +// RootFS includes data on the root filesystem of the image. +type RootFS struct { + Type string `json:"Type"` + Layers []digest.Digest `json:"Layers"` +} + +// Inspect inspects the image. Use `withSize` to also perform the +// comparatively expensive size computation of the image. +func (i *Image) Inspect(ctx context.Context, withSize bool) (*ImageData, error) { + logrus.Debugf("Inspecting image %s", i.ID()) + + if i.cached.completeInspectData != nil { + if withSize && i.cached.completeInspectData.Size == int64(-1) { + size, err := i.Size() + if err != nil { + return nil, err + } + i.cached.completeInspectData.Size = size + } + return i.cached.completeInspectData, nil + } + + // First assemble data that does not depend on the format of the image. + info, err := i.inspectInfo(ctx) + if err != nil { + return nil, err + } + ociImage, err := i.toOCI(ctx) + if err != nil { + return nil, err + } + parentImage, err := i.Parent(ctx) + if err != nil { + return nil, err + } + repoTags, err := i.RepoTags() + if err != nil { + return nil, err + } + repoDigests, err := i.RepoDigests() + if err != nil { + return nil, err + } + driverData, err := i.driverData() + if err != nil { + return nil, err + } + + size := int64(-1) + if withSize { + size, err = i.Size() + if err != nil { + return nil, err + } + } + + data := &ImageData{ + ID: i.ID(), + RepoTags: repoTags, + RepoDigests: repoDigests, + Created: ociImage.Created, + Author: ociImage.Author, + Architecture: ociImage.Architecture, + Os: ociImage.OS, + Config: &ociImage.Config, + Version: info.DockerVersion, + Size: size, + VirtualSize: size, // TODO: they should be different (inherited from Podman) + Digest: i.Digest(), + Labels: info.Labels, + RootFS: &RootFS{ + Type: ociImage.RootFS.Type, + Layers: ociImage.RootFS.DiffIDs, + }, + GraphDriver: driverData, + User: ociImage.Config.User, + History: ociImage.History, + NamesHistory: i.NamesHistory(), + } + + if parentImage != nil { + data.Parent = parentImage.ID() + } + + // Determine the format of the image. How we determine certain data + // depends on the format (e.g., Docker v2s2, OCI v1). + src, err := i.source(ctx) + if err != nil { + return nil, err + } + manifestRaw, manifestType, err := src.GetManifest(ctx, nil) + if err != nil { + return nil, err + } + + data.ManifestType = manifestType + + switch manifestType { + // OCI image + case ociv1.MediaTypeImageManifest: + var ociManifest ociv1.Manifest + if err := json.Unmarshal(manifestRaw, &ociManifest); err != nil { + return nil, err + } + data.Annotations = ociManifest.Annotations + if len(ociImage.History) > 0 { + data.Comment = ociImage.History[0].Comment + } + + // Docker image + case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema2MediaType: + rawConfig, err := i.rawConfigBlob(ctx) + if err != nil { + return nil, err + } + var dockerManifest manifest.Schema2V1Image + if err := json.Unmarshal(rawConfig, &dockerManifest); err != nil { + return nil, err + } + data.Comment = dockerManifest.Comment + data.HealthCheck = dockerManifest.ContainerConfig.Healthcheck + } + + if data.Annotations == nil { + // Podman compat + data.Annotations = make(map[string]string) + } + + i.cached.completeInspectData = data + + return data, nil +} + +// inspectInfo returns the image inspect info. +func (i *Image) inspectInfo(ctx context.Context) (*types.ImageInspectInfo, error) { + if i.cached.partialInspectData != nil { + return i.cached.partialInspectData, nil + } + + ref, err := i.StorageReference() + if err != nil { + + return nil, err + } + + img, err := ref.NewImage(ctx, i.runtime.systemContextCopy()) + if err != nil { + return nil, err + } + defer img.Close() + + data, err := img.Inspect(ctx) + if err != nil { + return nil, err + } + + i.cached.partialInspectData = data + return data, nil +} diff --git a/libpod/image/layer_tree.go b/vendor/github.com/containers/common/libimage/layer_tree.go index aa3084449..7e0940339 100644 --- a/libpod/image/layer_tree.go +++ b/vendor/github.com/containers/common/libimage/layer_tree.go @@ -1,8 +1,9 @@ -package image +package libimage import ( "context" + "github.com/containers/storage" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) @@ -31,7 +32,7 @@ func (t *layerTree) toOCI(ctx context.Context, i *Image) (*ociv1.Image, error) { var err error oci, exists := t.ociCache[i.ID()] if !exists { - oci, err = i.ociv1Image(ctx) + oci, err = i.toOCI(ctx) if err == nil { t.ociCache[i.ID()] = oci } @@ -44,17 +45,40 @@ type layerNode struct { children []*layerNode images []*Image parent *layerNode + layer *storage.Layer +} + +// repoTags assemble all repo tags all of images of the layer node. +func (l *layerNode) repoTags() ([]string, error) { + orderedTags := []string{} + visitedTags := make(map[string]bool) + + for _, image := range l.images { + repoTags, err := image.RepoTags() + if err != nil { + return nil, err + } + for _, tag := range repoTags { + if _, visted := visitedTags[tag]; visted { + continue + } + visitedTags[tag] = true + orderedTags = append(orderedTags, tag) + } + } + + return orderedTags, nil } // layerTree extracts a layerTree from the layers in the local storage and // relates them to the specified images. -func (ir *Runtime) layerTree() (*layerTree, error) { - layers, err := ir.store.Layers() +func (r *Runtime) layerTree() (*layerTree, error) { + layers, err := r.store.Layers() if err != nil { return nil, err } - images, err := ir.GetImages() + images, err := r.ListImages(context.Background(), nil, nil) if err != nil { return nil, err } @@ -65,12 +89,13 @@ func (ir *Runtime) layerTree() (*layerTree, error) { } // First build a tree purely based on layer information. - for _, layer := range layers { - node := tree.node(layer.ID) - if layer.Parent == "" { + for i := range layers { + node := tree.node(layers[i].ID) + node.layer = &layers[i] + if layers[i].Parent == "" { continue } - parent := tree.node(layer.Parent) + parent := tree.node(layers[i].Parent) node.parent = parent parent.children = append(parent.children, node) } @@ -97,18 +122,17 @@ func (ir *Runtime) layerTree() (*layerTree, error) { return &tree, nil } -// children returns the image IDs of children . Child images are images -// with either the same top layer as parent or parent being the true parent -// layer. Furthermore, the history of the parent and child images must match -// with the parent having one history item less. -// If all is true, all images are returned. Otherwise, the first image is -// returned. -func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]string, error) { +// children returns the child images of parent. Child images are images with +// either the same top layer as parent or parent being the true parent layer. +// Furthermore, the history of the parent and child images must match with the +// parent having one history item less. If all is true, all images are +// returned. Otherwise, the first image is returned. +func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]*Image, error) { if parent.TopLayer() == "" { return nil, nil } - var children []string + var children []*Image parentNode, exists := t.nodes[parent.TopLayer()] if !exists { @@ -116,7 +140,7 @@ func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]st // mistake. Users may not be able to recover, so we're now // throwing a warning to guide them to resolve the issue and // turn the errors non-fatal. - logrus.Warnf("Layer %s not found in layer. The storage may be corrupted, consider running `podman system reset`.", parent.TopLayer()) + logrus.Warnf("Layer %s not found in layer tree. The storage may be corrupted, consider running `podman system reset`.", parent.TopLayer()) return children, nil } @@ -143,14 +167,14 @@ func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]st // true if any image is a child of parent. addChildrenFromNode := func(node *layerNode) (bool, error) { foundChildren := false - for _, childImage := range node.images { + for i, childImage := range node.images { isChild, err := checkParent(childImage) if err != nil { return foundChildren, err } if isChild { foundChildren = true - children = append(children, childImage.ID()) + children = append(children, node.images[i]) if all { return foundChildren, nil } @@ -191,7 +215,7 @@ func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) { // mistake. Users may not be able to recover, so we're now // throwing a warning to guide them to resolve the issue and // turn the errors non-fatal. - logrus.Warnf("Layer %s not found in layer. The storage may be corrupted, consider running `podman system reset`.", child.TopLayer()) + logrus.Warnf("Layer %s not found in layer tree. The storage may be corrupted, consider running `podman system reset`.", child.TopLayer()) return nil, nil } @@ -223,17 +247,3 @@ func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) { return nil, nil } - -// hasChildrenAndParent returns true if the specified image has children and a -// parent. -func (t *layerTree) hasChildrenAndParent(ctx context.Context, i *Image) (bool, error) { - children, err := t.children(ctx, i, false) - if err != nil { - return false, err - } - if len(children) == 0 { - return false, nil - } - parent, err := t.parent(ctx, i) - return parent != nil, err -} diff --git a/vendor/github.com/containers/common/libimage/load.go b/vendor/github.com/containers/common/libimage/load.go new file mode 100644 index 000000000..c606aca5b --- /dev/null +++ b/vendor/github.com/containers/common/libimage/load.go @@ -0,0 +1,125 @@ +package libimage + +import ( + "context" + "errors" + "os" + + dirTransport "github.com/containers/image/v5/directory" + dockerArchiveTransport "github.com/containers/image/v5/docker/archive" + ociArchiveTransport "github.com/containers/image/v5/oci/archive" + ociTransport "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/types" + "github.com/sirupsen/logrus" +) + +type LoadOptions struct { + CopyOptions +} + +// Load loads one or more images (depending on the transport) from the +// specified path. The path may point to an image the following transports: +// oci, oci-archive, dir, docker-archive. +func (r *Runtime) Load(ctx context.Context, path string, options *LoadOptions) ([]string, error) { + logrus.Debugf("Loading image from %q", path) + + var ( + loadedImages []string + loadError error + ) + + if options == nil { + options = &LoadOptions{} + } + + for _, f := range []func() ([]string, error){ + // OCI + func() ([]string, error) { + logrus.Debugf("-> Attempting to load %q as an OCI directory", path) + ref, err := ociTransport.NewReference(path, "") + if err != nil { + return nil, err + } + return r.copyFromDefault(ctx, ref, &options.CopyOptions) + }, + + // OCI-ARCHIVE + func() ([]string, error) { + logrus.Debugf("-> Attempting to load %q as an OCI archive", path) + ref, err := ociArchiveTransport.NewReference(path, "") + if err != nil { + return nil, err + } + return r.copyFromDefault(ctx, ref, &options.CopyOptions) + }, + + // DIR + func() ([]string, error) { + logrus.Debugf("-> Attempting to load %q as a Docker dir", path) + ref, err := dirTransport.NewReference(path) + if err != nil { + return nil, err + } + return r.copyFromDefault(ctx, ref, &options.CopyOptions) + }, + + // DOCKER-ARCHIVE + func() ([]string, error) { + logrus.Debugf("-> Attempting to load %q as a Docker archive", path) + ref, err := dockerArchiveTransport.ParseReference(path) + if err != nil { + return nil, err + } + return r.loadMultiImageDockerArchive(ctx, ref, &options.CopyOptions) + }, + + // Give a decent error message if nothing above worked. + func() ([]string, error) { + return nil, errors.New("payload does not match any of the supported image formats (oci, oci-archive, dir, docker-archive)") + }, + } { + loadedImages, loadError = f() + if loadError == nil { + return loadedImages, loadError + } + logrus.Debugf("Error loading %s: %v", path, loadError) + } + + return nil, loadError +} + +// loadMultiImageDockerArchive loads the docker archive specified by ref. In +// case the path@reference notation was used, only the specifiec image will be +// loaded. Otherwise, all images will be loaded. +func (r *Runtime) loadMultiImageDockerArchive(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) { + // If we cannot stat the path, it either does not exist OR the correct + // syntax to reference an image within the archive was used, so we + // should. + path := ref.StringWithinTransport() + if _, err := os.Stat(path); err != nil { + return r.copyFromDockerArchive(ctx, ref, options) + } + + reader, err := dockerArchiveTransport.NewReader(r.systemContextCopy(), path) + if err != nil { + return nil, err + } + + refLists, err := reader.List() + if err != nil { + return nil, err + } + + var copiedImages []string + for _, list := range refLists { + for _, listRef := range list { + names, err := r.copyFromDockerArchiveReaderReference(ctx, reader, listRef, options) + if err != nil { + return nil, err + } + copiedImages = append(copiedImages, names...) + } + } + + return copiedImages, nil +} diff --git a/vendor/github.com/containers/common/libimage/manifest_list.go b/vendor/github.com/containers/common/libimage/manifest_list.go new file mode 100644 index 000000000..72a2cf55f --- /dev/null +++ b/vendor/github.com/containers/common/libimage/manifest_list.go @@ -0,0 +1,389 @@ +package libimage + +import ( + "context" + "fmt" + + "github.com/containers/common/libimage/manifests" + imageCopy "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" +) + +// NOTE: the abstractions and APIs here are a first step to further merge +// `libimage/manifests` into `libimage`. + +// ManifestList represents a manifest list (Docker) or an image index (OCI) in +// the local containers storage. +type ManifestList struct { + // NOTE: the *List* suffix is intentional as the term "manifest" is + // used ambiguously across the ecosystem. It may refer to the (JSON) + // manifest of an ordinary image OR to a manifest *list* (Docker) or to + // image index (OCI). + // It's a bit more work when typing but without ambiguity. + + // The underlying image in the containers storage. + image *Image + + // The underlying manifest list. + list manifests.List +} + +// ID returns the ID of the manifest list. +func (m *ManifestList) ID() string { + return m.image.ID() +} + +// CreateManifestList creates a new empty manifest list with the specified +// name. +func (r *Runtime) CreateManifestList(name string) (*ManifestList, error) { + normalized, err := NormalizeName(name) + if err != nil { + return nil, err + } + + list := manifests.Create() + listID, err := list.SaveToImage(r.store, "", []string{normalized.String()}, manifest.DockerV2ListMediaType) + if err != nil { + return nil, err + } + + mList, err := r.LookupManifestList(listID) + if err != nil { + return nil, err + } + + return mList, nil +} + +// LookupManifestList looks up a manifest list with the specified name in the +// containers storage. +func (r *Runtime) LookupManifestList(name string) (*ManifestList, error) { + image, list, err := r.lookupManifestList(name) + if err != nil { + return nil, err + } + return &ManifestList{image: image, list: list}, nil +} + +func (r *Runtime) lookupManifestList(name string) (*Image, manifests.List, error) { + image, _, err := r.LookupImage(name, &LookupImageOptions{IgnorePlatform: true}) + if err != nil { + return nil, nil, err + } + if err := image.reload(); err != nil { + return nil, nil, err + } + list, err := image.getManifestList() + if err != nil { + return nil, nil, err + } + return image, list, nil +} + +// ToManifestList converts the image into a manifest list. An error is thrown +// if the image is no manifest list. +func (i *Image) ToManifestList() (*ManifestList, error) { + list, err := i.getManifestList() + if err != nil { + return nil, err + } + return &ManifestList{image: i, list: list}, nil +} + +// LookupInstance looks up an instance of the manifest list matching the +// specified platform. The local machine's platform is used if left empty. +func (m *ManifestList) LookupInstance(ctx context.Context, architecture, os, variant string) (*Image, error) { + sys := m.image.runtime.systemContextCopy() + if architecture != "" { + sys.ArchitectureChoice = architecture + } + if os != "" { + sys.OSChoice = os + } + if architecture != "" { + sys.VariantChoice = variant + } + + // Now look at the *manifest* and select a matching instance. + rawManifest, manifestType, err := m.image.Manifest(ctx) + if err != nil { + return nil, err + } + list, err := manifest.ListFromBlob(rawManifest, manifestType) + if err != nil { + return nil, err + } + instanceDigest, err := list.ChooseInstance(sys) + if err != nil { + return nil, err + } + + allImages, err := m.image.runtime.ListImages(ctx, nil, nil) + if err != nil { + return nil, err + } + + for _, image := range allImages { + for _, imageDigest := range append(image.Digests(), image.Digest()) { + if imageDigest == instanceDigest { + return image, nil + } + } + } + + return nil, errors.Wrapf(storage.ErrImageUnknown, "could not find image instance %s of manifest list %s in local containers storage", instanceDigest, m.ID()) +} + +// Saves the specified manifest list and reloads it from storage with the new ID. +func (m *ManifestList) saveAndReload() error { + newID, err := m.list.SaveToImage(m.image.runtime.store, m.image.ID(), nil, "") + if err != nil { + return err + } + + // Make sure to reload the image from the containers storage to fetch + // the latest data (e.g., new or delete digests). + if err := m.image.reload(); err != nil { + return err + } + image, list, err := m.image.runtime.lookupManifestList(newID) + if err != nil { + return err + } + m.image = image + m.list = list + return nil +} + +// getManifestList is a helper to obtain a manifest list +func (i *Image) getManifestList() (manifests.List, error) { + _, list, err := manifests.LoadFromImage(i.runtime.store, i.ID()) + return list, err +} + +// IsManifestList returns true if the image is a manifest list (Docker) or an +// image index (OCI). This information may be critical to make certain +// execution paths more robust (e.g., suppress certain errors). +func (i *Image) IsManifestList(ctx context.Context) (bool, error) { + ref, err := i.StorageReference() + if err != nil { + return false, err + } + imgRef, err := ref.NewImageSource(ctx, i.runtime.systemContextCopy()) + if err != nil { + return false, err + } + _, manifestType, err := imgRef.GetManifest(ctx, nil) + if err != nil { + return false, err + } + return manifest.MIMETypeIsMultiImage(manifestType), nil +} + +// Inspect returns a dockerized version of the manifest list. +func (m *ManifestList) Inspect() (*manifest.Schema2List, error) { + return m.list.Docker(), nil +} + +// Options for adding a manifest list. +type ManifestListAddOptions struct { + // Add all images to the list if the to-be-added image itself is a + // manifest list. + All bool `json:"all"` + // containers-auth.json(5) file to use when authenticating against + // container registries. + AuthFilePath string + // Path to the certificates directory. + CertDirPath string + // Allow contacting registries over HTTP, or HTTPS with failed TLS + // verification. Note that this does not affect other TLS connections. + InsecureSkipTLSVerify types.OptionalBool + // Username to use when authenticating at a container registry. + Username string + // Password to use when authenticating at a container registry. + Password string +} + +// Add adds one or more manifests to the manifest list and returns the digest +// of the added instance. +func (m *ManifestList) Add(ctx context.Context, name string, options *ManifestListAddOptions) (digest.Digest, error) { + if options == nil { + options = &ManifestListAddOptions{} + } + + ref, err := alltransports.ParseImageName(name) + if err != nil { + withDocker := fmt.Sprintf("%s://%s", docker.Transport.Name(), name) + ref, err = alltransports.ParseImageName(withDocker) + if err != nil { + return "", err + } + } + + // Now massage in the copy-related options into the system context. + systemContext := m.image.runtime.systemContextCopy() + if options.AuthFilePath != "" { + systemContext.AuthFilePath = options.AuthFilePath + } + if options.CertDirPath != "" { + systemContext.DockerCertPath = options.CertDirPath + } + if options.InsecureSkipTLSVerify != types.OptionalBoolUndefined { + systemContext.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify + systemContext.OCIInsecureSkipTLSVerify = options.InsecureSkipTLSVerify == types.OptionalBoolTrue + systemContext.DockerDaemonInsecureSkipTLSVerify = options.InsecureSkipTLSVerify == types.OptionalBoolTrue + } + if options.Username != "" { + systemContext.DockerAuthConfig = &types.DockerAuthConfig{ + Username: options.Username, + Password: options.Password, + } + } + + newDigest, err := m.list.Add(ctx, systemContext, ref, options.All) + if err != nil { + return "", err + } + + // Write the changes to disk. + if err := m.saveAndReload(); err != nil { + return "", err + } + return newDigest, nil +} + +// Options for annotationg a manifest list. +type ManifestListAnnotateOptions struct { + // Add the specified annotations to the added image. + Annotations map[string]string + // Add the specified architecture to the added image. + Architecture string + // Add the specified features to the added image. + Features []string + // Add the specified OS to the added image. + OS string + // Add the specified OS features to the added image. + OSFeatures []string + // Add the specified OS version to the added image. + OSVersion string + // Add the specified variant to the added image. + Variant string +} + +// Annotate an image instance specified by `d` in the manifest list. +func (m *ManifestList) AnnotateInstance(d digest.Digest, options *ManifestListAnnotateOptions) error { + if options == nil { + return nil + } + + if len(options.OS) > 0 { + if err := m.list.SetOS(d, options.OS); err != nil { + return err + } + } + if len(options.OSVersion) > 0 { + if err := m.list.SetOSVersion(d, options.OSVersion); err != nil { + return err + } + } + if len(options.Features) > 0 { + if err := m.list.SetFeatures(d, options.Features); err != nil { + return err + } + } + if len(options.OSFeatures) > 0 { + if err := m.list.SetOSFeatures(d, options.OSFeatures); err != nil { + return err + } + } + if len(options.Architecture) > 0 { + if err := m.list.SetArchitecture(d, options.Architecture); err != nil { + return err + } + } + if len(options.Variant) > 0 { + if err := m.list.SetVariant(d, options.Variant); err != nil { + return err + } + } + if len(options.Annotations) > 0 { + if err := m.list.SetAnnotations(&d, options.Annotations); err != nil { + return err + } + } + + // Write the changes to disk. + if err := m.saveAndReload(); err != nil { + return err + } + return nil +} + +// RemoveInstance removes the instance specified by `d` from the manifest list. +// Returns the new ID of the image. +func (m *ManifestList) RemoveInstance(d digest.Digest) error { + if err := m.list.Remove(d); err != nil { + return err + } + + // Write the changes to disk. + if err := m.saveAndReload(); err != nil { + return err + } + return nil +} + +// ManifestListPushOptions allow for customizing pushing a manifest list. +type ManifestListPushOptions struct { + CopyOptions + + // For tweaking the list selection. + ImageListSelection imageCopy.ImageListSelection + // Use when selecting only specific imags. + Instances []digest.Digest +} + +// Push pushes a manifest to the specified destination. +func (m *ManifestList) Push(ctx context.Context, destination string, options *ManifestListPushOptions) (digest.Digest, error) { + if options == nil { + options = &ManifestListPushOptions{} + } + + dest, err := alltransports.ParseImageName(destination) + if err != nil { + oldErr := err + dest, err = alltransports.ParseImageName("docker://" + destination) + if err != nil { + return "", oldErr + } + } + + // NOTE: we're using the logic in copier to create a proper + // types.SystemContext. This prevents us from having an error prone + // code duplicate here. + copier, err := m.image.runtime.newCopier(&options.CopyOptions) + if err != nil { + return "", err + } + defer copier.close() + + pushOptions := manifests.PushOptions{ + Store: m.image.runtime.store, + SystemContext: copier.systemContext, + ImageListSelection: options.ImageListSelection, + Instances: options.Instances, + ReportWriter: options.Writer, + SignBy: options.SignBy, + RemoveSignatures: options.RemoveSignatures, + ManifestType: options.ManifestMIMEType, + } + + _, d, err := m.list.Push(ctx, dest, pushOptions) + return d, err +} diff --git a/vendor/github.com/containers/buildah/manifests/copy.go b/vendor/github.com/containers/common/libimage/manifests/copy.go index 7e651a46c..7e651a46c 100644 --- a/vendor/github.com/containers/buildah/manifests/copy.go +++ b/vendor/github.com/containers/common/libimage/manifests/copy.go diff --git a/vendor/github.com/containers/buildah/manifests/manifests.go b/vendor/github.com/containers/common/libimage/manifests/manifests.go index 0fe7e477b..875c2948d 100644 --- a/vendor/github.com/containers/buildah/manifests/manifests.go +++ b/vendor/github.com/containers/common/libimage/manifests/manifests.go @@ -6,8 +6,8 @@ import ( stderrors "errors" "io" - "github.com/containers/buildah/pkg/manifests" - "github.com/containers/buildah/pkg/supplemented" + "github.com/containers/common/pkg/manifests" + "github.com/containers/common/pkg/supplemented" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/image" diff --git a/vendor/github.com/containers/common/libimage/normalize.go b/vendor/github.com/containers/common/libimage/normalize.go new file mode 100644 index 000000000..03d2456de --- /dev/null +++ b/vendor/github.com/containers/common/libimage/normalize.go @@ -0,0 +1,92 @@ +package libimage + +import ( + "strings" + + "github.com/containers/image/v5/docker/reference" + "github.com/pkg/errors" +) + +// NormalizeName normalizes the provided name according to the conventions by +// Podman and Buildah. If tag and digest are missing, the "latest" tag will be +// used. If it's a short name, it will be prefixed with "localhost/". +// +// References to docker.io are normalized according to the Docker conventions. +// For instance, "docker.io/foo" turns into "docker.io/library/foo". +func NormalizeName(name string) (reference.Named, error) { + // NOTE: this code is in symmetrie with containers/image/pkg/shortnames. + ref, err := reference.Parse(name) + if err != nil { + return nil, errors.Wrapf(err, "error normalizing name %q", name) + } + + named, ok := ref.(reference.Named) + if !ok { + return nil, errors.Errorf("%q is not a named reference", name) + } + + // Enforce "localhost" if needed. + registry := reference.Domain(named) + if !(strings.ContainsAny(registry, ".:") || registry == "localhost") { + name = toLocalImageName(ref.String()) + } + + // Another parse which also makes sure that docker.io references are + // correctly normalized (e.g., docker.io/alpine to + // docker.io/library/alpine). + named, err = reference.ParseNormalizedNamed(name) + if err != nil { + return nil, err + } + + if _, hasTag := named.(reference.NamedTagged); hasTag { + return named, nil + } + if _, hasDigest := named.(reference.Digested); hasDigest { + return named, nil + } + + // Make sure to tag "latest". + return reference.TagNameOnly(named), nil +} + +// prefix the specified name with "localhost/". +func toLocalImageName(name string) string { + return "localhost/" + strings.TrimLeft(name, "/") +} + +// NameTagPair represents a RepoTag of an image. +type NameTagPair struct { + // Name of the RepoTag. Maybe "<none>". + Name string + // Tag of the RepoTag. Maybe "<none>". + Tag string + + // for internal use + named reference.Named +} + +// ToNameTagsPairs splits repoTags into name&tag pairs. +// Guaranteed to return at least one pair. +func ToNameTagPairs(repoTags []reference.Named) ([]NameTagPair, error) { + none := "<none>" + + var pairs []NameTagPair + for i, named := range repoTags { + pair := NameTagPair{ + Name: named.Name(), + Tag: none, + named: repoTags[i], + } + + if tagged, isTagged := named.(reference.NamedTagged); isTagged { + pair.Tag = tagged.Tag() + } + pairs = append(pairs, pair) + } + + if len(pairs) == 0 { + pairs = append(pairs, NameTagPair{Name: none, Tag: none}) + } + return pairs, nil +} diff --git a/vendor/github.com/containers/common/libimage/oci.go b/vendor/github.com/containers/common/libimage/oci.go new file mode 100644 index 000000000..b88d6613d --- /dev/null +++ b/vendor/github.com/containers/common/libimage/oci.go @@ -0,0 +1,97 @@ +package libimage + +import ( + "context" + + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// toOCI returns the image as OCI v1 image. +func (i *Image) toOCI(ctx context.Context) (*ociv1.Image, error) { + if i.cached.ociv1Image != nil { + return i.cached.ociv1Image, nil + } + ref, err := i.StorageReference() + if err != nil { + return nil, err + } + + img, err := ref.NewImage(ctx, i.runtime.systemContextCopy()) + if err != nil { + return nil, err + } + defer img.Close() + + return img.OCIConfig(ctx) +} + +// historiesMatch returns the number of entries in the histories which have the +// same contents +func historiesMatch(a, b []ociv1.History) int { + i := 0 + for i < len(a) && i < len(b) { + if a[i].Created != nil && b[i].Created == nil { + return i + } + if a[i].Created == nil && b[i].Created != nil { + return i + } + if a[i].Created != nil && b[i].Created != nil { + if !a[i].Created.Equal(*(b[i].Created)) { + return i + } + } + if a[i].CreatedBy != b[i].CreatedBy { + return i + } + if a[i].Author != b[i].Author { + return i + } + if a[i].Comment != b[i].Comment { + return i + } + if a[i].EmptyLayer != b[i].EmptyLayer { + return i + } + i++ + } + return i +} + +// areParentAndChild checks diff ID and history in the two images and return +// true if the second should be considered to be directly based on the first +func areParentAndChild(parent, child *ociv1.Image) bool { + // the child and candidate parent should share all of the + // candidate parent's diff IDs, which together would have + // controlled which layers were used + + // Both, child and parent, may be nil when the storage is left in an + // incoherent state. Issue #7444 describes such a case when a build + // has been killed. + if child == nil || parent == nil { + return false + } + + if len(parent.RootFS.DiffIDs) > len(child.RootFS.DiffIDs) { + return false + } + childUsesCandidateDiffs := true + for i := range parent.RootFS.DiffIDs { + if child.RootFS.DiffIDs[i] != parent.RootFS.DiffIDs[i] { + childUsesCandidateDiffs = false + break + } + } + if !childUsesCandidateDiffs { + return false + } + // the child should have the same history as the parent, plus + // one more entry + if len(parent.History)+1 != len(child.History) { + return false + } + if historiesMatch(parent.History, child.History) != len(parent.History) { + return false + } + return true +} diff --git a/vendor/github.com/containers/common/libimage/pull.go b/vendor/github.com/containers/common/libimage/pull.go new file mode 100644 index 000000000..b92a5e15e --- /dev/null +++ b/vendor/github.com/containers/common/libimage/pull.go @@ -0,0 +1,458 @@ +package libimage + +import ( + "context" + "fmt" + "io" + "strings" + + "github.com/containers/common/pkg/config" + dirTransport "github.com/containers/image/v5/directory" + dockerTransport "github.com/containers/image/v5/docker" + dockerArchiveTransport "github.com/containers/image/v5/docker/archive" + "github.com/containers/image/v5/docker/reference" + ociArchiveTransport "github.com/containers/image/v5/oci/archive" + ociTransport "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/pkg/shortnames" + storageTransport "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// PullOptions allows for custommizing image pulls. +type PullOptions struct { + CopyOptions + + // If true, all tags of the image will be pulled from the container + // registry. Only supported for the docker transport. + AllTags bool +} + +// Pull pulls the specified name. Name may refer to any of the supported +// transports from github.com/containers/image. If no transport is encoded, +// name will be treated as a reference to a registry (i.e., docker transport). +// +// Note that pullPolicy is only used when pulling from a container registry but +// it *must* be different than the default value `config.PullPolicyUnsupported`. This +// way, callers are forced to decide on the pull behaviour. The reasoning +// behind is that some (commands of some) tools have different default pull +// policies (e.g., buildah-bud versus podman-build). Making the pull-policy +// choice explicit is an attempt to prevent silent regressions. +// +// The errror is storage.ErrImageUnknown iff the pull policy is set to "never" +// and no local image has been found. This allows for an easier integration +// into some users of this package (e.g., Buildah). +func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullPolicy, options *PullOptions) ([]*Image, error) { + logrus.Debugf("Pulling image %s (policy: %s)", name, pullPolicy) + + if options == nil { + options = &PullOptions{} + } + + ref, err := alltransports.ParseImageName(name) + if err != nil { + // If the image clearly refers to a local one, we can look it up directly. + // In fact, we need to since they are not parseable. + if strings.HasPrefix(name, "sha256:") || (len(name) == 64 && !strings.Contains(name, "/.:@")) { + if pullPolicy == config.PullPolicyAlways { + return nil, errors.Errorf("pull policy is always but image has been referred to by ID (%s)", name) + } + local, _, err := r.LookupImage(name, nil) + if err != nil { + return nil, err + } + return []*Image{local}, err + } + + // If the input does not include a transport assume it refers + // to a registry. + dockerRef, dockerErr := alltransports.ParseImageName("docker://" + name) + if dockerErr != nil { + return nil, err + } + ref = dockerRef + } + + if options.AllTags && ref.Transport().Name() != dockerTransport.Transport.Name() { + return nil, errors.Errorf("pulling all tags is not supported for %s transport", ref.Transport().Name()) + } + + var ( + pulledImages []string + pullError error + ) + + // Dispatch the copy operation. + switch ref.Transport().Name() { + + // DOCKER/REGISTRY + case dockerTransport.Transport.Name(): + pulledImages, pullError = r.copyFromRegistry(ctx, ref, strings.TrimPrefix(name, "docker://"), pullPolicy, options) + + // DOCKER ARCHIVE + case dockerArchiveTransport.Transport.Name(): + pulledImages, pullError = r.copyFromDockerArchive(ctx, ref, &options.CopyOptions) + + // OCI + case ociTransport.Transport.Name(): + pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions) + + // OCI ARCHIVE + case ociArchiveTransport.Transport.Name(): + pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions) + + // DIR + case dirTransport.Transport.Name(): + pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions) + + // UNSUPPORTED + default: + return nil, errors.Errorf("unsupported transport %q for pulling", ref.Transport().Name()) + } + + if pullError != nil { + return nil, pullError + } + + localImages := []*Image{} + lookupOptions := &LookupImageOptions{IgnorePlatform: true} + for _, name := range pulledImages { + local, _, err := r.LookupImage(name, lookupOptions) + if err != nil { + return nil, errors.Wrapf(err, "error locating pulled image %q name in containers storage", name) + } + localImages = append(localImages, local) + } + + return localImages, pullError +} + +// copyFromDefault is the default copier for a number of transports. Other +// transports require some specific dancing, sometimes Yoga. +func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) { + c, err := r.newCopier(options) + if err != nil { + return nil, err + } + defer c.close() + + // Figure out a name for the storage destination. + var storageName, imageName string + switch ref.Transport().Name() { + + case ociTransport.Transport.Name(): + split := strings.SplitN(ref.StringWithinTransport(), ":", 2) + storageName = toLocalImageName(split[0]) + imageName = storageName + + case ociArchiveTransport.Transport.Name(): + manifest, err := ociArchiveTransport.LoadManifestDescriptor(ref) + if err != nil { + return nil, err + } + // if index.json has no reference name, compute the image digest instead + if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { + storageName, err = getImageDigest(ctx, ref, nil) + if err != nil { + return nil, err + } + imageName = "sha256:" + storageName[1:] + } else { + storageName = manifest.Annotations["org.opencontainers.image.ref.name"] + imageName = storageName + } + + default: + storageName = toLocalImageName(ref.StringWithinTransport()) + imageName = storageName + } + + // Create a storage reference. + destRef, err := storageTransport.Transport.ParseStoreReference(r.store, storageName) + if err != nil { + return nil, err + } + + _, err = c.copy(ctx, ref, destRef) + return []string{imageName}, err +} + +// storageReferencesFromArchiveReader returns a slice of image references inside the +// archive reader. A docker archive may include more than one image and this +// method allows for extracting them into containers storage references which +// can later be used from copying. +func (r *Runtime) storageReferencesReferencesFromArchiveReader(ctx context.Context, readerRef types.ImageReference, reader *dockerArchiveTransport.Reader) ([]types.ImageReference, []string, error) { + destNames, err := reader.ManifestTagsForReference(readerRef) + if err != nil { + return nil, nil, err + } + + var imageNames []string + if len(destNames) == 0 { + destName, err := getImageDigest(ctx, readerRef, &r.systemContext) + if err != nil { + return nil, nil, err + } + destNames = append(destNames, destName) + // Make sure the image can be loaded after the pull by + // replacing the @ with sha256:. + imageNames = append(imageNames, "sha256:"+destName[1:]) + } else { + for i := range destNames { + ref, err := NormalizeName(destNames[i]) + if err != nil { + return nil, nil, err + } + destNames[i] = ref.String() + } + imageNames = destNames + } + + references := []types.ImageReference{} + for _, destName := range destNames { + destRef, err := storageTransport.Transport.ParseStoreReference(r.store, destName) + if err != nil { + return nil, nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName) + } + references = append(references, destRef) + } + + return references, imageNames, nil +} + +// copyFromDockerArchive copies one image from the specified reference. +func (r *Runtime) copyFromDockerArchive(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) { + // There may be more than one image inside the docker archive, so we + // need a quick glimpse inside. + reader, readerRef, err := dockerArchiveTransport.NewReaderForReference(&r.systemContext, ref) + if err != nil { + return nil, err + } + + return r.copyFromDockerArchiveReaderReference(ctx, reader, readerRef, options) +} + +// copyFromDockerArchiveReaderReference copies the specified readerRef from reader. +func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, reader *dockerArchiveTransport.Reader, readerRef types.ImageReference, options *CopyOptions) ([]string, error) { + c, err := r.newCopier(options) + if err != nil { + return nil, err + } + defer c.close() + + // Get a slice of storage references we can copy. + references, destNames, err := r.storageReferencesReferencesFromArchiveReader(ctx, readerRef, reader) + if err != nil { + return nil, err + } + + // Now copy all of the images. Use readerRef for performance. + for _, destRef := range references { + if _, err := c.copy(ctx, readerRef, destRef); err != nil { + return nil, err + } + } + + return destNames, nil +} + +// copyFromRegistry pulls the specified, possibly unqualified, name from a +// registry. On successful pull it returns the used fully-qualified name that +// can later be used to look up the image in the local containers storage. +// +// If options.All is set, all tags from the specified registry will be pulled. +func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference, inputName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) { + // Sanity check. + if err := pullPolicy.Validate(); err != nil { + return nil, err + } + + if !options.AllTags { + return r.copySingleImageFromRegistry(ctx, inputName, pullPolicy, options) + } + + named := reference.TrimNamed(ref.DockerReference()) + tags, err := dockerTransport.GetRepositoryTags(ctx, &r.systemContext, ref) + if err != nil { + return nil, err + } + + pulledTags := []string{} + for _, tag := range tags { + select { // Let's be gentle with Podman remote. + case <-ctx.Done(): + return nil, errors.Errorf("pulling cancelled") + default: + // We can continue. + } + tagged, err := reference.WithTag(named, tag) + if err != nil { + return nil, errors.Wrapf(err, "error creating tagged reference (name %s, tag %s)", named.String(), tag) + } + pulled, err := r.copySingleImageFromRegistry(ctx, tagged.String(), pullPolicy, options) + if err != nil { + return nil, err + } + pulledTags = append(pulledTags, pulled...) + } + + return pulledTags, nil +} + +// copySingleImageFromRegistry pulls the specified, possibly unqualified, name +// from a registry. On successful pull it returns the used fully-qualified +// name that can later be used to look up the image in the local containers +// storage. +func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) { + // Sanity check. + if err := pullPolicy.Validate(); err != nil { + return nil, err + } + + var ( + localImage *Image + resolvedImageName string + err error + ) + + // Always check if there's a local image. If, we should use it's + // resolved name for pulling. Assume we're doing a `pull foo`. + // If there's already a local image "localhost/foo", then we should + // attempt pulling that instead of doing the full short-name dance. + localImage, resolvedImageName, err = r.LookupImage(imageName, nil) + if err != nil && errors.Cause(err) != storage.ErrImageUnknown { + return nil, errors.Wrap(err, "error looking up local image") + } + + if pullPolicy == config.PullPolicyNever { + if localImage != nil { + logrus.Debugf("Pull policy %q but no local image has been found for %s", pullPolicy, imageName) + return []string{resolvedImageName}, nil + } + logrus.Debugf("Pull policy %q and %s resolved to local image %s", pullPolicy, imageName, resolvedImageName) + return nil, errors.Wrap(storage.ErrImageUnknown, imageName) + } + + if pullPolicy == config.PullPolicyMissing && localImage != nil { + return []string{resolvedImageName}, nil + } + + // If we looked up the image by ID, we cannot really pull from anywhere. + if localImage != nil && strings.HasPrefix(localImage.ID(), imageName) { + switch pullPolicy { + case config.PullPolicyAlways: + return nil, errors.Errorf("pull policy is always but image has been referred to by ID (%s)", imageName) + default: + return []string{resolvedImageName}, nil + } + } + + // If we found a local image, we should use it's locally resolved name + // (see containers/buildah #2904). + if localImage != nil { + if imageName != resolvedImageName { + logrus.Debugf("Image %s resolved to local image %s which will be used for pulling", imageName, resolvedImageName) + } + imageName = resolvedImageName + } + + sys := r.systemContextCopy() + resolved, err := shortnames.Resolve(sys, imageName) + if err != nil { + return nil, err + } + + // NOTE: Below we print the description from the short-name resolution. + // In theory we could print it here. In practice, however, this is + // causing a hard time for Buildah uses who are doing a `buildah from + // image` and expect just the container name to be printed if the image + // is present locally. + // The pragmatic solution is to only print the description when we found + // a _newer_ image that we're about to pull. + wroteDesc := false + writeDesc := func() error { + if wroteDesc { + return nil + } + wroteDesc = true + if desc := resolved.Description(); len(desc) > 0 { + logrus.Debug(desc) + if options.Writer != nil { + if _, err := options.Writer.Write([]byte(desc + "\n")); err != nil { + return err + } + } + } + return nil + } + + c, err := r.newCopier(&options.CopyOptions) + if err != nil { + return nil, err + } + defer c.close() + + var pullErrors []error + for _, candidate := range resolved.PullCandidates { + candidateString := candidate.Value.String() + logrus.Debugf("Attempting to pull candidate %s for %s", candidateString, imageName) + srcRef, err := dockerTransport.NewReference(candidate.Value) + if err != nil { + return nil, err + } + + if pullPolicy == config.PullPolicyNewer && localImage != nil { + isNewer, err := localImage.HasDifferentDigest(ctx, srcRef) + if err != nil { + pullErrors = append(pullErrors, err) + continue + } + + if !isNewer { + logrus.Debugf("Skipping pull candidate %s as the image is not newer (pull policy %s)", candidateString, pullPolicy) + continue + } + } + + destRef, err := storageTransport.Transport.ParseStoreReference(r.store, candidate.Value.String()) + if err != nil { + return nil, err + } + + if err := writeDesc(); err != nil { + return nil, err + } + if options.Writer != nil { + if _, err := io.WriteString(options.Writer, fmt.Sprintf("Trying to pull %s...\n", candidateString)); err != nil { + return nil, err + } + } + if _, err := c.copy(ctx, srcRef, destRef); err != nil { + logrus.Debugf("Error pulling candidate %s: %v", candidateString, err) + pullErrors = append(pullErrors, err) + continue + } + if err := candidate.Record(); err != nil { + // Only log the recording errors. Podman has seen + // reports where users set most of the system to + // read-only which can cause issues. + logrus.Errorf("Error recording short-name alias %q: %v", candidateString, err) + } + + logrus.Debugf("Pulled candidate %s successfully", candidateString) + return []string{candidate.Value.String()}, nil + } + + if localImage != nil && pullPolicy == config.PullPolicyNewer { + return []string{resolvedImageName}, nil + } + + if len(pullErrors) == 0 { + return nil, errors.Errorf("internal error: no image pulled (pull policy %s)", pullPolicy) + } + + return nil, resolved.FormatPullErrors(pullErrors) +} diff --git a/vendor/github.com/containers/common/libimage/push.go b/vendor/github.com/containers/common/libimage/push.go new file mode 100644 index 000000000..8ff5d5ffd --- /dev/null +++ b/vendor/github.com/containers/common/libimage/push.go @@ -0,0 +1,83 @@ +package libimage + +import ( + "context" + + dockerArchiveTransport "github.com/containers/image/v5/docker/archive" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/transports/alltransports" + "github.com/sirupsen/logrus" +) + +// PushOptions allows for custommizing image pushes. +type PushOptions struct { + CopyOptions +} + +// Push pushes the specified source which must refer to an image in the local +// containers storage. It may or may not have the `containers-storage:` +// prefix. Use destination to push to a custom destination. The destination +// can refer to any supported transport. If not transport is specified, the +// docker transport (i.e., a registry) is implied. If destination is left +// empty, the docker destination will be extrapolated from the source. +// +// Return storage.ErrImageUnknown if source could not be found in the local +// containers storage. +func (r *Runtime) Push(ctx context.Context, source, destination string, options *PushOptions) ([]byte, error) { + if options == nil { + options = &PushOptions{} + } + + // Look up the local image. + image, resolvedSource, err := r.LookupImage(source, nil) + if err != nil { + return nil, err + } + + srcRef, err := image.StorageReference() + if err != nil { + return nil, err + } + + // Make sure we have a proper destination, and parse it into an image + // reference for copying. + if destination == "" { + // Doing an ID check here is tempting but false positives (due + // to a short partial IDs) are more painful than false + // negatives. + destination = resolvedSource + } + + logrus.Debugf("Pushing image %s to %s", source, destination) + + destRef, err := alltransports.ParseImageName(destination) + if err != nil { + // If the input does not include a transport assume it refers + // to a registry. + dockerRef, dockerErr := alltransports.ParseImageName("docker://" + destination) + if dockerErr != nil { + return nil, err + } + destRef = dockerRef + } + + // Buildah compat: Make sure to tag the destination image if it's a + // Docker archive. This way, we preseve the image name. + if destRef.Transport().Name() == dockerArchiveTransport.Transport.Name() { + if named, err := reference.ParseNamed(resolvedSource); err == nil { + tagged, isTagged := named.(reference.NamedTagged) + if isTagged { + options.dockerArchiveAdditionalTags = []reference.NamedTagged{tagged} + } + } + } + + c, err := r.newCopier(&options.CopyOptions) + if err != nil { + return nil, err + } + + defer c.close() + + return c.copy(ctx, srcRef, destRef) +} diff --git a/vendor/github.com/containers/common/libimage/runtime.go b/vendor/github.com/containers/common/libimage/runtime.go new file mode 100644 index 000000000..4e6bd2cf2 --- /dev/null +++ b/vendor/github.com/containers/common/libimage/runtime.go @@ -0,0 +1,573 @@ +package libimage + +import ( + "context" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/shortnames" + storageTransport "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + deepcopy "github.com/jinzhu/copier" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// RuntimeOptions allow for creating a customized Runtime. +type RuntimeOptions struct { + SystemContext *types.SystemContext +} + +// setRegistriesConfPath sets the registries.conf path for the specified context. +func setRegistriesConfPath(systemContext *types.SystemContext) { + if systemContext.SystemRegistriesConfPath != "" { + return + } + if envOverride, ok := os.LookupEnv("CONTAINERS_REGISTRIES_CONF"); ok { + systemContext.SystemRegistriesConfPath = envOverride + return + } + if envOverride, ok := os.LookupEnv("REGISTRIES_CONFIG_PATH"); ok { + systemContext.SystemRegistriesConfPath = envOverride + return + } +} + +// Runtime is responsible for image management and storing them in a containers +// storage. +type Runtime struct { + // Underlying storage store. + store storage.Store + // Global system context. No pointer to simplify copying and modifying + // it. + systemContext types.SystemContext +} + +// Returns a copy of the runtime's system context. +func (r *Runtime) systemContextCopy() *types.SystemContext { + var sys types.SystemContext + deepcopy.Copy(&sys, &r.systemContext) + return &sys +} + +// RuntimeFromStore returns a Runtime for the specified store. +func RuntimeFromStore(store storage.Store, options *RuntimeOptions) (*Runtime, error) { + if options == nil { + options = &RuntimeOptions{} + } + + var systemContext types.SystemContext + if options.SystemContext != nil { + systemContext = *options.SystemContext + } else { + systemContext = types.SystemContext{} + } + + setRegistriesConfPath(&systemContext) + + if systemContext.BlobInfoCacheDir == "" { + systemContext.BlobInfoCacheDir = filepath.Join(store.GraphRoot(), "cache") + } + + return &Runtime{ + store: store, + systemContext: systemContext, + }, nil +} + +// RuntimeFromStoreOptions returns a return for the specified store options. +func RuntimeFromStoreOptions(runtimeOptions *RuntimeOptions, storeOptions *storage.StoreOptions) (*Runtime, error) { + if storeOptions == nil { + storeOptions = &storage.StoreOptions{} + } + store, err := storage.GetStore(*storeOptions) + if err != nil { + return nil, err + } + storageTransport.Transport.SetStore(store) + return RuntimeFromStore(store, runtimeOptions) +} + +// Shutdown attempts to free any kernel resources which are being used by the +// underlying driver. If "force" is true, any mounted (i.e., in use) layers +// are unmounted beforehand. If "force" is not true, then layers being in use +// is considered to be an error condition. +func (r *Runtime) Shutdown(force bool) error { + _, err := r.store.Shutdown(force) + return err +} + +// storageToImage transforms a storage.Image to an Image. +func (r *Runtime) storageToImage(storageImage *storage.Image, ref types.ImageReference) *Image { + return &Image{ + runtime: r, + storageImage: storageImage, + storageReference: ref, + } +} + +// Exists returns true if the specicifed image exists in the local containers +// storage. +func (r *Runtime) Exists(name string) (bool, error) { + image, _, err := r.LookupImage(name, &LookupImageOptions{IgnorePlatform: true}) + if err != nil && errors.Cause(err) != storage.ErrImageUnknown { + return false, err + } + return image != nil, nil +} + +// LookupImageOptions allow for customizing local image lookups. +type LookupImageOptions struct { + // If set, the image will be purely looked up by name. No matching to + // the current platform will be performed. This can be helpful when + // the platform does not matter, for instance, for image removal. + IgnorePlatform bool +} + +// Lookup Image looks up `name` in the local container storage matching the +// specified SystemContext. Returns the image and the name it has been found +// with. Note that name may also use the `containers-storage:` prefix used to +// refer to the containers-storage transport. Returns storage.ErrImageUnknown +// if the image could not be found. +// +// If the specified name uses the `containers-storage` transport, the resolved +// name is empty. +func (r *Runtime) LookupImage(name string, options *LookupImageOptions) (*Image, string, error) { + logrus.Debugf("Looking up image %q in local containers storage", name) + + if options == nil { + options = &LookupImageOptions{} + } + + // If needed extract the name sans transport. + storageRef, err := alltransports.ParseImageName(name) + if err == nil { + if storageRef.Transport().Name() != storageTransport.Transport.Name() { + return nil, "", errors.Errorf("unsupported transport %q for looking up local images", storageRef.Transport().Name()) + } + img, err := storageTransport.Transport.GetStoreImage(r.store, storageRef) + if err != nil { + return nil, "", err + } + logrus.Debugf("Found image %q in local containers storage (%s)", name, storageRef.StringWithinTransport()) + return r.storageToImage(img, storageRef), "", nil + } + + originalName := name + idByDigest := false + if strings.HasPrefix(name, "sha256:") { + // Strip off the sha256 prefix so it can be parsed later on. + idByDigest = true + name = strings.TrimPrefix(name, "sha256:") + } + + // First, check if we have an exact match in the storage. Maybe an ID + // or a fully-qualified image name. + img, err := r.lookupImageInLocalStorage(name, name, options) + if err != nil { + return nil, "", err + } + if img != nil { + return img, originalName, nil + } + + // If the name clearly referred to a local image, there's nothing we can + // do anymore. + if storageRef != nil || idByDigest { + return nil, "", errors.Wrap(storage.ErrImageUnknown, originalName) + } + + // Second, try out the candidates as resolved by shortnames. This takes + // "localhost/" prefixed images into account as well. + candidates, err := shortnames.ResolveLocally(&r.systemContext, name) + if err != nil { + return nil, "", errors.Wrap(storage.ErrImageUnknown, originalName) + } + // Backwards compat: normalize to docker.io as some users may very well + // rely on that. + if dockerNamed, err := reference.ParseDockerRef(name); err == nil { + candidates = append(candidates, dockerNamed) + } + + for _, candidate := range candidates { + img, err := r.lookupImageInLocalStorage(name, candidate.String(), options) + if err != nil { + return nil, "", err + } + if img != nil { + return img, candidate.String(), err + } + } + + return r.lookupImageInDigestsAndRepoTags(originalName, options) +} + +// lookupImageInLocalStorage looks up the specified candidate for name in the +// storage and checks whether it's matching the system context. +func (r *Runtime) lookupImageInLocalStorage(name, candidate string, options *LookupImageOptions) (*Image, error) { + logrus.Debugf("Trying %q ...", candidate) + img, err := r.store.Image(candidate) + if err != nil && errors.Cause(err) != storage.ErrImageUnknown { + return nil, err + } + if img == nil { + return nil, nil + } + ref, err := storageTransport.Transport.ParseStoreReference(r.store, img.ID) + if err != nil { + return nil, err + } + + image := r.storageToImage(img, ref) + if options.IgnorePlatform { + logrus.Debugf("Found image %q as %q in local containers storage", name, candidate) + return image, nil + } + + // If we referenced a manifest list, we need to check whether we can + // find a matching instance in the local containers storage. + isManifestList, err := image.IsManifestList(context.Background()) + if err != nil { + return nil, err + } + if isManifestList { + manifestList, err := image.ToManifestList() + if err != nil { + return nil, err + } + image, err = manifestList.LookupInstance(context.Background(), "", "", "") + if err != nil { + return nil, err + } + ref, err = storageTransport.Transport.ParseStoreReference(r.store, "@"+image.ID()) + if err != nil { + return nil, err + } + } + + matches, err := imageReferenceMatchesContext(context.Background(), ref, &r.systemContext) + if err != nil { + return nil, err + } + + // NOTE: if the user referenced by ID we must optimistically assume + // that they know what they're doing. Given, we already did the + // manifest limbo above, we may already have resolved it. + if !matches && !strings.HasPrefix(image.ID(), candidate) { + return nil, nil + } + // Also print the string within the storage transport. That may aid in + // debugging when using additional stores since we see explicitly where + // the store is and which driver (options) are used. + logrus.Debugf("Found image %q as %q in local containers storage (%s)", name, candidate, ref.StringWithinTransport()) + return image, nil +} + +// lookupImageInDigestsAndRepoTags attempts to match name against any image in +// the local containers storage. If name is digested, it will be compared +// against image digests. Otherwise, it will be looked up in the repo tags. +func (r *Runtime) lookupImageInDigestsAndRepoTags(name string, options *LookupImageOptions) (*Image, string, error) { + // Until now, we've tried very hard to find an image but now it is time + // for limbo. If the image includes a digest that we couldn't detect + // verbatim in the storage, we must have a look at all digests of all + // images. Those may change over time (e.g., via manifest lists). + // Both Podman and Buildah want us to do that dance. + allImages, err := r.ListImages(context.Background(), nil, nil) + if err != nil { + return nil, "", err + } + + if !shortnames.IsShortName(name) { + named, err := reference.ParseNormalizedNamed(name) + if err != nil { + return nil, "", err + } + digested, hasDigest := named.(reference.Digested) + if !hasDigest { + return nil, "", errors.Wrap(storage.ErrImageUnknown, name) + } + + logrus.Debug("Looking for image with matching recorded digests") + digest := digested.Digest() + for _, image := range allImages { + for _, d := range image.Digests() { + if d == digest { + return image, name, nil + } + } + } + + return nil, "", errors.Wrap(storage.ErrImageUnknown, name) + } + + // Podman compat: if we're looking for a short name but couldn't + // resolve it via the registries.conf dance, we need to look at *all* + // images and check if the name we're looking for matches a repo tag. + // Split the name into a repo/tag pair + split := strings.SplitN(name, ":", 2) + repo := split[0] + tag := "" + if len(split) == 2 { + tag = split[1] + } + for _, image := range allImages { + named, err := image.inRepoTags(repo, tag) + if err != nil { + return nil, "", err + } + if named == nil { + continue + } + img, err := r.lookupImageInLocalStorage(name, named.String(), options) + if err != nil { + return nil, "", err + } + if img != nil { + return img, named.String(), err + } + } + + return nil, "", errors.Wrap(storage.ErrImageUnknown, name) +} + +// ResolveName resolves the specified name. If the name resolves to a local +// image, the fully resolved name will be returned. Otherwise, the name will +// be properly normalized. +// +// Note that an empty string is returned as is. +func (r *Runtime) ResolveName(name string) (string, error) { + if name == "" { + return "", nil + } + image, resolvedName, err := r.LookupImage(name, &LookupImageOptions{IgnorePlatform: true}) + if err != nil && errors.Cause(err) != storage.ErrImageUnknown { + return "", err + } + + if image != nil && !strings.HasPrefix(image.ID(), resolvedName) { + return resolvedName, err + } + + normalized, err := NormalizeName(name) + if err != nil { + return "", err + } + + return normalized.String(), nil +} + +// imageReferenceMatchesContext return true if the specified reference matches +// the platform (os, arch, variant) as specified by the system context. +func imageReferenceMatchesContext(ctx context.Context, ref types.ImageReference, sys *types.SystemContext) (bool, error) { + if sys == nil { + return true, nil + } + img, err := ref.NewImage(ctx, sys) + if err != nil { + return false, err + } + defer img.Close() + data, err := img.Inspect(ctx) + if err != nil { + return false, err + } + osChoice := sys.OSChoice + if osChoice == "" { + osChoice = runtime.GOOS + } + arch := sys.ArchitectureChoice + if arch == "" { + arch = runtime.GOARCH + } + if osChoice == data.Os && arch == data.Architecture { + if sys.VariantChoice == "" || sys.VariantChoice == data.Variant { + return true, nil + } + } + return false, nil +} + +// ListImagesOptions allow for customizing listing images. +type ListImagesOptions struct { + // Filters to filter the listed images. Supported filters are + // * after,before,since=image + // * dangling=true,false + // * intermediate=true,false (useful for pruning images) + // * id=id + // * label=key[=value] + // * readonly=true,false + // * reference=name[:tag] (wildcards allowed) + Filters []string +} + +// ListImages lists images in the local container storage. If names are +// specified, only images with the specified names are looked up and filtered. +func (r *Runtime) ListImages(ctx context.Context, names []string, options *ListImagesOptions) ([]*Image, error) { + if options == nil { + options = &ListImagesOptions{} + } + + var images []*Image + if len(names) > 0 { + lookupOpts := LookupImageOptions{IgnorePlatform: true} + for _, name := range names { + image, _, err := r.LookupImage(name, &lookupOpts) + if err != nil { + return nil, err + } + images = append(images, image) + } + } else { + storageImages, err := r.store.Images() + if err != nil { + return nil, err + } + for i := range storageImages { + images = append(images, r.storageToImage(&storageImages[i], nil)) + } + } + + var filters []filterFunc + if len(options.Filters) > 0 { + compiledFilters, err := r.compileImageFilters(ctx, options.Filters) + if err != nil { + return nil, err + } + filters = append(filters, compiledFilters...) + } + + return filterImages(images, filters) +} + +// RemoveImagesOptions allow for customizing image removal. +type RemoveImagesOptions struct { + // Force will remove all containers from the local storage that are + // using a removed image. Use RemoveContainerFunc for a custom logic. + // If set, all child images will be removed as well. + Force bool + // RemoveContainerFunc allows for a custom logic for removing + // containers using a specific image. By default, all containers in + // the local containers storage will be removed (if Force is set). + RemoveContainerFunc RemoveContainerFunc + // Filters to filter the removed images. Supported filters are + // * after,before,since=image + // * dangling=true,false + // * intermediate=true,false (useful for pruning images) + // * id=id + // * label=key[=value] + // * readonly=true,false + // * reference=name[:tag] (wildcards allowed) + Filters []string + // The RemoveImagesReport will include the size of the removed image. + // This information may be useful when pruning images to figure out how + // much space was freed. However, computing the size of an image is + // comparatively expensive, so it is made optional. + WithSize bool +} + +// RemoveImages removes images specified by names. All images are expected to +// exist in the local containers storage. +// +// If an image has more names than one name, the image will be untagged with +// the specified name. RemoveImages returns a slice of untagged and removed +// images. +// +// Note that most errors are non-fatal and collected into `rmErrors` return +// value. +func (r *Runtime) RemoveImages(ctx context.Context, names []string, options *RemoveImagesOptions) (reports []*RemoveImageReport, rmErrors []error) { + if options == nil { + options = &RemoveImagesOptions{} + } + + // The logic here may require some explanation. Image removal is + // surprisingly complex since it is recursive (intermediate parents are + // removed) and since multiple items in `names` may resolve to the + // *same* image. On top, the data in the containers storage is shared, + // so we need to be careful and the code must be robust. That is why + // users can only remove images via this function; the logic may be + // complex but the execution path is clear. + + // Bundle an image with a possible empty slice of names to untag. That + // allows for a decent untagging logic and to bundle multiple + // references to the same *Image (and circumvent consistency issues). + type deleteMe struct { + image *Image + referencedBy []string + } + + appendError := func(err error) { + rmErrors = append(rmErrors, err) + } + + orderedIDs := []string{} // determinism and relative order + deleteMap := make(map[string]*deleteMe) // ID -> deleteMe + + // Look up images in the local containers storage and fill out + // orderedIDs and the deleteMap. + switch { + case len(names) > 0: + lookupOptions := LookupImageOptions{IgnorePlatform: true} + for _, name := range names { + img, resolvedName, err := r.LookupImage(name, &lookupOptions) + if err != nil { + appendError(err) + continue + } + dm, exists := deleteMap[img.ID()] + if !exists { + orderedIDs = append(orderedIDs, img.ID()) + dm = &deleteMe{image: img} + deleteMap[img.ID()] = dm + } + dm.referencedBy = append(dm.referencedBy, resolvedName) + } + if len(orderedIDs) == 0 { + return nil, rmErrors + } + + case len(options.Filters) > 0: + filteredImages, err := r.ListImages(ctx, nil, &ListImagesOptions{Filters: options.Filters}) + if err != nil { + appendError(err) + return nil, rmErrors + } + for _, img := range filteredImages { + orderedIDs = append(orderedIDs, img.ID()) + deleteMap[img.ID()] = &deleteMe{image: img} + } + } + + // Now remove the images in the given order. + rmMap := make(map[string]*RemoveImageReport) + for _, id := range orderedIDs { + del, exists := deleteMap[id] + if !exists { + appendError(errors.Errorf("internal error: ID %s not in found in image-deletion map", id)) + continue + } + if len(del.referencedBy) == 0 { + del.referencedBy = []string{""} + } + for _, ref := range del.referencedBy { + if err := del.image.remove(ctx, rmMap, ref, options); err != nil { + appendError(err) + continue + } + } + } + + // Finally, we can assemble the reports slice. + for _, id := range orderedIDs { + report, exists := rmMap[id] + if exists { + reports = append(reports, report) + } + } + + return reports, rmErrors +} diff --git a/vendor/github.com/containers/common/libimage/save.go b/vendor/github.com/containers/common/libimage/save.go new file mode 100644 index 000000000..c03437682 --- /dev/null +++ b/vendor/github.com/containers/common/libimage/save.go @@ -0,0 +1,202 @@ +package libimage + +import ( + "context" + "strings" + + dirTransport "github.com/containers/image/v5/directory" + dockerArchiveTransport "github.com/containers/image/v5/docker/archive" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + ociArchiveTransport "github.com/containers/image/v5/oci/archive" + ociTransport "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/types" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// SaveOptions allow for customizing saving images. +type SaveOptions struct { + CopyOptions + + // AdditionalTags for the saved image. Incompatible when saving + // multiple images. + AdditionalTags []string +} + +// Save saves one or more images indicated by `names` in the specified `format` +// to `path`. Supported formats are oci-archive, docker-archive, oci-dir and +// docker-dir. The latter two adhere to the dir transport in the corresponding +// oci or docker v2s2 format. Please note that only docker-archive supports +// saving more than one images. Other formats will yield an error attempting +// to save more than one. +func (r *Runtime) Save(ctx context.Context, names []string, format, path string, options *SaveOptions) error { + logrus.Debugf("Saving one more images (%s) to %q", names, path) + + if options == nil { + options = &SaveOptions{} + } + + // First some sanity checks to simplify subsequent code. + switch len(names) { + case 0: + return errors.New("no image specified for saving images") + case 1: + // All formats support saving 1. + default: + if format != "docker-archive" { + return errors.Errorf("unspported format %q for saving multiple images (only docker-archive)", format) + } + if len(options.AdditionalTags) > 0 { + return errors.Errorf("cannot save multiple images with multiple tags") + } + } + + // Dispatch the save operations. + switch format { + case "oci-archive", "oci-dir", "docker-dir": + return r.saveSingleImage(ctx, names[0], format, path, options) + + case "docker-archive": + return r.saveDockerArchive(ctx, names, path, options) + } + + return errors.Errorf("unspported format %q for saving images", format) + +} + +// saveSingleImage saves the specified image name to the specified path. +// Supported formats are "oci-archive", "oci-dir" and "docker-dir". +func (r *Runtime) saveSingleImage(ctx context.Context, name, format, path string, options *SaveOptions) error { + image, imageName, err := r.LookupImage(name, nil) + if err != nil { + return err + } + + // Unless the image was referenced by ID, use the resolved name as a + // tag. + var tag string + if !strings.HasPrefix(image.ID(), imageName) { + tag = imageName + } + + srcRef, err := image.StorageReference() + if err != nil { + return err + } + + // Prepare the destination reference. + var destRef types.ImageReference + switch format { + case "oci-archive": + destRef, err = ociArchiveTransport.NewReference(path, tag) + + case "oci-dir": + destRef, err = ociTransport.NewReference(path, tag) + options.ManifestMIMEType = ociv1.MediaTypeImageManifest + + case "docker-dir": + destRef, err = dirTransport.NewReference(path) + options.ManifestMIMEType = manifest.DockerV2Schema2MediaType + + default: + return errors.Errorf("unspported format %q for saving images", format) + } + + if err != nil { + return err + } + + c, err := r.newCopier(&options.CopyOptions) + if err != nil { + return err + } + defer c.close() + + _, err = c.copy(ctx, srcRef, destRef) + return err +} + +// saveDockerArchive saves the specified images indicated by names to the path. +// It loads all images from the local containers storage and assembles the meta +// data needed to properly save images. Since multiple names could refer to +// the *same* image, we need to dance a bit and store additional "names". +// Those can then be used as additional tags when copying. +func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path string, options *SaveOptions) error { + type localImage struct { + image *Image + tags []reference.NamedTagged + } + + orderedIDs := []string{} // to preserve the relative order + localImages := make(map[string]*localImage) // to assemble tags + visitedNames := make(map[string]bool) // filters duplicate names + for _, name := range names { + // Look up local images. + image, imageName, err := r.LookupImage(name, nil) + if err != nil { + return err + } + // Make sure to filter duplicates purely based on the resolved + // name. + if _, exists := visitedNames[imageName]; exists { + continue + } + visitedNames[imageName] = true + // Extract and assemble the data. + local, exists := localImages[image.ID()] + if !exists { + local = &localImage{image: image} + orderedIDs = append(orderedIDs, image.ID()) + } + // Add the tag if the locally resolved name is properly tagged + // (which it should unless we looked it up by ID). + named, err := reference.ParseNamed(imageName) + if err == nil { + tagged, withTag := named.(reference.NamedTagged) + if withTag { + local.tags = append(local.tags, tagged) + } + } + localImages[image.ID()] = local + } + + writer, err := dockerArchiveTransport.NewWriter(r.systemContextCopy(), path) + if err != nil { + return err + } + defer writer.Close() + + for _, id := range orderedIDs { + local, exists := localImages[id] + if !exists { + return errors.Errorf("internal error: saveDockerArchive: ID %s not found in local map", id) + } + + copyOpts := options.CopyOptions + copyOpts.dockerArchiveAdditionalTags = local.tags + + c, err := r.newCopier(©Opts) + if err != nil { + return err + } + defer c.close() + + destRef, err := writer.NewReference(nil) + if err != nil { + return err + } + + srcRef, err := local.image.StorageReference() + if err != nil { + return err + } + + if _, err := c.copy(ctx, srcRef, destRef); err != nil { + return err + } + } + + return nil +} diff --git a/libpod/image/search.go b/vendor/github.com/containers/common/libimage/search.go index 714551e6e..b36b6d2a3 100644 --- a/libpod/image/search.go +++ b/vendor/github.com/containers/common/libimage/search.go @@ -1,4 +1,4 @@ -package image +package libimage import ( "context" @@ -7,19 +7,22 @@ import ( "strings" "sync" - "github.com/containers/image/v5/docker" + dockerTransport "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" - sysreg "github.com/containers/podman/v3/pkg/registries" + "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" ) const ( - descriptionTruncLength = 44 - maxQueries = 25 - maxParallelSearches = int64(6) + searchTruncLength = 44 + searchMaxQueries = 25 + // Let's follow Firefox by limiting parallel downloads to 6. We do the + // same when pulling images in c/image. + searchMaxParallel = int64(6) ) // SearchResult is holding image-search related data. @@ -40,7 +43,7 @@ type SearchResult struct { Tag string } -// SearchOptions are used to control the behaviour of SearchImages. +// SearchOptions customize searching images. type SearchOptions struct { // Filter allows to filter the results. Filter SearchFilter @@ -57,7 +60,7 @@ type SearchOptions struct { ListTags bool } -// SearchFilter allows filtering the results of SearchImages. +// SearchFilter allows filtering images while searching. type SearchFilter struct { // Stars describes the minimal amount of starts of an image. Stars int @@ -67,10 +70,47 @@ type SearchFilter struct { IsOfficial types.OptionalBool } -// SearchImages searches images based on term and the specified SearchOptions -// in all registries. -func SearchImages(term string, options SearchOptions) ([]SearchResult, error) { - registry := "" +// ParseSearchFilter turns the filter into a SearchFilter that can be used for +// searching images. +func ParseSearchFilter(filter []string) (*SearchFilter, error) { + sFilter := new(SearchFilter) + for _, f := range filter { + arr := strings.SplitN(f, "=", 2) + switch arr[0] { + case "stars": + if len(arr) < 2 { + return nil, errors.Errorf("invalid `stars` filter %q, should be stars=<value>", filter) + } + stars, err := strconv.Atoi(arr[1]) + if err != nil { + return nil, errors.Wrapf(err, "incorrect value type for stars filter") + } + sFilter.Stars = stars + case "is-automated": + if len(arr) == 2 && arr[1] == "false" { + sFilter.IsAutomated = types.OptionalBoolFalse + } else { + sFilter.IsAutomated = types.OptionalBoolTrue + } + case "is-official": + if len(arr) == 2 && arr[1] == "false" { + sFilter.IsOfficial = types.OptionalBoolFalse + } else { + sFilter.IsOfficial = types.OptionalBoolTrue + } + default: + return nil, errors.Errorf("invalid filter type %q", f) + } + } + return sFilter, nil +} + +func (r *Runtime) Search(ctx context.Context, term string, options *SearchOptions) ([]SearchResult, error) { + if options == nil { + options = &SearchOptions{} + } + + var searchRegistries []string // Try to extract a registry from the specified search term. We // consider everything before the first slash to be the registry. Note @@ -78,14 +118,17 @@ func SearchImages(term string, options SearchOptions) ([]SearchResult, error) { // library as the search term may container arbitrary input such as // wildcards. See bugzilla.redhat.com/show_bug.cgi?id=1846629. if spl := strings.SplitN(term, "/", 2); len(spl) > 1 { - registry = spl[0] + searchRegistries = append(searchRegistries, spl[0]) term = spl[1] + } else { + regs, err := sysregistriesv2.UnqualifiedSearchRegistries(r.systemContextCopy()) + if err != nil { + return nil, err + } + searchRegistries = regs } - registries, err := getRegistries(registry) - if err != nil { - return nil, err - } + logrus.Debugf("Searching images matching term %s at the following registries %s", term, searchRegistries) // searchOutputData is used as a return value for searching in parallel. type searchOutputData struct { @@ -93,83 +136,64 @@ func SearchImages(term string, options SearchOptions) ([]SearchResult, error) { err error } - // Let's follow Firefox by limiting parallel downloads to 6. - sem := semaphore.NewWeighted(maxParallelSearches) + sem := semaphore.NewWeighted(searchMaxParallel) wg := sync.WaitGroup{} - wg.Add(len(registries)) - data := make([]searchOutputData, len(registries)) - - searchImageInRegistryHelper := func(index int, registry string) { - defer sem.Release(1) - defer wg.Done() - searchOutput, err := searchImageInRegistry(term, registry, options) - data[index] = searchOutputData{data: searchOutput, err: err} - } + wg.Add(len(searchRegistries)) + data := make([]searchOutputData, len(searchRegistries)) - ctx := context.Background() - for i := range registries { + for i := range searchRegistries { if err := sem.Acquire(ctx, 1); err != nil { return nil, err } - go searchImageInRegistryHelper(i, registries[i]) + index := i + go func() { + defer sem.Release(1) + defer wg.Done() + searchOutput, err := r.searchImageInRegistry(ctx, term, searchRegistries[index], options) + data[index] = searchOutputData{data: searchOutput, err: err} + }() } wg.Wait() results := []SearchResult{} - var lastError error + var multiErr error for _, d := range data { if d.err != nil { - if lastError != nil { - logrus.Errorf("%v", lastError) - } - lastError = d.err + multiErr = multierror.Append(multiErr, d.err) continue } results = append(results, d.data...) } + + // Optimistically assume that one successfully searched registry + // includes what the user is looking for. if len(results) > 0 { return results, nil } - return results, lastError -} - -// getRegistries returns the list of registries to search, depending on an optional registry specification -func getRegistries(registry string) ([]string, error) { - var registries []string - if registry != "" { - registries = append(registries, registry) - } else { - var err error - registries, err = sysreg.GetRegistries() - if err != nil { - return nil, errors.Wrapf(err, "error getting registries to search") - } - } - return registries, nil + return results, multiErr } -func searchImageInRegistry(term string, registry string, options SearchOptions) ([]SearchResult, error) { +func (r *Runtime) searchImageInRegistry(ctx context.Context, term, registry string, options *SearchOptions) ([]SearchResult, error) { // Max number of queries by default is 25 - limit := maxQueries + limit := searchMaxQueries if options.Limit > 0 { limit = options.Limit } - sc := GetSystemContext("", options.Authfile, false) - sc.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify - // FIXME: Set this more globally. Probably no reason not to have it in - // every types.SystemContext, and to compute the value just once in one - // place. - sc.SystemRegistriesConfPath = sysreg.SystemRegistriesConfPath() + sys := r.systemContextCopy() + if options.InsecureSkipTLSVerify != types.OptionalBoolUndefined { + sys.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify + } + if options.ListTags { - results, err := searchRepositoryTags(registry, term, sc, options) + results, err := searchRepositoryTags(ctx, sys, registry, term, options) if err != nil { return []SearchResult{}, err } return results, nil } - results, err := docker.SearchRegistry(context.TODO(), sc, registry, term, limit) + results, err := dockerTransport.SearchRegistry(ctx, sys, registry, term, limit) if err != nil { return []SearchResult{}, err } @@ -182,7 +206,7 @@ func searchImageInRegistry(term string, registry string, options SearchOptions) // limit is the number of results to output // if the total number of results is less than the limit, output all // if the limit has been set by the user, output those number of queries - limit = maxQueries + limit = searchMaxQueries if len(results) < limit { limit = len(results) } @@ -207,9 +231,9 @@ func searchImageInRegistry(term string, registry string, options SearchOptions) if results[i].IsAutomated { automated = "[OK]" } - description := strings.Replace(results[i].Description, "\n", " ", -1) + description := strings.ReplaceAll(results[i].Description, "\n", " ") if len(description) > 44 && !options.NoTrunc { - description = description[:descriptionTruncLength] + "..." + description = description[:searchTruncLength] + "..." } name := registry + "/" + results[i].Name if index == "docker.io" && !strings.Contains(results[i].Name, "/") { @@ -228,10 +252,10 @@ func searchImageInRegistry(term string, registry string, options SearchOptions) return paramsArr, nil } -func searchRepositoryTags(registry, term string, sc *types.SystemContext, options SearchOptions) ([]SearchResult, error) { - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) +func searchRepositoryTags(ctx context.Context, sys *types.SystemContext, registry, term string, options *SearchOptions) ([]SearchResult, error) { + dockerPrefix := "docker://" imageRef, err := alltransports.ParseImageName(fmt.Sprintf("%s/%s", registry, term)) - if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { + if err == nil && imageRef.Transport().Name() != dockerTransport.Transport.Name() { return nil, errors.Errorf("reference %q must be a docker reference", term) } else if err != nil { imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, fmt.Sprintf("%s/%s", registry, term))) @@ -239,11 +263,11 @@ func searchRepositoryTags(registry, term string, sc *types.SystemContext, option return nil, errors.Errorf("reference %q must be a docker reference", term) } } - tags, err := docker.GetRepositoryTags(context.TODO(), sc, imageRef) + tags, err := dockerTransport.GetRepositoryTags(ctx, sys, imageRef) if err != nil { return nil, errors.Errorf("error getting repository tags: %v", err) } - limit := maxQueries + limit := searchMaxQueries if len(tags) < limit { limit = len(tags) } @@ -264,53 +288,18 @@ func searchRepositoryTags(registry, term string, sc *types.SystemContext, option return paramsArr, nil } -// ParseSearchFilter turns the filter into a SearchFilter that can be used for -// searching images. -func ParseSearchFilter(filter []string) (*SearchFilter, error) { - sFilter := new(SearchFilter) - for _, f := range filter { - arr := strings.SplitN(f, "=", 2) - switch arr[0] { - case "stars": - if len(arr) < 2 { - return nil, errors.Errorf("invalid `stars` filter %q, should be stars=<value>", filter) - } - stars, err := strconv.Atoi(arr[1]) - if err != nil { - return nil, errors.Wrapf(err, "incorrect value type for stars filter") - } - sFilter.Stars = stars - case "is-automated": - if len(arr) == 2 && arr[1] == "false" { - sFilter.IsAutomated = types.OptionalBoolFalse - } else { - sFilter.IsAutomated = types.OptionalBoolTrue - } - case "is-official": - if len(arr) == 2 && arr[1] == "false" { - sFilter.IsOfficial = types.OptionalBoolFalse - } else { - sFilter.IsOfficial = types.OptionalBoolTrue - } - default: - return nil, errors.Errorf("invalid filter type %q", f) - } - } - return sFilter, nil -} - -func (f *SearchFilter) matchesStarFilter(result docker.SearchResult) bool { +func (f *SearchFilter) matchesStarFilter(result dockerTransport.SearchResult) bool { return result.StarCount >= f.Stars } -func (f *SearchFilter) matchesAutomatedFilter(result docker.SearchResult) bool { +func (f *SearchFilter) matchesAutomatedFilter(result dockerTransport.SearchResult) bool { if f.IsAutomated != types.OptionalBoolUndefined { return result.IsAutomated == (f.IsAutomated == types.OptionalBoolTrue) } return true } -func (f *SearchFilter) matchesOfficialFilter(result docker.SearchResult) bool { +func (f *SearchFilter) matchesOfficialFilter(result dockerTransport.SearchResult) bool { if f.IsOfficial != types.OptionalBoolUndefined { return result.IsOfficial == (f.IsOfficial == types.OptionalBoolTrue) } diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index 1629bea29..371dd3667 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -47,18 +47,6 @@ const ( BoltDBStateStore RuntimeStateStore = iota ) -// PullPolicy whether to pull new image -type PullPolicy int - -const ( - // PullImageAlways always try to pull new image when create or run - PullImageAlways PullPolicy = iota - // PullImageMissing pulls image if it is not locally - PullImageMissing - // PullImageNever will never pull new image - PullImageNever -) - // Config contains configuration options for container tools type Config struct { // Containers specify settings that configure how containers will run ont the system @@ -263,6 +251,9 @@ type EngineConfig struct { // LockType is the type of locking to use. LockType string `toml:"lock_type,omitempty"` + // MachineEnabled indicates if Podman is running in a podman-machine VM + MachineEnabled bool `toml:"machine_enabled,omitempty"` + // MultiImageArchive - if true, the container engine allows for storing // archives (e.g., of the docker-archive transport) with multiple // images. By default, Podman creates single-image archives. @@ -697,23 +688,6 @@ func (c *NetworkConfig) Validate() error { return errors.Errorf("invalid cni_plugin_dirs: %s", strings.Join(c.CNIPluginDirs, ",")) } -// ValidatePullPolicy check if the pullPolicy from CLI is valid and returns the valid enum type -// if the value from CLI or containers.conf is invalid returns the error -func ValidatePullPolicy(pullPolicy string) (PullPolicy, error) { - switch strings.ToLower(pullPolicy) { - case "always": - return PullImageAlways, nil - case "missing", "ifnotpresent": - return PullImageMissing, nil - case "never": - return PullImageNever, nil - case "": - return PullImageMissing, nil - default: - return PullImageMissing, errors.Errorf("invalid pull policy %q", pullPolicy) - } -} - // FindConmon iterates over (*Config).ConmonPath and returns the path // to first (version) matching conmon binary. If non is found, we try // to do a path lookup of "conmon". diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf b/vendor/github.com/containers/common/pkg/config/containers.conf index 0114f2975..00edd5438 100644 --- a/vendor/github.com/containers/common/pkg/config/containers.conf +++ b/vendor/github.com/containers/common/pkg/config/containers.conf @@ -336,6 +336,11 @@ default_sysctls = [ # # lock_type** = "shm" +# Indicates if Podman is running inside a VM via Podman Machine. +# Podman uses this value to do extra setup around networking from the +# container inside the VM to to host. +# machine_enabled=false + # MultiImageArchive - if true, the container engine allows for storing archives # (e.g., of the docker-archive transport) with multiple images. By default, # Podman creates single-image archives. @@ -403,7 +408,7 @@ default_sysctls = [ # List of the OCI runtimes that support --format=json. When json is supported # engine will use it for reporting nicer errors. # -# runtime_supports_json = ["crun", "runc", "kata"] +# runtime_supports_json = ["crun", "runc", "kata", "runsc"] # List of the OCI runtimes that supports running containers without cgroups. # @@ -432,7 +437,7 @@ default_sysctls = [ # Path to file containing ssh identity key # identity = "~/.ssh/id_rsa" -# Paths to look for a valid OCI runtime (crun, runc, kata, etc) +# Paths to look for a valid OCI runtime (crun, runc, kata, runsc, etc) [engine.runtimes] # crun = [ # "/usr/bin/crun", @@ -465,6 +470,16 @@ default_sysctls = [ # "/usr/bin/kata-fc", # ] +# runsc = [ +# "/usr/bin/runsc", +# "/usr/sbin/runsc", +# "/usr/local/bin/runsc", +# "/usr/local/sbin/runsc", +# "/bin/runsc", +# "/sbin/runsc", +# "/run/current-system/sw/bin/runsc", +# ] + [engine.volume_plugins] # testplugin = "/run/podman/plugins/test.sock" diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go index 72744bb12..34a360bf5 100644 --- a/vendor/github.com/containers/common/pkg/config/default.go +++ b/vendor/github.com/containers/common/pkg/config/default.go @@ -278,6 +278,15 @@ func defaultConfigFromMemory() (*EngineConfig, error) { "/usr/bin/kata-qemu", "/usr/bin/kata-fc", }, + "runsc": { + "/usr/bin/runsc", + "/usr/sbin/runsc", + "/usr/local/bin/runsc", + "/usr/local/sbin/runsc", + "/bin/runsc", + "/sbin/runsc", + "/run/current-system/sw/bin/runsc", + }, } // Needs to be called after populating c.OCIRuntimes c.OCIRuntime = c.findRuntime() @@ -299,6 +308,8 @@ func defaultConfigFromMemory() (*EngineConfig, error) { c.RuntimeSupportsJSON = []string{ "crun", "runc", + "kata", + "runsc", } c.RuntimeSupportsNoCgroups = []string{"crun"} c.RuntimeSupportsKVM = []string{"kata", "kata-runtime", "kata-qemu", "kata-fc"} @@ -314,6 +325,7 @@ func defaultConfigFromMemory() (*EngineConfig, error) { // TODO - ideally we should expose a `type LockType string` along with // constants. c.LockType = "shm" + c.MachineEnabled = false return c, nil } @@ -524,3 +536,7 @@ func (c *Config) Umask() string { func (c *Config) LogDriver() string { return c.Containers.LogDriver } + +func (c *Config) MachineEnabled() bool { + return c.Engine.MachineEnabled +} diff --git a/vendor/github.com/containers/common/pkg/config/pull_policy.go b/vendor/github.com/containers/common/pkg/config/pull_policy.go new file mode 100644 index 000000000..7c32dd660 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/config/pull_policy.go @@ -0,0 +1,95 @@ +package config + +import ( + "fmt" + + "github.com/pkg/errors" +) + +// PullPolicy determines how and which images are being pulled from a container +// registry (i.e., docker transport only). +// +// Supported string values are: +// * "always" <-> PullPolicyAlways +// * "missing" <-> PullPolicyMissing +// * "newer" <-> PullPolicyNewer +// * "never" <-> PullPolicyNever +type PullPolicy int + +const ( + // Always pull the image. + PullPolicyAlways PullPolicy = iota + // Pull the image only if it could not be found in the local containers + // storage. + PullPolicyMissing + // Never pull the image but use the one from the local containers + // storage. + PullPolicyNever + // Pull if the image on the registry is new than the one in the local + // containers storage. An image is considered to be newer when the + // digests are different. Comparing the time stamps is prone to + // errors. + PullPolicyNewer + + // Ideally this should be the first `ioata` but backwards compatibility + // prevents us from changing the values. + PullPolicyUnsupported = -1 +) + +// String converts a PullPolicy into a string. +// +// Supported string values are: +// * "always" <-> PullPolicyAlways +// * "missing" <-> PullPolicyMissing +// * "newer" <-> PullPolicyNewer +// * "never" <-> PullPolicyNever +func (p PullPolicy) String() string { + switch p { + case PullPolicyAlways: + return "always" + case PullPolicyMissing: + return "missing" + case PullPolicyNewer: + return "newer" + case PullPolicyNever: + return "never" + } + return fmt.Sprintf("unrecognized policy %d", p) +} + +// Validate returns if the pull policy is not supported. +func (p PullPolicy) Validate() error { + switch p { + case PullPolicyAlways, PullPolicyMissing, PullPolicyNewer, PullPolicyNever: + return nil + default: + return errors.Errorf("unsupported pull policy %d", p) + } +} + +// ParsePullPolicy parses the string into a pull policy. +// +// Supported string values are: +// * "always" <-> PullPolicyAlways +// * "missing" <-> PullPolicyMissing (also "ifnotpresent" and "") +// * "newer" <-> PullPolicyNewer (also "ifnewer") +// * "never" <-> PullPolicyNever +func ParsePullPolicy(s string) (PullPolicy, error) { + switch s { + case "always": + return PullPolicyAlways, nil + case "missing", "ifnotpresent", "": + return PullPolicyMissing, nil + case "newer", "ifnewer": + return PullPolicyNewer, nil + case "never": + return PullPolicyNever, nil + default: + return PullPolicyUnsupported, errors.Errorf("unsupported pull policy %q", s) + } +} + +// Deprecated: please use `ParsePullPolicy` instead. +func ValidatePullPolicy(s string) (PullPolicy, error) { + return ParsePullPolicy(s) +} diff --git a/vendor/github.com/containers/common/pkg/filters/filters.go b/vendor/github.com/containers/common/pkg/filters/filters.go new file mode 100644 index 000000000..53f420db2 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/filters/filters.go @@ -0,0 +1,118 @@ +package filters + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/containers/common/pkg/timetype" + "github.com/pkg/errors" +) + +// ComputeUntilTimestamp extracts until timestamp from filters +func ComputeUntilTimestamp(filterValues []string) (time.Time, error) { + invalid := time.Time{} + if len(filterValues) != 1 { + return invalid, errors.Errorf("specify exactly one timestamp for until") + } + ts, err := timetype.GetTimestamp(filterValues[0], time.Now()) + if err != nil { + return invalid, err + } + seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0) + if err != nil { + return invalid, err + } + return time.Unix(seconds, nanoseconds), nil +} + +// filtersFromRequests extracts the "filters" parameter from the specified +// http.Request. The parameter can either be a `map[string][]string` as done +// in new versions of Docker and libpod, or a `map[string]map[string]bool` as +// done in older versions of Docker. We have to do a bit of Yoga to support +// both - just as Docker does as well. +// +// Please refer to https://github.com/containers/podman/issues/6899 for some +// background. +func FiltersFromRequest(r *http.Request) ([]string, error) { + var ( + compatFilters map[string]map[string]bool + filters map[string][]string + libpodFilters []string + raw []byte + ) + + if _, found := r.URL.Query()["filters"]; found { + raw = []byte(r.Form.Get("filters")) + } else if _, found := r.URL.Query()["Filters"]; found { + raw = []byte(r.Form.Get("Filters")) + } else { + return []string{}, nil + } + + // Backwards compat with older versions of Docker. + if err := json.Unmarshal(raw, &compatFilters); err == nil { + for filterKey, filterMap := range compatFilters { + for filterValue, toAdd := range filterMap { + if toAdd { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + } + return libpodFilters, nil + } + + if err := json.Unmarshal(raw, &filters); err != nil { + return nil, err + } + + for filterKey, filterSlice := range filters { + f := filterKey + for _, filterValue := range filterSlice { + f += "=" + filterValue + } + libpodFilters = append(libpodFilters, f) + } + + return libpodFilters, nil +} + +// PrepareFilters prepares a *map[string][]string of filters to be later searched +// in lipod and compat API to get desired filters +func PrepareFilters(r *http.Request) (map[string][]string, error) { + filtersList, err := FiltersFromRequest(r) + if err != nil { + return nil, err + } + filterMap := map[string][]string{} + for _, filter := range filtersList { + split := strings.SplitN(filter, "=", 2) + if len(split) > 1 { + filterMap[split[0]] = append(filterMap[split[0]], split[1]) + } + } + return filterMap, nil +} + +// MatchLabelFilters matches labels and returs true if they are valid +func MatchLabelFilters(filterValues []string, labels map[string]string) bool { +outer: + for _, filterValue := range filterValues { + filterArray := strings.SplitN(filterValue, "=", 2) + filterKey := filterArray[0] + if len(filterArray) > 1 { + filterValue = filterArray[1] + } else { + filterValue = "" + } + for labelKey, labelValue := range labels { + if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) { + continue outer + } + } + return false + } + return true +} diff --git a/vendor/github.com/containers/buildah/pkg/manifests/errors.go b/vendor/github.com/containers/common/pkg/manifests/errors.go index 8398d7efc..8398d7efc 100644 --- a/vendor/github.com/containers/buildah/pkg/manifests/errors.go +++ b/vendor/github.com/containers/common/pkg/manifests/errors.go diff --git a/vendor/github.com/containers/buildah/pkg/manifests/manifests.go b/vendor/github.com/containers/common/pkg/manifests/manifests.go index ea9495ee7..ea9495ee7 100644 --- a/vendor/github.com/containers/buildah/pkg/manifests/manifests.go +++ b/vendor/github.com/containers/common/pkg/manifests/manifests.go diff --git a/vendor/github.com/containers/common/pkg/signal/signal_common.go b/vendor/github.com/containers/common/pkg/signal/signal_common.go new file mode 100644 index 000000000..7c2662909 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/signal/signal_common.go @@ -0,0 +1,41 @@ +package signal + +import ( + "fmt" + "strconv" + "strings" + "syscall" +) + +// ParseSignal translates a string to a valid syscall signal. +// It returns an error if the signal map doesn't include the given signal. +func ParseSignal(rawSignal string) (syscall.Signal, error) { + s, err := strconv.Atoi(rawSignal) + if err == nil { + if s == 0 { + return -1, fmt.Errorf("invalid signal: %s", rawSignal) + } + return syscall.Signal(s), nil + } + sig, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] + if !ok { + return -1, fmt.Errorf("invalid signal: %s", rawSignal) + } + return sig, nil +} + +// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input +// can be a name or number representation i.e. "KILL" "9" +func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) { + basename := strings.TrimPrefix(rawSignal, "-") + s, err := ParseSignal(basename) + if err == nil { + return s, nil + } + for k, v := range signalMap { + if strings.EqualFold(k, basename) { + return v, nil + } + } + return -1, fmt.Errorf("invalid signal: %s", basename) +} diff --git a/vendor/github.com/containers/common/pkg/signal/signal_linux.go b/vendor/github.com/containers/common/pkg/signal/signal_linux.go new file mode 100644 index 000000000..305b9d21f --- /dev/null +++ b/vendor/github.com/containers/common/pkg/signal/signal_linux.go @@ -0,0 +1,108 @@ +// +build linux +// +build !mips,!mipsle,!mips64,!mips64le + +// Signal handling for Linux only. +package signal + +// Copyright 2013-2018 Docker, Inc. + +// NOTE: this package has originally been copied from github.com/docker/docker. + +import ( + "os" + "os/signal" + "syscall" + + "golang.org/x/sys/unix" +) + +const ( + sigrtmin = 34 + sigrtmax = 64 + + SIGWINCH = syscall.SIGWINCH // For cross-compilation with Windows +) + +// signalMap is a map of Linux signals. +var signalMap = map[string]syscall.Signal{ + "ABRT": unix.SIGABRT, + "ALRM": unix.SIGALRM, + "BUS": unix.SIGBUS, + "CHLD": unix.SIGCHLD, + "CLD": unix.SIGCLD, + "CONT": unix.SIGCONT, + "FPE": unix.SIGFPE, + "HUP": unix.SIGHUP, + "ILL": unix.SIGILL, + "INT": unix.SIGINT, + "IO": unix.SIGIO, + "IOT": unix.SIGIOT, + "KILL": unix.SIGKILL, + "PIPE": unix.SIGPIPE, + "POLL": unix.SIGPOLL, + "PROF": unix.SIGPROF, + "PWR": unix.SIGPWR, + "QUIT": unix.SIGQUIT, + "SEGV": unix.SIGSEGV, + "STKFLT": unix.SIGSTKFLT, + "STOP": unix.SIGSTOP, + "SYS": unix.SIGSYS, + "TERM": unix.SIGTERM, + "TRAP": unix.SIGTRAP, + "TSTP": unix.SIGTSTP, + "TTIN": unix.SIGTTIN, + "TTOU": unix.SIGTTOU, + "URG": unix.SIGURG, + "USR1": unix.SIGUSR1, + "USR2": unix.SIGUSR2, + "VTALRM": unix.SIGVTALRM, + "WINCH": unix.SIGWINCH, + "XCPU": unix.SIGXCPU, + "XFSZ": unix.SIGXFSZ, + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} + +// CatchAll catches all signals and relays them to the specified channel. +func CatchAll(sigc chan os.Signal) { + handledSigs := make([]os.Signal, 0, len(signalMap)) + for _, s := range signalMap { + handledSigs = append(handledSigs, s) + } + signal.Notify(sigc, handledSigs...) +} + +// StopCatch stops catching the signals and closes the specified channel. +func StopCatch(sigc chan os.Signal) { + signal.Stop(sigc) + close(sigc) +} diff --git a/vendor/github.com/containers/common/pkg/signal/signal_linux_mipsx.go b/vendor/github.com/containers/common/pkg/signal/signal_linux_mipsx.go new file mode 100644 index 000000000..45c9d5af1 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/signal/signal_linux_mipsx.go @@ -0,0 +1,108 @@ +// +build linux +// +build mips mipsle mips64 mips64le + +// Special signal handling for mips architecture +package signal + +// Copyright 2013-2018 Docker, Inc. + +// NOTE: this package has originally been copied from github.com/docker/docker. + +import ( + "os" + "os/signal" + "syscall" + + "golang.org/x/sys/unix" +) + +const ( + sigrtmin = 34 + sigrtmax = 127 + + SIGWINCH = syscall.SIGWINCH +) + +// signalMap is a map of Linux signals. +var signalMap = map[string]syscall.Signal{ + "ABRT": unix.SIGABRT, + "ALRM": unix.SIGALRM, + "BUS": unix.SIGBUS, + "CHLD": unix.SIGCHLD, + "CLD": unix.SIGCLD, + "CONT": unix.SIGCONT, + "FPE": unix.SIGFPE, + "HUP": unix.SIGHUP, + "ILL": unix.SIGILL, + "INT": unix.SIGINT, + "IO": unix.SIGIO, + "IOT": unix.SIGIOT, + "KILL": unix.SIGKILL, + "PIPE": unix.SIGPIPE, + "POLL": unix.SIGPOLL, + "PROF": unix.SIGPROF, + "PWR": unix.SIGPWR, + "QUIT": unix.SIGQUIT, + "SEGV": unix.SIGSEGV, + "EMT": unix.SIGEMT, + "STOP": unix.SIGSTOP, + "SYS": unix.SIGSYS, + "TERM": unix.SIGTERM, + "TRAP": unix.SIGTRAP, + "TSTP": unix.SIGTSTP, + "TTIN": unix.SIGTTIN, + "TTOU": unix.SIGTTOU, + "URG": unix.SIGURG, + "USR1": unix.SIGUSR1, + "USR2": unix.SIGUSR2, + "VTALRM": unix.SIGVTALRM, + "WINCH": unix.SIGWINCH, + "XCPU": unix.SIGXCPU, + "XFSZ": unix.SIGXFSZ, + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} + +// CatchAll catches all signals and relays them to the specified channel. +func CatchAll(sigc chan os.Signal) { + handledSigs := make([]os.Signal, 0, len(signalMap)) + for _, s := range signalMap { + handledSigs = append(handledSigs, s) + } + signal.Notify(sigc, handledSigs...) +} + +// StopCatch stops catching the signals and closes the specified channel. +func StopCatch(sigc chan os.Signal) { + signal.Stop(sigc) + close(sigc) +} diff --git a/vendor/github.com/containers/common/pkg/signal/signal_unsupported.go b/vendor/github.com/containers/common/pkg/signal/signal_unsupported.go new file mode 100644 index 000000000..9d1733c02 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/signal/signal_unsupported.go @@ -0,0 +1,99 @@ +// +build !linux + +// Signal handling for Linux only. +package signal + +import ( + "os" + "syscall" +) + +const ( + sigrtmin = 34 + sigrtmax = 64 + + SIGWINCH = syscall.Signal(0xff) +) + +// signalMap is a map of Linux signals. +// These constants are sourced from the Linux version of golang.org/x/sys/unix +// (I don't see much risk of this changing). +// This should work as long as Podman only runs containers on Linux, which seems +// a safe assumption for now. +var signalMap = map[string]syscall.Signal{ + "ABRT": syscall.Signal(0x6), + "ALRM": syscall.Signal(0xe), + "BUS": syscall.Signal(0x7), + "CHLD": syscall.Signal(0x11), + "CLD": syscall.Signal(0x11), + "CONT": syscall.Signal(0x12), + "FPE": syscall.Signal(0x8), + "HUP": syscall.Signal(0x1), + "ILL": syscall.Signal(0x4), + "INT": syscall.Signal(0x2), + "IO": syscall.Signal(0x1d), + "IOT": syscall.Signal(0x6), + "KILL": syscall.Signal(0x9), + "PIPE": syscall.Signal(0xd), + "POLL": syscall.Signal(0x1d), + "PROF": syscall.Signal(0x1b), + "PWR": syscall.Signal(0x1e), + "QUIT": syscall.Signal(0x3), + "SEGV": syscall.Signal(0xb), + "STKFLT": syscall.Signal(0x10), + "STOP": syscall.Signal(0x13), + "SYS": syscall.Signal(0x1f), + "TERM": syscall.Signal(0xf), + "TRAP": syscall.Signal(0x5), + "TSTP": syscall.Signal(0x14), + "TTIN": syscall.Signal(0x15), + "TTOU": syscall.Signal(0x16), + "URG": syscall.Signal(0x17), + "USR1": syscall.Signal(0xa), + "USR2": syscall.Signal(0xc), + "VTALRM": syscall.Signal(0x1a), + "WINCH": syscall.Signal(0x1c), + "XCPU": syscall.Signal(0x18), + "XFSZ": syscall.Signal(0x19), + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} + +// CatchAll catches all signals and relays them to the specified channel. +func CatchAll(sigc chan os.Signal) { + panic("Unsupported on non-linux platforms") +} + +// StopCatch stops catching the signals and closes the specified channel. +func StopCatch(sigc chan os.Signal) { + panic("Unsupported on non-linux platforms") +} diff --git a/vendor/github.com/containers/buildah/pkg/supplemented/errors.go b/vendor/github.com/containers/common/pkg/supplemented/errors.go index 6de679b50..a031951f1 100644 --- a/vendor/github.com/containers/buildah/pkg/supplemented/errors.go +++ b/vendor/github.com/containers/common/pkg/supplemented/errors.go @@ -3,7 +3,7 @@ package supplemented import ( "errors" - "github.com/containers/buildah/pkg/manifests" + "github.com/containers/common/pkg/manifests" ) var ( diff --git a/vendor/github.com/containers/buildah/pkg/supplemented/supplemented.go b/vendor/github.com/containers/common/pkg/supplemented/supplemented.go index a36c3eda4..a36c3eda4 100644 --- a/vendor/github.com/containers/buildah/pkg/supplemented/supplemented.go +++ b/vendor/github.com/containers/common/pkg/supplemented/supplemented.go diff --git a/vendor/github.com/containers/common/pkg/timetype/timestamp.go b/vendor/github.com/containers/common/pkg/timetype/timestamp.go new file mode 100644 index 000000000..ce2cb64f2 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/timetype/timestamp.go @@ -0,0 +1,131 @@ +package timetype + +// code adapted from https://github.com/moby/moby/blob/master/api/types/time/timestamp.go + +import ( + "fmt" + "math" + "strconv" + "strings" + "time" +) + +// These are additional predefined layouts for use in Time.Format and Time.Parse +// with --since and --until parameters for `docker logs` and `docker events` +const ( + rFC3339Local = "2006-01-02T15:04:05" // RFC3339 with local timezone + rFC3339NanoLocal = "2006-01-02T15:04:05.999999999" // RFC3339Nano with local timezone + dateWithZone = "2006-01-02Z07:00" // RFC3339 with time at 00:00:00 + dateLocal = "2006-01-02" // RFC3339 with local timezone and time at 00:00:00 +) + +// GetTimestamp tries to parse given string as golang duration, +// then RFC3339 time and finally as a Unix timestamp. If +// any of these were successful, it returns a Unix timestamp +// as string otherwise returns the given value back. +// In case of duration input, the returned timestamp is computed +// as the given reference time minus the amount of the duration. +func GetTimestamp(value string, reference time.Time) (string, error) { + if d, err := time.ParseDuration(value); value != "0" && err == nil { + return strconv.FormatInt(reference.Add(-d).Unix(), 10), nil + } + + var format string + // if the string has a Z or a + or three dashes use parse otherwise use parseinlocation + parseInLocation := !(strings.ContainsAny(value, "zZ+") || strings.Count(value, "-") == 3) + + if strings.Contains(value, ".") { // nolint:gocritic + if parseInLocation { + format = rFC3339NanoLocal + } else { + format = time.RFC3339Nano + } + } else if strings.Contains(value, "T") { + // we want the number of colons in the T portion of the timestamp + tcolons := strings.Count(value, ":") + // if parseInLocation is off and we have a +/- zone offset (not Z) then + // there will be an extra colon in the input for the tz offset subtract that + // colon from the tcolons count + if !parseInLocation && !strings.ContainsAny(value, "zZ") && tcolons > 0 { + tcolons-- + } + if parseInLocation { + switch tcolons { + case 0: + format = "2006-01-02T15" + case 1: + format = "2006-01-02T15:04" + default: + format = rFC3339Local + } + } else { + switch tcolons { + case 0: + format = "2006-01-02T15Z07:00" + case 1: + format = "2006-01-02T15:04Z07:00" + default: + format = time.RFC3339 + } + } + } else if parseInLocation { + format = dateLocal + } else { + format = dateWithZone + } + + var t time.Time + var err error + + if parseInLocation { + t, err = time.ParseInLocation(format, value, time.FixedZone(reference.Zone())) + } else { + t, err = time.Parse(format, value) + } + + if err != nil { + // if there is a `-` then it's an RFC3339 like timestamp + if strings.Contains(value, "-") { + return "", err // was probably an RFC3339 like timestamp but the parser failed with an error + } + if _, _, err := parseTimestamp(value); err != nil { + return "", fmt.Errorf("failed to parse value as time or duration: %q", value) + } + return value, nil // unix timestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) + } + + return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil +} + +// ParseTimestamps returns seconds and nanoseconds from a timestamp that has the +// format "%d.%09d", time.Unix(), int64(time.Nanosecond())) +// if the incoming nanosecond portion is longer or shorter than 9 digits it is +// converted to nanoseconds. The expectation is that the seconds and +// seconds will be used to create a time variable. For example: +// seconds, nanoseconds, err := ParseTimestamp("1136073600.000000001",0) +// if err == nil since := time.Unix(seconds, nanoseconds) +// returns seconds as def(aultSeconds) if value == "" +func ParseTimestamps(value string, def int64) (secs, nanoSecs int64, err error) { + if value == "" { + return def, 0, nil + } + return parseTimestamp(value) +} + +func parseTimestamp(value string) (int64, int64, error) { // nolint:gocritic + sa := strings.SplitN(value, ".", 2) + s, err := strconv.ParseInt(sa[0], 10, 64) + if err != nil { + return s, 0, err + } + if len(sa) != 2 { + return s, 0, nil + } + n, err := strconv.ParseInt(sa[1], 10, 64) + if err != nil { + return s, n, err + } + // should already be in nanoseconds but just in case convert n to nanoseconds + n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1])))) + return s, n, nil +} diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index d9e7ffec7..af0a1269e 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.37.0" +const Version = "0.37.2-dev" diff --git a/vendor/github.com/containers/image/v5/docker/tarfile/dest.go b/vendor/github.com/containers/image/v5/docker/tarfile/dest.go deleted file mode 100644 index 4f2465cac..000000000 --- a/vendor/github.com/containers/image/v5/docker/tarfile/dest.go +++ /dev/null @@ -1,119 +0,0 @@ -package tarfile - -import ( - "context" - "io" - - internal "github.com/containers/image/v5/docker/internal/tarfile" - "github.com/containers/image/v5/docker/reference" - "github.com/containers/image/v5/types" - "github.com/opencontainers/go-digest" -) - -// Destination is a partial implementation of types.ImageDestination for writing to an io.Writer. -type Destination struct { - internal *internal.Destination - archive *internal.Writer -} - -// NewDestination returns a tarfile.Destination for the specified io.Writer. -// Deprecated: please use NewDestinationWithContext instead -func NewDestination(dest io.Writer, ref reference.NamedTagged) *Destination { - return NewDestinationWithContext(nil, dest, ref) -} - -// NewDestinationWithContext returns a tarfile.Destination for the specified io.Writer. -func NewDestinationWithContext(sys *types.SystemContext, dest io.Writer, ref reference.NamedTagged) *Destination { - archive := internal.NewWriter(dest) - return &Destination{ - internal: internal.NewDestination(sys, archive, ref), - archive: archive, - } -} - -// AddRepoTags adds the specified tags to the destination's repoTags. -func (d *Destination) AddRepoTags(tags []reference.NamedTagged) { - d.internal.AddRepoTags(tags) -} - -// SupportedManifestMIMETypes tells which manifest mime types the destination supports -// If an empty slice or nil it's returned, then any mime type can be tried to upload -func (d *Destination) SupportedManifestMIMETypes() []string { - return d.internal.SupportedManifestMIMETypes() -} - -// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures. -// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil. -func (d *Destination) SupportsSignatures(ctx context.Context) error { - return d.internal.SupportsSignatures(ctx) -} - -// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually -// uploaded to the image destination, true otherwise. -func (d *Destination) AcceptsForeignLayerURLs() bool { - return d.internal.AcceptsForeignLayerURLs() -} - -// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime architecture and OS. False otherwise. -func (d *Destination) MustMatchRuntimeOS() bool { - return d.internal.MustMatchRuntimeOS() -} - -// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(), -// and would prefer to receive an unmodified manifest instead of one modified for the destination. -// Does not make a difference if Reference().DockerReference() is nil. -func (d *Destination) IgnoresEmbeddedDockerReference() bool { - return d.internal.IgnoresEmbeddedDockerReference() -} - -// HasThreadSafePutBlob indicates whether PutBlob can be executed concurrently. -func (d *Destination) HasThreadSafePutBlob() bool { - return d.internal.HasThreadSafePutBlob() -} - -// PutBlob writes contents of stream and returns data representing the result (with all data filled in). -// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it. -// inputInfo.Size is the expected length of stream, if known. -// May update cache. -// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available -// to any other readers for download using the supplied digest. -// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far. -func (d *Destination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, cache types.BlobInfoCache, isConfig bool) (types.BlobInfo, error) { - return d.internal.PutBlob(ctx, stream, inputInfo, cache, isConfig) -} - -// TryReusingBlob checks whether the transport already contains, or can efficiently reuse, a blob, and if so, applies it to the current destination -// (e.g. if the blob is a filesystem layer, this signifies that the changes it describes need to be applied again when composing a filesystem tree). -// info.Digest must not be empty. -// If canSubstitute, TryReusingBlob can use an equivalent equivalent of the desired blob; in that case the returned info may not match the input. -// If the blob has been successfully reused, returns (true, info, nil); info must contain at least a digest and size, and may -// include CompressionOperation and CompressionAlgorithm fields to indicate that a change to the compression type should be -// reflected in the manifest that will be written. -// If the transport can not reuse the requested blob, TryReusingBlob returns (false, {}, nil); it returns a non-nil error only on an unexpected failure. -// May use and/or update cache. -func (d *Destination) TryReusingBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache, canSubstitute bool) (bool, types.BlobInfo, error) { - return d.internal.TryReusingBlob(ctx, info, cache, canSubstitute) -} - -// PutManifest writes manifest to the destination. -// The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so -// there can be no secondary manifests. -// FIXME? This should also receive a MIME type if known, to differentiate between schema versions. -// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), -// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. -func (d *Destination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error { - return d.internal.PutManifest(ctx, m, instanceDigest) -} - -// PutSignatures would add the given signatures to the docker tarfile (currently not supported). -// The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so -// there can be no secondary manifests. MUST be called after PutManifest (signatures reference manifest contents). -func (d *Destination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { - return d.internal.PutSignatures(ctx, signatures, instanceDigest) -} - -// Commit finishes writing data to the underlying io.Writer. -// It is the caller's responsibility to close it, if necessary. -func (d *Destination) Commit(ctx context.Context) error { - return d.archive.Close() -} diff --git a/vendor/github.com/containers/image/v5/docker/tarfile/doc.go b/vendor/github.com/containers/image/v5/docker/tarfile/doc.go deleted file mode 100644 index 4ea5369c0..000000000 --- a/vendor/github.com/containers/image/v5/docker/tarfile/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package tarfile is an internal implementation detail of some transports. -// Do not use outside of the github.com/containers/image repo! -package tarfile diff --git a/vendor/github.com/containers/image/v5/docker/tarfile/src.go b/vendor/github.com/containers/image/v5/docker/tarfile/src.go deleted file mode 100644 index ee341eb39..000000000 --- a/vendor/github.com/containers/image/v5/docker/tarfile/src.go +++ /dev/null @@ -1,104 +0,0 @@ -package tarfile - -import ( - "context" - "io" - - internal "github.com/containers/image/v5/docker/internal/tarfile" - "github.com/containers/image/v5/types" - digest "github.com/opencontainers/go-digest" -) - -// Source is a partial implementation of types.ImageSource for reading from tarPath. -// Most users should use this via implementations of ImageReference from docker/archive or docker/daemon. -type Source struct { - internal *internal.Source -} - -// NewSourceFromFile returns a tarfile.Source for the specified path. -// Deprecated: Please use NewSourceFromFileWithContext which will allows you to configure temp directory -// for big files through SystemContext.BigFilesTemporaryDir -func NewSourceFromFile(path string) (*Source, error) { - return NewSourceFromFileWithContext(nil, path) -} - -// NewSourceFromFileWithContext returns a tarfile.Source for the specified path. -func NewSourceFromFileWithContext(sys *types.SystemContext, path string) (*Source, error) { - archive, err := internal.NewReaderFromFile(sys, path) - if err != nil { - return nil, err - } - src := internal.NewSource(archive, true, nil, -1) - return &Source{internal: src}, nil -} - -// NewSourceFromStream returns a tarfile.Source for the specified inputStream, -// which can be either compressed or uncompressed. The caller can close the -// inputStream immediately after NewSourceFromFile returns. -// Deprecated: Please use NewSourceFromStreamWithSystemContext which will allows you to configure -// temp directory for big files through SystemContext.BigFilesTemporaryDir -func NewSourceFromStream(inputStream io.Reader) (*Source, error) { - return NewSourceFromStreamWithSystemContext(nil, inputStream) -} - -// NewSourceFromStreamWithSystemContext returns a tarfile.Source for the specified inputStream, -// which can be either compressed or uncompressed. The caller can close the -// inputStream immediately after NewSourceFromFile returns. -func NewSourceFromStreamWithSystemContext(sys *types.SystemContext, inputStream io.Reader) (*Source, error) { - archive, err := internal.NewReaderFromStream(sys, inputStream) - if err != nil { - return nil, err - } - src := internal.NewSource(archive, true, nil, -1) - return &Source{internal: src}, nil -} - -// Close removes resources associated with an initialized Source, if any. -func (s *Source) Close() error { - return s.internal.Close() -} - -// LoadTarManifest loads and decodes the manifest.json -func (s *Source) LoadTarManifest() ([]ManifestItem, error) { - return s.internal.TarManifest(), nil -} - -// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available). -// It may use a remote (= slow) service. -// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list); -// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists). -// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, -// as the primary manifest can not be a list, so there can be no secondary instances. -func (s *Source) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { - return s.internal.GetManifest(ctx, instanceDigest) -} - -// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently. -func (s *Source) HasThreadSafeGetBlob() bool { - return s.internal.HasThreadSafeGetBlob() -} - -// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown). -// The Digest field in BlobInfo is guaranteed to be provided, Size may be -1 and MediaType may be optionally provided. -// May update BlobInfoCache, preferably after it knows for certain that a blob truly exists at a specific location. -func (s *Source) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) { - return s.internal.GetBlob(ctx, info, cache) -} - -// GetSignatures returns the image's signatures. It may use a remote (= slow) service. -// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, -// as there can be no secondary manifests. -func (s *Source) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { - return s.internal.GetSignatures(ctx, instanceDigest) -} - -// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer -// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() -// to read the image's layers. -// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, -// as the primary manifest can not be a list, so there can be no secondary manifests. -// The Digest field is guaranteed to be provided; Size may be -1. -// WARNING: The list may contain duplicates, and they are semantically relevant. -func (s *Source) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { - return s.internal.LayerInfosForCopy(ctx, instanceDigest) -} diff --git a/vendor/github.com/containers/image/v5/docker/tarfile/types.go b/vendor/github.com/containers/image/v5/docker/tarfile/types.go deleted file mode 100644 index 0f14389e6..000000000 --- a/vendor/github.com/containers/image/v5/docker/tarfile/types.go +++ /dev/null @@ -1,8 +0,0 @@ -package tarfile - -import ( - internal "github.com/containers/image/v5/docker/internal/tarfile" -) - -// ManifestItem is an element of the array stored in the top-level manifest.json file. -type ManifestItem = internal.ManifestItem // All public members from the internal package remain accessible. diff --git a/vendor/github.com/disiqueira/gotree/v3/.gitignore b/vendor/github.com/disiqueira/gotree/v3/.gitignore new file mode 100644 index 000000000..3236c30ab --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/.gitignore @@ -0,0 +1,137 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.idea/ +GoTree.iml +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* +### Windows template +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Go template +# Compiled Object files, Static and Dynamic libs (Shared Objects) + +# Folders + +# Architecture specific extensions/prefixes + + + +### OSX template +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/vendor/github.com/disiqueira/gotree/v3/.travis.yml b/vendor/github.com/disiqueira/gotree/v3/.travis.yml new file mode 100644 index 000000000..29261dfff --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/.travis.yml @@ -0,0 +1,11 @@ +language: go +go_import_path: github.com/disiqueira/gotree +git: + depth: 1 +env: + - GO111MODULE=on + - GO111MODULE=off +go: [ 1.11.x, 1.12.x, 1.13.x ] +os: [ linux, osx ] +script: + - go test -race -v ./... diff --git a/vendor/github.com/disiqueira/gotree/v3/LICENSE b/vendor/github.com/disiqueira/gotree/v3/LICENSE new file mode 100644 index 000000000..e790b5a52 --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Diego Siqueira + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/disiqueira/gotree/v3/README.md b/vendor/github.com/disiqueira/gotree/v3/README.md new file mode 100644 index 000000000..d09d4a98c --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/README.md @@ -0,0 +1,104 @@ +# ![GoTree](https://rawgit.com/DiSiqueira/GoTree/master/gotree-logo.png) + +# GoTree ![Language Badge](https://img.shields.io/badge/Language-Go-blue.svg) ![Go Report](https://goreportcard.com/badge/github.com/DiSiqueira/GoTree) ![License Badge](https://img.shields.io/badge/License-MIT-blue.svg) ![Status Badge](https://img.shields.io/badge/Status-Beta-brightgreen.svg) [![GoDoc](https://godoc.org/github.com/DiSiqueira/GoTree?status.svg)](https://godoc.org/github.com/DiSiqueira/GoTree) [![Build Status](https://travis-ci.org/DiSiqueira/GoTree.svg?branch=master)](https://travis-ci.org/DiSiqueira/GoTree) + +Simple Go module to print tree structures in terminal. Heavily inpired by [The Tree Command for Linux][treecommand] + +The GoTree's goal is to be a simple tool providing a stupidly easy-to-use and fast way to print recursive structures. + +[treecommand]: http://mama.indstate.edu/users/ice/tree/ + +## Project Status + +GoTree is on beta. Pull Requests [are welcome](https://github.com/DiSiqueira/GoTree#social-coding) + +![](http://image.prntscr.com/image/2a0dbf0777454446b8083fb6a0dc51fe.png) + +## Features + +- Very simple and fast code +- Intuitive names +- Easy to extend +- Uses only native libs +- STUPIDLY [EASY TO USE](https://github.com/DiSiqueira/GoTree#usage) + +## Installation + +### Go Get + +```bash +$ go get github.com/disiqueira/gotree +``` + +## Usage + +### Simple create, populate and print example + +![](http://image.prntscr.com/image/dd2fe3737e6543f7b21941a6953598c2.png) + +```golang +package main + +import ( + "fmt" + + "github.com/disiqueira/gotree" +) + +func main() { + artist := gotree.New("Pantera") + album := artist.Add("Far Beyond Driven") + album.Add("5 minutes Alone") + + fmt.Println(artist.Print()) +} +``` + +## Contributing + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/DiSiqueira/GoTree/issues) to report any bugs or file feature requests. + +### Developing + +PRs are welcome. To begin developing, do this: + +```bash +$ git clone --recursive git@github.com:DiSiqueira/GoTree.git +$ cd GoTree/ +``` + +## Social Coding + +1. Create an issue to discuss about your idea +2. [Fork it] (https://github.com/DiSiqueira/GoTree/fork) +3. Create your feature branch (`git checkout -b my-new-feature`) +4. Commit your changes (`git commit -am 'Add some feature'`) +5. Push to the branch (`git push origin my-new-feature`) +6. Create a new Pull Request +7. Profit! :white_check_mark: + +## License + +The MIT License (MIT) + +Copyright (c) 2013-2018 Diego Siqueira + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/disiqueira/gotree/v3/_config.yml b/vendor/github.com/disiqueira/gotree/v3/_config.yml new file mode 100644 index 000000000..c74188174 --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate
\ No newline at end of file diff --git a/vendor/github.com/disiqueira/gotree/v3/go.mod b/vendor/github.com/disiqueira/gotree/v3/go.mod new file mode 100644 index 000000000..7e17c637e --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/go.mod @@ -0,0 +1,3 @@ +module github.com/disiqueira/gotree/v3 + +go 1.13 diff --git a/vendor/github.com/disiqueira/gotree/v3/gotree-logo.png b/vendor/github.com/disiqueira/gotree/v3/gotree-logo.png Binary files differnew file mode 100644 index 000000000..1735c6008 --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/gotree-logo.png diff --git a/vendor/github.com/disiqueira/gotree/v3/gotree.go b/vendor/github.com/disiqueira/gotree/v3/gotree.go new file mode 100644 index 000000000..c529f62be --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/gotree.go @@ -0,0 +1,129 @@ +// Package gotree create and print tree. +package gotree + +import ( + "strings" +) + +const ( + newLine = "\n" + emptySpace = " " + middleItem = "├── " + continueItem = "│ " + lastItem = "└── " +) + +type ( + tree struct { + text string + items []Tree + } + + // Tree is tree interface + Tree interface { + Add(text string) Tree + AddTree(tree Tree) + Items() []Tree + Text() string + Print() string + } + + printer struct { + } + + // Printer is printer interface + Printer interface { + Print(Tree) string + } +) + +//New returns a new GoTree.Tree +func New(text string) Tree { + return &tree{ + text: text, + items: []Tree{}, + } +} + +//Add adds a node to the tree +func (t *tree) Add(text string) Tree { + n := New(text) + t.items = append(t.items, n) + return n +} + +//AddTree adds a tree as an item +func (t *tree) AddTree(tree Tree) { + t.items = append(t.items, tree) +} + +//Text returns the node's value +func (t *tree) Text() string { + return t.text +} + +//Items returns all items in the tree +func (t *tree) Items() []Tree { + return t.items +} + +//Print returns an visual representation of the tree +func (t *tree) Print() string { + return newPrinter().Print(t) +} + +func newPrinter() Printer { + return &printer{} +} + +//Print prints a tree to a string +func (p *printer) Print(t Tree) string { + return t.Text() + newLine + p.printItems(t.Items(), []bool{}) +} + +func (p *printer) printText(text string, spaces []bool, last bool) string { + var result string + for _, space := range spaces { + if space { + result += emptySpace + } else { + result += continueItem + } + } + + indicator := middleItem + if last { + indicator = lastItem + } + + var out string + lines := strings.Split(text, "\n") + for i := range lines { + text := lines[i] + if i == 0 { + out += result + indicator + text + newLine + continue + } + if last { + indicator = emptySpace + } else { + indicator = continueItem + } + out += result + indicator + text + newLine + } + + return out +} + +func (p *printer) printItems(t []Tree, spaces []bool) string { + var result string + for i, f := range t { + last := i == len(t)-1 + result += p.printText(f.Text(), spaces, last) + if len(f.Items()) > 0 { + spacesChild := append(spaces, last) + result += p.printItems(f.Items(), spacesChild) + } + } + return result +} diff --git a/vendor/github.com/ishidawataru/sctp/.travis.yml b/vendor/github.com/ishidawataru/sctp/.travis.yml index 01a76be9a..a1c693c01 100644 --- a/vendor/github.com/ishidawataru/sctp/.travis.yml +++ b/vendor/github.com/ishidawataru/sctp/.travis.yml @@ -1,10 +1,20 @@ language: go +arch: + - amd64 + - ppc64le go: - 1.9.x - 1.10.x - 1.11.x - 1.12.x - 1.13.x +# allowing test cases to fail for the versions were not suppotred by ppc64le +matrix: + allow_failures: + - go: 1.9.x + - go: 1.10.x + - go: 1.13.x + script: - go test -v -race ./... @@ -12,6 +22,7 @@ script: - GOOS=linux GOARCH=arm go build . - GOOS=linux GOARCH=arm64 go build . - GOOS=linux GOARCH=ppc64le go build . + - GOOS=linux GOARCH=mips64le go build . - (go version | grep go1.6 > /dev/null) || GOOS=linux GOARCH=s390x go build . # can be compiled but not functional: - GOOS=linux GOARCH=386 go build . diff --git a/vendor/github.com/ishidawataru/sctp/sctp_linux.go b/vendor/github.com/ishidawataru/sctp/sctp_linux.go index ac340ddfb..d96d09e5c 100644 --- a/vendor/github.com/ishidawataru/sctp/sctp_linux.go +++ b/vendor/github.com/ishidawataru/sctp/sctp_linux.go @@ -212,7 +212,7 @@ func listenSCTPExtConfig(network string, laddr *SCTPAddr, options InitMsg, contr laddr.IPAddrs = append(laddr.IPAddrs, net.IPAddr{IP: net.IPv6zero}) } } - err := SCTPBind(sock, laddr, SCTP_BINDX_ADD_ADDR) + err = SCTPBind(sock, laddr, SCTP_BINDX_ADD_ADDR) if err != nil { return nil, err } diff --git a/vendor/github.com/jinzhu/copier/License b/vendor/github.com/jinzhu/copier/License new file mode 100644 index 000000000..e2dc5381e --- /dev/null +++ b/vendor/github.com/jinzhu/copier/License @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Jinzhu + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/jinzhu/copier/README.md b/vendor/github.com/jinzhu/copier/README.md new file mode 100644 index 000000000..cff72405c --- /dev/null +++ b/vendor/github.com/jinzhu/copier/README.md @@ -0,0 +1,131 @@ +# Copier + + I am a copier, I copy everything from one to another + +[![test status](https://github.com/jinzhu/copier/workflows/tests/badge.svg?branch=master "test status")](https://github.com/jinzhu/copier/actions) + +## Features + +* Copy from field to field with same name +* Copy from method to field with same name +* Copy from field to method with same name +* Copy from slice to slice +* Copy from struct to slice +* Copy from map to map +* Enforce copying a field with a tag +* Ignore a field with a tag +* Deep Copy + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/jinzhu/copier" +) + +type User struct { + Name string + Role string + Age int32 + + // Explicitly ignored in the destination struct. + Salary int +} + +func (user *User) DoubleAge() int32 { + return 2 * user.Age +} + +// Tags in the destination Struct provide instructions to copier.Copy to ignore +// or enforce copying and to panic or return an error if a field was not copied. +type Employee struct { + // Tell copier.Copy to panic if this field is not copied. + Name string `copier:"must"` + + // Tell copier.Copy to return an error if this field is not copied. + Age int32 `copier:"must,nopanic"` + + // Tell copier.Copy to explicitly ignore copying this field. + Salary int `copier:"-"` + + DoubleAge int32 + EmployeId int64 + SuperRole string +} + +func (employee *Employee) Role(role string) { + employee.SuperRole = "Super " + role +} + +func main() { + var ( + user = User{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 200000} + users = []User{{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 100000}, {Name: "jinzhu 2", Age: 30, Role: "Dev", Salary: 60000}} + employee = Employee{Salary: 150000} + employees = []Employee{} + ) + + copier.Copy(&employee, &user) + + fmt.Printf("%#v \n", employee) + // Employee{ + // Name: "Jinzhu", // Copy from field + // Age: 18, // Copy from field + // Salary:150000, // Copying explicitly ignored + // DoubleAge: 36, // Copy from method + // EmployeeId: 0, // Ignored + // SuperRole: "Super Admin", // Copy to method + // } + + // Copy struct to slice + copier.Copy(&employees, &user) + + fmt.Printf("%#v \n", employees) + // []Employee{ + // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRole: "Super Admin"} + // } + + // Copy slice to slice + employees = []Employee{} + copier.Copy(&employees, &users) + + fmt.Printf("%#v \n", employees) + // []Employee{ + // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRole: "Super Admin"}, + // {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeId: 0, SuperRole: "Super Dev"}, + // } + + // Copy map to map + map1 := map[int]int{3: 6, 4: 8} + map2 := map[int32]int8{} + copier.Copy(&map2, map1) + + fmt.Printf("%#v \n", map2) + // map[int32]int8{3:6, 4:8} +} +``` + +### Copy with Option + +```go +copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true, DeepCopy: true}) +``` + +## Contributing + +You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do. + +# Author + +**jinzhu** + +* <http://github.com/jinzhu> +* <wosmvp@gmail.com> +* <http://twitter.com/zhangjinzhu> + +## License + +Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License). diff --git a/vendor/github.com/jinzhu/copier/copier.go b/vendor/github.com/jinzhu/copier/copier.go new file mode 100644 index 000000000..72bf65c78 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/copier.go @@ -0,0 +1,491 @@ +package copier + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + "strings" +) + +// These flags define options for tag handling +const ( + // Denotes that a destination field must be copied to. If copying fails then a panic will ensue. + tagMust uint8 = 1 << iota + + // Denotes that the program should not panic when the must flag is on and + // value is not copied. The program will return an error instead. + tagNoPanic + + // Ignore a destination field from being copied to. + tagIgnore + + // Denotes that the value as been copied + hasCopied +) + +// Option sets copy options +type Option struct { + // setting this value to true will ignore copying zero values of all the fields, including bools, as well as a + // struct having all it's fields set to their zero values respectively (see IsZero() in reflect/value.go) + IgnoreEmpty bool + DeepCopy bool +} + +// Copy copy things +func Copy(toValue interface{}, fromValue interface{}) (err error) { + return copier(toValue, fromValue, Option{}) +} + +// CopyWithOption copy with option +func CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) { + return copier(toValue, fromValue, opt) +} + +func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) { + var ( + isSlice bool + amount = 1 + from = indirect(reflect.ValueOf(fromValue)) + to = indirect(reflect.ValueOf(toValue)) + ) + + if !to.CanAddr() { + return ErrInvalidCopyDestination + } + + // Return is from value is invalid + if !from.IsValid() { + return ErrInvalidCopyFrom + } + + fromType, isPtrFrom := indirectType(from.Type()) + toType, _ := indirectType(to.Type()) + + if fromType.Kind() == reflect.Interface { + fromType = reflect.TypeOf(from.Interface()) + } + + if toType.Kind() == reflect.Interface { + toType, _ = indirectType(reflect.TypeOf(to.Interface())) + oldTo := to + to = reflect.New(reflect.TypeOf(to.Interface())).Elem() + defer func() { + oldTo.Set(to) + }() + } + + // Just set it if possible to assign for normal types + if from.Kind() != reflect.Slice && from.Kind() != reflect.Struct && from.Kind() != reflect.Map && (from.Type().AssignableTo(to.Type()) || from.Type().ConvertibleTo(to.Type())) { + if !isPtrFrom || !opt.DeepCopy { + to.Set(from.Convert(to.Type())) + } else { + fromCopy := reflect.New(from.Type()) + fromCopy.Set(from.Elem()) + to.Set(fromCopy.Convert(to.Type())) + } + return + } + + if from.Kind() != reflect.Slice && fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map { + if !fromType.Key().ConvertibleTo(toType.Key()) { + return ErrMapKeyNotMatch + } + + if to.IsNil() { + to.Set(reflect.MakeMapWithSize(toType, from.Len())) + } + + for _, k := range from.MapKeys() { + toKey := indirect(reflect.New(toType.Key())) + if !set(toKey, k, opt.DeepCopy) { + return fmt.Errorf("%w map, old key: %v, new key: %v", ErrNotSupported, k.Type(), toType.Key()) + } + + elemType, _ := indirectType(toType.Elem()) + toValue := indirect(reflect.New(elemType)) + if !set(toValue, from.MapIndex(k), opt.DeepCopy) { + if err = copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil { + return err + } + } + + for { + if elemType == toType.Elem() { + to.SetMapIndex(toKey, toValue) + break + } + elemType = reflect.PtrTo(elemType) + toValue = toValue.Addr() + } + } + return + } + + if from.Kind() == reflect.Slice && to.Kind() == reflect.Slice && fromType.ConvertibleTo(toType) { + if to.IsNil() { + slice := reflect.MakeSlice(reflect.SliceOf(to.Type().Elem()), from.Len(), from.Cap()) + to.Set(slice) + } + + for i := 0; i < from.Len(); i++ { + if to.Len() < i+1 { + to.Set(reflect.Append(to, reflect.New(to.Type().Elem()).Elem())) + } + + if !set(to.Index(i), from.Index(i), opt.DeepCopy) { + err = CopyWithOption(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt) + if err != nil { + continue + } + } + } + return + } + + if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { + // skip not supported type + return + } + + if to.Kind() == reflect.Slice { + isSlice = true + if from.Kind() == reflect.Slice { + amount = from.Len() + } + } + + for i := 0; i < amount; i++ { + var dest, source reflect.Value + + if isSlice { + // source + if from.Kind() == reflect.Slice { + source = indirect(from.Index(i)) + } else { + source = indirect(from) + } + // dest + dest = indirect(reflect.New(toType).Elem()) + } else { + source = indirect(from) + dest = indirect(to) + } + + destKind := dest.Kind() + initDest := false + if destKind == reflect.Interface { + initDest = true + dest = indirect(reflect.New(toType)) + } + + // Get tag options + tagBitFlags := map[string]uint8{} + if dest.IsValid() { + tagBitFlags = getBitFlags(toType) + } + + // check source + if source.IsValid() { + // Copy from source field to dest field or method + fromTypeFields := deepFields(fromType) + for _, field := range fromTypeFields { + name := field.Name + + // Get bit flags for field + fieldFlags, _ := tagBitFlags[name] + + // Check if we should ignore copying + if (fieldFlags & tagIgnore) != 0 { + continue + } + + if fromField := source.FieldByName(name); fromField.IsValid() && !shouldIgnore(fromField, opt.IgnoreEmpty) { + // process for nested anonymous field + destFieldNotSet := false + if f, ok := dest.Type().FieldByName(name); ok { + for idx := range f.Index { + destField := dest.FieldByIndex(f.Index[:idx+1]) + + if destField.Kind() != reflect.Ptr { + continue + } + + if !destField.IsNil() { + continue + } + if !destField.CanSet() { + destFieldNotSet = true + break + } + + // destField is a nil pointer that can be set + newValue := reflect.New(destField.Type().Elem()) + destField.Set(newValue) + } + } + + if destFieldNotSet { + break + } + + toField := dest.FieldByName(name) + if toField.IsValid() { + if toField.CanSet() { + if !set(toField, fromField, opt.DeepCopy) { + if err := copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil { + return err + } + } + if fieldFlags != 0 { + // Note that a copy was made + tagBitFlags[name] = fieldFlags | hasCopied + } + } + } else { + // try to set to method + var toMethod reflect.Value + if dest.CanAddr() { + toMethod = dest.Addr().MethodByName(name) + } else { + toMethod = dest.MethodByName(name) + } + + if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { + toMethod.Call([]reflect.Value{fromField}) + } + } + } + } + + // Copy from from method to dest field + for _, field := range deepFields(toType) { + name := field.Name + + var fromMethod reflect.Value + if source.CanAddr() { + fromMethod = source.Addr().MethodByName(name) + } else { + fromMethod = source.MethodByName(name) + } + + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, opt.IgnoreEmpty) { + if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() { + values := fromMethod.Call([]reflect.Value{}) + if len(values) >= 1 { + set(toField, values[0], opt.DeepCopy) + } + } + } + } + } + + if isSlice { + if dest.Addr().Type().AssignableTo(to.Type().Elem()) { + if to.Len() < i+1 { + to.Set(reflect.Append(to, dest.Addr())) + } else { + set(to.Index(i), dest.Addr(), opt.DeepCopy) + } + } else if dest.Type().AssignableTo(to.Type().Elem()) { + if to.Len() < i+1 { + to.Set(reflect.Append(to, dest)) + } else { + set(to.Index(i), dest, opt.DeepCopy) + } + } + } else if initDest { + to.Set(dest) + } + + err = checkBitFlags(tagBitFlags) + } + + return +} + +func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool { + if !ignoreEmpty { + return false + } + + return v.IsZero() +} + +func deepFields(reflectType reflect.Type) []reflect.StructField { + if reflectType, _ = indirectType(reflectType); reflectType.Kind() == reflect.Struct { + fields := make([]reflect.StructField, 0, reflectType.NumField()) + + for i := 0; i < reflectType.NumField(); i++ { + v := reflectType.Field(i) + if v.Anonymous { + fields = append(fields, deepFields(v.Type)...) + } else { + fields = append(fields, v) + } + } + + return fields + } + + return nil +} + +func indirect(reflectValue reflect.Value) reflect.Value { + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + return reflectValue +} + +func indirectType(reflectType reflect.Type) (_ reflect.Type, isPtr bool) { + for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice { + reflectType = reflectType.Elem() + isPtr = true + } + return reflectType, isPtr +} + +func set(to, from reflect.Value, deepCopy bool) bool { + if from.IsValid() { + if to.Kind() == reflect.Ptr { + // set `to` to nil if from is nil + if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) + return true + } else if to.IsNil() { + // `from` -> `to` + // sql.NullString -> *string + if fromValuer, ok := driverValuer(from); ok { + v, err := fromValuer.Value() + if err != nil { + return false + } + // if `from` is not valid do nothing with `to` + if v == nil { + return true + } + } + // allocate new `to` variable with default value (eg. *string -> new(string)) + to.Set(reflect.New(to.Type().Elem())) + } + // depointer `to` + to = to.Elem() + } + + if deepCopy { + toKind := to.Kind() + if toKind == reflect.Interface && to.IsNil() { + if reflect.TypeOf(from.Interface()) != nil { + to.Set(reflect.New(reflect.TypeOf(from.Interface())).Elem()) + toKind = reflect.TypeOf(to.Interface()).Kind() + } + } + if toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice { + return false + } + } + + if from.Type().ConvertibleTo(to.Type()) { + to.Set(from.Convert(to.Type())) + } else if toScanner, ok := to.Addr().Interface().(sql.Scanner); ok { + // `from` -> `to` + // *string -> sql.NullString + if from.Kind() == reflect.Ptr { + // if `from` is nil do nothing with `to` + if from.IsNil() { + return true + } + // depointer `from` + from = indirect(from) + } + // `from` -> `to` + // string -> sql.NullString + // set `to` by invoking method Scan(`from`) + err := toScanner.Scan(from.Interface()) + if err != nil { + return false + } + } else if fromValuer, ok := driverValuer(from); ok { + // `from` -> `to` + // sql.NullString -> string + v, err := fromValuer.Value() + if err != nil { + return false + } + // if `from` is not valid do nothing with `to` + if v == nil { + return true + } + rv := reflect.ValueOf(v) + if rv.Type().AssignableTo(to.Type()) { + to.Set(rv) + } + } else if from.Kind() == reflect.Ptr { + return set(to, from.Elem(), deepCopy) + } else { + return false + } + } + + return true +} + +// parseTags Parses struct tags and returns uint8 bit flags. +func parseTags(tag string) (flags uint8) { + for _, t := range strings.Split(tag, ",") { + switch t { + case "-": + flags = tagIgnore + return + case "must": + flags = flags | tagMust + case "nopanic": + flags = flags | tagNoPanic + } + } + return +} + +// getBitFlags Parses struct tags for bit flags. +func getBitFlags(toType reflect.Type) map[string]uint8 { + flags := map[string]uint8{} + toTypeFields := deepFields(toType) + + // Get a list dest of tags + for _, field := range toTypeFields { + tags := field.Tag.Get("copier") + if tags != "" { + flags[field.Name] = parseTags(tags) + } + } + return flags +} + +// checkBitFlags Checks flags for error or panic conditions. +func checkBitFlags(flagsList map[string]uint8) (err error) { + // Check flag conditions were met + for name, flags := range flagsList { + if flags&hasCopied == 0 { + switch { + case flags&tagMust != 0 && flags&tagNoPanic != 0: + err = fmt.Errorf("field %s has must tag but was not copied", name) + return + case flags&(tagMust) != 0: + panic(fmt.Sprintf("Field %s has must tag but was not copied", name)) + } + } + } + return +} + +func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) { + + if !v.CanAddr() { + i, ok = v.Interface().(driver.Valuer) + return + } + + i, ok = v.Addr().Interface().(driver.Valuer) + return +} diff --git a/vendor/github.com/jinzhu/copier/errors.go b/vendor/github.com/jinzhu/copier/errors.go new file mode 100644 index 000000000..cf7c5e74b --- /dev/null +++ b/vendor/github.com/jinzhu/copier/errors.go @@ -0,0 +1,10 @@ +package copier + +import "errors" + +var ( + ErrInvalidCopyDestination = errors.New("copy destination is invalid") + ErrInvalidCopyFrom = errors.New("copy from is invalid") + ErrMapKeyNotMatch = errors.New("map's key type doesn't match") + ErrNotSupported = errors.New("not supported") +) diff --git a/vendor/github.com/jinzhu/copier/go.mod b/vendor/github.com/jinzhu/copier/go.mod new file mode 100644 index 000000000..531422dcb --- /dev/null +++ b/vendor/github.com/jinzhu/copier/go.mod @@ -0,0 +1,3 @@ +module github.com/jinzhu/copier + +go 1.15 diff --git a/vendor/github.com/openshift/imagebuilder/README.md b/vendor/github.com/openshift/imagebuilder/README.md index 748bff971..4acfaa2bb 100644 --- a/vendor/github.com/openshift/imagebuilder/README.md +++ b/vendor/github.com/openshift/imagebuilder/README.md @@ -102,6 +102,8 @@ Example of usage from OpenShift's experimental `dockerbuild` [command with mount ## Run conformance tests (very slow): ``` +docker rmi busybox; docker pull busybox +docker rmi centos:7; docker pull centos:7 chmod -R go-w ./dockerclient/testdata -go test ./dockerclient/conformance_test.go -tags conformance -timeout 30m +go test ./dockerclient -tags conformance -timeout 30m ``` diff --git a/vendor/github.com/openshift/imagebuilder/builder.go b/vendor/github.com/openshift/imagebuilder/builder.go index dd8b09c05..df5269904 100644 --- a/vendor/github.com/openshift/imagebuilder/builder.go +++ b/vendor/github.com/openshift/imagebuilder/builder.go @@ -37,6 +37,8 @@ type Copy struct { type Run struct { Shell bool Args []string + // Mounts are mounts specified through the --mount flag inside the Containerfile + Mounts []string } type Executor interface { @@ -67,7 +69,7 @@ func (logExecutor) Copy(excludes []string, copies ...Copy) error { } func (logExecutor) Run(run Run, config docker.Config) error { - log.Printf("RUN %v %t (%v)", run.Args, run.Shell, config.Env) + log.Printf("RUN %v %v %t (%v)", run.Args, run.Mounts, run.Shell, config.Env) return nil } diff --git a/vendor/github.com/openshift/imagebuilder/dispatchers.go b/vendor/github.com/openshift/imagebuilder/dispatchers.go index 2294ae0a7..0d82136e7 100644 --- a/vendor/github.com/openshift/imagebuilder/dispatchers.go +++ b/vendor/github.com/openshift/imagebuilder/dispatchers.go @@ -306,7 +306,26 @@ func run(b *Builder, args []string, attributes map[string]bool, flagArgs []strin args = handleJSONArgs(args, attributes) - run := Run{Args: args} + var mounts []string + userArgs := mergeEnv(envMapAsSlice(b.Args), b.Env) + for _, a := range flagArgs { + arg, err := ProcessWord(a, userArgs) + if err != nil { + return err + } + switch { + case strings.HasPrefix(arg, "--mount="): + mount := strings.TrimPrefix(arg, "--mount=") + mounts = append(mounts, mount) + default: + return fmt.Errorf("RUN only supports the --mount flag") + } + } + + run := Run{ + Args: args, + Mounts: mounts, + } if !attributes["json"] { run.Shell = true diff --git a/vendor/github.com/openshift/imagebuilder/imagebuilder.spec b/vendor/github.com/openshift/imagebuilder/imagebuilder.spec index 684946ece..79d16ec39 100644 --- a/vendor/github.com/openshift/imagebuilder/imagebuilder.spec +++ b/vendor/github.com/openshift/imagebuilder/imagebuilder.spec @@ -12,7 +12,7 @@ # %global golang_version 1.8.1 -%{!?version: %global version 1.2.0} +%{!?version: %global version 1.2.2-dev} %{!?release: %global release 1} %global package_name imagebuilder %global product_name Container Image Builder diff --git a/vendor/modules.txt b/vendor/modules.txt index d59b6b731..c30aae016 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -77,7 +77,7 @@ github.com/containernetworking/plugins/pkg/utils/hwaddr github.com/containernetworking/plugins/pkg/utils/sysctl github.com/containernetworking/plugins/plugins/ipam/host-local/backend github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator -# github.com/containers/buildah v1.20.1-0.20210402144408-36a37402d0c8 +# github.com/containers/buildah v1.20.2-0.20210504130217-903dc56408ac github.com/containers/buildah github.com/containers/buildah/bind github.com/containers/buildah/chroot @@ -85,18 +85,17 @@ github.com/containers/buildah/copier github.com/containers/buildah/define github.com/containers/buildah/docker github.com/containers/buildah/imagebuildah -github.com/containers/buildah/manifests github.com/containers/buildah/pkg/blobcache github.com/containers/buildah/pkg/chrootuser github.com/containers/buildah/pkg/cli github.com/containers/buildah/pkg/completion -github.com/containers/buildah/pkg/manifests github.com/containers/buildah/pkg/overlay github.com/containers/buildah/pkg/parse github.com/containers/buildah/pkg/rusage -github.com/containers/buildah/pkg/supplemented github.com/containers/buildah/util -# github.com/containers/common v0.37.0 +# github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce +github.com/containers/common/libimage +github.com/containers/common/libimage/manifests github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/apparmor/internal/supported github.com/containers/common/pkg/auth @@ -105,6 +104,8 @@ github.com/containers/common/pkg/cgroupv2 github.com/containers/common/pkg/chown github.com/containers/common/pkg/completion github.com/containers/common/pkg/config +github.com/containers/common/pkg/filters +github.com/containers/common/pkg/manifests github.com/containers/common/pkg/parse github.com/containers/common/pkg/report github.com/containers/common/pkg/report/camelcase @@ -112,8 +113,11 @@ github.com/containers/common/pkg/retry github.com/containers/common/pkg/seccomp github.com/containers/common/pkg/secrets github.com/containers/common/pkg/secrets/filedriver +github.com/containers/common/pkg/signal github.com/containers/common/pkg/subscriptions +github.com/containers/common/pkg/supplemented github.com/containers/common/pkg/sysinfo +github.com/containers/common/pkg/timetype github.com/containers/common/pkg/umask github.com/containers/common/version # github.com/containers/conmon v2.0.20+incompatible @@ -128,7 +132,6 @@ github.com/containers/image/v5/docker/daemon github.com/containers/image/v5/docker/internal/tarfile github.com/containers/image/v5/docker/policyconfiguration github.com/containers/image/v5/docker/reference -github.com/containers/image/v5/docker/tarfile github.com/containers/image/v5/image github.com/containers/image/v5/internal/blobinfocache github.com/containers/image/v5/internal/iolimits @@ -262,6 +265,8 @@ github.com/digitalocean/go-libvirt/internal/event github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2 # github.com/digitalocean/go-qemu v0.0.0-20210209191958-152a1535e49f github.com/digitalocean/go-qemu/qmp +# github.com/disiqueira/gotree/v3 v3.0.2 +github.com/disiqueira/gotree/v3 # github.com/docker/distribution v2.7.1+incompatible github.com/docker/distribution github.com/docker/distribution/digestset @@ -382,8 +387,10 @@ github.com/hpcloud/tail/winfile github.com/imdario/mergo # github.com/inconshreveable/mousetrap v1.0.0 github.com/inconshreveable/mousetrap -# github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 +# github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee github.com/ishidawataru/sctp +# github.com/jinzhu/copier v0.3.0 +github.com/jinzhu/copier # github.com/json-iterator/go v1.1.11 github.com/json-iterator/go # github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a @@ -512,7 +519,7 @@ github.com/opencontainers/runtime-tools/validate github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/pkg/pwalk -# github.com/openshift/imagebuilder v1.2.0 +# github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656 github.com/openshift/imagebuilder github.com/openshift/imagebuilder/dockerfile/command github.com/openshift/imagebuilder/dockerfile/parser @@ -579,7 +586,7 @@ github.com/stretchr/testify/require github.com/syndtr/gocapability/capability # github.com/tchap/go-patricia v2.3.0+incompatible github.com/tchap/go-patricia/patricia -# github.com/uber/jaeger-client-go v2.27.0+incompatible +# github.com/uber/jaeger-client-go v2.28.0+incompatible github.com/uber/jaeger-client-go/log github.com/uber/jaeger-client-go/thrift github.com/uber/jaeger-client-go/thrift-gen/agent |