<commit_before>package approvals

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/google/uuid"
	"github.com/keel-hq/keel/pkg/store"
	"github.com/keel-hq/keel/types"

	log "github.com/sirupsen/logrus"
)

// Manager is used to manage updates
type Manager interface {
	// Subscribe for approval request events, subscriber should provide
	// its name. Indented to be used by extensions that collect
	// approvals
	Subscribe(ctx context.Context) (<-chan *types.Approval, error)

	// SubscribeApproved - is used to get approved events by the manager
	SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error)

	// request approval for deployment/release/etc..
	Create(r *types.Approval) error
	// Update whole approval object
	Update(r *types.Approval) error

	// Increases Approval votes by 1
	Approve(identifier, voter string) (*types.Approval, error)
	// Rejects Approval
	Reject(identifier string) (*types.Approval, error)

	Get(identifier string) (*types.Approval, error)
	List() ([]*types.Approval, error)
	Delete(*types.Approval) error
	Archive(identifier string) error

	StartExpiryService(ctx context.Context) error
}

// Approvals related errors
var (
	ErrApprovalAlreadyExists = errors.New("approval already exists")
)

// Approvals cache prefix
const (
	ApprovalsPrefix = "approvals"
)

// DefaultManager - default manager implementation
type DefaultManager struct {
	// cache is used to store approvals, key example:
	// approvals/<provider name>/<identifier>
	// cache cache.Cache

	store store.Store

	// subscriber channels
	channels map[uint32]chan *types.Approval
	index    uint32

	// approved channels
	approvedCh map[uint32]chan *types.Approval

	mu    *sync.Mutex
	subMu *sync.RWMutex
}

type Opts struct {
	Store store.Store
	// Cache cache.Cache
}

// New create new instance of default manager
func New(opts *Opts) *DefaultManager {
	man := &DefaultManager{
		// cache:      opts.Cache,
		store:      opts.Store,
		channels:   make(map[uint32]chan *types.Approval),
		approvedCh: make(map[uint32]chan *types.Approval),
		index:      0,
		mu:         &sync.Mutex{},
		subMu:      &sync.RWMutex{},
	}

	return man
}

// StartExpiryService - starts approval expiry service which deletes approvals
// that already reached their deadline
func (m *DefaultManager) StartExpiryService(ctx context.Context) error {
	ticker := time.NewTicker(60 * time.Minute)
	defer ticker.Stop()
	err := m.expireEntries()
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("approvals.StartExpiryService: got error while performing initial expired approvals check")
	}

	for {
		select {
		case <-ctx.Done():
			return nil
		case <-ticker.C:
			err := m.expireEntries()
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
				}).Error("approvals.StartExpiryService: got error while performing routinely expired approvals check")
			}
		}
	}
}

func (m *DefaultManager) expireEntries() error {
	approvals, err := m.store.ListApprovals(&types.GetApprovalQuery{
		Archived: false,
	})
	if err != nil {
		return err
	}

	for _, approval := range approvals {
		if approval.Expired() {
			err = m.Delete(approval)
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
					// "identifier": k,
				}).Error("approvals.expireEntries: failed to delete expired approval")
				continue
			}

			m.addAuditEntry(approval, types.AuditActionApprovalExpired, "")
		}
	}

	return nil
}

// Subscribe - subscribe for approval events
func (m *DefaultManager) Subscribe(ctx context.Context) (<-chan *types.Approval, error) {
	m.subMu.Lock()
	index := atomic.AddUint32(&m.index, 1)
	approvalsCh := make(chan *types.Approval, 10)
	m.channels[index] = approvalsCh
	m.subMu.Unlock()

	go func() {
		for {
			select {
			case <-ctx.Done():
				m.subMu.Lock()

				delete(m.channels, index)

				m.subMu.Unlock()
				return
			}
		}
	}()

	return approvalsCh, nil
}

// SubscribeApproved - subscribe for approved update requests
func (m *DefaultManager) SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error) {
	m.subMu.Lock()
	index := atomic.AddUint32(&m.index, 1)
	approvedCh := make(chan *types.Approval, 10)
	m.approvedCh[index] = approvedCh
	m.subMu.Unlock()

	go func() {
		for {
			select {
			case <-ctx.Done():
				m.subMu.Lock()

				delete(m.approvedCh, index)

				m.subMu.Unlock()
				return
			}
		}
	}()

	return approvedCh, nil
}

func (m *DefaultManager) publishRequest(approval *types.Approval) error {
	m.subMu.RLock()
	defer m.subMu.RUnlock()

	for _, subscriber := range m.channels {
		subscriber <- approval
	}
	return nil
}

func (m *DefaultManager) publishApproved(approval *types.Approval) error {
	m.subMu.RLock()
	defer m.subMu.RUnlock()

	for _, subscriber := range m.approvedCh {
		subscriber <- approval
	}
	return nil
}

// Update - update approval
func (m *DefaultManager) Update(r *types.Approval) error {
	_, err := m.Get(r.Identifier)
	if err != nil {
		return err
	}

	if r.Status() == types.ApprovalStatusApproved {
		err = m.publishApproved(r)
		if err != nil {
			log.WithFields(log.Fields{
				"error":    err,
				"approval": r.Identifier,
				"provider": r.Provider,
			}).Error("approvals.manager: failed to re-submit event after approvals were collected")
		}
	}

	return m.store.UpdateApproval(r)
}

// Approve - increase VotesReceived by 1 and returns updated version
func (m *DefaultManager) Approve(identifier, voter string) (*types.Approval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	existing, err := m.Get(identifier)
	if err != nil {
		log.WithFields(log.Fields{
			"identifier": identifier,
			"error":      err,
		}).Error("approvals.manager: failed to get")
		return nil, err
	}

	for _, v := range existing.GetVoters() {
		if v == voter {
			// nothing to do, same voter
			return existing, nil
		}
	}

	existing.AddVoter(voter)
	existing.VotesReceived++

	err = m.Update(existing)
	if err != nil {
		log.WithFields(log.Fields{
			"identifier": identifier,
			"error":      err,
		}).Error("approvals.manager: failed to update")
		return nil, err
	}

	m.addAuditEntry(existing, types.AuditActionApprovalApproved, voter)

	log.WithFields(log.Fields{
		"identifier": identifier,
	}).Info("approvals.manager: approved")

	return existing, nil
}

func (m *DefaultManager) addAuditEntry(approval *types.Approval, action string, voter string) {

	entry := &types.AuditLog{
		ID:           uuid.New().String(),
		AccountID:    voter,
		Username:     voter,
		Action:       action,
		ResourceKind: types.AuditResourceKindApproval,
		Identifier:   approval.Identifier,
	}

	entry.SetMetadata(map[string]string{
		"provider":        approval.Provider.String(),
		"approval_id":     approval.ID,
		"new_version":     approval.NewVersion,
		"current_version": approval.CurrentVersion,
		"votes_required":  strconv.Itoa(approval.VotesReceived),
		"votes_received":  strconv.Itoa(approval.VotesReceived),
	})

	_, err := m.store.CreateAuditLog(entry)
	if err != nil {
		log.WithFields(log.Fields{
			"error":  err,
			"module": "approvals",
		}).Error("failed to create audit log")
	}
}

// Reject - rejects approval (marks rejected=true), approval will not be valid even if it
// collects required votes
func (m *DefaultManager) Reject(identifier string) (*types.Approval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	existing, err := m.Get(identifier)
	if err != nil {
		return nil, err
	}

	existing.Rejected = true

	err = m.Update(existing)
	if err != nil {
		return nil, err
	}

	m.addAuditEntry(existing, types.AuditActionApprovalRejected, "")

	return existing, nil
}

// Get - get specified, not archived approval
func (m *DefaultManager) Get(identifier string) (*types.Approval, error) {

	a, err := m.store.GetApproval(&types.GetApprovalQuery{
		Identifier: identifier,
		Archived:   false,
	})
	if err != nil {
		return nil, err
	}

	// if it's archived, don't display it
	if a.Archived {
		return nil, store.ErrRecordNotFound
	}

	return a, nil
}

// List - list not archived approvals (for expiration service)
func (m *DefaultManager) List() ([]*types.Approval, error) {
	approvals, err := m.store.ListApprovals(&types.GetApprovalQuery{
		Archived: false,
	})
	return approvals, err
}

// Delete - delete specified approval
func (m *DefaultManager) Delete(approval *types.Approval) error {
	existing, err := m.store.GetApproval(&types.GetApprovalQuery{
		ID: approval.ID,
	})
	if err != nil {
		return err
	}

	m.addAuditEntry(existing, types.AuditActionDeleted, "")

	return m.store.DeleteApproval(existing)
}

func (m *DefaultManager) Archive(identifier string) error {
	existing, err := m.Get(identifier)
	if err != nil {
		return fmt.Errorf("approval not found: %s", err)
	}
	existing.Archived = true

	m.addAuditEntry(existing, types.AuditActionApprovalArchived, "")

	return m.store.UpdateApproval(existing)
}

// Create - creates new approval request and publishes to all subscribers
func (m *DefaultManager) Create(r *types.Approval) error {
	_, err := m.Get(r.Identifier)
	if err == nil {
		return ErrApprovalAlreadyExists
	}

	r.CreatedAt = time.Now()
	r.UpdatedAt = time.Now()

	created, err := m.store.CreateApproval(r)
	if err != nil {
		return fmt.Errorf("failed to create approval: %s", err)
	}

	return m.publishRequest(created)
}

func getKey(identifier string) string {
	return ApprovalsPrefix + "/" + identifier
}
<commit_msg>add a pending approvals prometheus metric<commit_after>package approvals

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/google/uuid"
	"github.com/keel-hq/keel/pkg/store"
	"github.com/keel-hq/keel/types"

	"github.com/prometheus/client_golang/prometheus"
	log "github.com/sirupsen/logrus"
)

// Manager is used to manage updates
type Manager interface {
	// Subscribe for approval request events, subscriber should provide
	// its name. Indented to be used by extensions that collect
	// approvals
	Subscribe(ctx context.Context) (<-chan *types.Approval, error)

	// SubscribeApproved - is used to get approved events by the manager
	SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error)

	// request approval for deployment/release/etc..
	Create(r *types.Approval) error
	// Update whole approval object
	Update(r *types.Approval) error

	// Increases Approval votes by 1
	Approve(identifier, voter string) (*types.Approval, error)
	// Rejects Approval
	Reject(identifier string) (*types.Approval, error)

	Get(identifier string) (*types.Approval, error)
	List() ([]*types.Approval, error)
	Delete(*types.Approval) error
	Archive(identifier string) error

	StartExpiryService(ctx context.Context) error
}

// Approvals related errors
var (
	ErrApprovalAlreadyExists = errors.New("approval already exists")
)

// Approvals cache prefix
const (
	ApprovalsPrefix = "approvals"
)

// DefaultManager - default manager implementation
type DefaultManager struct {
	// cache is used to store approvals, key example:
	// approvals/<provider name>/<identifier>
	// cache cache.Cache

	store store.Store

	// subscriber channels
	channels map[uint32]chan *types.Approval
	index    uint32

	// approved channels
	approvedCh map[uint32]chan *types.Approval

	mu    *sync.Mutex
	subMu *sync.RWMutex
}

type Opts struct {
	Store store.Store
	// Cache cache.Cache
}

// New create new instance of default manager
func New(opts *Opts) *DefaultManager {
	man := &DefaultManager{
		// cache:      opts.Cache,
		store:      opts.Store,
		channels:   make(map[uint32]chan *types.Approval),
		approvedCh: make(map[uint32]chan *types.Approval),
		index:      0,
		mu:         &sync.Mutex{},
		subMu:      &sync.RWMutex{},
	}

	prom_pending := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
		Name: "keel_pending_approvals",
		Help: "number of the pending approvals",
	}, func() float64 {
		approvals, _ := man.List()
		return float64(len(approvals))
	})
	prometheus.MustRegister(prom_pending)

	return man
}

