github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/eventing/service.go (about) 1 package eventing 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/kyma-incubator/compass/components/director/pkg/normalizer" 8 9 "github.com/kyma-incubator/compass/components/director/internal/domain/label" 10 11 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 12 13 "strings" 14 15 "github.com/google/uuid" 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/pkg/errors" 20 ) 21 22 const ( 23 isNormalizedLabel = "isNormalized" 24 // RuntimeEventingURLLabel missing godoc 25 RuntimeEventingURLLabel = "runtime_eventServiceUrl" 26 // EmptyEventingURL missing godoc 27 EmptyEventingURL = "" 28 // RuntimeDefaultEventingLabelf missing godoc 29 RuntimeDefaultEventingLabelf = "%s_defaultEventing" 30 ) 31 32 // RuntimeRepository missing godoc 33 //go:generate mockery --name=RuntimeRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 34 type RuntimeRepository interface { 35 GetByFiltersAndID(ctx context.Context, tenant, id string, filter []*labelfilter.LabelFilter) (*model.Runtime, error) 36 GetOldestForFilters(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) (*model.Runtime, error) 37 List(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimePage, error) 38 } 39 40 // LabelRepository missing godoc 41 //go:generate mockery --name=LabelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 42 type LabelRepository interface { 43 Delete(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, key string) error 44 GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error) 45 DeleteByKey(ctx context.Context, tenant string, key string) error 46 Upsert(ctx context.Context, tenant string, label *model.Label) error 47 } 48 49 type service struct { 50 appNameNormalizer normalizer.Normalizator 51 runtimeRepo RuntimeRepository 52 labelRepo LabelRepository 53 } 54 55 // NewService missing godoc 56 func NewService(appNameNormalizer normalizer.Normalizator, runtimeRepo RuntimeRepository, labelRepo LabelRepository) *service { 57 return &service{ 58 appNameNormalizer: appNameNormalizer, 59 runtimeRepo: runtimeRepo, 60 labelRepo: labelRepo, 61 } 62 } 63 64 // CleanupAfterUnregisteringApplication missing godoc 65 func (s *service) CleanupAfterUnregisteringApplication(ctx context.Context, appID uuid.UUID) (*model.ApplicationEventingConfiguration, error) { 66 tenantID, err := tenant.LoadFromContext(ctx) 67 if err != nil { 68 return nil, errors.Wrapf(err, "while loading tenant from context") 69 } 70 71 labelKey := getDefaultEventingForAppLabelKey(appID) 72 err = s.labelRepo.DeleteByKey(ctx, tenantID, labelKey) 73 if err != nil { 74 return nil, errors.Wrapf(err, "while deleting Labels for Application with id %s", appID) 75 } 76 77 return model.NewEmptyApplicationEventingConfig() 78 } 79 80 // SetForApplication missing godoc 81 func (s *service) SetForApplication(ctx context.Context, runtimeID uuid.UUID, app model.Application) (*model.ApplicationEventingConfiguration, error) { 82 tenantID, err := tenant.LoadFromContext(ctx) 83 if err != nil { 84 return nil, errors.Wrapf(err, "while loading tenant from context") 85 } 86 87 appID, err := uuid.Parse(app.ID) 88 if err != nil { 89 return nil, errors.Wrapf(err, "while parsing application ID: %s", app.ID) 90 } 91 92 _, _, err = s.unsetForApplication(ctx, tenantID, appID) 93 if err != nil { 94 return nil, errors.Wrap(err, "while deleting default eventing for application") 95 } 96 97 runtime, found, err := s.getRuntimeForApplicationScenarios(ctx, tenantID, runtimeID, appID) 98 if err != nil { 99 return nil, errors.Wrap(err, "while getting the runtime") 100 } 101 102 if !found { 103 return nil, fmt.Errorf("does not find the given runtime [ID=%s] assigned to the application scenarios", runtimeID) 104 } 105 106 if err := s.setRuntimeForAppEventing(ctx, tenantID, *runtime, appID); err != nil { 107 return nil, errors.Wrap(err, "while setting the runtime as default for eventing for application") 108 } 109 110 runtimeEventingCfg, err := s.GetForRuntime(ctx, runtimeID) 111 if err != nil { 112 return nil, errors.Wrap(err, "while fetching eventing configuration for runtime") 113 } 114 115 shouldNormalize, err := s.shouldNormalizeApplicationName(ctx, tenantID, runtime) 116 if err != nil { 117 return nil, errors.Wrap(err, "while determining whether application name should be normalized in runtime eventing URL") 118 } 119 120 appName := app.Name 121 if shouldNormalize { 122 appName = s.appNameNormalizer.Normalize(app.Name) 123 } 124 125 return model.NewApplicationEventingConfiguration(runtimeEventingCfg.DefaultURL, appName) 126 } 127 128 // UnsetForApplication missing godoc 129 func (s *service) UnsetForApplication(ctx context.Context, app model.Application) (*model.ApplicationEventingConfiguration, error) { 130 tenantID, err := tenant.LoadFromContext(ctx) 131 if err != nil { 132 return nil, errors.Wrapf(err, "while loading tenant from context") 133 } 134 135 appID, err := uuid.Parse(app.ID) 136 if err != nil { 137 return nil, errors.Wrapf(err, "while parsing application ID: %s", app.ID) 138 } 139 140 runtime, found, err := s.unsetForApplication(ctx, tenantID, appID) 141 if err != nil { 142 return nil, errors.Wrap(err, "while deleting default eventing for application") 143 } 144 145 if !found { 146 return model.NewEmptyApplicationEventingConfig() 147 } 148 149 runtimeID, err := uuid.Parse(runtime.ID) 150 if err != nil { 151 return nil, errors.Wrap(err, "while parsing runtime ID as UUID") 152 } 153 154 runtimeEventingCfg, err := s.GetForRuntime(ctx, runtimeID) 155 if err != nil { 156 return nil, errors.Wrap(err, "while fetching eventing configuration for runtime") 157 } 158 159 shouldNormalize, err := s.shouldNormalizeApplicationName(ctx, tenantID, runtime) 160 if err != nil { 161 return nil, errors.Wrap(err, "while determining whether application name should be normalized in runtime eventing URL") 162 } 163 164 appName := app.Name 165 if shouldNormalize { 166 appName = s.appNameNormalizer.Normalize(app.Name) 167 } 168 169 return model.NewApplicationEventingConfiguration(runtimeEventingCfg.DefaultURL, appName) 170 } 171 172 // GetForApplication missing godoc 173 func (s *service) GetForApplication(ctx context.Context, app model.Application) (*model.ApplicationEventingConfiguration, error) { 174 tenantID, err := tenant.LoadFromContext(ctx) 175 if err != nil { 176 return nil, errors.Wrapf(err, "while loading tenant from context") 177 } 178 179 appID, err := uuid.Parse(app.ID) 180 if err != nil { 181 return nil, errors.Wrapf(err, "while parsing application ID: %s", app.ID) 182 } 183 184 var defaultVerified, foundDefault, foundOldest bool 185 runtime, foundDefault, err := s.getDefaultRuntimeForAppEventing(ctx, tenantID, appID) 186 if err != nil { 187 return nil, errors.Wrap(err, "while getting default runtime for app eventing") 188 } 189 190 if foundDefault { 191 if defaultVerified, err = s.ensureScenariosOrDeleteLabel(ctx, tenantID, *runtime, appID); err != nil { 192 return nil, errors.Wrap(err, "while ensuring the scenarios assigned to the runtime and application") 193 } 194 } 195 196 if !defaultVerified { 197 runtime, foundOldest, err = s.getOldestRuntime(ctx, tenantID, appID) 198 if err != nil { 199 return nil, errors.Wrap(err, "while getting the oldest runtime for scenarios") 200 } 201 202 if foundOldest { 203 if err := s.setRuntimeForAppEventing(ctx, tenantID, *runtime, appID); err != nil { 204 return nil, errors.Wrap(err, "while setting the runtime as default for eventing for application") 205 } 206 } 207 } 208 209 if runtime == nil { 210 return model.NewEmptyApplicationEventingConfig() 211 } 212 213 runtimeID, err := uuid.Parse(runtime.ID) 214 if err != nil { 215 return nil, errors.Wrap(err, "while parsing runtime ID as UUID") 216 } 217 218 runtimeEventingCfg, err := s.GetForRuntime(ctx, runtimeID) 219 if err != nil { 220 return nil, errors.Wrap(err, "while fetching eventing configuration for runtime") 221 } 222 223 shouldNormalize, err := s.shouldNormalizeApplicationName(ctx, tenantID, runtime) 224 if err != nil { 225 return nil, errors.Wrap(err, "while determining whether application name should be normalized in runtime eventing URL") 226 } 227 228 appName := app.Name 229 if shouldNormalize { 230 appName = s.appNameNormalizer.Normalize(app.Name) 231 } 232 233 if app.SystemNumber != nil { 234 appName += "-" + *app.SystemNumber 235 } 236 237 return model.NewApplicationEventingConfiguration(runtimeEventingCfg.DefaultURL, appName) 238 } 239 240 // GetForRuntime missing godoc 241 func (s *service) GetForRuntime(ctx context.Context, runtimeID uuid.UUID) (*model.RuntimeEventingConfiguration, error) { 242 tenantID, err := tenant.LoadFromContext(ctx) 243 if err != nil { 244 return nil, errors.Wrapf(err, "while loading tenant from context") 245 } 246 247 var eventingURL string 248 label, err := s.labelRepo.GetByKey(ctx, tenantID, model.RuntimeLabelableObject, runtimeID.String(), RuntimeEventingURLLabel) 249 if err != nil { 250 if !apperrors.IsNotFoundError(err) { 251 return nil, errors.Wrap(err, fmt.Sprintf("while getting the label [key=%s] for runtime [ID=%s]", RuntimeEventingURLLabel, runtimeID)) 252 } 253 254 return model.NewRuntimeEventingConfiguration(EmptyEventingURL) 255 } 256 257 if label != nil { 258 var ok bool 259 if eventingURL, ok = label.Value.(string); !ok { 260 return nil, fmt.Errorf("unable to cast label [key=%s, runtimeID=%s] value as a string", RuntimeEventingURLLabel, runtimeID) 261 } 262 } 263 264 return model.NewRuntimeEventingConfiguration(eventingURL) 265 } 266 267 func (s *service) shouldNormalizeApplicationName(ctx context.Context, tenant string, runtime *model.Runtime) (bool, error) { 268 label, err := s.labelRepo.GetByKey(ctx, tenant, model.RuntimeLabelableObject, runtime.ID, isNormalizedLabel) 269 notFoundErr := apperrors.IsNotFoundError(err) 270 if !notFoundErr && err != nil { 271 return false, errors.Wrap(err, fmt.Sprintf("while getting the label [key=%s] for runtime [ID=%s]", isNormalizedLabel, runtime.ID)) 272 } 273 274 return notFoundErr || label.Value == "true", nil 275 } 276 277 func (s *service) unsetForApplication(ctx context.Context, tenantID string, appID uuid.UUID) (*model.Runtime, bool, error) { 278 runtime, foundDefault, err := s.getDefaultRuntimeForAppEventing(ctx, tenantID, appID) 279 if err != nil { 280 return nil, false, errors.Wrap(err, "while getting default runtime for app eventing") 281 } 282 283 if !foundDefault { 284 return nil, foundDefault, nil 285 } 286 287 runtimeID, err := uuid.Parse(runtime.ID) 288 if err != nil { 289 return nil, foundDefault, errors.Wrap(err, "while parsing runtime ID as UUID") 290 } 291 292 labelKey := getDefaultEventingForAppLabelKey(appID) 293 err = s.deleteLabelFromRuntime(ctx, tenantID, labelKey, runtimeID) 294 if err != nil { 295 return nil, foundDefault, errors.Wrap(err, "while deleting label") 296 } 297 298 return runtime, foundDefault, nil 299 } 300 301 func (s *service) getDefaultRuntimeForAppEventing(ctx context.Context, tenantID string, appID uuid.UUID) (*model.Runtime, bool, error) { 302 labelKey := getDefaultEventingForAppLabelKey(appID) 303 labelFilterForRuntime := []*labelfilter.LabelFilter{labelfilter.NewForKey(labelKey)} 304 305 var cursor string 306 runtimesPage, err := s.runtimeRepo.List(ctx, tenantID, labelFilterForRuntime, 1, cursor) 307 if err != nil { 308 return nil, false, errors.Wrap(err, fmt.Sprintf("while fetching runtimes with label [key=%s]", labelKey)) 309 } 310 311 if runtimesPage.TotalCount == 0 { 312 return nil, false, nil 313 } 314 315 if runtimesPage.TotalCount > 1 { 316 return nil, false, fmt.Errorf("got multpile runtimes labeled [key=%s] as default for eventing", labelKey) 317 } 318 319 runtime := runtimesPage.Data[0] 320 321 return runtime, true, nil 322 } 323 324 func (s *service) ensureScenariosOrDeleteLabel(ctx context.Context, tenantID string, runtime model.Runtime, appID uuid.UUID) (bool, error) { 325 runtimeID, err := uuid.Parse(runtime.ID) 326 if err != nil { 327 return false, errors.Wrap(err, "while parsing runtime ID as UUID") 328 } 329 330 _, belongsToScenarios, err := s.getRuntimeForApplicationScenarios(ctx, tenantID, runtimeID, appID) 331 if err != nil { 332 return false, errors.Wrap(err, fmt.Sprintf("while verifing whether runtime [ID=%s] belongs to the application scenarios", runtimeID)) 333 } 334 335 if !belongsToScenarios { 336 labelKey := getDefaultEventingForAppLabelKey(appID) 337 if err = s.deleteLabelFromRuntime(ctx, tenantID, labelKey, runtimeID); err != nil { 338 return false, errors.Wrap(err, "when deleting current default runtime for the application because of scenarios mismatch") 339 } 340 } 341 342 return belongsToScenarios, nil 343 } 344 345 func (s *service) getRuntimeForApplicationScenarios(ctx context.Context, tenantID string, runtimeID, appID uuid.UUID) (*model.Runtime, bool, error) { 346 runtimeScenariosFilter, hasScenarios, err := s.getScenariosFilter(ctx, tenantID, appID) 347 if err != nil { 348 return nil, false, errors.Wrap(err, "while getting application scenarios") 349 } 350 351 if !hasScenarios { 352 return nil, false, nil 353 } 354 355 runtime, err := s.runtimeRepo.GetByFiltersAndID(ctx, tenantID, runtimeID.String(), runtimeScenariosFilter) 356 if err != nil { 357 if !apperrors.IsNotFoundError(err) { 358 return nil, false, errors.Wrap(err, fmt.Sprintf("while getting the runtime [ID=%s] with scenarios with filter", runtimeID)) 359 } 360 361 return nil, false, nil 362 } 363 364 return runtime, true, nil 365 } 366 367 func (s *service) deleteLabelFromRuntime(ctx context.Context, tenantID, labelKey string, runtimeID uuid.UUID) error { 368 if err := s.labelRepo.Delete(ctx, tenantID, model.RuntimeLabelableObject, runtimeID.String(), labelKey); err != nil { 369 return errors.Wrap(err, fmt.Sprintf("while deleting label [key=%s, runtimeID=%s]", labelKey, runtimeID)) 370 } 371 372 return nil 373 } 374 375 func (s *service) getOldestRuntime(ctx context.Context, tenantID string, appID uuid.UUID) (*model.Runtime, bool, error) { 376 runtimeScenariosFilter, hasScenarios, err := s.getScenariosFilter(ctx, tenantID, appID) 377 if err != nil { 378 return nil, false, errors.Wrap(err, "while getting application scenarios") 379 } 380 381 if !hasScenarios { 382 return nil, false, nil 383 } 384 385 runtime, err := s.runtimeRepo.GetOldestForFilters(ctx, tenantID, runtimeScenariosFilter) 386 if err != nil { 387 if !apperrors.IsNotFoundError(err) { 388 return nil, false, errors.Wrap(err, fmt.Sprintf("while getting the oldest runtime for application [ID=%s] scenarios with filter", appID)) 389 } 390 391 return nil, false, nil 392 } 393 394 return runtime, true, nil 395 } 396 397 func (s *service) getScenariosFilter(ctx context.Context, tenantID string, appID uuid.UUID) ([]*labelfilter.LabelFilter, bool, error) { 398 appScenariosLabel, err := s.labelRepo.GetByKey(ctx, tenantID, model.ApplicationLabelableObject, appID.String(), model.ScenariosKey) 399 if err != nil { 400 if !apperrors.IsNotFoundError(err) { 401 return nil, false, errors.Wrap(err, fmt.Sprintf("while getting the label [key=%s] for application [ID=%s]", model.ScenariosKey, appID)) 402 } 403 404 return nil, false, nil 405 } 406 407 scenarios, err := label.ValueToStringsSlice(appScenariosLabel.Value) 408 if err != nil { 409 return nil, false, errors.Wrap(err, fmt.Sprintf("while converting label [key=%s] value to a slice of strings", model.ScenariosKey)) 410 } 411 412 scenariosQuery := BuildQueryForScenarios(scenarios) 413 runtimeScenariosFilter := []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, scenariosQuery)} 414 415 return runtimeScenariosFilter, true, nil 416 } 417 418 func (s *service) setRuntimeForAppEventing(ctx context.Context, tenant string, runtime model.Runtime, appID uuid.UUID) error { 419 defaultEventingForAppLabel := model.NewLabelForRuntime(runtime.ID, tenant, getDefaultEventingForAppLabelKey(appID), "true") 420 if err := s.labelRepo.Upsert(ctx, tenant, defaultEventingForAppLabel); err != nil { 421 return errors.Wrap(err, fmt.Sprintf("while labeling the runtime [ID=%s] as default for eventing for application [ID=%s]", runtime.ID, appID)) 422 } 423 424 return nil 425 } 426 427 // BuildQueryForScenarios missing godoc 428 func BuildQueryForScenarios(scenarios []string) string { 429 var queryBuilder strings.Builder 430 for idx, scenario := range scenarios { 431 if idx > 0 { 432 queryBuilder.WriteString(` || `) 433 } 434 435 queryBuilder.WriteString(fmt.Sprintf(`@ == "%s"`, scenario)) 436 } 437 query := fmt.Sprintf(`$[*] ? ( %s )`, queryBuilder.String()) 438 439 return query 440 } 441 442 func getDefaultEventingForAppLabelKey(appID uuid.UUID) string { 443 return fmt.Sprintf(RuntimeDefaultEventingLabelf, appID.String()) 444 }