Skip to content

Commit e5dd128

Browse files
Code for extension install and delete
Signed-off-by: Lalatendu Mohanty <[email protected]>
1 parent b496b10 commit e5dd128

File tree

7 files changed

+209
-92
lines changed

7 files changed

+209
-92
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package olmv1
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/spf13/pflag"
6+
7+
"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
8+
v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
9+
"github.com/operator-framework/kubectl-operator/pkg/action"
10+
)
11+
12+
func NewExtensionDeleteCmd(cfg *action.Configuration) *cobra.Command {
13+
e := v1action.NewExtensionDelete(cfg)
14+
e.Logf = log.Printf
15+
16+
cmd := &cobra.Command{
17+
Use: "extension <extension-name>",
18+
Short: "Delete an extension",
19+
Long: `Warning: Permanently deletes the named cluster extension object.
20+
If the extension contains CRDs, the CRDs will be deleted, which
21+
cascades to the deletion of all operands.`,
22+
Args: cobra.ExactArgs(1),
23+
Run: func(cmd *cobra.Command, args []string) {
24+
e.ExtensionName = args[0]
25+
if err := e.Run(cmd.Context()); err != nil {
26+
log.Fatalf("delete extension: %v", err)
27+
}
28+
log.Printf("deleted extension %q", e.ExtensionName)
29+
},
30+
}
31+
bindExtensionDeleteFlags(cmd.Flags(), e)
32+
return cmd
33+
}
34+
35+
func bindExtensionDeleteFlags(fs *pflag.FlagSet, e *v1action.ExtensionDeletion) {
36+
fs.BoolVarP(&e.DeleteAll, "all", "a", false, "delete all extensions")
37+
}

internal/cmd/internal/olmv1/extension_install.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package olmv1
22

33
import (
4+
"time"
5+
46
"github.com/spf13/cobra"
7+
"github.com/spf13/pflag"
58

69
"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
710
v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
@@ -13,18 +16,31 @@ func NewExtensionInstallCmd(cfg *action.Configuration) *cobra.Command {
1316
i.Logf = log.Printf
1417

1518
cmd := &cobra.Command{
16-
Use: "install <extension>",
19+
Use: "extension <extension_name>",
1720
Short: "Install an extension",
1821
Args: cobra.ExactArgs(1),
1922
Run: func(cmd *cobra.Command, args []string) {
20-
i.Package = args[0]
23+
i.ExtensionName = args[0]
2124
_, err := i.Run(cmd.Context())
2225
if err != nil {
2326
log.Fatalf("failed to install extension: %v", err)
2427
}
25-
log.Printf("extension %q created", i.Package)
28+
log.Printf("extension %q created", i.ExtensionName)
2629
},
2730
}
31+
bindOperatorInstallFlags(cmd.Flags(), i)
2832

2933
return cmd
3034
}
35+
36+
func bindOperatorInstallFlags(fs *pflag.FlagSet, i *v1action.ExtensionInstall) {
37+
fs.StringVarP(&i.Namespace.Name, "namespace", "n", "", "namespace to install the operator in")
38+
fs.ArgsLenAtDash()
39+
fs.StringVarP(&i.PackageName, "package-name", "p", "", "package name of the operator to install")
40+
fs.StringSliceVarP(&i.Channels, "channels", "c", []string{}, "channels which would be to used for getting updates")
41+
fs.StringVarP(&i.Version, "version", "v", "", "version (or version range) from which to resolve bundles")
42+
fs.StringVarP(&i.ServiceAccount, "service-account", "s", "default", "service account name to use for the extension installation")
43+
fs.StringToStringVarP(&i.CatalogSelector.MatchLabels, "labels", "l", map[string]string{}, "labels that will be used to select catalog")
44+
fs.BoolVarP(&i.UnsafeCreateClusterRoleBinding, "unsafe-create-cluster-role-binding", "u", false, "create a cluster role binding for the operator's service account")
45+
fs.DurationVarP(&i.CleanupTimeout, "cleanup-timeout", "d", time.Minute, "the amount of time to wait before cancelling cleanup after a failed creation attempt")
46+
}

internal/cmd/internal/olmv1/extension_uninstall.go

-34
This file was deleted.

internal/cmd/olmv1.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
3636
Short: "Delete a resource",
3737
Long: "Delete a resource",
3838
}
39-
deleteCmd.AddCommand(olmv1.NewCatalogDeleteCmd(cfg))
39+
deleteCmd.AddCommand(
40+
olmv1.NewCatalogDeleteCmd(cfg),
41+
olmv1.NewExtensionDeleteCmd(cfg),
42+
)
4043

