<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.com/uber-go/zap"
	"github.com/yulrizka/bot"
	"github.com/yulrizka/fam100"
)

var (
	// cmdRateDelay is time before we serve score command
	cmdRateDelay = 30 * time.Second

	gameQueue chan struct{}
)

var lastCmdRequest = make(map[string]time.Time)

// handleJoin handles "/join". Create game and start it if quorum
func (b *fam100Bot) cmdJoin(msg *bot.Message) bool {
	defer cmdJoinTimer.UpdateSince(time.Now())

	if b.handleDisabled(msg) {
		return true
	}

	commandJoinCount.Inc(1)
	chanID := msg.Chat.ID
	chanName := msg.Chat.Title
	ch, ok := b.channels[chanID]
	if !ok {
		playerJoinedCount.Inc(1)
		// create a new game
		quorumPlayer := map[string]bool{msg.From.ID: true}
		players := map[string]string{msg.From.ID: msg.From.FullName()}

		gameIn := make(chan fam100.Message, gameInBufferSize)
		game, err := fam100.NewGame(chanID, chanName, gameIn, b.gameOut)
		if err != nil {
			log.Error("creating a game", zap.String("chanID", chanID))
			return true
		}

		ch := &channel{ID: chanID, game: game, quorumPlayer: quorumPlayer, players: players}
		b.channels[chanID] = ch
		if len(ch.quorumPlayer) == minQuorum {
			ch.game.Start()
			return true
		}
		log.Info("User joined", zap.String("playerID", msg.From.ID), zap.String("chanID", chanID))

		go func() {
			start := time.Now()
			// handle queueing
			shouldBeQueued := true
			if queueConf, err := fam100.DefaultDB.ChannelConfig(chanID, "queued", ""); err == nil && queueConf == "no" {
				shouldBeQueued = false
			}

			select {
			case gameQueue <- struct{}{}:
				game.State = fam100.Created
			default:
				if !shouldBeQueued {
					break
				}

				// it's waiting for the queue
				text := fmt.Sprintf(
					"game telah dimasukkan dalam antrian, rata-rata waktu antrian %.0fs.\nUser lain dapat tetap melakukan '/join@fam100bot'. Game akan dimulai automatis ketika antrian selesai",
					time.Duration(gameWaitingTimer.Mean()).Seconds())

				b.out <- bot.Message{Chat: bot.Chat{ID: chanID}, Text: text, Format: bot.HTML, DiscardAfter: time.Now().Add(5 * time.Second)}
				log.Info("game in queue", zap.Int64("gameID", game.ID), zap.String("chanID", chanID))

				gameCurrentQueuedCount.Inc(1)
				gameQueue <- struct{}{} // wait until it's available
				gameCurrentQueuedCount.Dec(1)
				gameWaitingTimer.UpdateSince(start)
				game.State = fam100.Created

				// maybe it's already quorum after finished waiting
				if len(ch.quorumPlayer) == minQuorum {
					ch.game.Start()
					return
				}
			}
			gameCurrentQuorumCount.Inc(1)
			ch.startQuorumTimer(time.Duration(quorumWait)*time.Second, b.out)
			ch.startQuorumNotifyTimer(5*time.Second, b.out)
		}()
		return true
	}

	switch {
	case ch.game.State == fam100.Queued:
	case ch.game.State == fam100.Created:
	case ch.quorumPlayer[msg.From.ID]:
		return true
	default:
		return true
	}

	// new player joined
	playerJoinedCount.Inc(1)
	ch.quorumPlayer[msg.From.ID] = true
	ch.players[msg.From.ID] = msg.From.FullName()

	if ch.game.State == fam100.Queued {
		return true
	}

	// state is Created, waiting for quorum
	ch.cancelTimer()
	if len(ch.quorumPlayer) == minQuorum {
		if ch.cancelNotifyTimer != nil {
			ch.cancelNotifyTimer()
		}

		gameCurrentQuorumCount.Dec(1)
		ch.game.Start()
		return true
	}
	ch.startQuorumTimer(time.Duration(quorumWait)*time.Second, b.out)
	if ch.cancelNotifyTimer == nil {
		ch.startQuorumNotifyTimer(5*time.Second, b.out)
	}
	log.Info("User joined", zap.String("playerID", msg.From.ID), zap.String("chanID", chanID))

	return true
}

func (b *fam100Bot) cmdHelp(msg *bot.Message) bool {
	defer cmdHelpTimer.UpdateSince(time.Now())

	if b.handleDisabled(msg) {
		return true
	}

	if rateLimited("help", msg.Chat.ID, cmdRateDelay) {
		return true
	}
	text := `Cara bermain, menambahkan bot ke group sendiri dapat dilihat di <a href="http://labs.yulrizka.com/fam100/faq.html">F.A.Q</a>`
	b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: text, Format: bot.HTML, DiscardAfter: time.Now().Add(5 * time.Second)}

	return true
}

// handleJoin handles "/score" show top score for current channel
func (b *fam100Bot) cmdScore(msg *bot.Message) bool {
	defer cmdScoreTimer.UpdateSince(time.Now())

	if b.handleDisabled(msg) {
		return true
	}

	if rateLimited("score", msg.Chat.ID, cmdRateDelay) {
		return true
	}

	go func() {
		commandScoreCount.Inc(1)
		chanID := msg.Chat.ID
		rank, err := fam100.DefaultDB.ChannelRanking(chanID, 20)
		if err != nil {
			log.Error("getting channel ranking failed", zap.String("chanID", chanID), zap.Error(err))
			return
		}

		text := "Top Score:\n" + formatRankText(rank)
		text += fmt.Sprintf("\nFull Score", chanID)
		b.out <- bot.Message{Chat: bot.Chat{ID: chanID}, Text: text, Format: bot.HTML, DiscardAfter: time.Now().Add(20 * time.Second)}
	}()

	return true
}

func (b *fam100Bot) handleDisabled(msg *bot.Message) bool {
	chanID := msg.Chat.ID
	disabledMsg, _ := fam100.DefaultDB.ChannelConfig(chanID, "disabled", "")

	if disabledMsg != "" {
		log.Debug("channel is disabled", zap.String("chanID", chanID), zap.String("msg", disabledMsg))
		b.out <- bot.Message{Chat: bot.Chat{ID: chanID}, Text: disabledMsg, Format: bot.Markdown, DiscardAfter: time.Now().Add(5 * time.Second)}
		return true
	}

	return false
}

func formatRoundText(msg fam100.QNAMessage) string {
	var b bytes.Buffer
	w := bufio.NewWriter(&b)

	fmt.Fprintf(w, "[id: %d] %s?\n\n", msg.QuestionID, msg.QuestionText)
	for i, a := range msg.Answers {
		if a.Answered {
			if a.Highlight {
				fmt.Fprintf(w, "%d. (%2d) %s \n  ✓ %s\n", i+1, a.Score, escape(a.Text), escape(a.PlayerName))
			} else {
				fmt.Fprintf(w, "%d. (%2d) %s \n  ✓ %s\n", i+1, a.Score, escape(a.Text), escape(a.PlayerName))
			}
		} else {
			if msg.ShowUnanswered {
				fmt.Fprintf(w, "%d. (%2d) %s \n", i+1, a.Score, escape(a.Text))
			} else {
				fmt.Fprintf(w, "%d. _________________________\n", i+1)
			}
		}
	}
	w.Flush()

	return b.String()
}

func formatRankText(rank fam100.Rank) string {
	var b bytes.Buffer
	w := bufio.NewWriter(&b)

	fmt.Fprintf(w, "\n")
	lastPos := 0
	if len(rank) == 0 {
		fmt.Fprintf(w, fam100.T("Tidak ada\n"))
	} else {
		for _, ps := range rank {
			if lastPos != 0 && lastPos+1 != ps.Position {
				fmt.Fprintf(w, "...\n")
			}
			fmt.Fprintf(w, "%d. (%2d) %s\n", ps.Position, ps.Score, ps.Name)
			lastPos = ps.Position
		}
	}
	w.Flush()

	return escape(b.String())
}

func escape(s string) string {
	s = strings.Replace(s, "&", "&", -1)
	s = strings.Replace(s, "<", "<", -1)
	s = strings.Replace(s, ">", ">", -1)

	return s
}

// cmdSay handles /say [chan_id] [message]
func (b *fam100Bot) cmdSay(msg *bot.Message) bool {
	fields := strings.SplitN(msg.Text, " ", 3)
	if len(fields) < 3 {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "usage: `/say [chanID] [message]`", Format: bot.Markdown}
		return true
	}
	chatID, text := fields[1], fields[2]
	b.out <- bot.Message{Chat: bot.Chat{ID: chatID}, Text: text, Format: bot.Markdown}

	return true
}

// cmdSay handles /channels [pattern]. empty pattern matches all
func (b *fam100Bot) cmdChannels(msg *bot.Message) bool {
	fields := strings.SplitN(msg.Text, " ", 2)
	if len(fields) < 2 || fields[1] == "" {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "usage: `/channels [regex pattern]`", Format: bot.Markdown}
		return true
	}

	channels, err := fam100.DefaultDB.Channels()
	if err != nil {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "channels failed. " + err.Error(), Format: bot.Markdown}
	}

	// filter out by regex
	r, err := regexp.Compile(fields[1])
	if err != nil {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "regex failed. " + err.Error(), Format: bot.Markdown}
	}

	results := make(map[string]string)
	for id, desc := range channels {
		if r.MatchString(desc) {
			results[id] = desc
		}
	}

	buf := bytes.NewBuffer(nil)
	for id, desc := range results {
		if r.MatchString(desc) {
			buf.WriteString("\n")
			buf.WriteString(id)
			buf.WriteString(" ")
			buf.WriteString(desc)
		}
	}

	text := fmt.Sprintf("found %d channels:", len(results))
	body := buf.String()
	if len(body) > 3000 {
		body = body[:3000]
		body = body[:strings.LastIndex(body, "\n")]
		body += "\n ... truncated"
	}

	b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: text + body, Format: bot.Text}

	return true
}

// cmdBroadcast handles /broadcast [msg]. Broadcast message to all channels
func (b *fam100Bot) cmdBroadcast(msg *bot.Message) bool {
	fields := strings.SplitN(msg.Text, " ", 2)
	if len(fields) < 2 || fields[1] == "" {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "usage: `/broadcast [message]`", Format: bot.Markdown}
		return true
	}

	channels, err := fam100.DefaultDB.Channels()
	if err != nil {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "channels failed. " + err.Error(), Format: bot.Markdown}
	}

	go func() {
		text := fields[1]
		for id := range channels {
			b.out <- bot.Message{Chat: bot.Chat{ID: id}, Text: text, Format: bot.Text}
			time.Sleep(1 * time.Second)
		}
	}()

	return true
}

// rateLimited returns true if call should be ignored becasue of the rate limit
func rateLimited(cmd, chatID string, duration time.Duration) bool {

	now := time.Now()
	if lastTime, ok := lastCmdRequest[chatID]; ok &&
		now.Before(lastTime.Add(duration)) {
		return true
	}

	lastCmdRequest[chatID] = now
	return false
}
<commit_msg>Fix but after waiting game is not started<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.com/uber-go/zap"
	"github.com/yulrizka/bot"
	"github.com/yulrizka/fam100"
)

var (
	// cmdRateDelay is time before we serve score command
	cmdRateDelay = 30 * time.Second

	gameQueue chan struct{}
)

var lastCmdRequest = make(map[string]time.Time)

// handleJoin handles "/join". Create game and start it if quorum
func (b *fam100Bot) cmdJoin(msg *bot.Message) bool {
	defer cmdJoinTimer.UpdateSince(time.Now())

	if b.handleDisabled(msg) {
		return true
	}

	commandJoinCount.Inc(1)
	chanID := msg.Chat.ID
	chanName := msg.Chat.Title
	ch, ok := b.channels[chanID]
	if !ok {
		playerJoinedCount.Inc(1)
		// create a new game
		quorumPlayer := map[string]bool{msg.From.ID: true}
		players := map[string]string{msg.From.ID: msg.From.FullName()}

		gameIn := make(chan fam100.Message, gameInBufferSize)
		game, err := fam100.NewGame(chanID, chanName, gameIn, b.gameOut)
		if err != nil {
			log.Error("creating a game", zap.String("chanID", chanID))
			return true
		}

		ch := &channel{ID: chanID, game: game, quorumPlayer: quorumPlayer, players: players}
		b.channels[chanID] = ch
		if len(ch.quorumPlayer) == minQuorum {
			ch.game.Start()
			return true
		}
		log.Info("User joined", zap.String("playerID", msg.From.ID), zap.String("chanID", chanID))

		go func() {
			start := time.Now()
			// handle queueing
			shouldBeQueued := true
			if queueConf, err := fam100.DefaultDB.ChannelConfig(chanID, "queued", ""); err == nil && queueConf == "no" {
				shouldBeQueued = false
			}

			select {
			case gameQueue <- struct{}{}:
				game.State = fam100.Created
			default:
				if !shouldBeQueued {
					game.State = fam100.Created
					break
				}

				// it's waiting for the queue
				text := fmt.Sprintf(
					"game telah dimasukkan dalam antrian, rata-rata waktu antrian %.0fs. User lain dapat tetap melakukan '/join@%s'. Game akan dimulai automatis ketika antrian selesai",
					time.Duration(gameWaitingTimer.Mean()).Seconds(),
					botName)

				b.out <- bot.Message{Chat: bot.Chat{ID: chanID}, Text: text, Format: bot.HTML, DiscardAfter: time.Now().Add(5 * time.Second)}
				log.Info("game in queue", zap.Int64("gameID", game.ID), zap.String("chanID", chanID))

				gameCurrentQueuedCount.Inc(1)
				gameQueue <- struct{}{} // wait until it's available
				gameCurrentQueuedCount.Dec(1)
				gameWaitingTimer.UpdateSince(start)
				game.State = fam100.Created

				// maybe it's already quorum after finished waiting
				if len(ch.quorumPlayer) >= minQuorum {
					ch.game.Start()
					return
				}
			}
			gameCurrentQuorumCount.Inc(1)
			ch.startQuorumTimer(time.Duration(quorumWait)*time.Second, b.out)
			ch.startQuorumNotifyTimer(5*time.Second, b.out)
		}()
		return true
	}

	switch {
	case ch.game.State == fam100.Queued:
	case ch.game.State == fam100.Created:
	case ch.quorumPlayer[msg.From.ID]:
		return true
	default:
		return true
	}

	// new player joined
	playerJoinedCount.Inc(1)
	ch.quorumPlayer[msg.From.ID] = true
	ch.players[msg.From.ID] = msg.From.FullName()

	if ch.game.State == fam100.Queued {
		return true
	}

	// state is Created, waiting for quorum
	ch.cancelTimer()
	if len(ch.quorumPlayer) >= minQuorum {
		if ch.cancelNotifyTimer != nil {
			ch.cancelNotifyTimer()
		}

		gameCurrentQuorumCount.Dec(1)
		ch.game.Start()
		return true
	}
	ch.startQuorumTimer(time.Duration(quorumWait)*time.Second, b.out)
	if ch.cancelNotifyTimer == nil {
		ch.startQuorumNotifyTimer(5*time.Second, b.out)
	}
	log.Info("User joined", zap.String("playerID", msg.From.ID), zap.String("chanID", chanID))

	return true
}

