<commit_before>package p2p

import (
	"bytes"
	"fmt"
	"math/rand"
	"reflect"
	"time"

	. "github.com/tendermint/go-common"
	wire "github.com/tendermint/go-wire"
)

const (
	PexChannel               = byte(0x00)
	ensurePeersPeriodSeconds = 30
	minNumOutboundPeers      = 10
	maxPexMessageSize        = 1048576 // 1MB
)

/*
PEXReactor handles PEX (peer exchange) and ensures that an
adequate number of peers are connected to the switch.
*/
type PEXReactor struct {
	BaseReactor

	sw   *Switch
	book *AddrBook
}

func NewPEXReactor(book *AddrBook) *PEXReactor {
	pexR := &PEXReactor{
		book: book,
	}
	pexR.BaseReactor = *NewBaseReactor(log, "PEXReactor", pexR)
	return pexR
}

func (pexR *PEXReactor) OnStart() error {
	pexR.BaseReactor.OnStart()
	pexR.book.Start()
	go pexR.ensurePeersRoutine()
	return nil
}

func (pexR *PEXReactor) OnStop() {
	pexR.BaseReactor.OnStop()
	pexR.book.Stop()
}

// Implements Reactor
func (pexR *PEXReactor) GetChannels() []*ChannelDescriptor {
	return []*ChannelDescriptor{
		&ChannelDescriptor{
			ID:                PexChannel,
			Priority:          1,
			SendQueueCapacity: 10,
		},
	}
}

// Implements Reactor
func (pexR *PEXReactor) AddPeer(peer *Peer) {
	// Add the peer to the address book
	netAddr, err := NewNetAddressString(peer.ListenAddr)
	if err != nil {
		// this should never happen
		log.Error("Error in AddPeer: invalid peer address", "addr", peer.ListenAddr, "error", err)
		return
	}

	if peer.IsOutbound() {
		if pexR.book.NeedMoreAddrs() {
			pexR.RequestPEX(peer)
		}
	} else {
		// For inbound connections, the peer is its own source
		// (For outbound peers, the address is already in the books)
		pexR.book.AddAddress(netAddr, netAddr)
	}
}

// Implements Reactor
func (pexR *PEXReactor) RemovePeer(peer *Peer, reason interface{}) {
	// TODO
}

// Implements Reactor
// Handles incoming PEX messages.
func (pexR *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) {

	// decode message
	_, msg, err := DecodeMessage(msgBytes)
	if err != nil {
		log.Warn("Error decoding message", "error", err)
		return
	}
	log.Notice("Received message", "msg", msg)

	switch msg := msg.(type) {
	case *pexRequestMessage:
		// src requested some peers.
		// TODO: prevent abuse.
		pexR.SendAddrs(src, pexR.book.GetSelection())
	case *pexAddrsMessage:
		// We received some peer addresses from src.
		// TODO: prevent abuse.
		// (We don't want to get spammed with bad peers)
		srcAddr := src.Connection().RemoteAddress
		for _, addr := range msg.Addrs {
			if addr != nil {
				pexR.book.AddAddress(addr, srcAddr)
			}
		}
	default:
		log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg)))
	}
}

// Asks peer for more addresses.
func (pexR *PEXReactor) RequestPEX(peer *Peer) {
	peer.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}})
}

func (pexR *PEXReactor) SendAddrs(peer *Peer, addrs []*NetAddress) {
	peer.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
}

// Ensures that sufficient peers are connected. (continuous)
func (pexR *PEXReactor) ensurePeersRoutine() {
	// Randomize when routine starts
	time.Sleep(time.Duration(rand.Int63n(500*ensurePeersPeriodSeconds)) * time.Millisecond)

	// fire once immediately.
	pexR.ensurePeers()
	// fire periodically
	timer := NewRepeatTimer("pex", ensurePeersPeriodSeconds*time.Second)
FOR_LOOP:
	for {
		select {
		case <-timer.Ch:
			pexR.ensurePeers()
		case <-pexR.Quit:
			break FOR_LOOP
		}
	}

	// Cleanup
	timer.Stop()
}

// Ensures that sufficient peers are connected. (once)
func (pexR *PEXReactor) ensurePeers() {
	numOutPeers, _, numDialing := pexR.Switch.NumPeers()
	numToDial := minNumOutboundPeers - (numOutPeers + numDialing)
	log.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial)
	if numToDial <= 0 {
		return
	}
	toDial := NewCMap()

	// Try to pick numToDial addresses to dial.
	// TODO: improve logic.
	for i := 0; i < numToDial; i++ {
		newBias := MinInt(numOutPeers, 8)*10 + 10
		var picked *NetAddress
		// Try to fetch a new peer 3 times.
		// This caps the maximum number of tries to 3 * numToDial.
		for j := 0; j < 3; j++ {
			try := pexR.book.PickAddress(newBias)
			if try == nil {
				break
			}
			alreadySelected := toDial.Has(try.IP.String())
			alreadyDialing := pexR.Switch.IsDialing(try)
			alreadyConnected := pexR.Switch.Peers().Has(try.IP.String())
			if alreadySelected || alreadyDialing || alreadyConnected {
				/*
					log.Info("Cannot dial address", "addr", try,
						"alreadySelected", alreadySelected,
						"alreadyDialing", alreadyDialing,
						"alreadyConnected", alreadyConnected)
				*/
				continue
			} else {
				log.Info("Will dial address", "addr", try)
				picked = try
				break
			}
		}
		if picked == nil {
			continue
		}
		toDial.Set(picked.IP.String(), picked)
	}

	// Dial picked addresses
	for _, item := range toDial.Values() {
		go func(picked *NetAddress) {
			_, err := pexR.Switch.DialPeerWithAddress(picked, false)
			if err != nil {
				pexR.book.MarkAttempt(picked)
			}
		}(item.(*NetAddress))
	}

	// If we need more addresses, pick a random peer and ask for more.
	if pexR.book.NeedMoreAddrs() {
		if peers := pexR.Switch.Peers().List(); len(peers) > 0 {
			i := rand.Int() % len(peers)
			peer := peers[i]
			log.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer)
			pexR.RequestPEX(peer)
		}
	}
}

//-----------------------------------------------------------------------------
// Messages

const (
	msgTypeRequest = byte(0x01)
	msgTypeAddrs   = byte(0x02)
)

type PexMessage interface{}

var _ = wire.RegisterInterface(
	struct{ PexMessage }{},
	wire.ConcreteType{&pexRequestMessage{}, msgTypeRequest},
	wire.ConcreteType{&pexAddrsMessage{}, msgTypeAddrs},
)

func DecodeMessage(bz []byte) (msgType byte, msg PexMessage, err error) {
	msgType = bz[0]
	n := new(int)
	r := bytes.NewReader(bz)
	msg = wire.ReadBinary(struct{ PexMessage }{}, r, maxPexMessageSize, n, &err).(struct{ PexMessage }).PexMessage
	return
}

/*
A pexRequestMessage requests additional peer addresses.
*/
type pexRequestMessage struct {
}

func (m *pexRequestMessage) String() string {
	return "[pexRequest]"
}

/*
A message with announced peer addresses.
*/
type pexAddrsMessage struct {
	Addrs []*NetAddress
}

func (m *pexAddrsMessage) String() string {
	return fmt.Sprintf("[pexAddrs %v]", m.Addrs)
}
<commit_msg>prefer short names<commit_after>package p2p

import (
	"bytes"
	"fmt"
	"math/rand"
	"reflect"
	"time"

	. "github.com/tendermint/go-common"
	wire "github.com/tendermint/go-wire"
)

const (
	PexChannel               = byte(0x00)
	ensurePeersPeriodSeconds = 30
	minNumOutboundPeers      = 10
	maxPexMessageSize        = 1048576 // 1MB
)

// PEXReactor handles PEX (peer exchange) and ensures that an
// adequate number of peers are connected to the switch.
type PEXReactor struct {
	BaseReactor

	sw   *Switch
	book *AddrBook
}

func NewPEXReactor(b *AddrBook) *PEXReactor {
	r := &PEXReactor{
		book: b,
	}
	r.BaseReactor = *NewBaseReactor(log, "PEXReactor", r)
	return r
}

func (r *PEXReactor) OnStart() error {
	r.BaseReactor.OnStart()
	r.book.Start()
	go r.ensurePeersRoutine()
	return nil
}

func (r *PEXReactor) OnStop() {
	r.BaseReactor.OnStop()
	r.book.Stop()
}

// GetChannels implements Reactor
func (r *PEXReactor) GetChannels() []*ChannelDescriptor {
	return []*ChannelDescriptor{
		&ChannelDescriptor{
			ID:                PexChannel,
			Priority:          1,
			SendQueueCapacity: 10,
		},
	}
}

// AddPeer implements Reactor by adding peer to the address book (if inbound)
// or by requesting more addresses (if outbound).
func (r *PEXReactor) AddPeer(p *Peer) {
	netAddr, err := NewNetAddressString(p.ListenAddr)
	if err != nil {
		// this should never happen
		log.Error("Error in AddPeer: invalid peer address", "addr", p.ListenAddr, "error", err)
		return
	}

	if p.IsOutbound() { // For outbound peers, the address is already in the books
		if r.book.NeedMoreAddrs() {
			r.RequestPEX(p)
		}
	} else { // For inbound connections, the peer is its own source
		r.book.AddAddress(netAddr, netAddr)
	}
}

// RemovePeer implements Reactor
func (r *PEXReactor) RemovePeer(p *Peer, reason interface{}) {
	// TODO
}

// Receive implements Reactor by handling incoming PEX messages.
func (r *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) {
	_, msg, err := DecodeMessage(msgBytes)
	if err != nil {
		log.Warn("Error decoding message", "error", err)
		return
	}

	log.Notice("Received message", "msg", msg)

	switch msg := msg.(type) {
	case *pexRequestMessage:
		// src requested some peers.
		// TODO: prevent abuse.
		r.SendAddrs(src, r.book.GetSelection())
	case *pexAddrsMessage:
		// We received some peer addresses from src.
		// TODO: prevent abuse.
		// (We don't want to get spammed with bad peers)
		srcAddr := src.Connection().RemoteAddress
		for _, addr := range msg.Addrs {
			if addr != nil {
				r.book.AddAddress(addr, srcAddr)
			}
		}
	default:
		log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg)))
	}
}