4144
updateCmd := &cobra.Command{
4245
Use: "update",
@@ -47,9 +50,15 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
4750
olmv1.NewExtensionUpdateCmd(cfg),
4851
)
4952

53+
installCmd := &cobra.Command{
54+
Use: "install",
55+
Short: "Install a resource",
56+
Long: "Install a resource",
57+
}
58+
installCmd.AddCommand(olmv1.NewExtensionInstallCmd(cfg))
59+
5060
cmd.AddCommand(
51-
olmv1.NewExtensionInstallCmd(cfg),
52-
olmv1.NewExtensionUninstallCmd(cfg),
61+
installCmd,
5362
getCmd,
5463
createCmd,
5564
deleteCmd,

internal/pkg/v1/action/extension_uninstall.go renamed to internal/pkg/v1/action/extension_delete.go

+11-12
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,27 @@ import (
1313
"github.com/operator-framework/kubectl-operator/pkg/action"
1414
)
1515

16-
type ExtensionUninstall struct {
17-
config *action.Configuration
18-
19-
Package string
20-
21-
Logf func(string, ...interface{})
16+
type ExtensionDeletion struct {
17+
config *action.Configuration
18+
ExtensionName string
19+
DeleteAll bool
20+
Logf func(string, ...interface{})
2221
}
2322

24-
func NewExtensionUninstall(cfg *action.Configuration) *ExtensionUninstall {
25-
return &ExtensionUninstall{
23+
func NewExtensionDelete(cfg *action.Configuration) *ExtensionDeletion {
24+
return &ExtensionDeletion{
2625
config: cfg,
2726
Logf: func(string, ...interface{}) {},
2827
}
2928
}
3029

31-
func (u *ExtensionUninstall) Run(ctx context.Context) error {
32-
opKey := types.NamespacedName{Name: u.Package}
30+
func (u *ExtensionDeletion) Run(ctx context.Context) error {
31+
opKey := types.NamespacedName{Name: u.ExtensionName}
3332
op := &olmv1.ClusterExtension{}
3433
op.SetName(opKey.Name)
35-
op.SetGroupVersionKind(olmv1.GroupVersion.WithKind("Extension"))
36-
34+
op.SetGroupVersionKind(olmv1.GroupVersion.WithKind("ClusterExtension"))
3735
lowerKind := strings.ToLower(op.GetObjectKind().GroupVersionKind().Kind)
36+
//Lala:Fixme: return error if extension does not exist
3837
if err := u.config.Client.Delete(ctx, op); err != nil && !apierrors.IsNotFound(err) {
3938
return fmt.Errorf("delete %s %q: %v", lowerKind, op.GetName(), err)
4039
}

internal/pkg/v1/action/extension_install.go

+129-39
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,38 @@ package action
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
"time"
68

9+
"github.com/operator-framework/kubectl-operator/pkg/action"
10+
ocv1 "github.com/operator-framework/operator-controller/api/v1"
711
"k8s.io/apimachinery/pkg/api/meta"
812
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9-
"k8s.io/apimachinery/pkg/types"
1013
"k8s.io/apimachinery/pkg/util/wait"
14+
"sigs.k8s.io/controller-runtime/pkg/client"
1115

12-
olmv1 "github.com/operator-framework/operator-controller/api/v1"
13-
14-
"github.com/operator-framework/kubectl-operator/pkg/action"
16+
corev1 "k8s.io/api/core/v1"
17+
rbacv1 "k8s.io/api/rbac/v1"
1518
)
1619

1720
type ExtensionInstall struct {
18-
config *action.Configuration
19-
20-
Package string
21-
22-
Logf func(string, ...interface{})
21+
config *action.Configuration
22+
ExtensionName string
23+
Namespace NamespaceConfig
24+
PackageName string
25+
Channels []string
26+
Version string
27+
ServiceAccount string
28+
CatalogSelector metav1.LabelSelector
29+
UnsafeCreateClusterRoleBinding bool
30+
CleanupTimeout time.Duration
31+
Logf func(string, ...interface{})
32+
}
33+
type NamespaceConfig struct {
34+
Name string
35+
Labels map[string]string
36+
Annotations map[string]string
2337
}
2438

2539
func NewExtensionInstall(cfg *action.Configuration) *ExtensionInstall {
@@ -29,47 +43,123 @@ func NewExtensionInstall(cfg *action.Configuration) *ExtensionInstall {
2943
}
3044
}
3145

32-
func (i *ExtensionInstall) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
33-
// TODO(developer): Lookup package information when the OLMv1 equivalent of the
34-
// packagemanifests API is available. That way, we can check to see if the
35-
// package is actually available to the cluster before creating the Extension
36-
// object.
37-
38-
opKey := types.NamespacedName{Name: i.Package}
39-
op := &olmv1.ClusterExtension{
40-
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
41-
Spec: olmv1.ClusterExtensionSpec{
42-
Source: olmv1.SourceConfig{
43-
SourceType: "Catalog",
44-
Catalog: &olmv1.CatalogFilter{
45-
PackageName: i.Package,
46+
func (i *ExtensionInstall) buildClusterExtension() ocv1.ClusterExtension {
47+
extension := ocv1.ClusterExtension{
48+
ObjectMeta: metav1.ObjectMeta{
49+
Name: i.ExtensionName,
50+
},
51+
Spec: ocv1.ClusterExtensionSpec{
52+
Source: ocv1.SourceConfig{
53+
SourceType: ocv1.SourceTypeCatalog,
54+
Catalog: &ocv1.CatalogFilter{
55+
PackageName: i.PackageName,
56+
Version: i.Version,
4657
},
4758
},
59+
Namespace: i.Namespace.Name,
60+
ServiceAccount: ocv1.ServiceAccountReference{
61+
Name: i.ServiceAccount,
62+
},
4863
},
4964
}
50-
if err := i.config.Client.Create(ctx, op); err != nil {
51-
return nil, err
65+
66+
return extension
67+
}
68+
69+
func (i *ExtensionInstall) Run(ctx context.Context) (*ocv1.ClusterExtension, error) {
70+
extension := i.buildClusterExtension()
71+
// Add catalog selector to extension
72+
if len(i.CatalogSelector.MatchLabels) > 0 {
73+
extension.Spec.Source.Catalog.Selector = &i.CatalogSelector
74+
}
75+
// Add Channels to extension
76+
if len(i.Channels) > 0 {
77+
extension.Spec.Source.Catalog.Channels = i.Channels
5278
}
79+
//Add CatalogSelector to extension
80+
if len(i.CatalogSelector.MatchLabels) > 0 {
81+
extension.Spec.Source.Catalog.Selector = &i.CatalogSelector
82+
}
83+
// Create namespace
84+
/*
85+
namespace := &corev1.Namespace{
86+
ObjectMeta: metav1.ObjectMeta{
87+
Name: i.Namespace.Name,
88+
},
89+
}
5390
54-
// TODO(developer): Improve the logic in this poll wait once the Extension reconciler
55-
// and conditions types and reasons are improved. For now, this will stop waiting as
56-
// soon as a Ready condition is found, but we should probably wait until the Extension
57-
// stops progressing.
58-
// All Types will exist, so Ready may have a false Status. So, wait until
59-
// Type=Ready,Status=True happens
91+
if err := i.config.Client.Create(ctx, namespace); err != nil {
92+
return nil, err
93+
}
94+
*/
95+
// Create the extension
96+
if err := i.config.Client.Create(ctx, &extension); err != nil {
97+
return nil, err
98+
}
99+
clusterExtension, err := i.waitForClusterExtensionInstalled(ctx)
100+
if err != nil {
101+
cleanupCtx, cancelCleanup := context.WithTimeout(context.Background(), i.CleanupTimeout)
102+
defer cancelCleanup()
103+
cleanupErr := i.cleanup(cleanupCtx)
104+
return nil, errors.Join(err, cleanupErr)
105+
}
106+
return clusterExtension, nil
107+
}
60108

61-
if err := wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) {
62-
if err := i.config.Client.Get(conditionCtx, opKey, op); err != nil {
109+
func (i *ExtensionInstall) waitForClusterExtensionInstalled(ctx context.Context) (*ocv1.ClusterExtension, error) {
110+
clusterExtension := &ocv1.ClusterExtension{
111+
ObjectMeta: metav1.ObjectMeta{
112+
Name: i.ExtensionName,
113+
},
114+
}
115+
errMsg := ""
116+
key := client.ObjectKeyFromObject(clusterExtension)
117+
if err := wait.PollUntilContextCancel(ctx, time.Millisecond*250, true, func(conditionCtx context.Context) (bool, error) {
118+
if err := i.config.Client.Get(conditionCtx, key, clusterExtension); err != nil {
63119
return false, err
64120
}
65-
installedCondition := meta.FindStatusCondition(op.Status.Conditions, olmv1.TypeInstalled)
66-
if installedCondition != nil && installedCondition.Status == metav1.ConditionTrue {
67-
return true, nil
121+
progressingCondition := meta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing)
122+
if progressingCondition != nil && progressingCondition.Reason != ocv1.ReasonSucceeded {
123+
errMsg = progressingCondition.Message
124+
return false, nil
68125
}
69-
return false, nil
126+
if !meta.IsStatusConditionPresentAndEqual(clusterExtension.Status.Conditions, ocv1.TypeInstalled, metav1.ConditionTrue) {
127+
return false, nil
128+
}
129+
return true, nil
70130
}); err != nil {
71-
return nil, fmt.Errorf("waiting for extension to become ready: %v", err)
131+
if errMsg == "" {
132+
errMsg = err.Error()
133+
}
134+
return nil, fmt.Errorf("cluster extension %q did not finish installing: %s", clusterExtension.Name, errMsg)
72135
}
136+
return clusterExtension, nil
137+
}
73138

74-
return op, nil
139+
func (i *ExtensionInstall) cleanup(ctx context.Context) error {
140+
clusterExtension := &ocv1.ClusterExtension{
141+
ObjectMeta: metav1.ObjectMeta{
142+
Name: i.ExtensionName,
143+
},
144+
}
145+
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
146+
ObjectMeta: metav1.ObjectMeta{
147+
Name: fmt.Sprintf("kubectl-operator-%s-cluster-admin", i.ServiceAccount),
148+
},
149+
}
150+
serviceAccount := &corev1.ServiceAccount{
151+
ObjectMeta: metav1.ObjectMeta{
152+
Namespace: i.Namespace.Name,
153+
Name: i.ServiceAccount,
154+
},
155+
}
156+
namespace := &corev1.Namespace{
157+
ObjectMeta: metav1.ObjectMeta{
158+
Name: i.Namespace.Name,
159+
},
160+
}
161+
if err := waitForDeletion(ctx, i.config.Client, clusterExtension); err != nil {
162+
return fmt.Errorf("delete clusterextension %q: %v", i.ExtensionName, err)
163+
}
164+
return waitForDeletion(ctx, i.config.Client, clusterRoleBinding, serviceAccount, namespace)
75165
}

0 commit comments

Comments
 (0)