Skip to content

Commit 3044e84

Browse files
committed
feat: add --format flag to namespace ls command
Signed-off-by: Ruihua Wen <[email protected]>
1 parent bd89fee commit 3044e84

File tree

7 files changed

+239
-100
lines changed

7 files changed

+239
-100
lines changed

cmd/nerdctl/namespace/namespace.go

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,9 @@
1717
package namespace
1818

1919
import (
20-
"fmt"
21-
"sort"
22-
"strings"
23-
"text/tabwriter"
24-
2520
"github.com/spf13/cobra"
2621

27-
"github.com/containerd/containerd/v2/pkg/namespaces"
28-
"github.com/containerd/log"
29-
3022
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
31-
"github.com/containerd/nerdctl/v2/pkg/clientutil"
32-
"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore"
3323
)
3424

3525
func Command() *cobra.Command {
@@ -50,90 +40,3 @@ func Command() *cobra.Command {
5040
cmd.AddCommand(inspectCommand())
5141
return cmd
5242
}
53-
54-
func listCommand() *cobra.Command {
55-
cmd := &cobra.Command{
56-
Use: "ls",
57-
Aliases: []string{"list"},
58-
Short: "List containerd namespaces",
59-
RunE: listAction,
60-
SilenceUsage: true,
61-
SilenceErrors: true,
62-
}
63-
cmd.Flags().BoolP("quiet", "q", false, "Only display names")
64-
return cmd
65-
}
66-
67-
func listAction(cmd *cobra.Command, args []string) error {
68-
globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
69-
if err != nil {
70-
return err
71-
}
72-
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)
73-
if err != nil {
74-
return err
75-
}
76-
defer cancel()
77-
78-
nsService := client.NamespaceService()
79-
nsList, err := nsService.List(ctx)
80-
if err != nil {
81-
return err
82-
}
83-
quiet, err := cmd.Flags().GetBool("quiet")
84-
if err != nil {
85-
return err
86-
}
87-
if quiet {
88-
for _, ns := range nsList {
89-
fmt.Fprintln(cmd.OutOrStdout(), ns)
90-
}
91-
return nil
92-
}
93-
dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
94-
if err != nil {
95-
return err
96-
}
97-
98-
w := tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)
99-
// no "NETWORKS", because networks are global objects
100-
fmt.Fprintln(w, "NAME\tCONTAINERS\tIMAGES\tVOLUMES\tLABELS")
101-
for _, ns := range nsList {
102-
ctx = namespaces.WithNamespace(ctx, ns)
103-
var numContainers, numImages, numVolumes int
104-
var labelStrings []string
105-
106-
containers, err := client.Containers(ctx)
107-
if err != nil {
108-
log.L.Warn(err)
109-
}
110-
numContainers = len(containers)
111-
112-
images, err := client.ImageService().List(ctx)
113-
if err != nil {
114-
log.L.Warn(err)
115-
}
116-
numImages = len(images)
117-
118-
volStore, err := volumestore.New(dataStore, ns)
119-
if err != nil {
120-
log.L.Warn(err)
121-
} else {
122-
numVolumes, err = volStore.Count()
123-
if err != nil {
124-
log.L.Warn(err)
125-
}
126-
}
127-
128-
labels, err := client.NamespaceService().Labels(ctx, ns)
129-
if err != nil {
130-
return err
131-
}
132-
for k, v := range labels {
133-
labelStrings = append(labelStrings, strings.Join([]string{k, v}, "="))
134-
}
135-
sort.Strings(labelStrings)
136-
fmt.Fprintf(w, "%s\t%d\t%d\t%d\t%v\t\n", ns, numContainers, numImages, numVolumes, strings.Join(labelStrings, ","))
137-
}
138-
return w.Flush()
139-
}

cmd/nerdctl/namespace/namespace_inspect.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,5 @@ func inspectAction(cmd *cobra.Command, args []string) error {
7575
}
7676

7777
func namespaceInspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
78-
// show namespace names
7978
return completion.NamespaceNames(cmd, args, toComplete)
8079
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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 namespace
18+
19+
import (
20+
"github.com/spf13/cobra"
21+
22+
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
23+
"github.com/containerd/nerdctl/v2/pkg/api/types"
24+
"github.com/containerd/nerdctl/v2/pkg/clientutil"
25+
"github.com/containerd/nerdctl/v2/pkg/cmd/namespace"
26+
)
27+
28+
func listCommand() *cobra.Command {
29+
cmd := &cobra.Command{
30+
Use: "ls",
31+
Aliases: []string{"list"},
32+
Short: "List containerd namespaces",
33+
RunE: listAction,
34+
SilenceUsage: true,
35+
SilenceErrors: true,
36+
}
37+
cmd.Flags().BoolP("quiet", "q", false, "Only display names")
38+
cmd.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'")
39+
return cmd
40+
}
41+
42+
func listOptions(cmd *cobra.Command) (types.NamespaceListOptions, error) {
43+
globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
44+
if err != nil {
45+
return types.NamespaceListOptions{}, err
46+
}
47+
format, err := cmd.Flags().GetString("format")
48+
if err != nil {
49+
return types.NamespaceListOptions{}, err
50+
}
51+
quiet, err := cmd.Flags().GetBool("quiet")
52+
if err != nil {
53+
return types.NamespaceListOptions{}, err
54+
}
55+
return types.NamespaceListOptions{
56+
GOptions: globalOptions,
57+
Format: format,
58+
Quiet: quiet,
59+
Stdout: cmd.OutOrStdout(),
60+
}, nil
61+
}
62+
63+
func listAction(cmd *cobra.Command, args []string) error {
64+
options, err := listOptions(cmd)
65+
if err != nil {
66+
return err
67+
}
68+
69+
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)
70+
if err != nil {
71+
return err
72+
}
73+
defer cancel()
74+
75+
return namespace.List(ctx, client, options)
76+
}