func (b *fam100Bot) cmdHelp(msg *bot.Message) bool {
	defer cmdHelpTimer.UpdateSince(time.Now())

	if b.handleDisabled(msg) {
		return true
	}

	if rateLimited("help", msg.Chat.ID, cmdRateDelay) {
		return true
	}
	text := `Cara bermain, menambahkan bot ke group sendiri dapat dilihat di <a href="http://labs.yulrizka.com/fam100/faq.html">F.A.Q</a>`
	b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: text, Format: bot.HTML, DiscardAfter: time.Now().Add(5 * time.Second)}

	return true
}

// handleJoin handles "/score" show top score for current channel
func (b *fam100Bot) cmdScore(msg *bot.Message) bool {
	defer cmdScoreTimer.UpdateSince(time.Now())

	if b.handleDisabled(msg) {
		return true
	}

	if rateLimited("score", msg.Chat.ID, cmdRateDelay) {
		return true
	}

	go func() {
		commandScoreCount.Inc(1)
		chanID := msg.Chat.ID
		rank, err := fam100.DefaultDB.ChannelRanking(chanID, 20)
		if err != nil {
			log.Error("getting channel ranking failed", zap.String("chanID", chanID), zap.Error(err))
			return
		}

		text := "Top Score:\n" + formatRankText(rank)
		text += fmt.Sprintf("\nFull Score", chanID)
		b.out <- bot.Message{Chat: bot.Chat{ID: chanID}, Text: text, Format: bot.HTML, DiscardAfter: time.Now().Add(20 * time.Second)}
	}()

	return true
}

func (b *fam100Bot) handleDisabled(msg *bot.Message) bool {
	chanID := msg.Chat.ID
	disabledMsg, _ := fam100.DefaultDB.ChannelConfig(chanID, "disabled", "")

	if disabledMsg != "" {
		log.Debug("channel is disabled", zap.String("chanID", chanID), zap.String("msg", disabledMsg))
		b.out <- bot.Message{Chat: bot.Chat{ID: chanID}, Text: disabledMsg, Format: bot.Markdown, DiscardAfter: time.Now().Add(5 * time.Second)}
		return true
	}

	return false
}

func formatRoundText(msg fam100.QNAMessage) string {
	var b bytes.Buffer
	w := bufio.NewWriter(&b)

	fmt.Fprintf(w, "[id: %d] %s?\n\n", msg.QuestionID, msg.QuestionText)
	for i, a := range msg.Answers {
		if a.Answered {
			if a.Highlight {
				fmt.Fprintf(w, "%d. (%2d) %s \n  ✓ %s\n", i+1, a.Score, escape(a.Text), escape(a.PlayerName))
			} else {
				fmt.Fprintf(w, "%d. (%2d) %s \n  ✓ %s\n", i+1, a.Score, escape(a.Text), escape(a.PlayerName))
			}
		} else {
			if msg.ShowUnanswered {
				fmt.Fprintf(w, "%d. (%2d) %s \n", i+1, a.Score, escape(a.Text))
			} else {
				fmt.Fprintf(w, "%d. _________________________\n", i+1)
			}
		}
	}
	w.Flush()

	return b.String()
}

func formatRankText(rank fam100.Rank) string {
	var b bytes.Buffer
	w := bufio.NewWriter(&b)

	fmt.Fprintf(w, "\n")
	lastPos := 0
	if len(rank) == 0 {
		fmt.Fprintf(w, fam100.T("Tidak ada\n"))
	} else {
		for _, ps := range rank {
			if lastPos != 0 && lastPos+1 != ps.Position {
				fmt.Fprintf(w, "...\n")
			}
			fmt.Fprintf(w, "%d. (%2d) %s\n", ps.Position, ps.Score, ps.Name)
			lastPos = ps.Position
		}
	}
	w.Flush()

	return escape(b.String())
}

func escape(s string) string {
	s = strings.Replace(s, "&", "&", -1)
	s = strings.Replace(s, "<", "<", -1)
	s = strings.Replace(s, ">", ">", -1)

	return s
}

// cmdSay handles /say [chan_id] [message]
func (b *fam100Bot) cmdSay(msg *bot.Message) bool {
	fields := strings.SplitN(msg.Text, " ", 3)
	if len(fields) < 3 {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "usage: `/say [chanID] [message]`", Format: bot.Markdown}
		return true
	}
	chatID, text := fields[1], fields[2]
	b.out <- bot.Message{Chat: bot.Chat{ID: chatID}, Text: text, Format: bot.Markdown}

	return true
}

// cmdSay handles /channels [pattern]. empty pattern matches all
func (b *fam100Bot) cmdChannels(msg *bot.Message) bool {
	fields := strings.SplitN(msg.Text, " ", 2)
	if len(fields) < 2 || fields[1] == "" {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "usage: `/channels [regex pattern]`", Format: bot.Markdown}
		return true
	}

	channels, err := fam100.DefaultDB.Channels()
	if err != nil {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "channels failed. " + err.Error(), Format: bot.Markdown}
	}

	// filter out by regex
	r, err := regexp.Compile(fields[1])
	if err != nil {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "regex failed. " + err.Error(), Format: bot.Markdown}
	}

	results := make(map[string]string)
	for id, desc := range channels {
		if r.MatchString(desc) {
			results[id] = desc
		}
	}

	buf := bytes.NewBuffer(nil)
	for id, desc := range results {
		if r.MatchString(desc) {
			buf.WriteString("\n")
			buf.WriteString(id)
			buf.WriteString(" ")
			buf.WriteString(desc)
		}
	}

	text := fmt.Sprintf("found %d channels:", len(results))
	body := buf.String()
	if len(body) > 3000 {
		body = body[:3000]
		body = body[:strings.LastIndex(body, "\n")]
		body += "\n ... truncated"
	}

	b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: text + body, Format: bot.Text}

	return true
}

// cmdBroadcast handles /broadcast [msg]. Broadcast message to all channels
func (b *fam100Bot) cmdBroadcast(msg *bot.Message) bool {
	fields := strings.SplitN(msg.Text, " ", 2)
	if len(fields) < 2 || fields[1] == "" {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "usage: `/broadcast [message]`", Format: bot.Markdown}
		return true
	}

	channels, err := fam100.DefaultDB.Channels()
	if err != nil {
		b.out <- bot.Message{Chat: bot.Chat{ID: msg.Chat.ID}, Text: "channels failed. " + err.Error(), Format: bot.Markdown}
	}

	go func() {
		text := fields[1]
		for id := range channels {
			b.out <- bot.Message{Chat: bot.Chat{ID: id}, Text: text, Format: bot.Text}
			time.Sleep(1 * time.Second)
		}
	}()

	return true
}

// rateLimited returns true if call should be ignored becasue of the rate limit
func rateLimited(cmd, chatID string, duration time.Duration) bool {

	now := time.Now()
	if lastTime, ok := lastCmdRequest[chatID]; ok &&
		now.Before(lastTime.Add(duration)) {
		return true
	}

	lastCmdRequest[chatID] = now
	return false
}
<|endoftext|>


<commit_before><commit_msg>gbimporter: handle imports within $GOROOT correctly<commit_after><|endoftext|>


<commit_before>package net

import (
	"bytes"
	"cf"
	"cf/configuration"
	"cf/errors"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"
)

const (
	JOB_FINISHED             = "finished"
	JOB_FAILED               = "failed"
	DEFAULT_POLLING_THROTTLE = 5 * time.Second
	ASYNC_REQUEST_TIMEOUT    = 20 * time.Second
)

type JobEntity struct {
	Status string
}

type JobResponse struct {
	Entity JobEntity
}

type AsyncMetadata struct {
	Url string
}

type AsyncResponse struct {
	Metadata AsyncMetadata
}

type apiErrorHandler func(statusCode int, body []byte) error

type tokenRefresher interface {
	RefreshAuthToken() (string, error)
}

type Request struct {
	HttpReq      *http.Request
	SeekableBody io.ReadSeeker
}

type Gateway struct {
	authenticator   tokenRefresher
	errHandler      apiErrorHandler
	PollingEnabled  bool
	PollingThrottle time.Duration
	trustedCerts    []tls.Certificate
	config          configuration.Reader
}

func newGateway(errHandler apiErrorHandler, config configuration.Reader) (gateway Gateway) {
	gateway.errHandler = errHandler
	gateway.config = config
	gateway.PollingThrottle = DEFAULT_POLLING_THROTTLE
	return
}

func (gateway *Gateway) SetTokenRefresher(auth tokenRefresher) {
	gateway.authenticator = auth
}

func (gateway Gateway) GetResource(url string, resource interface{}) (err error) {
	request, err := gateway.NewRequest("GET", url, gateway.config.AccessToken(), nil)
	if err != nil {
		return
	}

	_, err = gateway.PerformRequestForJSONResponse(request, resource)
	return
}

func (gateway Gateway) CreateResource(url string, body io.ReadSeeker, resource ...interface{}) (apiErr error) {
	return gateway.createUpdateOrDeleteResource("POST", url, body, false, resource...)
}

func (gateway Gateway) UpdateResource(url string, body io.ReadSeeker, resource ...interface{}) (apiErr error) {
	return gateway.createUpdateOrDeleteResource("PUT", url, body, false, resource...)
}

func (gateway Gateway) UpdateResourceSync(url string, body io.ReadSeeker, resource ...interface{}) (apiErr error) {
	return gateway.createUpdateOrDeleteResource("PUT", url, body, true, resource...)
}

func (gateway Gateway) DeleteResource(url string) (apiErr error) {
	return gateway.createUpdateOrDeleteResource("DELETE", url, nil, false, &AsyncResponse{})
}

func (gateway Gateway) ListPaginatedResources(target string,
	path string,
	resource interface{},
	cb func(interface{}) bool) (apiErr error) {

	for path != "" {
		pagination := NewPaginatedResources(resource)
		apiErr = gateway.GetResource(fmt.Sprintf("%s%s", target, path), &pagination)
		if apiErr != nil {
			return
		}

		resources, err := pagination.Resources()
		if err != nil {
			return errors.NewWithError("Error parsing JSON", err)
		}

		for _, resource := range resources {
			if !cb(resource) {
				return
			}
		}

		path = pagination.NextURL
	}

	return
}

func (gateway Gateway) createUpdateOrDeleteResource(verb, url string, body io.ReadSeeker, sync bool, optionalResource ...interface{}) (apiErr error) {
	var resource interface{}
	if len(optionalResource) > 0 {
		resource = optionalResource[0]
	}

	request, apiErr := gateway.NewRequest(verb, url, gateway.config.AccessToken(), body)
	if apiErr != nil {
		return
	}

	if resource == nil {
		_, apiErr = gateway.PerformRequest(request)
		return
	}

	if gateway.PollingEnabled && !sync {
		_, apiErr = gateway.PerformPollingRequestForJSONResponse(request, resource, ASYNC_REQUEST_TIMEOUT)
		return
	} else {
		_, apiErr = gateway.PerformRequestForJSONResponse(request, resource)
		return
	}
}

func (gateway Gateway) NewRequest(method, path, accessToken string, body io.ReadSeeker) (req *Request, apiErr error) {
	if body != nil {
		body.Seek(0, 0)
	}

	request, err := http.NewRequest(method, path, body)
	if err != nil {
		apiErr = errors.NewWithError("Error building request", err)
		return
	}

	if accessToken != "" {
		request.Header.Set("Authorization", accessToken)
	}

	request.Header.Set("accept", "application/json")
	request.Header.Set("content-type", "application/json")
	request.Header.Set("User-Agent", "go-cli "+cf.Version+" / "+runtime.GOOS)

	if body != nil {
		switch v := body.(type) {
		case *os.File:
			fileStats, err := v.Stat()
			if err != nil {
				break
			}
			request.ContentLength = fileStats.Size()
		}
	}

	req = &Request{HttpReq: request, SeekableBody: body}
	return
}

