<commit_before>package workerconfig

import (
	"fmt"
	"koding/tools/config"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"os"
	"time"
)

const (
	Started WorkerStatus = iota
	Killed
	Dead
	Waiting
)

type WorkerStatus int

type WorkerResponse struct {
	Name    string `json:"name"`
	Uuid    string `json:"uuid"`
	Command string `json:"command"`
	Log     string `json:"log"`
}

type MemData struct {
	Rss       int    `json:"rss"`
	HeapTotal int    `json:"heaptotal"`
	HeapUsed  int    `json:"heapused"`
	Unit      string `json:"unit"`
}

type Monitor struct {
	Name   string
	Uuid   string
	Mem    *MemData
	Uptime int
}

type ApiRequest struct {
	Uuid    string `json:"uuid"`
	Command string `json:"command"`
}

type ClientRequest struct {
	Action   string
	Cmd      string
	Hostname string
	Pid      int
}

type Worker struct {
	Name               string       `json:"name"`
	ServiceGenericName string       `bson:"serviceGenericName" json:"serviceGenericName"`
	ServiceUniqueName  string       `bson:"serviceUniqueName" json:"serviceUniqueName"`
	Uuid               string       `json:"uuid"`
	Hostname           string       `json:"hostname"`
	Version            int          `json:"version"`
	Timestamp          time.Time    `json:"timestamp"`
	Pid                int          `json:"pid"`
	Status             WorkerStatus `json:"status"`
	Cmd                string       `json:"cmd"`
	ProcessData        string       `json:"processData"`
	Number             int          `json:"number"`
	Message            struct {
		Command string `json:"command"`
		Option  string `json:"option"`
	} `json:"message"`
	CompatibleWith map[string][]int `json:"compatibleWith"`
	Port           int              `json:"port"`
	RabbitKey      string           `json:"rabbitKey"`
	Monitor        struct {
		Mem    MemData `json:"mem"`
		Uptime int     `json:"uptime"`
	} `json:"monitor"`
}

func NewWorkerResponse(name, uuid, command, log string) *WorkerResponse {
	return &WorkerResponse{
		Name:    name,
		Uuid:    uuid,
		Command: command,
		Log:     log,
	}
}

type WorkerConfig struct {
	Hostname   string
	Session    *mgo.Session
	Collection *mgo.Collection
}

// Start point. Needs to be called in order to use other methods
func Connect() (*WorkerConfig, error) {
	session, err := mgo.Dial(config.Current.Mongo)
	if err != nil {
		return nil, err
	}
	session.SetMode(mgo.Strong, true)
	session.SetSafe(&mgo.Safe{})
	database := session.DB("")

	col := database.C("jKontrolWorkers")

	hostname, _ := os.Hostname()

	wk := &WorkerConfig{
		Hostname:   hostname,
		Session:    session,
		Collection: col,
	}

	return wk, nil
}

func (w *WorkerConfig) Delete(uuid string) error {
	worker, err := w.GetWorker(uuid)
	if err != nil {
		return fmt.Errorf("delete method error '%s'", err)
	}

	log.Printf("deleting worker '%s' with hostname '%s' from the db", worker.Name, worker.Hostname)
	w.DeleteWorker(uuid)
	return nil
}

func (w *WorkerConfig) Kill(uuid, mode string) (WorkerResponse, error) {
	worker, err := w.GetWorker(uuid)
	if err != nil {
		return WorkerResponse{}, fmt.Errorf("kill method error '%s'", err)
	}
	log.Printf("killing worker with pid: %d on hostname: %s", worker.Pid, worker.Hostname)

	// create response to be sent
	command := "kill"
	if mode == "force" {
		command = "killForce"
	}
	response := *NewWorkerResponse(worker.Name, worker.Uuid, command, "you got a kill message")

	// mark as waiting until we got a message
	worker.Status = Waiting
	w.UpdateWorker(worker)

	return response, nil
}

func (w *WorkerConfig) Start(uuid string) (WorkerResponse, error) {
	worker, err := w.GetWorker(uuid)
	if err != nil {
		return WorkerResponse{}, fmt.Errorf("start method error '%s'", err)
	}

	var command string
	if worker.Status == Dead || worker.Status == Killed {
		log.Printf("starting worker: '%s' on '%s'", worker.Name, worker.Hostname)
		worker.Status = Waiting
		w.UpdateWorker(worker)
		command = "start"
	} else {
		command = "noPermission"
	}

	response := *NewWorkerResponse(worker.Name, worker.Uuid, command, "you got a start message")
	return response, nil
}

func (w *WorkerConfig) Update(worker Worker) error {
	// No check for uuid, this is a destructive action. Thus use with caution.
	// After creating a processes, the process sends a new "update" message with
	// child pid, a new uuid and his new status.
	result := Worker{}
	found := false

	iter := w.Collection.Find(bson.M{"uuid": worker.Uuid, "hostname": worker.Hostname}).Iter()
	for iter.Next(&result) {
		w.DeleteWorker(result.Uuid)
		found = true
	}

	if !found {
		return fmt.Errorf("no worker found with name '%s' and hostname '%s'", worker.Name, worker.Hostname)
	}

	result.Timestamp = worker.Timestamp
	result.Status = worker.Status
	result.Pid = worker.Pid
	result.Uuid = worker.Uuid
	result.Version = worker.Version

	log.Printf("[%s (%d)] updating with new info from '%s'", worker.Name, worker.Version, worker.Hostname)
	w.AddWorker(result)
	return nil
}

func (w *WorkerConfig) Ack(worker Worker) error {
	existingWorker, err := w.GetWorker(worker.Uuid)
	if err != nil {
		return fmt.Errorf("ack method error for hostanme %s worker %s version %d '%s'", worker.Hostname, worker.Name, worker.Version, err)
	}

	existingWorker.Timestamp = worker.Timestamp
	existingWorker.Status = worker.Status
	existingWorker.Monitor.Uptime = worker.Monitor.Uptime

	w.UpdateWorker(existingWorker)
	return nil
}

func (w *WorkerConfig) RefreshStatusAll() error {
	worker := Worker{}
	iter := w.Collection.Find(nil).Iter()
	for iter.Next(&worker) {
		// this will be removed in the future, just added for backwards compability.
		// if worker.Status == Dead {
		// 	log.Printf("[%s (%d)] no activity at '%s' (pid: %d). removing from kontrol\n", worker.Name, worker.Version, worker.Hostname, worker.Pid)
		// 	w.Delete(worker.Uuid)
		// 	continue
		// }

		// every worker sends us an ack message every 10 seconds. That means if
		// we don't get a message after 10 seconds it is assumed as death. We
		// add an additional 5 second because of network timeouts/lagginess
		// problems.
		if worker.Timestamp.Add(15 * time.Second).Before(time.Now().UTC()) {
			log.Printf("[%s (%d)] no activity at '%s' (pid: %d). marking them as dead\n", worker.Name, worker.Version, worker.Hostname, worker.Pid)
			worker.Status = Dead
			worker.Monitor.Mem = MemData{}
			worker.Monitor.Uptime = 0
			w.UpdateWorker(worker)
		}
	}

	// otherwise the workers are still alive

	return nil
}

func (w *WorkerConfig) GetWorker(uuid string) (Worker, error) {
	result := Worker{}
	err := w.Collection.Find(bson.M{"uuid": uuid}).One(&result)
	if err != nil {
		return result, fmt.Errorf("no worker with the uuid %s exist.", uuid)
	}

	return result, nil

}

func (w *WorkerConfig) UpdateWorker(worker Worker) {
	err := w.Collection.Update(bson.M{"uuid": worker.Uuid}, worker)
	if err != nil {
		log.Println(err)
	}
}

func (w *WorkerConfig) AddWorker(worker Worker) {
	_, err := w.GetWorker(worker.Uuid)
	if err == nil {
		return // there is already a worker with the same uuid, don't insert it
	}

	err = w.Collection.Insert(worker)
	if err != nil {
		log.Println(err)
	}

}

func (w *WorkerConfig) DeleteWorker(uuid string) {
	err := w.Collection.Remove(bson.M{"uuid": uuid})
	if err != nil {
		log.Println(err)
	}
}

func (w *WorkerConfig) NumberOfWorker(name string, version int) int {
	count, _ := w.Collection.Find(bson.M{"name": name, "version": version}).Count()
	return count
}
<commit_msg>kontroldaemin: skip if it's dead already<commit_after>package workerconfig

import (
	"fmt"
	"koding/tools/config"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"os"
	"time"
)

const (
	Started WorkerStatus = iota
	Killed
	Dead
	Waiting
)

type WorkerStatus int

type WorkerResponse struct {
	Name    string `json:"name"`
	Uuid    string `json:"uuid"`
	Command string `json:"command"`
	Log     string `json:"log"`
}

type MemData struct {
	Rss       int    `json:"rss"`
	HeapTotal int    `json:"heaptotal"`
	HeapUsed  int    `json:"heapused"`
	Unit      string `json:"unit"`
}

type Monitor struct {
	Name   string
	Uuid   string
	Mem    *MemData
	Uptime int
}

type ApiRequest struct {
	Uuid    string `json:"uuid"`
	Command string `json:"command"`
}

type ClientRequest struct {
	Action   string
	Cmd      string
	Hostname string
	Pid      int
}

type Worker struct {
	Name               string       `json:"name"`
	ServiceGenericName string       `bson:"serviceGenericName" json:"serviceGenericName"`
	ServiceUniqueName  string       `bson:"serviceUniqueName" json:"serviceUniqueName"`
	Uuid               string       `json:"uuid"`
	Hostname           string       `json:"hostname"`
	Version            int          `json:"version"`
	Timestamp          time.Time    `json:"timestamp"`
	Pid                int          `json:"pid"`
	Status             WorkerStatus `json:"status"`
	Cmd                string       `json:"cmd"`
	ProcessData        string       `json:"processData"`
	Number             int          `json:"number"`
	Message            struct {
		Command string `json:"command"`
		Option  string `json:"option"`
	} `json:"message"`
	CompatibleWith map[string][]int `json:"compatibleWith"`
	Port           int              `json:"port"`
	RabbitKey      string           `json:"rabbitKey"`
	Monitor        struct {
		Mem    MemData `json:"mem"`
		Uptime int     `json:"uptime"`
	} `json:"monitor"`
}

func NewWorkerResponse(name, uuid, command, log string) *WorkerResponse {
	return &WorkerResponse{
		Name:    name,
		Uuid:    uuid,
		Command: command,
		Log:     log,
	}
}

type WorkerConfig struct {
	Hostname   string
	Session    *mgo.Session
	Collection *mgo.Collection
}

// Start point. Needs to be called in order to use other methods
func Connect() (*WorkerConfig, error) {
	session, err := mgo.Dial(config.Current.Mongo)
	if err != nil {
		return nil, err
	}
	session.SetMode(mgo.Strong, true)
	session.SetSafe(&mgo.Safe{})
	database := session.DB("")

	col := database.C("jKontrolWorkers")

	hostname, _ := os.Hostname()

	wk := &WorkerConfig{
		Hostname:   hostname,
		Session:    session,
		Collection: col,
	}

	return wk, nil
}

func (w *WorkerConfig) Delete(uuid string) error {
	worker, err := w.GetWorker(uuid)
	if err != nil {
		return fmt.Errorf("delete method error '%s'", err)
	}

	log.Printf("deleting worker '%s' with hostname '%s' from the db", worker.Name, worker.Hostname)
	w.DeleteWorker(uuid)
	return nil
}

func (w *WorkerConfig) Kill(uuid, mode string) (WorkerResponse, error) {
	worker, err := w.GetWorker(uuid)
	if err != nil {
		return WorkerResponse{}, fmt.Errorf("kill method error '%s'", err)
	}
	log.Printf("killing worker with pid: %d on hostname: %s", worker.Pid, worker.Hostname)

	// create response to be sent
	command := "kill"
	if mode == "force" {
		command = "killForce"
	}
	response := *NewWorkerResponse(worker.Name, worker.Uuid, command, "you got a kill message")

	// mark as waiting until we got a message
	worker.Status = Waiting
	w.UpdateWorker(worker)

	return response, nil
}

func (w *WorkerConfig) Start(uuid string) (WorkerResponse, error) {
	worker, err := w.GetWorker(uuid)
	if err != nil {
		return WorkerResponse{}, fmt.Errorf("start method error '%s'", err)
	}

	var command string
	if worker.Status == Dead || worker.Status == Killed {
		log.Printf("starting worker: '%s' on '%s'", worker.Name, worker.Hostname)
		worker.Status = Waiting
		w.UpdateWorker(worker)
		command = "start"
	} else {
		command = "noPermission"
	}

	response := *NewWorkerResponse(worker.Name, worker.Uuid, command, "you got a start message")
	return response, nil
}

