Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Support legacy push/pull for containerd 1.7.x
Add version detection to automatically select Transfer service (2.0+)
or legacy resolver methods (< 2.0) for better compatibility.

Signed-off-by: ChengyuZhu6 <hudson@cyzhu.com>
  • Loading branch information
ChengyuZhu6 committed Dec 3, 2025
commit 7d8ab7c5c18048a6c55af9ae14fad1f5ffa78f62
18 changes: 16 additions & 2 deletions pkg/cmd/image/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz"

"github.com/containerd/nerdctl/v2/pkg/api/types"
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
"github.com/containerd/nerdctl/v2/pkg/errutil"
"github.com/containerd/nerdctl/v2/pkg/imgutil"
nerdconverter "github.com/containerd/nerdctl/v2/pkg/imgutil/converter"
Expand Down Expand Up @@ -152,8 +153,21 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
return err
}
} else {
if err := imgutil.PushImageWithTransfer(ctx, client, parsedReference, pushRef, ref, options); err != nil {
return err
// Transfer service is available in containerd 1.7, but full support is only in 2.0+
// For containerd 1.7, use the legacy resolver-based push method for better compatibility
useTransferAPI := containerdutil.SupportsFullTransferService(ctx, client)
if !useTransferAPI {
log.G(ctx).Debug("Detected containerd < 2.0, using legacy push method")
}

if useTransferAPI {
if err := imgutil.PushImageWithTransfer(ctx, client, parsedReference, pushRef, ref, options); err != nil {
return err
}
} else {
if err := pushImageWithLocal(ctx, client, parsedReference, pushRef, ref, options, platMC); err != nil {
return err
}
}
}

Expand Down
53 changes: 53 additions & 0 deletions pkg/containerdutil/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Copyright The containerd Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package containerdutil

import (
"context"
"fmt"

"github.com/Masterminds/semver/v3"

containerd "github.com/containerd/containerd/v2/client"
)

func ServerSemVer(ctx context.Context, client *containerd.Client) (*semver.Version, error) {
v, err := client.Version(ctx)
if err != nil {
return nil, err
}
sv, err := semver.NewVersion(v.Version)
if err != nil {
return nil, fmt.Errorf("failed to parse the containerd version %q: %w", v.Version, err)
}
return sv, nil
}

// SupportsFullTransferService checks if the containerd version fully supports the Transfer service.
// While containerd 1.7 has Transfer service, full support is only available in 2.0+.
Copy link
Copy Markdown
Member

@AkihiroSuda AkihiroSuda Nov 28, 2025

Choose a reason for hiding this comment

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

Suggested change
// While containerd 1.7 has Transfer service, full support is only available in 2.0+.
// While containerd 1.7 has Transfer service, full support is only available in 2.0+.
// The following features are missing in containerd 1.7:
// - [ADD MISSING FEATURES]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

// The following features are missing in containerd 1.7:
// - Non-distributable artifacts support
// - Registry configuration options: WithHostDir(), WithDefaultScheme() etc.
func SupportsFullTransferService(ctx context.Context, client *containerd.Client) bool {
sv, err := ServerSemVer(ctx, client)
if err != nil {
// If we can't determine version, assume it's an older version for safety
return false
}
v20, _ := semver.NewVersion("2.0.0")
return !sv.LessThan(v20)
}
47 changes: 46 additions & 1 deletion pkg/imgutil/imgutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"

"github.com/opencontainers/image-spec/identity"
Expand All @@ -38,6 +39,8 @@ import (
"github.com/containerd/platforms"

"github.com/containerd/nerdctl/v2/pkg/api/types"
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
"github.com/containerd/nerdctl/v2/pkg/errutil"
"github.com/containerd/nerdctl/v2/pkg/healthcheck"
"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker"
"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver"
Expand Down Expand Up @@ -131,7 +134,49 @@ func EnsureImage(ctx context.Context, client *containerd.Client, rawRef string,
return nil, err
}

return PullImageWithTransfer(ctx, client, parsedReference, rawRef, options)
// Transfer service is available in containerd 1.7, but full support is only in 2.0+
// For containerd 1.7, use the legacy resolver-based pull method for better compatibility
useTransferAPI := containerdutil.SupportsFullTransferService(ctx, client)
if !useTransferAPI {
log.G(ctx).Debug("Detected containerd < 2.0, using legacy pull method")
}

if useTransferAPI {
return PullImageWithTransfer(ctx, client, parsedReference, rawRef, options)
}

var dOpts []dockerconfigresolver.Opt
if options.GOptions.InsecureRegistry {
log.G(ctx).Warnf("skipping verifying HTTPS certs for %q", parsedReference.Domain)
dOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))
}
dOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(options.GOptions.HostsDir))
resolver, err := dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)
if err != nil {
return nil, err
}

