github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/formation/notificationbuilder.go (about) 1 package formation 2 3 import ( 4 "context" 5 6 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 7 8 "github.com/kyma-incubator/compass/components/director/internal/model" 9 "github.com/kyma-incubator/compass/components/director/pkg/correlation" 10 formationconstraintpkg "github.com/kyma-incubator/compass/components/director/pkg/formationconstraint" 11 "github.com/kyma-incubator/compass/components/director/pkg/log" 12 webhookdir "github.com/kyma-incubator/compass/components/director/pkg/webhook" 13 webhookclient "github.com/kyma-incubator/compass/components/director/pkg/webhook_client" 14 "github.com/pkg/errors" 15 ) 16 17 //go:generate mockery --exported --name=webhookConverter --output=automock --outpkg=automock --case=underscore --disable-version-string 18 type webhookConverter interface { 19 ToGraphQL(in *model.Webhook) (*graphql.Webhook, error) 20 ToModel(in *graphql.Webhook) (*model.Webhook, error) 21 } 22 23 // NotificationBuilder is responsible for building notification requests 24 type NotificationBuilder struct { 25 webhookConverter webhookConverter 26 constraintEngine constraintEngine 27 runtimeTypeLabelKey string 28 applicationTypeLabelKey string 29 } 30 31 // NewNotificationsBuilder creates new NotificationBuilder 32 func NewNotificationsBuilder(webhookConverter webhookConverter, constraintEngine constraintEngine, runtimeTypeLabelKey, applicationTypeLabelKey string) *NotificationBuilder { 33 return &NotificationBuilder{ 34 webhookConverter: webhookConverter, 35 constraintEngine: constraintEngine, 36 runtimeTypeLabelKey: runtimeTypeLabelKey, 37 applicationTypeLabelKey: applicationTypeLabelKey, 38 } 39 } 40 41 // BuildFormationAssignmentNotificationRequest builds new formation assignment notification request 42 func (nb *NotificationBuilder) BuildFormationAssignmentNotificationRequest( 43 ctx context.Context, 44 formationTemplateID string, 45 joinPointDetails *formationconstraintpkg.GenerateFormationAssignmentNotificationOperationDetails, 46 webhook *model.Webhook, 47 ) (*webhookclient.FormationAssignmentNotificationRequest, error) { 48 log.C(ctx).Infof("Building formation assignment notification request...") 49 if err := nb.constraintEngine.EnforceConstraints(ctx, formationconstraintpkg.PreGenerateFormationAssignmentNotifications, joinPointDetails, formationTemplateID); err != nil { 50 log.C(ctx).Errorf("Did not generate notifications due to error: %v", errors.Wrapf(err, "While enforcing constraints for target operation %q and constraint type %q", model.GenerateFormationAssignmentNotificationOperation, model.PreOperation)) 51 return nil, nil 52 } 53 54 faInputBuilder, err := getFormationAssignmentInputBuilder(webhook.Type) 55 if err != nil { 56 return nil, err 57 } 58 59 req, err := nb.createWebhookRequest(ctx, webhook, faInputBuilder(joinPointDetails)) 60 if err != nil { 61 return nil, errors.Wrapf(err, "while creating webhook request") 62 } 63 64 if err := nb.constraintEngine.EnforceConstraints(ctx, formationconstraintpkg.PostGenerateFormationAssignmentNotifications, joinPointDetails, formationTemplateID); err != nil { 65 log.C(ctx).Errorf("Did not generate notifications due to error: %v", errors.Wrapf(err, "While enforcing constraints for target operation %q and constraint type %q", model.GenerateFormationAssignmentNotificationOperation, model.PostOperation)) 66 return nil, nil 67 } 68 69 return req, nil 70 } 71 72 // BuildFormationNotificationRequests builds new formation notification request 73 func (nb *NotificationBuilder) BuildFormationNotificationRequests(ctx context.Context, joinPointDetails *formationconstraintpkg.GenerateFormationNotificationOperationDetails, formation *model.Formation, formationTemplateWebhooks []*model.Webhook) ([]*webhookclient.FormationNotificationRequest, error) { 74 log.C(ctx).Infof("Building formation notification request for formation with name: %q...", formation.Name) 75 if err := nb.constraintEngine.EnforceConstraints(ctx, formationconstraintpkg.PreGenerateFormationNotifications, joinPointDetails, joinPointDetails.FormationTemplateID); err != nil { 76 return nil, errors.Wrapf(err, "while enforcing constraints for target operation %q and constraint type %q", model.GenerateFormationNotificationOperation, model.PreOperation) 77 } 78 79 if len(formationTemplateWebhooks) == 0 { 80 log.C(ctx).Infof("Formation template with ID: %q does not have any webhooks. No notifications will be generated.", joinPointDetails.FormationTemplateID) 81 return nil, nil 82 } 83 log.C(ctx).Infof("Formation template with ID: %q has/have %d webhook(s) of type: %q", joinPointDetails.FormationTemplateID, len(formationTemplateWebhooks), model.WebhookTypeFormationLifecycle) 84 85 formationTemplateInput := buildFormationLifecycleInput(joinPointDetails, formation) 86 87 requests := make([]*webhookclient.FormationNotificationRequest, 0, len(formationTemplateWebhooks)) 88 for _, webhook := range formationTemplateWebhooks { 89 gqlWebhook, err := nb.webhookConverter.ToGraphQL(webhook) 90 if err != nil { 91 return nil, errors.Wrapf(err, "while converting formation template webhook with ID: %s to graphql one", webhook.ID) 92 } 93 94 req := &webhookclient.FormationNotificationRequest{ 95 Request: webhookclient.NewRequest( 96 *gqlWebhook, 97 formationTemplateInput, 98 correlation.CorrelationIDFromContext(ctx), 99 ), 100 Operation: joinPointDetails.Operation, 101 Formation: formation, 102 FormationType: joinPointDetails.FormationType, 103 } 104 requests = append(requests, req) 105 } 106 107 if err := nb.constraintEngine.EnforceConstraints(ctx, formationconstraintpkg.PostGenerateFormationNotifications, joinPointDetails, joinPointDetails.FormationTemplateID); err != nil { 108 return nil, errors.Wrapf(err, "while enforcing constraints for target operation %q and constraint type %q", model.GenerateFormationNotificationOperation, model.PostOperation) 109 } 110 111 return requests, nil 112 } 113 114 // PrepareDetailsForConfigurationChangeNotificationGeneration returns GenerateFormationAssignmentNotificationOperationDetails for ConfigurationChanged webhooks 115 func (nb *NotificationBuilder) PrepareDetailsForConfigurationChangeNotificationGeneration( 116 operation model.FormationOperation, 117 formationID string, 118 formationTemplateID string, 119 applicationTemplate *webhookdir.ApplicationTemplateWithLabels, 120 application *webhookdir.ApplicationWithLabels, 121 runtime *webhookdir.RuntimeWithLabels, 122 runtimeContext *webhookdir.RuntimeContextWithLabels, 123 assignment *webhookdir.FormationAssignment, 124 reverseAssignment *webhookdir.FormationAssignment, 125 targetType model.ResourceType, 126 tenantContext *webhookdir.CustomerTenantContext, 127 tenantID string, 128 ) (*formationconstraintpkg.GenerateFormationAssignmentNotificationOperationDetails, error) { 129 details := &formationconstraintpkg.GenerateFormationAssignmentNotificationOperationDetails{ 130 Operation: operation, 131 FormationID: formationID, 132 FormationTemplateID: formationTemplateID, 133 CustomerTenantContext: tenantContext, 134 ApplicationTemplate: applicationTemplate, 135 Application: application, 136 Runtime: runtime, 137 RuntimeContext: runtimeContext, 138 Assignment: assignment, 139 ReverseAssignment: reverseAssignment, 140 ResourceType: targetType, 141 TenantID: tenantID, 142 } 143 switch targetType { 144 case model.ApplicationResourceType: 145 details.ResourceID = application.ID 146 147 subtype, err := determineResourceSubtype(application.Labels, nb.applicationTypeLabelKey) 148 if err != nil { 149 return nil, errors.Wrapf(err, "While determining subtype for application with ID %q", application.ID) 150 } 151 152 details.ResourceSubtype = subtype 153 case model.RuntimeResourceType: 154 details.ResourceID = runtime.ID 155 156 subtype, err := determineResourceSubtype(runtime.Labels, nb.runtimeTypeLabelKey) 157 if err != nil { 158 return nil, errors.Wrapf(err, "While determining subtype for runtime with ID %q", runtime.ID) 159 } 160 161 details.ResourceSubtype = subtype 162 case model.RuntimeContextResourceType: 163 details.ResourceID = runtimeContext.ID 164 165 subtype, err := determineResourceSubtype(runtime.Labels, nb.runtimeTypeLabelKey) 166 if err != nil { 167 return nil, errors.Wrapf(err, "While determining subtype for runtime context with ID %q", runtimeContext.ID) 168 } 169 170 details.ResourceSubtype = subtype 171 default: 172 return nil, errors.Errorf("Unsuported target resource subtype %q", targetType) 173 } 174 175 return details, nil 176 } 177 178 // PrepareDetailsForApplicationTenantMappingNotificationGeneration returns GenerateFormationAssignmentNotificationOperationDetails for applicationTenantMapping webhooks 179 func (nb *NotificationBuilder) PrepareDetailsForApplicationTenantMappingNotificationGeneration( 180 operation model.FormationOperation, 181 formationID string, 182 formationTemplateID string, 183 sourceApplicationTemplate *webhookdir.ApplicationTemplateWithLabels, 184 sourceApplication *webhookdir.ApplicationWithLabels, 185 targetApplicationTemplate *webhookdir.ApplicationTemplateWithLabels, 186 targetApplication *webhookdir.ApplicationWithLabels, 187 assignment *webhookdir.FormationAssignment, 188 reverseAssignment *webhookdir.FormationAssignment, 189 tenantContext *webhookdir.CustomerTenantContext, 190 tenantID string, 191 ) (*formationconstraintpkg.GenerateFormationAssignmentNotificationOperationDetails, error) { 192 details := &formationconstraintpkg.GenerateFormationAssignmentNotificationOperationDetails{ 193 Operation: operation, 194 FormationID: formationID, 195 FormationTemplateID: formationTemplateID, 196 CustomerTenantContext: tenantContext, 197 SourceApplicationTemplate: sourceApplicationTemplate, 198 SourceApplication: sourceApplication, 199 TargetApplicationTemplate: targetApplicationTemplate, 200 TargetApplication: targetApplication, 201 Assignment: assignment, 202 ReverseAssignment: reverseAssignment, 203 ResourceType: model.ApplicationResourceType, 204 ResourceID: targetApplication.ID, 205 TenantID: tenantID, 206 } 207 208 subtype, err := determineResourceSubtype(targetApplication.Labels, nb.applicationTypeLabelKey) 209 if err != nil { 210 return nil, errors.Wrapf(err, "While determining subtype for application with ID %q", targetApplication.ID) 211 } 212 213 details.ResourceSubtype = subtype 214 215 return details, nil 216 } 217 218 func (nb *NotificationBuilder) createWebhookRequest(ctx context.Context, webhook *model.Webhook, formationAssignmentTemplateInput webhookdir.FormationAssignmentTemplateInput) (*webhookclient.FormationAssignmentNotificationRequest, error) { 219 gqlWebhook, err := nb.webhookConverter.ToGraphQL(webhook) 220 if err != nil { 221 return nil, errors.Wrapf(err, "while converting webhook with ID %s", webhook.ID) 222 } 223 return &webhookclient.FormationAssignmentNotificationRequest{ 224 Webhook: *gqlWebhook, 225 Object: formationAssignmentTemplateInput, 226 CorrelationID: correlation.CorrelationIDFromContext(ctx), 227 }, nil 228 } 229 230 func getFormationAssignmentInputBuilder(webhookType model.WebhookType) (FormationAssignmentInputBuilder, error) { 231 switch webhookType { 232 case model.WebhookTypeConfigurationChanged: 233 return buildConfigurationChangeInputFromJoinpointDetails, nil 234 case model.WebhookTypeApplicationTenantMapping: 235 return buildApplicationTenantMappingInputFromJoinpointDetails, nil 236 default: 237 return nil, errors.Errorf("Unsupported Webhook Type %q", webhookType) 238 } 239 } 240 241 // FormationAssignmentInputBuilder represents expected signature for methods that create operator input from the provided details 242 type FormationAssignmentInputBuilder func(details *formationconstraintpkg.GenerateFormationAssignmentNotificationOperationDetails) webhookdir.FormationAssignmentTemplateInput 243 244 func buildConfigurationChangeInputFromJoinpointDetails(details *formationconstraintpkg.GenerateFormationAssignmentNotificationOperationDetails) webhookdir.FormationAssignmentTemplateInput { 245 return &webhookdir.FormationConfigurationChangeInput{ 246 Operation: details.Operation, 247 FormationID: details.FormationID, 248 ApplicationTemplate: details.ApplicationTemplate, 249 Application: details.Application, 250 Runtime: details.Runtime, 251 RuntimeContext: details.RuntimeContext, 252 CustomerTenantContext: details.CustomerTenantContext, 253 Assignment: details.Assignment, 254 ReverseAssignment: details.ReverseAssignment, 255 } 256 } 257 258 func buildApplicationTenantMappingInputFromJoinpointDetails(details *formationconstraintpkg.GenerateFormationAssignmentNotificationOperationDetails) webhookdir.FormationAssignmentTemplateInput { 259 return &webhookdir.ApplicationTenantMappingInput{ 260 Operation: details.Operation, 261 FormationID: details.FormationID, 262 SourceApplicationTemplate: details.SourceApplicationTemplate, 263 SourceApplication: details.SourceApplication, 264 TargetApplicationTemplate: details.TargetApplicationTemplate, 265 TargetApplication: details.TargetApplication, 266 CustomerTenantContext: details.CustomerTenantContext, 267 Assignment: details.Assignment, 268 ReverseAssignment: details.ReverseAssignment, 269 } 270 } 271 272 func buildFormationLifecycleInput(details *formationconstraintpkg.GenerateFormationNotificationOperationDetails, formation *model.Formation) *webhookdir.FormationLifecycleInput { 273 return &webhookdir.FormationLifecycleInput{ 274 Operation: details.Operation, 275 Formation: formation, 276 CustomerTenantContext: details.CustomerTenantContext, 277 } 278 } 279 280 func determineResourceSubtype(labels map[string]string, labelKey string) (string, error) { 281 labelValue, ok := labels[labelKey] 282 if !ok { 283 return "", errors.Errorf("Missing %q label", labelKey) 284 } 285 286 return labelValue, nil 287 }