<commit_before>package main

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	kingpin "gopkg.in/alecthomas/kingpin.v2"

	"github.com/ShowMax/go-fqdn"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/common/promlog"
	"github.com/prometheus/common/promlog/flag"
	"github.com/robustperception/pushprox/util"
)

var (
	myFqdn   = kingpin.Flag("fqdn", "FQDN to register with").Default(fqdn.Get()).String()
	proxyURL = kingpin.Flag("proxy-url", "Push proxy to talk to.").Required().String()
)

type Coordinator struct {
	logger log.Logger
}

func (c *Coordinator) doScrape(request *http.Request, client *http.Client) {
	logger := log.With(c.logger, "scrape_id", request.Header.Get("id"))
	ctx, _ := context.WithTimeout(request.Context(), util.GetScrapeTimeout(request.Header))
	request = request.WithContext(ctx)
	// We cannot handle http requests at the proxy, as we would only
	// see a CONNECT, so use a URL parameter to trigger it.
	params := request.URL.Query()
	if params.Get("_scheme") == "https" {
		request.URL.Scheme = "https"
		params.Del("_scheme")
		request.URL.RawQuery = params.Encode()
	}

	scrapeResp, err := client.Do(request)
	if err != nil {
		msg := fmt.Sprintf("Failed to scrape %s: %s", request.URL.String(), err)
		level.Warn(logger).Log("msg", "Failed to scrape", "Request URL", request.URL.String(), "err", err)
		resp := &http.Response{
			StatusCode: 500,
			Header:     http.Header{},
			Body:       ioutil.NopCloser(strings.NewReader(msg)),
		}
		err = c.doPush(resp, request, client)
		if err != nil {
			level.Warn(logger).Log("msg", "Failed to push failed scrape response:", "err", err)
			return
		}
		level.Info(logger).Log("msg", "Pushed failed scrape response")
		return
	}
	level.Info(logger).Log("msg", "Retrieved scrape response")
	err = c.doPush(scrapeResp, request, client)
	if err != nil {
		level.Warn(logger).Log("msg", "Failed to push scrape response:", "err", err)
		return
	}
	level.Info(logger).Log("msg", "Pushed scrape result")
}

// Report the result of the scrape back up to the proxy.
func (c *Coordinator) doPush(resp *http.Response, origRequest *http.Request, client *http.Client) error {
	resp.Header.Set("id", origRequest.Header.Get("id")) // Link the request and response
	// Remaining scrape deadline.
	deadline, _ := origRequest.Context().Deadline()
	resp.Header.Set("X-Prometheus-Scrape-Timeout", fmt.Sprintf("%f", float64(time.Until(deadline))/1e9))

	u, _ := url.Parse(*proxyURL + "/push")

	buf := &bytes.Buffer{}
	resp.Write(buf)
	request := &http.Request{
		Method:        "POST",
		URL:           u,
		Body:          ioutil.NopCloser(buf),
		ContentLength: int64(buf.Len()),
	}
	request = request.WithContext(origRequest.Context())
	_, err := client.Do(request)
	if err != nil {
		return err
	}
	return nil
}

func loop(c Coordinator) {
	client := &http.Client{}
	resp, err := client.Post(*proxyURL+"/poll", "", strings.NewReader(*myFqdn))
	if err != nil {
		level.Error(c.logger).Log("msg", "Error polling:", "err", err)
		time.Sleep(time.Second) // Don't pound the server. TODO: Randomised exponential backoff.
		return
	}
	defer resp.Body.Close()
	request, _ := http.ReadRequest(bufio.NewReader(resp.Body))
	level.Info(c.logger).Log("msg", "Got scrape request", "scrape_id", request.Header.Get("id"), "url", request.URL)

	request.RequestURI = ""

	go c.doScrape(request, client)
}

func main() {
	allowedLevel := promlog.AllowedLevel{}
	flag.AddFlags(kingpin.CommandLine, &allowedLevel)
	kingpin.HelpFlag.Short('h')
	kingpin.Parse()
	logger := promlog.New(allowedLevel)
	coordinator := Coordinator{logger: logger}
	if *proxyURL == "" {
		level.Error(coordinator.logger).Log("msg", "-proxy-url flag must be specified.")
		os.Exit(1)
	}
	level.Info(coordinator.logger).Log("msg", "URL and FQDN info", "proxy_url", *proxyURL, "Using FQDN of", *myFqdn)
	for {
		loop(coordinator)
	}
}
<commit_msg>Fixes incorrect joining of paths<commit_after>package main

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	kingpin "gopkg.in/alecthomas/kingpin.v2"

	"github.com/ShowMax/go-fqdn"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/common/promlog"
	"github.com/prometheus/common/promlog/flag"
	"github.com/robustperception/pushprox/util"
)

var (
	myFqdn   = kingpin.Flag("fqdn", "FQDN to register with").Default(fqdn.Get()).String()
	proxyURL = kingpin.Flag("proxy-url", "Push proxy to talk to.").Required().String()
)

type Coordinator struct {
	logger log.Logger
}

func (c *Coordinator) doScrape(request *http.Request, client *http.Client) {
	logger := log.With(c.logger, "scrape_id", request.Header.Get("id"))
	ctx, _ := context.WithTimeout(request.Context(), util.GetScrapeTimeout(request.Header))
	request = request.WithContext(ctx)
	// We cannot handle http requests at the proxy, as we would only
	// see a CONNECT, so use a URL parameter to trigger it.
	params := request.URL.Query()
	if params.Get("_scheme") == "https" {
		request.URL.Scheme = "https"
		params.Del("_scheme")
		request.URL.RawQuery = params.Encode()
	}

	scrapeResp, err := client.Do(request)
	if err != nil {
		msg := fmt.Sprintf("Failed to scrape %s: %s", request.URL.String(), err)
		level.Warn(logger).Log("msg", "Failed to scrape", "Request URL", request.URL.String(), "err", err)
		resp := &http.Response{
			StatusCode: 500,
			Header:     http.Header{},
			Body:       ioutil.NopCloser(strings.NewReader(msg)),
		}
		err = c.doPush(resp, request, client)
		if err != nil {
			level.Warn(logger).Log("msg", "Failed to push failed scrape response:", "err", err)
			return
		}
		level.Info(logger).Log("msg", "Pushed failed scrape response")
		return
	}
	level.Info(logger).Log("msg", "Retrieved scrape response")
	err = c.doPush(scrapeResp, request, client)
	if err != nil {
		level.Warn(logger).Log("msg", "Failed to push scrape response:", "err", err)
		return
	}
	level.Info(logger).Log("msg", "Pushed scrape result")
}

// Report the result of the scrape back up to the proxy.
func (c *Coordinator) doPush(resp *http.Response, origRequest *http.Request, client *http.Client) error {
	resp.Header.Set("id", origRequest.Header.Get("id")) // Link the request and response
	// Remaining scrape deadline.
	deadline, _ := origRequest.Context().Deadline()
	resp.Header.Set("X-Prometheus-Scrape-Timeout", fmt.Sprintf("%f", float64(time.Until(deadline))/1e9))

	base, err := url.Parse(*proxyURL)
	if err != nil {
		return err
	}
	u, err := url.Parse("/post")
	if err != nil {
		return err
	}
	url := base.ResolveReference(u)

	buf := &bytes.Buffer{}
	resp.Write(buf)
	request := &http.Request{
		Method:        "POST",
		URL:           url,
		Body:          ioutil.NopCloser(buf),
		ContentLength: int64(buf.Len()),
	}
	request = request.WithContext(origRequest.Context())
	_, err = client.Do(request)
	if err != nil {
		return err
	}
	return nil
}

func loop(c Coordinator) {
	client := &http.Client{}
	base, err := url.Parse(*proxyURL)
	if err != nil {
		level.Error(c.logger).Log("msg", "Error parsing url:", "err", err)
		return
	}
	u, err := url.Parse("/poll")
	if err != nil {
		level.Error(c.logger).Log("msg", "Error parsing url:", "err", err)
		return
	}
	url := base.ResolveReference(u)
	resp, err := client.Post(url.String(), "", strings.NewReader(*myFqdn))
	if err != nil {
		level.Error(c.logger).Log("msg", "Error polling:", "err", err)
		time.Sleep(time.Second) // Don't pound the server. TODO: Randomised exponential backoff.
		return
	}
	defer resp.Body.Close()
	request, _ := http.ReadRequest(bufio.NewReader(resp.Body))
	level.Info(c.logger).Log("msg", "Got scrape request", "scrape_id", request.Header.Get("id"), "url", request.URL)

	request.RequestURI = ""

	go c.doScrape(request, client)
}

func main() {
	allowedLevel := promlog.AllowedLevel{}
	flag.AddFlags(kingpin.CommandLine, &allowedLevel)
	kingpin.HelpFlag.Short('h')
	kingpin.Parse()
	logger := promlog.New(allowedLevel)
	coordinator := Coordinator{logger: logger}
	if *proxyURL == "" {
		level.Error(coordinator.logger).Log("msg", "-proxy-url flag must be specified.")
		os.Exit(1)
	}
	level.Info(coordinator.logger).Log("msg", "URL and FQDN info", "proxy_url", *proxyURL, "Using FQDN of", *myFqdn)
	for {
		loop(coordinator)
	}
}
<|endoftext|>


<commit_before>package client

import (
	"github.com/mitchellh/mapstructure"
	"github.com/regner/albiondata-client/log"
)

func decodeRequest(params map[string]interface{}) operation {
	if _, ok := params["253"]; !ok {
		return nil
	}

	code := params["253"].(int16)

	switch code {
	case 10:
		operation := operationGetGameServerByCluster{}
		mapstructure.Decode(params, &operation)

		return operation
	case 67:
		operation := operationAuctionGetOffers{}
		mapstructure.Decode(params, &operation)

		return operation
	case 166:
		operation := operationGetClusterMapInfo{}
		mapstructure.Decode(params, &operation)

		return operation
	case 217:
		operation := operationGoldMarketGetAverageInfo{}
		mapstructure.Decode(params, &operation)

		return operation
	case 232:
		operation := operationRealEstateGetAuctionData{}
		mapstructure.Decode(params, &operation)

		return operation
	case 233:
		operation := operationRealEstateBidOnAuction{}
		mapstructure.Decode(params, &operation)

		return operation
	}

	return nil
}

