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  }