Skip to content

Commit

Permalink
enrich binding with instance info and tags (#81)
Browse files Browse the repository at this point in the history
* enrich binding with instance info and service offering tags
  • Loading branch information
pavelmaliy authored Sep 14, 2021
1 parent a2116ef commit bad06ff
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 104 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ generate: controller-gen
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."

# Build the docker image
docker-build: test
docker-build:
docker build . -t ${IMG}

# Push the docker image
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ This feature is still under development, review, and testing.
| operationURL | `string` | The URL of the current operation performed on the service instance. |
| operationType | `string`| The type of the current operation. Possible values are CREATE, UPDATE, or DELETE. |
| conditions | `[]condition` | An array of conditions describing the status of the service instance.<br/>The possible condition types are:<br>- `Ready`: set to `true` if the instance is ready and usable<br/>- `Failed`: set to `true` when an operation on the service instance fails.<br/> In the case of failure, the details about the error are available in the condition message.<br>- `Succeeded`: set to `true` when an operation on the service instance succeeded. In case of `false` operation considered as in progress unless `Failed` condition exists.
| tags | `[]string` | Tags describing the ServiceInstance will be copied to `ServiceBinding` secret in key called `tags`.



Expand All @@ -235,9 +236,12 @@ This feature is still under development, review, and testing.
| serviceInstanceName`*` | `string` | The Kubernetes name of the service instance to bind, should be in the namespace of the binding. |
| externalName | `string` | The name for the service binding in SAP BTP, defaults to the binding `metadata.name` if not specified. |
| secretName | `string` | The name of the secret where the credentials are stored, defaults to the binding `metadata.name` if not specified. |
| secretKey | `string` | The key inside the binding secret to store the credentials returned by the broker encoded as json to support complex data structures. |
| parameters | `[]object` | Some services support the provisioning of additional configuration parameters during the bind request.<br/>For the list of supported parameters, check the documentation of the particular service offering.|
| parametersFrom | `[]object` | List of sources to populate parameters. |
| userInfo | `object` | Contains information about the user that last modified this service binding. |
| userInfo | `object` | Contains information about the user that last modified this service binding. |



#### Status
| Parameter | Type | Description |
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/serviceinstance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ type ServiceInstanceStatus struct {
// +optional
InstanceID string `json:"instanceID,omitempty"`

// Tags describing the ServiceInstance will be copied to `ServiceBinding` secret in key called `tags`.
Tags []string `json:"tags,omitempty"`

// URL of ongoing operation for the service instance
OperationURL string `json:"operationURL,omitempty"`

Expand Down
6 changes: 6 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 69 additions & 24 deletions client/sm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type Client interface {
ListInstances(*Parameters) (*types.ServiceInstances, error)
GetInstanceByID(string, *Parameters) (*types.ServiceInstance, error)
UpdateInstance(id string, updatedInstance *types.ServiceInstance, serviceName string, planName string, q *Parameters, user string) (*types.ServiceInstance, string, error)
Provision(instance *types.ServiceInstance, serviceName string, planName string, q *Parameters, user string) (string, string, error)
Provision(instance *types.ServiceInstance, serviceName string, planName string, q *Parameters, user string) (*ProvisionResponse, error)
Deprovision(id string, q *Parameters, user string) (string, error)

ListBindings(*Parameters) (*types.ServiceBindings, error)
Expand Down Expand Up @@ -78,6 +78,18 @@ type serviceManagerClient struct {
HTTPClient auth.HTTPClient
}

type planInfo struct {
planID string
serviceOffering *types.ServiceOffering
}

type ProvisionResponse struct {
InstanceID string
PlanID string
Location string
Tags json.RawMessage
}

// NewClientWithAuth returns new SM Client configured with the provided configuration
func NewClient(ctx context.Context, config *ClientConfig, httpClient auth.HTTPClient) (Client, error) {
if httpClient != nil {
Expand All @@ -104,33 +116,43 @@ func NewClient(ctx context.Context, config *ClientConfig, httpClient auth.HTTPCl
}

// Provision provisions a new service instance in service manager
func (client *serviceManagerClient) Provision(instance *types.ServiceInstance, serviceName string, planName string, q *Parameters, user string) (string, string, error) {
func (client *serviceManagerClient) Provision(instance *types.ServiceInstance, serviceName string, planName string, q *Parameters, user string) (*ProvisionResponse, error) {
var newInstance *types.ServiceInstance
var instanceID string
if len(serviceName) == 0 || len(planName) == 0 {
return "", "", fmt.Errorf("missing field values. Specify service name and plan name for the instance '%s'", instance.Name)
return nil, fmt.Errorf("missing field values. Specify service name and plan name for the instance '%s'", instance.Name)
}

planID, err := client.getAndValidatePlanID(instance.ServicePlanID, serviceName, planName)
planInfo, err := client.getPlanInfo(instance.ServicePlanID, serviceName, planName)
if err != nil {
return "", "", err
return nil, err
}

instance.ServicePlanID = planID
instance.ServicePlanID = planInfo.planID

location, err := client.register(instance, web.ServiceInstancesURL, q, user, &newInstance)
if err != nil {
return "", "", err
return nil, err
}
if len(location) > 0 {
instanceID = ExtractInstanceID(location)
if len(instanceID) == 0 {
return "", "", fmt.Errorf("failed to extract the service instance ID from the async operation URL: %s", location)
return nil, fmt.Errorf("failed to extract the service instance ID from the async operation URL: %s", location)
}
} else if newInstance != nil {
instanceID = newInstance.ID
}
return instanceID, location, nil

res := &ProvisionResponse{
InstanceID: instanceID,
Location: location,
PlanID: planInfo.planID,
}
if planInfo.serviceOffering != nil {
res.Tags = planInfo.serviceOffering.Tags
}
return res, nil

}

// Bind creates binding to an instance in service manager
Expand Down Expand Up @@ -193,11 +215,11 @@ func (client *serviceManagerClient) Unbind(id string, q *Parameters, user string
func (client *serviceManagerClient) UpdateInstance(id string, updatedInstance *types.ServiceInstance, serviceName string, planName string, q *Parameters, user string) (*types.ServiceInstance, string, error) {
var result *types.ServiceInstance

planID, err := client.getAndValidatePlanID(updatedInstance.ServicePlanID, serviceName, planName)
planInfo, err := client.getPlanInfo(updatedInstance.ServicePlanID, serviceName, planName)
if err != nil {
return nil, "", err
}
updatedInstance.ServicePlanID = planID
updatedInstance.ServicePlanID = planInfo.planID
location, err := client.update(updatedInstance, web.ServiceInstancesURL, id, q, user, &result)
if err != nil {
return nil, "", err
Expand Down Expand Up @@ -331,18 +353,15 @@ func (client *serviceManagerClient) callWithUser(method string, smpath string, b
return resp, nil
}

func (client *serviceManagerClient) getAndValidatePlanID(planID string, serviceName string, planName string) (string, error) {
query := &Parameters{
FieldQuery: []string{fmt.Sprintf("catalog_name eq '%s'", serviceName)},
}
offerings, err := client.ListOfferings(query)
func (client *serviceManagerClient) getPlanInfo(planID string, serviceName string, planName string) (*planInfo, error) {
offerings, err := client.getServiceOfferingsByName(serviceName)
if err != nil {
return "", err
return nil, err
}

var commaSepOfferingIds string
if len(offerings.ServiceOfferings) == 0 {
return "", fmt.Errorf("couldn't find the service offering '%s'", serviceName)
return nil, fmt.Errorf("couldn't find the service offering '%s'", serviceName)
}

serviceOfferingIds := make([]string, 0, len(offerings.ServiceOfferings))
Expand All @@ -351,22 +370,28 @@ func (client *serviceManagerClient) getAndValidatePlanID(planID string, serviceN
}
commaSepOfferingIds = "'" + strings.Join(serviceOfferingIds, "', '") + "'"

query = &Parameters{
query := &Parameters{
FieldQuery: []string{fmt.Sprintf("catalog_name eq '%s'", planName), fmt.Sprintf("service_offering_id in (%s)", commaSepOfferingIds)},
}

plans, err := client.ListPlans(query)
if err != nil {
return "", err
return nil, err
}
if len(plans.ServicePlans) == 0 {
return "", fmt.Errorf("couldn't find the service plan '%s' for the service offering '%s'", planName, serviceName)
return nil, fmt.Errorf("couldn't find the service plan '%s' for the service offering '%s'", planName, serviceName)
} else if len(plans.ServicePlans) == 1 && len(planID) == 0 {
return plans.ServicePlans[0].ID, nil
return &planInfo{
planID: plans.ServicePlans[0].ID,
serviceOffering: findOffering(plans.ServicePlans[0].ServiceOfferingID, offerings),
}, nil
} else {
for _, plan := range plans.ServicePlans {
if plan.ID == planID {
return plan.ID, nil
return &planInfo{
planID: plan.ID,
serviceOffering: findOffering(plan.ServiceOfferingID, offerings),
}, nil
}
}
}
Expand All @@ -376,8 +401,28 @@ func (client *serviceManagerClient) getAndValidatePlanID(planID string, serviceN
} else {
err = fmt.Errorf("ambiguity error: found more than one resource that matches the provided offering name '%s' and plan name '%s'. Please provide servicePlanID", serviceName, planName)
}
return "", err
return nil, err
}

func (client *serviceManagerClient) getServiceOfferingsByName(serviceName string) (*types.ServiceOfferings, error) {
query := &Parameters{
FieldQuery: []string{fmt.Sprintf("catalog_name eq '%s'", serviceName)},
}
offerings, err := client.ListOfferings(query)
if err != nil {
return nil, err
}
return offerings, nil
}

func findOffering(id string, offerings *types.ServiceOfferings) *types.ServiceOffering {
for _, off := range offerings.ServiceOfferings {
off := off
if off.ID == id {
return &off
}
}
return nil
}

// BuildURL builds the url with provided query parameters
Expand Down
Loading

0 comments on commit bad06ff

Please sign in to comment.