<commit_before>package main

import (
	"flag"
	"github.com/bmizerany/pat"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/api/service"
	"github.com/timeredbull/tsuru/config"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/log"
	"github.com/timeredbull/tsuru/repository"
	stdlog "log"
	"log/syslog"
	"net/http"
)

func main() {
	var err error
	log.Target, err = syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		panic(err)
	}
	configFile := flag.String("config", "/etc/tsuru/tsuru.conf", "tsuru config file")
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	flag.Parse()
	err = config.ReadConfigFile(*configFile)
	if err != nil {
		log.Panic(err.Error())
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		panic(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		panic(err)
	}
	db.Session, err = db.Open(connString, dbName)
	if err != nil {
		log.Panic(err.Error())
	}
	defer db.Session.Close()

	repository.RunAgent()
	m := pat.New()

	m.Post("/services", AuthorizationRequiredHandler(service.CreateHandler))
	m.Post("/services/instances", AuthorizationRequiredHandler(service.CreateInstanceHandler))
	m.Get("/services", AuthorizationRequiredHandler(service.ServicesHandler))
	m.Del("/services/:name", AuthorizationRequiredHandler(service.DeleteHandler))
	// m.Post("/services/bind", AuthorizationRequiredHandler(service.BindHandler))
	// m.Post("/services/unbind", AuthorizationRequiredHandler(service.UnbindHandler))
	m.Put("/services/:service/:team", AuthorizationRequiredHandler(service.GrantAccessToTeamHandler))
	m.Del("/services/:service/:team", AuthorizationRequiredHandler(service.RevokeAccessFromTeamHandler))

	m.Del("/apps/:name", AuthorizationRequiredHandler(app.AppDelete))
	m.Get("/apps/:name/repository/clone", Handler(app.CloneRepositoryHandler))
	m.Get("/apps/:name", AuthorizationRequiredHandler(app.AppInfo))
	m.Post("/apps/:name/run", AuthorizationRequiredHandler(app.RunCommand))
	m.Get("/apps/:name/env", AuthorizationRequiredHandler(app.GetEnv))
	m.Post("/apps/:name/env", AuthorizationRequiredHandler(app.SetEnv))
	m.Del("/apps/:name/env", AuthorizationRequiredHandler(app.UnsetEnv))
	m.Get("/apps", AuthorizationRequiredHandler(app.AppList))
	m.Post("/apps", AuthorizationRequiredHandler(app.CreateAppHandler))
	m.Put("/apps/:app/:team", AuthorizationRequiredHandler(app.GrantAccessToTeamHandler))
	m.Del("/apps/:app/:team", AuthorizationRequiredHandler(app.RevokeAccessFromTeamHandler))
	m.Get("/apps/:name/log", AuthorizationRequiredHandler(app.AppLog))

	m.Post("/users", Handler(auth.CreateUser))
	m.Post("/users/:email/tokens", Handler(auth.Login))
	m.Post("/users/keys", AuthorizationRequiredHandler(auth.AddKeyToUser))
	m.Del("/users/keys", AuthorizationRequiredHandler(auth.RemoveKeyFromUser))

	m.Post("/teams", AuthorizationRequiredHandler(auth.CreateTeam))
	m.Put("/teams/:team/:user", AuthorizationRequiredHandler(auth.AddUserToTeam))
	m.Del("/teams/:team/:user", AuthorizationRequiredHandler(auth.RemoveUserFromTeam))

	listen, err := config.GetString("listen")
	if err != nil {
		panic(err)
	}
	if !*dry {
		log.Fatal(http.ListenAndServe(listen, m))
	}
}
<commit_msg>api/webserver: mapping url for the BindHandler<commit_after>package main

import (
	"flag"
	"github.com/bmizerany/pat"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/api/service"
	"github.com/timeredbull/tsuru/config"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/log"
	"github.com/timeredbull/tsuru/repository"
	stdlog "log"
	"log/syslog"
	"net/http"
)

func main() {
	var err error
	log.Target, err = syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		panic(err)
	}
	configFile := flag.String("config", "/etc/tsuru/tsuru.conf", "tsuru config file")
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	flag.Parse()
	err = config.ReadConfigFile(*configFile)
	if err != nil {
		log.Panic(err.Error())
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		panic(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		panic(err)
	}
	db.Session, err = db.Open(connString, dbName)
	if err != nil {
		log.Panic(err.Error())
	}
	defer db.Session.Close()

	repository.RunAgent()
	m := pat.New()

	m.Post("/services/instances", AuthorizationRequiredHandler(service.CreateInstanceHandler))
	m.Put("/services/instances/:instance/:app", AuthorizationRequiredHandler(service.BindHandler))

	m.Post("/services", AuthorizationRequiredHandler(service.CreateHandler))
	m.Get("/services", AuthorizationRequiredHandler(service.ServicesHandler))
	m.Del("/services/:name", AuthorizationRequiredHandler(service.DeleteHandler))
	// m.Post("/services/unbind", AuthorizationRequiredHandler(service.UnbindHandler))
	m.Put("/services/:service/:team", AuthorizationRequiredHandler(service.GrantAccessToTeamHandler))
	m.Del("/services/:service/:team", AuthorizationRequiredHandler(service.RevokeAccessFromTeamHandler))

	m.Del("/apps/:name", AuthorizationRequiredHandler(app.AppDelete))
	m.Get("/apps/:name/repository/clone", Handler(app.CloneRepositoryHandler))
	m.Get("/apps/:name", AuthorizationRequiredHandler(app.AppInfo))
	m.Post("/apps/:name/run", AuthorizationRequiredHandler(app.RunCommand))
	m.Get("/apps/:name/env", AuthorizationRequiredHandler(app.GetEnv))
	m.Post("/apps/:name/env", AuthorizationRequiredHandler(app.SetEnv))
	m.Del("/apps/:name/env", AuthorizationRequiredHandler(app.UnsetEnv))
	m.Get("/apps", AuthorizationRequiredHandler(app.AppList))
	m.Post("/apps", AuthorizationRequiredHandler(app.CreateAppHandler))
	m.Put("/apps/:app/:team", AuthorizationRequiredHandler(app.GrantAccessToTeamHandler))
	m.Del("/apps/:app/:team", AuthorizationRequiredHandler(app.RevokeAccessFromTeamHandler))
	m.Get("/apps/:name/log", AuthorizationRequiredHandler(app.AppLog))

	m.Post("/users", Handler(auth.CreateUser))
	m.Post("/users/:email/tokens", Handler(auth.Login))
	m.Post("/users/keys", AuthorizationRequiredHandler(auth.AddKeyToUser))
	m.Del("/users/keys", AuthorizationRequiredHandler(auth.RemoveKeyFromUser))

	m.Post("/teams", AuthorizationRequiredHandler(auth.CreateTeam))
	m.Put("/teams/:team/:user", AuthorizationRequiredHandler(auth.AddUserToTeam))
	m.Del("/teams/:team/:user", AuthorizationRequiredHandler(auth.RemoveUserFromTeam))

	listen, err := config.GetString("listen")
	if err != nil {
		panic(err)
	}
	if !*dry {
		log.Fatal(http.ListenAndServe(listen, m))
	}
}
<|endoftext|>


<commit_before>package mbus

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"net"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	"github.com/nats-io/nats.go"

	"crypto/x509"
	"time"

	"crypto/tls"
	"regexp"

	boshhandler "github.com/cloudfoundry/bosh-agent/handler"
	boshplatform "github.com/cloudfoundry/bosh-agent/platform"
	boshsettings "github.com/cloudfoundry/bosh-agent/settings"
	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	boshlog "github.com/cloudfoundry/bosh-utils/logger"
)

const (
	responseMaxLength = 1024 * 1024
	natsHandlerLogTag = "NATS Handler"
	natsMinReconnectSeconds = 2.0
	//natsMaxReconnectSeconds should be lower than the setting we have in BOSH for https://github.com/cloudfoundry/bosh/blob/main/src/bosh-director/lib/bosh/director/agent_client.rb#L44.
	natsMaxReconnectSeconds = 10.0
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate

type Handler interface {
	Run(boshhandler.Func) error
	Start(boshhandler.Func) error
	RegisterAdditionalFunc(boshhandler.Func)
	Send(target boshhandler.Target, topic boshhandler.Topic, message interface{}) error
	Stop()
}

type NatsConnector func(url string, options ...nats.Option) (NatsConnection, error)

//counterfeiter:generate . NatsConnection

type NatsConnection interface {
	Close()
	Publish(subj string, data []byte) error
	Subscribe(subj string, cb nats.MsgHandler) (*nats.Subscription, error)
}

type natsHandler struct {
	settingsService boshsettings.Service
	connector       NatsConnector
	connection      NatsConnection
	platform        boshplatform.Platform

	handlerFuncs     []boshhandler.Func
	handlerFuncsLock sync.Mutex

	logger      boshlog.Logger
	auditLogger boshplatform.AuditLogger
	logTag      string
}

type ConnectionInfo struct {
	Addr      string
	IP        string
	Dial      func(network, address string) (net.Conn, error)
	TLSConfig *tls.Config
}

type ConnectionTLSInfo struct {
	CertPool              *x509.CertPool
	ClientCert            *tls.Certificate
	VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
}

func NewNatsHandler(
	settingsService boshsettings.Service,
	client NatsConnector,
	logger boshlog.Logger,
	platform boshplatform.Platform,
) Handler {
	return &natsHandler{
		settingsService: settingsService,
		connector:       client,
		platform:        platform,
		logger:          logger,
		logTag:          natsHandlerLogTag,
		auditLogger:     platform.GetAuditLogger(),
	}
}
func (h *natsHandler) arpClean() {
	connectionInfo, err := h.getConnectionInfo()
	if err != nil {
		h.logger.Error(h.logTag, "%v", bosherr.WrapError(err, "Getting connection info"))
	}
	err = h.platform.DeleteARPEntryWithIP(connectionInfo.IP)
	if err != nil {
		h.logger.Error(h.logTag, "Cleaning ip-mac address cache for: %s. Error: %v", connectionInfo.IP, err)
	}

	h.logger.Debug(h.logTag, "Cleaned ip-mac address cache for: %s.", connectionInfo.IP)
}

func (h *natsHandler) Run(handlerFunc boshhandler.Func) error {
	err := h.Start(handlerFunc)
	defer h.Stop()

	if err != nil {
		return bosherr.WrapError(err, "Starting nats handler")
	}

	h.runUntilInterrupted()
	return nil
}
func (h *natsHandler) Start(handlerFunc boshhandler.Func) error {
	h.RegisterAdditionalFunc(handlerFunc)

	connectionInfo, err := h.getConnectionInfo()
	if err != nil {
		return bosherr.WrapError(err, "Getting connection info")
	}
	h.logger.Info(h.logTag, "Attempting to connect to NATS")
	if net.ParseIP(connectionInfo.IP) != nil {
		h.arpClean()
	}
	var natsOptions = []nats.Option{
		nats.RetryOnFailedConnect(true),
		nats.DisconnectErrHandler(func(c *nats.Conn, err error) {
			h.logger.Debug(natsHandlerLogTag, "Nats disconnected with Error: %v", err.Error())
			h.logger.Debug(natsHandlerLogTag, "Attempting to reconnect: %v", c.IsReconnecting())
			for c.IsReconnecting() {
				h.arpClean()
				h.logger.Debug(natsHandlerLogTag, "Waiting to reconnect to nats.. Current attempt: %v, Connected: %v", c.Reconnects, c.IsConnected())
				time.Sleep(5 * time.Second)
			}
		}),
		nats.ReconnectHandler(func(c *nats.Conn) {
			h.logger.Debug(natsHandlerLogTag, "Reconnected to %v", c.ConnectedAddr())
		}),
		nats.ClosedHandler(func(c *nats.Conn) {
			h.logger.Debug(natsHandlerLogTag, "Connection Closed with: %v", c.LastError().Error())
		}),
		nats.ErrorHandler(func(c *nats.Conn, s *nats.Subscription, err error) {
			h.logger.Debug(natsHandlerLogTag, err.Error())
		}),
		nats.CustomReconnectDelay(func(attempts int) time.Duration {
			reconnectSeconds := natsMinReconnectSeconds * math.Pow(2.0, math.Min(20.0, float64(attempts - 1)))
			if (reconnectSeconds > natsMaxReconnectSeconds || reconnectSeconds <= 0.0) {
				reconnectSeconds = natsMaxReconnectSeconds
			}
			reconnectDelay := time.Duration(reconnectSeconds * float64(time.Second))
			h.logger.Debug(natsHandlerLogTag, "Increased reconnect to: %v", reconnectDelay)
			return reconnectDelay
		}),
		nats.MaxReconnects(-1),
		nats.Secure(connectionInfo.TLSConfig),
	}

	connection, err := h.connector(connectionInfo.Addr, natsOptions...)
	// just log this error. even if currently cannot connect to nats, we can eventually
	if err != nil {
		return bosherr.WrapError(err, "Connecting to NATS")
	}

	h.connection = connection

	settings := h.settingsService.GetSettings()

	subject := fmt.Sprintf("agent.%s", settings.AgentID)

	h.logger.Info(h.logTag, "Subscribing to %s", subject)

	_, err = h.connection.Subscribe(subject, func(natsMsg *nats.Msg) {
		// Do not lock handler funcs around possible network calls!
		h.handlerFuncsLock.Lock()
		handlerFuncs := h.handlerFuncs
		h.handlerFuncsLock.Unlock()

		for _, handlerFunc := range handlerFuncs {
			h.handleNatsMsg(natsMsg, handlerFunc)
		}
	})
	if err != nil {
		return bosherr.WrapErrorf(err, "Subscribing to %s", subject)
	}

	return nil
}

func (h *natsHandler) RegisterAdditionalFunc(handlerFunc boshhandler.Func) {
	// Currently not locking since RegisterAdditionalFunc
	// is not a primary way of adding handlerFunc.
	h.handlerFuncsLock.Lock()
	h.handlerFuncs = append(h.handlerFuncs, handlerFunc)
	h.handlerFuncsLock.Unlock()
}

func (h *natsHandler) Send(target boshhandler.Target, topic boshhandler.Topic, message interface{}) error {
	bytes, err := json.Marshal(message)
	if err != nil {
		return bosherr.WrapErrorf(err, "Marshalling message (target=%s, topic=%s): %#v", target, topic, message)
	}

	h.logger.Info(h.logTag, "Sending %s message '%s'", target, topic)
	h.logger.DebugWithDetails(h.logTag, "Message Payload", string(bytes))

	settings := h.settingsService.GetSettings()

	subject := fmt.Sprintf("%s.agent.%s.%s", target, topic, settings.AgentID)
	if h.connection != nil {
		return h.connection.Publish(subject, bytes)
	}
	return nil
}

func (h *natsHandler) Stop() {
	if h.connection != nil {
		h.connection.Close()
	}
}

func (h *natsHandler) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
	natsBoshInternalsRegexp := regexp.MustCompile(`^[a-zA-Z0-9*\-]*.nats.bosh-internal$`)
	for _, chain := range verifiedChains {
		if len(chain) == 0 {
			continue
		}
		commonName := chain[0].Subject.CommonName
		if natsBoshInternalsRegexp.MatchString(commonName) {
			return nil
		}
	}
	return errors.New("server Certificate CommonName does not match *.nats.bosh-internal")
}

