github.com/Axway/agent-sdk@v1.1.101/pkg/apic/client.go (about) 1 package apic 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strconv" 8 "strings" 9 "sync" 10 11 defs "github.com/Axway/agent-sdk/pkg/apic/definitions" 12 "github.com/Axway/agent-sdk/pkg/util" 13 14 cache2 "github.com/Axway/agent-sdk/pkg/agent/cache" 15 16 coreapi "github.com/Axway/agent-sdk/pkg/api" 17 apiv1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1" 18 management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1" 19 "github.com/Axway/agent-sdk/pkg/apic/auth" 20 "github.com/Axway/agent-sdk/pkg/cache" 21 corecfg "github.com/Axway/agent-sdk/pkg/config" 22 "github.com/Axway/agent-sdk/pkg/util/errors" 23 hc "github.com/Axway/agent-sdk/pkg/util/healthcheck" 24 "github.com/Axway/agent-sdk/pkg/util/log" 25 ) 26 27 // constants for auth policy types 28 const ( 29 Apikey = "verify-api-key" 30 Passthrough = "pass-through" 31 Oauth = "verify-oauth-token" 32 Basic = "http-basic" 33 ) 34 35 // other consts 36 const ( 37 TeamMapKey = "TeamMap" 38 ) 39 40 // constants for patch request 41 const ( 42 PatchOpAdd = "add" 43 PatchOpReplace = "replace" 44 PatchOpDelete = "delete" 45 PatchOpBuildObjectTree = "x-build-object-tree" 46 PatchOperation = "op" 47 PatchPath = "path" 48 PatchValue = "value" 49 ContentTypeJsonPatch = "application/json-patch+json" 50 ContentTypeJson = "application/json" 51 ) 52 53 // constants for patch request 54 const ( 55 BearerTokenPrefix = "Bearer " 56 HdrContentType = "Content-Type" 57 HdrAuthorization = "Authorization" 58 HdrAxwayTenantID = "X-Axway-Tenant-Id" 59 ) 60 61 // ValidPolicies - list of valid auth policies supported by Central. Add to this list as more policies are supported. 62 var ValidPolicies = []string{Apikey, Passthrough, Oauth, Basic} 63 64 // Client - interface 65 type Client interface { 66 SetTokenGetter(tokenRequester auth.PlatformTokenGetter) 67 SetConfig(cfg corecfg.CentralConfig) 68 PublishService(serviceBody *ServiceBody) (*management.APIService, error) 69 DeleteAPIServiceInstance(name string) error 70 DeleteServiceByName(name string) error 71 GetUserEmailAddress(ID string) (string, error) 72 GetUserName(ID string) (string, error) 73 ExecuteAPI(method, url string, queryParam map[string]string, buffer []byte) ([]byte, error) 74 Healthcheck(name string) *hc.Status 75 GetAPIRevisions(query map[string]string, stage string) ([]*management.APIServiceRevision, error) 76 GetAPIServiceRevisions(query map[string]string, URL, stage string) ([]*management.APIServiceRevision, error) 77 GetAPIServiceInstances(query map[string]string, URL string) ([]*management.APIServiceInstance, error) 78 GetAPIV1ResourceInstances(query map[string]string, URL string) ([]*apiv1.ResourceInstance, error) 79 GetAPIV1ResourceInstancesWithPageSize(query map[string]string, URL string, pageSize int) ([]*apiv1.ResourceInstance, error) 80 GetAPIServiceByName(name string) (*management.APIService, error) 81 GetAPIServiceInstanceByName(name string) (*management.APIServiceInstance, error) 82 GetAPIRevisionByName(name string) (*management.APIServiceRevision, error) 83 GetEnvironment() (*management.Environment, error) 84 GetCentralTeamByName(name string) (*defs.PlatformTeam, error) 85 GetTeam(query map[string]string) ([]defs.PlatformTeam, error) 86 GetAccessControlList(aclName string) (*management.AccessControlList, error) 87 UpdateAccessControlList(acl *management.AccessControlList) (*management.AccessControlList, error) 88 CreateAccessControlList(acl *management.AccessControlList) (*management.AccessControlList, error) 89 90 CreateSubResource(rm apiv1.ResourceMeta, subs map[string]interface{}) error 91 GetResource(url string) (*apiv1.ResourceInstance, error) 92 UpdateResourceFinalizer(ri *apiv1.ResourceInstance, finalizer, description string, addAction bool) (*apiv1.ResourceInstance, error) 93 94 UpdateResourceInstance(ri apiv1.Interface) (*apiv1.ResourceInstance, error) 95 CreateOrUpdateResource(ri apiv1.Interface) (*apiv1.ResourceInstance, error) 96 CreateResourceInstance(ri apiv1.Interface) (*apiv1.ResourceInstance, error) 97 PatchSubResource(ri apiv1.Interface, subResourceName string, patches []map[string]interface{}) (*apiv1.ResourceInstance, error) 98 DeleteResourceInstance(ri apiv1.Interface) error 99 GetResources(ri apiv1.Interface) ([]apiv1.Interface, error) 100 101 CreateResource(url string, bts []byte) (*apiv1.ResourceInstance, error) 102 UpdateResource(url string, bts []byte) (*apiv1.ResourceInstance, error) 103 } 104 105 // New creates a new Client 106 func New(cfg corecfg.CentralConfig, tokenRequester auth.PlatformTokenGetter, caches cache2.Manager) Client { 107 serviceClient := &ServiceClient{ 108 caches: caches, 109 pageSizes: map[string]int{}, 110 pageSizeMutex: &sync.Mutex{}, 111 } 112 serviceClient.logger = log.NewFieldLogger(). 113 WithComponent("serviceClient"). 114 WithPackage("sdk.apic") 115 116 serviceClient.SetTokenGetter(tokenRequester) 117 serviceClient.subscriptionSchemaCache = cache.New() 118 serviceClient.initClient(cfg) 119 120 return serviceClient 121 } 122 123 func (c *ServiceClient) createAPIServerURL(link string) string { 124 return fmt.Sprintf("%s/apis%s", c.cfg.GetURL(), link) 125 } 126 127 // getTeamFromCache - 128 func (c *ServiceClient) getTeamFromCache(name string) (string, bool) { 129 var team *defs.PlatformTeam 130 if name == "" { 131 team = c.caches.GetDefaultTeam() 132 if team == nil { 133 return "", false 134 } 135 return team.ID, true 136 } 137 138 team = c.caches.GetTeamByName(name) 139 if team == nil { 140 return "", false 141 } 142 143 return team.ID, true 144 } 145 146 // initClient - config change handler 147 func (c *ServiceClient) initClient(cfg corecfg.CentralConfig) { 148 c.cfg = cfg 149 c.apiClient = coreapi.NewClient(cfg.GetTLSConfig(), cfg.GetProxyURL(), 150 coreapi.WithTimeout(cfg.GetClientTimeout()), coreapi.WithSingleURL()) 151 152 err := c.setTeamCache() 153 if err != nil { 154 c.logger.Error(err) 155 } 156 157 } 158 159 // SetTokenGetter - sets the token getter 160 func (c *ServiceClient) SetTokenGetter(tokenRequester auth.PlatformTokenGetter) { 161 c.tokenRequester = tokenRequester 162 } 163 164 // SetConfig - sets the config and apiClient 165 func (c *ServiceClient) SetConfig(cfg corecfg.CentralConfig) { 166 c.cfg = cfg 167 c.apiClient = coreapi.NewClient(cfg.GetTLSConfig(), cfg.GetProxyURL(), 168 coreapi.WithTimeout(cfg.GetClientTimeout()), coreapi.WithSingleURL()) 169 } 170 171 // mapToTagsArray - 172 func mapToTagsArray(m map[string]interface{}, additionalTags string) []string { 173 strArr := []string{} 174 175 for key, val := range m { 176 value := key 177 if v, ok := val.(*string); ok && *v != "" { 178 value += "_" + *v 179 } else if v, ok := val.(string); ok && v != "" { 180 value += "_" + v 181 } 182 if len(value) > 80 { 183 value = value[:77] + "..." 184 } 185 strArr = append(strArr, value) 186 } 187 188 // Add any tags from config 189 if additionalTags != "" { 190 additionalTagsArray := strings.Split(additionalTags, ",") 191 192 for _, tag := range additionalTagsArray { 193 strArr = append(strArr, strings.TrimSpace(tag)) 194 } 195 } 196 197 return strArr 198 } 199 200 func readResponseErrors(status int, body []byte) string { 201 // Return error string only for error status code 202 if status < http.StatusBadRequest { 203 return "" 204 } 205 206 responseErr := &ResponseError{} 207 err := json.Unmarshal(body, &responseErr) 208 if err != nil || len(responseErr.Errors) == 0 { 209 errStr := getHTTPResponseErrorString(status, body) 210 log.Tracef("HTTP response error: %v", string(errStr)) 211 return errStr 212 } 213 214 // Get the first error from the API response errors 215 errStr := getAPIResponseErrorString(responseErr.Errors[0]) 216 log.Tracef("HTTP response error: %s", errStr) 217 return errStr 218 } 219 220 func getHTTPResponseErrorString(status int, body []byte) string { 221 detail := make(map[string]*json.RawMessage) 222 json.Unmarshal(body, &detail) 223 errorMsg := "" 224 for _, v := range detail { 225 buffer, _ := v.MarshalJSON() 226 errorMsg = string(buffer) 227 } 228 229 errStr := "status - " + strconv.Itoa(status) 230 if errorMsg != "" { 231 errStr += ", detail - " + errorMsg 232 } 233 return errStr 234 } 235 236 func getAPIResponseErrorString(apiError APIError) string { 237 errStr := "status - " + strconv.Itoa(apiError.Status) 238 if apiError.Title != "" { 239 errStr += ", title - " + apiError.Title 240 } 241 if apiError.Detail != "" { 242 errStr += ", detail - " + apiError.Detail 243 } 244 return errStr 245 } 246 247 func (c *ServiceClient) createHeader() (map[string]string, error) { 248 token, err := c.tokenRequester.GetToken() 249 if err != nil { 250 return nil, err 251 } 252 headers := make(map[string]string) 253 headers[HdrAxwayTenantID] = c.cfg.GetTenantID() 254 headers[HdrAuthorization] = BearerTokenPrefix + token 255 headers[HdrContentType] = ContentTypeJson 256 return headers, nil 257 } 258 259 // Healthcheck - verify connection to the platform 260 func (c *ServiceClient) Healthcheck(_ string) *hc.Status { 261 // Set a default response 262 s := hc.Status{ 263 Result: hc.OK, 264 } 265 266 // Check that we can reach the platform 267 err := c.checkPlatformHealth() 268 if err != nil { 269 s = hc.Status{ 270 Result: hc.FAIL, 271 Details: err.Error(), 272 } 273 } 274 275 _, err = c.GetEnvironment() 276 if err != nil { 277 s = hc.Status{ 278 Result: hc.FAIL, 279 Details: err.Error(), 280 } 281 } 282 283 // Return our response 284 return &s 285 } 286 287 func (c *ServiceClient) checkPlatformHealth() error { 288 // this doesn't make a call to platform every time. Only when the token is close to expiring. 289 _, err := c.tokenRequester.GetToken() 290 if err != nil { 291 return errors.Wrap(ErrAuthenticationCall, err.Error()) 292 } 293 return nil 294 } 295 296 func (c *ServiceClient) setTeamCache() error { 297 // passing nil to getTeam will return the full list of teams 298 platformTeams, err := c.GetTeam(make(map[string]string)) 299 if err != nil { 300 return err 301 } 302 303 teamMap := make(map[string]string) 304 for _, team := range platformTeams { 305 teamMap[team.Name] = team.ID 306 } 307 return cache.GetCache().Set(TeamMapKey, teamMap) 308 } 309 310 // GetEnvironment get an environment 311 func (c *ServiceClient) GetEnvironment() (*management.Environment, error) { 312 headers, err := c.createHeader() 313 if err != nil { 314 return nil, errors.Wrap(ErrAuthenticationCall, err.Error()) 315 } 316 317 queryParams := map[string]string{} 318 319 // do a request for the environment 320 bytes, err := c.sendServerRequest(c.cfg.GetEnvironmentURL(), headers, queryParams) 321 if err != nil { 322 return nil, err 323 } 324 325 // Get env id from apiServerEnvByte 326 env := &management.Environment{} 327 err = json.Unmarshal(bytes, env) 328 if err != nil { 329 return nil, errors.Wrap(ErrEnvironmentQuery, err.Error()) 330 } 331 332 // Validate that we actually get an environment ID back within the Metadata 333 if env.Metadata.ID == "" { 334 return nil, ErrEnvironmentQuery 335 } 336 337 return env, nil 338 } 339 340 func (c *ServiceClient) sendServerRequest(url string, headers, query map[string]string) ([]byte, error) { 341 request := coreapi.Request{ 342 Method: coreapi.GET, 343 URL: url, 344 QueryParams: query, 345 Headers: headers, 346 } 347 348 response, err := c.apiClient.Send(request) 349 if err != nil { 350 return nil, errors.Wrap(ErrNetwork, err.Error()) 351 } 352 353 switch response.Code { 354 case http.StatusOK: 355 return response.Body, nil 356 case http.StatusUnauthorized: 357 return nil, ErrAuthentication 358 default: 359 responseErr := readResponseErrors(response.Code, response.Body) 360 return nil, errors.Wrap(ErrRequestQuery, responseErr) 361 } 362 } 363 364 // GetPlatformUserInfo - request the platform user info 365 func (c *ServiceClient) getPlatformUserInfo(id string) (*defs.PlatformUserInfo, error) { 366 headers, err := c.createHeader() 367 if err != nil { 368 return nil, err 369 } 370 371 platformURL := fmt.Sprintf("%s/api/v1/user/%s", c.cfg.GetPlatformURL(), id) 372 c.logger.Tracef("Platform URL being used to get user information %s", platformURL) 373 374 platformUserBytes, reqErr := c.sendServerRequest(platformURL, headers, make(map[string]string, 0)) 375 if reqErr != nil { 376 if reqErr.(*errors.AgentError).GetErrorCode() == ErrRequestQuery.GetErrorCode() { 377 return nil, ErrNoAddressFound.FormatError(id) 378 } 379 return nil, reqErr 380 } 381 382 var platformUserInfo defs.PlatformUserInfo 383 err = json.Unmarshal(platformUserBytes, &platformUserInfo) 384 if err != nil { 385 return nil, err 386 } 387 388 return &platformUserInfo, nil 389 } 390 391 // GetUserEmailAddress - request the user email 392 func (c *ServiceClient) GetUserEmailAddress(id string) (string, error) { 393 394 platformUserInfo, err := c.getPlatformUserInfo(id) 395 if err != nil { 396 return "", err 397 } 398 399 email := platformUserInfo.Result.Email 400 c.logger.Tracef("Platform user email %s", email) 401 402 return email, nil 403 } 404 405 // GetUserName - request the user name 406 func (c *ServiceClient) GetUserName(id string) (string, error) { 407 platformUserInfo, err := c.getPlatformUserInfo(id) 408 if err != nil { 409 return "", err 410 } 411 412 userName := fmt.Sprintf("%s %s", platformUserInfo.Result.Firstname, platformUserInfo.Result.Lastname) 413 414 c.logger.Tracef("Platform user %s", userName) 415 416 return userName, nil 417 } 418 419 // GetCentralTeamByName - returns the team based on team name 420 func (c *ServiceClient) GetCentralTeamByName(name string) (*defs.PlatformTeam, error) { 421 // Query for the default, if no teamName is given 422 queryParams := map[string]string{} 423 424 if name != "" { 425 queryParams = map[string]string{ 426 "query": fmt.Sprintf("name==\"%s\"", name), 427 } 428 } 429 430 platformTeams, err := c.GetTeam(queryParams) 431 if err != nil { 432 return nil, err 433 } 434 435 if len(platformTeams) == 0 { 436 return nil, ErrTeamNotFound.FormatError(name) 437 } 438 439 team := platformTeams[0] 440 if name == "" { 441 // Loop through to find the default team 442 for i, platformTeam := range platformTeams { 443 if platformTeam.Default { 444 // Found the default, set as the team var and break 445 team = platformTeams[i] 446 break 447 } 448 } 449 } 450 451 return &team, nil 452 } 453 454 // GetTeam - returns the team ID based on filter 455 func (c *ServiceClient) GetTeam(query map[string]string) ([]defs.PlatformTeam, error) { 456 headers, err := c.createHeader() 457 if err != nil { 458 return nil, err 459 } 460 461 // Get the teams using Client registry service instead of from platform. 462 // Platform teams API require access and DOSA account will not have the access 463 platformURL := fmt.Sprintf("%s/api/v1/platformTeams", c.cfg.GetURL()) 464 465 response, reqErr := c.sendServerRequest(platformURL, headers, query) 466 if reqErr != nil { 467 return nil, reqErr 468 } 469 470 var platformTeams []defs.PlatformTeam 471 err = json.Unmarshal(response, &platformTeams) 472 if err != nil { 473 return nil, err 474 } 475 476 return platformTeams, nil 477 } 478 479 // GetAccessControlList - 480 func (c *ServiceClient) GetAccessControlList(name string) (*management.AccessControlList, error) { 481 headers, err := c.createHeader() 482 if err != nil { 483 return nil, err 484 } 485 486 request := coreapi.Request{ 487 Method: http.MethodGet, 488 URL: fmt.Sprintf("%s/%s", c.cfg.GetEnvironmentACLsURL(), name), 489 Headers: headers, 490 } 491 492 response, err := c.apiClient.Send(request) 493 if err != nil { 494 return nil, err 495 } 496 497 if response.Code != http.StatusOK { 498 responseErr := readResponseErrors(response.Code, response.Body) 499 return nil, errors.Wrap(ErrRequestQuery, responseErr) 500 } 501 502 var acl *management.AccessControlList 503 err = json.Unmarshal(response.Body, &acl) 504 if err != nil { 505 return nil, err 506 } 507 508 return acl, err 509 } 510 511 // UpdateAccessControlList - removes existing then creates new AccessControlList 512 func (c *ServiceClient) UpdateAccessControlList(acl *management.AccessControlList) (*management.AccessControlList, error) { 513 return c.deployAccessControl(acl, http.MethodPut) 514 } 515 516 // CreateAccessControlList - 517 func (c *ServiceClient) CreateAccessControlList(acl *management.AccessControlList) (*management.AccessControlList, error) { 518 return c.deployAccessControl(acl, http.MethodPost) 519 } 520 521 func (c *ServiceClient) deployAccessControl(acl *management.AccessControlList, method string) (*management.AccessControlList, error) { 522 headers, err := c.createHeader() 523 if err != nil { 524 return nil, err 525 } 526 527 url := c.cfg.GetEnvironmentACLsURL() 528 if method == http.MethodPut || method == http.MethodDelete { 529 url = fmt.Sprintf("%s/%s", url, acl.Name) 530 } 531 532 request := coreapi.Request{ 533 Method: method, 534 URL: url, 535 Headers: headers, 536 } 537 538 if method == http.MethodPut || method == http.MethodPost { 539 data, err := json.Marshal(*acl) 540 if err != nil { 541 return nil, err 542 } 543 request.Body = data 544 } 545 546 response, err := c.apiClient.Send(request) 547 if err != nil { 548 return nil, err 549 } 550 551 if method == http.MethodDelete && (response.Code == http.StatusNotFound || response.Code == http.StatusNoContent) { 552 return nil, nil 553 } 554 555 if response.Code == http.StatusConflict { 556 curACL, _ := c.GetResource(acl.GetSelfLink()) 557 c.caches.SetAccessControlList(curACL) 558 } 559 560 if response.Code != http.StatusCreated && response.Code != http.StatusOK { 561 responseErr := readResponseErrors(response.Code, response.Body) 562 return nil, errors.Wrap(ErrRequestQuery, responseErr) 563 } 564 565 var updatedACL *management.AccessControlList 566 if method == http.MethodPut || method == http.MethodPost { 567 updatedACL = &management.AccessControlList{} 568 err = json.Unmarshal(response.Body, updatedACL) 569 if err != nil { 570 return nil, err 571 } 572 } 573 574 return updatedACL, err 575 } 576 577 // executeAPI - execute the api 578 func (c *ServiceClient) executeAPI(method, url string, query map[string]string, buffer []byte, overrideHeaders map[string]string) (*coreapi.Response, error) { 579 headers, err := c.createHeader() 580 if err != nil { 581 return nil, err 582 } 583 584 for key, value := range overrideHeaders { 585 headers[key] = value 586 } 587 588 request := coreapi.Request{ 589 Method: method, 590 URL: url, 591 QueryParams: query, 592 Headers: headers, 593 Body: buffer, 594 } 595 596 return c.apiClient.Send(request) 597 } 598 599 // ExecuteAPI - execute the api 600 func (c *ServiceClient) ExecuteAPI(method, url string, query map[string]string, buffer []byte) ([]byte, error) { 601 return c.ExecuteAPIWithHeader(method, url, query, buffer, nil) 602 } 603 604 // ExecuteAPI - execute the api 605 func (c *ServiceClient) ExecuteAPIWithHeader(method, url string, query map[string]string, buffer []byte, headers map[string]string) ([]byte, error) { 606 response, err := c.executeAPI(method, url, query, buffer, headers) 607 if err != nil { 608 return nil, errors.Wrap(ErrNetwork, err.Error()) 609 } 610 611 switch { 612 case response.Code == http.StatusNoContent && method == http.MethodDelete: 613 return nil, nil 614 case response.Code == http.StatusOK: 615 fallthrough 616 case response.Code == http.StatusCreated: 617 return response.Body, nil 618 case response.Code == http.StatusUnauthorized: 619 return nil, ErrAuthentication 620 default: 621 responseErr := readResponseErrors(response.Code, response.Body) 622 return nil, errors.Wrap(ErrRequestQuery, responseErr) 623 } 624 } 625 626 // CreateSubResource creates a sub resource on the provided resource. 627 func (c *ServiceClient) CreateSubResource(rm apiv1.ResourceMeta, subs map[string]interface{}) error { 628 _, err := c.createSubResource(rm, subs) 629 return err 630 } 631 632 func (c *ServiceClient) createSubResource(rm apiv1.ResourceMeta, subs map[string]interface{}) (*apiv1.ResourceInstance, error) { 633 var execErr error 634 var instanceBytes []byte 635 wg := &sync.WaitGroup{} 636 bytesMutex := &sync.Mutex{} 637 638 for subName, sub := range subs { 639 wg.Add(1) 640 641 url := c.createAPIServerURL(fmt.Sprintf("%s/%s", rm.GetSelfLink(), subName)) 642 643 r := map[string]interface{}{ 644 subName: sub, 645 } 646 bts, err := json.Marshal(r) 647 if err != nil { 648 return nil, err 649 } 650 651 go func(sn string) { 652 defer wg.Done() 653 var err error 654 bytesMutex.Lock() 655 instanceBytes, err = c.ExecuteAPI(http.MethodPut, url, nil, bts) 656 if err != nil { 657 execErr = err 658 c.logger.Errorf("failed to link sub resource %s to resource %s: %v", sn, rm.Name, err) 659 } 660 bytesMutex.Unlock() 661 }(subName) 662 } 663 664 wg.Wait() 665 if execErr != nil { 666 return nil, execErr 667 } 668 669 ri := &apiv1.ResourceInstance{} 670 err := json.Unmarshal(instanceBytes, ri) 671 if err != nil { 672 return nil, err 673 } 674 675 return ri, nil 676 } 677 678 // GetResource gets a single resource 679 func (c *ServiceClient) GetResource(url string) (*apiv1.ResourceInstance, error) { 680 response, err := c.ExecuteAPI(http.MethodGet, c.createAPIServerURL(url), nil, nil) 681 if err != nil { 682 return nil, err 683 } 684 ri := &apiv1.ResourceInstance{} 685 err = json.Unmarshal(response, ri) 686 return ri, err 687 } 688 689 // GetResource gets a single resource 690 func (c *ServiceClient) GetResources(iface apiv1.Interface) ([]apiv1.Interface, error) { 691 inst, err := iface.AsInstance() 692 if err != nil { 693 return nil, err 694 } 695 696 response, err := c.ExecuteAPI(http.MethodGet, c.createAPIServerURL(inst.GetKindLink()), nil, nil) 697 if err != nil { 698 return nil, err 699 } 700 701 instances := []*apiv1.ResourceInstance{} 702 err = json.Unmarshal(response, &instances) 703 if err != nil { 704 return nil, err 705 } 706 707 ifaces := []apiv1.Interface{} 708 for i := range instances { 709 ifaces = append(ifaces, instances[i]) 710 } 711 return ifaces, nil 712 } 713 714 // UpdateResourceFinalizer - Add or remove a finalizer from a resource 715 func (c *ServiceClient) UpdateResourceFinalizer(res *apiv1.ResourceInstance, finalizer, description string, addAction bool) (*apiv1.ResourceInstance, error) { 716 if addAction { 717 res.Finalizers = append(res.Finalizers, apiv1.Finalizer{Name: finalizer, Description: description}) 718 } else { 719 cleanedFinalizer := make([]apiv1.Finalizer, 0) 720 for _, f := range res.Finalizers { 721 if f.Name != finalizer { 722 cleanedFinalizer = append(cleanedFinalizer, f) 723 } 724 } 725 res.Finalizers = cleanedFinalizer 726 } 727 728 return c.UpdateResourceInstance(res) 729 } 730 731 // UpdateResource updates a resource 732 func (c *ServiceClient) UpdateResource(url string, bts []byte) (*apiv1.ResourceInstance, error) { 733 log.DeprecationWarningReplace("UpdateResource", "UpdateResourceInstance") 734 735 response, err := c.ExecuteAPI(http.MethodPut, c.createAPIServerURL(url), nil, bts) 736 if err != nil { 737 return nil, err 738 } 739 ri := &apiv1.ResourceInstance{} 740 err = json.Unmarshal(response, ri) 741 return ri, err 742 } 743 744 // CreateResource deletes a resource 745 func (c *ServiceClient) CreateResource(url string, bts []byte) (*apiv1.ResourceInstance, error) { 746 log.DeprecationWarningReplace("CreateResource", "CreateResourceInstance") 747 748 response, err := c.ExecuteAPI(http.MethodPost, c.createAPIServerURL(url), nil, bts) 749 if err != nil { 750 return nil, err 751 } 752 ri := &apiv1.ResourceInstance{} 753 err = json.Unmarshal(response, ri) 754 return ri, err 755 } 756 757 func (c *ServiceClient) getCachedResource(data *apiv1.ResourceInstance) (*apiv1.ResourceInstance, error) { 758 switch data.Kind { 759 case management.AccessRequestDefinitionGVK().Kind: 760 return c.caches.GetAccessRequestDefinitionByName(data.Name) 761 case management.CredentialRequestDefinitionGVK().Kind: 762 return c.caches.GetCredentialRequestDefinitionByName(data.Name) 763 case management.APIServiceInstanceGVK().Kind: 764 return c.caches.GetAPIServiceInstanceByName(data.Name) 765 } 766 return nil, nil 767 } 768 769 func (c *ServiceClient) addResourceToCache(data *apiv1.ResourceInstance) { 770 switch data.Kind { 771 case management.AccessRequestDefinitionGVK().Kind: 772 c.caches.AddAccessRequestDefinition(data) 773 case management.CredentialRequestDefinitionGVK().Kind: 774 c.caches.AddCredentialRequestDefinition(data) 775 case management.APIServiceInstanceGVK().Kind: 776 c.caches.AddAPIServiceInstance(data) 777 } 778 } 779 780 // updateORCreateResourceInstance 781 func (c *ServiceClient) updateSpecORCreateResourceInstance(data *apiv1.ResourceInstance) (*apiv1.ResourceInstance, error) { 782 // default to post 783 url := c.createAPIServerURL(data.GetKindLink()) 784 method := coreapi.POST 785 786 // check if the KIND and ID combo have an item in the cache 787 existingRI, err := c.getCachedResource(data) 788 updateRI := true 789 updateAgentDetails := true 790 791 if err == nil && existingRI != nil && existingRI.Metadata.Scope.Name == data.Metadata.Scope.Name { 792 url = c.createAPIServerURL(data.GetSelfLink()) 793 method = coreapi.PUT 794 795 // check if either hash or title has changed and mark for update 796 oldHash, _ := util.GetAgentDetailsValue(existingRI, defs.AttrSpecHash) 797 newHash, _ := util.GetAgentDetailsValue(data, defs.AttrSpecHash) 798 if oldHash == newHash && existingRI.Title == data.Title { 799 log.Debug("no updates to the hash or to the title") 800 updateRI = false 801 } 802 803 // check if x-agent-details have changed and mark for update 804 oldAgentDetails := util.GetAgentDetails(existingRI) 805 newAgentDetails := util.GetAgentDetails(data) 806 if util.MapsEqual(oldAgentDetails, newAgentDetails) { 807 log.Debug("no updates to the x-agent-details") 808 updateAgentDetails = false 809 } 810 811 // if no changes altogether, return without update 812 if !updateRI && !updateAgentDetails { 813 log.Trace("no updates made to the resource instance or to the x-agent-details.") 814 return existingRI, nil 815 } 816 817 // Update the spec and agent details subresource, if they exist in incoming data 818 existingRI.Spec = data.Spec 819 existingRI.SubResources = data.SubResources 820 existingRI.Title = data.Title 821 existingRI.Metadata.ResourceVersion = "" 822 823 // set the data and subresources to be pushed 824 data = existingRI 825 } 826 827 newRI := &apiv1.ResourceInstance{} 828 if updateRI { 829 reqBytes, err := json.Marshal(data) 830 if err != nil { 831 return nil, err 832 } 833 834 response, err := c.ExecuteAPI(method, url, nil, reqBytes) 835 if err != nil { 836 return nil, err 837 } 838 839 err = json.Unmarshal(response, newRI) 840 if err != nil { 841 return nil, err 842 } 843 } else { 844 newRI = existingRI 845 } 846 847 if data := util.GetAgentDetails(data); data != nil && updateAgentDetails { 848 // only send in the agent details here, that is all the agent needs to update for anything here 849 newRI, err = c.createSubResource(newRI.ResourceMeta, map[string]interface{}{defs.XAgentDetails: data}) 850 if err != nil { 851 return nil, err 852 } 853 854 } 855 856 if existingRI == nil { 857 c.addResourceToCache(newRI) 858 } 859 return newRI, err 860 } 861 862 // CreateOrUpdateResource deletes a resource 863 func (c *ServiceClient) CreateOrUpdateResource(data apiv1.Interface) (*apiv1.ResourceInstance, error) { 864 data.SetScopeName(c.cfg.GetEnvironmentName()) 865 ri, err := data.AsInstance() 866 if err != nil { 867 return nil, err 868 } 869 870 ri, err = c.updateSpecORCreateResourceInstance(ri) 871 return ri, err 872 } 873 874 // UpdateResourceInstance - updates a ResourceInstance 875 func (c *ServiceClient) UpdateResourceInstance(ri apiv1.Interface) (*apiv1.ResourceInstance, error) { 876 inst, err := ri.AsInstance() 877 if err != nil { 878 return nil, err 879 } 880 if inst.GetSelfLink() == "" { 881 return nil, fmt.Errorf("could not remove resource instance, could not get self link") 882 } 883 inst.Metadata.ResourceVersion = "" 884 bts, err := json.Marshal(ri) 885 if err != nil { 886 return nil, err 887 } 888 bts, err = c.ExecuteAPI(coreapi.PUT, c.createAPIServerURL(inst.GetSelfLink()), nil, bts) 889 if err != nil { 890 return nil, err 891 } 892 r := &apiv1.ResourceInstance{} 893 err = json.Unmarshal(bts, r) 894 return r, err 895 } 896 897 // DeleteResourceInstance - deletes a ResourceInstance 898 func (c *ServiceClient) DeleteResourceInstance(ri apiv1.Interface) error { 899 inst, err := ri.AsInstance() 900 if err != nil { 901 return err 902 } 903 if inst.GetSelfLink() == "" { 904 return fmt.Errorf("could not remove resource instance, could not get self link") 905 } 906 bts, err := json.Marshal(ri) 907 if err != nil { 908 return err 909 } 910 _, err = c.ExecuteAPI(coreapi.DELETE, c.createAPIServerURL(inst.GetSelfLink()), nil, bts) 911 return err 912 } 913 914 // CreateResourceInstance - creates a ResourceInstance 915 func (c *ServiceClient) CreateResourceInstance(ri apiv1.Interface) (*apiv1.ResourceInstance, error) { 916 inst, err := ri.AsInstance() 917 if err != nil { 918 return nil, err 919 } 920 if inst.GetKindLink() == "" { 921 return nil, fmt.Errorf("could not create resource instance, could not get self link") 922 } 923 inst.Metadata.ResourceVersion = "" 924 bts, err := json.Marshal(ri) 925 if err != nil { 926 return nil, err 927 } 928 bts, err = c.ExecuteAPI(coreapi.POST, c.createAPIServerURL(inst.GetKindLink()), nil, bts) 929 if err != nil { 930 return nil, err 931 } 932 r := &apiv1.ResourceInstance{} 933 err = json.Unmarshal(bts, r) 934 return r, err 935 } 936 937 // PatchSubResource - applies the patches to the sub-resource 938 func (c *ServiceClient) PatchSubResource(ri apiv1.Interface, subResourceName string, patches []map[string]interface{}) (*apiv1.ResourceInstance, error) { 939 inst, err := ri.AsInstance() 940 if err != nil { 941 return nil, err 942 } 943 944 if inst.GetSelfLink() == "" { 945 return nil, fmt.Errorf("could not apply patch to resource instance, unable to get self link") 946 } 947 948 // no patches to be applied 949 if len(patches) == 0 { 950 return inst, nil 951 } 952 953 p := make([]map[string]interface{}, 0) 954 // add operation to build object tree to allow api-server 955 // to expand the sub-resources while applying the patch 956 p = append(p, map[string]interface{}{ 957 PatchOperation: PatchOpBuildObjectTree, 958 PatchPath: fmt.Sprintf("/%s", subResourceName), 959 }) 960 961 p = append(p, patches...) 962 963 bts, err := json.Marshal(p) 964 if err != nil { 965 return nil, err 966 } 967 968 url := c.createAPIServerURL(fmt.Sprintf("%s/%s", inst.GetSelfLink(), subResourceName)) 969 bts, err = c.ExecuteAPIWithHeader(coreapi.PATCH, url, nil, bts, map[string]string{HdrContentType: ContentTypeJsonPatch}) 970 if err != nil { 971 return nil, err 972 } 973 974 r := &apiv1.ResourceInstance{} 975 err = json.Unmarshal(bts, r) 976 return r, err 977 }