github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/spec/service.go (about) 1 package spec 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/kyma-incubator/compass/components/director/pkg/resource" 8 9 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 10 "github.com/kyma-incubator/compass/components/director/internal/timestamp" 11 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 12 "github.com/pkg/errors" 13 14 "github.com/kyma-incubator/compass/components/director/internal/model" 15 ) 16 17 // SpecRepository missing godoc 18 // 19 //go:generate mockery --name=SpecRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 20 type SpecRepository interface { 21 Create(ctx context.Context, tenant string, item *model.Spec) error 22 CreateGlobal(ctx context.Context, item *model.Spec) error 23 GetByID(ctx context.Context, tenantID string, id string, objectType model.SpecReferenceObjectType) (*model.Spec, error) 24 GetByIDGlobal(ctx context.Context, id string) (*model.Spec, error) 25 ListIDByReferenceObjectID(ctx context.Context, tenant string, objectType model.SpecReferenceObjectType, objectID string) ([]string, error) 26 ListIDByReferenceObjectIDGlobal(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string) ([]string, error) 27 ListByReferenceObjectID(ctx context.Context, tenant string, objectType model.SpecReferenceObjectType, objectID string) ([]*model.Spec, error) 28 ListByReferenceObjectIDGlobal(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string) ([]*model.Spec, error) 29 ListByReferenceObjectIDs(ctx context.Context, tenant string, objectType model.SpecReferenceObjectType, objectIDs []string) ([]*model.Spec, error) 30 Delete(ctx context.Context, tenant, id string, objectType model.SpecReferenceObjectType) error 31 DeleteByReferenceObjectID(ctx context.Context, tenant string, objectType model.SpecReferenceObjectType, objectID string) error 32 DeleteByReferenceObjectIDGlobal(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string) error 33 Update(ctx context.Context, tenant string, item *model.Spec) error 34 UpdateGlobal(ctx context.Context, item *model.Spec) error 35 Exists(ctx context.Context, tenantID, id string, objectType model.SpecReferenceObjectType) (bool, error) 36 } 37 38 // FetchRequestRepository missing godoc 39 // 40 //go:generate mockery --name=FetchRequestRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 41 type FetchRequestRepository interface { 42 Create(ctx context.Context, tenant string, item *model.FetchRequest) error 43 CreateGlobal(ctx context.Context, item *model.FetchRequest) error 44 GetByReferenceObjectID(ctx context.Context, tenant string, objectType model.FetchRequestReferenceObjectType, objectID string) (*model.FetchRequest, error) 45 DeleteByReferenceObjectID(ctx context.Context, tenant string, objectType model.FetchRequestReferenceObjectType, objectID string) error 46 DeleteByReferenceObjectIDGlobal(ctx context.Context, objectType model.FetchRequestReferenceObjectType, objectID string) error 47 ListByReferenceObjectIDs(ctx context.Context, tenant string, objectType model.FetchRequestReferenceObjectType, objectIDs []string) ([]*model.FetchRequest, error) 48 ListByReferenceObjectIDsGlobal(ctx context.Context, objectType model.FetchRequestReferenceObjectType, objectIDs []string) ([]*model.FetchRequest, error) 49 } 50 51 // UIDService missing godoc 52 // 53 //go:generate mockery --name=UIDService --output=automock --outpkg=automock --case=underscore --disable-version-string 54 type UIDService interface { 55 Generate() string 56 } 57 58 // FetchRequestService missing godoc 59 // 60 //go:generate mockery --name=FetchRequestService --output=automock --outpkg=automock --case=underscore --disable-version-string 61 type FetchRequestService interface { 62 HandleSpec(ctx context.Context, fr *model.FetchRequest) *string 63 } 64 65 type service struct { 66 repo SpecRepository 67 fetchRequestRepo FetchRequestRepository 68 uidService UIDService 69 fetchRequestService FetchRequestService 70 timestampGen timestamp.Generator 71 } 72 73 // NewService missing godoc 74 func NewService(repo SpecRepository, fetchRequestRepo FetchRequestRepository, uidService UIDService, fetchRequestService FetchRequestService) *service { 75 return &service{ 76 repo: repo, 77 fetchRequestRepo: fetchRequestRepo, 78 uidService: uidService, 79 fetchRequestService: fetchRequestService, 80 timestampGen: timestamp.DefaultGenerator, 81 } 82 } 83 84 // GetByID takes care of retrieving a specific spec entity from db based on a provided id and objectType (API or Event) 85 func (s *service) GetByID(ctx context.Context, id string, objectType model.SpecReferenceObjectType) (*model.Spec, error) { 86 tnt, err := tenant.LoadFromContext(ctx) 87 if err != nil { 88 return nil, err 89 } 90 91 return s.repo.GetByID(ctx, tnt, id, objectType) 92 } 93 94 func (s *service) GetByIDGlobal(ctx context.Context, id string) (*model.Spec, error) { 95 return s.repo.GetByIDGlobal(ctx, id) 96 } 97 98 // ListByReferenceObjectID missing godoc 99 func (s *service) ListByReferenceObjectID(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string) ([]*model.Spec, error) { 100 tnt, err := tenant.LoadFromContext(ctx) 101 if err != nil { 102 return nil, err 103 } 104 105 return s.repo.ListByReferenceObjectID(ctx, tnt, objectType, objectID) 106 } 107 108 // ListIDByReferenceObjectID retrieves all spec ids by objectType and objectID 109 func (s *service) ListIDByReferenceObjectID(ctx context.Context, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) ([]string, error) { 110 if resourceType.IsTenantIgnorable() { 111 return s.repo.ListIDByReferenceObjectIDGlobal(ctx, objectType, objectID) 112 } 113 114 tnt, err := tenant.LoadFromContext(ctx) 115 if err != nil { 116 return nil, err 117 } 118 119 return s.repo.ListIDByReferenceObjectID(ctx, tnt, objectType, objectID) 120 } 121 122 // GetByReferenceObjectID 123 // Until now APIs and Events had embedded specification in them, we will model this behavior by relying that the first created spec is the one which GraphQL expects 124 func (s *service) GetByReferenceObjectID(ctx context.Context, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) (*model.Spec, error) { 125 specs, err := s.listSpecsByReferenceObjectID(ctx, objectType, objectID, resourceType) 126 if err != nil { 127 return nil, err 128 } 129 130 if len(specs) > 0 { 131 return specs[0], nil 132 } 133 134 return nil, nil 135 } 136 137 // ListByReferenceObjectIDs missing godoc 138 func (s *service) ListByReferenceObjectIDs(ctx context.Context, objectType model.SpecReferenceObjectType, objectIDs []string) ([]*model.Spec, error) { 139 tnt, err := tenant.LoadFromContext(ctx) 140 if err != nil { 141 return nil, err 142 } 143 144 specs, err := s.repo.ListByReferenceObjectIDs(ctx, tnt, objectType, objectIDs) 145 146 return specs, err 147 } 148 149 // CreateByReferenceObjectID missing godoc 150 func (s *service) CreateByReferenceObjectID(ctx context.Context, in model.SpecInput, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) (string, error) { 151 id := s.uidService.Generate() 152 spec, err := in.ToSpec(id, objectType, objectID) 153 if err != nil { 154 return "", err 155 } 156 157 if err = s.createSpec(ctx, spec, resourceType); err != nil { 158 return "", errors.Wrapf(err, "while creating spec for %q with id %q", objectType, objectID) 159 } 160 161 if in.Data == nil && in.FetchRequest != nil { 162 fr, err := s.createFetchRequest(ctx, *in.FetchRequest, id, objectType, resourceType) 163 if err != nil { 164 return "", errors.Wrapf(err, "while creating FetchRequest for %s Specification with id %q", objectType, id) 165 } 166 167 spec.Data = s.fetchRequestService.HandleSpec(ctx, fr) 168 169 if err = s.updateSpec(ctx, spec, resourceType); err != nil { 170 return "", errors.Wrapf(err, "while updating %s Specification with id %q", objectType, id) 171 } 172 } 173 174 return id, nil 175 } 176 177 // CreateByReferenceObjectIDWithDelayedFetchRequest identical to CreateByReferenceObjectID with the only difference that the spec and fetch request entities are only persisted in DB and the fetch request itself is not executed 178 func (s *service) CreateByReferenceObjectIDWithDelayedFetchRequest(ctx context.Context, in model.SpecInput, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) (string, *model.FetchRequest, error) { 179 id := s.uidService.Generate() 180 spec, err := in.ToSpec(id, objectType, objectID) 181 if err != nil { 182 return "", nil, err 183 } 184 185 if err = s.createSpec(ctx, spec, resourceType); err != nil { 186 return "", nil, errors.Wrapf(err, "while creating spec for %q with id %q", objectType, objectID) 187 } 188 189 var fr *model.FetchRequest 190 if in.Data == nil && in.FetchRequest != nil { 191 fr, err = s.createFetchRequest(ctx, *in.FetchRequest, id, objectType, resourceType) 192 if err != nil { 193 return "", nil, errors.Wrapf(err, "while creating FetchRequest for %s Specification with id %q", objectType, id) 194 } 195 } 196 197 return id, fr, nil 198 } 199 200 // UpdateByReferenceObjectID missing godoc 201 func (s *service) UpdateByReferenceObjectID(ctx context.Context, id string, in model.SpecInput, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) error { 202 if err := s.deleteFetchRequestByReferenceObjectID(ctx, id, objectType, resourceType); err != nil { 203 return errors.Wrapf(err, "while deleting FetchRequest for Specification with id %q", id) 204 } 205 206 spec, err := in.ToSpec(id, objectType, objectID) 207 if err != nil { 208 return err 209 } 210 211 if in.Data == nil && in.FetchRequest != nil { 212 fr, err := s.createFetchRequest(ctx, *in.FetchRequest, id, objectType, resourceType) 213 if err != nil { 214 return errors.Wrapf(err, "while creating FetchRequest for %s Specification with id %q", objectType, id) 215 } 216 217 spec.Data = s.fetchRequestService.HandleSpec(ctx, fr) 218 } 219 220 if err = s.updateSpec(ctx, spec, resourceType); err != nil { 221 return errors.Wrapf(err, "while updating %s Specification with id %q", objectType, id) 222 } 223 224 return nil 225 } 226 227 // UpdateSpecOnly takes care of simply updating a single spec entity in db without looking and executing corresponding fetch requests that may be related to it 228 func (s *service) UpdateSpecOnly(ctx context.Context, spec model.Spec) error { 229 tnt, err := tenant.LoadFromContext(ctx) 230 if err != nil { 231 return err 232 } 233 234 if err = s.repo.Update(ctx, tnt, &spec); err != nil { 235 return errors.Wrapf(err, "while updating %s Specification with id %q", spec.ObjectType, spec.ID) 236 } 237 238 return nil 239 } 240 241 // UpdateSpecOnlyGlobal takes care of simply updating a single spec entity in db without looking and executing corresponding fetch requests that may be related to it 242 func (s *service) UpdateSpecOnlyGlobal(ctx context.Context, spec model.Spec) error { 243 if err := s.repo.UpdateGlobal(ctx, &spec); err != nil { 244 return errors.Wrapf(err, "while updating %s Specification with id %q", spec.ObjectType, spec.ID) 245 } 246 247 return nil 248 } 249 250 // DeleteByReferenceObjectID missing godoc 251 func (s *service) DeleteByReferenceObjectID(ctx context.Context, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) error { 252 if err := s.deleteSpecByReferenceObjectID(ctx, objectType, objectID, resourceType); err != nil { 253 return errors.Wrapf(err, "while deleting reference object type %s with id %s", objectType, objectID) 254 } 255 256 return nil 257 } 258 259 // Delete missing godoc 260 func (s *service) Delete(ctx context.Context, id string, objectType model.SpecReferenceObjectType) error { 261 tnt, err := tenant.LoadFromContext(ctx) 262 if err != nil { 263 return err 264 } 265 266 err = s.repo.Delete(ctx, tnt, id, objectType) 267 if err != nil { 268 return errors.Wrapf(err, "while deleting Specification with id %q", id) 269 } 270 271 return nil 272 } 273 274 // RefetchSpec missing godoc 275 func (s *service) RefetchSpec(ctx context.Context, id string, objectType model.SpecReferenceObjectType) (*model.Spec, error) { 276 tnt, err := tenant.LoadFromContext(ctx) 277 if err != nil { 278 return nil, err 279 } 280 281 spec, err := s.repo.GetByID(ctx, tnt, id, objectType) 282 if err != nil { 283 return nil, err 284 } 285 286 fetchRequest, err := s.fetchRequestRepo.GetByReferenceObjectID(ctx, tnt, getFetchRequestObjectTypeBySpecObjectType(objectType), id) 287 if err != nil && !apperrors.IsNotFoundError(err) { 288 return nil, errors.Wrapf(err, "while getting FetchRequest for Specification with id %q", id) 289 } 290 291 if fetchRequest != nil { 292 spec.Data = s.fetchRequestService.HandleSpec(ctx, fetchRequest) 293 } 294 295 if err = s.repo.Update(ctx, tnt, spec); err != nil { 296 return nil, errors.Wrapf(err, "while updating Specification with id %q", id) 297 } 298 299 return spec, nil 300 } 301 302 // GetFetchRequest missing godoc 303 func (s *service) GetFetchRequest(ctx context.Context, specID string, objectType model.SpecReferenceObjectType) (*model.FetchRequest, error) { 304 tnt, err := tenant.LoadFromContext(ctx) 305 if err != nil { 306 return nil, err 307 } 308 309 exists, err := s.repo.Exists(ctx, tnt, specID, objectType) 310 if err != nil { 311 return nil, errors.Wrapf(err, "while checking if Specification with id %q exists", specID) 312 } 313 if !exists { 314 return nil, fmt.Errorf("specification with id %q doesn't exist", specID) 315 } 316 317 fetchRequest, err := s.fetchRequestRepo.GetByReferenceObjectID(ctx, tnt, getFetchRequestObjectTypeBySpecObjectType(objectType), specID) 318 if err != nil { 319 return nil, errors.Wrapf(err, "while getting FetchRequest by Specification with id %q", specID) 320 } 321 322 return fetchRequest, nil 323 } 324 325 // ListFetchRequestsByReferenceObjectIDs lists specs by reference object IDs 326 func (s *service) ListFetchRequestsByReferenceObjectIDs(ctx context.Context, tenant string, objectIDs []string, objectType model.SpecReferenceObjectType) ([]*model.FetchRequest, error) { 327 return s.fetchRequestRepo.ListByReferenceObjectIDs(ctx, tenant, getFetchRequestObjectTypeBySpecObjectType(objectType), objectIDs) 328 } 329 330 // ListFetchRequestsByReferenceObjectIDsGlobal lists specs by reference object IDs without tenant isolation 331 func (s *service) ListFetchRequestsByReferenceObjectIDsGlobal(ctx context.Context, objectIDs []string, objectType model.SpecReferenceObjectType) ([]*model.FetchRequest, error) { 332 return s.fetchRequestRepo.ListByReferenceObjectIDsGlobal(ctx, getFetchRequestObjectTypeBySpecObjectType(objectType), objectIDs) 333 } 334 335 func (s *service) createFetchRequest(ctx context.Context, in model.FetchRequestInput, parentObjectID string, objectType model.SpecReferenceObjectType, resourceType resource.Type) (*model.FetchRequest, error) { 336 id := s.uidService.Generate() 337 fr := in.ToFetchRequest(s.timestampGen(), id, getFetchRequestObjectTypeBySpecObjectType(objectType), parentObjectID) 338 339 if resourceType.IsTenantIgnorable() { 340 if err := s.fetchRequestRepo.CreateGlobal(ctx, fr); err != nil { 341 return nil, err 342 } 343 344 return fr, nil 345 } 346 347 tnt, err := tenant.LoadFromContext(ctx) 348 if err != nil { 349 return nil, err 350 } 351 352 if err = s.fetchRequestRepo.Create(ctx, tnt, fr); err != nil { 353 return nil, err 354 } 355 356 return fr, nil 357 } 358 359 func (s *service) listSpecsByReferenceObjectID(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string, resourceType resource.Type) ([]*model.Spec, error) { 360 if resourceType.IsTenantIgnorable() { 361 return s.repo.ListByReferenceObjectIDGlobal(ctx, objectType, objectID) 362 } 363 364 tnt, err := tenant.LoadFromContext(ctx) 365 if err != nil { 366 return nil, err 367 } 368 369 return s.repo.ListByReferenceObjectID(ctx, tnt, objectType, objectID) 370 } 371 372 func (s *service) createSpec(ctx context.Context, spec *model.Spec, resourceType resource.Type) error { 373 if resourceType.IsTenantIgnorable() { 374 return s.repo.CreateGlobal(ctx, spec) 375 } 376 377 tnt, err := tenant.LoadFromContext(ctx) 378 if err != nil { 379 return err 380 } 381 382 return s.repo.Create(ctx, tnt, spec) 383 } 384 385 func (s *service) updateSpec(ctx context.Context, spec *model.Spec, resourceType resource.Type) error { 386 if resourceType.IsTenantIgnorable() { 387 return s.repo.UpdateGlobal(ctx, spec) 388 } 389 390 tnt, err := tenant.LoadFromContext(ctx) 391 if err != nil { 392 return err 393 } 394 395 return s.repo.Update(ctx, tnt, spec) 396 } 397 398 func (s *service) deleteSpecByReferenceObjectID(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string, resourceType resource.Type) error { 399 if resourceType.IsTenantIgnorable() { 400 return s.repo.DeleteByReferenceObjectIDGlobal(ctx, objectType, objectID) 401 } 402 403 tnt, err := tenant.LoadFromContext(ctx) 404 if err != nil { 405 return err 406 } 407 408 return s.repo.DeleteByReferenceObjectID(ctx, tnt, objectType, objectID) 409 } 410 411 func (s *service) deleteFetchRequestByReferenceObjectID(ctx context.Context, id string, objectType model.SpecReferenceObjectType, resourceType resource.Type) error { 412 if resourceType.IsTenantIgnorable() { 413 if _, err := s.repo.GetByIDGlobal(ctx, id); err != nil { 414 return err 415 } 416 417 return s.fetchRequestRepo.DeleteByReferenceObjectIDGlobal(ctx, getFetchRequestObjectTypeBySpecObjectType(objectType), id) 418 } 419 420 tnt, err := tenant.LoadFromContext(ctx) 421 if err != nil { 422 return err 423 } 424 425 if _, err = s.repo.GetByID(ctx, tnt, id, objectType); err != nil { 426 return err 427 } 428 429 return s.fetchRequestRepo.DeleteByReferenceObjectID(ctx, tnt, getFetchRequestObjectTypeBySpecObjectType(objectType), id) 430 } 431 432 func getFetchRequestObjectTypeBySpecObjectType(specObjectType model.SpecReferenceObjectType) model.FetchRequestReferenceObjectType { 433 switch specObjectType { 434 case model.APISpecReference: 435 return model.APISpecFetchRequestReference 436 case model.EventSpecReference: 437 return model.EventSpecFetchRequestReference 438 } 439 return "" 440 }