Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 130 additions & 27 deletions internal/config/ociartifact/ociartifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
"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"
)
Expand Down Expand Up @@ -84,37 +86,14 @@
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(pre-existing?) src must be closed, otherwise this leaves around keep-alive HTTP connections.

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)
Expand Down Expand Up @@ -164,6 +143,130 @@
}, 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{}
}

Check warning on line 152 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L146-L152

Added lines #L146 - L152 were not covered by tests

src, err := o.getSourceFromImageName(ctx, img, opts)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating an ImageSource is fairly costly (HTTP round-trips); it should only be done once, not from scratch for every operation. Same for GetConfig.


Overall, the way this file has been refactored seems mostly misguided to me; it introduces abstraction boundaries in wrong places.

if err != nil {
return nil, err
}

Check warning on line 157 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L154-L157

Added lines #L154 - L157 were not covered by tests

parsedManifest, err := o.getParsedManifest(ctx, src, opts)
if err != nil {
return nil, err
}

Check warning on line 162 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L159-L162

Added lines #L159 - L162 were not covered by tests

return parsedManifest, nil

Check warning on line 164 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L164

Added line #L164 was not covered by tests
}

func (o *OCIArtifact) GetConfig(ctx context.Context, img string, opts *PullOptions) (*v1.Image, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, artifacts don’t have image configs. If they have image configs, they are images. (I can see a semantic argument that “image is an artifact”, but… what are we doing here?)

log.Infof(ctx, "Getting config from ref: %s", img)

// Use default pull options
if opts == nil {
opts = &PullOptions{}
}

Check warning on line 173 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L167-L173

Added lines #L167 - L173 were not covered by tests

src, err := o.getSourceFromImageName(ctx, img, opts)
if err != nil {
return nil, err
}

Check warning on line 178 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L175-L178

Added lines #L175 - L178 were not covered by tests

unparsedToplevel := image.UnparsedInstance(src, nil)
topManifest, topMIMEType, err := unparsedToplevel.Manifest(ctx)
if err != nil {
return nil, fmt.Errorf("get manifest: %w", err)
}

Check warning on line 184 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L180-L184

Added lines #L180 - L184 were not covered by tests

unparsedInstance := unparsedToplevel
if manifest.MIMETypeIsMultiImage(topMIMEType) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is pretty surprising for GetConfig to do its own manifest resolution. Why isn’t that centralized?

This is both costly (fetching manifests twice or more), and risks that the code will diverge (… as it seems to already have done).

// 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)
}

Check warning on line 196 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L186-L196

Added lines #L186 - L196 were not covered by tests

unparsedInstance = image.UnparsedInstance(src, &instanceDigest)

Check warning on line 198 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L198

Added line #L198 was not covered by tests
}

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)

Check warning on line 205 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L201-L205

Added lines #L201 - L205 were not covered by tests
}

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetManifest means the caller is responsible for validating digest references; this one isn’t doing so.

Use UnparsedInstance instead; that can also handle caching to avoid repeated requests about the same image.

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)
}

Check warning on line 242 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L234-L242

Added lines #L234 - L242 were not covered by tests

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)
}

Check warning on line 253 in internal/config/ociartifact/ociartifact.go

View check run for this annotation

Codecov / codecov/patch

internal/config/ociartifact/ociartifact.go#L244-L253

Added lines #L244 - L253 were not covered by tests
}

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
Expand Down
11 changes: 6 additions & 5 deletions internal/factory/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have spent a lot of effort (and attracted a lot of disagreement and controversy) to explicitly document the weird semantics of these values, by using names like SomeNameOfThisImage, as well as accompanying comments.

So, naturally, I want all those caveats to be preserved and the comments to be duplicated if this is going to stop referencing the existing data structures.


// SpecAddDevices adds devices from the server config, and container CRI config
SpecAddDevices([]device.Device, []device.Device, bool, bool) error
Expand Down Expand Up @@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If imageID is mandatory (as seems to be the case), the parameter should not be a pointer — as documented for the StorageImageID type.