// RequestPEX asks peer for more addresses.
func (r *PEXReactor) RequestPEX(p *Peer) {
	p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}})
}

// SendAddrs sends addrs to the peer.
func (r *PEXReactor) SendAddrs(p *Peer, addrs []*NetAddress) {
	p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
}

// Ensures that sufficient peers are connected. (continuous)
func (r *PEXReactor) ensurePeersRoutine() {
	// Randomize when routine starts
	time.Sleep(time.Duration(rand.Int63n(500*ensurePeersPeriodSeconds)) * time.Millisecond)

	// fire once immediately.
	r.ensurePeers()

	// fire periodically
	timer := NewRepeatTimer("pex", ensurePeersPeriodSeconds*time.Second)
FOR_LOOP:
	for {
		select {
		case <-timer.Ch:
			r.ensurePeers()
		case <-r.Quit:
			break FOR_LOOP
		}
	}

	// Cleanup
	timer.Stop()
}

// Ensures that sufficient peers are connected. (once)
func (r *PEXReactor) ensurePeers() {
	numOutPeers, _, numDialing := r.Switch.NumPeers()
	numToDial := minNumOutboundPeers - (numOutPeers + numDialing)
	log.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial)
	if numToDial <= 0 {
		return
	}
	toDial := NewCMap()

	// Try to pick numToDial addresses to dial.
	// TODO: improve logic.
	for i := 0; i < numToDial; i++ {
		newBias := MinInt(numOutPeers, 8)*10 + 10
		var picked *NetAddress
		// Try to fetch a new peer 3 times.
		// This caps the maximum number of tries to 3 * numToDial.
		for j := 0; j < 3; j++ {
			try := r.book.PickAddress(newBias)
			if try == nil {
				break
			}
			alreadySelected := toDial.Has(try.IP.String())
			alreadyDialing := r.Switch.IsDialing(try)
			alreadyConnected := r.Switch.Peers().Has(try.IP.String())
			if alreadySelected || alreadyDialing || alreadyConnected {
				/*
					log.Info("Cannot dial address", "addr", try,
						"alreadySelected", alreadySelected,
						"alreadyDialing", alreadyDialing,
						"alreadyConnected", alreadyConnected)
				*/
				continue
			} else {
				log.Info("Will dial address", "addr", try)
				picked = try
				break
			}
		}
		if picked == nil {
			continue
		}
		toDial.Set(picked.IP.String(), picked)
	}

	// Dial picked addresses
	for _, item := range toDial.Values() {
		go func(picked *NetAddress) {
			_, err := r.Switch.DialPeerWithAddress(picked, false)
			if err != nil {
				r.book.MarkAttempt(picked)
			}
		}(item.(*NetAddress))
	}

	// If we need more addresses, pick a random peer and ask for more.
	if r.book.NeedMoreAddrs() {
		if peers := r.Switch.Peers().List(); len(peers) > 0 {
			i := rand.Int() % len(peers)
			peer := peers[i]
			log.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer)
			r.RequestPEX(peer)
		}
	}
}

//-----------------------------------------------------------------------------
// Messages

const (
	msgTypeRequest = byte(0x01)
	msgTypeAddrs   = byte(0x02)
)

type PexMessage interface{}

var _ = wire.RegisterInterface(
	struct{ PexMessage }{},
	wire.ConcreteType{&pexRequestMessage{}, msgTypeRequest},
	wire.ConcreteType{&pexAddrsMessage{}, msgTypeAddrs},
)

func DecodeMessage(bz []byte) (msgType byte, msg PexMessage, err error) {
	msgType = bz[0]
	n := new(int)
	r := bytes.NewReader(bz)
	msg = wire.ReadBinary(struct{ PexMessage }{}, r, maxPexMessageSize, n, &err).(struct{ PexMessage }).PexMessage
	return
}

/*
A pexRequestMessage requests additional peer addresses.
*/
type pexRequestMessage struct {
}

func (m *pexRequestMessage) String() string {
	return "[pexRequest]"
}

/*
A message with announced peer addresses.
*/
type pexAddrsMessage struct {
	Addrs []*NetAddress
}

func (m *pexAddrsMessage) String() string {
	return fmt.Sprintf("[pexAddrs %v]", m.Addrs)
}
<|endoftext|>


<commit_before><commit_msg>fix ansible-semaphore/semaphore#682<commit_after><|endoftext|>


<commit_before>package main

import (
	"bytes"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"
)

var (
	svc Service
	ctx context.Context
)

func init() {
	listenPort := ":8080"
	resetDB := false

	// Create the logger with the specified listen port.
	logger := createLogger(&listenPort)
	// Create the database connection.
	db := createDatabase()
	//
	s := createStore(resetDB, db)
	//
	es := createEventStore()

	// Create the main service with what it needs.

	// Create the main service with what it needs.
	svcLocal, ctxLocal, muxLocal := createService(logger, es, s)
	svc, ctx = svcLocal, ctxLocal

	registerService(logger)
	time.Sleep(time.Second * 1)
	http.Handle("/", muxLocal)
	go http.ListenAndServe(listenPort, nil)
}

func testFunction(method string, pattern string, body string) (string, error) {

	b := bytes.NewReader([]byte(body))
	req, err := http.NewRequest(method, os.Getenv("KITSVC_URL")+pattern, b)
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	buf := new(bytes.Buffer)
	if _, err := buf.ReadFrom(resp.Body); err != nil {
		panic(err)
	}
	s := strings.TrimSpace(buf.String())

	return s, err
}

func TestES(t *testing.T) {
	es := createEventStore()
	go setEventSubscription(es, []eventListener{
		{
			event:   "xxxxxxxxxxx",
			body:    make(map[string]interface{}),
			meta:    make(map[string]string),
			handler: svc.CatchEvent,
		}})
}

func TestServiceDiscoveryMessage(t *testing.T) {
	{
		body := ``
		expected := `{"status":"success","code":"success","message":"","payload":{"pong":"pong"}}`
		resp, _ := testFunction("GET", "/sd_health", body)

		assert.Equal(t, expected, resp, "Cannot ping to the service discovery health check function.")
	}
}

func TestDatabase(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Database didn't panic when the host is incorrect.")
		}
	}()

	db := createDatabase()
	createStore(true, db)

	os.Setenv("KITSVC_DATABASE_HOST", "xxxxxx")
	createDatabase()

}
<commit_msg>Added comments, cleaned up for main_test<commit_after>package main

import (
	"bytes"
	"math/rand"
	"net/http"
	"os"
	"strconv"
	"strings"
	"testing"
	"time"

	kitlog "github.com/go-kit/kit/log"
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"
)

var (
	testSvc    Service
	testCtx    context.Context
	testLogger kitlog.Logger
)

func init() {
	listenPort := ":8080"

	// Create the logger with the specified listen port.
	testLogger := createLogger(&listenPort)
	// Create the database connection.
	db := createDatabase()
	// Create the store with the database connection.
	s := createStore(false, db)
	// Create the event store.
	es := createEventStore()

	// Create the main service with what it needs.
	svcLocal, ctxLocal, muxLocal := createService(testLogger, es, s)
	testSvc, testCtx = svcLocal, ctxLocal

	// Sleep a little bit till we registered to the sd.
	time.Sleep(time.Second * 1)
	// Start the service and listening to the requests and let the mux router handles every things.
	go http.ListenAndServe(listenPort, muxLocal)
}

// testFunction sends the JSON request, and received the JSON response.
func testFunction(method string, pattern string, body string) (string, error) {

	// Converts the byte array to json
	b := bytes.NewReader([]byte(body))
	req, err := http.NewRequest(method, os.Getenv("KITSVC_URL")+pattern, b)
	req.Header.Set("Content-Type", "application/json")

	// Send the request.
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	// Converts the JSON response to string.
	buf := new(bytes.Buffer)
	if _, err := buf.ReadFrom(resp.Body); err != nil {
		return "", err
	}
	// Removes the newline symbol in the end of the string.
	s := strings.TrimSpace(buf.String())

	return s, err
}

// TestES tests the event store.
func TestES(t *testing.T) {
	es := createEventStore()
	randomEvent := rand.New(rand.NewSource(time.Now().UnixNano()))

	go setEventSubscription(es, testLogger, []eventListener{
		{
			event:   strconv.Itoa(randomEvent.Intn(999999)),
			body:    make(H),
			meta:    make(H),
			handler: testSvc.CatchUppercase,
		}})
}

// TestServiceDiscoveryMessage tests the health check handler for service discovery server.
func TestServiceDiscoveryMessage(t *testing.T) {
	{
		body := ``
		expected := `{"status":"success","code":"success","message":"","payload":{"pong":"pong"}}`
		resp, _ := testFunction("GET", "/sd_health", body)

		assert.Equal(t, expected, resp, "Cannot ping to the service discovery health check function.")
	}
}

// TestDatabase tests the database connection.
func TestDatabase(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Database didn't panic when the host is incorrect.")
		}
	}()

	// Test the database migration.
	db := createDatabase()
	createStore(true, db)

	// Test the incorrect host.
	os.Setenv("KITSVC_DATABASE_HOST", "xxxxxx")
	createDatabase()
}
<|endoftext|>


<commit_before>// Package pixelgl provides some abstractions around the basic OpenGL primitives and
// operations.
//
// All calls should be wrapped inside pixelgl.Do/DoNoBlock/DoErr/DoVal.
//
// This package deliberately does not handle nor report OpenGL errors, it's up to you to
// cause none.
package pixelgl
<commit_msg>update doc in pixelgl<commit_after>// Package pixelgl provides some abstractions around the basic OpenGL primitives and
// operations.
//
// All calls should be done from the main thread using "github.com/faiface/mainthread" package.
//
// This package deliberately does not handle nor report OpenGL errors, it's up to you to
// cause none.
package pixelgl
<|endoftext|>


<commit_before>package api

import (
	"github.com/Unknwon/macaron"
	"github.com/macaron-contrib/binding"
	"github.com/torkelo/grafana-pro/pkg/api/dtos"
	"github.com/torkelo/grafana-pro/pkg/middleware"
	m "github.com/torkelo/grafana-pro/pkg/models"
	"github.com/torkelo/grafana-pro/pkg/setting"
)

