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

package server

import (
	"bufio"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/armon/go-radix"
	api "github.com/osrg/gobgp/api"
	"github.com/osrg/gobgp/config"
	"github.com/osrg/gobgp/packet"
	"github.com/osrg/gobgp/table"
	"gopkg.in/tomb.v2"
	"net"
	"strconv"
	"strings"
	"time"
)

type roaBucket struct {
	Prefix    net.IP
	PrefixLen uint8
	entries   []*roa
}

type roa struct {
	Src    string
	MaxLen uint8
	AS     []uint32
}

const (
	CONNECTED uint8 = iota
	DISCONNECTED
	RTR
)

type roaClientEvent struct {
	eventType uint8
	src       string
	conn      *net.TCPConn
	data      []byte
}

type roaManager struct {
	AS        uint32
	roas      map[bgp.RouteFamily]*radix.Tree
	config    config.RpkiServers
	eventCh   chan *roaClientEvent
	clientMap map[string]*roaClient
}

func (c *roaManager) recieveROA() chan *roaClientEvent {
	return c.eventCh
}

func (m *roaManager) handleROAEvent(ev *roaClientEvent) {
	client, y := m.clientMap[ev.src]
	if !y {
		if ev.eventType == CONNECTED {
			ev.conn.Close()
		}
		log.Error("can't find %s roa server configuration", ev.src)
		return
	}
	switch ev.eventType {
	case DISCONNECTED:
		log.Info("roa server is disconnected, ", ev.src)
		client.state.Downtime = time.Now().Unix()
		// clear state
		client.state.RpkiMessages = config.RpkiMessages{}
		client.conn.Close()
		client.conn = nil
		client.t = tomb.Tomb{}
		client.t.Go(client.tryConnect)
	case CONNECTED:
		log.Info("roa server is connected, ", ev.src)
		client.conn = ev.conn
		client.state.Uptime = time.Now().Unix()
		client.t = tomb.Tomb{}
		client.t.Go(client.established)
	case RTR:
		m.handleRTRMsg(ev.src, client.state, ev.data)
	}
}

func addROA(host string, tree *radix.Tree, as uint32, prefix []byte, prefixLen, maxLen uint8) {
	key := table.IpToRadixkey(prefix, prefixLen)
	b, _ := tree.Get(key)
	if b == nil {
		p := make([]byte, len(prefix))
		copy(p, prefix)

		r := &roa{
			AS:     []uint32{as},
			MaxLen: maxLen,
			Src:    host,
		}

		b := &roaBucket{
			PrefixLen: prefixLen,
			Prefix:    p,
			entries:   []*roa{r},
		}

		tree.Insert(key, b)
	} else {
		bucket := b.(*roaBucket)
		found := false
		for _, r := range bucket.entries {
			if r.MaxLen == maxLen && r.Src == host {
				found = true
				r.AS = append(r.AS, as)
			}
		}
		if found == false {
			r := &roa{
				MaxLen: maxLen,
				AS:     []uint32{as},
				Src:    host,
			}
			bucket.entries = append(bucket.entries, r)
		}
	}
}

func (c *roaManager) handleRTRMsg(host string, state *config.RpkiServerState, buf []byte) {
	received := &state.RpkiMessages.RpkiReceived

	m, _ := bgp.ParseRTR(buf)
	if m != nil {
		switch msg := m.(type) {
		case *bgp.RTRSerialNotify:
			received.SerialNotify++
		case *bgp.RTRSerialQuery:
		case *bgp.RTRResetQuery:
		case *bgp.RTRCacheResponse:
			received.CacheResponse++
		case *bgp.RTRIPPrefix:
			var tree *radix.Tree
			if msg.Type == bgp.RTR_IPV4_PREFIX {
				received.Ipv4Prefix++
				tree = c.roas[bgp.RF_IPv4_UC]
			} else {
				received.Ipv6Prefix++
				tree = c.roas[bgp.RF_IPv6_UC]
			}
			addROA(host, tree, msg.AS, msg.Prefix, msg.PrefixLen, msg.MaxLen)
		case *bgp.RTREndOfData:
			received.EndOfData++
		case *bgp.RTRCacheReset:
			received.CacheReset++
		case *bgp.RTRErrorReport:
		}
	} else {
		received.Error++
	}
}

func splitHostPort(network string) (host string, port int) {
	if strings.HasPrefix(network, "[") {
		l := strings.Split(network, "]:")
		port, _ := strconv.Atoi(l[1])
		return l[0][1:], port
	} else {
		l := strings.Split(network, ":")
		port, _ := strconv.Atoi(l[1])
		return l[0], port
	}
}

func (c *roaManager) handleGRPC(grpcReq *GrpcRequest) {
	switch grpcReq.RequestType {
	case REQ_RPKI:
		results := make([]*GrpcResponse, 0)
		for _, client := range c.clientMap {
			state := client.state
			received := &state.RpkiMessages.RpkiReceived
			rpki := &api.RPKI{
				Conf: &api.RPKIConf{
					Address: client.addr,
				},
				State: &api.RPKIState{
					Uptime:       state.Uptime,
					ReceivedIpv4: received.Ipv4Prefix,
					ReceivedIpv6: received.Ipv6Prefix,
				},
			}
			result := &GrpcResponse{}
			result.Data = rpki
			results = append(results, result)
		}
		go sendMultipleResponses(grpcReq, results)

	case REQ_ROA:
		if len(c.clientMap) == 0 {
			result := &GrpcResponse{}
			result.ResponseErr = fmt.Errorf("RPKI server isn't configured.")
			grpcReq.ResponseCh <- result
			break
		}
		results := make([]*GrpcResponse, 0)
		var rfList []bgp.RouteFamily
		switch grpcReq.RouteFamily {
		case bgp.RF_IPv4_UC:
			rfList = []bgp.RouteFamily{bgp.RF_IPv4_UC}
		case bgp.RF_IPv6_UC:
			rfList = []bgp.RouteFamily{bgp.RF_IPv6_UC}
		default:
			rfList = []bgp.RouteFamily{bgp.RF_IPv4_UC, bgp.RF_IPv6_UC}
		}
		for _, rf := range rfList {
			if tree, ok := c.roas[rf]; ok {
				tree.Walk(func(s string, v interface{}) bool {
					b, _ := v.(*roaBucket)
					for _, r := range b.entries {
						for _, as := range r.AS {
							result := &GrpcResponse{}
							host, port := splitHostPort(r.Src)
							result.Data = &api.ROA{
								As:        as,
								Maxlen:    uint32(r.MaxLen),
								Prefixlen: uint32(b.PrefixLen),
								Prefix:    b.Prefix.String(),
								Conf: &api.RPKIConf{
									Address:    host,
									RemotePort: uint32(port),
								},
							}
							results = append(results, result)
						}
					}
					return false
				})
			}
		}
		go sendMultipleResponses(grpcReq, results)
	}
}

func validatePath(ownAs uint32, tree *radix.Tree, cidr string, asPath *bgp.PathAttributeAsPath) config.RpkiValidationResultType {
	var as uint32
	if asPath == nil || len(asPath.Value) == 0 {
		return config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND
	}
	asParam := asPath.Value[len(asPath.Value)-1].(*bgp.As4PathParam)
	switch asParam.Type {
	case bgp.BGP_ASPATH_ATTR_TYPE_SEQ:
		if len(asParam.AS) == 0 {
			return config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND
		}
		as = asParam.AS[len(asParam.AS)-1]
	case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ:
		as = ownAs
	default:
		return config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND
	}
	_, n, _ := net.ParseCIDR(cidr)
	ones, _ := n.Mask.Size()
	prefixLen := uint8(ones)
	_, b, _ := tree.LongestPrefix(table.IpToRadixkey(n.IP, prefixLen))
	if b == nil {
		return config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND
	} else {
		result := config.RPKI_VALIDATION_RESULT_TYPE_INVALID
		bucket, _ := b.(*roaBucket)
		for _, r := range bucket.entries {
			if prefixLen > r.MaxLen {
				continue
			}

			y := func(x uint32, asList []uint32) bool {
				for _, as := range asList {
					if x == as {
						return true
					}
				}
				return false
			}(as, r.AS)

			if y {
				result = config.RPKI_VALIDATION_RESULT_TYPE_VALID
				break
			}
		}
		return result
	}
}

func (c *roaManager) validate(pathList []*table.Path) {
	if c.roas[bgp.RF_IPv4_UC].Len() == 0 && c.roas[bgp.RF_IPv6_UC].Len() == 0 {
		return
	}
	for _, path := range pathList {
		if tree, ok := c.roas[path.GetRouteFamily()]; ok {
			path.Validation = validatePath(c.AS, tree, path.GetNlri().String(), path.GetAsPath())
		}
	}
}

type roaClient struct {
	t       tomb.Tomb
	host    string
	addr    string
	conn    *net.TCPConn
	state   *config.RpkiServerState
	eventCh chan *roaClientEvent
}

func (c *roaClient) kill() {
	c.t.Kill(nil)
	if c.conn != nil {
		c.conn.Close()
	}
}

func (c *roaClient) tryConnect() error {
	for c.t.Alive() {
		conn, err := net.Dial("tcp", c.host)
		if err != nil {
			time.Sleep(30 * time.Second)
		} else {
			c.eventCh <- &roaClientEvent{
				eventType: CONNECTED,
				src:       c.host,
				conn:      conn.(*net.TCPConn),
			}
			return nil
		}
	}
	return nil
}

func (c *roaClient) established() error {
	defer c.conn.Close()

	disconnected := func() {
		c.eventCh <- &roaClientEvent{
			eventType: DISCONNECTED,
			src:       c.host,
		}
	}

	r := bgp.NewRTRResetQuery()
	data, _ := r.Serialize()
	_, err := c.conn.Write(data)
	if err != nil {
		disconnected()
		return nil
	}

	c.state.RpkiMessages.RpkiSent.ResetQuery++

	reader := bufio.NewReader(c.conn)
	scanner := bufio.NewScanner(reader)
	scanner.Split(bgp.SplitRTR)

	for scanner.Scan() {
		c.eventCh <- &roaClientEvent{
			eventType: RTR,
			src:       c.host,
			data:      scanner.Bytes(),
		}
	}
	disconnected()
	return nil
}

