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

     1  package application
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"github.com/kyma-incubator/compass/components/director/pkg/accessstrategy"
    11  
    12  	"github.com/imdario/mergo"
    13  
    14  	"github.com/kyma-incubator/compass/components/director/internal/domain/eventing"
    15  	"github.com/kyma-incubator/compass/components/director/pkg/graphql"
    16  	"github.com/kyma-incubator/compass/components/director/pkg/operation"
    17  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    18  
    19  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    20  
    21  	"github.com/kyma-incubator/compass/components/director/pkg/normalizer"
    22  
    23  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    24  
    25  	"github.com/kyma-incubator/compass/components/director/internal/domain/label"
    26  
    27  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    28  
    29  	"github.com/google/uuid"
    30  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    31  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    32  	"github.com/kyma-incubator/compass/components/director/internal/model"
    33  	"github.com/kyma-incubator/compass/components/director/internal/timestamp"
    34  	"github.com/kyma-incubator/compass/components/director/pkg/pagination"
    35  	"github.com/pkg/errors"
    36  )
    37  
    38  const (
    39  	intSysKey                    = "integrationSystemID"
    40  	nameKey                      = "name"
    41  	sccLabelKey                  = "scc"
    42  	managedKey                   = "managed"
    43  	subaccountKey                = "Subaccount"
    44  	locationIDKey                = "LocationID"
    45  	urlSuffixToBeTrimmed         = "/"
    46  	applicationTypeLabelKey      = "applicationType"
    47  	ppmsProductVersionIDLabelKey = "ppmsProductVersionId"
    48  	urlSubdomainSeparator        = "."
    49  )
    50  
    51  type repoCreatorFunc func(ctx context.Context, tenant string, application *model.Application) error
    52  type repoUpserterFunc func(ctx context.Context, tenant string, application *model.Application) (string, error)
    53  
    54  // ApplicationRepository missing godoc
    55  //
    56  //go:generate mockery --name=ApplicationRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    57  type ApplicationRepository interface {
    58  	Exists(ctx context.Context, tenant, id string) (bool, error)
    59  	OwnerExists(ctx context.Context, tenant, id string) (bool, error)
    60  	GetByID(ctx context.Context, tenant, id string) (*model.Application, error)
    61  	GetByIDForUpdate(ctx context.Context, tenant, id string) (*model.Application, error)
    62  	GetGlobalByID(ctx context.Context, id string) (*model.Application, error)
    63  	GetBySystemNumber(ctx context.Context, tenant, systemNumber string) (*model.Application, error)
    64  	GetByFilter(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) (*model.Application, error)
    65  	List(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.ApplicationPage, error)
    66  	ListAll(ctx context.Context, tenant string) ([]*model.Application, error)
    67  	ListAllByFilter(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) ([]*model.Application, error)
    68  	ListGlobal(ctx context.Context, pageSize int, cursor string) (*model.ApplicationPage, error)
    69  	ListAllByApplicationTemplateID(ctx context.Context, applicationTemplateID string) ([]*model.Application, error)
    70  	ListByScenarios(ctx context.Context, tenantID uuid.UUID, scenarios []string, pageSize int, cursor string, hidingSelectors map[string][]string) (*model.ApplicationPage, error)
    71  	ListByScenariosNoPaging(ctx context.Context, tenant string, scenarios []string) ([]*model.Application, error)
    72  	ListListeningApplications(ctx context.Context, tenant string, whType model.WebhookType) ([]*model.Application, error)
    73  	ListAllByIDs(ctx context.Context, tenantID string, ids []string) ([]*model.Application, error)
    74  	ListByScenariosAndIDs(ctx context.Context, tenant string, scenarios []string, ids []string) ([]*model.Application, error)
    75  	Create(ctx context.Context, tenant string, item *model.Application) error
    76  	Update(ctx context.Context, tenant string, item *model.Application) error
    77  	Upsert(ctx context.Context, tenant string, model *model.Application) (string, error)
    78  	TrustedUpsert(ctx context.Context, tenant string, model *model.Application) (string, error)
    79  	TechnicalUpdate(ctx context.Context, item *model.Application) error
    80  	Delete(ctx context.Context, tenant, id string) error
    81  	DeleteGlobal(ctx context.Context, id string) error
    82  }
    83  
    84  // LabelRepository missing godoc
    85  //
    86  //go:generate mockery --name=LabelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    87  type LabelRepository interface {
    88  	GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error)
    89  	ListForObject(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error)
    90  	ListGlobalByKey(ctx context.Context, key string) ([]*model.Label, error)
    91  	ListGlobalByKeyAndObjects(ctx context.Context, objectType model.LabelableObject, objectIDs []string, key string) ([]*model.Label, error)
    92  	Delete(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, key string) error
    93  	DeleteAll(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) error
    94  }
    95  
    96  // WebhookRepository missing godoc
    97  //
    98  //go:generate mockery --name=WebhookRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    99  type WebhookRepository interface {
   100  	CreateMany(ctx context.Context, tenant string, items []*model.Webhook) error
   101  	ListByReferenceObjectID(ctx context.Context, tenant, objID string, objType model.WebhookReferenceObjectType) ([]*model.Webhook, error)
   102  }
   103  
   104  // FormationService missing godoc
   105  //
   106  //go:generate mockery --name=FormationService --output=automock --outpkg=automock --case=underscore --disable-version-string
   107  type FormationService interface {
   108  	AssignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error)
   109  	UnassignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error)
   110  }
   111  
   112  // RuntimeRepository missing godoc
   113  //
   114  //go:generate mockery --name=RuntimeRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
   115  type RuntimeRepository interface {
   116  	Exists(ctx context.Context, tenant, id string) (bool, error)
   117  	ListAll(ctx context.Context, tenantID string, filter []*labelfilter.LabelFilter) ([]*model.Runtime, error)
   118  }
   119  
   120  // IntegrationSystemRepository missing godoc
   121  //
   122  //go:generate mockery --name=IntegrationSystemRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
   123  type IntegrationSystemRepository interface {
   124  	Exists(ctx context.Context, id string) (bool, error)
   125  }
   126  
   127  // LabelService missing godoc
   128  //
   129  //go:generate mockery --name=LabelService --output=automock --outpkg=automock --case=underscore --disable-version-string
   130  type LabelService interface {
   131  	UpsertMultipleLabels(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, labels map[string]interface{}) error
   132  	UpsertLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) error
   133  	GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error)
   134  }
   135  
   136  // UIDService missing godoc
   137  //
   138  //go:generate mockery --name=UIDService --output=automock --outpkg=automock --case=underscore --disable-version-string
   139  type UIDService interface {
   140  	Generate() string
   141  }
   142  
   143  // ApplicationHideCfgProvider missing godoc
   144  //
   145  //go:generate mockery --name=ApplicationHideCfgProvider --output=automock --outpkg=automock --case=underscore --disable-version-string
   146  type ApplicationHideCfgProvider interface {
   147  	GetApplicationHideSelectors() (map[string][]string, error)
   148  }
   149  
   150  type service struct {
   151  	appNameNormalizer  normalizer.Normalizator
   152  	appHideCfgProvider ApplicationHideCfgProvider
   153  
   154  	appRepo       ApplicationRepository
   155  	webhookRepo   WebhookRepository
   156  	labelRepo     LabelRepository
   157  	runtimeRepo   RuntimeRepository
   158  	intSystemRepo IntegrationSystemRepository
   159  
   160  	labelService     LabelService
   161  	uidService       UIDService
   162  	bndlService      BundleService
   163  	timestampGen     timestamp.Generator
   164  	formationService FormationService
   165  
   166  	selfRegisterDistinguishLabelKey string
   167  
   168  	ordWebhookMapping []ORDWebhookMapping
   169  }
   170  
   171  // NewService missing godoc
   172  func NewService(appNameNormalizer normalizer.Normalizator, appHideCfgProvider ApplicationHideCfgProvider, app ApplicationRepository, webhook WebhookRepository, runtimeRepo RuntimeRepository, labelRepo LabelRepository, intSystemRepo IntegrationSystemRepository, labelService LabelService, bndlService BundleService, uidService UIDService, formationService FormationService, selfRegisterDistinguishLabelKey string, ordWebhookMapping []ORDWebhookMapping) *service {
   173  	return &service{
   174  		appNameNormalizer:               appNameNormalizer,
   175  		appHideCfgProvider:              appHideCfgProvider,
   176  		appRepo:                         app,
   177  		webhookRepo:                     webhook,
   178  		runtimeRepo:                     runtimeRepo,
   179  		labelRepo:                       labelRepo,
   180  		intSystemRepo:                   intSystemRepo,
   181  		labelService:                    labelService,
   182  		bndlService:                     bndlService,
   183  		uidService:                      uidService,
   184  		timestampGen:                    timestamp.DefaultGenerator,
   185  		formationService:                formationService,
   186  		selfRegisterDistinguishLabelKey: selfRegisterDistinguishLabelKey,
   187  		ordWebhookMapping:               ordWebhookMapping,
   188  	}
   189  }
   190  
   191  // List missing godoc
   192  func (s *service) List(ctx context.Context, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.ApplicationPage, error) {
   193  	appTenant, err := tenant.LoadFromContext(ctx)
   194  	if err != nil {
   195  		return nil, errors.Wrapf(err, "while loading tenant from context")
   196  	}
   197  
   198  	if pageSize < 1 || pageSize > 200 {
   199  		return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200")
   200  	}
   201  
   202  	return s.appRepo.List(ctx, appTenant, filter, pageSize, cursor)
   203  }
   204  
   205  // ListAll lists tenant scoped applications
   206  func (s *service) ListAll(ctx context.Context) ([]*model.Application, error) {
   207  	appTenant, err := tenant.LoadFromContext(ctx)
   208  	if err != nil {
   209  		return nil, errors.Wrapf(err, "while loading tenant from context")
   210  	}
   211  
   212  	return s.appRepo.ListAll(ctx, appTenant)
   213  }
   214  
   215  // ListGlobal missing godoc
   216  func (s *service) ListGlobal(ctx context.Context, pageSize int, cursor string) (*model.ApplicationPage, error) {
   217  	if pageSize < 1 || pageSize > 200 {
   218  		return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200")
   219  	}
   220  
   221  	return s.appRepo.ListGlobal(ctx, pageSize, cursor)
   222  }
   223  
   224  // ListAllByApplicationTemplateID lists all applications which have the given app template id
   225  func (s *service) ListAllByApplicationTemplateID(ctx context.Context, applicationTemplateID string) ([]*model.Application, error) {
   226  	apps, err := s.appRepo.ListAllByApplicationTemplateID(ctx, applicationTemplateID)
   227  	if err != nil {
   228  		return nil, errors.Wrapf(err, "while getting applications for app template with id %q", applicationTemplateID)
   229  	}
   230  
   231  	if len(apps) == 0 {
   232  		return []*model.Application{}, nil
   233  	}
   234  
   235  	return apps, nil
   236  }
   237  
   238  // ListByRuntimeID missing godoc
   239  func (s *service) ListByRuntimeID(ctx context.Context, runtimeID uuid.UUID, pageSize int, cursor string) (*model.ApplicationPage, error) {
   240  	tenantID, err := tenant.LoadFromContext(ctx)
   241  
   242  	if err != nil {
   243  		return nil, errors.Wrapf(err, "while loading tenant from context")
   244  	}
   245  
   246  	tenantUUID, err := uuid.Parse(tenantID)
   247  	if err != nil {
   248  		return nil, apperrors.NewInvalidDataError("tenantID is not UUID")
   249  	}
   250  
   251  	exist, err := s.runtimeRepo.Exists(ctx, tenantID, runtimeID.String())
   252  	if err != nil {
   253  		return nil, errors.Wrap(err, "while checking if runtime exits")
   254  	}
   255  
   256  	if !exist {
   257  		return nil, apperrors.NewInvalidDataError("runtime does not exist")
   258  	}
   259  
   260  	scenariosLabel, err := s.labelRepo.GetByKey(ctx, tenantID, model.RuntimeLabelableObject, runtimeID.String(), model.ScenariosKey)
   261  	if err != nil {
   262  		if apperrors.IsNotFoundError(err) {
   263  			return &model.ApplicationPage{
   264  				Data:       []*model.Application{},
   265  				PageInfo:   &pagination.Page{},
   266  				TotalCount: 0,
   267  			}, nil
   268  		}
   269  		return nil, errors.Wrap(err, "while getting scenarios for runtime")
   270  	}
   271  
   272  	scenarios, err := label.ValueToStringsSlice(scenariosLabel.Value)
   273  	if err != nil {
   274  		return nil, errors.Wrap(err, "while converting scenarios labels")
   275  	}
   276  	if len(scenarios) == 0 {
   277  		return &model.ApplicationPage{
   278  			Data:       []*model.Application{},
   279  			TotalCount: 0,
   280  			PageInfo: &pagination.Page{
   281  				StartCursor: "",
   282  				EndCursor:   "",
   283  				HasNextPage: false,
   284  			},
   285  		}, nil
   286  	}
   287  
   288  	hidingSelectors, err := s.appHideCfgProvider.GetApplicationHideSelectors()
   289  	if err != nil {
   290  		return nil, errors.Wrap(err, "while getting application hide selectors from config")
   291  	}
   292  
   293  	return s.appRepo.ListByScenarios(ctx, tenantUUID, scenarios, pageSize, cursor, hidingSelectors)
   294  }
   295  
   296  // Get missing godoc
   297  func (s *service) Get(ctx context.Context, id string) (*model.Application, error) {
   298  	appTenant, err := tenant.LoadFromContext(ctx)
   299  	if err != nil {
   300  		return nil, errors.Wrapf(err, "while loading tenant from context")
   301  	}
   302  
   303  	app, err := s.appRepo.GetByID(ctx, appTenant, id)
   304  	if err != nil {
   305  		return nil, errors.Wrapf(err, "while getting Application with id %s", id)
   306  	}
   307  
   308  	return app, nil
   309  }
   310  
   311  // GetForUpdate returns an application retrieved globally (without tenant required in the context)
   312  func (s *service) GetForUpdate(ctx context.Context, id string) (*model.Application, error) {
   313  	appTenant, err := tenant.LoadFromContext(ctx)
   314  	if err != nil {
   315  		return nil, errors.Wrapf(err, "while loading tenant from context")
   316  	}
   317  	app, err := s.appRepo.GetByIDForUpdate(ctx, appTenant, id)
   318  	if err != nil {
   319  		return nil, errors.Wrapf(err, "while getting Application with id %s", id)
   320  	}
   321  
   322  	return app, nil
   323  }
   324  
   325  // GetBySystemNumber returns an application retrieved by systemNumber
   326  func (s *service) GetBySystemNumber(ctx context.Context, systemNumber string) (*model.Application, error) {
   327  	appTenant, err := tenant.LoadFromContext(ctx)
   328  	if err != nil {
   329  		return nil, errors.Wrapf(err, "while loading tenant from context")
   330  	}
   331  
   332  	app, err := s.appRepo.GetBySystemNumber(ctx, appTenant, systemNumber)
   333  	if err != nil {
   334  		return nil, errors.Wrapf(err, "while getting Application with system number %s", systemNumber)
   335  	}
   336  
   337  	return app, nil
   338  }
   339  
   340  // Exist missing godoc
   341  func (s *service) Exist(ctx context.Context, id string) (bool, error) {
   342  	appTenant, err := tenant.LoadFromContext(ctx)
   343  	if err != nil {
   344  		return false, errors.Wrapf(err, "while loading tenant from context")
   345  	}
   346  
   347  	exist, err := s.appRepo.Exists(ctx, appTenant, id)
   348  	if err != nil {
   349  		return false, errors.Wrapf(err, "while getting Application with ID %s", id)
   350  	}
   351  
   352  	return exist, nil
   353  }
   354  
   355  // Create missing godoc
   356  func (s *service) Create(ctx context.Context, in model.ApplicationRegisterInput) (string, error) {
   357  	creator := func(ctx context.Context, tenant string, application *model.Application) (err error) {
   358  		if err = s.appRepo.Create(ctx, tenant, application); err != nil {
   359  			return errors.Wrapf(err, "while creating Application with name %s", application.Name)
   360  		}
   361  		return
   362  	}
   363  
   364  	return s.genericCreate(ctx, in, creator)
   365  }
   366  
   367  // GetSccSystem retrieves an application with label key "scc" and value that matches specified subaccount, location id and virtual host
   368  func (s *service) GetSccSystem(ctx context.Context, sccSubaccount, locationID, virtualHost string) (*model.Application, error) {
   369  	appTenant, err := tenant.LoadFromContext(ctx)
   370  	if err != nil {
   371  		return nil, errors.Wrapf(err, "while loading tenant from context")
   372  	}
   373  
   374  	sccLabel := struct {
   375  		Host       string `json:"Host"`
   376  		Subaccount string `json:"Subaccount"`
   377  		LocationID string `json:"LocationID"`
   378  	}{
   379  		virtualHost, sccSubaccount, locationID,
   380  	}
   381  	marshal, err := json.Marshal(sccLabel)
   382  	if err != nil {
   383  		return nil, errors.Wrapf(err, "while marshaling sccLabel with subaccount: %s, locationId: %s and virtualHost: %s", appTenant, locationID, virtualHost)
   384  	}
   385  
   386  	filter := labelfilter.NewForKeyWithQuery(sccLabelKey, string(marshal))
   387  
   388  	app, err := s.appRepo.GetByFilter(ctx, appTenant, []*labelfilter.LabelFilter{filter})
   389  	if err != nil {
   390  		return nil, errors.Wrapf(err, "while getting Application with subaccount: %s, locationId: %s and virtualHost: %s", appTenant, locationID, virtualHost)
   391  	}
   392  
   393  	return app, nil
   394  }
   395  
   396  // ListBySCC retrieves all applications with label matching the specified filter
   397  func (s *service) ListBySCC(ctx context.Context, filter *labelfilter.LabelFilter) ([]*model.ApplicationWithLabel, error) {
   398  	appTenant, err := tenant.LoadFromContext(ctx)
   399  	if err != nil {
   400  		return nil, errors.Wrapf(err, "while loading tenant from context")
   401  	}
   402  
   403  	apps, err := s.appRepo.ListAllByFilter(ctx, appTenant, []*labelfilter.LabelFilter{filter})
   404  	if err != nil {
   405  		return nil, errors.Wrapf(err, "while getting Applications by filters: %v", filter)
   406  	}
   407  
   408  	if len(apps) == 0 {
   409  		return []*model.ApplicationWithLabel{}, nil
   410  	}
   411  
   412  	appIDs := make([]string, 0, len(apps))
   413  	for _, app := range apps {
   414  		appIDs = append(appIDs, app.ID)
   415  	}
   416  
   417  	labels, err := s.labelRepo.ListGlobalByKeyAndObjects(ctx, model.ApplicationLabelableObject, appIDs, sccLabelKey)
   418  	if err != nil {
   419  		return nil, errors.Wrapf(err, "while getting labels with key scc for applications with IDs: %v", appIDs)
   420  	}
   421  
   422  	appIDToLabel := make(map[string]*model.Label, len(labels))
   423  	for _, l := range labels {
   424  		appIDToLabel[l.ObjectID] = l
   425  	}
   426  
   427  	appsWithLabel := make([]*model.ApplicationWithLabel, 0, len(apps))
   428  	for _, app := range apps {
   429  		appWithLabel := &model.ApplicationWithLabel{
   430  			App:      app,
   431  			SccLabel: appIDToLabel[app.ID],
   432  		}
   433  		appsWithLabel = append(appsWithLabel, appWithLabel)
   434  	}
   435  
   436  	return appsWithLabel, nil
   437  }
   438  
   439  // ListSCCs retrieves all SCCs
   440  func (s *service) ListSCCs(ctx context.Context) ([]*model.SccMetadata, error) {
   441  	labels, err := s.labelRepo.ListGlobalByKey(ctx, sccLabelKey)
   442  	if err != nil {
   443  		return nil, errors.Wrap(err, "while getting SCCs by label key: scc")
   444  	}
   445  	sccs := make([]*model.SccMetadata, 0, len(labels))
   446  	for _, sccLabel := range labels {
   447  		v, ok := sccLabel.Value.(map[string]interface{})
   448  		if !ok {
   449  			return nil, errors.New("Label value is not of type map[string]interface{}")
   450  		}
   451  
   452  		scc := &model.SccMetadata{
   453  			Subaccount: v[subaccountKey].(string),
   454  			LocationID: v[locationIDKey].(string),
   455  		}
   456  
   457  		sccs = append(sccs, scc)
   458  	}
   459  	return sccs, nil
   460  }
   461  
   462  // CreateFromTemplate missing godoc
   463  func (s *service) CreateFromTemplate(ctx context.Context, in model.ApplicationRegisterInput, appTemplateID *string) (string, error) {
   464  	creator := func(ctx context.Context, tenant string, application *model.Application) (err error) {
   465  		application.ApplicationTemplateID = appTemplateID
   466  		if err = s.appRepo.Create(ctx, tenant, application); err != nil {
   467  			return errors.Wrapf(err, "while creating Application with name %s from template", application.Name)
   468  		}
   469  		return
   470  	}
   471  
   472  	return s.genericCreate(ctx, in, creator)
   473  }
   474  
   475  // CreateManyIfNotExistsWithEventualTemplate missing godoc
   476  func (s *service) CreateManyIfNotExistsWithEventualTemplate(ctx context.Context, applicationInputs []model.ApplicationRegisterInputWithTemplate) error {
   477  	appsToAdd, err := s.filterUniqueNonExistingApplications(ctx, applicationInputs)
   478  	if err != nil {
   479  		return errors.Wrap(err, "while filtering unique and non-existing applications")
   480  	}
   481  	log.C(ctx).Infof("Will create %d systems", len(appsToAdd))
   482  	for _, a := range appsToAdd {
   483  		if a.TemplateID == "" {
   484  			_, err = s.Create(ctx, a.ApplicationRegisterInput)
   485  			if err != nil {
   486  				return errors.Wrap(err, "while creating application")
   487  			}
   488  			continue
   489  		}
   490  		_, err = s.CreateFromTemplate(ctx, a.ApplicationRegisterInput, &a.TemplateID)
   491  		if err != nil {
   492  			return errors.Wrap(err, "while creating application")
   493  		}
   494  	}
   495  
   496  	return nil
   497  }
   498  
   499  // Update missing godoc
   500  func (s *service) Update(ctx context.Context, id string, in model.ApplicationUpdateInput) error {
   501  	appTenant, err := tenant.LoadFromContext(ctx)
   502  	if err != nil {
   503  		return errors.Wrapf(err, "while loading tenant from context")
   504  	}
   505  
   506  	exists, err := s.ensureIntSysExists(ctx, in.IntegrationSystemID)
   507  	if err != nil {
   508  		return errors.Wrap(err, "while validating Integration System ID")
   509  	}
   510  
   511  	if !exists {
   512  		return apperrors.NewNotFoundError(resource.IntegrationSystem, *in.IntegrationSystemID)
   513  	}
   514  
   515  	app, err := s.Get(ctx, id)
   516  	if err != nil {
   517  		return errors.Wrapf(err, "while getting Application with id %s", id)
   518  	}
   519  
   520  	app.SetFromUpdateInput(in, s.timestampGen())
   521  
   522  	if err = s.appRepo.Update(ctx, appTenant, app); err != nil {
   523  		return errors.Wrapf(err, "while updating Application with id %s", id)
   524  	}
   525  
   526  	if in.IntegrationSystemID != nil {
   527  		intSysLabel := createLabel(intSysKey, *in.IntegrationSystemID, id)
   528  		err = s.SetLabel(ctx, intSysLabel)
   529  		if err != nil {
   530  			return errors.Wrapf(err, "while setting the integration system label for %s with id %s", intSysLabel.ObjectType, intSysLabel.ObjectID)
   531  		}
   532  		log.C(ctx).Debugf("Successfully set Label for %s with id %s", intSysLabel.ObjectType, intSysLabel.ObjectID)
   533  	}
   534  
   535  	label := createLabel(nameKey, s.appNameNormalizer.Normalize(app.Name), app.ID)
   536  	err = s.SetLabel(ctx, label)
   537  	if err != nil {
   538  		return errors.Wrap(err, "while setting application name label")
   539  	}
   540  	log.C(ctx).Debugf("Successfully set Label for Application with id %s", app.ID)
   541  
   542  	appTypeLbl, err := s.labelService.GetByKey(ctx, appTenant, model.ApplicationLabelableObject, app.ID, applicationTypeLabelKey)
   543  	if err != nil {
   544  		if !apperrors.IsNotFoundError(err) {
   545  			return errors.Wrapf(err, "while getting label %q for %s with id %q", applicationTypeLabelKey, model.ApplicationLabelableObject, app.ID)
   546  		}
   547  
   548  		log.C(ctx).Infof("Label %q is missing for %s with id %q. Skipping ord webhook creation", applicationTypeLabelKey, model.ApplicationLabelableObject, app.ID)
   549  		return nil
   550  	}
   551  
   552  	ppmsProductVersionIDLbl, err := s.labelService.GetByKey(ctx, appTenant, model.ApplicationLabelableObject, app.ID, ppmsProductVersionIDLabelKey)
   553  	if err != nil {
   554  		if !apperrors.IsNotFoundError(err) {
   555  			return errors.Wrapf(err, "while getting label %q for %q with id %q", ppmsProductVersionIDLabelKey, model.ApplicationLabelableObject, app.ID)
   556  		}
   557  	}
   558  
   559  	ppmsProductVersionID := ""
   560  	if ppmsProductVersionIDLbl != nil && ppmsProductVersionIDLbl.Value != nil {
   561  		if ppmsProductVersionIDValue, ok := ppmsProductVersionIDLbl.Value.(string); ok {
   562  			ppmsProductVersionID = ppmsProductVersionIDValue
   563  		}
   564  	}
   565  
   566  	ordWebhook := s.prepareORDWebhook(ctx, str.PtrStrToStr(in.BaseURL), appTypeLbl.Value.(string), ppmsProductVersionID)
   567  	if ordWebhook == nil {
   568  		log.C(ctx).Infof("Skipping ORD Webhook creation for app with id %q.", app.ID)
   569  		return nil
   570  	}
   571  
   572  	if err = s.createWebhooksIfNotExist(ctx, app.ID, appTenant, []*model.WebhookInput{ordWebhook}); err != nil {
   573  		return errors.Wrapf(err, "while processing webhooks for application with id %q", app.ID)
   574  	}
   575  
   576  	return nil
   577  }
   578  
   579  // Upsert persists application or update it if it already exists
   580  func (s *service) Upsert(ctx context.Context, in model.ApplicationRegisterInput) error {
   581  	tenant, err := tenant.LoadFromContext(ctx)
   582  	if err != nil {
   583  		return errors.Wrapf(err, "while loading tenant from context")
   584  	}
   585  
   586  	upserterFunc := func(ctx context.Context, tenant string, application *model.Application) (string, error) {
   587  		id, err := s.appRepo.Upsert(ctx, tenant, application)
   588  		if err != nil {
   589  			return "", errors.Wrapf(err, "while upserting Application with name %s", application.Name)
   590  		}
   591  		return id, nil
   592  	}
   593  
   594  	return s.genericUpsert(ctx, tenant, in, upserterFunc)
   595  }
   596  
   597  // UpdateBaseURL Gets application by ID. If the application does not have a BaseURL set, the API TargetURL is parsed and set as BaseURL
   598  func (s *service) UpdateBaseURL(ctx context.Context, appID, targetURL string) error {
   599  	appTenant, err := tenant.LoadFromContext(ctx)
   600  	if err != nil {
   601  		return errors.Wrapf(err, "while loading tenant from context")
   602  	}
   603  
   604  	app, err := s.Get(ctx, appID)
   605  	if err != nil {
   606  		return err
   607  	}
   608  
   609  	if app.BaseURL != nil && len(*app.BaseURL) > 0 {
   610  		log.C(ctx).Infof("BaseURL for Application %s already exists. Will not update it.", appID)
   611  		return nil
   612  	}
   613  
   614  	log.C(ctx).Infof("BaseURL for Application %s does not exist. Will update it.", appID)
   615  
   616  	parsedTargetURL, err := url.Parse(targetURL)
   617  	if err != nil {
   618  		return errors.Wrapf(err, "while parsing targetURL")
   619  	}
   620  
   621  	app.BaseURL = str.Ptr(fmt.Sprintf("%s://%s", parsedTargetURL.Scheme, parsedTargetURL.Host))
   622  
   623  	return s.appRepo.Update(ctx, appTenant, app)
   624  }
   625  
   626  // TrustedUpsert persists application or update it if it already exists ignoring tenant isolation
   627  func (s *service) TrustedUpsert(ctx context.Context, in model.ApplicationRegisterInput) error {
   628  	tenant, err := tenant.LoadFromContext(ctx)
   629  	if err != nil {
   630  		return errors.Wrapf(err, "while loading tenant from context")
   631  	}
   632  
   633  	upserterFunc := func(ctx context.Context, tenant string, application *model.Application) (string, error) {
   634  		id, err := s.appRepo.TrustedUpsert(ctx, tenant, application)
   635  		if err != nil {
   636  			return "", errors.Wrapf(err, "while upserting Application with name %s", application.Name)
   637  		}
   638  		return id, nil
   639  	}
   640  
   641  	return s.genericUpsert(ctx, tenant, in, upserterFunc)
   642  }
   643  
   644  // TrustedUpsertFromTemplate persists application from template id or update it if it already exists ignoring tenant isolation
   645  func (s *service) TrustedUpsertFromTemplate(ctx context.Context, in model.ApplicationRegisterInput, appTemplateID *string) error {
   646  	tenant, err := tenant.LoadFromContext(ctx)
   647  	if err != nil {
   648  		return errors.Wrapf(err, "while loading tenant from context")
   649  	}
   650  
   651  	upserterFunc := func(ctx context.Context, tenant string, application *model.Application) (string, error) {
   652  		application.ApplicationTemplateID = appTemplateID
   653  		id, err := s.appRepo.TrustedUpsert(ctx, tenant, application)
   654  		if err != nil {
   655  			return "", errors.Wrapf(err, "while upserting Application with name %s from template", application.Name)
   656  		}
   657  		return id, nil
   658  	}
   659  
   660  	return s.genericUpsert(ctx, tenant, in, upserterFunc)
   661  }
   662  
   663  // Delete missing godoc
   664  func (s *service) Delete(ctx context.Context, id string) error {
   665  	appTenant, err := tenant.LoadFromContext(ctx)
   666  	if err != nil {
   667  		return errors.Wrapf(err, "while loading tenant from context")
   668  	}
   669  
   670  	if err := s.ensureApplicationNotPartOfAnyScenario(ctx, appTenant, id); err != nil {
   671  		return err
   672  	}
   673  
   674  	err = s.appRepo.Delete(ctx, appTenant, id)
   675  	if err != nil {
   676  		return errors.Wrapf(err, "while deleting Application with id %s", id)
   677  	}
   678  
   679  	return nil
   680  }
   681  
   682  // Unpair Checks if the given application is in a scenario with a runtime. Fails if it is.
   683  // When the operation mode is sync, it sets the status condition to model.ApplicationStatusConditionInitial and does a db update, otherwise it only makes an "empty" db update.
   684  func (s *service) Unpair(ctx context.Context, id string) error {
   685  	appTenant, err := tenant.LoadFromContext(ctx)
   686  	if err != nil {
   687  		return errors.Wrapf(err, "while loading tenant from context")
   688  	}
   689  
   690  	if err := s.ensureApplicationNotPartOfScenarioWithRuntime(ctx, appTenant, id); err != nil {
   691  		return err
   692  	}
   693  
   694  	app, err := s.appRepo.GetByID(ctx, appTenant, id)
   695  	if err != nil {
   696  		return err
   697  	}
   698  
   699  	if opMode := operation.ModeFromCtx(ctx); opMode == graphql.OperationModeSync {
   700  		app.Status = &model.ApplicationStatus{
   701  			Condition: model.ApplicationStatusConditionInitial,
   702  			Timestamp: s.timestampGen(),
   703  		}
   704  	}
   705  
   706  	if err = s.appRepo.Update(ctx, appTenant, app); err != nil {
   707  		return err
   708  	}
   709  
   710  	return nil
   711  }
   712  
   713  // SetLabel updates application label with given input label
   714  // In the case of a scenario label, it assigns the newly added formations from the input and
   715  // unassigns old formations that are not present in the input label, but are stored in the database
   716  func (s *service) SetLabel(ctx context.Context, labelInput *model.LabelInput) error {
   717  	appTenant, err := tenant.LoadFromContext(ctx)
   718  	if err != nil {
   719  		return errors.Wrapf(err, "while loading tenant from context")
   720  	}
   721  
   722  	appExists, err := s.appRepo.Exists(ctx, appTenant, labelInput.ObjectID)
   723  	if err != nil {
   724  		return errors.Wrap(err, "while checking Application existence")
   725  	}
   726  	if !appExists {
   727  		return apperrors.NewNotFoundError(resource.Application, labelInput.ObjectID)
   728  	}
   729  
   730  	if labelInput.Key == model.ScenariosKey {
   731  		return s.setScenarioLabel(ctx, appTenant, labelInput)
   732  	}
   733  
   734  	err = s.labelService.UpsertLabel(ctx, appTenant, labelInput)
   735  	if err != nil {
   736  		return errors.Wrapf(err, "while creating label for Application")
   737  	}
   738  
   739  	return nil
   740  }
   741  
   742  // GetLabel missing godoc
   743  func (s *service) GetLabel(ctx context.Context, applicationID string, key string) (*model.Label, error) {
   744  	appTenant, err := tenant.LoadFromContext(ctx)
   745  	if err != nil {
   746  		return nil, errors.Wrapf(err, "while loading tenant from context")
   747  	}
   748  
   749  	appExists, err := s.appRepo.Exists(ctx, appTenant, applicationID)
   750  	if err != nil {
   751  		return nil, errors.Wrap(err, "while checking Application existence")
   752  	}
   753  	if !appExists {
   754  		return nil, fmt.Errorf("application with ID %s doesn't exist", applicationID)
   755  	}
   756  
   757  	label, err := s.labelRepo.GetByKey(ctx, appTenant, model.ApplicationLabelableObject, applicationID, key)
   758  	if err != nil {
   759  		return nil, errors.Wrap(err, "while getting label for Application")
   760  	}
   761  
   762  	return label, nil
   763  }
   764  
   765  // ListLabels missing godoc
   766  func (s *service) ListLabels(ctx context.Context, applicationID string) (map[string]*model.Label, error) {
   767  	appTenant, err := tenant.LoadFromContext(ctx)
   768  	if err != nil {
   769  		return nil, errors.Wrapf(err, "while loading tenant from context")
   770  	}
   771  
   772  	appExists, err := s.appRepo.Exists(ctx, appTenant, applicationID)
   773  	if err != nil {
   774  		return nil, errors.Wrap(err, "while checking Application existence")
   775  	}
   776  
   777  	if !appExists {
   778  		return nil, errors.Errorf("application with ID %s doesn't exist", applicationID)
   779  	}
   780  
   781  	labels, err := s.labelRepo.ListForObject(ctx, appTenant, model.ApplicationLabelableObject, applicationID)
   782  	if err != nil {
   783  		return nil, errors.Wrap(err, "while getting label for Application")
   784  	}
   785  
   786  	return labels, nil
   787  }
   788  
   789  // DeleteLabel delete label given application ID, label key and label value.
   790  func (s *service) DeleteLabel(ctx context.Context, applicationID string, key string) error {
   791  	appTenant, err := tenant.LoadFromContext(ctx)
   792  	if err != nil {
   793  		return errors.Wrapf(err, "while loading tenant from context")
   794  	}
   795  
   796  	appExists, err := s.appRepo.Exists(ctx, appTenant, applicationID)
   797  	if err != nil {
   798  		return errors.Wrap(err, "while checking Application existence")
   799  	}
   800  	if !appExists {
   801  		return errors.Errorf("application with ID %s doesn't exist", applicationID)
   802  	}
   803  
   804  	if key == model.ScenariosKey {
   805  		storedLabel, err := s.labelRepo.GetByKey(ctx, appTenant, model.ApplicationLabelableObject, applicationID, key)
   806  		if err != nil {
   807  			return errors.Wrapf(err, "while getting scenario label for %s", applicationID)
   808  		}
   809  		scenarios, err := label.ValueToStringsSlice(storedLabel.Value)
   810  		if err != nil {
   811  			return errors.Wrapf(err, "while converting label to string slice")
   812  		}
   813  		if err = s.unassignFormations(ctx, appTenant, applicationID, scenarios, allowAllCriteria); err != nil {
   814  			return errors.Wrapf(err, "while unassigning formations")
   815  		}
   816  		return nil
   817  	}
   818  
   819  	err = s.labelRepo.Delete(ctx, appTenant, model.ApplicationLabelableObject, applicationID, key)
   820  	if err != nil {
   821  		return errors.Wrapf(err, "while deleting Application label")
   822  	}
   823  
   824  	return nil
   825  }
   826  
   827  // Merge merges properties from Source Application into Destination Application, provided that the Destination's
   828  // Application does not have a value set for a given property. Then the Source Application is being deleted.
   829  func (s *service) Merge(ctx context.Context, destID, srcID string) (*model.Application, error) {
   830  	appTenant, err := tenant.LoadFromContext(ctx)
   831  	if err != nil {
   832  		return nil, errors.Wrapf(err, "while loading tenant from context")
   833  	}
   834  
   835  	srcApp, err := s.Get(ctx, srcID)
   836  	if err != nil {
   837  		return nil, errors.Wrapf(err, "while getting source application")
   838  	}
   839  
   840  	if err := s.ensureApplicationNotPartOfAnyScenario(ctx, appTenant, srcID); err != nil {
   841  		return nil, err
   842  	}
   843  
   844  	destApp, err := s.Get(ctx, destID)
   845  	if err != nil {
   846  		return nil, errors.Wrapf(err, "while getting destination application")
   847  	}
   848  
   849  	destAppLabels, err := s.labelRepo.ListForObject(ctx, appTenant, model.ApplicationLabelableObject, destID)
   850  	if err != nil {
   851  		return nil, errors.Wrapf(err, "while getting labels for Application with id %s", destID)
   852  	}
   853  
   854  	srcAppLabels, err := s.labelRepo.ListForObject(ctx, appTenant, model.ApplicationLabelableObject, srcID)
   855  	if err != nil {
   856  		return nil, errors.Wrapf(err, "while getting labels for Application with id %s", srcID)
   857  	}
   858  
   859  	if destAppLabels == nil {
   860  		destAppLabels = make(map[string]*model.Label)
   861  	}
   862  
   863  	if srcAppLabels == nil {
   864  		srcAppLabels = make(map[string]*model.Label)
   865  	}
   866  
   867  	srcBaseURL := strings.TrimSuffix(str.PtrStrToStr(srcApp.BaseURL), urlSuffixToBeTrimmed)
   868  	destBaseURL := strings.TrimSuffix(str.PtrStrToStr(destApp.BaseURL), urlSuffixToBeTrimmed)
   869  	if len(srcBaseURL) == 0 || len(destBaseURL) == 0 || srcBaseURL != destBaseURL {
   870  		return nil, errors.Errorf("BaseURL for applications %s and %s are not the same. Destination app BaseURL: %s. Source app BaseURL: %s", destID, srcID, destBaseURL, srcBaseURL)
   871  	}
   872  
   873  	srcTemplateID := str.PtrStrToStr(srcApp.ApplicationTemplateID)
   874  	destTemplateID := str.PtrStrToStr(destApp.ApplicationTemplateID)
   875  	if len(srcTemplateID) == 0 || len(destTemplateID) == 0 || srcTemplateID != destTemplateID {
   876  		return nil, errors.Errorf("Application templates are not the same. Destination app template: %s. Source app template: %s", destTemplateID, srcTemplateID)
   877  	}
   878  
   879  	appTemplateLabels, err := s.labelRepo.ListForObject(ctx, appTenant, model.AppTemplateLabelableObject, srcTemplateID)
   880  	if err != nil {
   881  		return nil, errors.Wrapf(err, "while getting labels for app template with id %s", srcTemplateID)
   882  	}
   883  
   884  	if _, exists := appTemplateLabels[s.selfRegisterDistinguishLabelKey]; exists {
   885  		log.C(ctx).Infof("applications should not be merged, because an application template with id %s has label %s", srcTemplateID, s.selfRegisterDistinguishLabelKey)
   886  		return nil, errors.Errorf("app template: %s has label %s", srcTemplateID, s.selfRegisterDistinguishLabelKey)
   887  	}
   888  	if srcApp.Status == nil {
   889  		return nil, errors.Errorf("Could not determine status of source application with id %s", srcID)
   890  	}
   891  
   892  	if srcApp.Status.Condition != model.ApplicationStatusConditionInitial {
   893  		return nil, errors.Errorf("Cannot merge application with id %s, because it is in a %s status", srcID, model.ApplicationStatusConditionConnected)
   894  	}
   895  
   896  	log.C(ctx).Infof("Merging applications with ids %s and %s", destID, srcID)
   897  	if err := mergo.Merge(destApp, *srcApp); err != nil {
   898  		return nil, errors.Wrapf(err, "while trying to merge applications with ids %s and %s", destID, srcID)
   899  	}
   900  
   901  	log.C(ctx).Infof("Merging labels for applications with ids %s and %s", destID, srcID)
   902  	destAppLabelsMerged, err := s.handleMergeLabels(ctx, srcAppLabels, destAppLabels)
   903  	if err != nil {
   904  		return nil, errors.Wrapf(err, "while trying to merge labels for applications with ids %s and %s", destID, srcID)
   905  	}
   906  
   907  	log.C(ctx).Infof("Deleting source application with id %s", srcID)
   908  	if err := s.Delete(ctx, srcID); err != nil {
   909  		return nil, err
   910  	}
   911  
   912  	log.C(ctx).Infof("Updating destination app with id %s", srcID)
   913  	if err := s.appRepo.Update(ctx, appTenant, destApp); err != nil {
   914  		return nil, err
   915  	}
   916  
   917  	if err := s.labelService.UpsertMultipleLabels(ctx, appTenant, model.ApplicationLabelableObject, destID, destAppLabelsMerged); err != nil {
   918  		return nil, err
   919  	}
   920  
   921  	return s.appRepo.GetByID(ctx, appTenant, destID)
   922  }
   923  
   924  // handleMergeLabels merges source labels into destination labels. managedKey label is merged manually.
   925  // It is updated only if the source or destination label have a value "true"
   926  func (s *service) handleMergeLabels(ctx context.Context, srcAppLabels, destAppLabels map[string]*model.Label) (map[string]interface{}, error) {
   927  	destScenarios, ok := destAppLabels[model.ScenariosKey]
   928  	if !ok {
   929  		log.C(ctx).Infof("No %q label found in destination object.", model.ScenariosKey)
   930  		destScenarios = &model.Label{Value: make([]interface{}, 0)}
   931  	}
   932  
   933  	destScenariosStrSlice, err := label.ValueToStringsSlice(destScenarios.Value)
   934  	if err != nil {
   935  		return nil, errors.Wrapf(err, "while converting destination application labels to string slice")
   936  	}
   937  
   938  	if err := mergo.Merge(&destAppLabels, srcAppLabels); err != nil {
   939  		return nil, errors.Wrapf(err, "while trying to merge labels")
   940  	}
   941  
   942  	destAppLabels[model.ScenariosKey].Value = destScenariosStrSlice
   943  
   944  	srcLabelManaged, ok := srcAppLabels[managedKey]
   945  	if !ok {
   946  		log.C(ctx).Infof("No %q label found in source object.", managedKey)
   947  		srcLabelManaged = &model.Label{Value: "false"}
   948  	}
   949  
   950  	srcLabelManagedValue, err := str.CastToBool(srcLabelManaged.Value)
   951  	if err != nil {
   952  		return nil, errors.Wrapf(err, "while converting %s value for source label with ID: %s", managedKey, srcAppLabels[managedKey].ID)
   953  	}
   954  
   955  	destLabelManaged, ok := destAppLabels[managedKey]
   956  	if !ok {
   957  		log.C(ctx).Infof("No %q label found in destination object.", managedKey)
   958  		destLabelManaged = &model.Label{Value: "false"}
   959  	}
   960  
   961  	destLabelManagedValue, err := str.CastToBool(destLabelManaged.Value)
   962  	if err != nil {
   963  		return nil, errors.Wrapf(err, "while converting %s value for destination label with ID: %s", managedKey, destAppLabels[managedKey].ID)
   964  	}
   965  
   966  	if destLabelManagedValue || srcLabelManagedValue {
   967  		destAppLabels[managedKey].Value = "true"
   968  	}
   969  
   970  	conv := make(map[string]interface{}, len(destAppLabels))
   971  	for key, val := range destAppLabels {
   972  		conv[key] = val.Value
   973  	}
   974  
   975  	return conv, nil
   976  }
   977  
   978  // ensureApplicationNotPartOfScenarioWithRuntime Checks if an application has scenarios associated with it. if a runtime is part of any scenario, then the application is considered being used by that runtime.
   979  func (s *service) ensureApplicationNotPartOfScenarioWithRuntime(ctx context.Context, tenant, appID string) error {
   980  	scenarios, err := s.getScenarioNamesForApplication(ctx, appID)
   981  	if err != nil {
   982  		return err
   983  	}
   984  
   985  	if len(scenarios) > 0 {
   986  		runtimes, err := s.getRuntimeNamesForScenarios(ctx, tenant, scenarios)
   987  		if err != nil {
   988  			return err
   989  		}
   990  
   991  		if len(runtimes) > 0 {
   992  			application, err := s.appRepo.GetByID(ctx, tenant, appID)
   993  			if err != nil {
   994  				return errors.Wrapf(err, "while getting application with id %s", appID)
   995  			}
   996  			msg := fmt.Sprintf("System %s is still used and cannot be deleted. Unassign the system from the following formations first: %s. Then, unassign the system from the following runtimes, too: %s", application.Name, strings.Join(scenarios, ", "), strings.Join(runtimes, ", "))
   997  			return apperrors.NewInvalidOperationError(msg)
   998  		}
   999  
  1000  		return nil
  1001  	}
  1002  
  1003  	return nil
  1004  }
  1005  
  1006  // ensureApplicationNotPartOfAnyScenario Checks if an application has scenarios associated with it. If the application is
  1007  // associated with any scenario it can not be deleted before unassigning from that scenario
  1008  func (s *service) ensureApplicationNotPartOfAnyScenario(ctx context.Context, tenant, appID string) error {
  1009  	scenarios, err := s.getScenarioNamesForApplication(ctx, appID)
  1010  	if err != nil {
  1011  		return err
  1012  	}
  1013  
  1014  	if len(scenarios) > 0 {
  1015  		application, err := s.appRepo.GetByID(ctx, tenant, appID)
  1016  		if err != nil {
  1017  			return errors.Wrapf(err, "while getting application with id %s", appID)
  1018  		}
  1019  		msg := fmt.Sprintf("System %s is part of the following formations : %s", application.Name, strings.Join(scenarios, ", "))
  1020  		return apperrors.NewInvalidOperationError(msg)
  1021  	}
  1022  
  1023  	return nil
  1024  }
  1025  
  1026  func (s *service) createRelatedResources(ctx context.Context, in model.ApplicationRegisterInput, tenant string, applicationID string) error {
  1027  	var err error
  1028  	webhooks := make([]*model.Webhook, 0, len(in.Webhooks))
  1029  	for _, item := range in.Webhooks {
  1030  		webhooks = append(webhooks, item.ToWebhook(s.uidService.Generate(), applicationID, model.ApplicationWebhookReference))
  1031  	}
  1032  	if err = s.webhookRepo.CreateMany(ctx, tenant, webhooks); err != nil {
  1033  		return errors.Wrapf(err, "while creating Webhooks for application")
  1034  	}
  1035  
  1036  	return nil
  1037  }
  1038  
  1039  func (s *service) genericCreate(ctx context.Context, in model.ApplicationRegisterInput, repoCreatorFunc repoCreatorFunc) (string, error) {
  1040  	appTenant, err := tenant.LoadFromContext(ctx)
  1041  	if err != nil {
  1042  		return "", err
  1043  	}
  1044  	log.C(ctx).Debugf("Loaded Application Tenant %s from context", appTenant)
  1045  
  1046  	applications, err := s.appRepo.ListAll(ctx, appTenant)
  1047  	if err != nil {
  1048  		return "", err
  1049  	}
  1050  
  1051  	normalizedName := s.appNameNormalizer.Normalize(in.Name)
  1052  	for _, app := range applications {
  1053  		if normalizedName == s.appNameNormalizer.Normalize(app.Name) && in.SystemNumber == app.SystemNumber {
  1054  			return "", apperrors.NewNotUniqueNameError(resource.Application)
  1055  		}
  1056  	}
  1057  
  1058  	exists, err := s.ensureIntSysExists(ctx, in.IntegrationSystemID)
  1059  	if err != nil {
  1060  		return "", errors.Wrap(err, "while ensuring integration system exists")
  1061  	}
  1062  
  1063  	if !exists {
  1064  		return "", apperrors.NewNotFoundError(resource.IntegrationSystem, *in.IntegrationSystemID)
  1065  	}
  1066  
  1067  	id := s.uidService.Generate()
  1068  	log.C(ctx).Debugf("ID %s generated for Application with name %s", id, in.Name)
  1069  
  1070  	app := in.ToApplication(s.timestampGen(), id)
  1071  
  1072  	if err = repoCreatorFunc(ctx, appTenant, app); err != nil {
  1073  		return "", err
  1074  	}
  1075  
  1076  	if in.Labels == nil {
  1077  		in.Labels = map[string]interface{}{}
  1078  	}
  1079  	in.Labels[intSysKey] = ""
  1080  	if in.IntegrationSystemID != nil {
  1081  		in.Labels[intSysKey] = *in.IntegrationSystemID
  1082  	}
  1083  	in.Labels[nameKey] = normalizedName
  1084  
  1085  	var scenariosToAssign []string
  1086  	if scenarioLabel, ok := in.Labels[model.ScenariosKey]; ok {
  1087  		scenariosToAssign, err = label.ValueToStringsSlice(scenarioLabel)
  1088  		if err != nil {
  1089  			return "", errors.Wrapf(err, "while parsing formations from scenario label")
  1090  		}
  1091  
  1092  		// In order for the scenario label not to be attempted to be created during upsert later
  1093  		delete(in.Labels, model.ScenariosKey)
  1094  	}
  1095  
  1096  	err = s.labelService.UpsertMultipleLabels(ctx, appTenant, model.ApplicationLabelableObject, id, in.Labels)
  1097  	if err != nil {
  1098  		return id, errors.Wrapf(err, "while creating multiple labels for Application with id %s", id)
  1099  	}
  1100  
  1101  	if err = s.assignFormations(ctx, appTenant, id, scenariosToAssign, allowAllCriteria); err != nil {
  1102  		return "", errors.Wrapf(err, "while assigning formations")
  1103  	}
  1104  
  1105  	err = s.createRelatedResources(ctx, in, appTenant, app.ID)
  1106  	if err != nil {
  1107  		return "", errors.Wrapf(err, "while creating related resources for Application with id %s", id)
  1108  	}
  1109  
  1110  	if in.Bundles != nil {
  1111  		if err = s.bndlService.CreateMultiple(ctx, resource.Application, id, in.Bundles); err != nil {
  1112  			return "", errors.Wrapf(err, "while creating related Bundle resources for Application with id %s", id)
  1113  		}
  1114  	}
  1115  
  1116  	return id, nil
  1117  }
  1118  
  1119  func (s *service) filterUniqueNonExistingApplications(ctx context.Context, applicationInputs []model.ApplicationRegisterInputWithTemplate) ([]model.ApplicationRegisterInputWithTemplate, error) {
  1120  	appTenant, err := tenant.LoadFromContext(ctx)
  1121  	if err != nil {
  1122  		return nil, errors.Wrap(err, "while loading tenant from context")
  1123  	}
  1124  
  1125  	allApps, err := s.appRepo.ListAll(ctx, appTenant)
  1126  	if err != nil {
  1127  		return nil, errors.Wrapf(err, "while listing all applications for tenant %s", appTenant)
  1128  	}
  1129  	log.C(ctx).Debugf("Found %d existing systems", len(allApps))
  1130  
  1131  	type key struct {
  1132  		name         string
  1133  		systemNumber string
  1134  	}
  1135  
  1136  	uniqueNonExistingApps := make(map[key]int)
  1137  	keys := make([]key, 0)
  1138  	for index, ai := range applicationInputs {
  1139  		alreadyExits := false
  1140  		systemNumber := ""
  1141  		if ai.SystemNumber != nil {
  1142  			systemNumber = *ai.SystemNumber
  1143  		}
  1144  		aiKey := key{
  1145  			name:         ai.Name,
  1146  			systemNumber: systemNumber,
  1147  		}
  1148  
  1149  		if _, found := uniqueNonExistingApps[aiKey]; found {
  1150  			continue
  1151  		}
  1152  
  1153  		for _, a := range allApps {
  1154  			bothSystemsAreWithoutSystemNumber := (ai.SystemNumber == nil && a.SystemNumber == nil)
  1155  			bothSystemsHaveSystemNumber := (ai.SystemNumber != nil && a.SystemNumber != nil && *(ai.SystemNumber) == *(a.SystemNumber))
  1156  			if ai.Name == a.Name && (bothSystemsAreWithoutSystemNumber || bothSystemsHaveSystemNumber) {
  1157  				alreadyExits = true
  1158  				break
  1159  			}
  1160  		}
  1161  
  1162  		if !alreadyExits {
  1163  			uniqueNonExistingApps[aiKey] = index
  1164  			keys = append(keys, aiKey)
  1165  		}
  1166  	}
  1167  
  1168  	result := make([]model.ApplicationRegisterInputWithTemplate, 0, len(uniqueNonExistingApps))
  1169  	for _, key := range keys {
  1170  		appInputIndex := uniqueNonExistingApps[key]
  1171  		result = append(result, applicationInputs[appInputIndex])
  1172  	}
  1173  
  1174  	return result, nil
  1175  }
  1176  
  1177  func createLabel(key string, value string, objectID string) *model.LabelInput {
  1178  	return &model.LabelInput{
  1179  		Key:        key,
  1180  		Value:      value,
  1181  		ObjectID:   objectID,
  1182  		ObjectType: model.ApplicationLabelableObject,
  1183  	}
  1184  }
  1185  
  1186  func (s *service) ensureIntSysExists(ctx context.Context, id *string) (bool, error) {
  1187  	if id == nil {
  1188  		return true, nil
  1189  	}
  1190  
  1191  	log.C(ctx).Infof("Ensuring Integration System with id %s exists", *id)
  1192  	exists, err := s.intSystemRepo.Exists(ctx, *id)
  1193  	if err != nil {
  1194  		return false, err
  1195  	}
  1196  
  1197  	if !exists {
  1198  		log.C(ctx).Infof("Integration System with id %s does not exist", *id)
  1199  		return false, nil
  1200  	}
  1201  	log.C(ctx).Infof("Integration System with id %s exists", *id)
  1202  	return true, nil
  1203  }
  1204  
  1205  func (s *service) getScenarioNamesForApplication(ctx context.Context, applicationID string) ([]string, error) {
  1206  	log.C(ctx).Infof("Getting scenarios for application with id %s", applicationID)
  1207  
  1208  	applicationLabel, err := s.GetLabel(ctx, applicationID, model.ScenariosKey)
  1209  	if err != nil {
  1210  		if apperrors.ErrorCode(err) == apperrors.NotFound {
  1211  			log.C(ctx).Infof("No scenarios found for application")
  1212  			return nil, nil
  1213  		}
  1214  		return nil, err
  1215  	}
  1216  
  1217  	scenarios, err := label.ValueToStringsSlice(applicationLabel.Value)
  1218  	if err != nil {
  1219  		return nil, errors.Wrapf(err, "while parsing application label values")
  1220  	}
  1221  
  1222  	return scenarios, nil
  1223  }
  1224  
  1225  func (s *service) getRuntimeNamesForScenarios(ctx context.Context, tenant string, scenarios []string) ([]string, error) {
  1226  	scenariosQuery := eventing.BuildQueryForScenarios(scenarios)
  1227  	runtimeScenariosFilter := []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, scenariosQuery)}
  1228  
  1229  	log.C(ctx).Debugf("Listing runtimes matching the query %s", scenariosQuery)
  1230  	runtimes, err := s.runtimeRepo.ListAll(ctx, tenant, runtimeScenariosFilter)
  1231  	if err != nil {
  1232  		return nil, errors.Wrapf(err, "while getting runtimes")
  1233  	}
  1234  
  1235  	runtimesNames := make([]string, 0, len(runtimes))
  1236  	for _, r := range runtimes {
  1237  		runtimesNames = append(runtimesNames, r.Name)
  1238  	}
  1239  
  1240  	return runtimesNames, nil
  1241  }
  1242  
  1243  func (s *service) getStoredLabels(ctx context.Context, tenantID, objectID string) ([]string, error) {
  1244  	storedLabel, err := s.labelRepo.GetByKey(ctx, tenantID, model.ApplicationLabelableObject, objectID, model.ScenariosKey)
  1245  	storedLabels := make([]string, 0)
  1246  	if err != nil && apperrors.ErrorCode(err) != apperrors.NotFound {
  1247  		return nil, errors.Wrapf(err, "while getting label with id %s", objectID)
  1248  	} else if err == nil {
  1249  		if storedLabels, err = label.ValueToStringsSlice(storedLabel.Value); err != nil {
  1250  			return nil, errors.Wrapf(err, "while getting label with id %s", objectID)
  1251  		}
  1252  	}
  1253  	return storedLabels, nil
  1254  }
  1255  
  1256  func (s *service) genericUpsert(ctx context.Context, appTenant string, in model.ApplicationRegisterInput, repoUpserterFunc repoUpserterFunc) error {
  1257  	exists, err := s.ensureIntSysExists(ctx, in.IntegrationSystemID)
  1258  	if err != nil {
  1259  		return errors.Wrap(err, "while validating Integration System ID")
  1260  	}
  1261  
  1262  	if !exists {
  1263  		return apperrors.NewNotFoundError(resource.IntegrationSystem, *in.IntegrationSystemID)
  1264  	}
  1265  
  1266  	id := s.uidService.Generate()
  1267  	log.C(ctx).Debugf("ID %s generated for Application with name %s", id, in.Name)
  1268  	app := in.ToApplication(s.timestampGen(), id)
  1269  
  1270  	id, err = repoUpserterFunc(ctx, appTenant, app)
  1271  	if err != nil {
  1272  		return errors.Wrap(err, "while upserting application")
  1273  	}
  1274  
  1275  	app.ID = id
  1276  
  1277  	if in.Labels == nil {
  1278  		in.Labels = map[string]interface{}{}
  1279  	}
  1280  	in.Labels[intSysKey] = ""
  1281  	if in.IntegrationSystemID != nil {
  1282  		in.Labels[intSysKey] = *in.IntegrationSystemID
  1283  	}
  1284  	in.Labels[nameKey] = s.appNameNormalizer.Normalize(app.Name)
  1285  
  1286  	if scenarioLabel, ok := in.Labels[model.ScenariosKey]; ok {
  1287  		if err := s.setScenarioLabel(ctx, appTenant, &model.LabelInput{Value: scenarioLabel, ObjectID: id}); err != nil {
  1288  			return err
  1289  		}
  1290  
  1291  		// In order for the scenario label not to be attempted to be created during upsert later
  1292  		delete(in.Labels, model.ScenariosKey)
  1293  	}
  1294  
  1295  	err = s.labelService.UpsertMultipleLabels(ctx, appTenant, model.ApplicationLabelableObject, id, in.Labels)
  1296  	if err != nil {
  1297  		return errors.Wrapf(err, "while creating multiple labels for Application with id %s", id)
  1298  	}
  1299  
  1300  	appTypeLbl, ok := in.Labels[applicationTypeLabelKey]
  1301  	if !ok {
  1302  		log.C(ctx).Infof("Label %q is missing for %s with id %q. Skipping ord webhook creation", applicationTypeLabelKey, model.ApplicationLabelableObject, app.ID)
  1303  		return nil
  1304  	}
  1305  
  1306  	ppmsProductVersionID := ""
  1307  
  1308  	ppmsProductVersionIDLbl, ok := in.Labels[ppmsProductVersionIDLabelKey]
  1309  	if ppmsProductVersionIDLbl != nil && ok {
  1310  		if ppmsProductVersionIDValue, ok := ppmsProductVersionIDLbl.(string); ok {
  1311  			ppmsProductVersionID = ppmsProductVersionIDValue
  1312  		}
  1313  	}
  1314  
  1315  	ordWebhook := s.prepareORDWebhook(ctx, str.PtrStrToStr(in.BaseURL), appTypeLbl.(string), ppmsProductVersionID)
  1316  	if ordWebhook == nil {
  1317  		log.C(ctx).Infof("Skipping ORD Webhook creation for app with id %q.", app.ID)
  1318  		return nil
  1319  	}
  1320  
  1321  	if in.Webhooks == nil {
  1322  		in.Webhooks = []*model.WebhookInput{}
  1323  	}
  1324  
  1325  	in.Webhooks = append(in.Webhooks, ordWebhook)
  1326  
  1327  	if err = s.createWebhooksIfNotExist(ctx, app.ID, appTenant, in.Webhooks); err != nil {
  1328  		return errors.Wrapf(err, "while processing webhooks for application with id %q", app.ID)
  1329  	}
  1330  
  1331  	return nil
  1332  }
  1333  
  1334  func (s *service) createWebhooksIfNotExist(ctx context.Context, appID, appTenant string, appWebhooks []*model.WebhookInput) error {
  1335  	if len(appWebhooks) == 0 {
  1336  		return nil
  1337  	}
  1338  
  1339  	webhooksFromDB, err := s.webhookRepo.ListByReferenceObjectID(ctx, appTenant, appID, model.ApplicationWebhookReference)
  1340  	if err != nil {
  1341  		return errors.Wrapf(err, "while listig webhooks for application with id %q", appID)
  1342  	}
  1343  
  1344  	webhooks := make([]*model.Webhook, 0, len(appWebhooks))
  1345  	for _, item := range appWebhooks {
  1346  		webhooks = append(webhooks, item.ToWebhook(s.uidService.Generate(), appID, model.ApplicationWebhookReference))
  1347  	}
  1348  
  1349  	webhooksToCreate := make([]*model.Webhook, 0, len(appWebhooks))
  1350  	for _, wh := range webhooks {
  1351  		found := false
  1352  		for _, whDB := range webhooksFromDB {
  1353  			if wh.Type == whDB.Type && wh.ObjectID == whDB.ObjectID && str.PtrStrToStr(wh.URL) == str.PtrStrToStr(whDB.URL) {
  1354  				if (wh.Auth == nil && whDB.Auth == nil) || (wh.Auth != nil && whDB.Auth != nil && str.PtrStrToStr(wh.Auth.AccessStrategy) == str.PtrStrToStr(whDB.Auth.AccessStrategy)) {
  1355  					found = true
  1356  					break
  1357  				}
  1358  			}
  1359  		}
  1360  		if !found {
  1361  			webhooksToCreate = append(webhooksToCreate, wh)
  1362  		}
  1363  	}
  1364  
  1365  	if len(webhooksToCreate) > 0 {
  1366  		if err = s.webhookRepo.CreateMany(ctx, appTenant, webhooksToCreate); err != nil {
  1367  			return errors.Wrapf(err, "while creating webhooks for application with id %q", appID)
  1368  		}
  1369  	}
  1370  	return nil
  1371  }
  1372  
  1373  func (s *service) setScenarioLabel(ctx context.Context, appTenant string, labelInput *model.LabelInput) error {
  1374  	inputFormations, err := label.ValueToStringsSlice(labelInput.Value)
  1375  	if err != nil {
  1376  		return errors.Wrapf(err, "while parsing formations from input label value")
  1377  	}
  1378  
  1379  	inputFormationsMap := createMapFromFormationsSlice(inputFormations)
  1380  
  1381  	storedLabels, err := s.getStoredLabels(ctx, appTenant, labelInput.ObjectID)
  1382  	if err != nil {
  1383  		return errors.Wrapf(err, "while getting stored labels for label with id %s", labelInput.ObjectID)
  1384  	}
  1385  
  1386  	storedFormationsMap := createMapFromFormationsSlice(storedLabels)
  1387  	assignFormationCriteria := func(formation string) bool {
  1388  		_, ok := storedFormationsMap[formation]
  1389  		return !ok
  1390  	}
  1391  	if err = s.assignFormations(ctx, appTenant, labelInput.ObjectID, inputFormations, assignFormationCriteria); err != nil {
  1392  		return errors.Wrapf(err, "while assigning formations")
  1393  	}
  1394  
  1395  	unassignFormationCriteria := func(formation string) bool {
  1396  		_, ok := inputFormationsMap[formation]
  1397  		return !ok
  1398  	}
  1399  
  1400  	if err = s.unassignFormations(ctx, appTenant, labelInput.ObjectID, storedLabels, unassignFormationCriteria); err != nil {
  1401  		return errors.Wrapf(err, "while unnasigning formations")
  1402  	}
  1403  
  1404  	return nil
  1405  }
  1406  
  1407  func (s *service) assignFormations(ctx context.Context, appTenant, objectID string, formations []string, shouldAssignCriteria func(string) bool) error {
  1408  	for _, f := range formations {
  1409  		if shouldAssignCriteria(f) {
  1410  			if _, err := s.formationService.AssignFormation(ctx, appTenant, objectID, graphql.FormationObjectTypeApplication, model.Formation{Name: f}); err != nil {
  1411  				return errors.Wrapf(err, "while assigning formation with name %q from application with id %q", f, objectID)
  1412  			}
  1413  		}
  1414  	}
  1415  	return nil
  1416  }
  1417  
  1418  func (s *service) unassignFormations(ctx context.Context, appTenant, objectID string, formations []string, shouldUnassignCriteria func(string) bool) error {
  1419  	for _, f := range formations {
  1420  		if shouldUnassignCriteria(f) {
  1421  			if _, err := s.formationService.UnassignFormation(ctx, appTenant, objectID, graphql.FormationObjectTypeApplication, model.Formation{Name: f}); err != nil {
  1422  				return errors.Wrapf(err, "while unassigning formation with name %q from application with id %q", f, objectID)
  1423  			}
  1424  		}
  1425  	}
  1426  	return nil
  1427  }
  1428  
  1429  func (s *service) getMappingORDConfiguration(applicationType string) (ORDWebhookMapping, bool) {
  1430  	for _, wm := range s.ordWebhookMapping {
  1431  		if wm.Type == applicationType {
  1432  			return wm, true
  1433  		}
  1434  	}
  1435  	return ORDWebhookMapping{}, false
  1436  }
  1437  
  1438  func (s *service) prepareORDWebhook(ctx context.Context, baseURL, applicationType, ppmsProductVersionID string) *model.WebhookInput {
  1439  	if baseURL == "" {
  1440  		log.C(ctx).Infof("No baseURL found in input. Will not create a webhook")
  1441  		return nil
  1442  	}
  1443  
  1444  	mappingCfg, ok := s.getMappingORDConfiguration(applicationType)
  1445  	if !ok {
  1446  		log.C(ctx).Infof("Missing ord configuration for application type %q", applicationType)
  1447  		return nil
  1448  	}
  1449  
  1450  	if ppmsProductVersionID != "" && !isPpmsProductVersionPresentInConfig(ppmsProductVersionID, mappingCfg) {
  1451  		log.C(ctx).Infof("Product with ppms ID %q is not supported", ppmsProductVersionID)
  1452  		return nil
  1453  	}
  1454  
  1455  	webhookInput, err := createORDWebhookInput(baseURL, mappingCfg.SubdomainSuffix, mappingCfg.OrdURLPath)
  1456  	if err != nil {
  1457  		log.C(ctx).Infof("Creating ORD Webhook failed with error: %v", err)
  1458  		return nil
  1459  	}
  1460  
  1461  	return webhookInput
  1462  }
  1463  
  1464  func isPpmsProductVersionPresentInConfig(ppmsProductVersionID string, mappingCfg ORDWebhookMapping) bool {
  1465  	for _, productVersion := range mappingCfg.PpmsProductVersions {
  1466  		if productVersion == ppmsProductVersionID {
  1467  			return true
  1468  		}
  1469  	}
  1470  
  1471  	return false
  1472  }
  1473  
  1474  func buildWebhookURL(suffix string, ordPath string, baseURL *string) (string, error) {
  1475  	url, err := url.Parse(*baseURL)
  1476  	if err != nil {
  1477  		return "", err
  1478  	}
  1479  
  1480  	hostParts := strings.Split(url.Host, urlSubdomainSeparator)
  1481  
  1482  	if !strings.HasSuffix(hostParts[0], suffix) {
  1483  		hostParts[0] = fmt.Sprintf("%s%s", hostParts[0], suffix)
  1484  	}
  1485  
  1486  	url.Host = strings.Join(hostParts, urlSubdomainSeparator)
  1487  
  1488  	urlStr := strings.TrimSuffix(url.String(), urlSuffixToBeTrimmed)
  1489  
  1490  	return fmt.Sprintf("%s%s", urlStr, ordPath), nil
  1491  }
  1492  
  1493  func createORDWebhookInput(baseURL, suffix, ordPath string) (*model.WebhookInput, error) {
  1494  	webhookURL, err := buildWebhookURL(suffix, ordPath, &baseURL)
  1495  	if err != nil {
  1496  		return nil, err
  1497  	}
  1498  
  1499  	return &model.WebhookInput{
  1500  		Type: model.WebhookTypeOpenResourceDiscovery,
  1501  		URL:  str.Ptr(webhookURL),
  1502  		Auth: &model.AuthInput{
  1503  			AccessStrategy: str.Ptr(string(accessstrategy.CMPmTLSAccessStrategy)),
  1504  		},
  1505  	}, nil
  1506  }
  1507  
  1508  func createMapFromFormationsSlice(formations []string) map[string]struct{} {
  1509  	resultMap := make(map[string]struct{}, len(formations))
  1510  	for _, f := range formations {
  1511  		resultMap[f] = struct{}{}
  1512  	}
  1513  	return resultMap
  1514  }
  1515  
  1516  func allowAllCriteria(_ string) bool {
  1517  	return true
  1518  }