<commit_before>package bakery_test

import (
	"fmt"

	gc "gopkg.in/check.v1"

	"gopkg.in/macaroon-bakery.v0/bakery"
	"gopkg.in/macaroon.v1"
)

type ServiceSuite struct{}

var _ = gc.Suite(&ServiceSuite{})

type strCompFirstPartyChecker string

func (c strCompFirstPartyChecker) CheckFirstPartyCaveat(caveat string) error {
	if caveat != string(c) {
		return fmt.Errorf("%v doesn't match %s", caveat, c)
	}
	return nil
}

type strCompThirdPartyChecker string

func (c strCompThirdPartyChecker) CheckThirdPartyCaveat(caveatId string, caveat string) ([]bakery.Caveat, error) {
	if caveat != string(c) {
		return nil, fmt.Errorf("%v doesn't match %s", caveat, c)
	}
	return nil, nil
}

// TestSingleServiceFirstParty creates a single service
// with a macaroon with one first party caveat.
// Created a request with this macaroon and checks that the service
// can verify this macaroon as valid.
func (s *ServiceSuite) TestSingleServiceFirstParty(c *gc.C) {
	p := bakery.NewServiceParams{
		Location: "loc",
		Store:    nil,
		Key:      nil,
		Locator:  nil,
	}
	service, err := bakery.NewService(p)
	c.Assert(err, gc.IsNil)

	macaroon, err := service.NewMacaroon("", nil, nil)
	c.Assert(err, gc.IsNil)
	c.Assert(macaroon.Location(), gc.Equals, "loc")
	cav := bakery.Caveat{
		Location:  "",
		Condition: "something",
	}
	err = service.AddCaveat(macaroon, cav)
	c.Assert(err, gc.IsNil)

	checker := strCompFirstPartyChecker("something")
	req := service.NewRequest(checker)

	req.AddClientMacaroon(macaroon)

	err = req.Check()
	c.Assert(err, gc.IsNil)
}

// TestMacaroonPaperFig6 implements an example flow as described in the macaroons paper:
// http://theory.stanford.edu/~ataly/Papers/macaroons.pdf
// There are three services, ts, fs, as:
// ts is a storage service which has deligated authority to a forum service fs.
// The forum service wants to require its users to be logged into to an authentication service as.
//
// The client obtains a macaroon from fs (minted by ts, with a third party caveat addressed to as).
// The client obtains a discharge macaroon from as to satisfy this caveat.
// The target service verifies the original macaroon it delegated to fs
// No direct contact between as and ts is required
func (s *ServiceSuite) TestMacaroonPaperFig6(c *gc.C) {
	fsKeyPair, err := bakery.GenerateKey()
	c.Assert(err, gc.IsNil)
	asKeyPair, err := bakery.GenerateKey()
	c.Assert(err, gc.IsNil)

	publicKeyLocator := bakery.PublicKeyLocatorMap{
		"fs-loc": &fsKeyPair.Public,
		"as-loc": &asKeyPair.Public,
	}

	ts, err := bakery.NewService(bakery.NewServiceParams{Location: "ts-loc"})
	c.Assert(err, gc.IsNil)
	fs, err := bakery.NewService(bakery.NewServiceParams{
		Location: "fs-loc",
		Key:      fsKeyPair,
		Locator:  publicKeyLocator,
	})
	c.Assert(err, gc.IsNil)
	as, err := bakery.NewService(bakery.NewServiceParams{
		Location: "as-loc",
		Key:      asKeyPair,
		Locator:  publicKeyLocator,
	})
	c.Assert(err, gc.IsNil)

	// ts creates a macaroon.
	tsMacaroon, err := ts.NewMacaroon("", nil, nil)
	c.Assert(err, gc.IsNil)

	// ts somehow gets the macaroon to fs. fs adds a third party caveat to be discharged by as.
	err = fs.AddCaveat(tsMacaroon, bakery.Caveat{Location: "as-loc", Condition: "user==bob"})
	c.Assert(err, gc.IsNil)

	// client asks for a discharge macaroon for each third party caveat.
	// TODO (mattyw) why add the first party location
	// TODO (mattyw) Why does the third party checker pass the id in the encoded form?
	// Maybe the tpc is based on a shared secret between the two
	d, err := bakery.DischargeAll(tsMacaroon, func(firstPartyLocation string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
		c.Assert(firstPartyLocation, gc.Equals, "ts-loc")
		c.Assert(cav.Location, gc.Equals, "as-loc")
		mac, err := as.Discharge(strCompThirdPartyChecker("user==bob"), cav.Id)
		c.Assert(err, gc.IsNil)
		return mac, nil
	})

	// client makes request to ts
	req := ts.NewRequest(strCompFirstPartyChecker(""))
	req.AddClientMacaroon(tsMacaroon)
	// client has all the discharge macaroons. For each discharge macaroon bind it to our tsMacaroon
	// and add it to our request.
	for _, dm := range d {
		dm.Bind(tsMacaroon.Signature())
		req.AddClientMacaroon(dm)
	}

	err = req.Check()
	c.Assert(err, gc.IsNil)
}
<commit_msg>added tests that check for errors in fig6 example<commit_after>package bakery_test

import (
	"fmt"

	gc "gopkg.in/check.v1"

	"gopkg.in/macaroon-bakery.v0/bakery"
	"gopkg.in/macaroon.v1"
)

type ServiceSuite struct{}

var _ = gc.Suite(&ServiceSuite{})

// TestSingleServiceFirstParty creates a single service
// with a macaroon with one first party caveat.
// It creates a request with this macaroon and checks that the service
// can verify this macaroon as valid.
func (s *ServiceSuite) TestSingleServiceFirstParty(c *gc.C) {
	p := bakery.NewServiceParams{
		Location: "loc",
		Store:    nil,
		Key:      nil,
		Locator:  nil,
	}
	service, err := bakery.NewService(p)
	c.Assert(err, gc.IsNil)

	macaroon, err := service.NewMacaroon("", nil, nil)
	c.Assert(err, gc.IsNil)
	c.Assert(macaroon.Location(), gc.Equals, "loc")
	cav := bakery.Caveat{
		Location:  "",
		Condition: "something",
	}
	err = service.AddCaveat(macaroon, cav)
	c.Assert(err, gc.IsNil)

	checker := strCompFirstPartyChecker("something")
	req := service.NewRequest(checker)

	req.AddClientMacaroon(macaroon)

	err = req.Check()
	c.Assert(err, gc.IsNil)
}

// TestMacaroonPaperFig6 implements an example flow as described in the macaroons paper:
// http://theory.stanford.edu/~ataly/Papers/macaroons.pdf
// There are three services, ts, fs, as:
// ts is a storage service which has deligated authority to a forum service fs.
// The forum service wants to require its users to be logged into to an authentication service as.
//
// The client obtains a macaroon from fs (minted by ts, with a third party caveat addressed to as).
// The client obtains a discharge macaroon from as to satisfy this caveat.
// The target service verifies the original macaroon it delegated to fs
// No direct contact between as and ts is required
func (s *ServiceSuite) TestMacaroonPaperFig6(c *gc.C) {
	ts, fs, as := setUpFig6Services(c)
	tsMacaroon := createMacaroonWithThirdPartyCaveat(c, ts, fs, bakery.Caveat{Location: "as-loc", Condition: "user==bob"})
	d, err := bakery.DischargeAll(tsMacaroon, func(firstPartyLocation string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
		c.Assert(firstPartyLocation, gc.Equals, "ts-loc")
		c.Assert(cav.Location, gc.Equals, "as-loc")
		mac, err := as.Discharge(strCompThirdPartyChecker("user==bob"), cav.Id)
		c.Assert(err, gc.IsNil)
		return mac, nil
	})
	c.Assert(err, gc.IsNil)

	// client makes request to ts
	req := ts.NewRequest(strCompFirstPartyChecker(""))
	req.AddClientMacaroon(tsMacaroon)
	// client has all the discharge macaroons. For each discharge macaroon bind it to our tsMacaroon
	// and add it to our request.
	for _, dm := range d {
		dm.Bind(tsMacaroon.Signature())
		req.AddClientMacaroon(dm)
	}

	err = req.Check()
	c.Assert(err, gc.IsNil)
}

func (s *ServiceSuite) TestMacaroonPaperFig6FailsWithoutDischarges(c *gc.C) {
	ts, fs, _ := setUpFig6Services(c)
	tsMacaroon := createMacaroonWithThirdPartyCaveat(c, ts, fs, bakery.Caveat{Location: "as-loc", Condition: "user==bob"})

	req := ts.NewRequest(strCompFirstPartyChecker(""))
	req.AddClientMacaroon(tsMacaroon)

	err := req.Check()
	c.Assert(err, gc.ErrorMatches, `verification failed: cannot find discharge macaroon for caveat ".*"`)
}

func (s *ServiceSuite) TestMacaroonPaperFig6FailsWithBindingOnTamperedSignature(c *gc.C) {
	ts, fs, as := setUpFig6Services(c)
	tsMacaroon := createMacaroonWithThirdPartyCaveat(c, ts, fs, bakery.Caveat{Location: "as-loc", Condition: "user==bob"})
	d, err := bakery.DischargeAll(tsMacaroon, func(firstPartyLocation string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
		c.Assert(firstPartyLocation, gc.Equals, "ts-loc")
		c.Assert(cav.Location, gc.Equals, "as-loc")
		mac, err := as.Discharge(strCompThirdPartyChecker("user==bob"), cav.Id)
		c.Assert(err, gc.IsNil)
		return mac, nil
	})
	c.Assert(err, gc.IsNil)

	req := ts.NewRequest(strCompFirstPartyChecker(""))
	req.AddClientMacaroon(tsMacaroon)
	for _, dm := range d {
		dm.Bind([]byte("tampered-signature"))
		req.AddClientMacaroon(dm)
	}

	err = req.Check()
	c.Assert(err, gc.ErrorMatches, "verification failed: signature mismatch after caveat verification")
}

func setUpFig6Services(c *gc.C) (*bakery.Service, *bakery.Service, *bakery.Service) {
	fsKeyPair, err := bakery.GenerateKey()
	c.Assert(err, gc.IsNil)
	asKeyPair, err := bakery.GenerateKey()
	c.Assert(err, gc.IsNil)

	publicKeyLocator := bakery.PublicKeyLocatorMap{
		"fs-loc": &fsKeyPair.Public,
		"as-loc": &asKeyPair.Public,
	}

	ts, err := bakery.NewService(bakery.NewServiceParams{Location: "ts-loc"})
	c.Assert(err, gc.IsNil)
	fs, err := bakery.NewService(bakery.NewServiceParams{
		Location: "fs-loc",
		Key:      fsKeyPair,
		Locator:  publicKeyLocator,
	})
	c.Assert(err, gc.IsNil)
	as, err := bakery.NewService(bakery.NewServiceParams{
		Location: "as-loc",
		Key:      asKeyPair,
		Locator:  publicKeyLocator,
	})
	c.Assert(err, gc.IsNil)

	return ts, fs, as
}

func createMacaroonWithThirdPartyCaveat(c *gc.C, minter, caveater *bakery.Service, cav bakery.Caveat) *macaroon.Macaroon {
	mac, err := minter.NewMacaroon("", nil, nil)
	c.Assert(err, gc.IsNil)

	err = caveater.AddCaveat(mac, cav)
	c.Assert(err, gc.IsNil)
	return mac
}

type strCompFirstPartyChecker string

func (c strCompFirstPartyChecker) CheckFirstPartyCaveat(caveat string) error {
	if caveat != string(c) {
		return fmt.Errorf("%v doesn't match %s", caveat, c)
	}
	return nil
}

type strCompThirdPartyChecker string

func (c strCompThirdPartyChecker) CheckThirdPartyCaveat(caveatId string, caveat string) ([]bakery.Caveat, error) {
	if caveat != string(c) {
		return nil, fmt.Errorf("%v doesn't match %s", caveat, c)
	}
	return nil, nil
}
<|endoftext|>


<commit_before>package version

// Version of Functions
var Version = "0.3.572"
<commit_msg>fnserver: 0.3.573 release [skip ci]<commit_after>package version

// Version of Functions
var Version = "0.3.573"
<|endoftext|>


<commit_before>package version

// Version of Functions
var Version = "0.3.292"
<commit_msg>fnserver: 0.3.293 release [skip ci]<commit_after>package version

// Version of Functions
var Version = "0.3.293"
<|endoftext|>


<commit_before>package log

import (
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"
)

// Entry is the real logger
type Entry struct {
	Logger  *Logger
	Fields  Fields
	Time    time.Time
	Level   Level
	Message string
}

// AddField adds tag to entry
func (entry *Entry) AddField(key string, value string) {
	entry.Fields[key] = value
}

// AddFields adds multiple tags to entry
func (entry *Entry) AddFields(fields Fields) {
	for k, v := range fields {
		entry.Fields[k] = v
	}
}

// This function is not defined with a pointer receiver because we change
// the attribute of struct without using lock, if we use pointer, it would
// become race condition for multiple goroutines.
// see https://github.com/at15/go-learning/issues/3
func (entry Entry) log(level Level, msg string) bool {
	entry.Time = time.Now()
	entry.Level = level
	entry.Message = msg
	// don't log if it can't pass the filter
	for _, filter := range entry.Logger.Filters[level] {
		if !filter.Filter(&entry) {
			return false
		}
	}
	// add source code line if required
	if entry.Logger.showSourceLine {
		// TODO: what if the user also have tag called source
		_, file, line, ok := runtime.Caller(2)
		if !ok {
			file = "<?>"
			line = 1
		} else {
			lastSlash := strings.LastIndex(file, "/")
			file = file[lastSlash+1:]
		}
		entry.AddField("source", fmt.Sprintf("%s:%d", file, line))
	}

	serialized, err := entry.Logger.Formatter.Format(&entry)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to serialize, %v\n", err)
		return false
	}
	_, err = entry.Logger.Out.Write(serialized)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to write, %v\n", err)
		return false
	}
	return true
}

