Skip to content

Commit 1915338

Browse files
committed
Add commands to list and get olmv1 operators and catalogs
Signed-off-by: Artur Zych <[email protected]>
1 parent 1acf90b commit 1915338

File tree

12 files changed

+482
-0
lines changed

12 files changed

+482
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/onsi/gomega v1.36.2
1212
github.com/opencontainers/image-spec v1.1.0
1313
github.com/operator-framework/api v0.29.0
14+
github.com/operator-framework/catalogd v1.1.0
1415
github.com/operator-framework/operator-controller v1.1.0
1516
github.com/operator-framework/operator-lifecycle-manager v0.23.1
1617
github.com/operator-framework/operator-registry v1.50.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE
242242
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
243243
github.com/operator-framework/api v0.29.0 h1:TxAR8RCO+I4FjRrY4PSMgnlmbxNWeD8pzHXp7xwHNmw=
244244
github.com/operator-framework/api v0.29.0/go.mod h1:0whQE4mpMDd2zyHkQe+bFa3DLoRs6oGWCbu8dY/3pyc=
245+
github.com/operator-framework/catalogd v1.1.0 h1:mu2DYL5mpREEAAP+uPG+CMSsfsJkgrIasgLRG8nvwJg=
246+
github.com/operator-framework/catalogd v1.1.0/go.mod h1:8Je9CqMPwhNgRoqGX5OPsLYHsEoTDvPnELLLKRw1RHE=
245247
github.com/operator-framework/operator-controller v1.1.0 h1:h0b1SSuv9ZiIgI8dTuutSPVL4uIeyvTW3gOB2szkBMQ=
246248
github.com/operator-framework/operator-controller v1.1.0/go.mod h1:dJIt5/gfm1n3y9IeX4kpSlpu4CFq8WFVHU2n9ZDVUkA=
247249
github.com/operator-framework/operator-lifecycle-manager v0.23.1 h1:Xw2ml1T4W2ieoFaVwanW/eFlZ11yAOJZUpUI8RLSql8=
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package olmv1
2+
3+
import (
4+
"github.com/spf13/cobra"
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+
)
10+
11+
// NewCatalogInstalledGetCmd handles get commands in the form of:
12+
// catalog(s) [operatorName] - this will either list all the installed operators
13+
// if no operatorName has been provided or display the details of the specific
14+
// one otherwise
15+
func NewCatalogInstalledGetCmd(cfg *action.Configuration) *cobra.Command {
16+
i := v1action.NewCatalogInstalledGet(cfg)
17+
i.Logf = log.Printf
18+
19+
cmd := &cobra.Command{
20+
Use: "catalog",
21+
Aliases: []string{"catalogs"},
22+
Args: cobra.RangeArgs(0, 1),
23+
Short: "Display one or many installed catalogs",
24+
Run: func(cmd *cobra.Command, args []string) {
25+
if len(args) == 1 {
26+
i.CatalogName = args[0]
27+
}
28+
installedCatalogs, err := i.Run(cmd.Context())
29+
if err != nil {
30+
log.Fatalf("failed getting installed catalog(s): %v", err)
31+
}
32+
33+
printFormattedCatalogs(installedCatalogs...)
34+
},
35+
}
36+
37+
return cmd
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package olmv1
2+
3+
import (
4+
"github.com/spf13/cobra"
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+
)
10+
11+
// NewOperatorInstalledGetCmd handles get commands in the form of:
12+
// operator(s) [operatorName] - this will either list all the installed operators
13+
// if no operatorName has been provided or display the details of the specific
14+
// one otherwise
15+
func NewOperatorInstalledGetCmd(cfg *action.Configuration) *cobra.Command {
16+
i := v1action.NewOperatorInstalledGet(cfg)
17+
i.Logf = log.Printf
18+
19+
cmd := &cobra.Command{
20+
Use: "operator",
21+
Aliases: []string{"operators"},
22+
Args: cobra.RangeArgs(0, 1),
23+
Short: "Display one or many installed operators",
24+
Run: func(cmd *cobra.Command, args []string) {
25+
if len(args) == 1 {
26+
i.OperatorName = args[0]
27+
}
28+
installedExtensions, err := i.Run(cmd.Context())
29+
if err != nil {
30+
log.Fatalf("failed getting installed operator(s): %v", err)
31+
}
32+
33+
printFormattedOperators(installedExtensions...)
34+
},
35+
}
36+
37+
return cmd
38+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package olmv1
2+
3+
import (
4+
"cmp"
5+
"fmt"
6+
"os"
7+
"slices"
8+
"text/tabwriter"
9+
"time"
10+
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/util/duration"
13+
14+
catalogdv1 "github.com/operator-framework/catalogd/api/v1"
15+
olmv1 "github.com/operator-framework/operator-controller/api/v1"
16+
)
17+
18+
func printFormattedOperators(extensions ...olmv1.ClusterExtension) {
19+
tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0)
20+
_, _ = fmt.Fprint(tw, "NAME\tINSTALLED BUNDLE\tVERSION\tSOURCE TYPE\tINSTALLED\tPROGRESSING\tAGE\n")
21+
22+
// sort by name
23+
slices.SortFunc(extensions, func(a, b olmv1.ClusterExtension) int {
24+
return cmp.Compare(a.Name, b.Name)
25+
})
26+
27+
for _, ext := range extensions {
28+
age := time.Since(ext.CreationTimestamp.Time)
29+
_, _ = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
30+
ext.Name,
31+
ext.Status.Install.Bundle.Name,
32+
ext.Status.Install.Bundle.Version,
33+
ext.Spec.Source.SourceType,
34+
status(ext.Status.Conditions, olmv1.TypeInstalled),
35+
status(ext.Status.Conditions, olmv1.TypeProgressing),
36+
duration.HumanDuration(age),
37+
)
38+
}
39+
_ = tw.Flush()
40+
}
41+
42+
func printFormattedCatalogs(catalogs ...catalogdv1.ClusterCatalog) {
43+
tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0)
44+
_, _ = fmt.Fprint(tw, "NAME\tAVAILABILITY\tPRIORITY\tLASTUNPACKED\tSERVING\tAGE\n")
45+
46+
// sort by availability first, then by priority and name
47+
slices.SortFunc(catalogs, func(a, b catalogdv1.ClusterCatalog) int {
48+
return cmp.Or(
49+
cmp.Compare(a.Spec.AvailabilityMode, a.Spec.AvailabilityMode),
50+
cmp.Compare(a.Spec.Priority, b.Spec.Priority),
51+
cmp.Compare(a.Name, b.Name),
52+
)
53+
})
54+
55+
for _, cat := range catalogs {
56+
age := time.Since(cat.CreationTimestamp.Time)
57+
lastUnpacked := time.Since(cat.Status.LastUnpacked.Time)
58+
_, _ = fmt.Fprintf(tw, "%s\t%s\t%d\t%s\t%s\t%s\n",
59+
cat.Name,
60+
string(cat.Spec.AvailabilityMode),
61+
cat.Spec.Priority,
62+
duration.HumanDuration(lastUnpacked),
63+
status(cat.Status.Conditions, catalogdv1.TypeServing),
64+
duration.HumanDuration(age),
65+
)
66+
}
67+
_ = tw.Flush()
68+
}
69+
70+
func status(conditions []metav1.Condition, typ string) string {
71+
for _, condition := range conditions {
72+
if condition.Type == typ {
73+
return string(condition.Status)
74+
}
75+
}
76+
77+
return "Unknown"
78+
}