func (gateway Gateway) PerformRequest(request *Request) (rawResponse *http.Response, apiErr error) {
	return gateway.doRequestHandlingAuth(request)
}

func (gateway Gateway) performRequestForResponseBytes(request *Request) (bytes []byte, headers http.Header, rawResponse *http.Response, apiErr error) {
	rawResponse, apiErr = gateway.doRequestHandlingAuth(request)
	if apiErr != nil {
		return
	}

	bytes, err := ioutil.ReadAll(rawResponse.Body)
	if err != nil {
		apiErr = errors.NewWithError("Error reading response", err)
	}

	headers = rawResponse.Header
	return
}

func (gateway Gateway) PerformRequestForTextResponse(request *Request) (response string, headers http.Header, apiErr error) {
	bytes, headers, _, apiErr := gateway.performRequestForResponseBytes(request)
	response = string(bytes)
	return
}

func (gateway Gateway) PerformRequestForJSONResponse(request *Request, response interface{}) (headers http.Header, apiErr error) {
	bytes, headers, rawResponse, apiErr := gateway.performRequestForResponseBytes(request)
	if apiErr != nil {
		return
	}

	if rawResponse.StatusCode > 203 || strings.TrimSpace(string(bytes)) == "" {
		return
	}

	err := json.Unmarshal(bytes, &response)
	if err != nil {
		apiErr = errors.NewWithError("Invalid JSON response from server", err)
	}
	return
}

func (gateway Gateway) PerformPollingRequestForJSONResponse(request *Request, response interface{}, timeout time.Duration) (headers http.Header, apiErr error) {
	query := request.HttpReq.URL.Query()
	query.Add("async", "true")
	request.HttpReq.URL.RawQuery = query.Encode()

	bytes, headers, rawResponse, apiErr := gateway.performRequestForResponseBytes(request)
	if apiErr != nil {
		return
	}

	if rawResponse.StatusCode > 203 || strings.TrimSpace(string(bytes)) == "" {
		return
	}

	err := json.Unmarshal(bytes, &response)
	if err != nil {
		apiErr = errors.NewWithError("Invalid JSON response from server", err)
		return
	}

	asyncResponse := &AsyncResponse{}

	err = json.Unmarshal(bytes, &asyncResponse)
	if err != nil {
		apiErr = errors.NewWithError("Invalid async response from server", err)
		return
	}

	jobUrl := asyncResponse.Metadata.Url
	if jobUrl == "" {
		return
	}

	if !strings.Contains(jobUrl, "/jobs/") {
		return
	}

	jobUrl = fmt.Sprintf("%s://%s%s", request.HttpReq.URL.Scheme, request.HttpReq.URL.Host, asyncResponse.Metadata.Url)
	apiErr = gateway.waitForJob(jobUrl, request.HttpReq.Header.Get("Authorization"), timeout)

	return
}

func (gateway Gateway) waitForJob(jobUrl, accessToken string, timeout time.Duration) (err error) {
	startTime := time.Now()
	for true {
		if time.Since(startTime) > timeout {
			err = errors.NewWithFmt("Error: timed out waiting for async job '%s' to finish", jobUrl)
			return
		}

		var request *Request
		request, err = gateway.NewRequest("GET", jobUrl, accessToken, nil)
		response := &JobResponse{}

		_, err = gateway.PerformRequestForJSONResponse(request, response)
		if err != nil {
			return
		}

		switch response.Entity.Status {
		case JOB_FINISHED:
			return
		case JOB_FAILED:
			err = errors.New("Job failed")
			return
		}

		accessToken = request.HttpReq.Header.Get("Authorization")

		time.Sleep(gateway.PollingThrottle)
	}
	return
}

func (gateway Gateway) doRequestHandlingAuth(request *Request) (rawResponse *http.Response, err error) {
	httpReq := request.HttpReq

	if request.SeekableBody != nil {
		httpReq.Body = ioutil.NopCloser(request.SeekableBody)
	}

	// perform request
	rawResponse, err = gateway.doRequestAndHandlerError(request)
	if err == nil || gateway.authenticator == nil {
		return
	}

	switch err.(type) {
	case *errors.InvalidTokenError:
		// refresh the auth token
		var newToken string
		newToken, err = gateway.authenticator.RefreshAuthToken()
		if err != nil {
			return
		}

		// reset the auth token and request body
		httpReq.Header.Set("Authorization", newToken)
		if request.SeekableBody != nil {
			request.SeekableBody.Seek(0, 0)
			httpReq.Body = ioutil.NopCloser(request.SeekableBody)
		}

		// make the request again
		rawResponse, err = gateway.doRequestAndHandlerError(request)
	}

	return
}

func (gateway Gateway) doRequestAndHandlerError(request *Request) (rawResponse *http.Response, err error) {
	rawResponse, err = gateway.doRequest(request.HttpReq)
	if err != nil {
		err = WrapNetworkErrors(request.HttpReq.URL.Host, err)
		return
	}

	if rawResponse.StatusCode > 299 {
		jsonBytes, _ := ioutil.ReadAll(rawResponse.Body)
		rawResponse.Body.Close()
		rawResponse.Body = ioutil.NopCloser(bytes.NewBuffer(jsonBytes))
		err = gateway.errHandler(rawResponse.StatusCode, jsonBytes)
	}

	return
}

func (gateway Gateway) doRequest(request *http.Request) (response *http.Response, err error) {
	httpClient := newHttpClient(gateway.trustedCerts, gateway.config.IsSSLDisabled())

	dumpRequest(request)

	response, err = httpClient.Do(request)
	if err != nil {
		return
	}

	dumpResponse(response)
	return
}

func (gateway *Gateway) SetTrustedCerts(certificates []tls.Certificate) {
	gateway.trustedCerts = certificates
}
<commit_msg>Clean up resource structs in gateway<commit_after>package net

import (
	"bytes"
	"cf"
	"cf/configuration"
	"cf/errors"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"
)

const (
	JOB_FINISHED             = "finished"
	JOB_FAILED               = "failed"
	DEFAULT_POLLING_THROTTLE = 5 * time.Second
	ASYNC_REQUEST_TIMEOUT    = 20 * time.Second
)

type JobResource struct {
	Entity struct {
		Status string
	}
}

type AsyncResource struct {
	Metadata struct {
		URL string
	}
}

type apiErrorHandler func(statusCode int, body []byte) error

type tokenRefresher interface {
	RefreshAuthToken() (string, error)
}

type Request struct {
	HttpReq      *http.Request
	SeekableBody io.ReadSeeker
}

type Gateway struct {
	authenticator   tokenRefresher
	errHandler      apiErrorHandler
	PollingEnabled  bool
	PollingThrottle time.Duration
	trustedCerts    []tls.Certificate
	config          configuration.Reader
}

func newGateway(errHandler apiErrorHandler, config configuration.Reader) (gateway Gateway) {
	gateway.errHandler = errHandler
	gateway.config = config
	gateway.PollingThrottle = DEFAULT_POLLING_THROTTLE
	return
}

func (gateway *Gateway) SetTokenRefresher(auth tokenRefresher) {
	gateway.authenticator = auth
}

func (gateway Gateway) GetResource(url string, resource interface{}) (err error) {
	request, err := gateway.NewRequest("GET", url, gateway.config.AccessToken(), nil)
	if err != nil {
		return
	}

	_, err = gateway.PerformRequestForJSONResponse(request, resource)
	return
}

func (gateway Gateway) CreateResource(url string, body io.ReadSeeker, resource ...interface{}) (apiErr error) {
	return gateway.createUpdateOrDeleteResource("POST", url, body, false, resource...)
}

func (gateway Gateway) UpdateResource(url string, body io.ReadSeeker, resource ...interface{}) (apiErr error) {
	return gateway.createUpdateOrDeleteResource("PUT", url, body, false, resource...)
}

func (gateway Gateway) UpdateResourceSync(url string, body io.ReadSeeker, resource ...interface{}) (apiErr error) {
	return gateway.createUpdateOrDeleteResource("PUT", url, body, true, resource...)
}

func (gateway Gateway) DeleteResource(url string) (apiErr error) {
	return gateway.createUpdateOrDeleteResource("DELETE", url, nil, false, &AsyncResource{})
}

func (gateway Gateway) ListPaginatedResources(target string,
	path string,
	resource interface{},
	cb func(interface{}) bool) (apiErr error) {

	for path != "" {
		pagination := NewPaginatedResources(resource)
		apiErr = gateway.GetResource(fmt.Sprintf("%s%s", target, path), &pagination)
		if apiErr != nil {
			return
		}

		resources, err := pagination.Resources()
		if err != nil {
			return errors.NewWithError("Error parsing JSON", err)
		}

		for _, resource := range resources {
			if !cb(resource) {
				return
			}
		}

		path = pagination.NextURL
	}

	return
}

func (gateway Gateway) createUpdateOrDeleteResource(verb, url string, body io.ReadSeeker, sync bool, optionalResource ...interface{}) (apiErr error) {
	var resource interface{}
	if len(optionalResource) > 0 {
		resource = optionalResource[0]
	}

	request, apiErr := gateway.NewRequest(verb, url, gateway.config.AccessToken(), body)
	if apiErr != nil {
		return
	}

	if resource == nil {
		_, apiErr = gateway.PerformRequest(request)
		return
	}

	if gateway.PollingEnabled && !sync {
		_, apiErr = gateway.PerformPollingRequestForJSONResponse(request, resource, ASYNC_REQUEST_TIMEOUT)
		return
	} else {
		_, apiErr = gateway.PerformRequestForJSONResponse(request, resource)
		return
	}
}

func (gateway Gateway) NewRequest(method, path, accessToken string, body io.ReadSeeker) (req *Request, apiErr error) {
	if body != nil {
		body.Seek(0, 0)
	}

	request, err := http.NewRequest(method, path, body)
	if err != nil {
		apiErr = errors.NewWithError("Error building request", err)
		return
	}

	if accessToken != "" {
		request.Header.Set("Authorization", accessToken)
	}

	request.Header.Set("accept", "application/json")
	request.Header.Set("content-type", "application/json")
	request.Header.Set("User-Agent", "go-cli "+cf.Version+" / "+runtime.GOOS)

	if body != nil {
		switch v := body.(type) {
		case *os.File:
			fileStats, err := v.Stat()
			if err != nil {
				break
			}
			request.ContentLength = fileStats.Size()
		}
	}

	req = &Request{HttpReq: request, SeekableBody: body}
	return
}

func (gateway Gateway) PerformRequest(request *Request) (rawResponse *http.Response, apiErr error) {
	return gateway.doRequestHandlingAuth(request)
}

func (gateway Gateway) performRequestForResponseBytes(request *Request) (bytes []byte, headers http.Header, rawResponse *http.Response, apiErr error) {
	rawResponse, apiErr = gateway.doRequestHandlingAuth(request)
	if apiErr != nil {
		return
	}

	bytes, err := ioutil.ReadAll(rawResponse.Body)
	if err != nil {
		apiErr = errors.NewWithError("Error reading response", err)
	}

	headers = rawResponse.Header
	return
}

func (gateway Gateway) PerformRequestForTextResponse(request *Request) (response string, headers http.Header, apiErr error) {
	bytes, headers, _, apiErr := gateway.performRequestForResponseBytes(request)
	response = string(bytes)
	return
}

func (gateway Gateway) PerformRequestForJSONResponse(request *Request, response interface{}) (headers http.Header, apiErr error) {
	bytes, headers, rawResponse, apiErr := gateway.performRequestForResponseBytes(request)
	if apiErr != nil {
		return
	}

	if rawResponse.StatusCode > 203 || strings.TrimSpace(string(bytes)) == "" {
		return
	}

	err := json.Unmarshal(bytes, &response)
	if err != nil {
		apiErr = errors.NewWithError("Invalid JSON response from server", err)
	}
	return
}

func (gateway Gateway) PerformPollingRequestForJSONResponse(request *Request, response interface{}, timeout time.Duration) (headers http.Header, apiErr error) {
	query := request.HttpReq.URL.Query()
	query.Add("async", "true")
	request.HttpReq.URL.RawQuery = query.Encode()

	bytes, headers, rawResponse, apiErr := gateway.performRequestForResponseBytes(request)
	if apiErr != nil {
		return
	}

	if rawResponse.StatusCode > 203 || strings.TrimSpace(string(bytes)) == "" {
		return
	}

	err := json.Unmarshal(bytes, &response)
	if err != nil {
		apiErr = errors.NewWithError("Invalid JSON response from server", err)
		return
	}

	asyncResource := &AsyncResource{}
	err = json.Unmarshal(bytes, &asyncResource)
	if err != nil {
		apiErr = errors.NewWithError("Invalid async response from server", err)
		return
	}

	jobUrl := asyncResource.Metadata.URL
	if jobUrl == "" {
		return
	}

	if !strings.Contains(jobUrl, "/jobs/") {
		return
	}

	jobUrl = fmt.Sprintf("%s://%s%s", request.HttpReq.URL.Scheme, request.HttpReq.URL.Host, asyncResource.Metadata.URL)
	apiErr = gateway.waitForJob(jobUrl, request.HttpReq.Header.Get("Authorization"), timeout)

	return
}