func decodeResponse(params map[string]interface{}) operation {
	if _, ok := params["253"]; !ok {
		return nil
	}

	code := params["253"].(int16)

	switch code {
	case 67:
		operation := operationAuctionGetOffersResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 68:
		operation := operationAuctionGetRequestsResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 166:
		operation := operationGetClusterMapInfoResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 217:
		operation := operationGoldMarketGetAverageInfoResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 232:
		operation := operationRealEstateGetAuctionDataResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 233:
		operation := operationRealEstateBidOnAuctionResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	}

	return nil
}
<commit_msg>Removing unneeded import<commit_after>package client

import (
	"github.com/mitchellh/mapstructure"
)

func decodeRequest(params map[string]interface{}) operation {
	if _, ok := params["253"]; !ok {
		return nil
	}

	code := params["253"].(int16)

	switch code {
	case 10:
		operation := operationGetGameServerByCluster{}
		mapstructure.Decode(params, &operation)

		return operation
	case 67:
		operation := operationAuctionGetOffers{}
		mapstructure.Decode(params, &operation)

		return operation
	case 166:
		operation := operationGetClusterMapInfo{}
		mapstructure.Decode(params, &operation)

		return operation
	case 217:
		operation := operationGoldMarketGetAverageInfo{}
		mapstructure.Decode(params, &operation)

		return operation
	case 232:
		operation := operationRealEstateGetAuctionData{}
		mapstructure.Decode(params, &operation)

		return operation
	case 233:
		operation := operationRealEstateBidOnAuction{}
		mapstructure.Decode(params, &operation)

		return operation
	}

	return nil
}

func decodeResponse(params map[string]interface{}) operation {
	if _, ok := params["253"]; !ok {
		return nil
	}

	code := params["253"].(int16)

	switch code {
	case 67:
		operation := operationAuctionGetOffersResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 68:
		operation := operationAuctionGetRequestsResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 166:
		operation := operationGetClusterMapInfoResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 217:
		operation := operationGoldMarketGetAverageInfoResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 232:
		operation := operationRealEstateGetAuctionDataResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	case 233:
		operation := operationRealEstateBidOnAuctionResponse{}
		mapstructure.Decode(params, &operation)

		return operation
	}

	return nil
}
<|endoftext|>


<commit_before>package gigcity

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

	"github.com/bmizerany/pat"
)

// Under appengine our code runs as a package, not a binary.  Due to this
// define the routes during package initilization.  Normally this wourd happen
// with in main()
func init() {
	// hondle application paths
	m := pat.New()
	m.Get("/admin/location/add", http.HandlerFunc(addLocationHandler))
	m.Post("/admin/location/add", http.HandlerFunc(addLocationHandler))
	m.Get("/admin/location", http.HandlerFunc(locationHandler))
	m.Get("/admin/events/add", http.HandlerFunc(addEventHandler))
	m.Post("/admin/events/add", http.HandlerFunc(addEventHandler))
	m.Get("/admin", http.HandlerFunc(adminRootHandler))
	m.Get("/events/:event", http.HandlerFunc(getEventHandler))
	m.Get("/events", http.HandlerFunc(eventHandler))
	m.Get("/about", http.HandlerFunc(aboutHandler))
	m.Get("/", http.HandlerFunc(rootHandler))
	http.Handle("/", m)
}

// Handle messages that should be written out to the log.  lvl is the level of the message
// and msg contains the message body
func logHandler(lvl, msg string) {
	// switch on message log level
	// TODO work out a good way to see if this is running in a dev
	// environment
	switch lvl {
	case "INFO":
		log.Print("[INFO]: " + logTime() + " " + msg)
	case "WARN":
		log.Print("[WARNING]: " + logTime() + " " + msg)
	case "ERROR":
		log.Print("[ERROR]: " + logTime() + " " + msg)
	case "FATAL":
		log.Fatal("[FATEL]: " + logTime() + " " + msg)
	}
}

// returns the current time in RFC3339 format
func logTime() string {
	return time.Now().Format(time.RFC3339)
}

