Skip to content

Commit 0694abb

Browse files
committed
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 <[email protected]>
1 parent 9e786e7 commit 0694abb

File tree

6 files changed

+116
-20
lines changed

6 files changed

+116
-20
lines changed

pkg/cmd/image/push.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz"
4444

4545
"github.com/containerd/nerdctl/v2/pkg/api/types"
46+
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
4647
"github.com/containerd/nerdctl/v2/pkg/errutil"
4748
"github.com/containerd/nerdctl/v2/pkg/imgutil"
4849
nerdconverter "github.com/containerd/nerdctl/v2/pkg/imgutil/converter"
@@ -152,8 +153,21 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
152153
return err
153154
}
154155
} else {
155-
if err := imgutil.PushImageWithTransfer(ctx, client, parsedReference, pushRef, ref, options); err != nil {
156-
return err
156+
// Transfer service is available in containerd 1.7, but full support is only in 2.0+
157+
// For containerd 1.7, use the legacy resolver-based push method for better compatibility
158+
useTransferAPI := containerdutil.SupportsFullTransferService(ctx, client)
159+
if !useTransferAPI {
160+
log.G(ctx).Debug("Detected containerd < 2.0, using legacy push method")
161+
}
162+
163+
if useTransferAPI {
164+
if err := imgutil.PushImageWithTransfer(ctx, client, parsedReference, pushRef, ref, options); err != nil {
165+
return err
166+
}
167+
} else {
168+
if err := pushImageWithLocal(ctx, client, parsedReference, pushRef, ref, options, platMC); err != nil {
169+
return err
170+
}
157171
}
158172
}
159173

pkg/containerdutil/version.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package containerdutil
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/Masterminds/semver/v3"
24+
25+
containerd "github.com/containerd/containerd/v2/client"
26+
)
27+
28+
func ServerSemVer(ctx context.Context, client *containerd.Client) (*semver.Version, error) {
29+
v, err := client.Version(ctx)
30+
if err != nil {
31+
return nil, err
32+
}
33+
sv, err := semver.NewVersion(v.Version)
34+
if err != nil {
35+
return nil, fmt.Errorf("failed to parse the containerd version %q: %w", v.Version, err)
36+
}
37+
return sv, nil
38+
}
39+
40+
// SupportsFullTransferService checks if the containerd version fully supports the Transfer service.
41+
// While containerd 1.7 has Transfer service, full support is only available in 2.0+.
42+
func SupportsFullTransferService(ctx context.Context, client *containerd.Client) bool {
43+
sv, err := ServerSemVer(ctx, client)
44+
if err != nil {
45+
// If we can't determine version, assume it's an older version for safety
46+
return false
47+
}
48+
v20, _ := semver.NewVersion("2.0.0")
49+
return !sv.LessThan(v20)
50+
}

pkg/imgutil/imgutil.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/json"
2222
"errors"
2323
"fmt"
24+
"net/http"
2425
"reflect"
2526

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

4041
"github.com/containerd/nerdctl/v2/pkg/api/types"
42+
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
43+
"github.com/containerd/nerdctl/v2/pkg/errutil"
4144
"github.com/containerd/nerdctl/v2/pkg/healthcheck"
4245
"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker"
4346
"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver"
@@ -131,7 +134,49 @@ func EnsureImage(ctx context.Context, client *containerd.Client, rawRef string,
131134
return nil, err
132135
}
133136

