From b3defba9d3c5fde3c7a1f4f7ea846d6e6c4222b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Rop=C3=A9?= Date: Tue, 19 Nov 2024 15:02:55 +0100 Subject: [PATCH 1/5] ociartifact: add a functions to retrieve some files without pulling the full image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - extracting some functions out of "Pull()" to reuse - create the GetManifest() function - create the GetConfig() function Signed-off-by: Julien Ropé --- internal/config/ociartifact/ociartifact.go | 157 +++++++++++++++++---- 1 file changed, 130 insertions(+), 27 deletions(-) diff --git a/internal/config/ociartifact/ociartifact.go b/internal/config/ociartifact/ociartifact.go index f1f1346b6e2..ff082d8d93c 100644 --- a/internal/config/ociartifact/ociartifact.go +++ b/internal/config/ociartifact/ociartifact.go @@ -11,9 +11,11 @@ import ( "time" "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/pkg/blobinfocache" "github.com/containers/image/v5/types" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/cri-o/cri-o/internal/log" ) @@ -84,37 +86,14 @@ func (o *OCIArtifact) Pull(ctx context.Context, img string, opts *PullOptions) ( opts = &PullOptions{} } - name, err := o.impl.ParseNormalizedNamed(img) - if err != nil { - return nil, fmt.Errorf("parse image name: %w", err) - } - name = reference.TagNameOnly(name) // make sure to add ":latest" if needed - - ref, err := o.impl.NewReference(name) + src, err := o.getSourceFromImageName(ctx, img, opts) if err != nil { - return nil, fmt.Errorf("create docker reference: %w", err) + return nil, err } - src, err := o.impl.NewImageSource(ctx, ref, opts.SystemContext) - if err != nil { - return nil, fmt.Errorf("build image source: %w", err) - } - - manifestBytes, mimeType, err := o.impl.GetManifest(ctx, src, nil) + parsedManifest, err := o.getParsedManifest(ctx, src, opts) if err != nil { - return nil, fmt.Errorf("get manifest: %w", err) - } - - parsedManifest, err := o.impl.ManifestFromBlob(manifestBytes, mimeType) - if err != nil { - return nil, fmt.Errorf("parse manifest: %w", err) - } - if opts.EnforceConfigMediaType != "" && o.impl.ManifestConfigInfo(parsedManifest).MediaType != opts.EnforceConfigMediaType { - return nil, fmt.Errorf( - "wrong config media type %q, requires %q", - o.impl.ManifestConfigInfo(parsedManifest).MediaType, - opts.EnforceConfigMediaType, - ) + return nil, err } layers := o.impl.LayerInfos(parsedManifest) @@ -164,6 +143,130 @@ func (o *OCIArtifact) Pull(ctx context.Context, img string, opts *PullOptions) ( }, nil } +func (o *OCIArtifact) GetManifest(ctx context.Context, img string, opts *PullOptions) (manifest.Manifest, error) { + log.Infof(ctx, "Pulling manifest from ref: %s", img) + + // Use default pull options + if opts == nil { + opts = &PullOptions{} + } + + src, err := o.getSourceFromImageName(ctx, img, opts) + if err != nil { + return nil, err + } + + parsedManifest, err := o.getParsedManifest(ctx, src, opts) + if err != nil { + return nil, err + } + + return parsedManifest, nil +} + +func (o *OCIArtifact) GetConfig(ctx context.Context, img string, opts *PullOptions) (*v1.Image, error) { + log.Infof(ctx, "Getting config from ref: %s", img) + + // Use default pull options + if opts == nil { + opts = &PullOptions{} + } + + src, err := o.getSourceFromImageName(ctx, img, opts) + if err != nil { + return nil, err + } + + unparsedToplevel := image.UnparsedInstance(src, nil) + topManifest, topMIMEType, err := unparsedToplevel.Manifest(ctx) + if err != nil { + return nil, fmt.Errorf("get manifest: %w", err) + } + + unparsedInstance := unparsedToplevel + if manifest.MIMETypeIsMultiImage(topMIMEType) { + // This is a manifest list. We need to choose a single instance to work with. + manifestList, err := manifest.ListFromBlob(topManifest, topMIMEType) + if err != nil { + return nil, fmt.Errorf("parsing primary manifest as list: %w", err) + } + instanceDigest, err := manifestList.ChooseInstance(opts.SystemContext) + if err != nil { + return nil, fmt.Errorf("choosing an image from manifest list: %w", err) + } + + unparsedInstance = image.UnparsedInstance(src, &instanceDigest) + } + + sourcedImage, err := image.FromUnparsedImage(ctx, opts.SystemContext, unparsedInstance) + if err != nil { + return nil, fmt.Errorf("getting sourced image from unparsed image: %w", err) + } + return sourcedImage.OCIConfig(ctx) +} + +func (o *OCIArtifact) getSourceFromImageName(ctx context.Context, img string, opts *PullOptions) (types.ImageSource, error) { + name, err := o.impl.ParseNormalizedNamed(img) + if err != nil { + return nil, fmt.Errorf("parse image name: %w", err) + } + name = reference.TagNameOnly(name) // make sure to add ":latest" if needed + + ref, err := o.impl.NewReference(name) + if err != nil { + return nil, fmt.Errorf("create docker reference: %w", err) + } + + src, err := o.impl.NewImageSource(ctx, ref, opts.SystemContext) + if err != nil { + return nil, fmt.Errorf("build image source: %w", err) + } + return src, nil +} + +func (o *OCIArtifact) getParsedManifest(ctx context.Context, src types.ImageSource, opts *PullOptions) (manifest.Manifest, error) { + manifestBytes, mimeType, err := o.impl.GetManifest(ctx, src, nil) + if err != nil { + return nil, fmt.Errorf("get manifest: %w", err) + } + + if manifest.MIMETypeIsMultiImage(mimeType) { + // This is a manifest list. We need to choose a single instance to work with. + manifestList, err := manifest.ListFromBlob(manifestBytes, mimeType) + if err != nil { + return nil, fmt.Errorf("parsing primary manifest as list: %w", err) + } + instanceDigest, err := manifestList.ChooseInstance(opts.SystemContext) + if err != nil { + return nil, fmt.Errorf("choosing an image from manifest list: %w", err) + } + + unparsedInstance := image.UnparsedInstance(src, &instanceDigest) + + sourcedImage, err := image.FromUnparsedImage(ctx, opts.SystemContext, unparsedInstance) + if err != nil { + return nil, fmt.Errorf("getting sourced image from unparsed image: %w", err) + } + manifestBytes, mimeType, err = sourcedImage.Manifest(ctx) + if err != nil { + return nil, fmt.Errorf("getting manifest bytes from sourced image: %w", err) + } + } + + parsedManifest, err := o.impl.ManifestFromBlob(manifestBytes, mimeType) + if err != nil { + return nil, fmt.Errorf("parse manifest: %w", err) + } + if opts.EnforceConfigMediaType != "" && o.impl.ManifestConfigInfo(parsedManifest).MediaType != opts.EnforceConfigMediaType { + return nil, fmt.Errorf( + "wrong config media type %q, requires %q", + o.impl.ManifestConfigInfo(parsedManifest).MediaType, + opts.EnforceConfigMediaType, + ) + } + return parsedManifest, nil +} + func (o *OCIArtifact) prepareCache(ctx context.Context, opts *PullOptions) (useCache bool) { if opts.CachePath == "" { return false From f5a567893be1776bc95999cbce38a86d95f7e8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20ROP=C3=89?= Date: Mon, 17 Jun 2024 07:24:35 +0000 Subject: [PATCH 2/5] refactoring: get a function out of createSandboxContaienr() to retrieve image information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are several variables that we need from an ImageResult structure. Some were already copied to independent variables, others were taken directly from the imgResult that was lying around. This commit gets the imgResult variable out of createSandboxContainer() and makes sure that we can get all needed values out of it. It will make it possible to get the same information differently, without using an ImageResult structure, for different use cases. Signed-off-by: Julien ROPÉ --- internal/factory/container/container.go | 11 +-- internal/factory/container/container_test.go | 2 +- server/container_create_linux.go | 81 +++++++++++--------- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/internal/factory/container/container.go b/internal/factory/container/container.go index a92a5ad1fd4..0e07b25b046 100644 --- a/internal/factory/container/container.go +++ b/internal/factory/container/container.go @@ -32,6 +32,7 @@ import ( "github.com/cri-o/cri-o/internal/log" "github.com/cri-o/cri-o/internal/oci" "github.com/cri-o/cri-o/internal/storage" + "github.com/cri-o/cri-o/internal/storage/references" "github.com/cri-o/cri-o/pkg/annotations" "github.com/cri-o/cri-o/pkg/config" "github.com/cri-o/cri-o/utils" @@ -107,7 +108,7 @@ type Container interface { SpecAddMount(rspec.Mount) // SpecAddAnnotations adds annotations to the spec. - SpecAddAnnotations(ctx context.Context, sb SandboxIFace, containerVolume []oci.ContainerVolume, mountPoint, configStopSignal string, imageResult *storage.ImageResult, isSystemd bool, seccompRef, platformRuntimePath string) error + SpecAddAnnotations(ctx context.Context, sb SandboxIFace, containerVolume []oci.ContainerVolume, mountPoint, configStopSignal string, imageName *references.RegistryImageReference, imageID *storage.StorageImageID, isSystemd bool, seccompRef, platformRuntimePath string) error // SpecAddDevices adds devices from the server config, and container CRI config SpecAddDevices([]device.Device, []device.Device, bool, bool) error @@ -172,7 +173,7 @@ func (c *container) SpecAddMount(r rspec.Mount) { } // SpecAddAnnotation adds all annotations to the spec. -func (c *container) SpecAddAnnotations(ctx context.Context, sb SandboxIFace, containerVolumes []oci.ContainerVolume, mountPoint, configStopSignal string, imageResult *storage.ImageResult, isSystemd bool, seccompRef, platformRuntimePath string) (err error) { +func (c *container) SpecAddAnnotations(ctx context.Context, sb SandboxIFace, containerVolumes []oci.ContainerVolume, mountPoint, configStopSignal string, imageName *references.RegistryImageReference, imageID *storage.StorageImageID, isSystemd bool, seccompRef, platformRuntimePath string) (err error) { ctx, span := log.StartSpan(ctx) defer span.End() // Copied from k8s.io/kubernetes/pkg/kubelet/kuberuntime/labels.go @@ -223,11 +224,11 @@ func (c *container) SpecAddAnnotations(ctx context.Context, sb SandboxIFace, con c.spec.AddAnnotation(annotations.UserRequestedImage, userRequestedImage) someNameOfThisImage := "" - if imageResult.SomeNameOfThisImage != nil { - someNameOfThisImage = imageResult.SomeNameOfThisImage.StringForOutOfProcessConsumptionOnly() + if imageName != nil { + someNameOfThisImage = imageName.StringForOutOfProcessConsumptionOnly() } c.spec.AddAnnotation(annotations.SomeNameOfTheImage, someNameOfThisImage) - c.spec.AddAnnotation(annotations.ImageRef, imageResult.ID.IDStringForOutOfProcessConsumptionOnly()) + c.spec.AddAnnotation(annotations.ImageRef, imageID.IDStringForOutOfProcessConsumptionOnly()) c.spec.AddAnnotation(annotations.Name, c.Name()) c.spec.AddAnnotation(annotations.ContainerID, c.ID()) c.spec.AddAnnotation(annotations.SandboxID, sb.ID()) diff --git a/internal/factory/container/container_test.go b/internal/factory/container/container_test.go index 1cb17c13a2f..fc4dfb10e67 100644 --- a/internal/factory/container/container_test.go +++ b/internal/factory/container/container_test.go @@ -127,7 +127,7 @@ var _ = t.Describe("Container", func() { Expect(currentTime).ToNot(BeNil()) Expect(sb).ToNot(BeNil()) - err = sut.SpecAddAnnotations(context.Background(), sb, volumes, mountPoint, configStopSignal, &imageResult, false, "foo", "") + err = sut.SpecAddAnnotations(context.Background(), sb, volumes, mountPoint, configStopSignal, &someNameOfThisImage, &imageID, false, "foo", "") Expect(err).ToNot(HaveOccurred()) Expect(sut.Spec().Config.Annotations[annotations.UserRequestedImage]).To(Equal(image)) diff --git a/server/container_create_linux.go b/server/container_create_linux.go index 15b37b6ebb6..c33540ee528 100644 --- a/server/container_create_linux.go +++ b/server/container_create_linux.go @@ -162,39 +162,9 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrfactory.Cont } // Get imageName and imageID that are later requested in container status - var imgResult *storage.ImageResult - if id := s.StorageImageServer().HeuristicallyTryResolvingStringAsIDPrefix(userRequestedImage); id != nil { - imgResult, err = s.StorageImageServer().ImageStatusByID(s.config.SystemContext, *id) - if err != nil { - return nil, err - } - } else { - potentialMatches, err := s.StorageImageServer().CandidatesForPotentiallyShortImageName(s.config.SystemContext, userRequestedImage) - if err != nil { - return nil, err - } - var imgResultErr error - for _, name := range potentialMatches { - imgResult, imgResultErr = s.StorageImageServer().ImageStatusByName(s.config.SystemContext, name) - if imgResultErr == nil { - break - } - } - if imgResultErr != nil { - return nil, imgResultErr - } - } - // At this point we know userRequestedImage is not empty; "" is accepted by neither HeuristicallyTryResolvingStringAsIDPrefix - // nor CandidatesForPotentiallyShortImageName. Just to be sure: - if userRequestedImage == "" { - return nil, errors.New("internal error: successfully found an image, but userRequestedImage is empty") - } - - someNameOfTheImage := imgResult.SomeNameOfThisImage - imageID := imgResult.ID - someRepoDigest := "" - if len(imgResult.RepoDigests) > 0 { - someRepoDigest = imgResult.RepoDigests[0] + someNameOfTheImage, imageID, someRepoDigest, imageAnnotations, err := s.getInfoFromImage(userRequestedImage) + if err != nil { + return nil, err } // == Image lookup done. // == NEVER USE userRequestedImage (or even someNameOfTheImage) for anything but diagnostic logging past this point; it might @@ -528,7 +498,7 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrfactory.Cont created := time.Now() seccompRef := types.SecurityProfile_Unconfined.String() - if err := s.FilterDisallowedAnnotations(sb.Annotations(), imgResult.Annotations, sb.RuntimeHandler()); err != nil { + if err := s.FilterDisallowedAnnotations(sb.Annotations(), imageAnnotations, sb.RuntimeHandler()); err != nil { return nil, fmt.Errorf("filter image annotations: %w", err) } @@ -540,7 +510,7 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrfactory.Cont containerID, ctr.Config().Metadata.Name, sb.Annotations(), - imgResult.Annotations, + imageAnnotations, specgen, securityContext.Seccomp, ) @@ -569,7 +539,7 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrfactory.Cont if err != nil { return nil, err } - err = ctr.SpecAddAnnotations(ctx, sb, containerVolumes, mountPoint, containerImageConfig.Config.StopSignal, imgResult, s.config.CgroupManager().IsSystemd(), seccompRef, runtimePath) + err = ctr.SpecAddAnnotations(ctx, sb, containerVolumes, mountPoint, containerImageConfig.Config.StopSignal, someNameOfTheImage, &imageID, s.config.CgroupManager().IsSystemd(), seccompRef, runtimePath) if err != nil { return nil, err } @@ -858,6 +828,45 @@ func setContainerConfigSecurityContext(containerConfig *types.ContainerConfig) { } } +// This function returns information from the image, coming from the storage image server. +func (s *Server) getInfoFromImage(userRequestedImage string) (someNameOfTheImage *references.RegistryImageReference, imageID storage.StorageImageID, someRepoDigest string, imageAnnotations map[string]string, err error) { + var imgResult *storage.ImageResult + if id := s.StorageImageServer().HeuristicallyTryResolvingStringAsIDPrefix(userRequestedImage); id != nil { + imgResult, err = s.StorageImageServer().ImageStatusByID(s.config.SystemContext, *id) + if err != nil { + return nil, storage.StorageImageID{}, "", nil, err + } + } else { + var potentialMatches []references.RegistryImageReference + potentialMatches, err = s.StorageImageServer().CandidatesForPotentiallyShortImageName(s.config.SystemContext, userRequestedImage) + if err != nil { + return nil, storage.StorageImageID{}, "", nil, err + } + var imgResultErr error + for _, name := range potentialMatches { + imgResult, imgResultErr = s.StorageImageServer().ImageStatusByName(s.config.SystemContext, name) + if imgResultErr == nil { + break + } + } + if imgResultErr != nil { + return nil, storage.StorageImageID{}, "", nil, imgResultErr + } + } + // At this point we know userRequestedImage is not empty; "" is accepted by neither HeuristicallyTryResolvingStringAsIDPrefix + // nor CandidatesForPotentiallyShortImageName. Just to be sure: + if userRequestedImage == "" { + err = errors.New("internal error: successfully found an image, but userRequestedImage is empty") + return nil, storage.StorageImageID{}, "", nil, err + } + + someRepoDigest = "" + if len(imgResult.RepoDigests) > 0 { + someRepoDigest = imgResult.RepoDigests[0] + } + return imgResult.SomeNameOfThisImage, imgResult.ID, someRepoDigest, imgResult.Annotations, nil +} + func disableFipsForContainer(ctr ctrfactory.Container, containerDir string) error { // Create a unique filename for the FIPS setting file. fileName := filepath.Join(containerDir, "sysctl-fips") From 555607f591d77e32f0143212519550402ca75e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Rop=C3=A9?= Date: Tue, 5 Mar 2024 10:04:40 +0100 Subject: [PATCH 3/5] Confidential Containers: skip pullimage when the runtime is configured to handle it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RuntimePullImage flag tells crio that the runtime will handle the image by itself. crio should not try to pull the image in that case. This change is not only an optimization (avoiding the pull for an image that won't be used). It also prevents a failure if the image is encrypted, like can be the case in the confidential container use case, and crio doesn't have the key to read it. In that situation, pullimage would fail, and the process would stop before the runtime has a chance to do the job. Signed-off-by: Julien Ropé --- server/image_pull.go | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/server/image_pull.go b/server/image_pull.go index 635e139a6cf..038502ac63a 100644 --- a/server/image_pull.go +++ b/server/image_pull.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "syscall" "time" @@ -20,10 +21,43 @@ import ( "github.com/cri-o/cri-o/internal/log" "github.com/cri-o/cri-o/internal/storage" + "github.com/cri-o/cri-o/pkg/config" "github.com/cri-o/cri-o/server/metrics" "github.com/cri-o/cri-o/utils" ) +// GetRuntimeHandlerForPod returns the runtime handler for the given pod config. +func (s *Server) GetRuntimeHandlerForPod(ctx context.Context, cfg *types.PodSandboxConfig) (string, *config.RuntimeHandler) { + if cfg == nil || cfg.Metadata == nil { + return "", nil + } + name := strings.Join([]string{ + "k8s", + cfg.Metadata.Name, + cfg.Metadata.Namespace, + cfg.Metadata.Uid, + strconv.FormatUint(uint64(cfg.Metadata.Attempt), 10), + }, "_") + + podID, err := s.PodIDForName(name) + if err != nil { + log.Warnf(ctx, "Failed getting sandbox %s", name) + return "", nil + } + sbox := s.GetSandbox(podID) + if sbox == nil { + return "", nil + } + + if sbox.RuntimeHandler() != "" { + r, ok := s.config.Runtimes[sbox.RuntimeHandler()] + if ok { + return sbox.RuntimeHandler(), r + } + } + return "", nil +} + // PullImage pulls a image with authentication config. func (s *Server) PullImage(ctx context.Context, req *types.PullImageRequest) (*types.PullImageResponse, error) { ctx, span := log.StartSpan(ctx) @@ -49,6 +83,17 @@ func (s *Server) PullImage(ctx context.Context, req *types.PullImageRequest) (*t } } + rhName, rh := s.GetRuntimeHandlerForPod(ctx, req.SandboxConfig) + if rh != nil { + if rh.RuntimePullImage { + // don't pull the image in crio, as it will be done by the runtime + log.Debugf(ctx, "Skip image pull for runtime %s - image %s", rhName, image) + return &types.PullImageResponse{ + ImageRef: req.Image.Image, + }, nil + } + } + if req.Auth != nil { username := req.Auth.Username password := req.Auth.Password From 4226c2c0a17f31c6202842c7602bd0b551ff2994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20ROP=C3=89?= Date: Tue, 18 Jun 2024 14:17:13 +0000 Subject: [PATCH 4/5] container_create_linux: getting manifest information without an image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we skip the pull image phase, we can't get information about the image by looking at the local store. This commit uses ociartifact functions to retrieve the manifest, and from that the name, ID, digest and annotations associated with the container image. Signed-off-by: Julien ROPÉ --- server/container_create_linux.go | 61 ++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/server/container_create_linux.go b/server/container_create_linux.go index c33540ee528..ed0b0f52c40 100644 --- a/server/container_create_linux.go +++ b/server/container_create_linux.go @@ -15,12 +15,14 @@ import ( "github.com/containers/common/pkg/subscriptions" "github.com/containers/common/pkg/timezone" + "github.com/containers/image/v5/manifest" cstorage "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/unshare" securejoin "github.com/cyphar/filepath-securejoin" "github.com/intel/goresctrl/pkg/blockio" + "github.com/opencontainers/go-digest" rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "golang.org/x/sys/unix" @@ -29,6 +31,7 @@ import ( "github.com/cri-o/cri-o/internal/config/device" "github.com/cri-o/cri-o/internal/config/node" + "github.com/cri-o/cri-o/internal/config/ociartifact" "github.com/cri-o/cri-o/internal/config/rdt" ctrfactory "github.com/cri-o/cri-o/internal/factory/container" "github.com/cri-o/cri-o/internal/lib/sandbox" @@ -161,10 +164,30 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrfactory.Cont return nil, err } + isRuntimePullImage := false + if sb.RuntimeHandler() != "" { + r, ok := s.config.Runtimes[sb.RuntimeHandler()] + if ok { + isRuntimePullImage = r.RuntimePullImage + } + } + + var someNameOfTheImage *references.RegistryImageReference + var imageID storage.StorageImageID + var someRepoDigest string + var imageAnnotations map[string]string + // Get imageName and imageID that are later requested in container status - someNameOfTheImage, imageID, someRepoDigest, imageAnnotations, err := s.getInfoFromImage(userRequestedImage) - if err != nil { - return nil, err + if isRuntimePullImage { + someNameOfTheImage, imageID, someRepoDigest, imageAnnotations, err = s.getInfoFromArtifact(ctx, userRequestedImage) + if err != nil { + return nil, err + } + } else { + someNameOfTheImage, imageID, someRepoDigest, imageAnnotations, err = s.getInfoFromImage(userRequestedImage) + if err != nil { + return nil, err + } } // == Image lookup done. // == NEVER USE userRequestedImage (or even someNameOfTheImage) for anything but diagnostic logging past this point; it might @@ -867,6 +890,38 @@ func (s *Server) getInfoFromImage(userRequestedImage string) (someNameOfTheImage return imgResult.SomeNameOfThisImage, imgResult.ID, someRepoDigest, imgResult.Annotations, nil } +func (s *Server) getInfoFromArtifact(ctx context.Context, userRequestedImage string) (imageName *references.RegistryImageReference, imageID storage.StorageImageID, someRepoDigest string, imageAnnotations map[string]string, err error) { + artifact := ociartifact.New() + pullOpts := &ociartifact.PullOptions{} + + var imgManifest manifest.Manifest + imgManifest, err = artifact.GetManifest(ctx, userRequestedImage, pullOpts) + if err != nil { + return nil, storage.StorageImageID{}, "", nil, err + } + + var name references.RegistryImageReference + name, err = references.ParseRegistryImageReferenceFromOutOfProcessData(userRequestedImage) + if err != nil { + return nil, storage.StorageImageID{}, "", nil, err + } + + var diffIDs []digest.Digest + var strID string + strID, err = imgManifest.ImageID(diffIDs) + if err != nil { + return nil, storage.StorageImageID{}, "", nil, err + } + + var ID storage.StorageImageID + ID, err = storage.ParseStorageImageIDFromOutOfProcessData(strID) + if err != nil { + return nil, storage.StorageImageID{}, "", nil, err + } + + return &name, ID, imgManifest.ConfigInfo().Digest.String(), imgManifest.ConfigInfo().Annotations, nil +} + func disableFipsForContainer(ctr ctrfactory.Container, containerDir string) error { // Create a unique filename for the FIPS setting file. fileName := filepath.Join(containerDir, "sysctl-fips") From 0975645abc70a29ddc0cebfb6f1df899d5665fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Rop=C3=A9?= Date: Thu, 21 Nov 2024 16:44:52 +0000 Subject: [PATCH 5/5] storage/runtime: create the container without an underlying image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit changes the behaviour of the CreateContainer function so that, when the image is not in the repository, we: - get the config from the remote repo, without pulling the image - create the local storage with an empty image reference This results in an empty working directory for the container, but a valid image config to use for running it. Signed-off-by: Julien Ropé --- internal/storage/runtime.go | 54 +++++++++++++++++---------- internal/storage/runtime_test.go | 16 ++++---- server/container_create_linux.go | 1 + server/container_restore_test.go | 2 +- test/mocks/criostorage/criostorage.go | 8 ++-- 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/internal/storage/runtime.go b/internal/storage/runtime.go index e7b5e3a1776..9a3365495a9 100644 --- a/internal/storage/runtime.go +++ b/internal/storage/runtime.go @@ -12,6 +12,7 @@ import ( v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" + "github.com/cri-o/cri-o/internal/config/ociartifact" "github.com/cri-o/cri-o/internal/log" ) @@ -76,7 +77,7 @@ type RuntimeServer interface { // CreateContainer creates a container with the specified ID. // Pointer arguments can be nil. // All other arguments are required. - CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged bool) (ContainerInfo, error) + CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged, isRuntimePullImage bool) (ContainerInfo, error) // DeleteContainer deletes a container, unmounting it first if need be. DeleteContainer(ctx context.Context, idOrName string) error @@ -157,7 +158,7 @@ type runtimeContainerMetadataTemplate struct { privileged bool // Applicable to both PodSandboxes and Containers } -func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.SystemContext, containerID string, template *runtimeContainerMetadataTemplate, idMappingsOptions *storage.IDMappingOptions, labelOptions []string) (ci ContainerInfo, retErr error) { +func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.SystemContext, containerID string, template *runtimeContainerMetadataTemplate, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, isRuntimePullImage bool) (ci ContainerInfo, retErr error) { if template.podName == "" || template.podID == "" { return ContainerInfo{}, ErrInvalidPodName } @@ -187,21 +188,31 @@ func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.System // Pull out a copy of the image's configuration. // Ideally we would call imageID.imageRef(r.storageImageServer), but storageImageServer does not have access to private data. - ref, err := istorage.Transport.NewStoreReference(r.storageImageServer.GetStore(), nil, template.imageID.privateID) - if err != nil { - return ContainerInfo{}, err - } - image, err := ref.NewImage(r.ctx, systemContext) - if err != nil { - return ContainerInfo{}, err - } - defer image.Close() + var imageConfig *v1.Image + if isRuntimePullImage { + var err error + artifact := ociartifact.New() - imageConfig, err := image.OCIConfig(r.ctx) - if err != nil { - return ContainerInfo{}, err - } + imageConfig, err = artifact.GetConfig(r.ctx, metadata.ImageName, &ociartifact.PullOptions{}) + if err != nil { + return ContainerInfo{}, err + } + } else { + ref, err := istorage.Transport.NewStoreReference(r.storageImageServer.GetStore(), nil, template.imageID.privateID) + if err != nil { + return ContainerInfo{}, err + } + image, err := ref.NewImage(r.ctx, systemContext) + if err != nil { + return ContainerInfo{}, err + } + defer image.Close() + imageConfig, err = image.OCIConfig(r.ctx) + if err != nil { + return ContainerInfo{}, err + } + } metadata.Pod = (containerID == metadata.PodID) // Or should this be hard-coded in callers? The caller should know whether it is creating the infra container. metadata.CreatedAt = time.Now().Unix() mdata, err := json.Marshal(&metadata) @@ -222,7 +233,12 @@ func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.System if idMappingsOptions != nil { coptions.IDMappingOptions = *idMappingsOptions } - container, err := r.storageImageServer.GetStore().CreateContainer(containerID, names, template.imageID.privateID, "", string(mdata), &coptions) + imageName := template.imageID.privateID + if isRuntimePullImage { + // store.CreateContainer accepts empty image names + imageName = "" + } + container, err := r.storageImageServer.GetStore().CreateContainer(containerID, names, imageName, "", string(mdata), &coptions) if err != nil { if metadata.Pod { logrus.Debugf("Failed to create pod sandbox %s(%s): %v", metadata.PodName, metadata.PodID, err) @@ -350,10 +366,10 @@ func (r *runtimeService) CreatePodSandbox(systemContext *types.SystemContext, po namespace: namespace, attempt: attempt, privileged: privileged, - }, idMappingsOptions, labelOptions) + }, idMappingsOptions, labelOptions, false) } -func (r *runtimeService) CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged bool) (ContainerInfo, error) { +func (r *runtimeService) CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged, isRuntimePullImage bool) (ContainerInfo, error) { return r.createContainerOrPodSandbox(systemContext, containerID, &runtimeContainerMetadataTemplate{ podName: podName, podID: podID, @@ -365,7 +381,7 @@ func (r *runtimeService) CreateContainer(systemContext *types.SystemContext, pod namespace: "", attempt: attempt, privileged: privileged, - }, idMappingsOptions, labelOptions) + }, idMappingsOptions, labelOptions, isRuntimePullImage) } func (r *runtimeService) deleteLayerIfMapped(imageID, layerID string) { diff --git a/internal/storage/runtime_test.go b/internal/storage/runtime_test.go index 20408425948..a6a76f7d425 100644 --- a/internal/storage/runtime_test.go +++ b/internal/storage/runtime_test.go @@ -520,7 +520,7 @@ var _ = t.Describe("Runtime", func() { info, err = sut.CreateContainer(&types.SystemContext{}, "podName", "podID", "imagename", imageID, "containerName", "containerID", "", - 0, nil, []string{"mountLabel"}, false, + 0, nil, []string{"mountLabel"}, false, false, ) }) @@ -558,7 +558,7 @@ var _ = t.Describe("Runtime", func() { _, err := sut.CreateContainer(&types.SystemContext{}, "podName", "", "imagename", imageID, "containerName", "containerID", "metadataName", - 0, nil, []string{"mountLabel"}, false, + 0, nil, []string{"mountLabel"}, false, false, ) // Then @@ -573,7 +573,7 @@ var _ = t.Describe("Runtime", func() { _, err := sut.CreateContainer(&types.SystemContext{}, "", "podID", "imagename", imageID, "containerName", "containerID", "metadataName", - 0, nil, []string{"mountLabel"}, false, + 0, nil, []string{"mountLabel"}, false, false, ) // Then @@ -588,7 +588,7 @@ var _ = t.Describe("Runtime", func() { _, err := sut.CreateContainer(&types.SystemContext{}, "podName", "podID", "imagename", imageID, "", "containerID", "metadataName", - 0, nil, []string{"mountLabel"}, false, + 0, nil, []string{"mountLabel"}, false, false, ) // Then @@ -619,7 +619,7 @@ var _ = t.Describe("Runtime", func() { _, err := sut.CreateContainer(&types.SystemContext{}, "podName", "podID", "imagename", imageID, "containerName", "containerID", "metadataName", - 0, nil, []string{"mountLabel"}, false, + 0, nil, []string{"mountLabel"}, false, false, ) // Then @@ -646,7 +646,7 @@ var _ = t.Describe("Runtime", func() { _, err := sut.CreateContainer(&types.SystemContext{}, "podName", "podID", "imagename", imageID, "containerName", "containerID", "metadataName", - 0, nil, []string{"mountLabel"}, false, + 0, nil, []string{"mountLabel"}, false, false, ) // Then @@ -715,7 +715,7 @@ var _ = t.Describe("Runtime", func() { _, err := sut.CreateContainer(&types.SystemContext{}, "podName", "podID", "imagename", imageID, "containerName", "containerID", "metadataName", - 0, nil, []string{"mountLabel"}, false, + 0, nil, []string{"mountLabel"}, false, false, ) // Then @@ -740,7 +740,7 @@ var _ = t.Describe("Runtime", func() { _, err := sut.CreateContainer(&types.SystemContext{}, "podName", "podID", "imagename", imageID, "containerName", "containerID", "metadataName", - 0, nil, []string{"mountLabel"}, false, + 0, nil, []string{"mountLabel"}, false, false, ) // Then diff --git a/server/container_create_linux.go b/server/container_create_linux.go index ed0b0f52c40..b4d5f7c007c 100644 --- a/server/container_create_linux.go +++ b/server/container_create_linux.go @@ -253,6 +253,7 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrfactory.Cont idMappingOptions, labelOptions, ctr.Privileged(), + isRuntimePullImage, ) if err != nil { return nil, err diff --git a/server/container_restore_test.go b/server/container_restore_test.go index a807eecfa2b..587d4590297 100644 --- a/server/container_restore_test.go +++ b/server/container_restore_test.go @@ -503,7 +503,7 @@ var _ = t.Describe("ContainerRestore", func() { runtimeServerMock.EXPECT().CreateContainer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), imageID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - gomock.Any(), gomock.Any()). + gomock.Any(), gomock.Any(), gomock.Any()). Return(storage.ContainerInfo{ Config: &v1.Image{ Config: v1.ImageConfig{ diff --git a/test/mocks/criostorage/criostorage.go b/test/mocks/criostorage/criostorage.go index ff11aaa2d05..ed8f9dbd542 100644 --- a/test/mocks/criostorage/criostorage.go +++ b/test/mocks/criostorage/criostorage.go @@ -227,18 +227,18 @@ func (m *MockRuntimeServer) EXPECT() *MockRuntimeServerMockRecorder { } // CreateContainer mocks base method. -func (m *MockRuntimeServer) CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID storage0.StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *types0.IDMappingOptions, labelOptions []string, privileged bool) (storage0.ContainerInfo, error) { +func (m *MockRuntimeServer) CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID storage0.StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *types0.IDMappingOptions, labelOptions []string, privileged, isRuntimePullImage bool) (storage0.ContainerInfo, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateContainer", systemContext, podName, podID, userRequestedImage, imageID, containerName, containerID, metadataName, attempt, idMappingsOptions, labelOptions, privileged) + ret := m.ctrl.Call(m, "CreateContainer", systemContext, podName, podID, userRequestedImage, imageID, containerName, containerID, metadataName, attempt, idMappingsOptions, labelOptions, privileged, isRuntimePullImage) ret0, _ := ret[0].(storage0.ContainerInfo) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateContainer indicates an expected call of CreateContainer. -func (mr *MockRuntimeServerMockRecorder) CreateContainer(systemContext, podName, podID, userRequestedImage, imageID, containerName, containerID, metadataName, attempt, idMappingsOptions, labelOptions, privileged any) *gomock.Call { +func (mr *MockRuntimeServerMockRecorder) CreateContainer(systemContext, podName, podID, userRequestedImage, imageID, containerName, containerID, metadataName, attempt, idMappingsOptions, labelOptions, privileged, isRuntimePullImage any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateContainer", reflect.TypeOf((*MockRuntimeServer)(nil).CreateContainer), systemContext, podName, podID, userRequestedImage, imageID, containerName, containerID, metadataName, attempt, idMappingsOptions, labelOptions, privileged) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateContainer", reflect.TypeOf((*MockRuntimeServer)(nil).CreateContainer), systemContext, podName, podID, userRequestedImage, imageID, containerName, containerID, metadataName, attempt, idMappingsOptions, labelOptions, privileged, isRuntimePullImage) } // CreatePodSandbox mocks base method.