func (w *WorkerConfig) Update(worker Worker) error {
	// No check for uuid, this is a destructive action. Thus use with caution.
	// After creating a processes, the process sends a new "update" message with
	// child pid, a new uuid and his new status.
	result := Worker{}
	found := false

	iter := w.Collection.Find(bson.M{"uuid": worker.Uuid, "hostname": worker.Hostname}).Iter()
	for iter.Next(&result) {
		w.DeleteWorker(result.Uuid)
		found = true
	}

	if !found {
		return fmt.Errorf("no worker found with name '%s' and hostname '%s'", worker.Name, worker.Hostname)
	}

	result.Timestamp = worker.Timestamp
	result.Status = worker.Status
	result.Pid = worker.Pid
	result.Uuid = worker.Uuid
	result.Version = worker.Version

	log.Printf("[%s (%d)] updating with new info from '%s'", worker.Name, worker.Version, worker.Hostname)
	w.AddWorker(result)
	return nil
}

func (w *WorkerConfig) Ack(worker Worker) error {
	existingWorker, err := w.GetWorker(worker.Uuid)
	if err != nil {
		return fmt.Errorf("ack method error for hostanme %s worker %s version %d '%s'", worker.Hostname, worker.Name, worker.Version, err)
	}

	existingWorker.Timestamp = worker.Timestamp
	existingWorker.Status = worker.Status
	existingWorker.Monitor.Uptime = worker.Monitor.Uptime

	w.UpdateWorker(existingWorker)
	return nil
}

func (w *WorkerConfig) RefreshStatusAll() error {
	worker := Worker{}
	iter := w.Collection.Find(nil).Iter()
	for iter.Next(&worker) {
		if worker.Status == Dead {
			continue
		}
		// this will be removed in the future, just added for backwards compability.
		// if worker.Status == Dead {
		// 	log.Printf("[%s (%d)] no activity at '%s' (pid: %d). removing from kontrol\n", worker.Name, worker.Version, worker.Hostname, worker.Pid)
		// 	w.Delete(worker.Uuid)
		// 	continue
		// }

		// every worker sends us an ack message every 10 seconds. That means if
		// we don't get a message after 10 seconds it is assumed as death. We
		// add an additional 5 second because of network timeouts/lagginess
		// problems.
		if worker.Timestamp.Add(15 * time.Second).Before(time.Now().UTC()) {
			log.Printf("[%s (%d)] no activity at '%s' (pid: %d). marking them as dead\n", worker.Name, worker.Version, worker.Hostname, worker.Pid)
			worker.Status = Dead
			worker.Monitor.Mem = MemData{}
			worker.Monitor.Uptime = 0
			w.UpdateWorker(worker)
		}
	}

	// otherwise the workers are still alive

	return nil
}

func (w *WorkerConfig) GetWorker(uuid string) (Worker, error) {
	result := Worker{}
	err := w.Collection.Find(bson.M{"uuid": uuid}).One(&result)
	if err != nil {
		return result, fmt.Errorf("no worker with the uuid %s exist.", uuid)
	}

	return result, nil

}

func (w *WorkerConfig) UpdateWorker(worker Worker) {
	err := w.Collection.Update(bson.M{"uuid": worker.Uuid}, worker)
	if err != nil {
		log.Println(err)
	}
}

func (w *WorkerConfig) AddWorker(worker Worker) {
	_, err := w.GetWorker(worker.Uuid)
	if err == nil {
		return // there is already a worker with the same uuid, don't insert it
	}

	err = w.Collection.Insert(worker)
	if err != nil {
		log.Println(err)
	}

}

func (w *WorkerConfig) DeleteWorker(uuid string) {
	err := w.Collection.Remove(bson.M{"uuid": uuid})
	if err != nil {
		log.Println(err)
	}
}

func (w *WorkerConfig) NumberOfWorker(name string, version int) int {
	count, _ := w.Collection.Find(bson.M{"name": name, "version": version}).Count()
	return count
}
<|endoftext|>


<commit_before>package ptrace

import (
	"fmt"
	"sync/atomic"
)

type Ratio struct {
	Count Int
	Total Int
}

func (r *Ratio) HitIf(n bool) {
	if n {
		r.Hit()
	} else {
		r.Miss()
	}
}

func (r *Ratio) HitN(n int) {
	r.Count.AddInt(n)
	r.Total.Add(1)
}

func (r *Ratio) Hit() {
	r.Count.Add(1)
	r.Total.Add(1)
}

func (r *Ratio) Miss() {
	r.Total.Add(1)
}

func (r *Ratio) MarshalJSON() ([]byte, error) {
	if r.Total == 0 {
		return strJSON("NaN")
	}
	return strJSON(fmt.Sprintf("%.2f%%", float64(r.Count/r.Total*100)))
}

type Int int64

func (i *Int) AddInt(n int) int {
	return int(i.Add(int64(n)))
}

func (i *Int) Add(n int64) int64 {
	return atomic.AddInt64((*int64)(i), n)
}

type Size int64

func (s *Size) AddInt(n int) int {
	return (*Int)(s).AddInt(n)
}

func (s *Size) Add(n int64) int64 {
	return (*Int)(s).Add(n)
}

func (i *Size) MarshalJSON() ([]byte, error) {
	unit := Unit(atomic.LoadInt64((*int64)(i)))
	return strJSON(unit)
}
<commit_msg>[ptrace] add *<commit_after>package ptrace

import (
	"fmt"
	"sync/atomic"
	"time"
)

type RatioTime struct {
	Duration time.Duration
	Count    Int
}

func (r *RatioTime) AddNow(t time.Time) {
	r.Duration += time.Now().Sub(t)
	r.Count.Add(1)
}

func (r *RatioTime) String() string {
	if r.Count == 0 {
		return "NaN"
	}

	return fmt.Sprintf("%v (%v/%v)",
		r.Duration/time.Duration(r.Count),
		r.Duration,
		r.Count,
	)
}

func (r *RatioTime) MarshalJSON() ([]byte, error) { return strJSON(r.String()) }

// -----------------------------------------------------------------------------

type RatioSize struct {
	Size  Size
	Count Int
}

func (r *RatioSize) AddBuf(b []byte) {
	r.Add(int64(len(b)))
}

func (r *RatioSize) Add(s int64) {
	r.Size.Add(s)
	r.Count.Add(1)
}

func (r RatioSize) String() string {
	if r.Count == 0 {
		return "NaN"
	}
	ratio := float64(r.Size) / float64(r.Count)
	return fmt.Sprintf("%v (%v/%v)", Unit(int64(ratio)), r.Size, r.Count)
}

func (r *RatioSize) MarshalJSON() ([]byte, error) {
	return strJSON(r.String())
}

// -----------------------------------------------------------------------------

type Ratio struct {
	Value Int
	Count Int
}

func (r *Ratio) HitIf(n bool) {
	if n {
		r.Hit()
	} else {
		r.Miss()
	}
}

func (r *Ratio) HitN(n int) {
	r.Value.AddInt(n)
	r.Count.Add(1)
}

func (r *Ratio) Hit() {
	r.Value.Add(1)
	r.Count.Add(1)
}

func (r *Ratio) Miss() {
	r.Count.Add(1)
}

func (r *Ratio) MarshalJSON() ([]byte, error) {
	if r.Count == 0 {
		return strJSON("NaN")
	}
	return strJSON(fmt.Sprintf("%.2f%%(%v/%v)",
		float64(r.Value*100)/float64(r.Count), r.Value, r.Count))
}

type Int int64

func (i *Int) AddInt(n int) int {
	return int(i.Add(int64(n)))
}

func (i *Int) Add(n int64) int64 {
	return atomic.AddInt64((*int64)(i), n)
}

// -----------------------------------------------------------------------------

type Size int64

func (s *Size) AddInt(n int) int {
	return (*Int)(s).AddInt(n)
}

func (s Size) Rate(d time.Duration) Rate {
	return Rate{s, d}
}

func (s *Size) Add(n int64) int64 {
	return (*Int)(s).Add(n)
}

func (i *Size) String() string {
	return Unit(atomic.LoadInt64((*int64)(i)))
}

func (i *Size) MarshalJSON() ([]byte, error) {
	return strJSON(i.String())
}

// -----------------------------------------------------------------------------

type Rate struct {
	Size     Size
	Duration time.Duration
}

func (i Rate) String() string {
	speed := Size(int64(float64(i.Size) / i.Duration.Seconds()))

	return fmt.Sprintf("%v in %v (%v/S)",
		i.Size.String(), i.Duration.String(),
		speed.String(),
	)
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"

	socialmodels "socialapi/models"
	"socialapi/request"
	"socialapi/workers/notification/models"
	"time"
)

type MailerContainer struct {
	Activity        *models.NotificationActivity
	Content         *models.NotificationContent
	AccountId       int64
	Message         string
	Slug            string
	ActivityMessage string
	ObjectType      string
	Group           GroupContent
	CreatedAt       time.Time
}

func NewMailerContainer() *MailerContainer {
	return &MailerContainer{}
}

func (mc *MailerContainer) PrepareContainer() error {
	if err := mc.validateContainer(); err != nil {
		return err
	}

	// if content type not valid return
	contentType, err := mc.Content.GetContentType()
	if err != nil {
		return err
	}

	var target *socialmodels.ChannelMessage

	switch mc.Content.TypeConstant {
	case models.NotificationContent_TYPE_PM:
		target, err = fetchChannelTarget(mc.Content.TargetId)
		mc.Message = target.Body
	default:
		target, err = fetchMessageTarget(mc.Content.TargetId)
		mc.Message = target.Body
	}

	if err != nil {
		return err
	}

	mc.prepareGroup(target)
	mc.prepareSlug(target)
	mc.prepareObjectType(target)
	contentType.SetActorId(target.AccountId)
	contentType.SetListerId(mc.AccountId)

	mc.ActivityMessage = contentType.GetActivity()

	return nil
}

func fetchChannelTarget(channelId int64) (*socialmodels.ChannelMessage, error) {
	cml := socialmodels.NewChannelMessageList()
	q := request.NewQuery()
	q.Limit = 1
	messageIds, err := cml.FetchMessageIdsByChannelId(channelId, q)
	if err != nil {
		return nil, err
	}

	if len(messageIds) == 0 {
		return nil, fmt.Errorf("private message not found")
	}

	return fetchMessageTarget(messageIds[0])
}

func fetchMessageTarget(messageId int64) (*socialmodels.ChannelMessage, error) {
	target := socialmodels.NewChannelMessage()
	if err := target.ById(messageId); err != nil {
		return nil, fmt.Errorf("target message not found")
	}

	return target, nil
}

func (mc *MailerContainer) validateContainer() error {
	if mc.AccountId == 0 {
		return errors.New("account id is not set")
	}
	if mc.Activity == nil {
		return errors.New("activity is not set")
	}
	if mc.Content == nil {
		return errors.New("content is not set")
	}

	return nil
}

func (mc *MailerContainer) prepareGroup(cm *socialmodels.ChannelMessage) {
	c := socialmodels.NewChannel()
	if err := c.ById(cm.InitialChannelId); err != nil {
		return
	}
	// TODO fix these Slug and Name
	mc.Group = GroupContent{
		Slug: c.GroupName,
		Name: c.GroupName,
	}
}

func (mc *MailerContainer) prepareSlug(cm *socialmodels.ChannelMessage) {
	switch cm.TypeConstant {
	case socialmodels.ChannelMessage_TYPE_REPLY:
		// TODO we need append something like comment id to parent message slug
		mc.Slug = fetchRepliedMessage(cm.Id).Slug
	case socialmodels.ChannelMessage_TYPE_PRIVATE_MESSAGE:
		mc.Slug = fetchPrivateChannelSlug(cm.Id)
	default:
		mc.Slug = cm.Slug
	}
}

func (mc *MailerContainer) prepareObjectType(cm *socialmodels.ChannelMessage) {
	switch cm.TypeConstant {
	case socialmodels.ChannelMessage_TYPE_POST:
		mc.ObjectType = "status update"
	case socialmodels.ChannelMessage_TYPE_REPLY:
		mc.ObjectType = "comment"
	case socialmodels.ChannelMessage_TYPE_PRIVATE_MESSAGE:
		mc.ObjectType = "private message"
	}
}