internal/cmd/olmv1.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,20 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
1414
Long: "Manage operators via OLMv1 in a cluster from the command line.",
1515
}
1616

17+
getCmd := &cobra.Command{
18+
Use: "get",
19+
Short: "Display one or many OLMv1-specific resource(s)",
20+
Long: "Display one or many OLMv1-specific resource(s)",
21+
}
22+
getCmd.AddCommand(
23+
olmv1.NewOperatorInstalledGetCmd(cfg),
24+
olmv1.NewCatalogInstalledGetCmd(cfg),
25+
)
26+
1727
cmd.AddCommand(
1828
olmv1.NewOperatorInstallCmd(cfg),
1929
olmv1.NewOperatorUninstallCmd(cfg),
30+
getCmd,
2031
)
2132

2233
return cmd
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package action_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestCommand(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Internal action Suite")
13+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package action
2+
3+
import (
4+
"context"
5+
6+
"k8s.io/apimachinery/pkg/types"
7+
"sigs.k8s.io/controller-runtime/pkg/client"
8+
9+
olmv1catalogd "github.com/operator-framework/catalogd/api/v1"
10+
11+
"github.com/operator-framework/kubectl-operator/pkg/action"
12+
)
13+
14+
type CatalogInstalledGet struct {
15+
config *action.Configuration
16+
CatalogName string
17+
18+
Logf func(string, ...interface{})
19+
}
20+
21+
func NewCatalogInstalledGet(cfg *action.Configuration) *CatalogInstalledGet {
22+
return &CatalogInstalledGet{
23+
config: cfg,
24+
Logf: func(string, ...interface{}) {},
25+
}
26+
}
27+
28+
func (i *CatalogInstalledGet) Run(ctx context.Context) ([]olmv1catalogd.ClusterCatalog, error) {
29+
// get
30+
if i.CatalogName != "" {
31+
var result olmv1catalogd.ClusterCatalog
32+
33+
opKey := types.NamespacedName{Name: i.CatalogName}
34+
err := i.config.Client.Get(ctx, opKey, &result)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
return []olmv1catalogd.ClusterCatalog{result}, nil
40+
}
41+
42+
// list
43+
var result olmv1catalogd.ClusterCatalogList
44+
err := i.config.Client.List(ctx, &result, &client.ListOptions{})
45+
46+
return result.Items, err
47+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package action_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"slices"
7+
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"sigs.k8s.io/controller-runtime/pkg/client"
13+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
14+
15+
olmv1catalogd "github.com/operator-framework/catalogd/api/v1"
16+
17+
internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
18+
"github.com/operator-framework/kubectl-operator/pkg/action"
19+
)
20+
21+
var _ = Describe("CatalogInstalledGet", func() {
22+
setupEnv := func(catalogs ...client.Object) action.Configuration {
23+
var cfg action.Configuration
24+
25+
sch, err := action.NewScheme()
26+
Expect(err).To(BeNil())
27+
28+
cl := fake.NewClientBuilder().
29+
WithObjects(catalogs...).
30+
WithScheme(sch).
31+
Build()
32+
cfg.Scheme = sch
33+
cfg.Client = cl
34+
35+
return cfg
36+
}
37+
38+
It("lists all installed catalogs", func() {
39+
cfg := setupEnv(setupTestCatalogs(3)...)
40+
41+
getter := internalaction.NewCatalogInstalledGet(&cfg)
42+
catalogs, err := getter.Run(context.TODO())
43+
Expect(err).To(BeNil())
44+
Expect(catalogs).NotTo(BeEmpty())
45+
Expect(catalogs).To(HaveLen(3))
46+
47+
for _, testCatalogName := range []string{"cat1", "cat2", "cat3"} {
48+
Expect(slices.ContainsFunc(catalogs, func(cat olmv1catalogd.ClusterCatalog) bool {
49+
return cat.Name == testCatalogName
50+
})).To(BeTrue())
51+
}
52+
})
53+
54+
It("returns empty list in case no catalogs were found", func() {
55+
cfg := setupEnv()
56+
57+
getter := internalaction.NewOperatorInstalledGet(&cfg)
58+
operators, err := getter.Run(context.TODO())
59+
Expect(err).To(BeNil())
60+
Expect(operators).To(BeEmpty())
61+
})
62+
63+
It("gets an installed catalog", func() {
64+
cfg := setupEnv(setupTestCatalogs(3)...)
65+
66+
getter := internalaction.NewCatalogInstalledGet(&cfg)
67+
getter.CatalogName = "cat2"
68+
operators, err := getter.Run(context.TODO())
69+
Expect(err).To(BeNil())
70+
Expect(operators).NotTo(BeEmpty())
71+
Expect(operators).To(HaveLen(1))
72+
Expect(operators[0].Name).To(Equal("cat2"))
73+
})
74+
75+
It("returns an empty list when an installed catalog was not found", func() {
76+
cfg := setupEnv()
77+
78+
getter := internalaction.NewOperatorInstalledGet(&cfg)
79+
getter.OperatorName = "cat2"
80+
operators, err := getter.Run(context.TODO())
81+
Expect(err).NotTo(BeNil())
82+
Expect(operators).To(BeEmpty())
83+
})
84+
})
85+
86+
func setupTestCatalogs(n int) []client.Object {
87+
var result []client.Object
88+
for i := 1; i <= n; i++ {
89+
result = append(result, newClusterCatalog(fmt.Sprintf("cat%d", i)))
90+
}
91+
92+
return result
93+
}
94+
95+
func newClusterCatalog(name string) *olmv1catalogd.ClusterCatalog {
96+
return &olmv1catalogd.ClusterCatalog{
97+
ObjectMeta: metav1.ObjectMeta{Name: name},
98+
}
99+
}

0 commit comments

Comments
 (0)