func newROAManager(as uint32, conf config.RpkiServers) (*roaManager, error) {
	m := &roaManager{
		AS:     as,
		roas:   make(map[bgp.RouteFamily]*radix.Tree),
		config: conf,
	}
	m.roas[bgp.RF_IPv4_UC] = radix.New()
	m.roas[bgp.RF_IPv6_UC] = radix.New()
	m.eventCh = make(chan *roaClientEvent)
	m.clientMap = make(map[string]*roaClient)

	for _, entry := range conf.RpkiServerList {
		c := entry.RpkiServerConfig
		client := &roaClient{
			host:    net.JoinHostPort(c.Address.String(), strconv.Itoa(int(c.Port))),
			addr:    c.Address.String(),
			eventCh: m.eventCh,
			state:   &entry.RpkiServerState,
		}
		m.clientMap[client.host] = client
		client.t.Go(client.tryConnect)
	}

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

package server

import (
	"bufio"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/armon/go-radix"
	api "github.com/osrg/gobgp/api"
	"github.com/osrg/gobgp/config"
	"github.com/osrg/gobgp/packet"
	"github.com/osrg/gobgp/table"
	"gopkg.in/tomb.v2"
	"net"
	"strconv"
	"strings"
	"time"
)

type roaBucket struct {
	Prefix    net.IP
	PrefixLen uint8
	entries   []*roa
}

type roa struct {
	Src    string
	MaxLen uint8
	AS     []uint32
}

const (
	CONNECTED uint8 = iota
	DISCONNECTED
	RTR
)

type roaClientEvent struct {
	eventType uint8
	src       string
	conn      *net.TCPConn
	data      []byte
}

type roaManager struct {
	AS        uint32
	roas      map[bgp.RouteFamily]*radix.Tree
	config    config.RpkiServers
	eventCh   chan *roaClientEvent
	clientMap map[string]*roaClient
}

func (c *roaManager) recieveROA() chan *roaClientEvent {
	return c.eventCh
}

func (m *roaManager) handleROAEvent(ev *roaClientEvent) {
	client, y := m.clientMap[ev.src]
	if !y {
		if ev.eventType == CONNECTED {
			ev.conn.Close()
		}
		log.Error("can't find %s roa server configuration", ev.src)
		return
	}
	switch ev.eventType {
	case DISCONNECTED:
		log.Info("roa server is disconnected, ", ev.src)
		client.state.Downtime = time.Now().Unix()
		// clear state
		client.state.RpkiMessages = config.RpkiMessages{}
		client.conn.Close()
		client.conn = nil
		client.t = tomb.Tomb{}
		client.t.Go(client.tryConnect)
	case CONNECTED:
		log.Info("roa server is connected, ", ev.src)
		client.conn = ev.conn
		client.state.Uptime = time.Now().Unix()
		client.t = tomb.Tomb{}
		client.t.Go(client.established)
	case RTR:
		m.handleRTRMsg(ev.src, client.state, ev.data)
	}
}

func addROA(host string, tree *radix.Tree, as uint32, prefix []byte, prefixLen, maxLen uint8) {
	key := table.IpToRadixkey(prefix, prefixLen)
	b, _ := tree.Get(key)
	if b == nil {
		p := make([]byte, len(prefix))
		copy(p, prefix)

		r := &roa{
			AS:     []uint32{as},
			MaxLen: maxLen,
			Src:    host,
		}

		b := &roaBucket{
			PrefixLen: prefixLen,
			Prefix:    p,
			entries:   []*roa{r},
		}

		tree.Insert(key, b)
	} else {
		bucket := b.(*roaBucket)
		for _, r := range bucket.entries {
			if r.MaxLen == maxLen && r.Src == host {
				// we already have?
				for _, a := range r.AS {
					if a == as {
						return
					}
				}
				r.AS = append(r.AS, as)
				return
			}
		}
		r := &roa{
			MaxLen: maxLen,
			AS:     []uint32{as},
			Src:    host,
		}
		bucket.entries = append(bucket.entries, r)
	}
}

func (c *roaManager) handleRTRMsg(host string, state *config.RpkiServerState, buf []byte) {
	received := &state.RpkiMessages.RpkiReceived

	m, _ := bgp.ParseRTR(buf)
	if m != nil {
		switch msg := m.(type) {
		case *bgp.RTRSerialNotify:
			received.SerialNotify++
		case *bgp.RTRSerialQuery:
		case *bgp.RTRResetQuery:
		case *bgp.RTRCacheResponse:
			received.CacheResponse++
		case *bgp.RTRIPPrefix:
			var tree *radix.Tree
			if msg.Type == bgp.RTR_IPV4_PREFIX {
				received.Ipv4Prefix++
				tree = c.roas[bgp.RF_IPv4_UC]
			} else {
				received.Ipv6Prefix++
				tree = c.roas[bgp.RF_IPv6_UC]
			}
			addROA(host, tree, msg.AS, msg.Prefix, msg.PrefixLen, msg.MaxLen)
		case *bgp.RTREndOfData:
			received.EndOfData++
		case *bgp.RTRCacheReset:
			received.CacheReset++
		case *bgp.RTRErrorReport:
		}
	} else {
		received.Error++
	}
}

func splitHostPort(network string) (host string, port int) {
	if strings.HasPrefix(network, "[") {
		l := strings.Split(network, "]:")
		port, _ := strconv.Atoi(l[1])
		return l[0][1:], port
	} else {
		l := strings.Split(network, ":")
		port, _ := strconv.Atoi(l[1])
		return l[0], port
	}
}

func (c *roaManager) handleGRPC(grpcReq *GrpcRequest) {
	switch grpcReq.RequestType {
	case REQ_RPKI:
		results := make([]*GrpcResponse, 0)
		for _, client := range c.clientMap {
			state := client.state
			received := &state.RpkiMessages.RpkiReceived
			rpki := &api.RPKI{
				Conf: &api.RPKIConf{
					Address: client.addr,
				},
				State: &api.RPKIState{
					Uptime:       state.Uptime,
					ReceivedIpv4: received.Ipv4Prefix,
					ReceivedIpv6: received.Ipv6Prefix,
				},
			}
			result := &GrpcResponse{}
			result.Data = rpki
			results = append(results, result)
		}
		go sendMultipleResponses(grpcReq, results)

	case REQ_ROA:
		if len(c.clientMap) == 0 {
			result := &GrpcResponse{}
			result.ResponseErr = fmt.Errorf("RPKI server isn't configured.")
			grpcReq.ResponseCh <- result
			break
		}
		results := make([]*GrpcResponse, 0)
		var rfList []bgp.RouteFamily
		switch grpcReq.RouteFamily {
		case bgp.RF_IPv4_UC:
			rfList = []bgp.RouteFamily{bgp.RF_IPv4_UC}
		case bgp.RF_IPv6_UC:
			rfList = []bgp.RouteFamily{bgp.RF_IPv6_UC}
		default:
			rfList = []bgp.RouteFamily{bgp.RF_IPv4_UC, bgp.RF_IPv6_UC}
		}
		for _, rf := range rfList {
			if tree, ok := c.roas[rf]; ok {
				tree.Walk(func(s string, v interface{}) bool {
					b, _ := v.(*roaBucket)
					for _, r := range b.entries {
						for _, as := range r.AS {
							result := &GrpcResponse{}
							host, port := splitHostPort(r.Src)
							result.Data = &api.ROA{
								As:        as,
								Maxlen:    uint32(r.MaxLen),
								Prefixlen: uint32(b.PrefixLen),
								Prefix:    b.Prefix.String(),
								Conf: &api.RPKIConf{
									Address:    host,
									RemotePort: uint32(port),
								},
							}
							results = append(results, result)
						}
					}
					return false
				})
			}
		}
		go sendMultipleResponses(grpcReq, results)
	}
}

func validatePath(ownAs uint32, tree *radix.Tree, cidr string, asPath *bgp.PathAttributeAsPath) config.RpkiValidationResultType {
	var as uint32
	if asPath == nil || len(asPath.Value) == 0 {
		return config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND
	}
	asParam := asPath.Value[len(asPath.Value)-1].(*bgp.As4PathParam)
	switch asParam.Type {
	case bgp.BGP_ASPATH_ATTR_TYPE_SEQ:
		if len(asParam.AS) == 0 {
			return config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND
		}
		as = asParam.AS[len(asParam.AS)-1]
	case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ:
		as = ownAs
	default:
		return config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND
	}
	_, n, _ := net.ParseCIDR(cidr)
	ones, _ := n.Mask.Size()
	prefixLen := uint8(ones)
	_, b, _ := tree.LongestPrefix(table.IpToRadixkey(n.IP, prefixLen))
	if b == nil {
		return config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND
	} else {
		result := config.RPKI_VALIDATION_RESULT_TYPE_INVALID
		bucket, _ := b.(*roaBucket)
		for _, r := range bucket.entries {
			if prefixLen > r.MaxLen {
				continue
			}

			y := func(x uint32, asList []uint32) bool {
				for _, as := range asList {
					if x == as {
						return true
					}
				}
				return false
			}(as, r.AS)

			if y {
				result = config.RPKI_VALIDATION_RESULT_TYPE_VALID
				break
			}
		}
		return result
	}
}

func (c *roaManager) validate(pathList []*table.Path) {
	if c.roas[bgp.RF_IPv4_UC].Len() == 0 && c.roas[bgp.RF_IPv6_UC].Len() == 0 {
		return
	}
	for _, path := range pathList {
		if tree, ok := c.roas[path.GetRouteFamily()]; ok {
			path.Validation = validatePath(c.AS, tree, path.GetNlri().String(), path.GetAsPath())
		}
	}
}

type roaClient struct {
	t       tomb.Tomb
	host    string
	addr    string
	conn    *net.TCPConn
	state   *config.RpkiServerState
	eventCh chan *roaClientEvent
}

func (c *roaClient) kill() {
	c.t.Kill(nil)
	if c.conn != nil {
		c.conn.Close()
	}
}

func (c *roaClient) tryConnect() error {
	for c.t.Alive() {
		conn, err := net.Dial("tcp", c.host)
		if err != nil {
			time.Sleep(30 * time.Second)
		} else {
			c.eventCh <- &roaClientEvent{
				eventType: CONNECTED,
				src:       c.host,
				conn:      conn.(*net.TCPConn),
			}
			return nil
		}
	}
	return nil
}

func (c *roaClient) established() error {
	defer c.conn.Close()

	disconnected := func() {
		c.eventCh <- &roaClientEvent{
			eventType: DISCONNECTED,
			src:       c.host,
		}
	}

	r := bgp.NewRTRResetQuery()
	data, _ := r.Serialize()
	_, err := c.conn.Write(data)
	if err != nil {
		disconnected()
		return nil
	}

	c.state.RpkiMessages.RpkiSent.ResetQuery++

	reader := bufio.NewReader(c.conn)
	scanner := bufio.NewScanner(reader)
	scanner.Split(bgp.SplitRTR)

	for scanner.Scan() {
		c.eventCh <- &roaClientEvent{
			eventType: RTR,
			src:       c.host,
			data:      scanner.Bytes(),
		}
	}
	disconnected()
	return nil
}

func newROAManager(as uint32, conf config.RpkiServers) (*roaManager, error) {
	m := &roaManager{
		AS:     as,
		roas:   make(map[bgp.RouteFamily]*radix.Tree),
		config: conf,
	}
	m.roas[bgp.RF_IPv4_UC] = radix.New()
	m.roas[bgp.RF_IPv6_UC] = radix.New()
	m.eventCh = make(chan *roaClientEvent)
	m.clientMap = make(map[string]*roaClient)

	for _, entry := range conf.RpkiServerList {
		c := entry.RpkiServerConfig
		client := &roaClient{
			host:    net.JoinHostPort(c.Address.String(), strconv.Itoa(int(c.Port))),
			addr:    c.Address.String(),
			eventCh: m.eventCh,
			state:   &entry.RpkiServerState,
		}
		m.clientMap[client.host] = client
		client.t.Go(client.tryConnect)
	}

	return m, nil
}
<|endoftext|>


<commit_before>package ekanite

import (
	"html/template"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/gorilla/csrf"
	"github.com/gorilla/securecookie"
)

// Server serves query client connections.
type HttpServer struct {
	iface    string
	Searcher Searcher

	addr     net.Addr
	template *template.Template

	Logger *log.Logger
}

// NewServer returns a new Server instance.
func NewHttpServer(iface string, searcher Searcher) *HttpServer {
	return &HttpServer{
		iface:    iface,
		Searcher: searcher,
		Logger:   log.New(os.Stderr, "[httpserver] ", log.LstdFlags),
	}
}

// Start instructs the Server to bind to the interface and accept connections.
func (s *HttpServer) Start() error {
	ln, err := net.Listen("tcp", s.iface)
	if err != nil {
		return err
	}

	s.addr = ln.Addr()

	s.template, err = template.New("ServerTemplate").Parse(templateSource)
	if err != nil {
		ln.Close()
		return err
	}

	CSRF := csrf.Protect(securecookie.GenerateRandomKey(32),
		csrf.Secure(false),
		csrf.HttpOnly(true))

	go http.Serve(ln, CSRF(s))
	return nil
}

// Addr returns the address to which the Server is bound.
func (s *HttpServer) Addr() net.Addr {
	return s.addr
}

// ServeHTTP implements a http.Handler, serving the query interface for Ekanite
func (s *HttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	dontCache(w, r)

	if r.Method == "GET" || r.Method == "HEAD" {
		// HEAD is conveniently supported by net/http without further action
		err := serveIndex(s, w, r)

		if err != nil {
			s.Logger.Print("Error executing template: ", err)
			http.Error(w, "Error executing template: "+err.Error(), http.StatusInternalServerError)
			return
		}
	} else if r.Method == "POST" {
		err := r.ParseForm()
		if err != nil {
			s.Logger.Printf("Error parsing form '%s'", err)
			http.Error(w, "Error parsing form", http.StatusBadRequest)
			return
		}

		if len(r.FormValue("query")) == 0 {
			serveIndex(s, w, r)
			return
		}

		userQuery := r.FormValue("query")

		s.Logger.Printf("executing query '%s'", userQuery)

		resultSet, err := s.Searcher.Search(userQuery)
		var resultSlice []string

		if err != nil {
			s.Logger.Printf("Error executing query: '%s'", err)
			http.Error(w, "Error executing query: "+err.Error(), http.StatusInternalServerError)
			return
		}

		for s := range resultSet {
			resultSlice = append(resultSlice, s)
		}

		data := struct {
			Title         string
			Headline      string
			ReturnResults bool
			LogMessages   []string
			CsrfField     template.HTML
		}{
			"Ekanite query interface",
			"Ekanite - Listing " + strconv.Itoa(len(resultSlice)) + " results for '" + userQuery + "'",
			true,
			resultSlice,
			csrf.TemplateField(r),
		}

		if err := s.template.Execute(w, data); err != nil {
			s.Logger.Print("Error executing template: ", err)
		}

	} else {
		http.Error(w, "Unsupported method", http.StatusMethodNotAllowed)
	}
}

// serveIndex serves the plain index for the GET request and POST failovers
func serveIndex(s *HttpServer, w http.ResponseWriter, r *http.Request) error {
	data := struct {
		Title         string
		Headline      string
		ReturnResults bool
		LogMessages   []string
		CsrfField     template.HTML
	}{
		"Ekanite query interface",
		"Ekanite query interface",
		false,
		[]string{},
		csrf.TemplateField(r),
	}

	if err := s.template.Execute(w, data); err != nil {
		return err
	}

	return nil
}

// dontCache sets necessary headers to avoid client and intermediate caching of response
func dontCache(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Expires", time.Unix(0, 0).Format(time.RFC1123))
	w.Header().Set("Last-Modified", time.Now().Format(time.RFC1123))
	w.Header().Set("Cache-Control", "private, no-store, max-age=0, no-cache, must-revalidate, post-check=0, pre-check=0")

	return
}

const templateSource string = `
<!DOCTYPE html>

<meta charset="utf-8" />
<title>{{ $.Title }}</title>

{{ $.Headline }}

Query language reference: bleve

	<form action="/" method="POST">
    <textarea name="query" cols="100" rows="2"></textarea>

    <input name="submit" type="submit" class="button" value="Query">
    {{ .CsrfField }}
	</form>

{{ if $.ReturnResults }}

	{{range $message := $.LogMessages }}

{{ $message }}

	{{ end }}

{{ end }}

`
<commit_msg>Remove CSRF check<commit_after>package ekanite

import (
	"html/template"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"time"
)

// Server serves query client connections.
type HttpServer struct {
	iface    string
	Searcher Searcher

	addr     net.Addr
	template *template.Template

	Logger *log.Logger
}

// NewServer returns a new Server instance.
func NewHttpServer(iface string, searcher Searcher) *HttpServer {
	return &HttpServer{
		iface:    iface,
		Searcher: searcher,
		Logger:   log.New(os.Stderr, "[httpserver] ", log.LstdFlags),
	}
}

// Start instructs the Server to bind to the interface and accept connections.
func (s *HttpServer) Start() error {
	ln, err := net.Listen("tcp", s.iface)
	if err != nil {
		return err
	}

	s.addr = ln.Addr()

	s.template, err = template.New("ServerTemplate").Parse(templateSource)
	if err != nil {
		ln.Close()
		return err
	}

	go http.Serve(ln, s)
	return nil
}

// Addr returns the address to which the Server is bound.
func (s *HttpServer) Addr() net.Addr {
	return s.addr
}

// ServeHTTP implements a http.Handler, serving the query interface for Ekanite
func (s *HttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	dontCache(w, r)

	if r.Method == "GET" || r.Method == "HEAD" {
		// HEAD is conveniently supported by net/http without further action
		err := serveIndex(s, w, r)

		if err != nil {
			s.Logger.Print("Error executing template: ", err)
			http.Error(w, "Error executing template: "+err.Error(), http.StatusInternalServerError)
			return
		}
	} else if r.Method == "POST" {
		err := r.ParseForm()
		if err != nil {
			s.Logger.Printf("Error parsing form '%s'", err)
			http.Error(w, "Error parsing form", http.StatusBadRequest)
			return
		}

		if len(r.FormValue("query")) == 0 {
			serveIndex(s, w, r)
			return
		}

		userQuery := r.FormValue("query")

		s.Logger.Printf("executing query '%s'", userQuery)

		resultSet, err := s.Searcher.Search(userQuery)
		var resultSlice []string

		if err != nil {
			s.Logger.Printf("Error executing query: '%s'", err)
			http.Error(w, "Error executing query: "+err.Error(), http.StatusInternalServerError)
			return
		}

		for s := range resultSet {
			resultSlice = append(resultSlice, s)
		}

		data := struct {
			Title         string
			Headline      string
			ReturnResults bool
			LogMessages   []string
		}{
			"Ekanite query interface",
			"Ekanite - Listing " + strconv.Itoa(len(resultSlice)) + " results for '" + userQuery + "'",
			true,
			resultSlice,
		}

		if err := s.template.Execute(w, data); err != nil {
			s.Logger.Print("Error executing template: ", err)
		}

	} else {
		http.Error(w, "Unsupported method", http.StatusMethodNotAllowed)
	}
}

// serveIndex serves the plain index for the GET request and POST failovers
func serveIndex(s *HttpServer, w http.ResponseWriter, r *http.Request) error {
	data := struct {
		Title         string
		Headline      string
		ReturnResults bool
		LogMessages   []string
	}{
		"Ekanite query interface",
		"Ekanite query interface",
		false,
		[]string{},
	}

	if err := s.template.Execute(w, data); err != nil {
		return err
	}

	return nil
}

// dontCache sets necessary headers to avoid client and intermediate caching of response
func dontCache(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Expires", time.Unix(0, 0).Format(time.RFC1123))
	w.Header().Set("Last-Modified", time.Now().Format(time.RFC1123))
	w.Header().Set("Cache-Control", "private, no-store, max-age=0, no-cache, must-revalidate, post-check=0, pre-check=0")

	return
}

const templateSource string = `
<!DOCTYPE html>

<meta charset="utf-8" />
<title>{{ $.Title }}</title>

{{ $.Headline }}

Query language reference: bleve

	<form action="/" method="POST">
    <textarea name="query" cols="100" rows="2"></textarea>

    <input name="submit" type="submit" class="button" value="Query">
	</form>

{{ if $.ReturnResults }}

	{{range $message := $.LogMessages }}

{{ $message }}

	{{ end }}

{{ end }}

`
<|endoftext|>


<commit_before>package git

import (
	"errors"
	"net/http"
	"strings"

	api "github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

var ErrorNotFound = errors.New("not found")

type GithubRepositories struct {
	client *api.Client
}

func NewGithubRepositories(token string) *GithubRepositories {
	tokenSource := oauth2.StaticTokenSource(&oauth2.Token{
		AccessToken: token,
	})
	oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)

	return &GithubRepositories{
		client: api.NewClient(oauthClient),
	}
}

func (service *GithubRepositories) All() ([]*Repository, error) {
	opts := &api.RepositoryListOptions{
		ListOptions: api.ListOptions{
			PerPage: 50,
		},
	}

	var allRepos []*Repository

	paginatedRepos := make([]*Repository, opts.ListOptions.PerPage)
	for {
		githubRepos, response, err := service.client.Repositories.List("", opts)
		if err != nil {
			return nil, err
		}

		for i, githubRepo := range githubRepos {
			paginatedRepos[i] = &Repository{
				FullName:    *githubRepo.FullName,
				Description: *githubRepo.Description,
				Master:      *githubRepo.DefaultBranch,
				HTMLURL:     *githubRepo.HTMLURL,
			}
		}

		allRepos = append(allRepos, paginatedRepos[0:len(githubRepos)]...)
		if response.NextPage == 0 {
			break
		}

		opts.ListOptions.Page = response.NextPage
	}

	return allRepos, nil
}

func (service *GithubRepositories) Get(fullName string) (*Repository, error) {
	repoOwner, repoName := ParseRepositoryName(fullName)

	githubRepo, response, err := service.client.Repositories.Get(repoOwner, repoName)
	if err == nil {
		err = api.CheckResponse(response.Response)
	}

	if err != nil {
		if response.StatusCode == http.StatusNotFound {
			return nil, ErrorNotFound
		}

		return nil, err
	}

	masterBranch, _, err := service.client.Repositories.GetBranch(repoOwner, repoName, *githubRepo.DefaultBranch)
	if err != nil {
		return nil, err
	}

	lastCommit, _, err := service.client.Git.GetCommit(repoOwner, repoName, *masterBranch.Commit.SHA)
	if err != nil {
		return nil, err
	}

	repo := repositoryFromGithub(githubRepo)
	repo.LatestMasterCommit = commitFromGithub(lastCommit)

	return repo, nil
}

func (service *GithubRepositories) Track(fullName, callbackURL string) error {
	owner, name := ParseRepositoryName(fullName)
	return service.registerPushWebhook(owner, name, callbackURL)
}

func (service *GithubRepositories) registerPushWebhook(owner, repo, cbURL string) error {
	hook := &api.Hook{
		Name:   new(string),
		Active: new(bool),
		Events: []string{"push"},
		Config: map[string]interface{}{
			"url":          cbURL,
			"content_type": "json",
		},
	}
	*hook.Name = "web"
	*hook.Active = true

	_, response, err := service.client.Repositories.CreateHook(owner, repo, hook)
	if err == nil {
		err = api.CheckResponse(response.Response)
	}

	return err
}

// ParseRepositoryName returns owner and project name for given GitHub repository.
func ParseRepositoryName(fullName string) (string, string) {
	fields := strings.SplitN(fullName, "/", 2)
	return fields[0], fields[1]
}

func repositoryFromGithub(githubRepo *api.Repository) *Repository {
	return &Repository{
		FullName:    *githubRepo.FullName,
		Description: *githubRepo.Description,
		Master:      *githubRepo.DefaultBranch,
		HTMLURL:     *githubRepo.HTMLURL,
		GitURL:      *githubRepo.GitURL,
	}
}

func commitFromGithub(githubCommit *api.Commit) *Commit {
	return &Commit{
		SHA:     *githubCommit.SHA,
		Message: *githubCommit.Message,
		Author:  *githubCommit.Committer.Name,
		Date:    *githubCommit.Committer.Date,
	}
}
<commit_msg>Handle github repos without description<commit_after>package git

import (
	"errors"
	"log"
	"net/http"
	"strings"

	api "github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

var ErrorNotFound = errors.New("not found")

type GithubRepositories struct {
	client *api.Client
}

func NewGithubRepositories(token string) *GithubRepositories {
	tokenSource := oauth2.StaticTokenSource(&oauth2.Token{
		AccessToken: token,
	})
	oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)

	return &GithubRepositories{
		client: api.NewClient(oauthClient),
	}
}

func (service *GithubRepositories) All() ([]*Repository, error) {
	opts := &api.RepositoryListOptions{
		ListOptions: api.ListOptions{
			PerPage: 50,
		},
	}

	var allRepos []*Repository

	paginatedRepos := make([]*Repository, opts.ListOptions.PerPage)
	for {
		githubRepos, response, err := service.client.Repositories.List("", opts)
		if err != nil {
			return nil, err
		}

		for i, githubRepo := range githubRepos {
			if githubRepo.FullName == nil {
				log.Printf("[WARN] excluding GitHub repository without full_name %v", githubRepo)
				continue
			}

			if githubRepo.GitURL == nil {
				log.Printf("[WARN] excluding GitHub repository without git_url %v", githubRepo)
				continue
			}

			paginatedRepos[i] = &Repository{
				FullName: *githubRepo.FullName,
				Master:   "master",
			}

			if githubRepo.Description != nil {
				paginatedRepos[i].Description = *githubRepo.Description
			}

			if githubRepo.DefaultBranch != nil {
				paginatedRepos[i].Master = *githubRepo.DefaultBranch
			}

			if githubRepo.HTMLURL != nil {
				paginatedRepos[i].HTMLURL = *githubRepo.HTMLURL
			}
		}

		allRepos = append(allRepos, paginatedRepos[0:len(githubRepos)]...)
		if response.NextPage == 0 {
			break
		}

		opts.ListOptions.Page = response.NextPage
	}

	return allRepos, nil
}

func (service *GithubRepositories) Get(fullName string) (*Repository, error) {
	repoOwner, repoName := ParseRepositoryName(fullName)

	githubRepo, response, err := service.client.Repositories.Get(repoOwner, repoName)
	if err == nil {
		err = api.CheckResponse(response.Response)
	}

	if err != nil {
		if response.StatusCode == http.StatusNotFound {
			return nil, ErrorNotFound
		}

		return nil, err
	}

	masterBranch, _, err := service.client.Repositories.GetBranch(repoOwner, repoName, *githubRepo.DefaultBranch)
	if err != nil {
		return nil, err
	}

	lastCommit, _, err := service.client.Git.GetCommit(repoOwner, repoName, *masterBranch.Commit.SHA)
	if err != nil {
		return nil, err
	}

	repo := repositoryFromGithub(githubRepo)
	repo.LatestMasterCommit = commitFromGithub(lastCommit)

	return repo, nil
}

func (service *GithubRepositories) Track(fullName, callbackURL string) error {
	owner, name := ParseRepositoryName(fullName)
	return service.registerPushWebhook(owner, name, callbackURL)
}

func (service *GithubRepositories) registerPushWebhook(owner, repo, cbURL string) error {
	hook := &api.Hook{
		Name:   new(string),
		Active: new(bool),
		Events: []string{"push"},
		Config: map[string]interface{}{
			"url":          cbURL,
			"content_type": "json",
		},
	}
	*hook.Name = "web"
	*hook.Active = true

	_, response, err := service.client.Repositories.CreateHook(owner, repo, hook)
	if err == nil {
		err = api.CheckResponse(response.Response)
	}

	return err
}

// ParseRepositoryName returns owner and project name for given GitHub repository.
func ParseRepositoryName(fullName string) (string, string) {
	fields := strings.SplitN(fullName, "/", 2)
	return fields[0], fields[1]
}

func repositoryFromGithub(githubRepo *api.Repository) *Repository {
	return &Repository{
		FullName:    *githubRepo.FullName,
		Description: *githubRepo.Description,
		Master:      *githubRepo.DefaultBranch,
		HTMLURL:     *githubRepo.HTMLURL,
		GitURL:      *githubRepo.GitURL,
	}
}

func commitFromGithub(githubCommit *api.Commit) *Commit {
	return &Commit{
		SHA:     *githubCommit.SHA,
		Message: *githubCommit.Message,
		Author:  *githubCommit.Committer.Name,
		Date:    *githubCommit.Committer.Date,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"compress/gzip"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var (
	builtAt        = time.Now()
	compiledAssets = assetIntFS{}
	assets         AssetFileSystem
)

func debugStream() io.Writer {
	if os.Getenv("DEBUG") == "true" {
		return os.Stderr
	}
	return ioutil.Discard
}

var dbg = log.New(debugStream(), "[DEBUG] ", log.Lshortfile)

type assetProxy struct {
	devPath string
}

func (a *assetProxy) Open(name string) (http.File, error) {
	return a.fileSystem().Open(name)
}

func (a *assetProxy) AssetNames() []string {
	return a.fileSystem().AssetNames()
}

func (a *assetProxy) fileSystem() AssetFileSystem {
	dbg.Printf("getting file system for %q", a.devPath)
	if a.devPath != "" {
		dbg.Printf("using dev path %s", a.devPath)
		stat, e := os.Stat(a.devPath)
		if e == nil && stat.IsDir() {
			assets = &assetOsFS{root: a.devPath}
			return assets
		} else {
			dbg.Printf("dev path %s does not exist", a.devPath)
		}
	} else {
		dbg.Printf("dev path seems to be empty")
	}
	return compiledAssets
}

func FileSystem(devPath string) AssetFileSystem {
	return &assetProxy{devPath: devPath}
}

type AssetFileSystem interface {
	Open(name string) (http.File, error)
	AssetNames() []string
}

func assetNames() (names []string) {
	return assets.AssetNames()
}

func readAsset(key string) ([]byte, error) {
	r, e := assets.Open(key)
	if e != nil {
		return nil, e
	}
	defer func() {
		_ = r.Close()
	}()

	p, e := ioutil.ReadAll(r)
	if e != nil {
		return nil, e
	}
	return p, nil
}

func mustReadAsset(key string) []byte {
	p, e := readAsset(key)
	if e != nil {
		panic("could not read asset with key " + key + ": " + e.Error())
	}
	return p
}

type assetOsFS struct{ root string }

func (aFS assetOsFS) Open(name string) (http.File, error) {
	p := filepath.Join(aFS.root, name)
	dbg.Printf("opening local file %q", p)
	f, e := os.Open(p)
	if e != nil {
		dbg.Printf("ERROR reading local file: %q", name)
		return nil, e
	}
	return f, nil
}

func (aFS *assetOsFS) AssetNames() []string {
	names, e := filepath.Glob(aFS.root + "/*")
	if e != nil {
		log.Print(e)
	}
	return names
}

type assetIntFS map[string][]byte

type assetFile struct {
	name string
	*bytes.Reader
}

type assetFileInfo struct {
	*assetFile
}

func (info assetFileInfo) Name() string {
	return info.assetFile.name
}

func (info assetFileInfo) ModTime() time.Time {
	return builtAt
}

func (info assetFileInfo) Mode() os.FileMode {
	return 0644
}

func (info assetFileInfo) Sys() interface{} {
	return nil
}

func (info assetFileInfo) Size() int64 {
	return int64(info.assetFile.Reader.Len())
}

func (info assetFileInfo) IsDir() bool {
	return false
}

func (info assetFile) Readdir(count int) ([]os.FileInfo, error) {
	return nil, nil
}

func (f *assetFile) Stat() (os.FileInfo, error) {
	info := assetFileInfo{assetFile: f}
	return info, nil
}

func (afs assetIntFS) AssetNames() (names []string) {
	names = make([]string, 0, len(afs))
	for k, _ := range afs {
		names = append(names, k)
	}
	return names
}

func (afs assetIntFS) Open(name string) (af http.File, e error) {
	dbg.Printf("opening tpl %s", name)
	name = strings.TrimPrefix(name, "/")
	if name == "" {
		name = "index.html"
	}
	if asset, found := afs[name]; found {
		decomp, e := gzip.NewReader(bytes.NewBuffer(asset))
		if e != nil {
			return nil, e
		}
		defer func() {
			_ = decomp.Close()
		}()
		b, e := ioutil.ReadAll(decomp)
		if e != nil {
			return nil, e
		}
		af = &assetFile{Reader: bytes.NewReader(b), name: name}
		return af, nil
	}
	dbg.Printf("ERROR: index %s does not exist. known keys: %#v", name, afs.AssetNames())
	return nil, os.ErrNotExist
}

func (a *assetFile) Close() error {
	return nil
}

func (a *assetFile) Read(p []byte) (n int, e error) {
	if a.Reader == nil {
		return 0, os.ErrInvalid
	}
	return a.Reader.Read(p)
}
<commit_msg>extended the goassets scripts variant<commit_after>package main

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var (
	builtAt        = time.Now()
	compiledAssets = assetIntFS{}
	assets         AssetFileSystem
)

func debugStream() io.Writer {
	if os.Getenv("DEBUG") == "true" {
		return os.Stderr
	}
	return ioutil.Discard
}

var dbg = log.New(debugStream(), "[DEBUG] ", log.Lshortfile)

type assetProxy struct {
	devPath string
}

func (a *assetProxy) Open(name string) (http.File, error) {
	return a.fileSystem().Open(name)
}

func (a *assetProxy) AssetNames() []string {
	return a.fileSystem().AssetNames()
}

func (a *assetProxy) fileSystem() AssetFileSystem {
	dbg.Printf("getting file system for %q", a.devPath)
	if a.devPath != "" {
		dbg.Printf("using dev path %s", a.devPath)
		stat, e := os.Stat(a.devPath)
		if e == nil && stat.IsDir() {
			assets = &assetOsFS{root: a.devPath}
			return assets
		} else {
			dbg.Printf("dev path %s does not exist", a.devPath)
		}
	} else {
		dbg.Printf("dev path seems to be empty")
	}
	return compiledAssets
}

func FileSystem(devPath string) AssetFileSystem {
	return &assetProxy{devPath: devPath}
}

type AssetFileSystem interface {
	Open(name string) (http.File, error)
	AssetNames() []string
}

func assetNames() (names []string) {
	return assets.AssetNames()
}

func readAsset(key string) ([]byte, error) {
	r, e := assets.Open(key)
	if e != nil {
		return nil, e
	}
	defer func() {
		_ = r.Close()
	}()

	p, e := ioutil.ReadAll(r)
	if e != nil {
		return nil, e
	}
	return p, nil
}

func mustReadAsset(key string) []byte {
	p, e := readAsset(key)
	if e != nil {
		panic("could not read asset with key " + key + ": " + e.Error())
	}
	return p
}

type assetOsFS struct{ root string }

func (aFS assetOsFS) Open(name string) (http.File, error) {
	p := filepath.Join(aFS.root, name)
	dbg.Printf("opening local file %q", p)
	f, e := os.Open(p)
	if e != nil {
		dbg.Printf("ERROR reading local file: %q", name)
		return nil, e
	}
	return f, nil
}

func (aFS *assetOsFS) AssetNames() []string {
	names, e := filepath.Glob(aFS.root + "/*")
	if e != nil {
		log.Print(e)
	}
	return names
}

type assetIntFS map[string][]byte

type assetNode struct {
	name string
	data *bytes.Reader
	dir  bool

	children map[string]*assetNode
}

func addNode(root *assetNode, path string, content *bytes.Reader) error {
	node := root
	pathSegments := strings.Split(path, "/")
	if len(pathSegments) > 1 {
		for i := 0; i < len(pathSegments)-1; i++ {
			if val, ok := node.children[pathSegments[i]]; ok {
				node = val
			} else {
				newNode := &assetNode{name: pathSegments[i], dir: true, children: map[string]*assetNode{}}
				node.children[pathSegments[i]] = newNode
				node = newNode
			}
		}
	}
	filename := pathSegments[len(pathSegments)-1]
	if _, ok := node.children[filename]; ok {
		return fmt.Errorf("node %q already exists", filename)
	}
	node.children[filename] = &assetNode{name: filename, data: content}
	return nil
}

func (node *assetNode) Traverse(path []string) (*assetNode, error) {
	switch len(path) {
	case 0:
		return node, nil
	default:
		child, ok := node.children[path[0]]
		if !ok {
			return nil, os.ErrNotExist
		}
		return child.Traverse(path[1:])
	}
}

func (node *assetNode) Name() string {
	return node.name
}

func (node *assetNode) ModTime() time.Time {
	return builtAt
}

func (node *assetNode) Mode() os.FileMode {
	if node.dir {
		return 0755
	}
	return 0644
}

func (node *assetNode) Sys() interface{} {
	return nil
}

func (node *assetNode) Size() int64 {
	if node.dir {
		return 0
	}
	return int64(node.data.Len())
}

func (node *assetNode) IsDir() bool {
	return node.dir
}

func (node *assetNode) Readdir(count int) (stats []os.FileInfo, e error) {
	if !node.dir {
		return nil, nil
	}

	for _, child := range node.children {
		stat, e := child.Stat()
		if e != nil {
			return nil, e
		}
		stats = append(stats, stat)
	}
	return stats, nil
}

func (node *assetNode) Stat() (os.FileInfo, error) {
	return node, nil
}

func (node *assetNode) Close() error {
	return nil
}

func (node *assetNode) Read(p []byte) (int, error) {
	return node.data.Read(p)
}

func (node *assetNode) Seek(offset int64, whence int) (int64, error) {
	if node.dir {
		return 0, nil
	}
	return node.data.Seek(offset, whence)
}

func (node *assetNode) Open(name string) (af http.File, e error) {
	dbg.Printf("opening tpl %s", name)
	if name == "." {
		return node, nil
	}
	name = strings.TrimPrefix(name, "/")
	nameSegments := strings.Split(name, "/")
	return node.Traverse(nameSegments)
}

func (afs assetIntFS) AssetNames() (names []string) {
	names = make([]string, 0, len(afs))
	for k, _ := range afs {
		names = append(names, k)
	}
	return names
}

func (afs assetIntFS) Open(name string) (af http.File, e error) {
	dbg.Printf("opening tpl %s", name)

	switch name {
	case "":
		name = "index.html"
	case "/":
		name = ""
	default:
		name = strings.TrimPrefix(name, "/")
	}

	// single asset referenced, load it directly
	if asset, found := afs[name]; found {
		reader, e := createReader(asset)
		af = &assetNode{data: reader, name: name}
		return af, e
	}

	// directory request?
	switch {
	case name == "":
		// ignore
	case !strings.HasSuffix(name, "/"):
		name += "/"
	}
	root := &assetNode{dir: true, name: ".", children: map[string]*assetNode{}}
	for k, v := range afs {
		if strings.HasPrefix(k, name) {
			reader, e := createReader(v)
			if e != nil {
				return nil, e
			}
			dbg.Printf("adding node %q", k)
			addNode(root, strings.TrimPrefix(k, name), reader)
		}
	}
	if len(root.children) > 0 {
		return root, nil
	}

	dbg.Printf("ERROR: index %s does not exist. known keys: %#v", name, afs.AssetNames())
	return nil, os.ErrNotExist
}

func createReader(data []byte) (*bytes.Reader, error) {
	decomp, e := gzip.NewReader(bytes.NewBuffer(data))
	if e != nil {
		return nil, e
	}
	defer func() {
		_ = decomp.Close()
	}()
	b, e := ioutil.ReadAll(decomp)
	if e != nil {
		return nil, e
	}
	return bytes.NewReader(b), nil
}
<|endoftext|>


<commit_before>package gorush

import (
	"errors"
	"fmt"

	"github.com/appleboy/go-fcm"
	"github.com/sirupsen/logrus"
)

// InitFCMClient use for initialize FCM Client.
func InitFCMClient(key string) (*fcm.Client, error) {
	var err error

	if key == "" {
		return nil, errors.New("Missing Android API Key")
	}

	if key != PushConf.Android.APIKey {
		return fcm.NewClient(key)
	}

	if FCMClient == nil {
		FCMClient, err = fcm.NewClient(key)
		return FCMClient, err
	}

	return FCMClient, nil
}

// GetAndroidNotification use for define Android notification.
// HTTP Connection Server Reference for Android
// https://firebase.google.com/docs/cloud-messaging/http-server-ref
func GetAndroidNotification(req PushNotification) *fcm.Message {
	notification := &fcm.Message{
		To:                    req.To,
		Condition:             req.Condition,
		CollapseKey:           req.CollapseKey,
		ContentAvailable:      req.ContentAvailable,
		MutableContent:        req.MutableContent,
		DelayWhileIdle:        req.DelayWhileIdle,
		TimeToLive:            req.TimeToLive,
		RestrictedPackageName: req.RestrictedPackageName,
		DryRun:                req.DryRun,
	}

	if len(req.Tokens) > 0 {
		notification.RegistrationIDs = req.Tokens
	}

	if len(req.Priority) > 0 && req.Priority == "high" {
		notification.Priority = "high"
	}

	// Add another field
	if len(req.Data) > 0 {
		notification.Data = make(map[string]interface{})
		for k, v := range req.Data {
			notification.Data[k] = v
		}
	}

	notification.Notification = req.Notification

	if req.Notification != nil {
		// Set request message if body is empty
		if len(req.Message) > 0 {
			notification.Notification.Body = req.Message
		}

		if len(req.Title) > 0 {
			notification.Notification.Title = req.Title
		}

		if len(req.Image) > 0 {
			notification.Notification.Image = req.Image
		}

		if v, ok := req.Sound.(string); ok && len(v) > 0 {
			notification.Notification.Sound = v
		}
	}

	// handle iOS apns in fcm

	if len(req.Apns) > 0 {
		notification.Apns = req.Apns
	}

	return notification
}

// PushToAndroid provide send notification to Android server.
func PushToAndroid(req PushNotification) bool {
	LogAccess.Debug("Start push notification for Android")

	var (
		client     *fcm.Client
		retryCount = 0
		maxRetry   = PushConf.Android.MaxRetry
	)

	if req.Retry > 0 && req.Retry < maxRetry {
		maxRetry = req.Retry
	}

	// check message
	err := CheckMessage(req)

	if err != nil {
		LogError.Error("request error: " + err.Error())
		return false
	}

Retry:
	var isError = false

	notification := GetAndroidNotification(req)

	if req.APIKey != "" {
		client, err = InitFCMClient(req.APIKey)
	} else {
		client, err = InitFCMClient(PushConf.Android.APIKey)
	}

	if err != nil {
		// FCM server error
		LogError.Error("FCM server error: " + err.Error())
		return false
	}

	res, err := client.Send(notification)
	if err != nil {
		// Send Message error
		LogError.Error("FCM server send message error: " + err.Error())
		return false
	}

	if !req.IsTopic() {
		LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure))
	}

	StatStorage.AddAndroidSuccess(int64(res.Success))
	StatStorage.AddAndroidError(int64(res.Failure))

	var newTokens []string
	// result from Send messages to specific devices
	for k, result := range res.Results {
		to := ""
		if k < len(req.Tokens) {
			to = req.Tokens[k]
		} else {
			to = req.To
		}

		if result.Error != nil {
			isError = true
			newTokens = append(newTokens, to)
			LogPush(FailedPush, to, req, result.Error)
			if PushConf.Core.Sync {
				req.AddLog(getLogPushEntry(FailedPush, to, req, result.Error))
			} else if PushConf.Core.FeedbackURL != "" {
				go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) {
					err := DispatchFeedback(log, url, timeout)
					if err != nil {
						logger.Error(err)
					}
				}(LogError, getLogPushEntry(FailedPush, to, req, result.Error), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout)
			}
			continue
		}

		LogPush(SucceededPush, to, req, nil)
	}

	// result from Send messages to topics
	if req.IsTopic() {
		to := ""
		if req.To != "" {
			to = req.To
		} else {
			to = req.Condition
		}
		LogAccess.Debug("Send Topic Message: ", to)
		// Success
		if res.MessageID != 0 {
			LogPush(SucceededPush, to, req, nil)
		} else {
			isError = true
			// failure
			LogPush(FailedPush, to, req, res.Error)
			if PushConf.Core.Sync {
				req.AddLog(getLogPushEntry(FailedPush, to, req, res.Error))
			}
		}
	}

	// Device Group HTTP Response
	if len(res.FailedRegistrationIDs) > 0 {
		isError = true
		newTokens = append(newTokens, res.FailedRegistrationIDs...)

		LogPush(FailedPush, notification.To, req, errors.New("device group: partial success or all fails"))
		if PushConf.Core.Sync {
			req.AddLog(getLogPushEntry(FailedPush, notification.To, req, errors.New("device group: partial success or all fails")))
		}
	}

	if isError && retryCount < maxRetry {
		retryCount++

		// resend fail token
		req.Tokens = newTokens
		goto Retry
	}

	return isError
}
<commit_msg>fix fcm notification (#474)<commit_after>package gorush

import (
	"errors"
	"fmt"

	"github.com/appleboy/go-fcm"
	"github.com/sirupsen/logrus"
)

// InitFCMClient use for initialize FCM Client.
func InitFCMClient(key string) (*fcm.Client, error) {
	var err error

	if key == "" {
		return nil, errors.New("Missing Android API Key")
	}

	if key != PushConf.Android.APIKey {
		return fcm.NewClient(key)
	}

	if FCMClient == nil {
		FCMClient, err = fcm.NewClient(key)
		return FCMClient, err
	}

	return FCMClient, nil
}

// GetAndroidNotification use for define Android notification.
// HTTP Connection Server Reference for Android
// https://firebase.google.com/docs/cloud-messaging/http-server-ref
func GetAndroidNotification(req PushNotification) *fcm.Message {
	notification := &fcm.Message{
		To:                    req.To,
		Condition:             req.Condition,
		CollapseKey:           req.CollapseKey,
		ContentAvailable:      req.ContentAvailable,
		MutableContent:        req.MutableContent,
		DelayWhileIdle:        req.DelayWhileIdle,
		TimeToLive:            req.TimeToLive,
		RestrictedPackageName: req.RestrictedPackageName,
		DryRun:                req.DryRun,
	}

	if len(req.Tokens) > 0 {
		notification.RegistrationIDs = req.Tokens
	}

	if len(req.Priority) > 0 && req.Priority == "high" {
		notification.Priority = "high"
	}

	// Add another field
	if len(req.Data) > 0 {
		notification.Data = make(map[string]interface{})
		for k, v := range req.Data {
			notification.Data[k] = v
		}
	}

	n := &fcm.Notification{}
	isNotificationSet := false
	if req.Notification != nil {
		isNotificationSet = true
		n = req.Notification
	}

	if len(req.Message) > 0 {
		isNotificationSet = true
		n.Body = req.Message
	}

	if len(req.Title) > 0 {
		isNotificationSet = true
		n.Title = req.Title
	}

	if len(req.Image) > 0 {
		isNotificationSet = true
		n.Image = req.Image
	}

	if v, ok := req.Sound.(string); ok && len(v) > 0 {
		isNotificationSet = true
		n.Sound = v
	}

	if isNotificationSet {
		notification.Notification = n
	}

	// handle iOS apns in fcm

	if len(req.Apns) > 0 {
		notification.Apns = req.Apns
	}

	return notification
}

// PushToAndroid provide send notification to Android server.
func PushToAndroid(req PushNotification) bool {
	LogAccess.Debug("Start push notification for Android")

	var (
		client     *fcm.Client
		retryCount = 0
		maxRetry   = PushConf.Android.MaxRetry
	)

	if req.Retry > 0 && req.Retry < maxRetry {
		maxRetry = req.Retry
	}

	// check message
	err := CheckMessage(req)

	if err != nil {
		LogError.Error("request error: " + err.Error())
		return false
	}

Retry:
	var isError = false

	notification := GetAndroidNotification(req)

	if req.APIKey != "" {
		client, err = InitFCMClient(req.APIKey)
	} else {
		client, err = InitFCMClient(PushConf.Android.APIKey)
	}

	if err != nil {
		// FCM server error
		LogError.Error("FCM server error: " + err.Error())
		return false
	}

	res, err := client.Send(notification)
	if err != nil {
		// Send Message error
		LogError.Error("FCM server send message error: " + err.Error())
		return false
	}

	if !req.IsTopic() {
		LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure))
	}

	StatStorage.AddAndroidSuccess(int64(res.Success))
	StatStorage.AddAndroidError(int64(res.Failure))

	var newTokens []string
	// result from Send messages to specific devices
	for k, result := range res.Results {
		to := ""
		if k < len(req.Tokens) {
			to = req.Tokens[k]
		} else {
			to = req.To
		}

		if result.Error != nil {
			isError = true
			newTokens = append(newTokens, to)
			LogPush(FailedPush, to, req, result.Error)
			if PushConf.Core.Sync {
				req.AddLog(getLogPushEntry(FailedPush, to, req, result.Error))
			} else if PushConf.Core.FeedbackURL != "" {
				go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) {
					err := DispatchFeedback(log, url, timeout)
					if err != nil {
						logger.Error(err)
					}
				}(LogError, getLogPushEntry(FailedPush, to, req, result.Error), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout)
			}
			continue
		}

		LogPush(SucceededPush, to, req, nil)
	}

	// result from Send messages to topics
	if req.IsTopic() {
		to := ""
		if req.To != "" {
			to = req.To
		} else {
			to = req.Condition
		}
		LogAccess.Debug("Send Topic Message: ", to)
		// Success
		if res.MessageID != 0 {
			LogPush(SucceededPush, to, req, nil)
		} else {
			isError = true
			// failure
			LogPush(FailedPush, to, req, res.Error)
			if PushConf.Core.Sync {
				req.AddLog(getLogPushEntry(FailedPush, to, req, res.Error))
			}
		}
	}

	// Device Group HTTP Response
	if len(res.FailedRegistrationIDs) > 0 {
		isError = true
		newTokens = append(newTokens, res.FailedRegistrationIDs...)

		LogPush(FailedPush, notification.To, req, errors.New("device group: partial success or all fails"))
		if PushConf.Core.Sync {
			req.AddLog(getLogPushEntry(FailedPush, notification.To, req, errors.New("device group: partial success or all fails")))
		}
	}

	if isError && retryCount < maxRetry {
		retryCount++

		// resend fail token
		req.Tokens = newTokens
		goto Retry
	}

	return isError
}
<|endoftext|>


