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

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