Skip to content

Commit ca6ced0

Browse files
authored
Merge pull request #4449 from ChengyuZhu6/manifest-push
manifest: support nerdctl manifest push command
2 parents b6d2dc4 + 322e8ca commit ca6ced0

File tree

7 files changed

+484
-5
lines changed

7 files changed

+484
-5
lines changed

cmd/nerdctl/manifest/manifest.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func Command() *cobra.Command {
3737
CreateCommand(),
3838
AnnotateCommand(),
3939
RemoveCommand(),
40+
PushCommand(),
4041
)
4142

4243
return cmd

cmd/nerdctl/manifest/manifest_push.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 manifest
18+
19+
import (
20+
"github.com/spf13/cobra"
21+
22+
"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion"
23+
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
24+
"github.com/containerd/nerdctl/v2/pkg/api/types"
25+
"github.com/containerd/nerdctl/v2/pkg/cmd/manifest"
26+
)
27+
28+
func PushCommand() *cobra.Command {
29+
var cmd = &cobra.Command{
30+
Use: "push [OPTIONS] INDEX/MANIFESTLIST",
31+
Short: "Push a manifest list to a registry",
32+
Args: cobra.ExactArgs(1),
33+
RunE: pushAction,
34+
ValidArgsFunction: pushShellComplete,
35+
SilenceUsage: true,
36+
SilenceErrors: true,
37+
}
38+
cmd.Flags().Bool("insecure", false, "Allow communication with an insecure registry")
39+
cmd.Flags().Bool("purge", false, "Remove the manifest list after pushing")
40+
return cmd
41+
}
42+
43+
func processPushFlags(cmd *cobra.Command) (types.ManifestPushOptions, error) {
44+
globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
45+
if err != nil {
46+
return types.ManifestPushOptions{}, err
47+
}
48+
49+
insecure, err := cmd.Flags().GetBool("insecure")
50+
if err != nil {
51+
return types.ManifestPushOptions{}, err
52+
}
53+
purge, err := cmd.Flags().GetBool("purge")
54+
if err != nil {
55+
return types.ManifestPushOptions{}, err
56+
}
57+
58+
return types.ManifestPushOptions{
59+
Stdout: cmd.OutOrStdout(),
60+
GOptions: globalOptions,
61+
Insecure: insecure,
62+
Purge: purge,
63+
}, nil
64+
}
65+
66+
func pushAction(cmd *cobra.Command, args []string) error {
67+
pushOptions, err := processPushFlags(cmd)
68+
if err != nil {
69+
return err
70+
}
71+
err = manifest.Push(cmd.Context(), args[0], pushOptions)
72+
if err != nil {
73+
return err
74+
}
75+
return nil
76+
}
77+
78+
func pushShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
79+
return completion.ImageNames(cmd)
80+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 manifest
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"testing"
23+
24+
"github.com/containerd/nerdctl/mod/tigron/expect"
25+
"github.com/containerd/nerdctl/mod/tigron/require"
26+
"github.com/containerd/nerdctl/mod/tigron/test"
27+
28+
"github.com/containerd/nerdctl/v2/pkg/testutil"
29+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
30+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry"
31+
)
32+
33+
func TestManifestPushErrors(t *testing.T) {
34+
testCase := nerdtest.Setup()
35+
invalidName := "invalid/name/with/special@chars"
36+
testCase.SubTests = []*test.Case{
37+
{
38+
Description: "require-one-argument",
39+
Command: test.Command("manifest", "push", "arg1", "arg2"),
40+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
41+
return &test.Expected{
42+
ExitCode: 1,
43+
}
44+
},
45+
},
46+
{
47+
Description: "invalid-list-name",
48+
Command: test.Command("manifest", "push", invalidName),
49+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
50+
return &test.Expected{
51+
ExitCode: 1,
52+
Errors: []error{errors.New(data.Labels().Get("error"))},
53+
}
54+
},
55+
Data: test.WithLabels(map[string]string{
56+
"error": "invalid reference format",
57+
}),
58+
},
59+
}
60+
61+
testCase.Run(t)
62+
}
63+
64+
func TestManifestPush(t *testing.T) {
65+
nerdtest.Setup()
66+
67+
var registryTokenAuthHTTPSRandom *registry.Server
68+
var tokenServer *registry.TokenAuthServer
69+
70+
manifestRef := testutil.GetTestImageWithoutTag("alpine") + "@" + testutil.GetTestImageManifestDigest("alpine", "linux/amd64")
71+
expectedDigest := "sha256:5317ce2da263afa23570c692d62c1b01381285b2198b3ea9739ce64bec22aff2"
72+
73+
testCase := &test.Case{
74+
Require: require.All(
75+
require.Linux,
76+
nerdtest.Registry,
77+
),
78+
Setup: func(data test.Data, helpers test.Helpers) {
79+
registryTokenAuthHTTPSRandom, tokenServer = nerdtest.RegistryWithTokenAuth(data, helpers, "admin", "badmin", 0, true)
80+
tokenServer.Setup(data, helpers)
81+
registryTokenAuthHTTPSRandom.Setup(data, helpers)
82+
},
83+
Cleanup: func(data test.Data, helpers test.Helpers) {
84+
if registryTokenAuthHTTPSRandom != nil {
85+
registryTokenAuthHTTPSRandom.Cleanup(data, helpers)
86+
}
87+
if tokenServer != nil {
88+
tokenServer.Cleanup(data, helpers)
89+
}
90+
},
91+
SubTests: []*test.Case{
92+
{
93+
Description: "push-to-registry",
94+
Require: require.Not(nerdtest.Docker),
95+
Setup: func(data test.Data, helpers test.Helpers) {
96+
targetRef := fmt.Sprintf("%s:%d/%s",
97+
registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, "test-list-push:v1")
98+
helpers.Ensure("pull", manifestRef)
99+
helpers.Ensure("tag", manifestRef, targetRef)
100+
helpers.Ensure("--hosts-dir", registryTokenAuthHTTPSRandom.HostsDir, "login", "-u", "admin", "-p", "badmin",
101+
fmt.Sprintf("%s:%d", registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port))
102+
helpers.Ensure("push", "--hosts-dir", registryTokenAuthHTTPSRandom.HostsDir, targetRef)
103+
helpers.Ensure("rmi", targetRef)
104+
helpers.Ensure("manifest", "create", "--insecure", targetRef+"-success", targetRef)
105+
},
106+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
107+
targetRef := fmt.Sprintf("%s:%d/%s",
108+
registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, "test-list-push:v1")
109+
return helpers.Command("manifest", "push", "--insecure", targetRef+"-success")
110+
},
111+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
112+
return &test.Expected{
113+
ExitCode: 0,
114+
Output: expect.Contains(data.Labels().Get("output")),
115+
}
116+
},
117+
Data: test.WithLabels(map[string]string{
118+
"output": expectedDigest,
119+
}),
120+
},
121+
},
122+
}
123+
testCase.Run(t)
124+
}