<commit_before>package bot

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"log"
	"net"

	"github.com/BurntSushi/toml"
	"github.com/nightlyone/lockfile"

	"github.com/belak/irc"
)

type coreConfig struct {
	PidFile string

	Nick string
	User string
	Name string
	Pass string

	Host        string
	TLS         bool
	TLSNoVerify bool

	Cmds   []string
	Prefix string

	Plugins []string
}

// A Bot is our wrapper around the irc.Client. It could be used for a
// general client, but the provided convenience functions are designed
// around using this package to write a bot.
type Bot struct {
	// Everything needed for plugins
	BasicMux   *BasicMux
	CommandMux *CommandMux
	MentionMux *MentionMux

	Auth AuthProvider

	Plugins map[string]interface{}

	// Config stuff
	confValues map[string]toml.Primitive
	md         toml.MetaData

	// Internal things
	conn           *irc.Conn
	currentNick    string
	config         *coreConfig
	err            error
	loadingPlugins map[string]bool
}

// NewBot will return a new Bot given the name of a toml config file.
func NewBot(conf string) (*Bot, error) {
	b := &Bot{
		NewBasicMux(),
		nil,
		NewMentionMux(),
		&nullAuthProvider{},
		make(map[string]interface{}),
		make(map[string]toml.Primitive),
		toml.MetaData{},
		nil,
		"",
		&coreConfig{},
		nil,
		make(map[string]bool),
	}

	// Decode the file, but leave all the config sections intact
	// so we can decode those later.
	b.md, b.err = toml.DecodeFile(conf, b.confValues)
	if b.err != nil {
		return nil, b.err
	}

	// Load up the core config
	b.err = b.Config("core", b.config)
	if b.err != nil {
		return nil, b.err
	}

	// Load up the command mux
	b.CommandMux = NewCommandMux(b.config.Prefix)

	// Hook up the other muxes
	b.BasicMux.Event("PRIVMSG", b.CommandMux.HandleEvent)
	b.BasicMux.Event("PRIVMSG", b.MentionMux.HandleEvent)

	// Run commands on startup
	b.BasicMux.Event("001", func(b *Bot, m *irc.Message) {
		log.Println("Connected")
		for _, v := range b.config.Cmds {
			b.Write(v)
		}
	})

	return b, nil
}

