<commit_before>package service

import (
	"encoding/json"
	"fmt"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/ec2"
	"github.com/timeredbull/tsuru/errors"
	"github.com/timeredbull/tsuru/log"
	"io/ioutil"
	"labix.org/v2/mgo/bson"
	"launchpad.net/goyaml"
	"net/http"
	"time"
)

type serviceYaml struct {
	Id        string
	Endpoint  map[string]string
	Bootstrap map[string]string
}

// a service with a pointer to it's type
type serviceT struct {
	Name string
}

func CreateHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var sy serviceYaml
	err = goyaml.Unmarshal(body, &sy)
	if err != nil {
		return err
	}
	var teams []auth.Team
	db.Session.Teams().Find(bson.M{"users.email": u.Email}).All(&teams)
	if len(teams) == 0 {
		msg := "In order to create a service, you should be member of at least one team"
		return &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	n, err := db.Session.Services().Find(bson.M{"_id": sy.Id}).Count()
	if err != nil {
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	if n != 0 {
		msg := fmt.Sprintf("Service with name %s already exists.", sy.Id)
		return &errors.Http{Code: http.StatusInternalServerError, Message: msg}
	}
	s := Service{
		Name:      sy.Id,
		Endpoint:  sy.Endpoint,
		Bootstrap: sy.Bootstrap,
		Teams:     auth.GetTeamsNames(teams),
	}
	err = s.Create()
	if err != nil {
		return err
	}
	fmt.Fprint(w, "success")
	return nil
}

func CreateInstanceHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	log.Print("Receiving request to create a service instance")
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print("Got error while reading request body:")
		log.Print(err.Error())
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	var sJson map[string]string
	err = json.Unmarshal(b, &sJson)
	if err != nil {
		log.Print("Got a problem while unmarshalling request's json:")
		log.Print(err.Error())
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	var s Service
	err = validateForInstanceCreation(&s, sJson, u)
	if err != nil {
		log.Print("Got error while validation:")
		log.Print(err.Error())
		return err
	}
	instance := &ec2.Instance{}
	if s.Bootstrap["when"] == OnNewInstance {
		instance, err = ec2.RunInstance(s.Bootstrap["ami"], "") //missing user data
		if err != nil {
			msg := fmt.Sprintf("Instance for service could not be created. \nError: %s", err.Error())
			return &errors.Http{Code: http.StatusInternalServerError, Message: msg}
		}
	}
	var teamNames []string
	teams, err := u.Teams()
	if err != nil {
		return err
	}
	for _, t := range teams {
		if s.hasTeam(&t) {
			teamNames = append(teamNames, t.Name)
		}
	}
	si := ServiceInstance{
		Name:        sJson["name"],
		ServiceName: sJson["service_name"],
		Instance:    instance.Id,
		Teams:       teamNames,
	}
	go callServiceApi(s, si)
	err = si.Create()
	if err != nil {
		return err
	}
	fmt.Fprint(w, "success")
	return nil
}

func callServiceApi(s Service, si ServiceInstance) {
	checkInstanceState := func() bool {
		if when, ok := s.Bootstrap["when"]; !ok || when != OnNewInstance {
			return true
		}
		db.Session.ServiceInstances().Find(bson.M{"_id": si.Name}).One(&si)
		return si.Host != "" && si.State == "running"
	}
	ch := time.Tick(1e9)
	for _ = range ch {
		if checkInstanceState() {
			if cli, err := s.GetClient("production"); err == nil {
				si.Env, err = cli.Create(&si)
				if err != nil {
					log.Print("Error while calling create action from service api.")
					log.Print(err.Error())
				}
				si.State = "running"
				db.Session.ServiceInstances().Update(bson.M{"_id": si.Name}, si)
			}
			break
		}
	}
}

func validateForInstanceCreation(s *Service, sJson map[string]string, u *auth.User) error {
	err := db.Session.Services().Find(bson.M{"_id": sJson["service_name"]}).One(&s)
	if err != nil {
		msg := err.Error()
		if msg == "not found" {
			msg = fmt.Sprintf("Service %s does not exists.", sJson["service_name"])
		}
		return &errors.Http{Code: http.StatusNotFound, Message: msg}
	}
	var teams []auth.Team
	err = db.Session.Teams().Find(bson.M{"users.email": u.Email}).All(&teams)
	if err != nil {
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	q := bson.M{"_id": sJson["service_name"], "teams": bson.M{"$in": auth.GetTeamsNames(teams)}}
	n, err := db.Session.Services().Find(q).Count()
	if n == 0 {
		msg := fmt.Sprintf("You don't have access to service %s", sJson["service_name"])
		return &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	return nil
}

func DeleteHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	s := Service{Name: r.URL.Query().Get(":name")}
	err := s.Get()
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "Service not found"}
	}
	if !auth.CheckUserAccess(s.Teams, u) {
		msg := "This user does not have access to this service"
		return &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	s.Delete()
	fmt.Fprint(w, "success")
	return nil
}

func serviceInstanceAndAppOrError(instanceName, appName string, u *auth.User) (instance ServiceInstance, a app.App, err error) {
	err = db.Session.ServiceInstances().Find(bson.M{"_id": instanceName}).One(&instance)
	if err != nil {
		err = &errors.Http{Code: http.StatusNotFound, Message: "Instance not found"}
		return
	}
	if !auth.CheckUserAccess(instance.Teams, u) {
		err = &errors.Http{Code: http.StatusForbidden, Message: "This user does not have access to this instance"}
		return
	}
	if instance.State != "running" {
		err = &errors.Http{Code: http.StatusPreconditionFailed, Message: "This service instance is not ready yet."}
		return
	}
	err = db.Session.Apps().Find(bson.M{"name": appName}).One(&a)
	if err != nil {
		err = &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
		return
	}
	if !auth.CheckUserAccess(a.Teams, u) {
		err = &errors.Http{Code: http.StatusForbidden, Message: "This user does not have access to this app"}
		return
	}
	return
}

func BindHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	instanceName, appName := r.URL.Query().Get(":instance"), r.URL.Query().Get(":app")
	instance, a, err := serviceInstanceAndAppOrError(instanceName, appName, u)
	if err != nil {
		return err
	}
	for _, name := range instance.Apps {
		if name == a.Name {
			return &errors.Http{Code: http.StatusConflict, Message: "This app is already binded to this service instance."}
		}
	}
	instance.Apps = append(instance.Apps, a.Name)
	err = db.Session.ServiceInstances().Update(bson.M{"_id": instanceName}, instance)
	if err != nil {
		return err
	}
	var envVars []app.EnvVar
	var setEnv = func(env map[string]string) {
		for k, v := range env {
			envVars = append(envVars, app.EnvVar{
				Name:         k,
				Value:        v,
				Public:       false,
				InstanceName: instance.Name,
			})
		}
	}
	setEnv(instance.Env)
	var cli *Client
	if cli, err = instance.Service().GetClient("production"); err == nil {
		if len(a.Units) == 0 {
			return &errors.Http{Code: http.StatusPreconditionFailed, Message: "This app does not have an IP yet."}
		}
		env, err := cli.Bind(&instance, &a)
		if err != nil {
			return err
		}
		setEnv(env)
	}
	return app.SetEnvsToApp(&a, envVars, false)
}

