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 }