// Register adds http routes
func Register(r *macaron.Macaron) {
	reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
	reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
	reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_OWNER)
	bind := binding.Bind

	// not logged in views
	r.Get("/", reqSignedIn, Index)
	r.Post("/logout", LogoutPost)
	r.Post("/login", LoginPost)
	r.Get("/login/:name", OAuthLogin)
	r.Get("/login", Index)

	// authed views
	r.Get("/account/", reqSignedIn, Index)
	r.Get("/account/datasources/", reqSignedIn, Index)
	r.Get("/account/collaborators/", reqSignedIn, Index)
	r.Get("/account/apikeys/", reqSignedIn, Index)
	r.Get("/admin", reqSignedIn, Index)
	r.Get("/dashboard/*", reqSignedIn, Index)

	// sign up
	r.Get("/signup", Index)
	r.Post("/api/account/signup", SignUp)

	// authed api
	r.Group("/api", func() {
		// account
		r.Group("/account", func() {
			r.Get("/", GetAccount)
			r.Post("/", bind(m.UpdateAccountCommand{}), UpdateAccount)
			r.Put("/collaborators", bind(m.AddCollaboratorCommand{}), AddCollaborator)
			r.Get("/collaborators", GetCollaborators)
			r.Delete("/collaborators/:id", RemoveCollaborator)
			r.Post("/using/:id", SetUsingAccount)
			r.Get("/others", GetOtherAccounts)
		})
		// Token
		r.Group("/tokens", func() {
			r.Combo("/").
				Get(GetTokens).
				Put(bind(m.AddTokenCommand{}), AddToken).
				Post(bind(m.UpdateTokenCommand{}), UpdateToken)
			r.Delete("/:id", DeleteToken)
		})
		// Data sources
		r.Group("/datasources", func() {
			r.Combo("/").Get(GetDataSources).Put(AddDataSource).Post(UpdateDataSource)
			r.Delete("/:id", DeleteDataSource)
			r.Any("/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
		})
		// Dashboard
		r.Group("/dashboard", func() {
			r.Combo("/:slug").Get(GetDashboard).Delete(DeleteDashboard)
			r.Post("/", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
		})
		// Search
		r.Get("/search/", Search)
		// metrics
		r.Get("/metrics/test", GetTestMetrics)
	}, reqSignedIn)

	// admin api
	r.Group("/api/admin", func() {
		r.Get("/accounts", AdminSearchAccounts)
	}, reqGrafanaAdmin)

	// rendering
	r.Get("/render/*", reqSignedIn, RenderToPng)

	r.NotFound(NotFound)
}

func setIndexViewData(c *middleware.Context) error {
	settings, err := getFrontendSettings(c)
	if err != nil {
		return err
	}

	currentUser := &dtos.CurrentUser{}

	if c.IsSignedIn {
		currentUser = &dtos.CurrentUser{
			Login:            c.UserLogin,
			Email:            c.UserEmail,
			Name:             c.UserName,
			UsingAccountName: c.UsingAccountName,
			GravatarUrl:      dtos.GetGravatarUrl(c.UserEmail),
			IsGrafanaAdmin:   c.IsGrafanaAdmin,
			Role:             c.UserRole,
		}
	}

	c.Data["User"] = currentUser
	c.Data["Settings"] = settings
	c.Data["AppUrl"] = setting.AppUrl
	c.Data["AppSubUrl"] = setting.AppSubUrl

	return nil
}

func Index(c *middleware.Context) {
	if err := setIndexViewData(c); err != nil {
		c.Handle(500, "Failed to get settings", err)
		return
	}

	c.HTML(200, "index")
}

func NotFound(c *middleware.Context) {
	if c.IsApiRequest() {
		c.JsonApiErr(200, "Not found", nil)
		return
	}

	if err := setIndexViewData(c); err != nil {
		c.Handle(500, "Failed to get settings", err)
		return
	}

	c.HTML(404, "index")
}
<commit_msg>Api import route updated<commit_after>package api

import (
	"github.com/Unknwon/macaron"
	"github.com/macaron-contrib/binding"
	"github.com/torkelo/grafana-pro/pkg/api/dtos"
	"github.com/torkelo/grafana-pro/pkg/middleware"
	m "github.com/torkelo/grafana-pro/pkg/models"
	"github.com/torkelo/grafana-pro/pkg/setting"
)

// Register adds http routes
func Register(r *macaron.Macaron) {
	reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
	reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
	reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_OWNER)
	bind := binding.Bind

	// not logged in views
	r.Get("/", reqSignedIn, Index)
	r.Post("/logout", LogoutPost)
	r.Post("/login", LoginPost)
	r.Get("/login/:name", OAuthLogin)
	r.Get("/login", Index)

	// authed views
	r.Get("/account/", reqSignedIn, Index)
	r.Get("/account/datasources/", reqSignedIn, Index)
	r.Get("/account/collaborators/", reqSignedIn, Index)
	r.Get("/account/apikeys/", reqSignedIn, Index)
	r.Get("/account/import/", reqSignedIn, Index)
	r.Get("/admin", reqSignedIn, Index)
	r.Get("/dashboard/*", reqSignedIn, Index)

	// sign up
	r.Get("/signup", Index)
	r.Post("/api/account/signup", SignUp)

	// authed api
	r.Group("/api", func() {
		// account
		r.Group("/account", func() {
			r.Get("/", GetAccount)
			r.Post("/", bind(m.UpdateAccountCommand{}), UpdateAccount)
			r.Put("/collaborators", bind(m.AddCollaboratorCommand{}), AddCollaborator)
			r.Get("/collaborators", GetCollaborators)
			r.Delete("/collaborators/:id", RemoveCollaborator)
			r.Post("/using/:id", SetUsingAccount)
			r.Get("/others", GetOtherAccounts)
		})
		// Token
		r.Group("/tokens", func() {
			r.Combo("/").
				Get(GetTokens).
				Put(bind(m.AddTokenCommand{}), AddToken).
				Post(bind(m.UpdateTokenCommand{}), UpdateToken)
			r.Delete("/:id", DeleteToken)
		})
		// Data sources
		r.Group("/datasources", func() {
			r.Combo("/").Get(GetDataSources).Put(AddDataSource).Post(UpdateDataSource)
			r.Delete("/:id", DeleteDataSource)
			r.Any("/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
		})
		// Dashboard
		r.Group("/dashboard", func() {
			r.Combo("/:slug").Get(GetDashboard).Delete(DeleteDashboard)
			r.Post("/", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
		})
		// Search
		r.Get("/search/", Search)
		// metrics
		r.Get("/metrics/test", GetTestMetrics)
	}, reqSignedIn)

	// admin api
	r.Group("/api/admin", func() {
		r.Get("/accounts", AdminSearchAccounts)
	}, reqGrafanaAdmin)

	// rendering
	r.Get("/render/*", reqSignedIn, RenderToPng)

	r.NotFound(NotFound)
}

func setIndexViewData(c *middleware.Context) error {
	settings, err := getFrontendSettings(c)
	if err != nil {
		return err
	}

	currentUser := &dtos.CurrentUser{}

	if c.IsSignedIn {
		currentUser = &dtos.CurrentUser{
			Login:            c.UserLogin,
			Email:            c.UserEmail,
			Name:             c.UserName,
			UsingAccountName: c.UsingAccountName,
			GravatarUrl:      dtos.GetGravatarUrl(c.UserEmail),
			IsGrafanaAdmin:   c.IsGrafanaAdmin,
			Role:             c.UserRole,
		}
	}

	c.Data["User"] = currentUser
	c.Data["Settings"] = settings
	c.Data["AppUrl"] = setting.AppUrl
	c.Data["AppSubUrl"] = setting.AppSubUrl

	return nil
}

func Index(c *middleware.Context) {
	if err := setIndexViewData(c); err != nil {
		c.Handle(500, "Failed to get settings", err)
		return
	}

	c.HTML(200, "index")
}

func NotFound(c *middleware.Context) {
	if c.IsApiRequest() {
		c.JsonApiErr(200, "Not found", nil)
		return
	}

	if err := setIndexViewData(c); err != nil {
		c.Handle(500, "Failed to get settings", err)
		return
	}

	c.HTML(404, "index")
}
<|endoftext|>


<commit_before>package api

import (
	"github.com/Unknwon/macaron"
	"github.com/grafana/grafana/pkg/api/dtos"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/macaron-contrib/binding"
)

// Register adds http routes
func Register(r *macaron.Macaron) {
	reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
	reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
	reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
	regOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
	bind := binding.Bind

	// not logged in views
	r.Get("/", reqSignedIn, Index)
	r.Get("/logout", Logout)
	r.Post("/login", bind(dtos.LoginCommand{}), wrap(LoginPost))
	r.Get("/login/:name", OAuthLogin)
	r.Get("/login", LoginView)
	r.Get("/invite/:code", Index)

	// authed views
	r.Get("/profile/", reqSignedIn, Index)
	r.Get("/org/", reqSignedIn, Index)
	r.Get("/org/new", reqSignedIn, Index)
	r.Get("/datasources/", reqSignedIn, Index)
	r.Get("/datasources/edit/*", reqSignedIn, Index)
	r.Get("/org/users/", reqSignedIn, Index)
	r.Get("/org/apikeys/", reqSignedIn, Index)
	r.Get("/dashboard/import/", reqSignedIn, Index)
	r.Get("/admin/settings", reqGrafanaAdmin, Index)
	r.Get("/admin/users", reqGrafanaAdmin, Index)
	r.Get("/admin/users/create", reqGrafanaAdmin, Index)
	r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
	r.Get("/admin/orgs", reqGrafanaAdmin, Index)
	r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
	r.Get("/dashboard/*", reqSignedIn, Index)

	// sign up
	r.Get("/signup", Index)
	r.Get("/api/user/signup/options", wrap(GetSignUpOptions))
	r.Post("/api/user/signup", bind(dtos.SignUpForm{}), wrap(SignUp))
	r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))

	// invited
	r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))
	r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite))

	// reset password
	r.Get("/user/password/send-reset-email", Index)
	r.Get("/user/password/reset", Index)

	r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
	r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))

	// dashboard snapshots
	r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
	r.Get("/dashboard/snapshot/*", Index)

	r.Get("/api/snapshots/:key", GetDashboardSnapshot)
	r.Get("/api/snapshots-delete/:key", DeleteDashboardSnapshot)

	// api renew session based on remember cookie
	r.Get("/api/login/ping", LoginApiPing)

	// authed api
	r.Group("/api", func() {

		// user (signed in)
		r.Group("/user", func() {
			r.Get("/", wrap(GetSignedInUser))
			r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
			r.Post("/using/:id", wrap(UserSetUsingOrg))
			r.Get("/orgs", wrap(GetSignedInUserOrgList))
			r.Post("/stars/dashboard/:id", wrap(StarDashboard))
			r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
			r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
		})

		// users (admin permission required)
		r.Group("/users", func() {
			r.Get("/", wrap(SearchUsers))
			r.Get("/:id", wrap(GetUserById))
			r.Get("/:id/orgs", wrap(GetUserOrgList))
			r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
		}, reqGrafanaAdmin)

		// current org
		r.Group("/org", func() {
			r.Get("/", wrap(GetOrgCurrent))
			r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
			r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
			r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
			r.Get("/users", wrap(GetOrgUsersForCurrentOrg))
			r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
			r.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))

			// invites
			r.Get("/invites", wrap(GetPendingOrgInvites))
			r.Post("/invites", bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
			r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
		}, regOrgAdmin)

		// create new org
		r.Post("/orgs", bind(m.CreateOrgCommand{}), wrap(CreateOrg))

		// search all orgs
		r.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs))

		// orgs (admin routes)
		r.Group("/orgs/:orgId", func() {
			r.Get("/", wrap(GetOrgById))
			r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
			r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
			r.Delete("/", wrap(DeleteOrgById))
			r.Get("/users", wrap(GetOrgUsers))
			r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
			r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
			r.Delete("/users/:userId", wrap(RemoveOrgUser))
		}, reqGrafanaAdmin)

		// auth api keys
		r.Group("/auth/keys", func() {
			r.Get("/", wrap(GetApiKeys))
			r.Post("/", bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
			r.Delete("/:id", wrap(DeleteApiKey))
		}, regOrgAdmin)

		// Data sources
		r.Group("/datasources", func() {
			r.Get("/", GetDataSources)
			r.Post("/", bind(m.AddDataSourceCommand{}), AddDataSource)
			r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
			r.Delete("/:id", DeleteDataSource)
			r.Get("/:id", GetDataSourceById)
			r.Get("/plugins", GetDataSourcePlugins)
		}, regOrgAdmin)

		r.Get("/frontend/settings/", GetFrontendSettings)
		r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
		r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest)

		// Dashboard
		r.Group("/dashboards", func() {
			r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
			r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
			r.Get("/file/:file", GetDashboardFromJsonFile)
			r.Get("/home", GetHomeDashboard)
			r.Get("/tags", GetDashboardTags)
		})

		// Search
		r.Get("/search/", Search)

		// metrics
		r.Get("/metrics/test", GetTestMetrics)
	}, reqSignedIn)

	// admin api
	r.Group("/api/admin", func() {
		r.Get("/settings", AdminGetSettings)
		r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
		r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
		r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
		r.Delete("/users/:id", AdminDeleteUser)
	}, reqGrafanaAdmin)

	// rendering
	r.Get("/render/*", reqSignedIn, RenderToPng)

	r.NotFound(NotFoundHandler)
}
<commit_msg>fix(gofmt): somehow api.go did not pass gofmt test<commit_after>package api