img, err := PullImage(ctx, client, resolver, parsedReference.String(), options)
if err != nil {
// In some circumstance (e.g. people just use 80 port to support pure http), the error will contain message like "dial tcp <port>: connection refused".
if !errors.Is(err, http.ErrSchemeMismatch) && !errutil.IsErrConnectionRefused(err) {
return nil, err
}
if options.GOptions.InsecureRegistry {
log.G(ctx).WithError(err).Warnf("server %q does not seem to support HTTPS, falling back to plain HTTP", parsedReference.Domain)
dOpts = append(dOpts, dockerconfigresolver.WithPlainHTTP(true))
resolver, err = dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)
if err != nil {
return nil, err
}
return PullImage(ctx, client, resolver, parsedReference.String(), options)
}
log.G(ctx).WithError(err).Errorf("server %q does not seem to support HTTPS", parsedReference.Domain)
log.G(ctx).Info("Hint: you may want to try --insecure-registry to allow plain HTTP (if you are in a trusted network)")
return nil, err

}
return img, nil
}

// ResolveDigest resolves `rawRef` and returns its descriptor digest.
Expand Down
13 changes: 0 additions & 13 deletions pkg/infoutil/infoutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"strings"
"time"

"github.com/Masterminds/semver/v3"
"github.com/docker/docker/pkg/sysinfo"

containerd "github.com/containerd/containerd/v2/client"
Expand Down Expand Up @@ -146,18 +145,6 @@ func ServerVersion(ctx context.Context, client *containerd.Client) (*dockercompa
return v, nil
}

func ServerSemVer(ctx context.Context, client *containerd.Client) (*semver.Version, error) {
Comment thread
AkihiroSuda marked this conversation as resolved.
v, err := client.Version(ctx)
if err != nil {
return nil, err
}
sv, err := semver.NewVersion(v.Version)
if err != nil {
return nil, fmt.Errorf("failed to parse the containerd version %q: %w", v.Version, err)
}
return sv, nil
}

func buildctlVersion() dockercompat.ComponentVersion {
buildctlBinary, err := buildkitutil.BuildctlBinary()
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/taskutil/taskutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import (

"github.com/containerd/nerdctl/v2/pkg/cioutil"
"github.com/containerd/nerdctl/v2/pkg/consoleutil"
"github.com/containerd/nerdctl/v2/pkg/infoutil"
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
)

// TaskOptions contains options for creating a new task
Expand Down Expand Up @@ -201,7 +201,7 @@ func NewTask(ctx context.Context, client *containerd.Client, container container
} else {
var in io.Reader
if opts.IsInteractive {
if sv, err := infoutil.ServerSemVer(ctx, client); err != nil {
if sv, err := containerdutil.ServerSemVer(ctx, client); err != nil {
log.G(ctx).Warn(err)
} else if sv.LessThan(semver.MustParse("1.6.0-0")) {
log.G(ctx).Warnf("`nerdctl (run|exec) -i` without `-t` expects containerd 1.6 or later, got containerd %v", sv)
Expand Down
4 changes: 2 additions & 2 deletions pkg/testutil/nerdtest/requirements.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ import (

"github.com/containerd/nerdctl/v2/pkg/buildkitutil"
"github.com/containerd/nerdctl/v2/pkg/clientutil"
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
ncdefaults "github.com/containerd/nerdctl/v2/pkg/defaults"
"github.com/containerd/nerdctl/v2/pkg/infoutil"
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
"github.com/containerd/nerdctl/v2/pkg/netutil"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
Expand Down Expand Up @@ -477,7 +477,7 @@ func ContainerdVersion(v string) *test.Requirement {
return false, fmt.Sprintf("failed to create client: %v", err)
}
defer cancel()
if sv, err := infoutil.ServerSemVer(ctx, client); err != nil {
if sv, err := containerdutil.ServerSemVer(ctx, client); err != nil {
return false, err.Error()
} else if sv.LessThan(semver.MustParse(v)) {
return false, fmt.Sprintf("`nerdctl commit --compression expects containerd %s or later, got containerd %v", v, sv)
Expand Down
Loading