func (gateway Gateway) waitForJob(jobUrl, accessToken string, timeout time.Duration) (err error) {
	startTime := time.Now()
	for true {
		if time.Since(startTime) > timeout {
			err = errors.NewWithFmt("Error: timed out waiting for async job '%s' to finish", jobUrl)
			return
		}

		var request *Request
		request, err = gateway.NewRequest("GET", jobUrl, accessToken, nil)
		response := &JobResource{}

		_, err = gateway.PerformRequestForJSONResponse(request, response)
		if err != nil {
			return
		}

		switch response.Entity.Status {
		case JOB_FINISHED:
			return
		case JOB_FAILED:
			err = errors.New("Job failed")
			return
		}

		accessToken = request.HttpReq.Header.Get("Authorization")

		time.Sleep(gateway.PollingThrottle)
	}
	return
}

func (gateway Gateway) doRequestHandlingAuth(request *Request) (rawResponse *http.Response, err error) {
	httpReq := request.HttpReq

	if request.SeekableBody != nil {
		httpReq.Body = ioutil.NopCloser(request.SeekableBody)
	}

	// perform request
	rawResponse, err = gateway.doRequestAndHandlerError(request)
	if err == nil || gateway.authenticator == nil {
		return
	}

	switch err.(type) {
	case *errors.InvalidTokenError:
		// refresh the auth token
		var newToken string
		newToken, err = gateway.authenticator.RefreshAuthToken()
		if err != nil {
			return
		}

		// reset the auth token and request body
		httpReq.Header.Set("Authorization", newToken)
		if request.SeekableBody != nil {
			request.SeekableBody.Seek(0, 0)
			httpReq.Body = ioutil.NopCloser(request.SeekableBody)
		}

		// make the request again
		rawResponse, err = gateway.doRequestAndHandlerError(request)
	}

	return
}

func (gateway Gateway) doRequestAndHandlerError(request *Request) (rawResponse *http.Response, err error) {
	rawResponse, err = gateway.doRequest(request.HttpReq)
	if err != nil {
		err = WrapNetworkErrors(request.HttpReq.URL.Host, err)
		return
	}

	if rawResponse.StatusCode > 299 {
		jsonBytes, _ := ioutil.ReadAll(rawResponse.Body)
		rawResponse.Body.Close()
		rawResponse.Body = ioutil.NopCloser(bytes.NewBuffer(jsonBytes))
		err = gateway.errHandler(rawResponse.StatusCode, jsonBytes)
	}

	return
}

func (gateway Gateway) doRequest(request *http.Request) (response *http.Response, err error) {
	httpClient := newHttpClient(gateway.trustedCerts, gateway.config.IsSSLDisabled())

	dumpRequest(request)

	response, err = httpClient.Do(request)
	if err != nil {
		return
	}

	dumpResponse(response)
	return
}

func (gateway *Gateway) SetTrustedCerts(certificates []tls.Certificate) {
	gateway.trustedCerts = certificates
}
<|endoftext|>


<commit_before>package mux

import (
	"bytes"
	"log"
	"net/http"
	"net/http/httptest"
	"testing"

	"encoding/json"

	"github.com/buger/jsonparser"
	"github.com/gorilla/mux"
)

var simpleBench = &benchmarkData{
	basePath: "/api/v1",

	patternGorilla: "/users/{id:[[:xdigit:]]{24}}",
	patternCustom:  "/users/:id",

	matchedPath: "/api/v1/users/599ce026ff64e74a60086508",

	pathParams: map[string]string{"id": "599ce026ff64e74a60086508"},
}

func BenchmarkSimpleGorilla(b *testing.B) {
	benchmark(b, simpleBench, initGorillaRouter(simpleBench))
}

func BenchmarkSimpleCustom(b *testing.B) {
	benchmark(b, simpleBench, initCustomRouter(simpleBench))
}

var nestedBench = &benchmarkData{
	basePath: "/api/v1",

	patternGorilla: "/projects/{project_id:[[:xdigit:]]{24}}/tasks/{task_id:[[:xdigit:]]{24}}",
	patternCustom:  "/projects/:project_id/tasks/:task_id",

	matchedPath: "/api/v1/projects/599ce026ff64e74a60086508/tasks/599ca654ff64e71ad83a1bc6",

	pathParams: map[string]string{
		"project_id": "599ce026ff64e74a60086508",
		"task_id":    "599ca654ff64e71ad83a1bc6",
	},
}

func BenchmarkNestedGorilla(b *testing.B) {
	benchmark(b, nestedBench, initGorillaRouter(nestedBench))
}

func BenchmarkNestedCustom(b *testing.B) {
	benchmark(b, nestedBench, initCustomRouter(nestedBench))
}

// Executes simple benchmark atop of given data
func benchmark(b *testing.B, data *benchmarkData, router http.Handler) {
	helper := newGetHelper(data.matchedPath)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		router.ServeHTTP(helper.w, helper.r)
		if !data.Ok(helper.w.Body.Bytes()) {
			b.Fatalf("%s", helper.w.Body.String())
		}
		helper.clear()
	}
}

// Helper structure to store bench data
type benchmarkData struct {
	basePath string

	patternGorilla string
	patternCustom  string

	matchedPath string

	pathParams map[string]string
}

func (b benchmarkData) Ok(response []byte) bool {
	for key, value := range b.pathParams {
		str, err := jsonparser.GetString(response, key)
		if err != nil {
			return false
		}
		if str != value {
			return false
		}
	}
	return true
}

// Helpers to work with gorilla router
func initGorillaRouter(data *benchmarkData) *mux.Router {
	router := mux.NewRouter()
	apiRouter := router.PathPrefix(data.basePath).Subrouter()

	apiRouter.Path(data.patternGorilla).Methods(http.MethodGet).HandlerFunc(
		func(w http.ResponseWriter, req *http.Request) {
			vars := mux.Vars(req)
			data, err := json.Marshal(vars)
			if err != nil {
				log.Panicf("can not marshal data: %v", err)
			}
			w.Write(data)
		})

	return router
}

// Helper to work with custom router
func initCustomRouter(data *benchmarkData) *router {
	router, err := NewRouter(data.basePath)
	if err != nil {
		log.Panicf("can not create router: %v", err)
	}

	err = router.Get(data.patternCustom, func(w http.ResponseWriter, req *http.Request) {
		params := Params(req)
		data, err := json.Marshal(params.PathParams)
		if err != nil {
			log.Panicf("can not marshal data: %v", err)
		}
		w.Write(data)
	})
	if err != nil {
		log.Panicf("%v", err)
	}

	return router
}

// Helper to mock get requests
type getHelper struct {
	w *httptest.ResponseRecorder
	r *http.Request
}

func newGetHelper(path string) *getHelper {
	r, _ := http.NewRequest(http.MethodGet, path, nil)

	return &getHelper{
		w: httptest.NewRecorder(),
		r: r,
	}
}

func (helper *getHelper) clear() {
	helper.w.Body = bytes.NewBuffer(make([]byte, 0, 100))
}
<commit_msg>Save custom json serializer.<commit_after>package mux

import (
	"bytes"
	"log"
	"net/http"
	"net/http/httptest"
	"testing"

	"encoding/json"

	"fmt"
	"strings"

	"github.com/buger/jsonparser"
	"github.com/gorilla/mux"
)

var simpleBench = &benchmarkData{
	basePath: "/api/v1",

	patternGorilla: "/users/{id:[[:xdigit:]]{24}}",
	patternCustom:  "/users/:id",

	matchedPath: "/api/v1/users/599ce026ff64e74a60086508",

	pathParams: map[string]string{"id": "599ce026ff64e74a60086508"},
}

func BenchmarkSimpleGorilla(b *testing.B) {
	benchmark(b, simpleBench, initGorillaRouter(simpleBench))
}

func BenchmarkSimpleCustom(b *testing.B) {
	benchmark(b, simpleBench, initCustomRouter(simpleBench))
}

var nestedBench = &benchmarkData{
	basePath: "/api/v1",

	patternGorilla: "/projects/{project_id:[[:xdigit:]]{24}}/tasks/{task_id:[[:xdigit:]]{24}}",
	patternCustom:  "/projects/:project_id/tasks/:task_id",

	matchedPath: "/api/v1/projects/599ce026ff64e74a60086508/tasks/599ca654ff64e71ad83a1bc6",

	pathParams: map[string]string{
		"project_id": "599ce026ff64e74a60086508",
		"task_id":    "599ca654ff64e71ad83a1bc6",
	},
}

func BenchmarkNestedGorilla(b *testing.B) {
	benchmark(b, nestedBench, initGorillaRouter(nestedBench))
}

func BenchmarkNestedCustom(b *testing.B) {
	benchmark(b, nestedBench, initCustomRouter(nestedBench))
}

// Executes simple benchmark atop of given data
func benchmark(b *testing.B, data *benchmarkData, router http.Handler) {
	helper := newGetHelper(data.matchedPath)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		router.ServeHTTP(helper.w, helper.r)
		if !data.Ok(helper.w.Body.Bytes()) {
			b.Fatalf("%s", helper.w.Body.String())
		}
		helper.clear()
	}
}

// Helper structure to store bench data
type benchmarkData struct {
	basePath string

	patternGorilla string
	patternCustom  string

	matchedPath string

	pathParams map[string]string
}

func (b benchmarkData) Ok(response []byte) bool {
	for key, value := range b.pathParams {
		str, err := jsonparser.GetString(response, key)
		if err != nil {
			return false
		}
		if str != value {
			return false
		}
	}
	return true
}

// Helpers to work with gorilla router
func initGorillaRouter(data *benchmarkData) *mux.Router {
	router := mux.NewRouter()
	apiRouter := router.PathPrefix(data.basePath).Subrouter()

	apiRouter.Path(data.patternGorilla).Methods(http.MethodGet).HandlerFunc(
		func(w http.ResponseWriter, req *http.Request) {
			vars := mux.Vars(req)

			data, err := json.Marshal(vars)
			if err != nil {
				log.Panicf("can not marshal data: %v", err)
			}
			w.Write(data)
		})

	return router
}

// Helper to work with custom router
func initCustomRouter(data *benchmarkData) *router {
	router, err := NewRouter(data.basePath)
	if err != nil {
		log.Panicf("can not create router: %v", err)
	}

	err = router.Get(data.patternCustom, func(w http.ResponseWriter, req *http.Request) {
		params := Params(req)
		data, err := json.Marshal(params.PathParams)
		if err != nil {
			log.Panicf("can not marshal data: %v", err)
		}
		w.Write(data)
	})
	if err != nil {
		log.Panicf("%v", err)
	}

	return router
}

// Converts flat map to json
func flatMapToJson(data map[string]string) []byte {
	template := fmt.Sprintf(`{%s}`, strings.Repeat(`"%s":"%s",`, len(data)))

	keys := make([]string, 0, len(data))
	values := make([]string, 0, len(data))

	for key, value := range data {
		keys = append(keys, key)
		values = append(values, value)
	}

	toInsert := make([]string, 0, 2*len(data))
	for i := 0; i < len(data); i++ {
		toInsert = append(toInsert, keys[i], values[i])
	}

	str := fmt.Sprintf(template, toInsert)
	return bytes.NewBufferString(str).Bytes()
}

// Helper to mock get requests
type getHelper struct {
	w *httptest.ResponseRecorder
	r *http.Request
}

func newGetHelper(path string) *getHelper {
	r, _ := http.NewRequest(http.MethodGet, path, nil)

	return &getHelper{
		w: httptest.NewRecorder(),
		r: r,
	}
}

func (helper *getHelper) clear() {
	helper.w.Body = bytes.NewBuffer(make([]byte, 0, 100))
}
<|endoftext|>