func (h *natsHandler) handleNatsMsg(natsMsg *nats.Msg, handlerFunc boshhandler.Func) {
	respBytes, req, err := boshhandler.PerformHandlerWithJSON(
		natsMsg.Data,
		handlerFunc,
		responseMaxLength,
		h.logger,
	)

	if err != nil {
		h.logger.Error(h.logTag, "Running handler: %s", err)
		h.generateCEFLog(natsMsg, 7, err.Error())
		return
	}

	if len(respBytes) > 0 {
		err = h.connection.Publish(req.ReplyTo, respBytes)
		if err != nil {
			h.generateCEFLog(natsMsg, 7, err.Error())
			h.logger.Error(h.logTag, "Publishing to the client: %s", err.Error())
			return
		}
	}

	h.generateCEFLog(natsMsg, 1, "")
}

func (h *natsHandler) runUntilInterrupted() {
	defer h.connection.Close()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)

	keepRunning := true
	for keepRunning {
		<-c
		keepRunning = false
	}
}

func (h *natsHandler) getConnectionInfo() (*ConnectionInfo, error) {
	settings := h.settingsService.GetSettings()

	connInfo := new(ConnectionInfo)
	connInfo.Addr = settings.GetMbusURL()
	natsURL, err := url.Parse(connInfo.Addr)
	if err != nil {
		return nil, bosherr.WrapError(err, "Parsing Nats URL")
	}

	hostSplit := strings.Split(natsURL.Host, ":")
	connInfo.IP = hostSplit[0]

	connInfo.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12}

	caCert := settings.GetMbusCerts().CA
	if caCert != "" {
		connInfo.TLSConfig.RootCAs = x509.NewCertPool()
		if ok := connInfo.TLSConfig.RootCAs.AppendCertsFromPEM([]byte(caCert)); !ok {
			return nil, bosherr.Error("Failed to load Mbus CA cert")
		}
	}

	connInfo.TLSConfig.VerifyPeerCertificate = h.VerifyPeerCertificate

	clientCertificate, err := tls.X509KeyPair([]byte(settings.GetMbusCerts().Certificate), []byte(settings.GetMbusCerts().PrivateKey))
	if err != nil {
		return nil, bosherr.WrapError(err, "Parsing certificate and private key")
	}
	connInfo.TLSConfig.Certificates = []tls.Certificate{clientCertificate}

	return connInfo, nil
}

func (h *natsHandler) generateCEFLog(natsMsg *nats.Msg, severity int, statusReason string) {
	cef := boshhandler.NewCommonEventFormat()

	settings := h.settingsService.GetSettings()

	natsURL, err := url.Parse(settings.GetMbusURL())
	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
		return
	}

	hostSplit := strings.Split(natsURL.Host, ":")
	ip := hostSplit[0]
	payload := struct {
		Method  string `json:"method"`
		ReplyTo string `json:"reply_to"`
	}{}
	err = json.Unmarshal(natsMsg.Data, &payload)
	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
	}
	cefString, err := cef.ProduceNATSRequestEventLog(ip, hostSplit[1], payload.ReplyTo, payload.Method, severity, natsMsg.Subject, statusReason)

	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
		return
	}

	if severity == 7 {
		h.auditLogger.Err(cefString)
		return
	}

	h.auditLogger.Debug(cefString)
}
<commit_msg>simplify backoff mechanism in nats_handler.go / nats.CustomReconnectDelay()<commit_after>package mbus

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"net"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	"github.com/nats-io/nats.go"

	"crypto/x509"
	"time"

	"crypto/tls"
	"regexp"

	boshhandler "github.com/cloudfoundry/bosh-agent/handler"
	boshplatform "github.com/cloudfoundry/bosh-agent/platform"
	boshsettings "github.com/cloudfoundry/bosh-agent/settings"
	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	boshlog "github.com/cloudfoundry/bosh-utils/logger"
)

const (
	responseMaxLength = 1024 * 1024
	natsHandlerLogTag = "NATS Handler"
	natsMinReconnectSeconds = 2.0
	//natsMaxReconnectSeconds should be lower than the setting we have in BOSH for https://github.com/cloudfoundry/bosh/blob/main/src/bosh-director/lib/bosh/director/agent_client.rb#L44.
	natsMaxReconnectSeconds = 10.0
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate

type Handler interface {
	Run(boshhandler.Func) error
	Start(boshhandler.Func) error
	RegisterAdditionalFunc(boshhandler.Func)
	Send(target boshhandler.Target, topic boshhandler.Topic, message interface{}) error
	Stop()
}

type NatsConnector func(url string, options ...nats.Option) (NatsConnection, error)

//counterfeiter:generate . NatsConnection

type NatsConnection interface {
	Close()
	Publish(subj string, data []byte) error
	Subscribe(subj string, cb nats.MsgHandler) (*nats.Subscription, error)
}

type natsHandler struct {
	settingsService boshsettings.Service
	connector       NatsConnector
	connection      NatsConnection
	platform        boshplatform.Platform

	handlerFuncs     []boshhandler.Func
	handlerFuncsLock sync.Mutex

	logger      boshlog.Logger
	auditLogger boshplatform.AuditLogger
	logTag      string
}

type ConnectionInfo struct {
	Addr      string
	IP        string
	Dial      func(network, address string) (net.Conn, error)
	TLSConfig *tls.Config
}

type ConnectionTLSInfo struct {
	CertPool              *x509.CertPool
	ClientCert            *tls.Certificate
	VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
}

func NewNatsHandler(
	settingsService boshsettings.Service,
	client NatsConnector,
	logger boshlog.Logger,
	platform boshplatform.Platform,
) Handler {
	return &natsHandler{
		settingsService: settingsService,
		connector:       client,
		platform:        platform,
		logger:          logger,
		logTag:          natsHandlerLogTag,
		auditLogger:     platform.GetAuditLogger(),
	}
}
func (h *natsHandler) arpClean() {
	connectionInfo, err := h.getConnectionInfo()
	if err != nil {
		h.logger.Error(h.logTag, "%v", bosherr.WrapError(err, "Getting connection info"))
	}
	err = h.platform.DeleteARPEntryWithIP(connectionInfo.IP)
	if err != nil {
		h.logger.Error(h.logTag, "Cleaning ip-mac address cache for: %s. Error: %v", connectionInfo.IP, err)
	}

	h.logger.Debug(h.logTag, "Cleaned ip-mac address cache for: %s.", connectionInfo.IP)
}

func (h *natsHandler) Run(handlerFunc boshhandler.Func) error {
	err := h.Start(handlerFunc)
	defer h.Stop()

	if err != nil {
		return bosherr.WrapError(err, "Starting nats handler")
	}

	h.runUntilInterrupted()
	return nil
}
func (h *natsHandler) Start(handlerFunc boshhandler.Func) error {
	h.RegisterAdditionalFunc(handlerFunc)

	connectionInfo, err := h.getConnectionInfo()
	if err != nil {
		return bosherr.WrapError(err, "Getting connection info")
	}
	h.logger.Info(h.logTag, "Attempting to connect to NATS")
	if net.ParseIP(connectionInfo.IP) != nil {
		h.arpClean()
	}
	var natsOptions = []nats.Option{
		nats.RetryOnFailedConnect(true),
		nats.DisconnectErrHandler(func(c *nats.Conn, err error) {
			h.logger.Debug(natsHandlerLogTag, "Nats disconnected with Error: %v", err.Error())
			h.logger.Debug(natsHandlerLogTag, "Attempting to reconnect: %v", c.IsReconnecting())
			for c.IsReconnecting() {
				h.arpClean()
				h.logger.Debug(natsHandlerLogTag, "Waiting to reconnect to nats.. Current attempt: %v, Connected: %v", c.Reconnects, c.IsConnected())
				time.Sleep(5 * time.Second)
			}
		}),
		nats.ReconnectHandler(func(c *nats.Conn) {
			h.logger.Debug(natsHandlerLogTag, "Reconnected to %v", c.ConnectedAddr())
		}),
		nats.ClosedHandler(func(c *nats.Conn) {
			h.logger.Debug(natsHandlerLogTag, "Connection Closed with: %v", c.LastError().Error())
		}),
		nats.ErrorHandler(func(c *nats.Conn, s *nats.Subscription, err error) {
			h.logger.Debug(natsHandlerLogTag, err.Error())
		}),
		nats.CustomReconnectDelay(func(attempts int) time.Duration {
			reconnectSeconds := natsMinReconnectSeconds * float64(attempts)
			if (reconnectSeconds > natsMaxReconnectSeconds || reconnectSeconds <= 0.0) {
				reconnectSeconds = natsMaxReconnectSeconds
			}
			reconnectDelay := time.Duration(reconnectSeconds * float64(time.Second))
			h.logger.Debug(natsHandlerLogTag, "Increased reconnect to: %v", reconnectDelay)
			return reconnectDelay
		}),
		nats.MaxReconnects(-1),
		nats.Secure(connectionInfo.TLSConfig),
	}

	connection, err := h.connector(connectionInfo.Addr, natsOptions...)
	// just log this error. even if currently cannot connect to nats, we can eventually
	if err != nil {
		return bosherr.WrapError(err, "Connecting to NATS")
	}

	h.connection = connection

	settings := h.settingsService.GetSettings()

	subject := fmt.Sprintf("agent.%s", settings.AgentID)

	h.logger.Info(h.logTag, "Subscribing to %s", subject)

	_, err = h.connection.Subscribe(subject, func(natsMsg *nats.Msg) {
		// Do not lock handler funcs around possible network calls!
		h.handlerFuncsLock.Lock()
		handlerFuncs := h.handlerFuncs
		h.handlerFuncsLock.Unlock()

		for _, handlerFunc := range handlerFuncs {
			h.handleNatsMsg(natsMsg, handlerFunc)
		}
	})
	if err != nil {
		return bosherr.WrapErrorf(err, "Subscribing to %s", subject)
	}

	return nil
}

func (h *natsHandler) RegisterAdditionalFunc(handlerFunc boshhandler.Func) {
	// Currently not locking since RegisterAdditionalFunc
	// is not a primary way of adding handlerFunc.
	h.handlerFuncsLock.Lock()
	h.handlerFuncs = append(h.handlerFuncs, handlerFunc)
	h.handlerFuncsLock.Unlock()
}

func (h *natsHandler) Send(target boshhandler.Target, topic boshhandler.Topic, message interface{}) error {
	bytes, err := json.Marshal(message)
	if err != nil {
		return bosherr.WrapErrorf(err, "Marshalling message (target=%s, topic=%s): %#v", target, topic, message)
	}

	h.logger.Info(h.logTag, "Sending %s message '%s'", target, topic)
	h.logger.DebugWithDetails(h.logTag, "Message Payload", string(bytes))

	settings := h.settingsService.GetSettings()

	subject := fmt.Sprintf("%s.agent.%s.%s", target, topic, settings.AgentID)
	if h.connection != nil {
		return h.connection.Publish(subject, bytes)
	}
	return nil
}

func (h *natsHandler) Stop() {
	if h.connection != nil {
		h.connection.Close()
	}
}

func (h *natsHandler) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
	natsBoshInternalsRegexp := regexp.MustCompile(`^[a-zA-Z0-9*\-]*.nats.bosh-internal$`)
	for _, chain := range verifiedChains {
		if len(chain) == 0 {
			continue
		}
		commonName := chain[0].Subject.CommonName
		if natsBoshInternalsRegexp.MatchString(commonName) {
			return nil
		}
	}
	return errors.New("server Certificate CommonName does not match *.nats.bosh-internal")
}

