@@ -2,24 +2,38 @@ package action
22
33import (
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
1720type 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
2539func 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