github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/cf/api/services.go (about) 1 package api 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net/url" 8 "strings" 9 10 "code.cloudfoundry.org/cli/cf/api/resources" 11 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 12 "code.cloudfoundry.org/cli/cf/errors" 13 "code.cloudfoundry.org/cli/cf/models" 14 "code.cloudfoundry.org/cli/cf/net" 15 ) 16 17 //go:generate counterfeiter . ServiceRepository 18 19 type ServiceRepository interface { 20 PurgeServiceOffering(offering models.ServiceOffering) error 21 GetServiceOfferingByGUID(serviceGUID string) (offering models.ServiceOffering, apiErr error) 22 FindServiceOfferingsByLabel(name string) (offering models.ServiceOfferings, apiErr error) 23 FindServiceOfferingByLabelAndProvider(name, provider string) (offering models.ServiceOffering, apiErr error) 24 25 FindServiceOfferingsForSpaceByLabel(spaceGUID, name string) (offering models.ServiceOfferings, apiErr error) 26 27 GetAllServiceOfferings() (offerings models.ServiceOfferings, apiErr error) 28 GetServiceOfferingsForSpace(spaceGUID string) (offerings models.ServiceOfferings, apiErr error) 29 FindInstanceByName(name string) (instance models.ServiceInstance, apiErr error) 30 PurgeServiceInstance(instance models.ServiceInstance) error 31 CreateServiceInstance(name, planGUID string, params map[string]interface{}, tags []string) (apiErr error) 32 UpdateServiceInstance(instanceGUID, planGUID string, params map[string]interface{}, tags []string) (apiErr error) 33 RenameService(instance models.ServiceInstance, newName string) (apiErr error) 34 DeleteService(instance models.ServiceInstance) (apiErr error) 35 FindServicePlanByDescription(planDescription resources.ServicePlanDescription) (planGUID string, apiErr error) 36 ListServicesFromBroker(brokerGUID string) (services []models.ServiceOffering, err error) 37 ListServicesFromManyBrokers(brokerGUIDs []string) (services []models.ServiceOffering, err error) 38 GetServiceInstanceCountForServicePlan(v1PlanGUID string) (count int, apiErr error) 39 MigrateServicePlanFromV1ToV2(v1PlanGUID, v2PlanGUID string) (changedCount int, apiErr error) 40 } 41 42 type CloudControllerServiceRepository struct { 43 config coreconfig.Reader 44 gateway net.Gateway 45 } 46 47 func NewCloudControllerServiceRepository(config coreconfig.Reader, gateway net.Gateway) (repo CloudControllerServiceRepository) { 48 repo.config = config 49 repo.gateway = gateway 50 return 51 } 52 53 func (repo CloudControllerServiceRepository) GetServiceOfferingByGUID(serviceGUID string) (models.ServiceOffering, error) { 54 offering := new(resources.ServiceOfferingResource) 55 apiErr := repo.gateway.GetResource(repo.config.APIEndpoint()+fmt.Sprintf("/v2/services/%s", serviceGUID), offering) 56 serviceOffering := offering.ToFields() 57 return models.ServiceOffering{ServiceOfferingFields: serviceOffering}, apiErr 58 } 59 60 func (repo CloudControllerServiceRepository) GetServiceOfferingsForSpace(spaceGUID string) (models.ServiceOfferings, error) { 61 return repo.getServiceOfferings(fmt.Sprintf("/v2/spaces/%s/services", spaceGUID)) 62 } 63 64 func (repo CloudControllerServiceRepository) FindServiceOfferingsForSpaceByLabel(spaceGUID, name string) (offerings models.ServiceOfferings, err error) { 65 offerings, err = repo.getServiceOfferings(fmt.Sprintf("/v2/spaces/%s/services?q=%s", spaceGUID, url.QueryEscape("label:"+name))) 66 67 if httpErr, ok := err.(errors.HTTPError); ok && httpErr.ErrorCode() == errors.BadQueryParameter { 68 offerings, err = repo.findServiceOfferingsByPaginating(spaceGUID, name) 69 } 70 71 if err == nil && len(offerings) == 0 { 72 err = errors.NewModelNotFoundError("Service offering", name) 73 } 74 75 return 76 } 77 78 func (repo CloudControllerServiceRepository) findServiceOfferingsByPaginating(spaceGUID, label string) (offerings models.ServiceOfferings, apiErr error) { 79 offerings, apiErr = repo.GetServiceOfferingsForSpace(spaceGUID) 80 if apiErr != nil { 81 return 82 } 83 84 matchingOffering := models.ServiceOfferings{} 85 86 for _, offering := range offerings { 87 if offering.Label == label { 88 matchingOffering = append(matchingOffering, offering) 89 } 90 } 91 return matchingOffering, nil 92 } 93 94 func (repo CloudControllerServiceRepository) GetAllServiceOfferings() (models.ServiceOfferings, error) { 95 return repo.getServiceOfferings("/v2/services") 96 } 97 98 func (repo CloudControllerServiceRepository) getServiceOfferings(path string) ([]models.ServiceOffering, error) { 99 var offerings []models.ServiceOffering 100 apiErr := repo.gateway.ListPaginatedResources( 101 repo.config.APIEndpoint(), 102 path, 103 resources.ServiceOfferingResource{}, 104 func(resource interface{}) bool { 105 if so, ok := resource.(resources.ServiceOfferingResource); ok { 106 offerings = append(offerings, so.ToModel()) 107 } 108 return true 109 }) 110 111 return offerings, apiErr 112 } 113 114 func (repo CloudControllerServiceRepository) FindInstanceByName(name string) (instance models.ServiceInstance, apiErr error) { 115 path := fmt.Sprintf("%s/v2/spaces/%s/service_instances?return_user_provided_service_instances=true&q=%s&inline-relations-depth=1", repo.config.APIEndpoint(), repo.config.SpaceFields().GUID, url.QueryEscape("name:"+name)) 116 117 responseJSON := new(resources.PaginatedServiceInstanceResources) 118 apiErr = repo.gateway.GetResource(path, responseJSON) 119 if apiErr != nil { 120 return 121 } 122 123 if len(responseJSON.Resources) == 0 { 124 apiErr = errors.NewModelNotFoundError("Service instance", name) 125 return 126 } 127 128 instanceResource := responseJSON.Resources[0] 129 instance = instanceResource.ToModel() 130 131 if instanceResource.Entity.ServicePlan.Metadata.GUID != "" { 132 resource := &resources.ServiceOfferingResource{} 133 path = fmt.Sprintf("%s/v2/services/%s", repo.config.APIEndpoint(), instanceResource.Entity.ServicePlan.Entity.ServiceOfferingGUID) 134 apiErr = repo.gateway.GetResource(path, resource) 135 instance.ServiceOffering = resource.ToFields() 136 } 137 138 return 139 } 140 141 func (repo CloudControllerServiceRepository) CreateServiceInstance(name, planGUID string, params map[string]interface{}, tags []string) (err error) { 142 path := "/v2/service_instances?accepts_incomplete=true" 143 request := models.ServiceInstanceCreateRequest{ 144 Name: name, 145 PlanGUID: planGUID, 146 SpaceGUID: repo.config.SpaceFields().GUID, 147 Params: params, 148 Tags: tags, 149 } 150 151 jsonBytes, err := json.Marshal(request) 152 if err != nil { 153 return err 154 } 155 156 err = repo.gateway.CreateResource(repo.config.APIEndpoint(), path, bytes.NewReader(jsonBytes)) 157 158 if httpErr, ok := err.(errors.HTTPError); ok && httpErr.ErrorCode() == errors.ServiceInstanceNameTaken { 159 serviceInstance, findInstanceErr := repo.FindInstanceByName(name) 160 161 if findInstanceErr == nil && serviceInstance.ServicePlan.GUID == planGUID { 162 return errors.NewModelAlreadyExistsError("Service", name) 163 } 164 } 165 166 return 167 } 168 169 func (repo CloudControllerServiceRepository) UpdateServiceInstance(instanceGUID, planGUID string, params map[string]interface{}, tags []string) (err error) { 170 path := fmt.Sprintf("/v2/service_instances/%s?accepts_incomplete=true", instanceGUID) 171 request := models.ServiceInstanceUpdateRequest{ 172 PlanGUID: planGUID, 173 Params: params, 174 Tags: tags, 175 } 176 177 jsonBytes, err := json.Marshal(request) 178 if err != nil { 179 return err 180 } 181 182 err = repo.gateway.UpdateResource(repo.config.APIEndpoint(), path, bytes.NewReader(jsonBytes)) 183 184 return 185 } 186 187 func (repo CloudControllerServiceRepository) RenameService(instance models.ServiceInstance, newName string) (apiErr error) { 188 body := fmt.Sprintf(`{"name":"%s"}`, newName) 189 path := fmt.Sprintf("/v2/service_instances/%s?accepts_incomplete=true", instance.GUID) 190 191 if instance.IsUserProvided() { 192 path = fmt.Sprintf("/v2/user_provided_service_instances/%s", instance.GUID) 193 } 194 return repo.gateway.UpdateResource(repo.config.APIEndpoint(), path, strings.NewReader(body)) 195 } 196 197 func (repo CloudControllerServiceRepository) DeleteService(instance models.ServiceInstance) (apiErr error) { 198 if len(instance.ServiceBindings) > 0 || len(instance.ServiceKeys) > 0 { 199 return errors.NewServiceAssociationError() 200 } 201 path := fmt.Sprintf("/v2/service_instances/%s?%s", instance.GUID, "accepts_incomplete=true") 202 return repo.gateway.DeleteResource(repo.config.APIEndpoint(), path) 203 } 204 205 func (repo CloudControllerServiceRepository) PurgeServiceOffering(offering models.ServiceOffering) error { 206 url := fmt.Sprintf("/v2/services/%s?purge=true", offering.GUID) 207 return repo.gateway.DeleteResource(repo.config.APIEndpoint(), url) 208 } 209 210 func (repo CloudControllerServiceRepository) PurgeServiceInstance(instance models.ServiceInstance) error { 211 url := fmt.Sprintf("/v2/service_instances/%s?purge=true", instance.GUID) 212 return repo.gateway.DeleteResource(repo.config.APIEndpoint(), url) 213 } 214 215 func (repo CloudControllerServiceRepository) FindServiceOfferingsByLabel(label string) (models.ServiceOfferings, error) { 216 path := fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:"+label)) 217 offerings, apiErr := repo.getServiceOfferings(path) 218 219 if apiErr != nil { 220 return models.ServiceOfferings{}, apiErr 221 } else if len(offerings) == 0 { 222 apiErr = errors.NewModelNotFoundError("Service offering", label) 223 return models.ServiceOfferings{}, apiErr 224 } 225 226 return offerings, apiErr 227 } 228 229 func (repo CloudControllerServiceRepository) FindServiceOfferingByLabelAndProvider(label, provider string) (models.ServiceOffering, error) { 230 path := fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:"+label+";provider:"+provider)) 231 offerings, apiErr := repo.getServiceOfferings(path) 232 233 if apiErr != nil { 234 return models.ServiceOffering{}, apiErr 235 } else if len(offerings) == 0 { 236 apiErr = errors.NewModelNotFoundError("Service offering", label+" "+provider) 237 return models.ServiceOffering{}, apiErr 238 } 239 240 return offerings[0], apiErr 241 } 242 243 func (repo CloudControllerServiceRepository) FindServicePlanByDescription(planDescription resources.ServicePlanDescription) (string, error) { 244 path := fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", 245 url.QueryEscape("label:"+planDescription.ServiceLabel+";provider:"+planDescription.ServiceProvider)) 246 247 offerings, err := repo.getServiceOfferings(path) 248 if err != nil { 249 return "", err 250 } 251 252 for _, serviceOfferingResource := range offerings { 253 for _, servicePlanResource := range serviceOfferingResource.Plans { 254 if servicePlanResource.Name == planDescription.ServicePlanName { 255 return servicePlanResource.GUID, nil 256 } 257 } 258 } 259 260 return "", errors.NewModelNotFoundError("Plan", planDescription.String()) 261 } 262 263 func (repo CloudControllerServiceRepository) ListServicesFromManyBrokers(brokerGUIDs []string) ([]models.ServiceOffering, error) { 264 brokerGUIDsString := strings.Join(brokerGUIDs, ",") 265 services := []models.ServiceOffering{} 266 267 err := repo.gateway.ListPaginatedResources( 268 repo.config.APIEndpoint(), 269 fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("service_broker_guid IN "+brokerGUIDsString)), 270 resources.ServiceOfferingResource{}, 271 func(resource interface{}) bool { 272 if service, ok := resource.(resources.ServiceOfferingResource); ok { 273 services = append(services, service.ToModel()) 274 } 275 return true 276 }) 277 return services, err 278 } 279 280 func (repo CloudControllerServiceRepository) ListServicesFromBroker(brokerGUID string) (offerings []models.ServiceOffering, err error) { 281 err = repo.gateway.ListPaginatedResources( 282 repo.config.APIEndpoint(), 283 fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("service_broker_guid:"+brokerGUID)), 284 resources.ServiceOfferingResource{}, 285 func(resource interface{}) bool { 286 if offering, ok := resource.(resources.ServiceOfferingResource); ok { 287 offerings = append(offerings, offering.ToModel()) 288 } 289 return true 290 }) 291 return 292 } 293 294 func (repo CloudControllerServiceRepository) MigrateServicePlanFromV1ToV2(v1PlanGUID, v2PlanGUID string) (changedCount int, apiErr error) { 295 path := fmt.Sprintf("/v2/service_plans/%s/service_instances", v1PlanGUID) 296 body := strings.NewReader(fmt.Sprintf(`{"service_plan_guid":"%s"}`, v2PlanGUID)) 297 response := new(resources.ServiceMigrateV1ToV2Response) 298 299 apiErr = repo.gateway.UpdateResource(repo.config.APIEndpoint(), path, body, response) 300 if apiErr != nil { 301 return 302 } 303 304 changedCount = response.ChangedCount 305 return 306 } 307 308 func (repo CloudControllerServiceRepository) GetServiceInstanceCountForServicePlan(v1PlanGUID string) (count int, apiErr error) { 309 path := fmt.Sprintf("%s/v2/service_plans/%s/service_instances?results-per-page=1", repo.config.APIEndpoint(), v1PlanGUID) 310 response := new(resources.PaginatedServiceInstanceResources) 311 apiErr = repo.gateway.GetResource(path, response) 312 count = response.TotalResults 313 return 314 }