// Handle errors here, this allows us to control the format of the output rather
// than using http.Error() defaults
func errorHandler(w http.ResponseWriter, r *http.Request, status int, err string) {
	w.WriteHeader(status)
	switch status {
	case http.StatusNotFound:
		logHandler("ERROR", fmt.Sprintf("client %s tried to request %v", r.RemoteAddr, r.URL.Path))
		page := template.Must(template.ParseFiles(
			"static/_base.html",
			"static/404.html",
		))

		if err := page.Execute(w, nil); err != nil {
			errorHandler(w, r, http.StatusInternalServerError, err.Error())
			return
		}
	case http.StatusInternalServerError:
		logHandler("ERROR", fmt.Sprintf("an internal server error occured when %s requested %s with error:\n%s", r.RemoteAddr, r.URL.Path, err))
		page := template.Must(template.ParseFiles(
			"static/_base.html",
			"static/500.html",
		))

		if err := page.Execute(w, nil); err != nil {
			// IF for some reason the tempalets for 500 errors fails, fallback
			// on http.Error()
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

// Handles requests to '/' as well as any unmatched routes to the server
func rootHandler(w http.ResponseWriter, r *http.Request) {
	// If the request is not for the root of the app, then it is a 404
	if r.URL.Path != "/" {
		errorHandler(w, r, http.StatusNotFound, "")
		return
	}

	page := template.Must(template.ParseFiles(
		"static/_base.html",
		"static/index.html",
	))

	if err := page.Execute(w, nil); err != nil {
		errorHandler(w, r, http.StatusInternalServerError, err.Error())
		return
	}
}

// Handles requests to /about
func aboutHandler(w http.ResponseWriter, r *http.Request) {
	page := template.Must(template.ParseFiles(
		"static/_base.html",
		"static/about.html",
	))

	if err := page.Execute(w, nil); err != nil {
		errorHandler(w, r, http.StatusInternalServerError, err.Error())
		return
	}
}
<commit_msg>Adding missing fmt package<commit_after>package gigcity

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"time"

	"github.com/bmizerany/pat"
)

// Under appengine our code runs as a package, not a binary.  Due to this
// define the routes during package initilization.  Normally this wourd happen
// with in main()
func init() {
	// hondle application paths
	m := pat.New()
	m.Get("/admin/location/add", http.HandlerFunc(addLocationHandler))
	m.Post("/admin/location/add", http.HandlerFunc(addLocationHandler))
	m.Get("/admin/location", http.HandlerFunc(locationHandler))
	m.Get("/admin/events/add", http.HandlerFunc(addEventHandler))
	m.Post("/admin/events/add", http.HandlerFunc(addEventHandler))
	m.Get("/admin", http.HandlerFunc(adminRootHandler))
	m.Get("/events/:event", http.HandlerFunc(getEventHandler))
	m.Get("/events", http.HandlerFunc(eventHandler))
	m.Get("/about", http.HandlerFunc(aboutHandler))
	m.Get("/", http.HandlerFunc(rootHandler))
	http.Handle("/", m)
}

// Handle messages that should be written out to the log.  lvl is the level of the message
// and msg contains the message body
func logHandler(lvl, msg string) {
	// switch on message log level
	// TODO work out a good way to see if this is running in a dev
	// environment
	switch lvl {
	case "INFO":
		log.Print("[INFO]: " + logTime() + " " + msg)
	case "WARN":
		log.Print("[WARNING]: " + logTime() + " " + msg)
	case "ERROR":
		log.Print("[ERROR]: " + logTime() + " " + msg)
	case "FATAL":
		log.Fatal("[FATEL]: " + logTime() + " " + msg)
	}
}

// returns the current time in RFC3339 format
func logTime() string {
	return time.Now().Format(time.RFC3339)
}

// Handle errors here, this allows us to control the format of the output rather
// than using http.Error() defaults
func errorHandler(w http.ResponseWriter, r *http.Request, status int, err string) {
	w.WriteHeader(status)
	switch status {
	case http.StatusNotFound:
		logHandler("ERROR", fmt.Sprintf("client %s tried to request %v", r.RemoteAddr, r.URL.Path))
		page := template.Must(template.ParseFiles(
			"static/_base.html",
			"static/404.html",
		))

		if err := page.Execute(w, nil); err != nil {
			errorHandler(w, r, http.StatusInternalServerError, err.Error())
			return
		}
	case http.StatusInternalServerError:
		logHandler("ERROR", fmt.Sprintf("an internal server error occured when %s requested %s with error:\n%s", r.RemoteAddr, r.URL.Path, err))
		page := template.Must(template.ParseFiles(
			"static/_base.html",
			"static/500.html",
		))

		if err := page.Execute(w, nil); err != nil {
			// IF for some reason the tempalets for 500 errors fails, fallback
			// on http.Error()
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

// Handles requests to '/' as well as any unmatched routes to the server
func rootHandler(w http.ResponseWriter, r *http.Request) {
	// If the request is not for the root of the app, then it is a 404
	if r.URL.Path != "/" {
		errorHandler(w, r, http.StatusNotFound, "")
		return
	}

	page := template.Must(template.ParseFiles(
		"static/_base.html",
		"static/index.html",
	))

	if err := page.Execute(w, nil); err != nil {
		errorHandler(w, r, http.StatusInternalServerError, err.Error())
		return
	}
}

// Handles requests to /about
func aboutHandler(w http.ResponseWriter, r *http.Request) {
	page := template.Must(template.ParseFiles(
		"static/_base.html",
		"static/about.html",
	))

	if err := page.Execute(w, nil); err != nil {
		errorHandler(w, r, http.StatusInternalServerError, err.Error())
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net"

	"github.com/dustin/gomemcached"
	"github.com/dustin/gomemcached/server"
)

var port *int = flag.Int("port", 11212, "Port on which to listen")

type chanReq struct {
	req *gomemcached.MCRequest
	res chan *gomemcached.MCResponse
}

type reqHandler struct {
	ch chan chanReq
}

func (rh *reqHandler) HandleMessage(req *gomemcached.MCRequest) *gomemcached.MCResponse {
	cr := chanReq{
		req,
		make(chan *gomemcached.MCResponse),
	}

	rh.ch <- cr
	return <-cr.res
}

func waitForConnections(ls net.Listener) {
	reqChannel := make(chan chanReq)

	go RunServer(reqChannel)
	handler := &reqHandler{reqChannel}

	log.Printf("Listening on port %d", *port)
	for {
		s, e := ls.Accept()
		if e == nil {
			log.Print("Got a connection %s", s)
			go memcached.HandleIO(s, handler)
		} else {
			log.Printf("Error accepting from %s", ls)
		}
	}
}

func main() {
	flag.Parse()
	ls, e := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if e != nil {
		log.Fatalf("Got an error:  %s", e)
	}

	waitForConnections(ls)
}
<commit_msg>go vet fix in server<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net"

	"github.com/dustin/gomemcached"
	"github.com/dustin/gomemcached/server"
)

var port *int = flag.Int("port", 11212, "Port on which to listen")

type chanReq struct {
	req *gomemcached.MCRequest
	res chan *gomemcached.MCResponse
}

type reqHandler struct {
	ch chan chanReq
}

func (rh *reqHandler) HandleMessage(req *gomemcached.MCRequest) *gomemcached.MCResponse {
	cr := chanReq{
		req,
		make(chan *gomemcached.MCResponse),
	}

	rh.ch <- cr
	return <-cr.res
}

func waitForConnections(ls net.Listener) {
	reqChannel := make(chan chanReq)

	go RunServer(reqChannel)
	handler := &reqHandler{reqChannel}

	log.Printf("Listening on port %d", *port)
	for {
		s, e := ls.Accept()
		if e == nil {
			log.Printf("Got a connection from %v", s.RemoteAddr())
			go memcached.HandleIO(s, handler)
		} else {
			log.Printf("Error accepting from %s", ls)
		}
	}
}

func main() {
	flag.Parse()
	ls, e := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if e != nil {
		log.Fatalf("Got an error:  %s", e)
	}

	waitForConnections(ls)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"github.com/gorilla/feeds"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/calendar/v3"
	"html"
	"io/ioutil"
	"strings"
	"time"
)

func GetEventsFromGoogleCalendar(calendarId string) (*calendar.Events, error) {
	json, err := ioutil.ReadFile("google_client_credentials.json")
	if err != nil {
		return nil, err
	}

	config, err := google.JWTConfigFromJSON(json, calendar.CalendarReadonlyScope)
	if err != nil {
		return nil, err
	}

	client := config.Client(oauth2.NoContext)

	service, err := calendar.New(client)
	if err != nil {
		return nil, err
	}

	events, err := service.Events.List(calendarId).MaxResults(2500).OrderBy("updated").SingleEvents(true).Do()
	if err != nil {
		return nil, err
	}

	items := events.Items
	for pageToken := events.NextPageToken; events.NextPageToken != ""; {
		events, err := service.Events.List(calendarId).PageToken(pageToken).Do()
		if err != nil {
			return nil, err
		}
		items = append(items, events.Items...)
		pageToken = events.NextPageToken
	}
	events.Items = items
	return events, nil
}

func RenderGoogleCalendarFeed(events *calendar.Events, href string) (*feeds.Feed, error) {
	descriptionReplacer := strings.NewReplacer("\n", "
")

	var items []*feeds.Item
	items = make([]*feeds.Item, len(events.Items))
	for i, event := range events.Items {
		created, err := time.Parse(time.RFC3339, event.Created)
		if err != nil {
			return nil, err
		}
		updated, err := time.Parse(time.RFC3339, event.Updated)
		if err != nil {
			return nil, err
		}

		var duration string

		switch {
		case event.Start.Date != "" && event.End.Date != "":
			startLoc, err := time.LoadLocation(event.Start.TimeZone)
			if err != nil {
				return nil, err
			}
			start, err := time.ParseInLocation("2006-01-02", event.Start.Date, startLoc)
			if err != nil {
				return nil, err
			}
			endLoc, err := time.LoadLocation(event.Start.TimeZone)
			if err != nil {
				return nil, err
			}
			end, err := time.ParseInLocation("2006-01-02", event.End.Date, endLoc)
			if err != nil {
				return nil, err
			}

			if start.Format("2006-01-02") == end.Format("2006-01-02") {
				duration = start.Format("2006-01-02 (Mon)")
			} else {
				duration = start.Format("2006-01-02 (Mon)") + " - " + end.Format("2006-01-02 (Mon)")
			}

		case event.Start.DateTime != "" && event.End.DateTime != "":
			startLoc, err := time.LoadLocation(event.Start.TimeZone)
			if err != nil {
				return nil, err
			}
			start, err := time.ParseInLocation(time.RFC3339, event.Start.DateTime, startLoc)
			if err != nil {
				return nil, err
			}
			endLoc, err := time.LoadLocation(event.Start.TimeZone)
			if err != nil {
				return nil, err
			}
			end, err := time.ParseInLocation(time.RFC3339, event.End.DateTime, endLoc)
			if err != nil {
				return nil, err
			}

			if start.Format("2006-01-02") == end.Format("2006-01-02") {
				duration = start.Format("2006-01-02 (Mon) 15:04") + " - " + end.Format("15:04")
			} else {
				duration = start.Format("2006-01-02 (Mon) 15:04") + " - " + end.Format("2006-01-02 (Mon) 15:04")
			}

		default:
			return nil, errors.New("must not happen")
		}

		description := fmt.Sprintf("Location: %s
Duration: %s

%s", html.EscapeString(event.Location), html.EscapeString(duration), descriptionReplacer.Replace(html.EscapeString(event.Description)))

		items[i] = &feeds.Item{
			Id:          event.Etag,
			Title:       event.Summary,
			Description: description,
			Link:        &feeds.Link{Href: event.HtmlLink},
			Author:      &feeds.Author{Name: event.Creator.DisplayName, Email: event.Creator.Email},
			Created:     created,
			Updated:     updated,
		}
	}

	updated, err := time.Parse(time.RFC3339, events.Updated)
	if err != nil {
		return nil, err
	}

	feed := &feeds.Feed{
		Id:          events.Etag,
		Title:       events.Summary,
		Description: events.Description,
		Link:        &feeds.Link{Href: href},
		Updated:     updated,
		Items:       items,
	}
	return feed, nil
}
<commit_msg>Limit duration using timeMin<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/gorilla/feeds"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/calendar/v3"
	"html"
	"io/ioutil"
	"strings"
	"time"
)

func GetEventsFromGoogleCalendar(calendarId string) (*calendar.Events, error) {
	json, err := ioutil.ReadFile("google_client_credentials.json")
	if err != nil {
		return nil, err
	}

	config, err := google.JWTConfigFromJSON(json, calendar.CalendarReadonlyScope)
	if err != nil {
		return nil, err
	}

	client := config.Client(oauth2.NoContext)

	service, err := calendar.New(client)
	if err != nil {
		return nil, err
	}

	timeMin := time.Now().AddDate(0, -3, 0).Format(time.RFC3339)

	events, err := service.Events.List(calendarId).MaxResults(2500).OrderBy("updated").SingleEvents(true).TimeMin(timeMin).Do()
	if err != nil {
		return nil, err
	}

	items := events.Items
	for pageToken := events.NextPageToken; events.NextPageToken != ""; {
		events, err := service.Events.List(calendarId).PageToken(pageToken).Do()
		if err != nil {
			return nil, err
		}
		items = append(items, events.Items...)
		pageToken = events.NextPageToken
	}
	events.Items = items
	return events, nil
}

func RenderGoogleCalendarFeed(events *calendar.Events, href string) (*feeds.Feed, error) {
	descriptionReplacer := strings.NewReplacer("\n", "
")

	var items []*feeds.Item
	items = make([]*feeds.Item, len(events.Items))
	for i, event := range events.Items {
		created, err := time.Parse(time.RFC3339, event.Created)
		if err != nil {
			return nil, err
		}
		updated, err := time.Parse(time.RFC3339, event.Updated)
		if err != nil {
			return nil, err
		}

		var duration string

		switch {
		case event.Start.Date != "" && event.End.Date != "":
			startLoc, err := time.LoadLocation(event.Start.TimeZone)
			if err != nil {
				return nil, err
			}
			start, err := time.ParseInLocation("2006-01-02", event.Start.Date, startLoc)
			if err != nil {
				return nil, err
			}
			endLoc, err := time.LoadLocation(event.Start.TimeZone)
			if err != nil {
				return nil, err
			}
			end, err := time.ParseInLocation("2006-01-02", event.End.Date, endLoc)
			if err != nil {
				return nil, err
			}

			if start.Format("2006-01-02") == end.Format("2006-01-02") {
				duration = start.Format("2006-01-02 (Mon)")
			} else {
				duration = start.Format("2006-01-02 (Mon)") + " - " + end.Format("2006-01-02 (Mon)")
			}

		case event.Start.DateTime != "" && event.End.DateTime != "":
			startLoc, err := time.LoadLocation(event.Start.TimeZone)
			if err != nil {
				return nil, err
			}
			start, err := time.ParseInLocation(time.RFC3339, event.Start.DateTime, startLoc)
			if err != nil {
				return nil, err
			}
			endLoc, err := time.LoadLocation(event.Start.TimeZone)
			if err != nil {
				return nil, err
			}
			end, err := time.ParseInLocation(time.RFC3339, event.End.DateTime, endLoc)
			if err != nil {
				return nil, err
			}

			if start.Format("2006-01-02") == end.Format("2006-01-02") {
				duration = start.Format("2006-01-02 (Mon) 15:04") + " - " + end.Format("15:04")
			} else {
				duration = start.Format("2006-01-02 (Mon) 15:04") + " - " + end.Format("2006-01-02 (Mon) 15:04")
			}

		default:
			return nil, errors.New("must not happen")
		}

		description := fmt.Sprintf("Location: %s
Duration: %s

%s", html.EscapeString(event.Location), html.EscapeString(duration), descriptionReplacer.Replace(html.EscapeString(event.Description)))

		items[i] = &feeds.Item{
			Id:          event.Etag,
			Title:       event.Summary,
			Description: description,
			Link:        &feeds.Link{Href: event.HtmlLink},
			Author:      &feeds.Author{Name: event.Creator.DisplayName, Email: event.Creator.Email},
			Created:     created,
			Updated:     updated,
		}
	}

	updated, err := time.Parse(time.RFC3339, events.Updated)
	if err != nil {
		return nil, err
	}

	feed := &feeds.Feed{
		Id:          events.Etag,
		Title:       events.Summary,
		Description: events.Description,
		Link:        &feeds.Link{Href: href},
		Updated:     updated,
		Items:       items,
	}
	return feed, nil
}
<|endoftext|>


<commit_before>package goreporter

import (
	gi "github.com/onsi/ginkgo"
	gom "github.com/onsi/gomega"
)

var _ = gi.Describe("Goreporter", func() {
	gi.It("Basic creation", func() {
		url := "ipc:///tmp/test"
		rep, err := NewReporter(&url)
		gom.Expect(err).Should(gom.BeNil())
		key := "key"
		rep.AddStat(key, 30)
		rep.Flush()
	})
})
<commit_msg>fix tests<commit_after>package goreporter

import (
	gi "github.com/onsi/ginkgo"
	gom "github.com/onsi/gomega"
)

var _ = gi.Describe("Goreporter", func() {
	gi.It("Basic creation with invalid queue name", func() {
		url := "fail"
		r, err := NewReporter(&url)
		r.SetTimeout(1)
		gom.Expect(err).ShouldNot(gom.Equal(gom.BeNil()))
	})

	gi.It("Basic new stat with failed flush", func() {
		url := "ipc:///tmp/test"
		r, err := NewReporter(&url)
		r.SetTimeout(1)
		gom.Expect(err).Should(gom.BeNil())
		key := "key"
		r.AddStat(key, 30)
		err = r.Flush()
		gom.Expect(err).ShouldNot(gom.Equal(gom.BeNil()))
	})
})
<|endoftext|>


<commit_before>package goxpath

import (
	"github.com/ChrisTrenkamp/goxpath/goxpath/internal/parser"
	"github.com/ChrisTrenkamp/goxpath/tree"
)

//XPathExec is the XPath executor, compiled from an XPath string
type XPathExec []parser.XPExec

//Exec executes the XPath expression, xp, against the tree, t, with the
//namespace mappings, ns.
func Exec(xp XPathExec, t tree.Node, ns map[string]string) ([]tree.Res, error) {
	return parser.Exec(xp, t, ns)
}

//MustParse is like Parse, but panics instead of returning an error.
func MustParse(xp string) XPathExec {
	return parser.MustParse(xp)
}

//Parse parses the XPath expression, xp, returning an XPath executor.
func Parse(xp string) (XPathExec, error) {
	return parser.Parse(xp)
}
<commit_msg>Adding a MustExec method for convenience.<commit_after>package goxpath

import (
	"github.com/ChrisTrenkamp/goxpath/goxpath/internal/parser"
	"github.com/ChrisTrenkamp/goxpath/tree"
)

//XPathExec is the XPath executor, compiled from an XPath string
type XPathExec []parser.XPExec

//Exec executes the XPath expression, xp, against the tree, t, with the
//namespace mappings, ns.
func Exec(xp XPathExec, t tree.Node, ns map[string]string) ([]tree.Res, error) {
	return parser.Exec(xp, t, ns)
}

//MustExec is like Exec, but panics instead of returning an error.
func MustExec(xp XPathExec, t tree.Node, ns map[string]string) []tree.Res {
	res, err := parser.Exec(xp, t, ns)
	if err != nil {
		panic(err)
	}
	return res
}

//MustParse is like Parse, but panics instead of returning an error.
func MustParse(xp string) XPathExec {
	return parser.MustParse(xp)
}

//Parse parses the XPath expression, xp, returning an XPath executor.
func Parse(xp string) (XPathExec, error) {
	return parser.Parse(xp)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

// greenhouse implements a bazel remote cache service [1]
// supporting arbitrarily many workspaces stored within the same
// top level directory.
//
// the first path segment in each {PUT,GET} request is mapped to an individual
// workspace cache, the remaining segments should follow [2].
//
// nursery assumes you are using SHA256
//
// [1] https://docs.bazel.build/versions/master/remote-caching.html
// [2] https://docs.bazel.build/versions/master/remote-caching.html#http-caching-protocol
package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"

	"k8s.io/test-infra/greenhouse/diskcache"
	"k8s.io/test-infra/greenhouse/diskutil"
	"k8s.io/test-infra/prow/logrusutil"

	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/sirupsen/logrus"
)

var dir = flag.String("dir", "", "location to store cache entries on disk")
var host = flag.String("host", "", "host address to listen on")
var cachePort = flag.Int("cache-port", 8080, "port to listen on for cache requests")
var metricsPort = flag.Int("metrics-port", 9090, "port to listen on for prometheus metrics scraping")
var metricsUpdateInterval = flag.Duration("metrics-update-interval", time.Second*10,
	"interval between updating disk metrics")

// eviction knobs
var minPercentBlocksFree = flag.Float64("min-percent-blocks-free", 5,
	"minimum percent of blocks free on --dir's disk before evicting entries")
var evictUntilPercentBlocksFree = flag.Float64("evict-until-percent-blocks-free", 20,
	"continue evicting from the cache until at least this percent of blocks are free")
var diskCheckInterval = flag.Duration("disk-check-interval", time.Second*10,
	"interval between checking disk usage (and potentially evicting entries)")

// global metrics object, see prometheus.go
var promMetrics *prometheusMetrics

func init() {
	logrus.SetFormatter(
		logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "greenhouse"}),
	)
	logrus.SetOutput(os.Stdout)
	promMetrics = initMetrics()
}

func main() {
	flag.Parse()
	if *dir == "" {
		logrus.Fatal("--dir must be set!")
	}

	cache := diskcache.NewCache(*dir)
	go monitorDiskAndEvict(
		cache, *diskCheckInterval,
		*minPercentBlocksFree, *evictUntilPercentBlocksFree,
	)

	go updateMetrics(*metricsUpdateInterval, cache.DiskRoot())

	// listen for prometheus scraping
	metricsMux := http.NewServeMux()
	metricsMux.Handle("/prometheus", promhttp.Handler())
	metricsAddr := fmt.Sprintf("%s:%d", *host, *metricsPort)
	go func() {
		logrus.Infof("Metrics Listening on: %s", metricsAddr)
		logrus.WithField("mux", "metrics").WithError(
			http.ListenAndServe(metricsAddr, metricsMux),
		).Fatal("ListenAndServe returned.")
	}()

	// listen for cache requests
	cacheMux := http.NewServeMux()
	cacheMux.Handle("/", cacheHandler(cache))
	cacheAddr := fmt.Sprintf("%s:%d", *host, *cachePort)
	logrus.Infof("Cache Listening on: %s", cacheAddr)
	logrus.WithField("mux", "cache").WithError(
		http.ListenAndServe(cacheAddr, cacheMux),
	).Fatal("ListenAndServe returned.")
}

// file not found error, used below
var errNotFound = errors.New("entry not found")

func cacheHandler(cache *diskcache.Cache) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		logger := logrus.WithFields(logrus.Fields{
			"method": r.Method,
			"path":   r.URL.Path,
		})
		// parse and validate path
		// the last segment should be a hash, and the
		// the second to last segment should be "ac" or "cas"
		parts := strings.Split(r.URL.Path, "/")
		if len(parts) < 3 {
			logger.Warn("received an invalid request")
			http.Error(w, "invalid location", http.StatusBadRequest)
			return
		}
		hash := parts[len(parts)-1]
		acOrCAS := parts[len(parts)-2]
		if acOrCAS != "ac" && acOrCAS != "cas" {
			logger.Warn("received an invalid request at path")
			http.Error(w, "invalid location", http.StatusBadRequest)
			return
		}
		requestingAction := acOrCAS == "ac"

		// actually handle request depending on method
		switch m := r.Method; m {
		// handle retreival
		case http.MethodGet:
			err := cache.Get(r.URL.Path, func(exists bool, contents io.ReadSeeker) error {
				if !exists {
					return errNotFound
				}
				http.ServeContent(w, r, "", time.Time{}, contents)
				return nil
			})
			if err != nil {
				// file not present
				if err == errNotFound {
					if requestingAction {
						promMetrics.ActionCacheMisses.Inc()
					} else {
						promMetrics.CASMisses.Inc()
					}
					http.Error(w, err.Error(), http.StatusNotFound)
					return
				}
				// unknown error
				logger.WithError(err).Error("error getting key")
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			// success, log hit
			if requestingAction {
				promMetrics.ActionCacheHits.Inc()
			} else {
				promMetrics.CASHits.Inc()
			}

		// handle upload
		case http.MethodPut:
			// only hash CAS, not action cache
			// the action cache is hash -> metadata
			// the CAS is well, a CAS, which we can hash...
			if requestingAction {
				hash = ""
			}
			err := cache.Put(r.URL.Path, r.Body, hash)
			if err != nil {
				logger.WithError(err).Errorf("Failed to put: %v", r.URL.Path)
				http.Error(w, "failed to put in cache", http.StatusInternalServerError)
				return
			}

		// handle unsupported methods...
		default:
			logger.Warn("received an invalid request method")
			http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
		}
	})
}

// helper to update disk metrics
func updateMetrics(interval time.Duration, diskRoot string) {
	logger := logrus.WithField("sync-loop", "updateMetrics")
	ticker := time.NewTicker(interval)
	for ; true; <-ticker.C {
		logger.Info("tick")
		_, bytesFree, bytesUsed, err := diskutil.GetDiskUsage(diskRoot)
		if err != nil {
			logger.WithError(err).Error("Failed to get disk metrics")
		} else {
			promMetrics.DiskFree.Set(float64(bytesFree) / 1e9)
			promMetrics.DiskUsed.Set(float64(bytesUsed) / 1e9)
			promMetrics.DiskTotal.Set(float64(bytesFree+bytesUsed) / 1e9)
		}
	}
}
<commit_msg>Typo fix in main.go: the the second->the second<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

// greenhouse implements a bazel remote cache service [1]
// supporting arbitrarily many workspaces stored within the same
// top level directory.
//
// the first path segment in each {PUT,GET} request is mapped to an individual
// workspace cache, the remaining segments should follow [2].
//
// nursery assumes you are using SHA256
//
// [1] https://docs.bazel.build/versions/master/remote-caching.html
// [2] https://docs.bazel.build/versions/master/remote-caching.html#http-caching-protocol
package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"

	"k8s.io/test-infra/greenhouse/diskcache"
	"k8s.io/test-infra/greenhouse/diskutil"
	"k8s.io/test-infra/prow/logrusutil"

	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/sirupsen/logrus"
)

var dir = flag.String("dir", "", "location to store cache entries on disk")
var host = flag.String("host", "", "host address to listen on")
var cachePort = flag.Int("cache-port", 8080, "port to listen on for cache requests")
var metricsPort = flag.Int("metrics-port", 9090, "port to listen on for prometheus metrics scraping")
var metricsUpdateInterval = flag.Duration("metrics-update-interval", time.Second*10,
	"interval between updating disk metrics")

// eviction knobs
var minPercentBlocksFree = flag.Float64("min-percent-blocks-free", 5,
	"minimum percent of blocks free on --dir's disk before evicting entries")
var evictUntilPercentBlocksFree = flag.Float64("evict-until-percent-blocks-free", 20,
	"continue evicting from the cache until at least this percent of blocks are free")
var diskCheckInterval = flag.Duration("disk-check-interval", time.Second*10,
	"interval between checking disk usage (and potentially evicting entries)")

// global metrics object, see prometheus.go
var promMetrics *prometheusMetrics

func init() {
	logrus.SetFormatter(
		logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "greenhouse"}),
	)
	logrus.SetOutput(os.Stdout)
	promMetrics = initMetrics()
}

func main() {
	flag.Parse()
	if *dir == "" {
		logrus.Fatal("--dir must be set!")
	}

	cache := diskcache.NewCache(*dir)
	go monitorDiskAndEvict(
		cache, *diskCheckInterval,
		*minPercentBlocksFree, *evictUntilPercentBlocksFree,
	)

	go updateMetrics(*metricsUpdateInterval, cache.DiskRoot())

	// listen for prometheus scraping
	metricsMux := http.NewServeMux()
	metricsMux.Handle("/prometheus", promhttp.Handler())
	metricsAddr := fmt.Sprintf("%s:%d", *host, *metricsPort)
	go func() {
		logrus.Infof("Metrics Listening on: %s", metricsAddr)
		logrus.WithField("mux", "metrics").WithError(
			http.ListenAndServe(metricsAddr, metricsMux),
		).Fatal("ListenAndServe returned.")
	}()

	// listen for cache requests
	cacheMux := http.NewServeMux()
	cacheMux.Handle("/", cacheHandler(cache))
	cacheAddr := fmt.Sprintf("%s:%d", *host, *cachePort)
	logrus.Infof("Cache Listening on: %s", cacheAddr)
	logrus.WithField("mux", "cache").WithError(
		http.ListenAndServe(cacheAddr, cacheMux),
	).Fatal("ListenAndServe returned.")
}

// file not found error, used below
var errNotFound = errors.New("entry not found")

func cacheHandler(cache *diskcache.Cache) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		logger := logrus.WithFields(logrus.Fields{
			"method": r.Method,
			"path":   r.URL.Path,
		})
		// parse and validate path
		// the last segment should be a hash, and
		// the second to last segment should be "ac" or "cas"
		parts := strings.Split(r.URL.Path, "/")
		if len(parts) < 3 {
			logger.Warn("received an invalid request")
			http.Error(w, "invalid location", http.StatusBadRequest)
			return
		}
		hash := parts[len(parts)-1]
		acOrCAS := parts[len(parts)-2]
		if acOrCAS != "ac" && acOrCAS != "cas" {
			logger.Warn("received an invalid request at path")
			http.Error(w, "invalid location", http.StatusBadRequest)
			return
		}
		requestingAction := acOrCAS == "ac"

		// actually handle request depending on method
		switch m := r.Method; m {
		// handle retreival
		case http.MethodGet:
			err := cache.Get(r.URL.Path, func(exists bool, contents io.ReadSeeker) error {
				if !exists {
					return errNotFound
				}
				http.ServeContent(w, r, "", time.Time{}, contents)
				return nil
			})
			if err != nil {
				// file not present
				if err == errNotFound {
					if requestingAction {
						promMetrics.ActionCacheMisses.Inc()
					} else {
						promMetrics.CASMisses.Inc()
					}
					http.Error(w, err.Error(), http.StatusNotFound)
					return
				}
				// unknown error
				logger.WithError(err).Error("error getting key")
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			// success, log hit
			if requestingAction {
				promMetrics.ActionCacheHits.Inc()
			} else {
				promMetrics.CASHits.Inc()
			}

		// handle upload
		case http.MethodPut:
			// only hash CAS, not action cache
			// the action cache is hash -> metadata
			// the CAS is well, a CAS, which we can hash...
			if requestingAction {
				hash = ""
			}
			err := cache.Put(r.URL.Path, r.Body, hash)
			if err != nil {
				logger.WithError(err).Errorf("Failed to put: %v", r.URL.Path)
				http.Error(w, "failed to put in cache", http.StatusInternalServerError)
				return
			}

		// handle unsupported methods...
		default:
			logger.Warn("received an invalid request method")
			http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
		}
	})
}

