@@ -2,24 +2,38 @@ package action
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"fmt"
7
+ "time"
6
8
9
+ "github.com/operator-framework/kubectl-operator/pkg/action"
10
+ ocv1 "github.com/operator-framework/operator-controller/api/v1"
7
11
"k8s.io/apimachinery/pkg/api/meta"
8
12
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9
- "k8s.io/apimachinery/pkg/types"
10
13
"k8s.io/apimachinery/pkg/util/wait"
14
+ "sigs.k8s.io/controller-runtime/pkg/client"
11
15
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"
15
18
)
16
19
17
20
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
23
37
}
24
38
25
39
func NewExtensionInstall (cfg * action.Configuration ) * ExtensionInstall {
@@ -29,47 +43,123 @@ func NewExtensionInstall(cfg *action.Configuration) *ExtensionInstall {
29
43
}
30
44
}
31
45
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 ,
46
57
},
47
58
},
59
+ Namespace : i .Namespace .Name ,
60
+ ServiceAccount : ocv1.ServiceAccountReference {
61
+ Name : i .ServiceAccount ,
62
+ },
48
63
},
49
64
}
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
52
78
}
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
+ }
53
90
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
+ }
60
108
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 {
63
119
return false , err
64
120
}
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
68
125
}
69
- return false , nil
126
+ if ! meta .IsStatusConditionPresentAndEqual (clusterExtension .Status .Conditions , ocv1 .TypeInstalled , metav1 .ConditionTrue ) {
127
+ return false , nil
128
+ }
129
+ return true , nil
70
130
}); 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 )
72
135
}
136
+ return clusterExtension , nil
137
+ }
73
138
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 )
75
165
}
0 commit comments