func (entry *Entry) Panic(args ...interface{}) {
	if entry.Logger.Level >= PanicLevel {
		entry.log(PanicLevel, fmt.Sprint(args...))
	}
	panic(fmt.Sprint(args...))
}

func (entry *Entry) Fatal(args ...interface{}) {
	if entry.Logger.Level >= FatalLevel {
		entry.log(PanicLevel, fmt.Sprint(args...))
	}
	// TODO: allow register handlers like logrus
	os.Exit(1)
}

// Printf functions
// NOTE: the *f functions does NOT call * functions like logrus does, it just copy and paste

func (entry *Entry) Panicf(format string, args ...interface{}) {
	if entry.Logger.Level >= PanicLevel {
		entry.log(PanicLevel, fmt.Sprintf(format, args...))
	}
	panic(fmt.Sprint(args...))
}

func (entry *Entry) Fatalf(format string, args ...interface{}) {
	if entry.Logger.Level >= FatalLevel {
		entry.log(FatalLevel, fmt.Sprintf(format, args...))
	}
	// TODO: allow register handlers like logrus
	os.Exit(1)
}
<commit_msg>[log] Fatal was using PanicLevel when log<commit_after>package log

import (
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"
)

// Entry is the real logger
type Entry struct {
	Logger  *Logger
	Fields  Fields
	Time    time.Time
	Level   Level
	Message string
}

// AddField adds tag to entry
func (entry *Entry) AddField(key string, value string) {
	entry.Fields[key] = value
}

// AddFields adds multiple tags to entry
func (entry *Entry) AddFields(fields Fields) {
	for k, v := range fields {
		entry.Fields[k] = v
	}
}

// This function is not defined with a pointer receiver because we change
// the attribute of struct without using lock, if we use pointer, it would
// become race condition for multiple goroutines.
// see https://github.com/at15/go-learning/issues/3
func (entry Entry) log(level Level, msg string) bool {
	entry.Time = time.Now()
	entry.Level = level
	entry.Message = msg
	// don't log if it can't pass the filter
	for _, filter := range entry.Logger.Filters[level] {
		if !filter.Filter(&entry) {
			return false
		}
	}
	// add source code line if required
	if entry.Logger.showSourceLine {
		// TODO: what if the user also have tag called source
		_, file, line, ok := runtime.Caller(2)
		if !ok {
			file = "<?>"
			line = 1
		} else {
			lastSlash := strings.LastIndex(file, "/")
			file = file[lastSlash+1:]
		}
		entry.AddField("source", fmt.Sprintf("%s:%d", file, line))
	}

	serialized, err := entry.Logger.Formatter.Format(&entry)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to serialize, %v\n", err)
		return false
	}
	_, err = entry.Logger.Out.Write(serialized)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to write, %v\n", err)
		return false
	}
	return true
}

func (entry *Entry) Panic(args ...interface{}) {
	if entry.Logger.Level >= PanicLevel {
		entry.log(PanicLevel, fmt.Sprint(args...))
	}
	panic(fmt.Sprint(args...))
}

func (entry *Entry) Fatal(args ...interface{}) {
	if entry.Logger.Level >= FatalLevel {
		entry.log(FatalLevel, fmt.Sprint(args...))
	}
	// TODO: allow register handlers like logrus
	os.Exit(1)
}

// Printf functions
// NOTE: the *f functions does NOT call * functions like logrus does, it just copy and paste

func (entry *Entry) Panicf(format string, args ...interface{}) {
	if entry.Logger.Level >= PanicLevel {
		entry.log(PanicLevel, fmt.Sprintf(format, args...))
	}
	panic(fmt.Sprint(args...))
}

func (entry *Entry) Fatalf(format string, args ...interface{}) {
	if entry.Logger.Level >= FatalLevel {
		entry.log(FatalLevel, fmt.Sprintf(format, args...))
	}
	// TODO: allow register handlers like logrus
	os.Exit(1)
}
<|endoftext|>


<commit_before>package cert

import (
	"crypto/x509"
	"crypto/x509/pkix"
	"fmt"
	"testing"
	"time"
)

func stubCert() {
	serverCert = func(host, port string) ([]*x509.Certificate, string, error) {
		return []*x509.Certificate{
			&x509.Certificate{
				Issuer: pkix.Name{
					CommonName: "CA for test",
				},
				Subject: pkix.Name{
					CommonName: host,
				},
				DNSNames:  []string{host, "www." + host},
				NotBefore: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.Local),
				NotAfter:  time.Date(2018, time.January, 1, 0, 0, 0, 0, time.Local),
			},
		}, "127.0.0.1", nil
	}
}

func TestValidate(t *testing.T) {
	if err := validate([]string{"example.com"}); err != nil {
		t.Errorf(`unexpected err %s, want nil`, err.Error())
	}
}

func TestValidateError(t *testing.T) {
	if err := validate([]string{}); err == nil {
		t.Error(`unexpected nil, want error`)
	} else if err.Error() != "Input at least one domain name." {
		t.Errorf(`unexpected err message, want %q`, "Input at least one domain name.")
	}
}

func TestSplitHostPort(t *testing.T) {
	type want struct {
		host string
		port string
		err  error
	}
	var tests = []struct {
		input string
		want  want
	}{
		{"example.com", want{"example.com", defaultPort, nil}},
		{"example.com:443", want{"example.com", "443", nil}},
		{"imap.example.com:993", want{"imap.example.com", "993", nil}},
		{"smtp.example.com:465", want{"smtp.example.com", "465", nil}},
	}

	for _, test := range tests {
		host, port, err := SplitHostPort(test.input)
		got := want{
			host,
			port,
			err,
		}
		if got != test.want {
			t.Errorf("SplitHostPort(%q) = %v, want %v", test.input, got, test.want)
		}
	}
}

func TestNewCert(t *testing.T) {
	stubCert()

	input := "example.com"

	c := NewCert(input)
	certChain, _, _ := serverCert(input, defaultPort)
	origCert := certChain[0]

	if _, ok := interface{}(c).(*Cert); !ok {
		t.Errorf(`NewCert(%q) was not returned *Cert`, input)
	}
	if c.DomainName != "example.com" {
		t.Errorf(`unexpected Cert.DomainName %q, want %q`, c.DomainName, "example.com")
	}
	if c.IP != "127.0.0.1" {
		t.Errorf(`unexpected Cert.IP %q, want %q`, c.IP, "127.0.0.1")
	}
	if c.Issuer != "CA for test" {
		t.Errorf(`unexpected Cert.Issuer %q, want %q`, c.Issuer, "CA for test")
	}
	if c.CommonName != "example.com" {
		t.Errorf(`unexpected Cert.CommonName %q, want %q`, c.CommonName, "example.com")
	}
	if len(c.SANs) != 2 {
		t.Errorf(`unexpected Cert.SANs length %q, want %q`, len(c.SANs), 2)
	}
	if c.SANs[0] != "example.com" {
		t.Errorf(`unexpected Cert.SANs[0] %q, want %q`, c.SANs[0], "example.com")
	}
	if c.SANs[1] != "www.example.com" {
		t.Errorf(`unexpected Cert.SANs[1] %q, want %q`, c.SANs[1], "www.example.com")
	}
	if c.NotBefore != origCert.NotBefore.String() {
		t.Errorf(`unexpected Cert.NotBefore %q, want %q`, c.NotBefore, origCert.NotBefore.String())
	}
	if c.NotAfter != origCert.NotAfter.String() {
		t.Errorf(`unexpected Cert.NotAfter %q, want %q`, c.NotAfter, origCert.NotAfter.String())
	}
	if c.Error != "" {
		t.Errorf(`unexpected Cert.Error %q, want %q`, c.Error, "")
	}
}

func TestNewCerts(t *testing.T) {
	stubCert()

	input := []string{"example.com"}

	certs, _ := NewCerts(input)

	if _, ok := interface{}(certs).(Certs); !ok {
		t.Errorf(`unexpected return type %T, want Certs`, certs)
	}
}

func TestCertsAsString(t *testing.T) {
	stubCert()

	certChain, _, _ := serverCert("example.com", defaultPort)
	origCert := certChain[0]

	expected := fmt.Sprintf(`DomainName: example.com
IP:         127.0.0.1
Issuer:     CA for test
NotBefore:  %s
NotAfter:   %s
CommonName: example.com
SANs:       [example.com www.example.com]
Error:

`, origCert.NotBefore.String(), origCert.NotAfter.String())

	certs, _ := NewCerts([]string{"example.com"})

	if certs.String() != expected {
		t.Errorf(`unexpected return value %q, want %q`, certs.String(), expected)
	}
}

func TestCertsAsMarkdown(t *testing.T) {
	stubCert()

	certChain, _, _ := serverCert("example.com", defaultPort)
	origCert := certChain[0]

	expected := fmt.Sprintf(`DomainName | IP | Issuer | NotBefore | NotAfter | CN | SANs | Error
--- | --- | --- | --- | --- | --- | --- | ---
example.com | 127.0.0.1 | CA for test | %s | %s | example.com | example.com
www.example.com
 |

`, origCert.NotBefore.String(), origCert.NotAfter.String())

	certs, _ := NewCerts([]string{"example.com"})

	if certs.Markdown() != expected {
		t.Errorf(`unexpected return value %q, want %q`, certs.Markdown(), expected)
	}
}

func TestCertsAsJSON(t *testing.T) {
	stubCert()

	certChain, _, _ := serverCert("example.com", defaultPort)
	origCert := certChain[0]

	expected := fmt.Sprintf("[{\"domainName\":\"example.com\",\"ip\":\"127.0.0.1\",\"issuer\":\"CA for test\",\"commonName\":\"example.com\",\"sans\":[\"example.com\",\"www.example.com\"],\"notBefore\":%q,\"notAfter\":%q,\"error\":\"\"}]", origCert.NotBefore.String(), origCert.NotAfter.String())

	certs, _ := NewCerts([]string{"example.com"})

	if string(certs.JSON()) != expected {
		t.Errorf(`unexpected return value %q, want %q`, certs.JSON(), expected)
	}
}

func TestCertsEscapeStarInSANs(t *testing.T) {
	serverCert = func(host, port string) ([]*x509.Certificate, string, error) {
		return []*x509.Certificate{
			&x509.Certificate{
				Issuer: pkix.Name{
					CommonName: "CA for test",
				},
				Subject: pkix.Name{
					CommonName: host,
				},
				DNSNames:  []string{host, "*." + host}, // include star
				NotBefore: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.Local),
				NotAfter:  time.Date(2018, time.January, 1, 0, 0, 0, 0, time.Local),
			},
		}, "127.0.0.1", nil
	}

	certs, _ := NewCerts([]string{"example.com"})

	certs = certs.escapeStar()

	if certs[0].SANs[1] != "\\*.example.com" {
		t.Errorf(`unexpected escaped value %q, want %q`, certs[0].SANs[1], "\\*.example.com")
	}
}

func TestSetUserTempl(t *testing.T) {
	stubCert()
	_ = SetUserTempl("{{range .}}Issuer: {{.Issuer}}{{end}}")
	expected := "Issuer: CA for test"

	certs, _ := NewCerts([]string{"example.com"})

	if certs.String() != expected {
		t.Errorf(`unexpected return value %q, want %q`, certs.String(), expected)
	}
}
<commit_msg>Add test<commit_after>package cert

import (
	"crypto/x509"
	"crypto/x509/pkix"
	"fmt"
	"testing"
	"time"
)

func stubCert() {
	serverCert = func(host, port string) ([]*x509.Certificate, string, error) {
		return []*x509.Certificate{
			&x509.Certificate{
				Issuer: pkix.Name{
					CommonName: "CA for test",
				},
				Subject: pkix.Name{
					CommonName: host,
				},
				DNSNames:  []string{host, "www." + host},
				NotBefore: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.Local),
				NotAfter:  time.Date(2018, time.January, 1, 0, 0, 0, 0, time.Local),
			},
			&x509.Certificate{
				Issuer: pkix.Name{
					CommonName: "parent of CA for test",
				},
				Subject: pkix.Name{
					CommonName: host,
				},
				DNSNames:  []string{host, "www." + host},
				NotBefore: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.Local),
				NotAfter:  time.Date(2018, time.January, 1, 0, 0, 0, 0, time.Local),
			},
		}, "127.0.0.1", nil
	}
}

func TestValidate(t *testing.T) {
	if err := validate([]string{"example.com"}); err != nil {
		t.Errorf(`unexpected err %s, want nil`, err.Error())
	}
}

func TestValidateError(t *testing.T) {
	if err := validate([]string{}); err == nil {
		t.Error(`unexpected nil, want error`)
	} else if err.Error() != "Input at least one domain name." {
		t.Errorf(`unexpected err message, want %q`, "Input at least one domain name.")
	}
}

func TestSplitHostPort(t *testing.T) {
	type want struct {
		host string
		port string
		err  error
	}
	var tests = []struct {
		input string
		want  want
	}{
		{"example.com", want{"example.com", defaultPort, nil}},
		{"example.com:443", want{"example.com", "443", nil}},
		{"imap.example.com:993", want{"imap.example.com", "993", nil}},
		{"smtp.example.com:465", want{"smtp.example.com", "465", nil}},
	}

	for _, test := range tests {
		host, port, err := SplitHostPort(test.input)
		got := want{
			host,
			port,
			err,
		}
		if got != test.want {
			t.Errorf("SplitHostPort(%q) = %v, want %v", test.input, got, test.want)
		}
	}
}