func fetchPrivateChannelSlug(messageId int64) string {
	cml := socialmodels.NewChannelMessageList()
	ids, err := cml.FetchMessageChannelIds(messageId)
	if err != nil {
		return ""
	}

	if len(ids) == 0 {
		return ""
	}

	return fmt.Sprintf("Message/%d", ids[0])
}

func fetchRepliedMessage(replyId int64) *socialmodels.ChannelMessage {
	mr := socialmodels.NewMessageReply()
	mr.ReplyId = replyId

	parent, err := mr.FetchParent()
	if err != nil {
		parent = socialmodels.NewChannelMessage()
	}

	return parent
}

func fetchLastReplyBody(targetId int64) string {
	mr := socialmodels.NewMessageReply()
	mr.MessageId = targetId
	query := request.NewQuery()
	query.Limit = 1
	messages, err := mr.List(query)
	if err != nil {
		return ""
	}

	if len(messages) == 0 {
		return ""
	}

	return messages[0].Body
}
<commit_msg>EmailNotifier: Fix comment messages are not shown in daily email bug<commit_after>package models

import (
	"errors"
	"fmt"

	socialmodels "socialapi/models"
	"socialapi/request"
	"socialapi/workers/notification/models"
	"time"
)

type MailerContainer struct {
	Activity        *models.NotificationActivity
	Content         *models.NotificationContent
	AccountId       int64
	Message         string
	Slug            string
	ActivityMessage string
	ObjectType      string
	Group           GroupContent
	CreatedAt       time.Time
}

func NewMailerContainer() *MailerContainer {
	return &MailerContainer{}
}

func (mc *MailerContainer) PrepareContainer() error {
	if err := mc.validateContainer(); err != nil {
		return err
	}

	// if content type not valid return
	contentType, err := mc.Content.GetContentType()
	if err != nil {
		return err
	}

	var target *socialmodels.ChannelMessage

	switch mc.Content.TypeConstant {
	case models.NotificationContent_TYPE_PM:
		target, err = fetchChannelTarget(mc.Content.TargetId)
		mc.Message = target.Body
	case models.NotificationContent_TYPE_COMMENT:
		target, err = fetchMessageTarget(mc.Activity.MessageId)
		if err != nil {
			return err
		}
		mc.Message = target.Body
		target, err = fetchMessageParent(mc.Activity.MessageId)

	default:
		target, err = fetchMessageTarget(mc.Content.TargetId)
		mc.Message = target.Body
	}

	if err != nil {
		return err
	}

	mc.prepareGroup(target)
	mc.prepareSlug(target)
	mc.prepareObjectType(target)
	contentType.SetActorId(target.AccountId)
	contentType.SetListerId(mc.AccountId)

	mc.ActivityMessage = contentType.GetActivity()

	return nil
}

func fetchChannelTarget(channelId int64) (*socialmodels.ChannelMessage, error) {
	cml := socialmodels.NewChannelMessageList()
	q := request.NewQuery()
	q.Limit = 1
	messageIds, err := cml.FetchMessageIdsByChannelId(channelId, q)
	if err != nil {
		return nil, err
	}

	if len(messageIds) == 0 {
		return nil, fmt.Errorf("private message not found")
	}

	return fetchMessageTarget(messageIds[0])
}

func fetchMessageTarget(messageId int64) (*socialmodels.ChannelMessage, error) {
	target := socialmodels.NewChannelMessage()
	if err := target.ById(messageId); err != nil {
		return nil, fmt.Errorf("target message not found")
	}

	return target, nil
}

func fetchMessageParent(messageId int64) (*socialmodels.ChannelMessage, error) {
	mr := socialmodels.NewMessageReply()
	mr.ReplyId = messageId

	return mr.FetchParent()
}

func (mc *MailerContainer) validateContainer() error {
	if mc.AccountId == 0 {
		return errors.New("account id is not set")
	}
	if mc.Activity == nil {
		return errors.New("activity is not set")
	}
	if mc.Content == nil {
		return errors.New("content is not set")
	}

	return nil
}

func (mc *MailerContainer) prepareGroup(cm *socialmodels.ChannelMessage) {
	c := socialmodels.NewChannel()
	if err := c.ById(cm.InitialChannelId); err != nil {
		return
	}
	// TODO fix these Slug and Name
	mc.Group = GroupContent{
		Slug: c.GroupName,
		Name: c.GroupName,
	}
}

func (mc *MailerContainer) prepareSlug(cm *socialmodels.ChannelMessage) {
	switch cm.TypeConstant {
	case socialmodels.ChannelMessage_TYPE_REPLY:
		// TODO we need append something like comment id to parent message slug
		mc.Slug = fetchRepliedMessage(cm.Id).Slug
	case socialmodels.ChannelMessage_TYPE_PRIVATE_MESSAGE:
		mc.Slug = fetchPrivateChannelSlug(cm.Id)
	default:
		mc.Slug = cm.Slug
	}
}

func (mc *MailerContainer) prepareObjectType(cm *socialmodels.ChannelMessage) {
	switch cm.TypeConstant {
	case socialmodels.ChannelMessage_TYPE_POST:
		mc.ObjectType = "status update"
	case socialmodels.ChannelMessage_TYPE_REPLY:
		mc.ObjectType = "comment"
	case socialmodels.ChannelMessage_TYPE_PRIVATE_MESSAGE:
		mc.ObjectType = "private message"
	}
}

func fetchPrivateChannelSlug(messageId int64) string {
	cml := socialmodels.NewChannelMessageList()
	ids, err := cml.FetchMessageChannelIds(messageId)
	if err != nil {
		return ""
	}

	if len(ids) == 0 {
		return ""
	}

	return fmt.Sprintf("Message/%d", ids[0])
}

func fetchRepliedMessage(replyId int64) *socialmodels.ChannelMessage {
	mr := socialmodels.NewMessageReply()
	mr.ReplyId = replyId

	parent, err := mr.FetchParent()
	if err != nil {
		parent = socialmodels.NewChannelMessage()
	}

	return parent
}

func fetchLastReplyBody(targetId int64) string {
	mr := socialmodels.NewMessageReply()
	mr.MessageId = targetId
	query := request.NewQuery()
	query.Limit = 1
	messages, err := mr.List(query)
	if err != nil {
		return ""
	}

	if len(messages) == 0 {
		return ""
	}

	return messages[0].Body
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	api "github.com/osrg/gobgp/api"
	"github.com/spf13/cobra"
)

var globalOpts struct {
	Host         string
	Port         int
	Debug        bool
	Quiet        bool
	Json         bool
	GenCmpl      bool
	BashCmplFile string
}

var cmds []string
var client api.GobgpApiClient

func NewRootCmd() *cobra.Command {
	cobra.EnablePrefixMatching = true
	rootCmd := &cobra.Command{
		Use: "gobgp",
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			if !globalOpts.GenCmpl {
				conn := connGrpc()
				client = api.NewGobgpApiClient(conn)
			}
		},
		Run: func(cmd *cobra.Command, args []string) {
			cmd.GenBashCompletionFile(globalOpts.BashCmplFile)
		},
	}

	rootCmd.PersistentFlags().StringVarP(&globalOpts.Host, "host", "u", "127.0.0.1", "host")
	rootCmd.PersistentFlags().IntVarP(&globalOpts.Port, "port", "p", 50051, "port")
	rootCmd.PersistentFlags().BoolVarP(&globalOpts.Json, "json", "j", false, "use json format to output format")
	rootCmd.PersistentFlags().BoolVarP(&globalOpts.Debug, "debug", "d", false, "use debug")
	rootCmd.PersistentFlags().BoolVarP(&globalOpts.Quiet, "quiet", "q", false, "use quiet")
	rootCmd.PersistentFlags().BoolVarP(&globalOpts.GenCmpl, "gen-cmpl", "c", false, "generate completion file")
	rootCmd.PersistentFlags().StringVarP(&globalOpts.BashCmplFile, "bash-cmpl-file", "", "gobgp-completion.bash", "bash cmpl filename")

	globalCmd := NewGlobalCmd()
	neighborCmd := NewNeighborCmd()
	vrfCmd := NewVrfCmd()
	policyCmd := NewPolicyCmd()
	monitorCmd := NewMonitorCmd()
	mrtCmd := NewMrtCmd()
	rpkiCmd := NewRPKICmd()
	bmpCmd := NewBmpCmd()
	rootCmd.AddCommand(globalCmd, neighborCmd, vrfCmd, policyCmd, monitorCmd, mrtCmd, rpkiCmd, bmpCmd)
	return rootCmd
}
<commit_msg>cli: show help message when no argument passed<commit_after>// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	api "github.com/osrg/gobgp/api"
	"github.com/spf13/cobra"
)

var globalOpts struct {
	Host         string
	Port         int
	Debug        bool
	Quiet        bool
	Json         bool
	GenCmpl      bool
	BashCmplFile string
}

var cmds []string
var client api.GobgpApiClient

func NewRootCmd() *cobra.Command {
	cobra.EnablePrefixMatching = true
	rootCmd := &cobra.Command{
		Use: "gobgp",
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			if !globalOpts.GenCmpl {
				conn := connGrpc()
				client = api.NewGobgpApiClient(conn)
			}
		},
		Run: func(cmd *cobra.Command, args []string) {
			if globalOpts.GenCmpl {
				cmd.GenBashCompletionFile(globalOpts.BashCmplFile)
			} else {
				cmd.HelpFunc()(cmd, args)
			}
		},
	}

	rootCmd.PersistentFlags().StringVarP(&globalOpts.Host, "host", "u", "127.0.0.1", "host")
	rootCmd.PersistentFlags().IntVarP(&globalOpts.Port, "port", "p", 50051, "port")
	rootCmd.PersistentFlags().BoolVarP(&globalOpts.Json, "json", "j", false, "use json format to output format")
	rootCmd.PersistentFlags().BoolVarP(&globalOpts.Debug, "debug", "d", false, "use debug")
	rootCmd.PersistentFlags().BoolVarP(&globalOpts.Quiet, "quiet", "q", false, "use quiet")
	rootCmd.PersistentFlags().BoolVarP(&globalOpts.GenCmpl, "gen-cmpl", "c", false, "generate completion file")
	rootCmd.PersistentFlags().StringVarP(&globalOpts.BashCmplFile, "bash-cmpl-file", "", "gobgp-completion.bash", "bash cmpl filename")

	globalCmd := NewGlobalCmd()
	neighborCmd := NewNeighborCmd()
	vrfCmd := NewVrfCmd()
	policyCmd := NewPolicyCmd()
	monitorCmd := NewMonitorCmd()
	mrtCmd := NewMrtCmd()
	rpkiCmd := NewRPKICmd()
	bmpCmd := NewBmpCmd()
	rootCmd.AddCommand(globalCmd, neighborCmd, vrfCmd, policyCmd, monitorCmd, mrtCmd, rpkiCmd, bmpCmd)
	return rootCmd
}
<|endoftext|>


<commit_before>package gohistogram

// Histogram is the interface that wraps the Add and Quantile methods.
//
// Add adds a new value, n, to the histogram. Trimming is done automatically.
//
// Quantile returns an approximation.
type Histogram interface {
	Add(n float64)
	Quantile(n float64) (q float64)
}

type bin struct {
	value float64
	count float64
}
<commit_msg>fixed interface docs<commit_after>package gohistogram

// Histogram is the interface that wraps the Add and Quantile methods.
type Histogram interface {
	// Add adds a new value, n, to the histogram. Trimming is done
	// automatically.
	Add(n float64)

	// Quantile returns an approximation.
	Quantile(n float64) (q float64)
}

type bin struct {
	value float64
	count float64
}
<|endoftext|>


<commit_before>package migrate