// CurrentNick returns the current nick of the bot.
func (b *Bot) CurrentNick() string {
	return b.currentNick
}

// Config will decode the config section for the given name into the
// given interface{}
func (b *Bot) Config(name string, c interface{}) error {
	if v, ok := b.confValues[name]; ok {
		return b.md.PrimitiveDecode(v, c)
	}
	return fmt.Errorf("Config section for %q missing", name)
}

// Send is a simple function to send an IRC event
func (b *Bot) Send(m *irc.Message) {
	if b.err != nil {
		return
	}

	b.conn.WriteMessage(m)
}

// Reply to an irc.Message with a convenience wrapper around
// fmt.Sprintf
func (b *Bot) Reply(m *irc.Message, format string, v ...interface{}) error {
	if len(m.Params) < 1 || len(m.Params[0]) < 1 {
		return errors.New("Invalid IRC message")
	}

	target := m.Prefix.Name
	if m.FromChannel() {
		target = m.Params[0]
	}

	b.Send(&irc.Message{
		Prefix:  &irc.Prefix{},
		Command: "PRIVMSG",
		Params: []string{
			target,
			fmt.Sprintf(format, v...),
		},
	})

	return nil
}

// MentionReply acts the same as Bot.Reply but it will prefix it with
// the user's nick if we are in a channel.
func (b *Bot) MentionReply(m *irc.Message, format string, v ...interface{}) error {
	if m.FromChannel() {
		format = "%s: " + format
		v = prepend(v, m.Prefix.Name)
	}

	return b.Reply(m, format, v...)
}