func TestNewCert(t *testing.T) {
	stubCert()

	input := "example.com"

	c := NewCert(input)
	certChain, _, _ := serverCert(input, defaultPort)
	origCert := certChain[0]

	if _, ok := interface{}(c).(*Cert); !ok {
		t.Errorf(`NewCert(%q) was not returned *Cert`, input)
	}
	if c.DomainName != "example.com" {
		t.Errorf(`unexpected Cert.DomainName %q, want %q`, c.DomainName, "example.com")
	}
	if c.IP != "127.0.0.1" {
		t.Errorf(`unexpected Cert.IP %q, want %q`, c.IP, "127.0.0.1")
	}
	if c.Issuer != "CA for test" {
		t.Errorf(`unexpected Cert.Issuer %q, want %q`, c.Issuer, "CA for test")
	}
	if c.CommonName != "example.com" {
		t.Errorf(`unexpected Cert.CommonName %q, want %q`, c.CommonName, "example.com")
	}
	if len(c.SANs) != 2 {
		t.Errorf(`unexpected Cert.SANs length %q, want %q`, len(c.SANs), 2)
	}
	if c.SANs[0] != "example.com" {
		t.Errorf(`unexpected Cert.SANs[0] %q, want %q`, c.SANs[0], "example.com")
	}
	if c.SANs[1] != "www.example.com" {
		t.Errorf(`unexpected Cert.SANs[1] %q, want %q`, c.SANs[1], "www.example.com")
	}
	if c.NotBefore != origCert.NotBefore.String() {
		t.Errorf(`unexpected Cert.NotBefore %q, want %q`, c.NotBefore, origCert.NotBefore.String())
	}
	if c.NotAfter != origCert.NotAfter.String() {
		t.Errorf(`unexpected Cert.NotAfter %q, want %q`, c.NotAfter, origCert.NotAfter.String())
	}
	if c.Error != "" {
		t.Errorf(`unexpected Cert.Error %q, want %q`, c.Error, "")
	}
}

func TestNewCerts(t *testing.T) {
	stubCert()

	input := []string{"example.com"}

	certs, _ := NewCerts(input)

	if _, ok := interface{}(certs).(Certs); !ok {
		t.Errorf(`unexpected return type %T, want Certs`, certs)
	}
}

func TestCertsAsString(t *testing.T) {
	stubCert()

	certChain, _, _ := serverCert("example.com", defaultPort)
	origCert := certChain[0]

	expected := fmt.Sprintf(`DomainName: example.com
IP:         127.0.0.1
Issuer:     CA for test
NotBefore:  %s
NotAfter:   %s
CommonName: example.com
SANs:       [example.com www.example.com]
Error:

`, origCert.NotBefore.String(), origCert.NotAfter.String())

	certs, _ := NewCerts([]string{"example.com"})

	if certs.String() != expected {
		t.Errorf(`unexpected return value %q, want %q`, certs.String(), expected)
	}
}

func TestCertsAsMarkdown(t *testing.T) {
	stubCert()

	certChain, _, _ := serverCert("example.com", defaultPort)
	origCert := certChain[0]

	expected := fmt.Sprintf(`DomainName | IP | Issuer | NotBefore | NotAfter | CN | SANs | Error
--- | --- | --- | --- | --- | --- | --- | ---
example.com | 127.0.0.1 | CA for test | %s | %s | example.com | example.com
www.example.com
 |

`, origCert.NotBefore.String(), origCert.NotAfter.String())

	certs, _ := NewCerts([]string{"example.com"})

	if certs.Markdown() != expected {
		t.Errorf(`unexpected return value %q, want %q`, certs.Markdown(), expected)
	}
}

func TestCertsAsJSON(t *testing.T) {
	stubCert()

	certChain, _, _ := serverCert("example.com", defaultPort)
	origCert := certChain[0]

	expected := fmt.Sprintf("[{\"domainName\":\"example.com\",\"ip\":\"127.0.0.1\",\"issuer\":\"CA for test\",\"commonName\":\"example.com\",\"sans\":[\"example.com\",\"www.example.com\"],\"notBefore\":%q,\"notAfter\":%q,\"error\":\"\"}]", origCert.NotBefore.String(), origCert.NotAfter.String())

	certs, _ := NewCerts([]string{"example.com"})

	if string(certs.JSON()) != expected {
		t.Errorf(`unexpected return value %q, want %q`, certs.JSON(), expected)
	}
}

func TestCertsEscapeStarInSANs(t *testing.T) {
	serverCert = func(host, port string) ([]*x509.Certificate, string, error) {
		return []*x509.Certificate{
			&x509.Certificate{
				Issuer: pkix.Name{
					CommonName: "CA for test",
				},
				Subject: pkix.Name{
					CommonName: host,
				},
				DNSNames:  []string{host, "*." + host}, // include star
				NotBefore: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.Local),
				NotAfter:  time.Date(2018, time.January, 1, 0, 0, 0, 0, time.Local),
			},
		}, "127.0.0.1", nil
	}

	certs, _ := NewCerts([]string{"example.com"})

	certs = certs.escapeStar()

	if certs[0].SANs[1] != "\\*.example.com" {
		t.Errorf(`unexpected escaped value %q, want %q`, certs[0].SANs[1], "\\*.example.com")
	}
}

func TestSetUserTempl(t *testing.T) {
	stubCert()
	_ = SetUserTempl("{{range .}}Issuer: {{.Issuer}}{{end}}")
	expected := "Issuer: CA for test"

	certs, _ := NewCerts([]string{"example.com"})

	if certs.String() != expected {
		t.Errorf(`unexpected return value %q, want %q`, certs.String(), expected)
	}
}

func TestDetail(t *testing.T) {
	stubCert()

	input := "example.com"

	c := NewCert(input)
	certChain, _, _ := serverCert(input, defaultPort)
	origCert := certChain[0]
	detail := c.Detail()

	if _, ok := interface{}(detail).(*x509.Certificate); !ok {
		t.Errorf(`Cert.Detail() was not returned *x509.Certificate`)
	}

	if detail.Issuer.CommonName != origCert.Issuer.CommonName {
		t.Errorf(`unexpected issuer common name %q, want %q`, detail.Issuer.CommonName, origCert.Issuer.CommonName)
	}
}

func TestCertChain(t *testing.T) {
	stubCert()

	input := "example.com"

	c := NewCert(input)
	expectedChain, _, _ := serverCert(input, defaultPort)
	certChain := c.CertChain()

	if _, ok := interface{}(certChain).([]*x509.Certificate); !ok {
		t.Errorf(`Cert.CertChain() was not returned []*x509.Certificate`)
	}

	if len(expectedChain) != len(certChain) {
		t.Errorf(`unexpected length %q, want %q`, len(certChain), len(expectedChain))
	}

	if certChain[0].Issuer.CommonName != "CA for test" {
		t.Errorf(`unexpected issuer common name %q, want %q`, certChain[0].Issuer.CommonName, "CA for test")
	}

	if certChain[1].Issuer.CommonName != "parent of CA for test" {
		t.Errorf(`unexpected issuer common name %q, want %q`, certChain[1].Issuer.CommonName, "parent of CA for test")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/getcfs/megacfs/formic"
	"github.com/getcfs/megacfs/oort/api/server"
	"github.com/gholt/ring"
	"go.uber.org/zap"
	"golang.org/x/net/context"
)

const (
	ADDR_FORMIC = iota
	ADDR_GROUP_GRPC
	ADDR_GROUP_REPL
	ADDR_VALUE_GRPC
	ADDR_VALUE_REPL
)

func main() {
	ringPath := "/etc/cfsd/cfs.ring"
	caPath := "/etc/cfsd/ca.pem"
	dataPath := "/var/lib/cfsd"
	var formicIP string
	var grpcGroupIP string
	var replGroupIP string
	var grpcValueIP string
	var replValueIP string
	var formicCertPath string
	var formicKeyPath string
	var grpcGroupCertPath string
	var grpcGroupKeyPath string
	var replGroupCertPath string
	var replGroupKeyPath string
	var grpcValueCertPath string
	var grpcValueKeyPath string
	var replValueCertPath string
	var replValueKeyPath string

	debug := false
	for _, arg := range os.Args[1:] {
		switch arg {
		case "debug", "--debug":
			debug = true
		}
	}
	var baseLogger *zap.Logger
	var err error
	if debug {
		baseLogger, err = zap.NewDevelopmentConfig().Build()
	} else {
		baseLogger, err = zap.NewProduction()
	}
	if err != nil {
		panic(err)
	}
	logger := baseLogger.With(zap.String("name", "cfsd"))

	fp, err := os.Open(ringPath)
	if err != nil {
		logger.Fatal("Couldn't open ring file", zap.Error(err))
	}
	oneRing, err := ring.LoadRing(fp)
	if err != nil {
		logger.Fatal("Error loading ring", zap.Error(err))
	}
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		logger.Fatal("Couldn't find network interfaces", zap.Error(err))
	}
FIND_LOCAL_NODE:
	for _, addrObj := range addrs {
		if ipNet, ok := addrObj.(*net.IPNet); ok {
			for _, node := range oneRing.Nodes() {
				for _, nodeAddr := range node.Addresses() {
					hostPort, err := ring.CanonicalHostPort(nodeAddr, 1)
					if err != nil {
						continue
					}
					host, _, err := net.SplitHostPort(hostPort)
					if err != nil {
						continue
					}
					// TODO: I guess we should update this incase they put a
					// host name instead of a direct IP.
					nodeIP := net.ParseIP(host)
					if ipNet.IP.Equal(nodeIP) {
						oneRing.SetLocalNode(node.ID())
						nodeAddr = node.Address(ADDR_FORMIC)
						i := strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							formicIP = nodeAddr[:i]
						}
						nodeAddr = node.Address(ADDR_GROUP_GRPC)
						i = strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							grpcGroupIP = nodeAddr[:i]
						}
						nodeAddr = node.Address(ADDR_GROUP_REPL)
						i = strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							replGroupIP = nodeAddr[:i]
						}
						nodeAddr = node.Address(ADDR_VALUE_GRPC)
						i = strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							grpcValueIP = nodeAddr[:i]
						}
						nodeAddr = node.Address(ADDR_VALUE_REPL)
						i = strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							replValueIP = nodeAddr[:i]
						}
						break FIND_LOCAL_NODE
					}
				}
			}
		}
	}
	formicCertPath = "/etc/cfsd/" + formicIP + ".pem"
	formicKeyPath = "/etc/cfsd/" + formicIP + "-key.pem"
	grpcGroupCertPath = "/etc/cfsd/" + grpcGroupIP + ".pem"
	grpcGroupKeyPath = "/etc/cfsd/" + grpcGroupIP + "-key.pem"
	replGroupCertPath = "/etc/cfsd/" + replGroupIP + ".pem"
	replGroupKeyPath = "/etc/cfsd/" + replGroupIP + "-key.pem"
	grpcValueCertPath = "/etc/cfsd/" + grpcValueIP + ".pem"
	grpcValueKeyPath = "/etc/cfsd/" + grpcValueIP + "-key.pem"
	replValueCertPath = "/etc/cfsd/" + replValueIP + ".pem"
	replValueKeyPath = "/etc/cfsd/" + replValueIP + "-key.pem"

	waitGroup := &sync.WaitGroup{}
	shutdownChan := make(chan struct{})

	groupStore, groupStoreRestartChan, err := server.NewGroupStore(&server.GroupStoreConfig{
		GRPCAddressIndex: ADDR_GROUP_GRPC,
		ReplAddressIndex: ADDR_GROUP_REPL,
		GRPCCertFile:     grpcGroupCertPath,
		GRPCKeyFile:      grpcGroupKeyPath,
		ReplCertFile:     replGroupCertPath,
		ReplKeyFile:      replGroupKeyPath,
		CAFile:           caPath,
		Scale:            0.4,
		Path:             dataPath,
		Ring:             oneRing,
	})
	if err != nil {
		logger.Fatal("Error initializing group store", zap.Error(err))
	}
	waitGroup.Add(1)
	go func() {
		for {
			select {
			case <-groupStoreRestartChan:
				ctx, _ := context.WithTimeout(context.Background(), time.Minute)
				groupStore.Shutdown(ctx)
				ctx, _ = context.WithTimeout(context.Background(), time.Minute)
				groupStore.Startup(ctx)
			case <-shutdownChan:
				ctx, _ := context.WithTimeout(context.Background(), time.Minute)
				groupStore.Shutdown(ctx)
				waitGroup.Done()
				return
			}
		}
	}()
	ctx, _ := context.WithTimeout(context.Background(), time.Minute)
	if err = groupStore.Startup(ctx); err != nil {
		ctx, _ = context.WithTimeout(context.Background(), time.Minute)
		groupStore.Shutdown(ctx)
		logger.Fatal("Error starting group store", zap.Error(err))
	}

	valueStore, valueStoreRestartChan, err := server.NewValueStore(&server.ValueStoreConfig{
		GRPCAddressIndex: ADDR_VALUE_GRPC,
		ReplAddressIndex: ADDR_VALUE_REPL,
		GRPCCertFile:     grpcValueCertPath,
		GRPCKeyFile:      grpcValueKeyPath,
		ReplCertFile:     replValueCertPath,
		ReplKeyFile:      replValueKeyPath,
		CAFile:           caPath,
		Scale:            0.4,
		Path:             dataPath,
		Ring:             oneRing,
	})
	if err != nil {
		logger.Fatal("Error initializing value store", zap.Error(err))
	}
	waitGroup.Add(1)
	go func() {
		for {
			select {
			case <-valueStoreRestartChan:
				ctx, _ := context.WithTimeout(context.Background(), time.Minute)
				valueStore.Shutdown(ctx)
				ctx, _ = context.WithTimeout(context.Background(), time.Minute)
				valueStore.Startup(ctx)
			case <-shutdownChan:
				ctx, _ := context.WithTimeout(context.Background(), time.Minute)
				valueStore.Shutdown(ctx)
				waitGroup.Done()
				return
			}
		}
	}()
	ctx, _ = context.WithTimeout(context.Background(), time.Minute)
	if err = valueStore.Startup(ctx); err != nil {
		ctx, _ = context.WithTimeout(context.Background(), time.Minute)
		valueStore.Shutdown(ctx)
		logger.Fatal("Error starting value store", zap.Error(err))
	}

	// Startup formic
	err = formic.NewFormicServer(&formic.Config{
		FormicAddressIndex: ADDR_FORMIC,
		ValueAddressIndex:  ADDR_VALUE_GRPC,
		GroupAddressIndex:  ADDR_GROUP_GRPC,
		CertFile:           formicCertPath,
		KeyFile:            formicKeyPath,
		CAFile:             caPath,
		Ring:               oneRing,
		RingPath:           ringPath,
		IpAddr:             formicIP,
		AuthUrl:            "http://localhost:5000",
		AuthUser:           "admin",
		AuthPassword:       "admin",
	}, logger)
	if err != nil {
		logger.Fatal("Couldn't start up formic", zap.Error(err))
	}

	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	waitGroup.Add(1)
	go func() {
		for {
			select {
			case <-ch:
				fmt.Println("Shutting down due to signal")
				close(shutdownChan)
				waitGroup.Done()
				return
			case <-shutdownChan:
				waitGroup.Done()
				return
			}
		}
	}()

	fmt.Println("Done launching components")
	waitGroup.Wait()
}
<commit_msg>Exit when no local ip matches<commit_after>package main

