Add checks in both containerd-based and distribution-based image pull
code paths to detect and AI model images early in the pull process.
These are not yet supported directly by the Engine and need to be
handled by the `docker model` CLI plugin.
For distribution-based pull, reject the AI models pulls.
For containerd image service only emit a warning.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
"fmt" |
| 6 | 6 |
"io" |
| 7 | 7 |
"strings" |
| 8 |
+ "sync/atomic" |
|
| 8 | 9 |
"time" |
| 9 | 10 |
|
| 10 | 11 |
containerd "github.com/containerd/containerd/v2/client" |
| ... | ... |
@@ -150,17 +151,27 @@ func (i *ImageService) pullTag(ctx context.Context, ref reference.Named, platfor |
| 150 | 150 |
} |
| 151 | 151 |
}() |
| 152 | 152 |
|
| 153 |
- var sentPullingFrom bool |
|
| 153 |
+ var sentPullingFrom, sentModelNotSupported atomic.Bool |
|
| 154 | 154 |
ah := c8dimages.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
| 155 | 155 |
if desc.MediaType == c8dimages.MediaTypeDockerSchema1Manifest {
|
| 156 | 156 |
return nil, distribution.DeprecatedSchema1ImageError(ref) |
| 157 | 157 |
} |
| 158 |
+ |
|
| 159 |
+ ociAiArtifactManifest := c8dimages.IsManifestType(desc.MediaType) && isModelMediaType(desc.ArtifactType) |
|
| 160 |
+ aiMediaType := isModelMediaType(desc.MediaType) |
|
| 161 |
+ |
|
| 162 |
+ if ociAiArtifactManifest || aiMediaType {
|
|
| 163 |
+ if !sentModelNotSupported.Load() {
|
|
| 164 |
+ sentModelNotSupported.Store(true) |
|
| 165 |
+ progress.Message(out, "", `WARNING: AI models are not supported by the Engine yet, did you mean to use "docker model pull/run" instead?`) |
|
| 166 |
+ } |
|
| 167 |
+ } |
|
| 158 | 168 |
if c8dimages.IsLayerType(desc.MediaType) {
|
| 159 | 169 |
id := stringid.TruncateID(desc.Digest.String()) |
| 160 | 170 |
progress.Update(out, id, "Pulling fs layer") |
| 161 | 171 |
} |
| 162 | 172 |
if c8dimages.IsManifestType(desc.MediaType) {
|
| 163 |
- if !sentPullingFrom {
|
|
| 173 |
+ if !sentPullingFrom.Load() {
|
|
| 164 | 174 |
var tagOrDigest string |
| 165 | 175 |
if tagged, ok := ref.(reference.Tagged); ok {
|
| 166 | 176 |
tagOrDigest = tagged.Tag() |
| ... | ... |
@@ -168,7 +179,7 @@ func (i *ImageService) pullTag(ctx context.Context, ref reference.Named, platfor |
| 168 | 168 |
tagOrDigest = ref.String() |
| 169 | 169 |
} |
| 170 | 170 |
progress.Message(out, tagOrDigest, "Pulling from "+reference.Path(ref)) |
| 171 |
- sentPullingFrom = true |
|
| 171 |
+ sentPullingFrom.Store(true) |
|
| 172 | 172 |
} |
| 173 | 173 |
|
| 174 | 174 |
available, _, _, missing, err := c8dimages.Check(ctx, i.content, desc, p) |
| ... | ... |
@@ -254,3 +265,7 @@ func writeStatus(out progress.Output, requestedTag string, newerDownloaded bool) |
| 254 | 254 |
progress.Message(out, "", "Status: Image is up to date for "+requestedTag) |
| 255 | 255 |
} |
| 256 | 256 |
} |
| 257 |
+ |
|
| 258 |
+func isModelMediaType(mediaType string) bool {
|
|
| 259 |
+ return strings.HasPrefix(strings.ToLower(mediaType), "application/vnd.docker.ai.") |
|
| 260 |
+} |
| ... | ... |
@@ -3,6 +3,7 @@ package containerd |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"fmt" |
| 6 |
+ "strings" |
|
| 6 | 7 |
|
| 7 | 8 |
c8dimages "github.com/containerd/containerd/v2/core/images" |
| 8 | 9 |
"github.com/containerd/containerd/v2/core/leases" |
| ... | ... |
@@ -104,6 +105,15 @@ func (i *ImageService) getImageSnapshot(ctx context.Context, descriptor *ocispec |
| 104 | 104 |
return "", err |
| 105 | 105 |
} |
| 106 | 106 |
|
| 107 |
+ cfgDesc, err := platformImg.Config(ctx) |
|
| 108 |
+ if err != nil {
|
|
| 109 |
+ return "", err |
|
| 110 |
+ } |
|
| 111 |
+ |
|
| 112 |
+ if strings.HasPrefix(strings.ToLower(cfgDesc.MediaType), "application/vnd.docker.ai.") {
|
|
| 113 |
+ return "", errors.New("Running AI models directly by the Engine is not supported yet, please use 'docker model run' instead")
|
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 107 | 116 |
unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter) |
| 108 | 117 |
if err != nil {
|
| 109 | 118 |
return "", err |
| ... | ... |
@@ -203,6 +203,14 @@ func retryOnError(err error) error {
|
| 203 | 203 |
return err |
| 204 | 204 |
} |
| 205 | 205 |
|
| 206 |
+type AIModelNotSupportedError struct{}
|
|
| 207 |
+ |
|
| 208 |
+func (e AIModelNotSupportedError) Error() string {
|
|
| 209 |
+ return `AI models are not yet supported by the Engine, please use "docker model pull/run" instead` |
|
| 210 |
+} |
|
| 211 |
+ |
|
| 212 |
+func (e AIModelNotSupportedError) InvalidParameter() {}
|
|
| 213 |
+ |
|
| 206 | 214 |
type invalidManifestClassError struct {
|
| 207 | 215 |
mediaType string |
| 208 | 216 |
class string |
| ... | ... |
@@ -498,6 +498,9 @@ func (p *puller) validateMediaType(mediaType string) error {
|
| 498 | 498 |
|
| 499 | 499 |
func checkSupportedMediaType(mediaType string) error {
|
| 500 | 500 |
lowerMt := strings.ToLower(mediaType) |
| 501 |
+ if strings.HasPrefix(lowerMt, "application/vnd.docker.ai.") {
|
|
| 502 |
+ return AIModelNotSupportedError{}
|
|
| 503 |
+ } |
|
| 501 | 504 |
for _, mt := range supportedMediaTypes {
|
| 502 | 505 |
// The should either be an exact match, or have a valid prefix |
| 503 | 506 |
// we append a "." when matching prefixes to exclude "false positives"; |