// StartExpiryService - starts approval expiry service which deletes approvals
// that already reached their deadline
func (m *DefaultManager) StartExpiryService(ctx context.Context) error {
	ticker := time.NewTicker(60 * time.Minute)
	defer ticker.Stop()
	err := m.expireEntries()
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("approvals.StartExpiryService: got error while performing initial expired approvals check")
	}

	for {
		select {
		case <-ctx.Done():
			return nil
		case <-ticker.C:
			err := m.expireEntries()
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
				}).Error("approvals.StartExpiryService: got error while performing routinely expired approvals check")
			}
		}
	}
}

func (m *DefaultManager) expireEntries() error {
	approvals, err := m.store.ListApprovals(&types.GetApprovalQuery{
		Archived: false,
	})
	if err != nil {
		return err
	}

	for _, approval := range approvals {
		if approval.Expired() {
			err = m.Delete(approval)
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
					// "identifier": k,
				}).Error("approvals.expireEntries: failed to delete expired approval")
				continue
			}

			m.addAuditEntry(approval, types.AuditActionApprovalExpired, "")
		}
	}

	return nil
}

// Subscribe - subscribe for approval events
func (m *DefaultManager) Subscribe(ctx context.Context) (<-chan *types.Approval, error) {
	m.subMu.Lock()
	index := atomic.AddUint32(&m.index, 1)
	approvalsCh := make(chan *types.Approval, 10)
	m.channels[index] = approvalsCh
	m.subMu.Unlock()

	go func() {
		for {
			select {
			case <-ctx.Done():
				m.subMu.Lock()

				delete(m.channels, index)

				m.subMu.Unlock()
				return
			}
		}
	}()

	return approvalsCh, nil
}

// SubscribeApproved - subscribe for approved update requests
func (m *DefaultManager) SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error) {
	m.subMu.Lock()
	index := atomic.AddUint32(&m.index, 1)
	approvedCh := make(chan *types.Approval, 10)
	m.approvedCh[index] = approvedCh
	m.subMu.Unlock()

	go func() {
		for {
			select {
			case <-ctx.Done():
				m.subMu.Lock()

				delete(m.approvedCh, index)

				m.subMu.Unlock()
				return
			}
		}
	}()

	return approvedCh, nil
}

func (m *DefaultManager) publishRequest(approval *types.Approval) error {
	m.subMu.RLock()
	defer m.subMu.RUnlock()

	for _, subscriber := range m.channels {
		subscriber <- approval
	}
	return nil
}

func (m *DefaultManager) publishApproved(approval *types.Approval) error {
	m.subMu.RLock()
	defer m.subMu.RUnlock()

	for _, subscriber := range m.approvedCh {
		subscriber <- approval
	}
	return nil
}

// Update - update approval
func (m *DefaultManager) Update(r *types.Approval) error {
	_, err := m.Get(r.Identifier)
	if err != nil {
		return err
	}

	if r.Status() == types.ApprovalStatusApproved {
		err = m.publishApproved(r)
		if err != nil {
			log.WithFields(log.Fields{
				"error":    err,
				"approval": r.Identifier,
				"provider": r.Provider,
			}).Error("approvals.manager: failed to re-submit event after approvals were collected")
		}
	}

	return m.store.UpdateApproval(r)
}

// Approve - increase VotesReceived by 1 and returns updated version
func (m *DefaultManager) Approve(identifier, voter string) (*types.Approval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	existing, err := m.Get(identifier)
	if err != nil {
		log.WithFields(log.Fields{
			"identifier": identifier,
			"error":      err,
		}).Error("approvals.manager: failed to get")
		return nil, err
	}

	for _, v := range existing.GetVoters() {
		if v == voter {
			// nothing to do, same voter
			return existing, nil
		}
	}

	existing.AddVoter(voter)
	existing.VotesReceived++

	err = m.Update(existing)
	if err != nil {
		log.WithFields(log.Fields{
			"identifier": identifier,
			"error":      err,
		}).Error("approvals.manager: failed to update")
		return nil, err
	}

	m.addAuditEntry(existing, types.AuditActionApprovalApproved, voter)

	log.WithFields(log.Fields{
		"identifier": identifier,
	}).Info("approvals.manager: approved")

	return existing, nil
}

func (m *DefaultManager) addAuditEntry(approval *types.Approval, action string, voter string) {

	entry := &types.AuditLog{
		ID:           uuid.New().String(),
		AccountID:    voter,
		Username:     voter,
		Action:       action,
		ResourceKind: types.AuditResourceKindApproval,
		Identifier:   approval.Identifier,
	}

	entry.SetMetadata(map[string]string{
		"provider":        approval.Provider.String(),
		"approval_id":     approval.ID,
		"new_version":     approval.NewVersion,
		"current_version": approval.CurrentVersion,
		"votes_required":  strconv.Itoa(approval.VotesReceived),
		"votes_received":  strconv.Itoa(approval.VotesReceived),
	})

	_, err := m.store.CreateAuditLog(entry)
	if err != nil {
		log.WithFields(log.Fields{
			"error":  err,
			"module": "approvals",
		}).Error("failed to create audit log")
	}
}

// Reject - rejects approval (marks rejected=true), approval will not be valid even if it
// collects required votes
func (m *DefaultManager) Reject(identifier string) (*types.Approval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	existing, err := m.Get(identifier)
	if err != nil {
		return nil, err
	}

	existing.Rejected = true

	err = m.Update(existing)
	if err != nil {
		return nil, err
	}

	m.addAuditEntry(existing, types.AuditActionApprovalRejected, "")

	return existing, nil
}

// Get - get specified, not archived approval
func (m *DefaultManager) Get(identifier string) (*types.Approval, error) {

	a, err := m.store.GetApproval(&types.GetApprovalQuery{
		Identifier: identifier,
		Archived:   false,
	})
	if err != nil {
		return nil, err
	}

	// if it's archived, don't display it
	if a.Archived {
		return nil, store.ErrRecordNotFound
	}

	return a, nil
}

// List - list not archived approvals (for expiration service)
func (m *DefaultManager) List() ([]*types.Approval, error) {
	approvals, err := m.store.ListApprovals(&types.GetApprovalQuery{
		Archived: false,
	})
	return approvals, err
}

// Delete - delete specified approval
func (m *DefaultManager) Delete(approval *types.Approval) error {
	existing, err := m.store.GetApproval(&types.GetApprovalQuery{
		ID: approval.ID,
	})
	if err != nil {
		return err
	}

	m.addAuditEntry(existing, types.AuditActionDeleted, "")

	return m.store.DeleteApproval(existing)
}

func (m *DefaultManager) Archive(identifier string) error {
	existing, err := m.Get(identifier)
	if err != nil {
		return fmt.Errorf("approval not found: %s", err)
	}
	existing.Archived = true

	m.addAuditEntry(existing, types.AuditActionApprovalArchived, "")

	return m.store.UpdateApproval(existing)
}

// Create - creates new approval request and publishes to all subscribers
func (m *DefaultManager) Create(r *types.Approval) error {
	_, err := m.Get(r.Identifier)
	if err == nil {
		return ErrApprovalAlreadyExists
	}

	r.CreatedAt = time.Now()
	r.UpdatedAt = time.Now()

	created, err := m.store.CreateApproval(r)
	if err != nil {
		return fmt.Errorf("failed to create approval: %s", err)
	}

	return m.publishRequest(created)
}

func getKey(identifier string) string {
	return ApprovalsPrefix + "/" + identifier
}
<|endoftext|>


<commit_before>package main

