-
Notifications
You must be signed in to change notification settings - Fork 27
/
subresource_destroyer.go
195 lines (169 loc) · 7.63 KB
/
subresource_destroyer.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
package service
import (
"fmt"
"sync"
"github.com/RedHatInsights/sources-api-go/dao"
"github.com/RedHatInsights/sources-api-go/kafka"
logging "github.com/RedHatInsights/sources-api-go/logger"
"github.com/RedHatInsights/sources-api-go/model"
)
// DeleteCascade removes the resource type and all its dependants, raising an event for every deleted resource. Returns
// an error when the resources and its dependants could not be successfully removed.
//
// - In the case of the resource being a "Source", it removes its applications, its endpoints and its Red Hat Connector
// connections.
// - In the other cases, it removes the resource itself.
//
// In both cases the authentications are fetched for every single resource and sub resource, and they get deleted in
// batch.
//
// In the case of not being able to delete authentications, we simply log the error and leave them dangling, since we
// might either have a database or Vault backing our authentications, and we might need manual action to remove the
// ones that were left undeleted. In the case of a database it is true that it could be wrapped in a transaction, but
// with Vault that is not possible. What if we start deleting authentications and then some of them error out? We would
// be able to recover the database data, but some of their authentications would be gone, therefore leaving the
// resource in an inconsistent state.
//
// Plus, it's not the client's fault that we weren't able to delete the authentications, so by taking this approach
// the clients don't keep an "unremovable" resource because of some failure in deleting the authentications.
//
// Finally, those authentications are safely encrypted so they can stay in their datastores until we manually remove
// them.
func DeleteCascade(tenantId *int64, userId *int64, resourceType string, resourceId int64, headers []kafka.Header) error {
authenticationsDao := dao.GetAuthenticationDao(&dao.RequestParams{TenantID: tenantId})
var authentications []model.Authentication
// locking a lock right at the beginning to ensure messages are raised in order and then unlocking it when ready to
// do the final authentication cleanup
var lock sync.Mutex
lock.Lock()
switch resourceType {
case "Source":
sourceDao := dao.GetSourceDao(&dao.RequestParams{TenantID: tenantId, UserID: userId})
applicationAuthentications, applications, endpoints, rhcConnections, source, err := sourceDao.DeleteCascade(resourceId)
if err != nil {
return fmt.Errorf(`could not completely delete the source: %s`, err)
}
go func() {
// Raise an event for the deleted application authentications.
for _, appAuth := range applicationAuthentications {
err = RaiseEvent("ApplicationAuthentication.destroy", &appAuth, headers)
if err != nil {
logging.Log.Errorf(`Event "ApplicationAuthentication.destroy" could not be raised for application authentication %v: %s`, appAuth.ToEvent(), err)
}
}
// Raise events for the deleted applications.
var appIds []int64
for _, app := range applications {
appIds = append(appIds, app.ID)
err := RaiseEvent("Application.destroy", &app, headers)
if err != nil {
logging.Log.Errorf(`Event "Application.destroy" could not be raised for application %v: %s`, app.ToEvent(), err)
}
}
// Raise events for the deleted endpoints.
var endpointIds []int64
for _, endpoint := range endpoints {
endpointIds = append(endpointIds, endpoint.ID)
err := RaiseEvent("Endpoint.destroy", &endpoint, headers)
if err != nil {
logging.Log.Errorf(`Event "Endpoint.destroy" could not be raised for endpoint %v: %s`, endpoint.ToEvent(), err)
}
}
// Raise events for the deleted connections.
for _, connection := range rhcConnections {
err := RaiseEvent("RhcConnection.destroy", &connection, headers)
if err != nil {
logging.Log.Errorf(`Event "RhcConnection.destroy" could not be raised for rhcConnection %v: %s`, connection.ToEvent(), err)
}
}
// Raise an event for the source itself.
err = RaiseEvent("Source.destroy", source, headers)
if err != nil {
logging.Log.Errorf(`Event "Source.destroy" could not be raised for source %v: %s`, source.ToEvent(), err)
}
// Fetch all the authentications from the resources.
resourceTypes := []struct {
ResourceType string
ResourceIds []int64
}{
{ResourceType: "Application", ResourceIds: appIds},
{ResourceType: "Endpoint", ResourceIds: endpointIds},
{ResourceType: "Source", ResourceIds: []int64{resourceId}},
}
for _, res := range resourceTypes {
auths, err := authenticationsDao.ListIdsForResource(res.ResourceType, res.ResourceIds)
if err != nil {
logging.Log.Errorf(`[resource_type: "%s"][resource_ids: "%v"] Could not fetch authentications: %s`, res.ResourceType, res.ResourceIds, err)
}
authentications = append(authentications, auths...)
}
lock.Unlock()
}()
case "Application":
applicationsDao := dao.GetApplicationDao(&dao.RequestParams{TenantID: tenantId, UserID: userId})
applicationAuthentications, application, err := applicationsDao.DeleteCascade(resourceId)
if err != nil {
return fmt.Errorf(`could not completely delete the application: %s`, err)
}
go func() {
// Raise an event for the deleted application authentications.
for _, appAuth := range applicationAuthentications {
err = RaiseEvent("ApplicationAuthentication.destroy", &appAuth, headers)
if err != nil {
logging.Log.Errorf(`Event "ApplicationAuthentication.destroy" could not be raised for application authentication %v: %s`, appAuth.ToEvent(), err)
}
}
// Raise an event for the deleted application itself.
err = RaiseEvent("Application.destroy", application, headers)
if err != nil {
logging.Log.Errorf(`Event "Application.destroy" could not be raised for application %v: %s`, application.ToEvent(), err)
}
// Fetch all the application's authentications.
auths, err := authenticationsDao.ListIdsForResource("Application", []int64{resourceId})
if err != nil {
logging.Log.Errorf(`[resource_type: "Application"][resource_id: "%v"] Could not fetch authentications: %s`, resourceId, err)
}
authentications = append(authentications, auths...)
lock.Unlock()
}()
case "Endpoint":
// Delete the endpoint.
endpointDao := dao.GetEndpointDao(tenantId)
endpoint, err := endpointDao.Delete(&resourceId)
if err != nil {
return fmt.Errorf(`could not delete the endpoint: %s`, err)
}
go func() {
// Raise an event for the deleted endpoint.
err = RaiseEvent("Endpoint.destroy", endpoint, headers)
if err != nil {
logging.Log.Errorf(`Event "Endpoint.destroy" could not be raised for endpoint %v: %s`, endpoint.ToEvent(), err)
}
// Fetch all the endpoint's authentications.
auths, err := authenticationsDao.ListIdsForResource("Endpoint", []int64{resourceId})
if err != nil {
logging.Log.Errorf(`[resource_type: "Endpoint"][resource_id: "%v"] Could not fetch authentications: %s`, resourceId, err)
}
authentications = append(authentications, auths...)
lock.Unlock()
}()
}
go func() {
// this goroutine will wait until the case statement appropriate above completes.
lock.Lock()
defer lock.Unlock()
// Delete all the authentications.
deletedAuths, err := authenticationsDao.BulkDelete(authentications)
if err != nil {
logging.Log.Errorf(`Could not delete authentications: %s`, err)
}
for _, deletedAuth := range deletedAuths {
// Raise an event for the deleted authentication.
err = RaiseEvent("Authentication.destroy", &deletedAuth, headers)
if err != nil {
logging.Log.Errorf(`Could not raise "Authentication.destroy" event for authentication %v`, err)
}
}
}()
return nil
}