<commit_before>/*
 * This file is part of remco.
 * Based on code from confd. https://github.com/kelseyhightower/confd
 * © 2013 Kelsey Hightower
 * © 2016 The Remco Authors
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

package template

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/HeavyHorst/easyKV"
	"github.com/HeavyHorst/memkv"
	"github.com/HeavyHorst/pongo2"
	berr "github.com/HeavyHorst/remco/backends/error"
	"github.com/HeavyHorst/remco/log"
	"github.com/HeavyHorst/remco/template/fileutil"
	"github.com/Sirupsen/logrus"
)

// A BackendConfig - Every backend implements this interface. If Connect is called a new connection to the underlaying kv-store will be established.
// Connect should also set the name and the StoreClient of the Backend. The other values of Backend will be loaded from the configuration file.
type BackendConfig interface {
	Connect() (Backend, error)
}

// Backend is the representation of a template backend like etcd or consul
type Backend struct {
	easyKV.ReadWatcher
	Name     string
	Onetime  bool
	Watch    bool
	Prefix   string
	Interval int
	Keys     []string
	store    *memkv.Store
}

type Backends []Backend

// Close is calling the close method on all backends
func (b Backends) Close() {
	for _, v := range b {
		log.WithFields(logrus.Fields{
			"backend": v.Name,
		}).Debug("Closing client connection")
		v.ReadWatcher.Close()
	}
}

// Resource is the representation of a parsed template resource.
type Resource struct {
	backends []Backend
	funcMap  map[string]interface{}
	store    memkv.Store
	sources  []*ProcessConfig
}

// ErrEmptySrc is returned if an emty src template is passed to NewResource
var ErrEmptySrc = errors.New("empty src template")

// NewResource creates a Resource.
func NewResource(backends Backends, sources []*ProcessConfig) (*Resource, error) {
	if len(backends) == 0 {
		return nil, errors.New("A valid StoreClient is required.")
	}

	for _, v := range sources {
		if v.Src == "" {
			return nil, ErrEmptySrc
		}
	}

	tr := &Resource{
		backends: backends,
		store:    memkv.New(),
		funcMap:  newFuncMap(),
		sources:  sources,
	}

	// initialize the inidividual backend memkv Stores
	for i := range tr.backends {
		store := memkv.New()
		tr.backends[i].store = &store

		if tr.backends[i].Interval <= 0 && tr.backends[i].Onetime == false && tr.backends[i].Watch == false {
			log.Warning("Interval needs to be > 0: setting interval to 60")
			tr.backends[i].Interval = 60
		}
	}

	addFuncs(tr.funcMap, tr.store.FuncMap)

	return tr, nil
}

// setVars sets the Vars for a template resource.
func (t *Resource) setVars(storeClient Backend) error {
	var err error

	log.WithFields(logrus.Fields{
		"backend":    storeClient.Name,
		"key_prefix": storeClient.Prefix,
	}).Debug("Retrieving keys")

	result, err := storeClient.GetValues(appendPrefix(storeClient.Prefix, storeClient.Keys))
	if err != nil {
		return err
	}

	storeClient.store.Purge()

	for key, value := range result {
		storeClient.store.Set(path.Join("/", strings.TrimPrefix(key, storeClient.Prefix)), value)
	}

	//merge all stores
	t.store.Purge()
	for _, v := range t.backends {
		for _, kv := range v.store.GetAllKVs() {
			if t.store.Exists(kv.Key) {
				log.Warning("Key collision - " + kv.Key)
			}
			t.store.Set(kv.Key, kv.Value)
		}
	}

	return nil
}

// createStageFile stages the src configuration file by processing the src
// template and setting the desired owner, group, and mode. It also sets the
// StageFile for the template resource.
// It returns an error if any.
func (t *Resource) createStageFileAndSync() error {
	for _, s := range t.sources {
		if !fileutil.IsFileExist(s.Src) {
			return errors.New("Missing template: " + s.Src)
		}

		log.WithFields(logrus.Fields{
			"template": s.Src,
		}).Debug("Compiling source template")

		tmpl, err := pongo2.FromFile(s.Src)
		if err != nil {
			return fmt.Errorf("Unable to process template %s, %s", s.Src, err)
		}

		// create TempFile in Dest directory to avoid cross-filesystem issues
		temp, err := ioutil.TempFile(filepath.Dir(s.Dst), "."+filepath.Base(s.Dst))
		if err != nil {
			return err
		}

		if err = tmpl.ExecuteWriter(t.funcMap, temp); err != nil {
			temp.Close()
			os.Remove(temp.Name())
			return err
		}

		temp.Close()

		fileMode, err := s.getFileMode()
		if err != nil {
			return err
		}
		// Set the owner, group, and mode on the stage file now to make it easier to
		// compare against the destination configuration file later.
		os.Chmod(temp.Name(), fileMode)
		os.Chown(temp.Name(), s.UID, s.GID)
		s.stageFile = temp

		if err = t.sync(s); err != nil {
			return err
		}
	}
	return nil
}

// sync compares the staged and dest config files and attempts to sync them
// if they differ. sync will run a config check command if set before
// overwriting the target config file. Finally, sync will run a reload command
// if set to have the application or service pick up the changes.
// It returns an error if any.
func (t *Resource) sync(s *ProcessConfig) error {
	staged := s.stageFile.Name()
	defer os.Remove(staged)

	log.WithFields(logrus.Fields{
		"staged": path.Base(staged),
		"dest":   s.Dst,
	}).Debug("Comparing staged and dest config files")

	ok, err := fileutil.SameFile(staged, s.Dst)
	if err != nil {
		log.Error(err.Error())
	}

	if !ok {
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Info("Target config out of sync")

		if err := s.check(staged); err != nil {
			return errors.New("Config check failed: " + err.Error())
		}

		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Debug("Overwriting target config")

		fileMode, err := s.getFileMode()
		if err != nil {
			return err
		}
		if err := fileutil.ReplaceFile(staged, s.Dst, fileMode); err != nil {
			return err
		}

		// make sure owner and group match the temp file, in case the file was created with WriteFile
		os.Chown(s.Dst, s.UID, s.GID)

		if err := s.reload(); err != nil {
			return err
		}

		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Info("Target config has been updated")

	} else {
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Debug("Target config in sync")

	}
	return nil
}

// Process is a convenience function that wraps calls to the three main tasks
// required to keep local configuration files in sync. First we gather vars
// from the store, then we stage a candidate configuration file, and finally sync
// things up.
// It returns an error if any.
func (t *Resource) process(storeClients []Backend) error {
	for _, storeClient := range storeClients {
		if err := t.setVars(storeClient); err != nil {
			return berr.BackendError{
				Message: err.Error(),
				Backend: storeClient.Name,
			}
		}
	}
	if err := t.createStageFileAndSync(); err != nil {
		return err
	}
	return nil
}

func (s Backend) watch(ctx context.Context, processChan chan Backend) {
	var lastIndex uint64
	keysPrefix := appendPrefix(s.Prefix, s.Keys)

	for {
		select {
		case <-ctx.Done():
			return
		default:
			index, err := s.WatchPrefix(s.Prefix, ctx, easyKV.WithKeys(keysPrefix), easyKV.WithWaitIndex(lastIndex))
			if err != nil {
				if err != easyKV.ErrWatchCanceled {
					log.WithFields(logrus.Fields{
						"backend": s.Name,
					}).Error(err)
					time.Sleep(2 * time.Second)
				}
				continue
			}
			processChan <- s
			lastIndex = index
		}
	}
}

func (s Backend) interval(ctx context.Context, processChan chan Backend) {
	if s.Onetime {
		return
	}
	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(time.Duration(s.Interval) * time.Second):
			processChan <- s
		}
	}
}

// Monitor will start to monitor all given Backends for changes.
// It will process all given tamplates on changes.
func (t *Resource) Monitor(ctx context.Context) {
	wg := &sync.WaitGroup{}
	done := make(chan struct{})

	processChan := make(chan Backend)
	defer close(processChan)

	if err := t.process(t.backends); err != nil {
		switch err.(type) {
		case berr.BackendError:
			err := err.(berr.BackendError)
			log.WithFields(logrus.Fields{
				"backend": err.Backend,
			}).Error(err)
		default:
			log.Error(err)
		}
	}

	for _, sc := range t.backends {
		wg.Add(1)
		if sc.Watch {
			go func(s Backend) {
				defer wg.Done()
				s.watch(ctx, processChan)
			}(sc)
		} else {
			go func(s Backend) {
				defer wg.Done()
				s.interval(ctx, processChan)
			}(sc)
		}
	}

	go func() {
		// If there is no goroutine left - quit
		wg.Wait()
		close(done)
	}()

	for {
		select {
		case storeClient := <-processChan:
			if err := t.process([]Backend{storeClient}); err != nil {
				if _, ok := err.(berr.BackendError); ok {
					log.WithFields(logrus.Fields{
						"backend": storeClient.Name,
					}).Error(err)
				} else {
					log.Error(err)
				}
			}
		case <-ctx.Done():
			go func() {
				for range processChan {
				}
			}()
			wg.Wait()
			return
		case <-done:
			return
		}
	}
}
<commit_msg>fixed strange pongo2 race<commit_after>/*
 * This file is part of remco.
 * Based on code from confd. https://github.com/kelseyhightower/confd
 * © 2013 Kelsey Hightower
 * © 2016 The Remco Authors
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

package template

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/HeavyHorst/easyKV"
	"github.com/HeavyHorst/memkv"
	"github.com/HeavyHorst/pongo2"
	berr "github.com/HeavyHorst/remco/backends/error"
	"github.com/HeavyHorst/remco/log"
	"github.com/HeavyHorst/remco/template/fileutil"
	"github.com/Sirupsen/logrus"
)

// A BackendConfig - Every backend implements this interface. If Connect is called a new connection to the underlaying kv-store will be established.
// Connect should also set the name and the StoreClient of the Backend. The other values of Backend will be loaded from the configuration file.
type BackendConfig interface {
	Connect() (Backend, error)
}

// Backend is the representation of a template backend like etcd or consul
type Backend struct {
	easyKV.ReadWatcher
	Name     string
	Onetime  bool
	Watch    bool
	Prefix   string
	Interval int
	Keys     []string
	store    *memkv.Store
}

type Backends []Backend

// Close is calling the close method on all backends
func (b Backends) Close() {
	for _, v := range b {
		log.WithFields(logrus.Fields{
			"backend": v.Name,
		}).Debug("Closing client connection")
		v.ReadWatcher.Close()
	}
}

// Resource is the representation of a parsed template resource.
type Resource struct {
	backends []Backend
	funcMap  map[string]interface{}
	store    memkv.Store
	sources  []*ProcessConfig
}

// ErrEmptySrc is returned if an emty src template is passed to NewResource
var ErrEmptySrc = errors.New("empty src template")

// NewResource creates a Resource.
func NewResource(backends Backends, sources []*ProcessConfig) (*Resource, error) {
	if len(backends) == 0 {
		return nil, errors.New("A valid StoreClient is required.")
	}

	for _, v := range sources {
		if v.Src == "" {
			return nil, ErrEmptySrc
		}
	}

	tr := &Resource{
		backends: backends,
		store:    memkv.New(),
		funcMap:  newFuncMap(),
		sources:  sources,
	}

	// initialize the inidividual backend memkv Stores
	for i := range tr.backends {
		store := memkv.New()
		tr.backends[i].store = &store

		if tr.backends[i].Interval <= 0 && tr.backends[i].Onetime == false && tr.backends[i].Watch == false {
			log.Warning("Interval needs to be > 0: setting interval to 60")
			tr.backends[i].Interval = 60
		}
	}

	addFuncs(tr.funcMap, tr.store.FuncMap)

	return tr, nil
}

// setVars sets the Vars for a template resource.
func (t *Resource) setVars(storeClient Backend) error {
	var err error

	log.WithFields(logrus.Fields{
		"backend":    storeClient.Name,
		"key_prefix": storeClient.Prefix,
	}).Debug("Retrieving keys")

	result, err := storeClient.GetValues(appendPrefix(storeClient.Prefix, storeClient.Keys))
	if err != nil {
		return err
	}

	storeClient.store.Purge()

	for key, value := range result {
		storeClient.store.Set(path.Join("/", strings.TrimPrefix(key, storeClient.Prefix)), value)
	}

	//merge all stores
	t.store.Purge()
	for _, v := range t.backends {
		for _, kv := range v.store.GetAllKVs() {
			if t.store.Exists(kv.Key) {
				log.Warning("Key collision - " + kv.Key)
			}
			t.store.Set(kv.Key, kv.Value)
		}
	}

	return nil
}

// createStageFile stages the src configuration file by processing the src
// template and setting the desired owner, group, and mode. It also sets the
// StageFile for the template resource.
// It returns an error if any.
func (t *Resource) createStageFileAndSync() error {
	for _, s := range t.sources {
		if !fileutil.IsFileExist(s.Src) {
			return errors.New("Missing template: " + s.Src)
		}

		log.WithFields(logrus.Fields{
			"template": s.Src,
		}).Debug("Compiling source template")

		set := pongo2.NewSet("local", &pongo2.LocalFilesystemLoader{})
		tmpl, err := set.FromFile(s.Src)
		if err != nil {
			return fmt.Errorf("Unable to process template %s, %s", s.Src, err)
		}

		// create TempFile in Dest directory to avoid cross-filesystem issues
		temp, err := ioutil.TempFile(filepath.Dir(s.Dst), "."+filepath.Base(s.Dst))
		if err != nil {
			return err
		}

		if err = tmpl.ExecuteWriter(t.funcMap, temp); err != nil {
			temp.Close()
			os.Remove(temp.Name())
			return err
		}

		temp.Close()

		fileMode, err := s.getFileMode()
		if err != nil {
			return err
		}
		// Set the owner, group, and mode on the stage file now to make it easier to
		// compare against the destination configuration file later.
		os.Chmod(temp.Name(), fileMode)
		os.Chown(temp.Name(), s.UID, s.GID)
		s.stageFile = temp

		if err = t.sync(s); err != nil {
			return err
		}
	}
	return nil
}

// sync compares the staged and dest config files and attempts to sync them
// if they differ. sync will run a config check command if set before
// overwriting the target config file. Finally, sync will run a reload command
// if set to have the application or service pick up the changes.
// It returns an error if any.
func (t *Resource) sync(s *ProcessConfig) error {
	staged := s.stageFile.Name()
	defer os.Remove(staged)

	log.WithFields(logrus.Fields{
		"staged": path.Base(staged),
		"dest":   s.Dst,
	}).Debug("Comparing staged and dest config files")

	ok, err := fileutil.SameFile(staged, s.Dst)
	if err != nil {
		log.Error(err.Error())
	}

	if !ok {
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Info("Target config out of sync")

		if err := s.check(staged); err != nil {
			return errors.New("Config check failed: " + err.Error())
		}

		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Debug("Overwriting target config")

		fileMode, err := s.getFileMode()
		if err != nil {
			return err
		}
		if err := fileutil.ReplaceFile(staged, s.Dst, fileMode); err != nil {
			return err
		}

		// make sure owner and group match the temp file, in case the file was created with WriteFile
		os.Chown(s.Dst, s.UID, s.GID)

		if err := s.reload(); err != nil {
			return err
		}

		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Info("Target config has been updated")

	} else {
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Debug("Target config in sync")

	}
	return nil
}

// Process is a convenience function that wraps calls to the three main tasks
// required to keep local configuration files in sync. First we gather vars
// from the store, then we stage a candidate configuration file, and finally sync
// things up.
// It returns an error if any.
func (t *Resource) process(storeClients []Backend) error {
	for _, storeClient := range storeClients {
		if err := t.setVars(storeClient); err != nil {
			return berr.BackendError{
				Message: err.Error(),
				Backend: storeClient.Name,
			}
		}
	}
	if err := t.createStageFileAndSync(); err != nil {
		return err
	}
	return nil
}

func (s Backend) watch(ctx context.Context, processChan chan Backend) {
	var lastIndex uint64
	keysPrefix := appendPrefix(s.Prefix, s.Keys)

	for {
		select {
		case <-ctx.Done():
			return
		default:
			index, err := s.WatchPrefix(s.Prefix, ctx, easyKV.WithKeys(keysPrefix), easyKV.WithWaitIndex(lastIndex))
			if err != nil {
				if err != easyKV.ErrWatchCanceled {
					log.WithFields(logrus.Fields{
						"backend": s.Name,
					}).Error(err)
					time.Sleep(2 * time.Second)
				}
				continue
			}
			processChan <- s
			lastIndex = index
		}
	}
}

func (s Backend) interval(ctx context.Context, processChan chan Backend) {
	if s.Onetime {
		return
	}
	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(time.Duration(s.Interval) * time.Second):
			processChan <- s
		}
	}
}

// Monitor will start to monitor all given Backends for changes.
// It will process all given tamplates on changes.
func (t *Resource) Monitor(ctx context.Context) {
	wg := &sync.WaitGroup{}
	done := make(chan struct{})

	processChan := make(chan Backend)
	defer close(processChan)

	if err := t.process(t.backends); err != nil {
		switch err.(type) {
		case berr.BackendError:
			err := err.(berr.BackendError)
			log.WithFields(logrus.Fields{
				"backend": err.Backend,
			}).Error(err)
		default:
			log.Error(err)
		}
	}

	for _, sc := range t.backends {
		wg.Add(1)
		if sc.Watch {
			go func(s Backend) {
				defer wg.Done()
				s.watch(ctx, processChan)
			}(sc)
		} else {
			go func(s Backend) {
				defer wg.Done()
				s.interval(ctx, processChan)
			}(sc)
		}
	}

	go func() {
		// If there is no goroutine left - quit
		wg.Wait()
		close(done)
	}()

	for {
		select {
		case storeClient := <-processChan:
			if err := t.process([]Backend{storeClient}); err != nil {
				if _, ok := err.(berr.BackendError); ok {
					log.WithFields(logrus.Fields{
						"backend": storeClient.Name,
					}).Error(err)
				} else {
					log.Error(err)
				}
			}
		case <-ctx.Done():
			go func() {
				for range processChan {
				}
			}()
			wg.Wait()
			return
		case <-done:
			return
		}
	}
}
<|endoftext|>


<commit_before>// Package sanitize provides functions for sanitizing text.
package sanitize

import (
	"bytes"
	"html"
	"html/template"
	"io"
	"path"
	"regexp"
	"strings"

	parser "golang.org/x/net/html"
)

var (
	ignoreTags = []string{"title", "script", "style", "iframe", "frame", "frameset", "noframes", "noembed", "embed", "applet", "object", "base"}

	defaultTags = []string{"h1", "h2", "h3", "h4", "h5", "h6", "div", "span", "hr", "p", "br", "b", "i", "strong", "em", "ol", "ul", "li", "a", "img"}

	defaultAttributes = []string{"id", "class", "src", "href", "title", "alt", "name", "rel"}
)

// HTMLAllowing sanitizes html, allowing some tags.
// Arrays of allowed tags and allowed attributes may optionally be passed as the second and third arguments.
func HTMLAllowing(s string, args ...[]string) (string, error) {

	allowedTags := defaultTags
	if len(args) > 0 {
		allowedTags = args[0]
	}
	allowedAttributes := defaultAttributes
	if len(args) > 1 {
		allowedAttributes = args[1]
	}

	// Parse the html
	tokenizer := parser.NewTokenizer(strings.NewReader(s))

	buffer := bytes.NewBufferString("")
	ignore := ""

	for {
		tokenType := tokenizer.Next()
		token := tokenizer.Token()

		switch tokenType {

		case parser.ErrorToken:
			err := tokenizer.Err()
			if err == io.EOF {
				return buffer.String(), nil
			}
			return "", err

		case parser.StartTagToken:

			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = cleanAttributes(token.Attr, allowedAttributes)
				buffer.WriteString(token.String())
			} else if includes(ignoreTags, token.Data) {
				ignore = token.Data
			}

		case parser.SelfClosingTagToken:

			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = cleanAttributes(token.Attr, allowedAttributes)
				buffer.WriteString(token.String())
			} else if token.Data == ignore {
				ignore = ""
			}

		case parser.EndTagToken:
			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = []parser.Attribute{}
				buffer.WriteString(token.String())
			} else if token.Data == ignore {
				ignore = ""
			}

		case parser.TextToken:
			// We allow text content through, unless ignoring this entire tag and its contents (including other tags)
			if ignore == "" {
				buffer.WriteString(token.String())
			}
		case parser.CommentToken:
			// We ignore comments by default
		case parser.DoctypeToken:
			// We ignore doctypes by default - html5 does not require them and this is intended for sanitizing snippets of text
		default:
			// We ignore unknown token types by default

		}

	}

}

// HTML strips html tags, replace common entities, and escapes <>&;'" in the result.
// Note the returned text may contain entities as it is escaped by HTMLEscapeString, and most entities are not translated.
func HTML(s string) string {

	output := ""

	// Shortcut strings with no tags in them
	if !strings.ContainsAny(s, "<>") {
		output = s
	} else {

		rp, _ := regexp.Compile("(


<commit_before>package spice

import (
	"bytes"
	"crypto"
	"io"
	"reflect"
	"testing"

	"github.com/davecgh/go-spew/spew"
	"github.com/jsimonetti/go-spice/red"
)

func Test_readLinkPacket(t *testing.T) {
	type args struct {
		conn io.Reader
	}
	tests := []struct {
		name    string
		args    args
		want    []byte
		wantErr bool
	}{
		{
			name: "empty",
			args: args{
				bytes.NewBuffer(make([]byte, 0, 0)),
			},
			wantErr: true,
		},
		{
			name: "no packet",
			args: args{
				bytes.NewBuffer([]byte{
					0x52, 0x45, 0x44, 0x51, 0x02, 0x00, 0x00, 0x00,
					0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
				}),
			},
			wantErr: true,
		},
		{
			name: "1 byte",
			args: args{
				bytes.NewBuffer([]byte{
					0x52, 0x45, 0x44, 0x51, 0x02, 0x00, 0x00, 0x00,
					0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
					0xaa,
				}),
			},
			want: []byte{0xaa},
		},
		{
			name: "1 byte with extra",
			args: args{
				bytes.NewBuffer([]byte{
					0x52, 0x45, 0x44, 0x51, 0x02, 0x00, 0x00, 0x00,
					0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
					0xaa, 0xaa,
				}),
			},
			want: []byte{0xaa},
		},
		{
			name: "8 bytes",
			args: args{
				bytes.NewBuffer([]byte{
					0x52, 0x45, 0x44, 0x51, 0x02, 0x00, 0x00, 0x00,
					0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
					0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
				}),
			},
			want: []byte{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := readLinkPacket(tt.args.conn)
			if (err != nil) != tt.wantErr {
				t.Errorf("readLinkPacket() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !bytes.Equal(got, tt.want) {
				t.Errorf("readLinkPacket() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_sendServerTicket(t *testing.T) {
	type args struct {
		result red.ErrorCode
	}
	tests := []struct {
		name       string
		args       args
		wantWriter []byte
		wantErr    bool
	}{
		{
			name:       "empty",
			args:       args{},
			wantWriter: []byte{0x00, 0x00, 0x00, 0x00},
		},
		{
			name:       "ok",
			args:       args{red.ErrorOk},
			wantWriter: []byte{0x00, 0x00, 0x00, 0x00},
		},
		{
			name:       "denied",
			args:       args{red.ErrorPermissionDenied},
			wantWriter: []byte{0x07, 0x00, 0x00, 0x00},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			writer := bytes.NewBuffer(make([]byte, 0, 0))

			if err := sendServerTicket(tt.args.result, writer); (err != nil) != tt.wantErr {
				t.Errorf("sendServerTicket() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if gotWriter := writer.Bytes(); !bytes.Equal(gotWriter, tt.wantWriter) {
				spew.Dump(tt.wantWriter)
				t.Errorf("sendServerTicket() = %+#v, want %+#v", gotWriter, tt.wantWriter)
			}
		})
	}
}

func Test_sendServerLinkPacket(t *testing.T) {
	type args struct {
		key crypto.PublicKey
	}
	tests := []struct {
		name    string
		args    args
		wantWr  string
		wantErr bool
	}{
	// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			wr := &bytes.Buffer{}
			if err := sendServerLinkPacket(wr, tt.args.key); (err != nil) != tt.wantErr {
				t.Errorf("sendServerLinkPacket() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if gotWr := wr.String(); gotWr != tt.wantWr {
				t.Errorf("sendServerLinkPacket() = %v, want %v", gotWr, tt.wantWr)
			}
		})
	}
}

func Test_redPubKey(t *testing.T) {
	type args struct {
		key crypto.PublicKey
	}
	tests := []struct {
		name    string
		args    args
		wantRet red.PubKey
		wantErr bool
	}{
	// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotRet, err := redPubKey(tt.args.key)
			if (err != nil) != tt.wantErr {
				t.Errorf("redPubKey() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotRet, tt.wantRet) {
				t.Errorf("redPubKey() = %v, want %v", gotRet, tt.wantRet)
			}
		})
	}
}
<commit_msg>tenant: remove debug statement from test<commit_after>package spice

import (
	"bytes"
	"crypto"
	"io"
	"reflect"
	"testing"

	"github.com/jsimonetti/go-spice/red"
)

func Test_readLinkPacket(t *testing.T) {
	type args struct {
		conn io.Reader
	}
	tests := []struct {
		name    string
		args    args
		want    []byte
		wantErr bool
	}{
		{
			name: "empty",
			args: args{
				bytes.NewBuffer(make([]byte, 0, 0)),
			},
			wantErr: true,
		},
		{
			name: "no packet",
			args: args{
				bytes.NewBuffer([]byte{
					0x52, 0x45, 0x44, 0x51, 0x02, 0x00, 0x00, 0x00,
					0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
				}),
			},
			wantErr: true,
		},
		{
			name: "1 byte",
			args: args{
				bytes.NewBuffer([]byte{
					0x52, 0x45, 0x44, 0x51, 0x02, 0x00, 0x00, 0x00,
					0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
					0xaa,
				}),
			},
			want: []byte{0xaa},
		},
		{
			name: "1 byte with extra",
			args: args{
				bytes.NewBuffer([]byte{
					0x52, 0x45, 0x44, 0x51, 0x02, 0x00, 0x00, 0x00,
					0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
					0xaa, 0xaa,
				}),
			},
			want: []byte{0xaa},
		},
		{
			name: "8 bytes",
			args: args{
				bytes.NewBuffer([]byte{
					0x52, 0x45, 0x44, 0x51, 0x02, 0x00, 0x00, 0x00,
					0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
					0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
				}),
			},
			want: []byte{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := readLinkPacket(tt.args.conn)
			if (err != nil) != tt.wantErr {
				t.Errorf("readLinkPacket() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !bytes.Equal(got, tt.want) {
				t.Errorf("readLinkPacket() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_sendServerTicket(t *testing.T) {
	type args struct {
		result red.ErrorCode
	}
	tests := []struct {
		name       string
		args       args
		wantWriter []byte
		wantErr    bool
	}{
		{
			name:       "empty",
			args:       args{},
			wantWriter: []byte{0x00, 0x00, 0x00, 0x00},
		},
		{
			name:       "ok",
			args:       args{red.ErrorOk},
			wantWriter: []byte{0x00, 0x00, 0x00, 0x00},
		},
		{
			name:       "denied",
			args:       args{red.ErrorPermissionDenied},
			wantWriter: []byte{0x07, 0x00, 0x00, 0x00},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			writer := bytes.NewBuffer(make([]byte, 0, 0))

			if err := sendServerTicket(tt.args.result, writer); (err != nil) != tt.wantErr {
				t.Errorf("sendServerTicket() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if gotWriter := writer.Bytes(); !bytes.Equal(gotWriter, tt.wantWriter) {
				t.Errorf("sendServerTicket() = %+#v, want %+#v", gotWriter, tt.wantWriter)
			}
		})
	}
}

func Test_sendServerLinkPacket(t *testing.T) {
	type args struct {
		key crypto.PublicKey
	}
	tests := []struct {
		name    string
		args    args
		wantWr  string
		wantErr bool
	}{
	// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			wr := &bytes.Buffer{}
			if err := sendServerLinkPacket(wr, tt.args.key); (err != nil) != tt.wantErr {
				t.Errorf("sendServerLinkPacket() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if gotWr := wr.String(); gotWr != tt.wantWr {
				t.Errorf("sendServerLinkPacket() = %v, want %v", gotWr, tt.wantWr)
			}
		})
	}
}

func Test_redPubKey(t *testing.T) {
	type args struct {
		key crypto.PublicKey
	}
	tests := []struct {
		name    string
		args    args
		wantRet red.PubKey
		wantErr bool
	}{
	// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotRet, err := redPubKey(tt.args.key)
			if (err != nil) != tt.wantErr {
				t.Errorf("redPubKey() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotRet, tt.wantRet) {
				t.Errorf("redPubKey() = %v, want %v", gotRet, tt.wantRet)
			}
		})
	}
}
<|endoftext|>


<commit_before>package apidAnalytics

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/30x/apid"
	"github.com/apigee-labs/transicator/common"
)

const (
	LISTENER_TABLE_DATA_SCOPE = "edgex.data_scope"
)

var _ = Describe("ApigeeSync event", func() {

	var db apid.DB
	handler := handler{}

	BeforeEach(func() {
		db = getDB()

		config.Set(useCaching, true)

		snapshot := getDatascopeSnapshot()
		createTenantCache(&snapshot)
		Expect(len(tenantCache)).To(Equal(1))
	})

	AfterEach(func() {
		config.Set(useCaching, false)
		setDB(db)
	})

	Context("ApigeeSync snapshot event", func() {
		It("should set DB to appropriate version", func() {
			config.Set(useCaching, false)

			event := common.Snapshot{
				SnapshotInfo: "test_snapshot",
				Tables:       []common.Table{},
			}

			handler.Handle(&event)

			expectedDB, err := data.DBVersion(event.SnapshotInfo)
			Expect(err).NotTo(HaveOccurred())

			Expect(getDB() == expectedDB).Should(BeTrue())
		})

		It("should process a valid Snapshot", func() {
			event := common.Snapshot{
				SnapshotInfo: "test_snapshot_valid",
				Tables: []common.Table{
					{
						Name: LISTENER_TABLE_DATA_SCOPE,
						Rows: []common.Row{
							{
								"id":               &common.ColumnVal{Value: "i"},
								"_change_selector": &common.ColumnVal{Value: "c"},
								"apid_cluster_id":  &common.ColumnVal{Value: "a"},
								"scope":            &common.ColumnVal{Value: "s"},
								"org":              &common.ColumnVal{Value: "o"},
								"env":              &common.ColumnVal{Value: "e"},
								"created":          &common.ColumnVal{Value: "c"},
								"created_by":       &common.ColumnVal{Value: "c"},
								"updated":          &common.ColumnVal{Value: "u"},
								"updated_by":       &common.ColumnVal{Value: "u"},
							},
						},
					},
				},
			}

			handler.Handle(&event)
			tenant := tenantCache["i"]
			Expect(tenant.TenantId).To(Equal("s"))
			Expect(tenant.Org).To(Equal("o"))
			Expect(tenant.Env).To(Equal("e"))
		})
	})

	Context("Process changeList", func() {
		Context(LISTENER_TABLE_DATA_SCOPE, func() {
			It("insert/delete event should add/remove to/from cache if usecaching is true", func() {
				txn, err := getDB().Begin()
				Expect(err).ShouldNot(HaveOccurred())
				txn.Exec("INSERT INTO DATA_SCOPE (id, _change_selector, apid_cluster_id, scope, org, env) "+
					"VALUES"+
					"($1,$2,$3,$4,$5,$6)",
					"i2",
					"c2",
					"a2",
					"s2",
					"o2",
					"e2",
				)
				txn.Commit()

				insert := common.ChangeList{
					LastSequence: "test",
					Changes: []common.Change{
						{
							Operation: common.Insert,
							Table:     LISTENER_TABLE_DATA_SCOPE,
							NewRow: common.Row{
								"id":               &common.ColumnVal{Value: "i2"},
								"_change_selector": &common.ColumnVal{Value: "c2"},
								"apid_cluster_id":  &common.ColumnVal{Value: "a2"},
								"scope":            &common.ColumnVal{Value: "s2"},
								"org":              &common.ColumnVal{Value: "o2"},
								"env":              &common.ColumnVal{Value: "e2"},
								"created":          &common.ColumnVal{Value: "c2"},
								"created_by":       &common.ColumnVal{Value: "c2"},
								"updated":          &common.ColumnVal{Value: "u2"},
								"updated_by":       &common.ColumnVal{Value: "u2"},
							},
						},
					},
				}

				handler.Handle(&insert)
				tenant := tenantCache["i2"]
				Expect(tenant.TenantId).To(Equal("s2"))
				Expect(tenant.Org).To(Equal("o2"))
				Expect(tenant.Env).To(Equal("e2"))

				txn, err = getDB().Begin()
				Expect(err).ShouldNot(HaveOccurred())
				txn.Exec("DELETE FROM DATA_SCOPE where id = 'i2'")
				txn.Commit()

				delete := common.ChangeList{
					LastSequence: "test",
					Changes: []common.Change{
						{
							Operation: common.Delete,
							Table:     LISTENER_TABLE_DATA_SCOPE,
							OldRow:    insert.Changes[0].NewRow,
						},
					},
				}

				handler.Handle(&delete)
				_, exists := tenantCache["i2"]
				Expect(exists).To(BeFalse())
			})
		})
	})
})
<commit_msg>refactor apid-core -> apid for new file<commit_after>package apidAnalytics

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/30x/apid-core"
	"github.com/apigee-labs/transicator/common"
)

const (
	LISTENER_TABLE_DATA_SCOPE = "edgex.data_scope"
)

var _ = Describe("ApigeeSync event", func() {

	var db apid.DB
	handler := handler{}

	BeforeEach(func() {
		db = getDB()

		config.Set(useCaching, true)

		snapshot := getDatascopeSnapshot()
		createTenantCache(&snapshot)
		Expect(len(tenantCache)).To(Equal(1))
	})

	AfterEach(func() {
		config.Set(useCaching, false)
		setDB(db)
	})

	Context("ApigeeSync snapshot event", func() {
		It("should set DB to appropriate version", func() {
			config.Set(useCaching, false)

			event := common.Snapshot{
				SnapshotInfo: "test_snapshot",
				Tables:       []common.Table{},
			}

			handler.Handle(&event)

			expectedDB, err := data.DBVersion(event.SnapshotInfo)
			Expect(err).NotTo(HaveOccurred())

			Expect(getDB() == expectedDB).Should(BeTrue())
		})

		It("should process a valid Snapshot", func() {
			event := common.Snapshot{
				SnapshotInfo: "test_snapshot_valid",
				Tables: []common.Table{
					{
						Name: LISTENER_TABLE_DATA_SCOPE,
						Rows: []common.Row{
							{
								"id":               &common.ColumnVal{Value: "i"},
								"_change_selector": &common.ColumnVal{Value: "c"},
								"apid_cluster_id":  &common.ColumnVal{Value: "a"},
								"scope":            &common.ColumnVal{Value: "s"},
								"org":              &common.ColumnVal{Value: "o"},
								"env":              &common.ColumnVal{Value: "e"},
								"created":          &common.ColumnVal{Value: "c"},
								"created_by":       &common.ColumnVal{Value: "c"},
								"updated":          &common.ColumnVal{Value: "u"},
								"updated_by":       &common.ColumnVal{Value: "u"},
							},
						},
					},
				},
			}

			handler.Handle(&event)
			tenant := tenantCache["i"]
			Expect(tenant.TenantId).To(Equal("s"))
			Expect(tenant.Org).To(Equal("o"))
			Expect(tenant.Env).To(Equal("e"))
		})
	})

	Context("Process changeList", func() {
		Context(LISTENER_TABLE_DATA_SCOPE, func() {
			It("insert/delete event should add/remove to/from cache if usecaching is true", func() {
				txn, err := getDB().Begin()
				Expect(err).ShouldNot(HaveOccurred())
				txn.Exec("INSERT INTO DATA_SCOPE (id, _change_selector, apid_cluster_id, scope, org, env) "+
					"VALUES"+
					"($1,$2,$3,$4,$5,$6)",
					"i2",
					"c2",
					"a2",
					"s2",
					"o2",
					"e2",
				)
				txn.Commit()

				insert := common.ChangeList{
					LastSequence: "test",
					Changes: []common.Change{
						{
							Operation: common.Insert,
							Table:     LISTENER_TABLE_DATA_SCOPE,
							NewRow: common.Row{
								"id":               &common.ColumnVal{Value: "i2"},
								"_change_selector": &common.ColumnVal{Value: "c2"},
								"apid_cluster_id":  &common.ColumnVal{Value: "a2"},
								"scope":            &common.ColumnVal{Value: "s2"},
								"org":              &common.ColumnVal{Value: "o2"},
								"env":              &common.ColumnVal{Value: "e2"},
								"created":          &common.ColumnVal{Value: "c2"},
								"created_by":       &common.ColumnVal{Value: "c2"},
								"updated":          &common.ColumnVal{Value: "u2"},
								"updated_by":       &common.ColumnVal{Value: "u2"},
							},
						},
					},
				}

				handler.Handle(&insert)
				tenant := tenantCache["i2"]
				Expect(tenant.TenantId).To(Equal("s2"))
				Expect(tenant.Org).To(Equal("o2"))
				Expect(tenant.Env).To(Equal("e2"))

				txn, err = getDB().Begin()
				Expect(err).ShouldNot(HaveOccurred())
				txn.Exec("DELETE FROM DATA_SCOPE where id = 'i2'")
				txn.Commit()

				delete := common.ChangeList{
					LastSequence: "test",
					Changes: []common.Change{
						{
							Operation: common.Delete,
							Table:     LISTENER_TABLE_DATA_SCOPE,
							OldRow:    insert.Changes[0].NewRow,
						},
					},
				}

				handler.Handle(&delete)
				_, exists := tenantCache["i2"]
				Expect(exists).To(BeFalse())
			})
		})
	})
})
<|endoftext|>


<commit_before>package kodingcontext

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"path"

	"koding/kites/terraformer/kodingcontext/pkg"

	"github.com/hashicorp/terraform/plugin"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/cli"
)

const (
	terraformFileExt = ".tf"
	mainFileName     = "main"
	planFileName     = "plan"
)

var (
	ErrBaseDirNotSet   = errors.New("baseDir is not set")
	ErrVariablesNotSet = errors.New("Variables is not set")
)

type Context struct {
	Variables map[string]string

	// storage holds the plans of terraform
	Storage Storage

	baseDir      string
	providers    map[string]terraform.ResourceProviderFactory
	provisioners map[string]terraform.ResourceProvisionerFactory
	Buffer       *bytes.Buffer
	ui           *cli.PrefixedUi
}

func Init() (*Context, error) {

	config := pkg.BuiltinConfig
	if err := config.Discover(); err != nil {
		return nil, err
	}

	providers := config.ProviderFactories()
	provisioners := config.ProvisionerFactories()

	return NewContext(providers, provisioners), nil
}

func Close() {
	// Make sure we clean up any managed plugins at the end of this
	plugin.CleanupClients()
}

func NewContext(
	providers map[string]terraform.ResourceProviderFactory,
	provisioners map[string]terraform.ResourceProvisionerFactory,
) *Context {
	b := new(bytes.Buffer)

	return &Context{
		baseDir:      "",
		providers:    providers,
		provisioners: provisioners,
		id:           id.String(),
		Buffer:       b,
		ui:           NewUI(b),
	}
}

func (c *Context) Clone() *Context {
	return NewContext(c.providers, c.provisioners)
}

func (c *Context) TerraformContextOpts() *terraform.ContextOpts {
	return c.TerraformContextOptsWithPlan(nil)
}

func (c *Context) TerraformContextOptsWithPlan(p *terraform.Plan) *terraform.ContextOpts {
	if p == nil {
		p = &terraform.Plan{}
	}

	return &terraform.ContextOpts{
		Destroy:     false, // this should be true with kite.destroy command
		Parallelism: 0,

		Hooks: nil,

		// Targets      []string
		Module: p.Module,
		State:  p.State,
		Diff:   p.Diff,

		Providers:    c.providers,
		Provisioners: c.provisioners,
		Variables:    c.Variables,
	}
}

func (c *Context) Close() error {
	dir, err := c.Storage.BasePath()
	if err != nil {
		return err
	}

	return c.Storage.Clean(dir)
}

func (c *Context) createDirAndFile(content io.Reader) (dir string, err error) {
	dir, err = c.Storage.BasePath()
	if err != nil {
		return "", err
	}

	path := path.Join(dir, c.id+mainFileName+terraformFileExt)

	if err := c.Storage.Write(path, content); err != nil {
		return "", err
	}

	return dir, nil
}

// getBaseDir creates a new temp directory or returns the existing exclusive one
// for the current context
func (c *Context) getBaseDir() (string, error) {
	if c.baseDir != "" {
		return c.baseDir, nil
	}

	// create dir
	// calling TempDir simultaneously will not choose the same directory.
	dir, err := ioutil.TempDir("", "terraformer")
	if err != nil {
		return "", err
	}

	c.baseDir = dir

	return c.baseDir, nil
}
<commit_msg>Terraformer: use proper compatible extensions for terraform<commit_after>package kodingcontext

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"path"

	"koding/kites/terraformer/kodingcontext/pkg"

	"github.com/hashicorp/terraform/plugin"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/cli"
)

const (
	terraformFileExt      = ".tf"
	terraformPlanFileExt  = ".out"
	terraformStateFileExt = ".tfstate"
	mainFileName          = "main"
	planFileName          = "plan"
	stateFileName         = "state"
)

var (
	ErrBaseDirNotSet   = errors.New("baseDir is not set")
	ErrVariablesNotSet = errors.New("Variables is not set")
)

type Context struct {
	Variables map[string]string

	// storage holds the plans of terraform
	Storage Storage

	baseDir      string
	providers    map[string]terraform.ResourceProviderFactory
	provisioners map[string]terraform.ResourceProvisionerFactory
	Buffer       *bytes.Buffer
	ui           *cli.PrefixedUi
}

func Init() (*Context, error) {

	config := pkg.BuiltinConfig
	if err := config.Discover(); err != nil {
		return nil, err
	}

	providers := config.ProviderFactories()
	provisioners := config.ProvisionerFactories()

	return NewContext(providers, provisioners), nil
}

func Close() {
	// Make sure we clean up any managed plugins at the end of this
	plugin.CleanupClients()
}

func NewContext(
	providers map[string]terraform.ResourceProviderFactory,
	provisioners map[string]terraform.ResourceProvisionerFactory,
) *Context {
	b := new(bytes.Buffer)

	return &Context{
		baseDir:      "",
		providers:    providers,
		provisioners: provisioners,
		id:           id.String(),
		Buffer:       b,
		ui:           NewUI(b),
	}
}

func (c *Context) Clone() *Context {
	return NewContext(c.providers, c.provisioners)
}

func (c *Context) TerraformContextOpts() *terraform.ContextOpts {
	return c.TerraformContextOptsWithPlan(nil)
}

func (c *Context) TerraformContextOptsWithPlan(p *terraform.Plan) *terraform.ContextOpts {
	if p == nil {
		p = &terraform.Plan{}
	}

	return &terraform.ContextOpts{
		Destroy:     false, // this should be true with kite.destroy command
		Parallelism: 0,

		Hooks: nil,

		// Targets      []string
		Module: p.Module,
		State:  p.State,
		Diff:   p.Diff,

		Providers:    c.providers,
		Provisioners: c.provisioners,
		Variables:    c.Variables,
	}
}

func (c *Context) Close() error {
	dir, err := c.Storage.BasePath()
	if err != nil {
		return err
	}

	return c.Storage.Clean(dir)
}

func (c *Context) createDirAndFile(content io.Reader) (dir string, err error) {
	dir, err = c.Storage.BasePath()
	if err != nil {
		return "", err
	}

	path := path.Join(dir, c.id+mainFileName+terraformFileExt)

	if err := c.Storage.Write(path, content); err != nil {
		return "", err
	}

	return dir, nil
}

// getBaseDir creates a new temp directory or returns the existing exclusive one
// for the current context
func (c *Context) getBaseDir() (string, error) {
	if c.baseDir != "" {
		return c.baseDir, nil
	}

	// create dir
	// calling TempDir simultaneously will not choose the same directory.
	dir, err := ioutil.TempDir("", "terraformer")
	if err != nil {
		return "", err
	}

	c.baseDir = dir

	return c.baseDir, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"koding/db/mongodb/modelhelper"
	"socialapi/config"
	"socialapi/models"
	"socialapi/workers/team"

	"github.com/koding/runner"
)

var (
	// Name holds the worker name
	Name = "Participant Cleaner"
)

func main() {
	r := runner.New(Name)
	if err := r.Init(); err != nil {
		fmt.Println(err)
		return
	}
	// init mongo connection
	appConfig := config.MustRead(r.Conf.Path)
	modelhelper.Initialize(appConfig.Mongo)
	defer modelhelper.Close()

	// get participants
	cps := fetchPartcipantsWithNick()

	// init controller
	controller := team.NewController(r.Log, appConfig)

	// TODO
	// iterate over participants and remove each of participant
	for _, cp := range cps {
		_ = controller.HandleParticipant(cp)
	}

}

func fetchPartcipantsWithNick() []*models.ChannelParticipant {
	return nil
}
<commit_msg>socialapi: remove participant cleaner folder(unused)<commit_after><|endoftext|>


<commit_before>package gron

import (
	"time"

	"github.com/roylee0704/gron/xtime"
)

// Schedule is the interface that wraps the basic Next method.
//
// Next deduces next occuring time based on t and underlying states.
type Schedule interface {
	Next(t time.Time) time.Time
}

// Every returns a Schedule reoccurs every period p, p must be at least
// time.Second.
func Every(p time.Duration) Schedule {

	if p < time.Second {
		p = xtime.Second
	}

	p = p - time.Duration(p.Nanoseconds())%time.Second // truncates up to seconds

	return &periodicSchedule{
		period: p,
	}
}

type periodicSchedule struct {
	period time.Duration
}

// Next adds time t to underlying period, truncates up to unit of seconds.
func (ps periodicSchedule) Next(t time.Time) time.Time {
	return t.Truncate(time.Second).Add(ps.period)
}
<commit_msg>include atSchedule<commit_after>package gron

import (
	"time"

	"github.com/roylee0704/gron/xtime"
)

// Schedule is the interface that wraps the basic Next method.
//
// Next deduces next occuring time based on t and underlying states.
type Schedule interface {
	Next(t time.Time) time.Time
}

// Every returns a Schedule reoccurs every period p, p must be at least
// time.Second.
func Every(p time.Duration) Schedule {

	if p < time.Second {
		p = xtime.Second
	}

	p = p - time.Duration(p.Nanoseconds())%time.Second // truncates up to seconds

	return &periodicSchedule{
		period: p,
	}
}

type periodicSchedule struct {
	period time.Duration
}

// Next adds time t to underlying period, truncates up to unit of seconds.
func (ps periodicSchedule) Next(t time.Time) time.Time {
	return t.Truncate(time.Second).Add(ps.period)
}

// At returns a schedule which reoccurs every period p, at time t.
//
// Note: At panics when period p is less than xtime.Day
func (ps periodicSchedule) At(t string) Schedule {
	if ps.period < xtime.Day {
		panic("period must at least in days")
	}

	return &atSchedule{}
}

type atSchedule struct {
	period time.Duration
}

func (as atSchedule) Next(t time.Time) time.Time {
	return time.Time{}
}
<|endoftext|>


<commit_before>package tutum

import (
	"encoding/json"
	"log"
	"net/url"

	"code.google.com/p/go.net/websocket"
)

/*
func ListContainers
Returns : Array of Container objects
*/
func ListContainers() (CListResponse, error) {

	url := "container/"
	request := "GET"
	//Empty Body Request
	body := []byte(`{}`)
	var response CListResponse
	data, err := TutumCall(url, request, body)
	if err != nil {
		return response, err
	}
	err = json.Unmarshal(data, &response)
	if err != nil {
		return response, err
	}
	return response, nil
}

