diff --git a/libpod/container_internal_common.go b/libpod/container_internal_common.go index 63d35dfecf3..27e936fa9fa 100644 --- a/libpod/container_internal_common.go +++ b/libpod/container_internal_common.go @@ -544,6 +544,19 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc } if len(c.config.ArtifactVolumes) > 0 { + // Validate all artifacts exist before attempting to mount + mounts := make([]ArtifactMountValidation, len(c.config.ArtifactVolumes)) + for i, av := range c.config.ArtifactVolumes { + mounts[i] = ArtifactMountValidation{ + Source: av.Source, + Title: av.Title, + Digest: av.Digest, + } + } + if err := c.runtime.ValidateArtifactMounts(ctx, mounts); err != nil { + return nil, nil, err + } + artStore, err := c.runtime.ArtifactStore() if err != nil { return nil, nil, err @@ -3167,3 +3180,37 @@ func maybeClampOOMScoreAdj(oomScoreValue int) (int, error) { } return oomScoreValue, nil } + +// ValidateArtifactMounts checks that all artifact mounts are valid by verifying +// the artifacts exist and can be accessed. This is used during both container +// creation and start to provide early validation. +func (r *Runtime) ValidateArtifactMounts(ctx context.Context, mounts []ArtifactMountValidation) error { + if len(mounts) == 0 { + return nil + } + + artStore, err := r.ArtifactStore() + if err != nil { + return fmt.Errorf("accessing artifact store: %w", err) + } + + for _, mount := range mounts { + asr, err := store.NewArtifactStorageReference(mount.Source) + if err != nil { + return fmt.Errorf("invalid artifact reference %q: %w", mount.Source, err) + } + + // Validate artifact exists by attempting to resolve its blob mount paths + _, err = artStore.BlobMountPaths(ctx, asr, &libartTypes.BlobMountPathOptions{ + FilterBlobOptions: libartTypes.FilterBlobOptions{ + Title: mount.Title, + Digest: mount.Digest, + }, + }) + if err != nil { + return fmt.Errorf("validating artifact %q: %w", mount.Source, err) + } + } + + return nil +} diff --git a/libpod/runtime.go b/libpod/runtime.go index 5b50c7d7e98..5deda1bf9c0 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -47,6 +47,15 @@ import ( "go.podman.io/storage/pkg/unshare" ) +// ArtifactMountValidation contains the minimal information needed to validate +// an artifact mount without requiring the full ContainerArtifactVolume structure. +// This type is used to pass validation data to Runtime.ValidateArtifactMounts. +type ArtifactMountValidation struct { + Source string + Title string + Digest string +} + // Set up the JSON library for all of Libpod var json = jsoniter.ConfigCompatibleWithStandardLibrary diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index d69b0f3f444..90faddffc3f 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -233,7 +233,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener command := makeCommand(s, imageData) infraVol := len(compatibleOptions.Mounts) > 0 || len(compatibleOptions.Volumes) > 0 || len(compatibleOptions.ImageVolumes) > 0 || len(compatibleOptions.OverlayVolumes) > 0 - opts, err := createContainerOptions(rt, s, pod, finalVolumes, finalOverlays, imageData, command, infraVol, *compatibleOptions) + opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command, infraVol, *compatibleOptions) if err != nil { return nil, nil, nil, err } @@ -351,7 +351,7 @@ func isCDIDevice(device string) bool { return parser.IsQualifiedName(device) } -func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string, infraVolumes bool, compatibleOptions libpod.InfraInherit) ([]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, infraVolumes bool, compatibleOptions libpod.InfraInherit) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -509,6 +509,19 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l } if len(s.ArtifactVolumes) != 0 { + // Validate artifacts exist before creating the container + mounts := make([]libpod.ArtifactMountValidation, len(s.ArtifactVolumes)) + for i, av := range s.ArtifactVolumes { + mounts[i] = libpod.ArtifactMountValidation{ + Source: av.Source, + Title: av.Title, + Digest: av.Digest, + } + } + if err := rt.ValidateArtifactMounts(ctx, mounts); err != nil { + return nil, err + } + vols := make([]*libpod.ContainerArtifactVolume, 0, len(s.ArtifactVolumes)) for _, v := range s.ArtifactVolumes { vols = append(vols, &libpod.ContainerArtifactVolume{ diff --git a/test/e2e/artifact_mount_test.go b/test/e2e/artifact_mount_test.go index 066bd2cc62f..19c822e1c73 100644 --- a/test/e2e/artifact_mount_test.go +++ b/test/e2e/artifact_mount_test.go @@ -245,4 +245,45 @@ var _ = Describe("Podman artifact mount", func() { Expect(session).To(ExitWithError(125, "/test: duplicate mount destination")) } }) + + It("podman create should fail with non-existent artifact", func() { + // Verify that creating a container with a non-existent artifact fails at creation time + cmd := []string{ + "create", + "--name", "test-nonexistent", + "--mount", "type=artifact,source=nonexistent-artifact,destination=/data", + ALPINE, + "echo", "hello", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError(125, "validating artifact")) + Expect(session.ErrorToString()).To(ContainSubstring("nonexistent-artifact")) + + // Ensure container was not created + rmSession := podmanTest.Podman([]string{"rm", "test-nonexistent"}) + rmSession.WaitWithDefaultTimeout() + Expect(rmSession).To(ExitWithError(1, "no such container")) + }) + + It("podman run should fail with non-existent artifact", func() { + // Verify that running a container with a non-existent artifact fails at creation time + cmd := []string{ + "run", + "--name", "test-run-nonexistent", + "--mount", "type=artifact,source=invalid-artifact-ref,destination=/test", + ALPINE, + "echo", "hello", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError(125, "validating artifact")) + Expect(session.ErrorToString()).To(ContainSubstring("invalid-artifact-ref")) + + // Ensure container was not created + psSession := podmanTest.Podman([]string{"ps", "-a", "--filter", "name=test-run-nonexistent"}) + psSession.WaitWithDefaultTimeout() + Expect(psSession).Should(ExitCleanly()) + Expect(psSession.OutputToString()).ToNot(ContainSubstring("test-run-nonexistent")) + }) })