func addUnits(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) {
	n, err := numberOfUnits(r)
	if err != nil {
		return err
	}
	processName := InputValue(r, "process")
	version := InputValue(r, "version")
	appName := r.URL.Query().Get(":app")
	a, err := getAppFromContext(appName, r)
	if err != nil {
		return err
	}
	allowed := permission.Check(t, permission.PermAppUpdateUnitAdd,
		contextsForApp(&a)...,
	)
	if !allowed {
		return permission.ErrUnauthorized
	}
	evt, err := event.New(&event.Opts{
		Target:        appTarget(appName),
		Kind:          permission.PermAppUpdateUnitAdd,
		Owner:         t,
		CustomData:    event.FormToCustomData(InputFields(r)),
		Allowed:       event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...),
		AllowedCancel: event.Allowed(permission.PermAppUpdateEvents, contextsForApp(&a)...),
		Cancelable:    true,
	})
	if err != nil {
		return err
	}
	defer func() { evt.Done(err) }()
	ctx, cancel := evt.CancelableContext(a.Context())
	defer cancel()
	a.ReplaceContext(ctx)
	w.Header().Set("Content-Type", "application/x-json-stream")
	keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "")
	defer keepAliveWriter.Stop()
	writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)}
	evt.SetLogWriter(writer)
	return a.AddUnits(n, processName, version, evt)
}
title: add units
path: /apps/{name}/units
method: PUT
consume: application/x-www-form-urlencoded
produce: application/x-json-stream
responses:
200: Units added
400: Invalid data
401: Unauthorized
404: App not found
package api
import (
	stdContext "context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
	pkgErrors "github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tsuru/tsuru/api/context"
	"github.com/tsuru/tsuru/app"
	"github.com/tsuru/tsuru/app/bind"
	"github.com/tsuru/tsuru/app/image"
	"github.com/tsuru/tsuru/auth"
	"github.com/tsuru/tsuru/db"
	"github.com/tsuru/tsuru/errors"
	"github.com/tsuru/tsuru/event"
	tsuruIo "github.com/tsuru/tsuru/io"
	"github.com/tsuru/tsuru/log"
	tsuruNet "github.com/tsuru/tsuru/net"
	"github.com/tsuru/tsuru/permission"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/provision/pool"
	"github.com/tsuru/tsuru/router"
	"github.com/tsuru/tsuru/router/rebuild"
	"github.com/tsuru/tsuru/service"
	"github.com/tsuru/tsuru/servicemanager"
	apiTypes "github.com/tsuru/tsuru/types/api"
	appTypes "github.com/tsuru/tsuru/types/app"
	permTypes "github.com/tsuru/tsuru/types/permission"
	"github.com/tsuru/tsuru/types/quota"
)
var (
	logsAppTail = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "tsuru_logs_app_tail_current",
		Help: "The current number of active log tail queries for an app.",
	}, []string{"app"})
	logsAppTailEntries = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "tsuru_logs_app_tail_entries_total",
		Help: "The number of log entries read in tail requests for an app.",
	}, []string{"app"})
)
func init() {
	prometheus.MustRegister(logsAppTail)
	prometheus.MustRegister(logsAppTailEntries)
}
func appTarget(appName string) event.Target {
	return event.Target{Type: event.TargetTypeApp, Value: appName}
}
func getAppFromContext(name string, r *http.Request) (app.App, error) {
	var err error
	a := context.GetApp(r)
	if a == nil {
		a, err = getApp(r.Context(), name)
		if err != nil {
			return app.App{}, err
		}
		context.SetApp(r, a)
	}
	return *a, nil
}
func getApp(ctx stdContext.Context, name string) (*app.App, error) {
	a, err := app.GetByName(ctx, name)
	if err != nil {
		if err == appTypes.ErrAppNotFound {
			return nil, &errors.HTTP{Code: http.StatusNotFound, Message: fmt.Sprintf("App %s not found.", name)}
		}
		return nil, err
	}
	return a, nil
}
func appDelete(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) {
	ctx := r.Context()
	a, err := getAppFromContext(r.URL.Query().Get(":app"), r)
	if err != nil {
		return err
	}
	canDelete := permission.Check(t, permission.PermAppDelete,
		contextsForApp(&a)...,
	)
	if !canDelete {
		return permission.ErrUnauthorized
	}
	evt, err := event.New(&event.Opts{
		Target:     appTarget(a.Name),
		Kind:       permission.PermAppDelete,
		Owner:      t,
		CustomData: event.FormToCustomData(InputFields(r)),
		Allowed:    event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...),
	})
	if err != nil {
		return err
	}
	defer func() { evt.Done(err) }()
	keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "")
	defer keepAliveWriter.Stop()
	writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)}
	evt.SetLogWriter(writer)
	w.Header().Set("Content-Type", "application/x-json-stream")
	return app.Delete(ctx, &a, evt, requestIDHeader(r))
}
type miniApp struct {
	Name      string               `json:"name"`
	Pool      string               `json:"pool"`
	TeamOwner string               `json:"teamowner"`
	Plan      appTypes.Plan        `json:"plan"`
	Units     []provision.Unit     `json:"units"`
	CName     []string             `json:"cname"`
	IP        string               `json:"ip"`
	Routers   []appTypes.AppRouter `json:"routers"`
	Lock      appTypes.AppLock     `json:"lock"`
	Tags      []string             `json:"tags"`
	Error     string               `json:"error,omitempty"`
}
func minifyApp(app app.App, unitData app.AppUnitsResponse) (miniApp, error) {
	var errorStr string
	if unitData.Err != nil {
		errorStr = unitData.Err.Error()
	}
	if unitData.Units == nil {
		unitData.Units = []provision.Unit{}
	}
	ma := miniApp{
		Name:      app.Name,
		Pool:      app.Pool,
		Plan:      app.Plan,
		TeamOwner: app.TeamOwner,
		Units:     unitData.Units,
		CName:     app.CName,
		Routers:   app.Routers,
		Lock:      app.Lock,
		Tags:      app.Tags,
		Error:     errorStr,
	}
	if len(ma.Routers) > 0 {
		ma.IP = ma.Routers[0].Address
	}
	return ma, nil
}
func appFilterByContext(contexts []permTypes.PermissionContext, filter *app.Filter) *app.Filter {
	if filter == nil {
		filter = &app.Filter{}
	}
contextsLoop:
	for _, c := range contexts {
		switch c.CtxType {
		case permTypes.CtxGlobal:
			filter.Extra = nil
			break contextsLoop
		case permTypes.CtxTeam:
			filter.ExtraIn("teams", c.Value)
		case permTypes.CtxApp:
			filter.ExtraIn("name", c.Value)
		case permTypes.CtxPool:
			filter.ExtraIn("pool", c.Value)
		}
	}
	return filter
}
func appList(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	ctx := r.Context()
	filter := &app.Filter{}
	if name := r.URL.Query().Get("name"); name != "" {
		filter.NameMatches = name
	}
	if platform := r.URL.Query().Get("platform"); platform != "" {
		filter.Platform = platform
	}
	if teamOwner := r.URL.Query().Get("teamOwner"); teamOwner != "" {
		filter.TeamOwner = teamOwner
	}
	if owner := r.URL.Query().Get("owner"); owner != "" {
		filter.UserOwner = owner
	}
	if pool := r.URL.Query().Get("pool"); pool != "" {
		filter.Pool = pool
	}
	locked, _ := strconv.ParseBool(r.URL.Query().Get("locked"))
	if locked {
		filter.Locked = true
	}
	if status, ok := r.URL.Query()["status"]; ok {
		filter.Statuses = status
	}
	if tags, ok := r.URL.Query()["tag"]; ok {
		filter.Tags = tags
	}
	contexts := permission.ContextsForPermission(t, permission.PermAppRead)
	if len(contexts) == 0 {
		w.WriteHeader(http.StatusNoContent)
		return nil
	}
	apps, err := app.List(ctx, appFilterByContext(contexts, filter))
	if err != nil {
		return err
	}
	if len(apps) == 0 {
		w.WriteHeader(http.StatusNoContent)
		return nil
	}
	simple, _ := strconv.ParseBool(r.URL.Query().Get("simplified"))
	w.Header().Set("Content-Type", "application/json")
	miniApps := make([]miniApp, len(apps))
	if simple {
		for i, ap := range apps {
			ur := app.AppUnitsResponse{Units: nil, Err: nil}
			miniApps[i], err = minifyApp(ap, ur)
			if err != nil {
				return err
			}
		}
		return json.NewEncoder(w).Encode(miniApps)
	}
	appUnits, err := app.Units(ctx, apps)
	if err != nil {
		return err
	}
	for i, app := range apps {
		miniApps[i], err = minifyApp(app, appUnits[app.Name])
		if err != nil {
			return err
		}
	}
	return json.NewEncoder(w).Encode(miniApps)
}
func appInfo(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	a, err := getAppFromContext(r.URL.Query().Get(":app"), r)
	if err != nil {
		return err
	}
	canRead := permission.Check(t, permission.PermAppRead,
		contextsForApp(&a)...,
	)
	if !canRead {
		return permission.ErrUnauthorized
	}
	err = a.FillInternalAddresses()
	if err != nil {
		return err
	}
	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(&a)
}
type inputApp struct {
	TeamOwner    string
	Platform     string
	Plan         string
	Name         string
	Description  string
	Pool         string
	Router       string
	RouterOpts   map[string]string
	Tags         []string
	PlanOverride appTypes.PlanOverride
	Metadata     appTypes.Metadata
}
func autoTeamOwner(ctx stdContext.Context, t auth.Token, perm *permission.PermissionScheme) (string, error) {
	team, err := permission.TeamForPermission(t, perm)
	if err == nil {
		return team, nil
	}
	if err != permission.ErrTooManyTeams {
		return "", err
	}
	teams, listErr := servicemanager.Team.List(ctx)
	if listErr != nil {
		return "", listErr
	}
	if len(teams) != 1 {
		return "", err
	}
	return teams[0].Name, nil
}
func createApp(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) {
	ctx := r.Context()
	var ia inputApp
	err = ParseInput(r, &ia)
	if err != nil {
		return err
	}
	a := app.App{
		TeamOwner:   ia.TeamOwner,
		Platform:    ia.Platform,
		Plan:        appTypes.Plan{Name: ia.Plan},
		Name:        ia.Name,
		Description: ia.Description,
		Pool:        ia.Pool,
		RouterOpts:  ia.RouterOpts,
		Router:      ia.Router,
		Tags:        ia.Tags,
		Metadata:    ia.Metadata,
		Quota:       quota.UnlimitedQuota,
	}
	tags, _ := InputValues(r, "tag")
	a.Tags = append(a.Tags, tags...)  
	if a.TeamOwner == "" {
		a.TeamOwner, err = autoTeamOwner(ctx, t, permission.PermAppCreate)
		if err != nil {
			return err
		}
	}
	canCreate := permission.Check(t, permission.PermAppCreate,
		permission.Context(permTypes.CtxTeam, a.TeamOwner),
	)
	if !canCreate {
		return permission.ErrUnauthorized
	}
	u, err := auth.ConvertNewUser(t.User())
	if err != nil {
		return err
	}
	if a.Platform != "" {
		repo, _ := image.SplitImageName(a.Platform)
		platform, errPlat := servicemanager.Platform.FindByName(ctx, repo)
		if errPlat != nil {
			return errPlat
		}
		if platform.Disabled {
			canUsePlat := permission.Check(t, permission.PermPlatformUpdate) ||
				permission.Check(t, permission.PermPlatformCreate)
			if !canUsePlat {
				return &errors.HTTP{Code: http.StatusBadRequest, Message: appTypes.ErrInvalidPlatform.Error()}
			}
		}
	}
	evt, err := event.New(&event.Opts{
		Target:     appTarget(a.Name),
		Kind:       permission.PermAppCreate,
		Owner:      t,
		CustomData: event.FormToCustomData(InputFields(r)),
		Allowed:    event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...),
	})
	if err != nil {
		return err
	}
	defer func() { evt.Done(err) }()
	err = app.CreateApp(ctx, &a, u)
	if err != nil {
		log.Errorf("Got error while creating app: %s", err)
		if _, ok := err.(appTypes.NoTeamsError); ok {
			return &errors.HTTP{
				Code:    http.StatusBadRequest,
				Message: "In order to create an app, you should be member of at least one team",
			}
		}
		if e, ok := err.(*appTypes.AppCreationError); ok {
			if e.Err == app.ErrAppAlreadyExists {
				return &errors.HTTP{Code: http.StatusConflict, Message: e.Error()}
			}
			if _, ok := pkgErrors.Cause(e.Err).(*quota.QuotaExceededError); ok {
				return &errors.HTTP{
					Code:    http.StatusForbidden,
					Message: "Quota exceeded",
				}
			}
		}
		if err == appTypes.ErrInvalidPlatform {
			return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()}
		}
		return err
	}
	msg := map[string]interface{}{
		"status": "success",
	}
	addrs, err := a.GetAddresses()
	if err != nil {
		return err
	}
	if len(addrs) > 0 {
		msg["ip"] = addrs[0]
	}
	jsonMsg, err := json.Marshal(msg)
	if err != nil {
		return err
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	w.Write(jsonMsg)
	return nil
}
func updateApp(w http.ResponseWriter, r *http.Request, t auth.Token) (err error) {
	ctx := r.Context()
	var ia inputApp
	err = ParseInput(r, &ia)
	if err != nil {
		return err
	}
	imageReset, _ := strconv.ParseBool(InputValue(r, "imageReset"))
	updateData := app.App{
		TeamOwner:      ia.TeamOwner,
		Plan:           appTypes.Plan{Name: ia.Plan, Override: ia.PlanOverride},
		Pool:           ia.Pool,
		Description:    ia.Description,
		Router:         ia.Router,
		Tags:           ia.Tags,
		Platform:       InputValue(r, "platform"),
		UpdatePlatform: imageReset,
		RouterOpts:     ia.RouterOpts,
		Metadata:       ia.Metadata,
	}
	tags, _ := InputValues(r, "tag")
	noRestart, _ := strconv.ParseBool(InputValue(r, "noRestart"))
	updateData.Tags = append(updateData.Tags, tags...)  
	appName := r.URL.Query().Get(":app")
	a, err := getAppFromContext(appName, r)
	if err != nil {
		return err
	}
	var wantedPerms []*permission.PermissionScheme
	if updateData.Router != "" || len(updateData.RouterOpts) > 0 {
		return &errors.HTTP{Code: http.StatusBadRequest, Message: "updating router was deprecated, please add the wanted router and remove the old one"}
	}
	if updateData.Description != "" {
		wantedPerms = append(wantedPerms, permission.PermAppUpdateDescription)
	}
	if len(updateData.Tags) > 0 {
		wantedPerms = append(wantedPerms, permission.PermAppUpdateTags)
	}
	if updateData.Plan.Name != "" {
		wantedPerms = append(wantedPerms, permission.PermAppUpdatePlan)
	}
	if updateData.Plan.Override != (appTypes.PlanOverride{}) {
		wantedPerms = append(wantedPerms, permission.PermAppUpdatePlanoverride)
	}
	if updateData.Pool != "" {
		if noRestart {
			return &errors.HTTP{Code: http.StatusBadRequest, Message: "You must restart the app when changing the pool."}
		}
		wantedPerms = append(wantedPerms, permission.PermAppUpdatePool)
	}
	if updateData.TeamOwner != "" {
		wantedPerms = append(wantedPerms, permission.PermAppUpdateTeamowner)
	}
	if updateData.Platform != "" {
		repo, _ := image.SplitImageName(updateData.Platform)
		platform, errPlat := servicemanager.Platform.FindByName(ctx, repo)
		if errPlat != nil {
			return errPlat
		}
		if platform.Disabled {
			canUsePlat := permission.Check(t, permission.PermPlatformUpdate) ||
				permission.Check(t, permission.PermPlatformCreate)
			if !canUsePlat {
				return &errors.HTTP{Code: http.StatusBadRequest, Message: appTypes.ErrInvalidPlatform.Error()}
			}
		}
		wantedPerms = append(wantedPerms, permission.PermAppUpdatePlatform)
		updateData.UpdatePlatform = true
	}
	if updateData.UpdatePlatform {
		wantedPerms = append(wantedPerms, permission.PermAppUpdateImageReset)
	}
	if len(updateData.Metadata.Annotations) > 0 || len(updateData.Metadata.Labels) > 0 {
		wantedPerms = append(wantedPerms, permission.PermAppUpdateMetadata)
	}
	if len(wantedPerms) == 0 {
		msg := "Neither the description, tags, plan, pool, team owner or platform were set. You must define at least one."
		return &errors.HTTP{Code: http.StatusBadRequest, Message: msg}
	}
	for _, perm := range wantedPerms {
		allowed := permission.Check(t, perm,
			contextsForApp(&a)...,
		)
		if !allowed {
			return permission.ErrUnauthorized
		}
	}
	evt, err := event.New(&event.Opts{
		Target:        appTarget(appName),
		Kind:          permission.PermAppUpdate,
		Owner:         t,
		CustomData:    event.FormToCustomData(InputFields(r)),
		Allowed:       event.Allowed(permission.PermAppReadEvents, contextsForApp(&a)...),
		AllowedCancel: event.Allowed(permission.PermAppUpdateEvents, contextsForApp(&a)...),
		Cancelable:    true,
	})
	if err != nil {
		return err
	}
	defer func() { evt.Done(err) }()
	ctx, cancel := evt.CancelableContext(a.Context())
	defer cancel()
	a.ReplaceContext(ctx)
	keepAliveWriter := tsuruIo.NewKeepAliveWriter(w, 30*time.Second, "")
	defer keepAliveWriter.Stop()
	w.Header().Set("Content-Type", "application/x-json-stream")
	writer := &tsuruIo.SimpleJsonMessageEncoderWriter{Encoder: json.NewEncoder(keepAliveWriter)}
	evt.SetLogWriter(writer)
	err = a.Update(app.UpdateAppArgs{
		UpdateData:    updateData,
		Writer:        evt,
		ShouldRestart: !noRestart,
	})
	if err == appTypes.ErrPlanNotFound {
		return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()}
	}
	if _, ok := err.(*router.ErrRouterNotFound); ok {
		return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()}
	}
	return err
}
func numberOfUnits(r *http.Request) (uint, error) {
	unitsStr := InputValue(r, "units")
	if unitsStr == "" {
		return 0, &errors.HTTP{
			Code:    http.StatusBadRequest,
			Message: "You must provide the number of units.",
		}
	}
	n, err := strconv.ParseUint(unitsStr, 10, 32)
	if err != nil || n == 0 {
		return 0, &errors.HTTP{
			Code:    http.StatusBadRequest,
			Message: "Invalid number of units: the number must be an integer greater than 0.",
		}
	}
	return uint(n), nil
}