import (
	"github.com/Unknwon/macaron"
	"github.com/grafana/grafana/pkg/api/dtos"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/macaron-contrib/binding"
)

// Register adds http routes
func Register(r *macaron.Macaron) {
	reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
	reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
	reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
	regOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
	bind := binding.Bind

	// not logged in views
	r.Get("/", reqSignedIn, Index)
	r.Get("/logout", Logout)
	r.Post("/login", bind(dtos.LoginCommand{}), wrap(LoginPost))
	r.Get("/login/:name", OAuthLogin)
	r.Get("/login", LoginView)
	r.Get("/invite/:code", Index)

	// authed views
	r.Get("/profile/", reqSignedIn, Index)
	r.Get("/org/", reqSignedIn, Index)
	r.Get("/org/new", reqSignedIn, Index)
	r.Get("/datasources/", reqSignedIn, Index)
	r.Get("/datasources/edit/*", reqSignedIn, Index)
	r.Get("/org/users/", reqSignedIn, Index)
	r.Get("/org/apikeys/", reqSignedIn, Index)
	r.Get("/dashboard/import/", reqSignedIn, Index)
	r.Get("/admin/settings", reqGrafanaAdmin, Index)
	r.Get("/admin/users", reqGrafanaAdmin, Index)
	r.Get("/admin/users/create", reqGrafanaAdmin, Index)
	r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
	r.Get("/admin/orgs", reqGrafanaAdmin, Index)
	r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
	r.Get("/dashboard/*", reqSignedIn, Index)

	// sign up
	r.Get("/signup", Index)
	r.Get("/api/user/signup/options", wrap(GetSignUpOptions))
	r.Post("/api/user/signup", bind(dtos.SignUpForm{}), wrap(SignUp))
	r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))

	// invited
	r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))
	r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite))

	// reset password
	r.Get("/user/password/send-reset-email", Index)
	r.Get("/user/password/reset", Index)

	r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
	r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))

	// dashboard snapshots
	r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
	r.Get("/dashboard/snapshot/*", Index)

	r.Get("/api/snapshots/:key", GetDashboardSnapshot)
	r.Get("/api/snapshots-delete/:key", DeleteDashboardSnapshot)

	// api renew session based on remember cookie
	r.Get("/api/login/ping", LoginApiPing)

	// authed api
	r.Group("/api", func() {

		// user (signed in)
		r.Group("/user", func() {
			r.Get("/", wrap(GetSignedInUser))
			r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
			r.Post("/using/:id", wrap(UserSetUsingOrg))
			r.Get("/orgs", wrap(GetSignedInUserOrgList))
			r.Post("/stars/dashboard/:id", wrap(StarDashboard))
			r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
			r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
		})

		// users (admin permission required)
		r.Group("/users", func() {
			r.Get("/", wrap(SearchUsers))
			r.Get("/:id", wrap(GetUserById))
			r.Get("/:id/orgs", wrap(GetUserOrgList))
			r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
		}, reqGrafanaAdmin)

		// current org
		r.Group("/org", func() {
			r.Get("/", wrap(GetOrgCurrent))
			r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
			r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
			r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
			r.Get("/users", wrap(GetOrgUsersForCurrentOrg))
			r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
			r.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))

			// invites
			r.Get("/invites", wrap(GetPendingOrgInvites))
			r.Post("/invites", bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
			r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
		}, regOrgAdmin)

		// create new org
		r.Post("/orgs", bind(m.CreateOrgCommand{}), wrap(CreateOrg))

		// search all orgs
		r.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs))

		// orgs (admin routes)
		r.Group("/orgs/:orgId", func() {
			r.Get("/", wrap(GetOrgById))
			r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
			r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
			r.Delete("/", wrap(DeleteOrgById))
			r.Get("/users", wrap(GetOrgUsers))
			r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
			r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
			r.Delete("/users/:userId", wrap(RemoveOrgUser))
		}, reqGrafanaAdmin)

		// auth api keys
		r.Group("/auth/keys", func() {
			r.Get("/", wrap(GetApiKeys))
			r.Post("/", bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
			r.Delete("/:id", wrap(DeleteApiKey))
		}, regOrgAdmin)

		// Data sources
		r.Group("/datasources", func() {
			r.Get("/", GetDataSources)
			r.Post("/", bind(m.AddDataSourceCommand{}), AddDataSource)
			r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
			r.Delete("/:id", DeleteDataSource)
			r.Get("/:id", GetDataSourceById)
			r.Get("/plugins", GetDataSourcePlugins)
		}, regOrgAdmin)

		r.Get("/frontend/settings/", GetFrontendSettings)
		r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
		r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest)

		// Dashboard
		r.Group("/dashboards", func() {
			r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
			r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
			r.Get("/file/:file", GetDashboardFromJsonFile)
			r.Get("/home", GetHomeDashboard)
			r.Get("/tags", GetDashboardTags)
		})

		// Search
		r.Get("/search/", Search)

		// metrics
		r.Get("/metrics/test", GetTestMetrics)
	}, reqSignedIn)

	// admin api
	r.Group("/api/admin", func() {
		r.Get("/settings", AdminGetSettings)
		r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
		r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
		r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
		r.Delete("/users/:id", AdminDeleteUser)
	}, reqGrafanaAdmin)

	// rendering
	r.Get("/render/*", reqSignedIn, RenderToPng)

	r.NotFound(NotFoundHandler)
}
<|endoftext|>


<commit_before>package indexer

import (
	"errors"
	"fmt"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"text/scanner"
	"time"
	"unicode"

	"github.com/Sirupsen/logrus"
	"github.com/bcampbell/fuzzytime"
	"github.com/cardigann/cardigann/logger"
)

const (
	filterTimeFormat = time.RFC1123Z
)

var (
	filterLogger = logger.Logger
)

func invokeFilter(name string, args interface{}, value string) (string, error) {
	switch name {
	case "querystring":
		param, ok := args.(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument", name)
		}
		return filterQueryString(param, value)

	case "timeparse", "dateparse":
		if args == nil {
			return filterDateParse(nil, value)
		}
		if layout, ok := args.(string); ok {
			return filterDateParse([]string{layout}, value)
		}
		return "", fmt.Errorf("Filter argument type %T was invalid", args)

	case "regexp":
		pattern, ok := args.(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument", name)
		}
		return filterRegexp(pattern, value)

	case "split":
		sep, ok := (args.([]interface{}))[0].(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 0", name)
		}
		pos, ok := (args.([]interface{}))[1].(int)
		if !ok {
			return "", fmt.Errorf("Filter %q requires an int argument at idx 1", name)
		}
		return filterSplit(sep, pos, value)

	case "replace":
		from, ok := (args.([]interface{}))[0].(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 0", name)
		}
		to, ok := (args.([]interface{}))[1].(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 1", name)
		}
		return strings.Replace(value, from, to, -1), nil

	case "trim":
		cutset, ok := args.(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 0", name)
		}
		return strings.Trim(value, cutset), nil

	case "timeago", "fuzzytime", "reltime":
		return filterFuzzyTime(value, time.Now())
	}

	return "", errors.New("Unknown filter " + name)
}