// helper to update disk metrics
func updateMetrics(interval time.Duration, diskRoot string) {
	logger := logrus.WithField("sync-loop", "updateMetrics")
	ticker := time.NewTicker(interval)
	for ; true; <-ticker.C {
		logger.Info("tick")
		_, bytesFree, bytesUsed, err := diskutil.GetDiskUsage(diskRoot)
		if err != nil {
			logger.WithError(err).Error("Failed to get disk metrics")
		} else {
			promMetrics.DiskFree.Set(float64(bytesFree) / 1e9)
			promMetrics.DiskUsed.Set(float64(bytesUsed) / 1e9)
			promMetrics.DiskTotal.Set(float64(bytesFree+bytesUsed) / 1e9)
		}
	}
}
<|endoftext|>


<commit_before>package haproxy

import (
	"io"
	"os"
	"os/exec"
	"path"
	"regexp"
	"sort"
	"strconv"
	"text/template"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/newrelic/sidecar/catalog"
	"github.com/newrelic/sidecar/service"
)

type portset map[string]string
type portmap map[string]portset

// Configuration and state for the HAproxy management module
type HAproxy struct {
	ReloadCmd  string
	VerifyCmd  string
	BindIP     string
	Template   string
	ConfigFile string
	PidFile    string
	User       string
	Group      string
}

