github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/formation/notifications_generator.go (about)

     1  package formation
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/kyma-incubator/compass/components/director/internal/domain/formationassignment"
     7  	databuilder "github.com/kyma-incubator/compass/components/director/internal/domain/webhook/datainputbuilder"
     8  	"github.com/kyma-incubator/compass/components/director/internal/model"
     9  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    10  	"github.com/kyma-incubator/compass/components/director/pkg/formationconstraint"
    11  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    12  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    13  	webhookdir "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  //go:generate mockery --exported --name=applicationRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    19  type applicationRepository interface {
    20  	GetByID(ctx context.Context, tenant, id string) (*model.Application, error)
    21  	ListAllByIDs(ctx context.Context, tenantID string, ids []string) ([]*model.Application, error)
    22  	ListByScenariosNoPaging(ctx context.Context, tenant string, scenarios []string) ([]*model.Application, error)
    23  	ListByScenariosAndIDs(ctx context.Context, tenant string, scenarios []string, ids []string) ([]*model.Application, error)
    24  	ListListeningApplications(ctx context.Context, tenant string, whType model.WebhookType) ([]*model.Application, error)
    25  }
    26  
    27  //go:generate mockery --exported --name=applicationTemplateRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    28  type applicationTemplateRepository interface {
    29  	Get(ctx context.Context, id string) (*model.ApplicationTemplate, error)
    30  	ListByIDs(ctx context.Context, ids []string) ([]*model.ApplicationTemplate, error)
    31  }
    32  
    33  //go:generate mockery --exported --name=webhookRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    34  type webhookRepository interface {
    35  	ListByReferenceObjectTypeAndWebhookType(ctx context.Context, tenant string, whType model.WebhookType, objType model.WebhookReferenceObjectType) ([]*model.Webhook, error)
    36  	ListByReferenceObjectTypesAndWebhookType(ctx context.Context, tenant string, whType model.WebhookType, objTypes []model.WebhookReferenceObjectType) ([]*model.Webhook, error)
    37  	ListByReferenceObjectIDGlobal(ctx context.Context, objID string, objType model.WebhookReferenceObjectType) ([]*model.Webhook, error)
    38  	GetByIDAndWebhookType(ctx context.Context, tenant, objectID string, objectType model.WebhookReferenceObjectType, webhookType model.WebhookType) (*model.Webhook, error)
    39  }
    40  
    41  //go:generate mockery --exported --name=notificationBuilder --output=automock --outpkg=automock --case=underscore --disable-version-string
    42  type notificationBuilder interface {
    43  	BuildFormationAssignmentNotificationRequest(ctx context.Context, formationTemplateID string, joinPointDetails *formationconstraint.GenerateFormationAssignmentNotificationOperationDetails, webhook *model.Webhook) (*webhookclient.FormationAssignmentNotificationRequest, error)
    44  	BuildFormationNotificationRequests(ctx context.Context, joinPointDetails *formationconstraint.GenerateFormationNotificationOperationDetails, formation *model.Formation, formationTemplateWebhooks []*model.Webhook) ([]*webhookclient.FormationNotificationRequest, error)
    45  	PrepareDetailsForConfigurationChangeNotificationGeneration(operation model.FormationOperation, formationID string, formationTemplateID string, applicationTemplate *webhookdir.ApplicationTemplateWithLabels, application *webhookdir.ApplicationWithLabels, runtime *webhookdir.RuntimeWithLabels, runtimeContext *webhookdir.RuntimeContextWithLabels, assignment *webhookdir.FormationAssignment, reverseAssignment *webhookdir.FormationAssignment, targetType model.ResourceType, tenantContext *webhookdir.CustomerTenantContext, tenantID string) (*formationconstraint.GenerateFormationAssignmentNotificationOperationDetails, error)
    46  	PrepareDetailsForApplicationTenantMappingNotificationGeneration(operation model.FormationOperation, formationID string, formationTemplateID string, sourceApplicationTemplate *webhookdir.ApplicationTemplateWithLabels, sourceApplication *webhookdir.ApplicationWithLabels, targetApplicationTemplate *webhookdir.ApplicationTemplateWithLabels, targetApplication *webhookdir.ApplicationWithLabels, assignment *webhookdir.FormationAssignment, reverseAssignment *webhookdir.FormationAssignment, tenantContext *webhookdir.CustomerTenantContext, tenantID string) (*formationconstraint.GenerateFormationAssignmentNotificationOperationDetails, error)
    47  }
    48  
    49  // NotificationsGenerator is responsible for generation of notification requests
    50  type NotificationsGenerator struct {
    51  	applicationRepository         applicationRepository
    52  	applicationTemplateRepository applicationTemplateRepository
    53  	runtimeRepo                   runtimeRepository
    54  	runtimeContextRepo            runtimeContextRepository
    55  	labelRepository               labelRepository
    56  	webhookRepository             webhookRepository
    57  	webhookDataInputBuilder       databuilder.DataInputBuilder
    58  	notificationBuilder           notificationBuilder
    59  }
    60  
    61  // NewNotificationsGenerator returns an instance of NotificationsGenerator
    62  func NewNotificationsGenerator(
    63  	applicationRepository applicationRepository,
    64  	applicationTemplateRepository applicationTemplateRepository,
    65  	runtimeRepo runtimeRepository,
    66  	runtimeContextRepo runtimeContextRepository,
    67  	labelRepository labelRepository,
    68  	webhookRepository webhookRepository,
    69  	webhookDataInputBuilder databuilder.DataInputBuilder,
    70  	notificationBuilder notificationBuilder) *NotificationsGenerator {
    71  	return &NotificationsGenerator{
    72  		applicationRepository:         applicationRepository,
    73  		applicationTemplateRepository: applicationTemplateRepository,
    74  		runtimeRepo:                   runtimeRepo,
    75  		runtimeContextRepo:            runtimeContextRepo,
    76  		labelRepository:               labelRepository,
    77  		webhookRepository:             webhookRepository,
    78  		webhookDataInputBuilder:       webhookDataInputBuilder,
    79  		notificationBuilder:           notificationBuilder,
    80  	}
    81  }
    82  
    83  // GenerateNotificationsAboutRuntimeAndRuntimeContextForTheApplicationThatIsAssigned generates notification with target the application that is assigned about for each runtime and each runtimeContext that is part of the formation
    84  func (ns *NotificationsGenerator) GenerateNotificationsAboutRuntimeAndRuntimeContextForTheApplicationThatIsAssigned(ctx context.Context, tenant string, appID string, formation *model.Formation, operation model.FormationOperation, customerTenantContext *webhookdir.CustomerTenantContext) ([]*webhookclient.FormationAssignmentNotificationRequest, error) {
    85  	log.C(ctx).Infof("Generating %q notifications during %q operation about runtimes and runtime contexts in the same formation for application with ID: %q", model.WebhookTypeConfigurationChanged, operation, appID)
    86  	applicationWithLabels, appTemplateWithLabels, err := ns.webhookDataInputBuilder.PrepareApplicationAndAppTemplateWithLabels(ctx, tenant, appID)
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "while preparing application and application template with labels")
    89  	}
    90  
    91  	appTemplateID := ""
    92  	if appTemplateWithLabels != nil {
    93  		appTemplateID = appTemplateWithLabels.ID
    94  	}
    95  
    96  	webhook, err := formationassignment.GetWebhookForApplication(ctx, ns.webhookRepository, tenant, appID, appTemplateID, model.WebhookTypeConfigurationChanged)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	if webhook == nil {
   101  		return nil, nil
   102  	}
   103  
   104  	runtimesMapping, runtimesToRuntimeContextsMapping, err := ns.webhookDataInputBuilder.PrepareRuntimesAndRuntimeContextsMappingsInFormation(ctx, tenant, formation.Name)
   105  	if err != nil {
   106  		return nil, errors.Wrap(err, "while preparing runtime and runtime contexts mappings")
   107  	}
   108  
   109  	requests := make([]*webhookclient.FormationAssignmentNotificationRequest, 0, len(runtimesMapping))
   110  	for rtID := range runtimesMapping {
   111  		rtCtx := runtimesToRuntimeContextsMapping[rtID]
   112  		if rtCtx == nil {
   113  			log.C(ctx).Infof("There is no runtime context for runtime with ID: %q in formation %q. Will proceed without runtime context in the input for webhook with ID: %q", rtID, formation.Name, webhook.ID)
   114  		}
   115  		runtime := runtimesMapping[rtID]
   116  		if appTemplateWithLabels == nil {
   117  			log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template in the input for webhook with ID: %q", appID, webhook.ID)
   118  		}
   119  
   120  		details, err := ns.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration(
   121  			operation,
   122  			formation.ID,
   123  			formation.FormationTemplateID,
   124  			appTemplateWithLabels,
   125  			applicationWithLabels,
   126  			runtime,
   127  			rtCtx,
   128  			emptyFormationAssignment,
   129  			emptyFormationAssignment,
   130  			model.ApplicationResourceType,
   131  			customerTenantContext,
   132  			tenant)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  		req, err := ns.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, formation.FormationTemplateID, details, webhook)
   137  		if err != nil {
   138  			return nil, errors.Wrap(err, "Failed to build formation assignment notification request")
   139  		} else if req != nil {
   140  			requests = append(requests, req)
   141  		}
   142  	}
   143  	return requests, nil
   144  }
   145  
   146  // GenerateNotificationsForRuntimeAboutTheApplicationThatIsAssigned generates notification per runtime that is part of the formation with target the runtime and source the application on which `operation` is performed
   147  func (ns *NotificationsGenerator) GenerateNotificationsForRuntimeAboutTheApplicationThatIsAssigned(ctx context.Context, tenant string, appID string, formation *model.Formation, operation model.FormationOperation, customerTenantContext *webhookdir.CustomerTenantContext) ([]*webhookclient.FormationAssignmentNotificationRequest, error) {
   148  	log.C(ctx).Infof("Generating %q notifications during %q operation about application with ID: %q for all listening runtimes in the same formation", model.WebhookTypeConfigurationChanged, operation, appID)
   149  	applicationWithLabels, appTemplateWithLabels, err := ns.webhookDataInputBuilder.PrepareApplicationAndAppTemplateWithLabels(ctx, tenant, appID)
   150  	if err != nil {
   151  		return nil, errors.Wrap(err, "while preparing application and application template with labels")
   152  	}
   153  
   154  	webhooks, err := ns.webhookRepository.ListByReferenceObjectTypeAndWebhookType(ctx, tenant, model.WebhookTypeConfigurationChanged, model.RuntimeWebhookReference)
   155  	if err != nil {
   156  		return nil, errors.Wrap(err, "when listing configuration changed webhooks for runtimes")
   157  	}
   158  
   159  	if len(webhooks) == 0 {
   160  		log.C(ctx).Infof("There are no runtimes listening for %q notifications in tenant %q for formation with name: %q", model.WebhookTypeConfigurationChanged, tenant, formation.Name)
   161  		return nil, nil
   162  	}
   163  
   164  	listeningRuntimeIDs := make([]string, 0, len(webhooks))
   165  	for _, wh := range webhooks {
   166  		listeningRuntimeIDs = append(listeningRuntimeIDs, wh.ObjectID)
   167  	}
   168  
   169  	log.C(ctx).Infof("There is/are: %d runtimes listening for %q notifications in tenant %q for formation with name: %q", len(listeningRuntimeIDs), model.WebhookTypeConfigurationChanged, tenant, formation.Name)
   170  
   171  	runtimesInFormationMappings, runtimeIDToRuntimeContextInFormationMappings, err := ns.webhookDataInputBuilder.PrepareRuntimesAndRuntimeContextsMappingsInFormation(ctx, tenant, formation.Name)
   172  	if err != nil {
   173  		return nil, errors.Wrap(err, "while preparing runtime and runtime contexts mappings")
   174  	}
   175  
   176  	runtimeIDsToBeNotified := make(map[string]bool, len(listeningRuntimeIDs))
   177  	for i := range listeningRuntimeIDs {
   178  		if runtimesInFormationMappings[listeningRuntimeIDs[i]] != nil {
   179  			runtimeIDsToBeNotified[listeningRuntimeIDs[i]] = true
   180  		}
   181  	}
   182  
   183  	webhooksToCall := make(map[string]*model.Webhook, len(runtimeIDsToBeNotified))
   184  	for i := range webhooks {
   185  		if runtimeIDsToBeNotified[webhooks[i].ObjectID] {
   186  			webhooksToCall[webhooks[i].ObjectID] = webhooks[i]
   187  		}
   188  	}
   189  
   190  	requests := make([]*webhookclient.FormationAssignmentNotificationRequest, 0, len(runtimeIDsToBeNotified))
   191  	for rtID := range runtimeIDsToBeNotified {
   192  		rtCtx := runtimeIDToRuntimeContextInFormationMappings[rtID]
   193  		if rtCtx == nil {
   194  			log.C(ctx).Infof("There is no runtime context for runtime with ID: %q in formation %q. Will proceed without runtime context in the input for webhook with ID: %q", rtID, formation.Name, webhooksToCall[rtID].ID)
   195  		}
   196  		runtime := runtimesInFormationMappings[rtID]
   197  		if appTemplateWithLabels == nil {
   198  			log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template in the input for webhook with ID: %q", appID, webhooksToCall[rtID].ID)
   199  		}
   200  
   201  		details, err := ns.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration(
   202  			operation,
   203  			formation.ID,
   204  			formation.FormationTemplateID,
   205  			appTemplateWithLabels,
   206  			applicationWithLabels,
   207  			runtime,
   208  			rtCtx,
   209  			emptyFormationAssignment,
   210  			emptyFormationAssignment,
   211  			model.RuntimeResourceType,
   212  			customerTenantContext,
   213  			tenant)
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  
   218  		req, err := ns.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, formation.FormationTemplateID, details, webhooksToCall[runtime.ID])
   219  		if err != nil {
   220  			return nil, errors.Wrap(err, "Failed to build formation assignment notification request")
   221  		} else if req != nil {
   222  			requests = append(requests, req)
   223  		}
   224  	}
   225  
   226  	return requests, nil
   227  }
   228  
   229  // GenerateNotificationsForApplicationsAboutTheApplicationThatIsAssigned generates notification per application that is part of the formation with target the application and source the application on which `operation` is performed
   230  func (ns *NotificationsGenerator) GenerateNotificationsForApplicationsAboutTheApplicationThatIsAssigned(ctx context.Context, tenant string, appID string, formation *model.Formation, operation model.FormationOperation, customerTenantContext *webhookdir.CustomerTenantContext) ([]*webhookclient.FormationAssignmentNotificationRequest, error) {
   231  	log.C(ctx).Infof("Generating %q notifications during %q operation for application with ID: %q", model.WebhookTypeApplicationTenantMapping, operation, appID)
   232  	applicationWithLabels, appTemplateWithLabels, err := ns.webhookDataInputBuilder.PrepareApplicationAndAppTemplateWithLabels(ctx, tenant, appID)
   233  	if err != nil {
   234  		return nil, errors.Wrap(err, "while preparing application and application template with labels")
   235  	}
   236  
   237  	webhooks, err := ns.webhookRepository.ListByReferenceObjectTypesAndWebhookType(ctx, tenant, model.WebhookTypeApplicationTenantMapping, []model.WebhookReferenceObjectType{model.ApplicationWebhookReference, model.ApplicationTemplateWebhookReference})
   238  	if err != nil {
   239  		return nil, errors.Wrapf(err, "when listing %q webhooks for applications and their application templates", model.WebhookTypeApplicationTenantMapping)
   240  	}
   241  
   242  	resourceIDToWebhookMapping := make(map[string]*model.Webhook, len(webhooks))
   243  	for _, webhook := range webhooks {
   244  		resourceIDToWebhookMapping[webhook.ObjectID] = webhook
   245  	}
   246  
   247  	// list applications that either have WebhookTypeApplicationTenantMapping webhook or their applicationTemplate has WebhookTypeApplicationTenantMapping webhook
   248  	listeningApps, err := ns.applicationRepository.ListListeningApplications(ctx, tenant, model.WebhookTypeApplicationTenantMapping)
   249  	if err != nil {
   250  		return nil, errors.Wrap(err, "while listing listening applications")
   251  	}
   252  
   253  	if len(listeningApps) == 0 {
   254  		log.C(ctx).Infof("There are no applications listening for %q notifications in tenant %q for formation with name: %q", tenant, model.WebhookTypeApplicationTenantMapping, formation.Name)
   255  		return nil, nil
   256  	}
   257  
   258  	listeningAppsByID := make(map[string]*model.Application, len(listeningApps))
   259  	for i := range listeningApps {
   260  		listeningAppsByID[listeningApps[i].ID] = listeningApps[i]
   261  	}
   262  
   263  	appIDToWebhookMapping := make(map[string]*model.Webhook)
   264  	for _, app := range listeningApps {
   265  		// if webhook for the application exists use it
   266  		// if webhook for the application does not exist use the webhook for its application template
   267  		if resourceIDToWebhookMapping[app.ID] != nil {
   268  			appIDToWebhookMapping[app.ID] = resourceIDToWebhookMapping[app.ID]
   269  		} else {
   270  			appIDToWebhookMapping[app.ID] = resourceIDToWebhookMapping[str.PtrStrToStr(app.ApplicationTemplateID)]
   271  		}
   272  	}
   273  
   274  	log.C(ctx).Infof("There are %d applications listening for %q notifications in tenant %q for formation with name: %q", len(listeningAppsByID), model.WebhookTypeApplicationTenantMapping, tenant, formation.Name)
   275  
   276  	applicationsInFormationMapping, appTemplatesMapping, err := ns.webhookDataInputBuilder.PrepareApplicationMappingsInFormation(ctx, tenant, formation.Name)
   277  	if err != nil {
   278  		return nil, errors.Wrap(err, "while preparing application and application template mappings")
   279  	}
   280  
   281  	requests := make([]*webhookclient.FormationAssignmentNotificationRequest, 0, len(listeningAppsByID))
   282  	if listeningAppsByID[appID] != nil {
   283  		log.C(ctx).Infof("The application with ID: %q that is being %q is also listening for %q notifications. Will create notifications about all other apps in the formation...", appID, operation, model.WebhookTypeApplicationTenantMapping)
   284  		webhook := appIDToWebhookMapping[appID]
   285  
   286  		appsInFormationCountExcludingAppCurrentlyAssigned := len(applicationsInFormationMapping)
   287  		if operation == model.AssignFormation {
   288  			appsInFormationCountExcludingAppCurrentlyAssigned -= 1
   289  		}
   290  
   291  		log.C(ctx).Infof("The number of other application(s) in formation %q is/are: %d. Notification(s) will be sent about them to application with ID: %q that is being %q.", formation.Name, appsInFormationCountExcludingAppCurrentlyAssigned, appID, operation)
   292  
   293  		for _, sourceApp := range applicationsInFormationMapping {
   294  			if sourceApp.ID == appID {
   295  				continue // Do not notify about itself
   296  			}
   297  			var appTemplate *webhookdir.ApplicationTemplateWithLabels
   298  			if sourceApp.ApplicationTemplateID != nil {
   299  				appTemplate = appTemplatesMapping[*sourceApp.ApplicationTemplateID]
   300  			} else {
   301  				log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template for the source application in the input for webhook with ID: %q", sourceApp.ID, webhook.ID)
   302  			}
   303  			if appTemplateWithLabels == nil {
   304  				log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template for the target application in the input for webhook with ID: %q", appID, webhook.ID)
   305  			}
   306  
   307  			details, err := ns.notificationBuilder.PrepareDetailsForApplicationTenantMappingNotificationGeneration(
   308  				operation,
   309  				formation.ID,
   310  				formation.FormationTemplateID,
   311  				appTemplate,
   312  				sourceApp,
   313  				appTemplateWithLabels,
   314  				applicationWithLabels,
   315  				emptyFormationAssignment,
   316  				emptyFormationAssignment,
   317  				customerTenantContext,
   318  				tenant,
   319  			)
   320  			if err != nil {
   321  				return nil, err
   322  			}
   323  
   324  			req, err := ns.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, formation.FormationTemplateID, details, webhook)
   325  			if err != nil {
   326  				return nil, errors.Wrap(err, "Failed to build formation assignment notification request")
   327  			} else if req != nil {
   328  				requests = append(requests, req)
   329  			}
   330  		}
   331  
   332  		delete(listeningAppsByID, appID)
   333  	}
   334  
   335  	listeningApplicationsInFormationIds := make([]string, 0, len(listeningAppsByID))
   336  	for id := range listeningAppsByID {
   337  		if applicationsInFormationMapping[id] != nil {
   338  			listeningApplicationsInFormationIds = append(listeningApplicationsInFormationIds, id)
   339  		}
   340  	}
   341  
   342  	for _, appID := range listeningApplicationsInFormationIds {
   343  		targetApp := applicationsInFormationMapping[appID]
   344  		var targetAppTemplate *webhookdir.ApplicationTemplateWithLabels
   345  		if targetApp.ApplicationTemplateID != nil {
   346  			targetAppTemplate = appTemplatesMapping[*targetApp.ApplicationTemplateID]
   347  		} else {
   348  			log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template for the target application in the input for webhook with ID: %q", appID, appIDToWebhookMapping[appID].ID)
   349  		}
   350  		if appTemplateWithLabels == nil {
   351  			log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template for the source application in the input for webhook with ID: %q", appID, appIDToWebhookMapping[appID].ID)
   352  		}
   353  
   354  		details, err := ns.notificationBuilder.PrepareDetailsForApplicationTenantMappingNotificationGeneration(
   355  			operation,
   356  			formation.ID,
   357  			formation.FormationTemplateID,
   358  			appTemplateWithLabels,
   359  			applicationWithLabels,
   360  			targetAppTemplate,
   361  			targetApp,
   362  			emptyFormationAssignment,
   363  			emptyFormationAssignment,
   364  			customerTenantContext,
   365  			tenant,
   366  		)
   367  		if err != nil {
   368  			return nil, err
   369  		}
   370  
   371  		req, err := ns.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, formation.FormationTemplateID, details, appIDToWebhookMapping[appID])
   372  		if err != nil {
   373  			return nil, errors.Wrap(err, "Failed to build formation assignment notification request")
   374  		} else if req != nil {
   375  			requests = append(requests, req)
   376  		}
   377  	}
   378  
   379  	log.C(ctx).Infof("Total number of %q notifications for application with ID: %q that is being %q is/are: %d", model.WebhookTypeApplicationTenantMapping, appID, operation, len(requests))
   380  
   381  	return requests, nil
   382  }
   383  
   384  // GenerateNotificationsForApplicationsAboutTheRuntimeContextThatIsAssigned generates notification per application that is part of the formation with target the application and source the runtime context on which `operation` is performed
   385  func (ns *NotificationsGenerator) GenerateNotificationsForApplicationsAboutTheRuntimeContextThatIsAssigned(ctx context.Context, tenant, runtimeCtxID string, formation *model.Formation, operation model.FormationOperation, customerTenantContext *webhookdir.CustomerTenantContext) ([]*webhookclient.FormationAssignmentNotificationRequest, error) {
   386  	log.C(ctx).Infof("Generating %q notifications about runtime context with ID: %q for all interested applications in the formation", operation, runtimeCtxID)
   387  	runtimeCtxWithLabels, err := ns.webhookDataInputBuilder.PrepareRuntimeContextWithLabels(ctx, tenant, runtimeCtxID)
   388  	if err != nil {
   389  		return nil, errors.Wrap(err, "while preparing runtime context with labels")
   390  	}
   391  
   392  	requests, err := ns.GenerateNotificationsForApplicationsAboutTheRuntimeThatIsAssigned(ctx, tenant, runtimeCtxWithLabels.RuntimeID, formation, operation, customerTenantContext)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	for _, request := range requests {
   397  		request.Object.(*webhookdir.FormationConfigurationChangeInput).RuntimeContext = runtimeCtxWithLabels
   398  	}
   399  	return requests, nil
   400  }
   401  
   402  // GenerateNotificationsForApplicationsAboutTheRuntimeThatIsAssigned generates notification per application that is part of the formation with target the application and source the runtime on which `operation` is performed
   403  func (ns *NotificationsGenerator) GenerateNotificationsForApplicationsAboutTheRuntimeThatIsAssigned(ctx context.Context, tenant, runtimeID string, formation *model.Formation, operation model.FormationOperation, customerTenantContext *webhookdir.CustomerTenantContext) ([]*webhookclient.FormationAssignmentNotificationRequest, error) {
   404  	log.C(ctx).Infof("Generating %q notifications during %q operation about runtime with ID: %q for all interested applications in the formation", model.WebhookTypeConfigurationChanged, operation, runtimeID)
   405  	runtimeWithLabels, err := ns.webhookDataInputBuilder.PrepareRuntimeWithLabels(ctx, tenant, runtimeID)
   406  	if err != nil {
   407  		return nil, errors.Wrap(err, "while preparing runtime with labels")
   408  	}
   409  
   410  	webhooks, err := ns.webhookRepository.ListByReferenceObjectTypesAndWebhookType(ctx, tenant, model.WebhookTypeConfigurationChanged, []model.WebhookReferenceObjectType{model.ApplicationWebhookReference, model.ApplicationTemplateWebhookReference})
   411  	if err != nil {
   412  		return nil, errors.Wrapf(err, "when listing %q webhooks for applications and their application templates", model.WebhookTypeConfigurationChanged)
   413  	}
   414  
   415  	resourceIDToWebhookMapping := make(map[string]*model.Webhook, len(webhooks))
   416  	for _, webhook := range webhooks {
   417  		resourceIDToWebhookMapping[webhook.ObjectID] = webhook
   418  	}
   419  
   420  	listeningApps, err := ns.applicationRepository.ListListeningApplications(ctx, tenant, model.WebhookTypeConfigurationChanged)
   421  	if err != nil {
   422  		return nil, errors.Wrap(err, "while listing listening applications")
   423  	}
   424  
   425  	if len(listeningApps) == 0 {
   426  		log.C(ctx).Infof("There are no applications listening for %q notifications in tenant %q for formation with name: %q", tenant, model.WebhookTypeConfigurationChanged, formation.Name)
   427  		return nil, nil
   428  	}
   429  
   430  	listeningApplicationIDs := make([]string, 0, len(listeningApps))
   431  	for _, app := range listeningApps {
   432  		listeningApplicationIDs = append(listeningApplicationIDs, app.ID)
   433  	}
   434  
   435  	appIDToWebhookMapping := make(map[string]*model.Webhook)
   436  	for _, app := range listeningApps {
   437  		// if webhook for the application exists use it
   438  		// if webhook for the application does not exist use the webhook for its application template
   439  		if resourceIDToWebhookMapping[app.ID] != nil {
   440  			appIDToWebhookMapping[app.ID] = resourceIDToWebhookMapping[app.ID]
   441  		} else {
   442  			appIDToWebhookMapping[app.ID] = resourceIDToWebhookMapping[str.PtrStrToStr(app.ApplicationTemplateID)]
   443  		}
   444  	}
   445  
   446  	log.C(ctx).Infof("There are %d applications listening for %q notifications in tenant %q for formation with ID: %q", len(listeningApplicationIDs), model.WebhookTypeConfigurationChanged, tenant, formation.Name)
   447  
   448  	applicationsInFormationMapping, appTemplatesMapping, err := ns.webhookDataInputBuilder.PrepareApplicationMappingsInFormation(ctx, tenant, formation.Name)
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  
   453  	listeningApplicationsInFormationIds := make([]string, 0, len(listeningApps))
   454  	for i := range listeningApps {
   455  		if applicationsInFormationMapping[listeningApps[i].ID] != nil {
   456  			listeningApplicationsInFormationIds = append(listeningApplicationsInFormationIds, listeningApps[i].ID)
   457  		}
   458  	}
   459  
   460  	requests := make([]*webhookclient.FormationAssignmentNotificationRequest, 0, len(applicationsInFormationMapping))
   461  	for _, appID := range listeningApplicationsInFormationIds {
   462  		app := applicationsInFormationMapping[appID]
   463  		var appTemplate *webhookdir.ApplicationTemplateWithLabels
   464  		if app.ApplicationTemplateID != nil {
   465  			appTemplate = appTemplatesMapping[*app.ApplicationTemplateID]
   466  		} else {
   467  			log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template in the input for webhook with ID: %q", appID, appIDToWebhookMapping[appID].ID)
   468  		}
   469  
   470  		details, err := ns.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration(
   471  			operation,
   472  			formation.ID,
   473  			formation.FormationTemplateID,
   474  			appTemplate,
   475  			app,
   476  			runtimeWithLabels,
   477  			nil,
   478  			emptyFormationAssignment,
   479  			emptyFormationAssignment,
   480  			model.ApplicationResourceType,
   481  			customerTenantContext,
   482  			tenant,
   483  		)
   484  		if err != nil {
   485  			return nil, err
   486  		}
   487  
   488  		req, err := ns.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, formation.FormationTemplateID, details, appIDToWebhookMapping[appID])
   489  		if err != nil {
   490  			return nil, errors.Wrap(err, "Failed to build formation assignment notification request")
   491  		} else if req != nil {
   492  			requests = append(requests, req)
   493  		}
   494  	}
   495  	return requests, nil
   496  }
   497  
   498  // GenerateNotificationsAboutApplicationsForTheRuntimeContextThatIsAssigned generates notification per runtime context that is part of the formation with target the runtime context and source the application on which `operation` is performed
   499  func (ns *NotificationsGenerator) GenerateNotificationsAboutApplicationsForTheRuntimeContextThatIsAssigned(ctx context.Context, tenant, runtimeCtxID string, formation *model.Formation, operation model.FormationOperation, customerTenantContext *webhookdir.CustomerTenantContext) ([]*webhookclient.FormationAssignmentNotificationRequest, error) {
   500  	log.C(ctx).Infof("Generating %q notifications during %q operation for runtime context with ID: %q", model.WebhookTypeConfigurationChanged, operation, runtimeCtxID)
   501  	runtimeCtxWithLabels, err := ns.webhookDataInputBuilder.PrepareRuntimeContextWithLabels(ctx, tenant, runtimeCtxID)
   502  	if err != nil {
   503  		return nil, errors.Wrap(err, "while preparing runtime context with labels")
   504  	}
   505  
   506  	runtimeID := runtimeCtxWithLabels.RuntimeID
   507  
   508  	webhook, err := ns.webhookRepository.GetByIDAndWebhookType(ctx, tenant, runtimeID, model.RuntimeWebhookReference, model.WebhookTypeConfigurationChanged)
   509  	if err != nil {
   510  		if apperrors.IsNotFoundError(err) {
   511  			log.C(ctx).Infof("There is no %q webhook for runtime with ID: %q. No notifications will be generated.", model.WebhookTypeConfigurationChanged, runtimeID)
   512  			return nil, nil
   513  		}
   514  		return nil, errors.Wrapf(err, "while listing configuration changed webhooks for runtime %s", runtimeID)
   515  	}
   516  
   517  	runtimeWithLabels, err := ns.webhookDataInputBuilder.PrepareRuntimeWithLabels(ctx, tenant, runtimeID)
   518  	if err != nil {
   519  		return nil, errors.Wrap(err, "while preparing runtime with labels")
   520  	}
   521  
   522  	applicationMapping, applicationTemplatesMapping, err := ns.webhookDataInputBuilder.PrepareApplicationMappingsInFormation(ctx, tenant, formation.Name)
   523  	if err != nil {
   524  		return nil, err
   525  	}
   526  
   527  	requests := make([]*webhookclient.FormationAssignmentNotificationRequest, 0, len(applicationMapping))
   528  	for _, app := range applicationMapping {
   529  		var appTemplate *webhookdir.ApplicationTemplateWithLabels
   530  		if app.ApplicationTemplateID != nil {
   531  			appTemplate = applicationTemplatesMapping[*app.ApplicationTemplateID]
   532  		} else {
   533  			log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template in the input for webhook with ID: %q", app.ID, webhook.ID)
   534  		}
   535  
   536  		details, err := ns.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration(
   537  			operation,
   538  			formation.ID,
   539  			formation.FormationTemplateID,
   540  			appTemplate,
   541  			app,
   542  			runtimeWithLabels,
   543  			runtimeCtxWithLabels,
   544  			emptyFormationAssignment,
   545  			emptyFormationAssignment,
   546  			model.RuntimeContextResourceType,
   547  			customerTenantContext,
   548  			tenant,
   549  		)
   550  		if err != nil {
   551  			return nil, err
   552  		}
   553  
   554  		req, err := ns.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, formation.FormationTemplateID, details, webhook)
   555  		if err != nil {
   556  			return nil, errors.Wrap(err, "Failed to build formation assignment notification request")
   557  		} else if req != nil {
   558  			requests = append(requests, req)
   559  		}
   560  	}
   561  
   562  	return requests, nil
   563  }
   564  
   565  // GenerateNotificationsAboutApplicationsForTheRuntimeThatIsAssigned generates notification per runtime that is part of the formation with target the runtime and source the application on which `operation` is performed
   566  func (ns *NotificationsGenerator) GenerateNotificationsAboutApplicationsForTheRuntimeThatIsAssigned(ctx context.Context, tenant, runtimeID string, formation *model.Formation, operation model.FormationOperation, customerTenantContext *webhookdir.CustomerTenantContext) ([]*webhookclient.FormationAssignmentNotificationRequest, error) {
   567  	log.C(ctx).Infof("Generating %q notifications during %q operation about all applications in the formation for runtime with ID: %q", model.WebhookTypeConfigurationChanged, operation, runtimeID)
   568  	runtimeWithLabels, err := ns.webhookDataInputBuilder.PrepareRuntimeWithLabels(ctx, tenant, runtimeID)
   569  	if err != nil {
   570  		return nil, errors.Wrap(err, "while preparing runtime with labels")
   571  	}
   572  
   573  	webhook, err := ns.webhookRepository.GetByIDAndWebhookType(ctx, tenant, runtimeID, model.RuntimeWebhookReference, model.WebhookTypeConfigurationChanged)
   574  	if err != nil {
   575  		if apperrors.IsNotFoundError(err) {
   576  			log.C(ctx).Infof("There is no %q webhook for runtime with ID: %q. No notifications will be generated.", model.WebhookTypeConfigurationChanged, runtimeID)
   577  			return nil, nil
   578  		}
   579  		return nil, errors.Wrapf(err, "while listing configuration changed webhooks for runtime %s", runtimeID)
   580  	}
   581  
   582  	applicationMapping, applicationTemplatesMapping, err := ns.webhookDataInputBuilder.PrepareApplicationMappingsInFormation(ctx, tenant, formation.Name)
   583  	if err != nil {
   584  		return nil, err
   585  	}
   586  
   587  	requests := make([]*webhookclient.FormationAssignmentNotificationRequest, 0, len(applicationMapping))
   588  	for _, app := range applicationMapping {
   589  		var appTemplate *webhookdir.ApplicationTemplateWithLabels
   590  		if app.ApplicationTemplateID != nil {
   591  			appTemplate = applicationTemplatesMapping[*app.ApplicationTemplateID]
   592  		} else {
   593  			log.C(ctx).Infof("Application with ID: %q has no application template. Will proceed without application template in the input for webhook with ID: %q", app.ID, webhook.ID)
   594  		}
   595  
   596  		details, err := ns.notificationBuilder.PrepareDetailsForConfigurationChangeNotificationGeneration(
   597  			operation,
   598  			formation.ID,
   599  			formation.FormationTemplateID,
   600  			appTemplate,
   601  			app,
   602  			runtimeWithLabels,
   603  			nil,
   604  			emptyFormationAssignment,
   605  			emptyFormationAssignment,
   606  			model.RuntimeResourceType,
   607  			customerTenantContext,
   608  			tenant,
   609  		)
   610  		if err != nil {
   611  			return nil, err
   612  		}
   613  
   614  		req, err := ns.notificationBuilder.BuildFormationAssignmentNotificationRequest(ctx, formation.FormationTemplateID, details, webhook)
   615  		if err != nil {
   616  			return nil, errors.Wrap(err, "Failed to build formation assignment notification request")
   617  		} else if req != nil {
   618  			requests = append(requests, req)
   619  		}
   620  	}
   621  
   622  	return requests, nil
   623  }
   624  
   625  // GenerateFormationLifecycleNotifications generates formation notifications for the provided webhooks
   626  func (ns *NotificationsGenerator) GenerateFormationLifecycleNotifications(ctx context.Context, formationTemplateWebhooks []*model.Webhook, tenantID string, formation *model.Formation, formationTemplateName, formationTemplateID string, formationOperation model.FormationOperation, customerTenantContext *webhookdir.CustomerTenantContext) ([]*webhookclient.FormationNotificationRequest, error) {
   627  	details := &formationconstraint.GenerateFormationNotificationOperationDetails{
   628  		Operation:             formationOperation,
   629  		FormationID:           formation.ID,
   630  		FormationName:         formation.Name,
   631  		FormationType:         formationTemplateName,
   632  		FormationTemplateID:   formationTemplateID,
   633  		TenantID:              tenantID,
   634  		CustomerTenantContext: customerTenantContext,
   635  	}
   636  
   637  	reqs, err := ns.notificationBuilder.BuildFormationNotificationRequests(ctx, details, formation, formationTemplateWebhooks)
   638  	if err != nil {
   639  		log.C(ctx).Errorf("Failed to build formation notification requests due to: %v", err)
   640  	}
   641  
   642  	return reqs, nil
   643  }