/*
func GetContainer
Argument : uuid
Returns : Container JSON object
*/
func GetContainer(uuid string) (Container, error) {

	url := ""
	if string(uuid[0]) == "/" {
		url = uuid[8:]
	} else {
		url = "container/" + uuid + "/"
	}

	request := "GET"
	//Empty Body Request
	body := []byte(`{}`)
	var response Container

	data, err := TutumCall(url, request, body)
	if err != nil {
		return response, err
	}

	err = json.Unmarshal(data, &response)
	if err != nil {
		return response, err
	}

	return response, nil
}

/*
func GetContainerLogs
Argument : a channel of type string for the output
*/
func (self *Container) Logs(c chan string) {

	endpoint := "container/" + self.Uuid + "/logs/?user=" + User + "&token=" + ApiKey
	origin := "http://localhost/"
	url := "wss://live-test.tutum.co:443/v1/" + endpoint
	ws, err := websocket.Dial(url, "", origin)

	if err != nil {
		log.Println(err)
	}

	msg := make([]byte, 512)

	for {
		ws.Request()
		if _, err = ws.Read(msg); err != nil {
			log.Println(err)
		}
		c <- string(msg)
	}
}

/*
func Exec
Arguments : the command to execute, a channel of type string for the output
*/

func (self *Container) Exec(command string, c chan string) {
	go self.Run(command, c)
Loop:
	for {
		select {
		case s := <-c:
			if s != "EOF" {
				log.Println(s)
			} else {
				break Loop
			}
		}
	}
}

