Skip to content

Commit ce72c46

Browse files
Adding comments
Signed-off-by: katara-Jayprakash <katarajayprakash@icloud.com>
1 parent 8ed8c60 commit ce72c46

9 files changed

Lines changed: 220 additions & 63 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: 2023 SUSE LLC
1+
# SPDX-FileCopyrightText: 2026 SUSE LLC
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

mgrctl/cmd/get/get.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2026 SUSE LLC
1+
// SPDX-FileCopyrightText: 2026 Jayprakash
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

@@ -29,15 +29,32 @@ func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
2929
cmd := &cobra.Command{
3030
Use: "get <resource-type> [flags]",
3131
Short: L("Display one or many resources"),
32+
Example: ` mgrctl get system
33+
mgrctl get systemgroup
34+
mgrctl get system --page 0 --page-size 10
35+
mgrctl get system --filter "extra_pkg_count>0"`,
36+
Long: L(`Fetch and display Uyuni API resources in table, JSON, or YAML format.
37+
38+
The resource type is passed as the first argument. Filtering and pagination
39+
are handled server-side when the API endpoint supports it.
40+
41+
Available resource types:
42+
` + GetResourceHelp() + `
43+
44+
Filter Operators:
45+
>=, <=, !=, =, >, < (e.g. extra_pkg_count>0)
46+
47+
Custom Columns:
48+
Use -o custom-columns=HEADER:path,HEADER:path to extract specific JSON fields.
49+
Example: -o custom-columns=ID:.id,NAME:.name`),
3250
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
3351
ValidArgs: registeredTypes(),
3452
RunE: func(cmd *cobra.Command, args []string) error {
3553
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, runGet)
3654
},
3755
}
3856

39-
cmd.Flags().StringVarP(&flags.OutputFormat, "output", "o", "table",
40-
L("Output format: table|json|yaml|custom-columns=SPEC|custom-columns-file=PATH"))
57+
utils.AddOutputFlag(cmd, &flags.OutputFormat)
4158
cmd.Flags().StringVar(&flags.Filter, "filter", "",
4259
L("Filter expression (e.g. extra_pkg_count=0)"))
4360
cmd.Flags().IntVar(&flags.Page, "page", 0,
@@ -66,10 +83,14 @@ func runGet(_ *types.GlobalFlags, flags *getFlags, _ *cobra.Command, args []stri
6683
return utils.Errorf(err, L("unable to login to the server"))
6784
}
6885

69-
items, err := fetcher.List(client, flags.Filter, flags.Page, flags.PageSize)
86+
items, total, err := fetcher.List(client, flags.Filter, flags.Page, flags.PageSize)
7087
if err != nil {
7188
return err
7289
}
7390

74-
return printOutput(flags.OutputFormat, items, fetcher.Columns(), os.Stdout)
91+
if total > 0 && flags.PageSize > 0 {
92+
log.Info().Msgf("Fetched %d items out of %d total", len(items), total)
93+
}
94+
95+
return utils.PrintOutput(flags.OutputFormat, items, fetcher.Columns(), os.Stdout)
7596
}

mgrctl/cmd/get/resource.go

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,49 @@
1-
// SPDX-FileCopyrightText: 2026 SUSE LLC
1+
// SPDX-FileCopyrightText: 2026 Jayprakash
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

55
package get
66

77
import (
88
"fmt"
9-
"net/url"
109
"sort"
1110
"strings"
1211

1312
"github.com/uyuni-project/uyuni-tools/shared/api"
1413
apitypes "github.com/uyuni-project/uyuni-tools/shared/api/types"
1514
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
15+
"github.com/uyuni-project/uyuni-tools/shared/utils"
1616
)
1717