import (
	"context"
	"database/sql"

	"github.com/ovh/cds/engine/api/project"

	"github.com/go-gorp/gorp"
	"github.com/ovh/cds/engine/api/cache"
	"github.com/ovh/cds/engine/api/workflow"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

func CleanDuplicateHooks(ctx context.Context, db *gorp.DbMap, store cache.Store, dryrun bool) error {
	var ids []int64

	if _, err := db.Select(&ids, "select id from workflow"); err != nil {
		return sdk.WrapError(err, "unable to select workflow")
	}

	var mError = new(sdk.MultiError)
	for _, id := range ids {
		if err := cleanDuplicateHooks(ctx, db, store, id, dryrun); err != nil {
			mError.Append(err)
			log.Error(ctx, "migrate.CleanDuplicateHooks> unable to clean workflow %d: %v", id, err)
		}
	}

	if mError.IsEmpty() {
		return nil
	}
	return mError
}

func cleanDuplicateHooks(ctx context.Context, db *gorp.DbMap, store cache.Store, workflowID int64, dryrun bool) error {
	tx, err := db.Begin()
	if err != nil {
		return sdk.WithStack(err)
	}

	defer tx.Rollback() // nolint

	projectID, err := tx.SelectInt("SELECT project_id FROM workflow WHERE id = $1", workflowID)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil
		}
		return sdk.WithStack(err)
	}

	proj, err := project.LoadByID(tx, store, projectID,
		project.LoadOptions.WithApplicationWithDeploymentStrategies,
		project.LoadOptions.WithPipelines,
		project.LoadOptions.WithEnvironments,
		project.LoadOptions.WithIntegrations)
	if err != nil {
		return sdk.WrapError(err, "unable to load project %d", projectID)
	}

	w, err := workflow.LoadAndLockByID(ctx, tx, store, *proj, workflowID, workflow.LoadOptions{})
	if err != nil {
		if sdk.ErrorIs(err, sdk.ErrNotFound) {
			return nil
		}
		return err
	}

	if w.FromRepository != "" {
		return nil
	}

	if w.FromTemplate != "" {
		return nil
	}

	nbHooks := len(w.WorkflowData.Node.Hooks)
	if nbHooks < 2 {
		return nil
	}

	var hookDoublons = []struct {
		x int
		y int
	}{}

	for i, h1 := range w.WorkflowData.Node.Hooks {
		for j, h2 := range w.WorkflowData.Node.Hooks {
			if i != j && i < j && h1.Ref() == h2.Ref() {
				hookDoublons = append(hookDoublons, struct{ x, y int }{i, j})
			}
		}
	}

	if len(hookDoublons) == 0 {
		return nil
	}

	var idxToRemove []int64
	for _, doublon := range hookDoublons {
		h1 := w.WorkflowData.Node.Hooks[doublon.x]
		h2 := w.WorkflowData.Node.Hooks[doublon.y]
		if h1.ID < h2.ID {
			idxToRemove = append(idxToRemove, int64(doublon.y))
		} else {
			idxToRemove = append(idxToRemove, int64(doublon.x))
		}
	}

	var newHooks []sdk.NodeHook
	for i, h := range w.WorkflowData.Node.Hooks {
		if !sdk.IsInInt64Array(int64(i), idxToRemove) {
			newHooks = append(newHooks, h)
		}
	}
	w.WorkflowData.Node.Hooks = newHooks

	if err := workflow.Update(ctx, tx, store, *proj, w, workflow.UpdateOptions{DisableHookManagement: dryrun}); err != nil {
		return err
	}

	if dryrun {
		log.Info(ctx, "migrate.cleanDuplicateHooks> workflow %s/%s (%d) should been cleaned", proj.Name, w.Name, w.ID)
	} else {
		if err := tx.Commit(); err != nil {
			return err
		}
		log.Info(ctx, "migrate.cleanDuplicateHooks> workflow %s/%s (%d) has been cleaned", proj.Name, w.Name, w.ID)
	}

	return nil
}
<commit_msg>fix(api): avoid error if project cannot be loaded (#5096)<commit_after>package migrate

import (
	"context"
	"database/sql"

	"github.com/ovh/cds/engine/api/project"

	"github.com/go-gorp/gorp"
	"github.com/ovh/cds/engine/api/cache"
	"github.com/ovh/cds/engine/api/workflow"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

func CleanDuplicateHooks(ctx context.Context, db *gorp.DbMap, store cache.Store, dryrun bool) error {
	var ids []int64

	if _, err := db.Select(&ids, "select id from workflow"); err != nil {
		return sdk.WrapError(err, "unable to select workflow")
	}

	var mError = new(sdk.MultiError)
	for _, id := range ids {
		if err := cleanDuplicateHooks(ctx, db, store, id, dryrun); err != nil {
			mError.Append(err)
			log.Error(ctx, "migrate.CleanDuplicateHooks> unable to clean workflow %d: %v", id, err)
		}
	}

	if mError.IsEmpty() {
		return nil
	}
	return mError
}

func cleanDuplicateHooks(ctx context.Context, db *gorp.DbMap, store cache.Store, workflowID int64, dryrun bool) error {
	tx, err := db.Begin()
	if err != nil {
		return sdk.WithStack(err)
	}

	defer tx.Rollback() // nolint

	projectID, err := tx.SelectInt("SELECT project_id FROM workflow WHERE id = $1", workflowID)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil
		}
		return sdk.WithStack(err)
	}

	if projectID == 0 {
		return nil
	}

	proj, err := project.LoadByID(tx, store, projectID,
		project.LoadOptions.WithApplicationWithDeploymentStrategies,
		project.LoadOptions.WithPipelines,
		project.LoadOptions.WithEnvironments,
		project.LoadOptions.WithIntegrations)
	if err != nil {
		return sdk.WrapError(err, "unable to load project %d", projectID)
	}

	w, err := workflow.LoadAndLockByID(ctx, tx, store, *proj, workflowID, workflow.LoadOptions{})
	if err != nil {
		if sdk.ErrorIs(err, sdk.ErrNotFound) {
			return nil
		}
		return err
	}

	if w.FromRepository != "" {
		return nil
	}

	if w.FromTemplate != "" {
		return nil
	}

	nbHooks := len(w.WorkflowData.Node.Hooks)
	if nbHooks < 2 {
		return nil
	}

	var hookDoublons = []struct {
		x int
		y int
	}{}

	for i, h1 := range w.WorkflowData.Node.Hooks {
		for j, h2 := range w.WorkflowData.Node.Hooks {
			if i != j && i < j && h1.Ref() == h2.Ref() {
				hookDoublons = append(hookDoublons, struct{ x, y int }{i, j})
			}
		}
	}

	if len(hookDoublons) == 0 {
		return nil
	}

	var idxToRemove []int64
	for _, doublon := range hookDoublons {
		h1 := w.WorkflowData.Node.Hooks[doublon.x]
		h2 := w.WorkflowData.Node.Hooks[doublon.y]
		if h1.ID < h2.ID {
			idxToRemove = append(idxToRemove, int64(doublon.y))
		} else {
			idxToRemove = append(idxToRemove, int64(doublon.x))
		}
	}

	var newHooks []sdk.NodeHook
	for i, h := range w.WorkflowData.Node.Hooks {
		if !sdk.IsInInt64Array(int64(i), idxToRemove) {
			newHooks = append(newHooks, h)
		}
	}
	w.WorkflowData.Node.Hooks = newHooks

	if err := workflow.Update(ctx, tx, store, *proj, w, workflow.UpdateOptions{DisableHookManagement: dryrun}); err != nil {
		return err
	}

	if dryrun {
		log.Info(ctx, "migrate.cleanDuplicateHooks> workflow %s/%s (%d) should been cleaned", proj.Name, w.Name, w.ID)
	} else {
		if err := tx.Commit(); err != nil {
			return err
		}
		log.Info(ctx, "migrate.cleanDuplicateHooks> workflow %s/%s (%d) has been cleaned", proj.Name, w.Name, w.ID)
	}

	return nil
}
<|endoftext|>


<commit_before>package host

import (
	"bytes"
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"time"

	conf "github.com/citruspi/Iago/configuration"
	"github.com/citruspi/Iago/travis"
)

type Host struct {
	Hostname   string    `json:"hostname"`
	Expiration time.Time `json:"expiration"`
	Protocol   string    `json:"protocol"`
	Port       int       `json:"port"`
	Path       string    `json:"path"`
}

type Notification struct {
	Repository string `json:"repository"`
	Owner      string `json:"owner"`
	Commit     string `json:"commit"`
	Branch     string `json:"branch"`
}

var (
	List []Host
)

func (h Host) URL() string {
	var buffer bytes.Buffer

	buffer.WriteString(h.Protocol)
	buffer.WriteString("://")
	buffer.WriteString(h.Hostname)
	buffer.WriteString(":")
	buffer.WriteString(strconv.Itoa(h.Port))
	buffer.WriteString(h.Path)

	return string(buffer.Bytes())
}

func (h Host) CheckIn() {
	for i, e := range List {
		if e.Hostname == h.Hostname {
			diff := List
			diff = append(diff[:i], diff[i+1:]...)
			List = diff
		}
	}

	if h.Protocol == "" {
		h.Protocol = "http"
	}

	if h.Port == 0 {
		if h.Protocol == "http" {
			h.Port = 80
		} else if h.Protocol == "https" {
			h.Port = 443
		}
	}

	if h.Path == "" {
		h.Path = "/"
	}

	expiration := time.Now().UTC()
	expiration = expiration.Add(time.Duration(conf.Host.TTL) * time.Second)

	h.Expiration = expiration

	List = append(List, h)
}

func Cleanup() {
	for {
		for i, h := range List {
			if time.Now().UTC().After(h.Expiration) {
				diff := List
				diff = append(diff[:i], diff[i+1:]...)
				List = diff
			}
		}
		time.Sleep(time.Duration(conf.Host.TTL) * time.Second)
	}
}

func Notify(announcement travis.Announcement) {
	deploy := Notification{}
	deploy.Repository = announcement.Payload.Repository.Name
	deploy.Owner = announcement.Payload.Repository.Owner
	deploy.Commit = announcement.Payload.Commit
	deploy.Branch = announcement.Payload.Branch

	payload, _ := json.Marshal(deploy)
	body := bytes.NewBuffer(payload)

	for _, host := range List {
		urlStr := host.URL()

		req, err := http.NewRequest("POST", urlStr, body)
		req.Header.Set("Content-Type", "application/json")

		client := &http.Client{}
		resp, err := client.Do(req)

		if err != nil {
			log.Fatal(err)
		}

		defer resp.Body.Close()
	}
}
<commit_msg>Rebranded the deploy variable notification<commit_after>package host

import (
	"bytes"
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"time"

	conf "github.com/citruspi/Iago/configuration"
	"github.com/citruspi/Iago/travis"
)

type Host struct {
	Hostname   string    `json:"hostname"`
	Expiration time.Time `json:"expiration"`
	Protocol   string    `json:"protocol"`
	Port       int       `json:"port"`
	Path       string    `json:"path"`
}

type Notification struct {
	Repository string `json:"repository"`
	Owner      string `json:"owner"`
	Commit     string `json:"commit"`
	Branch     string `json:"branch"`
}

var (
	List []Host
)

func (h Host) URL() string {
	var buffer bytes.Buffer

	buffer.WriteString(h.Protocol)
	buffer.WriteString("://")
	buffer.WriteString(h.Hostname)
	buffer.WriteString(":")
	buffer.WriteString(strconv.Itoa(h.Port))
	buffer.WriteString(h.Path)

	return string(buffer.Bytes())
}

func (h Host) CheckIn() {
	for i, e := range List {
		if e.Hostname == h.Hostname {
			diff := List
			diff = append(diff[:i], diff[i+1:]...)
			List = diff
		}
	}

	if h.Protocol == "" {
		h.Protocol = "http"
	}

	if h.Port == 0 {
		if h.Protocol == "http" {
			h.Port = 80
		} else if h.Protocol == "https" {
			h.Port = 443
		}
	}

	if h.Path == "" {
		h.Path = "/"
	}

	expiration := time.Now().UTC()
	expiration = expiration.Add(time.Duration(conf.Host.TTL) * time.Second)

	h.Expiration = expiration

	List = append(List, h)
}

func Cleanup() {
	for {
		for i, h := range List {
			if time.Now().UTC().After(h.Expiration) {
				diff := List
				diff = append(diff[:i], diff[i+1:]...)
				List = diff
			}
		}
		time.Sleep(time.Duration(conf.Host.TTL) * time.Second)
	}
}