func (self *Container) Run(command string, c chan string) {

	endpoint := "container/" + self.Uuid + "/exec/?user=" + User + "&token=" + ApiKey + "&command=" + url.QueryEscape(command)
	log.Println(endpoint)
	origin := "http://localhost/"
	url := "wss://live-test.tutum.co:443/v1/" + endpoint
	ws, err := websocket.Dial(url, "", origin)

	if err != nil {
		if err.Error() != "EOF" {
			log.Println(err)
		}
	}

	msg := make([]byte, 1024)

	for {
		select {
		case input := <-c:
			log.Println(input)
			//self.Exec(input, c)
		default:
			if _, err = ws.Read(msg); err != nil {
				if err.Error() == "EOF" {
					c <- err.Error()
				}
			}
			c <- string(msg)
		}

	}
}

/*
func StartContainer
Returns : Error
*/
func (self *Container) Start() error {

	url := "container/" + self.Uuid + "/start/"
	request := "POST"
	//Empty Body Request
	body := []byte(`{}`)
	var response Container

	data, err := TutumCall(url, request, body)
	if err != nil {
		return err
	}

	err = json.Unmarshal(data, &response)
	if err != nil {
		return err
	}

	return nil
}

/*
func StopContainer
Returns : Error
*/
func (self *Container) Stop() error {

	url := "container/" + self.Uuid + "/stop/"
	request := "POST"
	//Empty Body Request
	body := []byte(`{}`)

	_, err := TutumCall(url, request, body)
	if err != nil {
		return err
	}

	return nil
}

