@@ -5,14 +5,21 @@ package managementcomponents
5
5
6
6
import (
7
7
"context"
8
+ "encoding/json"
9
+ "fmt"
8
10
"os"
9
11
"time"
10
12
11
13
"github.com/pkg/errors"
12
14
"golang.org/x/sync/errgroup"
15
+ corev1 "k8s.io/api/core/v1"
16
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
13
17
"k8s.io/apimachinery/pkg/labels"
14
18
"k8s.io/apimachinery/pkg/selection"
19
+ "k8s.io/apimachinery/pkg/types"
20
+ "k8s.io/client-go/util/retry"
15
21
crtclient "sigs.k8s.io/controller-runtime/pkg/client"
22
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
16
23
17
24
kappipkg "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/packaging/v1alpha1"
18
25
"github.com/vmware-tanzu/tanzu-framework/packageclients/pkg/packageclient"
@@ -22,6 +29,11 @@ import (
22
29
"github.com/vmware-tanzu/tanzu-framework/tkg/log"
23
30
)
24
31
32
+ const (
33
+ addonsManagerName = "addons-manager"
34
+ addonFinalizer = "tkg.tanzu.vmware.com/addon"
35
+ )
36
+
25
37
// ClusterOptions specifies cluster configuration
26
38
type ClusterOptions struct {
27
39
Kubeconfig string
@@ -48,18 +60,202 @@ type ManagementComponentsInstallOptions struct {
48
60
ManagementPackageRepositoryOptions ManagementPackageRepositoryOptions
49
61
}
50
62
63
+ func generateAddonSecretName (clusterName , addonName string ) string {
64
+ return fmt .Sprintf ("%s-tanzu-%s-addon" , clusterName , addonName )
65
+ }
66
+
67
+ func pauseAddonSecretReconciliation (clusterClient clusterclient.Client , addonSecreteName , namespace string ) error {
68
+ log .Infof ("Pausing reconciliation for %s/%s secret" , namespace , addonSecreteName )
69
+ secret := & corev1.Secret {}
70
+ jsonPatch := []map [string ]interface {}{
71
+ {
72
+ "op" : "add" ,
73
+ "path" : fmt .Sprintf ("/metadata/annotations/tkg.tanzu.vmware.com~1addon-paused" ),
74
+ "value" : "" ,
75
+ },
76
+ }
77
+ payloadBytes , err := json .Marshal (jsonPatch )
78
+ if err != nil {
79
+ return errors .Wrap (err , "unable to generate json patch" )
80
+ }
81
+
82
+ err = clusterClient .PatchResource (secret , addonSecreteName , namespace , string (payloadBytes ), types .JSONPatchType , nil )
83
+ if apierrors .IsNotFound (err ) {
84
+ return nil
85
+ }
86
+ if err != nil {
87
+ return errors .Wrapf (err , "failed to pause %s secret reconciliation" , addonSecreteName )
88
+ }
89
+ return nil
90
+ }
91
+
92
+ func pausePackageInstallReconciliation (clusterClient clusterclient.Client , pkgiName , namespace string ) error {
93
+ log .Infof ("Pausing reconciliation for %s/%s packageinstall" , namespace , pkgiName )
94
+ pkgi := & kappipkg.PackageInstall {}
95
+ jsonPatch := []map [string ]interface {}{
96
+ {
97
+ "op" : "add" ,
98
+ "path" : "/spec/paused" ,
99
+ "value" : true ,
100
+ },
101
+ }
102
+ payloadBytes , err := json .Marshal (jsonPatch )
103
+ if err != nil {
104
+ return errors .Wrap (err , "unable to generate json patch" )
105
+ }
106
+ err = clusterClient .PatchResource (pkgi , pkgiName , namespace , string (payloadBytes ), types .JSONPatchType , nil )
107
+ if apierrors .IsNotFound (err ) {
108
+ return nil
109
+ }
110
+ if err != nil {
111
+ return errors .Wrapf (err , "failed to pause %s packageinstall reconciliation" , pkgiName )
112
+ }
113
+ return nil
114
+ }
115
+
116
+ // PausedAddonLifecycleManagement pauses/unpauses the lifecycle management of addon package with given name and namespace
117
+ func PauseAddonLifecycleManagement (clusterClient clusterclient.Client , clusterName , addonName , namespace string ) error {
118
+ log .Infof ("Pausing lifecycle management for %s" , addonName )
119
+ pkgiName := fmt .Sprintf ("tanzu-%s" , addonsManagerName )
120
+ addonSecretName := generateAddonSecretName (clusterName , addonsManagerName )
121
+
122
+ err := pauseAddonSecretReconciliation (clusterClient , addonSecretName , namespace )
123
+ if err != nil {
124
+ return err
125
+ }
126
+
127
+ err = pausePackageInstallReconciliation (clusterClient , pkgiName , namespace )
128
+ if err != nil {
129
+ return err
130
+ }
131
+
132
+ return nil
133
+ }
134
+
135
+ // NoopDeletePackageInstall sets spec.noopdelete = true before deleting the package install
136
+ func NoopDeletePackageInstall (clusterClient clusterclient.Client , addonName , namespace string ) error {
137
+ log .Infof ("Deleting %s/%s packageinstall with noopdelete" , namespace , addonName )
138
+ pkgiName := fmt .Sprintf ("tanzu-%s" , addonName )
139
+
140
+ jsonPatch := []map [string ]interface {}{
141
+ {
142
+ "op" : "add" ,
143
+ "path" : "/spec/noopDelete" ,
144
+ "value" : true ,
145
+ },
146
+ }
147
+ payloadBytes , err := json .Marshal (jsonPatch )
148
+ if err != nil {
149
+ return errors .Wrap (err , "unable to generate json patch" )
150
+ }
151
+ pkgi := & kappipkg.PackageInstall {}
152
+ err = clusterClient .PatchResource (pkgi , pkgiName , namespace , string (payloadBytes ), types .JSONPatchType , nil )
153
+ if apierrors .IsNotFound (err ) {
154
+ return nil
155
+ }
156
+ if err != nil {
157
+ return errors .Wrapf (err , "failed to patch %s packageinstall" , pkgiName )
158
+ }
159
+
160
+ pkgi .Name = pkgiName
161
+ pkgi .Namespace = namespace
162
+ err = clusterClient .DeleteResource (pkgi )
163
+ if err != nil && ! apierrors .IsNotFound (err ) {
164
+ return errors .Wrapf (err , "failed to delete PackageInstall resource %s" , pkgiName )
165
+ }
166
+ return nil
167
+ }
168
+
169
+ // DeleteAddonSecret deletes the secrete associated with the addon if present. Return no error if secret not found.
170
+ func DeleteAddonSecret (clusterClient clusterclient.Client , clusterName , addonName , namespace string ) error {
171
+ addonSecret := & corev1.Secret {}
172
+ addonSecret .Name = generateAddonSecretName (clusterName , addonName )
173
+ addonSecret .Namespace = constants .TkgNamespace
174
+ log .Infof ("Deleting %s/%s secret" , addonSecret .Namespace , addonSecret .Name )
175
+ err := clusterClient .GetResource (addonSecret , addonSecret .Name , addonSecret .Namespace , nil , nil )
176
+ if apierrors .IsNotFound (err ) {
177
+ return nil
178
+ }
179
+ if err != nil {
180
+ return err
181
+ }
182
+
183
+ if controllerutil .ContainsFinalizer (addonSecret , addonFinalizer ) {
184
+ controllerutil .RemoveFinalizer (addonSecret , addonFinalizer )
185
+ }
186
+
187
+ err = retry .RetryOnConflict (retry .DefaultRetry , func () error {
188
+ return clusterClient .UpdateResource (addonSecret , addonSecret .Name , addonSecret .Namespace )
189
+ })
190
+
191
+ if apierrors .IsNotFound (err ) {
192
+ return nil
193
+ }
194
+ if err != nil {
195
+ return err
196
+ }
197
+
198
+ err = clusterClient .DeleteResource (addonSecret )
199
+ if apierrors .IsNotFound (err ) {
200
+ return nil
201
+ }
202
+ if err != nil {
203
+ return errors .Wrapf (err , "failed to delete addon addonSecret %s" , addonSecret )
204
+ }
205
+ return nil
206
+ }
207
+
208
+ // AddonSecretExists returns true if given addon is present and was installed from core repository.
209
+ func AddonSecretExists (clusterClient clusterclient.Client , clusterName , addonName , namespace string ) (bool , error ) {
210
+ addonSecret := & corev1.Secret {}
211
+ addonSecret .Name = generateAddonSecretName (clusterName , addonName )
212
+ addonSecret .Namespace = constants .TkgNamespace
213
+
214
+ err := clusterClient .GetResource (addonSecret , addonSecret .Name , addonSecret .Namespace , nil , nil )
215
+ if apierrors .IsNotFound (err ) {
216
+ return false , nil
217
+ }
218
+ if err != nil {
219
+ return false , err
220
+ }
221
+ return true , nil
222
+ }
223
+
51
224
// InstallManagementComponents installs the management component to cluster
52
225
func InstallManagementComponents (mcip * ManagementComponentsInstallOptions ) error {
53
226
clusterClient , err := clusterclient .NewClient (mcip .ClusterOptions .Kubeconfig , mcip .ClusterOptions .Kubecontext , clusterclient.Options {})
54
227
if err != nil {
55
228
return errors .Wrap (err , "unable to get cluster client" )
56
229
}
230
+ clusterName , err := clusterClient .GetCurrentClusterName (mcip .ClusterOptions .Kubecontext )
231
+ if err != nil {
232
+ return errors .Wrap (err , "unable to get cluster name" )
233
+ }
234
+
235
+ // If the addons-manager is moving from core repository to management repository, its lifecycle management
236
+ // needs to be paused.
237
+ previousAddonsManagerIsFromCoreRepo , err := AddonSecretExists (clusterClient , clusterName , addonsManagerName , constants .TkgNamespace )
238
+ if err != nil {
239
+ return err
240
+ }
241
+ if previousAddonsManagerIsFromCoreRepo {
242
+ err = PauseAddonLifecycleManagement (clusterClient , clusterName , addonsManagerName , constants .TkgNamespace )
243
+ if err != nil {
244
+ return err
245
+ }
246
+
247
+ err = NoopDeletePackageInstall (clusterClient , addonsManagerName , constants .TkgNamespace )
248
+ if err != nil {
249
+ return err
250
+ }
251
+ }
57
252
58
253
// create package client
59
254
pkgClient , err := packageclient .NewPackageClientForContext (mcip .ClusterOptions .Kubeconfig , mcip .ClusterOptions .Kubecontext )
60
255
if err != nil {
61
256
return err
62
257
}
258
+
63
259
if err = InstallManagementPackages (pkgClient , mcip .ManagementPackageRepositoryOptions ); err != nil {
64
260
// instead of throwing error here, wait for some additional time for packages to get reconciled successfully
65
261
// error will be thrown at the next step if packages are not reconciled after timeout value
@@ -83,6 +279,13 @@ func InstallManagementComponents(mcip *ManagementComponentsInstallOptions) error
83
279
}
84
280
}
85
281
282
+ if previousAddonsManagerIsFromCoreRepo {
283
+ err = DeleteAddonSecret (clusterClient , clusterName , addonsManagerName , constants .TkgNamespace )
284
+ if err != nil {
285
+ return err
286
+ }
287
+ }
288
+
86
289
return nil
87
290
}
88
291
0 commit comments