import (
	iniconf "code.google.com/p/goconf/conf"
	"encoding/json"
	"fmt"
	// "github.com/gamelost/bot3server/server"
	nsq "github.com/gamelost/go-nsq"
	// irc "github.com/gamelost/goirc/client"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

const (
	GLCD_CONFIG = "glcd.config"
)

type Message map[string]interface{}

func main() {
	// the quit channel
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	// read in necessary configuration
	configFile, err := iniconf.ReadConfigFile(GLCD_CONFIG)
	if err != nil {
		log.Fatal("Unable to read configuration file. Exiting now.")
	}

	glcd := &GLCD{}
	glcd.QuitChan = sigChan
	glcd.init(configFile)

	// receiving quit shuts down
	<-sigChan
}

type GLCClient struct {
	Name      string
	Topic     string
	Writer    *nsq.Writer
	Heartbeat time.Time
	Clientid  string
	State     *Message // json representation of player state.
}

// struct type for Bot3
type GLCD struct {
	Online     bool
	ConfigFile *iniconf.ConfigFile

	// NSQ input/output to bot3Server
	GLCInput *nsq.Reader
	Clients  map[string]*GLCClient

	QuitChan chan os.Signal

	MongoSession *mgo.Session
	MongoDB      *mgo.Database
}

func (glcd *GLCD) init(conf *iniconf.ConfigFile) error {

	glcd.ConfigFile = conf
	glcd.Online = false

	glcd.Clients = map[string]*GLCClient{}

	// Connect to Mongo.
	servers, err := glcd.ConfigFile.GetString("mongo", "servers")

	if err != nil {
		return fmt.Errorf("Mongo: No server configured.")
	}

	glcd.MongoSession, err = mgo.Dial(servers)

	if err != nil {
	}

	db, err := glcd.ConfigFile.GetString("mongo", "db")

	if err != nil {
		return fmt.Errorf("Mongo: No database configured.")
	}

	glcd.MongoDB = glcd.MongoSession.DB(db)

	lookupdAddress, _ := conf.GetString("nsq", "lookupd-address")
	nsqdAddress, _ := conf.GetString("nsq", "nsqd-address")
	serverTopic, _ := conf.GetString("nsq", "server-topic")
	serverChannel, _ := conf.GetString("nsq", "server-channel")

	// Create the channel, by connecting to lookupd. (TODO; if it doesn't
	// exist. Also do it the right way with a Register command?)
	writer := nsq.NewWriter(nsqdAddress)
	writer.Publish(serverTopic, []byte("{\"client\":\"server\"}"))

	// set up listener for heartbeat from bot3server
	reader, err := nsq.NewReader(serverTopic, serverChannel)
	if err != nil {
		panic(err)
		glcd.QuitChan <- syscall.SIGINT
	}
	glcd.GLCInput = reader

	glcd.GLCInput.AddHandler(glcd)
	glcd.GLCInput.ConnectToLookupd(lookupdAddress)

	// Spawn goroutine to clear out clients who don't send hearbeats
	// anymore.
	go glcd.CleanupClients()

	return nil
}

func (cl *GLCClient) Publish(msg *Message) {
	if cl.Writer == nil {
		args := strings.SplitN(cl.Clientid, ":", 3)
		if len(args) != 3 {
			return
		}
		host, port, topic := args[0], args[1], args[2]
		// log.Printf("cl.publish: Attempting to connect to '" + host + ":" + port + "'")
		cl.Writer = nsq.NewWriter(host + ":" + port)
		cl.Topic = topic
	}
	encodedRequest, _ := json.Marshal(*msg)
	cl.Writer.Publish(cl.Topic, encodedRequest)
}

func (cl *GLCClient) SendCommand(command string, data *Message) {
	msg := Message{}
	msg["command"] = command
	msg["data"] = data
	cl.Publish(&msg)
}

func (cl *GLCClient) SendMessage(text string) {
	cl.SendCommand("message", &Message{"data": text})
}

func (glcd *GLCD) SendCommandAll(command string, data *Message) {
	msg := Message{}
	msg["command"] = command
	msg["data"] = data
	for _, v := range glcd.Clients {
		v.Publish(&msg)
	}
}

func (glcd *GLCD) CleanupClients() error {
	for {
		exp := time.Now().Unix()
		<-time.After(time.Second * 10)
		// Expire any clients who haven't sent a heartbeat in the last 10 seconds.
		for k, v := range glcd.Clients {
			if v.Heartbeat.Unix() < exp {
				delete(glcd.Clients, k)
				glcd.SendCommandAll("playerGone", &Message{"client": k})
			}
		}
	}
}

// Send a zone file update.
func (glcd *GLCD) SendZones(cl *GLCClient) {
	c := glcd.MongoDB.C("zones")
	q := c.Find(nil)

	if q == nil {
		cl.SendMessage(fmt.Sprintf("No zones found"))
	} else {
		var results []interface{}
		err := q.All(&results)
		if err == nil {
			for _, res := range results {
				cl.SendCommand("updateZone", &Message{"data": res})
			}
		} else {
			cl.SendMessage(fmt.Sprintf("Unable to fetch zones: %v", err))
		}
	}
}

func (glcd *GLCD) SendZone(cl *GLCClient, zone string) {
	c := glcd.MongoDB.C("zones")
	query := bson.M{"zone": zone}
	results := c.Find(query)

	if results == nil {
		cl.SendMessage(fmt.Sprintf("No such zone '%s'", zone))
	} else {
		var res interface{}
		err := results.One(&res)
		if err == nil {
			cl.SendCommand("updateZone", &Message{"data": res})
		} else {
			cl.SendMessage(fmt.Sprintf("Unable to fetch zone: %v", err))
		}
	}
}

// Send a zone file update.
func (glcd *GLCD) UpdateZone(cl *GLCClient, zone string, zdata interface{}) {
	query := bson.M{"zone": zone}

	c := glcd.MongoDB.C("zones")
	val := bson.M{"type": "zone", "zdata": zdata, "timestamp": time.Now()}
	change := bson.M{"$set": val}

	err := c.Update(query, change)

	if err == mgo.ErrNotFound {
		val["id"], _ = c.Count()
		change = bson.M{"$set": val}
		err = c.Update(query, change)
	}

	if err != nil {
		cl.SendMessage(fmt.Sprintf("Unable to update zone: %v", err))
	} else {
		cl.SendMessage(fmt.Sprintf("Updated zone '%s'", zone))
	}
}

func (glcd *GLCD) HandleMessage(message *nsq.Message) error {
	msg := Message{}

	err := json.Unmarshal(message.Body, &msg)

	if err != nil {
		return fmt.Errorf("Not a JSON interface")
	}

	// If "client" is not in the JSON received, dump it.
	cdata, ok := msg["client"]
	if !ok {
		return fmt.Errorf("No client provided")
	}

	// If "client" from JSON is
	clientid, ok := cdata.(string)
	if !ok {
		return fmt.Errorf("Invalid format: client is not a string.")
	}

	// Ignore our silly "create a topic" message.
	if clientid == "server" {
		return nil
	}

	// Make sure client exists in glcd.Clients
	cl, exists := glcd.Clients[clientid]
	if !exists {
		cl = &GLCClient{}
		cl.Clientid = clientid
		//glcd.Clients[clientid] = cl
	}
	cl.Heartbeat = time.Now()

	// Now perform the client's action.
	cmddata, ok := msg["command"]
	if !ok {
		// Lacking a command is okay - It's a heartbeat.
		return nil
	}

	// If "client" from JSON is
	command, ok := cmddata.(string)
	if !ok {
		return fmt.Errorf("Invalid format: command is not a string.")
	}
	// log.Printf("We got a command: ", command)

	switch command {
	case "ping":
		cl.Publish(&Message{"pong": fmt.Sprint(time.Now())})
		break
	case "playerState":
		_, ok := msg["data"]
		if ok {
			cl.State = &msg
			glcd.SendCommandAll("playerState", &msg)
		}
		break
	case "updateZone":
		args, ok := msg["data"]
		if !ok {
			break
		}
		margs, ok := args.(Message)
		if !ok {
			break
		}
		zonei, ok := margs["zone"]
		if !ok {
			break
		}
		zone, ok := zonei.(string)
		if !ok {
			break
		}

		zdata, ok := margs["data"]
		if !ok {
			break
		}

		glcd.UpdateZone(cl, zone, zdata)
	case "sendZone":
		args, ok := msg["data"]
		if !ok {
			log.Printf("No data in sendZone")
			break
		}
		log.Println("data:")
		log.Println(args)
		margs, ok := args.(map[string]interface{})
		if !ok {
			log.Printf("args is not a message in sendZone")
			break
		}
		zonei, ok := margs["zone"]
		if !ok {
			log.Printf("No zone in sendZone")
			break
		}
		zone, ok := zonei.(string)
		if !ok {
			log.Printf("Zone is not a string in sendZone")
			break
		}

		glcd.SendZone(cl, zone)
		break
	case "connected":
		// Send all Zones
		glcd.SendZones(cl)
		// Send all player states.
		for _, v := range glcd.Clients {
			cl.SendCommand("playerState", v.State)
		}
		break
	case "wall":
		for _, v := range glcd.Clients {
			v.Publish(&msg)
		}
	}
	return nil
}
<commit_msg>Rearchitecting glcd.<commit_after>package main

import (
	iniconf "code.google.com/p/goconf/conf"
	"encoding/json"
	"fmt"
	// "github.com/gamelost/bot3server/server"
	nsq "github.com/gamelost/go-nsq"
	// irc "github.com/gamelost/goirc/client"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

const (
	GLCD_CONFIG = "glcd.config"
)

var gamestateTopic = ""

type Message map[string]interface{}

func main() {
	// the quit channel
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	// read in necessary configuration
	configFile, err := iniconf.ReadConfigFile(GLCD_CONFIG)
	if err != nil {
		log.Fatal("Unable to read configuration file. Exiting now.")
	}

	glcd := &GLCD{}
	glcd.QuitChan = sigChan
	glcd.init(configFile)

	// receiving quit shuts down
	<-sigChan
}

type GLCClient struct {
	Name      string
	Heartbeat time.Time
	Clientid  string
	State     *Message // json representation of player state.
}

// struct type for Bot3
type GLCD struct {
	Online     bool
	ConfigFile *iniconf.ConfigFile

	// NSQ input/output
	Writer    *nsq.Writer
	GLCInput *nsq.Reader
	Clients  map[string]*GLCClient

	QuitChan chan os.Signal

	MongoSession *mgo.Session
	MongoDB      *mgo.Database
}

func (glcd *GLCD) init(conf *iniconf.ConfigFile) error {

	glcd.ConfigFile = conf
	glcd.Online = false

	glcd.Clients = map[string]*GLCClient{}

	// Connect to Mongo.
	servers, err := glcd.ConfigFile.GetString("mongo", "servers")

	if err != nil {
		return fmt.Errorf("Mongo: No server configured.")
	}

	glcd.MongoSession, err = mgo.Dial(servers)

	if err != nil {
	}

	db, err := glcd.ConfigFile.GetString("mongo", "db")

	if err != nil {
		return fmt.Errorf("Mongo: No database configured.")
	}

	glcd.MongoDB = glcd.MongoSession.DB(db)

	lookupdAddress, _ := conf.GetString("nsq", "lookupd-address")
	nsqdAddress, _ := conf.GetString("nsq", "nsqd-address")
	gamestateTopic, _ = conf.GetString("nsq", "server-topic")
	//gamestateChannel, _ := conf.GetString("nsq", "server-channel")
	glcdTopic, _ := conf.GetString("nsq", "glcd-topic")

	// Create the channel, by connecting to lookupd. (TODO; if it doesn't
	// exist. Also do it the right way with a Register command?)
	glcd.Writer = nsq.NewWriter(nsqdAddress)
	glcd.Writer.Publish(gamestateTopic, []byte("{\"client\":\"server\"}"))

	// set up listener for heartbeat from bot3server
	reader, err := nsq.NewReader(glcdTopic, "main")
	if err != nil {
		glcd.QuitChan <- syscall.SIGINT
	}
	glcd.GLCInput = reader
	glcd.GLCInput.AddHandler(glcd)
	glcd.GLCInput.ConnectToLookupd(lookupdAddress)

	// Spawn goroutine to clear out clients who don't send hearbeats
	// anymore.
	go glcd.CleanupClients()

	return nil
}

func (glcd *GLCD) Publish(msg *Message) {
	encodedRequest, _ := json.Marshal(*msg)
	glcd.Writer.Publish(gamestateTopic, encodedRequest)
}

func (glcd *GLCD) CleanupClients() error {
	for {
		exp := time.Now().Unix()
		<-time.After(time.Second * 10)
		// Expire any clients who haven't sent a heartbeat in the last 10 seconds.
		for k, v := range glcd.Clients {
			if v.Heartbeat.Unix() < exp {
				delete(glcd.Clients, k)
				glcd.Publish(&Message{"playerGone": k})
			}
		}
	}
}

// Send a zone file update.
func (glcd *GLCD) SendZones(cl *GLCClient) {
	c := glcd.MongoDB.C("zones")
	q := c.Find(nil)

	if q == nil {
		glcd.Publish(&Message{"error": fmt.Sprintf("No zones found")})
	} else {
		var results []interface{}
		err := q.All(&results)
		if err == nil {
			for _, res := range results {
				glcd.Publish(&Message{"updateZone": res})
			}
		} else {
			glcd.Publish(&Message{"error": fmt.Sprintf("Unable to fetch zones: %v", err)})
		}
	}
}

func (glcd *GLCD) SendZone(cl *GLCClient, zone string) {
	c := glcd.MongoDB.C("zones")
	query := bson.M{"zone": zone}
	results := c.Find(query)

	if results == nil {
		glcd.Publish(&Message{"error": fmt.Sprintf("No such zone '%s'", zone)})
	} else {
		var res interface{}
		err := results.One(&res)
		if err == nil {
			glcd.Publish(&Message{"updateZone": res})
		} else {
			glcd.Publish(&Message{"error": fmt.Sprintf("Unable to fetch zone: %v", err)})
		}
	}
}

// Send a zone file update.
func (glcd *GLCD) UpdateZone(cl *GLCClient, zone string, zdata interface{}) {
	query := bson.M{"zone": zone}

	c := glcd.MongoDB.C("zones")
	val := bson.M{"type": "zone", "zdata": zdata, "timestamp": time.Now()}
	change := bson.M{"$set": val}

	err := c.Update(query, change)

	if err == mgo.ErrNotFound {
		val["id"], _ = c.Count()
		change = bson.M{"$set": val}
		err = c.Update(query, change)
	}

	if err != nil {
		glcd.Publish(&Message{"error": fmt.Sprintf("Unable to update zone: %v", err)})
	} else {
		glcd.Publish(&Message{"error": fmt.Sprintf("Updated zone '%s'", zone)})
	}
}

func (glcd *GLCD) HandleMessage(message *nsq.Message) error {

	fmt.Println("In handlemessage")

	msg := Message{}

	err := json.Unmarshal(message.Body, &msg)

	if err != nil {
		return fmt.Errorf("Not a JSON interface")
	}

	// If "client" is not in the JSON received, dump it.
	cdata, ok := msg["client"]
	if !ok {
		fmt.Printf("No client provided")
		return nil
	}

	// If "client" from JSON is
	clientid, ok := cdata.(string)
	if !ok {
		fmt.Printf("Invalid format: client is not a string.")
		return nil
	}

	// Ignore our silly "create a topic" message.
	if clientid == "server" {
		return nil
	}

	// Make sure client exists in glcd.Clients
	cl, exists := glcd.Clients[clientid]
	if !exists {
		cl = &GLCClient{}
		cl.Clientid = clientid
		glcd.Clients[clientid] = cl
	}
	cl.Heartbeat = time.Now()

	// Now perform the client's action.
	cmddata, ok := msg["command"]
	if !ok {
		// Lacking a command is okay - It's a heartbeat.
		return nil
	}

	// If "client" from JSON is
	command, ok := cmddata.(string)
	if !ok {
		return fmt.Errorf("Invalid format: command is not a string.")
	}
	// log.Printf("We got a command: ", command)

	switch command {
	case "ping":
		//cl.Publish(&Message{"pong": fmt.Sprint(time.Now())})
		break
	case "playerState":
		_, ok := msg["data"]
		if ok {
			cl.State = &msg
			glcd.Publish(&Message{"playerState": &msg})
		}
		break
	case "updateZone":
		args, ok := msg["data"]
		if !ok {
			break
		}
		margs, ok := args.(Message)
		if !ok {
			break
		}
		zonei, ok := margs["zone"]
		if !ok {
			break
		}
		zone, ok := zonei.(string)
		if !ok {
			break
		}

		zdata, ok := margs["data"]
		if !ok {
			break
		}

		glcd.UpdateZone(cl, zone, zdata)
	case "sendZone":
		args, ok := msg["data"]
		if !ok {
			log.Printf("No data in sendZone")
			break
		}
		log.Println("data:")
		log.Println(args)
		margs, ok := args.(map[string]interface{})
		if !ok {
			log.Printf("args is not a message in sendZone")
			break
		}
		zonei, ok := margs["zone"]
		if !ok {
			log.Printf("No zone in sendZone")
			break
		}
		zone, ok := zonei.(string)
		if !ok {
			log.Printf("Zone is not a string in sendZone")
			break
		}

		glcd.SendZone(cl, zone)
		break
	case "connected":
		// Send all Zones
		glcd.SendZones(cl)
		// Send all player states.
		for _, v := range glcd.Clients {
			glcd.Publish(&Message{"playerState": v.State})
		}
		break
	case "wall":
		glcd.Publish(&msg)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/microcosm-cc/bluemonday"
	"github.com/russross/blackfriday"
	"io/ioutil"
	"log"
	"os"
)

var input_file string

func exist(name string) bool {

	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

func parse() string {

	var (
		input    []byte
		out      *os.File
		err      error
		out_file string = "output.html"
	)
	// check out file exist
	if !exist(input_file) {
		log.Fatal("File is not exist")
		os.Exit(-1)
	}
	// open markdown file
	if input, err = ioutil.ReadFile(input_file); err != nil {
		log.Fatal("Error of reading markdown file")
		os.Exit(-1)
	}

	// transform markdown to the html
	unsafe := blackfriday.MarkdownCommon(input)
	html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)

	// create html file
	if out, err = os.Create(out_file); err != nil {
		log.Fatal("Error of creating html")
		os.Exit(-1)
	}

	defer func() {
		if err = out.Close(); err != nil {
			log.Fatal("Can't close the file")
			os.Exit(-1)
		}
	}()

	if _, err = out.Write(html); err != nil {
		log.Fatal("Error of writing html")
		os.Exit(-1)
	}

	return out_file
}

func command() (string, string) {
	fmt.Println("Usage")
	usage := "\tExecute : ./glue [inputfile] [port]\n\tHelp    : ./glue"

	if len(os.Args) <= 1 {
		fmt.Println(usage)
		os.Exit(-1)
	} else if len(os.Args) <= 2 && len(os.Args) > 1 {
		return string(os.Args[1]), "9090"
	}

	return string(os.Args[1]), string(os.Args[2])
}

func main() {
	var port string
	input_file, port = command()

	server(":" + port)
}
<commit_msg>fix check file err<commit_after>package main

import (
	"fmt"
	"github.com/microcosm-cc/bluemonday"
	"github.com/russross/blackfriday"
	"io/ioutil"
	"log"
	"os"
)

var input_file string

func exist(name string) bool {

	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

func parse() string {

	var (
		input    []byte
		out      *os.File
		err      error
		out_file string = "output.html"
	)

	// open markdown file
	if input, err = ioutil.ReadFile(input_file); err != nil {
		log.Fatal("Error of reading markdown file")
		os.Exit(-1)
	}

	// transform markdown to the html
	unsafe := blackfriday.MarkdownCommon(input)
	html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)

	// create html file
	if out, err = os.Create(out_file); err != nil {
		log.Fatal("Error of creating html")
		os.Exit(-1)
	}

	defer func() {
		if err = out.Close(); err != nil {
			log.Fatal("Can't close the file")
			os.Exit(-1)
		}
	}()

	if _, err = out.Write(html); err != nil {
		log.Fatal("Error of writing html")
		os.Exit(-1)
	}

	return out_file
}

func command() (string, string) {
	usage := "\tExecute : ./glue [inputfile] [port]\n\tHelp    : ./glue"

	if len(os.Args) <= 1 {
		fmt.Println("Usage")
		fmt.Println(usage)
		os.Exit(-1)
	} else if len(os.Args) <= 2 && len(os.Args) > 1 {
		return string(os.Args[1]), "9090"
	}

	return string(os.Args[1]), string(os.Args[2])
}

func main() {
	var port string
	input_file, port = command()

	// check out file exist
	if !exist(input_file) {
		log.Fatal("File is not exist")
		os.Exit(-1)
	}

	server(":" + port)
}
<|endoftext|>


<commit_before>package main

//go:generate go-bindata -o glue/generated_lua.go -pkg=glue -prefix "./lua" ./lua

import (
	"./glue"
	"fmt"
	//"github.com/gin-gonic/contrib/gzip"
	"github.com/gin-gonic/contrib/gzip"
	"github.com/gin-gonic/contrib/static"
	"github.com/gin-gonic/gin"
	"github.com/pmylund/go-cache"
	lua "github.com/vifino/golua/lua"
	luar "github.com/vifino/luar"
	"io/ioutil"
	//"net/http"
	//"runtime"
	"./scheduler"
	"errors"
	"flag"
	"log"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"
)

// Preloader/Starter
var jobs *int
var preloaded chan *lua.State

func preloader() {
	preloaded = make(chan *lua.State, *jobs)
	for {
		state := luar.Init()
		err := state.DoString(glue.Glue())
		if err != nil {
			fmt.Println(err)
		}
		preloaded <- state
	}
}
func getInstance() *lua.State {
	return <-preloaded
}

// Cache
var cfe *cache.Cache
var cbc *cache.Cache

func cacheRead(c *cache.Cache, file string) (string, error) {
	res := ""
	data_tmp, found := c.Get(file)
	if found == false {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			return "", err
		}
		res = string(data)
		c.Set(file, res, cache.DefaultExpiration)
	} else {
		log.Printf("Using cache for %s", file)
		res = data_tmp.(string)
	}
	return res, nil
}
func cacheDump(L *lua.State, file string) (string, error, bool) {
	data_tmp, found := cbc.Get(file)
	if found == false {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			return "", err, false
		}
		if L.LoadString(string(data)) != 0 {
			return "", errors.New(L.ToString(-1)), true
		}
		res := L.FDump()
		L.Pop(1)
		cbc.Set(file, res, cache.DefaultExpiration)
		return res, nil, false
	} else {
		log.Printf("Using Bytecode-cache for %s", file)
		return data_tmp.(string), nil, false
	}
}
func cacheFileExists(file string) bool {
	data_tmp, found := cfe.Get(file)
	if found == false {
		exists := fileExists(file)
		cfe.Set(file, exists, cache.DefaultExpiration)
		return exists
	} else {
		return data_tmp.(bool)
	}
}

// FS Helper
func fileExists(file string) bool {
	if _, err := os.Stat(file); os.IsNotExist(err) {
		return false
	}
	return true
}

// Server

func new_server() *gin.Engine {
	r := gin.New()
	r.Use(gin.Logger())
	r.Use(gin.Recovery())
	return r
}

// Routes
func logic_switcher(dir string) func(*gin.Context) {
	st := static.Serve("/", static.LocalFile(dir, false))
	lr := luaroute(dir)
	return func(context *gin.Context) {
		file := context.Params.ByName("file")
		fe := cacheFileExists(file)
		if fe == true {
			if strings.HasSuffix(file, ".lua") {
				lr(context)
			} else {
				st(context)
			}
		} else {
			context.String(404, "404 page not found")
		}
	}
}

func luaroute(dir string) func(*gin.Context) {
	LDumper := luar.Init()
	return func(context *gin.Context) {
		L := getInstance()
		file := dir + context.Request.URL.Path
		luar.Register(L, "", luar.Map{
			"context": context,
			"req":     context.Request,
		})
		code, err, lerr := cacheDump(LDumper, file)
		if err != nil {
			if lerr == false {
				context.String(404, "404 page not found")
				context.Abort()
			} else {
				context.HTMLString(http.StatusInternalServerError, `
				<title>Syntax Error in `+context.Request.URL.Path+`</title>

Syntax Error in file `+context.Request.URL.Path+`

					`+string(err.Error())+`

				`)
				context.Abort()
			}
		}
		L.LoadBuffer(code, len(code), file)
		if L.Pcall(0, 0, 0) != 0 {
			context.HTMLString(http.StatusInternalServerError, `
			<title>Runtime Error in `+context.Request.URL.Path+`</title>

Runtime Error in file `+context.Request.URL.Path+`

				`+L.ToString(-1)+`

			`)
			context.Abort()
		}
		/*L.DoString("return CONTENT_TO_RETURN")
		v := luar.CopyTableToMap(L, nil, -1)
		m := v.(map[string]interface{})
		i := int(m["code"].(float64))
		if err != nil {
			i = http.StatusOK
		}*/
		scheduler.Add(func() {
			L.Close()
		})
		//context.HTMLString(i, m["content"].(string))
	}
}

func run(host string, port int, dir string, useGzip bool) {
	go preloader()     // Run the instance starter.
	go scheduler.Run() // Run the scheduler.
	srv := new_server()
	if useGzip == true {
		srv.Use(gzip.Gzip(gzip.DefaultCompression))
	}
	srv.GET(`/:file`, logic_switcher(dir))

	//srv.Use(martini.Static(dir))
	srv.Run(host + ":" + strconv.Itoa(port))
}

func main() {
	cbc = cache.New(5*time.Minute, 30*time.Second) // Initialize cache with 5 minute lifetime and purge every 30 seconds
	cfe = cache.New(5*time.Minute, 30*time.Second) // File-Exists Cache

	var host = flag.String("host", "", "IP of host to run webserver on")
	var port = flag.Int("port", 8080, "Port to run webserver on")
	jobs = flag.Int("states", 16, "Number of Preinitialized Lua States")
	var workers = flag.Int("workers", runtime.NumCPU(), "Number of Worker threads.")
	var webroot = flag.String("root", ".", "Path to web root")

	useGzip := flag.Bool("gzip", false, "Use GZIP")

	flag.Parse()

	runtime.GOMAXPROCS(*workers)

	run(*host, *port, *webroot, *useGzip)
}
<commit_msg>More flags<commit_after>package main

//go:generate go-bindata -o glue/generated_lua.go -pkg=glue -prefix "./lua" ./lua

import (
	"./glue"
	"fmt"
	//"github.com/gin-gonic/contrib/gzip"
	"github.com/gin-gonic/contrib/gzip"
	"github.com/gin-gonic/contrib/static"
	"github.com/gin-gonic/gin"
	"github.com/pmylund/go-cache"
	lua "github.com/vifino/golua/lua"
	luar "github.com/vifino/luar"
	"io/ioutil"
	//"net/http"
	//"runtime"
	"./scheduler"
	"errors"
	"flag"
	"log"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"
)

// Preloader/Starter
var jobs *int
var preloaded chan *lua.State

func preloader() {
	preloaded = make(chan *lua.State, *jobs)
	for {
		state := luar.Init()
		err := state.DoString(glue.Glue())
		if err != nil {
			fmt.Println(err)
		}
		preloaded <- state
	}
}
func getInstance() *lua.State {
	return <-preloaded
}

// Cache
var cfe *cache.Cache
var cbc *cache.Cache

func cacheRead(c *cache.Cache, file string) (string, error) {
	res := ""
	data_tmp, found := c.Get(file)
	if found == false {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			return "", err
		}
		res = string(data)
		c.Set(file, res, cache.DefaultExpiration)
	} else {
		log.Printf("Using cache for %s", file)
		res = data_tmp.(string)
	}
	return res, nil
}
func cacheDump(L *lua.State, file string) (string, error, bool) {
	data_tmp, found := cbc.Get(file)
	if found == false {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			return "", err, false
		}
		if L.LoadString(string(data)) != 0 {
			return "", errors.New(L.ToString(-1)), true
		}
		res := L.FDump()
		L.Pop(1)
		cbc.Set(file, res, cache.DefaultExpiration)
		return res, nil, false
	} else {
		log.Printf("Using Bytecode-cache for %s", file)
		return data_tmp.(string), nil, false
	}
}
func cacheFileExists(file string) bool {
	data_tmp, found := cfe.Get(file)
	if found == false {
		exists := fileExists(file)
		cfe.Set(file, exists, cache.DefaultExpiration)
		return exists
	} else {
		return data_tmp.(bool)
	}
}