func Unbind(instance ServiceInstance, a app.App) error {
	err := instance.RemoveApp(a.Name)
	if err != nil {
		return &errors.Http{Code: http.StatusPreconditionFailed, Message: err.Error()}
	}
	err = db.Session.ServiceInstances().Update(bson.M{"_id": instance.Name}, instance)
	if err != nil {
		return err
	}
	go func(i ServiceInstance, a app.App) {
		if cli, err := i.Service().GetClient("production"); err == nil {
			cli.Unbind(&i, &a)
		}
	}(instance, a)
	var envVars []string
	for k, _ := range a.InstanceEnv(instance.Name) {
		envVars = append(envVars, k)
	}
	return app.UnsetEnvFromApp(&a, envVars, false)
}

func UnbindHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	instanceName, appName := r.URL.Query().Get(":instance"), r.URL.Query().Get(":app")
	instance, a, err := serviceInstanceAndAppOrError(instanceName, appName, u)
	if err != nil {
		return err<commit_msg>api/service: fix method name (bind, instead of Bind)<commit_after>package service

import (
	"encoding/json"
	"fmt"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/ec2"
	"github.com/timeredbull/tsuru/errors"
	"github.com/timeredbull/tsuru/log"
	"io/ioutil"
	"labix.org/v2/mgo/bson"
	"launchpad.net/goyaml"
	"net/http"
	"time"
)

type serviceYaml struct {
	Id        string
	Endpoint  map[string]string
	Bootstrap map[string]string
}

// a service with a pointer to it's type
type serviceT struct {
	Name string
}

func CreateHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var sy serviceYaml
	err = goyaml.Unmarshal(body, &sy)
	if err != nil {
		return err
	}
	var teams []auth.Team
	db.Session.Teams().Find(bson.M{"users.email": u.Email}).All(&teams)
	if len(teams) == 0 {
		msg := "In order to create a service, you should be member of at least one team"
		return &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	n, err := db.Session.Services().Find(bson.M{"_id": sy.Id}).Count()
	if err != nil {
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	if n != 0 {
		msg := fmt.Sprintf("Service with name %s already exists.", sy.Id)
		return &errors.Http{Code: http.StatusInternalServerError, Message: msg}
	}
	s := Service{
		Name:      sy.Id,
		Endpoint:  sy.Endpoint,
		Bootstrap: sy.Bootstrap,
		Teams:     auth.GetTeamsNames(teams),
	}
	err = s.Create()
	if err != nil {
		return err
	}
	fmt.Fprint(w, "success")
	return nil
}

func CreateInstanceHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	log.Print("Receiving request to create a service instance")
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print("Got error while reading request body:")
		log.Print(err.Error())
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	var sJson map[string]string
	err = json.Unmarshal(b, &sJson)
	if err != nil {
		log.Print("Got a problem while unmarshalling request's json:")
		log.Print(err.Error())
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	var s Service
	err = validateForInstanceCreation(&s, sJson, u)
	if err != nil {
		log.Print("Got error while validation:")
		log.Print(err.Error())
		return err
	}
	instance := &ec2.Instance{}
	if s.Bootstrap["when"] == OnNewInstance {
		instance, err = ec2.RunInstance(s.Bootstrap["ami"], "") //missing user data
		if err != nil {
			msg := fmt.Sprintf("Instance for service could not be created. \nError: %s", err.Error())
			return &errors.Http{Code: http.StatusInternalServerError, Message: msg}
		}
	}
	var teamNames []string
	teams, err := u.Teams()
	if err != nil {
		return err
	}
	for _, t := range teams {
		if s.hasTeam(&t) {
			teamNames = append(teamNames, t.Name)
		}
	}
	si := ServiceInstance{
		Name:        sJson["name"],
		ServiceName: sJson["service_name"],
		Instance:    instance.Id,
		Teams:       teamNames,
	}
	go callServiceApi(s, si)
	err = si.Create()
	if err != nil {
		return err
	}
	fmt.Fprint(w, "success")
	return nil
}

func callServiceApi(s Service, si ServiceInstance) {
	checkInstanceState := func() bool {
		if when, ok := s.Bootstrap["when"]; !ok || when != OnNewInstance {
			return true
		}
		db.Session.ServiceInstances().Find(bson.M{"_id": si.Name}).One(&si)
		return si.Host != "" && si.State == "running"
	}
	ch := time.Tick(1e9)
	for _ = range ch {
		if checkInstanceState() {
			if cli, err := s.GetClient("production"); err == nil {
				si.Env, err = cli.Create(&si)
				if err != nil {
					log.Print("Error while calling create action from service api.")
					log.Print(err.Error())
				}
				si.State = "running"
				db.Session.ServiceInstances().Update(bson.M{"_id": si.Name}, si)
			}
			break
		}
	}
}

func validateForInstanceCreation(s *Service, sJson map[string]string, u *auth.User) error {
	err := db.Session.Services().Find(bson.M{"_id": sJson["service_name"]}).One(&s)
	if err != nil {
		msg := err.Error()
		if msg == "not found" {
			msg = fmt.Sprintf("Service %s does not exists.", sJson["service_name"])
		}
		return &errors.Http{Code: http.StatusNotFound, Message: msg}
	}
	var teams []auth.Team
	err = db.Session.Teams().Find(bson.M{"users.email": u.Email}).All(&teams)
	if err != nil {
		return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()}
	}
	q := bson.M{"_id": sJson["service_name"], "teams": bson.M{"$in": auth.GetTeamsNames(teams)}}
	n, err := db.Session.Services().Find(q).Count()
	if n == 0 {
		msg := fmt.Sprintf("You don't have access to service %s", sJson["service_name"])
		return &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	return nil
}

func DeleteHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	s := Service{Name: r.URL.Query().Get(":name")}
	err := s.Get()
	if err != nil {
		return &errors.Http{Code: http.StatusNotFound, Message: "Service not found"}
	}
	if !auth.CheckUserAccess(s.Teams, u) {
		msg := "This user does not have access to this service"
		return &errors.Http{Code: http.StatusForbidden, Message: msg}
	}
	s.Delete()
	fmt.Fprint(w, "success")
	return nil
}

func serviceInstanceAndAppOrError(instanceName, appName string, u *auth.User) (instance ServiceInstance, a app.App, err error) {
	err = db.Session.ServiceInstances().Find(bson.M{"_id": instanceName}).One(&instance)
	if err != nil {
		err = &errors.Http{Code: http.StatusNotFound, Message: "Instance not found"}
		return
	}
	if !auth.CheckUserAccess(instance.Teams, u) {
		err = &errors.Http{Code: http.StatusForbidden, Message: "This user does not have access to this instance"}
		return
	}
	if instance.State != "running" {
		err = &errors.Http{Code: http.StatusPreconditionFailed, Message: "This service instance is not ready yet."}
		return
	}
	err = db.Session.Apps().Find(bson.M{"name": appName}).One(&a)
	if err != nil {
		err = &errors.Http{Code: http.StatusNotFound, Message: "App not found"}
		return
	}
	if !auth.CheckUserAccess(a.Teams, u) {
		err = &errors.Http{Code: http.StatusForbidden, Message: "This user does not have access to this app"}
		return
	}
	return
}

func BindHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	instanceName, appName := r.URL.Query().Get(":instance"), r.URL.Query().Get(":app")
	instance, a, err := serviceInstanceAndAppOrError(instanceName, appName, u)
	if err != nil {
		return err
	}
	for _, name := range instance.Apps {
		if name == a.Name {
			return &errors.Http{Code: http.StatusConflict, Message: "This app is already binded to this service instance."}
		}
	}
	instance.Apps = append(instance.Apps, a.Name)
	err = db.Session.ServiceInstances().Update(bson.M{"_id": instanceName}, instance)
	if err != nil {
		return err
	}
	var envVars []app.EnvVar
	var setEnv = func(env map[string]string) {
		for k, v := range env {
			envVars = append(envVars, app.EnvVar{
				Name:         k,
				Value:        v,
				Public:       false,
				InstanceName: instance.Name,
			})
		}
	}
	setEnv(instance.Env)
	var cli *Client
	if cli, err = instance.Service().GetClient("production"); err == nil {
		if len(a.Units) == 0 {
			return &errors.Http{Code: http.StatusPreconditionFailed, Message: "This app does not have an IP yet."}
		}
		env, err := cli.Bind(&instance, &a)
		if err != nil {
			return err
		}
		setEnv(env)
	}
	return app.SetEnvsToApp(&a, envVars, false)
}