// CTCPReply is a convenience function to respond to CTCP requests.
func (b *Bot) CTCPReply(m *irc.Message, format string, v ...interface{}) error {
	if m.Command != "CTCP" {
		return errors.New("Invalid CTCP message")
	}

	b.Send(&irc.Message{
		Prefix:  &irc.Prefix{},
		Command: "NOTICE",
		Params: []string{
			m.Prefix.Name,
			fmt.Sprintf(format, v...),
		},
	})
	return nil
}

// HasPerm is a convenience function for checking a user's permissions
func (b *Bot) HasPerm(m *irc.Message, perm string) bool {
	user := b.Auth.LookupUser(b, m.Prefix)

	return user.HasPerm(b, perm)
}

func (b *Bot) mainLoop() error {
	//, b.config.Nick, b.config.User, b.config.Name, b.config.Pass)
	// Send the initial connection info
	if len(b.config.Pass) > 0 {
		b.conn.Writef("PASS %s", b.config.Pass)
	}

	// Set the nick setting as the current nick
	b.currentNick = b.config.Nick

	b.conn.Writef("NICK %s", b.config.Nick)
	b.conn.Writef("USER %s 0.0.0.0 0.0.0.0 :%s", b.config.User, b.config.Name)

	var m *irc.Message
	for {
		m, b.err = b.conn.ReadMessage()
		if b.err != nil {
			break
		}

		// Internal handlers to make sure we track the
		// currentNick correctly and send PONGs
		if m.Command == "NICK" {
			if m.Prefix.Name == b.currentNick && len(m.Params) > 0 {
				b.currentNick = m.Params[0]
			}
		} else if m.Command == "PING" {
			b.conn.Writef("PONG :%s", m.Trailing())
		} else if m.Command == "001" {
			b.currentNick = m.Params[0]
		} else if m.Command == "437" || m.Command == "433" {
			b.currentNick = b.currentNick + "_"
			b.conn.Writef("NICK %s", b.currentNick)
		}

		log.Println(m)

		b.BasicMux.HandleEvent(b, m)

		// TODO: Make this work better
		if b.err != nil {
			break
		}
	}

	return b.err
}

// Write will write an raw IRC message to the stream
func (b *Bot) Write(line string) {
	b.conn.Write(line)
}

// Writef is a convenience method around fmt.Sprintf and Bot.Write
func (b *Bot) Writef(format string, args ...interface{}) {
	b.conn.Writef(format, args...)
}

// PluginLoaded will return true if a plugin is loaded and false otherwise
func (b *Bot) PluginLoaded(name string) bool {
	_, ok := b.Plugins[name]
	return ok
}