docs/command-reference.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ It does not necessarily mean that the corresponding features are missing in cont
5656
- [:whale: nerdctl manifest annotate](#whale-nerdctl-manifest-annotate)
5757
- [:whale: nerdctl manifest create](#whale-nerdctl-manifest-create)
5858
- [:whale: nerdctl manifest inspect](#whale-nerdctl-manifest-inspect)
59+
- [:whale: nerdctl manifest push](#whale-nerdctl-manifest-push)
5960
- [:whale: nerdctl manifest rm](#whale-nerdctl-manifest-rm)
6061
- [Registry](#registry)
6162
- [:whale: nerdctl login](#whale-nerdctl-login)
@@ -1108,6 +1109,24 @@ nerdctl manifest inspect alpine:3.22.1
11081109
nerdctl manifest inspect alpine@sha256:eafc1edb577d2e9b458664a15f23ea1c370214193226069eb22921169fc7e43f
11091110
```
11101111

1112+
### :whale: nerdctl manifest push
1113+
1114+
Push a manifest list to a registry.
1115+
1116+
Usage: `nerdctl manifest push [OPTIONS] INDEX/MANIFESTLIST`
1117+
1118+
Flags:
1119+
1120+
- `--insecure`: Allow communication with an insecure registry
1121+
- `--purge`: Remove the manifest list after pushing
1122+
1123+
Examples:
1124+
1125+
```bash
1126+
# Push a manifest list to a registry
1127+
nerdctl manifest push myapp:latest
1128+
```
1129+
11111130
### :whale: nerdctl manifest rm
11121131

11131132
Remove one or more index/manifest lists.

pkg/api/types/manifest_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,13 @@ type ManifestInspectOptions struct {
4848
// Allow communication with an insecure registry
4949
Insecure bool
5050
}
51+
52+
// ManifestPushOptions specifies options for `nerdctl manifest push`.
53+
type ManifestPushOptions struct {
54+
Stdout io.Writer
55+
GOptions GlobalCommandOptions
56+
// Allow communication with an insecure registry
57+
Insecure bool
58+
// Remove the manifest list after pushing
59+
Purge bool
60+
}

0 commit comments

Comments
 (0)