import (
	"fmt"
	"net"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/getcfs/megacfs/formic"
	"github.com/getcfs/megacfs/oort/api/server"
	"github.com/gholt/ring"
	"go.uber.org/zap"
	"golang.org/x/net/context"
)

const (
	ADDR_FORMIC = iota
	ADDR_GROUP_GRPC
	ADDR_GROUP_REPL
	ADDR_VALUE_GRPC
	ADDR_VALUE_REPL
)

func main() {
	ringPath := "/etc/cfsd/cfs.ring"
	caPath := "/etc/cfsd/ca.pem"
	dataPath := "/var/lib/cfsd"
	var formicIP string
	var grpcGroupIP string
	var replGroupIP string
	var grpcValueIP string
	var replValueIP string
	var formicCertPath string
	var formicKeyPath string
	var grpcGroupCertPath string
	var grpcGroupKeyPath string
	var replGroupCertPath string
	var replGroupKeyPath string
	var grpcValueCertPath string
	var grpcValueKeyPath string
	var replValueCertPath string
	var replValueKeyPath string

	debug := false
	for _, arg := range os.Args[1:] {
		switch arg {
		case "debug", "--debug":
			debug = true
		}
	}
	var baseLogger *zap.Logger
	var err error
	if debug {
		baseLogger, err = zap.NewDevelopmentConfig().Build()
	} else {
		baseLogger, err = zap.NewProduction()
	}
	if err != nil {
		panic(err)
	}
	logger := baseLogger.With(zap.String("name", "cfsd"))

	fp, err := os.Open(ringPath)
	if err != nil {
		logger.Fatal("Couldn't open ring file", zap.Error(err))
	}
	oneRing, err := ring.LoadRing(fp)
	if err != nil {
		logger.Fatal("Error loading ring", zap.Error(err))
	}
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		logger.Fatal("Couldn't find network interfaces", zap.Error(err))
	}
FIND_LOCAL_NODE:
	for _, addrObj := range addrs {
		if ipNet, ok := addrObj.(*net.IPNet); ok {
			for _, node := range oneRing.Nodes() {
				for _, nodeAddr := range node.Addresses() {
					hostPort, err := ring.CanonicalHostPort(nodeAddr, 1)
					if err != nil {
						continue
					}
					host, _, err := net.SplitHostPort(hostPort)
					if err != nil {
						continue
					}
					// TODO: I guess we should update this incase they put a
					// host name instead of a direct IP.
					nodeIP := net.ParseIP(host)
					if ipNet.IP.Equal(nodeIP) {
						oneRing.SetLocalNode(node.ID())
						nodeAddr = node.Address(ADDR_FORMIC)
						i := strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							formicIP = nodeAddr[:i]
						}
						nodeAddr = node.Address(ADDR_GROUP_GRPC)
						i = strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							grpcGroupIP = nodeAddr[:i]
						}
						nodeAddr = node.Address(ADDR_GROUP_REPL)
						i = strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							replGroupIP = nodeAddr[:i]
						}
						nodeAddr = node.Address(ADDR_VALUE_GRPC)
						i = strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							grpcValueIP = nodeAddr[:i]
						}
						nodeAddr = node.Address(ADDR_VALUE_REPL)
						i = strings.LastIndex(nodeAddr, ":")
						if i >= 0 {
							replValueIP = nodeAddr[:i]
						}
						break FIND_LOCAL_NODE
					}
				}
			}
		}
	}
	if formicIP == "" {
		logger.Fatal("No local IP match within ring.")
	}
	formicCertPath = "/etc/cfsd/" + formicIP + ".pem"
	formicKeyPath = "/etc/cfsd/" + formicIP + "-key.pem"
	grpcGroupCertPath = "/etc/cfsd/" + grpcGroupIP + ".pem"
	grpcGroupKeyPath = "/etc/cfsd/" + grpcGroupIP + "-key.pem"
	replGroupCertPath = "/etc/cfsd/" + replGroupIP + ".pem"
	replGroupKeyPath = "/etc/cfsd/" + replGroupIP + "-key.pem"
	grpcValueCertPath = "/etc/cfsd/" + grpcValueIP + ".pem"
	grpcValueKeyPath = "/etc/cfsd/" + grpcValueIP + "-key.pem"
	replValueCertPath = "/etc/cfsd/" + replValueIP + ".pem"
	replValueKeyPath = "/etc/cfsd/" + replValueIP + "-key.pem"

	waitGroup := &sync.WaitGroup{}
	shutdownChan := make(chan struct{})

	groupStore, groupStoreRestartChan, err := server.NewGroupStore(&server.GroupStoreConfig{
		GRPCAddressIndex: ADDR_GROUP_GRPC,
		ReplAddressIndex: ADDR_GROUP_REPL,
		GRPCCertFile:     grpcGroupCertPath,
		GRPCKeyFile:      grpcGroupKeyPath,
		ReplCertFile:     replGroupCertPath,
		ReplKeyFile:      replGroupKeyPath,
		CAFile:           caPath,
		Scale:            0.4,
		Path:             dataPath,
		Ring:             oneRing,
	})
	if err != nil {
		logger.Fatal("Error initializing group store", zap.Error(err))
	}
	waitGroup.Add(1)
	go func() {
		for {
			select {
			case <-groupStoreRestartChan:
				ctx, _ := context.WithTimeout(context.Background(), time.Minute)
				groupStore.Shutdown(ctx)
				ctx, _ = context.WithTimeout(context.Background(), time.Minute)
				groupStore.Startup(ctx)
			case <-shutdownChan:
				ctx, _ := context.WithTimeout(context.Background(), time.Minute)
				groupStore.Shutdown(ctx)
				waitGroup.Done()
				return
			}
		}
	}()
	ctx, _ := context.WithTimeout(context.Background(), time.Minute)
	if err = groupStore.Startup(ctx); err != nil {
		ctx, _ = context.WithTimeout(context.Background(), time.Minute)
		groupStore.Shutdown(ctx)
		logger.Fatal("Error starting group store", zap.Error(err))
	}

	valueStore, valueStoreRestartChan, err := server.NewValueStore(&server.ValueStoreConfig{
		GRPCAddressIndex: ADDR_VALUE_GRPC,
		ReplAddressIndex: ADDR_VALUE_REPL,
		GRPCCertFile:     grpcValueCertPath,
		GRPCKeyFile:      grpcValueKeyPath,
		ReplCertFile:     replValueCertPath,
		ReplKeyFile:      replValueKeyPath,
		CAFile:           caPath,
		Scale:            0.4,
		Path:             dataPath,
		Ring:             oneRing,
	})
	if err != nil {
		logger.Fatal("Error initializing value store", zap.Error(err))
	}
	waitGroup.Add(1)
	go func() {
		for {
			select {
			case <-valueStoreRestartChan:
				ctx, _ := context.WithTimeout(context.Background(), time.Minute)
				valueStore.Shutdown(ctx)
				ctx, _ = context.WithTimeout(context.Background(), time.Minute)
				valueStore.Startup(ctx)
			case <-shutdownChan:
				ctx, _ := context.WithTimeout(context.Background(), time.Minute)
				valueStore.Shutdown(ctx)
				waitGroup.Done()
				return
			}
		}
	}()
	ctx, _ = context.WithTimeout(context.Background(), time.Minute)
	if err = valueStore.Startup(ctx); err != nil {
		ctx, _ = context.WithTimeout(context.Background(), time.Minute)
		valueStore.Shutdown(ctx)
		logger.Fatal("Error starting value store", zap.Error(err))
	}

	// Startup formic
	err = formic.NewFormicServer(&formic.Config{
		FormicAddressIndex: ADDR_FORMIC,
		ValueAddressIndex:  ADDR_VALUE_GRPC,
		GroupAddressIndex:  ADDR_GROUP_GRPC,
		CertFile:           formicCertPath,
		KeyFile:            formicKeyPath,
		CAFile:             caPath,
		Ring:               oneRing,
		RingPath:           ringPath,
		IpAddr:             formicIP,
		AuthUrl:            "http://localhost:5000",
		AuthUser:           "admin",
		AuthPassword:       "admin",
	}, logger)
	if err != nil {
		logger.Fatal("Couldn't start up formic", zap.Error(err))
	}

	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	waitGroup.Add(1)
	go func() {
		for {
			select {
			case <-ch:
				fmt.Println("Shutting down due to signal")
				close(shutdownChan)
				waitGroup.Done()
				return
			case <-shutdownChan:
				waitGroup.Done()
				return
			}
		}
	}()

	fmt.Println("Done launching components")
	waitGroup.Wait()
}
<|endoftext|>


<commit_before>package sms // import "github.com/mndrix/sms-over-xmpp"

import (
	"crypto/rand"
	"encoding/base32"
	"encoding/xml"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	xco "github.com/mndrix/go-xco"
	"github.com/pkg/errors"
)

// ErrIgnoreMessage should be returned to indicate that a message
// should be ignored; as if it never happened.
var ErrIgnoreMessage = errors.New("ignore this message")

// Component represents an SMS-over-XMPP component.
type Component struct {
	config Config

	// xmpp is the XMPP component which handles all interactions
	// with an XMPP server.
	xmpp *xco.Component

	// xmppMutex serializes access to the XMPP component to avoid
	// collisions while talking to the XMPP server.
	xmppMutex sync.Mutex
}

// Main runs a component using the given configuration.  It's the main
// entrypoint for launching your own component if you don't want to
// use the sms-over-xmpp command.
func Main(config Config) {
	sc := &Component{config: config}

	// start goroutine for handling XMPP
	xmppErr, err := sc.runXmppComponent()
	if err != nil {
		panic(err)
	}

	// start goroutine for handling HTTP
	httpErr := sc.runHttpServer()

	for {
		select {
		case err := <-httpErr:
			log.Printf("ERROR HTTP: %s", err)
		case err := <-xmppErr:
			log.Printf("ERROR XMPP: %s", err)
		}
	}
}

func (sc *Component) runHttpServer() <-chan error {
	config := sc.config
	addr := fmt.Sprintf("%s:%d", config.HttpHost(), config.HttpPort())
	errCh := make(chan error)
	go func() {
		defer func() { close(errCh) }()
		for {
			errCh <- http.ListenAndServe(addr, sc)
			log.Printf("HTTP server quit. Restarting")
			time.Sleep(1 * time.Second)
		}
	}()
	return errCh
}

func (sc *Component) runXmppComponent() (<-chan error, error) {
	config := sc.config
	opts := xco.Options{
		Name:         config.ComponentName(),
		SharedSecret: config.SharedSecret(),
		Address:      fmt.Sprintf("%s:%d", config.XmppHost(), config.XmppPort()),
		Logger:       log.New(os.Stderr, "", log.LstdFlags),
	}
	c, err := xco.NewComponent(opts)
	if err != nil {
		return nil, err
	}

	c.MessageHandler = sc.onMessage
	c.PresenceHandler = sc.onPresence
	c.IqHandler = sc.onIq
	c.UnknownHandler = sc.onUnknown
	sc.xmpp = c

	errCh := make(chan error)
	go func() {
		defer func() { close(errCh) }()
		for {
			errCh <- c.Run()
			log.Printf("lost XMPP connection. Reconnecting")
			time.Sleep(1 * time.Second)
		}
	}()
	return errCh, nil
}

