github.com/swisscom/cloudfoundry-cli@v7.1.0+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  }