/*
func RedeployContainer
Returns : Error
*/
func (self *Container) Redeploy() error {

	url := "container/" + self.Uuid + "/redeploy/"
	request := "POST"
	//Empty Body Request
	body := []byte(`{}`)

	_, err := TutumCall(url, request, body)
	if err != nil {
		return err
	}

	return nil
}

/*
func TerminateContainer
Returns : Error
*/
func (self *Container) Terminate() error {

	url := "container/" + self.Uuid + "/"
	request := "DELETE"
	//Empty Body Request
	body := []byte(`{}`)

	_, err := TutumCall(url, request, body)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>remove useless case<commit_after>package tutum

import (
	"encoding/json"
	"log"
	"net/url"

	"code.google.com/p/go.net/websocket"
)

/*
func ListContainers
Returns : Array of Container objects
*/
func ListContainers() (CListResponse, error) {

	url := "container/"
	request := "GET"
	//Empty Body Request
	body := []byte(`{}`)
	var response CListResponse
	data, err := TutumCall(url, request, body)
	if err != nil {
		return response, err
	}
	err = json.Unmarshal(data, &response)
	if err != nil {
		return response, err
	}
	return response, nil
}

/*
func GetContainer
Argument : uuid
Returns : Container JSON object
*/
func GetContainer(uuid string) (Container, error) {

	url := ""
	if string(uuid[0]) == "/" {
		url = uuid[8:]
	} else {
		url = "container/" + uuid + "/"
	}

	request := "GET"
	//Empty Body Request
	body := []byte(`{}`)
	var response Container

	data, err := TutumCall(url, request, body)
	if err != nil {
		return response, err
	}

	err = json.Unmarshal(data, &response)
	if err != nil {
		return response, err
	}

	return response, nil
}

/*
func GetContainerLogs
Argument : a channel of type string for the output
*/
func (self *Container) Logs(c chan string) {

	endpoint := "container/" + self.Uuid + "/logs/?user=" + User + "&token=" + ApiKey
	origin := "http://localhost/"
	url := "wss://live-test.tutum.co:443/v1/" + endpoint
	ws, err := websocket.Dial(url, "", origin)

	if err != nil {
		log.Println(err)
	}

	msg := make([]byte, 512)

	for {
		ws.Request()
		if _, err = ws.Read(msg); err != nil {
			log.Println(err)
		}
		c <- string(msg)
	}
}

/*
func Exec
Arguments : the command to execute, a channel of type string for the output
*/

func (self *Container) Exec(command string, c chan string) {
	go self.Run(command, c)
Loop:
	for {
		select {
		case s := <-c:
			if s != "EOF" {
				log.Println(s)
			} else {
				break Loop
			}
		}
	}
}

func (self *Container) Run(command string, c chan string) {

	endpoint := "container/" + self.Uuid + "/exec/?user=" + User + "&token=" + ApiKey + "&command=" + url.QueryEscape(command)
	log.Println(endpoint)
	origin := "http://localhost/"
	url := "wss://live-test.tutum.co:443/v1/" + endpoint
	ws, err := websocket.Dial(url, "", origin)

	if err != nil {
		if err.Error() != "EOF" {
			log.Println(err)
		}
	}

	msg := make([]byte, 1024)

	for {
		if _, err = ws.Read(msg); err != nil {
			if err.Error() == "EOF" {
				c <- err.Error()
			}
		}
		c <- string(msg)

	}
}

/*
func StartContainer
Returns : Error
*/
func (self *Container) Start() error {

	url := "container/" + self.Uuid + "/start/"
	request := "POST"
	//Empty Body Request
	body := []byte(`{}`)
	var response Container

	data, err := TutumCall(url, request, body)
	if err != nil {
		return err
	}

	err = json.Unmarshal(data, &response)
	if err != nil {
		return err
	}

	return nil
}

/*
func StopContainer
Returns : Error
*/
func (self *Container) Stop() error {

	url := "container/" + self.Uuid + "/stop/"
	request := "POST"
	//Empty Body Request
	body := []byte(`{}`)

	_, err := TutumCall(url, request, body)
	if err != nil {
		return err
	}

	return nil
}

/*
func RedeployContainer
Returns : Error
*/
func (self *Container) Redeploy() error {

	url := "container/" + self.Uuid + "/redeploy/"
	request := "POST"
	//Empty Body Request
	body := []byte(`{}`)

	_, err := TutumCall(url, request, body)
	if err != nil {
		return err
	}

	return nil
}

/*
func TerminateContainer
Returns : Error
*/
func (self *Container) Terminate() error {

	url := "container/" + self.Uuid + "/"
	request := "DELETE"
	//Empty Body Request
	body := []byte(`{}`)

	_, err := TutumCall(url, request, body)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>