func (sc *Component) onMessage(c *xco.Component, m *xco.Message) error {
	log.Printf("Message: %+v", m)
	if m.Body == "" {
		log.Printf("  ignoring message with empty body")
		return nil
	}

	// convert recipient address into a phone number
	toPhone, err := sc.config.AddressToPhone(m.To)
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		return nil
	default:
		return errors.Wrap(err, "converting 'to' address to phone")
	}

	// convert author's address into a phone number
	fromPhone, err := sc.config.AddressToPhone(m.From)
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		return nil
	default:
		return errors.Wrap(err, "converting 'from' address to phone")
	}

	// choose an SMS provider
	provider, err := sc.config.SmsProvider()
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		return nil
	default:
		return errors.Wrap(err, "choosing an SMS provider")
	}

	// send the message
	err = provider.SendSms(fromPhone, toPhone, m.Body)
	return errors.Wrap(err, "sending SMS")
}

func (sc *Component) onPresence(c *xco.Component, p *xco.Presence) error {
	log.Printf("Presence: %+v", p)
	return nil
}

func (sc *Component) onIq(c *xco.Component, iq *xco.Iq) error {
	log.Printf("Iq: %+v", iq)
	return nil
}

func (sc *Component) onUnknown(c *xco.Component, x *xml.StartElement) error {
	log.Printf("Unknown: %+v", x)
	return nil
}

func (sc *Component) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	msgSid := r.FormValue("MessageSid")
	log.Printf("%s %s (%s)", r.Method, r.URL.Path, msgSid)

	// which SMS provider is applicable?
	provider, err := sc.config.SmsProvider()
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		msg := "ignored during provider selection"
		log.Println(msg)
		return
	default:
		msg := fmt.Sprintf("ERROR: choosing an SMS provider: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, msg)
		log.Println(msg)
		return
	}

	fromPhone, toPhone, body, err := provider.ReceiveSms(r)

	// convert author's phone number into XMPP address
	from, err := sc.config.PhoneToAddress(fromPhone)
	switch err {
	case nil:
		// all is well. proceed
	case ErrIgnoreMessage:
		msg := "ignored based on From address"
		log.Println(msg)
		return
	default:
		msg := fmt.Sprintf("ERROR: From address %s: %s", fromPhone, err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, msg)
		log.Println(msg)
		return
	}

	// convert recipient's phone number into XMPP address
	to, err := sc.config.PhoneToAddress(toPhone)
	switch err {
	case nil:
		// all is well. proceed
	case ErrIgnoreMessage:
		msg := "ignored based on To address"
		log.Println(msg)
		return
	default:
		msg := fmt.Sprintf("ERROR: To address %s: %s", toPhone, err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, msg)
		log.Println(msg)
		return
	}

	// deliver message over XMPP
	msg := &xco.Message{
		XMLName: xml.Name{
			Local: "message",
			Space: "jabber:component:accept",
		},

		Header: xco.Header{
			From: from,
			To:   to,
			ID:   NewId(),
		},
		Type: "chat",
		Body: body,
	}
	err = sc.xmppSend(msg)
	if err != nil {
		log.Printf("ERROR: can't send message: %s", err)
	}
}

// xmppSend sends a single XML stanza over the XMPP connection.  It
// serializes concurrent access to avoid collisions on the wire.
func (sc *Component) xmppSend(msg interface{}) error {
	sc.xmppMutex.Lock()
	defer func() { sc.xmppMutex.Unlock() }()

	return sc.xmpp.Send(msg)
}

// NewId generates a random string which is suitable as an XMPP stanza
// ID.  The string contains enough entropy to be universally unique.
func NewId() string {
	// generate 128 random bits (6 more than standard UUID)
	bytes := make([]byte, 16)
	_, err := rand.Read(bytes)
	if err != nil {
		panic(err)
	}

	// convert them to base 32 encoding
	s := base32.StdEncoding.EncodeToString(bytes)
	return strings.ToLower(strings.TrimRight(s, "="))
}
<commit_msg>Respect XMPP mutex during creation<commit_after>package sms // import "github.com/mndrix/sms-over-xmpp"

import (
	"crypto/rand"
	"encoding/base32"
	"encoding/xml"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	xco "github.com/mndrix/go-xco"
	"github.com/pkg/errors"
)

// ErrIgnoreMessage should be returned to indicate that a message
// should be ignored; as if it never happened.
var ErrIgnoreMessage = errors.New("ignore this message")

// Component represents an SMS-over-XMPP component.
type Component struct {
	config Config

	// xmpp is the XMPP component which handles all interactions
	// with an XMPP server.
	xmpp *xco.Component

	// xmppMutex serializes access to the XMPP component to avoid
	// collisions while talking to the XMPP server.
	xmppMutex sync.Mutex
}

// Main runs a component using the given configuration.  It's the main
// entrypoint for launching your own component if you don't want to
// use the sms-over-xmpp command.
func Main(config Config) {
	sc := &Component{config: config}

	// start goroutine for handling XMPP
	xmppErr, err := sc.runXmppComponent()
	if err != nil {
		panic(err)
	}

	// start goroutine for handling HTTP
	httpErr := sc.runHttpServer()

	for {
		select {
		case err := <-httpErr:
			log.Printf("ERROR HTTP: %s", err)
		case err := <-xmppErr:
			log.Printf("ERROR XMPP: %s", err)
		}
	}
}

func (sc *Component) runHttpServer() <-chan error {
	config := sc.config
	addr := fmt.Sprintf("%s:%d", config.HttpHost(), config.HttpPort())
	errCh := make(chan error)
	go func() {
		defer func() { close(errCh) }()
		for {
			errCh <- http.ListenAndServe(addr, sc)
			log.Printf("HTTP server quit. Restarting")
			time.Sleep(1 * time.Second)
		}
	}()
	return errCh
}

func (sc *Component) runXmppComponent() (<-chan error, error) {
	config := sc.config
	opts := xco.Options{
		Name:         config.ComponentName(),
		SharedSecret: config.SharedSecret(),
		Address:      fmt.Sprintf("%s:%d", config.XmppHost(), config.XmppPort()),
		Logger:       log.New(os.Stderr, "", log.LstdFlags),
	}
	c, err := xco.NewComponent(opts)
	if err != nil {
		return nil, err
	}

	c.MessageHandler = sc.onMessage
	c.PresenceHandler = sc.onPresence
	c.IqHandler = sc.onIq
	c.UnknownHandler = sc.onUnknown
	sc.setXmpp(c)

	errCh := make(chan error)
	go func() {
		defer func() { close(errCh) }()
		for {
			errCh <- c.Run()
			log.Printf("lost XMPP connection. Reconnecting")
			time.Sleep(1 * time.Second)
		}
	}()
	return errCh, nil
}

func (sc *Component) setXmpp(c *xco.Component) {
	sc.xmppMutex.Lock()
	defer func() { sc.xmppMutex.Unlock() }()

	sc.xmpp = c
}

func (sc *Component) onMessage(c *xco.Component, m *xco.Message) error {
	log.Printf("Message: %+v", m)
	if m.Body == "" {
		log.Printf("  ignoring message with empty body")
		return nil
	}

	// convert recipient address into a phone number
	toPhone, err := sc.config.AddressToPhone(m.To)
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		return nil
	default:
		return errors.Wrap(err, "converting 'to' address to phone")
	}

	// convert author's address into a phone number
	fromPhone, err := sc.config.AddressToPhone(m.From)
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		return nil
	default:
		return errors.Wrap(err, "converting 'from' address to phone")
	}

	// choose an SMS provider
	provider, err := sc.config.SmsProvider()
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		return nil
	default:
		return errors.Wrap(err, "choosing an SMS provider")
	}

	// send the message
	err = provider.SendSms(fromPhone, toPhone, m.Body)
	return errors.Wrap(err, "sending SMS")
}

func (sc *Component) onPresence(c *xco.Component, p *xco.Presence) error {
	log.Printf("Presence: %+v", p)
	return nil
}

func (sc *Component) onIq(c *xco.Component, iq *xco.Iq) error {
	log.Printf("Iq: %+v", iq)
	return nil
}

func (sc *Component) onUnknown(c *xco.Component, x *xml.StartElement) error {
	log.Printf("Unknown: %+v", x)
	return nil
}

func (sc *Component) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	msgSid := r.FormValue("MessageSid")
	log.Printf("%s %s (%s)", r.Method, r.URL.Path, msgSid)

	// which SMS provider is applicable?
	provider, err := sc.config.SmsProvider()
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		msg := "ignored during provider selection"
		log.Println(msg)
		return
	default:
		msg := fmt.Sprintf("ERROR: choosing an SMS provider: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, msg)
		log.Println(msg)
		return
	}

	fromPhone, toPhone, body, err := provider.ReceiveSms(r)

	// convert author's phone number into XMPP address
	from, err := sc.config.PhoneToAddress(fromPhone)
	switch err {
	case nil:
		// all is well. proceed
	case ErrIgnoreMessage:
		msg := "ignored based on From address"
		log.Println(msg)
		return
	default:
		msg := fmt.Sprintf("ERROR: From address %s: %s", fromPhone, err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, msg)
		log.Println(msg)
		return
	}

	// convert recipient's phone number into XMPP address
	to, err := sc.config.PhoneToAddress(toPhone)
	switch err {
	case nil:
		// all is well. proceed
	case ErrIgnoreMessage:
		msg := "ignored based on To address"
		log.Println(msg)
		return
	default:
		msg := fmt.Sprintf("ERROR: To address %s: %s", toPhone, err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, msg)
		log.Println(msg)
		return
	}

	// deliver message over XMPP
	msg := &xco.Message{
		XMLName: xml.Name{
			Local: "message",
			Space: "jabber:component:accept",
		},

		Header: xco.Header{
			From: from,
			To:   to,
			ID:   NewId(),
		},
		Type: "chat",
		Body: body,
	}
	err = sc.xmppSend(msg)
	if err != nil {
		log.Printf("ERROR: can't send message: %s", err)
	}
}

// xmppSend sends a single XML stanza over the XMPP connection.  It
// serializes concurrent access to avoid collisions on the wire.
func (sc *Component) xmppSend(msg interface{}) error {
	sc.xmppMutex.Lock()
	defer func() { sc.xmppMutex.Unlock() }()

	return sc.xmpp.Send(msg)
}

// NewId generates a random string which is suitable as an XMPP stanza
// ID.  The string contains enough entropy to be universally unique.
func NewId() string {
	// generate 128 random bits (6 more than standard UUID)
	bytes := make([]byte, 16)
	_, err := rand.Read(bytes)
	if err != nil {
		panic(err)
	}

	// convert them to base 32 encoding
	s := base32.StdEncoding.EncodeToString(bytes)
	return strings.ToLower(strings.TrimRight(s, "="))
}
<|endoftext|>


<commit_before>package conf

import (
	"fmt"
	"io"
	"io/ioutil"
	"regexp"
	"runtime"
	"strings"
	"text/template"

	"github.com/StackExchange/scollector/opentsdb"
	"github.com/StackExchange/tsaf/conf/parse"
	"github.com/StackExchange/tsaf/expr"
	eparse "github.com/StackExchange/tsaf/expr/parse"
)

type Conf struct {
	Vars
	Name        string // Config file name
	WebDir      string // Static content web directory: web
	TsdbHost    string // OpenTSDB relay and query destination: ny-devtsdb04:4242
	RelayListen string // OpenTSDB relay listen address: :4242
	HttpListen  string // Web server listen address: :80
	SmtpHost    string // SMTP address: ny-mail:25
	Templates   map[string]*Template
	Alerts      map[string]*Alert

	tree *parse.Tree
	node parse.Node
}

// at marks the state to be on node n, for error reporting.
func (c *Conf) at(node parse.Node) {
	c.node = node
}

func (c *Conf) error(err error) {
	c.errorf(err.Error())
}

// errorf formats the error and terminates processing.
func (c *Conf) errorf(format string, args ...interface{}) {
	if c.node == nil {
		format = fmt.Sprintf("conf: %s: %s", c.Name, format)
	} else {
		location, context := c.tree.ErrorContext(c.node)
		format = fmt.Sprintf("conf: %s: at <%s>: %s", location, context, format)
	}
	panic(fmt.Errorf(format, args...))
}

// errRecover is the handler that turns panics into returns from the top
// level of Parse.
func errRecover(errp *error) {
	e := recover()
	if e != nil {
		switch err := e.(type) {
		case runtime.Error:
			panic(e)
		case error:
			*errp = err
		default:
			panic(e)
		}
	}
}

type Alert struct {
	Vars
	*Template  `json:"-"`
	Name       string
	Owner      string     `json:",omitempty"`
	Crit       *expr.Expr `json:",omitempty"`
	Warn       *expr.Expr `json:",omitempty"`
	Overriders []*Alert   `json:"-"`
	Overrides  *Alert     `json:",omitempty"`

	crit, warn string
	template   string
	override   string
}

type Template struct {
	Vars
	Name          string
	Body, Subject *template.Template

	body, subject string
}

type context struct {
	Alert   *Alert
	Tags    opentsdb.TagSet
	Context opentsdb.Context
}

// E executes the given expression and returns a value with corresponding tags
// to the context's tags. If no such result is found, the first result with nil
// tags is returned. If no such result is found, nil is returned.
func (c *context) E(v string) expr.Value {
	e, err := expr.New(v)
	if err != nil {
		return nil
	}
	res, err := e.Execute(c.Context, nil)
	if err != nil {
		return nil
	}
	for _, r := range res {
		if r.Group.Equal(c.Tags) {
			return r.Value
		}
	}
	for _, r := range res {
		if r.Group == nil {
			return r.Value
		}
	}
	return nil
}

func (a *Alert) data(group opentsdb.TagSet, c opentsdb.Context) interface{} {
	return &context{
		a,
		group,
		c,
	}
}

func (a *Alert) ExecuteBody(w io.Writer, group opentsdb.TagSet, c opentsdb.Context) error {
	if a.Template.Body == nil {
		return nil
	}
	return a.Template.Body.Execute(w, a.data(group, c))
}

