/
reconciler.go
215 lines (186 loc) · 8.39 KB
/
reconciler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package cloudresources
import (
"context"
"fmt"
"github.com/sirupsen/logrus"
crov1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1"
crov1alpha1Types "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types"
croUtil "github.com/integr8ly/cloud-resource-operator/pkg/resources"
integreatlyv1alpha1 "github.com/integr8ly/integreatly-operator/pkg/apis/integreatly/v1alpha1"
"github.com/integr8ly/integreatly-operator/pkg/resources"
"github.com/integr8ly/integreatly-operator/pkg/resources/events"
k8serr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/integr8ly/integreatly-operator/pkg/config"
"github.com/integr8ly/integreatly-operator/pkg/resources/marketplace"
)
const (
defaultInstallationNamespace = "cloud-resources"
defaultSubscriptionName = "rhmi-cloud-resources"
manifestPackage = "integreatly-cloud-resources"
)
type Reconciler struct {
Config *config.CloudResources
ConfigManager config.ConfigReadWriter
mpm marketplace.MarketplaceInterface
logger *logrus.Entry
*resources.Reconciler
recorder record.EventRecorder
}
func NewReconciler(configManager config.ConfigReadWriter, installation *integreatlyv1alpha1.RHMI, mpm marketplace.MarketplaceInterface, recorder record.EventRecorder) (*Reconciler, error) {
config, err := configManager.ReadCloudResources()
if err != nil {
return nil, fmt.Errorf("could not read cloud resources config: %w", err)
}
if config.GetNamespace() == "" {
config.SetNamespace(installation.Spec.NamespacePrefix + defaultInstallationNamespace)
}
if config.GetOperatorNamespace() == "" {
if installation.Spec.OperatorsInProductNamespace {
config.SetOperatorNamespace(config.GetNamespace())
} else {
config.SetOperatorNamespace(config.GetNamespace() + "-operator")
}
}
logger := logrus.WithFields(logrus.Fields{"product": config.GetProductName()})
return &Reconciler{
ConfigManager: configManager,
Config: config,
mpm: mpm,
logger: logger,
Reconciler: resources.NewReconciler(mpm),
recorder: recorder,
}, nil
}
func (r *Reconciler) GetPreflightObject(ns string) runtime.Object {
return nil
}
func (r *Reconciler) Reconcile(ctx context.Context, installation *integreatlyv1alpha1.RHMI, product *integreatlyv1alpha1.RHMIProductStatus, client k8sclient.Client) (integreatlyv1alpha1.StatusPhase, error) {
ns := r.Config.GetOperatorNamespace()
phase, err := r.ReconcileFinalizer(ctx, client, installation, string(r.Config.GetProductName()), func() (integreatlyv1alpha1.StatusPhase, error) {
// Check if namespace is still present before trying to delete it resources
_, err := resources.GetNS(ctx, ns, client)
if !k8serr.IsNotFound(err) {
// ensure resources are cleaned up before deleting the namespace
phase, err := r.cleanupResources(ctx, installation, client)
if err != nil || phase != integreatlyv1alpha1.PhaseCompleted {
return phase, err
}
// remove the namespace
phase, err = resources.RemoveNamespace(ctx, installation, client, ns)
if err != nil || phase != integreatlyv1alpha1.PhaseCompleted {
return phase, err
}
}
return integreatlyv1alpha1.PhaseCompleted, nil
})
if err != nil || phase != integreatlyv1alpha1.PhaseCompleted {
events.HandleError(r.recorder, installation, phase, "Failed to reconcile finalizer", err)
return phase, err
}
phase, err = r.ReconcileNamespace(ctx, ns, installation, client)
if err != nil || phase != integreatlyv1alpha1.PhaseCompleted {
events.HandleError(r.recorder, installation, phase, fmt.Sprintf("Failed to reconcile %s namespace", ns), err)
return phase, err
}
namespace, err := resources.GetNS(ctx, ns, client)
if err != nil {
events.HandleError(r.recorder, installation, integreatlyv1alpha1.PhaseFailed, fmt.Sprintf("Failed to retrieve %s namespace", ns), err)
return integreatlyv1alpha1.PhaseFailed, err
}
phase, err = r.ReconcileSubscription(ctx, namespace, marketplace.Target{Pkg: defaultSubscriptionName, Channel: marketplace.IntegreatlyChannel, Namespace: r.Config.GetOperatorNamespace(), ManifestPackage: manifestPackage}, []string{installation.Namespace}, client)
if err != nil || phase != integreatlyv1alpha1.PhaseCompleted {
events.HandleError(r.recorder, installation, phase, fmt.Sprintf("Failed to reconcile %s subscription", defaultSubscriptionName), err)
return phase, err
}
phase, err = r.reconcileBackupsStorage(ctx, installation, client)
if err != nil || phase != integreatlyv1alpha1.PhaseCompleted {
return phase, err
}
product.Host = r.Config.GetHost()
product.Version = r.Config.GetProductVersion()
product.OperatorVersion = r.Config.GetOperatorVersion()
err = r.ConfigManager.WriteConfig(r.Config)
if err != nil {
return integreatlyv1alpha1.PhaseFailed, fmt.Errorf("could not write cloud resources config: %w", err)
}
events.HandleProductComplete(r.recorder, installation, integreatlyv1alpha1.CloudResourcesStage, r.Config.GetProductName())
r.logger.Infof("%s has reconciled successfully", r.Config.GetProductName())
return integreatlyv1alpha1.PhaseCompleted, nil
}
func (r *Reconciler) cleanupResources(ctx context.Context, installation *integreatlyv1alpha1.RHMI, client k8sclient.Client) (integreatlyv1alpha1.StatusPhase, error) {
r.logger.Info("ensuring cloud resources are cleaned up")
// ensure postgres instances are cleaned up
postgresInstances := &crov1alpha1.PostgresList{}
postgresInstanceOpts := []k8sclient.ListOption{
k8sclient.InNamespace(installation.Namespace),
}
err := client.List(ctx, postgresInstances, postgresInstanceOpts...)
if err != nil {
return integreatlyv1alpha1.PhaseFailed, fmt.Errorf("failed to list postgres instances: %w", err)
}
for _, pgInst := range postgresInstances.Items {
if err := client.Delete(ctx, &pgInst); err != nil {
return integreatlyv1alpha1.PhaseFailed, err
}
}
if len(postgresInstances.Items) > 0 {
r.logger.Info("deletion of postgres instances in progress")
return integreatlyv1alpha1.PhaseInProgress, nil
}
// ensure redis instances are cleaned up
redisInstances := &crov1alpha1.RedisList{}
redisInstanceOpts := []k8sclient.ListOption{
k8sclient.InNamespace(installation.Namespace),
}
err = client.List(ctx, redisInstances, redisInstanceOpts...)
if err != nil {
return integreatlyv1alpha1.PhaseFailed, fmt.Errorf("failed to list redis instances: %w", err)
}
for _, redisInst := range redisInstances.Items {
if err := client.Delete(ctx, &redisInst); err != nil {
return integreatlyv1alpha1.PhaseFailed, err
}
}
if len(redisInstances.Items) > 0 {
r.logger.Info("deletion of redis instances in progress")
return integreatlyv1alpha1.PhaseInProgress, nil
}
// ensure blob storage instances are cleaned up
blobStorages := &crov1alpha1.BlobStorageList{}
blobStorageOpts := []k8sclient.ListOption{
k8sclient.InNamespace(installation.Namespace),
}
err = client.List(ctx, blobStorages, blobStorageOpts...)
if err != nil {
return integreatlyv1alpha1.PhaseFailed, fmt.Errorf("failed to list blobStorage instances: %w", err)
}
for _, bsInst := range blobStorages.Items {
if err := client.Delete(ctx, &bsInst); err != nil {
return integreatlyv1alpha1.PhaseFailed, err
}
}
if len(blobStorages.Items) > 0 {
r.logger.Info("deletion of blob storage instances in progress")
return integreatlyv1alpha1.PhaseInProgress, nil
}
// everything has been cleaned up, delete the ns
return integreatlyv1alpha1.PhaseCompleted, nil
}
func (r *Reconciler) reconcileBackupsStorage(ctx context.Context, installation *integreatlyv1alpha1.RHMI, client k8sclient.Client) (integreatlyv1alpha1.StatusPhase, error) {
blobStorageName := fmt.Sprintf("backups-blobstorage-%s", installation.Name)
blobStorage, err := croUtil.ReconcileBlobStorage(ctx, client, defaultInstallationNamespace, installation.Spec.Type, "production", blobStorageName, installation.Namespace, r.ConfigManager.GetBackupsSecretName(), installation.Namespace, func(cr metav1.Object) error {
return nil
})
if err != nil {
return integreatlyv1alpha1.PhaseFailed, fmt.Errorf("failed to reconcile blob storage request: %w", err)
}
// wait for the blob storage cr to reconcile
if blobStorage.Status.Phase != crov1alpha1Types.PhaseComplete {
return integreatlyv1alpha1.PhaseAwaitingComponents, nil
}
return integreatlyv1alpha1.PhaseCompleted, nil
}