github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/actor/v2action/service_instance_summary.go (about)

     1  package v2action
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
     8  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2"
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/constant"
    10  	"code.cloudfoundry.org/cli/util/sorting"
    11  	log "github.com/sirupsen/logrus"
    12  )
    13  
    14  type ServiceInstanceShareType string
    15  
    16  type LastOperation ccv2.LastOperation
    17  
    18  const (
    19  	ServiceInstanceIsSharedFrom ServiceInstanceShareType = "SharedFrom"
    20  	ServiceInstanceIsSharedTo   ServiceInstanceShareType = "SharedTo"
    21  	ServiceInstanceIsNotShared  ServiceInstanceShareType = "NotShared"
    22  )
    23  
    24  type ServiceInstanceSummary struct {
    25  	ServiceInstance
    26  
    27  	ServicePlan                       ServicePlan
    28  	Service                           Service
    29  	ServiceInstanceSharingFeatureFlag bool
    30  	ServiceInstanceShareType          ServiceInstanceShareType
    31  	ServiceInstanceSharedFrom         ServiceInstanceSharedFrom
    32  	ServiceInstanceSharedTos          []ServiceInstanceSharedTo
    33  	BoundApplications                 []BoundApplication
    34  }
    35  
    36  func (s ServiceInstanceSummary) IsShareable() bool {
    37  	return s.ServiceInstanceSharingFeatureFlag && s.Service.Extra.Shareable
    38  }
    39  
    40  func (s ServiceInstanceSummary) IsNotShared() bool {
    41  	return s.ServiceInstanceShareType == ServiceInstanceIsNotShared
    42  }
    43  
    44  func (s ServiceInstanceSummary) IsSharedFrom() bool {
    45  	return s.ServiceInstanceShareType == ServiceInstanceIsSharedFrom
    46  }
    47  
    48  func (s ServiceInstanceSummary) IsSharedTo() bool {
    49  	return s.ServiceInstanceShareType == ServiceInstanceIsSharedTo
    50  }
    51  
    52  type BoundApplication struct {
    53  	AppName            string
    54  	LastOperation      LastOperation
    55  	ServiceBindingName string
    56  }
    57  
    58  func (actor Actor) GetServiceInstanceSummaryByNameAndSpace(name string, spaceGUID string) (ServiceInstanceSummary, Warnings, error) {
    59  	serviceInstance, instanceWarnings, err := actor.GetServiceInstanceByNameAndSpace(name, spaceGUID)
    60  	allWarnings := Warnings(instanceWarnings)
    61  	if err != nil {
    62  		return ServiceInstanceSummary{}, allWarnings, err
    63  	}
    64  
    65  	serviceInstanceSummary, warnings, err := actor.getSummaryInfoCompositeForInstance(spaceGUID, serviceInstance, true)
    66  	return serviceInstanceSummary, append(allWarnings, warnings...), err
    67  }
    68  
    69  func (actor Actor) GetServiceInstancesSummaryBySpace(spaceGUID string) ([]ServiceInstanceSummary, Warnings, error) {
    70  	spaceSummary, warnings, err := actor.CloudControllerClient.GetSpaceSummary(spaceGUID)
    71  	allWarnings := Warnings(warnings)
    72  	if err != nil {
    73  		return []ServiceInstanceSummary{}, allWarnings, err
    74  	}
    75  
    76  	services, warnings, err := actor.CloudControllerClient.GetServices()
    77  	allWarnings = append(allWarnings, warnings...)
    78  	if err != nil {
    79  		return []ServiceInstanceSummary{}, allWarnings, err
    80  	}
    81  
    82  	serviceGUIDToBrokerName := map[string]string{}
    83  	for _, service := range services {
    84  		serviceGUIDToBrokerName[service.GUID] = service.ServiceBrokerName
    85  	}
    86  
    87  	var serviceInstanceSummaries []ServiceInstanceSummary
    88  	for _, serviceInstance := range spaceSummary.ServiceInstances {
    89  		instanceSummary := ServiceInstanceSummary{}
    90  
    91  		instanceSummary.Name = serviceInstance.Name
    92  		instanceSummary.ServicePlan.Name = serviceInstance.ServicePlan.Name
    93  		instanceSummary.Service.Label = serviceInstance.ServicePlan.Service.Label
    94  		instanceSummary.LastOperation = serviceInstance.LastOperation
    95  		instanceSummary.Service.ServiceBrokerName = serviceGUIDToBrokerName[serviceInstance.ServicePlan.Service.GUID]
    96  		instanceSummary.ServiceInstance.Type = discoverServiceInstanceType(serviceInstance)
    97  		instanceSummary.BoundApplications = findServiceInstanceBoundApplications(serviceInstance.Name, spaceSummary.Applications)
    98  
    99  		serviceInstanceSummaries = append(serviceInstanceSummaries, instanceSummary)
   100  	}
   101  
   102  	return serviceInstanceSummaries, allWarnings, nil
   103  }
   104  
   105  // getAndSetSharedInformation gets a service instance's shared from or shared to information,
   106  func (actor Actor) getAndSetSharedInformation(summary *ServiceInstanceSummary, spaceGUID string) (Warnings, error) {
   107  	var (
   108  		warnings Warnings
   109  		err      error
   110  	)
   111  
   112  	// Part of determining if a service instance is shareable, we need to find
   113  	// out if the service_instance_sharing feature flag is enabled
   114  	featureFlags, featureFlagsWarnings, featureFlagsErr := actor.CloudControllerClient.GetConfigFeatureFlags()
   115  	allWarnings := Warnings(featureFlagsWarnings)
   116  	if featureFlagsErr != nil {
   117  		return allWarnings, featureFlagsErr
   118  	}
   119  
   120  	for _, flag := range featureFlags {
   121  		if flag.Name == string(constant.FeatureFlagServiceInstanceSharing) {
   122  			summary.ServiceInstanceSharingFeatureFlag = flag.Enabled
   123  		}
   124  	}
   125  
   126  	// Service instance is shared from if:
   127  	// 1. the source space of the service instance is empty (API returns json null)
   128  	// 2. the targeted space is not the same as the source space of the service instance AND
   129  	//    we call the shared_from url and it returns a non-empty resource
   130  	if summary.ServiceInstance.SpaceGUID == "" || summary.ServiceInstance.SpaceGUID != spaceGUID {
   131  		summary.ServiceInstanceSharedFrom, warnings, err = actor.GetServiceInstanceSharedFromByServiceInstance(summary.ServiceInstance.GUID)
   132  		allWarnings = append(allWarnings, warnings...)
   133  		if err != nil {
   134  			// if the API version does not support service instance sharing, ignore the 404
   135  			if _, ok := err.(ccerror.ResourceNotFoundError); !ok {
   136  				return allWarnings, err
   137  			}
   138  		}
   139  
   140  		if summary.ServiceInstanceSharedFrom.SpaceGUID != "" {
   141  			summary.ServiceInstanceShareType = ServiceInstanceIsSharedFrom
   142  		} else {
   143  			summary.ServiceInstanceShareType = ServiceInstanceIsNotShared
   144  		}
   145  
   146  		return allWarnings, nil
   147  	}
   148  
   149  	// Service instance is shared to if:
   150  	// the targeted space is the same as the source space of the service instance AND
   151  	// we call the shared_to url and get a non-empty list
   152  	summary.ServiceInstanceSharedTos, warnings, err = actor.GetServiceInstanceSharedTosByServiceInstance(summary.ServiceInstance.GUID)
   153  	allWarnings = append(allWarnings, warnings...)
   154  	if err != nil {
   155  		// if the API version does not support service instance sharing, ignore the 404
   156  		if _, ok := err.(ccerror.ResourceNotFoundError); !ok {
   157  			return allWarnings, err
   158  		}
   159  	}
   160  
   161  	if len(summary.ServiceInstanceSharedTos) > 0 {
   162  		summary.ServiceInstanceShareType = ServiceInstanceIsSharedTo
   163  	} else {
   164  		summary.ServiceInstanceShareType = ServiceInstanceIsNotShared
   165  	}
   166  
   167  	return allWarnings, nil
   168  }
   169  
   170  func (actor Actor) getSummaryInfoCompositeForInstance(spaceGUID string, serviceInstance ServiceInstance, retrieveSharedInfo bool) (ServiceInstanceSummary, Warnings, error) {
   171  	log.WithField("GUID", serviceInstance.GUID).Info("looking up service instance info")
   172  
   173  	serviceInstanceSummary := ServiceInstanceSummary{ServiceInstance: serviceInstance}
   174  	var (
   175  		serviceBindings []ServiceBinding
   176  		allWarnings     Warnings
   177  	)
   178  
   179  	if serviceInstance.IsManaged() {
   180  		log.Debug("service is managed")
   181  		if retrieveSharedInfo {
   182  			sharedWarnings, err := actor.getAndSetSharedInformation(&serviceInstanceSummary, spaceGUID)
   183  			allWarnings = Warnings(sharedWarnings)
   184  			if err != nil {
   185  				log.WithField("GUID", serviceInstance.GUID).Errorln("looking up share info:", err)
   186  				return serviceInstanceSummary, allWarnings, err
   187  			}
   188  		}
   189  
   190  		servicePlan, planWarnings, err := actor.GetServicePlan(serviceInstance.ServicePlanGUID)
   191  		allWarnings = append(allWarnings, planWarnings...)
   192  		if err != nil {
   193  			log.WithField("service_plan_guid", serviceInstance.ServicePlanGUID).Errorln("looking up service plan:", err)
   194  			if _, ok := err.(ccerror.ForbiddenError); !ok {
   195  				return serviceInstanceSummary, allWarnings, err
   196  			}
   197  			log.Warning("Forbidden Error - ignoring and continue")
   198  			allWarnings = append(allWarnings, fmt.Sprintf("This org is not authorized to view necessary data about this service plan. Contact your administrator regarding service GUID %s.", serviceInstance.ServicePlanGUID))
   199  		}
   200  		serviceInstanceSummary.ServicePlan = servicePlan
   201  
   202  		service, serviceWarnings, err := actor.GetService(serviceInstance.ServiceGUID)
   203  		allWarnings = append(allWarnings, serviceWarnings...)
   204  		if err != nil {
   205  			log.WithField("service_guid", serviceInstance.ServiceGUID).Errorln("looking up service:", err)
   206  			if _, ok := err.(ccerror.ForbiddenError); !ok {
   207  				return serviceInstanceSummary, allWarnings, err
   208  			}
   209  			log.Warning("Forbidden Error - ignoring and continue")
   210  			allWarnings = append(allWarnings, fmt.Sprintf("This org is not authorized to view necessary data about this service. Contact your administrator regarding service GUID %s.", serviceInstance.ServiceGUID))
   211  		}
   212  		serviceInstanceSummary.Service = service
   213  
   214  		var bindingsWarnings Warnings
   215  		serviceBindings, bindingsWarnings, err = actor.GetServiceBindingsByServiceInstance(serviceInstance.GUID)
   216  		allWarnings = append(allWarnings, bindingsWarnings...)
   217  		if err != nil {
   218  			log.WithField("GUID", serviceInstance.GUID).Errorln("looking up service binding:", err)
   219  			return serviceInstanceSummary, allWarnings, err
   220  		}
   221  	} else {
   222  		log.Debug("service is user provided")
   223  		var bindingsWarnings Warnings
   224  		var err error
   225  		serviceBindings, bindingsWarnings, err = actor.GetServiceBindingsByUserProvidedServiceInstance(serviceInstance.GUID)
   226  		allWarnings = append(allWarnings, bindingsWarnings...)
   227  		if err != nil {
   228  			log.WithField("service_instance_guid", serviceInstance.GUID).Errorln("looking up service bindings:", err)
   229  			return serviceInstanceSummary, allWarnings, err
   230  		}
   231  	}
   232  
   233  	for _, serviceBinding := range serviceBindings {
   234  		log.WithFields(log.Fields{
   235  			"app_guid":             serviceBinding.AppGUID,
   236  			"service_binding_guid": serviceBinding.GUID,
   237  		}).Debug("application lookup")
   238  
   239  		app, appWarnings, err := actor.GetApplication(serviceBinding.AppGUID)
   240  		allWarnings = append(allWarnings, appWarnings...)
   241  		if err != nil {
   242  			log.WithFields(log.Fields{
   243  				"app_guid":             serviceBinding.AppGUID,
   244  				"service_binding_guid": serviceBinding.GUID,
   245  			}).Errorln("looking up application:", err)
   246  			return serviceInstanceSummary, allWarnings, err
   247  		}
   248  
   249  		serviceInstanceSummary.BoundApplications = append(
   250  			serviceInstanceSummary.BoundApplications,
   251  			BoundApplication{
   252  				AppName:            app.Name,
   253  				ServiceBindingName: serviceBinding.Name,
   254  				LastOperation:      LastOperation(serviceBinding.LastOperation),
   255  			})
   256  	}
   257  
   258  	sort.Slice(
   259  		serviceInstanceSummary.BoundApplications,
   260  		func(i, j int) bool {
   261  			return sorting.LessIgnoreCase(serviceInstanceSummary.BoundApplications[i].AppName, serviceInstanceSummary.BoundApplications[j].AppName)
   262  		})
   263  
   264  	return serviceInstanceSummary, allWarnings, nil
   265  }
   266  
   267  func findServiceInstanceBoundApplications(instanceName string, spaceApps []ccv2.SpaceSummaryApplication) []BoundApplication {
   268  	var applicationNames []BoundApplication
   269  
   270  	for _, app := range spaceApps {
   271  		for _, name := range app.ServiceNames {
   272  			if name == instanceName {
   273  				applicationNames = append(applicationNames, BoundApplication{AppName: app.Name})
   274  			}
   275  		}
   276  	}
   277  
   278  	return applicationNames
   279  }
   280  
   281  func discoverServiceInstanceType(instance ccv2.SpaceSummaryServiceInstance) constant.ServiceInstanceType {
   282  	if isUserProvided(instance) {
   283  		return constant.ServiceInstanceTypeUserProvidedService
   284  	}
   285  	return constant.ServiceInstanceTypeManagedService
   286  }
   287  
   288  func isUserProvided(instance ccv2.SpaceSummaryServiceInstance) bool {
   289  	// For now we rely on empty service_plan guid to decide if the instance is user provided or managed
   290  	// This could be improved in future if we add the Type of the service instnace to the summary endpoint result body
   291  	return instance.ServicePlan.GUID == ""
   292  }