// FS Helper
func fileExists(file string) bool {
	if _, err := os.Stat(file); os.IsNotExist(err) {
		return false
	}
	return true
}

// Server

func new_server() *gin.Engine {
	r := gin.New()
	return r
}
func bootstrap(srv *gin.Engine, dir string) *gin.Engine {
	go preloader()     // Run the instance starter.
	go scheduler.Run() // Run the scheduler.
	srv.GET(`/:file`, logic_switcher(dir))
	//srv.Use(martini.Static(dir))
	return srv
}

// Routes
func logic_switcher(dir string) func(*gin.Context) {
	st := static.Serve("/", static.LocalFile(dir, false))
	lr := luaroute(dir)
	return func(context *gin.Context) {
		file := context.Params.ByName("file")
		fe := cacheFileExists(file)
		if fe == true {
			if strings.HasSuffix(file, ".lua") {
				lr(context)
			} else {
				st(context)
			}
		} else {
			context.String(404, "404 page not found")
		}
	}
}

func luaroute(dir string) func(*gin.Context) {
	LDumper := luar.Init()
	return func(context *gin.Context) {
		L := getInstance()
		file := dir + context.Request.URL.Path
		luar.Register(L, "", luar.Map{
			"context": context,
			"req":     context.Request,
		})
		code, err, lerr := cacheDump(LDumper, file)
		if err != nil {
			if lerr == false {
				context.String(404, "404 page not found")
				context.Abort()
			} else {
				context.HTMLString(http.StatusInternalServerError, `
				<title>Syntax Error in `+context.Request.URL.Path+`</title>

Syntax Error in file `+context.Request.URL.Path+`

					`+string(err.Error())+`

				`)
				context.Abort()
			}
		}
		L.LoadBuffer(code, len(code), file)
		if L.Pcall(0, 0, 0) != 0 {
			context.HTMLString(http.StatusInternalServerError, `
			<title>Runtime Error in `+context.Request.URL.Path+`</title>

Runtime Error in file `+context.Request.URL.Path+`

				`+L.ToString(-1)+`

			`)
			context.Abort()
		}
		/*L.DoString("return CONTENT_TO_RETURN")
		v := luar.CopyTableToMap(L, nil, -1)
		m := v.(map[string]interface{})
		i := int(m["code"].(float64))
		if err != nil {
			i = http.StatusOK
		}*/
		scheduler.Add(func() {
			L.Close()
		})
		//context.HTMLString(i, m["content"].(string))
	}
}