func (a *Alert) ExecuteSubject(w io.Writer, group opentsdb.TagSet, c opentsdb.Context) error {
	if a.Template.Subject == nil {
		return nil
	}
	return a.Template.Subject.Execute(w, a.data(group, c))
}

type Vars map[string]string

func ParseFile(fname string) (*Conf, error) {
	f, err := ioutil.ReadFile(fname)
	if err != nil {
		return nil, err
	}
	return New(fname, string(f))
}

func New(name, text string) (c *Conf, err error) {
	defer errRecover(&err)
	c = &Conf{
		Name:        name,
		HttpListen:  ":8070",
		RelayListen: ":4242",
		WebDir:      "web",
		Vars:        make(map[string]string),
		Templates:   make(map[string]*Template),
		Alerts:      make(map[string]*Alert),
	}
	c.tree, err = parse.Parse(name, text)
	if err != nil {
		c.error(err)
	}
	for _, n := range c.tree.Root.Nodes {
		c.at(n)
		switch n := n.(type) {
		case *parse.PairNode:
			c.loadGlobal(n)
		case *parse.SectionNode:
			c.loadSection(n)
		default:
			c.errorf("unexpected parse node %s", n)
		}
	}
	if c.TsdbHost == "" {
		c.at(nil)
		c.errorf("tsdbHost required")
	}
	return
}

func (c *Conf) loadGlobal(p *parse.PairNode) {
	v := p.Val.Text
	switch k := p.Key.Text; k {
	case "tsdbHost":
		c.TsdbHost = c.expand(v, nil)
	case "httpListen":
		c.HttpListen = c.expand(v, nil)
	case "relayListen":
		c.RelayListen = c.expand(v, nil)
	case "webDir":
		c.WebDir = c.expand(v, nil)
	case "smtpHost":
		c.SmtpHost = c.expand(v, nil)
	default:
		if !strings.HasPrefix(k, "$") {
			c.errorf("unknown key %s", k)
		}
		c.Vars[k] = c.expand(v, nil)
	}
}

func (c *Conf) loadSection(s *parse.SectionNode) {
	switch s.SectionType.Text {
	case "template":
		c.loadTemplate(s)
	case "alert":
		c.loadAlert(s)
	default:
		c.errorf("unknown section type: %s", s.SectionType.Text)
	}
}

func (c *Conf) loadTemplate(s *parse.SectionNode) {
	name := s.Name.Text
	if _, ok := c.Templates[name]; ok {
		c.errorf("duplicate template name: %s", name)
	}
	t := Template{
		Vars: make(map[string]string),
		Name: name,
	}
	V := func(v string) string {
		return c.expand(v, t.Vars)
	}
	master := template.New(name).Funcs(template.FuncMap{
		"V": V,
	})
	for _, p := range s.Nodes {
		c.at(p)
		v := p.Val.Text
		switch k := p.Key.Text; k {
		case "body":
			t.body = v
			tmpl := master.New(k)
			_, err := tmpl.Parse(t.body)
			if err != nil {
				c.error(err)
			}
			t.Body = tmpl
		case "subject":
			t.subject = v
			tmpl := master.New(k)
			_, err := tmpl.Parse(t.subject)
			if err != nil {
				c.error(err)
			}
			t.Subject = tmpl
		default:
			if !strings.HasPrefix(k, "$") {
				c.errorf("unknown key %s", k)
			}
			t.Vars[k] = v
			t.Vars[k[1:]] = t.Vars[k]
		}
	}
	c.at(s)
	if t.Body == nil && t.Subject == nil {
		c.errorf("neither body or subject specified")
	}
	c.Templates[name] = &t
}

func (c *Conf) loadAlert(s *parse.SectionNode) {
	name := s.Name.Text
	if _, ok := c.Alerts[name]; ok {
		c.errorf("duplicate template name: %s", name)
	}
	a := Alert{
		Vars: make(map[string]string),
		Name: name,
	}
	for _, p := range s.Nodes {
		c.at(p)
		v := p.Val.Text
		switch k := p.Key.Text; k {
		case "owner":
			if c.SmtpHost == "" {
				c.errorf("no smtpHost specified, can't specify owner")
			}
			a.Owner = c.expand(v, a.Vars)
		case "template":
			a.template = c.expand(v, a.Vars)
			t, ok := c.Templates[a.template]
			if !ok {
				c.errorf("unknown template %s", a.template)
			}
			a.Template = t
		case "override":
			a.override = c.expand(v, a.Vars)
			o, ok := c.Alerts[a.override]
			if !ok {
				c.errorf("unknown alert %s", a.override)
			}
			a.Overrides = o
			o.Overriders = append(o.Overriders, &a)
		case "crit":
			a.crit = c.expand(v, a.Vars)
			crit, err := expr.New(a.crit)
			if err != nil {
				c.error(err)
			}
			if crit.Root.Return() != eparse.TYPE_NUMBER {
				c.errorf("crit must return a number")
			}
			a.Crit = crit
		case "warn":
			a.warn = c.expand(v, a.Vars)
			warn, err := expr.New(a.warn)
			if err != nil {
				c.error(err)
			}
			if warn.Root.Return() != eparse.TYPE_NUMBER {
				c.errorf("warn must return a number")
			}
			a.Warn = warn
		default:
			if !strings.HasPrefix(k, "$") {
				c.errorf("unknown key %s", k)
			}
			a.Vars[k] = c.expand(v, a.Vars)
			a.Vars[k[1:]] = a.Vars[k]
		}
	}
	c.at(s)
	if a.Crit == nil && a.Warn == nil {
		c.errorf("neither crit or warn specified")
	}
	c.Alerts[name] = &a
}

var exRE = regexp.MustCompile(`\$\w+`)

func (c *Conf) expand(v string, vars map[string]string) string {
	v = exRE.ReplaceAllStringFunc(v, func(s string) string {
		if vars != nil {
			if n, ok := vars[s]; ok {
				return c.expand(n, vars)
			}
		}
		n, ok := c.Vars[s]
		if !ok {
			c.errorf("unknown variable %s", s)
		}
		return c.expand(n, nil)
	})
	return v
}
<commit_msg>Also allow nil templates<commit_after>package conf

import (
	"fmt"
	"io"
	"io/ioutil"
	"regexp"
	"runtime"
	"strings"
	"text/template"

	"github.com/StackExchange/scollector/opentsdb"
	"github.com/StackExchange/tsaf/conf/parse"
	"github.com/StackExchange/tsaf/expr"
	eparse "github.com/StackExchange/tsaf/expr/parse"
)

type Conf struct {
	Vars
	Name        string // Config file name
	WebDir      string // Static content web directory: web
	TsdbHost    string // OpenTSDB relay and query destination: ny-devtsdb04:4242
	RelayListen string // OpenTSDB relay listen address: :4242
	HttpListen  string // Web server listen address: :80
	SmtpHost    string // SMTP address: ny-mail:25
	Templates   map[string]*Template
	Alerts      map[string]*Alert

	tree *parse.Tree
	node parse.Node
}

// at marks the state to be on node n, for error reporting.
func (c *Conf) at(node parse.Node) {
	c.node = node
}

func (c *Conf) error(err error) {
	c.errorf(err.Error())
}

// errorf formats the error and terminates processing.
func (c *Conf) errorf(format string, args ...interface{}) {
	if c.node == nil {
		format = fmt.Sprintf("conf: %s: %s", c.Name, format)
	} else {
		location, context := c.tree.ErrorContext(c.node)
		format = fmt.Sprintf("conf: %s: at <%s>: %s", location, context, format)
	}
	panic(fmt.Errorf(format, args...))
}

// errRecover is the handler that turns panics into returns from the top
// level of Parse.
func errRecover(errp *error) {
	e := recover()
	if e != nil {
		switch err := e.(type) {
		case runtime.Error:
			panic(e)
		case error:
			*errp = err
		default:
			panic(e)
		}
	}
}

type Alert struct {
	Vars
	*Template  `json:"-"`
	Name       string
	Owner      string     `json:",omitempty"`
	Crit       *expr.Expr `json:",omitempty"`
	Warn       *expr.Expr `json:",omitempty"`
	Overriders []*Alert   `json:"-"`
	Overrides  *Alert     `json:",omitempty"`

	crit, warn string
	template   string
	override   string
}

type Template struct {
	Vars
	Name          string
	Body, Subject *template.Template

	body, subject string
}

type context struct {
	Alert   *Alert
	Tags    opentsdb.TagSet
	Context opentsdb.Context
}

// E executes the given expression and returns a value with corresponding tags
// to the context's tags. If no such result is found, the first result with nil
// tags is returned. If no such result is found, nil is returned.
func (c *context) E(v string) expr.Value {
	e, err := expr.New(v)
	if err != nil {
		return nil
	}
	res, err := e.Execute(c.Context, nil)
	if err != nil {
		return nil
	}
	for _, r := range res {
		if r.Group.Equal(c.Tags) {
			return r.Value
		}
	}
	for _, r := range res {
		if r.Group == nil {
			return r.Value
		}
	}
	return nil
}

func (a *Alert) data(group opentsdb.TagSet, c opentsdb.Context) interface{} {
	return &context{
		a,
		group,
		c,
	}
}

func (a *Alert) ExecuteBody(w io.Writer, group opentsdb.TagSet, c opentsdb.Context) error {
	if a.Template == nil || a.Template.Body == nil {
		return nil
	}
	return a.Template.Body.Execute(w, a.data(group, c))
}

func (a *Alert) ExecuteSubject(w io.Writer, group opentsdb.TagSet, c opentsdb.Context) error {
	if a.Template == nil || a.Template.Subject == nil {
		return nil
	}
	return a.Template.Subject.Execute(w, a.data(group, c))
}

type Vars map[string]string

func ParseFile(fname string) (*Conf, error) {
	f, err := ioutil.ReadFile(fname)
	if err != nil {
		return nil, err
	}
	return New(fname, string(f))
}

func New(name, text string) (c *Conf, err error) {
	defer errRecover(&err)
	c = &Conf{
		Name:        name,
		HttpListen:  ":8070",
		RelayListen: ":4242",
		WebDir:      "web",
		Vars:        make(map[string]string),
		Templates:   make(map[string]*Template),
		Alerts:      make(map[string]*Alert),
	}
	c.tree, err = parse.Parse(name, text)
	if err != nil {
		c.error(err)
	}
	for _, n := range c.tree.Root.Nodes {
		c.at(n)
		switch n := n.(type) {
		case *parse.PairNode:
			c.loadGlobal(n)
		case *parse.SectionNode:
			c.loadSection(n)
		default:
			c.errorf("unexpected parse node %s", n)
		}
	}
	if c.TsdbHost == "" {
		c.at(nil)
		c.errorf("tsdbHost required")
	}
	return
}

func (c *Conf) loadGlobal(p *parse.PairNode) {
	v := p.Val.Text
	switch k := p.Key.Text; k {
	case "tsdbHost":
		c.TsdbHost = c.expand(v, nil)
	case "httpListen":
		c.HttpListen = c.expand(v, nil)
	case "relayListen":
		c.RelayListen = c.expand(v, nil)
	case "webDir":
		c.WebDir = c.expand(v, nil)
	case "smtpHost":
		c.SmtpHost = c.expand(v, nil)
	default:
		if !strings.HasPrefix(k, "$") {
			c.errorf("unknown key %s", k)
		}
		c.Vars[k] = c.expand(v, nil)
	}
}

func (c *Conf) loadSection(s *parse.SectionNode) {
	switch s.SectionType.Text {
	case "template":
		c.loadTemplate(s)
	case "alert":
		c.loadAlert(s)
	default:
		c.errorf("unknown section type: %s", s.SectionType.Text)
	}
}

func (c *Conf) loadTemplate(s *parse.SectionNode) {
	name := s.Name.Text
	if _, ok := c.Templates[name]; ok {
		c.errorf("duplicate template name: %s", name)
	}
	t := Template{
		Vars: make(map[string]string),
		Name: name,
	}
	V := func(v string) string {
		return c.expand(v, t.Vars)
	}
	master := template.New(name).Funcs(template.FuncMap{
		"V": V,
	})
	for _, p := range s.Nodes {
		c.at(p)
		v := p.Val.Text
		switch k := p.Key.Text; k {
		case "body":
			t.body = v
			tmpl := master.New(k)
			_, err := tmpl.Parse(t.body)
			if err != nil {
				c.error(err)
			}
			t.Body = tmpl
		case "subject":
			t.subject = v
			tmpl := master.New(k)
			_, err := tmpl.Parse(t.subject)
			if err != nil {
				c.error(err)
			}
			t.Subject = tmpl
		default:
			if !strings.HasPrefix(k, "$") {
				c.errorf("unknown key %s", k)
			}
			t.Vars[k] = v
			t.Vars[k[1:]] = t.Vars[k]
		}
	}
	c.at(s)
	if t.Body == nil && t.Subject == nil {
		c.errorf("neither body or subject specified")
	}
	c.Templates[name] = &t
}

