github.com/swisscom/cloudfoundry-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 }