func filterQueryString(param string, value string) (string, error) {
	u, err := url.Parse(value)
	if err != nil {
		return "", err
	}
	return u.Query().Get(param), nil
}

func filterDateParse(layouts []string, value string) (string, error) {
	for _, layout := range layouts {
		if t, err := time.Parse(layout, value); err == nil {
			return t.Format(filterTimeFormat), nil
		}
	}
	return "", fmt.Errorf("No matching date pattern for %s", value)
}

func filterSplit(sep string, pos int, value string) (string, error) {
	frags := strings.Split(value, sep)
	if pos < 0 {
		pos = len(frags) + pos
	}
	return frags[pos], nil
}

func filterRegexp(pattern string, value string) (string, error) {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return "", err
	}

	matches := re.FindStringSubmatch(value)

	if len(matches) == 0 {
		return "", errors.New("No matches found for pattern")
	}

	filterLogger.WithFields(logrus.Fields{"matches": matches}).Debug("Regex matched")

	if len(matches) > 1 {
		return matches[1], nil
	}

	return matches[0], nil
}

func splitDecimalStr(s string) (int, float64, error) {
	if parts := strings.SplitN(s, ".", 2); len(parts) == 2 {
		i, err := strconv.Atoi(parts[0])
		if err != nil {
			return 0, 0, err
		}
		f, err := strconv.ParseFloat("0."+parts[1], 64)
		if err != nil {
			return 0, 0, err
		}
		return i, f, nil
	}
	i, err := strconv.Atoi(s)
	if err != nil {
		return 0, 0, err
	}
	return i, 0, nil
}

var (
	timeAgoRegexp     = regexp.MustCompile(`(?i)\bago`)
	todayRegexp       = regexp.MustCompile(`(?i)\btoday([\s,]+|$)`)
	tomorrowRegexp    = regexp.MustCompile(`(?i)\btomorrow([\s,]+|$)`)
	yesterdayRegexp   = regexp.MustCompile(`(?i)\byesterday([\s,]+|$)`)
	missingYearRegexp = regexp.MustCompile(`^\d{1,2}-\d{1,2}\b`)
)

func normalizeSpace(s string) string {
	return strings.TrimSpace(strings.Map(func(r rune) rune {
		if unicode.IsSpace(r) {
			return ' '
		}
		return r
	}, s))
}

func parseTimeAgo(src string, now time.Time) (time.Time, error) {
	normalized := normalizeSpace(src)
	normalized = strings.ToLower(normalized)

	var s scanner.Scanner
	s.Init(strings.NewReader(normalized))
	var tok rune
	for tok != scanner.EOF {
		tok = s.Scan()

		switch s.TokenText() {
		case ",", "ago", "", "and":
			continue
		}

		v, fraction, err := splitDecimalStr(s.TokenText())
		if err != nil {
			return now, fmt.Errorf(
				"failed to parse decimal time %q in time format at %s", s.TokenText(), s.Pos())
		}

		tok = s.Scan()
		if tok == scanner.EOF {
			return now, fmt.Errorf(
				"expected a time unit at %s", s.TokenText(), s.Pos())
		}

		switch strings.TrimSuffix(s.TokenText(), "s") {
		case "year", "yr", "y":
			now = now.AddDate(-v, 0, 0)
			if fraction > 0 {
				now = now.Add(time.Duration(float64(now.AddDate(-1, 0, 0).Sub(now)) * fraction))
			}
		case "month", "mnth":
			now = now.AddDate(0, -v, 0)
			if fraction > 0 {
				now = now.Add(time.Duration(float64(now.AddDate(0, -1, 0).Sub(now)) * fraction))
			}
		case "week", "wk", "w":
			now = now.AddDate(0, 0, -7)
			if fraction > 0 {
				now = now.Add(time.Duration(float64(now.AddDate(0, 0, -7).Sub(now)) * fraction))
			}
		case "day", "d":
			now = now.AddDate(0, 0, -v)
			if fraction > 0 {
				now = now.Add(time.Minute * -time.Duration(fraction*1440))
			}
		case "hour", "hr", "h":
			now = now.Add(time.Hour * -time.Duration(v))
			if fraction > 0 {
				now = now.Add(time.Second * -time.Duration(fraction*3600))
			}
		case "minute", "min", "m":
			now = now.Add(time.Minute * -time.Duration(v))
			if fraction > 0 {
				now = now.Add(time.Second * -time.Duration(fraction*60))
			}
		case "second", "sec", "s":
			now = now.Add(time.Second * -time.Duration(v))
		default:
			return now, fmt.Errorf("Unsupporting unit of time %q", s.TokenText())
		}
	}

	return now, nil
}

func parseFuzzyTime(src string, now time.Time) (time.Time, error) {
	if timeAgoRegexp.MatchString(src) {
		t, err := parseTimeAgo(src, now)
		if err != nil {
			return t, fmt.Errorf("error parsing time ago %q: %v", src, err)
		}
		return t, nil
	}

	out := todayRegexp.ReplaceAllLiteralString(src, now.Format("Mon, 02 Jan 2006 "))
	out = tomorrowRegexp.ReplaceAllLiteralString(out, now.AddDate(0, 0, 1).Format("Mon, 02 Jan 2006 "))
	out = yesterdayRegexp.ReplaceAllLiteralString(out, now.AddDate(0, 0, -1).Format("Mon, 02 Jan 2006 "))

	if m := missingYearRegexp.FindStringSubmatch(out); len(m) > 0 {
		out = missingYearRegexp.ReplaceAllLiteralString(src, m[0]+now.Format("-2006"))
	}

	dt, _, err := fuzzytime.USContext.Extract(out)
	if err != nil {
		return time.Time{}, fmt.Errorf("error extracting date from %q: %v", out, err)
	}

	if dt.Time.Empty() {
		dt.Time.SetHour(0)
		dt.Time.SetMinute(0)
	}

	if !dt.HasFullDate() {
		return time.Time{}, fmt.Errorf("found only partial date %v", dt.ISOFormat())
	}

	if !dt.Time.HasSecond() {
		dt.Time.SetSecond(0)
	}

	if !dt.HasTZOffset() {
		dt.Time.SetTZOffset(0)
	}

	return time.Parse("2006-01-02T15:04:05Z07:00", dt.ISOFormat())
}

func filterFuzzyTime(src string, now time.Time) (string, error) {
	t, err := parseFuzzyTime(src, now)
	if err != nil {
		return "", fmt.Errorf("error parsing fuzzy time %q: %v", src, err)
	}
	return t.Format(filterTimeFormat), nil
}
<commit_msg>Add an append filter<commit_after>package indexer

import (
	"errors"
	"fmt"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"text/scanner"
	"time"
	"unicode"

	"github.com/Sirupsen/logrus"
	"github.com/bcampbell/fuzzytime"
	"github.com/cardigann/cardigann/logger"
)

const (
	filterTimeFormat = time.RFC1123Z
)

var (
	filterLogger = logger.Logger
)

func invokeFilter(name string, args interface{}, value string) (string, error) {
	switch name {
	case "querystring":
		param, ok := args.(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument", name)
		}
		return filterQueryString(param, value)

	case "timeparse", "dateparse":
		if args == nil {
			return filterDateParse(nil, value)
		}
		if layout, ok := args.(string); ok {
			return filterDateParse([]string{layout}, value)
		}
		return "", fmt.Errorf("Filter argument type %T was invalid", args)

	case "regexp":
		pattern, ok := args.(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument", name)
		}
		return filterRegexp(pattern, value)

	case "split":
		sep, ok := (args.([]interface{}))[0].(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 0", name)
		}
		pos, ok := (args.([]interface{}))[1].(int)
		if !ok {
			return "", fmt.Errorf("Filter %q requires an int argument at idx 1", name)
		}
		return filterSplit(sep, pos, value)

	case "replace":
		from, ok := (args.([]interface{}))[0].(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 0", name)
		}
		to, ok := (args.([]interface{}))[1].(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 1", name)
		}
		return strings.Replace(value, from, to, -1), nil

	case "trim":
		cutset, ok := args.(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 0", name)
		}
		return strings.Trim(value, cutset), nil

	case "append":
		str, ok := args.(string)
		if !ok {
			return "", fmt.Errorf("Filter %q requires a string argument at idx 0", name)
		}
		return value + str, nil

	case "timeago", "fuzzytime", "reltime":
		return filterFuzzyTime(value, time.Now())
	}

	return "", errors.New("Unknown filter " + name)
}

func filterQueryString(param string, value string) (string, error) {
	u, err := url.Parse(value)
	if err != nil {
		return "", err
	}
	return u.Query().Get(param), nil
}

func filterDateParse(layouts []string, value string) (string, error) {
	for _, layout := range layouts {
		if t, err := time.Parse(layout, value); err == nil {
			return t.Format(filterTimeFormat), nil
		}
	}
	return "", fmt.Errorf("No matching date pattern for %s", value)
}

func filterSplit(sep string, pos int, value string) (string, error) {
	frags := strings.Split(value, sep)
	if pos < 0 {
		pos = len(frags) + pos
	}
	return frags[pos], nil
}

func filterRegexp(pattern string, value string) (string, error) {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return "", err
	}

	matches := re.FindStringSubmatch(value)

	if len(matches) == 0 {
		return "", errors.New("No matches found for pattern")
	}

	filterLogger.WithFields(logrus.Fields{"matches": matches}).Debug("Regex matched")

	if len(matches) > 1 {
		return matches[1], nil
	}

	return matches[0], nil
}

func splitDecimalStr(s string) (int, float64, error) {
	if parts := strings.SplitN(s, ".", 2); len(parts) == 2 {
		i, err := strconv.Atoi(parts[0])
		if err != nil {
			return 0, 0, err
		}
		f, err := strconv.ParseFloat("0."+parts[1], 64)
		if err != nil {
			return 0, 0, err
		}
		return i, f, nil
	}
	i, err := strconv.Atoi(s)
	if err != nil {
		return 0, 0, err
	}
	return i, 0, nil
}