// Constructs a properly configure HAProxy and returns a pointer to it
func New(configFile string, pidFile string) *HAproxy {
	reloadCmd := "haproxy -f " + configFile + " -p " + pidFile + " `[[ -f " + pidFile + " ]] && echo \"-sf $(cat " + pidFile + ")\"]]`"
	verifyCmd := "haproxy -c -f " + configFile

	proxy := HAproxy{
		ReloadCmd:  reloadCmd,
		VerifyCmd:  verifyCmd,
		Template:   "views/haproxy.cfg",
		ConfigFile: configFile,
		PidFile:    pidFile,
	}

	return &proxy
}

// Returns a map of ServicePort:Port pairs
func (h *HAproxy) makePortmap(services map[string][]*service.Service) portmap {
	ports := make(portmap)

	for name, svcList := range services {
		if _, ok := ports[name]; !ok {
			ports[name] = make(portset, 5)
		}

		for _, service := range svcList {
			for _, port := range service.Ports {
				// Currently only handle TCP, and we skip ports that aren't exported.
				// That's the effect of not specifying a ServicePort.
				if port.Type == "tcp" && port.ServicePort != 0 {
					svcPort := strconv.FormatInt(port.ServicePort, 10)
					internalPort := strconv.FormatInt(port.Port, 10)
					ports[name][svcPort] = internalPort
				}
			}
		}
	}

	return ports
}

// Clean up image names for writing as HAproxy frontend and backend entries
func sanitizeName(image string) string {
	replace := regexp.MustCompile("[^a-z0-9-]")
	return replace.ReplaceAllString(image, "-")
}

// Create an HAproxy config from the supplied ServicesState. Write it out to the
// supplied io.Writer interface. This gets a list from servicesWithPorts() and
// builds a list of unique ports for all services, then passes these to the
// template. Ports are looked up by the func getPorts().
func (h *HAproxy) WriteConfig(state *catalog.ServicesState, output io.Writer) {
	services := servicesWithPorts(state)
	ports := h.makePortmap(services)
	modes := getModes(state)

	data := struct {
		Services map[string][]*service.Service
		User     string
		Group    string
	}{
		Services: services,
		User:     h.User,
		Group:    h.Group,
	}

	funcMap := template.FuncMap{
		"now": time.Now().UTC,
		"getMode": func(k string) string {
			return modes[k]
		},
		"getPorts": func(k string) map[string]string {
			return ports[k]
		},
		"bindIP":       func() string { return h.BindIP },
		"sanitizeName": sanitizeName,
	}

	t, err := template.New("haproxy").Funcs(funcMap).ParseFiles(h.Template)
	if err != nil {
		log.Errorf("Error Parsing template '%s': %s", h.Template, err.Error())
		return
	}
	t.ExecuteTemplate(output, path.Base(h.Template), data)
}