func Notify(announcement travis.Announcement) {
	notification := Notification{}
	notification.Repository = announcement.Payload.Repository.Name
	notification.Owner = announcement.Payload.Repository.Owner
	notification.Commit = announcement.Payload.Commit
	notification.Branch = announcement.Payload.Branch

	content, _ := json.Marshal(notification)
	body := bytes.NewBuffer(content)

	for _, host := range List {
		urlStr := host.URL()

		req, err := http.NewRequest("POST", urlStr, body)
		req.Header.Set("Content-Type", "application/json")

		client := &http.Client{}
		resp, err := client.Do(req)

		if err != nil {
			log.Fatal(err)
		}

		defer resp.Body.Close()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/jmhodges/howsmyssl/tls"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"
)

var (
	httpsAddr = flag.String("httpsAddr", "localhost:10443", "address to boot the HTTPS server on")
	httpAddr  = flag.String("httpAddr", "localhost:10080", "address to boot the HTTP server on")
	vhost     = flag.String("vhost", "localhost", "public domain to use in redirects and templates")
	certPath  = flag.String("cert", "./config/development.crt", "file path to the TLS certificate to serve with")
	keyPath   = flag.String("key", "./config/development.key", "file path to the TLS key to serve with")
	staticDir = flag.String("staticDir", "./static", "file path to the directory of static files to serve")
	tmplDir   = flag.String("templateDir", "./templates", "file path to the directory of templates")

	index *template.Template
)

func main() {
	flag.Parse()
	index = loadIndex()
	_, httpsPort, err := net.SplitHostPort(*httpsAddr)
	if err != nil {
		log.Fatalf("unable to parse httpsAddr: %s", err)
	}
	cert, err := tls.LoadX509KeyPair(*certPath, *keyPath)
	if err != nil {
		log.Fatalf("unable to load TLS key cert pair %s: %s", certPath, err)
	}

	tlsConf := &tls.Config{
		Certificates: []tls.Certificate{cert},
		NextProtos:   []string{"https"},
	}
	tlsConf.BuildNameToCertificate()

	tlsListener, err := tls.Listen("tcp", *httpsAddr, tlsConf)
	if err != nil {
		log.Fatalf("unable to listen for the HTTPS server on %s: %s", *httpsAddr, err)
	}
	plaintextListener, err := net.Listen("tcp", *httpAddr)
	if err != nil {
		log.Fatalf("unable to listen for the HTTP server on %s: %s", *httpAddr, err)
	}
	l := &listener{tlsListener}

	m := tlsMux(
		*vhost,
		httpsPort,
		http.HandlerFunc(handleWeb),
		http.HandlerFunc(handleAPI),
		http.StripPrefix("/s/", http.FileServer(http.Dir(*staticDir))))

	log.Printf("Booting HTTPS on %s and HTTP on %s", *httpsAddr, *httpAddr)
	go func() {
		err := http.Serve(l, m)
		if err != nil {
			log.Fatalf("https server error: %s", err)
		}
	}()
	err = http.Serve(plaintextListener, plaintextRedirect(*vhost, httpsPort))
	if err != nil {
		log.Fatalf("http server error: %s", err)
	}
}

func tlsMux(vhost, port string, webHandler, apiHandler, staticHandler http.Handler) *http.ServeMux {
	m := http.NewServeMux()
	prefix := vhost
	if port != "443" {
		prefix = net.JoinHostPort(vhost, port)
	}
	m.Handle(prefix+"/s/", staticHandler)
	m.Handle(prefix+"/a/check", apiHandler)
	m.Handle(prefix+"/", webHandler)
	m.Handle("/", tlsRedirect(vhost, port, webHandler))
	return m
}

func renderHTML(data *clientInfo) ([]byte, error) {
	b := new(bytes.Buffer)
	err := index.Execute(b, data)
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

func renderJSON(data *clientInfo) ([]byte, error) {
	return json.Marshal(data)
}

func handleWeb(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.Error(w, "404 Not Found", http.StatusNotFound)
		return
	}

	hijackHandle(w, r, "text/html;charset=utf-8", renderHTML)
}

func handleAPI(w http.ResponseWriter, r *http.Request) {
	hijackHandle(w, r, "application/json", renderJSON)
}

func hijackHandle(w http.ResponseWriter, r *http.Request, contentType string, render func(*clientInfo) ([]byte, error)) {
	hj, ok := w.(http.Hijacker)
	if !ok {
		log.Printf("server not hijackable\n")
		http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
		return
	}

	c, brw, err := hj.Hijack()
	if err != nil {
		log.Printf("server errored during hijack: %s\n", err)
		return
	}
	defer c.Close()
	h := make(http.Header)
	h.Set("Date", time.Now().Format(http.TimeFormat))
	h.Set("Content-Type", contentType)
	h.Set("Connection", "close")
	tc, ok := c.(*conn)
	if !ok {
		log.Printf("Unable to convert net.Conn to *conn: %s\n", err)
		hijacked500(h, brw)
	}
	data := ClientInfo(tc)
	bs, err := render(data)
	if err != nil {
		log.Printf("Unable to excute index template: %s\n", err)
		hijacked500(h, brw)
		return
	}
	contentLength := int64(len(bs))
	h.Set("Content-Length", strconv.FormatInt(contentLength, 10))
	resp := &http.Response{
		StatusCode:    200,
		ContentLength: contentLength,
		Header:        h,
		Body:          ioutil.NopCloser(bytes.NewBuffer(bs)),
		ProtoMajor:    1,
		ProtoMinor:    1, // TODO(jmhodges): respond to HTTP/1.0 correctly
	}
	bs, err = httputil.DumpResponse(resp, true)
	if err != nil {
		log.Printf("unable to write response: %s\n", err)
		hijacked500(h, brw)
		return
	}
	brw.Write(bs)
	brw.Flush()
}

func hijacked500(h http.Header, brw *bufio.ReadWriter) {
	msg := []byte("500 Internal Server Error")
	h.Set("Content-Length", strconv.FormatInt(int64(len(msg)), 10))
	resp := &http.Response{
		StatusCode:    500,
		ContentLength: int64(len(msg)),
		Header:        h,
		Body:          ioutil.NopCloser(bytes.NewBuffer(msg)),
		ProtoMajor:    1,
		ProtoMinor:    1,
	}
	bs, _ := httputil.DumpResponse(resp, true)
	if bs != nil {
		brw.Write(bs)
	}
	brw.Flush()
}

func commonRedirect(w http.ResponseWriter, r *http.Request, vhostWithPort string) {
	var u url.URL
	u = *r.URL
	u.Scheme = "https"
	u.Host = vhostWithPort
	http.Redirect(w, r, u.String(), http.StatusMovedPermanently)

}

func plaintextRedirect(vhost, port string) http.Handler {
	if port != "443" {
		vhost = net.JoinHostPort(vhost, port)
	}
	var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
		commonRedirect(w, r, vhost)
	}
	return h
}

func tlsRedirect(vhost, port string, webHandler http.Handler) http.Handler {
	vhostWithPort := vhost
	if port != "443" {
		vhostWithPort = net.JoinHostPort(vhost, port)
	}
	var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
		host, _, err := net.SplitHostPort(r.Host)
		if err != nil {
			host = r.Host
		}
		if vhost == host {
			webHandler.ServeHTTP(w, r)
			return
		}
		commonRedirect(w, r, vhostWithPort)
	}
	return h
}

func loadIndex() *template.Template {
	return template.Must(template.New("index.html").
		Funcs(template.FuncMap{"sentence": sentence, "ratingSpan": ratingSpan}).
		ParseFiles(*tmplDir + "/index.html"))

}

func ratingSpan(rating Rating) template.HTML {
	class := ""
	switch rating {
	case okay:
		class = "okay"
	case improvable:
		class = "improvable"
	case bad:
		class = "bad"
	}
	return template.HTML(fmt.Sprintf(`<span class="%s">%s</span>`, class, rating))
}

func sentence(parts []string) string {
	if len(parts) == 1 {
		return parts[0] + "."
	}
	commaed := parts[:len(parts)-1]
	return strings.Join(commaed, ", ") + ", and " + parts[len(parts)-1] + "."
}
<commit_msg>store 500 response as global var<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/jmhodges/howsmyssl/tls"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	resp500Format = `HTTP/1.1 500 Internal Server Error
Content-Length: 26
Connection: close
Content-Type: text/plain; charset="utf-8"
Date: %s