var (
	timeAgoRegexp     = regexp.MustCompile(`(?i)\bago`)
	todayRegexp       = regexp.MustCompile(`(?i)\btoday([\s,]+|$)`)
	tomorrowRegexp    = regexp.MustCompile(`(?i)\btomorrow([\s,]+|$)`)
	yesterdayRegexp   = regexp.MustCompile(`(?i)\byesterday([\s,]+|$)`)
	missingYearRegexp = regexp.MustCompile(`^\d{1,2}-\d{1,2}\b`)
)

func normalizeSpace(s string) string {
	return strings.TrimSpace(strings.Map(func(r rune) rune {
		if unicode.IsSpace(r) {
			return ' '
		}
		return r
	}, s))
}

func parseTimeAgo(src string, now time.Time) (time.Time, error) {
	normalized := normalizeSpace(src)
	normalized = strings.ToLower(normalized)

	var s scanner.Scanner
	s.Init(strings.NewReader(normalized))
	var tok rune
	for tok != scanner.EOF {
		tok = s.Scan()

		switch s.TokenText() {
		case ",", "ago", "", "and":
			continue
		}

		v, fraction, err := splitDecimalStr(s.TokenText())
		if err != nil {
			return now, fmt.Errorf(
				"failed to parse decimal time %q in time format at %s", s.TokenText(), s.Pos())
		}

		tok = s.Scan()
		if tok == scanner.EOF {
			return now, fmt.Errorf(
				"expected a time unit at %s", s.TokenText(), s.Pos())
		}

		switch strings.TrimSuffix(s.TokenText(), "s") {
		case "year", "yr", "y":
			now = now.AddDate(-v, 0, 0)
			if fraction > 0 {
				now = now.Add(time.Duration(float64(now.AddDate(-1, 0, 0).Sub(now)) * fraction))
			}
		case "month", "mnth":
			now = now.AddDate(0, -v, 0)
			if fraction > 0 {
				now = now.Add(time.Duration(float64(now.AddDate(0, -1, 0).Sub(now)) * fraction))
			}
		case "week", "wk", "w":
			now = now.AddDate(0, 0, -7)
			if fraction > 0 {
				now = now.Add(time.Duration(float64(now.AddDate(0, 0, -7).Sub(now)) * fraction))
			}
		case "day", "d":
			now = now.AddDate(0, 0, -v)
			if fraction > 0 {
				now = now.Add(time.Minute * -time.Duration(fraction*1440))
			}
		case "hour", "hr", "h":
			now = now.Add(time.Hour * -time.Duration(v))
			if fraction > 0 {
				now = now.Add(time.Second * -time.Duration(fraction*3600))
			}
		case "minute", "min", "m":
			now = now.Add(time.Minute * -time.Duration(v))
			if fraction > 0 {
				now = now.Add(time.Second * -time.Duration(fraction*60))
			}
		case "second", "sec", "s":
			now = now.Add(time.Second * -time.Duration(v))
		default:
			return now, fmt.Errorf("Unsupporting unit of time %q", s.TokenText())
		}
	}

	return now, nil
}

func parseFuzzyTime(src string, now time.Time) (time.Time, error) {
	if timeAgoRegexp.MatchString(src) {
		t, err := parseTimeAgo(src, now)
		if err != nil {
			return t, fmt.Errorf("error parsing time ago %q: %v", src, err)
		}
		return t, nil
	}

	out := todayRegexp.ReplaceAllLiteralString(src, now.Format("Mon, 02 Jan 2006 "))
	out = tomorrowRegexp.ReplaceAllLiteralString(out, now.AddDate(0, 0, 1).Format("Mon, 02 Jan 2006 "))
	out = yesterdayRegexp.ReplaceAllLiteralString(out, now.AddDate(0, 0, -1).Format("Mon, 02 Jan 2006 "))

	if m := missingYearRegexp.FindStringSubmatch(out); len(m) > 0 {
		out = missingYearRegexp.ReplaceAllLiteralString(src, m[0]+now.Format("-2006"))
	}

	dt, _, err := fuzzytime.USContext.Extract(out)
	if err != nil {
		return time.Time{}, fmt.Errorf("error extracting date from %q: %v", out, err)
	}

	if dt.Time.Empty() {
		dt.Time.SetHour(0)
		dt.Time.SetMinute(0)
	}

	if !dt.HasFullDate() {
		return time.Time{}, fmt.Errorf("found only partial date %v", dt.ISOFormat())
	}

	if !dt.Time.HasSecond() {
		dt.Time.SetSecond(0)
	}

	if !dt.HasTZOffset() {
		dt.Time.SetTZOffset(0)
	}

	return time.Parse("2006-01-02T15:04:05Z07:00", dt.ISOFormat())
}

func filterFuzzyTime(src string, now time.Time) (string, error) {
	t, err := parseFuzzyTime(src, now)
	if err != nil {
		return "", fmt.Errorf("error parsing fuzzy time %q: %v", src, err)
	}
	return t.Format(filterTimeFormat), nil
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"
	"strings"

	"github.com/ViBiOh/auth/pkg/auth"
	"github.com/ViBiOh/dashboard/pkg/deploy"
	"github.com/ViBiOh/dashboard/pkg/docker"
	"github.com/ViBiOh/httputils/pkg/httperror"
)

const (
	healthPrefix     = `/health`
	containersPrefix = `/containers`
)

// App stores informations
type App struct {
	authApp   *auth.App
	dockerApp *docker.App
	deployApp *deploy.App
}

// NewApp creates new App from dependencies
func NewApp(authApp *auth.App, dockerApp *docker.App, deployApp *deploy.App) *App {
	return &App{
		authApp:   authApp,
		dockerApp: dockerApp,
		deployApp: deployApp,
	}
}

// Handler for Docker request. Should be use with net/http
func (a *App) Handler() http.Handler {
	containerHandler := http.StripPrefix(containersPrefix, a.dockerApp.Handler())
	deployHandler := http.StripPrefix(containersPrefix, a.deployApp.Handler())

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodOptions {
			if _, err := w.Write(nil); err != nil {
				httperror.InternalServerError(w, err)
			}
			return
		}

		if strings.HasPrefix(r.URL.Path, healthPrefix) && r.Method == http.MethodGet {
			a.healthHandler(w, r)
			return
		}

		if strings.HasPrefix(r.URL.Path, containersPrefix) {
			if r.Method == http.MethodPost {
				deployHandler.ServeHTTP(w, r)
			} else {
				containerHandler.ServeHTTP(w, r)
			}
		}

		httperror.NotFound(w)
	})
}

func (a *App) healthHandler(w http.ResponseWriter, r *http.Request) {
	if a.dockerApp == nil {
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	if !a.dockerApp.Healthcheck() {
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	w.WriteHeader(http.StatusOK)
}
<commit_msg>Fixing errors in e2e<commit_after>package api

import (
	"net/http"
	"strings"

	"github.com/ViBiOh/auth/pkg/auth"
	"github.com/ViBiOh/dashboard/pkg/deploy"
	"github.com/ViBiOh/dashboard/pkg/docker"
	"github.com/ViBiOh/httputils/pkg/httperror"
)

const (
	healthPrefix     = `/health`
	containersPrefix = `/containers`
)

// App stores informations
type App struct {
	authApp   *auth.App
	dockerApp *docker.App
	deployApp *deploy.App
}

// NewApp creates new App from dependencies
func NewApp(authApp *auth.App, dockerApp *docker.App, deployApp *deploy.App) *App {
	return &App{
		authApp:   authApp,
		dockerApp: dockerApp,
		deployApp: deployApp,
	}
}

// Handler for Docker request. Should be use with net/http
func (a *App) Handler() http.Handler {
	containerHandler := http.StripPrefix(containersPrefix, a.dockerApp.Handler())
	deployHandler := http.StripPrefix(containersPrefix, a.deployApp.Handler())

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodOptions {
			if _, err := w.Write(nil); err != nil {
				httperror.InternalServerError(w, err)
			}
			return
		}

		if strings.HasPrefix(r.URL.Path, healthPrefix) && r.Method == http.MethodGet {
			a.healthHandler(w, r)
			return
		}

		if strings.HasPrefix(r.URL.Path, containersPrefix) {
			if r.Method == http.MethodPost {
				deployHandler.ServeHTTP(w, r)
			} else {
				containerHandler.ServeHTTP(w, r)
			}

			return
		}

		httperror.NotFound(w)
	})
}

func (a *App) healthHandler(w http.ResponseWriter, r *http.Request) {
	if a.dockerApp == nil {
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	if !a.dockerApp.Healthcheck() {
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	w.WriteHeader(http.StatusOK)
}
<|endoftext|>


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

package github

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
)

func TestActionsService_ListWorkflowJobs(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/runs/29679449/jobs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"per_page": "2", "page": "2"})
		fmt.Fprint(w, `{"total_count":4,"jobs":[{"id":399444496,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"},{"id":399444497,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}]}`)
	})

	opts := &ListWorkflowJobsOptions{ListOptions: ListOptions{Page: 2, PerPage: 2}}
	ctx := context.Background()
	jobs, _, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
	if err != nil {
		t.Errorf("Actions.ListWorkflowJobs returned error: %v", err)
	}

	want := &Jobs{
		TotalCount: Int(4),
		Jobs: []*WorkflowJob{
			{ID: Int64(399444496), RunID: Int64(29679449), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
			{ID: Int64(399444497), RunID: Int64(29679449), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
		},
	}
	if !cmp.Equal(jobs, want) {
		t.Errorf("Actions.ListWorkflowJobs returned %+v, want %+v", jobs, want)
	}

	const methodName = "ListWorkflowJobs"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Actions.ListWorkflowJobs(ctx, "\n", "\n", 29679449, opts)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestActionsService_ListWorkflowJobs_Filter(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/runs/29679449/jobs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"filter": "all", "per_page": "2", "page": "2"})
		fmt.Fprint(w, `{"total_count":4,"jobs":[{"id":399444496,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"},{"id":399444497,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}]}`)
	})

	opts := &ListWorkflowJobsOptions{Filter: "all", ListOptions: ListOptions{Page: 2, PerPage: 2}}
	ctx := context.Background()
	jobs, _, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
	if err != nil {
		t.Errorf("Actions.ListWorkflowJobs returned error: %v", err)
	}

	want := &Jobs{
		TotalCount: Int(4),
		Jobs: []*WorkflowJob{
			{ID: Int64(399444496), RunID: Int64(29679449), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
			{ID: Int64(399444497), RunID: Int64(29679449), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
		},
	}
	if !cmp.Equal(jobs, want) {
		t.Errorf("Actions.ListWorkflowJobs returned %+v, want %+v", jobs, want)
	}
}

func TestActionsService_GetWorkflowJobByID(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/jobs/399444496", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"id":399444496,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}`)
	})

	ctx := context.Background()
	job, _, err := client.Actions.GetWorkflowJobByID(ctx, "o", "r", 399444496)
	if err != nil {
		t.Errorf("Actions.GetWorkflowJobByID returned error: %v", err)
	}

	want := &WorkflowJob{
		ID:          Int64(399444496),
		StartedAt:   &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)},
		CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)},
	}
	if !cmp.Equal(job, want) {
		t.Errorf("Actions.GetWorkflowJobByID returned %+v, want %+v", job, want)
	}

	const methodName = "GetWorkflowJobByID"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Actions.GetWorkflowJobByID(ctx, "\n", "\n", 399444496)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Actions.GetWorkflowJobByID(ctx, "o", "r", 399444496)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestActionsService_GetWorkflowJobLogs(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		http.Redirect(w, r, "http://github.com/a", http.StatusFound)
	})

	ctx := context.Background()
	url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, true)
	if err != nil {
		t.Errorf("Actions.GetWorkflowJobLogs returned error: %v", err)
	}
	if resp.StatusCode != http.StatusFound {
		t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusFound)
	}
	want := "http://github.com/a"
	if url.String() != want {
		t.Errorf("Actions.GetWorkflowJobLogs returned %+v, want %+v", url.String(), want)
	}

	const methodName = "GetWorkflowJobLogs"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Actions.GetWorkflowJobLogs(ctx, "\n", "\n", 399444496, true)
		return err
	})
}

