func (si *ServiceInstance) UnbindApp(unbindArgs UnbindAppArgs) error {
	if si.FindApp(unbindArgs.App.GetName()) == -1 {
		return ErrAppNotBound
	}
	args := bindPipelineArgs{
		serviceInstance: si,
		app:             unbindArgs.App,
		writer:          unbindArgs.Event,
		shouldRestart:   unbindArgs.Restart,
		event:           unbindArgs.Event,
		requestID:       unbindArgs.RequestID,
		forceRemove:     unbindArgs.ForceRemove,
	}
	actions := []*action.Action{
		&unbindUnits,
		&unbindAppDB,
		&unbindAppEndpoint,
		&removeBoundEnvs,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(si.ctx, &args)
}
UnbindApp makes the unbind between the service instance and an app.
package service
import (
	"context"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"github.com/globalsign/mgo"
	"github.com/globalsign/mgo/bson"
	"github.com/pkg/errors"
	"github.com/tsuru/tsuru/action"
	"github.com/tsuru/tsuru/app/bind"
	"github.com/tsuru/tsuru/db"
	tsuruErrors "github.com/tsuru/tsuru/errors"
	"github.com/tsuru/tsuru/event"
	"github.com/tsuru/tsuru/log"
	"github.com/tsuru/tsuru/servicemanager"
	authTypes "github.com/tsuru/tsuru/types/auth"
)
var (
	ErrServiceInstanceNotFound                  = errors.New("service instance not found")
	ErrInvalidInstanceName                      = errors.New("invalid service instance name")
	ErrInstanceNameAlreadyExists                = errors.New("instance name already exists.")
	ErrAccessNotAllowed                         = errors.New("user does not have access to this service instance")
	ErrTeamMandatory                            = errors.New("please specify the team that owns the service instance")
	ErrAppAlreadyBound                          = errors.New("app is already bound to this service instance")
	ErrAppNotBound                              = errors.New("app is not bound to this service instance")
	ErrUnitNotBound                             = errors.New("unit is not bound to this service instance")
	ErrServiceInstanceBound                     = errors.New("This service instance is bound to at least one app. Unbind them before removing it")
	ErrMultiClusterServiceRequiresPool          = errors.New("multi-cluster service instance requires a pool")
	ErrMultiClusterPoolDoesNotMatch             = errors.New("pools between app and multi-cluster service instance does not match")
	ErrRegularServiceInstanceCannotBelongToPool = errors.New("regular (non-multi-cluster) service instance cannot belong to a pool")
	instanceNameRegexp                          = regexp.MustCompile(`^[A-Za-z][-a-zA-Z0-9_]+$`)
)
type ServiceInstance struct {
	Name        string                 `json:"name"`
	Id          int                    `json:"id"`
	ServiceName string                 `bson:"service_name" json:"service_name"`
	PlanName    string                 `bson:"plan_name" json:"plan_name"`
	Apps        []string               `json:"apps"`
	BoundUnits  []Unit                 `bson:"bound_units" json:"bound_units"`
	Teams       []string               `json:"teams"`
	TeamOwner   string                 `json:"team_owner"`
	Description string                 `json:"description"`
	Tags        []string               `json:"tags"`
	Parameters  map[string]interface{} `json:"parameters,omitempty"`
	Pool string `json:"pool,omitempty"`
	BrokerData *BrokerInstanceData `json:"broker_data,omitempty" bson:"broker_data"`
	ForceRemove bool `bson:"-" json:"-"`
	ctx context.Context
}
type BrokerInstanceData struct {
	UUID             string
	ServiceID        string
	PlanID           string
	OrgID            string
	SpaceID          string
	LastOperationKey string
	Binds map[string]BrokerInstanceBind
}
type BrokerInstanceBind struct {
	UUID         string
	OperationKey string
	Parameters   map[string]interface{}
}
type Unit struct {
	AppName string `json:"app_name"`
	ID      string `json:"id"`
	IP      string `json:"ip"`
}
func (bu Unit) GetID() string {
	return bu.ID
}
func (bu Unit) GetIp() string {
	return bu.IP
}
func DeleteInstance(ctx context.Context, si *ServiceInstance, evt *event.Event, requestID string) error {
	if len(si.Apps) > 0 {
		return ErrServiceInstanceBound
	}
	s, err := Get(ctx, si.ServiceName)
	if err != nil {
		return err
	}
	endpoint, err := s.getClientForPool(ctx, si.Pool)
	if err != nil {
		return err
	}
	err = endpoint.Destroy(ctx, si, evt, requestID)
	if err != nil {
		if !si.ForceRemove {
			return err
		}
		fmt.Fprintf(evt, "could not delete the service instance on service api: %v. ignoring this error due to force removal...\n", err)
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	return conn.ServiceInstances().Remove(bson.M{"name": si.Name, "service_name": si.ServiceName})
}
func (si *ServiceInstance) GetIdentifier() string {
	if si.Id != 0 {
		return strconv.Itoa(si.Id)
	}
	return si.Name
}
type ServiceInstanceWithInfo struct {
	Id          int
	Name        string
	Pool        string
	Teams       []string
	PlanName    string
	Apps        []string
	ServiceName string
	Info        map[string]string
	TeamOwner   string
}
func (si *ServiceInstance) ToInfo() (ServiceInstanceWithInfo, error) {
	info, err := si.Info("")
	if err != nil {
		info = nil
	}
	return ServiceInstanceWithInfo{
		Id:          si.Id,
		Name:        si.Name,
		Pool:        si.Pool,
		Teams:       si.Teams,
		PlanName:    si.PlanName,
		Apps:        si.Apps,
		ServiceName: si.ServiceName,
		Info:        info,
		TeamOwner:   si.TeamOwner,
	}, nil
}
func (si *ServiceInstance) Info(requestID string) (map[string]string, error) {
	s, err := Get(si.ctx, si.ServiceName)
	if err != nil {
		return nil, err
	}
	endpoint, err := s.getClientForPool(si.ctx, si.Pool)
	if err != nil {
		return nil, err
	}
	result, err := endpoint.Info(si.ctx, si, requestID)
	if err != nil {
		return nil, err
	}
	info := map[string]string{}
	for _, d := range result {
		info[d["label"]] = d["value"]
	}
	return info, nil
}
func (si *ServiceInstance) FindApp(appName string) int {
	index := -1
	for i, name := range si.Apps {
		if name == appName {
			index = i
			break
		}
	}
	return index
}
func (si *ServiceInstance) Update(service Service, updateData ServiceInstance, evt *event.Event, requestID string) error {
	err := validateServiceInstanceTeamOwner(updateData)
	if err != nil {
		return err
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	tags := processTags(updateData.Tags)
	if tags == nil {
		updateData.Tags = si.Tags
	} else {
		updateData.Tags = tags
	}
	actions := []*action.Action{&updateServiceInstance, &notifyUpdateServiceInstance}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(si.ctx, service, *si, updateData, evt, requestID)
}
func (si *ServiceInstance) updateData(update bson.M) error {
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	return conn.ServiceInstances().Update(bson.M{"name": si.Name, "service_name": si.ServiceName}, update)
}
func (si *ServiceInstance) BindApp(app bind.App, params BindAppParameters, shouldRestart bool, writer io.Writer, evt *event.Event, requestID string) error {
	args := bindPipelineArgs{
		serviceInstance: si,
		app:             app,
		writer:          writer,
		shouldRestart:   shouldRestart,
		params:          params,
		event:           evt,
		requestID:       requestID,
	}
	actions := []*action.Action{
		bindAppDBAction,
		bindAppEndpointAction,
		setBoundEnvsAction,
		bindUnitsAction,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(si.ctx, &args)
}
func (si *ServiceInstance) BindUnit(app bind.App, unit bind.Unit) error {
	s, err := Get(si.ctx, si.ServiceName)
	if err != nil {
		return err
	}
	endpoint, err := s.getClientForPool(si.ctx, si.Pool)
	if err != nil {
		return err
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	updateOp := bson.M{
		"$addToSet": bson.M{
			"bound_units": bson.D([]bson.DocElem{
				{Name: "appname", Value: app.GetName()},
				{Name: "id", Value: unit.GetID()},
				{Name: "ip", Value: unit.GetIp()},
			}),
		},
	}
	err = conn.ServiceInstances().Update(bson.M{"name": si.Name, "service_name": si.ServiceName, "bound_units.id": bson.M{"$ne": unit.GetID()}}, updateOp)
	conn.Close()
	if err != nil {
		if err == mgo.ErrNotFound {
			return nil
		}
		return err
	}
	err = endpoint.BindUnit(si.ctx, si, app, unit)
	if err != nil {
		updateOp = bson.M{
			"$pull": bson.M{
				"bound_units": bson.D([]bson.DocElem{
					{Name: "appname", Value: app.GetName()},
					{Name: "id", Value: unit.GetID()},
					{Name: "ip", Value: unit.GetIp()},
				}),
			},
		}
		rollbackErr := si.updateData(updateOp)
		if rollbackErr != nil {
			log.Errorf("[bind unit] could not remove stil unbound unit from db after failure: %s", rollbackErr)
		}
		return err
	}
	return nil
}
type UnbindAppArgs struct {
	App         bind.App
	Restart     bool
	ForceRemove bool
	Event       *event.Event
	RequestID   string
}