ctx, span := log.StartSpan(ctx)
defer span.End()
// Copied from k8s.io/kubernetes/pkg/kubelet/kuberuntime/labels.go
Expand Down Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion internal/factory/container/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
54 changes: 35 additions & 19 deletions internal/storage/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"

"github.com/cri-o/cri-o/internal/config/ociartifact"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config/ociartifact?

Is everyone happy with the package names and dependencies here? (That’s entirely up to CRI-O maintainers, not me.)

"github.com/cri-o/cri-o/internal/log"
)

Expand Down Expand Up @@ -76,7 +77,7 @@
// 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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the function declaration, I don’t understand what isRuntimePullImage means, at all. Please document the parameter, if it is going to remain.

// DeleteContainer deletes a container, unmounting it first if need be.
DeleteContainer(ctx context.Context, idOrName string) error

Expand Down Expand Up @@ -157,7 +158,7 @@
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
}
Expand Down Expand Up @@ -187,21 +188,31 @@

// 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t know why this parameter needs to exist, and I’m generally suspicious of call trees where a function deep down behaves significantly differently based on an option (and I do consider reading local files vs. reaching out to a network significant); can’t the code be refactored to

  • read imageConfig earlier
  • possibly turn the reading of imageConfig into a stand-alone helper,
  • then move the read of imageConfig to the callers?

Callers could then pass the imageConfig directly instead of the isRuntimePullImage flag. That’s not any more complex, at least.

var err error
artifact := ociartifact.New()

Check warning on line 194 in internal/storage/runtime.go

View check run for this annotation

Codecov / codecov/patch

internal/storage/runtime.go#L193-L194

Added lines #L193 - L194 were not covered by tests

imageConfig, err := image.OCIConfig(r.ctx)
if err != nil {
return ContainerInfo{}, err
}
imageConfig, err = artifact.GetConfig(r.ctx, metadata.ImageName, &ociartifact.PullOptions{})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metadata.ImageName == userRequestedImage is not a stable identifier. Nothing guarantees that the data read here is going to be consistent with some other use of userRequestedImage, e.g. if a tag is moved.

I don’t understand the full context but this strongly suggests to me a high risk of a significant structural problem.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do credentials for registry access come from? Can this work without them, only relying on the node-global cluster pull secret ??!

if err != nil {
return ContainerInfo{}, err
}

Check warning on line 199 in internal/storage/runtime.go

View check run for this annotation

Codecov / codecov/patch

internal/storage/runtime.go#L196-L199

Added lines #L196 - L199 were not covered by tests
} else {
ref, err := istorage.Transport.NewStoreReference(r.storageImageServer.GetStore(), nil, template.imageID.privateID)
if err != nil {
return ContainerInfo{}, err
}

Check warning on line 204 in internal/storage/runtime.go

View check run for this annotation

Codecov / codecov/patch

internal/storage/runtime.go#L203-L204

Added lines #L203 - L204 were not covered by tests
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
}

Check warning on line 214 in internal/storage/runtime.go

View check run for this annotation

Codecov / codecov/patch

internal/storage/runtime.go#L213-L214

Added lines #L213 - L214 were not covered by tests
}
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)
Expand All @@ -222,7 +233,12 @@
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 = ""
}

Check warning on line 240 in internal/storage/runtime.go

View check run for this annotation

Codecov / codecov/patch

internal/storage/runtime.go#L238-L240

Added lines #L238 - L240 were not covered by tests
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)
Expand Down Expand Up @@ -350,10 +366,10 @@
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,
Expand All @@ -365,7 +381,7 @@
namespace: "",
attempt: attempt,
privileged: privileged,
}, idMappingsOptions, labelOptions)
}, idMappingsOptions, labelOptions, isRuntimePullImage)
}

func (r *runtimeService) deleteLayerIfMapped(imageID, layerID string) {
Expand Down
Loading
Loading