func (c *Conf) loadAlert(s *parse.SectionNode) {
	name := s.Name.Text
	if _, ok := c.Alerts[name]; ok {
		c.errorf("duplicate template name: %s", name)
	}
	a := Alert{
		Vars: make(map[string]string),
		Name: name,
	}
	for _, p := range s.Nodes {
		c.at(p)
		v := p.Val.Text
		switch k := p.Key.Text; k {
		case "owner":
			if c.SmtpHost == "" {
				c.errorf("no smtpHost specified, can't specify owner")
			}
			a.Owner = c.expand(v, a.Vars)
		case "template":
			a.template = c.expand(v, a.Vars)
			t, ok := c.Templates[a.template]
			if !ok {
				c.errorf("unknown template %s", a.template)
			}
			a.Template = t
		case "override":
			a.override = c.expand(v, a.Vars)
			o, ok := c.Alerts[a.override]
			if !ok {
				c.errorf("unknown alert %s", a.override)
			}
			a.Overrides = o
			o.Overriders = append(o.Overriders, &a)
		case "crit":
			a.crit = c.expand(v, a.Vars)
			crit, err := expr.New(a.crit)
			if err != nil {
				c.error(err)
			}
			if crit.Root.Return() != eparse.TYPE_NUMBER {
				c.errorf("crit must return a number")
			}
			a.Crit = crit
		case "warn":
			a.warn = c.expand(v, a.Vars)
			warn, err := expr.New(a.warn)
			if err != nil {
				c.error(err)
			}
			if warn.Root.Return() != eparse.TYPE_NUMBER {
				c.errorf("warn must return a number")
			}
			a.Warn = warn
		default:
			if !strings.HasPrefix(k, "$") {
				c.errorf("unknown key %s", k)
			}
			a.Vars[k] = c.expand(v, a.Vars)
			a.Vars[k[1:]] = a.Vars[k]
		}
	}
	c.at(s)
	if a.Crit == nil && a.Warn == nil {
		c.errorf("neither crit or warn specified")
	}
	c.Alerts[name] = &a
}

var exRE = regexp.MustCompile(`\$\w+`)

func (c *Conf) expand(v string, vars map[string]string) string {
	v = exRE.ReplaceAllStringFunc(v, func(s string) string {
		if vars != nil {
			if n, ok := vars[s]; ok {
				return c.expand(n, vars)
			}
		}
		n, ok := c.Vars[s]
		if !ok {
			c.errorf("unknown variable %s", s)
		}
		return c.expand(n, nil)
	})
	return v
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"os"
	"os/exec"
	"testing"
)

func TestLbpkrSelfBdist(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "test-lbpkr-")
	if err != nil {
		t.Fatalf("error creating temporary directory: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	cmd := newCommand("lbpkr", "self", "bdist")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = tmpdir

	err = cmd.Run()
	if err != nil {
		t.Fatalf("error running bdist: %v", err)
	}
}

func TestLbpkrSelfBdistRpm(t *testing.T) {
	if _, err := exec.LookPath("rpmbuild"); err != nil {
		t.Skip("no rpmbuild installed")
	}

	tmpdir, err := ioutil.TempDir("", "test-lbpkr-")
	if err != nil {
		t.Fatalf("error creating temporary directory: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	cmd := newCommand("lbpkr", "self", "bdist-rpm")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = tmpdir

	err = cmd.Run()
	if err != nil {
		t.Fatalf("error running bdist-rpm: %v", err)
	}
}

func TestLbpkrInstallLbpkr(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "test-lbpkr-")
	if err != nil {
		t.Fatalf("error creating temporary directory: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	cmd := newCommand("lbpkr", "install", "-siteroot="+tmpdir, "lbpkr")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = tmpdir

	err = cmd.Run()
	if err != nil {
		t.Fatalf("error running install: %v", err)
	}
}

func TestRPMSplit(t *testing.T) {
	for _, table := range []struct {
		rpm  string
		want [3]string
	}{
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0.0-",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0.0", ""},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-10.20.30-1",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "10.20.30", "1"},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0.0-1",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0.0", "1"},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0.0-71",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0.0", "71"},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0-71",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0", "71"},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-10.20.30",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "10.20.30", ""},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0.0",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0.0", ""},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "", ""},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-1.0.0-1",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "1.0.0", "1"},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-10.20.30-1",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "10.20.30", "1"},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-1.0.0-71",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "1.0.0", "71"},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-10.20.30",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "10.20.30", ""},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-1.0.0",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "1.0.0", ""},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "", ""},
		},
		{
			rpm:  "BRUNEL_v45r1-1.0.0-21",
			want: [3]string{"BRUNEL_v45r1", "1.0.0", "21"},
		},
		{
			rpm:  "BRUNEL_v45r1-1.0.0-1",
			want: [3]string{"BRUNEL_v45r1", "1.0.0", "1"},
		},
		{
			rpm:  "BRUNEL_v45r1-1.0.0",
			want: [3]string{"BRUNEL_v45r1", "1.0.0", ""},
		},
		{
			rpm:  "BRUNEL_v45r1",
			want: [3]string{"BRUNEL_v45r1", "", ""},
		},
		{
			rpm:  "BRUNEL_v45r1_x86_64_slc6_gcc48_opt-1.0.0-21",
			want: [3]string{"BRUNEL_v45r1_x86_64_slc6_gcc48_opt", "1.0.0", "21"},
		},
		{
			rpm:  "BRUNEL_v45r1_x86_64_slc6_gcc48_opt-1.0.0-1",
			want: [3]string{"BRUNEL_v45r1_x86_64_slc6_gcc48_opt", "1.0.0", "1"},
		},
		{
			rpm:  "BRUNEL_v45r1_x86_64_slc6_gcc48_opt-1.0.0",
			want: [3]string{"BRUNEL_v45r1_x86_64_slc6_gcc48_opt", "1.0.0", ""},
		},
		{
			rpm:  "BRUNEL_v45r1_x86_64_slc6_gcc48_opt",
			want: [3]string{"BRUNEL_v45r1_x86_64_slc6_gcc48_opt", "", ""},
		},
	} {
		rpm := splitRPM(table.rpm)
		if rpm != table.want {
			t.Errorf(
				"%s: error.\nwant=[name=%q version=%q release=%q].\n got=[name=%q version=%q release=%q]\n",
				table.rpm,
				table.want[0], table.want[1], table.want[2],
				rpm[0], rpm[1], rpm[2],
			)
		}
	}
}
<commit_msg>test: skip some tests in short mode<commit_after>package main

import (
	"io/ioutil"
	"os"
	"os/exec"
	"testing"
)