func (h *natsHandler) handleNatsMsg(natsMsg *nats.Msg, handlerFunc boshhandler.Func) {
	respBytes, req, err := boshhandler.PerformHandlerWithJSON(
		natsMsg.Data,
		handlerFunc,
		responseMaxLength,
		h.logger,
	)

	if err != nil {
		h.logger.Error(h.logTag, "Running handler: %s", err)
		h.generateCEFLog(natsMsg, 7, err.Error())
		return
	}

	if len(respBytes) > 0 {
		err = h.connection.Publish(req.ReplyTo, respBytes)
		if err != nil {
			h.generateCEFLog(natsMsg, 7, err.Error())
			h.logger.Error(h.logTag, "Publishing to the client: %s", err.Error())
			return
		}
	}

	h.generateCEFLog(natsMsg, 1, "")
}

func (h *natsHandler) runUntilInterrupted() {
	defer h.connection.Close()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)

	keepRunning := true
	for keepRunning {
		<-c
		keepRunning = false
	}
}

func (h *natsHandler) getConnectionInfo() (*ConnectionInfo, error) {
	settings := h.settingsService.GetSettings()

	connInfo := new(ConnectionInfo)
	connInfo.Addr = settings.GetMbusURL()
	natsURL, err := url.Parse(connInfo.Addr)
	if err != nil {
		return nil, bosherr.WrapError(err, "Parsing Nats URL")
	}

	hostSplit := strings.Split(natsURL.Host, ":")
	connInfo.IP = hostSplit[0]

	connInfo.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12}

	caCert := settings.GetMbusCerts().CA
	if caCert != "" {
		connInfo.TLSConfig.RootCAs = x509.NewCertPool()
		if ok := connInfo.TLSConfig.RootCAs.AppendCertsFromPEM([]byte(caCert)); !ok {
			return nil, bosherr.Error("Failed to load Mbus CA cert")
		}
	}

	connInfo.TLSConfig.VerifyPeerCertificate = h.VerifyPeerCertificate

	clientCertificate, err := tls.X509KeyPair([]byte(settings.GetMbusCerts().Certificate), []byte(settings.GetMbusCerts().PrivateKey))
	if err != nil {
		return nil, bosherr.WrapError(err, "Parsing certificate and private key")
	}
	connInfo.TLSConfig.Certificates = []tls.Certificate{clientCertificate}

	return connInfo, nil
}

func (h *natsHandler) generateCEFLog(natsMsg *nats.Msg, severity int, statusReason string) {
	cef := boshhandler.NewCommonEventFormat()

	settings := h.settingsService.GetSettings()

	natsURL, err := url.Parse(settings.GetMbusURL())
	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
		return
	}

	hostSplit := strings.Split(natsURL.Host, ":")
	ip := hostSplit[0]
	payload := struct {
		Method  string `json:"method"`
		ReplyTo string `json:"reply_to"`
	}{}
	err = json.Unmarshal(natsMsg.Data, &payload)
	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
	}
	cefString, err := cef.ProduceNATSRequestEventLog(ip, hostSplit[1], payload.ReplyTo, payload.Method, severity, natsMsg.Subject, statusReason)

	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
		return
	}

	if severity == 7 {
		h.auditLogger.Err(cefString)
		return
	}

	h.auditLogger.Debug(cefString)
}
<|endoftext|>


<commit_before>package main

// Based on http://stackoverflow.com/a/36672164

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"

	"encoding/json"
	"github.com/gorilla/mux"
	"github.com/nu7hatch/gouuid"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/facebook"
	"os"
	"strconv"
	"time"
)

var (
	oauthConf        *oauth2.Config
	oauthStateString string
)

// FacebookAuthResponse is the data we get back on the initial auth
type FacebookAuthResponse struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
	Token string `json:"access_token"`
}

// FacebookJoinRequest is the request to join the showdown <3
type FacebookJoinRequest struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Nick  string `json:"nick"`
	Color string `json:"color"`
}

func init() {
	// You need to have these three env vars in your env for Facebook to work.
	// If you need access to them, talk to @thiderman.
	oauthConf = &oauth2.Config{
		ClientID:     os.Getenv("DF_FB_ID"),
		ClientSecret: os.Getenv("DF_FB_SECRET"),
		RedirectURL:  os.Getenv("DF_FB_CALLBACK"),
		Scopes:       []string{"public_profile", "email"},
		Endpoint:     facebook.Endpoint,
	}

	if os.Getenv("DF_FB_ID") != "" {
		log.Print("Facebook configuration loaded.")
	} else {
		log.Print("Facebook app configuration missing. Auth will not work.")
	}

	randomUUID, err := uuid.NewV4()
	if err != nil {
		log.Fatal(err)
	}

	oauthStateString = randomUUID.String()
}

const htmlIndex = `
Log in with facebook

`

func (s *Server) handleMain(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(htmlIndex))
}