// LoadPlugin will ensure a plugin is loaded. It is designed to be
// usable in other plugins, so they can ensure plugins they depend on
// are loaded before using them.
func (b *Bot) LoadPlugin(name string) error {
	// We don't need to load the plugin if it's already loaded
	if b.PluginLoaded(name) {
		return nil
	}

	// Ensure the plugin exists
	factory, ok := plugins[name]
	if !ok {
		return fmt.Errorf("Plugin %s does not exist", name)
	}

	// If we're trying to load this plugin already, this is a
	// circular load and we should bail.
	if v := b.loadingPlugins[name]; v {
		return fmt.Errorf("Plugin %s getting loaded circularly", name)
	}

	// Set a flag stating that we're loading this plugin
	b.loadingPlugins[name] = true

	// Actually load the plugin
	plugin, err := factory(b)
	if err != nil {
		return fmt.Errorf("Plugin %s failed to load", name)
	}

	// Save the value returned from the plugin factory.
	b.Plugins[name] = plugin

	// Unset the flag saying we're loading this because we're done now.
	delete(b.loadingPlugins, name)

	return nil
}

// Run starts the bot and loops until it dies
func (b *Bot) Run() error {
	var err error
	// Load all the plugins we need. If there were plugins
	// specified in the config, just load those. Otherwise load
	// ALL of them.
	if len(b.config.Plugins) != 0 {
		for _, name := range b.config.Plugins {
			err = b.LoadPlugin(name)
			if err != nil {
				return err
			}
		}
	} else {
		for name := range plugins {
			err = b.LoadPlugin(name)
			if err != nil {
				return err
			}
		}
	}

	// If we have a pidfile configured, create it and write the PID
	if b.config.PidFile != "" {
		l, err := lockfile.New(b.config.PidFile)
		if err != nil {
			return err
		}

		err = l.TryLock()
		if err != nil {
			return err
		}

		defer l.Unlock()
	}

	// The ReadWriteCloser will contain either a *net.Conn or *tls.Conn
	var c io.ReadWriteCloser
	if b.config.TLS {
		conf := &tls.Config{
			InsecureSkipVerify: b.config.TLSNoVerify,
		}

		c, err = tls.Dial("tcp", b.config.Host, conf)
	} else {
		c, err = net.Dial("tcp", b.config.Host)
	}

	if err != nil {
		return err
	}

	// Create a client from the connection we've just opened
	b.conn = irc.NewConn(c)

	b.conn.DebugCallback = func(line string) {
		log.Println(line)
	}

	// Start the main loop
	return b.mainLoop()
}
<commit_msg>Clean up some logging<commit_after>package bot

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"log"
	"net"

	"github.com/BurntSushi/toml"
	"github.com/nightlyone/lockfile"

	"github.com/belak/irc"
)

type coreConfig struct {
	PidFile string

	Nick string
	User string
	Name string
	Pass string

	Host        string
	TLS         bool
	TLSNoVerify bool

	Cmds   []string
	Prefix string

	Plugins []string

	Quiet bool
}

// A Bot is our wrapper around the irc.Client. It could be used for a
// general client, but the provided convenience functions are designed
// around using this package to write a bot.
type Bot struct {
	// Everything needed for plugins
	BasicMux   *BasicMux
	CommandMux *CommandMux
	MentionMux *MentionMux

	Auth AuthProvider

	Plugins map[string]interface{}

	// Config stuff
	confValues map[string]toml.Primitive
	md         toml.MetaData

	// Internal things
	conn           *irc.Conn
	currentNick    string
	config         *coreConfig
	err            error
	loadingPlugins map[string]bool
}

// NewBot will return a new Bot given the name of a toml config file.
func NewBot(conf string) (*Bot, error) {
	b := &Bot{
		NewBasicMux(),
		nil,
		NewMentionMux(),
		&nullAuthProvider{},
		make(map[string]interface{}),
		make(map[string]toml.Primitive),
		toml.MetaData{},
		nil,
		"",
		&coreConfig{},
		nil,
		make(map[string]bool),
	}

	// Decode the file, but leave all the config sections intact
	// so we can decode those later.
	b.md, b.err = toml.DecodeFile(conf, b.confValues)
	if b.err != nil {
		return nil, b.err
	}

	// Load up the core config
	b.err = b.Config("core", b.config)
	if b.err != nil {
		return nil, b.err
	}

	// Load up the command mux
	b.CommandMux = NewCommandMux(b.config.Prefix)

	// Hook up the other muxes
	b.BasicMux.Event("PRIVMSG", b.CommandMux.HandleEvent)
	b.BasicMux.Event("PRIVMSG", b.MentionMux.HandleEvent)

	// Run commands on startup
	b.BasicMux.Event("001", func(b *Bot, m *irc.Message) {
		log.Println("Connected")
		for _, v := range b.config.Cmds {
			b.Write(v)
		}
	})

	return b, nil
}

// CurrentNick returns the current nick of the bot.
func (b *Bot) CurrentNick() string {
	return b.currentNick
}

// Config will decode the config section for the given name into the
// given interface{}
func (b *Bot) Config(name string, c interface{}) error {
	if v, ok := b.confValues[name]; ok {
		return b.md.PrimitiveDecode(v, c)
	}
	return fmt.Errorf("Config section for %q missing", name)
}

// Send is a simple function to send an IRC event
func (b *Bot) Send(m *irc.Message) {
	if b.err != nil {
		return
	}

	b.conn.WriteMessage(m)
}

// Reply to an irc.Message with a convenience wrapper around
// fmt.Sprintf
func (b *Bot) Reply(m *irc.Message, format string, v ...interface{}) error {
	if len(m.Params) < 1 || len(m.Params[0]) < 1 {
		return errors.New("Invalid IRC message")
	}

	target := m.Prefix.Name
	if m.FromChannel() {
		target = m.Params[0]
	}

	b.Send(&irc.Message{
		Prefix:  &irc.Prefix{},
		Command: "PRIVMSG",
		Params: []string{
			target,
			fmt.Sprintf(format, v...),
		},
	})

	return nil
}

// MentionReply acts the same as Bot.Reply but it will prefix it with
// the user's nick if we are in a channel.
func (b *Bot) MentionReply(m *irc.Message, format string, v ...interface{}) error {
	if m.FromChannel() {
		format = "%s: " + format
		v = prepend(v, m.Prefix.Name)
	}

	return b.Reply(m, format, v...)
}

// CTCPReply is a convenience function to respond to CTCP requests.
func (b *Bot) CTCPReply(m *irc.Message, format string, v ...interface{}) error {
	if m.Command != "CTCP" {
		return errors.New("Invalid CTCP message")
	}

	b.Send(&irc.Message{
		Prefix:  &irc.Prefix{},
		Command: "NOTICE",
		Params: []string{
			m.Prefix.Name,
			fmt.Sprintf(format, v...),
		},
	})
	return nil
}

// HasPerm is a convenience function for checking a user's permissions
func (b *Bot) HasPerm(m *irc.Message, perm string) bool {
	user := b.Auth.LookupUser(b, m.Prefix)

	return user.HasPerm(b, perm)
}

func (b *Bot) mainLoop() error {
	//, b.config.Nick, b.config.User, b.config.Name, b.config.Pass)
	// Send the initial connection info
	if len(b.config.Pass) > 0 {
		b.conn.Writef("PASS %s", b.config.Pass)
	}

	// Set the nick setting as the current nick
	b.currentNick = b.config.Nick

	b.conn.Writef("NICK %s", b.config.Nick)
	b.conn.Writef("USER %s 0.0.0.0 0.0.0.0 :%s", b.config.User, b.config.Name)

	var m *irc.Message
	for {
		m, b.err = b.conn.ReadMessage()
		if b.err != nil {
			break
		}

		// Internal handlers to make sure we track the
		// currentNick correctly and send PONGs
		if m.Command == "NICK" {
			if m.Prefix.Name == b.currentNick && len(m.Params) > 0 {
				b.currentNick = m.Params[0]
			}
		} else if m.Command == "PING" {
			b.conn.Writef("PONG :%s", m.Trailing())
		} else if m.Command == "001" {
			b.currentNick = m.Params[0]
		} else if m.Command == "437" || m.Command == "433" {
			b.currentNick = b.currentNick + "_"
			b.conn.Writef("NICK %s", b.currentNick)
		}

		b.BasicMux.HandleEvent(b, m)

		// TODO: Make this work better
		if b.err != nil {
			break
		}
	}

	return b.err
}

// Write will write an raw IRC message to the stream
func (b *Bot) Write(line string) {
	b.conn.Write(line)
}

// Writef is a convenience method around fmt.Sprintf and Bot.Write
func (b *Bot) Writef(format string, args ...interface{}) {
	b.conn.Writef(format, args...)
}

// PluginLoaded will return true if a plugin is loaded and false otherwise
func (b *Bot) PluginLoaded(name string) bool {
	_, ok := b.Plugins[name]
	return ok
}

// LoadPlugin will ensure a plugin is loaded. It is designed to be
// usable in other plugins, so they can ensure plugins they depend on
// are loaded before using them.
func (b *Bot) LoadPlugin(name string) error {
	// We don't need to load the plugin if it's already loaded
	if b.PluginLoaded(name) {
		return nil
	}

	// Ensure the plugin exists
	factory, ok := plugins[name]
	if !ok {
		return fmt.Errorf("Plugin %s does not exist", name)
	}

	// If we're trying to load this plugin already, this is a
	// circular load and we should bail.
	if v := b.loadingPlugins[name]; v {
		return fmt.Errorf("Plugin %s getting loaded circularly", name)
	}

	// Set a flag stating that we're loading this plugin
	b.loadingPlugins[name] = true

	// Actually load the plugin
	plugin, err := factory(b)
	if err != nil {
		return fmt.Errorf("Plugin %s failed to load", name)
	}

	// Save the value returned from the plugin factory.
	b.Plugins[name] = plugin

	// Unset the flag saying we're loading this because we're done now.
	delete(b.loadingPlugins, name)

	return nil
}