func TestLbpkrSelfBdist(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	tmpdir, err := ioutil.TempDir("", "test-lbpkr-")
	if err != nil {
		t.Fatalf("error creating temporary directory: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	cmd := newCommand("lbpkr", "self", "bdist")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = tmpdir

	err = cmd.Run()
	if err != nil {
		t.Fatalf("error running bdist: %v", err)
	}
}

func TestLbpkrSelfBdistRpm(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	if _, err := exec.LookPath("rpmbuild"); err != nil {
		t.Skip("no rpmbuild installed")
	}

	tmpdir, err := ioutil.TempDir("", "test-lbpkr-")
	if err != nil {
		t.Fatalf("error creating temporary directory: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	cmd := newCommand("lbpkr", "self", "bdist-rpm")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = tmpdir

	err = cmd.Run()
	if err != nil {
		t.Fatalf("error running bdist-rpm: %v", err)
	}
}

func TestLbpkrInstallLbpkr(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	tmpdir, err := ioutil.TempDir("", "test-lbpkr-")
	if err != nil {
		t.Fatalf("error creating temporary directory: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	cmd := newCommand("lbpkr", "install", "-siteroot="+tmpdir, "lbpkr")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = tmpdir

	err = cmd.Run()
	if err != nil {
		t.Fatalf("error running install: %v", err)
	}
}

func TestRPMSplit(t *testing.T) {
	for _, table := range []struct {
		rpm  string
		want [3]string
	}{
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0.0-",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0.0", ""},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-10.20.30-1",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "10.20.30", "1"},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0.0-1",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0.0", "1"},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0.0-71",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0.0", "71"},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0-71",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0", "71"},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-10.20.30",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "10.20.30", ""},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt-1.0.0",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "1.0.0", ""},
		},
		{
			rpm:  "AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt",
			want: [3]string{"AIDA-3fe9f_3.2.1_x86_64_slc6_gcc49_opt", "", ""},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-1.0.0-1",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "1.0.0", "1"},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-10.20.30-1",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "10.20.30", "1"},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-1.0.0-71",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "1.0.0", "71"},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-10.20.30",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "10.20.30", ""},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt-1.0.0",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "1.0.0", ""},
		},
		{
			rpm:  "LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt",
			want: [3]string{"LCG_67_AIDA_3.2.1_x86_64_slc6_gcc47_opt", "", ""},
		},
		{
			rpm:  "BRUNEL_v45r1-1.0.0-21",
			want: [3]string{"BRUNEL_v45r1", "1.0.0", "21"},
		},
		{
			rpm:  "BRUNEL_v45r1-1.0.0-1",
			want: [3]string{"BRUNEL_v45r1", "1.0.0", "1"},
		},
		{
			rpm:  "BRUNEL_v45r1-1.0.0",
			want: [3]string{"BRUNEL_v45r1", "1.0.0", ""},
		},
		{
			rpm:  "BRUNEL_v45r1",
			want: [3]string{"BRUNEL_v45r1", "", ""},
		},
		{
			rpm:  "BRUNEL_v45r1_x86_64_slc6_gcc48_opt-1.0.0-21",
			want: [3]string{"BRUNEL_v45r1_x86_64_slc6_gcc48_opt", "1.0.0", "21"},
		},
		{
			rpm:  "BRUNEL_v45r1_x86_64_slc6_gcc48_opt-1.0.0-1",
			want: [3]string{"BRUNEL_v45r1_x86_64_slc6_gcc48_opt", "1.0.0", "1"},
		},
		{
			rpm:  "BRUNEL_v45r1_x86_64_slc6_gcc48_opt-1.0.0",
			want: [3]string{"BRUNEL_v45r1_x86_64_slc6_gcc48_opt", "1.0.0", ""},
		},
		{
			rpm:  "BRUNEL_v45r1_x86_64_slc6_gcc48_opt",
			want: [3]string{"BRUNEL_v45r1_x86_64_slc6_gcc48_opt", "", ""},
		},
	} {
		rpm := splitRPM(table.rpm)
		if rpm != table.want {
			t.Errorf(
				"%s: error.\nwant=[name=%q version=%q release=%q].\n got=[name=%q version=%q release=%q]\n",
				table.rpm,
				table.want[0], table.want[1], table.want[2],
				rpm[0], rpm[1], rpm[2],
			)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
	"time"
)

// Test for function returnTime.
func Test_returnTime(t *testing.T) {
	tests := []struct {
		name    string
		t       string
		want    time.Time
		wantErr bool
	}{
		{"unix basic test", "1450137600", time.Unix(1450137600, 0).UTC(), false},
		{"natural language test", "December 15, 2015", time.Unix(1450137600, 0).UTC(), false},
		{"nonsense", "ooglyboo", time.Time{}, true},
		{"Check that UNIX time is correct", "January 29, 2031", time.Unix(1927411200, 0).UTC(), false},
		{"natural language test with short month", "Dec 15, 2015", time.Unix(1450137600, 0).UTC(), false},
	}
	for _, tt := range tests {
		got, err := returnTime(tt.t)
		if (err != nil) != tt.wantErr {
			t.Errorf("%q. returnTime() error = %v, wantErr %v", tt.name, err, tt.wantErr)
			continue
		}
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("%q. returnTime() = %v, want %v", tt.name, got, tt.want)
		}
	}
}

func Test_timestamp(t *testing.T) {
	cases := []struct {
		in, out string
	}{
		{"1450137600", `{"unix":1450137600,"natural":"December 15, 2015"}`},
		{"December 15, 2015", `{"unix":1450137600,"natural":"December 15, 2015"}`},
		{"garbage Not Date", `{"unix":null,"natural":null}`},
	}
	for _, c := range cases {
		req, err := http.NewRequest(
			http.MethodGet,
			"http://localhost:8080/"+c.in,
			nil,
		)
		if err != nil {
			t.Fatalf("could not create request: %v", err)
		}

		rec := httptest.NewRecorder()
		timestamp(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("expected status 200; got %d", rec.Code)
		}
		if rec.Body.String() != c.out {
			t.Errorf("unexpected body in response: %q", rec.Body.String())
		}
	}
}
<commit_msg>added short garbage test.<commit_after>package main

import (
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
	"time"
)

// Test for function returnTime.
func Test_returnTime(t *testing.T) {
	tests := []struct {
		name    string
		t       string
		want    time.Time
		wantErr bool
	}{
		{"unix basic test", "1450137600", time.Unix(1450137600, 0).UTC(), false},
		{"natural language test", "December 15, 2015", time.Unix(1450137600, 0).UTC(), false},
		{"nonsense", "ooglyboo", time.Time{}, true},
		{"Check that UNIX time is correct", "January 29, 2031", time.Unix(1927411200, 0).UTC(), false},
		{"natural language test with short month", "Dec 15, 2015", time.Unix(1450137600, 0).UTC(), false},
		{"short garbage", "1 1 1", time.Time{}, true},
	}
	for _, tt := range tests {
		got, err := returnTime(tt.t)
		if (err != nil) != tt.wantErr {
			t.Errorf("%q. returnTime() error = %v, wantErr %v", tt.name, err, tt.wantErr)
			continue
		}
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("%q. returnTime() = %v, want %v", tt.name, got, tt.want)
		}
	}
}

func Test_timestamp(t *testing.T) {
	cases := []struct {
		in, out string
	}{
		{"1450137600", `{"unix":1450137600,"natural":"December 15, 2015"}`},
		{"December 15, 2015", `{"unix":1450137600,"natural":"December 15, 2015"}`},
		{"garbage Not Date", `{"unix":null,"natural":null}`},
	}
	for _, c := range cases {
		req, err := http.NewRequest(
			http.MethodGet,
			"http://localhost:8080/"+c.in,
			nil,
		)
		if err != nil {
			t.Fatalf("could not create request: %v", err)
		}

		rec := httptest.NewRecorder()
		timestamp(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("expected status 200; got %d", rec.Code)
		}
		if rec.Body.String() != c.out {
			t.Errorf("unexpected body in response: %q", rec.Body.String())
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"testing"
)

var (
	testInputFile     = "./pb/test.pb.go"
	testInputFileTemp = "./pb/test.pb.go_tmp"
)

func TestTagFromComment(t *testing.T) {
	var tests = []struct {
		comment string
		tag     string
	}{
		{comment: `//@inject_tag: valid:"abc"`, tag: `valid:"abc"`},
		{comment: `//   @inject_tag: valid:"abcd"`, tag: `valid:"abcd"`},
		{comment: `// @inject_tag:      valid:"xyz"`, tag: `valid:"xyz"`},
		{comment: `// fdsafsa`, tag: ""},
		{comment: `//@inject_tag:`, tag: ""},
		{comment: `// @inject_tag: json:"abc" yaml:"abc`, tag: `json:"abc" yaml:"abc`},
		{comment: `// test @inject_tag: json:"abc" yaml:"abc`, tag: `json:"abc" yaml:"abc`},
	}
	for _, test := range tests {
		result := tagFromComment(test.comment)
		if result != test.tag {
			t.Errorf("expected tag: %q, got: %q", test.tag, result)
		}
	}
}

func TestParseWriteFile(t *testing.T) {
	expectedTag := `valid:"ip" yaml:"ip" json:"overrided"`

	areas, err := parseFile(testInputFile, []string{})
	if err != nil {
		t.Fatal(err)
	}
	if len(areas) != 8 {
		t.Fatalf("expected 8 areas to replace, got: %d", len(areas))
	}
	area := areas[0]
	t.Logf("area: %v", area)
	if area.InjectTag != expectedTag {
		t.Errorf("expected tag: %q, got: %q", expectedTag, area.InjectTag)
	}

	// make a copy of test file
	contents, err := ioutil.ReadFile(testInputFile)
	if err != nil {
		t.Fatal(err)
	}
	if err = ioutil.WriteFile(testInputFileTemp, contents, 0644); err != nil {
		t.Fatal(err)
	}
	defer os.Remove(testInputFileTemp)

	if err = writeFile(testInputFileTemp, areas); err != nil {
		t.Fatal(err)
	}

	// check if file contains custom tag
	contents, err = ioutil.ReadFile(testInputFileTemp)
	if err != nil {
		t.Fatal(err)
	}
	expectedExpr := "Address[ \t]+string[ \t]+`protobuf:\"bytes,1,opt,name=Address,proto3\" json:\"overrided\" valid:\"ip\" yaml:\"ip\"`"
	matched, err := regexp.Match(expectedExpr, contents)
	if err != nil || matched != true {
		t.Error("file doesn't contains custom tag after writing")
		t.Log(string(contents))
	}
}

func TestNewTagItems(t *testing.T) {
	var tests = []struct {
		tag   string
		items tagItems
	}{
		{
			tag: `valid:"ip" yaml:"ip, required" json:"overrided"`,
			items: []tagItem{
				{key: "valid", value: `"ip"`},
				{key: "yaml", value: `"ip, required"`},
				{key: "json", value: `"overrided"`},
			},
		},
		{
			tag: `validate:"omitempty,oneof=a b c d"`,
			items: []tagItem{
				{key: "validate", value: `"omitempty,oneof=a b c d"`},
			},
		},
	}

	for _, test := range tests {
		for i, item := range newTagItems(test.tag) {
			if item.key != test.items[i].key || item.value != test.items[i].value {
				t.Errorf("wrong tag item for tag %s, expected %v, got: %v",
					test.tag, test.items[i], item)
			}
		}
	}
}

func TestContinueParsingWhenSkippingFields(t *testing.T) {
	expectedTags := []string{
		`valid:"ip" yaml:"ip" json:"overrided"`,
		`valid:"http|https"`,
		`valid:"nonzero"`,
		`validate:"omitempty"`,
		`xml:"-"`,
		`validate:"omitempty"`,
		`tag:"foo_bar"`,
		`tag:"foo"`,
		`tag:"bar"`,
	}

	areas, err := parseFile(testInputFile, []string{"xml"})
	if err != nil {
		t.Fatal(err)
	}

	if len(areas) != len(expectedTags) {
		t.Fatalf("expected %d areas to replace, got: %d", len(expectedTags), len(areas))
	}

	for i, a := range areas {
		if a.InjectTag != expectedTags[i] {
			t.Errorf("expected tag: %q, got: %q", expectedTags[i], a.InjectTag)
		}
	}

	// make a copy of test file
	contents, err := ioutil.ReadFile(testInputFile)
	if err != nil {
		t.Fatal(err)
	}
	if err = ioutil.WriteFile(testInputFileTemp, contents, 0644); err != nil {
		t.Fatal(err)
	}
	defer os.Remove(testInputFileTemp)

	if err = writeFile(testInputFileTemp, areas); err != nil {
		t.Fatal(err)
	}

	// check if file contains custom tags
	contents, err = ioutil.ReadFile(testInputFileTemp)
	if err != nil {
		t.Fatal(err)
	}

	expectedExprs := []string{
		"Address[ \t]+string[ \t]+`protobuf:\"[^\"]+\" json:\"overrided\" valid:\"ip\" yaml:\"ip\"`",
		"Address[ \t]+string[ \t]+`protobuf:\"[^\"]+\" json:\"overrided\" valid:\"ip\" yaml:\"ip\"`",
		"Scheme[ \t]+string[ \t]+`protobuf:\"[^\"]+\" json:\"scheme,omitempty\" valid:\"http|https\"`",
		"Port[ \t]+int32[ \t]+`protobuf:\"[^\"]+\" json:\"port,omitempty\" valid:\"nonzero\"`",
		"FooBar[ \t]+isOneOfObject_FooBar[ \t]+`protobuf_oneof:\"[^\"]+\" tag:\"foo_bar\"`",
		"Foo[ \t]+string[ \t]+`protobuf:\"[^\"]+\" tag:\"foo\"`",
		"Bar[ \t]+int64[ \t]+`protobuf:\"[^\"]+\" tag:\"bar\"`",
		"XXX_Deprecated[ \t]+string[ \t]+`protobuf:\"[^\"]+\" json:\"XXX__deprecated,omitempty\" xml:\"-\"`",
	}

	for i, expr := range expectedExprs {
		matched, err := regexp.Match(expr, contents)
		if err != nil || matched != true {
			t.Errorf("file doesn't contains custom tag #%d after writing", i+1)
			t.Log(string(contents))
			break
		}
	}
}

func TestVerbose(t *testing.T) {
	b := new(bytes.Buffer)
	log.SetOutput(b)
	verbose = false
	logf("test")
	if len(b.Bytes()) > 0 {
		t.Errorf("verbose should be off")
	}
	verbose = true
	logf("test")
	if len(b.Bytes()) == 0 {
		t.Errorf("verbose should be on")
	}
}
<commit_msg>correct tests based off new field entries<commit_after>package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"testing"
)

var (
	testInputFile     = "./pb/test.pb.go"
	testInputFileTemp = "./pb/test.pb.go_tmp"
)

func TestTagFromComment(t *testing.T) {
	var tests = []struct {
		comment string
		tag     string
	}{
		{comment: `//@inject_tag: valid:"abc"`, tag: `valid:"abc"`},
		{comment: `//   @inject_tag: valid:"abcd"`, tag: `valid:"abcd"`},
		{comment: `// @inject_tag:      valid:"xyz"`, tag: `valid:"xyz"`},
		{comment: `// fdsafsa`, tag: ""},
		{comment: `//@inject_tag:`, tag: ""},
		{comment: `// @inject_tag: json:"abc" yaml:"abc`, tag: `json:"abc" yaml:"abc`},
		{comment: `// test @inject_tag: json:"abc" yaml:"abc`, tag: `json:"abc" yaml:"abc`},
	}
	for _, test := range tests {
		result := tagFromComment(test.comment)
		if result != test.tag {
			t.Errorf("expected tag: %q, got: %q", test.tag, result)
		}
	}
}

func TestParseWriteFile(t *testing.T) {
	expectedTag := `valid:"ip" yaml:"ip" json:"overrided"`

	areas, err := parseFile(testInputFile, []string{})
	if err != nil {
		t.Fatal(err)
	}
	if len(areas) != 9 {
		t.Fatalf("expected 9 areas to replace, got: %d", len(areas))
	}
	area := areas[0]
	t.Logf("area: %v", area)
	if area.InjectTag != expectedTag {
		t.Errorf("expected tag: %q, got: %q", expectedTag, area.InjectTag)
	}

	// make a copy of test file
	contents, err := ioutil.ReadFile(testInputFile)
	if err != nil {
		t.Fatal(err)
	}
	if err = ioutil.WriteFile(testInputFileTemp, contents, 0644); err != nil {
		t.Fatal(err)
	}
	defer os.Remove(testInputFileTemp)

	if err = writeFile(testInputFileTemp, areas); err != nil {
		t.Fatal(err)
	}

	// check if file contains custom tag
	contents, err = ioutil.ReadFile(testInputFileTemp)
	if err != nil {
		t.Fatal(err)
	}
	expectedExpr := "Address[ \t]+string[ \t]+`protobuf:\"bytes,1,opt,name=Address,proto3\" json:\"overrided\" valid:\"ip\" yaml:\"ip\"`"
	matched, err := regexp.Match(expectedExpr, contents)
	if err != nil || matched != true {
		t.Error("file doesn't contains custom tag after writing")
		t.Log(string(contents))
	}
}

func TestNewTagItems(t *testing.T) {
	var tests = []struct {
		tag   string
		items tagItems
	}{
		{
			tag: `valid:"ip" yaml:"ip, required" json:"overrided"`,
			items: []tagItem{
				{key: "valid", value: `"ip"`},
				{key: "yaml", value: `"ip, required"`},
				{key: "json", value: `"overrided"`},
			},
		},
		{
			tag: `validate:"omitempty,oneof=a b c d"`,
			items: []tagItem{
				{key: "validate", value: `"omitempty,oneof=a b c d"`},
			},
		},
	}

	for _, test := range tests {
		for i, item := range newTagItems(test.tag) {
			if item.key != test.items[i].key || item.value != test.items[i].value {
				t.Errorf("wrong tag item for tag %s, expected %v, got: %v",
					test.tag, test.items[i], item)
			}
		}
	}
}

func TestContinueParsingWhenSkippingFields(t *testing.T) {
	expectedTags := []string{
		`valid:"ip" yaml:"ip" json:"overrided"`,
		`valid:"-"`,
		`valid:"http|https"`,
		`valid:"nonzero"`,
		`validate:"omitempty"`,
		`xml:"-"`,
		`validate:"omitempty"`,
		`tag:"foo_bar"`,
		`tag:"foo"`,
		`tag:"bar"`,
	}

	areas, err := parseFile(testInputFile, []string{"xml"})
	if err != nil {
		t.Fatal(err)
	}

	if len(areas) != len(expectedTags) {
		t.Fatalf("expected %d areas to replace, got: %d", len(expectedTags), len(areas))
	}

	for i, a := range areas {
		if a.InjectTag != expectedTags[i] {
			t.Errorf("expected tag: %q, got: %q", expectedTags[i], a.InjectTag)
		}
	}

	// make a copy of test file
	contents, err := ioutil.ReadFile(testInputFile)
	if err != nil {
		t.Fatal(err)
	}
	if err = ioutil.WriteFile(testInputFileTemp, contents, 0644); err != nil {
		t.Fatal(err)
	}
	defer os.Remove(testInputFileTemp)

	if err = writeFile(testInputFileTemp, areas); err != nil {
		t.Fatal(err)
	}

	// check if file contains custom tags
	contents, err = ioutil.ReadFile(testInputFileTemp)
	if err != nil {
		t.Fatal(err)
	}

	expectedExprs := []string{
		"Address[ \t]+string[ \t]+`protobuf:\"[^\"]+\" json:\"overrided\" valid:\"ip\" yaml:\"ip\"`",
		"Address[ \t]+string[ \t]+`protobuf:\"[^\"]+\" json:\"overrided\" valid:\"ip\" yaml:\"ip\"`",
		"Scheme[ \t]+string[ \t]+`protobuf:\"[^\"]+\" json:\"scheme,omitempty\" valid:\"http|https\"`",
		"Port[ \t]+int32[ \t]+`protobuf:\"[^\"]+\" json:\"port,omitempty\" valid:\"nonzero\"`",
		"FooBar[ \t]+isOneOfObject_FooBar[ \t]+`protobuf_oneof:\"[^\"]+\" tag:\"foo_bar\"`",
		"Foo[ \t]+string[ \t]+`protobuf:\"[^\"]+\" tag:\"foo\"`",
		"Bar[ \t]+int64[ \t]+`protobuf:\"[^\"]+\" tag:\"bar\"`",
		"XXX_Deprecated[ \t]+string[ \t]+`protobuf:\"[^\"]+\" json:\"XXX__deprecated,omitempty\" xml:\"-\"`",
	}

	for i, expr := range expectedExprs {
		matched, err := regexp.Match(expr, contents)
		if err != nil || matched != true {
			t.Errorf("file doesn't contains custom tag #%d after writing", i+1)
			t.Log(string(contents))
			break
		}
	}
}

func TestVerbose(t *testing.T) {
	b := new(bytes.Buffer)
	log.SetOutput(b)
	verbose = false
	logf("test")
	if len(b.Bytes()) > 0 {
		t.Errorf("verbose should be off")
	}
	verbose = true
	logf("test")
	if len(b.Bytes()) == 0 {
		t.Errorf("verbose should be on")
	}
}
<|endoftext|>