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

     1  package subscription
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/kyma-incubator/compass/components/director/internal/repo"
     9  
    10  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    11  
    12  	"github.com/kyma-incubator/compass/components/director/pkg/graphql"
    13  	"github.com/kyma-incubator/compass/components/director/pkg/inputvalidation"
    14  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    15  
    16  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    17  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    18  	"github.com/kyma-incubator/compass/components/director/internal/model"
    19  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    20  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  // Config is configuration for the tenant subscription flow
    25  type Config struct {
    26  	ProviderLabelKey           string `envconfig:"APP_SUBSCRIPTION_PROVIDER_LABEL_KEY,default=subscriptionProviderId"`
    27  	ConsumerSubaccountLabelKey string `envconfig:"APP_CONSUMER_SUBACCOUNT_LABEL_KEY,default=global_subaccount_id"`
    28  	SubscriptionLabelKey       string `envconfig:"APP_SUBSCRIPTION_LABEL_KEY,default=subscription"`
    29  	RuntimeTypeLabelKey        string `envconfig:"APP_RUNTIME_TYPE_LABEL_KEY,default=runtimeType"`
    30  }
    31  
    32  const (
    33  	// SubdomainLabelKey is the key of the tenant label for subdomain.
    34  	SubdomainLabelKey = "subdomain"
    35  	// RegionPrefix a prefix to be trimmed from the region placeholder value when creating an app from template
    36  	RegionPrefix = "cf-"
    37  	// InstancesLabelKey is the key of the instances label, that stores the number of created instances.
    38  	InstancesLabelKey = "instances"
    39  	// DefaultNumberOfInstancesForAlreadySubscribedTenant is the default number of instances set to instances label value for already subscribed tenant
    40  	DefaultNumberOfInstancesForAlreadySubscribedTenant = 2
    41  )
    42  
    43  // RuntimeService is responsible for Runtime operations
    44  //
    45  //go:generate mockery --name=RuntimeService --output=automock --outpkg=automock --case=underscore --disable-version-string
    46  type RuntimeService interface {
    47  	GetByFiltersGlobal(ctx context.Context, filters []*labelfilter.LabelFilter) (*model.Runtime, error)
    48  	GetByFilters(ctx context.Context, filters []*labelfilter.LabelFilter) (*model.Runtime, error)
    49  }
    50  
    51  // RuntimeCtxService provide functionality to interact with the runtime contexts(create, list, delete).
    52  //
    53  //go:generate mockery --name=RuntimeCtxService --output=automock --outpkg=automock --case=underscore
    54  type RuntimeCtxService interface {
    55  	Create(ctx context.Context, in model.RuntimeContextInput) (string, error)
    56  	Delete(ctx context.Context, id string) error
    57  	ListByFilter(ctx context.Context, runtimeID string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimeContextPage, error)
    58  }
    59  
    60  // TenantService provides functionality for retrieving, and creating tenants.
    61  //
    62  //go:generate mockery --name=TenantService --output=automock --outpkg=automock --case=underscore --unroll-variadic=False --disable-version-string
    63  type TenantService interface {
    64  	GetLowestOwnerForResource(ctx context.Context, resourceType resource.Type, objectID string) (string, error)
    65  	GetInternalTenant(ctx context.Context, externalTenant string) (string, error)
    66  }
    67  
    68  // LabelService is responsible updating already existing labels, and their label definitions.
    69  //
    70  //go:generate mockery --name=LabelService --output=automock --outpkg=automock --case=underscore --disable-version-string
    71  type LabelService interface {
    72  	GetLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) (*model.Label, error)
    73  	CreateLabel(ctx context.Context, tenant, id string, labelInput *model.LabelInput) error
    74  	UpdateLabel(ctx context.Context, tenant, id string, labelInput *model.LabelInput) error
    75  	UpsertLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) error
    76  	GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error)
    77  }
    78  
    79  //go:generate mockery --exported --name=uidService --output=automock --outpkg=automock --case=underscore --disable-version-string
    80  type uidService interface {
    81  	Generate() string
    82  }
    83  
    84  // ApplicationTemplateService is responsible for Application Template operations
    85  //
    86  //go:generate mockery --name=ApplicationTemplateService --output=automock --outpkg=automock --case=underscore --disable-version-string
    87  type ApplicationTemplateService interface {
    88  	Exists(ctx context.Context, id string) (bool, error)
    89  	GetByFilters(ctx context.Context, filter []*labelfilter.LabelFilter) (*model.ApplicationTemplate, error)
    90  	PrepareApplicationCreateInputJSON(appTemplate *model.ApplicationTemplate, values model.ApplicationFromTemplateInputValues) (string, error)
    91  }
    92  
    93  // ApplicationTemplateConverter missing godoc
    94  //
    95  //go:generate mockery --name=ApplicationTemplateConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
    96  type ApplicationTemplateConverter interface {
    97  	ApplicationFromTemplateInputFromGraphQL(appTemplate *model.ApplicationTemplate, in graphql.ApplicationFromTemplateInput) (model.ApplicationFromTemplateInput, error)
    98  }
    99  
   100  // ApplicationConverter is converting graphql and model Applications
   101  //
   102  //go:generate mockery --name=ApplicationConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
   103  type ApplicationConverter interface {
   104  	ToGraphQL(in *model.Application) *graphql.Application
   105  	CreateRegisterInputJSONToGQL(in string) (graphql.ApplicationRegisterInput, error)
   106  	CreateInputFromGraphQL(ctx context.Context, in graphql.ApplicationRegisterInput) (model.ApplicationRegisterInput, error)
   107  }
   108  
   109  // ApplicationService is responsible for Application operations
   110  //
   111  //go:generate mockery --name=ApplicationService --output=automock --outpkg=automock --case=underscore --disable-version-string
   112  type ApplicationService interface {
   113  	CreateFromTemplate(ctx context.Context, in model.ApplicationRegisterInput, appTemplateID *string) (string, error)
   114  	ListAll(ctx context.Context) ([]*model.Application, error)
   115  	Delete(ctx context.Context, id string) error
   116  }
   117  
   118  type service struct {
   119  	runtimeSvc                   RuntimeService
   120  	runtimeCtxSvc                RuntimeCtxService
   121  	tenantSvc                    TenantService
   122  	labelSvc                     LabelService
   123  	appTemplateSvc               ApplicationTemplateService
   124  	appConv                      ApplicationConverter
   125  	appTemplateConv              ApplicationTemplateConverter
   126  	appSvc                       ApplicationService
   127  	uidSvc                       uidService
   128  	consumerSubaccountLabelKey   string
   129  	subscriptionLabelKey         string
   130  	runtimeTypeLabelKey          string
   131  	subscriptionProviderLabelKey string
   132  }
   133  
   134  // NewService returns a new object responsible for service-layer Subscription operations.
   135  func NewService(runtimeSvc RuntimeService, runtimeCtxSvc RuntimeCtxService, tenantSvc TenantService, labelSvc LabelService, appTemplateSvc ApplicationTemplateService, appConv ApplicationConverter, appTemplateConv ApplicationTemplateConverter, appSvc ApplicationService, uidService uidService,
   136  	consumerSubaccountLabelKey, subscriptionLabelKey, runtimeTypeLabelKey, subscriptionProviderLabelKey string) *service {
   137  	return &service{
   138  		runtimeSvc:                   runtimeSvc,
   139  		runtimeCtxSvc:                runtimeCtxSvc,
   140  		tenantSvc:                    tenantSvc,
   141  		labelSvc:                     labelSvc,
   142  		appTemplateSvc:               appTemplateSvc,
   143  		appConv:                      appConv,
   144  		appTemplateConv:              appTemplateConv,
   145  		appSvc:                       appSvc,
   146  		uidSvc:                       uidService,
   147  		consumerSubaccountLabelKey:   consumerSubaccountLabelKey,
   148  		subscriptionLabelKey:         subscriptionLabelKey,
   149  		runtimeTypeLabelKey:          runtimeTypeLabelKey,
   150  		subscriptionProviderLabelKey: subscriptionProviderLabelKey,
   151  	}
   152  }
   153  
   154  // SubscribeTenantToRuntime subscribes a tenant to runtimes by labeling the runtime
   155  func (s *service) SubscribeTenantToRuntime(ctx context.Context, providerID, subaccountTenantID, providerSubaccountID, consumerTenantID, region, subscriptionAppName string) (bool, error) {
   156  	log.C(ctx).Infof("Subscribe request is triggerred between consumer with tenant: %q and subaccount: %q and provider with subaccount: %q and application name: %q", consumerTenantID, subaccountTenantID, providerSubaccountID, subscriptionAppName)
   157  	providerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, providerSubaccountID)
   158  	if err != nil {
   159  		return false, errors.Wrapf(err, "while getting provider subaccount internal ID from external ID: %q", providerSubaccountID)
   160  	}
   161  	ctx = tenant.SaveToContext(ctx, providerInternalTenant, providerSubaccountID)
   162  
   163  	filters := s.buildLabelFilters(providerID, region)
   164  	log.C(ctx).Infof("Getting provider runtime in tenant %q for labels %q: %q and %q: %q", providerSubaccountID, tenant.RegionLabelKey, region, s.subscriptionProviderLabelKey, providerID)
   165  	runtime, err := s.runtimeSvc.GetByFilters(ctx, filters)
   166  	if err != nil {
   167  		if apperrors.IsNotFoundError(err) {
   168  			return false, nil
   169  		}
   170  
   171  		return false, errors.Wrap(err, fmt.Sprintf("Failed to get runtime for labels %q: %q and %q: %q", tenant.RegionLabelKey, region, s.subscriptionProviderLabelKey, providerID))
   172  	}
   173  
   174  	consumerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, subaccountTenantID)
   175  	if err != nil {
   176  		log.C(ctx).Errorf("An error occurred while getting tenant by external ID: %q during subscription: %v", subaccountTenantID, err)
   177  		return false, errors.Wrapf(err, "while getting tenant with external ID: %q", subaccountTenantID)
   178  	}
   179  
   180  	runtimeID := runtime.ID
   181  	log.C(ctx).Infof("Listing runtime context(s) in the consumer tenant %q for label with key: %q and value: %q", subaccountTenantID, s.consumerSubaccountLabelKey, subaccountTenantID)
   182  	rtmCtxPage, err := s.runtimeCtxSvc.ListByFilter(tenant.SaveToContext(ctx, consumerInternalTenant, subaccountTenantID), runtimeID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(s.consumerSubaccountLabelKey, fmt.Sprintf("\"%s\"", subaccountTenantID))}, 100, "")
   183  	if err != nil {
   184  		log.C(ctx).Errorf("An error occurred while listing runtime contexts with key: %q and value: %q for runtime with ID: %q: %v", s.consumerSubaccountLabelKey, subaccountTenantID, runtimeID, err)
   185  		return false, err
   186  	}
   187  	log.C(ctx).Infof("Found %d runtime context(s) with key: %q and value: %q for runtime with ID: %q", len(rtmCtxPage.Data), s.consumerSubaccountLabelKey, subaccountTenantID, runtimeID)
   188  
   189  	for _, rtmCtx := range rtmCtxPage.Data {
   190  		if rtmCtx.Value == consumerTenantID {
   191  			// Already subscribed
   192  			log.C(ctx).Infof("Consumer %q is already subscribed. Increasing the %q label value by one", consumerTenantID, InstancesLabelKey)
   193  			if err := s.manageInstancesLabelOnSubscribe(ctx, consumerInternalTenant, model.RuntimeContextLabelableObject, rtmCtx.ID); err != nil {
   194  				return false, err
   195  			}
   196  			return true, nil
   197  		}
   198  	}
   199  
   200  	tnt, err := s.tenantSvc.GetLowestOwnerForResource(ctx, resource.Runtime, runtime.ID)
   201  	if err != nil {
   202  		log.C(ctx).Errorf("An error occurred while getting lowest owner for resource type: %q with ID: %q: %v", resource.Runtime, runtime.ID, err)
   203  		return false, err
   204  	}
   205  
   206  	log.C(ctx).Debugf("Upserting runtime label with key: %q and value: %q", s.runtimeTypeLabelKey, subscriptionAppName)
   207  	if err := s.labelSvc.UpsertLabel(ctx, tnt, &model.LabelInput{
   208  		Key:        s.runtimeTypeLabelKey,
   209  		Value:      subscriptionAppName,
   210  		ObjectType: model.RuntimeLabelableObject,
   211  		ObjectID:   runtime.ID,
   212  	}); err != nil {
   213  		log.C(ctx).Errorf("An error occurred while upserting label with key: %q and value: %q for object type: %q and ID: %q: %v", s.runtimeTypeLabelKey, subscriptionAppName, model.RuntimeLabelableObject, runtime.ID, err)
   214  		return false, err
   215  	}
   216  
   217  	ctx = tenant.SaveToContext(ctx, consumerInternalTenant, subaccountTenantID)
   218  
   219  	m2mTable, ok := resource.Runtime.TenantAccessTable()
   220  	if !ok {
   221  		return false, errors.Errorf("entity %s does not have access table", resource.Runtime)
   222  	}
   223  
   224  	if err := repo.UpsertTenantAccessRecursively(ctx, m2mTable, &repo.TenantAccess{
   225  		TenantID:   consumerInternalTenant,
   226  		ResourceID: runtime.ID,
   227  		Owner:      false,
   228  	}); err != nil {
   229  		return false, err
   230  	}
   231  
   232  	rtmCtxID, err := s.runtimeCtxSvc.Create(ctx, model.RuntimeContextInput{
   233  		Key:       s.subscriptionLabelKey,
   234  		Value:     consumerTenantID,
   235  		RuntimeID: runtime.ID,
   236  	})
   237  	if err != nil {
   238  		log.C(ctx).Errorf("An error occurred while creating runtime context with key: %q and value: %q, and runtime ID: %q: %v", s.subscriptionLabelKey, consumerTenantID, runtime.ID, err)
   239  		return false, errors.Wrapf(err, "while creating runtime context with value: %q and runtime ID: %q during subscription", consumerTenantID, runtime.ID)
   240  	}
   241  
   242  	log.C(ctx).Infof("Creating label for runtime context with ID: %q with key: %q and value: %q", rtmCtxID, s.consumerSubaccountLabelKey, subaccountTenantID)
   243  	if err := s.labelSvc.CreateLabel(ctx, consumerInternalTenant, s.uidSvc.Generate(), &model.LabelInput{
   244  		Key:        s.consumerSubaccountLabelKey,
   245  		Value:      subaccountTenantID,
   246  		ObjectID:   rtmCtxID,
   247  		ObjectType: model.RuntimeContextLabelableObject,
   248  	}); err != nil {
   249  		log.C(ctx).Errorf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q: %v", s.consumerSubaccountLabelKey, subaccountTenantID, model.RuntimeContextLabelableObject, rtmCtxID, err)
   250  		return false, errors.Wrap(err, fmt.Sprintf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q", s.consumerSubaccountLabelKey, subaccountTenantID, model.RuntimeContextLabelableObject, rtmCtxID))
   251  	}
   252  
   253  	log.C(ctx).Infof("Creating label for runtime context with ID: %q with key: %q and value: %q", rtmCtxID, InstancesLabelKey, 1)
   254  	if err := s.labelSvc.CreateLabel(ctx, consumerInternalTenant, s.uidSvc.Generate(), &model.LabelInput{
   255  		Key:        InstancesLabelKey,
   256  		Value:      1,
   257  		ObjectID:   rtmCtxID,
   258  		ObjectType: model.RuntimeContextLabelableObject,
   259  	}); err != nil {
   260  		log.C(ctx).Errorf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q: %v", InstancesLabelKey, 1, model.RuntimeContextLabelableObject, rtmCtxID, err)
   261  		return false, errors.Wrap(err, fmt.Sprintf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q", InstancesLabelKey, 1, model.RuntimeContextLabelableObject, rtmCtxID))
   262  	}
   263  
   264  	return true, nil
   265  }
   266  
   267  // UnsubscribeTenantFromRuntime unsubscribes a tenant from runtimes by removing labels from runtime
   268  func (s *service) UnsubscribeTenantFromRuntime(ctx context.Context, providerID, subaccountTenantID, providerSubaccountID, consumerTenantID, region string) (bool, error) {
   269  	log.C(ctx).Infof("Unsubscribe request is triggerred between consumer with tenant: %q and subaccount: %q and provider with subaccount: %q", consumerTenantID, subaccountTenantID, providerSubaccountID)
   270  	providerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, providerSubaccountID)
   271  	if err != nil {
   272  		return false, errors.Wrapf(err, "while getting provider subaccount internal ID from external ID: %q", providerSubaccountID)
   273  	}
   274  	ctx = tenant.SaveToContext(ctx, providerInternalTenant, providerSubaccountID)
   275  
   276  	filters := s.buildLabelFilters(providerID, region)
   277  	log.C(ctx).Infof("Getting provider runtime in tenant %q for labels %q: %q and %q: %q", providerSubaccountID, tenant.RegionLabelKey, region, s.subscriptionProviderLabelKey, providerID)
   278  	runtime, err := s.runtimeSvc.GetByFilters(ctx, filters)
   279  	if err != nil {
   280  		if apperrors.IsNotFoundError(err) {
   281  			return false, nil
   282  		}
   283  
   284  		return false, errors.Wrap(err, fmt.Sprintf("Failed to get runtime for labels %q: %q and %q: %q", tenant.RegionLabelKey, region, s.subscriptionProviderLabelKey, providerID))
   285  	}
   286  
   287  	consumerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, subaccountTenantID)
   288  	if err != nil {
   289  		log.C(ctx).Errorf("An error occurred while getting tenant by external ID: %q during subscription: %v", subaccountTenantID, err)
   290  		return false, errors.Wrapf(err, "while getting tenant with external ID: %q", subaccountTenantID)
   291  	}
   292  	ctx = tenant.SaveToContext(ctx, consumerInternalTenant, subaccountTenantID)
   293  
   294  	runtimeID := runtime.ID
   295  	log.C(ctx).Infof("Listing runtime context(s) in the consumer tenant %q for label with key: %q and value: %q", subaccountTenantID, s.consumerSubaccountLabelKey, subaccountTenantID)
   296  	rtmCtxPage, err := s.runtimeCtxSvc.ListByFilter(ctx, runtimeID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(s.consumerSubaccountLabelKey, fmt.Sprintf("\"%s\"", subaccountTenantID))}, 100, "")
   297  	if err != nil {
   298  		log.C(ctx).Errorf("An error occurred while listing runtime contexts with key: %q and value: %q for runtime with ID: %q: %v", s.consumerSubaccountLabelKey, subaccountTenantID, runtimeID, err)
   299  		return false, err
   300  	}
   301  	log.C(ctx).Infof("Found %d runtime context(s) with key: %q and value: %q for runtime with ID: %q", len(rtmCtxPage.Data), s.consumerSubaccountLabelKey, subaccountTenantID, runtimeID)
   302  
   303  	for _, rtmCtx := range rtmCtxPage.Data {
   304  		// if the current subscription(runtime context) is the one for which the unsubscribe request is initiated, delete the record from the DB
   305  		if rtmCtx.Value == consumerTenantID {
   306  			if err := s.deleteOnUnsubscribe(ctx, consumerInternalTenant, model.RuntimeContextLabelableObject, rtmCtx.ID, s.runtimeCtxSvc.Delete); err != nil {
   307  				return false, err
   308  			}
   309  			break
   310  		}
   311  	}
   312  
   313  	return true, nil
   314  }
   315  
   316  // SubscribeTenantToApplication fetches model.ApplicationTemplate by region and provider and registers an Application from that template
   317  func (s *service) SubscribeTenantToApplication(ctx context.Context, providerID, subscribedSubaccountID, consumerTenantID, region, subscribedAppName string, subscriptionPayload string) (bool, error) {
   318  	filters := s.buildLabelFilters(providerID, region)
   319  	appTemplate, err := s.appTemplateSvc.GetByFilters(ctx, filters)
   320  	if err != nil {
   321  		if apperrors.IsNotFoundError(err) {
   322  			return false, nil
   323  		}
   324  
   325  		return false, errors.Wrapf(err, "while getting application template with filter labels %q and %q", providerID, region)
   326  	}
   327  
   328  	consumerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, subscribedSubaccountID)
   329  	if err != nil {
   330  		log.C(ctx).Errorf("An error occurred while getting tenant by external ID: %q during application subscription: %v", subscribedSubaccountID, err)
   331  		return false, errors.Wrapf(err, "while getting tenant with external ID: %q", subscribedSubaccountID)
   332  	}
   333  
   334  	ctx = tenant.SaveToContext(ctx, consumerInternalTenant, subscribedSubaccountID)
   335  
   336  	applications, err := s.appSvc.ListAll(ctx)
   337  	if err != nil {
   338  		return false, errors.Wrapf(err, "while listing applications")
   339  	}
   340  
   341  	for _, app := range applications {
   342  		if str.PtrStrToStr(app.ApplicationTemplateID) == appTemplate.ID {
   343  			// Already subscribed
   344  			log.C(ctx).Infof("Consumer %q is already subscribed. Increasing the %q label value by one", consumerTenantID, InstancesLabelKey)
   345  			if err := s.manageInstancesLabelOnSubscribe(ctx, consumerInternalTenant, model.ApplicationLabelableObject, app.ID); err != nil {
   346  				return false, err
   347  			}
   348  			return true, nil
   349  		}
   350  	}
   351  
   352  	subdomainLabel, err := s.labelSvc.GetByKey(ctx, consumerInternalTenant, model.TenantLabelableObject, consumerInternalTenant, SubdomainLabelKey)
   353  	if err != nil {
   354  		if !apperrors.IsNotFoundError(err) {
   355  			return false, errors.Wrapf(err, "while getting label %q for %q with id %q", SubdomainLabelKey, model.TenantLabelableObject, consumerInternalTenant)
   356  		}
   357  	}
   358  
   359  	subdomainValue := ""
   360  	if subdomainLabel != nil && subdomainLabel.Value != nil {
   361  		if subdomainLabelValue, ok := subdomainLabel.Value.(string); ok {
   362  			subdomainValue = subdomainLabelValue
   363  		}
   364  	}
   365  
   366  	if err := s.createApplicationFromTemplate(ctx, appTemplate, subscribedSubaccountID, consumerTenantID, subscribedAppName, subdomainValue, region, subscriptionPayload); err != nil {
   367  		return false, err
   368  	}
   369  
   370  	return true, nil
   371  }
   372  
   373  // UnsubscribeTenantFromApplication fetches model.ApplicationTemplate by region and provider, lists all applications for
   374  // the subscribedSubaccountID tenant and deletes them synchronously
   375  func (s *service) UnsubscribeTenantFromApplication(ctx context.Context, providerID, subscribedSubaccountID, region string) (bool, error) {
   376  	filters := s.buildLabelFilters(providerID, region)
   377  	appTemplate, err := s.appTemplateSvc.GetByFilters(ctx, filters)
   378  	if err != nil {
   379  		if apperrors.IsNotFoundError(err) {
   380  			return false, nil
   381  		}
   382  
   383  		return false, errors.Wrapf(err, "while getting application template with filter labels %q and %q", providerID, region)
   384  	}
   385  
   386  	consumerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, subscribedSubaccountID)
   387  	if err != nil {
   388  		log.C(ctx).Errorf("An error occurred while getting tenant by external ID: %q during application unsubscription: %v", subscribedSubaccountID, err)
   389  		return false, errors.Wrapf(err, "while getting tenant with external ID: %q", subscribedSubaccountID)
   390  	}
   391  
   392  	ctx = tenant.SaveToContext(ctx, consumerInternalTenant, subscribedSubaccountID)
   393  
   394  	if err := s.deleteApplicationsByAppTemplateID(ctx, appTemplate.ID); err != nil {
   395  		return false, err
   396  	}
   397  
   398  	return true, nil
   399  }
   400  
   401  // DetermineSubscriptionFlow determines if the subscription flow is resource.ApplicationTemplate or resource.Runtime
   402  // by fetching both resources by provider and region
   403  func (s *service) DetermineSubscriptionFlow(ctx context.Context, providerID, region string) (resource.Type, error) {
   404  	filters := s.buildLabelFilters(providerID, region)
   405  	runtime, err := s.runtimeSvc.GetByFiltersGlobal(ctx, filters)
   406  	if err != nil {
   407  		if !apperrors.IsNotFoundError(err) {
   408  			return "", errors.Wrapf(err, "while getting runtime with filter labels provider (%q) and region (%q)", providerID, region)
   409  		}
   410  	}
   411  
   412  	appTemplate, err := s.appTemplateSvc.GetByFilters(ctx, filters)
   413  	if err != nil {
   414  		if !apperrors.IsNotFoundError(err) {
   415  			return "", errors.Wrapf(err, "while getting app template with filter labels provider (%q) and region (%q)", providerID, region)
   416  		}
   417  	}
   418  
   419  	if runtime != nil && appTemplate == nil {
   420  		return resource.Runtime, nil
   421  	}
   422  
   423  	if runtime == nil && appTemplate != nil {
   424  		return resource.ApplicationTemplate, nil
   425  	}
   426  
   427  	if runtime == nil && appTemplate == nil {
   428  		return "", nil
   429  	}
   430  
   431  	if runtime != nil && appTemplate != nil {
   432  		return "", errors.Errorf("both a runtime (%+v) and application template (%+v) exist with filter labels provider (%q) and region (%q)", runtime, appTemplate, providerID, region)
   433  	}
   434  
   435  	return "", errors.Errorf("could not determine flow")
   436  }
   437  
   438  func (s *service) createApplicationFromTemplate(ctx context.Context, appTemplate *model.ApplicationTemplate, subscribedSubaccountID, consumerTenantID, subscribedAppName, subdomain, region string, subscriptionPayload string) error {
   439  	log.C(ctx).Debugf("Preparing Values for Application Template with name %q", appTemplate.Name)
   440  	values, err := s.preparePlaceholderValues(appTemplate, subdomain, region, subscriptionPayload)
   441  	if err != nil {
   442  		return errors.Wrapf(err, "while preparing the values for Application template %q", appTemplate.Name)
   443  	}
   444  
   445  	log.C(ctx).Debugf("Preparing ApplicationCreateInput JSON from Application Template with name %q", appTemplate.Name)
   446  	appCreateInputJSON, err := s.appTemplateSvc.PrepareApplicationCreateInputJSON(appTemplate, values)
   447  	if err != nil {
   448  		return errors.Wrapf(err, "while preparing ApplicationCreateInput JSON from Application Template with name %q", appTemplate.Name)
   449  	}
   450  
   451  	log.C(ctx).Debugf("Converting ApplicationCreateInput JSON to GraphQL ApplicationRegistrationInput from Application Template with name %q", appTemplate.Name)
   452  	appCreateInputGQL, err := s.appConv.CreateRegisterInputJSONToGQL(appCreateInputJSON)
   453  	if err != nil {
   454  		return errors.Wrapf(err, "while converting ApplicationCreateInput JSON to GraphQL ApplicationRegistrationInput from Application Template with name %q", appTemplate.Name)
   455  	}
   456  
   457  	log.C(ctx).Infof("Validating GraphQL ApplicationRegistrationInput from Application Template with name %q", appTemplate.Name)
   458  	if err := inputvalidation.Validate(appCreateInputGQL); err != nil {
   459  		return errors.Wrapf(err, "while validating application input from Application Template with name %q", appTemplate.Name)
   460  	}
   461  
   462  	appCreateInputModel, err := s.appConv.CreateInputFromGraphQL(ctx, appCreateInputGQL)
   463  	if err != nil {
   464  		return errors.Wrap(err, "while converting ApplicationFromTemplate input")
   465  	}
   466  
   467  	if appCreateInputModel.Labels == nil {
   468  		appCreateInputModel.Labels = make(map[string]interface{})
   469  	}
   470  	appCreateInputModel.Labels["managed"] = "false"
   471  	appCreateInputModel.Labels[InstancesLabelKey] = 1
   472  	appCreateInputModel.Labels[s.consumerSubaccountLabelKey] = subscribedSubaccountID
   473  	appCreateInputModel.LocalTenantID = &consumerTenantID
   474  
   475  	log.C(ctx).Infof("Creating an Application with name %q from Application Template with name %q", subscribedAppName, appTemplate.Name)
   476  	_, err = s.appSvc.CreateFromTemplate(ctx, appCreateInputModel, &appTemplate.ID)
   477  	if err != nil {
   478  		return errors.Wrapf(err, "while creating an Application with name %s from Application Template with name %s", subscribedAppName, appTemplate.Name)
   479  	}
   480  
   481  	return nil
   482  }
   483  
   484  func (s *service) preparePlaceholderValues(appTemplate *model.ApplicationTemplate, subdomain, region string, subscriptionPayload string) ([]*model.ApplicationTemplateValueInput, error) {
   485  	values := []*model.ApplicationTemplateValueInput{
   486  		{Placeholder: "subdomain", Value: subdomain},
   487  		{Placeholder: "region", Value: strings.TrimPrefix(region, RegionPrefix)},
   488  	}
   489  
   490  	oldPlaceholders := appTemplate.Placeholders
   491  
   492  	newPlaceholders := []model.ApplicationTemplatePlaceholder{}
   493  	for _, placeholder := range oldPlaceholders {
   494  		if placeholder.Name != "subdomain" && placeholder.Name != "region" {
   495  			newPlaceholders = append(newPlaceholders, placeholder)
   496  		}
   497  	}
   498  	appTemplate.Placeholders = newPlaceholders
   499  
   500  	appFromTemplateInput, err := s.appTemplateConv.ApplicationFromTemplateInputFromGraphQL(appTemplate, graphql.ApplicationFromTemplateInput{
   501  		TemplateName:        appTemplate.Name,
   502  		PlaceholdersPayload: &subscriptionPayload,
   503  	})
   504  
   505  	if err != nil {
   506  		return nil, errors.Wrapf(err, "while parsing the callback payload with the Application template %q", appTemplate.Name)
   507  	}
   508  
   509  	appTemplate.Placeholders = oldPlaceholders
   510  	values = append(appFromTemplateInput.Values, values...)
   511  	return values, nil
   512  }
   513  
   514  func (s *service) deleteApplicationsByAppTemplateID(ctx context.Context, appTemplateID string) error {
   515  	applications, err := s.appSvc.ListAll(ctx)
   516  	if err != nil {
   517  		return errors.Wrapf(err, "while listing applications")
   518  	}
   519  
   520  	for _, app := range applications {
   521  		if str.PtrStrToStr(app.ApplicationTemplateID) == appTemplateID {
   522  			internalTenant, err := tenant.LoadFromContext(ctx)
   523  			if err != nil {
   524  				return errors.Wrapf(err, "An error occurred while loading tenant from context")
   525  			}
   526  			if err := s.deleteOnUnsubscribe(ctx, internalTenant, model.ApplicationLabelableObject, app.ID, s.appSvc.Delete); err != nil {
   527  				return err
   528  			}
   529  		}
   530  	}
   531  
   532  	return nil
   533  }
   534  
   535  func (s *service) manageInstancesLabelOnSubscribe(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) error {
   536  	instancesLabel, err := s.labelSvc.GetByKey(ctx, tenant, objectType, objectID, InstancesLabelKey)
   537  	if err != nil {
   538  		if !apperrors.IsNotFoundError(err) {
   539  			log.C(ctx).WithError(err).Errorf("An error occurred while getting label with key: %q for object type: %q and ID: %q", InstancesLabelKey, objectType, objectID)
   540  			return errors.Wrapf(err, "while getting label with key: %q for object type: %q and ID: %q", InstancesLabelKey, objectType, objectID)
   541  		}
   542  
   543  		if err := s.labelSvc.CreateLabel(ctx, tenant, s.uidSvc.Generate(), &model.LabelInput{
   544  			Key:        InstancesLabelKey,
   545  			Value:      DefaultNumberOfInstancesForAlreadySubscribedTenant,
   546  			ObjectID:   objectID,
   547  			ObjectType: objectType,
   548  		}); err != nil {
   549  			log.C(ctx).WithError(err).Errorf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q", InstancesLabelKey, DefaultNumberOfInstancesForAlreadySubscribedTenant, objectType, objectID)
   550  			return errors.Wrapf(err, "while creating label with key: %q and value: %q for object type: %q and ID: %q", InstancesLabelKey, DefaultNumberOfInstancesForAlreadySubscribedTenant, objectType, objectID)
   551  		}
   552  
   553  		log.C(ctx).Debugf("%q label created, for already subscibed tenant to %q with id %q", InstancesLabelKey, objectType, objectID)
   554  		return nil
   555  	}
   556  
   557  	instances, ok := instancesLabel.Value.(float64)
   558  	if !ok {
   559  		return errors.Errorf("cannot cast %q label value of type %T to int", InstancesLabelKey, instancesLabel.Value)
   560  	}
   561  
   562  	log.C(ctx).Debugf("Increasing %q label value. Current value %f", InstancesLabelKey, instances)
   563  	instances++
   564  	if err := s.labelSvc.UpdateLabel(ctx, tenant, instancesLabel.ID, &model.LabelInput{
   565  		Key:        instancesLabel.Key,
   566  		Value:      instances,
   567  		ObjectID:   instancesLabel.ObjectID,
   568  		ObjectType: instancesLabel.ObjectType,
   569  		Version:    instancesLabel.Version,
   570  	}); err != nil {
   571  		log.C(ctx).WithError(err).Errorf("An error occurred while updating label with key: %q and value: %f for object type: %q and ID: %q", InstancesLabelKey, instances, objectType, objectID)
   572  		return errors.Wrapf(err, "while updating label with key: %q and value: %f for object type: %q and ID: %q", InstancesLabelKey, instances, objectType, objectID)
   573  	}
   574  
   575  	log.C(ctx).Debugf("Successfully increased %q label value to %f for %q with id %q", InstancesLabelKey, instances, objectType, objectID)
   576  	return nil
   577  }
   578  
   579  func (s *service) deleteOnUnsubscribe(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, deleteObject func(context.Context, string) error) error {
   580  	instancesLabel, err := s.labelSvc.GetByKey(ctx, tenant, objectType, objectID, InstancesLabelKey)
   581  	if err != nil {
   582  		if !apperrors.IsNotFoundError(err) {
   583  			log.C(ctx).WithError(err).Errorf("An error occurred while getting label with key: %q for object type: %q and ID: %q", InstancesLabelKey, objectType, objectID)
   584  			return errors.Wrapf(err, "while getting label with key: %q for object type: %q and ID: %q", InstancesLabelKey, objectType, objectID)
   585  		}
   586  
   587  		log.C(ctx).Debugf("Cannot find label with key %q for %q with ID %q. Triggering deletion of %q with ID %q...", InstancesLabelKey, objectType, objectID, objectType, objectID)
   588  		if err := deleteObject(ctx, objectID); err != nil {
   589  			log.C(ctx).WithError(err).Errorf("An error occurred while trying to delete %q with ID: %q", objectType, objectID)
   590  			return errors.Wrapf(err, "while trying to delete %q with ID: %q", objectType, objectID)
   591  		}
   592  		log.C(ctx).Infof("Successfully deleted %q with ID %q", objectType, objectID)
   593  		return nil
   594  	}
   595  
   596  	instances, ok := instancesLabel.Value.(float64)
   597  	if !ok {
   598  		return errors.Errorf("cannot cast %q label value of type %T to int", InstancesLabelKey, instancesLabel.Value)
   599  	}
   600  
   601  	if instances <= 1 {
   602  		log.C(ctx).Debugf("The number of %q for %q with ID %q is <=1. Triggering deletion of %q with ID %q...", InstancesLabelKey, objectType, objectID, objectType, objectID)
   603  		if err := deleteObject(ctx, objectID); err != nil {
   604  			log.C(ctx).WithError(err).Errorf("An error occurred while deleting %q with ID: %q", objectType, objectID)
   605  			return errors.Wrapf(err, "while deleting %q with ID: %q", objectType, objectID)
   606  		}
   607  		log.C(ctx).Infof("Successfully deleted %q with ID %q", objectType, objectID)
   608  		return nil
   609  	}
   610  
   611  	instances--
   612  	if err := s.labelSvc.UpdateLabel(ctx, tenant, instancesLabel.ID, &model.LabelInput{
   613  		Key:        instancesLabel.Key,
   614  		Value:      instances,
   615  		ObjectID:   instancesLabel.ObjectID,
   616  		ObjectType: instancesLabel.ObjectType,
   617  		Version:    instancesLabel.Version,
   618  	}); err != nil {
   619  		log.C(ctx).WithError(err).Errorf("An error occurred while updating label with key: %q and value: %f for object type: %q and ID: %q", InstancesLabelKey, instances, objectType, objectID)
   620  		return errors.Wrapf(err, "while updating label with key: %q and value: %f for object type: %q and ID: %q", InstancesLabelKey, instances, objectType, objectID)
   621  	}
   622  	log.C(ctx).Debugf("Successfully decreased %q label value to %f for %q with ID %q", InstancesLabelKey, instances, objectType, objectID)
   623  
   624  	return nil
   625  }
   626  
   627  func (s *service) buildLabelFilters(subscriptionProviderID, region string) []*labelfilter.LabelFilter {
   628  	return []*labelfilter.LabelFilter{
   629  		labelfilter.NewForKeyWithQuery(s.subscriptionProviderLabelKey, fmt.Sprintf("\"%s\"", subscriptionProviderID)),
   630  		labelfilter.NewForKeyWithQuery(tenant.RegionLabelKey, fmt.Sprintf("\"%s\"", region)),
   631  	}
   632  }