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