18-
type ColumnDef struct {
19-
Header string
20-
Field string
18+
type ResourceFetcher interface {
19+
List(client *api.APIClient, filter string, page, pageSize int) ([]map[string]any, int, error)
20+
Columns() []utils.ColumnDef
2121
}
2222

23-
type ResourceFetcher interface {
24-
List(client *api.APIClient, filter string, page, pageSize int) ([]map[string]any, error)
25-
Columns() []ColumnDef
23+
type Resource struct {
24+
Fetcher ResourceFetcher
25+
Aliases []string
26+
Description string
2627
}
2728

28-
var resourceTypes = map[string]ResourceFetcher{
29-
"system": systemFetcher{},
30-
"systemgroup": systemGroupFetcher{},
29+
var resourceTypes = map[string]Resource{
30+
"system": {
31+
Fetcher: systemFetcher{},
32+
Aliases: []string{"sys"},
33+
Description: "List systems",
34+
},
35+
"systemgroup": {
36+
Fetcher: systemGroupFetcher{},
37+
Aliases: []string{"grp"},
38+
Description: "List system groups",
39+
},
3140
}
3241

3342
func registeredTypes() []string {
34-
names := make([]string, 0, len(resourceTypes))
35-
for name := range resourceTypes {
43+
names := make([]string, 0)
44+
for name, res := range resourceTypes {
3645
names = append(names, name)
46+
names = append(names, res.Aliases...)
3747
}
3848
sort.Strings(names)
3949
return names
@@ -43,40 +53,37 @@ func registeredTypesText() string {
4353
return strings.Join(registeredTypes(), ", ")
4454
}
4555

46-
func lookupFetcher(name string) (ResourceFetcher, error) {
47-
fetcher, ok := resourceTypes[name]
48-
if !ok {
49-
return nil, fmt.Errorf(L("unknown resource type %q; available: %s"), name, registeredTypesText())
56+
func GetResourceHelp() string {
57+
var lines []string
58+
for name, res := range resourceTypes {
59+
aliases := ""
60+
if len(res.Aliases) > 0 {
61+
aliases = fmt.Sprintf(" (%s)", strings.Join(res.Aliases, ", "))
62+
}
63+
lines = append(lines, fmt.Sprintf(" %-20s - %s", name+aliases, L(res.Description)))
5064
}
51-
return fetcher, nil
65+
sort.Strings(lines)
66+
return strings.Join(lines, "\n")
5267
}
5368

54-
func listFiltered(client *api.APIClient, endpoint, filter string, page, pageSize int) ([]map[string]any, error) {
55-
filterKey, filterValue := "", ""
56-
if filter != "" {
57-
filterKey, filterValue = parseFilter(filter)
69+
func lookupFetcher(name string) (ResourceFetcher, error) {
70+
if res, ok := resourceTypes[name]; ok {
71+
return res.Fetcher, nil
5872
}
73+
for _, res := range resourceTypes {
74+
if utils.Contains(res.Aliases, name) {
75+
return res.Fetcher, nil
76+
}
77+
}
78+
return nil, fmt.Errorf(L("unknown resource type %q; available: %s"), name, registeredTypesText())
79+
}
5980

60-
query := url.Values{}
61-
query.Set("filterKey", filterKey)
62-
query.Set("filterValue", filterValue)
63-
query.Set("page", fmt.Sprintf("%d", page))
64-
query.Set("pageSize", fmt.Sprintf("%d", pageSize))
65-
81+
func listFiltered(client *api.APIClient, endpoint, query string) ([]map[string]any, int, error) {
6682
res, err := api.Get[apitypes.FilteredResponse[map[string]any]](
67-
client, fmt.Sprintf("%s?%s", endpoint, query.Encode()),
83+
client, fmt.Sprintf("%s?%s", endpoint, query),
6884
)
6985
if err != nil {
70-
return nil, err
71-
}
72-
return res.Result.Data, nil
73-
}
74-
75-
func parseFilter(expr string) (string, string) {
76-
for _, op := range []string{">=", "<=", "!=", "=", ">", "<"} {
77-
if i := strings.Index(expr, op); i >= 0 {
78-
return strings.TrimSpace(expr[:i]), strings.TrimSpace(expr[i:])
79-
}
86+
return nil, 0, err
8087
}
81-
return strings.TrimSpace(expr), ""
88+
return res.Result.Data, res.Result.Total, nil
8289
}

mgrctl/cmd/get/resource_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-FileCopyrightText: 2026 Jayprakash
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package get
6+
7+
import (
8+
"testing"
9+
)
10+
11+
func TestResourceTypesNoDuplicates(t *testing.T) {
12+
seen := make(map[string]bool)
13+
14+
for name, res := range resourceTypes {
15+
if seen[name] {
16+
t.Errorf("Duplicate resource key found: %s", name)
17+
}
18+
seen[name] = true
19+
20+
for _, alias := range res.Aliases {
21+
if seen[alias] {
22+
t.Errorf("Duplicate resource alias found: %s (in resource %s)", alias, name)
23+
}
24+
seen[alias] = true
25+
}
26+
}
27+
}

mgrctl/cmd/get/system.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,46 @@
1-
// SPDX-FileCopyrightText: 2026 SUSE LLC
1+
// SPDX-FileCopyrightText: 2026 Jayprakash
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

55
package get
66

7-
import "github.com/uyuni-project/uyuni-tools/shared/api"
7+
import (
8+
"fmt"
9+
"net/url"
10+
"strings"
11+
12+
"github.com/uyuni-project/uyuni-tools/shared/api"
13+
"github.com/uyuni-project/uyuni-tools/shared/utils"
14+
)
815

916
type systemFetcher struct{}
1017

11-
func (systemFetcher) List(client *api.APIClient, filter string, page, pageSize int) ([]map[string]any, error) {
12-
return listFiltered(client, "system/listSystemsFiltered", filter, page, pageSize)
18+
func (systemFetcher) List(client *api.APIClient, filter string, page, pageSize int) ([]map[string]any, int, error) {
19+
filterKey, filterValue := "", ""
20+
if filter != "" {
21+
filterKey, filterValue = parseFilter(filter)
22+
}
23+
24+
query := url.Values{}
25+
query.Set("filterKey", filterKey)
26+
query.Set("filterValue", filterValue)
27+
query.Set("page", fmt.Sprintf("%d", page))
28+
query.Set("pageSize", fmt.Sprintf("%d", pageSize))
29+
30+
return listFiltered(client, "system/listSystemsFiltered", query.Encode())
31+
}
32+
33+
func parseFilter(expr string) (string, string) {
34+
for _, op := range []string{">=", "<=", "!=", "=", ">", "<"} {
35+
if i := strings.Index(expr, op); i >= 0 {
36+
return strings.TrimSpace(expr[:i]), strings.TrimSpace(expr[i:])
37+
}
38+
}
39+
return strings.TrimSpace(expr), ""
1340
}
1441

15-
func (systemFetcher) Columns() []ColumnDef {
16-
return []ColumnDef{
42+
func (systemFetcher) Columns() []utils.ColumnDef {
43+
return []utils.ColumnDef{
1744
{Header: "ID", Field: "id"},
1845
{Header: "NAME", Field: "name"},
1946
{Header: "LAST_CHECKIN", Field: "last_checkin"},

mgrctl/cmd/get/systemgroup.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1-
// SPDX-FileCopyrightText: 2026 SUSE LLC
1+
// SPDX-FileCopyrightText: 2026 Jayprakash
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

55
package get
66

7-
import "github.com/uyuni-project/uyuni-tools/shared/api"
7+
import (
8+
"github.com/uyuni-project/uyuni-tools/shared/api"
9+
"github.com/uyuni-project/uyuni-tools/shared/utils"
10+
)
811

912
type systemGroupFetcher struct{}
1013

11-
func (systemGroupFetcher) List(client *api.APIClient, _ string, _, _ int) ([]map[string]any, error) {
14+
func (systemGroupFetcher) List(client *api.APIClient, _ string, _, _ int) ([]map[string]any, int, error) {
1215
res, err := api.Get[[]map[string]any](client, "systemgroup/listAllGroups")
1316
if err != nil {
14-
return nil, err
17+
return nil, 0, err
1518
}
16-
return res.Result, nil
19+
return res.Result, len(res.Result), nil
1720
}
1821

19-
func (systemGroupFetcher) Columns() []ColumnDef {
20-
return []ColumnDef{
22+
func (systemGroupFetcher) Columns() []utils.ColumnDef {
23+
return []utils.ColumnDef{
2124
{Header: "ID", Field: "id"},
2225
{Header: "NAME", Field: "name"},
2326
{Header: "DESCRIPTION", Field: "description"},

shared/api/types/filtered.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2026 SUSE LLC
1+
// SPDX-FileCopyrightText: 2026 Jayprakash
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
// SPDX-FileCopyrightText: 2026 SUSE LLC
1+
// SPDX-FileCopyrightText: 2026 Jayprakash
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5-
package get
5+
package utils
66

77
import (
88
"encoding/json"
@@ -12,10 +12,22 @@ import (
1212
"strings"
1313
"text/tabwriter"
1414

15+
"github.com/spf13/cobra"
16+
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
1517
"gopkg.in/yaml.v2"
1618
)
1719

18-
func printOutput(format string, items []map[string]any, cols []ColumnDef, out io.Writer) error {
20+
type ColumnDef struct {
21+
Header string
22+
Field string
23+
}
24+
25+
func AddOutputFlag(cmd *cobra.Command, outputFormat *string) {
26+
cmd.Flags().StringVarP(outputFormat, "output", "o", "table",
27+
L("Output format: table|json|yaml|custom-columns=SPEC|custom-columns-file=PATH"))
28+
}
29+
30+
func PrintOutput(format string, items []map[string]any, cols []ColumnDef, out io.Writer) error {
1931
if strings.HasPrefix(format, "custom-columns=") {
2032
spec := strings.TrimPrefix(format, "custom-columns=")
2133
parsed := parseCustomColumns(spec)
@@ -97,6 +109,7 @@ func formatValue(v any) string {
97109

98110
func parseCustomColumns(spec string) []ColumnDef {
99111
var cols []ColumnDef
112+
100113
for _, part := range strings.Split(spec, ",") {
101114
kv := strings.SplitN(part, ":", 2)
102115
if len(kv) == 2 {
@@ -157,10 +170,12 @@ func fieldValue(item map[string]any, path string) (any, bool) {
157170
if part == "" {
158171
return nil, false
159172
}
173+
160174
m, ok := current.(map[string]any)
161175
if !ok {
162176
return nil, false
163177
}
178+
164179
current, ok = m[part]
165180
if !ok {
166181
return nil, false

0 commit comments

Comments
 (0)