Skip to content

Commit 99673ce

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

File tree

7 files changed

+205
-89
lines changed

7 files changed

+205
-89
lines changed
Lines changed: 37 additions & 0 deletions
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

Lines changed: 19 additions & 3 deletions
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

Lines changed: 0 additions & 34 deletions
This file was deleted.

internal/cmd/olmv1.go

Lines changed: 12 additions & 3 deletions
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

Lines changed: 11 additions & 12 deletions
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

Lines changed: 125 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,39 @@ 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"
11+
olmv1 "github.com/operator-framework/operator-controller/api/v1"
712
"k8s.io/apimachinery/pkg/api/meta"
813
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9-
"k8s.io/apimachinery/pkg/types"
1014
"k8s.io/apimachinery/pkg/util/wait"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
1116

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

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

2540
func NewExtensionInstall(cfg *action.Configuration) *ExtensionInstall {
@@ -29,47 +44,121 @@ func NewExtensionInstall(cfg *action.Configuration) *ExtensionInstall {
2944
}
3045
}
3146

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},
47+
func (i *ExtensionInstall) buildClusterExtension() olmv1.ClusterExtension {
48+
extension := olmv1.ClusterExtension{
49+
ObjectMeta: metav1.ObjectMeta{
50+
Name: i.ExtensionName,
51+
},
4152
Spec: olmv1.ClusterExtensionSpec{
4253
Source: olmv1.SourceConfig{
43-
SourceType: "Catalog",
54+
SourceType: olmv1.SourceTypeCatalog,
4455
Catalog: &olmv1.CatalogFilter{
45-
PackageName: i.Package,
56+
PackageName: i.PackageName,
57+
Version: i.Version,
4658
},
4759
},
60+
Namespace: i.Namespace.Name,
61+
ServiceAccount: olmv1.ServiceAccountReference{
62+
Name: i.ServiceAccount,
63+
},
64+
},
65+
}
66+
67+
return extension
68+
}
69+
70+
func (i *ExtensionInstall) Run(ctx context.Context) (*ocv1.ClusterExtension, error) {
71+
extension := i.buildClusterExtension()
72+
// Add catalog selector to extension
73+
if len(i.CatalogSelector.MatchLabels) > 0 {
74+
extension.Spec.Source.Catalog.Selector = &i.CatalogSelector
75+
}
76+
// Add Channels to extension
77+
if len(i.Channels) > 0 {
78+
extension.Spec.Source.Catalog.Channels = i.Channels
79+
}
80+
//Add CatalogSelector to extension
81+
if len(i.CatalogSelector.MatchLabels) > 0 {
82+
extension.Spec.Source.Catalog.Selector = &i.CatalogSelector
83+
}
84+
// Create namespace
85+
namespace := &corev1.Namespace{
86+
ObjectMeta: metav1.ObjectMeta{
87+
Name: i.Namespace.Name,
88+
Labels: i.Namespace.Labels,
89+
Annotations: i.Namespace.Annotations,
4890
},
4991
}
50-
if err := i.config.Client.Create(ctx, op); err != nil {
92+
if err := i.config.Client.Create(ctx, namespace); err != nil {
5193
return nil, err
5294
}
95+
if err := i.config.Client.Create(ctx, &extension); err != nil {
96+
return nil, err
97+
}
98+
clusterExtension, err := i.waitForClusterExtensionInstalled(ctx)
99+
if err != nil {
100+
cleanupCtx, cancelCleanup := context.WithTimeout(context.Background(), i.CleanupTimeout)
101+
defer cancelCleanup()
102+
cleanupErr := i.cleanup(cleanupCtx)
103+
return nil, errors.Join(err, cleanupErr)
104+
}
105+
return clusterExtension, nil
106+
}
53107

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
60-
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 {
108+
func (i *ExtensionInstall) waitForClusterExtensionInstalled(ctx context.Context) (*ocv1.ClusterExtension, error) {
109+
clusterExtension := &ocv1.ClusterExtension{
110+
ObjectMeta: metav1.ObjectMeta{
111+
Name: i.ExtensionName,
112+
},
113+
}
114+
errMsg := ""
115+
key := client.ObjectKeyFromObject(clusterExtension)
116+
if err := wait.PollUntilContextCancel(ctx, time.Millisecond*250, true, func(conditionCtx context.Context) (bool, error) {
117+
if err := i.config.Client.Get(conditionCtx, key, clusterExtension); err != nil {
63118
return false, err
64119
}
65-
installedCondition := meta.FindStatusCondition(op.Status.Conditions, olmv1.TypeInstalled)
66-
if installedCondition != nil && installedCondition.Status == metav1.ConditionTrue {
67-
return true, nil
120+
progressingCondition := meta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing)
121+
if progressingCondition != nil && progressingCondition.Reason != ocv1.ReasonSucceeded {
122+
errMsg = progressingCondition.Message
123+
return false, nil
124+
}
125+
if !meta.IsStatusConditionPresentAndEqual(clusterExtension.Status.Conditions, ocv1.TypeInstalled, metav1.ConditionTrue) {
126+
return false, nil
68127
}
69-
return false, nil
128+
return true, nil
70129
}); err != nil {
71-
return nil, fmt.Errorf("waiting for extension to become ready: %v", err)
130+
if errMsg == "" {
131+
errMsg = err.Error()
132+
}
133+
return nil, fmt.Errorf("cluster extension %q did not finish installing: %s", clusterExtension.Name, errMsg)
72134
}
135+
return clusterExtension, nil
136+
}
73137

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

0 commit comments

Comments
 (0)