// Execute a command and log the error, but bubble it up as well
func (h *HAproxy) run(command string) error {
	cmd := exec.Command("/bin/bash", "-c", command)
	err := cmd.Run()
	if err != nil {
		log.Errorf("Error running '%s': %s", command, err.Error())
	}

	return err
}

// Run the HAproxy reload command to load the new config and restart.
// Best to use a command with -sf specified to keep the connections up.
func (h *HAproxy) Reload() error {
	return h.run(h.ReloadCmd)
}

// Run HAproxy with the verify command that will check the validity of
// the current config. Used to gate a Reload() so we don't load a bad
// config and tear everything down.
func (h *HAproxy) Verify() error {
	return h.run(h.VerifyCmd)
}

// Watch the state of a ServicesState struct and generate a new proxy
// config file (haproxy.ConfigFile) when the state changes. Also notifies
// the service that it needs to reload once the new file has been written
// and verified.
func (h *HAproxy) Watch(state *catalog.ServicesState) {
	eventChannel := make(chan catalog.ChangeEvent, 2)
	state.AddListener(eventChannel)

	for event := range eventChannel {
		log.Println("State change event from " + event.Hostname)
		h.WriteAndReload(state)
	}
}

// Write out the the HAproxy config and reload the service.
func (h *HAproxy) WriteAndReload(state *catalog.ServicesState) {
	outfile, err := os.Create(h.ConfigFile)
	if err != nil {
		log.Errorf("Unable to write to %s! (%s)", h.ConfigFile, err.Error())
		return
	}

	h.WriteConfig(state, outfile)
	if err := h.Verify(); err != nil {
		log.Errorf("Failed to verify HAproxy config! (%s)", err.Error())
		return
	}

	h.Reload()
}

func getModes(state *catalog.ServicesState) map[string]string {
	modeMap := make(map[string]string)
	state.EachServiceSorted(
		func(hostname *string, serviceId *string, svc *service.Service) {
			svcName := state.ServiceName(svc)
			modeMap[svcName] = svc.ProxyMode
		},
	)
	return modeMap
}

// Like state.ByService() but only stores information for services which
// actually have public ports. Only matches services that have the same name
// and the same ports. Otherwise log an error.
func servicesWithPorts(state *catalog.ServicesState) map[string][]*service.Service {
	serviceMap := make(map[string][]*service.Service)

	state.EachServiceSorted(
		func(hostname *string, serviceId *string, svc *service.Service) {
			if len(svc.Ports) < 1 {
				return
			}

			// We only want things that are alive and healthy!
			if !svc.IsAlive() {
				return
			}

			svcName := state.ServiceName(svc)
			if _, ok := serviceMap[svcName]; !ok {
				serviceMap[svcName] = make([]*service.Service, 0, 3)
			}

			// If this is the first one, just add it to the list
			if len(serviceMap[svcName]) < 1 {
				serviceMap[svcName] = append(serviceMap[svcName], svc)
				return
			}

			// Otherwise we need to make sure the ServicePorts match
			match := serviceMap[svcName][0] // Get the first entry for comparison

			// Build up a sorted list of ServicePorts from the existing service
			portsToMatch := getSortedServicePorts(match)

			// Get the list of our ports
			portsWeHave := getSortedServicePorts(svc)

			// Compare the two sorted lists
			for i, port := range portsToMatch {
				if portsWeHave[i] != port {
					// TODO should we just add another service with this port added
					// to the name? We have to find out which port.
					log.Warnf("%s service from %s not added: non-matching ports! (%v vs %v)",
						state.ServiceName(svc), svc.Hostname, port, portsWeHave[i])
					return
				}
			}

			// It was a match! Append to the list.
			serviceMap[svcName] = append(serviceMap[svcName], svc)
		},
	)

	return serviceMap
}

func getSortedServicePorts(svc *service.Service) []string {
	var portList []string
	for _, port := range svc.Ports {
		portList = append(portList, strconv.FormatInt(port.ServicePort, 10))
	}

	sort.Strings(portList)
	return portList
}
<commit_msg>Prepare HAproxy struct to act as config.<commit_after>package haproxy

import (
	"io"
	"os"
	"os/exec"
	"path"
	"regexp"
	"sort"
	"strconv"
	"text/template"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/newrelic/sidecar/catalog"
	"github.com/newrelic/sidecar/service"
)

type portset map[string]string
type portmap map[string]portset

// Configuration and state for the HAproxy management module
type HAproxy struct {
	ReloadCmd  string `toml:"reload_cmd"`
	VerifyCmd  string `toml:"verify_cmd"`
	BindIP     string `toml:"bind_ip"`
	Template   string `toml:"template"`
	ConfigFile string `toml:"config_file"`
	PidFile    string `toml:"pid_file"`
	User       string `toml:"user"`
	Group      string `toml:"group"`
}

// Constructs a properly configured HAProxy and returns a pointer to it
func New(configFile string, pidFile string) *HAproxy {
	reloadCmd := "haproxy -f " + configFile + " -p " + pidFile + " `[[ -f " + pidFile + " ]] && echo \"-sf $(cat " + pidFile + ")\"]]`"
	verifyCmd := "haproxy -c -f " + configFile

	proxy := HAproxy{
		ReloadCmd:  reloadCmd,
		VerifyCmd:  verifyCmd,
		Template:   "views/haproxy.cfg",
		ConfigFile: configFile,
		PidFile:    pidFile,
	}

	return &proxy
}

// Returns a map of ServicePort:Port pairs
func (h *HAproxy) makePortmap(services map[string][]*service.Service) portmap {
	ports := make(portmap)

	for name, svcList := range services {
		if _, ok := ports[name]; !ok {
			ports[name] = make(portset, 5)
		}

		for _, service := range svcList {
			for _, port := range service.Ports {
				// Currently only handle TCP, and we skip ports that aren't exported.
				// That's the effect of not specifying a ServicePort.
				if port.Type == "tcp" && port.ServicePort != 0 {
					svcPort := strconv.FormatInt(port.ServicePort, 10)
					internalPort := strconv.FormatInt(port.Port, 10)
					ports[name][svcPort] = internalPort
				}
			}
		}
	}

	return ports
}

// Clean up image names for writing as HAproxy frontend and backend entries
func sanitizeName(image string) string {
	replace := regexp.MustCompile("[^a-z0-9-]")
	return replace.ReplaceAllString(image, "-")
}

// Create an HAproxy config from the supplied ServicesState. Write it out to the
// supplied io.Writer interface. This gets a list from servicesWithPorts() and
// builds a list of unique ports for all services, then passes these to the
// template. Ports are looked up by the func getPorts().
func (h *HAproxy) WriteConfig(state *catalog.ServicesState, output io.Writer) {
	services := servicesWithPorts(state)
	ports := h.makePortmap(services)
	modes := getModes(state)

	data := struct {
		Services map[string][]*service.Service
		User     string
		Group    string
	}{
		Services: services,
		User:     h.User,
		Group:    h.Group,
	}

	funcMap := template.FuncMap{
		"now": time.Now().UTC,
		"getMode": func(k string) string {
			return modes[k]
		},
		"getPorts": func(k string) map[string]string {
			return ports[k]
		},
		"bindIP":       func() string { return h.BindIP },
		"sanitizeName": sanitizeName,
	}

	t, err := template.New("haproxy").Funcs(funcMap).ParseFiles(h.Template)
	if err != nil {
		log.Errorf("Error Parsing template '%s': %s", h.Template, err.Error())
		return
	}
	t.ExecuteTemplate(output, path.Base(h.Template), data)
}

// Execute a command and log the error, but bubble it up as well
func (h *HAproxy) run(command string) error {
	cmd := exec.Command("/bin/bash", "-c", command)
	err := cmd.Run()
	if err != nil {
		log.Errorf("Error running '%s': %s", command, err.Error())
	}

	return err
}

// Run the HAproxy reload command to load the new config and restart.
// Best to use a command with -sf specified to keep the connections up.
func (h *HAproxy) Reload() error {
	return h.run(h.ReloadCmd)
}

// Run HAproxy with the verify command that will check the validity of
// the current config. Used to gate a Reload() so we don't load a bad
// config and tear everything down.
func (h *HAproxy) Verify() error {
	return h.run(h.VerifyCmd)
}

// Watch the state of a ServicesState struct and generate a new proxy
// config file (haproxy.ConfigFile) when the state changes. Also notifies
// the service that it needs to reload once the new file has been written
// and verified.
func (h *HAproxy) Watch(state *catalog.ServicesState) {
	eventChannel := make(chan catalog.ChangeEvent, 2)
	state.AddListener(eventChannel)

	for event := range eventChannel {
		log.Println("State change event from " + event.Hostname)
		h.WriteAndReload(state)
	}
}

// Write out the the HAproxy config and reload the service.
func (h *HAproxy) WriteAndReload(state *catalog.ServicesState) {
	outfile, err := os.Create(h.ConfigFile)
	if err != nil {
		log.Errorf("Unable to write to %s! (%s)", h.ConfigFile, err.Error())
		return
	}

	h.WriteConfig(state, outfile)
	if err := h.Verify(); err != nil {
		log.Errorf("Failed to verify HAproxy config! (%s)", err.Error())
		return
	}

	h.Reload()
}

func getModes(state *catalog.ServicesState) map[string]string {
	modeMap := make(map[string]string)
	state.EachServiceSorted(
		func(hostname *string, serviceId *string, svc *service.Service) {
			svcName := state.ServiceName(svc)
			modeMap[svcName] = svc.ProxyMode
		},
	)
	return modeMap
}

