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

     1  package datainputbuilder
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"strconv"
     7  
     8  	"github.com/kyma-incubator/compass/components/director/pkg/log"
     9  
    10  	"github.com/kyma-incubator/compass/components/director/pkg/webhook"
    11  
    12  	"github.com/kyma-incubator/compass/components/director/internal/model"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  //go:generate mockery --exported --name=applicationRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    17  type applicationRepository interface {
    18  	GetByID(ctx context.Context, tenant, id string) (*model.Application, error)
    19  	ListByScenariosNoPaging(ctx context.Context, tenant string, scenarios []string) ([]*model.Application, error)
    20  }
    21  
    22  //go:generate mockery --exported --name=applicationTemplateRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    23  type applicationTemplateRepository interface {
    24  	Get(ctx context.Context, id string) (*model.ApplicationTemplate, error)
    25  	ListByIDs(ctx context.Context, ids []string) ([]*model.ApplicationTemplate, error)
    26  }
    27  
    28  //go:generate mockery --exported --name=runtimeRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    29  type runtimeRepository interface {
    30  	GetByID(ctx context.Context, tenant, id string) (*model.Runtime, error)
    31  	ListByScenarios(ctx context.Context, tenant string, scenarios []string) ([]*model.Runtime, error)
    32  	ListByIDs(ctx context.Context, tenant string, ids []string) ([]*model.Runtime, error)
    33  }
    34  
    35  //go:generate mockery --exported --name=runtimeContextRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    36  type runtimeContextRepository interface {
    37  	GetByID(ctx context.Context, tenant, id string) (*model.RuntimeContext, error)
    38  	GetByRuntimeID(ctx context.Context, tenant, runtimeID string) (*model.RuntimeContext, error)
    39  	ListByScenarios(ctx context.Context, tenant string, scenarios []string) ([]*model.RuntimeContext, error)
    40  }
    41  
    42  //go:generate mockery --exported --name=labelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    43  type labelRepository interface {
    44  	ListForObject(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error)
    45  	ListForObjectIDs(ctx context.Context, tenant string, objectType model.LabelableObject, objectIDs []string) (map[string]map[string]interface{}, error)
    46  }
    47  
    48  // DataInputBuilder is responsible to prepare and build different entity data needed for a webhook input
    49  //go:generate mockery --exported --name=DataInputBuilder --output=automock --outpkg=automock --case=underscore --disable-version-string
    50  type DataInputBuilder interface {
    51  	PrepareApplicationAndAppTemplateWithLabels(ctx context.Context, tenant, appID string) (*webhook.ApplicationWithLabels, *webhook.ApplicationTemplateWithLabels, error)
    52  	PrepareRuntimeWithLabels(ctx context.Context, tenant, runtimeID string) (*webhook.RuntimeWithLabels, error)
    53  	PrepareRuntimeContextWithLabels(ctx context.Context, tenant, runtimeCtxID string) (*webhook.RuntimeContextWithLabels, error)
    54  	PrepareRuntimeAndRuntimeContextWithLabels(ctx context.Context, tenant, runtimeID string) (*webhook.RuntimeWithLabels, *webhook.RuntimeContextWithLabels, error)
    55  	PrepareRuntimesAndRuntimeContextsMappingsInFormation(ctx context.Context, tenant string, scenario string) (map[string]*webhook.RuntimeWithLabels, map[string]*webhook.RuntimeContextWithLabels, error)
    56  	PrepareApplicationMappingsInFormation(ctx context.Context, tenant string, scenario string) (map[string]*webhook.ApplicationWithLabels, map[string]*webhook.ApplicationTemplateWithLabels, error)
    57  }
    58  
    59  // WebhookDataInputBuilder take cares to get and build different webhook input data such as application, runtime, runtime contexts
    60  type WebhookDataInputBuilder struct {
    61  	applicationRepository         applicationRepository
    62  	applicationTemplateRepository applicationTemplateRepository
    63  	runtimeRepo                   runtimeRepository
    64  	runtimeContextRepo            runtimeContextRepository
    65  	labelRepository               labelRepository
    66  }
    67  
    68  // NewWebhookDataInputBuilder creates a WebhookDataInputBuilder
    69  func NewWebhookDataInputBuilder(applicationRepository applicationRepository, applicationTemplateRepository applicationTemplateRepository, runtimeRepo runtimeRepository, runtimeContextRepo runtimeContextRepository, labelRepository labelRepository) *WebhookDataInputBuilder {
    70  	return &WebhookDataInputBuilder{
    71  		applicationRepository:         applicationRepository,
    72  		applicationTemplateRepository: applicationTemplateRepository,
    73  		runtimeRepo:                   runtimeRepo,
    74  		runtimeContextRepo:            runtimeContextRepo,
    75  		labelRepository:               labelRepository,
    76  	}
    77  }
    78  
    79  // PrepareApplicationAndAppTemplateWithLabels construct ApplicationWithLabels and ApplicationTemplateWithLabels based on tenant and ID
    80  func (b *WebhookDataInputBuilder) PrepareApplicationAndAppTemplateWithLabels(ctx context.Context, tenant, appID string) (*webhook.ApplicationWithLabels, *webhook.ApplicationTemplateWithLabels, error) {
    81  	application, err := b.applicationRepository.GetByID(ctx, tenant, appID)
    82  	if err != nil {
    83  		return nil, nil, errors.Wrapf(err, "while getting application by ID: %q", appID)
    84  	}
    85  	applicationLabels, err := b.getLabelsForObject(ctx, tenant, appID, model.ApplicationLabelableObject)
    86  	if err != nil {
    87  		return nil, nil, err
    88  	}
    89  	applicationWithLabels := &webhook.ApplicationWithLabels{
    90  		Application: application,
    91  		Labels:      applicationLabels,
    92  	}
    93  
    94  	var appTemplateWithLabels *webhook.ApplicationTemplateWithLabels
    95  	if application.ApplicationTemplateID != nil {
    96  		appTemplate, err := b.applicationTemplateRepository.Get(ctx, *application.ApplicationTemplateID)
    97  		if err != nil {
    98  			return nil, nil, errors.Wrapf(err, "while getting application template with ID: %q", *application.ApplicationTemplateID)
    99  		}
   100  		applicationTemplateLabels, err := b.getLabelsForObject(ctx, tenant, appTemplate.ID, model.AppTemplateLabelableObject)
   101  		if err != nil {
   102  			return nil, nil, err
   103  		}
   104  		appTemplateWithLabels = &webhook.ApplicationTemplateWithLabels{
   105  			ApplicationTemplate: appTemplate,
   106  			Labels:              applicationTemplateLabels,
   107  		}
   108  	}
   109  	return applicationWithLabels, appTemplateWithLabels, nil
   110  }
   111  
   112  // PrepareRuntimeWithLabels construct RuntimeWithLabels based on tenant and runtimeID
   113  func (b *WebhookDataInputBuilder) PrepareRuntimeWithLabels(ctx context.Context, tenant, runtimeID string) (*webhook.RuntimeWithLabels, error) {
   114  	runtime, err := b.runtimeRepo.GetByID(ctx, tenant, runtimeID)
   115  	if err != nil {
   116  		return nil, errors.Wrapf(err, "while getting runtime by ID: %q", runtimeID)
   117  	}
   118  
   119  	runtimeLabels, err := b.getLabelsForObject(ctx, tenant, runtimeID, model.RuntimeLabelableObject)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	runtimeWithLabels := &webhook.RuntimeWithLabels{
   125  		Runtime: runtime,
   126  		Labels:  runtimeLabels,
   127  	}
   128  
   129  	return runtimeWithLabels, nil
   130  }
   131  
   132  // PrepareRuntimeContextWithLabels construct RuntimeContextWithLabels based on tenant and runtimeCtxID
   133  func (b *WebhookDataInputBuilder) PrepareRuntimeContextWithLabels(ctx context.Context, tenant, runtimeCtxID string) (*webhook.RuntimeContextWithLabels, error) {
   134  	runtimeCtx, err := b.runtimeContextRepo.GetByID(ctx, tenant, runtimeCtxID)
   135  	if err != nil {
   136  		return nil, errors.Wrapf(err, "while getting runtime context by ID: %q", runtimeCtxID)
   137  	}
   138  
   139  	runtimeCtxLabels, err := b.getLabelsForObject(ctx, tenant, runtimeCtx.ID, model.RuntimeContextLabelableObject)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	runtimeContextWithLabels := &webhook.RuntimeContextWithLabels{
   145  		RuntimeContext: runtimeCtx,
   146  		Labels:         runtimeCtxLabels,
   147  	}
   148  
   149  	return runtimeContextWithLabels, nil
   150  }
   151  
   152  // PrepareRuntimeAndRuntimeContextWithLabels construct RuntimeWithLabels and RuntimeContextWithLabels based on tenant and runtimeID
   153  func (b *WebhookDataInputBuilder) PrepareRuntimeAndRuntimeContextWithLabels(ctx context.Context, tenant, runtimeID string) (*webhook.RuntimeWithLabels, *webhook.RuntimeContextWithLabels, error) {
   154  	runtimeWithLabels, err := b.PrepareRuntimeWithLabels(ctx, tenant, runtimeID)
   155  	if err != nil {
   156  		return nil, nil, err
   157  	}
   158  
   159  	runtimeCtx, err := b.runtimeContextRepo.GetByRuntimeID(ctx, tenant, runtimeID)
   160  	if err != nil {
   161  		return nil, nil, errors.Wrapf(err, "while getting runtime context for runtime with ID: %q", runtimeID)
   162  	}
   163  
   164  	runtimeCtxLabels, err := b.getLabelsForObject(ctx, tenant, runtimeCtx.ID, model.RuntimeContextLabelableObject)
   165  	if err != nil {
   166  		return nil, nil, err
   167  	}
   168  
   169  	runtimeContextWithLabels := &webhook.RuntimeContextWithLabels{
   170  		RuntimeContext: runtimeCtx,
   171  		Labels:         runtimeCtxLabels,
   172  	}
   173  
   174  	return runtimeWithLabels, runtimeContextWithLabels, nil
   175  }
   176  
   177  // PrepareRuntimesAndRuntimeContextsMappingsInFormation constructs:
   178  // map from runtime ID to RuntimeWithLabels with entries for each runtime part of the formation and for each runtime whose child runtime context is part of the formation
   179  // map from parent runtime ID to RuntimeContextWithLabels with entries for all runtime contexts part of the formation.
   180  func (b *WebhookDataInputBuilder) PrepareRuntimesAndRuntimeContextsMappingsInFormation(ctx context.Context, tenant string, scenario string) (map[string]*webhook.RuntimeWithLabels, map[string]*webhook.RuntimeContextWithLabels, error) {
   181  	runtimesInFormation, err := b.runtimeRepo.ListByScenarios(ctx, tenant, []string{scenario})
   182  	if err != nil {
   183  		return nil, nil, errors.Wrapf(err, "while listing runtimes in scenario %s", scenario)
   184  	}
   185  
   186  	runtimeContextsInFormation, err := b.runtimeContextRepo.ListByScenarios(ctx, tenant, []string{scenario})
   187  	if err != nil {
   188  		return nil, nil, errors.Wrapf(err, "while listing runtime contexts in scenario %s", scenario)
   189  	}
   190  
   191  	runtimeContextsIDs := make([]string, 0, len(runtimeContextsInFormation))
   192  	parentRuntimeIDs := make([]string, 0, len(runtimeContextsInFormation))
   193  	for _, rtCtx := range runtimeContextsInFormation {
   194  		runtimeContextsIDs = append(runtimeContextsIDs, rtCtx.ID)
   195  		parentRuntimeIDs = append(parentRuntimeIDs, rtCtx.RuntimeID)
   196  	}
   197  
   198  	// the parent runtime of the runtime context may not be in the formation - that's why we list them separately
   199  	parentRuntimesOfRuntimeContextsInFormation, err := b.runtimeRepo.ListByIDs(ctx, tenant, parentRuntimeIDs)
   200  	if err != nil {
   201  		return nil, nil, errors.Wrapf(err, "while listing parent runtimes of runtime contexts in scenario %s", scenario)
   202  	}
   203  
   204  	runtimes := append(runtimesInFormation, parentRuntimesOfRuntimeContextsInFormation...)
   205  	runtimesIDs := make([]string, 0, len(runtimes))
   206  	for _, rt := range runtimes {
   207  		runtimesIDs = append(runtimesIDs, rt.ID)
   208  	}
   209  
   210  	runtimesLabels, err := b.getLabelsForObjects(ctx, tenant, runtimesIDs, model.RuntimeLabelableObject)
   211  	if err != nil {
   212  		return nil, nil, errors.Wrap(err, "while listing runtime labels")
   213  	}
   214  
   215  	runtimesMapping := make(map[string]*webhook.RuntimeWithLabels, len(runtimesLabels))
   216  	for _, rt := range runtimes {
   217  		runtimesMapping[rt.ID] = &webhook.RuntimeWithLabels{
   218  			Runtime: rt,
   219  			Labels:  runtimesLabels[rt.ID],
   220  		}
   221  	}
   222  
   223  	runtimeContextsLabels, err := b.getLabelsForObjects(ctx, tenant, runtimeContextsIDs, model.RuntimeContextLabelableObject)
   224  	if err != nil {
   225  		return nil, nil, errors.Wrap(err, "while listing labels for runtime contexts")
   226  	}
   227  
   228  	runtimesToRuntimeContextsMapping := make(map[string]*webhook.RuntimeContextWithLabels, len(runtimeContextsInFormation))
   229  	for _, rtCtx := range runtimeContextsInFormation {
   230  		runtimesToRuntimeContextsMapping[rtCtx.RuntimeID] = &webhook.RuntimeContextWithLabels{
   231  			RuntimeContext: rtCtx,
   232  			Labels:         runtimeContextsLabels[rtCtx.ID],
   233  		}
   234  	}
   235  
   236  	return runtimesMapping, runtimesToRuntimeContextsMapping, nil
   237  }
   238  
   239  // PrepareApplicationMappingsInFormation constructs:
   240  // map from application ID to ApplicationWithLabels with entries for each application part of the formation
   241  // map from applicationTemplate ID to ApplicationTemplateWithLabels with entries for each application template whose child application is part of the formation
   242  func (b *WebhookDataInputBuilder) PrepareApplicationMappingsInFormation(ctx context.Context, tenant string, scenario string) (map[string]*webhook.ApplicationWithLabels, map[string]*webhook.ApplicationTemplateWithLabels, error) {
   243  	applicationsToBeNotifiedFor, err := b.applicationRepository.ListByScenariosNoPaging(ctx, tenant, []string{scenario})
   244  	if err != nil {
   245  		return nil, nil, errors.Wrapf(err, "while listing applications in formation %s", scenario)
   246  	}
   247  
   248  	if len(applicationsToBeNotifiedFor) == 0 {
   249  		log.C(ctx).Infof("There are no applications in scenario %s.", scenario)
   250  		return make(map[string]*webhook.ApplicationWithLabels, 0), make(map[string]*webhook.ApplicationTemplateWithLabels, 0), nil
   251  	}
   252  
   253  	applicationsToBeNotifiedForIDs := make([]string, 0, len(applicationsToBeNotifiedFor))
   254  	applicationsTemplateIDs := make([]string, 0, len(applicationsToBeNotifiedFor))
   255  	for _, app := range applicationsToBeNotifiedFor {
   256  		applicationsToBeNotifiedForIDs = append(applicationsToBeNotifiedForIDs, app.ID)
   257  		if app.ApplicationTemplateID != nil {
   258  			applicationsTemplateIDs = append(applicationsTemplateIDs, *app.ApplicationTemplateID)
   259  		}
   260  	}
   261  
   262  	applicationsToBeNotifiedForLabels, err := b.getLabelsForObjects(ctx, tenant, applicationsToBeNotifiedForIDs, model.ApplicationLabelableObject)
   263  	if err != nil {
   264  		return nil, nil, errors.Wrap(err, "while listing labels for applications")
   265  	}
   266  
   267  	applicationMapping := make(map[string]*webhook.ApplicationWithLabels, len(applicationsToBeNotifiedForIDs))
   268  	for i, app := range applicationsToBeNotifiedFor {
   269  		applicationMapping[app.ID] = &webhook.ApplicationWithLabels{
   270  			Application: applicationsToBeNotifiedFor[i],
   271  			Labels:      applicationsToBeNotifiedForLabels[app.ID],
   272  		}
   273  	}
   274  
   275  	applicationTemplates, err := b.applicationTemplateRepository.ListByIDs(ctx, applicationsTemplateIDs)
   276  	if err != nil {
   277  		return nil, nil, errors.Wrap(err, "while listing application templates")
   278  	}
   279  
   280  	applicationTemplatesLabels, err := b.getLabelsForObjects(ctx, tenant, applicationsTemplateIDs, model.AppTemplateLabelableObject)
   281  	if err != nil {
   282  		return nil, nil, errors.Wrap(err, "while listing labels for application templates")
   283  	}
   284  
   285  	applicationTemplatesMapping := make(map[string]*webhook.ApplicationTemplateWithLabels, len(applicationTemplates))
   286  	for i, appTemplate := range applicationTemplates {
   287  		applicationTemplatesMapping[appTemplate.ID] = &webhook.ApplicationTemplateWithLabels{
   288  			ApplicationTemplate: applicationTemplates[i],
   289  			Labels:              applicationTemplatesLabels[appTemplate.ID],
   290  		}
   291  	}
   292  
   293  	return applicationMapping, applicationTemplatesMapping, nil
   294  }
   295  
   296  func (b *WebhookDataInputBuilder) getLabelsForObject(ctx context.Context, tenant, objectID string, objectType model.LabelableObject) (map[string]string, error) {
   297  	labels, err := b.labelRepository.ListForObject(ctx, tenant, objectType, objectID)
   298  	if err != nil {
   299  		return nil, errors.Wrapf(err, "while listing labels for %q with ID: %q", objectType, objectID)
   300  	}
   301  	labelsMap := make(map[string]string, len(labels))
   302  	for _, l := range labels {
   303  		labelBytes, err := json.Marshal(l.Value)
   304  		if err != nil {
   305  			return nil, errors.Wrap(err, "while unmarshaling label value")
   306  		}
   307  
   308  		stringLabel := string(labelBytes)
   309  		unquotedLabel, err := strconv.Unquote(stringLabel)
   310  		if err != nil {
   311  			labelsMap[l.Key] = stringLabel
   312  		} else {
   313  			labelsMap[l.Key] = unquotedLabel
   314  		}
   315  	}
   316  	return labelsMap, nil
   317  }
   318  
   319  func (b *WebhookDataInputBuilder) getLabelsForObjects(ctx context.Context, tenant string, objectIDs []string, objectType model.LabelableObject) (map[string]map[string]string, error) {
   320  	labelsForResources, err := b.labelRepository.ListForObjectIDs(ctx, tenant, objectType, objectIDs)
   321  	if err != nil {
   322  		return nil, errors.Wrapf(err, "while listing labels for %q with IDs: %q", objectType, objectIDs)
   323  	}
   324  	labelsForResourcesMap := make(map[string]map[string]string, len(labelsForResources))
   325  	for resourceID, labels := range labelsForResources {
   326  		for key, value := range labels {
   327  			labelBytes, err := json.Marshal(value)
   328  			if err != nil {
   329  				return nil, errors.Wrap(err, "while marshaling label value")
   330  			}
   331  
   332  			if _, ok := labelsForResourcesMap[resourceID]; !ok {
   333  				labelsForResourcesMap[resourceID] = make(map[string]string)
   334  			}
   335  
   336  			stringLabel := string(labelBytes)
   337  			unquotedLabel, err := strconv.Unquote(stringLabel)
   338  			if err != nil {
   339  				labelsForResourcesMap[resourceID][key] = stringLabel
   340  			} else {
   341  				labelsForResourcesMap[resourceID][key] = unquotedLabel
   342  			}
   343  		}
   344  	}
   345  	return labelsForResourcesMap, nil
   346  }