Skip to content

Commit c788f7c

Browse files
authored
Add commands to list and get olmv1 operators and catalogs (#218)
Signed-off-by: Artur Zych <[email protected]> Co-authored-by: Artur Zych <[email protected]>
1 parent 1acf90b commit c788f7c

14 files changed

+576
-1
lines changed

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ go 1.23.0
55
toolchain go1.23.4
66

77
require (
8+
github.com/blang/semver/v4 v4.0.0
89
github.com/containerd/containerd v1.7.25
910
github.com/containerd/platforms v0.2.1
1011
github.com/onsi/ginkgo v1.16.5
1112
github.com/onsi/gomega v1.36.2
1213
github.com/opencontainers/image-spec v1.1.0
1314
github.com/operator-framework/api v0.29.0
15+
github.com/operator-framework/catalogd v1.1.0
1416
github.com/operator-framework/operator-controller v1.1.0
1517
github.com/operator-framework/operator-lifecycle-manager v0.23.1
1618
github.com/operator-framework/operator-registry v1.50.0
@@ -32,7 +34,6 @@ require (
3234
github.com/Microsoft/go-winio v0.6.2 // indirect
3335
github.com/Microsoft/hcsshim v0.12.9 // indirect
3436
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
35-
github.com/blang/semver/v4 v4.0.0 // indirect
3637
github.com/containerd/cgroups/v3 v3.0.3 // indirect
3738
github.com/containerd/containerd/api v1.8.0 // indirect
3839
github.com/containerd/continuity v0.4.4 // indirect

go.sum

+2
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=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package olmv1
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+
}
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) [catalog_name] - this will either list all the installed operators
13+
// if no catalog_name 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 [catalog_name]",
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+
}
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) [operator_name] - this will either list all the installed operators
13+
// if no operator_name 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 [operator_name]",
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+
}
+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package olmv1
2+
3+
import (
4+
"cmp"
5+
"fmt"
6+
"os"
7+
"slices"
8+
"text/tabwriter"
9+
"time"
10+
11+
"github.com/blang/semver/v4"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/util/duration"
14+
15+
catalogdv1 "github.com/operator-framework/catalogd/api/v1"
16+
olmv1 "github.com/operator-framework/operator-controller/api/v1"
17+
)
18+
19+
func printFormattedOperators(extensions ...olmv1.ClusterExtension) {
20+
tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0)
21+
_, _ = fmt.Fprint(tw, "NAME\tINSTALLED BUNDLE\tVERSION\tSOURCE TYPE\tINSTALLED\tPROGRESSING\tAGE\n")
22+
23+
sortOperators(extensions)
24+
for _, ext := range extensions {
25+
age := time.Since(ext.CreationTimestamp.Time)
26+
_, _ = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
27+
ext.Name,
28+
ext.Status.Install.Bundle.Name,
29+
ext.Status.Install.Bundle.Version,
30+
ext.Spec.Source.SourceType,
31+
status(ext.Status.Conditions, olmv1.TypeInstalled),
32+
status(ext.Status.Conditions, olmv1.TypeProgressing),
33+
duration.HumanDuration(age),
34+
)
35+
}
36+
_ = tw.Flush()
37+
}
38+
39+
func printFormattedCatalogs(catalogs ...catalogdv1.ClusterCatalog) {
40+
tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0)
41+
_, _ = fmt.Fprint(tw, "NAME\tAVAILABILITY\tPRIORITY\tLASTUNPACKED\tSERVING\tAGE\n")
42+
43+
sortCatalogs(catalogs)
44+
for _, cat := range catalogs {
45+
age := time.Since(cat.CreationTimestamp.Time)
46+
lastUnpacked := time.Since(cat.Status.LastUnpacked.Time)
47+
_, _ = fmt.Fprintf(tw, "%s\t%s\t%d\t%s\t%s\t%s\n",
48+
cat.Name,
49+
string(cat.Spec.AvailabilityMode),
50+
cat.Spec.Priority,
51+
duration.HumanDuration(lastUnpacked),
52+
status(cat.Status.Conditions, catalogdv1.TypeServing),
53+
duration.HumanDuration(age),
54+
)
55+
}
56+
_ = tw.Flush()
57+
}
58+
59+
// sortOperators sorts operators in place and uses the following sorting order:
60+
// name (asc), version (desc)
61+
func sortOperators(extensions []olmv1.ClusterExtension) {
62+
slices.SortFunc(extensions, func(a, b olmv1.ClusterExtension) int {
63+
return cmp.Or(
64+
cmp.Compare(a.Name, b.Name),
65+
-semver.MustParse(a.Status.Install.Bundle.Version).Compare(semver.MustParse(b.Status.Install.Bundle.Version)),
66+
)
67+
})
68+
}
69+
70+
// sortCatalogs sorts catalogs in place and uses the following sorting order:
71+
// availability (asc), priority (desc), name (asc)
72+
func sortCatalogs(catalogs []catalogdv1.ClusterCatalog) {
73+
slices.SortFunc(catalogs, func(a, b catalogdv1.ClusterCatalog) int {
74+
return cmp.Or(
75+
cmp.Compare(a.Spec.AvailabilityMode, b.Spec.AvailabilityMode),
76+
-cmp.Compare(a.Spec.Priority, b.Spec.Priority),
77+
cmp.Compare(a.Name, b.Name),
78+
)
79+
})
80+
}
81+
82+
func status(conditions []metav1.Condition, typ string) string {
83+
for _, condition := range conditions {
84+
if condition.Type == typ {
85+
return string(condition.Status)
86+
}
87+
}
88+
89+
return "Unknown"
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package olmv1
2+
3+
import (
4+
. "github.com/onsi/ginkgo"
5+
. "github.com/onsi/gomega"
6+
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
9+
olmv1catalogd "github.com/operator-framework/catalogd/api/v1"
10+
olmv1 "github.com/operator-framework/operator-controller/api/v1"
11+
)
12+
13+
var _ = Describe("SortCatalogs", func() {
14+
It("sorts catalogs in correct order", func() {
15+
catalogs := []olmv1catalogd.ClusterCatalog{
16+
newClusterCatalog("cat-unavailable-0", olmv1catalogd.AvailabilityModeUnavailable, 0),
17+
newClusterCatalog("cat-unavailable-1", olmv1catalogd.AvailabilityModeUnavailable, 1),
18+
newClusterCatalog("cat-available-0", olmv1catalogd.AvailabilityModeAvailable, 0),
19+
newClusterCatalog("cat-available-1", olmv1catalogd.AvailabilityModeAvailable, 1),
20+
}
21+
sortCatalogs(catalogs)
22+
23+
Expect(catalogs[0].Name).To(Equal("cat-available-1"))
24+
Expect(catalogs[1].Name).To(Equal("cat-available-0"))
25+
Expect(catalogs[2].Name).To(Equal("cat-unavailable-1"))
26+
Expect(catalogs[3].Name).To(Equal("cat-unavailable-0"))
27+
})
28+
})
29+
30+
var _ = Describe("SortOperators", func() {
31+
It("sorts operators in correct order", func() {
32+
operators := []olmv1.ClusterExtension{
33+
newClusterExtension("op-1", "1.0.0"),
34+
newClusterExtension("op-1", "1.0.1"),
35+
newClusterExtension("op-1", "1.0.1-rc4"),
36+
newClusterExtension("op-1", "1.0.1-rc2"),
37+
newClusterExtension("op-2", "2.0.0"),
38+
}
39+
sortOperators(operators)
40+
41+
Expect(operators[0].Status.Install.Bundle.Version).To(Equal("1.0.1"))
42+
Expect(operators[1].Status.Install.Bundle.Version).To(Equal("1.0.1-rc4"))
43+
Expect(operators[2].Status.Install.Bundle.Version).To(Equal("1.0.1-rc2"))
44+
Expect(operators[3].Status.Install.Bundle.Version).To(Equal("1.0.0"))
45+
Expect(operators[4].Status.Install.Bundle.Version).To(Equal("2.0.0"))
46+
})
47+
})
48+
49+
func newClusterCatalog(name string, availabilityMode olmv1catalogd.AvailabilityMode, priority int32) olmv1catalogd.ClusterCatalog {
50+
return olmv1catalogd.ClusterCatalog{
51+
ObjectMeta: metav1.ObjectMeta{Name: name},
52+
Spec: olmv1catalogd.ClusterCatalogSpec{AvailabilityMode: availabilityMode, Priority: priority},
53+
}
54+
}
55+
56+
func newClusterExtension(name, version string) olmv1.ClusterExtension {
57+
return olmv1.ClusterExtension{
58+
ObjectMeta: metav1.ObjectMeta{Name: name},
59+
Status: olmv1.ClusterExtensionStatus{
60+
Install: &olmv1.ClusterExtensionInstallStatus{
61+
Bundle: olmv1.BundleMetadata{
62+
Name: name,
63+
Version: version,
64+
},
65+
},
66+
},
67+
}
68+
}

internal/cmd/olmv1.go

+11
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
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+
}
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+
}

0 commit comments

Comments
 (0)