// Like state.ByService() but only stores information for services which
// actually have public ports. Only matches services that have the same name
// and the same ports. Otherwise log an error.
func servicesWithPorts(state *catalog.ServicesState) map[string][]*service.Service {
	serviceMap := make(map[string][]*service.Service)

	state.EachServiceSorted(
		func(hostname *string, serviceId *string, svc *service.Service) {
			if len(svc.Ports) < 1 {
				return
			}

			// We only want things that are alive and healthy!
			if !svc.IsAlive() {
				return
			}

			svcName := state.ServiceName(svc)
			if _, ok := serviceMap[svcName]; !ok {
				serviceMap[svcName] = make([]*service.Service, 0, 3)
			}

			// If this is the first one, just add it to the list
			if len(serviceMap[svcName]) < 1 {
				serviceMap[svcName] = append(serviceMap[svcName], svc)
				return
			}

			// Otherwise we need to make sure the ServicePorts match
			match := serviceMap[svcName][0] // Get the first entry for comparison

			// Build up a sorted list of ServicePorts from the existing service
			portsToMatch := getSortedServicePorts(match)

			// Get the list of our ports
			portsWeHave := getSortedServicePorts(svc)

			// Compare the two sorted lists
			for i, port := range portsToMatch {
				if portsWeHave[i] != port {
					// TODO should we just add another service with this port added
					// to the name? We have to find out which port.
					log.Warnf("%s service from %s not added: non-matching ports! (%v vs %v)",
						state.ServiceName(svc), svc.Hostname, port, portsWeHave[i])
					return
				}
			}

			// It was a match! Append to the list.
			serviceMap[svcName] = append(serviceMap[svcName], svc)
		},
	)

	return serviceMap
}

func getSortedServicePorts(svc *service.Service) []string {
	var portList []string
	for _, port := range svc.Ports {
		portList = append(portList, strconv.FormatInt(port.ServicePort, 10))
	}

	sort.Strings(portList)
	return portList
}
<|endoftext|>


<commit_before>package harness

// This file handles the app code introspection.
// It catalogs the controllers, their methods, and their arguments.

import (
	"go/ast"
	"go/parser"
	"go/scanner"
	"go/token"
	"log"
	"os"
	"play"
	"unicode"
)

type ControllerSpec struct {
	PackageName string
	StructName  string
	ImportPath  string
	MethodSpecs []*MethodSpec

	// Used internally to identify controllers that indirectly embed *play.Controller.
	embeddedTypes []*embeddedTypeName
}

// This is a description of a call to c.Render(..)
// It documents the argument names used, in order to propagate them to RenderArgs.
type renderCall struct {
	Line  int
	Names []string
}

type MethodSpec struct {
	Name        string        // Name of the method, e.g. "Index"
	Args        []*MethodArg  // Argument descriptors
	RenderCalls []*renderCall // Descriptions of Render() invocations from this Method.
}

type MethodArg struct {
	Name     string // Name of the argument.
	TypeName string // The name of the type, e.g. "int", "*pkg.UserType"
}

type embeddedTypeName struct {
	PackageName, StructName string
}

// Maps a controller simple name (e.g. "Login") to the methods for which it is a
// receiver.
type methodMap map[string][]*MethodSpec

// Parse the app directory and return a list of the controller types found.
// Returns a CompileError if the parsing fails.
func ScanControllers(path string) (specs []*ControllerSpec, compileError *play.CompileError) {
	// Parse files within the path.
	var pkgs map[string]*ast.Package
	fset := token.NewFileSet()
	pkgs, err := parser.ParseDir(fset, path, func(f os.FileInfo) bool { return !f.IsDir() }, 0)
	if err != nil {
		if errList, ok := err.(scanner.ErrorList); ok {
			var pos token.Position = errList[0].Pos
			return nil, &play.CompileError{
				SourceType:  ".go source",
				Title:       "Go Compilation Error",
				Path:        pos.Filename,
				Description: errList[0].Msg,
				Line:        pos.Line,
				Column:      pos.Column,
				SourceLines: play.MustReadLines(pos.Filename),
			}
		}
		ast.Print(nil, err)
		log.Fatalf("Failed to parse dir: %s", err)
	}

	// For each package... (often only "controllers")
	for _, pkg := range pkgs {
		var structSpecs []*ControllerSpec
		methodSpecs := make(methodMap)

		// For each source file in the package...
		for _, file := range pkg.Files {

			// For each declaration in the source file...
			for _, decl := range file.Decls {

				// Match and add both structs and methods
				structSpecs = appendStruct(structSpecs, pkg, decl)
				appendMethod(fset, methodSpecs, decl)
			}
		}

		// Filter the struct specs to just the ones that embed play.Controller.
		structSpecs = filterControllers(structSpecs)

		// Add the method specs to them.
		for _, spec := range structSpecs {
			spec.MethodSpecs = methodSpecs[spec.StructName]
		}

		// Add the prepared ControllerSpecs to the list.
		specs = append(specs, structSpecs...)
	}
	return
}

// If this Decl is a struct type definition, it is summarized and added to specs.
// Else, specs is returned unchanged.
func appendStruct(specs []*ControllerSpec, pkg *ast.Package, decl ast.Decl) []*ControllerSpec {
	// Filter out non-Struct type declarations.
	genDecl, ok := decl.(*ast.GenDecl)
	if !ok {
		return specs
	}

	if genDecl.Tok != token.TYPE {
		return specs
	}

	if len(genDecl.Specs) != 1 {
		play.LOG.Printf("Surprising: Decl does not have 1 Spec: %v", genDecl)
		return specs
	}

	spec := genDecl.Specs[0].(*ast.TypeSpec)
	structType, ok := spec.Type.(*ast.StructType)
	if !ok {
		return specs
	}

	// At this point we know it's a type declaration for a struct.
	// Fill in the rest of the info by diving into the fields.
	// Add it provisionally to the Controller list -- it's later filtered using field info.
	controllerSpec := &ControllerSpec{
		PackageName: pkg.Name,
		StructName:  spec.Name.Name,
		ImportPath:  play.ImportPath + "/app/" + pkg.Name,
	}

	for _, field := range structType.Fields.List {
		// If field.Names is set, it's not an embedded type.
		if field.Names != nil {
			continue
		}

		// A direct "sub-type" has an ast.Field like:
		// StarExpr { SelectorExpr { "play", "Controller" } }
		starExpr, ok := field.Type.(*ast.StarExpr)
		if !ok {
			continue
		}

		selectorExpr, ok := starExpr.X.(*ast.SelectorExpr)
		if !ok {
			continue
		}

		pkgIdent, ok := selectorExpr.X.(*ast.Ident)
		if !ok {
			continue
		}

		controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
			PackageName: pkgIdent.Name,
			StructName:  selectorExpr.Sel.Name,
		})
	}

	return append(specs, controllerSpec)
}

// If decl is a Method declaration, it is summarized and added to the array
// underneath its receiver type.
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
func appendMethod(fset *token.FileSet, mm methodMap, decl ast.Decl) {
	// Func declaration?
	funcDecl, ok := decl.(*ast.FuncDecl)
	if !ok {
		return
	}

	// Have a receiver?
	if funcDecl.Recv == nil {
		return
	}

	// Is it public?
	if !unicode.IsUpper([]rune(funcDecl.Name.Name)[0]) {
		return
	}

	// Does it return a play.Result?
	if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
		return
	}
	selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
	if !ok {
		return
	}
	if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || pkgIdent.Name != "play" {
		return
	}
	if selExpr.Sel.Name != "Result" {
		return
	}

	// Get the receiver type, "dereferencing" it if necessary
	var recvTypeName string
	var recvType ast.Expr = funcDecl.Recv.List[0].Type
	if recvStarType, ok := recvType.(*ast.StarExpr); ok {
		recvTypeName = recvStarType.X.(*ast.Ident).Name
	} else {
		recvTypeName = recvType.(*ast.Ident).Name
	}

	method := &MethodSpec{
		Name: funcDecl.Name.Name,
	}

	// Add a description of the arguments to the method.
	for _, field := range funcDecl.Type.Params.List {
		for _, name := range field.Names {
			method.Args = append(method.Args, &MethodArg{
				Name:     name.Name,
				TypeName: ExprName(field.Type),
			})
		}
	}

	// Add a description of the calls to Render from the method.
	// Inspect every node (e.g. always return true).
	method.RenderCalls = []*renderCall{}
	ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
		// Is it a function call?
		callExpr, ok := node.(*ast.CallExpr)
		if !ok {
			return true
		}

		// Is it calling (*Controller).Render?
		selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
		if !ok {
			return true
		}

		// The type of the receiver is not easily available, so just store every
		// call to any method called Render.
		if selExpr.Sel.Name != "Render" {
			return true
		}

		// Add this call's args to the renderArgs.
		pos := fset.Position(callExpr.Rparen)
		renderCall := &renderCall{
			Line:  pos.Line,
			Names: []string{},
		}
		for _, arg := range callExpr.Args {
			argIdent, ok := arg.(*ast.Ident)
			if !ok {
				log.Println("Unnamed argument to Render call:", pos)
				continue
			}
			renderCall.Names = append(renderCall.Names, argIdent.Name)
		}
		method.RenderCalls = append(method.RenderCalls, renderCall)
		return true
	})

	mm[recvTypeName] = append(mm[recvTypeName], method)
}

func (s *ControllerSpec) SimpleName() string {
	return s.PackageName + "." + s.StructName
}

func (s *embeddedTypeName) SimpleName() string {
	return s.PackageName + "." + s.StructName
}

// Remove any types that do not (directly or indirectly) embed *play.Controller.
func filterControllers(specs []*ControllerSpec) (filtered []*ControllerSpec) {
	// Do a search in the "embedded type graph", starting with play.Controller.
	nodeQueue := []string{"play.Controller"}
	for _, controllerSimpleName := range nodeQueue {
		for _, spec := range specs {
			if play.ContainsString(nodeQueue, spec.SimpleName()) {
				continue // Already added
			}

			// Look through the embedded types to see if the current type is among them.
			for _, embeddedType := range spec.embeddedTypes {

				// If so, add this type's simple name to the nodeQueue, and it's spec to
				// the filtered list.
				if controllerSimpleName == embeddedType.SimpleName() {
					nodeQueue = append(nodeQueue, spec.SimpleName())
					filtered = append(filtered, spec)
					break
				}
			}
		}
	}
	return
}