500 Internal Server Error
`
)

var (
	httpsAddr = flag.String("httpsAddr", "localhost:10443", "address to boot the HTTPS server on")
	httpAddr  = flag.String("httpAddr", "localhost:10080", "address to boot the HTTP server on")
	vhost     = flag.String("vhost", "localhost", "public domain to use in redirects and templates")
	certPath  = flag.String("cert", "./config/development.crt", "file path to the TLS certificate to serve with")
	keyPath   = flag.String("key", "./config/development.key", "file path to the TLS key to serve with")
	staticDir = flag.String("staticDir", "./static", "file path to the directory of static files to serve")
	tmplDir   = flag.String("templateDir", "./templates", "file path to the directory of templates")

	index *template.Template
)

func main() {
	flag.Parse()
	index = loadIndex()
	_, httpsPort, err := net.SplitHostPort(*httpsAddr)
	if err != nil {
		log.Fatalf("unable to parse httpsAddr: %s", err)
	}
	cert, err := tls.LoadX509KeyPair(*certPath, *keyPath)
	if err != nil {
		log.Fatalf("unable to load TLS key cert pair %s: %s", certPath, err)
	}

	tlsConf := &tls.Config{
		Certificates: []tls.Certificate{cert},
		NextProtos:   []string{"https"},
	}
	tlsConf.BuildNameToCertificate()

	tlsListener, err := tls.Listen("tcp", *httpsAddr, tlsConf)
	if err != nil {
		log.Fatalf("unable to listen for the HTTPS server on %s: %s", *httpsAddr, err)
	}
	plaintextListener, err := net.Listen("tcp", *httpAddr)
	if err != nil {
		log.Fatalf("unable to listen for the HTTP server on %s: %s", *httpAddr, err)
	}
	l := &listener{tlsListener}

	m := tlsMux(
		*vhost,
		httpsPort,
		http.HandlerFunc(handleWeb),
		http.HandlerFunc(handleAPI),
		http.StripPrefix("/s/", http.FileServer(http.Dir(*staticDir))))

	log.Printf("Booting HTTPS on %s and HTTP on %s", *httpsAddr, *httpAddr)
	go func() {
		err := http.Serve(l, m)
		if err != nil {
			log.Fatalf("https server error: %s", err)
		}
	}()
	err = http.Serve(plaintextListener, plaintextRedirect(*vhost, httpsPort))
	if err != nil {
		log.Fatalf("http server error: %s", err)
	}
}

func tlsMux(vhost, port string, webHandler, apiHandler, staticHandler http.Handler) *http.ServeMux {
	m := http.NewServeMux()
	prefix := vhost
	if port != "443" {
		prefix = net.JoinHostPort(vhost, port)
	}
	m.Handle(prefix+"/s/", staticHandler)
	m.Handle(prefix+"/a/check", apiHandler)
	m.Handle(prefix+"/", webHandler)
	m.Handle("/", tlsRedirect(vhost, port, webHandler))
	return m
}

func renderHTML(data *clientInfo) ([]byte, error) {
	b := new(bytes.Buffer)
	err := index.Execute(b, data)
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

func renderJSON(data *clientInfo) ([]byte, error) {
	return json.Marshal(data)
}

func handleWeb(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.Error(w, "404 Not Found", http.StatusNotFound)
		return
	}

	hijackHandle(w, r, "text/html;charset=utf-8", renderHTML)
}

func handleAPI(w http.ResponseWriter, r *http.Request) {
	hijackHandle(w, r, "application/json", renderJSON)
}

func hijackHandle(w http.ResponseWriter, r *http.Request, contentType string, render func(*clientInfo) ([]byte, error)) {
	hj, ok := w.(http.Hijacker)
	if !ok {
		log.Printf("server not hijackable\n")
		http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
		return
	}

	c, brw, err := hj.Hijack()
	if err != nil {
		log.Printf("server errored during hijack: %s\n", err)
		return
	}
	defer c.Close()
	tc, ok := c.(*conn)
	if !ok {
		log.Printf("Unable to convert net.Conn to *conn: %s\n", err)
		hijacked500(brw)
	}
	data := ClientInfo(tc)
	bs, err := render(data)
	if err != nil {
		log.Printf("Unable to excute index template: %s\n", err)
		hijacked500(brw)
		return
	}
	contentLength := int64(len(bs))
	h := make(http.Header)
	h.Set("Date", time.Now().Format(http.TimeFormat))
	h.Set("Content-Type", contentType)
	h.Set("Connection", "close")
	h.Set("Content-Length", strconv.FormatInt(contentLength, 10))
	resp := &http.Response{
		StatusCode:    200,
		ContentLength: contentLength,
		Header:        h,
		Body:          ioutil.NopCloser(bytes.NewBuffer(bs)),
		ProtoMajor:    1,
		ProtoMinor:    1, // TODO(jmhodges): respond to HTTP/1.0 correctly
	}
	bs, err = httputil.DumpResponse(resp, true)
	if err != nil {
		log.Printf("unable to write response: %s\n", err)
		hijacked500(brw)
		return
	}
	brw.Write(bs)
	brw.Flush()
}

func hijacked500(brw *bufio.ReadWriter) {
	s := fmt.Sprintf(resp500Format, time.Now().Format(http.TimeFormat))
	brw.WriteString(s)
	brw.Flush()
}

func commonRedirect(w http.ResponseWriter, r *http.Request, vhostWithPort string) {
	var u url.URL
	u = *r.URL
	u.Scheme = "https"
	u.Host = vhostWithPort
	http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
}

func plaintextRedirect(vhost, port string) http.Handler {
	if port != "443" {
		vhost = net.JoinHostPort(vhost, port)
	}
	var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
		commonRedirect(w, r, vhost)
	}
	return h
}

func tlsRedirect(vhost, port string, webHandler http.Handler) http.Handler {
	vhostWithPort := vhost
	if port != "443" {
		vhostWithPort = net.JoinHostPort(vhost, port)
	}
	var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
		host, _, err := net.SplitHostPort(r.Host)
		if err != nil {
			host = r.Host
		}
		if vhost == host {
			webHandler.ServeHTTP(w, r)
			return
		}
		commonRedirect(w, r, vhostWithPort)
	}
	return h
}

func loadIndex() *template.Template {
	return template.Must(template.New("index.html").
		Funcs(template.FuncMap{"sentence": sentence, "ratingSpan": ratingSpan}).
		ParseFiles(*tmplDir + "/index.html"))

}

func ratingSpan(rating Rating) template.HTML {
	class := ""
	switch rating {
	case okay:
		class = "okay"
	case improvable:
		class = "improvable"
	case bad:
		class = "bad"
	}
	return template.HTML(fmt.Sprintf(`<span class="%s">%s</span>`, class, rating))
}

func sentence(parts []string) string {
	if len(parts) == 1 {
		return parts[0] + "."
	}
	commaed := parts[:len(parts)-1]
	return strings.Join(commaed, ", ") + ", and " + parts[len(parts)-1] + "."
}
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2013-2016 Oryx(ossrs)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// The oryx http package provides standard request and response in json.
//			Error, when error, use this handler.
//			Data, when no error, use this handler.
//			SystemError, application level error code.
// The standard server response:
//			code, an int error code.
//			data, specifies the data.
// The global variable Server is the header["Server"].
package http

import (
	"encoding/json"
	"fmt"
	ologger "github.com/ossrs/go-oryx-lib/logger"
	"net/http"
)

// header["Content-Type"] in response.
const HttpJson = "application/json"

// header["Server"] in response.
var Server = "Oryx"

// system int error.
type SystemError int

func (v SystemError) Error() string {
	return fmt.Sprintf("Licenser error=%d", int(v))
}

// http standard error response.
func Error(ctx ologger.Context, err error) http.Handler {
	// for int error, use code instead.
	if v, ok := err.(SystemError); ok {
		return jsonHandler(ctx, map[string]int{"code": int(v)})
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		SetHeader(w)
		w.Header().Set("Content-Type", HttpJson)

		// unknown error, log and response detail
		http.Error(w, err.Error(), http.StatusInternalServerError)
		ologger.Error.Println(ctx, "Serve", r.URL, "failed. err is", err.Error())
	})
}

// http normal response.
func Data(ctx ologger.Context, v interface{}) http.Handler {
	rv := map[string]interface{}{
		"code": 0,
		"data": v,
	}

	// for string, directly use it without convert,
	// for the type covert by golang maybe modify the content.
	if v, ok := v.(string); ok {
		rv["data"] = v
	}

	return jsonHandler(ctx, rv)
}

// set http header.
func SetHeader(w http.ResponseWriter) {
	w.Header().Set("Server", Server)
}

// response json directly.
func jsonHandler(ctx ologger.Context, rv interface{}) http.Handler {
	var err error
	var b []byte
	if b, err = json.Marshal(rv); err != nil {
		return Error(ctx, err)
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		SetHeader(w)
		w.Header().Set("Content-Type", HttpJson)

		q := r.URL.Query()
		if cb := q.Get("callback"); cb != "" {
			fmt.Fprintf(w, "%s(%s)", cb, string(b))
		} else {
			w.Write(b)
		}
	})
}
<commit_msg>for bug #5303: header改为application/avascript.<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2013-2016 Oryx(ossrs)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// The oryx http package provides standard request and response in json.
//			Error, when error, use this handler.
//			Data, when no error, use this handler.
//			SystemError, application level error code.
// The standard server response:
//			code, an int error code.
//			data, specifies the data.
// The global variable Server is the header["Server"].
package http

import (
	"encoding/json"
	"fmt"
	ologger "github.com/ossrs/go-oryx-lib/logger"
	"net/http"
)

// header["Content-Type"] in response.
const (
	HttpJson = "application/json"
	HttpJavaScript = "application/javascript"
)

// header["Server"] in response.
var Server = "Oryx"

// system int error.
type SystemError int

func (v SystemError) Error() string {
	return fmt.Sprintf("Licenser error=%d", int(v))
}

// http standard error response.
func Error(ctx ologger.Context, err error) http.Handler {
	// for int error, use code instead.
	if v, ok := err.(SystemError); ok {
		return jsonHandler(ctx, map[string]int{"code": int(v)})
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		SetHeader(w)
		w.Header().Set("Content-Type", HttpJson)

		// unknown error, log and response detail
		http.Error(w, err.Error(), http.StatusInternalServerError)
		ologger.Error.Println(ctx, "Serve", r.URL, "failed. err is", err.Error())
	})
}

// http normal response.
func Data(ctx ologger.Context, v interface{}) http.Handler {
	rv := map[string]interface{}{
		"code": 0,
		"data": v,
	}

	// for string, directly use it without convert,
	// for the type covert by golang maybe modify the content.
	if v, ok := v.(string); ok {
		rv["data"] = v
	}

	return jsonHandler(ctx, rv)
}

// set http header.
func SetHeader(w http.ResponseWriter) {
	w.Header().Set("Server", Server)
}

// response json directly.
func jsonHandler(ctx ologger.Context, rv interface{}) http.Handler {
	var err error
	var b []byte
	if b, err = json.Marshal(rv); err != nil {
		return Error(ctx, err)
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		SetHeader(w)

		q := r.URL.Query()
		if cb := q.Get("callback"); cb != "" {
			w.Header().Set("Content-Type", HttpJavaScript)
			fmt.Fprintf(w, "%s(%s)", cb, string(b))
		} else {
			w.Header().Set("Content-Type", HttpJson)
			w.Write(b)
		}
	})
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"os"
	"os/exec"
	"syscall"
	"github.com/docker/docker/pkg/reexec"
	"path/filepath"
	"github.com/getsentry/raven-go"
	"../../models"
	"../../config"
	"flag"
	"strings"
	"bytes"
	"time"
	"strconv"
)

func init() {
	raven.SetDSN(config.SENTRY_DSN)
	reexec.Register("justiceInit", justiceInit)
	if reexec.Init() {
		os.Exit(models.CODE_OK)
	}
}

func pivotRoot(newRoot string) error {
	putOld := filepath.Join(newRoot, "/.pivot_root")

	// bind mount newRoot to itself - this is a slight hack needed to satisfy requirement (2)
	//
	// The following restrictions apply to new_root and put_old:
	// 1.  They must be directories.
	// 2.  new_root and put_old must not be on the same filesystem as the current root.
	// 3.  put_old must be underneath new_root, that is, adding a nonzero
	//     number of /.. to the string pointed to by put_old must yield the same directory as new_root.
	// 4.  No other filesystem may be mounted on put_old.
	if err := syscall.Mount(newRoot, newRoot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
		return err
	}

	// create put_old directory
	if err := os.MkdirAll(putOld, 0700); err != nil {
		return err
	}

	// call pivotRoot
	if err := syscall.PivotRoot(newRoot, putOld); err != nil {
		return err
	}

	// Note that this also applies to the calling process: pivotRoot() may
	// or may not affect its current working directory.  It is therefore
	// recommended to call chdir("/") immediately after pivotRoot().
	if err := os.Chdir("/"); err != nil {
		return err
	}

	// umount putOld, which now lives at /.pivot_root
	putOld = "/.pivot_root"
	if err := syscall.Unmount(putOld, syscall.MNT_DETACH); err != nil {
		return err
	}

	// remove putOld
	if err := os.RemoveAll(putOld); err != nil {
		return err
	}

	return nil
}

func mountProc(newRoot string) error {
	target := filepath.Join(newRoot, "/proc")
	os.MkdirAll(target, 0755)
	return syscall.Mount("proc", target, "proc", uintptr(0), "")
}

func justiceInit() {
	newRootPath := os.Args[1]
	input := os.Args[2]
	expected := os.Args[3]
	timeout, _ := strconv.ParseInt(os.Args[4], 10, 32)

	if err := mountProc(newRootPath); err != nil {
		raven.CaptureErrorAndWait(err, map[string]string{"error": "InitContainerFailed"})
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		os.Exit(models.CODE_INIT_CONTAINER_FAILED)
	}

	if err := pivotRoot(newRootPath); err != nil {
		raven.CaptureErrorAndWait(err, map[string]string{"error": "InitContainerFailed"})
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		os.Exit(models.CODE_INIT_CONTAINER_FAILED)
	}

	if err := syscall.Sethostname([]byte("justice")); err != nil {
		raven.CaptureErrorAndWait(err, map[string]string{"error": "InitContainerFailed"})
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		os.Exit(models.CODE_INIT_CONTAINER_FAILED)
	}

	justiceRun(input, expected, int32(timeout))
}

func justiceRun(input, expected string, timeout int32) {
	// for c programs, compiled binary with name [Main] will be located in "/"
	var o, e bytes.Buffer
	cmd := exec.Command("/Main")
	cmd.Stdin = strings.NewReader(input)
	cmd.Stdout = &o
	cmd.Stderr = &e
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}
	cmd.Env = []string{"PS1=[justice] # "}

	time.AfterFunc(time.Duration(timeout)*time.Millisecond, func() {
		syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
	})

	// ms
	startTime := time.Now().UnixNano() / int64(time.Millisecond)
	if err := cmd.Run(); err != nil {
		raven.CaptureErrorAndWait(err, map[string]string{"error": "ContainerRunTimeError"})
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		return
	}
	endTime := time.Now().UnixNano() / int64(time.Millisecond)

	if e.Len() > 0 {
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		return
	}

	// MB
	memory := cmd.ProcessState.SysUsage().(*syscall.Rusage).Maxrss / 1024

	output := strings.TrimSpace(o.String())
	if output == expected {
		result, _ := json.Marshal(models.GetAccepptedTaskResult(endTime - startTime, memory))
		os.Stdout.Write(result)
	} else {
		result, _ := json.Marshal(models.GetWrongAnswerTaskResult(input, output, expected))
		os.Stdout.Write(result)
	}
}

func main() {
	basedir := flag.String("basedir", "/tmp", "basedir of tmp C binary")
	input := flag.String("input", "<input>", "test case input")
	expected := flag.String("expected", "<expected>", "test case expected")
	timeout := flag.String("timeout", "2000", "timeout in milliseconds")
	flag.Parse()

	cmd := reexec.Command("justiceInit", *basedir, *input, *expected, *timeout)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWNET |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}

	if err := cmd.Run(); err != nil {
		os.Exit(models.CODE_CONTAINER_RUNTIME_ERROR)
	}

	os.Exit(models.CODE_OK)
}
<commit_msg>comments update<commit_after>package main

import (
	"encoding/json"
	"os"
	"os/exec"
	"syscall"
	"github.com/docker/docker/pkg/reexec"
	"path/filepath"
	"github.com/getsentry/raven-go"
	"../../models"
	"../../config"
	"flag"
	"strings"
	"bytes"
	"time"
	"strconv"
)

func init() {
	raven.SetDSN(config.SENTRY_DSN)
	reexec.Register("justiceInit", justiceInit)
	if reexec.Init() {
		os.Exit(models.CODE_OK)
	}
}

func pivotRoot(newRoot string) error {
	putOld := filepath.Join(newRoot, "/.pivot_root")

	// bind mount new_root to itself - this is a slight hack needed to satisfy requirement (2)
	//
	// The following restrictions apply to new_root and put_old:
	// 1.  They must be directories.
	// 2.  new_root and put_old must not be on the same filesystem as the current root.
	// 3.  put_old must be underneath new_root, that is, adding a nonzero
	//     number of /.. to the string pointed to by put_old must yield the same directory as new_root.
	// 4.  No other filesystem may be mounted on put_old.
	if err := syscall.Mount(newRoot, newRoot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
		return err
	}

	// create put_old directory
	if err := os.MkdirAll(putOld, 0700); err != nil {
		return err
	}

	// call pivotRoot
	if err := syscall.PivotRoot(newRoot, putOld); err != nil {
		return err
	}

	// Note that this also applies to the calling process: pivotRoot() may
	// or may not affect its current working directory.  It is therefore
	// recommended to call chdir("/") immediately after pivotRoot().
	if err := os.Chdir("/"); err != nil {
		return err
	}

	// umount put_old, which now lives at /.pivot_root
	putOld = "/.pivot_root"
	if err := syscall.Unmount(putOld, syscall.MNT_DETACH); err != nil {
		return err
	}

	// remove put_old
	if err := os.RemoveAll(putOld); err != nil {
		return err
	}

	return nil
}

func mountProc(newRoot string) error {
	target := filepath.Join(newRoot, "/proc")
	os.MkdirAll(target, 0755)
	return syscall.Mount("proc", target, "proc", uintptr(0), "")
}

func justiceInit() {
	newRootPath := os.Args[1]
	input := os.Args[2]
	expected := os.Args[3]
	timeout, _ := strconv.ParseInt(os.Args[4], 10, 32)

	if err := mountProc(newRootPath); err != nil {
		raven.CaptureErrorAndWait(err, map[string]string{"error": "InitContainerFailed"})
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		os.Exit(models.CODE_INIT_CONTAINER_FAILED)
	}

	if err := pivotRoot(newRootPath); err != nil {
		raven.CaptureErrorAndWait(err, map[string]string{"error": "InitContainerFailed"})
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		os.Exit(models.CODE_INIT_CONTAINER_FAILED)
	}

	if err := syscall.Sethostname([]byte("justice")); err != nil {
		raven.CaptureErrorAndWait(err, map[string]string{"error": "InitContainerFailed"})
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		os.Exit(models.CODE_INIT_CONTAINER_FAILED)
	}

	justiceRun(input, expected, int32(timeout))
}

func justiceRun(input, expected string, timeout int32) {
	// for c programs, compiled binary with name [Main] will be located in "/"
	var o, e bytes.Buffer
	cmd := exec.Command("/Main")
	cmd.Stdin = strings.NewReader(input)
	cmd.Stdout = &o
	cmd.Stderr = &e
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}
	cmd.Env = []string{"PS1=[justice] # "}

	time.AfterFunc(time.Duration(timeout)*time.Millisecond, func() {
		syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
	})

	// ms
	startTime := time.Now().UnixNano() / int64(time.Millisecond)
	if err := cmd.Run(); err != nil {
		raven.CaptureErrorAndWait(err, map[string]string{"error": "ContainerRunTimeError"})
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		return
	}
	endTime := time.Now().UnixNano() / int64(time.Millisecond)

	if e.Len() > 0 {
		result, _ := json.Marshal(models.GetRuntimeErrorTaskResult())
		os.Stdout.Write(result)
		return
	}

	// MB
	memory := cmd.ProcessState.SysUsage().(*syscall.Rusage).Maxrss / 1024

	output := strings.TrimSpace(o.String())
	if output == expected {
		result, _ := json.Marshal(models.GetAccepptedTaskResult(endTime - startTime, memory))
		os.Stdout.Write(result)
	} else {
		result, _ := json.Marshal(models.GetWrongAnswerTaskResult(input, output, expected))
		os.Stdout.Write(result)
	}
}

func main() {
	basedir := flag.String("basedir", "/tmp", "basedir of tmp C binary")
	input := flag.String("input", "<input>", "test case input")
	expected := flag.String("expected", "<expected>", "test case expected")
	timeout := flag.String("timeout", "2000", "timeout in milliseconds")
	flag.Parse()

	cmd := reexec.Command("justiceInit", *basedir, *input, *expected, *timeout)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWNET |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}

	if err := cmd.Run(); err != nil {
		os.Exit(models.CODE_CONTAINER_RUNTIME_ERROR)
	}

	os.Exit(models.CODE_OK)
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// +build go1.3

package lxdclient

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/juju/errors"
	"github.com/lxc/lxd/shared"
)

// TODO(ericsnow) We probably need to address some of the things that
// get handled in container/lxc/clonetemplate.go.

func (client *Client) addInstance(spec InstanceSpec) error {
	// TODO(ericsnow) Default to spec.ImageRemote (once it gets added).
	imageRemote := ""
	if imageRemote == "" {
		imageRemote = client.remote
	}
	imageAlias := "ubuntu" // TODO(ericsnow) Do not hard-code.
	//imageAlias := spec.Image
	var profiles *[]string
	if len(spec.Profiles) > 0 {
		profiles = &spec.Profiles
	}

	// TODO(ericsnow) Copy the image first?

	resp, err := client.raw.Init(spec.Name, imageRemote, imageAlias, profiles, spec.Ephemeral)
	if err != nil {
		return errors.Trace(err)
	}

	// Init is an async operation, since the tar -xvf (or whatever) might
	// take a while; the result is an LXD operation id, which we can just
	// wait on until it is finished.
	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
		// TODO(ericsnow) Handle different failures (from the async
		// operation) differently?
		return errors.Trace(err)
	}

	if err := client.initInstanceConfig(spec); err != nil {
		if err := client.removeInstance(spec.Name); err != nil {
			logger.Errorf("could not remove container %q after configuring it failed", spec.Name)
		}
		return errors.Trace(err)
	}

	if client.remote == Local.ID() {
		// TODO(ericsnow) Only do this if it's a state server...
		if err := client.exposeHostAPI(spec); err != nil {
			if err := client.removeInstance(spec.Name); err != nil {
				logger.Errorf("could not remove container %q after exposing the API sock failed", spec.Name)
			}
			return errors.Trace(err)
		}
	}

	return nil
}

func (client *Client) initInstanceConfig(spec InstanceSpec) error {
	config := spec.config()
	for key, value := range config {
		err := client.raw.SetContainerConfig(spec.Name, key, value)
		if err != nil {
			return errors.Trace(err)
		}
	}
	return nil
}

func (client *Client) exposeHostAPI(spec InstanceSpec) error {
	// lxc config device add juju-container lxdsock disk \
	// source=/var/lib/lxd/unix.socket path=var/lib/lxd/unix.socket
	const apiDevName = "lxdsock"
	const devType = "disk"
	const filename = "/var/lib/lxd/unix.socket"
	props := []string{
		// TODO(ericsnow) hard-coded, unix-centric...
		"source=/var/lib/lxd/unix.socket",
		"path=var/lib/lxd/unix.socket",
	}
	resp, err := client.raw.ContainerDeviceAdd(spec.Name, apiDevName, devType, props)
	if err != nil {
		return errors.Trace(err)
	}

	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
		// TODO(ericsnow) Handle different failures (from the async
		// operation) differently?
		return errors.Trace(err)
	}

	return nil
}

type execFailure struct {
	cmd    string
	code   int
	stderr string
}

// Error returns the string representation of the error.
func (err execFailure) Error() string {
	return fmt.Sprintf("got non-zero code from %q: (%d) %s", err.cmd, err.code, err.stderr)
}

func (client *Client) exec(spec InstanceSpec, cmd []string) error {
	var env map[string]string

	stdin, stdout, stderr, err := ioFiles()
	if err != nil {
		return errors.Trace(err)
	}

	cmdStr := strings.Join(cmd, " ")
	fmt.Println("running", cmdStr)

	rc, err := client.raw.Exec(spec.Name, cmd, env, stdin, stdout, stderr)
	if err != nil {
		return errors.Trace(err)
	} else if rc != 0 {
		msg := "<reason unknown>"
		if _, err := stdout.Seek(0, 0); err == nil {
			data, err := ioutil.ReadAll(stdout)
			if err == nil {
				msg = string(data)
			}
		}
		err := &execFailure{
			cmd:    cmdStr,
			code:   rc,
			stderr: msg,
		}
		return errors.Trace(err)
	}

	return nil
}

// TODO(ericsnow) We *should* be able to use bytes.Buffer instead...
func ioFiles() (*os.File, *os.File, *os.File, error) {
	infile, err := ioutil.TempFile("", "")
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}

	outfile, err := ioutil.TempFile("", "")
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}

	// We combine stdout and stderr...
	return infile, outfile, outfile, nil
}

func (client *Client) chmod(spec InstanceSpec, filename string, mode os.FileMode) error {
	cmd := []string{
		"/bin/chmod",
		fmt.Sprintf("%s", mode),
		filename,
	}

	if err := client.exec(spec, cmd); err != nil {
		return errors.Trace(err)
	}
	return nil
}

func (client *Client) startInstance(spec InstanceSpec) error {
	timeout := -1
	force := false
	resp, err := client.raw.Action(spec.Name, shared.Start, timeout, force)
	if err != nil {
		return errors.Trace(err)
	}

	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
		// TODO(ericsnow) Handle different failures (from the async
		// operation) differently?
		return errors.Trace(err)
	}

	return nil
}

func (client *Client) fixSockfile(spec InstanceSpec) error {
	const filename = "/var/lib/lxd/unix.socket"

	//info, err := os.Stat(filename)
	//if err != nil {
	//	return nil, errors.Trace(err)
	//}
	//gid := info.Sys().(*syscall.Stat_t).Gid

	//cmd := []string{
	//	"/usr/sbin/groupadd",
	//	fmt.Sprintf("--gid=%d", gid),
	//	"lxd",
	//}
	//if err := client.exec(spec, cmd); err != nil {
	//	return errors.Trace(err)
	//}

	//cmd = []string{
	//	"/usr/sbin/usermod",
	//	"-a",
	//	"-G", "lxd",
	//	"root",
	//}
	//if err := client.exec(spec, cmd); err != nil {
	//	return errors.Trace(err)
	//}

	// TODO(ericsnow) Instead of modifying the socket file, add the
	// "lxd" group, ensure the GID matches the one on the host, and add
	// the root user to that group.

	// TODO(ericsnow) For now, ensure that your local unix.socket is 0666...
	//if err := client.chmod(spec, filename, 0666); err != nil {
	//	fmt.Println("---- ", err)
	//	//return errors.Trace(err)
	//}

	return nil
}

// AddInstance creates a new instance based on the spec's data and
// returns it. The instance will be created using the client.
func (client *Client) AddInstance(spec InstanceSpec) (*Instance, error) {
	if err := client.addInstance(spec); err != nil {
		return nil, errors.Trace(err)
	}

	if err := client.startInstance(spec); err != nil {
		if err := client.removeInstance(spec.Name); err != nil {
			logger.Errorf("could not remove container %q after starting it failed", spec.Name)
		}
		return nil, errors.Trace(err)
	}

	// TODO(ericsnow) This is a hack tied to exposeHostAPI().
	if err := client.fixSockfile(spec); err != nil {
		return nil, errors.Trace(err)
	}

	inst, err := client.Instance(spec.Name)
	if err != nil {
		return nil, errors.Trace(err)
	}
	inst.spec = &spec

	return inst, nil
}

// Instance gets the up-to-date info about the given instance
// and returns it.
func (client *Client) Instance(name string) (*Instance, error) {
	info, err := client.raw.ContainerStatus(name)
	if err != nil {
		return nil, errors.Trace(err)
	}

	inst := newInstance(info, nil)
	return inst, nil
}

// Instances sends a request to the API for a list of all instances
// (in the Client's namespace) for which the name starts with the
// provided prefix. The result is also limited to those instances with
// one of the specified statuses (if any).
func (client *Client) Instances(prefix string, statuses ...string) ([]Instance, error) {
	infos, err := client.raw.ListContainers()
	if err != nil {
		return nil, errors.Trace(err)
	}

	var insts []Instance
	for _, info := range infos {
		name := info.State.Name
		if prefix != "" && !strings.HasPrefix(name, prefix) {
			continue
		}
		if len(statuses) > 0 && !checkStatus(info, statuses) {
			continue
		}

		inst := newInstance(&info.State, nil)
		insts = append(insts, *inst)
	}
	return insts, nil
}

func checkStatus(info shared.ContainerInfo, statuses []string) bool {
	for _, status := range statuses {
		statusCode := allStatuses[status]
		if info.State.Status.StatusCode == statusCode {
			return true
		}
	}
	return false
}

// removeInstance sends a request to the API to remove the instance
// with the provided ID. The call blocks until the instance is removed
// (or the request fails).
func (client *Client) removeInstance(name string) error {
	info, err := client.raw.ContainerStatus(name)
	if err != nil {
		return errors.Trace(err)
	}

	//if info.Status.StatusCode != 0 && info.Status.StatusCode != shared.Stopped {
	if info.Status.StatusCode != shared.Stopped {
		timeout := -1
		force := true
		resp, err := client.raw.Action(name, shared.Stop, timeout, force)
		if err != nil {
			return errors.Trace(err)
		}

		if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
			// TODO(ericsnow) Handle different failures (from the async
			// operation) differently?
			return errors.Trace(err)
		}
	}

	resp, err := client.raw.Delete(name)
	if err != nil {
		return errors.Trace(err)
	}

	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
		// TODO(ericsnow) Handle different failures (from the async
		// operation) differently?
		return errors.Trace(err)
	}

	return nil
}

// RemoveInstances sends a request to the API to terminate all
// instances (in the Client's namespace) that match one of the
// provided IDs. If a prefix is provided, only IDs that start with the
// prefix will be considered. The call blocks until all the instances
// are removed or the request fails.
func (client *Client) RemoveInstances(prefix string, names ...string) error {
	if len(names) == 0 {
		return nil
	}

	instances, err := client.Instances(prefix)
	if err != nil {
		return errors.Annotatef(err, "while removing instances %v", names)
	}

	var failed []string
	for _, name := range names {
		if !checkInstanceName(name, instances) {
			// We ignore unknown instance names.
			continue
		}

		if err := client.removeInstance(name); err != nil {
			failed = append(failed, name)
			logger.Errorf("while removing instance %q: %v", name, err)
		}
	}
	if len(failed) != 0 {
		return errors.Errorf("some instance removals failed: %v", failed)
	}
	return nil
}

func checkInstanceName(name string, instances []Instance) bool {
	for _, inst := range instances {
		if inst.Name == name {
			return true
		}
	}
	return false
}
<commit_msg>Use Remote.isLocal() (missed one).<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// +build go1.3

package lxdclient

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/juju/errors"
	"github.com/lxc/lxd/shared"
)

// TODO(ericsnow) We probably need to address some of the things that
// get handled in container/lxc/clonetemplate.go.

func (client *Client) addInstance(spec InstanceSpec) error {
	// TODO(ericsnow) Default to spec.ImageRemote (once it gets added).
	imageRemote := ""
	if imageRemote == "" {
		imageRemote = client.remote
	}
	imageAlias := "ubuntu" // TODO(ericsnow) Do not hard-code.
	//imageAlias := spec.Image
	var profiles *[]string
	if len(spec.Profiles) > 0 {
		profiles = &spec.Profiles
	}

	// TODO(ericsnow) Copy the image first?

	resp, err := client.raw.Init(spec.Name, imageRemote, imageAlias, profiles, spec.Ephemeral)
	if err != nil {
		return errors.Trace(err)
	}

	// Init is an async operation, since the tar -xvf (or whatever) might
	// take a while; the result is an LXD operation id, which we can just
	// wait on until it is finished.
	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
		// TODO(ericsnow) Handle different failures (from the async
		// operation) differently?
		return errors.Trace(err)
	}

	if err := client.initInstanceConfig(spec); err != nil {
		if err := client.removeInstance(spec.Name); err != nil {
			logger.Errorf("could not remove container %q after configuring it failed", spec.Name)
		}
		return errors.Trace(err)
	}

	if client.remote.isLocal() {
		// TODO(ericsnow) Only do this if it's a state server...
		if err := client.exposeHostAPI(spec); err != nil {
			if err := client.removeInstance(spec.Name); err != nil {
				logger.Errorf("could not remove container %q after exposing the API sock failed", spec.Name)
			}
			return errors.Trace(err)
		}
	}

	return nil
}

func (client *Client) initInstanceConfig(spec InstanceSpec) error {
	config := spec.config()
	for key, value := range config {
		err := client.raw.SetContainerConfig(spec.Name, key, value)
		if err != nil {
			return errors.Trace(err)
		}
	}
	return nil
}

func (client *Client) exposeHostAPI(spec InstanceSpec) error {
	// lxc config device add juju-container lxdsock disk \
	// source=/var/lib/lxd/unix.socket path=var/lib/lxd/unix.socket
	const apiDevName = "lxdsock"
	const devType = "disk"
	const filename = "/var/lib/lxd/unix.socket"
	props := []string{
		// TODO(ericsnow) hard-coded, unix-centric...
		"source=/var/lib/lxd/unix.socket",
		"path=var/lib/lxd/unix.socket",
	}
	resp, err := client.raw.ContainerDeviceAdd(spec.Name, apiDevName, devType, props)
	if err != nil {
		return errors.Trace(err)
	}

	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
		// TODO(ericsnow) Handle different failures (from the async
		// operation) differently?
		return errors.Trace(err)
	}

	return nil
}

type execFailure struct {
	cmd    string
	code   int
	stderr string
}

// Error returns the string representation of the error.
func (err execFailure) Error() string {
	return fmt.Sprintf("got non-zero code from %q: (%d) %s", err.cmd, err.code, err.stderr)
}

func (client *Client) exec(spec InstanceSpec, cmd []string) error {
	var env map[string]string

	stdin, stdout, stderr, err := ioFiles()
	if err != nil {
		return errors.Trace(err)
	}

	cmdStr := strings.Join(cmd, " ")
	fmt.Println("running", cmdStr)

	rc, err := client.raw.Exec(spec.Name, cmd, env, stdin, stdout, stderr)
	if err != nil {
		return errors.Trace(err)
	} else if rc != 0 {
		msg := "<reason unknown>"
		if _, err := stdout.Seek(0, 0); err == nil {
			data, err := ioutil.ReadAll(stdout)
			if err == nil {
				msg = string(data)
			}
		}
		err := &execFailure{
			cmd:    cmdStr,
			code:   rc,
			stderr: msg,
		}
		return errors.Trace(err)
	}

	return nil
}

// TODO(ericsnow) We *should* be able to use bytes.Buffer instead...
func ioFiles() (*os.File, *os.File, *os.File, error) {
	infile, err := ioutil.TempFile("", "")
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}

	outfile, err := ioutil.TempFile("", "")
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}

	// We combine stdout and stderr...
	return infile, outfile, outfile, nil
}

func (client *Client) chmod(spec InstanceSpec, filename string, mode os.FileMode) error {
	cmd := []string{
		"/bin/chmod",
		fmt.Sprintf("%s", mode),
		filename,
	}

	if err := client.exec(spec, cmd); err != nil {
		return errors.Trace(err)
	}
	return nil
}

func (client *Client) startInstance(spec InstanceSpec) error {
	timeout := -1
	force := false
	resp, err := client.raw.Action(spec.Name, shared.Start, timeout, force)
	if err != nil {
		return errors.Trace(err)
	}

	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
		// TODO(ericsnow) Handle different failures (from the async
		// operation) differently?
		return errors.Trace(err)
	}

	return nil
}

func (client *Client) fixSockfile(spec InstanceSpec) error {
	const filename = "/var/lib/lxd/unix.socket"

	//info, err := os.Stat(filename)
	//if err != nil {
	//	return nil, errors.Trace(err)
	//}
	//gid := info.Sys().(*syscall.Stat_t).Gid

	//cmd := []string{
	//	"/usr/sbin/groupadd",
	//	fmt.Sprintf("--gid=%d", gid),
	//	"lxd",
	//}
	//if err := client.exec(spec, cmd); err != nil {
	//	return errors.Trace(err)
	//}

	//cmd = []string{
	//	"/usr/sbin/usermod",
	//	"-a",
	//	"-G", "lxd",
	//	"root",
	//}
	//if err := client.exec(spec, cmd); err != nil {
	//	return errors.Trace(err)
	//}

	// TODO(ericsnow) Instead of modifying the socket file, add the
	// "lxd" group, ensure the GID matches the one on the host, and add
	// the root user to that group.

	// TODO(ericsnow) For now, ensure that your local unix.socket is 0666...
	//if err := client.chmod(spec, filename, 0666); err != nil {
	//	fmt.Println("---- ", err)
	//	//return errors.Trace(err)
	//}

	return nil
}

// AddInstance creates a new instance based on the spec's data and
// returns it. The instance will be created using the client.
func (client *Client) AddInstance(spec InstanceSpec) (*Instance, error) {
	if err := client.addInstance(spec); err != nil {
		return nil, errors.Trace(err)
	}

	if err := client.startInstance(spec); err != nil {
		if err := client.removeInstance(spec.Name); err != nil {
			logger.Errorf("could not remove container %q after starting it failed", spec.Name)
		}
		return nil, errors.Trace(err)
	}

	// TODO(ericsnow) This is a hack tied to exposeHostAPI().
	if err := client.fixSockfile(spec); err != nil {
		return nil, errors.Trace(err)
	}

	inst, err := client.Instance(spec.Name)
	if err != nil {
		return nil, errors.Trace(err)
	}
	inst.spec = &spec

	return inst, nil
}

// Instance gets the up-to-date info about the given instance
// and returns it.
func (client *Client) Instance(name string) (*Instance, error) {
	info, err := client.raw.ContainerStatus(name)
	if err != nil {
		return nil, errors.Trace(err)
	}

	inst := newInstance(info, nil)
	return inst, nil
}

// Instances sends a request to the API for a list of all instances
// (in the Client's namespace) for which the name starts with the
// provided prefix. The result is also limited to those instances with
// one of the specified statuses (if any).
func (client *Client) Instances(prefix string, statuses ...string) ([]Instance, error) {
	infos, err := client.raw.ListContainers()
	if err != nil {
		return nil, errors.Trace(err)
	}

	var insts []Instance
	for _, info := range infos {
		name := info.State.Name
		if prefix != "" && !strings.HasPrefix(name, prefix) {
			continue
		}
		if len(statuses) > 0 && !checkStatus(info, statuses) {
			continue
		}

		inst := newInstance(&info.State, nil)
		insts = append(insts, *inst)
	}
	return insts, nil
}

func checkStatus(info shared.ContainerInfo, statuses []string) bool {
	for _, status := range statuses {
		statusCode := allStatuses[status]
		if info.State.Status.StatusCode == statusCode {
			return true
		}
	}
	return false
}

// removeInstance sends a request to the API to remove the instance
// with the provided ID. The call blocks until the instance is removed
// (or the request fails).
func (client *Client) removeInstance(name string) error {
	info, err := client.raw.ContainerStatus(name)
	if err != nil {
		return errors.Trace(err)
	}

	//if info.Status.StatusCode != 0 && info.Status.StatusCode != shared.Stopped {
	if info.Status.StatusCode != shared.Stopped {
		timeout := -1
		force := true
		resp, err := client.raw.Action(name, shared.Stop, timeout, force)
		if err != nil {
			return errors.Trace(err)
		}

		if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
			// TODO(ericsnow) Handle different failures (from the async
			// operation) differently?
			return errors.Trace(err)
		}
	}

	resp, err := client.raw.Delete(name)
	if err != nil {
		return errors.Trace(err)
	}

	if err := client.raw.WaitForSuccess(resp.Operation); err != nil {
		// TODO(ericsnow) Handle different failures (from the async
		// operation) differently?
		return errors.Trace(err)
	}

	return nil
}

// RemoveInstances sends a request to the API to terminate all
// instances (in the Client's namespace) that match one of the
// provided IDs. If a prefix is provided, only IDs that start with the
// prefix will be considered. The call blocks until all the instances
// are removed or the request fails.
func (client *Client) RemoveInstances(prefix string, names ...string) error {
	if len(names) == 0 {
		return nil
	}

	instances, err := client.Instances(prefix)
	if err != nil {
		return errors.Annotatef(err, "while removing instances %v", names)
	}

	var failed []string
	for _, name := range names {
		if !checkInstanceName(name, instances) {
			// We ignore unknown instance names.
			continue
		}

		if err := client.removeInstance(name); err != nil {
			failed = append(failed, name)
			logger.Errorf("while removing instance %q: %v", name, err)
		}
	}
	if len(failed) != 0 {
		return errors.Errorf("some instance removals failed: %v", failed)
	}
	return nil
}

func checkInstanceName(name string, instances []Instance) bool {
	for _, inst := range instances {
		if inst.Name == name {
			return true
		}
	}
	return false
}
<|endoftext|>