func (s *Server) handleFacebookLogin(w http.ResponseWriter, r *http.Request) {
	URL, err := url.Parse(oauthConf.Endpoint.AuthURL)
	if err != nil {
		log.Fatal("Parse: ", err)
	}
	parameters := url.Values{}
	parameters.Add("client_id", oauthConf.ClientID)
	parameters.Add("scope", strings.Join(oauthConf.Scopes, " "))
	parameters.Add("redirect_uri", oauthConf.RedirectURL)
	parameters.Add("response_type", "code")
	parameters.Add("state", oauthStateString)
	URL.RawQuery = parameters.Encode()
	url := URL.String()
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func (s *Server) handleFacebookCallback(w http.ResponseWriter, r *http.Request) {
	state := r.FormValue("state")
	if state != oauthStateString {
		fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	code := r.FormValue("code")

	token, err := oauthConf.Exchange(oauth2.NoContext, code)
	if err != nil {
		fmt.Printf("oauthConf.Exchange() failed with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	fbURL := "https://graph.facebook.com/me?fields=id,name,email&access_token="
	escToken := url.QueryEscape(token.AccessToken)
	resp, err := http.Get(fbURL + escToken)
	if err != nil {
		fmt.Printf("Get: %s\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}
	defer resp.Body.Close()

	response, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("ReadAll: %s\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	log.Printf("parseResponseBody: %s\n", string(response))
	req := &FacebookAuthResponse{
		Token: escToken,
	}

	err = json.Unmarshal(response, req)
	if err != nil {
		log.Print(err)
		return
	}

	print(req)

	p := CreateFromFacebook(s, req)
	v := url.Values{}
	v.Add("id", p.ID)
	v.Add("name", p.Name)
	v.Add("nick", p.Nick)

	lastURL := "/facebook/finalize?" + v.Encode()
	http.Redirect(w, r, lastURL, http.StatusTemporaryRedirect)
}

// handleFacebookRegister gets the POST from the user that saves the final
// state of Facebook registration
func (s *Server) handleFacebookRegister(w http.ResponseWriter, r *http.Request) {
	var req FacebookJoinRequest

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print(err)
		return
	}

	err = json.Unmarshal(body, &req)
	if err != nil {
		log.Print(err)
		return
	}
	log.Print(req)

	p := s.DB.GetPerson(req.ID)
	p.UpdatePerson(&req)
	s.DB.SavePerson(p)
	log.Printf("%s has joined DrunkenFall!", req.Name)

	// Set the cookie of the userlevel for also glory
	c := &http.Cookie{
		Name:    "userlevel",
		Value:   strconv.Itoa(p.Userlevel),
		Path:    "/",
		Expires: time.Now().Add(30 * 24 * time.Hour), // Set to the same as CookieStore
	}
	http.SetCookie(w, c)

	// Set the session for great glory - only used by the frontend
	session, _ := CookieStore.Get(r, "session")
	session.Values["user"] = p.ID
	session.Values["userlevel"] = p.Userlevel
	session.Save(r, w)

	s.Redirect(w, "/")
}

// FacebookRouter builds the paths for Facebook handling
func (s *Server) FacebookRouter(r *mux.Router) {
	r.HandleFunc("/facebook/login", s.handleFacebookLogin)
	r.HandleFunc("/facebook/oauth2callback", s.handleFacebookCallback)
	r.HandleFunc("/facebook/register", s.handleFacebookRegister)
}
<commit_msg>Differentiate FB test and prod<commit_after>package main

// Based on http://stackoverflow.com/a/36672164

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"

	"encoding/json"
	"github.com/gorilla/mux"
	"github.com/nu7hatch/gouuid"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/facebook"
	"os"
	"strconv"
	"time"
)

var (
	oauthConf        *oauth2.Config
	oauthStateString string
)

// FacebookAuthResponse is the data we get back on the initial auth
type FacebookAuthResponse struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
	Token string `json:"access_token"`
}

// FacebookJoinRequest is the request to join the showdown <3
type FacebookJoinRequest struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Nick  string `json:"nick"`
	Color string `json:"color"`
}

func init() {
	// You need to have these three env vars in your env for Facebook to work.
	// If you need access to them, talk to @thiderman.
	oauthConf = &oauth2.Config{
		ClientID:     os.Getenv("DF_FB_ID"),
		ClientSecret: os.Getenv("DF_FB_SECRET"),
		RedirectURL:  os.Getenv("DF_FB_CALLBACK"),
		Scopes:       []string{"public_profile", "email"},
		Endpoint:     facebook.Endpoint,
	}

	if os.Getenv("DF_FB_ID") != "" {
		if strings.Contains(oauthConf.RedirectURL, "localhost") {
			log.Print("Facebook dev configuration loaded.")
		} else {
			log.Print("Facebook configuration loaded.")
		}
	} else {
		log.Print("Facebook app configuration missing. Auth will not work.")
	}

	randomUUID, err := uuid.NewV4()
	if err != nil {
		log.Fatal(err)
	}

	oauthStateString = randomUUID.String()
}

const htmlIndex = `
Log in with facebook

`

func (s *Server) handleMain(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(htmlIndex))
}

func (s *Server) handleFacebookLogin(w http.ResponseWriter, r *http.Request) {
	URL, err := url.Parse(oauthConf.Endpoint.AuthURL)
	if err != nil {
		log.Fatal("Parse: ", err)
	}
	parameters := url.Values{}
	parameters.Add("client_id", oauthConf.ClientID)
	parameters.Add("scope", strings.Join(oauthConf.Scopes, " "))
	parameters.Add("redirect_uri", oauthConf.RedirectURL)
	parameters.Add("response_type", "code")
	parameters.Add("state", oauthStateString)
	URL.RawQuery = parameters.Encode()
	url := URL.String()
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func (s *Server) handleFacebookCallback(w http.ResponseWriter, r *http.Request) {
	state := r.FormValue("state")
	if state != oauthStateString {
		fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	code := r.FormValue("code")

	token, err := oauthConf.Exchange(oauth2.NoContext, code)
	if err != nil {
		fmt.Printf("oauthConf.Exchange() failed with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	fbURL := "https://graph.facebook.com/me?fields=id,name,email&access_token="
	escToken := url.QueryEscape(token.AccessToken)
	resp, err := http.Get(fbURL + escToken)
	if err != nil {
		fmt.Printf("Get: %s\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}
	defer resp.Body.Close()

	response, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("ReadAll: %s\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	log.Printf("parseResponseBody: %s\n", string(response))
	req := &FacebookAuthResponse{
		Token: escToken,
	}

	err = json.Unmarshal(response, req)
	if err != nil {
		log.Print(err)
		return
	}

	print(req)

	p := CreateFromFacebook(s, req)
	v := url.Values{}
	v.Add("id", p.ID)
	v.Add("name", p.Name)
	v.Add("nick", p.Nick)

	lastURL := "/facebook/finalize?" + v.Encode()
	http.Redirect(w, r, lastURL, http.StatusTemporaryRedirect)
}

// handleFacebookRegister gets the POST from the user that saves the final
// state of Facebook registration
func (s *Server) handleFacebookRegister(w http.ResponseWriter, r *http.Request) {
	var req FacebookJoinRequest

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print(err)
		return
	}

	err = json.Unmarshal(body, &req)
	if err != nil {
		log.Print(err)
		return
	}
	log.Print(req)

	p := s.DB.GetPerson(req.ID)
	p.UpdatePerson(&req)
	s.DB.SavePerson(p)
	log.Printf("%s has joined DrunkenFall!", req.Name)

	// Set the cookie of the userlevel for also glory
	c := &http.Cookie{
		Name:    "userlevel",
		Value:   strconv.Itoa(p.Userlevel),
		Path:    "/",
		Expires: time.Now().Add(30 * 24 * time.Hour), // Set to the same as CookieStore
	}
	http.SetCookie(w, c)

	// Set the session for great glory - only used by the frontend
	session, _ := CookieStore.Get(r, "session")
	session.Values["user"] = p.ID
	session.Values["userlevel"] = p.Userlevel
	session.Save(r, w)

	s.Redirect(w, "/")
}

// FacebookRouter builds the paths for Facebook handling
func (s *Server) FacebookRouter(r *mux.Router) {
	r.HandleFunc("/facebook/login", s.handleFacebookLogin)
	r.HandleFunc("/facebook/oauth2callback", s.handleFacebookCallback)
	r.HandleFunc("/facebook/register", s.handleFacebookRegister)
}
<|endoftext|>


<commit_before>// Copyright 2016, RadiantBlue Technologies, Inc.
//
// 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 logger

import (
	"log"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"github.com/venicegeo/pz-gocommon/elasticsearch"
	piazza "github.com/venicegeo/pz-gocommon/gocommon"
)

const MOCKING = true

func sleep() {
	time.Sleep(1 * time.Second)
}

type LoggerTester struct {
	suite.Suite

	esi    elasticsearch.IIndex
	sys    *piazza.SystemConfig
	client IClient

	genericServer *piazza.GenericServer
}

func (suite *LoggerTester) setupFixture() {
	t := suite.T()
	assert := assert.New(t)

	var required []piazza.ServiceName
	if MOCKING {
		required = []piazza.ServiceName{}
	} else {
		required = []piazza.ServiceName{piazza.PzElasticSearch}
	}
	sys, err := piazza.NewSystemConfig(piazza.PzLogger, required)
	assert.NoError(err)
	suite.sys = sys

	esi, err := elasticsearch.NewIndexInterface(sys, "loggertest$", "", MOCKING)
	assert.NoError(err)
	suite.esi = esi

	if MOCKING {
		client, err := NewMockClient(sys)
		assert.NoError(err)
		suite.client = client
	} else {
		client, err := NewClient(sys)
		assert.NoError(err)
		suite.client = client
	}

	loggerService := &LoggerService{}
	err = loggerService.Init(sys, esi)
	assert.NoError(err)

	loggerServer := &LoggerServer{}
	loggerServer.Init(loggerService)

	suite.genericServer = &piazza.GenericServer{Sys: sys}

	err = suite.genericServer.Configure(loggerServer.Routes)
	if err != nil {
		log.Fatal(err)
	}

	_, err = suite.genericServer.Start()
	assert.NoError(err)
}

func (suite *LoggerTester) teardownFixture() {
	suite.genericServer.Stop()

	suite.esi.Close()
	suite.esi.Delete()
}

func TestRunSuite(t *testing.T) {
	s := &LoggerTester{}
	suite.Run(t, s)
}

func (suite *LoggerTester) getLastMessage() string {
	t := suite.T()
	assert := assert.New(t)

	client := suite.client

	format := elasticsearch.QueryFormat{Size: 100, From: 0, Order: elasticsearch.SortAscending, Key: "CreatedOn"}
	ms, err := client.GetFromMessages(format, map[string]string{})
	assert.NoError(err)
	assert.True(len(ms) > 0)

	return ms[len(ms)-1].String()
}

func (suite *LoggerTester) Test00Time() {
	t := suite.T()
	assert := assert.New(t)

	a := "2006-01-02T15:04:05+07:00"
	b, err := time.Parse(time.RFC3339, a)
	assert.NoError(err)
	c := b.Format(time.RFC3339)
	assert.EqualValues(a, c)
}

func (suite *LoggerTester) Test01Elasticsearch() {
	t := suite.T()
	assert := assert.New(t)

	suite.setupFixture()
	defer suite.teardownFixture()

	version := suite.esi.GetVersion()
	assert.Contains("2.2.0", version)
}

func (suite *LoggerTester) Test02One() {
	t := suite.T()
	assert := assert.New(t)

	suite.setupFixture()
	defer suite.teardownFixture()

	client := suite.client

	var err error

	data1 := Message{
		Service:   "log-tester",
		Address:   "128.1.2.3",
		CreatedOn: time.Now(),
		Severity:  "Info",
		Message:   "The quick brown fox",
	}

	data2 := Message{
		Service:   "log-tester",
		Address:   "128.0.0.0",
		CreatedOn: time.Now(),
		Severity:  "Fatal",
		Message:   "The quick brown fox",
	}

	{
		err = client.LogMessage(&data1)
		assert.NoError(err, "PostToMessages")
	}

	sleep()

	{
		actualMssg := suite.getLastMessage()
		expectedMssg := data1.String()
		assert.EqualValues(actualMssg, expectedMssg)
	}

	{
		err = client.LogMessage(&data2)
		assert.NoError(err, "PostToMessages")
	}

	sleep()

	{
		actualMssg := suite.getLastMessage()
		expectedMssg := data2.String()
		assert.EqualValues(actualMssg, expectedMssg)
	}

	{
		stats, err := client.GetFromAdminStats()
		assert.NoError(err, "GetFromAdminStats")
		assert.Equal(2, stats.NumMessages, "stats check")
	}
}

func (suite *LoggerTester) Test03Help() {
	t := suite.T()
	assert := assert.New(t)

	err := suite.client.Log("mocktest", "0.0.0.0", SeverityInfo, time.Now(), "message from logger unit test via piazza.Log()")
	assert.NoError(err, "pzService.Log()")
}

func (suite *LoggerTester) Test04Admin() {
	t := suite.T()
	assert := assert.New(t)

	suite.setupFixture()
	defer suite.teardownFixture()

	client := suite.client

	_, err := client.GetFromAdminStats()
	assert.NoError(err, "GetFromAdminStats")
}

func (suite *LoggerTester) Test05Pagination() {
	t := suite.T()
	assert := assert.New(t)

	if MOCKING {
		//t.Skip("Skipping test, because mocking.")
	}

	suite.setupFixture()
	defer suite.teardownFixture()

	client := suite.client

	client.SetService("myservice", "1.2.3.4")

	err := client.Debug("d")
	assert.NoError(err)
	sleep()
	err = client.Info("i")
	assert.NoError(err)
	sleep()
	err = client.Warn("w")
	assert.NoError(err)
	sleep()
	err = client.Error("e")
	assert.NoError(err)
	sleep()
	err = client.Fatal("f")
	assert.NoError(err)
	sleep()

	format := elasticsearch.QueryFormat{Size: 1, From: 0, Key: "CreatedOn", Order: elasticsearch.SortAscending}
	ms, err := client.GetFromMessages(format, map[string]string{})
	assert.NoError(err)
	assert.Len(ms, 1)
	assert.EqualValues(SeverityDebug, ms[0].Severity)

	format = elasticsearch.QueryFormat{Size: 5, From: 0, Key: "CreatedOn", Order: elasticsearch.SortAscending}
	ms, err = client.GetFromMessages(format, map[string]string{})
	assert.NoError(err)
	assert.Len(ms, 5)
	assert.EqualValues(SeverityFatal, ms[4].Severity)

	format = elasticsearch.QueryFormat{Size: 3, From: 2, Key: "CreatedOn", Order: elasticsearch.SortDescending}
	ms, err = client.GetFromMessages(format, map[string]string{})
	assert.NoError(err)
	assert.Len(ms, 3)
	assert.EqualValues(SeverityWarning, ms[2].Severity)
	assert.EqualValues(SeverityError, ms[1].Severity)
	assert.EqualValues(SeverityFatal, ms[0].Severity)
}

func (suite *LoggerTester) Test06OtherParams() {
	t := suite.T()
	assert := assert.New(t)

	if MOCKING {
		t.Skip("Skipping test, because mocking.")
	}

	suite.setupFixture()
	defer suite.teardownFixture()

	client := suite.client

	client.SetService("myservice", "1.2.3.4")

	sometime := time.Now()

	var testData = []Message{
		{
			Address:   "gnemud7smfv/10.254.0.66",
			Message:   "Received Message to Relay on topic Request-Job with key f3b63085-b482-4ae8-8297-3c7d1fcfff7d",
			Service:   "Dispatcher",
			Severity:  "Info",
			CreatedOn: sometime,
		}, {
			Address:   "gnfbnqsn5m9/10.254.0.14",
			Message:   "Processed Update Status for Job 6d0ea538-4382-4ea5-9669-56519b8c8f58 with Status Success",
			Service:   "JobManager",
			Severity:  "Info",
			CreatedOn: sometime,
		}, {
			Address:   "0.0.0.0",
			Message:   "generated 1: 09d4ec60-ea61-4066-8697-5568a47f84bf",
			Service:   "pz-uuidgen",
			Severity:  "Info",
			CreatedOn: sometime,
		}, {
			Address:   "gnfbnqsn5m9/10.254.0.14",
			Message:   "Handling Job with Topic Create-Job for Job ID 09d4ec60-ea61-4066-8697-5568a47f84bf",
			Service:   "JobManager",
			Severity:  "Info",
			CreatedOn: sometime,
		}, {
			Address:   "gnfbnqsn5m9/10.254.0.14",
			Message:   "Handling Job with Topic Update-Job for Job ID be4ce034-1187-4a4f-95a9-a9c31826248b",
			Service:   "JobManager",
			Severity:  "Info",
			CreatedOn: sometime,
		},
	}

	for _, e := range testData {
		// log.Printf("%d, %v\n", i, e)
		err := client.LogMessage(&e)
		assert.NoError(err)
	}

	sleep()

	format := elasticsearch.QueryFormat{
		Size: 100, From: 0,
		Order: elasticsearch.SortDescending,
		Key:   "CreatedOn"}

	msgs, err := client.GetFromMessages(format,
		map[string]string{
			"service":  "JobManager",
			"contains": "Success",
		})
	assert.NoError(err)
	assert.Len(msgs, 1)

	//for _, msg := range msgs {
	//log.Printf("%v\n", msg)
	//}

	msgs, err = client.GetFromMessages(format,
		map[string]string{
			"before": "1461181461",
			"after":  "1461181362",
		})

	assert.NoError(err)
	assert.Len(msgs, 4)

	//for _, msg := range msgs {
	//log.Printf("%v\n", msg)
	//}

}
<commit_msg>missed commit<commit_after>// Copyright 2016, RadiantBlue Technologies, Inc.
//
// 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 logger

import (
	"log"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"github.com/venicegeo/pz-gocommon/elasticsearch"
	piazza "github.com/venicegeo/pz-gocommon/gocommon"
)

const MOCKING = true

func sleep() {
	time.Sleep(1 * time.Second)
}

type LoggerTester struct {
	suite.Suite

	esi    elasticsearch.IIndex
	sys    *piazza.SystemConfig
	client IClient

	genericServer *piazza.GenericServer
}

func (suite *LoggerTester) setupFixture() {
	t := suite.T()
	assert := assert.New(t)

	var required []piazza.ServiceName
	if MOCKING {
		required = []piazza.ServiceName{}
	} else {
		required = []piazza.ServiceName{piazza.PzElasticSearch}
	}
	sys, err := piazza.NewSystemConfig(piazza.PzLogger, required)
	assert.NoError(err)
	suite.sys = sys

	esi, err := elasticsearch.NewIndexInterface(sys, "loggertest$", "", MOCKING)
	assert.NoError(err)
	suite.esi = esi

	if MOCKING {
		client, err := NewMockClient(sys)
		assert.NoError(err)
		suite.client = client
	} else {
		client, err := NewClient(sys)
		assert.NoError(err)
		suite.client = client
	}

	loggerService := &LoggerService{}
	err = loggerService.Init(sys, esi)
	assert.NoError(err)

	loggerServer := &LoggerServer{}
	loggerServer.Init(loggerService)

	suite.genericServer = &piazza.GenericServer{Sys: sys}

	err = suite.genericServer.Configure(loggerServer.Routes)
	if err != nil {
		log.Fatal(err)
	}

	_, err = suite.genericServer.Start()
	assert.NoError(err)
}

func (suite *LoggerTester) teardownFixture() {
	suite.genericServer.Stop()

	suite.esi.Close()
	suite.esi.Delete()
}

func TestRunSuite(t *testing.T) {
	s := &LoggerTester{}
	suite.Run(t, s)
}

func (suite *LoggerTester) getLastMessage() string {
	t := suite.T()
	assert := assert.New(t)

	client := suite.client

	format := elasticsearch.QueryFormat{Size: 100, From: 0, Order: elasticsearch.SortAscending, Key: "CreatedOn"}
	ms, err := client.GetMessages(format, map[string]string{})
	assert.NoError(err)
	assert.True(len(ms) > 0)

	return ms[len(ms)-1].String()
}

func (suite *LoggerTester) Test00Time() {
	t := suite.T()
	assert := assert.New(t)

	a := "2006-01-02T15:04:05+07:00"
	b, err := time.Parse(time.RFC3339, a)
	assert.NoError(err)
	c := b.Format(time.RFC3339)
	assert.EqualValues(a, c)
}

func (suite *LoggerTester) Test01Elasticsearch() {
	t := suite.T()
	assert := assert.New(t)

	suite.setupFixture()
	defer suite.teardownFixture()

	version := suite.esi.GetVersion()
	assert.Contains("2.2.0", version)
}

func (suite *LoggerTester) Test02One() {
	t := suite.T()
	assert := assert.New(t)

	suite.setupFixture()
	defer suite.teardownFixture()

	client := suite.client

	var err error

	data1 := Message{
		Service:   "log-tester",
		Address:   "128.1.2.3",
		CreatedOn: time.Now(),
		Severity:  "Info",
		Message:   "The quick brown fox",
	}

	data2 := Message{
		Service:   "log-tester",
		Address:   "128.0.0.0",
		CreatedOn: time.Now(),
		Severity:  "Fatal",
		Message:   "The quick brown fox",
	}

	{
		err = client.PostMessage(&data1)
		assert.NoError(err, "PostToMessages")
	}

	sleep()

	{
		actualMssg := suite.getLastMessage()
		expectedMssg := data1.String()
		assert.EqualValues(actualMssg, expectedMssg)
	}

	{
		err = client.PostMessage(&data2)
		assert.NoError(err, "PostToMessages")
	}

	sleep()

	{
		actualMssg := suite.getLastMessage()
		expectedMssg := data2.String()
		assert.EqualValues(actualMssg, expectedMssg)
	}

	{
		stats, err := client.GetStats()
		assert.NoError(err, "GetFromAdminStats")
		assert.Equal(2, stats.NumMessages, "stats check")
	}
}

func (suite *LoggerTester) Test03Help() {
	t := suite.T()
	assert := assert.New(t)

	err := suite.client.PostLog("mocktest", "0.0.0.0", SeverityInfo, time.Now(), "message from logger unit test via piazza.Log()")
	assert.NoError(err, "pzService.Log()")
}

func (suite *LoggerTester) Test04Admin() {
	t := suite.T()
	assert := assert.New(t)

	suite.setupFixture()
	defer suite.teardownFixture()

	client := suite.client

	_, err := client.GetStats()
	assert.NoError(err, "GetFromAdminStats")
}

func (suite *LoggerTester) Test05Pagination() {
	t := suite.T()
	assert := assert.New(t)

	if MOCKING {
		//t.Skip("Skipping test, because mocking.")
	}

	suite.setupFixture()
	defer suite.teardownFixture()

	client := suite.client

	client.SetService("myservice", "1.2.3.4")

	err := client.Debug("d")
	assert.NoError(err)
	sleep()
	err = client.Info("i")
	assert.NoError(err)
	sleep()
	err = client.Warn("w")
	assert.NoError(err)
	sleep()
	err = client.Error("e")
	assert.NoError(err)
	sleep()
	err = client.Fatal("f")
	assert.NoError(err)
	sleep()

	format := elasticsearch.QueryFormat{Size: 1, From: 0, Key: "CreatedOn", Order: elasticsearch.SortAscending}
	ms, err := client.GetMessages(format, map[string]string{})
	assert.NoError(err)
	assert.Len(ms, 1)
	assert.EqualValues(SeverityDebug, ms[0].Severity)

	format = elasticsearch.QueryFormat{Size: 5, From: 0, Key: "CreatedOn", Order: elasticsearch.SortAscending}
	ms, err = client.GetMessages(format, map[string]string{})
	assert.NoError(err)
	assert.Len(ms, 5)
	assert.EqualValues(SeverityFatal, ms[4].Severity)

	format = elasticsearch.QueryFormat{Size: 3, From: 2, Key: "CreatedOn", Order: elasticsearch.SortDescending}
	ms, err = client.GetMessages(format, map[string]string{})
	assert.NoError(err)
	assert.Len(ms, 3)
	assert.EqualValues(SeverityWarning, ms[2].Severity)
	assert.EqualValues(SeverityError, ms[1].Severity)
	assert.EqualValues(SeverityFatal, ms[0].Severity)
}

func (suite *LoggerTester) Test06OtherParams() {
	t := suite.T()
	assert := assert.New(t)

	if MOCKING {
		t.Skip("Skipping test, because mocking.")
	}

	suite.setupFixture()
	defer suite.teardownFixture()

	client := suite.client

	client.SetService("myservice", "1.2.3.4")

	sometime := time.Now()

	var testData = []Message{
		{
			Address:   "gnemud7smfv/10.254.0.66",
			Message:   "Received Message to Relay on topic Request-Job with key f3b63085-b482-4ae8-8297-3c7d1fcfff7d",
			Service:   "Dispatcher",
			Severity:  "Info",
			CreatedOn: sometime,
		}, {
			Address:   "gnfbnqsn5m9/10.254.0.14",
			Message:   "Processed Update Status for Job 6d0ea538-4382-4ea5-9669-56519b8c8f58 with Status Success",
			Service:   "JobManager",
			Severity:  "Info",
			CreatedOn: sometime,
		}, {
			Address:   "0.0.0.0",
			Message:   "generated 1: 09d4ec60-ea61-4066-8697-5568a47f84bf",
			Service:   "pz-uuidgen",
			Severity:  "Info",
			CreatedOn: sometime,
		}, {
			Address:   "gnfbnqsn5m9/10.254.0.14",
			Message:   "Handling Job with Topic Create-Job for Job ID 09d4ec60-ea61-4066-8697-5568a47f84bf",
			Service:   "JobManager",
			Severity:  "Info",
			CreatedOn: sometime,
		}, {
			Address:   "gnfbnqsn5m9/10.254.0.14",
			Message:   "Handling Job with Topic Update-Job for Job ID be4ce034-1187-4a4f-95a9-a9c31826248b",
			Service:   "JobManager",
			Severity:  "Info",
			CreatedOn: sometime,
		},
	}

	for _, e := range testData {
		// log.Printf("%d, %v\n", i, e)
		err := client.PostMessage(&e)
		assert.NoError(err)
	}

	sleep()

	format := elasticsearch.QueryFormat{
		Size: 100, From: 0,
		Order: elasticsearch.SortDescending,
		Key:   "CreatedOn"}

	msgs, err := client.GetMessages(format,
		map[string]string{
			"service":  "JobManager",
			"contains": "Success",
		})
	assert.NoError(err)
	assert.Len(msgs, 1)

	//for _, msg := range msgs {
	//log.Printf("%v\n", msg)
	//}

	msgs, err = client.GetMessages(format,
		map[string]string{
			"before": "1461181461",
			"after":  "1461181362",
		})

	assert.NoError(err)
	assert.Len(msgs, 4)

	//for _, msg := range msgs {
	//log.Printf("%v\n", msg)
	//}

}
<|endoftext|>


<commit_before>package parse

import (
	"fmt"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/execdriver"
	"github.com/docker/engine-api/types"
	"github.com/opencontainers/runc/libcontainer/user"
	"github.com/opencontainers/specs/specs-go"
)

const (
	// SpecVersion is the version of the opencontainers spec that will be created.
	SpecVersion = "0.3.0"

	// DefaultApparmorProfile is docker engine's default apparmor profile for containers.
	DefaultApparmorProfile = "docker-default"

	// DefaultCurrentWorkingDirectory defines the default directory the container
	// will enter in, if not already specified by the user.
	DefaultCurrentWorkingDirectory = "/"

	// DefaultTerminal is the default TERM for containers.
	DefaultTerminal = "xterm"

	// DefaultUserNSHostID is the default start mapped host id for userns.
	DefaultUserNSHostID = 886432
	// DefaultUserNSMapSize is the default size for the uid and gid mappings for userns.
	DefaultUserNSMapSize = 46578392
)

var (
	// DefaultTerminalEnv holds the minimum terminal env vars needed for an
	// interactive session.
	DefaultTerminalEnv = []string{
		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
	}

	// DefaultMounts are the default mounts for a container.
	DefaultMounts = []specs.Mount{
		{
			Destination: "/proc",
			Type:        "proc",
			Source:      "proc",
			Options:     nil,
		},
		{
			Destination: "/dev",
			Type:        "tmpfs",
			Source:      "tmpfs",
			Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
		},
		{
			Destination: "/dev/pts",
			Type:        "devpts",
			Source:      "devpts",
			Options:     []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"},
		},
		{
			Destination: "/dev/shm",
			Type:        "tmpfs",
			Source:      "shm",
			Options:     []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
		},
		{
			Destination: "/dev/mqueue",
			Type:        "mqueue",
			Source:      "mqueue",
			Options:     []string{"nosuid", "noexec", "nodev"},
		},
		{
			Destination: "/sys",
			Type:        "sysfs",
			Source:      "sysfs",
			Options:     []string{"nosuid", "noexec", "nodev"},
		},
		{
			Destination: "/sys/fs/cgroup",
			Type:        "cgroup",
			Source:      "cgroup",
			Options:     []string{"nosuid", "noexec", "nodev", "relatime"},
		},
	}

	// NetworkMounts are the mounts needed for default networking.
	NetworkMounts = []specs.Mount{
		{
			Destination: "/etc/hosts",
			Type:        "bind",
			Source:      "/etc/hosts",
			Options:     []string{"rbind", "rprivate", "ro"},
		},
		{
			Destination: "/etc/resolv.conf",
			Type:        "bind",
			Source:      "/etc/resolv.conf",
			Options:     []string{"rbind", "rprivate", "ro"},
		},
	}
)

// Config takes ContainerJSON and converts it into the opencontainers spec.
func Config(c types.ContainerJSON, osType, architecture string, capabilities []string, idroot, idlen uint32) (config *specs.Spec, err error) {
	// for user namespaces use defaults unless another range specified
	if idroot == 0 {
		idroot = DefaultUserNSHostID
	}
	if idlen == 0 {
		idlen = DefaultUserNSMapSize
	}
	config = &specs.Spec{
		Version: SpecVersion,
		Platform: specs.Platform{
			OS:   osType,
			Arch: architecture,
		},
		Process: specs.Process{
			Terminal: c.Config.Tty,
			User:     specs.User{
			// TODO: user stuffs
			},
			Args: append([]string{c.Path}, c.Args...),
			Env:  c.Config.Env,
			Cwd:  c.Config.WorkingDir,
			// TODO: add parsing of Ulimits
			Rlimits: []specs.Rlimit{
				{
					Type: "RLIMIT_NOFILE",
					Hard: uint64(1024),
					Soft: uint64(1024),
				},
			},
			NoNewPrivileges: true,
			ApparmorProfile: c.AppArmorProfile,
		},
		Root: specs.Root{
			Path:     "rootfs",
			Readonly: c.HostConfig.ReadonlyRootfs,
		},
		Mounts: []specs.Mount{},
		Linux: specs.Linux{
			Namespaces: []specs.Namespace{
				{
					Type: "ipc",
				},
				{
					Type: "uts",
				},
				{
					Type: "mount",
				},
			},
			UIDMappings: []specs.IDMapping{
				{
					ContainerID: 0,
					HostID:      idroot,
					Size:        idlen,
				},
			},
			GIDMappings: []specs.IDMapping{
				{
					ContainerID: 0,
					HostID:      idroot,
					Size:        idlen,
				},
			},
			Resources: &specs.Resources{
				Devices: []specs.DeviceCgroup{
					{
						Allow:  false,
						Access: sPtr("rwm"),
					},
				},
				DisableOOMKiller: c.HostConfig.Resources.OomKillDisable,
				OOMScoreAdj:      &c.HostConfig.OomScoreAdj,
				Memory: &specs.Memory{
					Limit:       uint64ptr(c.HostConfig.Resources.Memory),
					Reservation: uint64ptr(c.HostConfig.Resources.MemoryReservation),
					Swap:        uint64ptr(c.HostConfig.Resources.MemorySwap),
					Swappiness:  uint64ptr(*c.HostConfig.Resources.MemorySwappiness),
					Kernel:      uint64ptr(c.HostConfig.Resources.KernelMemory),
				},
				CPU: &specs.CPU{
					Shares: uint64ptr(c.HostConfig.Resources.CPUShares),
					Quota:  uint64ptr(c.HostConfig.Resources.CPUQuota),
					Period: uint64ptr(c.HostConfig.Resources.CPUPeriod),
					Cpus:   &c.HostConfig.Resources.CpusetCpus,
					Mems:   &c.HostConfig.Resources.CpusetMems,
				},
				Pids: &specs.Pids{
					Limit: &c.HostConfig.Resources.PidsLimit,
				},
				BlockIO: &specs.BlockIO{
					Weight: &c.HostConfig.Resources.BlkioWeight,
					// TODO: add parsing for Throttle/Weight Devices
				},
			},
			RootfsPropagation: "",
		},
	}

	// make sure the current working directory is not blank
	if config.Process.Cwd == "" {
		config.Process.Cwd = DefaultCurrentWorkingDirectory
	}

	// get the user
	if c.Config.User != "" {
		u, err := user.LookupUser(c.Config.User)
		if err != nil {
			config.Process.User = specs.User{
				UID: uint32(u.Uid),
				GID: uint32(u.Gid),
			}
		} else {
			//return nil, fmt.Errorf("Looking up user (%s) failed: %v", c.Config.User, err)
			logrus.Warnf("Looking up user (%s) failed: %v", c.Config.User, err)
		}
	}
	// add the additional groups
	for _, group := range c.HostConfig.GroupAdd {
		g, err := user.LookupGroup(group)
		if err != nil {
			return nil, fmt.Errorf("Looking up group (%s) failed: %v", group, err)
		}
		config.Process.User.AdditionalGids = append(config.Process.User.AdditionalGids, uint32(g.Gid))
	}

	// get the hostname, if the hostname is the name as the first 12 characters of the id,
	// then set the hostname as the container name
	if c.ID[:12] == c.Config.Hostname {
		config.Hostname = strings.TrimPrefix(c.Name, "/")
	}

	// set privileged
	if c.HostConfig.Privileged {
		// allow all caps
		capabilities = execdriver.GetAllCapabilities()
	}

	// get the capabilities
	config.Process.Capabilities, err = execdriver.TweakCapabilities(capabilities, c.HostConfig.CapAdd, c.HostConfig.CapDrop)
	if err != nil {
		return nil, fmt.Errorf("setting capabilities failed: %v", err)
	}

	// add CAP_ prefix
	// TODO: this is awful
	for i, cap := range config.Process.Capabilities {
		if !strings.HasPrefix(cap, "CAP_") {
			config.Process.Capabilities[i] = fmt.Sprintf("CAP_%s", cap)
		}
	}

	// if we have a container that needs a terminal but no env vars, then set
	// default env vars for the terminal to function
	if config.Process.Terminal && len(config.Process.Env) <= 0 {
		config.Process.Env = DefaultTerminalEnv
	}
	if config.Process.Terminal {
		// make sure we have TERM set
		var termSet bool
		for _, env := range config.Process.Env {
			if strings.HasPrefix(env, "TERM=") {
				termSet = true
				break
			}
		}
		if !termSet {
			// set the term variable
			config.Process.Env = append(config.Process.Env, fmt.Sprintf("TERM=%s", DefaultTerminal))
		}
	}

	// check namespaces
	if !c.HostConfig.NetworkMode.IsHost() {
		config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{
			Type: "network",
		})
	}
	if !c.HostConfig.PidMode.IsHost() {
		config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{
			Type: "pid",
		})
	}
	if !c.HostConfig.NetworkMode.IsHost() && !c.HostConfig.PidMode.IsHost() && !c.HostConfig.Privileged {
		config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{
			Type: "user",
		})
	} else {
		// reset uid and gid mappings
		config.Linux.UIDMappings = []specs.IDMapping{}
		config.Linux.GIDMappings = []specs.IDMapping{}
	}

	// get mounts
	mounts := map[string]bool{}
	for _, mount := range c.Mounts {
		mounts[mount.Destination] = true
		var opt []string
		if mount.RW {
			opt = append(opt, "rw")
		}
		if mount.Mode != "" {
			opt = append(opt, mount.Mode)
		}
		opt = append(opt, []string{"rbind", "rprivate"}...)

		config.Mounts = append(config.Mounts, specs.Mount{
			Destination: mount.Destination,
			Type:        "bind",
			Source:      mount.Source,
			Options:     opt,
		})
	}

	// add /etc/hosts and /etc/resolv.conf if we should have networking
	if c.HostConfig.NetworkMode != "none" && c.HostConfig.NetworkMode != "host" {
		DefaultMounts = append(DefaultMounts, NetworkMounts...)
	}

	// if we aren't doing something crazy like mounting a default mount ourselves,
	// the we can mount it the default way
	for _, mount := range DefaultMounts {
		if _, ok := mounts[mount.Destination]; !ok {
			config.Mounts = append(config.Mounts, mount)
		}
	}

	// fix default mounts for cgroups and devpts without user namespaces
	// see: https://github.com/opencontainers/runc/issues/225#issuecomment-136519577
	if len(config.Linux.UIDMappings) == 0 {
		for k, mount := range config.Mounts {
			switch mount.Destination {
			case "/sys/fs/cgroup":
				config.Mounts[k].Options = append(config.Mounts[k].Options, "ro")
			case "/dev/pts":
				config.Mounts[k].Options = append(config.Mounts[k].Options, "gid=5")
			}
		}
	}

	// parse additional groups and add them to gid mappings
	if err := parseMappings(config, c.HostConfig); err != nil {
		return nil, err
	}

	// parse devices
	if err := parseDevices(config, c.HostConfig); err != nil {
		return nil, err
	}

	// parse security opt
	if err := parseSecurityOpt(config, c.HostConfig); err != nil {
		return nil, err
	}

	// set privileged
	if c.HostConfig.Privileged {
		if !c.HostConfig.ReadonlyRootfs {
			// clear readonly for cgroup
			//	config.Mounts["cgroup"] = DefaultMountpoints["cgroup"]
		}
	}

	return config, nil
}
<commit_msg>Only addd userns if has a valid config (#9)<commit_after>package parse

import (
	"fmt"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/execdriver"
	"github.com/docker/engine-api/types"
	"github.com/opencontainers/runc/libcontainer/user"
	"github.com/opencontainers/specs/specs-go"
)

const (
	// SpecVersion is the version of the opencontainers spec that will be created.
	SpecVersion = "0.3.0"

	// DefaultApparmorProfile is docker engine's default apparmor profile for containers.
	DefaultApparmorProfile = "docker-default"

	// DefaultCurrentWorkingDirectory defines the default directory the container
	// will enter in, if not already specified by the user.
	DefaultCurrentWorkingDirectory = "/"

	// DefaultTerminal is the default TERM for containers.
	DefaultTerminal = "xterm"

	// DefaultUserNSHostID is the default start mapped host id for userns.
	DefaultUserNSHostID = 886432
	// DefaultUserNSMapSize is the default size for the uid and gid mappings for userns.
	DefaultUserNSMapSize = 46578392
)

var (
	// DefaultTerminalEnv holds the minimum terminal env vars needed for an
	// interactive session.
	DefaultTerminalEnv = []string{
		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
	}

	// DefaultMounts are the default mounts for a container.
	DefaultMounts = []specs.Mount{
		{
			Destination: "/proc",
			Type:        "proc",
			Source:      "proc",
			Options:     nil,
		},
		{
			Destination: "/dev",
			Type:        "tmpfs",
			Source:      "tmpfs",
			Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
		},
		{
			Destination: "/dev/pts",
			Type:        "devpts",
			Source:      "devpts",
			Options:     []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"},
		},
		{
			Destination: "/dev/shm",
			Type:        "tmpfs",
			Source:      "shm",
			Options:     []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
		},
		{
			Destination: "/dev/mqueue",
			Type:        "mqueue",
			Source:      "mqueue",
			Options:     []string{"nosuid", "noexec", "nodev"},
		},
		{
			Destination: "/sys",
			Type:        "sysfs",
			Source:      "sysfs",
			Options:     []string{"nosuid", "noexec", "nodev"},
		},
		{
			Destination: "/sys/fs/cgroup",
			Type:        "cgroup",
			Source:      "cgroup",
			Options:     []string{"nosuid", "noexec", "nodev", "relatime"},
		},
	}

	// NetworkMounts are the mounts needed for default networking.
	NetworkMounts = []specs.Mount{
		{
			Destination: "/etc/hosts",
			Type:        "bind",
			Source:      "/etc/hosts",
			Options:     []string{"rbind", "rprivate", "ro"},
		},
		{
			Destination: "/etc/resolv.conf",
			Type:        "bind",
			Source:      "/etc/resolv.conf",
			Options:     []string{"rbind", "rprivate", "ro"},
		},
	}
)

// Config takes ContainerJSON and converts it into the opencontainers spec.
func Config(c types.ContainerJSON, osType, architecture string, capabilities []string, idroot, idlen uint32) (config *specs.Spec, err error) {
	// for user namespaces use defaults unless another range specified
	if idroot == 0 {
		idroot = DefaultUserNSHostID
	}
	if idlen == 0 {
		idlen = DefaultUserNSMapSize
	}
	config = &specs.Spec{
		Version: SpecVersion,
		Platform: specs.Platform{
			OS:   osType,
			Arch: architecture,
		},
		Process: specs.Process{
			Terminal: c.Config.Tty,
			User:     specs.User{
			// TODO: user stuffs
			},
			Args: append([]string{c.Path}, c.Args...),
			Env:  c.Config.Env,
			Cwd:  c.Config.WorkingDir,
			// TODO: add parsing of Ulimits
			Rlimits: []specs.Rlimit{
				{
					Type: "RLIMIT_NOFILE",
					Hard: uint64(1024),
					Soft: uint64(1024),
				},
			},
			NoNewPrivileges: true,
			ApparmorProfile: c.AppArmorProfile,
		},
		Root: specs.Root{
			Path:     "rootfs",
			Readonly: c.HostConfig.ReadonlyRootfs,
		},
		Mounts: []specs.Mount{},
		Linux: specs.Linux{
			Namespaces: []specs.Namespace{
				{
					Type: "ipc",
				},
				{
					Type: "uts",
				},
				{
					Type: "mount",
				},
			},
			UIDMappings: []specs.IDMapping{
				{
					ContainerID: 0,
					HostID:      idroot,
					Size:        idlen,
				},
			},
			GIDMappings: []specs.IDMapping{
				{
					ContainerID: 0,
					HostID:      idroot,
					Size:        idlen,
				},
			},
			Resources: &specs.Resources{
				Devices: []specs.DeviceCgroup{
					{
						Allow:  false,
						Access: sPtr("rwm"),
					},
				},
				DisableOOMKiller: c.HostConfig.Resources.OomKillDisable,
				OOMScoreAdj:      &c.HostConfig.OomScoreAdj,
				Memory: &specs.Memory{
					Limit:       uint64ptr(c.HostConfig.Resources.Memory),
					Reservation: uint64ptr(c.HostConfig.Resources.MemoryReservation),
					Swap:        uint64ptr(c.HostConfig.Resources.MemorySwap),
					Swappiness:  uint64ptr(*c.HostConfig.Resources.MemorySwappiness),
					Kernel:      uint64ptr(c.HostConfig.Resources.KernelMemory),
				},
				CPU: &specs.CPU{
					Shares: uint64ptr(c.HostConfig.Resources.CPUShares),
					Quota:  uint64ptr(c.HostConfig.Resources.CPUQuota),
					Period: uint64ptr(c.HostConfig.Resources.CPUPeriod),
					Cpus:   &c.HostConfig.Resources.CpusetCpus,
					Mems:   &c.HostConfig.Resources.CpusetMems,
				},
				Pids: &specs.Pids{
					Limit: &c.HostConfig.Resources.PidsLimit,
				},
				BlockIO: &specs.BlockIO{
					Weight: &c.HostConfig.Resources.BlkioWeight,
					// TODO: add parsing for Throttle/Weight Devices
				},
			},
			RootfsPropagation: "",
		},
	}

	// make sure the current working directory is not blank
	if config.Process.Cwd == "" {
		config.Process.Cwd = DefaultCurrentWorkingDirectory
	}

	// get the user
	if c.Config.User != "" {
		u, err := user.LookupUser(c.Config.User)
		if err != nil {
			config.Process.User = specs.User{
				UID: uint32(u.Uid),
				GID: uint32(u.Gid),
			}
		} else {
			//return nil, fmt.Errorf("Looking up user (%s) failed: %v", c.Config.User, err)
			logrus.Warnf("Looking up user (%s) failed: %v", c.Config.User, err)
		}
	}
	// add the additional groups
	for _, group := range c.HostConfig.GroupAdd {
		g, err := user.LookupGroup(group)
		if err != nil {
			return nil, fmt.Errorf("Looking up group (%s) failed: %v", group, err)
		}
		config.Process.User.AdditionalGids = append(config.Process.User.AdditionalGids, uint32(g.Gid))
	}

	// get the hostname, if the hostname is the name as the first 12 characters of the id,
	// then set the hostname as the container name
	if c.ID[:12] == c.Config.Hostname {
		config.Hostname = strings.TrimPrefix(c.Name, "/")
	}

	// set privileged
	if c.HostConfig.Privileged {
		// allow all caps
		capabilities = execdriver.GetAllCapabilities()
	}

	// get the capabilities
	config.Process.Capabilities, err = execdriver.TweakCapabilities(capabilities, c.HostConfig.CapAdd, c.HostConfig.CapDrop)
	if err != nil {
		return nil, fmt.Errorf("setting capabilities failed: %v", err)
	}

	// add CAP_ prefix
	// TODO: this is awful
	for i, cap := range config.Process.Capabilities {
		if !strings.HasPrefix(cap, "CAP_") {
			config.Process.Capabilities[i] = fmt.Sprintf("CAP_%s", cap)
		}
	}

	// if we have a container that needs a terminal but no env vars, then set
	// default env vars for the terminal to function
	if config.Process.Terminal && len(config.Process.Env) <= 0 {
		config.Process.Env = DefaultTerminalEnv
	}
	if config.Process.Terminal {
		// make sure we have TERM set
		var termSet bool
		for _, env := range config.Process.Env {
			if strings.HasPrefix(env, "TERM=") {
				termSet = true
				break
			}
		}
		if !termSet {
			// set the term variable
			config.Process.Env = append(config.Process.Env, fmt.Sprintf("TERM=%s", DefaultTerminal))
		}
	}

	// check namespaces
	if !c.HostConfig.NetworkMode.IsHost() {
		config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{
			Type: "network",
		})
	}
	if !c.HostConfig.PidMode.IsHost() {
		config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{
			Type: "pid",
		})
	}
	if c.HostConfig.UsernsMode.Valid() && !c.HostConfig.NetworkMode.IsHost() && !c.HostConfig.PidMode.IsHost() && !c.HostConfig.Privileged {
		config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{
			Type: "user",
		})
	} else {
		// reset uid and gid mappings
		config.Linux.UIDMappings = []specs.IDMapping{}
		config.Linux.GIDMappings = []specs.IDMapping{}
	}

	// get mounts
	mounts := map[string]bool{}
	for _, mount := range c.Mounts {
		mounts[mount.Destination] = true
		var opt []string
		if mount.RW {
			opt = append(opt, "rw")
		}
		if mount.Mode != "" {
			opt = append(opt, mount.Mode)
		}
		opt = append(opt, []string{"rbind", "rprivate"}...)

		config.Mounts = append(config.Mounts, specs.Mount{
			Destination: mount.Destination,
			Type:        "bind",
			Source:      mount.Source,
			Options:     opt,
		})
	}

	// add /etc/hosts and /etc/resolv.conf if we should have networking
	if c.HostConfig.NetworkMode != "none" && c.HostConfig.NetworkMode != "host" {
		DefaultMounts = append(DefaultMounts, NetworkMounts...)
	}

	// if we aren't doing something crazy like mounting a default mount ourselves,
	// the we can mount it the default way
	for _, mount := range DefaultMounts {
		if _, ok := mounts[mount.Destination]; !ok {
			config.Mounts = append(config.Mounts, mount)
		}
	}

	// fix default mounts for cgroups and devpts without user namespaces
	// see: https://github.com/opencontainers/runc/issues/225#issuecomment-136519577
	if len(config.Linux.UIDMappings) == 0 {
		for k, mount := range config.Mounts {
			switch mount.Destination {
			case "/sys/fs/cgroup":
				config.Mounts[k].Options = append(config.Mounts[k].Options, "ro")
			case "/dev/pts":
				config.Mounts[k].Options = append(config.Mounts[k].Options, "gid=5")
			}
		}
	}

	// parse additional groups and add them to gid mappings
	if err := parseMappings(config, c.HostConfig); err != nil {
		return nil, err
	}

	// parse devices
	if err := parseDevices(config, c.HostConfig); err != nil {
		return nil, err
	}

	// parse security opt
	if err := parseSecurityOpt(config, c.HostConfig); err != nil {
		return nil, err
	}

	// set privileged
	if c.HostConfig.Privileged {
		if !c.HostConfig.ReadonlyRootfs {
			// clear readonly for cgroup
			//	config.Mounts["cgroup"] = DefaultMountpoints["cgroup"]
		}
	}

	return config, nil
}
<|endoftext|>


<commit_before>package parser

import (
	"strconv"
	"strings"

	"github.com/pajlada/pajbot2/common"
)

/*
Parse parses an IRC message into a more readable bot.Msg
*/
func Parse(line string) common.Msg {
	m := &common.Msg{
		User: common.User{},
		Type: common.MsgUnknown,
	}

	// msg is the string we will keep working on/reducing as we parse things
	msg := line

	var splitLine []string

	// The message starts with @, that means there are IRCv3 tags available to parse
	if strings.HasPrefix(line, "@") {
		splitLine = strings.SplitN(msg, " ", 2)
		parseTags(m, splitLine[0][1:])
		msg = splitLine[1]
	}

	// Parse source
	splitLine = strings.SplitN(msg, " ", 2)
	parseSource(m, splitLine[0])
	msg = splitLine[1]

	// Parse message type
	splitLine = strings.SplitN(msg, " ", 2)
	parseMsgType(m, splitLine[0])
	msg = splitLine[1]

	if m.Type == common.MsgUnknown {
		m.Type = common.MsgThrowAway
		return *m
	}

	splitLine = strings.SplitN(msg, " ", 2)
	if m.Type == common.MsgPrivmsg {
		parseChannel(m, splitLine[0])
	}

	if len(splitLine) == 2 {
		msg = splitLine[1]

		// Parse message text + msg type (if it's a /me message or not)
		parseText(m, msg)

		if m.User.Name == "twitchnotify" {
			if !strings.Contains(m.Text, " to ") && !strings.Contains(m.Text, " while ") {
				parseNewSub(m)
			}
		}
	}

	// If the destination of the message is the same as the username,
	// then we tag the user as the channel owner. This will automatically
	// give him access to broadcaster commands
	if m.Channel == m.User.Name {
		m.User.ChannelOwner = true
	}

	if m.Tags != nil {
		// Parse tags further, such as the msg-id value for determinig the msg type
		parseExtendedTags(m)
	}

	return *m
}

func parseTwitchEmotes(m *common.Msg, emotetag string) {
	// TODO: Parse more emote information (bttv (and ffz?), name, size, isGif)
	// will we done by a module in the bot itself
	m.Emotes = make([]common.Emote, 0)
	if emotetag == "" {
		return
	}
	emoteSlice := strings.Split(emotetag, "/")
	for i := range emoteSlice {
		spl := strings.Split(emoteSlice[i], ":")
		id := spl[0]
		e := &common.Emote{}
		e.Type = "twitch"
		e.Name = getEmoteName(m, spl[1])
		e.ID = id
		// 28 px should be fine for twitch emotes
		e.SizeX = 28
		e.SizeY = 28
		e.Count = strings.Count(emoteSlice[i], "-")
		m.Emotes = append(m.Emotes, *e)
	}
}

func getEmoteName(m *common.Msg, pos string) string {
	pos = strings.Split(pos, ",")[0]
	spl := strings.Split(pos, "-")
	start, _ := strconv.Atoi(spl[0])
	end, _ := strconv.Atoi(spl[1])
	runes := []rune(m.Text)
	name := runes[start : end+1]
	return string(name)
}

func parseTagValues(m *common.Msg) {
	// TODO: Parse id and color
	// color and id is pretty useless imo
	if m.Tags["display-name"] == "" {
		m.User.DisplayName = m.User.Name
	} else {
		m.User.DisplayName = m.Tags["display-name"]
	}
	delete(m.Tags, "display-name")
	m.User.Type = m.Tags["user-type"]
	delete(m.Tags, "user-type")
	// fucking linter
	one := "1"
	if m.Tags["turbo"] == one {
		m.User.Turbo = true
	}
	delete(m.Tags, "turbo")
	if m.Tags["mod"] == one {
		m.User.Mod = true
	}
	delete(m.Tags, "mod")
	if m.Tags["subscriber"] == one {
		m.User.Sub = true
	}
	delete(m.Tags, "subscriber")
}

func parseExtendedTags(m *common.Msg) {
	// Parse twitch emotes from the "emotes" tag
	parseTwitchEmotes(m, m.Tags["emotes"])
	delete(m.Tags, "emotes")

	switch m.Tags["msg-id"] {
	case "resub":
		m.Type = common.MsgReSub

	case "subs_on":
		m.Type = common.MsgSubsOn

	case "subs_off":
		m.Type = common.MsgSubsOff

	case "slow_on":
		// Slow mode duration is found in the tag slow_duration
		m.Type = common.MsgSlowOn

	case "slow_off":
		m.Type = common.MsgSlowOff

	case "r9k_on":
		m.Type = common.MsgR9kOn

	case "r9k_off":
		m.Type = common.MsgR9kOff

	case "host_on":
		// Host target can be found in target_channel tag
		m.Type = common.MsgHostOn

	case "host_off":
		m.Type = common.MsgHostOff

	case "":
		break

	default:
		m.Type = common.MsgUnknown
	}

	if m.Tags["login"] != "" {
		m.User.Name = m.Tags["login"]
	}
}

/*
XXX: Should user properties stay at their zero value when there are no tags? Do we even care about this scenario?
*/
func parseTags(m *common.Msg, msg string) {
	m.Tags = make(map[string]string)
	// IRCv3-tags are separated by semicolons
	for _, tagValue := range strings.Split(msg, ";") {
		spl := strings.Split(tagValue, "=")
		k := spl[0]
		v := strings.Replace(spl[1], "\\s", " ", -1)
		m.Tags[k] = v
	}

	parseTagValues(m)

}

func parseSource(m *common.Msg, msg string) {
	if strings.HasPrefix(msg, ":") {
		msg = msg[1:]
	}
	// Check if the source is a user
	userSepPos := strings.Index(msg, "!")
	hostSepPos := strings.Index(msg, "@")
	if userSepPos > -1 && hostSepPos > -1 && userSepPos < hostSepPos {
		// A valid user address is found!
		m.User.Name = msg[0:userSepPos]
	}
}

func parseMsgType(m *common.Msg, msg string) {
	switch msg {
	case "PRIVMSG":
		m.Type = common.MsgPrivmsg

	case "WHISPER":
		m.Type = common.MsgWhisper

	case "USERNOTICE":
		m.Type = common.MsgUserNotice

	case "NOTICE":
		m.Type = common.MsgNotice

	case "ROOMSTATE":
		m.Type = common.MsgRoomState
	}
}

func parseChannel(m *common.Msg, msg string) {
	m.Channel = strings.Replace(msg[1:], "#", "", 0)
}

func parseText(m *common.Msg, msg string) {
	m.Text = msg[1:]

	// figure out whether the message is an ACTION or not
	getAction(m)
}

// regex in 2016 LUL
func getAction(m *common.Msg) {
	if strings.HasPrefix(m.Text, "\u0001ACTION ") && strings.HasSuffix(m.Text, "\u0001") {
		m.Me = true
		msg := m.Text
		msg = strings.Replace(msg, "\u0001ACTION ", "", 1)
		msg = strings.Replace(msg, "\u0001", "", 1)
		m.Text = msg
	}
}

func parseNewSub(m *common.Msg) {
	m.Type = common.MsgSub
	m.User.DisplayName = strings.Split(m.Text, " ")[0]
	m.User.Name = strings.ToLower(m.User.DisplayName)
}
<commit_msg>fixed message parsing<commit_after>package parser

import (
	"strconv"
	"strings"

	"github.com/pajlada/pajbot2/common"
)

/*
Parse parses an IRC message into a more readable bot.Msg
*/
func Parse(line string) common.Msg {
	m := &common.Msg{
		User: common.User{},
		Type: common.MsgUnknown,
	}

	// msg is the string we will keep working on/reducing as we parse things
	msg := line

	var splitLine []string

	// The message starts with @, that means there are IRCv3 tags available to parse
	if strings.HasPrefix(line, "@") {
		splitLine = strings.SplitN(msg, " ", 2)
		parseTags(m, splitLine[0][1:])
		msg = splitLine[1]
	}

	// Parse source
	splitLine = strings.SplitN(msg, " ", 2)
	parseSource(m, splitLine[0])
	msg = splitLine[1]

	// Parse message type
	splitLine = strings.SplitN(msg, " ", 2)
	parseMsgType(m, splitLine[0])
	msg = splitLine[1]

	if m.Type == common.MsgUnknown {
		m.Type = common.MsgThrowAway
		return *m
	}

	splitLine = strings.SplitN(msg, " ", 2)
	if m.Type != common.MsgWhisper {
		parseChannel(m, splitLine[0])
	}

	if len(splitLine) == 2 {
		msg = splitLine[1]

		// Parse message text + msg type (if it's a /me message or not)
		parseText(m, msg)

		if m.User.Name == "twitchnotify" {
			if !strings.Contains(m.Text, " to ") && !strings.Contains(m.Text, " while ") {
				parseNewSub(m)
			}
		}
	}

	// If the destination of the message is the same as the username,
	// then we tag the user as the channel owner. This will automatically
	// give him access to broadcaster commands
	if m.Channel == m.User.Name {
		m.User.ChannelOwner = true
	}

	if m.Tags != nil {
		// Parse tags further, such as the msg-id value for determinig the msg type
		parseExtendedTags(m)
	}

	return *m
}

func parseTwitchEmotes(m *common.Msg, emotetag string) {
	// TODO: Parse more emote information (bttv (and ffz?), name, size, isGif)
	// will we done by a module in the bot itself
	m.Emotes = make([]common.Emote, 0)
	if emotetag == "" {
		return
	}
	emoteSlice := strings.Split(emotetag, "/")
	for i := range emoteSlice {
		spl := strings.Split(emoteSlice[i], ":")
		id := spl[0]
		e := &common.Emote{}
		e.Type = "twitch"
		e.Name = getEmoteName(m, spl[1])
		e.ID = id
		// 28 px should be fine for twitch emotes
		e.SizeX = 28
		e.SizeY = 28
		e.Count = strings.Count(emoteSlice[i], "-")
		m.Emotes = append(m.Emotes, *e)
	}
}

func getEmoteName(m *common.Msg, pos string) string {
	pos = strings.Split(pos, ",")[0]
	spl := strings.Split(pos, "-")
	start, _ := strconv.Atoi(spl[0])
	end, _ := strconv.Atoi(spl[1])
	runes := []rune(m.Text)
	name := runes[start : end+1]
	return string(name)
}

func parseTagValues(m *common.Msg) {
	// TODO: Parse id and color
	// color and id is pretty useless imo
	if m.Tags["display-name"] == "" {
		m.User.DisplayName = m.User.Name
	} else {
		m.User.DisplayName = m.Tags["display-name"]
	}
	delete(m.Tags, "display-name")
	m.User.Type = m.Tags["user-type"]
	delete(m.Tags, "user-type")
	// fucking linter
	one := "1"
	if m.Tags["turbo"] == one {
		m.User.Turbo = true
	}
	delete(m.Tags, "turbo")
	if m.Tags["mod"] == one {
		m.User.Mod = true
	}
	delete(m.Tags, "mod")
	if m.Tags["subscriber"] == one {
		m.User.Sub = true
	}
	delete(m.Tags, "subscriber")
}

func parseExtendedTags(m *common.Msg) {
	// Parse twitch emotes from the "emotes" tag
	parseTwitchEmotes(m, m.Tags["emotes"])
	delete(m.Tags, "emotes")

	switch m.Tags["msg-id"] {
	case "resub":
		m.Type = common.MsgReSub

	case "subs_on":
		m.Type = common.MsgSubsOn

	case "subs_off":
		m.Type = common.MsgSubsOff

	case "slow_on":
		// Slow mode duration is found in the tag slow_duration
		m.Type = common.MsgSlowOn

	case "slow_off":
		m.Type = common.MsgSlowOff

	case "r9k_on":
		m.Type = common.MsgR9kOn

	case "r9k_off":
		m.Type = common.MsgR9kOff

	case "host_on":
		// Host target can be found in target_channel tag
		m.Type = common.MsgHostOn

	case "host_off":
		m.Type = common.MsgHostOff

	case "":
		break

	default:
		m.Type = common.MsgUnknown
	}

	if m.Tags["login"] != "" {
		m.User.Name = m.Tags["login"]
	}
}

/*
XXX: Should user properties stay at their zero value when there are no tags? Do we even care about this scenario?
*/
func parseTags(m *common.Msg, msg string) {
	m.Tags = make(map[string]string)
	// IRCv3-tags are separated by semicolons
	for _, tagValue := range strings.Split(msg, ";") {
		spl := strings.Split(tagValue, "=")
		k := spl[0]
		v := strings.Replace(spl[1], "\\s", " ", -1)
		m.Tags[k] = v
	}

	parseTagValues(m)

}

func parseSource(m *common.Msg, msg string) {
	if strings.HasPrefix(msg, ":") {
		msg = msg[1:]
	}
	// Check if the source is a user
	userSepPos := strings.Index(msg, "!")
	hostSepPos := strings.Index(msg, "@")
	if userSepPos > -1 && hostSepPos > -1 && userSepPos < hostSepPos {
		// A valid user address is found!
		m.User.Name = msg[0:userSepPos]
	}
}

func parseMsgType(m *common.Msg, msg string) {
	switch msg {
	case "PRIVMSG":
		m.Type = common.MsgPrivmsg

	case "WHISPER":
		m.Type = common.MsgWhisper

	case "USERNOTICE":
		m.Type = common.MsgUserNotice

	case "NOTICE":
		m.Type = common.MsgNotice

	case "ROOMSTATE":
		m.Type = common.MsgRoomState
	}
}

func parseChannel(m *common.Msg, msg string) {
	m.Channel = strings.Replace(msg[1:], "#", "", 0)
}

func parseText(m *common.Msg, msg string) {
	m.Text = msg[1:]

	// figure out whether the message is an ACTION or not
	getAction(m)
}

// regex in 2016 LUL
func getAction(m *common.Msg) {
	if strings.HasPrefix(m.Text, "\u0001ACTION ") && strings.HasSuffix(m.Text, "\u0001") {
		m.Me = true
		msg := m.Text
		msg = strings.Replace(msg, "\u0001ACTION ", "", 1)
		msg = strings.Replace(msg, "\u0001", "", 1)
		m.Text = msg
	}
}

func parseNewSub(m *common.Msg) {
	m.Type = common.MsgSub
	m.User.DisplayName = strings.Split(m.Text, " ")[0]
	m.User.Name = strings.ToLower(m.User.DisplayName)
}
<|endoftext|>


<commit_before>package code

import (
	"fmt"
	"math/rand"

	"github.com/telecoda/pico-go/console"
)

// Code must implement console.Cartridge interface

type cartridge struct {
	cfg                 console.Config // holds details of console config
	console.PixelBuffer                // ref to console display

	// example vars below
	mapAnim      bool
	frameCount   int
	totalFrames  int
	currentColor int
}

// NewCart - initialise a struct implementing Cartridge interface
func NewCart() console.Cartridge {
	return &cartridge{
		cfg: console.DefaultConfig(),
	}
}

// GetConfig - return config need for Cart to run
func (c *cartridge) GetConfig() console.Config {
	return c.cfg
}

// Init - called once when cart is initialised
func (c *cartridge) Init(pb console.PixelBuffer) {
	// the Init method receives a PixelBuffer reference
	// hold onto this reference, this is the display that
	// your code will be drawing onto each frame
	c.PixelBuffer = pb
	c.frameCount = 0
	c.totalFrames = 25
	c.currentColor = 0
	c.mapAnim = false
}

// Update -  called once every frame
func (c *cartridge) Update() {
	c.frameCount++
	if c.frameCount > c.totalFrames {
		// trigger update

		if c.mapAnim {
			c.MapColor(console.Color(c.currentColor), console.RED)
			c.MapColor(console.Color(15), console.Color(rand.Intn(16)))
		} else {
			c.SetTransparent(console.Color(c.currentColor), true)
		}
		// reset counters
		c.frameCount = 0
		c.currentColor++
		if c.currentColor > 16 {
			// all colors have been swapped reset
			c.currentColor = 0
			c.PaletteReset()
			c.mapAnim = !c.mapAnim
		}
	}
}

// Render - called once every frame
func (c *cartridge) Render() {
	c.Cls()
	c.RectFillWithColor(0, 0, 32, 32, 0)
	c.RectFillWithColor(32, 0, 64, 32, 1)
	c.RectFillWithColor(64, 0, 96, 32, 2)
	c.RectFillWithColor(96, 0, 128, 32, 3)

	c.RectFillWithColor(0, 32, 32, 64, 4)
	c.RectFillWithColor(32, 32, 64, 64, 5)
	c.RectFillWithColor(64, 32, 96, 64, 6)
	c.RectFillWithColor(96, 32, 128, 64, 7)

	c.RectFillWithColor(0, 64, 32, 96, 8)
	c.RectFillWithColor(32, 64, 64, 96, 9)
	c.RectFillWithColor(64, 64, 96, 96, 10)
	c.RectFillWithColor(96, 64, 128, 96, 11)

	c.RectFillWithColor(0, 96, 32, 128, 12)
	c.RectFillWithColor(32, 96, 64, 128, 13)
	c.RectFillWithColor(64, 96, 96, 128, 14)
	c.RectFillWithColor(96, 96, 128, 128, 15)

	c.PrintAtWithColor("PALETTE:", 46, 5, 15)
	c.Line(0, 12, 128, 12)

	if c.mapAnim {
		c.PrintAtWithColor("COLORS CAN BE SWAPPED.", 20, 20, 15)
	} else {
		c.PrintAtWithColor("COLORS CAN BE TRANSPARENT.", 12, 20, 15)
	}
	c.PrintAtWithColor(fmt.Sprintf("COUNT:%d", c.frameCount), 46, 100, 15)

}
<commit_msg>Remove counter<commit_after>package code

import (
	"math/rand"

	"github.com/telecoda/pico-go/console"
)

// Code must implement console.Cartridge interface

type cartridge struct {
	cfg                 console.Config // holds details of console config
	console.PixelBuffer                // ref to console display

	// example vars below
	mapAnim      bool
	frameCount   int
	totalFrames  int
	currentColor int
}

// NewCart - initialise a struct implementing Cartridge interface
func NewCart() console.Cartridge {
	return &cartridge{
		cfg: console.DefaultConfig(),
	}
}

// GetConfig - return config need for Cart to run
func (c *cartridge) GetConfig() console.Config {
	return c.cfg
}

// Init - called once when cart is initialised
func (c *cartridge) Init(pb console.PixelBuffer) {
	// the Init method receives a PixelBuffer reference
	// hold onto this reference, this is the display that
	// your code will be drawing onto each frame
	c.PixelBuffer = pb
	c.frameCount = 0
	c.totalFrames = 25
	c.currentColor = 0
	c.mapAnim = false
}

// Update -  called once every frame
func (c *cartridge) Update() {
	c.frameCount++
	if c.frameCount > c.totalFrames {
		// trigger update

		if c.mapAnim {
			c.MapColor(console.Color(c.currentColor), console.RED)
			c.MapColor(console.Color(15), console.Color(rand.Intn(16)))
		} else {
			c.SetTransparent(console.Color(c.currentColor), true)
		}
		// reset counters
		c.frameCount = 0
		c.currentColor++
		if c.currentColor > 16 {
			// all colors have been swapped reset
			c.currentColor = 0
			c.PaletteReset()
			c.mapAnim = !c.mapAnim
		}
	}
}

// Render - called once every frame
func (c *cartridge) Render() {
	c.Cls()
	c.RectFillWithColor(0, 0, 32, 32, 0)
	c.RectFillWithColor(32, 0, 64, 32, 1)
	c.RectFillWithColor(64, 0, 96, 32, 2)
	c.RectFillWithColor(96, 0, 128, 32, 3)

	c.RectFillWithColor(0, 32, 32, 64, 4)
	c.RectFillWithColor(32, 32, 64, 64, 5)
	c.RectFillWithColor(64, 32, 96, 64, 6)
	c.RectFillWithColor(96, 32, 128, 64, 7)

	c.RectFillWithColor(0, 64, 32, 96, 8)
	c.RectFillWithColor(32, 64, 64, 96, 9)
	c.RectFillWithColor(64, 64, 96, 96, 10)
	c.RectFillWithColor(96, 64, 128, 96, 11)

	c.RectFillWithColor(0, 96, 32, 128, 12)
	c.RectFillWithColor(32, 96, 64, 128, 13)
	c.RectFillWithColor(64, 96, 96, 128, 14)
	c.RectFillWithColor(96, 96, 128, 128, 15)

	c.PrintAtWithColor("PALETTE:", 46, 5, 15)
	c.Line(0, 12, 128, 12)

	if c.mapAnim {
		c.PrintAtWithColor("COLORS CAN BE SWAPPED.", 20, 20, 15)
	} else {
		c.PrintAtWithColor("COLORS CAN BE TRANSPARENT.", 12, 20, 15)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Heavily inspired by https://github.com/btcsuite/btcd/blob/master/version.go
// Copyright (C) 2015-2017 The Lightning Network Developers

package build

import (
	"fmt"
	"strings"
)

var (
	// Commit stores the current commit of this build, which includes the
	// most recent tag, the number of commits since that tag (if non-zero),
	// the commit hash, and a dirty marker. This should be set using the
	// -ldflags during compilation.
	Commit string

	// CommitHash stores the current commit hash of this build, this should
	// be set using the -ldflags during compilation.
	CommitHash string

	// RawTags contains the raw set of build tags, separated by commas. This
	// should be set using -ldflags during compilation.
	RawTags string

	// GoVersion stores the go version that the executable was compiled
	// with. This hsould be set using -ldflags during compilation.
	GoVersion string
)

// semanticAlphabet is the set of characters that are permitted for use in an
// AppPreRelease.
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."

// These constants define the application version and follow the semantic
// versioning 2.0.0 spec (http://semver.org/).
const (
	// AppMajor defines the major version of this binary.
	AppMajor uint = 0

	// AppMinor defines the minor version of this binary.
	AppMinor uint = 11

	// AppPatch defines the application patch for this binary.
	AppPatch uint = 0

	// AppPreRelease MUST only contain characters from semanticAlphabet
	// per the semantic versioning spec.
	AppPreRelease = "beta.rc2"
)

func init() {
	// Assert that AppPreRelease is valid according to the semantic
	// versioning guidelines for pre-release version and build metadata
	// strings. In particular it MUST only contain characters in
	// semanticAlphabet.
	for _, r := range AppPreRelease {
		if !strings.ContainsRune(semanticAlphabet, r) {
			panic(fmt.Errorf("rune: %v is not in the semantic "+
				"alphabet", r))
		}
	}
}

// Version returns the application version as a properly formed string per the
// semantic versioning 2.0.0 spec (http://semver.org/).
func Version() string {
	// Start with the major, minor, and patch versions.
	version := fmt.Sprintf("%d.%d.%d", AppMajor, AppMinor, AppPatch)

	// Append pre-release version if there is one. The hyphen called for by
	// the semantic versioning spec is automatically appended and should not
	// be contained in the pre-release string.
	if AppPreRelease != "" {
		version = fmt.Sprintf("%s-%s", version, AppPreRelease)
	}

	return version
}

// Tags returns the list of build tags that were compiled into the executable.
func Tags() []string {
	if len(RawTags) == 0 {
		return nil
	}

	return strings.Split(RawTags, ",")
}
<commit_msg>build/version: bump to 0.11.0-beta.rc3<commit_after>// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Heavily inspired by https://github.com/btcsuite/btcd/blob/master/version.go
// Copyright (C) 2015-2017 The Lightning Network Developers

package build

import (
	"fmt"
	"strings"
)

var (
	// Commit stores the current commit of this build, which includes the
	// most recent tag, the number of commits since that tag (if non-zero),
	// the commit hash, and a dirty marker. This should be set using the
	// -ldflags during compilation.
	Commit string

	// CommitHash stores the current commit hash of this build, this should
	// be set using the -ldflags during compilation.
	CommitHash string

	// RawTags contains the raw set of build tags, separated by commas. This
	// should be set using -ldflags during compilation.
	RawTags string

	// GoVersion stores the go version that the executable was compiled
	// with. This hsould be set using -ldflags during compilation.
	GoVersion string
)

// semanticAlphabet is the set of characters that are permitted for use in an
// AppPreRelease.
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."

// These constants define the application version and follow the semantic
// versioning 2.0.0 spec (http://semver.org/).
const (
	// AppMajor defines the major version of this binary.
	AppMajor uint = 0

	// AppMinor defines the minor version of this binary.
	AppMinor uint = 11

	// AppPatch defines the application patch for this binary.
	AppPatch uint = 0

	// AppPreRelease MUST only contain characters from semanticAlphabet
	// per the semantic versioning spec.
	AppPreRelease = "beta.rc3"
)

func init() {
	// Assert that AppPreRelease is valid according to the semantic
	// versioning guidelines for pre-release version and build metadata
	// strings. In particular it MUST only contain characters in
	// semanticAlphabet.
	for _, r := range AppPreRelease {
		if !strings.ContainsRune(semanticAlphabet, r) {
			panic(fmt.Errorf("rune: %v is not in the semantic "+
				"alphabet", r))
		}
	}
}

// Version returns the application version as a properly formed string per the
// semantic versioning 2.0.0 spec (http://semver.org/).
func Version() string {
	// Start with the major, minor, and patch versions.
	version := fmt.Sprintf("%d.%d.%d", AppMajor, AppMinor, AppPatch)

	// Append pre-release version if there is one. The hyphen called for by
	// the semantic versioning spec is automatically appended and should not
	// be contained in the pre-release string.
	if AppPreRelease != "" {
		version = fmt.Sprintf("%s-%s", version, AppPreRelease)
	}

	return version
}

// Tags returns the list of build tags that were compiled into the executable.
func Tags() []string {
	if len(RawTags) == 0 {
		return nil
	}

	return strings.Split(RawTags, ",")
}
<|endoftext|>