// Run starts the bot and loops until it dies
func (b *Bot) Run() error {
	var err error
	// Load all the plugins we need. If there were plugins
	// specified in the config, just load those. Otherwise load
	// ALL of them.
	if len(b.config.Plugins) != 0 {
		for _, name := range b.config.Plugins {
			err = b.LoadPlugin(name)
			if err != nil {
				return err
			}
		}
	} else {
		for name := range plugins {
			err = b.LoadPlugin(name)
			if err != nil {
				return err
			}
		}
	}

	// If we have a pidfile configured, create it and write the PID
	if b.config.PidFile != "" {
		l, err := lockfile.New(b.config.PidFile)
		if err != nil {
			return err
		}

		err = l.TryLock()
		if err != nil {
			return err
		}

		defer l.Unlock()
	}

	// The ReadWriteCloser will contain either a *net.Conn or *tls.Conn
	var c io.ReadWriteCloser
	if b.config.TLS {
		conf := &tls.Config{
			InsecureSkipVerify: b.config.TLSNoVerify,
		}

		c, err = tls.Dial("tcp", b.config.Host, conf)
	} else {
		c, err = net.Dial("tcp", b.config.Host)
	}

	if err != nil {
		return err
	}

	// Create a client from the connection we've just opened
	b.conn = irc.NewConn(c)

	b.conn.DebugCallback = func(line string) {
		if !b.config.Quiet {
			log.Println(line)
		}
	}

	// Start the main loop
	return b.mainLoop()
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package trigger

import (
	"fmt"
	"strings"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/kube"
	"k8s.io/test-infra/prow/pjutil"
	"k8s.io/test-infra/prow/plugins"
)

const (
	needsOkToTest = "needs-ok-to-test"
)

func handlePR(c client, trustedOrg string, pr github.PullRequestEvent) error {
	author := pr.PullRequest.User.Login
	switch pr.Action {
	case github.PullRequestActionOpened:
		// When a PR is opened, if the author is in the org then build it.
		// Otherwise, ask for "/ok-to-test". There's no need to look for previous
		// "/ok-to-test" comments since the PR was just opened!
		member, err := isUserTrusted(c.GitHubClient, author, trustedOrg, pr.Repo.Owner.Login)
		if err != nil {
			return fmt.Errorf("could not check membership: %s", err)
		}
		if member {
			c.Logger.Info("Starting all jobs for new PR.")
			return buildAll(c, pr.PullRequest, pr.GUID)
		} else {
			c.Logger.Infof("Welcome message to PR author %q.", author)
			if err := welcomeMsg(c.GitHubClient, pr.PullRequest, trustedOrg); err != nil {
				return fmt.Errorf("could not welcome non-org member %q: %v", author, err)
			}
		}
	case github.PullRequestActionReopened:
		// When a PR is reopened, check that the user is in the org or that an org
		// member had said "/ok-to-test" before building.
		comments, err := c.GitHubClient.ListIssueComments(pr.PullRequest.Base.Repo.Owner.Login, pr.PullRequest.Base.Repo.Name, pr.PullRequest.Number)
		if err != nil {
			return err
		}
		trusted, err := trustedPullRequest(c.GitHubClient, pr.PullRequest, trustedOrg, comments)
		if err != nil {
			return fmt.Errorf("could not validate PR: %s", err)
		} else if trusted {
			err = clearStaleComments(c.GitHubClient, trustedOrg, pr.PullRequest, comments)
			if err != nil {
				c.Logger.Warnf("Failed to clear stale comments: %v.", err)
			}
			// Just try to remove "needs-ok-to-test" label if existing, we don't care about the result.
			c.GitHubClient.RemoveLabel(trustedOrg, pr.PullRequest.Base.Repo.Name, pr.PullRequest.Number, needsOkToTest)
			c.Logger.Info("Starting all jobs for updated PR.")
			return buildAll(c, pr.PullRequest, pr.GUID)
		}
	case github.PullRequestActionSynchronize:
		// When a PR is updated, check that the user is in the org or that an org
		// member has said "/ok-to-test" before building. There's no need to ask
		// for "/ok-to-test" because we do that once when the PR is created.
		comments, err := c.GitHubClient.ListIssueComments(pr.PullRequest.Base.Repo.Owner.Login, pr.PullRequest.Base.Repo.Name, pr.PullRequest.Number)
		if err != nil {
			return err
		}
		trusted, err := trustedPullRequest(c.GitHubClient, pr.PullRequest, trustedOrg, comments)
		if err != nil {
			return fmt.Errorf("could not validate PR: %s", err)
		} else if trusted {
			err = clearStaleComments(c.GitHubClient, trustedOrg, pr.PullRequest, comments)
			if err != nil {
				c.Logger.Warnf("Failed to clear stale comments: %v.", err)
			}
			c.Logger.Info("Starting all jobs for updated PR.")
			return buildAll(c, pr.PullRequest, pr.GUID)
		}
	case github.PullRequestActionLabeled:
		comments, err := c.GitHubClient.ListIssueComments(pr.PullRequest.Base.Repo.Owner.Login, pr.PullRequest.Base.Repo.Name, pr.PullRequest.Number)
		if err != nil {
			return err
		}
		// When a PR is LGTMd, if it is untrusted then build it once.
		if pr.Label.Name == lgtmLabel {
			trusted, err := trustedPullRequest(c.GitHubClient, pr.PullRequest, trustedOrg, comments)
			if err != nil {
				return fmt.Errorf("could not validate PR: %s", err)
			} else if !trusted {
				c.Logger.Info("Starting all jobs for untrusted PR with LGTM.")
				return buildAll(c, pr.PullRequest, pr.GUID)
			}
		}
	}
	return nil
}

func welcomeMsg(ghc githubClient, pr github.PullRequest, trustedOrg string) error {
	commentTemplate := `Hi @%s. Thanks for your PR.

I'm waiting for a [%s](https://github.com/orgs/%s/people) %smember to verify that this patch is reasonable to test. If it is, they should reply with ` + "`/ok-to-test`" + ` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

I understand the commands that are listed [here](https://github.com/kubernetes/test-infra/blob/master/commands.md).

<details>

%s
</details>
`
	owner := pr.Base.Repo.Owner.Login
	name := pr.Base.Repo.Name
	var more string
	if owner != trustedOrg {
		more = fmt.Sprintf("or [%s](https://github.com/orgs/%s/people) ", owner, owner)
	}
	comment := fmt.Sprintf(commentTemplate, pr.User.Login, trustedOrg, trustedOrg, more, plugins.AboutThisBotWithoutCommands)

	err1 := ghc.AddLabel(owner, name, pr.Number, needsOkToTest)
	err2 := ghc.CreateComment(owner, name, pr.Number, comment)
	if err1 != nil || err2 != nil {
		return fmt.Errorf("welcomeMsg: error adding label: %v, error creating comment: %v", err1, err2)
	}
	return nil
}

// trustedPullRequest returns whether or not the given PR should be tested.
// It first checks if the author is in the org, then looks for "/ok-to-test"
// comments by org members.
func trustedPullRequest(ghc githubClient, pr github.PullRequest, trustedOrg string, comments []github.IssueComment) (bool, error) {
	author := pr.User.Login
	org := pr.Base.Repo.Owner.Login
	// First check if the author is a member of the org.
	orgMember, err := isUserTrusted(ghc, author, trustedOrg, org)
	if err != nil {
		return false, err
	}
	if orgMember {
		return true, nil
	}
	botName, err := ghc.BotName()
	if err != nil {
		return false, err
	}
	// Next look for "/ok-to-test" comments on the PR.
	for _, comment := range comments {
		commentAuthor := comment.User.Login
		// Skip comments: by the PR author, or by bot, or not matching "/ok-to-test".
		if commentAuthor == author || commentAuthor == botName || !okToTest.MatchString(comment.Body) {
			continue
		}
		// Ensure that the commenter is in the org.
		commentAuthorMember, err := isUserTrusted(ghc, commentAuthor, trustedOrg, org)
		if err != nil {
			return false, err
		}
		if commentAuthorMember {
			return true, nil
		}
	}
	return false, nil
}

func buildAll(c client, pr github.PullRequest, eventGUID string) error {
	org := pr.Base.Repo.Owner.Login
	repo := pr.Base.Repo.Name
	var ref string
	var changes []string // lazily initialized

	for _, job := range c.Config.Presubmits[pr.Base.Repo.FullName] {
		skip := false
		if job.RunIfChanged != "" {
			if changes == nil {
				changesFull, err := c.GitHubClient.GetPullRequestChanges(org, repo, pr.Number)
				if err != nil {
					return err
				}
				// We only care about the filenames here
				for _, change := range changesFull {
					changes = append(changes, change.Filename)
				}
			}
			skip = !job.RunsAgainstChanges(changes)
		} else if !job.AlwaysRun {
			continue
		}

		if (skip || !job.RunsAgainstBranch(pr.Base.Ref)) && !job.SkipReport {
			if err := c.GitHubClient.CreateStatus(org, repo, pr.Head.SHA, github.Status{
				State:       github.StatusSuccess,
				Context:     job.Context,
				Description: "Skipped",
			}); err != nil {
				return err
			}
			continue
		}

		// Only get master ref once.
		if ref == "" {
			r, err := c.GitHubClient.GetRef(org, repo, "heads/"+pr.Base.Ref)
			if err != nil {
				return err
			}
			ref = r
		}
		kr := kube.Refs{
			Org:     org,
			Repo:    repo,
			BaseRef: pr.Base.Ref,
			BaseSHA: ref,
			Pulls: []kube.Pull{
				{
					Number: pr.Number,
					Author: pr.User.Login,
					SHA:    pr.Head.SHA,
				},
			},
		}
		labels := make(map[string]string)
		for k, v := range job.Labels {
			labels[k] = v
		}
		labels[github.EventGUID] = eventGUID
		pj := pjutil.NewProwJob(pjutil.PresubmitSpec(job, kr), labels)
		c.Logger.WithFields(pjutil.ProwJobFields(&pj)).Info("Creating a new prowjob.")
		if _, err := c.KubeClient.CreateProwJob(pj); err != nil {
			return err
		}
	}
	return nil
}

// clearStaleComments deletes old comments that are no longer applicable.
func clearStaleComments(gc githubClient, trustedOrg string, pr github.PullRequest, comments []github.IssueComment) error {
	botName, err := gc.BotName()
	if err != nil {
		return err
	}

	var more string
	if org := pr.Base.Repo.Owner.Login; org != trustedOrg {
		more = fmt.Sprintf("or [%s](https://github.com/orgs/%s/people) ", org, org)
	}
	waitingComment := fmt.Sprintf("I'm waiting for a [%s](https://github.com/orgs/%s/people) %smember to verify that this patch is reasonable to test.", trustedOrg, trustedOrg, more)

	return gc.DeleteStaleComments(
		trustedOrg,
		pr.Base.Repo.Name,
		pr.Number,
		comments,
		func(c github.IssueComment) bool { // isStale function
			return c.User.Login == botName && strings.Contains(c.Body, waitingComment)
		},
	)
}
<commit_msg>link to contributor guidlines in ok-to-test message<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package trigger

import (
	"fmt"
	"strings"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/kube"
	"k8s.io/test-infra/prow/pjutil"
	"k8s.io/test-infra/prow/plugins"
)

const (
	needsOkToTest = "needs-ok-to-test"
)

func handlePR(c client, trustedOrg string, pr github.PullRequestEvent) error {
	author := pr.PullRequest.User.Login
	switch pr.Action {
	case github.PullRequestActionOpened:
		// When a PR is opened, if the author is in the org then build it.
		// Otherwise, ask for "/ok-to-test". There's no need to look for previous
		// "/ok-to-test" comments since the PR was just opened!
		member, err := isUserTrusted(c.GitHubClient, author, trustedOrg, pr.Repo.Owner.Login)
		if err != nil {
			return fmt.Errorf("could not check membership: %s", err)
		}
		if member {
			c.Logger.Info("Starting all jobs for new PR.")
			return buildAll(c, pr.PullRequest, pr.GUID)
		} else {
			c.Logger.Infof("Welcome message to PR author %q.", author)
			if err := welcomeMsg(c.GitHubClient, pr.PullRequest, trustedOrg); err != nil {
				return fmt.Errorf("could not welcome non-org member %q: %v", author, err)
			}
		}
	case github.PullRequestActionReopened:
		// When a PR is reopened, check that the user is in the org or that an org
		// member had said "/ok-to-test" before building.
		comments, err := c.GitHubClient.ListIssueComments(pr.PullRequest.Base.Repo.Owner.Login, pr.PullRequest.Base.Repo.Name, pr.PullRequest.Number)
		if err != nil {
			return err
		}
		trusted, err := trustedPullRequest(c.GitHubClient, pr.PullRequest, trustedOrg, comments)
		if err != nil {
			return fmt.Errorf("could not validate PR: %s", err)
		} else if trusted {
			err = clearStaleComments(c.GitHubClient, trustedOrg, pr.PullRequest, comments)
			if err != nil {
				c.Logger.Warnf("Failed to clear stale comments: %v.", err)
			}
			// Just try to remove "needs-ok-to-test" label if existing, we don't care about the result.
			c.GitHubClient.RemoveLabel(trustedOrg, pr.PullRequest.Base.Repo.Name, pr.PullRequest.Number, needsOkToTest)
			c.Logger.Info("Starting all jobs for updated PR.")
			return buildAll(c, pr.PullRequest, pr.GUID)
		}
	case github.PullRequestActionSynchronize:
		// When a PR is updated, check that the user is in the org or that an org
		// member has said "/ok-to-test" before building. There's no need to ask
		// for "/ok-to-test" because we do that once when the PR is created.
		comments, err := c.GitHubClient.ListIssueComments(pr.PullRequest.Base.Repo.Owner.Login, pr.PullRequest.Base.Repo.Name, pr.PullRequest.Number)
		if err != nil {
			return err
		}
		trusted, err := trustedPullRequest(c.GitHubClient, pr.PullRequest, trustedOrg, comments)
		if err != nil {
			return fmt.Errorf("could not validate PR: %s", err)
		} else if trusted {
			err = clearStaleComments(c.GitHubClient, trustedOrg, pr.PullRequest, comments)
			if err != nil {
				c.Logger.Warnf("Failed to clear stale comments: %v.", err)
			}
			c.Logger.Info("Starting all jobs for updated PR.")
			return buildAll(c, pr.PullRequest, pr.GUID)
		}
	case github.PullRequestActionLabeled:
		comments, err := c.GitHubClient.ListIssueComments(pr.PullRequest.Base.Repo.Owner.Login, pr.PullRequest.Base.Repo.Name, pr.PullRequest.Number)
		if err != nil {
			return err
		}
		// When a PR is LGTMd, if it is untrusted then build it once.
		if pr.Label.Name == lgtmLabel {
			trusted, err := trustedPullRequest(c.GitHubClient, pr.PullRequest, trustedOrg, comments)
			if err != nil {
				return fmt.Errorf("could not validate PR: %s", err)
			} else if !trusted {
				c.Logger.Info("Starting all jobs for untrusted PR with LGTM.")
				return buildAll(c, pr.PullRequest, pr.GUID)
			}
		}
	}
	return nil
}

func welcomeMsg(ghc githubClient, pr github.PullRequest, trustedOrg string) error {
	commentTemplate := `Hi @%s. Thanks for your PR.

I'm waiting for a [%s](https://github.com/orgs/%s/people) %smember to verify that this patch is reasonable to test. If it is, they should reply with ` + "`/ok-to-test`" + ` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/kubernetes/community/blob/master/community-membership.md#member) to skip this step.

I understand the commands that are listed [here](https://github.com/kubernetes/test-infra/blob/master/commands.md).

<details>

%s
</details>
`
	owner := pr.Base.Repo.Owner.Login
	name := pr.Base.Repo.Name
	var more string
	if owner != trustedOrg {
		more = fmt.Sprintf("or [%s](https://github.com/orgs/%s/people) ", owner, owner)
	}
	comment := fmt.Sprintf(commentTemplate, pr.User.Login, trustedOrg, trustedOrg, more, plugins.AboutThisBotWithoutCommands)

	err1 := ghc.AddLabel(owner, name, pr.Number, needsOkToTest)
	err2 := ghc.CreateComment(owner, name, pr.Number, comment)
	if err1 != nil || err2 != nil {
		return fmt.Errorf("welcomeMsg: error adding label: %v, error creating comment: %v", err1, err2)
	}
	return nil
}

// trustedPullRequest returns whether or not the given PR should be tested.
// It first checks if the author is in the org, then looks for "/ok-to-test"
// comments by org members.
func trustedPullRequest(ghc githubClient, pr github.PullRequest, trustedOrg string, comments []github.IssueComment) (bool, error) {
	author := pr.User.Login
	org := pr.Base.Repo.Owner.Login
	// First check if the author is a member of the org.
	orgMember, err := isUserTrusted(ghc, author, trustedOrg, org)
	if err != nil {
		return false, err
	}
	if orgMember {
		return true, nil
	}
	botName, err := ghc.BotName()
	if err != nil {
		return false, err
	}
	// Next look for "/ok-to-test" comments on the PR.
	for _, comment := range comments {
		commentAuthor := comment.User.Login
		// Skip comments: by the PR author, or by bot, or not matching "/ok-to-test".
		if commentAuthor == author || commentAuthor == botName || !okToTest.MatchString(comment.Body) {
			continue
		}
		// Ensure that the commenter is in the org.
		commentAuthorMember, err := isUserTrusted(ghc, commentAuthor, trustedOrg, org)
		if err != nil {
			return false, err
		}
		if commentAuthorMember {
			return true, nil
		}
	}
	return false, nil
}

func buildAll(c client, pr github.PullRequest, eventGUID string) error {
	org := pr.Base.Repo.Owner.Login
	repo := pr.Base.Repo.Name
	var ref string
	var changes []string // lazily initialized

	for _, job := range c.Config.Presubmits[pr.Base.Repo.FullName] {
		skip := false
		if job.RunIfChanged != "" {
			if changes == nil {
				changesFull, err := c.GitHubClient.GetPullRequestChanges(org, repo, pr.Number)
				if err != nil {
					return err
				}
				// We only care about the filenames here
				for _, change := range changesFull {
					changes = append(changes, change.Filename)
				}
			}
			skip = !job.RunsAgainstChanges(changes)
		} else if !job.AlwaysRun {
			continue
		}

		if (skip || !job.RunsAgainstBranch(pr.Base.Ref)) && !job.SkipReport {
			if err := c.GitHubClient.CreateStatus(org, repo, pr.Head.SHA, github.Status{
				State:       github.StatusSuccess,
				Context:     job.Context,
				Description: "Skipped",
			}); err != nil {
				return err
			}
			continue
		}

		// Only get master ref once.
		if ref == "" {
			r, err := c.GitHubClient.GetRef(org, repo, "heads/"+pr.Base.Ref)
			if err != nil {
				return err
			}
			ref = r
		}
		kr := kube.Refs{
			Org:     org,
			Repo:    repo,
			BaseRef: pr.Base.Ref,
			BaseSHA: ref,
			Pulls: []kube.Pull{
				{
					Number: pr.Number,
					Author: pr.User.Login,
					SHA:    pr.Head.SHA,
				},
			},
		}
		labels := make(map[string]string)
		for k, v := range job.Labels {
			labels[k] = v
		}
		labels[github.EventGUID] = eventGUID
		pj := pjutil.NewProwJob(pjutil.PresubmitSpec(job, kr), labels)
		c.Logger.WithFields(pjutil.ProwJobFields(&pj)).Info("Creating a new prowjob.")
		if _, err := c.KubeClient.CreateProwJob(pj); err != nil {
			return err
		}
	}
	return nil
}

// clearStaleComments deletes old comments that are no longer applicable.
func clearStaleComments(gc githubClient, trustedOrg string, pr github.PullRequest, comments []github.IssueComment) error {
	botName, err := gc.BotName()
	if err != nil {
		return err
	}

	var more string
	if org := pr.Base.Repo.Owner.Login; org != trustedOrg {
		more = fmt.Sprintf("or [%s](https://github.com/orgs/%s/people) ", org, org)
	}
	waitingComment := fmt.Sprintf("I'm waiting for a [%s](https://github.com/orgs/%s/people) %smember to verify that this patch is reasonable to test.", trustedOrg, trustedOrg, more)

	return gc.DeleteStaleComments(
		trustedOrg,
		pr.Base.Repo.Name,
		pr.Number,
		comments,
		func(c github.IssueComment) bool { // isStale function
			return c.User.Login == botName && strings.Contains(c.Body, waitingComment)
		},
	)
}
<|endoftext|>


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

package zoekt

import (
	"testing"

	"golang.org/x/net/context"

	"github.com/google/zoekt/query"
)

type testLoader struct {
	searchers []Searcher
}

func (l *testLoader) Close() {}
func (l *testLoader) getShards() []Searcher {
	return l.searchers
}

func (l *testLoader) rlock()         {}
func (l *testLoader) runlock()       {}
func (l *testLoader) String() string { return "test" }

type crashSearcher struct{}

func (s *crashSearcher) Search(ctx context.Context, q query.Q, opts *SearchOptions) (*SearchResult, error) {
	panic("search")
}

func (s *crashSearcher) List(ctx context.Context, q query.Q) (*RepoList, error) {
	panic("list")
}

func (s *crashSearcher) Stats() (*RepoStats, error) {
	return &RepoStats{}, nil
}

func (s *crashSearcher) Close() {}

func (s *crashSearcher) String() string { return "crashSearcher" }

func TestCrashResilience(t *testing.T) {
	ss := &shardedSearcher{&testLoader{[]Searcher{&crashSearcher{}}}}

	q := &query.Substring{Pattern: "hoi"}
	opts := &SearchOptions{}
	if res, err := ss.Search(context.Background(), q, opts); err != nil {
		t.Fatalf("Search: %v", err)
	} else if res.Stats.Crashes != 1 {
		t.Errorf("got stats %#v, want crashes = 1", res.Stats)
	}

	if res, err := ss.List(context.Background(), q); err != nil {
		t.Fatalf("List: %v", err)
	} else if res.Crashes != 1 {
		t.Errorf("got result %#v, want crashes = 1", res)
	}
}
<commit_msg>Redirect crash test log output.<commit_after>// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package zoekt

import (
	"bytes"
	"log"
	"os"
	"testing"

	"golang.org/x/net/context"

	"github.com/google/zoekt/query"
)

type testLoader struct {
	searchers []Searcher
}

func (l *testLoader) Close() {}
func (l *testLoader) getShards() []Searcher {
	return l.searchers
}

func (l *testLoader) rlock()         {}
func (l *testLoader) runlock()       {}
func (l *testLoader) String() string { return "test" }

type crashSearcher struct{}

func (s *crashSearcher) Search(ctx context.Context, q query.Q, opts *SearchOptions) (*SearchResult, error) {
	panic("search")
}

func (s *crashSearcher) List(ctx context.Context, q query.Q) (*RepoList, error) {
	panic("list")
}

func (s *crashSearcher) Stats() (*RepoStats, error) {
	return &RepoStats{}, nil
}

func (s *crashSearcher) Close() {}

func (s *crashSearcher) String() string { return "crashSearcher" }

func TestCrashResilience(t *testing.T) {
	out := &bytes.Buffer{}
	log.SetOutput(out)
	defer log.SetOutput(os.Stderr)
	ss := &shardedSearcher{&testLoader{[]Searcher{&crashSearcher{}}}}

	q := &query.Substring{Pattern: "hoi"}
	opts := &SearchOptions{}
	if res, err := ss.Search(context.Background(), q, opts); err != nil {
		t.Fatalf("Search: %v", err)
	} else if res.Stats.Crashes != 1 {
		t.Errorf("got stats %#v, want crashes = 1", res.Stats)
	}

	if res, err := ss.List(context.Background(), q); err != nil {
		t.Fatalf("List: %v", err)
	} else if res.Crashes != 1 {
		t.Errorf("got result %#v, want crashes = 1", res)
	}
}
<|endoftext|>


<commit_before>package gubled

import (
	"github.com/smancke/guble/client"
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server"

	"github.com/stretchr/testify/assert"
	"testing"

	"encoding/json"
	"time"
)

func TestSimplePingPong(t *testing.T) {
	_, client1, client2, tearDown := initServerAndClients(t)
	defer tearDown()

	client1.Subscribe("/foo")
	expectStatusMessage(t, client1, protocol.SUCCESS_SUBSCRIBED_TO, "/foo")

	time.Sleep(time.Millisecond * 10)
	client2.Send("/foo 42", "Hello", `{"key": "value"}`)
	expectStatusMessage(t, client2, protocol.SUCCESS_SEND, "42")

	select {
	case msg := <-client1.Messages():
		assert.Equal(t, "Hello", msg.BodyAsString())
		assert.Equal(t, "user2", msg.UserID)
		assert.Equal(t, `{"key": "value"}`, msg.HeaderJSON)
		assert.Equal(t, uint64(1), msg.ID)
	case msg := <-client1.Errors():
		t.Logf("received error: %v", msg)
		t.FailNow()
	case <-time.After(time.Millisecond * 100):
		t.Log("no message received")
		t.FailNow()
	}
}

func initServerAndClients(t *testing.T) (*server.Service, client.Client, client.Client, func()) {
	service := StartupService(Args{Listen: "localhost:0", KVBackend: "memory"})

	time.Sleep(time.Millisecond * 100)

	var err error
	client1, err := client.Open("ws://"+service.GetWebServer().GetAddr()+"/stream/user/user1", "http://localhost", 1, false)
	assert.NoError(t, err)

	checkConnectedNotificationJson(t, "user1",
		expectStatusMessage(t, client1, protocol.SUCCESS_CONNECTED, "You are connected to the server."),
	)

	client2, err := client.Open("ws://"+service.GetWebServer().GetAddr()+"/stream/user/user2", "http://localhost", 1, false)
	assert.NoError(t, err)
	checkConnectedNotificationJson(t, "user2",
		expectStatusMessage(t, client2, protocol.SUCCESS_CONNECTED, "You are connected to the server."),
	)

	return service, client1, client2, func() {
		service.Stop()

		if client1 != nil {
			client1.Close()
		}
		if client2 != nil {
			client2.Close()
		}
	}
}

func expectStatusMessage(t *testing.T, client client.Client, name string, arg string) string {
	select {
	case notify := <-client.StatusMessages():
		assert.Equal(t, name, notify.Name)
		assert.Equal(t, arg, notify.Arg)
		return notify.Json
	case <-time.After(time.Second * 10):
		t.Logf("no notification of type %s after 2 second", name)
		t.Fail()
		return ""
	}
}

func checkConnectedNotificationJson(t *testing.T, user string, connectedJson string) {
	m := make(map[string]string)
	err := json.Unmarshal([]byte(connectedJson), &m)
	assert.NoError(t, err)
	assert.Equal(t, user, m["UserId"])
	assert.True(t, len(m["ApplicationId"]) > 0)
	_, e := time.Parse(time.RFC3339, m["Time"])
	assert.NoError(t, e)
}
<commit_msg>Test without timeout<commit_after>package gubled

import (
	"github.com/smancke/guble/client"
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server"

	"github.com/stretchr/testify/assert"
	"testing"

	"encoding/json"
	"time"
)

func TestSimplePingPong(t *testing.T) {
	_, client1, client2, tearDown := initServerAndClients(t)
	defer tearDown()

	client1.Subscribe("/foo")
	expectStatusMessage(t, client1, protocol.SUCCESS_SUBSCRIBED_TO, "/foo")

	client2.Send("/foo 42", "Hello", `{"key": "value"}`)
	expectStatusMessage(t, client2, protocol.SUCCESS_SEND, "42")
	// time.Sleep(time.Millisecond * 10)

	// select {
	// case msg := <-client1.Messages():
	// 	assert.Equal(t, "Hello", msg.BodyAsString())
	// 	assert.Equal(t, "user2", msg.UserID)
	// 	assert.Equal(t, `{"key": "value"}`, msg.HeaderJSON)
	// 	assert.Equal(t, uint64(1), msg.ID)
	// case msg := <-client1.Errors():
	// 	t.Logf("received error: %v", msg)
	// 	t.FailNow()
	// case <-time.After(time.Millisecond * 100):
	// 	t.Log("no message received")
	// 	t.FailNow()
	// }
}

func initServerAndClients(t *testing.T) (*server.Service, client.Client, client.Client, func()) {
	service := StartupService(Args{Listen: "localhost:0", KVBackend: "memory"})

	time.Sleep(time.Millisecond * 100)

	var err error
	client1, err := client.Open("ws://"+service.GetWebServer().GetAddr()+"/stream/user/user1", "http://localhost", 1, false)
	assert.NoError(t, err)

	checkConnectedNotificationJSON(t, "user1",
		expectStatusMessage(t, client1, protocol.SUCCESS_CONNECTED, "You are connected to the server."),
	)

	client2, err := client.Open("ws://"+service.GetWebServer().GetAddr()+"/stream/user/user2", "http://localhost", 1, false)
	assert.NoError(t, err)
	checkConnectedNotificationJSON(t, "user2",
		expectStatusMessage(t, client2, protocol.SUCCESS_CONNECTED, "You are connected to the server."),
	)

	return service, client1, client2, func() {
		service.Stop()

		if client1 != nil {
			client1.Close()
		}
		if client2 != nil {
			client2.Close()
		}
	}
}

func expectStatusMessage(t *testing.T, client client.Client, name string, arg string) string {
	select {
	case notify := <-client.StatusMessages():
		assert.Equal(t, name, notify.Name)
		assert.Equal(t, arg, notify.Arg)
		return notify.Json
	case <-time.After(time.Second * 10):
		t.Logf("no notification of type %s after 2 second", name)
		t.Fail()
		return ""
	}
}

func checkConnectedNotificationJSON(t *testing.T, user string, connectedJSON string) {
	m := make(map[string]string)
	err := json.Unmarshal([]byte(connectedJSON), &m)
	assert.NoError(t, err)
	assert.Equal(t, user, m["UserId"])
	assert.True(t, len(m["ApplicationId"]) > 0)
	_, e := time.Parse(time.RFC3339, m["Time"])
	assert.NoError(t, e)
}
<|endoftext|>