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  }