Skip to content

Commit 359a0f0

Browse files
committed
clustercatalog content discovery for packages and bundle versions
Signed-off-by: Ankita Thomas <ankithom@redhat.com>
1 parent 4b54083 commit 359a0f0

File tree

7 files changed

+609
-0
lines changed

7 files changed

+609
-0
lines changed

go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ require (
5757
github.com/felixge/httpsnoop v1.0.4 // indirect
5858
github.com/fsnotify/fsnotify v1.8.0 // indirect
5959
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
60+
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
61+
github.com/go-git/go-billy/v5 v5.6.1 // indirect
62+
github.com/go-git/go-git/v5 v5.13.1 // indirect
6063
github.com/go-logr/logr v1.4.2 // indirect
6164
github.com/go-logr/stdr v1.2.2 // indirect
6265
github.com/go-openapi/jsonpointer v0.21.0 // indirect
@@ -71,14 +74,18 @@ require (
7174
github.com/google/gofuzz v1.2.0 // indirect
7275
github.com/google/uuid v1.6.0 // indirect
7376
github.com/gorilla/mux v1.8.1 // indirect
77+
github.com/gorilla/websocket v1.5.0 // indirect
7478
github.com/h2non/filetype v1.1.3 // indirect
7579
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c // indirect
7680
github.com/inconshreveable/mousetrap v1.1.0 // indirect
81+
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
82+
github.com/joelanford/ignore v0.1.1 // indirect
7783
github.com/josharian/intern v1.0.0 // indirect
7884
github.com/json-iterator/go v1.1.12 // indirect
7985
github.com/klauspost/compress v1.18.0 // indirect
8086
github.com/mailru/easyjson v0.9.0 // indirect
8187
github.com/moby/locker v1.0.1 // indirect
88+
github.com/moby/spdystream v0.5.0 // indirect
8289
github.com/moby/sys/capability v0.3.0 // indirect
8390
github.com/moby/sys/mountinfo v0.7.2 // indirect
8491
github.com/moby/sys/sequential v0.5.0 // indirect
@@ -87,6 +94,7 @@ require (
8794
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
8895
github.com/modern-go/reflect2 v1.0.2 // indirect
8996
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
97+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
9098
github.com/nxadm/tail v1.4.8 // indirect
9199
github.com/opencontainers/go-digest v1.0.0 // indirect
92100
github.com/opencontainers/runtime-spec v1.2.0 // indirect
@@ -116,6 +124,7 @@ require (
116124
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
117125
gopkg.in/inf.v0 v0.9.1 // indirect
118126
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
127+
gopkg.in/warnings.v0 v0.1.2 // indirect
119128
gopkg.in/yaml.v3 v3.0.1 // indirect
120129
k8s.io/klog/v2 v2.130.1 // indirect
121130
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package olmv1
2+
3+
import (
4+
"os"
5+
6+
"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
7+
v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
8+
"github.com/operator-framework/kubectl-operator/pkg/action"
9+
"github.com/spf13/cobra"
10+
"github.com/spf13/pflag"
11+
)
12+
13+
// NewCatalogInstalledGetCmd handles get commands in the form of:
14+
// catalog(s) [catalog_name] - this will either list all the installed operators
15+
// if no catalog_name has been provided or display the details of the specific
16+
// one otherwise
17+
func NewCatalogSearchCmd(cfg *action.Configuration) *cobra.Command {
18+
i := v1action.NewCatalogSearch(cfg)
19+
i.Logf = log.Printf
20+
21+
cmd := &cobra.Command{
22+
Use: "catalog",
23+
Aliases: []string{"catalogs"},
24+
Args: cobra.RangeArgs(0, 1),
25+
Short: "Search catalogs for installable operators matching parameters",
26+
Run: func(cmd *cobra.Command, args []string) {
27+
catalogContents, err := i.Run(cmd.Context())
28+
if err != nil {
29+
log.Fatalf("failed querying catalog(s): %v", err)
30+
}
31+
switch i.OutputFormat {
32+
case "", "table":
33+
printFormattedDeclCfg(os.Stdout, catalogContents, i.ListVersions)
34+
case "json":
35+
printDeclCfgJSON(os.Stdout, catalogContents)
36+
case "yaml":
37+
printDeclCfgYAML(os.Stdout, catalogContents)
38+
default:
39+
log.Fatalf("unsupported output format %s: allwed formats are (json|yaml|table)", i.OutputFormat)
40+
}
41+
},
42+
}
43+
bindCatalogSearchFlags(cmd.Flags(), i)
44+
45+
return cmd
46+
}
47+
48+
func bindCatalogSearchFlags(fs *pflag.FlagSet, i *v1action.CatalogSearch) {
49+
fs.StringVar(&i.Catalog, "catalog", "", "Catalog to search on. If not provided, all available catalogs are searched.")
50+
fs.StringVarP(&i.Selector, "selector", "l", "", "Selector (label query) to filter catalogs on, supports '=', '==', and '!='")
51+
fs.StringVarP(&i.OutputFormat, "output", "o", "", "output format. One of: (yaml|json)")
52+
fs.BoolVar(&i.ListVersions, "list-versions", false, "List all versions available for each package")
53+
fs.StringVar(&i.Package, "package", "", "Search for package by name. If empty, all available packages will be listed")
54+
fs.StringVar(&i.CatalogdNamespace, "catalogd-namespace", "olmv1-system", "Namespace for the catalogd controller")
55+
fs.StringVar(&i.Timeout, "timeout", "5m", "Timeout for fetching catalog contents")
56+
// installable vs uninstallable, all versions, channels
57+
// fs.StringVar(&i.showAll, "image", "", "Image reference for the catalog source. Leave unset to retain the current image.")
58+
}

internal/cmd/internal/olmv1/printing.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ package olmv1
33
import (
44
"cmp"
55
"fmt"
6+
"io"
67
"os"
78
"slices"
9+
"sort"
10+
"strings"
811
"text/tabwriter"
912
"time"
1013

1114
"github.com/blang/semver/v4"
15+
"github.com/operator-framework/operator-registry/alpha/declcfg"
16+
"github.com/operator-framework/operator-registry/alpha/property"
1217
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1318
"k8s.io/apimachinery/pkg/util/duration"
19+
"k8s.io/apimachinery/pkg/util/json"
1420

1521
olmv1 "github.com/operator-framework/operator-controller/api/v1"
1622
)
@@ -63,6 +69,107 @@ func printFormattedCatalogs(catalogs ...olmv1.ClusterCatalog) {
6369
_ = tw.Flush()
6470
}
6571

72+
func printFormattedDeclCfg(w io.Writer, catalogDcfg map[string]*declcfg.DeclarativeConfig, listVersions bool) {
73+
tw := tabwriter.NewWriter(w, 3, 4, 2, ' ', 0)
74+
if listVersions {
75+
_, _ = fmt.Fprint(tw, "PACKAGE\tCATALOG\tPROVIDER\tVERSION\n")
76+
} else {
77+
_, _ = fmt.Fprint(tw, "PACKAGE\tCATALOG\tPROVIDER\tCHANNELS\n")
78+
}
79+
sortedCatalogs := []string{}
80+
for catalogName := range catalogDcfg {
81+
sortedCatalogs = append(sortedCatalogs, catalogName)
82+
}
83+
sort.Strings(sortedCatalogs)
84+
for _, catalogName := range sortedCatalogs {
85+
dcfg := catalogDcfg[catalogName]
86+
type dcfgPrintMeta struct {
87+
provider string
88+
channels []string
89+
versions []semver.Version
90+
}
91+
pkgProviders := map[string]*dcfgPrintMeta{}
92+
sort.SliceStable(dcfg.Packages, func(i, j int) bool {
93+
return dcfg.Packages[i].Name < dcfg.Packages[j].Name
94+
})
95+
96+
if listVersions {
97+
for _, b := range dcfg.Bundles {
98+
if pkgProviders[b.Package] == nil {
99+
pkgProviders[b.Package] = &dcfgPrintMeta{}
100+
}
101+
if pkgProviders[b.Package].versions == nil {
102+
pkgProviders[b.Package].versions = []semver.Version{}
103+
}
104+
for _, versionProp := range b.Properties {
105+
if versionProp.Type == property.TypePackage {
106+
var pkgProp property.Package
107+
if err := json.Unmarshal(versionProp.Value, &pkgProp); err == nil && len(pkgProp.Version) > 0 {
108+
if parsedVersion, err := semver.Parse(pkgProp.Version); err == nil {
109+
pkgProviders[b.Package].versions = append(pkgProviders[b.Package].versions, parsedVersion)
110+
}
111+
}
112+
continue
113+
}
114+
if versionProp.Type == property.TypeCSVMetadata {
115+
var pkgProp property.CSVMetadata
116+
if err := json.Unmarshal(versionProp.Value, &pkgProp); err == nil && len(pkgProp.Provider.Name) > 0 {
117+
pkgProviders[b.Package].provider = pkgProp.Provider.Name
118+
}
119+
continue
120+
}
121+
}
122+
}
123+
} else {
124+
for _, c := range dcfg.Channels {
125+
if pkgProviders[c.Package] == nil {
126+
pkgProviders[c.Package] = &dcfgPrintMeta{}
127+
}
128+
if pkgProviders[c.Package].channels == nil {
129+
pkgProviders[c.Package].channels = []string{}
130+
}
131+
pkgProviders[c.Package].channels = append(pkgProviders[c.Package].channels, c.Name)
132+
}
133+
}
134+
135+
for _, p := range dcfg.Packages {
136+
if listVersions {
137+
sort.SliceStable(pkgProviders[p.Name].versions, func(i, j int) bool {
138+
return pkgProviders[p.Name].versions[i].GT(pkgProviders[p.Name].versions[j])
139+
})
140+
for _, v := range pkgProviders[p.Name].versions {
141+
_, _ = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n",
142+
p.Name,
143+
catalogName,
144+
pkgProviders[p.Name].provider,
145+
v)
146+
}
147+
} else {
148+
sort.Strings(pkgProviders[p.Name].channels)
149+
_, _ = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n",
150+
p.Name,
151+
catalogName,
152+
pkgProviders[p.Name].provider,
153+
strings.Join(pkgProviders[p.Name].channels, ","))
154+
}
155+
}
156+
}
157+
_ = tw.Flush()
158+
}
159+
160+
func printDeclCfgJSON(w io.Writer, catalogDcfg map[string]*declcfg.DeclarativeConfig) {
161+
for _, dcfg := range catalogDcfg {
162+
_ = declcfg.WriteJSON(*dcfg, w)
163+
}
164+
}
165+
166+
func printDeclCfgYAML(w io.Writer, catalogDcfg map[string]*declcfg.DeclarativeConfig) {
167+
for _, dcfg := range catalogDcfg {
168+
_ = declcfg.WriteYAML(*dcfg, w)
169+
_, _ = w.Write([]byte("---\n"))
170+
}
171+
}
172+
66173
// sortExtensions sorts extensions in place and uses the following sorting order:
67174
// name (asc), version (desc)
68175
func sortExtensions(extensions []olmv1.ClusterExtension) {

internal/cmd/olmv1.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,20 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
5858
}
5959
installCmd.AddCommand(olmv1.NewExtensionInstallCmd(cfg))
6060

61+
searchCmd := &cobra.Command{
62+
Use: "search",
63+
Short: "Search for packages",
64+
Long: "Search one or all available catalogs for packages or versions",
65+
}
66+
searchCmd.AddCommand(olmv1.NewCatalogSearchCmd(cfg))
67+
6168
cmd.AddCommand(
6269
installCmd,
6370
getCmd,
6471
createCmd,
6572
deleteCmd,
6673
updateCmd,
74+
searchCmd,
6775
)
6876

6977
return cmd

0 commit comments

Comments
 (0)