func TestActionsService_GetWorkflowJobLogs_StatusMovedPermanently_dontFollowRedirects(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		http.Redirect(w, r, "http://github.com/a", http.StatusMovedPermanently)
	})

	ctx := context.Background()
	_, resp, _ := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, false)
	if resp.StatusCode != http.StatusMovedPermanently {
		t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusMovedPermanently)
	}
}

func TestActionsService_GetWorkflowJobLogs_StatusMovedPermanently_followRedirects(t *testing.T) {
	client, mux, serverURL, teardown := setup()
	defer teardown()

	// Mock a redirect link, which leads to an archive link
	mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect")
		http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently)
	})

	mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		http.Redirect(w, r, "http://github.com/a", http.StatusFound)
	})

	ctx := context.Background()
	url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, true)
	if err != nil {
		t.Errorf("Actions.GetWorkflowJobLogs returned error: %v", err)
	}

	if resp.StatusCode != http.StatusFound {
		t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusFound)
	}

	want := "http://github.com/a"
	if url.String() != want {
		t.Errorf("Actions.GetWorkflowJobLogs returned %+v, want %+v", url.String(), want)
	}
}
<commit_msg>Adds test cases for JSON resource marshalling (#1911)<commit_after>// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
)

func TestActionsService_ListWorkflowJobs(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/runs/29679449/jobs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"per_page": "2", "page": "2"})
		fmt.Fprint(w, `{"total_count":4,"jobs":[{"id":399444496,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"},{"id":399444497,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}]}`)
	})

	opts := &ListWorkflowJobsOptions{ListOptions: ListOptions{Page: 2, PerPage: 2}}
	ctx := context.Background()
	jobs, _, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
	if err != nil {
		t.Errorf("Actions.ListWorkflowJobs returned error: %v", err)
	}

	want := &Jobs{
		TotalCount: Int(4),
		Jobs: []*WorkflowJob{
			{ID: Int64(399444496), RunID: Int64(29679449), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
			{ID: Int64(399444497), RunID: Int64(29679449), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
		},
	}
	if !cmp.Equal(jobs, want) {
		t.Errorf("Actions.ListWorkflowJobs returned %+v, want %+v", jobs, want)
	}

	const methodName = "ListWorkflowJobs"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Actions.ListWorkflowJobs(ctx, "\n", "\n", 29679449, opts)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestActionsService_ListWorkflowJobs_Filter(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/runs/29679449/jobs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"filter": "all", "per_page": "2", "page": "2"})
		fmt.Fprint(w, `{"total_count":4,"jobs":[{"id":399444496,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"},{"id":399444497,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}]}`)
	})

	opts := &ListWorkflowJobsOptions{Filter: "all", ListOptions: ListOptions{Page: 2, PerPage: 2}}
	ctx := context.Background()
	jobs, _, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
	if err != nil {
		t.Errorf("Actions.ListWorkflowJobs returned error: %v", err)
	}

	want := &Jobs{
		TotalCount: Int(4),
		Jobs: []*WorkflowJob{
			{ID: Int64(399444496), RunID: Int64(29679449), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
			{ID: Int64(399444497), RunID: Int64(29679449), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
		},
	}
	if !cmp.Equal(jobs, want) {
		t.Errorf("Actions.ListWorkflowJobs returned %+v, want %+v", jobs, want)
	}
}

func TestActionsService_GetWorkflowJobByID(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/jobs/399444496", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"id":399444496,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}`)
	})

	ctx := context.Background()
	job, _, err := client.Actions.GetWorkflowJobByID(ctx, "o", "r", 399444496)
	if err != nil {
		t.Errorf("Actions.GetWorkflowJobByID returned error: %v", err)
	}

	want := &WorkflowJob{
		ID:          Int64(399444496),
		StartedAt:   &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)},
		CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)},
	}
	if !cmp.Equal(job, want) {
		t.Errorf("Actions.GetWorkflowJobByID returned %+v, want %+v", job, want)
	}

	const methodName = "GetWorkflowJobByID"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Actions.GetWorkflowJobByID(ctx, "\n", "\n", 399444496)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Actions.GetWorkflowJobByID(ctx, "o", "r", 399444496)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestActionsService_GetWorkflowJobLogs(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		http.Redirect(w, r, "http://github.com/a", http.StatusFound)
	})

	ctx := context.Background()
	url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, true)
	if err != nil {
		t.Errorf("Actions.GetWorkflowJobLogs returned error: %v", err)
	}
	if resp.StatusCode != http.StatusFound {
		t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusFound)
	}
	want := "http://github.com/a"
	if url.String() != want {
		t.Errorf("Actions.GetWorkflowJobLogs returned %+v, want %+v", url.String(), want)
	}

	const methodName = "GetWorkflowJobLogs"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Actions.GetWorkflowJobLogs(ctx, "\n", "\n", 399444496, true)
		return err
	})
}

func TestActionsService_GetWorkflowJobLogs_StatusMovedPermanently_dontFollowRedirects(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		http.Redirect(w, r, "http://github.com/a", http.StatusMovedPermanently)
	})

	ctx := context.Background()
	_, resp, _ := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, false)
	if resp.StatusCode != http.StatusMovedPermanently {
		t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusMovedPermanently)
	}
}

func TestActionsService_GetWorkflowJobLogs_StatusMovedPermanently_followRedirects(t *testing.T) {
	client, mux, serverURL, teardown := setup()
	defer teardown()

	// Mock a redirect link, which leads to an archive link
	mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect")
		http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently)
	})

	mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		http.Redirect(w, r, "http://github.com/a", http.StatusFound)
	})

	ctx := context.Background()
	url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, true)
	if err != nil {
		t.Errorf("Actions.GetWorkflowJobLogs returned error: %v", err)
	}

	if resp.StatusCode != http.StatusFound {
		t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusFound)
	}

	want := "http://github.com/a"
	if url.String() != want {
		t.Errorf("Actions.GetWorkflowJobLogs returned %+v, want %+v", url.String(), want)
	}
}

func TestTaskStep_Marshal(t *testing.T) {
	testJSONMarshal(t, &TaskStep{}, "{}")

	u := &TaskStep{
		Name:        String("n"),
		Status:      String("s"),
		Conclusion:  String("c"),
		Number:      Int64(1),
		StartedAt:   &Timestamp{referenceTime},
		CompletedAt: &Timestamp{referenceTime},
	}

	want := `{
		"name": "n",
		"status": "s",
		"conclusion": "c",
		"number": 1,
		"started_at": ` + referenceTimeStr + `,
		"completed_at": ` + referenceTimeStr + `
	}`

	testJSONMarshal(t, u, want)
}

func TestWorkflowJob_Marshal(t *testing.T) {
	testJSONMarshal(t, &WorkflowJob{}, "{}")

	u := &WorkflowJob{
		ID:          Int64(1),
		RunID:       Int64(1),
		RunURL:      String("r"),
		NodeID:      String("n"),
		HeadSHA:     String("h"),
		URL:         String("u"),
		HTMLURL:     String("h"),
		Status:      String("s"),
		Conclusion:  String("c"),
		StartedAt:   &Timestamp{referenceTime},
		CompletedAt: &Timestamp{referenceTime},
		Name:        String("n"),
		Steps: []*TaskStep{
			{
				Name:        String("n"),
				Status:      String("s"),
				Conclusion:  String("c"),
				Number:      Int64(1),
				StartedAt:   &Timestamp{referenceTime},
				CompletedAt: &Timestamp{referenceTime},
			},
		},
		CheckRunURL: String("c"),
	}

	want := `{
		"id": 1,
		"run_id": 1,
		"run_url": "r",
		"node_id": "n",
		"head_sha": "h",
		"url": "u",
		"html_url": "h",
		"status": "s",
		"conclusion": "c",
		"started_at": ` + referenceTimeStr + `,
		"completed_at": ` + referenceTimeStr + `,
		"name": "n",
		"steps": [{
			"name": "n",
			"status": "s",
			"conclusion": "c",
			"number": 1,
			"started_at": ` + referenceTimeStr + `,
			"completed_at": ` + referenceTimeStr + `
		}],
		"check_run_url": "c"
	}`

	testJSONMarshal(t, u, want)
}