func main() {
	cbc = cache.New(5*time.Minute, 30*time.Second) // Initialize cache with 5 minute lifetime and purge every 30 seconds
	cfe = cache.New(5*time.Minute, 30*time.Second) // File-Exists Cache

	var host = flag.String("host", "", "IP of host to run webserver on")
	var port = flag.Int("port", 8080, "Port to run webserver on")
	jobs = flag.Int("states", 16, "Number of Preinitialized Lua States")
	var workers = flag.Int("workers", runtime.NumCPU(), "Number of Worker threads.")
	var webroot = flag.String("root", ".", "Path to web root")

	// Middleware options
	useRecovery := flag.Bool("recovery", false, "Recover from panics")
	useLogger := flag.Bool("logger", true, "Log requests")
	useGzip := flag.Bool("gzip", false, "Use GZIP")

	flag.Parse()

	runtime.GOMAXPROCS(*workers)

	srv := new_server()
	if *useLogger {
		srv.Use(gin.Logger())
	}
	if *useRecovery {
		srv.Use(gin.Recovery())
	}
	if *useGzip {
		srv.Use(gzip.Gzip(gzip.DefaultCompression))
	}
	bootstrap(srv, *webroot)

	srv.Run(*host + ":" + strconv.Itoa(*port))
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Matt Jibson <matt.jibson@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package goon

import (
	"appengine"
	"appengine/datastore"
	"appengine/memcache"
	"errors"
	"fmt"
	"net/http"
	"reflect"
)

var (
	// LogErrors issues appengine.Context.Errorf on any error.
	LogErrors bool = true
)

// Goon holds the app engine context and request memory cache.
type Goon struct {
	context       appengine.Context
	cache         map[string]interface{}
	inTransaction bool
	toSet         map[string]interface{}
	toDelete      []string
}

func memkey(k *datastore.Key) string {
	return k.Encode()
}

// NewGoon creates a new Goon object from the given request.
func NewGoon(r *http.Request) *Goon {
	return FromContext(appengine.NewContext(r))
}

// FromContext creates a new Goon object from the given appengine Context.
func FromContext(c appengine.Context) *Goon {
	return &Goon{
		context: c,
		cache:   make(map[string]interface{}),
	}
}

func (g *Goon) error(err error) {
	if LogErrors {
		g.context.Errorf("goon: %v", err.Error())
	}
}

func (g *Goon) extractKeys(src interface{}) ([]*datastore.Key, error) {
	v := reflect.Indirect(reflect.ValueOf(src))
	if v.Kind() != reflect.Slice {
		return nil, errors.New("goon: value must be a slice or pointer-to-slice")
	}
	l := v.Len()

	keys := make([]*datastore.Key, l)
	for i := 0; i < l; i++ {
		vi := v.Index(i)
		key, err := g.getStructKey(vi.Interface())
		if err != nil {
			return nil, err
		}
		keys[i] = key
	}
	return keys, nil
}

// Key is the same as KeyError, except nil is returned on error.
func (g *Goon) Key(src interface{}) *datastore.Key {
	if k, err := g.KeyError(src); err == nil {
		return k
	}
	return nil
}

// Key returns the key of src based on its properties.
func (g *Goon) KeyError(src interface{}) (*datastore.Key, error) {
	return g.getStructKey(src)
}

// RunInTransaction runs f in a transaction. It calls f with a transaction
// context tg that f should use for all App Engine operations. Neither cache nor
// memcache are used or set during a transaction.
//
// Otherwise similar to appengine/datastore.RunInTransaction:
// https://developers.google.com/appengine/docs/go/datastore/reference#RunInTransaction
func (g *Goon) RunInTransaction(f func(tg *Goon) error, opts *datastore.TransactionOptions) error {
	var ng *Goon
	err := datastore.RunInTransaction(g.context, func(tc appengine.Context) error {
		ng = &Goon{
			context:       tc,
			inTransaction: true,
			toSet:         make(map[string]interface{}),
		}
		return f(ng)
	}, opts)

	if err == nil {
		for k, v := range ng.toSet {
			g.cache[k] = v
		}

		for _, k := range ng.toDelete {
			delete(g.cache, k)
		}
	} else {
		g.error(err)
	}

	return err
}

// Put saves the entity src into the datastore based on src's key. If k is an
// incomplete key, the returned key will be a unique key generated by the
// datastore.
func (g *Goon) Put(src interface{}) error {
	return g.PutMulti([]interface{}{src})
}

// PutMany is a wrapper around PutMulti.
func (g *Goon) PutMany(srcs ...interface{}) error {
	return g.PutMulti(srcs)
}

const putMultiLimit = 500

// PutMulti is a batch version of Put.
//
// src must satisfy the same conditions as the dst argument to GetMulti.
func (g *Goon) PutMulti(src interface{}) error {
	keys, err := g.extractKeys(src)
	if err != nil {
		return err
	}

	var memkeys []string
	for _, key := range keys {
		if !key.Incomplete() {
			memkeys = append(memkeys, memkey(key))
		}
	}

	// Memcache needs to be updated after the datastore to prevent a common race condition
	defer memcache.DeleteMulti(g.context, memkeys)

	v := reflect.Indirect(reflect.ValueOf(src))
	for i := 0; i <= len(keys)/putMultiLimit; i++ {
		lo := i * putMultiLimit
		hi := (i + 1) * putMultiLimit
		if hi > len(keys) {
			hi = len(keys)
		}
		rkeys, err := datastore.PutMulti(g.context, keys[lo:hi], v.Slice(lo, hi).Interface())
		if err != nil {
			g.error(err)
			return err
		}

		for i, key := range keys[lo:hi] {
			vi := v.Index(lo + i).Interface()
			if key.Incomplete() {
				setStructKey(vi, rkeys[i])
			}
			if g.inTransaction {
				g.toSet[memkey(rkeys[i])] = vi
			}
		}
	}

	if !g.inTransaction {
		g.putMemoryMulti(src)
	}

	return nil
}

func (g *Goon) putMemoryMulti(src interface{}) {
	v := reflect.Indirect(reflect.ValueOf(src))
	for i := 0; i < v.Len(); i++ {
		g.putMemory(v.Index(i).Interface())
	}
}

func (g *Goon) putMemory(src interface{}) {
	key, _ := g.getStructKey(src)
	g.cache[memkey(key)] = src
}

func (g *Goon) putMemcache(srcs []interface{}) error {
	items := make([]*memcache.Item, len(srcs))

	for i, src := range srcs {
		gob, err := toGob(src)
		if err != nil {
			g.error(err)
			return err
		}
		key, err := g.getStructKey(src)

		items[i] = &memcache.Item{
			Key:   memkey(key),
			Value: gob,
		}
	}

	memcache.SetMulti(g.context, items)
	g.putMemoryMulti(srcs)
	return nil
}

// Get loads the entity based on dst's key into dst
// If there is no such entity for the key, Get returns
// datastore.ErrNoSuchEntity.
func (g *Goon) Get(dst interface{}) error {
	dsts := []interface{}{dst}
	if err := g.GetMulti(dsts); err != nil {
		// Look for an embedded error if it's multi
		if me, ok := err.(appengine.MultiError); ok {
			for i, merr := range me {
				if i == 0 {
					return merr
				}
			}
		}
		// Not multi, normal error
		return err
	}
	return nil
}

// GetMulti is a batch version of Get.
//
// dst has similar constraints as datastore.GetMulti.
func (g *Goon) GetMulti(dst interface{}) error {
	keys, err := g.extractKeys(dst)
	if err != nil {
		return err
	}

	if g.inTransaction {
		return datastore.GetMulti(g.context, keys, dst)
	}

	var dskeys []*datastore.Key
	var dsdst []interface{}
	var dixs []int

	var memkeys []string
	var mixs []int

	v := reflect.Indirect(reflect.ValueOf(dst))
	for i, key := range keys {
		m := memkey(key)
		if s, present := g.cache[m]; present {
			vi := v.Index(i)
			vi.Set(reflect.ValueOf(s))
		} else {
			memkeys = append(memkeys, m)
			mixs = append(mixs, i)
		}
	}

	memvalues, _ := memcache.GetMulti(g.context, memkeys)

	for i, m := range memkeys {
		d := v.Index(mixs[i]).Interface()
		if s, present := memvalues[m]; present {
			err := fromGob(d, s.Value)
			if err != nil {
				g.error(err)
				return err
			}

			g.putMemory(d)
		} else {
			key, err := g.getStructKey(d)
			if err != nil {
				g.error(err)
				return err
			}
			dskeys = append(dskeys, key)
			dsdst = append(dsdst, d)
			dixs = append(dixs, mixs[i])
		}
	}

	gmerr := datastore.GetMulti(g.context, dskeys, dsdst)
	var ret error
	var multiErr appengine.MultiError
	var toCache []interface{}
	if gmerr != nil {
		merr, ok := gmerr.(appengine.MultiError)
		if !ok {
			g.error(gmerr)
			return gmerr
		}
		multiErr = make(appengine.MultiError, len(keys))
		for i, idx := range dixs {
			multiErr[idx] = merr[i]
			if merr[i] == nil {
				toCache = append(toCache, dsdst[i])
			}
		}
		ret = multiErr
	} else {
		toCache = dsdst
	}

	if len(dskeys) > 0 {
		if err := g.putMemcache(toCache); err != nil {
			g.error(err)
			return err
		}
	}

	return ret
}

// Delete deletes the entity for the given key.
func (g *Goon) Delete(key *datastore.Key) error {
	keys := []*datastore.Key{key}
	return g.DeleteMulti(keys)
}

// DeleteMulti is a batch version of Delete.
func (g *Goon) DeleteMulti(keys []*datastore.Key) error {
	memkeys := make([]string, len(keys))
	for i, k := range keys {
		mk := memkey(k)
		memkeys[i] = mk

		if g.inTransaction {
			g.toDelete = append(g.toDelete, mk)
		} else {
			delete(g.cache, mk)
		}
	}

	// Memcache needs to be updated after the datastore to prevent a common race condition
	defer memcache.DeleteMulti(g.context, memkeys)

	return datastore.DeleteMulti(g.context, keys)
}

// NotFound returns true if err is an appengine.MultiError and err[idx] is a datastore.ErrNoSuchEntity.
func NotFound(err error, idx int) bool {
	if merr, ok := err.(appengine.MultiError); ok {
		return idx < len(merr) && merr[idx] == datastore.ErrNoSuchEntity
	}
	return false
}
<commit_msg>Unused import<commit_after>/*
 * Copyright (c) 2012 Matt Jibson <matt.jibson@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package goon

import (
	"appengine"
	"appengine/datastore"
	"appengine/memcache"
	"errors"
	"net/http"
	"reflect"
)

var (
	// LogErrors issues appengine.Context.Errorf on any error.
	LogErrors bool = true
)

// Goon holds the app engine context and request memory cache.
type Goon struct {
	context       appengine.Context
	cache         map[string]interface{}
	inTransaction bool
	toSet         map[string]interface{}
	toDelete      []string
}

func memkey(k *datastore.Key) string {
	return k.Encode()
}

// NewGoon creates a new Goon object from the given request.
func NewGoon(r *http.Request) *Goon {
	return FromContext(appengine.NewContext(r))
}

// FromContext creates a new Goon object from the given appengine Context.
func FromContext(c appengine.Context) *Goon {
	return &Goon{
		context: c,
		cache:   make(map[string]interface{}),
	}
}

func (g *Goon) error(err error) {
	if LogErrors {
		g.context.Errorf("goon: %v", err.Error())
	}
}

func (g *Goon) extractKeys(src interface{}) ([]*datastore.Key, error) {
	v := reflect.Indirect(reflect.ValueOf(src))
	if v.Kind() != reflect.Slice {
		return nil, errors.New("goon: value must be a slice or pointer-to-slice")
	}
	l := v.Len()

	keys := make([]*datastore.Key, l)
	for i := 0; i < l; i++ {
		vi := v.Index(i)
		key, err := g.getStructKey(vi.Interface())
		if err != nil {
			return nil, err
		}
		keys[i] = key
	}
	return keys, nil
}

// Key is the same as KeyError, except nil is returned on error.
func (g *Goon) Key(src interface{}) *datastore.Key {
	if k, err := g.KeyError(src); err == nil {
		return k
	}
	return nil
}

// Key returns the key of src based on its properties.
func (g *Goon) KeyError(src interface{}) (*datastore.Key, error) {
	return g.getStructKey(src)
}

// RunInTransaction runs f in a transaction. It calls f with a transaction
// context tg that f should use for all App Engine operations. Neither cache nor
// memcache are used or set during a transaction.
//
// Otherwise similar to appengine/datastore.RunInTransaction:
// https://developers.google.com/appengine/docs/go/datastore/reference#RunInTransaction
func (g *Goon) RunInTransaction(f func(tg *Goon) error, opts *datastore.TransactionOptions) error {
	var ng *Goon
	err := datastore.RunInTransaction(g.context, func(tc appengine.Context) error {
		ng = &Goon{
			context:       tc,
			inTransaction: true,
			toSet:         make(map[string]interface{}),
		}
		return f(ng)
	}, opts)

	if err == nil {
		for k, v := range ng.toSet {
			g.cache[k] = v
		}

		for _, k := range ng.toDelete {
			delete(g.cache, k)
		}
	} else {
		g.error(err)
	}

	return err
}

// Put saves the entity src into the datastore based on src's key. If k is an
// incomplete key, the returned key will be a unique key generated by the
// datastore.
func (g *Goon) Put(src interface{}) error {
	return g.PutMulti([]interface{}{src})
}

// PutMany is a wrapper around PutMulti.
func (g *Goon) PutMany(srcs ...interface{}) error {
	return g.PutMulti(srcs)
}

const putMultiLimit = 500

// PutMulti is a batch version of Put.
//
// src must satisfy the same conditions as the dst argument to GetMulti.
func (g *Goon) PutMulti(src interface{}) error {
	keys, err := g.extractKeys(src)
	if err != nil {
		return err
	}

	var memkeys []string
	for _, key := range keys {
		if !key.Incomplete() {
			memkeys = append(memkeys, memkey(key))
		}
	}

	// Memcache needs to be updated after the datastore to prevent a common race condition
	defer memcache.DeleteMulti(g.context, memkeys)

	v := reflect.Indirect(reflect.ValueOf(src))
	for i := 0; i <= len(keys)/putMultiLimit; i++ {
		lo := i * putMultiLimit
		hi := (i + 1) * putMultiLimit
		if hi > len(keys) {
			hi = len(keys)
		}
		rkeys, err := datastore.PutMulti(g.context, keys[lo:hi], v.Slice(lo, hi).Interface())
		if err != nil {
			g.error(err)
			return err
		}

		for i, key := range keys[lo:hi] {
			vi := v.Index(lo + i).Interface()
			if key.Incomplete() {
				setStructKey(vi, rkeys[i])
			}
			if g.inTransaction {
				g.toSet[memkey(rkeys[i])] = vi
			}
		}
	}

	if !g.inTransaction {
		g.putMemoryMulti(src)
	}

	return nil
}

func (g *Goon) putMemoryMulti(src interface{}) {
	v := reflect.Indirect(reflect.ValueOf(src))
	for i := 0; i < v.Len(); i++ {
		g.putMemory(v.Index(i).Interface())
	}
}

func (g *Goon) putMemory(src interface{}) {
	key, _ := g.getStructKey(src)
	g.cache[memkey(key)] = src
}

func (g *Goon) putMemcache(srcs []interface{}) error {
	items := make([]*memcache.Item, len(srcs))

	for i, src := range srcs {
		gob, err := toGob(src)
		if err != nil {
			g.error(err)
			return err
		}
		key, err := g.getStructKey(src)

		items[i] = &memcache.Item{
			Key:   memkey(key),
			Value: gob,
		}
	}

	memcache.SetMulti(g.context, items)
	g.putMemoryMulti(srcs)
	return nil
}

// Get loads the entity based on dst's key into dst
// If there is no such entity for the key, Get returns
// datastore.ErrNoSuchEntity.
func (g *Goon) Get(dst interface{}) error {
	dsts := []interface{}{dst}
	if err := g.GetMulti(dsts); err != nil {
		// Look for an embedded error if it's multi
		if me, ok := err.(appengine.MultiError); ok {
			for i, merr := range me {
				if i == 0 {
					return merr
				}
			}
		}
		// Not multi, normal error
		return err
	}
	return nil
}

// GetMulti is a batch version of Get.
//
// dst has similar constraints as datastore.GetMulti.
func (g *Goon) GetMulti(dst interface{}) error {
	keys, err := g.extractKeys(dst)
	if err != nil {
		return err
	}

	if g.inTransaction {
		return datastore.GetMulti(g.context, keys, dst)
	}

	var dskeys []*datastore.Key
	var dsdst []interface{}
	var dixs []int

	var memkeys []string
	var mixs []int

	v := reflect.Indirect(reflect.ValueOf(dst))
	for i, key := range keys {
		m := memkey(key)
		if s, present := g.cache[m]; present {
			vi := v.Index(i)
			vi.Set(reflect.ValueOf(s))
		} else {
			memkeys = append(memkeys, m)
			mixs = append(mixs, i)
		}
	}

	memvalues, _ := memcache.GetMulti(g.context, memkeys)

	for i, m := range memkeys {
		d := v.Index(mixs[i]).Interface()
		if s, present := memvalues[m]; present {
			err := fromGob(d, s.Value)
			if err != nil {
				g.error(err)
				return err
			}

			g.putMemory(d)
		} else {
			key, err := g.getStructKey(d)
			if err != nil {
				g.error(err)
				return err
			}
			dskeys = append(dskeys, key)
			dsdst = append(dsdst, d)
			dixs = append(dixs, mixs[i])
		}
	}

	gmerr := datastore.GetMulti(g.context, dskeys, dsdst)
	var ret error
	var multiErr appengine.MultiError
	var toCache []interface{}
	if gmerr != nil {
		merr, ok := gmerr.(appengine.MultiError)
		if !ok {
			g.error(gmerr)
			return gmerr
		}
		multiErr = make(appengine.MultiError, len(keys))
		for i, idx := range dixs {
			multiErr[idx] = merr[i]
			if merr[i] == nil {
				toCache = append(toCache, dsdst[i])
			}
		}
		ret = multiErr
	} else {
		toCache = dsdst
	}

	if len(dskeys) > 0 {
		if err := g.putMemcache(toCache); err != nil {
			g.error(err)
			return err
		}
	}

	return ret
}

// Delete deletes the entity for the given key.
func (g *Goon) Delete(key *datastore.Key) error {
	keys := []*datastore.Key{key}
	return g.DeleteMulti(keys)
}

// DeleteMulti is a batch version of Delete.
func (g *Goon) DeleteMulti(keys []*datastore.Key) error {
	memkeys := make([]string, len(keys))
	for i, k := range keys {
		mk := memkey(k)
		memkeys[i] = mk

		if g.inTransaction {
			g.toDelete = append(g.toDelete, mk)
		} else {
			delete(g.cache, mk)
		}
	}

	// Memcache needs to be updated after the datastore to prevent a common race condition
	defer memcache.DeleteMulti(g.context, memkeys)

	return datastore.DeleteMulti(g.context, keys)
}

// NotFound returns true if err is an appengine.MultiError and err[idx] is a datastore.ErrNoSuchEntity.
func NotFound(err error, idx int) bool {
	if merr, ok := err.(appengine.MultiError); ok {
		return idx < len(merr) && merr[idx] == datastore.ErrNoSuchEntity
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Program gops is a tool to list currently running Go processes.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"

	"github.com/google/gops/signal"

	"github.com/google/gops/internal/objfile"

	ps "github.com/keybase/go-ps"
)

const helpText = `Usage: gops is a tool to list and diagnose Go processes.

    gops              Lists all Go processes currently running.
    gops [options...] See the section below.

Options:
    -stack    Prints the stack trace.
    -gc       Runs the garbage collector and blocks until successful.
    -gcstats  Prints the garbage collection stats.
    -version  Prints the Go version used to build the program.

All options require the agent and the -p=<pid> flag.
`

// TODO(jbd): add link that explains the use of agent.

var (
	pid      = flag.Int("p", -1, "")
	stack    = flag.Bool("stack", false, "")
	gc       = flag.Bool("gc", false, "")
	memstats = flag.Bool("memstats", false, "")
	version  = flag.Bool("version", false, "")
	help     = flag.Bool("help", false, "")
)

func main() {
	flag.Usage = usage
	flag.Parse()

	if len(os.Args) < 2 {
		goProcesses()
		return
	}
	if *pid == -1 || *help {
		usage()
	}
	if *stack {
		out, err := cmd(signal.Stack)
		exitIfError(err)
		fmt.Println(out)
	}
	if *gc {
		_, err := cmd(signal.GC)
		exitIfError(err)
	}
	if *memstats {
		out, err := cmd(signal.MemStats)
		exitIfError(err)
		fmt.Printf(out)
	}
	if *version {
		out, err := cmd(signal.Version)
		exitIfError(err)
		fmt.Printf(out)
	}
}

func cmd(c byte) (string, error) {
	sock := fmt.Sprintf("/tmp/gops%d.sock", *pid)
	conn, err := net.Dial("unix", sock)
	if err != nil {
		return "", err
	}
	if _, err := conn.Write([]byte{c}); err != nil {
		return "", err
	}
	all, err := ioutil.ReadAll(conn)
	if err != nil {
		return "", err
	}
	return string(all), nil
}

func goProcesses() {
	pss, err := ps.Processes()
	if err != nil {
		log.Fatal(err)
	}
	var undetermined int
	for _, pr := range pss {
		name, err := pr.Path()
		if err != nil {
			undetermined++
			continue
		}
		ok, err := isGo(name)
		if err != nil {
			// TODO(jbd): worth to report the number?
			continue
		}
		if ok {
			fmt.Printf("%d\t%v\t(%v)\n", pr.Pid(), pr.Executable(), name)
		}
	}
	if undetermined > 0 {
		fmt.Printf("\n%d processes left undetermined\n", undetermined)
	}
}

func isGo(filename string) (ok bool, err error) {
	obj, err := objfile.Open(filename)
	if err != nil {
		return false, err
	}
	defer obj.Close()

	symbols, err := obj.Symbols()
	if err != nil {
		return false, err
	}

	// TODO(jbd): find a faster way to determine Go programs.
	for _, s := range symbols {
		if s.Name == "runtime.buildVersion" {
			return true, nil
		}
	}
	return false, nil
}

func usage() {
	fmt.Fprintf(os.Stderr, "%v\n", helpText)
	os.Exit(1)
}

func exitIfError(err error) {
	if err == nil {
		return
	}
	fmt.Fprintf(os.Stderr, "%v\n", err)
	os.Exit(1)
}
<commit_msg>add TODOs<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Program gops is a tool to list currently running Go processes.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"

	"github.com/google/gops/signal"

	"github.com/google/gops/internal/objfile"

	ps "github.com/keybase/go-ps"
)

const helpText = `Usage: gops is a tool to list and diagnose Go processes.

    gops              Lists all Go processes currently running.
    gops [options...] See the section below.

Options:
    -stack    Prints the stack trace.
    -gc       Runs the garbage collector and blocks until successful.
    -gcstats  Prints the garbage collection stats.
    -version  Prints the Go version used to build the program.

All options require the agent and the -p=<pid> flag.
`

// TODO(jbd): add link that explains the use of agent.

var (
	pid      = flag.Int("p", -1, "")
	stack    = flag.Bool("stack", false, "")
	gc       = flag.Bool("gc", false, "")
	memstats = flag.Bool("memstats", false, "")
	version  = flag.Bool("version", false, "")
	help     = flag.Bool("help", false, "")
)

func main() {
	flag.Usage = usage
	flag.Parse()

	if len(os.Args) < 2 {
		goProcesses()
		return
	}
	if *pid == -1 || *help {
		usage()
	}
	if *stack {
		out, err := cmd(signal.Stack)
		exitIfError(err)
		fmt.Println(out)
	}
	if *gc {
		_, err := cmd(signal.GC)
		exitIfError(err)
	}
	if *memstats {
		out, err := cmd(signal.MemStats)
		exitIfError(err)
		fmt.Printf(out)
	}
	if *version {
		out, err := cmd(signal.Version)
		exitIfError(err)
		fmt.Printf(out)
	}
	// TODO(jbd): kill by name?
}

func cmd(c byte) (string, error) {
	sock := fmt.Sprintf("/tmp/gops%d.sock", *pid)
	conn, err := net.Dial("unix", sock)
	if err != nil {
		return "", err
	}
	if _, err := conn.Write([]byte{c}); err != nil {
		return "", err
	}
	all, err := ioutil.ReadAll(conn)
	if err != nil {
		return "", err
	}
	return string(all), nil
}

func goProcesses() {
	pss, err := ps.Processes()
	if err != nil {
		log.Fatal(err)
	}
	var undetermined int
	for _, pr := range pss {
		name, err := pr.Path()
		if err != nil {
			undetermined++
			continue
		}
		ok, err := isGo(name)
		if err != nil {
			// TODO(jbd): worth to report the number?
			continue
		}
		if ok {
			// TODO(jbd): List if the program is running the agent.
			fmt.Printf("%d\t%v\t(%v)\n", pr.Pid(), pr.Executable(), name)
		}
	}
	if undetermined > 0 {
		fmt.Printf("\n%d processes left undetermined\n", undetermined)
	}
}

func isGo(filename string) (ok bool, err error) {
	obj, err := objfile.Open(filename)
	if err != nil {
		return false, err
	}
	defer obj.Close()

	symbols, err := obj.Symbols()
	if err != nil {
		return false, err
	}

	// TODO(jbd): find a faster way to determine Go programs.
	for _, s := range symbols {
		if s.Name == "runtime.buildVersion" {
			return true, nil
		}
	}
	return false, nil
}

func usage() {
	fmt.Fprintf(os.Stderr, "%v\n", helpText)
	os.Exit(1)
}

func exitIfError(err error) {
	if err == nil {
		return
	}
	fmt.Fprintf(os.Stderr, "%v\n", err)
	os.Exit(1)
}
<|endoftext|>


<commit_before>package gost

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"io"
	"math/big"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/go-log/log"
)

// Version is the gost version.
const Version = "2.8.0"

// Debug is a flag that enables the debug log.
var Debug bool

var (
	tinyBufferSize   = 128
	smallBufferSize  = 1 * 1024  // 1KB small buffer
	mediumBufferSize = 8 * 1024  // 8KB medium buffer
	largeBufferSize  = 32 * 1024 // 32KB large buffer
)

var (
	sPool = sync.Pool{
		New: func() interface{} {
			return make([]byte, smallBufferSize)
		},
	}
	mPool = sync.Pool{
		New: func() interface{} {
			return make([]byte, mediumBufferSize)
		},
	}
	lPool = sync.Pool{
		New: func() interface{} {
			return make([]byte, largeBufferSize)
		},
	}
)

var (
	// KeepAliveTime is the keep alive time period for TCP connection.
	KeepAliveTime = 180 * time.Second
	// DialTimeout is the timeout of dial.
	DialTimeout = 5 * time.Second
	// HandshakeTimeout is the timeout of handshake.
	HandshakeTimeout = 5 * time.Second
	// ConnectTimeout is the timeout for connect.
	ConnectTimeout = 5 * time.Second
	// ReadTimeout is the timeout for reading.
	ReadTimeout = 10 * time.Second
	// WriteTimeout is the timeout for writing.
	WriteTimeout = 10 * time.Second
	// PingTimeout is the timeout for pinging.
	PingTimeout = 30 * time.Second
	// PingRetries is the reties of ping.
	PingRetries = 1
	// default udp node TTL in second for udp port forwarding.
	defaultTTL = 60 * time.Second
)

var (
	// DefaultTLSConfig is a default TLS config for internal use.
	DefaultTLSConfig *tls.Config

	// DefaultUserAgent is the default HTTP User-Agent header used by HTTP and websocket.
	DefaultUserAgent = "Chrome/60.0.3112.90"
)

// SetLogger sets a new logger for internal log system.
func SetLogger(logger log.Logger) {
	log.DefaultLogger = logger
}

// GenCertificate generates a random TLS certificate.
func GenCertificate() (cert tls.Certificate, err error) {
	rawCert, rawKey, err := generateKeyPair()
	if err != nil {
		return
	}
	return tls.X509KeyPair(rawCert, rawKey)
}

func generateKeyPair() (rawCert, rawKey []byte, err error) {
	// Create private key and self-signed certificate
	// Adapted from https://golang.org/src/crypto/tls/generate_cert.go

	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return
	}
	validFor := time.Hour * 24 * 365 * 10 // ten years
	notBefore := time.Now()
	notAfter := notBefore.Add(validFor)
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"gost"},
		},
		NotBefore: notBefore,
		NotAfter:  notAfter,

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
	}
	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return
	}

	rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})

	return
}

type readWriter struct {
	r io.Reader
	w io.Writer
}

func (rw *readWriter) Read(p []byte) (n int, err error) {
	return rw.r.Read(p)
}

func (rw *readWriter) Write(p []byte) (n int, err error) {
	return rw.w.Write(p)
}

var (
	nopClientConn = &nopConn{}
)

// a nop connection implements net.Conn,
// it does nothing.
type nopConn struct{}

func (c *nopConn) Read(b []byte) (n int, err error) {
	return 0, &net.OpError{Op: "read", Net: "nop", Source: nil, Addr: nil, Err: errors.New("read not supported")}
}

func (c *nopConn) Write(b []byte) (n int, err error) {
	return 0, &net.OpError{Op: "write", Net: "nop", Source: nil, Addr: nil, Err: errors.New("write not supported")}
}

func (c *nopConn) Close() error {
	return nil
}

func (c *nopConn) LocalAddr() net.Addr {
	return nil
}

func (c *nopConn) RemoteAddr() net.Addr {
	return nil
}

func (c *nopConn) SetDeadline(t time.Time) error {
	return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

func (c *nopConn) SetReadDeadline(t time.Time) error {
	return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

func (c *nopConn) SetWriteDeadline(t time.Time) error {
	return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

// splitLine splits a line text by white space, mainly used by config parser.
func splitLine(line string) []string {
	if line == "" {
		return nil
	}
	if n := strings.IndexByte(line, '#'); n >= 0 {
		line = line[:n]
	}
	line = strings.Replace(line, "\t", " ", -1)
	line = strings.TrimSpace(line)

	var ss []string
	for _, s := range strings.Split(line, " ") {
		if s = strings.TrimSpace(s); s != "" {
			ss = append(ss, s)
		}
	}
	return ss
}
<commit_msg>v2.8.1<commit_after>package gost

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"io"
	"math/big"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/go-log/log"
)

// Version is the gost version.
const Version = "2.8.1"

// Debug is a flag that enables the debug log.
var Debug bool

var (
	tinyBufferSize   = 128
	smallBufferSize  = 1 * 1024  // 1KB small buffer
	mediumBufferSize = 8 * 1024  // 8KB medium buffer
	largeBufferSize  = 32 * 1024 // 32KB large buffer
)

var (
	sPool = sync.Pool{
		New: func() interface{} {
			return make([]byte, smallBufferSize)
		},
	}
	mPool = sync.Pool{
		New: func() interface{} {
			return make([]byte, mediumBufferSize)
		},
	}
	lPool = sync.Pool{
		New: func() interface{} {
			return make([]byte, largeBufferSize)
		},
	}
)

var (
	// KeepAliveTime is the keep alive time period for TCP connection.
	KeepAliveTime = 180 * time.Second
	// DialTimeout is the timeout of dial.
	DialTimeout = 5 * time.Second
	// HandshakeTimeout is the timeout of handshake.
	HandshakeTimeout = 5 * time.Second
	// ConnectTimeout is the timeout for connect.
	ConnectTimeout = 5 * time.Second
	// ReadTimeout is the timeout for reading.
	ReadTimeout = 10 * time.Second
	// WriteTimeout is the timeout for writing.
	WriteTimeout = 10 * time.Second
	// PingTimeout is the timeout for pinging.
	PingTimeout = 30 * time.Second
	// PingRetries is the reties of ping.
	PingRetries = 1
	// default udp node TTL in second for udp port forwarding.
	defaultTTL = 60 * time.Second
)

var (
	// DefaultTLSConfig is a default TLS config for internal use.
	DefaultTLSConfig *tls.Config

	// DefaultUserAgent is the default HTTP User-Agent header used by HTTP and websocket.
	DefaultUserAgent = "Chrome/60.0.3112.90"
)

// SetLogger sets a new logger for internal log system.
func SetLogger(logger log.Logger) {
	log.DefaultLogger = logger
}

// GenCertificate generates a random TLS certificate.
func GenCertificate() (cert tls.Certificate, err error) {
	rawCert, rawKey, err := generateKeyPair()
	if err != nil {
		return
	}
	return tls.X509KeyPair(rawCert, rawKey)
}

func generateKeyPair() (rawCert, rawKey []byte, err error) {
	// Create private key and self-signed certificate
	// Adapted from https://golang.org/src/crypto/tls/generate_cert.go

	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return
	}
	validFor := time.Hour * 24 * 365 * 10 // ten years
	notBefore := time.Now()
	notAfter := notBefore.Add(validFor)
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"gost"},
		},
		NotBefore: notBefore,
		NotAfter:  notAfter,

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
	}
	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return
	}

	rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})

	return
}

type readWriter struct {
	r io.Reader
	w io.Writer
}

func (rw *readWriter) Read(p []byte) (n int, err error) {
	return rw.r.Read(p)
}

func (rw *readWriter) Write(p []byte) (n int, err error) {
	return rw.w.Write(p)
}

var (
	nopClientConn = &nopConn{}
)

// a nop connection implements net.Conn,
// it does nothing.
type nopConn struct{}

func (c *nopConn) Read(b []byte) (n int, err error) {
	return 0, &net.OpError{Op: "read", Net: "nop", Source: nil, Addr: nil, Err: errors.New("read not supported")}
}

func (c *nopConn) Write(b []byte) (n int, err error) {
	return 0, &net.OpError{Op: "write", Net: "nop", Source: nil, Addr: nil, Err: errors.New("write not supported")}
}

func (c *nopConn) Close() error {
	return nil
}

func (c *nopConn) LocalAddr() net.Addr {
	return nil
}

func (c *nopConn) RemoteAddr() net.Addr {
	return nil
}

func (c *nopConn) SetDeadline(t time.Time) error {
	return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

func (c *nopConn) SetReadDeadline(t time.Time) error {
	return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

func (c *nopConn) SetWriteDeadline(t time.Time) error {
	return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

// splitLine splits a line text by white space, mainly used by config parser.
func splitLine(line string) []string {
	if line == "" {
		return nil
	}
	if n := strings.IndexByte(line, '#'); n >= 0 {
		line = line[:n]
	}
	line = strings.Replace(line, "\t", " ", -1)
	line = strings.TrimSpace(line)

	var ss []string
	for _, s := range strings.Split(line, " ") {
		if s = strings.TrimSpace(s); s != "" {
			ss = append(ss, s)
		}
	}
	return ss
}

func connStateCallback(conn net.Conn, cs http.ConnState) {
	switch cs {
	case http.StateNew:
		conn.SetReadDeadline(time.Now().Add(30 * time.Second))
	default:
	}
}
<|endoftext|>


<commit_before>package gtnd

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"

	"github.com/google/go-querystring/query"
)

const (
	URL = "http://api.atnd.org/events/"
)

func Search(parameter SearchParam) (*SearchResult, error) {
	a, _ := addOptions(URL, parameter)
	//	pp.Print(a)
	//	param := url.Values{}
	//	param.Add("keyword", parameter.Keyword)
	//	param.Add("format", "json")

	resp, err := http.Get(a)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	val, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result *SearchResult
	if err = json.Unmarshal(val, &result); err != nil {
		return nil, err
	}
	return result, nil
}

func addOptions(r string, opt interface{}) (string, error) {
	v := reflect.ValueOf(opt)
	if v.Kind() == reflect.Ptr && v.IsNil() {
		return r, nil
	}
	u, err := url.Parse(r)
	if err != nil {
		return r, err
	}

	qs, err := query.Values(opt)
	if err != nil {
		return r, err
	}
	u.RawQuery = qs.Encode()
	return u.String(), nil
}
<commit_msg>refact<commit_after>package gtnd

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"

	"github.com/google/go-querystring/query"
)

const (
	URL = "http://api.atnd.org/events/"
)

func Search(parameter SearchParam) (*SearchResult, error) {
	a, err := addOptions(URL, parameter)
	if err != nil {
		return nil, err
	}
	//	pp.Print(a)
	//	param := url.Values{}
	//	param.Add("keyword", parameter.Keyword)
	//	param.Add("format", "json")

	resp, err := http.Get(a)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	val, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result *SearchResult
	if err = json.Unmarshal(val, &result); err != nil {
		return nil, err
	}
	return result, nil
}

func addOptions(r string, opt interface{}) (string, error) {
	v := reflect.ValueOf(opt)
	if v.Kind() == reflect.Ptr && v.IsNil() {
		return r, nil
	}
	u, err := url.Parse(r)
	if err != nil {
		return r, err
	}

	qs, err := query.Values(opt)
	if err != nil {
		return r, err
	}
	u.RawQuery = qs.Encode()
	return u.String(), nil
}
<|endoftext|>


<commit_before>package nd

import (
  "fmt"
  "strings"
  "crypto/rand"
  "encoding/hex"
)

var Guidv4 func() []byte

func init() {
  ResetGuidv4()
}

func ResetGuidv4() {
  Guidv4 = func() []byte {
    t := make([]byte, 16)
    rand.Read(t)
    return t
  }
}

func Guidv4String() string {
  t := Guidv4()
  return fmt.Sprintf("%x-%x-%x-%x-%x", t[0:4], t[4:6], t[6:8], t[8:10], t[10:])
}

func ForceGuid(guid string) {
  guid = strings.Replace(guid, "-", "", -1)
  g, _ := hex.DecodeString(guid)
  Guidv4 = func() []byte { return g }
}
<commit_msg>Dont swallow the errors<commit_after>package nd

import (
  "fmt"
  "strings"
  "crypto/rand"
  "encoding/hex"
)

var Guidv4 func() []byte

func init() {
  ResetGuidv4()
}

func ResetGuidv4() {
  Guidv4 = func() []byte {
    t := make([]byte, 16)
    rand.Read(t)
    return t
  }
}

func Guidv4String() string {
  t := Guidv4()
  return fmt.Sprintf("%x-%x-%x-%x-%x", t[0:4], t[4:6], t[6:8], t[8:10], t[10:])
}

func ForceGuid(guid string) error {
  guid = strings.Replace(guid, "-", "", -1)
  g, err := hex.DecodeString(guid)
  if err != nil { return err }
  Guidv4 = func() []byte { return g }
  return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 - António Meireles  <antonio.meireles@reformi.st>
//
// 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 main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/spf13/cobra"
)

var (
	killCmd = &cobra.Command{
		Use:     "kill [VMids]",
		Aliases: []string{"stop", "halt"},
		Short:   "Halts one or more running CoreOS instances",
		PreRunE: func(cmd *cobra.Command, args []string) (err error) {
			engine.rawArgs.BindPFlags(cmd.Flags())
			if len(args) < 1 && !engine.rawArgs.GetBool("all") {
				err = fmt.Errorf("This command requires either at least " +
					"one argument to work or --all.")
			}
			return
		},
		RunE: killCommand,
	}
)

func killCommand(cmd *cobra.Command, args []string) (err error) {
	var (
		up []VMInfo
		vm VMInfo
	)
	if up, err = allRunningInstances(); err != nil {
		return
	}
	if engine.rawArgs.GetBool("all") {
		for _, vm := range up {
			if err = vm.halt(); err != nil {
				return
			}
		}
		return
	}
	for _, arg := range args {
		if vm, err = vmInfo(arg); err != nil {
			return
		} else if err = vm.halt(); err != nil {
			return
		}
	}
	return
}

func (vm VMInfo) halt() (err error) {
	var (
		sshSession *sshClient
		command    = "sudo sync;sudo halt"
		hardKill   = func(e error) (err error) {
			if e != nil {
				// ssh messed up for some reason or target has no IP
				log.Printf("couldn't ssh to %v (%v)...\n", vm.Name, e)
				if canKill := engine.allowedToRun(); canKill != nil {
					return canKill
				}
				if p, ee := os.FindProcess(vm.Pid); ee == nil {
					log.Println("hard kill...")
					if err = p.Kill(); err != nil {
						return
					}
				}
			}
			return
		}
	)
	if sshSession, err = vm.startSSHsession(); err != nil {
		if err = hardKill(err); err != nil {
			return
		}
	} else {
		defer sshSession.close()
		if err =
			hardKill(sshSession.executeRemoteCommand(command)); err != nil {
			return
		}
	}
	// wait until it's _really_ dead, but not forever
	select {
	case <-time.After(3 * time.Second):
		err = fmt.Errorf("VM didn't shutdown normally after 3s (!)... ")
	case <-time.Tick(100 * time.Millisecond):
		if _, ee := os.FindProcess(vm.Pid); ee == nil {
			if e :=
				os.RemoveAll(filepath.Join(engine.runDir,
					vm.UUID)); e != nil {
				log.Println(e.Error())
			}
			log.Printf("successfully halted '%s'\n", vm.Name)
		}
	}
	return
}

func init() {
	killCmd.Flags().BoolP("all", "a", false, "halts all running instances")
	RootCmd.AddCommand(killCmd)
}
<commit_msg>wait up to 30s for a VM to halt gracefully.<commit_after>// Copyright 2015 - António Meireles  <antonio.meireles@reformi.st>
//
// 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 main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/spf13/cobra"
)

var (
	killCmd = &cobra.Command{
		Use:     "kill [VMids]",
		Aliases: []string{"stop", "halt"},
		Short:   "Halts one or more running CoreOS instances",
		PreRunE: func(cmd *cobra.Command, args []string) (err error) {
			engine.rawArgs.BindPFlags(cmd.Flags())
			if len(args) < 1 && !engine.rawArgs.GetBool("all") {
				err = fmt.Errorf("This command requires either at least " +
					"one argument to work or --all.")
			}
			return
		},
		RunE: killCommand,
	}
)

func killCommand(cmd *cobra.Command, args []string) (err error) {
	var (
		up []VMInfo
		vm VMInfo
	)
	if up, err = allRunningInstances(); err != nil {
		return
	}
	if engine.rawArgs.GetBool("all") {
		for _, vm := range up {
			if err = vm.halt(); err != nil {
				return
			}
		}
		return
	}
	for _, arg := range args {
		if vm, err = vmInfo(arg); err != nil {
			return
		} else if err = vm.halt(); err != nil {
			return
		}
	}
	return
}

func (vm VMInfo) halt() (err error) {
	var (
		sshSession *sshClient
		command    = "sudo sync;sudo halt"
		hardKill   = func(e error) (err error) {
			if e != nil {
				// ssh messed up for some reason or target has no IP
				log.Printf("couldn't ssh to %v (%v)...\n", vm.Name, e)
				if canKill := engine.allowedToRun(); canKill != nil {
					return canKill
				}
				if p, ee := os.FindProcess(vm.Pid); ee == nil {
					log.Println("hard kill...")
					if err = p.Kill(); err != nil {
						return
					}
				}
			}
			return
		}
	)
	if sshSession, err = vm.startSSHsession(); err != nil {
		if err = hardKill(err); err != nil {
			return
		}
	} else {
		defer sshSession.close()
		if err =
			hardKill(sshSession.executeRemoteCommand(command)); err != nil {
			return
		}
	}
	// wait until it's _really_ dead, but not forever
	for {
		select {
		// unmounts may take a bit in slow drives...
		case <-time.After(30 * time.Second):
			return fmt.Errorf(fmt.Sprintf("'%s' didn't shutdown normally "+
				"after 30s (!)... ", vm.Name))
		case <-time.Tick(100 * time.Millisecond):
			if _, ee := os.FindProcess(vm.Pid); ee == nil {
				if e :=
					os.RemoveAll(filepath.Join(engine.runDir,
						vm.UUID)); e != nil {
					log.Println(e.Error())
				}
				log.Printf("successfully halted '%s'\n", vm.Name)
				return
			}
		}
	}
}

func init() {
	killCmd.Flags().BoolP("all", "a", false, "halts all running instances")
	RootCmd.AddCommand(killCmd)
}
<|endoftext|>