134-
return PullImageWithTransfer(ctx, client, parsedReference, rawRef, options)
137+
// Transfer service is available in containerd 1.7, but full support is only in 2.0+
138+
// For containerd 1.7, use the legacy resolver-based pull method for better compatibility
139+
useTransferAPI := containerdutil.SupportsFullTransferService(ctx, client)
140+
if !useTransferAPI {
141+
log.G(ctx).Debug("Detected containerd < 2.0, using legacy pull method")
142+
}
143+
144+
if useTransferAPI {
145+
return PullImageWithTransfer(ctx, client, parsedReference, rawRef, options)
146+
}
147+
148+
var dOpts []dockerconfigresolver.Opt
149+
if options.GOptions.InsecureRegistry {
150+
log.G(ctx).Warnf("skipping verifying HTTPS certs for %q", parsedReference.Domain)
151+
dOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))
152+
}
153+
dOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(options.GOptions.HostsDir))
154+
resolver, err := dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)
155+
if err != nil {
156+
return nil, err
157+
}
158+
159+
img, err := PullImage(ctx, client, resolver, parsedReference.String(), options)
160+
if err != nil {
161+
// 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".
162+
if !errors.Is(err, http.ErrSchemeMismatch) && !errutil.IsErrConnectionRefused(err) {
163+
return nil, err
164+
}
165+
if options.GOptions.InsecureRegistry {
166+
log.G(ctx).WithError(err).Warnf("server %q does not seem to support HTTPS, falling back to plain HTTP", parsedReference.Domain)
167+
dOpts = append(dOpts, dockerconfigresolver.WithPlainHTTP(true))
168+
resolver, err = dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)
169+
if err != nil {
170+
return nil, err
171+
}
172+
return PullImage(ctx, client, resolver, parsedReference.String(), options)
173+
}
174+
log.G(ctx).WithError(err).Errorf("server %q does not seem to support HTTPS", parsedReference.Domain)
175+
log.G(ctx).Info("Hint: you may want to try --insecure-registry to allow plain HTTP (if you are in a trusted network)")
176+
return nil, err
177+
178+
}
179+
return img, nil
135180
}
136181

137182
// ResolveDigest resolves `rawRef` and returns its descriptor digest.

pkg/infoutil/infoutil.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"strings"
2626
"time"
2727

28-
"github.com/Masterminds/semver/v3"
2928
"github.com/docker/docker/pkg/sysinfo"
3029

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

149-
func ServerSemVer(ctx context.Context, client *containerd.Client) (*semver.Version, error) {
150-
v, err := client.Version(ctx)
151-
if err != nil {
152-
return nil, err
153-
}
154-
sv, err := semver.NewVersion(v.Version)
155-
if err != nil {
156-
return nil, fmt.Errorf("failed to parse the containerd version %q: %w", v.Version, err)
157-
}
158-
return sv, nil
159-
}
160-
161148
func buildctlVersion() dockercompat.ComponentVersion {
162149
buildctlBinary, err := buildkitutil.BuildctlBinary()
163150
if err != nil {

pkg/taskutil/taskutil.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import (
4646

4747
"github.com/containerd/nerdctl/v2/pkg/cioutil"
4848
"github.com/containerd/nerdctl/v2/pkg/consoleutil"
49-
"github.com/containerd/nerdctl/v2/pkg/infoutil"
49+
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
5050
)
5151

5252
// TaskOptions contains options for creating a new task
@@ -201,7 +201,7 @@ func NewTask(ctx context.Context, client *containerd.Client, container container
201201
} else {
202202
var in io.Reader
203203
if opts.IsInteractive {
204-
if sv, err := infoutil.ServerSemVer(ctx, client); err != nil {
204+
if sv, err := containerdutil.ServerSemVer(ctx, client); err != nil {
205205
log.G(ctx).Warn(err)
206206
} else if sv.LessThan(semver.MustParse("1.6.0-0")) {
207207
log.G(ctx).Warnf("`nerdctl (run|exec) -i` without `-t` expects containerd 1.6 or later, got containerd %v", sv)

pkg/testutil/nerdtest/requirements.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import (
3333

3434
"github.com/containerd/nerdctl/v2/pkg/buildkitutil"
3535
"github.com/containerd/nerdctl/v2/pkg/clientutil"
36+
"github.com/containerd/nerdctl/v2/pkg/containerdutil"
3637
ncdefaults "github.com/containerd/nerdctl/v2/pkg/defaults"
37-
"github.com/containerd/nerdctl/v2/pkg/infoutil"
3838
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
3939
"github.com/containerd/nerdctl/v2/pkg/netutil"
4040
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
@@ -477,7 +477,7 @@ func ContainerdVersion(v string) *test.Requirement {
477477
return false, fmt.Sprintf("failed to create client: %v", err)
478478
}
479479
defer cancel()
480-
if sv, err := infoutil.ServerSemVer(ctx, client); err != nil {
480+
if sv, err := containerdutil.ServerSemVer(ctx, client); err != nil {
481481
return false, err.Error()
482482
} else if sv.LessThan(semver.MustParse(v)) {
483483
return false, fmt.Sprintf("`nerdctl commit --compression expects containerd %s or later, got containerd %v", v, sv)

0 commit comments

Comments
 (0)