func TestJobs_Marshal(t *testing.T) {
	testJSONMarshal(t, &Jobs{}, "{}")

	u := &Jobs{
		TotalCount: Int(1),
		Jobs: []*WorkflowJob{
			{
				ID:          Int64(1),
				RunID:       Int64(1),
				RunURL:      String("r"),
				NodeID:      String("n"),
				HeadSHA:     String("h"),
				URL:         String("u"),
				HTMLURL:     String("h"),
				Status:      String("s"),
				Conclusion:  String("c"),
				StartedAt:   &Timestamp{referenceTime},
				CompletedAt: &Timestamp{referenceTime},
				Name:        String("n"),
				Steps: []*TaskStep{
					{
						Name:        String("n"),
						Status:      String("s"),
						Conclusion:  String("c"),
						Number:      Int64(1),
						StartedAt:   &Timestamp{referenceTime},
						CompletedAt: &Timestamp{referenceTime},
					},
				},
				CheckRunURL: String("c"),
			},
		},
	}

	want := `{
		"total_count": 1,
		"jobs": [{
			"id": 1,
			"run_id": 1,
			"run_url": "r",
			"node_id": "n",
			"head_sha": "h",
			"url": "u",
			"html_url": "h",
			"status": "s",
			"conclusion": "c",
			"started_at": ` + referenceTimeStr + `,
			"completed_at": ` + referenceTimeStr + `,
			"name": "n",
			"steps": [{
				"name": "n",
				"status": "s",
				"conclusion": "c",
				"number": 1,
				"started_at": ` + referenceTimeStr + `,
				"completed_at": ` + referenceTimeStr + `
			}],
			"check_run_url": "c"
		}]
	}`

	testJSONMarshal(t, u, want)
}
<|endoftext|>


<commit_before>package apiutil

import (
	"sync"
	"time"

	"github.com/koding/kite"

	"koding/api"
	"koding/kites/config"
	"koding/kites/kloud/stack"
)

// Kite is an interface for kite.Kite, that is used
// to make RPC calls.
type Kite interface {
	Call(method string, req, resp interface{}) error
}

// LazyKite is a wrapper for Client that dials the
// kite on the first request.
type LazyKite struct {
	Client      *kite.Client  // kite client to use; required
	DialTimeout time.Duration // max dial time; 30s by default
	CallTimeout time.Duration // max call time; 60s by default

	mu     sync.Mutex
	dialed bool
}

var _ Kite = (*LazyKite)(nil)

// Call implements the Kite interface.
func (lk *LazyKite) Call(method string, req, resp interface{}) error {
	if err := lk.init(); err != nil {
		return err
	}

	r, err := lk.Client.TellWithTimeout(method, lk.callTimeout(), req)
	if err != nil {
		return err
	}

	if resp != nil {
		return r.Unmarshal(resp)
	}

	return nil
}

func (lk *LazyKite) init() error {
	lk.mu.Lock()
	defer lk.mu.Unlock()

	if !lk.dialed {
		if err := lk.Client.DialTimeout(lk.dialTimeout()); err != nil {
			return err
		}

		lk.dialed = true
	}

	return nil
}

func (lk *LazyKite) dialTimeout() time.Duration {
	if lk.DialTimeout != 0 {
		return lk.DialTimeout
	}
	return 30 * time.Second
}

func (lk *LazyKite) callTimeout() time.Duration {
	if lk.CallTimeout != 0 {
		return lk.CallTimeout
	}
	return 60 * time.Second
}

// KloudAuth provides api.AuthFunc that is
// backed by kite RPC.
type KloudAuth struct {
	Kite    Kite        // kite transport to use; required
	Storage api.Storage // storage for cache to use; optional

	once sync.Once
	auth api.AuthFunc
}

var _ api.AuthFunc = (&KloudAuth{}).Auth

func (ka *KloudAuth) init() {
	ka.once.Do(ka.initAuth)
}

func (ka *KloudAuth) initAuth() {
	if ka.Storage != nil {
		cache := api.NewCache(ka.rpcAuth)
		cache.Storage = ka.Storage
		ka.auth = cache.Auth
	} else {
		ka.auth = ka.rpcAuth
	}
}

// Auth obtains user session by calling "auth.login" method over Kite transport.
func (ka *KloudAuth) Auth(opts *api.AuthOptions) (*api.Session, error) {
	ka.init()
	return ka.auth(opts)
}

func (ka *KloudAuth) rpcAuth(opts *api.AuthOptions) (*api.Session, error) {
	var req = &stack.LoginRequest{GroupName: opts.User.Team}
	var resp stack.LoginResponse

	if err := ka.Kite.Call("auth.login", req, &resp); err != nil {
		return nil, err
	}

	return &api.Session{
		ClientID: resp.ClientID,
		// TODO(rjeczalik): add Username field to stack.LoginResponse
		// Username: resp.Username,
		User: &api.User{
			Username: opts.User.Username,
			Team:     resp.GroupName,
		},
	}, nil
}

// Storage is wrapper for config.Cache that implements api.Storage.
type Storage struct {
	Cache *config.Cache
}

var _ api.Storage = (*Storage)(nil)

// Get implements the api.Storage interface.
func (st *Storage) Get(u *api.User) (*api.Session, error) {
	var sessions map[string]api.Session

	if err := st.Cache.GetValue("auth.sessions", &sessions); err != nil {
		return nil, err
	}

	session, ok := sessions[u.String()]
	if !ok {
		return nil, api.ErrSessionNotFound
	}

	return &session, nil
}

// Set implements the api.Storage interface.
func (st *Storage) Set(s *api.Session) error {
	sessions := make(map[string]api.Session)

	if err := st.Cache.GetValue("auth.sessions", &sessions); err != nil {
		return err
	}

	sessions[s.User.String()] = *s

	return st.Cache.SetValue("auth.sessions", sessions)
}

// Delete implements the api.Storage interface.
func (st *Storage) Delete(s *api.Session) error {
	sessions := make(map[string]api.Session)

	if err := st.Cache.GetValue("auth.sessions", &sessions); err != nil {
		return err
	}

	delete(sessions, s.User.String())

	return st.Cache.SetValue("auth.sessions", sessions)
}
<commit_msg>api/apitutil: read username from response<commit_after>package apiutil

import (
	"sync"
	"time"

	"github.com/koding/kite"

	"koding/api"
	"koding/kites/config"
	"koding/kites/kloud/stack"
)

// Kite is an interface for kite.Kite, that is used
// to make RPC calls.
type Kite interface {
	Call(method string, req, resp interface{}) error
}

// LazyKite is a wrapper for Client that dials the
// kite on the first request.
type LazyKite struct {
	Client      *kite.Client  // kite client to use; required
	DialTimeout time.Duration // max dial time; 30s by default
	CallTimeout time.Duration // max call time; 60s by default

	mu     sync.Mutex
	dialed bool
}

var _ Kite = (*LazyKite)(nil)

// Call implements the Kite interface.
func (lk *LazyKite) Call(method string, req, resp interface{}) error {
	if err := lk.init(); err != nil {
		return err
	}

	r, err := lk.Client.TellWithTimeout(method, lk.callTimeout(), req)
	if err != nil {
		return err
	}

	if resp != nil {
		return r.Unmarshal(resp)
	}

	return nil
}

func (lk *LazyKite) init() error {
	lk.mu.Lock()
	defer lk.mu.Unlock()

	if !lk.dialed {
		if err := lk.Client.DialTimeout(lk.dialTimeout()); err != nil {
			return err
		}

		lk.dialed = true
	}

	return nil
}

func (lk *LazyKite) dialTimeout() time.Duration {
	if lk.DialTimeout != 0 {
		return lk.DialTimeout
	}
	return 30 * time.Second
}

func (lk *LazyKite) callTimeout() time.Duration {
	if lk.CallTimeout != 0 {
		return lk.CallTimeout
	}
	return 60 * time.Second
}

// KloudAuth provides api.AuthFunc that is
// backed by kite RPC.
type KloudAuth struct {
	Kite    Kite        // kite transport to use; required
	Storage api.Storage // storage for cache to use; optional

	once sync.Once
	auth api.AuthFunc
}

var _ api.AuthFunc = (&KloudAuth{}).Auth

func (ka *KloudAuth) init() {
	ka.once.Do(ka.initAuth)
}

func (ka *KloudAuth) initAuth() {
	if ka.Storage != nil {
		cache := api.NewCache(ka.rpcAuth)
		cache.Storage = ka.Storage
		ka.auth = cache.Auth
	} else {
		ka.auth = ka.rpcAuth
	}
}

// Auth obtains user session by calling "auth.login" method over Kite transport.
func (ka *KloudAuth) Auth(opts *api.AuthOptions) (*api.Session, error) {
	ka.init()
	return ka.auth(opts)
}

func (ka *KloudAuth) rpcAuth(opts *api.AuthOptions) (*api.Session, error) {
	var req = &stack.LoginRequest{GroupName: opts.User.Team}
	var resp stack.LoginResponse

	if err := ka.Kite.Call("auth.login", req, &resp); err != nil {
		return nil, err
	}

	return &api.Session{
		ClientID: resp.ClientID,
		User: &api.User{
			Username: resp.Username,
			Team:     resp.GroupName,
		},
	}, nil
}

// Storage is wrapper for config.Cache that implements api.Storage.
type Storage struct {
	Cache *config.Cache
}

var _ api.Storage = (*Storage)(nil)

// Get implements the api.Storage interface.
func (st *Storage) Get(u *api.User) (*api.Session, error) {
	var sessions map[string]api.Session

	if err := st.Cache.GetValue("auth.sessions", &sessions); err != nil {
		return nil, err
	}

	session, ok := sessions[u.String()]
	if !ok {
		return nil, api.ErrSessionNotFound
	}

	return &session, nil
}

// Set implements the api.Storage interface.
func (st *Storage) Set(s *api.Session) error {
	sessions := make(map[string]api.Session)

	if err := st.Cache.GetValue("auth.sessions", &sessions); err != nil {
		return err
	}

	sessions[s.User.String()] = *s

	return st.Cache.SetValue("auth.sessions", sessions)
}

// Delete implements the api.Storage interface.
func (st *Storage) Delete(s *api.Session) error {
	sessions := make(map[string]api.Session)

	if err := st.Cache.GetValue("auth.sessions", &sessions); err != nil {
		return err
	}

	delete(sessions, s.User.String())

	return st.Cache.SetValue("auth.sessions", sessions)
}
<|endoftext|>