cmd/nerdctl/namespace/namespace_remove.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,5 @@ func removeAction(cmd *cobra.Command, args []string) error {
7373
}
7474

7575
func namespaceRemoveShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
76-
// show namespace names
7776
return completion.NamespaceNames(cmd, args, toComplete)
7877
}

cmd/nerdctl/namespace/namespace_update.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,5 @@ func updateAction(cmd *cobra.Command, args []string) error {
7171
}
7272

7373
func namespaceUpdateShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
74-
// show namespace names
7574
return completion.NamespaceNames(cmd, args, toComplete)
7675
}

pkg/api/types/namespace_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,13 @@ type NamespaceInspectOptions struct {
4343
// Format the output using the given Go template, e.g, '{{json .}}'
4444
Format string
4545
}
46+
47+
// NamespaceListOptions specifies options for `nerdctl namespace ls`.
48+
type NamespaceListOptions struct {
49+
Stdout io.Writer
50+
GOptions GlobalCommandOptions
51+
// Format the output using the given Go template, e.g, '{{json .}}'
52+
Format string
53+
// Quiet suppresses extra information and only prints namespace names
54+
Quiet bool
55+
}

pkg/cmd/namespace/list.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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 namespace
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"errors"
23+
"fmt"
24+
"sort"
25+
"strings"
26+
"text/tabwriter"
27+
"text/template"
28+
29+
containerd "github.com/containerd/containerd/v2/client"
30+
"github.com/containerd/containerd/v2/pkg/namespaces"
31+
"github.com/containerd/log"
32+
33+
"github.com/containerd/nerdctl/v2/pkg/api/types"
34+
"github.com/containerd/nerdctl/v2/pkg/clientutil"
35+
"github.com/containerd/nerdctl/v2/pkg/formatter"
36+
"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore"
37+
)
38+
39+
func List(ctx context.Context, client *containerd.Client, options types.NamespaceListOptions) error {
40+
nsStore := client.NamespaceService()
41+
nsList, err := nsStore.List(ctx)
42+
if err != nil {
43+
return err
44+
}
45+
46+
dataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)
47+
if err != nil {
48+
return err
49+
}
50+
51+
w := options.Stdout
52+
var tmpl *template.Template
53+
namespaceMap := make(map[string]namespace, len(nsList))
54+
for _, ns := range nsList {
55+
ctx = namespaces.WithNamespace(ctx, ns)
56+
var numContainers, numImages, numVolumes int
57+
58+
containers, err := client.Containers(ctx)
59+
if err != nil {
60+
log.L.Warn(err)
61+
}
62+
numContainers = len(containers)
63+
64+
images, err := client.ImageService().List(ctx)
65+
if err != nil {
66+
log.L.Warn(err)
67+
}
68+
numImages = len(images)
69+
70+
volStore, err := volumestore.New(dataStore, ns)
71+
if err != nil {
72+
log.L.Warn(err)
73+
} else {
74+
numVolumes, err = volStore.Count()
75+
if err != nil {
76+
log.L.Warn(err)
77+
}
78+
}
79+
80+
labels, err := client.NamespaceService().Labels(ctx, ns)
81+
if err != nil {
82+
return err
83+
}
84+
namespaceMap[ns] = namespace{
85+
Name: ns,
86+
Containers: numContainers,
87+
Images: numImages,
88+
Volumes: numVolumes,
89+
Labels: labels,
90+
}
91+
}
92+
93+
switch options.Format {
94+
case "", "table", "wide":
95+
if !options.Quiet {
96+
w = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)
97+
// no "NETWORKS", because networks are global objects
98+
fmt.Fprintln(w, "NAME\tCONTAINERS\tIMAGES\tVOLUMES\tLABELS")
99+
}
100+
case "raw":
101+
return errors.New("unsupported format: \"raw\"")
102+
default:
103+
if options.Quiet {
104+
return errors.New("format and quiet must not be specified together")
105+
}
106+
var err error
107+
tmpl, err = formatter.ParseTemplate(options.Format)
108+
if err != nil {
109+
return err
110+
}
111+
}
112+
113+
for _, namespace := range namespaceMap {
114+
if tmpl != nil {
115+
var b bytes.Buffer
116+
if err := tmpl.Execute(&b, namespace); err != nil {
117+
return err
118+
}
119+
if _, err := fmt.Fprintln(w, b.String()); err != nil {
120+
return err
121+
}
122+
} else if options.Quiet {
123+
if _, err := fmt.Fprintln(w, namespace.Name); err != nil {
124+
return err
125+
}
126+
} else {
127+
format := "%s\t%d\t%d\t%d\t%v\t\n"
128+
var labelStrings []string
129+
for k, v := range namespace.Labels {
130+
labelStrings = append(labelStrings, strings.Join([]string{k, v}, "="))
131+
}
132+
sort.Strings(labelStrings)
133+
args := []interface{}{}
134+
args = append(args, namespace.Name, namespace.Containers, namespace.Images, namespace.Volumes, strings.Join(labelStrings, ","))
135+
if _, err := fmt.Fprintf(w, format, args...); err != nil {
136+
return err
137+
}
138+
}
139+
}
140+
141+
if f, ok := w.(formatter.Flusher); ok {
142+
return f.Flush()
143+
}
144+
return nil
145+
}
146+
147+
type namespace struct {
148+
Name string
149+
Containers int
150+
Images int
151+
Volumes int
152+
Labels map[string]string
153+
}

0 commit comments

Comments
 (0)