func unbind(instance ServiceInstance, a app.App) error {
	err := instance.RemoveApp(a.Name)
	if err != nil {
		return &errors.Http{Code: http.StatusPreconditionFailed, Message: err.Error()}
	}
	err = db.Session.ServiceInstances().Update(bson.M{"_id": instance.Name}, instance)
	if err != nil {
		return err
	}
	go func(i ServiceInstance, a app.App) {
		if cli, err := i.Service().GetClient("production"); err == nil {
			cli.Unbind(&i, &a)
		}
	}(instance, a)
	var envVars []string
	for k, _ := range a.InstanceEnv(instance.Name) {
		envVars = append(envVars, k)
	}
	return app.UnsetEnvFromApp(&a, envVars, false)
}

func UnbindHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error {
	instanceName, appName := r.URL.Query().Get(":instance"), r.URL.Query().Get(":app")
	instance, a, err := serviceInstanceAndAppOrError(instanceName, appName, u)
	if err != nil {
		return err
{'fraction_non_alphanumeric': 0.15572165865812423, 'fraction_numerical': 0.0011864507326333274, 'mean_word_length': 3.9596940276551926, 'pattern_counts': {'":': 36, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5592913', 'n_tokens_mistral': 6329, 'n_tokens_neox': 6031, 'n_words': 1510}