github.com/newrelic/newrelic-client-go@v1.1.0/pkg/nerdstorage/nerdstorage.go (about) 1 package nerdstorage 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 8 "github.com/imdario/mergo" 9 10 "github.com/newrelic/newrelic-client-go/internal/http" 11 "github.com/newrelic/newrelic-client-go/pkg/config" 12 "github.com/newrelic/newrelic-client-go/pkg/logging" 13 ) 14 15 const ( 16 accountScope = "ACCOUNT" 17 actorScope = "ACTOR" 18 entityScope = "ENTITY" 19 ) 20 21 // GetDocumentInput represents the input data required for retrieving a NerdStorage document. 22 type GetDocumentInput struct { 23 Collection string 24 DocumentID string 25 PackageID string 26 } 27 28 // GetDocumentWithAccountScope retrieves a NerdStorage document with account scope. 29 func (e *NerdStorage) GetDocumentWithAccountScope(accountID int, input GetDocumentInput) (interface{}, error) { 30 return e.GetDocumentWithAccountScopeWithContext(context.Background(), accountID, input) 31 } 32 33 // GetDocumentWithAccountScopeWithContext retrieves a NerdStorage document with account scope. 34 func (e *NerdStorage) GetDocumentWithAccountScopeWithContext(ctx context.Context, accountID int, input GetDocumentInput) (interface{}, error) { 35 if accountID == 0 { 36 return nil, fmt.Errorf("account ID is required when using account scope") 37 } 38 39 scopeID := strconv.Itoa(accountID) 40 vars := map[string]interface{}{"accountId": accountID} 41 42 resp, err := e.getDocumentWithScope(ctx, accountScope, scopeID, getDocumentWithAccountScopeQuery, vars, input) 43 if err != nil { 44 return nil, err 45 } 46 47 return resp.Actor.Account.NerdStorage.Document, nil 48 } 49 50 // GetDocumentWithUserScope retrieves a NerdStorage document with user scope. 51 func (e *NerdStorage) GetDocumentWithUserScope(input GetDocumentInput) (interface{}, error) { 52 return e.GetDocumentWithUserScopeWithContext(context.Background(), input) 53 } 54 55 // GetDocumentWithUserScopeWithContext retrieves a NerdStorage document with user scope. 56 func (e *NerdStorage) GetDocumentWithUserScopeWithContext(ctx context.Context, input GetDocumentInput) (interface{}, error) { 57 userID, err := e.getUserID(ctx) 58 if err != nil { 59 return nil, err 60 } 61 62 scopeID := strconv.Itoa(userID) 63 64 resp, err := e.getDocumentWithScope(ctx, actorScope, scopeID, getDocumentWithUserScopeQuery, nil, input) 65 if err != nil { 66 return nil, err 67 } 68 69 return resp.Actor.NerdStorage.Document, nil 70 } 71 72 // GetDocumentWithEntityScope retrieves a NerdStorage document with entity scope. 73 func (e *NerdStorage) GetDocumentWithEntityScope(entityGUID string, input GetDocumentInput) (interface{}, error) { 74 return e.GetDocumentWithEntityScopeWithContext(context.Background(), entityGUID, input) 75 } 76 77 // GetDocumentWithEntityScopeWithContext retrieves a NerdStorage document with entity scope. 78 func (e *NerdStorage) GetDocumentWithEntityScopeWithContext(ctx context.Context, entityGUID string, input GetDocumentInput) (interface{}, error) { 79 if entityGUID == "" { 80 return nil, fmt.Errorf("entity GUID is required when using entity scope") 81 } 82 83 vars := map[string]interface{}{"entityGuid": entityGUID} 84 resp, err := e.getDocumentWithScope(ctx, entityScope, entityGUID, getDocumentWithEntityScopeQuery, vars, input) 85 if err != nil { 86 return nil, err 87 } 88 89 return resp.Actor.Entity.NerdStorage.Document, nil 90 } 91 92 // GetCollectionInput represents the input data required for retrieving a NerdStorage collection. 93 type GetCollectionInput struct { 94 Collection string 95 PackageID string 96 } 97 98 // GetCollectionWithAccountScope retrieves a NerdStorage collection with account scope. 99 func (e *NerdStorage) GetCollectionWithAccountScope(accountID int, input GetCollectionInput) ([]interface{}, error) { 100 return e.GetCollectionWithAccountScopeWithContext(context.Background(), accountID, input) 101 } 102 103 // GetCollectionWithAccountScopeWithContext retrieves a NerdStorage collection with account scope. 104 func (e *NerdStorage) GetCollectionWithAccountScopeWithContext(ctx context.Context, accountID int, input GetCollectionInput) ([]interface{}, error) { 105 if accountID == 0 { 106 return nil, fmt.Errorf("account ID is required when using account scope") 107 } 108 109 scopeID := strconv.Itoa(accountID) 110 vars := map[string]interface{}{"accountId": accountID} 111 112 resp, err := e.getCollectionWithScope(ctx, accountScope, scopeID, getCollectionWithAccountScopeQuery, vars, input) 113 if err != nil { 114 return nil, err 115 } 116 117 return resp.Actor.Account.NerdStorage.Collection, nil 118 } 119 120 // GetCollectionWithUserScope retrieves a NerdStorage collection with user scope. 121 func (e *NerdStorage) GetCollectionWithUserScope(input GetCollectionInput) ([]interface{}, error) { 122 return e.GetCollectionWithUserScopeWithContext(context.Background(), input) 123 } 124 125 // GetCollectionWithUserScopeWithContext retrieves a NerdStorage collection with user scope. 126 func (e *NerdStorage) GetCollectionWithUserScopeWithContext(ctx context.Context, input GetCollectionInput) ([]interface{}, error) { 127 userID, err := e.getUserID(ctx) 128 if err != nil { 129 return nil, err 130 } 131 132 scopeID := strconv.Itoa(userID) 133 resp, err := e.getCollectionWithScope(ctx, actorScope, scopeID, getCollectionWithUserScopeQuery, nil, input) 134 if err != nil { 135 return nil, err 136 } 137 138 return resp.Actor.NerdStorage.Collection, nil 139 } 140 141 // GetCollectionWithEntityScope wretrieves a NerdStorage collection with entity scope. 142 func (e *NerdStorage) GetCollectionWithEntityScope(entityGUID string, input GetCollectionInput) ([]interface{}, error) { 143 return e.GetCollectionWithEntityScopeWithContext(context.Background(), entityGUID, input) 144 } 145 146 // GetCollectionWithEntityScopeWithContext wretrieves a NerdStorage collection with entity scope. 147 func (e *NerdStorage) GetCollectionWithEntityScopeWithContext(ctx context.Context, entityGUID string, input GetCollectionInput) ([]interface{}, error) { 148 if entityGUID == "" { 149 return nil, fmt.Errorf("entity GUID is required when using entity scope") 150 } 151 152 vars := map[string]interface{}{"entityGuid": entityGUID} 153 resp, err := e.getCollectionWithScope(ctx, entityScope, entityGUID, getCollectionWithEntityScopeQuery, vars, input) 154 if err != nil { 155 return nil, err 156 } 157 158 return resp.Actor.Entity.NerdStorage.Collection, nil 159 } 160 161 // WriteDocumentInput represents the input data required for the WriteDocument mutation. 162 type WriteDocumentInput struct { 163 Collection string 164 Document interface{} 165 DocumentID string 166 PackageID string 167 } 168 169 // WriteDocumentWithAccountScope writes a NerdStorage document with account scope. 170 func (e *NerdStorage) WriteDocumentWithAccountScope(accountID int, input WriteDocumentInput) (interface{}, error) { 171 return e.WriteDocumentWithAccountScopeWithContext(context.Background(), accountID, input) 172 } 173 174 // WriteDocumentWithAccountScopeWithContext writes a NerdStorage document with account scope. 175 func (e *NerdStorage) WriteDocumentWithAccountScopeWithContext(ctx context.Context, accountID int, input WriteDocumentInput) (interface{}, error) { 176 if accountID == 0 { 177 return nil, fmt.Errorf("account ID is required when using account scope") 178 } 179 180 scopeID := strconv.Itoa(accountID) 181 182 return e.writeDocumentWithScope(ctx, accountScope, scopeID, input) 183 } 184 185 // WriteDocumentWithUserScope writes a NerdStorage document with user scope. 186 func (e *NerdStorage) WriteDocumentWithUserScope(input WriteDocumentInput) (interface{}, error) { 187 return e.WriteDocumentWithUserScopeWithContext(context.Background(), input) 188 } 189 190 // WriteDocumentWithUserScopeWithContext writes a NerdStorage document with user scope. 191 func (e *NerdStorage) WriteDocumentWithUserScopeWithContext(ctx context.Context, input WriteDocumentInput) (interface{}, error) { 192 userID, err := e.getUserID(ctx) 193 if err != nil { 194 return nil, err 195 } 196 197 scopeID := strconv.Itoa(userID) 198 return e.writeDocumentWithScope(ctx, actorScope, scopeID, input) 199 } 200 201 // WriteDocumentWithEntityScope writes a NerdStorage document with entity scope. 202 func (e *NerdStorage) WriteDocumentWithEntityScope(entityGUID string, input WriteDocumentInput) (interface{}, error) { 203 return e.WriteDocumentWithEntityScopeWithContext(context.Background(), entityGUID, input) 204 } 205 206 // WriteDocumentWithEntityScopeWithContext writes a NerdStorage document with entity scope. 207 func (e *NerdStorage) WriteDocumentWithEntityScopeWithContext(ctx context.Context, entityGUID string, input WriteDocumentInput) (interface{}, error) { 208 if entityGUID == "" { 209 return nil, fmt.Errorf("entity GUID is required when using entity scope") 210 } 211 212 return e.writeDocumentWithScope(ctx, entityScope, entityGUID, input) 213 } 214 215 // DeleteDocumentInput represents the input data required for the DeleteDocument mutation. 216 type DeleteDocumentInput struct { 217 Collection string 218 DocumentID string 219 PackageID string 220 } 221 222 // DeleteDocumentWithAccountScope deletes a NerdStorage document with account scope. 223 func (e *NerdStorage) DeleteDocumentWithAccountScope(accountID int, input DeleteDocumentInput) (bool, error) { 224 return e.DeleteDocumentWithAccountScopeWithContext(context.Background(), accountID, input) 225 } 226 227 // DeleteDocumentWithAccountScopeWithContext deletes a NerdStorage document with account scope. 228 func (e *NerdStorage) DeleteDocumentWithAccountScopeWithContext(ctx context.Context, accountID int, input DeleteDocumentInput) (bool, error) { 229 if accountID == 0 { 230 return false, fmt.Errorf("account ID is required when using account scope") 231 } 232 233 scopeID := strconv.Itoa(accountID) 234 235 return e.deleteDocumentWithScope(ctx, accountScope, scopeID, input) 236 } 237 238 // DeleteDocumentWithUserScope deletes a NerdStorage document with user scope. 239 func (e *NerdStorage) DeleteDocumentWithUserScope(input DeleteDocumentInput) (bool, error) { 240 return e.DeleteDocumentWithUserScopeWithContext(context.Background(), input) 241 } 242 243 // DeleteDocumentWithUserScopeWithContext deletes a NerdStorage document with user scope. 244 func (e *NerdStorage) DeleteDocumentWithUserScopeWithContext(ctx context.Context, input DeleteDocumentInput) (bool, error) { 245 userID, err := e.getUserID(ctx) 246 if err != nil { 247 return false, err 248 } 249 250 scopeID := strconv.Itoa(userID) 251 return e.deleteDocumentWithScope(ctx, actorScope, scopeID, input) 252 } 253 254 // DeleteDocumentWithEntityScope deletes a NerdStorage document with entity scope. 255 func (e *NerdStorage) DeleteDocumentWithEntityScope(entityGUID string, input DeleteDocumentInput) (bool, error) { 256 return e.DeleteDocumentWithEntityScopeWithContext(context.Background(), entityGUID, input) 257 } 258 259 // DeleteDocumentWithEntityScopeWithContext deletes a NerdStorage document with entity scope. 260 func (e *NerdStorage) DeleteDocumentWithEntityScopeWithContext(ctx context.Context, entityGUID string, input DeleteDocumentInput) (bool, error) { 261 if entityGUID == "" { 262 return false, fmt.Errorf("entity GUID is required when using entity scope") 263 } 264 265 return e.deleteDocumentWithScope(ctx, entityScope, entityGUID, input) 266 } 267 268 // DeleteCollectionInput represents the input data required for the DeleteCollection mutation. 269 type DeleteCollectionInput struct { 270 Collection string 271 PackageID string 272 } 273 274 // DeleteCollectionWithAccountScope deletes a NerdStorage collection with account scope. 275 func (e *NerdStorage) DeleteCollectionWithAccountScope(accountID int, input DeleteCollectionInput) (bool, error) { 276 return e.DeleteCollectionWithAccountScopeWithContext(context.Background(), accountID, input) 277 } 278 279 // DeleteCollectionWithAccountScopeWithContext deletes a NerdStorage collection with account scope. 280 func (e *NerdStorage) DeleteCollectionWithAccountScopeWithContext(ctx context.Context, accountID int, input DeleteCollectionInput) (bool, error) { 281 if accountID == 0 { 282 return false, fmt.Errorf("account ID is required when using account scope") 283 } 284 285 scopeID := strconv.Itoa(accountID) 286 287 return e.deleteCollectionWithScope(ctx, accountScope, scopeID, input) 288 } 289 290 // DeleteCollectionWithUserScope deletes a NerdStorage collection with user scope. 291 func (e *NerdStorage) DeleteCollectionWithUserScope(input DeleteCollectionInput) (bool, error) { 292 return e.DeleteCollectionWithUserScopeWithContext(context.Background(), input) 293 } 294 295 // DeleteCollectionWithUserScopeWithContext deletes a NerdStorage collection with user scope. 296 func (e *NerdStorage) DeleteCollectionWithUserScopeWithContext(ctx context.Context, input DeleteCollectionInput) (bool, error) { 297 userID, err := e.getUserID(ctx) 298 if err != nil { 299 return false, err 300 } 301 302 scopeID := strconv.Itoa(userID) 303 304 return e.deleteCollectionWithScope(ctx, actorScope, scopeID, input) 305 } 306 307 // DeleteCollectionWithEntityScope deletes a NerdStorage collection with entity scope. 308 func (e *NerdStorage) DeleteCollectionWithEntityScope(entityGUID string, input DeleteCollectionInput) (bool, error) { 309 return e.DeleteCollectionWithEntityScopeWithContext(context.Background(), entityGUID, input) 310 } 311 312 // DeleteCollectionWithEntityScopeWithContext deletes a NerdStorage collection with entity scope. 313 func (e *NerdStorage) DeleteCollectionWithEntityScopeWithContext(ctx context.Context, entityGUID string, input DeleteCollectionInput) (bool, error) { 314 if entityGUID == "" { 315 return false, fmt.Errorf("entity GUID is required when using entity scope") 316 } 317 318 return e.deleteCollectionWithScope(ctx, entityScope, entityGUID, input) 319 } 320 321 func (e *NerdStorage) getDocumentWithScope(ctx context.Context, scope string, scopeID string, query string, vars map[string]interface{}, input GetDocumentInput) (*getResponse, error) { 322 var resp getResponse 323 324 v := map[string]interface{}{ 325 "collection": input.Collection, 326 "documentId": input.DocumentID, 327 "scope": scopeInput{ 328 Name: scope, 329 ID: scopeID, 330 }, 331 } 332 333 err := mergo.Merge(&v, vars) 334 if err != nil { 335 return nil, err 336 } 337 338 req, err := e.client.NewNerdGraphRequest(query, v, &resp) 339 if err != nil { 340 return nil, err 341 } 342 343 req.WithContext(ctx) 344 req.SetHeader("NewRelic-Package-ID", input.PackageID) 345 346 _, err = e.client.Do(req) 347 if err != nil { 348 return nil, err 349 } 350 351 return &resp, nil 352 } 353 354 func (e *NerdStorage) getCollectionWithScope(ctx context.Context, scope string, scopeID string, query string, vars map[string]interface{}, input GetCollectionInput) (*getResponse, error) { 355 var resp getResponse 356 357 v := map[string]interface{}{ 358 "collection": input.Collection, 359 "scope": scopeInput{ 360 Name: scope, 361 ID: scopeID, 362 }, 363 } 364 365 err := mergo.Merge(&v, vars) 366 if err != nil { 367 return nil, err 368 } 369 370 req, err := e.client.NewNerdGraphRequest(query, v, &resp) 371 if err != nil { 372 return nil, err 373 } 374 375 req.WithContext(ctx) 376 req.SetHeader("NewRelic-Package-ID", input.PackageID) 377 378 _, err = e.client.Do(req) 379 if err != nil { 380 return nil, err 381 } 382 383 return &resp, nil 384 } 385 386 func (e *NerdStorage) writeDocumentWithScope(ctx context.Context, scope string, scopeID string, input WriteDocumentInput) (interface{}, error) { 387 var resp writeDocumentResponse 388 389 vars := map[string]interface{}{ 390 "collection": input.Collection, 391 "document": input.Document, 392 "documentId": input.DocumentID, 393 "scope": scopeInput{ 394 Name: scope, 395 ID: scopeID, 396 }, 397 } 398 399 req, err := e.client.NewNerdGraphRequest(writeDocumentMutation, vars, &resp) 400 if err != nil { 401 return "", err 402 } 403 404 req.WithContext(ctx) 405 req.SetHeader("NewRelic-Package-ID", input.PackageID) 406 407 _, err = e.client.Do(req) 408 if err != nil { 409 return "", err 410 } 411 412 return resp.NerdStorageWriteDocument, nil 413 } 414 415 func (e *NerdStorage) deleteDocumentWithScope(ctx context.Context, scope string, scopeID string, input DeleteDocumentInput) (bool, error) { 416 var resp deleteDocumentResponse 417 418 vars := map[string]interface{}{ 419 "collection": input.Collection, 420 "documentId": input.DocumentID, 421 "scope": scopeInput{ 422 Name: scope, 423 ID: scopeID, 424 }, 425 } 426 427 req, err := e.client.NewNerdGraphRequest(deleteDocumentMutation, vars, &resp) 428 if err != nil { 429 return false, err 430 } 431 432 req.WithContext(ctx) 433 req.SetHeader("NewRelic-Package-ID", input.PackageID) 434 435 _, err = e.client.Do(req) 436 if err != nil { 437 return false, err 438 } 439 440 return resp.NerdStorageDeleteDocument.Deleted != 0, nil 441 } 442 443 func (e *NerdStorage) deleteCollectionWithScope(ctx context.Context, scope string, scopeID string, input DeleteCollectionInput) (bool, error) { 444 var resp deleteCollectionResponse 445 446 vars := map[string]interface{}{ 447 "collection": input.Collection, 448 "scope": scopeInput{ 449 Name: scope, 450 ID: scopeID, 451 }, 452 } 453 454 req, err := e.client.NewNerdGraphRequest(deleteCollectionMutation, vars, &resp) 455 if err != nil { 456 return false, err 457 } 458 459 req.WithContext(ctx) 460 req.SetHeader("NewRelic-Package-ID", input.PackageID) 461 462 _, err = e.client.Do(req) 463 if err != nil { 464 return false, err 465 } 466 467 return resp.NerdStorageDeleteCollection.Deleted != 0, nil 468 } 469 470 func (e *NerdStorage) getUserID(ctx context.Context) (int, error) { 471 var resp userIDResponse 472 473 err := e.client.NerdGraphQueryWithContext(ctx, getUserIDQuery, nil, &resp) 474 if err != nil { 475 return 0, err 476 } 477 478 return resp.Actor.User.ID, nil 479 } 480 481 const ( 482 getDocumentWithAccountScopeQuery = ` 483 query($accountId: Int!, $documentId: String!, $collection: String!) { 484 actor { 485 account(id: $accountId) { 486 nerdStorage { 487 document(collection: $collection, documentId: $documentId) 488 } 489 } 490 } 491 }` 492 493 getDocumentWithEntityScopeQuery = ` 494 query($entityGuid: EntityGuid!, $documentId: String!, $collection: String!) { 495 actor { 496 entity(guid: $entityGuid) { 497 nerdStorage { 498 document(collection: $collection, documentId: $documentId) 499 } 500 } 501 } 502 }` 503 504 getDocumentWithUserScopeQuery = ` 505 query($documentId: String!, $collection: String!) { 506 actor { 507 nerdStorage { 508 document(collection: $collection, documentId: $documentId) 509 } 510 } 511 }` 512 513 getCollectionWithAccountScopeQuery = ` 514 query($accountId: Int!, $collection: String!) { 515 actor { 516 account(id: $accountId) { 517 nerdStorage { 518 collection(collection: $collection) { 519 document 520 id 521 } 522 } 523 } 524 } 525 }` 526 527 getCollectionWithEntityScopeQuery = ` 528 query($entityGuid: EntityGuid!, $collection: String!) { 529 actor { 530 entity(guid: $entityGuid) { 531 nerdStorage { 532 collection(collection: $collection) { 533 document 534 id 535 } 536 } 537 } 538 } 539 }` 540 541 getCollectionWithUserScopeQuery = ` 542 query($collection: String!) { 543 actor { 544 nerdStorage { 545 collection(collection: $collection) { 546 document 547 id 548 } 549 } 550 } 551 }` 552 553 getUserIDQuery = ` 554 query { 555 actor { 556 user { 557 id 558 } 559 } 560 }` 561 562 writeDocumentMutation = ` 563 mutation($collection: String!, $document: NerdStorageDocument!, $documentId: String!, $scope: NerdStorageScopeInput!) { 564 nerdStorageWriteDocument(collection: $collection, document: $document, documentId: $documentId, scope: $scope) 565 }` 566 567 deleteDocumentMutation = ` 568 mutation($collection: String!, $documentId: String!, $scope: NerdStorageScopeInput!) { 569 nerdStorageDeleteDocument(collection: $collection, documentId: $documentId, scope: $scope) { 570 deleted 571 } 572 }` 573 574 deleteCollectionMutation = ` 575 mutation($collection: String!, $scope: NerdStorageScopeInput!) { 576 nerdStorageDeleteCollection(collection: $collection, scope: $scope) { 577 deleted 578 } 579 }` 580 ) 581 582 type scopeInput struct { 583 Name string 584 ID string 585 } 586 587 type getResponse struct { 588 Actor struct { 589 Account struct { 590 NerdStorage struct { 591 Document interface{} 592 Collection []interface{} 593 } 594 } 595 Entity struct { 596 NerdStorage struct { 597 Document interface{} 598 Collection []interface{} 599 } 600 } 601 NerdStorage struct { 602 Document interface{} 603 Collection []interface{} 604 } 605 } 606 } 607 608 type userIDResponse struct { 609 Actor struct { 610 User struct { 611 ID int 612 } 613 } 614 } 615 616 type writeDocumentResponse struct { 617 NerdStorageWriteDocument interface{} 618 } 619 620 type deleteDocumentResponse struct { 621 NerdStorageDeleteDocument struct { 622 Deleted int 623 } 624 } 625 626 type deleteCollectionResponse struct { 627 NerdStorageDeleteCollection struct { 628 Deleted int 629 } 630 } 631 632 // NerdStorage is used to communicate with the New Relic Workloads product. 633 type NerdStorage struct { 634 client http.Client 635 logger logging.Logger 636 } 637 638 // New returns a new client for interacting with the New Relic One NerdStorage 639 // document store. 640 func New(config config.Config) NerdStorage { 641 return NerdStorage{ 642 client: http.NewClient(config), 643 logger: config.GetLogger(), 644 } 645 }