github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/formationassignment/notifications.go (about) 1 package formationassignment 2 3 import ( 4 "context" 5 6 "github.com/kyma-incubator/compass/components/director/pkg/formationconstraint" 7 "github.com/kyma-incubator/compass/components/director/pkg/tenant" 8 9 databuilder "github.com/kyma-incubator/compass/components/director/internal/domain/webhook/datainputbuilder" 10 "github.com/kyma-incubator/compass/components/director/internal/model" 11 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 12 "github.com/kyma-incubator/compass/components/director/pkg/log" 13 "github.com/kyma-incubator/compass/components/director/pkg/webhook" 14 webhookclient "github.com/kyma-incubator/compass/components/director/pkg/webhook_client" 15 "github.com/pkg/errors" 16 ) 17 18 // formationRepository represents the Formations repository layer 19 // 20 //go:generate mockery --exported --name=formationRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 21 type formationRepository interface { 22 Get(ctx context.Context, id, tenantID string) (*model.Formation, error) 23 } 24 25 //go:generate mockery --exported --name=notificationService --output=automock --outpkg=automock --case=underscore --disable-version-string 26 type notificationService interface { 27 SendNotification(ctx context.Context, webhookNotificationReq webhookclient.WebhookExtRequest) (*webhook.Response, error) 28 } 29 30 //go:generate mockery --exported --name=notificationBuilder --output=automock --outpkg=automock --case=underscore --disable-version-string 31 type notificationBuilder interface { 32 BuildFormationAssignmentNotificationRequest(ctx context.Context, formationTemplateID string, joinPointDetails *formationconstraint.GenerateFormationAssignmentNotificationOperationDetails, webhook *model.Webhook) (*webhookclient.FormationAssignmentNotificationRequest, error) 33 PrepareDetailsForConfigurationChangeNotificationGeneration(operation model.FormationOperation, formationID string, formationTemplateID string, applicationTemplate *webhook.ApplicationTemplateWithLabels, application *webhook.ApplicationWithLabels, runtime *webhook.RuntimeWithLabels, runtimeContext *webhook.RuntimeContextWithLabels, assignment *webhook.FormationAssignment, reverseAssignment *webhook.FormationAssignment, targetType model.ResourceType, tenantContext *webhook.CustomerTenantContext, tenantID string) (*formationconstraint.GenerateFormationAssignmentNotificationOperationDetails, error) 34 PrepareDetailsForApplicationTenantMappingNotificationGeneration(operation model.FormationOperation, formationID string, formationTemplateID string, sourceApplicationTemplate *webhook.ApplicationTemplateWithLabels, sourceApplication *webhook.ApplicationWithLabels, targetApplicationTemplate *webhook.ApplicationTemplateWithLabels, targetApplication *webhook.ApplicationWithLabels, assignment *webhook.FormationAssignment, reverseAssignment *webhook.FormationAssignment, tenantContext *webhook.CustomerTenantContext, tenantID string) (*formationconstraint.GenerateFormationAssignmentNotificationOperationDetails, error) 35 } 36 37 type formationAssignmentNotificationService struct { 38 formationAssignmentRepo FormationAssignmentRepository 39 webhookConverter webhookConverter 40 webhookRepository webhookRepository 41 tenantRepository tenantRepository 42 webhookDataInputBuilder databuilder.DataInputBuilder 43 formationRepository formationRepository 44 notificationBuilder notificationBuilder 45 runtimeContextRepo runtimeContextRepository 46 labelService labelService 47 runtimeTypeLabelKey string 48 applicationTypeLabelKey string 49 } 50 51 // NewFormationAssignmentNotificationService creates formation assignment notifications service 52 func NewFormationAssignmentNotificationService(formationAssignmentRepo FormationAssignmentRepository, webhookConverter webhookConverter, webhookRepository webhookRepository, tenantRepository tenantRepository, webhookDataInputBuilder databuilder.DataInputBuilder, formationRepository formationRepository, notificationBuilder notificationBuilder, runtimeContextRepo runtimeContextRepository, labelService labelService, runtimeTypeLabelKey, applicationTypeLabelKey string) *formationAssignmentNotificationService { 53 return &formationAssignmentNotificationService{ 54 formationAssignmentRepo: formationAssignmentRepo, 55 webhookConverter: webhookConverter, 56 webhookRepository: webhookRepository, 57 tenantRepository: tenantRepository, 58 webhookDataInputBuilder: webhookDataInputBuilder, 59 formationRepository: formationRepository, 60 notificationBuilder: notificationBuilder, 61 runtimeContextRepo: runtimeContextRepo, 62 labelService: labelService, 63 runtimeTypeLabelKey: runtimeTypeLabelKey, 64 applicationTypeLabelKey: applicationTypeLabelKey, 65 } 66 } 67 68 // GenerateFormationAssignmentNotification generates formation assignment notification by provided model.FormationAssignment 69 func (fan *formationAssignmentNotificationService) GenerateFormationAssignmentNotification(ctx context.Context, fa *model.FormationAssignment, operation model.FormationOperation) (*webhookclient.FormationAssignmentNotificationRequest, error) { 70 log.C(ctx).Infof("Generating notification for formation assignment with ID: %q and target type: %q and target ID: %q", fa.ID, fa.TargetType, fa.Target) 71 72 customerTenantContext, err := fan.extractCustomerTenantContext(ctx, fa.TenantID) 73 if err != nil { 74 return nil, errors.Wrapf(err, "while extracting customer tenant context for tenant with internal ID %s", fa.TenantID) 75 } 76 77 referencedFormation, err := fan.formationRepository.Get(ctx, fa.FormationID, fa.TenantID) 78 if err != nil { 79 return nil, err 80 } 81 82 switch fa.TargetType { 83 case model.FormationAssignmentTypeApplication: 84 return fan.generateApplicationFANotification(ctx, fa, referencedFormation, customerTenantContext, operation) 85 case model.FormationAssignmentTypeRuntime: 86 return fan.generateRuntimeFANotification(ctx, fa, referencedFormation, customerTenantContext, operation) 87 case model.FormationAssignmentTypeRuntimeContext: 88 return fan.generateRuntimeContextFANotification(ctx, fa, referencedFormation, customerTenantContext, operation) 89 default: 90 return nil, errors.Errorf("Unknown formation assignment type: %q", fa.TargetType) 91 } 92 } 93 94 // PrepareDetailsForNotificationStatusReturned creates NotificationStatusReturnedOperationDetails by given tenantID, formation assignment and formation operation 95 func (fan *formationAssignmentNotificationService) PrepareDetailsForNotificationStatusReturned(ctx context.Context, tenantID string, fa *model.FormationAssignment, operation model.FormationOperation) (*formationconstraint.NotificationStatusReturnedOperationDetails, error) { 96 var targetType model.ResourceType 97 switch fa.TargetType { 98 case model.FormationAssignmentTypeApplication: 99 targetType = model.ApplicationResourceType 100 case model.FormationAssignmentTypeRuntime: 101 targetType = model.RuntimeResourceType 102 case model.FormationAssignmentTypeRuntimeContext: 103 targetType = model.RuntimeContextResourceType 104 } 105 106 targetSubtype, err := fan.getObjectSubtype(ctx, fa.TenantID, fa.Target, fa.TargetType) 107 if err != nil { 108 return nil, err 109 } 110 111 formation, err := fan.formationRepository.Get(ctx, fa.FormationID, tenantID) 112 if err != nil { 113 log.C(ctx).Errorf("An error occurred while getting formation with ID %q in tenant %q: %v", fa.FormationID, tenantID, err) 114 return nil, errors.Wrapf(err, "An error occurred while getting formation with ID %q in tenant %q", fa.FormationID, tenantID) 115 } 116 117 reverseFa, err := fan.getReverseBySourceAndTarget(ctx, tenantID, formation.ID, fa.Source, fa.Target) 118 if err != nil { 119 if !apperrors.IsNotFoundError(err) { 120 log.C(ctx).Errorf("An error occurred while getting reverse formation assignment: %v", err) 121 return nil, errors.Wrap(err, "An error occurred while getting reverse formation assignment") 122 } 123 log.C(ctx).Debugf("Reverse assignment with source %q and target %q in formation with ID %q is not found.", fa.Target, fa.Source, formation.ID) 124 } 125 126 return &formationconstraint.NotificationStatusReturnedOperationDetails{ 127 ResourceType: targetType, 128 ResourceSubtype: targetSubtype, 129 Operation: operation, 130 FormationAssignment: fa, 131 ReverseFormationAssignment: reverseFa, 132 Formation: formation, 133 }, nil 134 } 135 136 // GenerateFormationAssignmentNotificationExt generates extended formation assignment notification by given formation(and reverse formation) assignment request mapping and formation operation 137 func (fan *formationAssignmentNotificationService) GenerateFormationAssignmentNotificationExt(ctx context.Context, faRequestMapping, reverseFaRequestMapping *FormationAssignmentRequestMapping, operation model.FormationOperation) (*webhookclient.FormationAssignmentNotificationRequestExt, error) { 138 targetSubtype, err := fan.getObjectSubtype(ctx, faRequestMapping.FormationAssignment.TenantID, faRequestMapping.FormationAssignment.Target, faRequestMapping.FormationAssignment.TargetType) 139 if err != nil { 140 return nil, err 141 } 142 143 formation, err := fan.formationRepository.Get(ctx, faRequestMapping.FormationAssignment.FormationID, faRequestMapping.FormationAssignment.TenantID) 144 if err != nil { 145 return nil, err 146 } 147 148 var reverseFa *model.FormationAssignment 149 if reverseFaRequestMapping != nil { 150 reverseFa = reverseFaRequestMapping.FormationAssignment 151 } 152 153 return &webhookclient.FormationAssignmentNotificationRequestExt{ 154 Operation: operation, 155 FormationAssignmentNotificationRequest: faRequestMapping.Request, 156 FormationAssignment: faRequestMapping.FormationAssignment, 157 ReverseFormationAssignment: reverseFa, 158 Formation: formation, 159 TargetSubtype: targetSubtype, 160 }, nil 161 } 162 163 func (fan *formationAssignmentNotificationService) getObjectSubtype(ctx context.Context, tnt, objectID string, objectType model.FormationAssignmentType) (string, error) { 164 switch objectType { 165 case model.FormationAssignmentTypeApplication: 166 applicationTypeLabel, err := fan.labelService.GetLabel(ctx, tnt, &model.LabelInput{ 167 Key: fan.applicationTypeLabelKey, 168 ObjectID: objectID, 169 ObjectType: model.ApplicationLabelableObject, 170 }) 171 if err != nil { 172 if apperrors.IsNotFoundError(err) { 173 return "", nil 174 } 175 return "", errors.Wrapf(err, "while getting label %q for application with ID %q", fan.applicationTypeLabelKey, objectID) 176 } 177 178 applicationType, ok := applicationTypeLabel.Value.(string) 179 if !ok { 180 return "", errors.Errorf("Missing application type for application %q", objectID) 181 } 182 return applicationType, nil 183 184 case model.FormationAssignmentTypeRuntime: 185 runtimeTypeLabel, err := fan.labelService.GetLabel(ctx, tnt, &model.LabelInput{ 186 Key: fan.runtimeTypeLabelKey, 187 ObjectID: objectID, 188 ObjectType: model.RuntimeLabelableObject, 189 }) 190 if err != nil { 191 if apperrors.IsNotFoundError(err) { 192 return "", nil 193 } 194 return "", errors.Wrapf(err, "while getting label %q for runtime with ID %q", fan.runtimeTypeLabelKey, objectID) 195 } 196 197 runtimeType, ok := runtimeTypeLabel.Value.(string) 198 if !ok { 199 return "", errors.Errorf("Missing runtime type for runtime %q", objectID) 200 } 201 return runtimeType, nil 202 203 case model.FormationAssignmentTypeRuntimeContext: 204 rtmCtx, err := fan.runtimeContextRepo.GetByID(ctx, tnt, objectID) 205 if err != nil { 206 return "", errors.Wrapf(err, "while fetching runtime context with ID %q", objectID) 207 } 208 209 runtimeTypeLabel, err := fan.labelService.GetLabel(ctx, tnt, &model.LabelInput{ 210 Key: fan.runtimeTypeLabelKey, 211 ObjectID: rtmCtx.RuntimeID, 212 ObjectType: model.RuntimeLabelableObject, 213 }) 214 if err != nil { 215 return "", errors.Wrapf(err, "while getting label %q for runtime with ID %q", fan.runtimeTypeLabelKey, objectID) 216 } 217 218 runtimeType, ok := runtimeTypeLabel.Value.(string) 219 if !ok { 220 return "", errors.Errorf("Missing runtime type for runtime %q", rtmCtx.RuntimeID) 221 } 222 return runtimeType, nil 223 224 default: 225 return "", errors.Errorf("unknown object type %q", objectType) 226 } 227 } 228 229 func (fan *formationAssignmentNotificationService) getReverseBySourceAndTarget(ctx context.Context, tnt, formationID, sourceID, targetID string) (*model.FormationAssignment, error) { 230 log.C(ctx).Infof("Getting reverse formation assignment for formation ID: %q and source: %q and target: %q", formationID, sourceID, targetID) 231 232 reverseFA, err := fan.formationAssignmentRepo.GetReverseBySourceAndTarget(ctx, tnt, formationID, sourceID, targetID) 233 if err != nil { 234 return nil, errors.Wrapf(err, "while getting reverse formation assignment for formation ID: %q and source: %q and target: %q", formationID, sourceID, targetID) 235 } 236 237 return reverseFA, nil 238 } 239 240 // generateApplicationFANotification generates application formation assignment notification based on the reverse(source) type of the formation assignment 241 func (fan *formationAssignmentNotificationService) generateApplicationFANotification(ctx context.Context, fa *model.FormationAssignment, referencedFormation *model.Formation, customerTenantContext *webhook.CustomerTenantContext, operation model.FormationOperation) (*webhookclient.FormationAssignmentNotificationRequest, error) { 242 tenantID := fa.TenantID 243 appID := fa.Target 244 245 applicationWithLabels, appTemplateWithLabels, err := fan.webhookDataInputBuilder.PrepareApplicationAndAppTemplateWithLabels(ctx, tenantID, appID) 246 if err != nil { 247 log.C(ctx).Error(err) 248 return nil, err 249 } 250 251 appTemplateID := "" 252 if appTemplateWithLabels != nil { 253 appTemplateID = appTemplateWithLabels.ID 254 } 255 256 if fa.SourceType == model.FormationAssignmentTypeApplication { 257 reverseAppID := fa.Source 258 log.C(ctx).Infof("The formation assignment reverse object type is %q and has ID: %q", model.FormationAssignmentTypeApplication, reverseAppID) 259 260 reverseAppWithLabels, reverseAppTemplateWithLabels, err := fan.webhookDataInputBuilder.PrepareApplicationAndAppTemplateWithLabels(ctx, tenantID, reverseAppID) 261 if err != nil { 262 log.C(ctx).Error(err) 263 return nil, err 264 } 265 266 reverseFA, err := fan.formationAssignmentRepo.GetReverseBySourceAndTarget(ctx, tenantID, fa.FormationID, fa.Source, fa.Target) 267 if err != nil && !apperrors.IsNotFoundError(err) { 268 log.C(ctx).Error(err) 269 return nil, err 270 } 271 272 log.C(ctx).Infof("Preparing join point details for application tenant mapping notification generation") 273 details, err := fan.notificationBuilder.PrepareDetailsForApplicationTenantMappingNotificationGeneration( 274 operation, 275 fa.FormationID, 276 referencedFormation.FormationTemplateID, 277 reverseAppTemplateWithLabels, 278 reverseAppWithLabels, 279 appTemplateWithLabels, 280 applicationWithLabels, 281 convertFormationAssignmentFromModel(fa), 282 convertFormationAssignmentFromModel(reverseFA), 283 customerTenantContext, 284 tenantID, 285 ) 286 if err != nil { 287 log.C(ctx).Errorf("while preparing join point details for application tenant mapping notification generation: %v", err) 288 return nil, err 289 } 290 291 appToAppWebhook, err := GetWebhookForApplication(ctx, fan.webhookRepository, tenantID, appID, appTemplateID, model.WebhookTypeApplicationTenantMapping) 292 if err != nil { 293 return nil, err 294 } 295 if appToAppWebhook == nil { 296 return nil, nil 297 } 298 299 notificationReq, err := fan.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, referencedFormation.FormationTemplateID, details, appToAppWebhook) 300 if err != nil { 301 log.C(ctx).Errorf("while building notification request: %v", err) 302 return nil, err 303 } 304 305 return notificationReq, nil 306 } else if fa.SourceType == model.FormationAssignmentTypeRuntime { 307 runtimeID := fa.Source 308 log.C(ctx).Infof("The formation assignment reverse object type is %q and has ID: %q", model.FormationAssignmentTypeRuntime, runtimeID) 309 310 runtimeWithLabels, err := fan.webhookDataInputBuilder.PrepareRuntimeWithLabels(ctx, tenantID, runtimeID) 311 if err != nil { 312 log.C(ctx).Error(err) 313 return nil, err 314 } 315 316 reverseFA, err := fan.formationAssignmentRepo.GetReverseBySourceAndTarget(ctx, tenantID, fa.FormationID, fa.Source, fa.Target) 317 if err != nil && !apperrors.IsNotFoundError(err) { 318 log.C(ctx).Error(err) 319 return nil, err 320 } 321 322 log.C(ctx).Infof("Preparing join point details for configuration change notification generation") 323 details, err := fan.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration( 324 operation, 325 fa.FormationID, 326 referencedFormation.FormationTemplateID, 327 appTemplateWithLabels, 328 applicationWithLabels, 329 runtimeWithLabels, 330 nil, 331 convertFormationAssignmentFromModel(fa), 332 convertFormationAssignmentFromModel(reverseFA), 333 model.ApplicationResourceType, 334 customerTenantContext, 335 tenantID, 336 ) 337 if err != nil { 338 log.C(ctx).Errorf("while preparing join point details for configuration change notification generation: %v", err) 339 return nil, err 340 } 341 342 appWebhook, err := GetWebhookForApplication(ctx, fan.webhookRepository, tenantID, appID, appTemplateID, model.WebhookTypeConfigurationChanged) 343 if err != nil { 344 return nil, err 345 } 346 if appWebhook == nil { 347 return nil, nil 348 } 349 350 notificationReq, err := fan.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, referencedFormation.FormationTemplateID, details, appWebhook) 351 if err != nil { 352 log.C(ctx).Errorf("while building notification request: %v", err) 353 return nil, err 354 } 355 356 return notificationReq, nil 357 } else { 358 runtimeCtxID := fa.Source 359 log.C(ctx).Infof("The formation assignment reverse object type is %q and has ID: %q", model.FormationAssignmentTypeRuntimeContext, runtimeCtxID) 360 361 runtimeContextWithLabels, err := fan.webhookDataInputBuilder.PrepareRuntimeContextWithLabels(ctx, tenantID, runtimeCtxID) 362 if err != nil { 363 log.C(ctx).Error(err) 364 return nil, err 365 } 366 367 runtimeID := runtimeContextWithLabels.RuntimeContext.RuntimeID 368 runtimeWithLabels, err := fan.webhookDataInputBuilder.PrepareRuntimeWithLabels(ctx, tenantID, runtimeID) 369 if err != nil { 370 log.C(ctx).Error(err) 371 return nil, err 372 } 373 374 reverseFA, err := fan.formationAssignmentRepo.GetReverseBySourceAndTarget(ctx, tenantID, fa.FormationID, fa.Source, fa.Target) 375 if err != nil && !apperrors.IsNotFoundError(err) { 376 log.C(ctx).Error(err) 377 return nil, err 378 } 379 380 log.C(ctx).Infof("Preparing join point details for configuration change notification generation") 381 details, err := fan.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration( 382 operation, 383 fa.FormationID, 384 referencedFormation.FormationTemplateID, 385 appTemplateWithLabels, 386 applicationWithLabels, 387 runtimeWithLabels, 388 runtimeContextWithLabels, 389 convertFormationAssignmentFromModel(fa), 390 convertFormationAssignmentFromModel(reverseFA), 391 model.ApplicationResourceType, 392 customerTenantContext, 393 tenantID, 394 ) 395 if err != nil { 396 log.C(ctx).Errorf("while preparing join point details for configuration change notification generation: %v", err) 397 return nil, err 398 } 399 400 appWebhook, err := GetWebhookForApplication(ctx, fan.webhookRepository, tenantID, appID, appTemplateID, model.WebhookTypeConfigurationChanged) 401 if err != nil { 402 return nil, err 403 } 404 if appWebhook == nil { 405 return nil, nil 406 } 407 408 notificationReq, err := fan.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, referencedFormation.FormationTemplateID, details, appWebhook) 409 if err != nil { 410 log.C(ctx).Errorf("while building notification request: %v", err) 411 return nil, err 412 } 413 414 return notificationReq, nil 415 } 416 } 417 418 // generateRuntimeFANotification generates runtime formation assignment notification based on the reverse(source) type of the formation 419 func (fan *formationAssignmentNotificationService) generateRuntimeFANotification(ctx context.Context, fa *model.FormationAssignment, referencedFormation *model.Formation, customerTenantContext *webhook.CustomerTenantContext, operation model.FormationOperation) (*webhookclient.FormationAssignmentNotificationRequest, error) { 420 tenantID := fa.TenantID 421 runtimeID := fa.Target 422 423 runtimeWebhook, err := fan.webhookRepository.GetByIDAndWebhookType(ctx, tenantID, runtimeID, model.RuntimeWebhookReference, model.WebhookTypeConfigurationChanged) 424 if err != nil { 425 if apperrors.IsNotFoundError(err) { 426 log.C(ctx).Infof("There is no configuration changed webhook for runtime with ID: %q. There are no notifications to be generated", runtimeID) 427 return nil, nil 428 } 429 return nil, errors.Wrapf(err, "while getting configuration changed webhook for runtime with ID: %q", runtimeID) 430 } 431 432 if fa.SourceType != model.FormationAssignmentTypeApplication { 433 log.C(ctx).Errorf("The formation assignmet with ID: %q and target type: %q has unsupported reverse(source) type: %q", fa.ID, fa.TargetType, fa.SourceType) 434 return nil, errors.Errorf("The formation assignmet with ID: %q and target type: %q has unsupported reverse(source) type: %q", fa.ID, fa.TargetType, fa.SourceType) 435 } 436 437 appID := fa.Source 438 log.C(ctx).Infof("The formation assignment reverse object type is %q and has ID: %q", model.FormationAssignmentTypeApplication, appID) 439 440 applicationWithLabels, appTemplateWithLabels, err := fan.webhookDataInputBuilder.PrepareApplicationAndAppTemplateWithLabels(ctx, tenantID, appID) 441 if err != nil { 442 log.C(ctx).Error(err) 443 return nil, err 444 } 445 446 runtimeWithLabels, err := fan.webhookDataInputBuilder.PrepareRuntimeWithLabels(ctx, tenantID, runtimeID) 447 if err != nil { 448 log.C(ctx).Error(err) 449 return nil, err 450 } 451 452 reverseFA, err := fan.formationAssignmentRepo.GetReverseBySourceAndTarget(ctx, tenantID, fa.FormationID, fa.Source, fa.Target) 453 if err != nil && !apperrors.IsNotFoundError(err) { 454 log.C(ctx).Error(err) 455 return nil, err 456 } 457 458 log.C(ctx).Infof("Preparing join point details for configuration change notification generation") 459 details, err := fan.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration( 460 operation, 461 fa.FormationID, 462 referencedFormation.FormationTemplateID, 463 appTemplateWithLabels, 464 applicationWithLabels, 465 runtimeWithLabels, 466 nil, 467 convertFormationAssignmentFromModel(fa), 468 convertFormationAssignmentFromModel(reverseFA), 469 model.RuntimeResourceType, 470 customerTenantContext, 471 tenantID, 472 ) 473 if err != nil { 474 log.C(ctx).Errorf("while preparing join point details for configuration change notification generation: %v", err) 475 return nil, err 476 } 477 478 notificationReq, err := fan.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, referencedFormation.FormationTemplateID, details, runtimeWebhook) 479 if err != nil { 480 log.C(ctx).Errorf("while building notification request: %v", err) 481 return nil, err 482 } 483 484 return notificationReq, nil 485 } 486 487 // generateRuntimeContextFANotification generates runtime context formation assignment notification based on the reverse(source) type of the formation assignment 488 func (fan *formationAssignmentNotificationService) generateRuntimeContextFANotification(ctx context.Context, fa *model.FormationAssignment, referencedFormation *model.Formation, customerTenantContext *webhook.CustomerTenantContext, operation model.FormationOperation) (*webhookclient.FormationAssignmentNotificationRequest, error) { 489 tenantID := fa.TenantID 490 runtimeCtxID := fa.Target 491 492 runtimeContextWithLabels, err := fan.webhookDataInputBuilder.PrepareRuntimeContextWithLabels(ctx, tenantID, runtimeCtxID) 493 if err != nil { 494 log.C(ctx).Error(err) 495 return nil, err 496 } 497 498 runtimeID := runtimeContextWithLabels.RuntimeContext.RuntimeID 499 runtimeWebhook, err := fan.webhookRepository.GetByIDAndWebhookType(ctx, tenantID, runtimeID, model.RuntimeWebhookReference, model.WebhookTypeConfigurationChanged) 500 if err != nil { 501 if apperrors.IsNotFoundError(err) { 502 log.C(ctx).Infof("There is no configuration changed webhook for runtime with ID: %q. There are no notifications to be generated", runtimeID) 503 return nil, nil 504 } 505 return nil, errors.Wrapf(err, "while getting configuration changed webhook for runtime with ID: %q", runtimeID) 506 } 507 508 if fa.SourceType != model.FormationAssignmentTypeApplication { 509 log.C(ctx).Errorf("The formation assignmet with ID: %q and target type: %q has unsupported reverse(source) type: %q", fa.ID, fa.TargetType, fa.SourceType) 510 return nil, errors.Errorf("The formation assignmet with ID: %q and target type: %q has unsupported reverse(source) type: %q", fa.ID, fa.TargetType, fa.SourceType) 511 } 512 513 appID := fa.Source 514 log.C(ctx).Infof("The formation assignment reverse object type is %q and has ID: %q", model.FormationAssignmentTypeApplication, appID) 515 516 applicationWithLabels, appTemplateWithLabels, err := fan.webhookDataInputBuilder.PrepareApplicationAndAppTemplateWithLabels(ctx, tenantID, appID) 517 if err != nil { 518 log.C(ctx).Error(err) 519 return nil, err 520 } 521 522 runtimeWithLabels, err := fan.webhookDataInputBuilder.PrepareRuntimeWithLabels(ctx, tenantID, runtimeID) 523 if err != nil { 524 log.C(ctx).Error(err) 525 return nil, err 526 } 527 528 reverseFA, err := fan.formationAssignmentRepo.GetReverseBySourceAndTarget(ctx, tenantID, fa.FormationID, fa.Source, fa.Target) 529 if err != nil && !apperrors.IsNotFoundError(err) { 530 log.C(ctx).Error(err) 531 return nil, err 532 } 533 534 log.C(ctx).Infof("Preparing join point details for configuration change notification generation") 535 details, err := fan.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration( 536 operation, 537 fa.FormationID, 538 referencedFormation.FormationTemplateID, 539 appTemplateWithLabels, 540 applicationWithLabels, 541 runtimeWithLabels, 542 runtimeContextWithLabels, 543 convertFormationAssignmentFromModel(fa), 544 convertFormationAssignmentFromModel(reverseFA), 545 model.RuntimeContextResourceType, 546 customerTenantContext, 547 tenantID, 548 ) 549 if err != nil { 550 log.C(ctx).Errorf("while preparing join point details for configuration change notification generation: %v", err) 551 return nil, err 552 } 553 554 notificationReq, err := fan.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, referencedFormation.FormationTemplateID, details, runtimeWebhook) 555 if err != nil { 556 log.C(ctx).Errorf("while building notification request: %v", err) 557 return nil, err 558 } 559 560 return notificationReq, nil 561 } 562 563 func convertFormationAssignmentFromModel(formationAssignment *model.FormationAssignment) *webhook.FormationAssignment { 564 if formationAssignment == nil { 565 return &webhook.FormationAssignment{Value: "\"\""} 566 } 567 config := string(formationAssignment.Value) 568 if config == "" || formationAssignment.State == string(model.CreateErrorAssignmentState) || formationAssignment.State == string(model.DeleteErrorAssignmentState) { 569 config = "\"\"" 570 } 571 return &webhook.FormationAssignment{ 572 ID: formationAssignment.ID, 573 FormationID: formationAssignment.FormationID, 574 TenantID: formationAssignment.TenantID, 575 Source: formationAssignment.Source, 576 SourceType: formationAssignment.SourceType, 577 Target: formationAssignment.Target, 578 TargetType: formationAssignment.TargetType, 579 State: formationAssignment.State, 580 Value: config, 581 } 582 } 583 584 func (fan *formationAssignmentNotificationService) extractCustomerTenantContext(ctx context.Context, internalTenantID string) (*webhook.CustomerTenantContext, error) { 585 tenantObject, err := fan.tenantRepository.Get(ctx, internalTenantID) 586 if err != nil { 587 return nil, err 588 } 589 590 customerID, err := fan.tenantRepository.GetCustomerIDParentRecursively(ctx, internalTenantID) 591 if err != nil { 592 return nil, err 593 } 594 595 var accountID *string 596 var path *string 597 if tenantObject.Type == tenant.Account { 598 accountID = &tenantObject.ExternalTenant 599 } else if tenantObject.Type == tenant.ResourceGroup { 600 path = &tenantObject.ExternalTenant 601 } 602 603 return &webhook.CustomerTenantContext{ 604 CustomerID: customerID, 605 AccountID: accountID, 606 Path: path, 607 }, nil 608 } 609 610 // GetWebhookForApplication gets webhook of type webhookType for the application with ID appID 611 // If the application has webhook of type webhookType it is returned 612 // If the application does not have a webhook of type webhookType, but its application template has one it is returned 613 // If both application and application template does not have a webhook of type webhookType, no webhook is returned 614 func GetWebhookForApplication(ctx context.Context, webhookRepo webhookRepository, tenant, appID, appTemplateID string, webhookType model.WebhookType) (*model.Webhook, error) { 615 webhook, err := webhookRepo.GetByIDAndWebhookType(ctx, tenant, appID, model.ApplicationWebhookReference, webhookType) 616 if err != nil { 617 if !apperrors.IsNotFoundError(err) { 618 return nil, errors.Wrapf(err, "while listing %s webhooks for application %s", webhookType, appID) 619 } 620 621 log.C(ctx).Infof("There is no %q webhook attached to application with ID: %q. Looking for %q webhook attached to application template.", webhookType, appID, webhookType) 622 623 if appTemplateID == "" { 624 log.C(ctx).Infof("There is no application template for application with ID: %q. No notifications will be generated.", appID) 625 return nil, nil 626 } 627 628 webhook, err = webhookRepo.GetByIDAndWebhookType(ctx, tenant, appTemplateID, model.ApplicationTemplateWebhookReference, webhookType) 629 if err != nil { 630 if !apperrors.IsNotFoundError(err) { 631 return nil, errors.Wrapf(err, "while listing %q webhooks for application template with ID: %q on behalf of application with ID: %q", webhookType, appTemplateID, appID) 632 } 633 634 log.C(ctx).Infof("There is no %q webhook attached to application template with ID: %q. No notifications will be generated.", webhookType, appTemplateID) 635 return nil, nil 636 } 637 } 638 639 return webhook, nil 640 }