func ExprName(expr ast.Expr) string {
	switch t := expr.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.SelectorExpr:
		return ExprName(t.X) + "." + ExprName(t.Sel)
	case *ast.StarExpr:
		return "*" + ExprName(t.X)
	default:
		ast.Print(nil, expr)
		panic("Failed to generate name for field.")
	}
	return ""
}
<commit_msg>Find App Controllers that embed play.Controller, regardless of the number of indirections.<commit_after>package harness

// This file handles the app code introspection.
// It catalogs the controllers, their methods, and their arguments.

import (
	"go/ast"
	"go/parser"
	"go/scanner"
	"go/token"
	"log"
	"os"
	"play"
	"unicode"
)

type ControllerSpec struct {
	PackageName string
	StructName  string
	ImportPath  string
	MethodSpecs []*MethodSpec

	// Used internally to identify controllers that indirectly embed *play.Controller.
	embeddedTypes []*embeddedTypeName
}

// This is a description of a call to c.Render(..)
// It documents the argument names used, in order to propagate them to RenderArgs.
type renderCall struct {
	Line  int
	Names []string
}

type MethodSpec struct {
	Name        string        // Name of the method, e.g. "Index"
	Args        []*MethodArg  // Argument descriptors
	RenderCalls []*renderCall // Descriptions of Render() invocations from this Method.
}

type MethodArg struct {
	Name     string // Name of the argument.
	TypeName string // The name of the type, e.g. "int", "*pkg.UserType"
}

type embeddedTypeName struct {
	PackageName, StructName string
}

// Maps a controller simple name (e.g. "Login") to the methods for which it is a
// receiver.
type methodMap map[string][]*MethodSpec

// Parse the app directory and return a list of the controller types found.
// Returns a CompileError if the parsing fails.
func ScanControllers(path string) (specs []*ControllerSpec, compileError *play.CompileError) {
	// Parse files within the path.
	var pkgs map[string]*ast.Package
	fset := token.NewFileSet()
	pkgs, err := parser.ParseDir(fset, path, func(f os.FileInfo) bool { return !f.IsDir() }, 0)
	if err != nil {
		if errList, ok := err.(scanner.ErrorList); ok {
			var pos token.Position = errList[0].Pos
			return nil, &play.CompileError{
				SourceType:  ".go source",
				Title:       "Go Compilation Error",
				Path:        pos.Filename,
				Description: errList[0].Msg,
				Line:        pos.Line,
				Column:      pos.Column,
				SourceLines: play.MustReadLines(pos.Filename),
			}
		}
		ast.Print(nil, err)
		log.Fatalf("Failed to parse dir: %s", err)
	}

	// For each package... (often only "controllers")
	for _, pkg := range pkgs {
		var structSpecs []*ControllerSpec
		methodSpecs := make(methodMap)

		// For each source file in the package...
		for _, file := range pkg.Files {

			// For each declaration in the source file...
			for _, decl := range file.Decls {

				// Match and add both structs and methods
				structSpecs = appendStruct(structSpecs, pkg, decl)
				appendMethod(fset, methodSpecs, decl)
			}
		}

		// Filter the struct specs to just the ones that embed play.Controller.
		structSpecs = filterControllers(structSpecs)

		// Add the method specs to them.
		for _, spec := range structSpecs {
			spec.MethodSpecs = methodSpecs[spec.StructName]
		}

		// Add the prepared ControllerSpecs to the list.
		specs = append(specs, structSpecs...)
	}
	return
}

// If this Decl is a struct type definition, it is summarized and added to specs.
// Else, specs is returned unchanged.
func appendStruct(specs []*ControllerSpec, pkg *ast.Package, decl ast.Decl) []*ControllerSpec {
	// Filter out non-Struct type declarations.
	genDecl, ok := decl.(*ast.GenDecl)
	if !ok {
		return specs
	}

	if genDecl.Tok != token.TYPE {
		return specs
	}

	if len(genDecl.Specs) != 1 {
		play.LOG.Printf("Surprising: Decl does not have 1 Spec: %v", genDecl)
		return specs
	}

	spec := genDecl.Specs[0].(*ast.TypeSpec)
	structType, ok := spec.Type.(*ast.StructType)
	if !ok {
		return specs
	}

	// At this point we know it's a type declaration for a struct.
	// Fill in the rest of the info by diving into the fields.
	// Add it provisionally to the Controller list -- it's later filtered using field info.
	controllerSpec := &ControllerSpec{
		PackageName: pkg.Name,
		StructName:  spec.Name.Name,
		ImportPath:  play.ImportPath + "/app/" + pkg.Name,
	}

NEXT_FIELD:
	for _, field := range structType.Fields.List {
		// If field.Names is set, it's not an embedded type.
		if field.Names != nil {
			continue
		}

		// A direct "sub-type" has an ast.Field as either:
		// SelectorExpr { "play", "Controller" }
		// StarExpr { SelectorExpr { "play", "Controller" } }
		// Drill through all the StarExprs to get the SelectorExpr.
		fieldType := field.Type
		var selectorExpr *ast.SelectorExpr
		for {
			if selectorExpr, ok = fieldType.(*ast.SelectorExpr); ok {
				break
			}
			if starExpr, ok := fieldType.(*ast.StarExpr); ok {
				fieldType = starExpr.X
			} else {
				continue NEXT_FIELD
			}
		}

		pkgIdent, ok := selectorExpr.X.(*ast.Ident)
		if !ok {
			continue
		}

		controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
			PackageName: pkgIdent.Name,
			StructName:  selectorExpr.Sel.Name,
		})
	}

	return append(specs, controllerSpec)
}

// If decl is a Method declaration, it is summarized and added to the array
// underneath its receiver type.
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
func appendMethod(fset *token.FileSet, mm methodMap, decl ast.Decl) {
	// Func declaration?
	funcDecl, ok := decl.(*ast.FuncDecl)
	if !ok {
		return
	}

	// Have a receiver?
	if funcDecl.Recv == nil {
		return
	}

	// Is it public?
	if !unicode.IsUpper([]rune(funcDecl.Name.Name)[0]) {
		return
	}

	// Does it return a play.Result?
	if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
		return
	}
	selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
	if !ok {
		return
	}
	if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || pkgIdent.Name != "play" {
		return
	}
	if selExpr.Sel.Name != "Result" {
		return
	}

	// Get the receiver type, "dereferencing" it if necessary
	var recvTypeName string
	var recvType ast.Expr = funcDecl.Recv.List[0].Type
	if recvStarType, ok := recvType.(*ast.StarExpr); ok {
		recvTypeName = recvStarType.X.(*ast.Ident).Name
	} else {
		recvTypeName = recvType.(*ast.Ident).Name
	}

	method := &MethodSpec{
		Name: funcDecl.Name.Name,
	}

	// Add a description of the arguments to the method.
	for _, field := range funcDecl.Type.Params.List {
		for _, name := range field.Names {
			method.Args = append(method.Args, &MethodArg{
				Name:     name.Name,
				TypeName: ExprName(field.Type),
			})
		}
	}

	// Add a description of the calls to Render from the method.
	// Inspect every node (e.g. always return true).
	method.RenderCalls = []*renderCall{}
	ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
		// Is it a function call?
		callExpr, ok := node.(*ast.CallExpr)
		if !ok {
			return true
		}

		// Is it calling (*Controller).Render?
		selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
		if !ok {
			return true
		}

		// The type of the receiver is not easily available, so just store every
		// call to any method called Render.
		if selExpr.Sel.Name != "Render" {
			return true
		}

		// Add this call's args to the renderArgs.
		pos := fset.Position(callExpr.Rparen)
		renderCall := &renderCall{
			Line:  pos.Line,
			Names: []string{},
		}
		for _, arg := range callExpr.Args {
			argIdent, ok := arg.(*ast.Ident)
			if !ok {
				log.Println("Unnamed argument to Render call:", pos)
				continue
			}
			renderCall.Names = append(renderCall.Names, argIdent.Name)
		}
		method.RenderCalls = append(method.RenderCalls, renderCall)
		return true
	})

	mm[recvTypeName] = append(mm[recvTypeName], method)
}

func (s *ControllerSpec) SimpleName() string {
	return s.PackageName + "." + s.StructName
}

func (s *embeddedTypeName) SimpleName() string {
	return s.PackageName + "." + s.StructName
}

// Remove any types that do not (directly or indirectly) embed *play.Controller.
func filterControllers(specs []*ControllerSpec) (filtered []*ControllerSpec) {
	// Do a search in the "embedded type graph", starting with play.Controller.
	nodeQueue := []string{"play.Controller"}
	for _, controllerSimpleName := range nodeQueue {
		for _, spec := range specs {
			if play.ContainsString(nodeQueue, spec.SimpleName()) {
				continue // Already added
			}

			// Look through the embedded types to see if the current type is among them.
			for _, embeddedType := range spec.embeddedTypes {

				// If so, add this type's simple name to the nodeQueue, and it's spec to
				// the filtered list.
				if controllerSimpleName == embeddedType.SimpleName() {
					nodeQueue = append(nodeQueue, spec.SimpleName())
					filtered = append(filtered, spec)
					break
				}
			}
		}
	}
	return
}

func ExprName(expr ast.Expr) string {
	switch t := expr.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.SelectorExpr:
		return ExprName(t.X) + "." + ExprName(t.Sel)
	case *ast.StarExpr:
		return "*" + ExprName(t.X)
	default:
		ast.Print(nil, expr)
		panic("Failed to generate name for field.")
	}
	return ""
}
<|endoftext|>