<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"io/ioutil"
	"math"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/prometheus/common/model"
	log "github.com/sirupsen/logrus"
)

var (
	timeLayout    = "2006-01-02 15:04:05"
	grafanaAddr   = flag.String("grafana_addr", "http://localhost:3000/", "The address of the Grafana instance to snapshot.")
	grafanaAPIKey = flag.String("grafana_api_key", "", "The address of the Grafana instance to snapshot.")
	dashSlug      = flag.String("dashboard_slug", "home", "The url friendly version of the dashboard title to snapshot from the \"grafana_addr\" address.")
	snapshotAddr  = flag.String("snapshot_addr", *grafanaAddr, "The location to submit the snapshot. Defaults to the grafana address.")
	fromTimestamp = flag.String("from", (time.Now().Truncate(time.Hour * 24)).Format(timeLayout), "The \"from\" time range. Must be absolute in the form \"YYYY-MM-DD HH:mm:ss\" (\"2017-01-23 12:34:56\"). Defaults to start of day.")
	toTimestamp   = flag.String("to", time.Now().Format(timeLayout), "The \"to\" time range. Must be absolute in the form \"YYYY-MM-DD HH:mm:ss\" (\"2017-01-23 12:34:57\"). Must be greater than to \"to\" value. Defaults to now")
	templateVars  = flag.String("template_vars", "", "a list of key value pairs to set the dashboard's template variables, in the format 'key1=val1;key2=val2'")
	debug         = flag.Bool("debug", false, "Turns on debug logging")
)

type config struct {
	grafanaAddr  url.URL
	apiKey       string
	dashSlug     string
	snapshotAddr url.URL
	from         time.Time
	to           time.Time
	vars         map[string]string
}

type snapshotData struct {
	Target     string          `json:"target"`
	Datapoints [][]interface{} `json:"datapoints"`
	// Metric is a set of labels (e.g. instance=alp) which is retained
	// so that we can replace labels according to target.legendFormat.
	Metric model.Metric `json:"-"`
}

func parseAndValidateFlags() (*config, error) {
	flag.Parse()
	config := &config{}

	if *debug {
		log.SetLevel(log.DebugLevel)
	}

	// Parse Grafana Address
	gURL, err := url.Parse(*grafanaAddr)
	if err != nil {
		return nil, err
	}
	if !strings.HasSuffix(gURL.Path, "/") {
		gURL.Path = gURL.Path + "/"
	}
	config.grafanaAddr = *gURL

	// Grafana API key
	if len(*grafanaAPIKey) == 0 {
		return nil, errors.New("\"grafana_api_key\" cannot be empty")
	}
	config.apiKey = *grafanaAPIKey

	// Dashboard slug
	if strings.Index(*dashSlug, " ") != -1 {
		return nil, errors.New("\"dashboard_slug\" contained an invalid character: \" \"")
	}
	config.dashSlug = *dashSlug

	// Parse Snapshot host Address
	if len(*snapshotAddr) == 0 {
		*snapshotAddr = *grafanaAddr
	}
	sURL, err := url.Parse(*snapshotAddr)
	if err != nil {
		return nil, err
	}
	config.snapshotAddr = *sURL

	// From timestamp
	from, err := time.Parse(timeLayout, *fromTimestamp)
	if err != nil {
		return nil, err
	}
	config.from = from
	// To timestamp
	to, err := time.Parse(timeLayout, *toTimestamp)
	if err != nil {
		return nil, err
	}
	config.to = to

	// Template vars
	config.vars = make(map[string]string)
	for _, pairS := range strings.Split(*templateVars, ";") {
		if len(pairS) > 2 {
			pairA := strings.Split(pairS, "=")
			if len(pairA) != 2 {
				return nil, errors.New("\"template_vars\" contained an invalid pairing: \"" + pairS + "\"")
			}

			config.vars[pairA[0]] = pairA[1]
		}
	}

	return config, nil
}

func getDashboardDef(config *config) (string, error) {
	// Get dashboard def
	reqURL := config.grafanaAddr
	reqURL.Path = reqURL.Path + "api/dashboards/db/" + config.dashSlug
	log.Debugf("Requesting dashboard definition from: %s", reqURL.String())

	req, err := http.NewRequest("get", reqURL.String(), nil)
	if err != nil {
		return "", err
	}
	req.Header.Add("Authorization", "Bearer "+config.apiKey)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func getDatasourceDefs(config *config) (map[string]interface{}, error) {
	// Get datasource defs
	reqURL := config.grafanaAddr
	reqURL.Path = reqURL.Path + "api/datasources"
	log.Debugf("Requesting datasource definitions from: %s", reqURL.String())

	req, err := http.NewRequest("get", reqURL.String(), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", "Bearer "+config.apiKey)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, errors.New("Unexpected status code: " + resp.Status)
	}
	// read body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	// parse body
	var datasources []interface{}
	if err = json.Unmarshal(body, &datasources); err != nil {
		return nil, err
	}
	// map datasources to their names
	datasourceMap := make(map[string]interface{})
	for _, ds := range datasources {
		datasourceMap[ds.(map[string]interface{})["name"].(string)] = ds
	}

	return datasourceMap, nil
}

func substituteVars(config *config, dashboardString string) (string, error) {
	for k, v := range config.vars {
		vk := "$" + k
		log.Debugf("Replacing \"%s\" with \"%s\"", vk, v)
		dashboardString = strings.Replace(dashboardString, vk, v, -1)
	}
	return dashboardString, nil
}

func fetchDataPointsPrometheus(config *config, target, datasource map[string]interface{}) ([]snapshotData, error) {
	return nil, nil
}

func fetchDataPointsElastic(config *config, target, datasource map[string]interface{}) ([]snapshotData, error) {
	return nil, nil
}

var aliasRe = regexp.MustCompile(`{{\s*(.+?)\s*}}`)

// renderTemplate is a re-implementation of renderTemplate in
// Grafana’s Prometheus datasource; for the original, see:
// https://github.com/grafana/grafana/blob/79138e211fac98bf1d12f1645ecd9fab5846f4fb/public/app/plugins/datasource/prometheus/datasource.ts#L263
func renderTemplate(format string, metric model.Metric) string {
	return aliasRe.ReplaceAllStringFunc(format, func(match string) string {
		matches := aliasRe.FindStringSubmatch(match)
		return string(metric[model.LabelName(matches[1])])
	})
}

func main() {
	// Configure
	config, err := parseAndValidateFlags()
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	log.Info("Snapshot config:")
	log.Infof("Grafana Address: %s", config.grafanaAddr.String())
	log.Infof("Grafana API Key: %s", config.apiKey)
	log.Infof("Dashboard Slug: %s", config.dashSlug)
	log.Infof("Snapshot Address: %s", config.snapshotAddr.String())
	log.Infof("From Timestamp: %s", config.from.String())
	log.Infof("To Timestamp: %s", config.to.String())
	log.Infof("Template Vars:")
	for k, v := range config.vars {
		log.Infof("  %s = %s", k, v)
	}

	// Get the dashboard to snapshot
	rawDashString, err := getDashboardDef(config)
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	log.Infof("Fetched dashboard definition for \"%s\"", config.dashSlug)

	// Get available datasources and map them to their names
	datasourceMap, err := getDatasourceDefs(config)
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	log.Info("Fetched datasource definitions")

	// Replace all templated variables
	subbedDashString, err := substituteVars(config, rawDashString)
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	log.Infof("Substituted dashboard variables")

	// Unmarshal it to a simplejson object
	var dash map[string]interface{}
	if err = json.Unmarshal([]byte(subbedDashString), &dash); err != nil {
		log.Errorf("Could not decode dashboard json: %s", err.Error())
		log.Exit(1)
	}

	// For each row in dashboard...
	for _, row := range dash["dashboard"].(map[string]interface{})["rows"].([]interface{}) {
		// For each panel in row...
		for _, p := range row.(map[string]interface{})["panels"].([]interface{}) {
			panel := p.(map[string]interface{})
			// Get the datasource and targets
			datasourceName := panel["datasource"].(string)
			targets := panel["targets"].([]interface{})
			// For each target in panel...
			for _, t := range targets {
				target := t.(map[string]interface{})
				// Calculate “step” like Grafana. For the original code, see:
				// https://github.com/grafana/grafana/blob/79138e211fac98bf1d12f1645ecd9fab5846f4fb/public/app/plugins/datasource/prometheus/datasource.ts#L83
				intervalFactor := float64(1)
				if target["intervalFactor"] != nil {
					intervalFactor = target["intervalFactor"].(float64)
				}
				queryRange := config.to.Sub(config.from).Seconds()
				maxDataPoints := float64(500)
				step := math.Ceil((queryRange / maxDataPoints) * intervalFactor)
				if queryRange/step > 11000 {
					step = math.Ceil(queryRange / 11000)
				}
				// Lookup datasource
				datasource := datasourceMap[datasourceName].(map[string]interface{})

				// Fetch data points from datasource proxy
				var dataPoints []snapshotData
				switch datasource["type"].(string) {
				case "prometheus":
					dataPoints, err = fetchDataPointsPrometheus(config, target, datasource)
					if err != nil {
						log.Error(err)
						log.Exit(1)
					}
				case "elasticsearch":
					dataPoints, err = fetchDataPointsElastic(config, target, datasource)
					if err != nil {
						log.Error(err)
						log.Exit(1)
					}
				default:
					log.Errorf("Unsupported datasource type: %s", datasource["type"].(string))
					continue
				}
				var snapshotData []interface{}
				// build snapshot data
				for idx, dp := range dataPoints {
					if target["legendFormat"] != nil && target["legendFormat"].(string) != "" {
						dp.Target = renderTemplate(target["legendFormat"].(string), dp.Metric)
					} else {
						dp.Target = dp.Metric.String()
					}
					dataPoints[idx] = dp
					snapshotData = append(snapshotData, dp)
				}
				// insert snapshot data into panels
				panel["snapshotData"] = snapshotData
				panel["targets"] = []interface{}{}
				panel["links"] = []interface{}{}
				panel["datasource"] = []interface{}{}
			}
		}
	}
	b, err := json.Marshal(dash["dashboard"])
	log.Info(string(b))
}
<commit_msg>Mostly works for prometheus graphs<commit_after>package main

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/prometheus/client_golang/api/prometheus"
	"github.com/prometheus/common/model"
	log "github.com/sirupsen/logrus"
)

var (
	timeLayout      = "2006-01-02 15:04:05"
	grafanaAddr     = flag.String("grafana_addr", "http://localhost:3000/", "The address of the Grafana instance to snapshot.")
	grafanaAPIKey   = flag.String("grafana_api_key", "", "The address of the Grafana instance to snapshot.")
	dashSlug        = flag.String("dashboard_slug", "home", "The url friendly version of the dashboard title to snapshot from the \"grafana_addr\" address.")
	snapshotAddr    = flag.String("snapshot_addr", "", "The location to submit the snapshot. Defaults to the grafana address.")
	snapshotExpires = flag.Duration("snapshot_expires", 0, "How long to keep the snapshot for (60s, 1h, 10d, etc), defaults to never.")
	snapshotName    = flag.String("snapshot_name", "", "What to call the snapshot. Defaults to \"from\" date plus dashboard slug.")
	fromTimestamp   = flag.String("from", (time.Now().Truncate(time.Hour * 24)).Format(timeLayout), "The \"from\" time range. Must be absolute in the form \"YYYY-MM-DD HH:mm:ss\" (\"2017-01-23 12:34:56\"). Defaults to start of day.")
	toTimestamp     = flag.String("to", time.Now().Format(timeLayout), "The \"to\" time range. Must be absolute in the form \"YYYY-MM-DD HH:mm:ss\" (\"2017-01-23 12:34:57\"). Must be greater than to \"to\" value. Defaults to now")
	templateVars    = flag.String("template_vars", "", "a list of key value pairs to set the dashboard's template variables, in the format 'key1=val1;key2=val2'")
	debug           = flag.Bool("debug", false, "Turns on debug logging")
)

type config struct {
	grafanaAddr  url.URL
	apiKey       string
	dashSlug     string
	snapshotAddr url.URL
	snapshotName string
	from         time.Time
	to           time.Time
	vars         map[string]string
	expires      int
}

type snapshotData struct {
	Target     string          `json:"target"`
	Datapoints [][]interface{} `json:"datapoints"`
	// Metric is a set of labels (e.g. instance=alp) which is retained
	// so that we can replace labels according to target.legendFormat.
	Metric model.Metric `json:"-"`
}

func parseAndValidateFlags() (*config, error) {
	flag.Parse()
	config := &config{}

	if *debug {
		log.SetLevel(log.DebugLevel)
	}

	// Parse Grafana Address
	gURL, err := url.Parse(*grafanaAddr)
	if err != nil {
		return nil, err
	}
	if !strings.HasSuffix(gURL.Path, "/") {
		gURL.Path = gURL.Path + "/"
	}
	config.grafanaAddr = *gURL

	// Grafana API key
	if len(*grafanaAPIKey) == 0 {
		return nil, errors.New("\"grafana_api_key\" cannot be empty")
	}
	config.apiKey = *grafanaAPIKey

	// Dashboard slug
	if strings.Index(*dashSlug, " ") != -1 {
		return nil, errors.New("\"dashboard_slug\" contained an invalid character: \" \"")
	}
	config.dashSlug = *dashSlug

	// Parse Snapshot host Address
	if len(*snapshotAddr) == 0 {
		*snapshotAddr = *grafanaAddr
	}
	sURL, err := url.Parse(*snapshotAddr)
	if err != nil {
		return nil, err
	}
	if !strings.HasSuffix(sURL.Path, "/") {
		sURL.Path = sURL.Path + "/"
	}
	config.snapshotAddr = *sURL

	// Parse expiry
	config.expires = int(*snapshotExpires / time.Second)

	// From timestamp
	from, err := time.Parse(timeLayout, *fromTimestamp)
	if err != nil {
		return nil, err
	}
	config.from = from
	// To timestamp
	to, err := time.Parse(timeLayout, *toTimestamp)
	if err != nil {
		return nil, err
	}
	config.to = to

	// Parse name
	if len(*snapshotName) == 0 {
		*snapshotName = fmt.Sprintf("%s %s", config.to.Format("2006-01-02"), config.dashSlug)
	}
	config.snapshotName = *snapshotName

	// Template vars
	config.vars = make(map[string]string)
	for _, pairS := range strings.Split(*templateVars, ";") {
		if len(pairS) > 2 {
			pairA := strings.Split(pairS, "=")
			if len(pairA) != 2 {
				return nil, errors.New("\"template_vars\" contained an invalid pairing: \"" + pairS + "\"")
			}

			config.vars[pairA[0]] = pairA[1]
		}
	}

	return config, nil
}

func getDashboardDef(config *config) (string, error) {
	// Get dashboard def
	reqURL := config.grafanaAddr
	reqURL.Path = reqURL.Path + "api/dashboards/db/" + config.dashSlug
	log.Debugf("Requesting dashboard definition from: %s", reqURL.String())

	req, err := http.NewRequest("get", reqURL.String(), nil)
	if err != nil {
		return "", err
	}
	req.Header.Add("Authorization", "Bearer "+config.apiKey)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func getDatasourceDefs(config *config) (map[string]interface{}, error) {
	// Get datasource defs
	reqURL := config.grafanaAddr
	reqURL.Path = reqURL.Path + "api/datasources"
	log.Debugf("Requesting datasource definitions from: %s", reqURL.String())

	req, err := http.NewRequest("get", reqURL.String(), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", "Bearer "+config.apiKey)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, errors.New("Unexpected status code: " + resp.Status)
	}
	// read body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	// parse body
	var datasources []interface{}
	if err = json.Unmarshal(body, &datasources); err != nil {
		return nil, err
	}
	// map datasources to their names
	datasourceMap := make(map[string]interface{})
	for _, ds := range datasources {
		datasourceMap[ds.(map[string]interface{})["name"].(string)] = ds
	}

	return datasourceMap, nil
}

func substituteVars(config *config, dashboardString string) (string, error) {
	for k, v := range config.vars {
		vk := "$" + k
		log.Debugf("Replacing \"%s\" with \"%s\"", vk, v)
		dashboardString = strings.Replace(dashboardString, vk, v, -1)
	}
	return dashboardString, nil
}

type grafanaProxyTransport struct {
	http.Transport
	grafanaAPIKey string
}

func (gpt *grafanaProxyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	req.Header.Add("Authorization", "Bearer "+gpt.grafanaAPIKey)
	t := http.Transport{}
	resp, err := t.RoundTrip(req)
	return resp, err
}

func fetchDataPointsPrometheus(config *config, target, datasource map[string]interface{}, step float64) ([]snapshotData, error) {
	reqURL := config.grafanaAddr
	reqURL.Path = reqURL.Path + "api/datasources/proxy/" + strconv.Itoa(int(datasource["id"].(float64)))
	log.Debugf("Requesting data points from: %s", reqURL.String())

	transport := grafanaProxyTransport{grafanaAPIKey: config.apiKey}

	client, err := prometheus.New(prometheus.Config{Address: reqURL.String(), Transport: &transport})
	if err != nil {
		return nil, err
	}
	api := prometheus.NewQueryAPI(client)

	// Query
	val, err := api.QueryRange(context.Background(), target["expr"].(string), prometheus.Range{
		Start: config.from,
		End:   config.to,
		Step:  time.Duration(step) * time.Second,
	})
	if err != nil {
		return nil, err
	}

	if val.Type() != model.ValMatrix {
		return nil, fmt.Errorf("Unexpected value type: got %q, want %q", val.Type(), model.ValMatrix)
	}
	matrix, ok := val.(model.Matrix)
	if !ok {
		return nil, fmt.Errorf("Bug: val.Type() == model.ValMatrix, but type assertion failed")
	}

	results := make([]snapshotData, matrix.Len())
	for idx, stream := range matrix {
		datapoints := make([][]interface{}, len(stream.Values))
		for idx, samplepair := range stream.Values {
			if math.IsNaN(float64(samplepair.Value)) {
				datapoints[idx] = []interface{}{nil, float64(samplepair.Timestamp)}
			} else {
				datapoints[idx] = []interface{}{float64(samplepair.Value), float64(samplepair.Timestamp)}
			}
		}

		results[idx] = snapshotData{
			Metric:     stream.Metric,
			Datapoints: datapoints,
		}
	}

	return results, nil
}

func fetchDataPointsElastic(config *config, target, datasource map[string]interface{}, step float64) ([]snapshotData, error) {
	return nil, nil
}

var aliasRe = regexp.MustCompile(`{{\s*(.+?)\s*}}`)

// renderTemplate is a re-implementation of renderTemplate in
// Grafana’s Prometheus datasource; for the original, see:
// https://github.com/grafana/grafana/blob/79138e211fac98bf1d12f1645ecd9fab5846f4fb/public/app/plugins/datasource/prometheus/datasource.ts#L263
func renderTemplate(format string, metric model.Metric) string {
	return aliasRe.ReplaceAllStringFunc(format, func(match string) string {
		matches := aliasRe.FindStringSubmatch(match)
		return string(metric[model.LabelName(matches[1])])
	})
}

func main() {
	// Configure
	config, err := parseAndValidateFlags()
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	log.Info("Snapshot config:")
	log.Infof("Grafana Address: %s", config.grafanaAddr.String())
	log.Infof("Grafana API Key: %s", config.apiKey)
	log.Infof("Dashboard Slug: %s", config.dashSlug)
	log.Infof("Snapshot Address: %s", config.snapshotAddr.String())
	log.Infof("From Timestamp: %s", config.from.String())
	log.Infof("To Timestamp: %s", config.to.String())
	log.Infof("Template Vars:")
	for k, v := range config.vars {
		log.Infof("  %s = %s", k, v)
	}

	// Get the dashboard to snapshot
	rawDashString, err := getDashboardDef(config)
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	log.Infof("Fetched dashboard definition for \"%s\"", config.dashSlug)

	// Get available datasources and map them to their names
	datasourceMap, err := getDatasourceDefs(config)
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	log.Info("Fetched datasource definitions")

	// Replace all templated variables
	subbedDashString, err := substituteVars(config, rawDashString)
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	log.Infof("Substituted dashboard variables")

	// Unmarshal it to a simplejson object
	var dash map[string]interface{}
	if err = json.Unmarshal([]byte(subbedDashString), &dash); err != nil {
		log.Errorf("Could not decode dashboard json: %s", err.Error())
		log.Exit(1)
	}

	// For each row in dashboard...
	for _, row := range dash["dashboard"].(map[string]interface{})["rows"].([]interface{}) {
		// For each panel in row...
		for _, p := range row.(map[string]interface{})["panels"].([]interface{}) {
			panel := p.(map[string]interface{})
			// Get the datasource and targets
			datasourceName := panel["datasource"].(string)
			targets := panel["targets"].([]interface{})
			// For each target in panel...
			for _, t := range targets {
				target := t.(map[string]interface{})
				// Calculate “step” like Grafana. For the original code, see:
				// https://github.com/grafana/grafana/blob/79138e211fac98bf1d12f1645ecd9fab5846f4fb/public/app/plugins/datasource/prometheus/datasource.ts#L83
				intervalFactor := float64(1)
				if target["intervalFactor"] != nil {
					intervalFactor = target["intervalFactor"].(float64)
				}
				queryRange := config.to.Sub(config.from).Seconds()
				maxDataPoints := float64(500)
				step := math.Ceil((queryRange / maxDataPoints) * intervalFactor)
				if queryRange/step > 11000 {
					step = math.Ceil(queryRange / 11000)
				}
				// Lookup datasource
				datasource := datasourceMap[datasourceName].(map[string]interface{})

				// Fetch data points from datasource proxy
				var dataPoints []snapshotData
				switch datasource["type"].(string) {
				case "prometheus":
					dataPoints, err = fetchDataPointsPrometheus(config, target, datasource, step)
					if err != nil {
						log.Error(err)
						log.Exit(1)
					}
				case "elasticsearch":
					dataPoints, err = fetchDataPointsElastic(config, target, datasource, step)
					if err != nil {
						log.Error(err)
						log.Exit(1)
					}
				default:
					log.Errorf("Unsupported datasource type: %s", datasource["type"].(string))
					continue
				}
				var snapshotData []interface{}
				// build snapshot data
				for idx, dp := range dataPoints {
					if target["legendFormat"] != nil && target["legendFormat"].(string) != "" {
						dp.Target = renderTemplate(target["legendFormat"].(string), dp.Metric)
					} else {
						dp.Target = dp.Metric.String()
					}
					dataPoints[idx] = dp
					snapshotData = append(snapshotData, dp)
				}
				// insert snapshot data into panels
				panel["snapshotData"] = snapshotData
				panel["targets"] = []interface{}{}
				panel["links"] = []interface{}{}
				panel["datasource"] = []interface{}{}
			}
		}
	}

	// Build Snapshot
	snapshot := make(map[string]interface{})
	snapshot["dashboard"] = dash["dashboard"]
	snapshot["expires"] = config.expires
	snapshot["name"] = config.snapshotName
	b, err := json.Marshal(snapshot)

	// Post Snapshot
	reqURL := config.snapshotAddr
	reqURL.Path = reqURL.Path + "api/snapshots"
	log.Debugf("Posting snapshot to: %s", reqURL.String())

	req, err := http.NewRequest("post", reqURL.String(), bytes.NewReader(b))
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	req.Header.Add("Authorization", "Bearer "+config.apiKey)
	req.Header.Add("Content-Type", "application/json")
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		log.Errorf("Unexpected status code: %s", resp.Status)
		log.Exit(1)
	}
	// read body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Error(err)
		log.Exit(1)
	}
	// parse body
	var snapshotResponse map[string]interface{}
	if err = json.Unmarshal(body, &snapshotResponse); err != nil {
		log.Error(err)
		log.Exit(1)
	}

	log.Infof("Snapshot URL: %s", snapshotResponse["url"].(string))
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/fatih/color"
)

var (
	internRegex     = regexp.MustCompile(`(?i)[^\w](?:interns?|internship)[^\w]`)
	ansiEscapeRegex = regexp.MustCompile(`[[:cntrl:]]`)
	bayRegex        = regexp.MustCompile(`(?i)[^\w](?:san fran\w*|sf|bay area|mountain view|oakland|berkeley)[^\w]`)
	dallasRegex     = regexp.MustCompile(`(?i)[^\w](?:dallas|dfw|fort worth|richardson)[^\w]`)

	postTitleColor      = color.New(color.BgGreen).Add(color.Bold).SprintfFunc()
	commentTitleColor   = color.New(color.BgBlue).SprintfFunc()
	bayCommentColor     = color.New(color.FgMagenta).SprintfFunc()
	dallasCommentColor  = color.New(color.FgCyan).SprintfFunc()
	regularCommentColor = fmt.Sprintf
)

func panicIf(err error) {
	if err != nil {
		panic(err)
	}
}

func displayPaginatedString(paginatedString string, pager string) {
	cmd := exec.Command(pager)
	cmd.Stdin = strings.NewReader(paginatedString)
	cmd.Stdout = os.Stdout

	err := cmd.Run()
	panicIf(err)
}

func getLatestPost() (title string, url string) {
	whoishiring, err := goquery.NewDocument("https://news.ycombinator.com/submitted?id=whoishiring")
	panicIf(err)

	posts := whoishiring.Find("tr > td:nth-child(3)")
	for i := 0; i < posts.Length(); i++ {
		post := posts.Eq(i)
		title = post.Text()

		if strings.Contains(title, "Who is hiring?") {
			url, _ = post.Find("a").Attr("href")
			return title, url
		}
	}
	panic(errors.New("Error fetching/parsing whoishiring's posts."))
}

func getFormattedInternComments(url string) (formattedComments string, totalBay int, totalDallas int) {
	latestPost, err := goquery.NewDocument("https://news.ycombinator.com/" + url)
	panicIf(err)

	totalComments := 0
	comments := latestPost.Find("table .comtr > td > table > tbody > tr")
	for i := 0; i < comments.Length(); i++ {
		comment := comments.Eq(i)

		width, _ := comment.Find(".ind img").Attr("width")
		if width == "0" {
			text := ansiEscapeRegex.ReplaceAllStringFunc(comment.Text(), func(match string) string {
				if match == "\n" {
					return match
				}
				return ""
			})

			if internRegex.MatchString(text) {
				var formatComment func(string, ...interface{}) string

				switch {
				case bayRegex.MatchString(text):
					totalBay++
					formatComment = bayCommentColor
				case dallasRegex.MatchString(text):
					totalDallas++
					formatComment = dallasCommentColor
				default:
					formatComment = regularCommentColor
				}

				totalComments++
				formattedComments += "\n" // adding newlines to a template string with bgcolor makes it look bad
				formattedComments += commentTitleColor("Intern Comment No. %d", totalComments)
				formattedComments += "\n"
				formattedComments += formatComment("%s", text)
				formattedComments += "\n"
			}
		}
	}
	return
}

func main() {
	pager := os.Getenv("PAGER")

	title, url := getLatestPost()
	postTitle := postTitleColor("%s", title) + "\n"

	formattedComments, totalBay, totalDallas := getFormattedInternComments(url)
	postPrefix := bayCommentColor("Bay Area Comments (%d)", totalBay) + ", " + dallasCommentColor("Dallas Area Comments (%d)", totalDallas) + "\n"

	paginatedString := postTitle + postPrefix + formattedComments

	displayPaginatedString(paginatedString, pager)
}
<commit_msg>Whoops, newline escape code apparently escapes ANSI color codes as well, fixed.<commit_after>package main

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/fatih/color"
)

var (
	internRegex     = regexp.MustCompile(`(?i)[^\w](?:interns?|internship)[^\w]`)
	ansiEscapeRegex = regexp.MustCompile(`[[:cntrl:]]`)
	bayRegex        = regexp.MustCompile(`(?i)[^\w](?:san fran\w*|sf|bay area|mountain view|oakland|berkeley)[^\w]`)
	dallasRegex     = regexp.MustCompile(`(?i)[^\w](?:dallas|dfw|fort worth|richardson)[^\w]`)

	postTitleColor      = color.New(color.BgGreen).Add(color.Bold).SprintfFunc()
	commentTitleColor   = color.New(color.BgBlue).SprintfFunc()
	bayCommentColor     = color.New(color.FgMagenta).SprintfFunc()
	dallasCommentColor  = color.New(color.FgCyan).SprintfFunc()
	regularCommentColor = fmt.Sprintf
)

func panicIf(err error) {
	if err != nil {
		panic(err)
	}
}

func displayPaginatedString(paginatedString string, pager string) {
	cmd := exec.Command(pager)
	cmd.Stdin = strings.NewReader(paginatedString)
	cmd.Stdout = os.Stdout

	err := cmd.Run()
	panicIf(err)
}

func getLatestPost() (title string, url string) {
	whoishiring, err := goquery.NewDocument("https://news.ycombinator.com/submitted?id=whoishiring")
	panicIf(err)

	posts := whoishiring.Find("tr > td:nth-child(3)")
	for i := 0; i < posts.Length(); i++ {
		post := posts.Eq(i)
		title = post.Text()

		if strings.Contains(title, "Who is hiring?") {
			url, _ = post.Find("a").Attr("href")
			return title, url
		}
	}
	panic(errors.New("Error fetching/parsing whoishiring's posts."))
}

func colorMultiLineText(text string, formatComment func(string, ...interface{}) string) string {
	stringArray := strings.Split(text, "\n")

	for i, _ := range stringArray {
		stringArray[i] = formatComment("%s", stringArray[i])
	}

	return strings.Join(stringArray, "\n")
}

func getFormattedInternComments(url string) (formattedComments string, totalBay int, totalDallas int) {
	latestPost, err := goquery.NewDocument("https://news.ycombinator.com/" + url)
	panicIf(err)

	totalComments := 0
	comments := latestPost.Find("table .comtr > td > table > tbody > tr")
	for i := 0; i < comments.Length(); i++ {
		comment := comments.Eq(i)

		width, _ := comment.Find(".ind img").Attr("width")
		if width == "0" {
			text := ansiEscapeRegex.ReplaceAllStringFunc(comment.Text(), func(match string) string {
				if match == "\n" {
					return match
				}
				return ""
			})

			if internRegex.MatchString(text) {
				var formatComment func(string, ...interface{}) string

				switch {
				case bayRegex.MatchString(text):
					totalBay++
					formatComment = bayCommentColor
				case dallasRegex.MatchString(text):
					totalDallas++
					formatComment = dallasCommentColor
				default:
					formatComment = regularCommentColor
				}

				totalComments++
				formattedComments += "\n" // adding newlines to a template string with bgcolor makes it look bad
				formattedComments += commentTitleColor("Intern Comment No. %d", totalComments)
				formattedComments += "\n"
				formattedComments += colorMultiLineText(text, formatComment)
			}
		}
	}
	return
}

func main() {
	pager := os.Getenv("PAGER")

	title, url := getLatestPost()
	postTitle := postTitleColor("%s", title) + "\n"

	formattedComments, totalBay, totalDallas := getFormattedInternComments(url)
	postPrefix := bayCommentColor("Bay Area Comments (%d)", totalBay) + ", " + dallasCommentColor("Dallas Area Comments (%d)", totalDallas) + "\n"

	paginatedString := postTitle + postPrefix + formattedComments

	displayPaginatedString(paginatedString, pager)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"runtime"
	"runtime/debug"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/fabiolb/fabio/admin"
	"github.com/fabiolb/fabio/cert"
	"github.com/fabiolb/fabio/config"
	"github.com/fabiolb/fabio/exit"
	"github.com/fabiolb/fabio/logger"
	"github.com/fabiolb/fabio/metrics"
	"github.com/fabiolb/fabio/proxy"
	"github.com/fabiolb/fabio/proxy/tcp"
	"github.com/fabiolb/fabio/registry"
	"github.com/fabiolb/fabio/registry/consul"
	"github.com/fabiolb/fabio/registry/file"
	"github.com/fabiolb/fabio/registry/static"
	"github.com/fabiolb/fabio/route"
	dmp "github.com/sergi/go-diff/diffmatchpatch"
)

// version contains the version number
//
// It is set by build/release.sh for tagged releases
// so that 'go get' just works.
//
// It is also set by the linker when fabio
// is built via the Makefile or the build/docker.sh
// script to ensure the correct version nubmer
var version = "1.4.3"

var shuttingDown int32

func main() {
	cfg, err := config.Load(os.Args, os.Environ())
	if err != nil {
		exit.Fatalf("[FATAL] %s. %s", version, err)
	}
	if cfg == nil {
		fmt.Println(version)
		return
	}

	log.Printf("[INFO] Runtime config\n" + toJSON(cfg))
	log.Printf("[INFO] Version %s starting", version)
	log.Printf("[INFO] Go runtime is %s", runtime.Version())

	exit.Listen(func(s os.Signal) {
		atomic.StoreInt32(&shuttingDown, 1)
		proxy.Shutdown(cfg.Proxy.ShutdownWait)
		if registry.Default == nil {
			return
		}
		registry.Default.Deregister()
	})

	// init metrics early since that create the global metric registries
	// that are used by other parts of the code.
	initMetrics(cfg)
	initRuntime(cfg)
	initBackend(cfg)
	startAdmin(cfg)

	first := make(chan bool)
	go watchBackend(cfg, first)
	log.Print("[INFO] Waiting for first routing table")
	<-first

	// create proxies after metrics since they use the metrics registry.
	startServers(cfg)
	exit.Wait()
	log.Print("[INFO] Down")
}

func newHTTPProxy(cfg *config.Config) http.Handler {
	var w io.Writer
	switch cfg.Log.AccessTarget {
	case "":
		log.Printf("[INFO] Access logging disabled")
	case "stdout":
		log.Printf("[INFO] Writing access log to stdout")
		w = os.Stdout
	default:
		exit.Fatal("[FATAL] Invalid access log target ", cfg.Log.AccessTarget)
	}

	format := cfg.Log.AccessFormat
	switch format {
	case "common":
		format = logger.CommonFormat
	case "combined":
		format = logger.CombinedFormat
	}

	l, err := logger.New(w, format)
	if err != nil {
		exit.Fatal("[FATAL] Invalid log format: ", err)
	}

	pick := route.Picker[cfg.Proxy.Strategy]
	match := route.Matcher[cfg.Proxy.Matcher]
	notFound := metrics.DefaultRegistry.GetCounter("notfound")
	log.Printf("[INFO] Using routing strategy %q", cfg.Proxy.Strategy)
	log.Printf("[INFO] Using route matching %q", cfg.Proxy.Matcher)

	newTransport := func(tlscfg *tls.Config) *http.Transport {
		return &http.Transport{
			ResponseHeaderTimeout: cfg.Proxy.ResponseHeaderTimeout,
			MaxIdleConnsPerHost:   cfg.Proxy.MaxConn,
			Dial: (&net.Dialer{
				Timeout:   cfg.Proxy.DialTimeout,
				KeepAlive: cfg.Proxy.KeepAliveTimeout,
			}).Dial,
			TLSClientConfig: tlscfg,
		}
	}

	return &proxy.HTTPProxy{
		Config:            cfg.Proxy,
		Transport:         newTransport(nil),
		InsecureTransport: newTransport(&tls.Config{InsecureSkipVerify: true}),
		Lookup: func(r *http.Request) *route.Target {
			t := route.GetTable().Lookup(r, r.Header.Get("trace"), pick, match)
			if t == nil {
				notFound.Inc(1)
				log.Print("[WARN] No route for ", r.Host, r.URL)
			}
			return t
		},
		Requests: metrics.DefaultRegistry.GetTimer("requests"),
		Noroute:  metrics.DefaultRegistry.GetCounter("notfound"),
		Logger:   l,
	}
}

func lookupHostFn(cfg *config.Config) func(string) string {
	pick := route.Picker[cfg.Proxy.Strategy]
	notFound := metrics.DefaultRegistry.GetCounter("notfound")
	return func(host string) string {
		t := route.GetTable().LookupHost(host, pick)
		if t == nil {
			notFound.Inc(1)
			log.Print("[WARN] No route for ", host)
			return ""
		}
		return t.URL.Host
	}
}

func makeTLSConfig(l config.Listen) *tls.Config {
	if l.CertSource.Name == "" {
		return nil
	}
	src, err := cert.NewSource(l.CertSource)
	if err != nil {
		exit.Fatalf("[FATAL] Failed to create cert source %s. %s", l.CertSource.Name, err)
	}
	tlscfg, err := cert.TLSConfig(src, l.StrictMatch)
	if err != nil {
		exit.Fatalf("[FATAL] Failed to create TLS config for cert source %s. %s", l.CertSource.Name, err)
	}
	return tlscfg
}

func startAdmin(cfg *config.Config) {
	log.Printf("[INFO] Admin server listening on %q", cfg.UI.Listen.Addr)
	go func() {
		l := cfg.UI.Listen
		tlscfg := makeTLSConfig(l)
		srv := &admin.Server{
			Color:    cfg.UI.Color,
			Title:    cfg.UI.Title,
			Version:  version,
			Commands: route.Commands,
			Cfg:      cfg,
		}
		if err := srv.ListenAndServe(l, tlscfg); err != nil {
			exit.Fatal("[FATAL] ui: ", err)
		}
	}()
}

func startServers(cfg *config.Config) {
	for _, l := range cfg.Listen {
		l := l // capture loop var for go routines below
		tlscfg := makeTLSConfig(l)

		log.Printf("[INFO] %s proxy listening on %s", strings.ToUpper(l.Proto), l.Addr)
		if tlscfg != nil && tlscfg.ClientAuth == tls.RequireAndVerifyClientCert {
			log.Printf("[INFO] Client certificate authentication enabled on %s", l.Addr)
		}

		switch l.Proto {
		case "http", "https":
			go func() {
				h := newHTTPProxy(cfg)
				if err := proxy.ListenAndServeHTTP(l, h, tlscfg); err != nil {
					exit.Fatal("[FATAL] ", err)
				}
			}()
		case "tcp":
			go func() {
				h := &tcp.Proxy{cfg.Proxy.DialTimeout, lookupHostFn(cfg)}
				if err := proxy.ListenAndServeTCP(l, h, tlscfg); err != nil {
					exit.Fatal("[FATAL] ", err)
				}
			}()
		case "tcp+sni":
			go func() {
				h := &tcp.SNIProxy{cfg.Proxy.DialTimeout, lookupHostFn(cfg)}
				if err := proxy.ListenAndServeTCP(l, h, tlscfg); err != nil {
					exit.Fatal("[FATAL] ", err)
				}
			}()
		default:
			exit.Fatal("[FATAL] Invalid protocol ", l.Proto)
		}
	}
}

func initMetrics(cfg *config.Config) {
	if cfg.Metrics.Target == "" {
		log.Printf("[INFO] Metrics disabled")
		return
	}

	var err error
	if metrics.DefaultRegistry, err = metrics.NewRegistry(cfg.Metrics); err != nil {
		exit.Fatal("[FATAL] ", err)
	}
	if route.ServiceRegistry, err = metrics.NewRegistry(cfg.Metrics); err != nil {
		exit.Fatal("[FATAL] ", err)
	}
}

func initRuntime(cfg *config.Config) {
	if os.Getenv("GOGC") == "" {
		log.Print("[INFO] Setting GOGC=", cfg.Runtime.GOGC)
		debug.SetGCPercent(cfg.Runtime.GOGC)
	} else {
		log.Print("[INFO] Using GOGC=", os.Getenv("GOGC"), " from env")
	}

	if os.Getenv("GOMAXPROCS") == "" {
		log.Print("[INFO] Setting GOMAXPROCS=", cfg.Runtime.GOMAXPROCS)
		runtime.GOMAXPROCS(cfg.Runtime.GOMAXPROCS)
	} else {
		log.Print("[INFO] Using GOMAXPROCS=", os.Getenv("GOMAXPROCS"), " from env")
	}
}

func initBackend(cfg *config.Config) {
	var deadline = time.Now().Add(cfg.Registry.Timeout)

	var err error
	for {
		switch cfg.Registry.Backend {
		case "file":
			registry.Default, err = file.NewBackend(cfg.Registry.File.Path)
		case "static":
			registry.Default, err = static.NewBackend(cfg.Registry.Static.Routes)
		case "consul":
			registry.Default, err = consul.NewBackend(&cfg.Registry.Consul)
		default:
			exit.Fatal("[FATAL] Unknown registry backend ", cfg.Registry.Backend)
		}

		if err == nil {
			if err = registry.Default.Register(); err == nil {
				return
			}
		}
		log.Print("[WARN] Error initializing backend. ", err)

		if time.Now().After(deadline) {
			exit.Fatal("[FATAL] Timeout registering backend.")
		}

		time.Sleep(cfg.Registry.Retry)
		if atomic.LoadInt32(&shuttingDown) > 0 {
			exit.Exit(1)
		}
	}
}

func watchBackend(cfg *config.Config, first chan bool) {
	var (
		last   string
		svccfg string
		mancfg string

		once sync.Once
	)

	svc := registry.Default.WatchServices()
	man := registry.Default.WatchManual()

	for {
		select {
		case svccfg = <-svc:
		case mancfg = <-man:
		}

		// manual config overrides service config
		// order matters
		next := svccfg + "\n" + mancfg
		if next == last {
			continue
		}

		t, err := route.NewTable(next)
		if err != nil {
			log.Printf("[WARN] %s", err)
			continue
		}
		route.SetTable(t)
		logRoutes(last, next, cfg.Log.RoutesFormat)
		last = next

		once.Do(func() { close(first) })
	}
}

func logRoutes(last, next, format string) {
	fmtDiff := func(diffs []dmp.Diff) string {
		var b bytes.Buffer
		for _, d := range diffs {
			t := strings.TrimSpace(d.Text)
			if t == "" {
				continue
			}
			switch d.Type {
			case dmp.DiffDelete:
				b.WriteString("- ")
				b.WriteString(strings.Replace(t, "\n", "\n- ", -1))
			case dmp.DiffInsert:
				b.WriteString("+ ")
				b.WriteString(strings.Replace(t, "\n", "\n+ ", -1))
			}
		}
		return b.String()
	}

	const defFormat = "delta"
	switch format {
	case "delta":
		if delta := fmtDiff(dmp.New().DiffMain(last, next, true)); delta != "" {
			log.Printf("[INFO] Config updates\n%s", delta)
		}

	case "all":
		log.Printf("[INFO] Updated config to\n%s", next)

	default:
		log.Printf("[WARN] Invalid route format %q. Defaulting to %q", format, defFormat)
		logRoutes(last, next, defFormat)
	}
}

func toJSON(v interface{}) string {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic("json: " + err.Error())
	}
	return string(data)
}
<commit_msg>Release v1.4.4<commit_after>package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"runtime"
	"runtime/debug"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/fabiolb/fabio/admin"
	"github.com/fabiolb/fabio/cert"
	"github.com/fabiolb/fabio/config"
	"github.com/fabiolb/fabio/exit"
	"github.com/fabiolb/fabio/logger"
	"github.com/fabiolb/fabio/metrics"
	"github.com/fabiolb/fabio/proxy"
	"github.com/fabiolb/fabio/proxy/tcp"
	"github.com/fabiolb/fabio/registry"
	"github.com/fabiolb/fabio/registry/consul"
	"github.com/fabiolb/fabio/registry/file"
	"github.com/fabiolb/fabio/registry/static"
	"github.com/fabiolb/fabio/route"
	dmp "github.com/sergi/go-diff/diffmatchpatch"
)

// version contains the version number
//
// It is set by build/release.sh for tagged releases
// so that 'go get' just works.
//
// It is also set by the linker when fabio
// is built via the Makefile or the build/docker.sh
// script to ensure the correct version nubmer
var version = "1.4.4"

var shuttingDown int32

func main() {
	cfg, err := config.Load(os.Args, os.Environ())
	if err != nil {
		exit.Fatalf("[FATAL] %s. %s", version, err)
	}
	if cfg == nil {
		fmt.Println(version)
		return
	}

	log.Printf("[INFO] Runtime config\n" + toJSON(cfg))
	log.Printf("[INFO] Version %s starting", version)
	log.Printf("[INFO] Go runtime is %s", runtime.Version())

	exit.Listen(func(s os.Signal) {
		atomic.StoreInt32(&shuttingDown, 1)
		proxy.Shutdown(cfg.Proxy.ShutdownWait)
		if registry.Default == nil {
			return
		}
		registry.Default.Deregister()
	})

	// init metrics early since that create the global metric registries
	// that are used by other parts of the code.
	initMetrics(cfg)
	initRuntime(cfg)
	initBackend(cfg)
	startAdmin(cfg)

	first := make(chan bool)
	go watchBackend(cfg, first)
	log.Print("[INFO] Waiting for first routing table")
	<-first

	// create proxies after metrics since they use the metrics registry.
	startServers(cfg)
	exit.Wait()
	log.Print("[INFO] Down")
}

func newHTTPProxy(cfg *config.Config) http.Handler {
	var w io.Writer
	switch cfg.Log.AccessTarget {
	case "":
		log.Printf("[INFO] Access logging disabled")
	case "stdout":
		log.Printf("[INFO] Writing access log to stdout")
		w = os.Stdout
	default:
		exit.Fatal("[FATAL] Invalid access log target ", cfg.Log.AccessTarget)
	}

	format := cfg.Log.AccessFormat
	switch format {
	case "common":
		format = logger.CommonFormat
	case "combined":
		format = logger.CombinedFormat
	}

	l, err := logger.New(w, format)
	if err != nil {
		exit.Fatal("[FATAL] Invalid log format: ", err)
	}

	pick := route.Picker[cfg.Proxy.Strategy]
	match := route.Matcher[cfg.Proxy.Matcher]
	notFound := metrics.DefaultRegistry.GetCounter("notfound")
	log.Printf("[INFO] Using routing strategy %q", cfg.Proxy.Strategy)
	log.Printf("[INFO] Using route matching %q", cfg.Proxy.Matcher)

	newTransport := func(tlscfg *tls.Config) *http.Transport {
		return &http.Transport{
			ResponseHeaderTimeout: cfg.Proxy.ResponseHeaderTimeout,
			MaxIdleConnsPerHost:   cfg.Proxy.MaxConn,
			Dial: (&net.Dialer{
				Timeout:   cfg.Proxy.DialTimeout,
				KeepAlive: cfg.Proxy.KeepAliveTimeout,
			}).Dial,
			TLSClientConfig: tlscfg,
		}
	}

	return &proxy.HTTPProxy{
		Config:            cfg.Proxy,
		Transport:         newTransport(nil),
		InsecureTransport: newTransport(&tls.Config{InsecureSkipVerify: true}),
		Lookup: func(r *http.Request) *route.Target {
			t := route.GetTable().Lookup(r, r.Header.Get("trace"), pick, match)
			if t == nil {
				notFound.Inc(1)
				log.Print("[WARN] No route for ", r.Host, r.URL)
			}
			return t
		},
		Requests: metrics.DefaultRegistry.GetTimer("requests"),
		Noroute:  metrics.DefaultRegistry.GetCounter("notfound"),
		Logger:   l,
	}
}

func lookupHostFn(cfg *config.Config) func(string) string {
	pick := route.Picker[cfg.Proxy.Strategy]
	notFound := metrics.DefaultRegistry.GetCounter("notfound")
	return func(host string) string {
		t := route.GetTable().LookupHost(host, pick)
		if t == nil {
			notFound.Inc(1)
			log.Print("[WARN] No route for ", host)
			return ""
		}
		return t.URL.Host
	}
}

func makeTLSConfig(l config.Listen) *tls.Config {
	if l.CertSource.Name == "" {
		return nil
	}
	src, err := cert.NewSource(l.CertSource)
	if err != nil {
		exit.Fatalf("[FATAL] Failed to create cert source %s. %s", l.CertSource.Name, err)
	}
	tlscfg, err := cert.TLSConfig(src, l.StrictMatch)
	if err != nil {
		exit.Fatalf("[FATAL] Failed to create TLS config for cert source %s. %s", l.CertSource.Name, err)
	}
	return tlscfg
}

func startAdmin(cfg *config.Config) {
	log.Printf("[INFO] Admin server listening on %q", cfg.UI.Listen.Addr)
	go func() {
		l := cfg.UI.Listen
		tlscfg := makeTLSConfig(l)
		srv := &admin.Server{
			Color:    cfg.UI.Color,
			Title:    cfg.UI.Title,
			Version:  version,
			Commands: route.Commands,
			Cfg:      cfg,
		}
		if err := srv.ListenAndServe(l, tlscfg); err != nil {
			exit.Fatal("[FATAL] ui: ", err)
		}
	}()
}

func startServers(cfg *config.Config) {
	for _, l := range cfg.Listen {
		l := l // capture loop var for go routines below
		tlscfg := makeTLSConfig(l)

		log.Printf("[INFO] %s proxy listening on %s", strings.ToUpper(l.Proto), l.Addr)
		if tlscfg != nil && tlscfg.ClientAuth == tls.RequireAndVerifyClientCert {
			log.Printf("[INFO] Client certificate authentication enabled on %s", l.Addr)
		}

		switch l.Proto {
		case "http", "https":
			go func() {
				h := newHTTPProxy(cfg)
				if err := proxy.ListenAndServeHTTP(l, h, tlscfg); err != nil {
					exit.Fatal("[FATAL] ", err)
				}
			}()
		case "tcp":
			go func() {
				h := &tcp.Proxy{cfg.Proxy.DialTimeout, lookupHostFn(cfg)}
				if err := proxy.ListenAndServeTCP(l, h, tlscfg); err != nil {
					exit.Fatal("[FATAL] ", err)
				}
			}()
		case "tcp+sni":
			go func() {
				h := &tcp.SNIProxy{cfg.Proxy.DialTimeout, lookupHostFn(cfg)}
				if err := proxy.ListenAndServeTCP(l, h, tlscfg); err != nil {
					exit.Fatal("[FATAL] ", err)
				}
			}()
		default:
			exit.Fatal("[FATAL] Invalid protocol ", l.Proto)
		}
	}
}

func initMetrics(cfg *config.Config) {
	if cfg.Metrics.Target == "" {
		log.Printf("[INFO] Metrics disabled")
		return
	}

	var err error
	if metrics.DefaultRegistry, err = metrics.NewRegistry(cfg.Metrics); err != nil {
		exit.Fatal("[FATAL] ", err)
	}
	if route.ServiceRegistry, err = metrics.NewRegistry(cfg.Metrics); err != nil {
		exit.Fatal("[FATAL] ", err)
	}
}

func initRuntime(cfg *config.Config) {
	if os.Getenv("GOGC") == "" {
		log.Print("[INFO] Setting GOGC=", cfg.Runtime.GOGC)
		debug.SetGCPercent(cfg.Runtime.GOGC)
	} else {
		log.Print("[INFO] Using GOGC=", os.Getenv("GOGC"), " from env")
	}

	if os.Getenv("GOMAXPROCS") == "" {
		log.Print("[INFO] Setting GOMAXPROCS=", cfg.Runtime.GOMAXPROCS)
		runtime.GOMAXPROCS(cfg.Runtime.GOMAXPROCS)
	} else {
		log.Print("[INFO] Using GOMAXPROCS=", os.Getenv("GOMAXPROCS"), " from env")
	}
}

func initBackend(cfg *config.Config) {
	var deadline = time.Now().Add(cfg.Registry.Timeout)

	var err error
	for {
		switch cfg.Registry.Backend {
		case "file":
			registry.Default, err = file.NewBackend(cfg.Registry.File.Path)
		case "static":
			registry.Default, err = static.NewBackend(cfg.Registry.Static.Routes)
		case "consul":
			registry.Default, err = consul.NewBackend(&cfg.Registry.Consul)
		default:
			exit.Fatal("[FATAL] Unknown registry backend ", cfg.Registry.Backend)
		}

		if err == nil {
			if err = registry.Default.Register(); err == nil {
				return
			}
		}
		log.Print("[WARN] Error initializing backend. ", err)

		if time.Now().After(deadline) {
			exit.Fatal("[FATAL] Timeout registering backend.")
		}

		time.Sleep(cfg.Registry.Retry)
		if atomic.LoadInt32(&shuttingDown) > 0 {
			exit.Exit(1)
		}
	}
}

func watchBackend(cfg *config.Config, first chan bool) {
	var (
		last   string
		svccfg string
		mancfg string

		once sync.Once
	)

	svc := registry.Default.WatchServices()
	man := registry.Default.WatchManual()

	for {
		select {
		case svccfg = <-svc:
		case mancfg = <-man:
		}

		// manual config overrides service config
		// order matters
		next := svccfg + "\n" + mancfg
		if next == last {
			continue
		}

		t, err := route.NewTable(next)
		if err != nil {
			log.Printf("[WARN] %s", err)
			continue
		}
		route.SetTable(t)
		logRoutes(last, next, cfg.Log.RoutesFormat)
		last = next

		once.Do(func() { close(first) })
	}
}

func logRoutes(last, next, format string) {
	fmtDiff := func(diffs []dmp.Diff) string {
		var b bytes.Buffer
		for _, d := range diffs {
			t := strings.TrimSpace(d.Text)
			if t == "" {
				continue
			}
			switch d.Type {
			case dmp.DiffDelete:
				b.WriteString("- ")
				b.WriteString(strings.Replace(t, "\n", "\n- ", -1))
			case dmp.DiffInsert:
				b.WriteString("+ ")
				b.WriteString(strings.Replace(t, "\n", "\n+ ", -1))
			}
		}
		return b.String()
	}

	const defFormat = "delta"
	switch format {
	case "delta":
		if delta := fmtDiff(dmp.New().DiffMain(last, next, true)); delta != "" {
			log.Printf("[INFO] Config updates\n%s", delta)
		}

	case "all":
		log.Printf("[INFO] Updated config to\n%s", next)

	default:
		log.Printf("[WARN] Invalid route format %q. Defaulting to %q", format, defFormat)
		logRoutes(last, next, defFormat)
	}
}

func toJSON(v interface{}) string {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic("json: " + err.Error())
	}
	return string(data)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/codegangsta/cli"
)

const (
	Version = "0.3.2"
)

type httpHandleFunc func(w http.ResponseWriter, r *http.Request)

func main() {

	app := cli.NewApp()

	// App Info
	app.Name = "servemd"
	app.Usage = "a simple http server for markdown content"
	app.UsageText = app.Name + " [options]"
	app.Version = Version
	app.Authors = []cli.Author{
		cli.Author{
			Name:  "Kevin Stock",
			Email: "kevinstock@tantalic.com",
		},
	}

	// CLI Flags
	app.Flags = []cli.Flag{
		// HTTP Server
		cli.StringFlag{
			Name:   "host",
			Value:  "0.0.0.0",
			Usage:  "the host/ip address to listen on for http",
			EnvVar: "HOST",
		},
		cli.IntFlag{
			Name:   "port",
			Value:  3000,
			Usage:  "the port to listen on for http",
			EnvVar: "PORT",
		},

		// Content
		cli.StringFlag{
			Name:   "dir",
			Value:  ".",
			Usage:  "the content directory to serve",
			EnvVar: "DOCUMENT_ROOT",
		},
		cli.StringFlag{
			Name:   "extension",
			Value:  ".md",
			Usage:  "the extension used for markdown files",
			EnvVar: "DOCUMENT_EXTENSION",
		},
		cli.StringFlag{
			Name:   "index",
			Value:  "index",
			Usage:  "the filename (without extension) to use for directory index",
			EnvVar: "DIRECTORY_INDEX",
		},

		// Theme
		cli.StringFlag{
			Name:   "markdown-theme",
			Value:  "clean",
			Usage:  "the theme to use for styling markdown html",
			EnvVar: "MARKDOWN_THEME",
		},
		cli.StringFlag{
			Name:   "code-theme",
			Usage:  "the highlight.js theme to use for syntax highlighting",
			EnvVar: "CODE_THEME",
		},
	}

	app.Action = start
	app.Run(os.Args)
}

func start(c *cli.Context) error {
	// Static Asset Handler
	staticAssetHandler := staticAssetServer()
	staticAssetHandlerFunc := func(w http.ResponseWriter, r *http.Request) {
		staticAssetHandler.ServeHTTP(w, r)
	}
	http.HandleFunc("/assets/", headerMiddleware(staticAssetHandlerFunc))

	// Markdown File Handler
	markdownHandlerFunc := markdownHandleFunc(MarkdownHandlerOptions{
		DocRoot:       c.String("dir"),
		DocExtension:  c.String("extension"),
		DirIndex:      c.String("index"),
		MarkdownTheme: c.String("markdown-theme"),
		CodeTheme:     c.String("code-theme"),
	})
	http.HandleFunc("/", headerMiddleware(markdownHandlerFunc))

	// Start HTTP server
	addr := fmt.Sprintf("%s:%d", c.String("host"), c.Int("port"))
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		return fmt.Errorf("Error starting server (%s).", err)
	}

	return nil
}
<commit_msg>Improves CLI, making the document root an option instead of a flag<commit_after>package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/jawher/mow.cli"
)

const (
	Version = "0.3.2"
)

type httpHandleFunc func(w http.ResponseWriter, r *http.Request)

func main() {
	app := cli.App("servemd", "a simple http server for markdown content")
	app.Version("v version", Version)
	app.Spec = "[OPTIONS] [DIR]"

	var (
		// HTTP Options
		host = app.String(cli.StringOpt{
			Name:   "a host",
			Desc:   "Host/IP address to listen on for HTTP",
			Value:  "",
			EnvVar: "HOST",
		})
		port = app.Int(cli.IntOpt{
			Name:   "p port",
			Desc:   "TCP PORT to listen on for HTTP",
			Value:  3000,
			EnvVar: "PORT",
		})

		// Content
		dir = app.String(cli.StringArg{
			Name:   "DIR",
			Desc:   "Directory to serve content from",
			Value:  ".",
			EnvVar: "DOCUMENT_ROOT",
		})
		extension = app.String(cli.StringOpt{
			Name:   "e extension",
			Desc:   "Extension used for markdown files",
			Value:  ".md",
			EnvVar: "DOCUMENT_EXTENSION",
		})
		index = app.String(cli.StringOpt{
			Name:   "i index",
			Desc:   "Filename (without extension) to use for directory index",
			Value:  "index",
			EnvVar: "DIRECTORY_INDEX",
		})

		// Theme
		markdownTheme = app.String(cli.StringOpt{
			Name:   "m markdown-theme",
			Desc:   "Theme to use for styling markdown html",
			Value:  "clean",
			EnvVar: "MARKDOWN_THEME",
		})
		codeTheme = app.String(cli.StringOpt{
			Name:   "c code-theme",
			Desc:   "Highlight.js theme to use for syntax highlighting",
			Value:  "",
			EnvVar: "CODE_THEME",
		})
	)

	app.Action = func() {
		// Static Asset Handler
		staticAssetHandler := staticAssetServer()
		staticAssetHandlerFunc := func(w http.ResponseWriter, r *http.Request) {
			staticAssetHandler.ServeHTTP(w, r)
		}
		http.HandleFunc("/assets/", headerMiddleware(staticAssetHandlerFunc))

		// Markdown File Handler
		markdownHandlerFunc := markdownHandleFunc(MarkdownHandlerOptions{
			DocRoot:       *dir,
			DocExtension:  *extension,
			DirIndex:      *index,
			MarkdownTheme: *markdownTheme,
			CodeTheme:     *codeTheme,
		})
		http.HandleFunc("/", headerMiddleware(markdownHandlerFunc))

		// Start HTTP server
		addr := fmt.Sprintf("%s:%d", *host, *port)
		err := http.ListenAndServe(addr, nil)
		if err != nil {
			fmt.Fprint(os.Stderr, "Error starting server (%s).", err)
			cli.Exit(1)
		}

	}

	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package jujerr

import (
	"github.com/gin-gonic/gin"
	"github.com/juju/errors"
	"github.com/loopfz/gadgeto/tonic"
)

func ErrHook(c *gin.Context, e error) (int, interface{}) {

	errcode, errpl := 500, e.Error()
	if _, ok := e.(tonic.InputError); ok {
		errcode, errpl = 400, e.Error()
	} else {
		switch {
		case errors.IsBadRequest(e) || errors.IsNotValid(e) || errors.IsAlreadyExists(e) || errors.IsNotSupported(e) || errors.IsNotAssigned(e) || errors.IsNotProvisioned(e):
			errcode, errpl = 400, e.Error()
		case errors.IsMethodNotAllowed(e):
			errcode, errpl = 405, e.Error()
		case errors.IsNotFound(e) || errors.IsUserNotFound(e):
			errcode, errpl = 404, e.Error()
		case errors.IsUnauthorized(e):
			errcode, errpl = 401, e.Error()
		case errors.IsNotImplemented(e):
			errcode, errpl = 501, e.Error()
		}
	}

	return errcode, gin.H{`error`: errpl}
}
<commit_msg>jujerr: new juju.errors type: Forbidden -> 403<commit_after>package jujerr

import (
	"github.com/gin-gonic/gin"
	"github.com/juju/errors"
	"github.com/loopfz/gadgeto/tonic"
)

func ErrHook(c *gin.Context, e error) (int, interface{}) {

	errcode, errpl := 500, e.Error()
	if _, ok := e.(tonic.InputError); ok {
		errcode, errpl = 400, e.Error()
	} else {
		switch {
		case errors.IsBadRequest(e) || errors.IsNotValid(e) || errors.IsAlreadyExists(e) || errors.IsNotSupported(e) || errors.IsNotAssigned(e) || errors.IsNotProvisioned(e):
			errcode, errpl = 400, e.Error()
		case errors.IsForbidden(e):
			errcode, errpl = 403, e.Error()
		case errors.IsMethodNotAllowed(e):
			errcode, errpl = 405, e.Error()
		case errors.IsNotFound(e) || errors.IsUserNotFound(e):
			errcode, errpl = 404, e.Error()
		case errors.IsUnauthorized(e):
			errcode, errpl = 401, e.Error()
		case errors.IsNotImplemented(e):
			errcode, errpl = 501, e.Error()
		}
	}

	return errcode, gin.H{`error`: errpl}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"os"
	"regexp"
	"strconv"

	logrus "github.com/Sirupsen/logrus"
	docopt "github.com/docopt/docopt-go"

	"github.com/9seconds/ah/app/commands"
	"github.com/9seconds/ah/app/environments"
	"github.com/9seconds/ah/app/slices"
	"github.com/9seconds/ah/app/utils"
)

const docoptOptions = `ah - A better history.

Ah is a better way to traverse the history of your shell prompts. Right now it
supports only 3 additional possibilities you are probably have dreamt about:
    1. Good searching;
    2. Better memoizing of entries;
    3. Storing an output of the commands.

Searching is done using 's' command. You may filter output or fetch a history
slice you are wondering about. Filtering uses regular expressions or fuzzy
matching out of box.

Memoizing means that you have an ability to bookmark some favourite commands
and use ah to store some sort of short snippets or ad-hoc shell scripts.

And ah gives you a possibility to have a persistent storage of an output
of any command you are executing. And you can return back to it any time
you want.

Usage:
    ah [options] s [-z] [-g PATTERN] [<lastNcommands> | <startFromNCommand> <finishByMCommand>]
    ah [options] b <commandNumber> <bookmarkAs>
    ah [options] e [-y] <commandNumberOrBookMarkName>
    ah [options] t [-y] [--] <command>...
    ah [options] l <numberOfCommandYouWantToCheck>
    ah [options] lb
    ah [options] rb <bookmarkToRemove>...
    ah [options] (gt | gb) (--keepLatest <keepLatest> | --olderThan <olderThan> | --all)
    ah (-h | --help)
    ah --version

Options:
    -s SHELL, --shell=SHELL                               Shell flavour you are using. By default, ah will do some shallow investigations.
    -f HISTFILE, --histfile=HISTFILE                      The path to a history file. By default ah will try to use default history file of your shell
    -t HISTTIMEFORMAT, --histtimeformat=HISTTIMEFORMAT    A time format for history output. Will use $HISTTIMEFORMAT by default.
    -d APPDIR, --appdir=APPDIR                            A place where ah has to store its data.
    -g PATTERN, --grep PATTERN                            A pattern to filter command lines. It is regular expression if no -f option is set.
    -y, --tty                                             Allocates pseudo-tty is necessary
    -z, --fuzzy                                           Interpret -g pattern as fuzzy match string.
    -v, --debug                                           Shows a debug log of command execution.`

const version = "ah 0.7"

var validateBookmarkName = utils.CreateRegexp(`^[A-Za-z_]\w*$`)

type executor func(map[string]interface{}, *environments.Environment)

func main() {
	defer func() {
		if exc := recover(); exc != nil {
			utils.Logger.Fatal(exc)
		}
	}()

	arguments, err := docopt.Parse(docoptOptions, nil, true, version, false)
	if err != nil {
		panic(err)
	}

	if arguments["--debug"].(bool) {
		utils.EnableLogging()
	} else {
		utils.DisableLogging()
	}

	env := new(environments.Environment)
	argShell := arguments["--shell"]
	if argShell == nil {
		env.DiscoverShell()
	} else {
		env.SetShell(argShell.(string))
	}

	argHistFile := arguments["--histfile"]
	if argHistFile == nil {
		env.DiscoverHistFile()
	} else {
		env.SetHistFile(argHistFile.(string))
	}

	argHistTimeFormat := arguments["--histtimeformat"]
	if argHistTimeFormat == nil {
		env.DiscoverHistTimeFormat()
	} else {
		env.SetHistTimeFormat(argHistTimeFormat.(string))
	}

	argAppDir := arguments["--appdir"]
	if argAppDir == nil {
		env.DiscoverAppDir()
	} else {
		env.SetAppDir(argAppDir.(string))
	}

	utils.Logger.WithFields(logrus.Fields{
		"arguments":   arguments,
		"environment": env,
	}).Debug("Ready to start")

	utils.Logger.WithFields(logrus.Fields{
		"error": os.MkdirAll(env.GetTracesDir(), 0777),
	}).Info("Create traces dir")
	utils.Logger.WithFields(logrus.Fields{
		"error": os.MkdirAll(env.GetBookmarksDir(), 0777),
	}).Info("Create bookmarks dir")

	var exec executor
	switch {
	case arguments["t"].(bool):
		utils.Logger.Info("Execute command 'tee'")
		exec = executeTee
	case arguments["s"].(bool):
		utils.Logger.Info("Execute command 'show'")
		exec = executeShow
	case arguments["l"].(bool):
		utils.Logger.Info("Execute command 'listTrace'")
		exec = executeListTrace
	case arguments["b"].(bool):
		utils.Logger.Info("Execute command 'listTrace'")
		exec = executeListTrace
	case arguments["e"].(bool):
		utils.Logger.Info("Execute command 'execute'")
		exec = executeExec
	case arguments["lb"].(bool):
		utils.Logger.Info("Execute command 'listBookmarks'")
		exec = executeListBookmarks
	case arguments["rb"].(bool):
		utils.Logger.Info("Execute command 'removeBookmarks'")
		exec = executeRemoveBookmarks
	case arguments["gt"].(bool) || arguments["gb"].(bool):
		utils.Logger.Info("Execute command 'gc'")
		exec = executeGC
	default:
		utils.Logger.Panic("Unknown command. Please be more precise")
		return
	}
	exec(arguments, env)
}

func executeTee(arguments map[string]interface{}, env *environments.Environment) {
	cmds := arguments["<command>"].([]string)
	tty := arguments["--tty"].(bool)

	utils.Logger.WithFields(logrus.Fields{
		"command arguments": cmds,
		"pseudo-tty":        tty,
	}).Info("Arguments of 'tee'")

	commands.Tee(cmds, tty, env)
}

func executeShow(arguments map[string]interface{}, env *environments.Environment) {
	slice, err := slices.ExtractSlice(
		arguments["<lastNcommands>"],
		arguments["<startFromNCommand>"],
		arguments["<finishByMCommand>"])
	if err != nil {
		utils.Logger.Panic(err)
	}

	var filter *utils.Regexp
	if arguments["--grep"] != nil {
		query := arguments["--grep"].(string)
		if arguments["--fuzzy"].(bool) {
			regex := new(bytes.Buffer)
			for _, character := range query {
				regex.WriteString(".*?")
				regex.WriteString(regexp.QuoteMeta(string(character)))
			}
			regex.WriteString(".*?")
			query = regex.String()
		}
		filter = utils.CreateRegexp(query)
	}

	utils.Logger.WithFields(logrus.Fields{
		"slice":  slice,
		"filter": filter,
	}).Info("Arguments of 'show'")

	commands.Show(slice, filter, env)
}

func executeListTrace(arguments map[string]interface{}, env *environments.Environment) {
	cmd := arguments["<numberOfCommandYouWantToCheck>"].(string)

	utils.Logger.WithFields(logrus.Fields{
		"cmd": cmd,
	}).Info("Arguments of 'listTrace'")

	commands.ListTrace(cmd, env)
}

func executeBookmark(arguments map[string]interface{}, env *environments.Environment) {
	number, err := strconv.Atoi(arguments["<commandNumber>"].(string))
	if err != nil {
		utils.Logger.Panicf("Cannot understand command number: %d", number)
	}

	bookmarkAs := arguments["<bookmarkAs>"].(string)
	if !validateBookmarkName.Match(bookmarkAs) {
		utils.Logger.Panic("Incorrect bookmark name!")
	}

	utils.Logger.WithFields(logrus.Fields{
		"commandNumber": number,
		"bookmarkAs":    bookmarkAs,
	}).Info("Arguments of 'bookmark'")

	commands.Bookmark(number, bookmarkAs, env)
}

func executeExec(arguments map[string]interface{}, env *environments.Environment) {
	commandNumberOrBookMarkName := arguments["<commandNumberOrBookMarkName>"].(string)
	tty := arguments["--tty"].(bool)

	utils.Logger.WithFields(logrus.Fields{
		"commandNumberOrBookMarkName": commandNumberOrBookMarkName,
		"tty": tty,
	}).Info("Arguments of 'bookmark'")

	commandNumber, err := strconv.Atoi(commandNumberOrBookMarkName)
	switch {
	case err == nil:
		utils.Logger.Info("Execute command number ", commandNumber)
		commands.ExecuteCommandNumber(tty, commandNumber, env)
	case validateBookmarkName.Match(commandNumberOrBookMarkName):
		utils.Logger.Info("Execute bookmark ", commandNumberOrBookMarkName)
		commands.ExecuteBookmark(tty, commandNumberOrBookMarkName, env)
	default:
		utils.Logger.Panic("Incorrect bookmark name! It should be started with alphabet letter, and alphabet or digits after!")
	}
}

func executeGC(arguments map[string]interface{}, env *environments.Environment) {
	gcDir := commands.GcTracesDir
	if arguments["gb"].(bool) {
		gcDir = commands.GcBookmarksDir
	}

	var gcType commands.GcType
	stringParam := "1"
	switch {
	case arguments["--keepLatest"].(bool):
		gcType = commands.GcKeepLatest
		stringParam = arguments["<keepLatest>"].(string)
	case arguments["--olderThan"].(bool):
		gcType = commands.GcOlderThan
		stringParam = arguments["<keepLatest>"].(string)
	case arguments["--all"].(bool):
		gcType = commands.GcAll
	default:
		utils.Logger.Panic("Unknown subcommand command")
	}

	param, err := strconv.Atoi(stringParam)
	if err != nil {
		utils.Logger.Panic(err)
	} else if param <= 0 {
		utils.Logger.Panic("Parameter of garbage collection has to be > 0")
	}

	utils.Logger.WithFields(logrus.Fields{
		"gcType": gcType,
		"param":  param,
	}).Info("Arguments")

	commands.GC(gcType, gcDir, param, env)
}

func executeListBookmarks(_ map[string]interface{}, env *environments.Environment) {
	commands.ListBookmarks(env)
}

func executeRemoveBookmarks(arguments map[string]interface{}, env *environments.Environment) {
	bookmarks, ok := arguments["<bookmarkToRemove>"].([]string)
	if !ok || bookmarks == nil || len(bookmarks) == 0 {
		utils.Logger.Info("Nothing to do here")
		return
	}

	for _, bookmark := range bookmarks {
		if !validateBookmarkName.Match(bookmark) {
			utils.Logger.WithFields(logrus.Fields{
				"bookmark": bookmark,
			}).Panicf("Bookmark name %s is invalid", bookmark)
		}
	}

	commands.RemoveBookmarks(bookmarks, env)
}
<commit_msg>Bump up to the next version<commit_after>package main

import (
	"bytes"
	"os"
	"regexp"
	"strconv"

	logrus "github.com/Sirupsen/logrus"
	docopt "github.com/docopt/docopt-go"

	"github.com/9seconds/ah/app/commands"
	"github.com/9seconds/ah/app/environments"
	"github.com/9seconds/ah/app/slices"
	"github.com/9seconds/ah/app/utils"
)

const docoptOptions = `ah - A better history.

Ah is a better way to traverse the history of your shell prompts. Right now it
supports only 3 additional possibilities you are probably have dreamt about:
    1. Good searching;
    2. Better memoizing of entries;
    3. Storing an output of the commands.

Searching is done using 's' command. You may filter output or fetch a history
slice you are wondering about. Filtering uses regular expressions or fuzzy
matching out of box.

Memoizing means that you have an ability to bookmark some favourite commands
and use ah to store some sort of short snippets or ad-hoc shell scripts.

And ah gives you a possibility to have a persistent storage of an output
of any command you are executing. And you can return back to it any time
you want.

Usage:
    ah [options] s [-z] [-g PATTERN] [<lastNcommands> | <startFromNCommand> <finishByMCommand>]
    ah [options] b <commandNumber> <bookmarkAs>
    ah [options] e [-y] <commandNumberOrBookMarkName>
    ah [options] t [-y] [--] <command>...
    ah [options] l <numberOfCommandYouWantToCheck>
    ah [options] lb
    ah [options] rb <bookmarkToRemove>...
    ah [options] (gt | gb) (--keepLatest <keepLatest> | --olderThan <olderThan> | --all)
    ah (-h | --help)
    ah --version

Options:
    -s SHELL, --shell=SHELL                               Shell flavour you are using. By default, ah will do some shallow investigations.
    -f HISTFILE, --histfile=HISTFILE                      The path to a history file. By default ah will try to use default history file of your shell
    -t HISTTIMEFORMAT, --histtimeformat=HISTTIMEFORMAT    A time format for history output. Will use $HISTTIMEFORMAT by default.
    -d APPDIR, --appdir=APPDIR                            A place where ah has to store its data.
    -g PATTERN, --grep PATTERN                            A pattern to filter command lines. It is regular expression if no -f option is set.
    -y, --tty                                             Allocates pseudo-tty is necessary
    -z, --fuzzy                                           Interpret -g pattern as fuzzy match string.
    -v, --debug                                           Shows a debug log of command execution.`

const version = "ah 0.7.2"

var validateBookmarkName = utils.CreateRegexp(`^[A-Za-z_]\w*$`)

type executor func(map[string]interface{}, *environments.Environment)

func main() {
	defer func() {
		if exc := recover(); exc != nil {
			utils.Logger.Fatal(exc)
		}
	}()

	arguments, err := docopt.Parse(docoptOptions, nil, true, version, false)
	if err != nil {
		panic(err)
	}

	if arguments["--debug"].(bool) {
		utils.EnableLogging()
	} else {
		utils.DisableLogging()
	}

	env := new(environments.Environment)
	argShell := arguments["--shell"]
	if argShell == nil {
		env.DiscoverShell()
	} else {
		env.SetShell(argShell.(string))
	}

	argHistFile := arguments["--histfile"]
	if argHistFile == nil {
		env.DiscoverHistFile()
	} else {
		env.SetHistFile(argHistFile.(string))
	}

	argHistTimeFormat := arguments["--histtimeformat"]
	if argHistTimeFormat == nil {
		env.DiscoverHistTimeFormat()
	} else {
		env.SetHistTimeFormat(argHistTimeFormat.(string))
	}

	argAppDir := arguments["--appdir"]
	if argAppDir == nil {
		env.DiscoverAppDir()
	} else {
		env.SetAppDir(argAppDir.(string))
	}

	utils.Logger.WithFields(logrus.Fields{
		"arguments":   arguments,
		"environment": env,
	}).Debug("Ready to start")

	utils.Logger.WithFields(logrus.Fields{
		"error": os.MkdirAll(env.GetTracesDir(), 0777),
	}).Info("Create traces dir")
	utils.Logger.WithFields(logrus.Fields{
		"error": os.MkdirAll(env.GetBookmarksDir(), 0777),
	}).Info("Create bookmarks dir")

	var exec executor
	switch {
	case arguments["t"].(bool):
		utils.Logger.Info("Execute command 'tee'")
		exec = executeTee
	case arguments["s"].(bool):
		utils.Logger.Info("Execute command 'show'")
		exec = executeShow
	case arguments["l"].(bool):
		utils.Logger.Info("Execute command 'listTrace'")
		exec = executeListTrace
	case arguments["b"].(bool):
		utils.Logger.Info("Execute command 'listTrace'")
		exec = executeListTrace
	case arguments["e"].(bool):
		utils.Logger.Info("Execute command 'execute'")
		exec = executeExec
	case arguments["lb"].(bool):
		utils.Logger.Info("Execute command 'listBookmarks'")
		exec = executeListBookmarks
	case arguments["rb"].(bool):
		utils.Logger.Info("Execute command 'removeBookmarks'")
		exec = executeRemoveBookmarks
	case arguments["gt"].(bool) || arguments["gb"].(bool):
		utils.Logger.Info("Execute command 'gc'")
		exec = executeGC
	default:
		utils.Logger.Panic("Unknown command. Please be more precise")
		return
	}
	exec(arguments, env)
}

func executeTee(arguments map[string]interface{}, env *environments.Environment) {
	cmds := arguments["<command>"].([]string)
	tty := arguments["--tty"].(bool)

	utils.Logger.WithFields(logrus.Fields{
		"command arguments": cmds,
		"pseudo-tty":        tty,
	}).Info("Arguments of 'tee'")

	commands.Tee(cmds, tty, env)
}

func executeShow(arguments map[string]interface{}, env *environments.Environment) {
	slice, err := slices.ExtractSlice(
		arguments["<lastNcommands>"],
		arguments["<startFromNCommand>"],
		arguments["<finishByMCommand>"])
	if err != nil {
		utils.Logger.Panic(err)
	}

	var filter *utils.Regexp
	if arguments["--grep"] != nil {
		query := arguments["--grep"].(string)
		if arguments["--fuzzy"].(bool) {
			regex := new(bytes.Buffer)
			for _, character := range query {
				regex.WriteString(".*?")
				regex.WriteString(regexp.QuoteMeta(string(character)))
			}
			regex.WriteString(".*?")
			query = regex.String()
		}
		filter = utils.CreateRegexp(query)
	}

	utils.Logger.WithFields(logrus.Fields{
		"slice":  slice,
		"filter": filter,
	}).Info("Arguments of 'show'")

	commands.Show(slice, filter, env)
}

func executeListTrace(arguments map[string]interface{}, env *environments.Environment) {
	cmd := arguments["<numberOfCommandYouWantToCheck>"].(string)

	utils.Logger.WithFields(logrus.Fields{
		"cmd": cmd,
	}).Info("Arguments of 'listTrace'")

	commands.ListTrace(cmd, env)
}

func executeBookmark(arguments map[string]interface{}, env *environments.Environment) {
	number, err := strconv.Atoi(arguments["<commandNumber>"].(string))
	if err != nil {
		utils.Logger.Panicf("Cannot understand command number: %d", number)
	}

	bookmarkAs := arguments["<bookmarkAs>"].(string)
	if !validateBookmarkName.Match(bookmarkAs) {
		utils.Logger.Panic("Incorrect bookmark name!")
	}

	utils.Logger.WithFields(logrus.Fields{
		"commandNumber": number,
		"bookmarkAs":    bookmarkAs,
	}).Info("Arguments of 'bookmark'")

	commands.Bookmark(number, bookmarkAs, env)
}

func executeExec(arguments map[string]interface{}, env *environments.Environment) {
	commandNumberOrBookMarkName := arguments["<commandNumberOrBookMarkName>"].(string)
	tty := arguments["--tty"].(bool)

	utils.Logger.WithFields(logrus.Fields{
		"commandNumberOrBookMarkName": commandNumberOrBookMarkName,
		"tty": tty,
	}).Info("Arguments of 'bookmark'")

	commandNumber, err := strconv.Atoi(commandNumberOrBookMarkName)
	switch {
	case err == nil:
		utils.Logger.Info("Execute command number ", commandNumber)
		commands.ExecuteCommandNumber(tty, commandNumber, env)
	case validateBookmarkName.Match(commandNumberOrBookMarkName):
		utils.Logger.Info("Execute bookmark ", commandNumberOrBookMarkName)
		commands.ExecuteBookmark(tty, commandNumberOrBookMarkName, env)
	default:
		utils.Logger.Panic("Incorrect bookmark name! It should be started with alphabet letter, and alphabet or digits after!")
	}
}

func executeGC(arguments map[string]interface{}, env *environments.Environment) {
	gcDir := commands.GcTracesDir
	if arguments["gb"].(bool) {
		gcDir = commands.GcBookmarksDir
	}

	var gcType commands.GcType
	stringParam := "1"
	switch {
	case arguments["--keepLatest"].(bool):
		gcType = commands.GcKeepLatest
		stringParam = arguments["<keepLatest>"].(string)
	case arguments["--olderThan"].(bool):
		gcType = commands.GcOlderThan
		stringParam = arguments["<keepLatest>"].(string)
	case arguments["--all"].(bool):
		gcType = commands.GcAll
	default:
		utils.Logger.Panic("Unknown subcommand command")
	}

	param, err := strconv.Atoi(stringParam)
	if err != nil {
		utils.Logger.Panic(err)
	} else if param <= 0 {
		utils.Logger.Panic("Parameter of garbage collection has to be > 0")
	}

	utils.Logger.WithFields(logrus.Fields{
		"gcType": gcType,
		"param":  param,
	}).Info("Arguments")

	commands.GC(gcType, gcDir, param, env)
}

func executeListBookmarks(_ map[string]interface{}, env *environments.Environment) {
	commands.ListBookmarks(env)
}

func executeRemoveBookmarks(arguments map[string]interface{}, env *environments.Environment) {
	bookmarks, ok := arguments["<bookmarkToRemove>"].([]string)
	if !ok || bookmarks == nil || len(bookmarks) == 0 {
		utils.Logger.Info("Nothing to do here")
		return
	}

	for _, bookmark := range bookmarks {
		if !validateBookmarkName.Match(bookmark) {
			utils.Logger.WithFields(logrus.Fields{
				"bookmark": bookmark,
			}).Panicf("Bookmark name %s is invalid", bookmark)
		}
	}

	commands.RemoveBookmarks(bookmarks, env)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"strconv"

	"github.com/elazarl/go-bindata-assetfs"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
)

var (
	client  *clientConn
	mpdAddr = flag.String("mpdaddr", "127.0.0.1:6600", "MPD address")
	port    = flag.String("port", ":8080", "listen port")
)

func main() {
	flag.Parse()
	glog.Infof("Starting API for MPD at %s.", *mpdAddr)
	go h.run()

	watch := newWatchConn(*mpdAddr)
	defer watch.Close()

	client = newClientConn(*mpdAddr)
	defer client.Close()

	r := mux.NewRouter()
	r.HandleFunc("/websocket", serveWebsocket)
	r.HandleFunc("/next", NextHandler)
	r.HandleFunc("/previous", PreviousHandler)
	r.HandleFunc("/play", PlayHandler)
	r.HandleFunc("/pause", PauseHandler)
	r.HandleFunc("/randomOn", RandomOnHandler)
	r.HandleFunc("/randomOff", RandomOffHandler)
	r.HandleFunc("/files", FileListHandler)
	r.HandleFunc("/playlist", PlayListHandler)

	// The front-end assets are served from a go-bindata file.
	r.PathPrefix("/").Handler(
		http.FileServer(&assetfs.AssetFS{Asset, AssetDir, ""}),
	)
	http.Handle("/", r)
	glog.Infof("Listening on %s.", *port)
	err := http.ListenAndServe(*port, nil)
	if err != nil {
		glog.Errorf("http.ListenAndServe %s failed: %s", *port, err)
		return
	}
}

func mpdStatus() ([]byte, error) {
	data, err := client.c.Status()
	if err != nil {
		return nil, err
	}
	song, err := client.c.CurrentSong()
	if err != nil {
		return nil, err
	}
	for k, v := range song {
		data[k] = v
	}
	b, err := json.Marshal(data)
	return b, err
}

func NextHandler(w http.ResponseWriter, r *http.Request) {
	err := client.c.Next()
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func PreviousHandler(w http.ResponseWriter, r *http.Request) {
	err := client.c.Previous()
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func PlayHandler(w http.ResponseWriter, r *http.Request) {
	err := client.c.Play(-1)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func PauseHandler(w http.ResponseWriter, r *http.Request) {
	err := client.c.Pause(true)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func random(on bool, w http.ResponseWriter) {
	err := client.c.Random(on)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}
func RandomOnHandler(w http.ResponseWriter, r *http.Request) {
	random(true, w)
}
func RandomOffHandler(w http.ResponseWriter, r *http.Request) {
	random(false, w)
}

func FileListHandler(w http.ResponseWriter, r *http.Request) {
	data, err := client.c.LsInfo(r.FormValue("uri"))
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	b, err := json.Marshal(data)
	w.Header().Add("Content-Type", "application/json")
	fmt.Fprint(w, string(b))
}

func PlayListHandler(w http.ResponseWriter, r *http.Request) {

	// Parse the JSON body.
	decoder := json.NewDecoder(r.Body)
	var params map[string]interface{}
	err := decoder.Decode(&params)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	uri := params["uri"].(string)
	replace := params["replace"].(bool)
	play := params["play"].(bool)
	pos := 0
	if uri == "" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Clear the playlist.
	if replace {
		err := client.c.Clear()
		if err != nil {
			glog.Errorln(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

	// To play from the start of the new items in the playlist, we need to get the
	// current playlist position.
	if !replace {
		data, err := client.c.Status()
		if err != nil {
			glog.Errorln(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		pos, err = strconv.Atoi(data["playlistlength"])
		if err != nil {
			glog.Errorln(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		glog.Infof("pos: %d", pos)
	}

	// Add to the playlist.
	err = client.c.Add(uri)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// Play.
	if play {
		err := client.c.Play(pos)
		if err != nil {
			glog.Errorln(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

	w.WriteHeader(http.StatusOK)
}
<commit_msg>method has renamed to ListInfo<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"strconv"

	"github.com/elazarl/go-bindata-assetfs"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
)

var (
	client  *clientConn
	mpdAddr = flag.String("mpdaddr", "127.0.0.1:6600", "MPD address")
	port    = flag.String("port", ":8080", "listen port")
)

func main() {
	flag.Parse()
	glog.Infof("Starting API for MPD at %s.", *mpdAddr)
	go h.run()

	watch := newWatchConn(*mpdAddr)
	defer watch.Close()

	client = newClientConn(*mpdAddr)
	defer client.Close()

	r := mux.NewRouter()
	r.HandleFunc("/websocket", serveWebsocket)
	r.HandleFunc("/next", NextHandler)
	r.HandleFunc("/previous", PreviousHandler)
	r.HandleFunc("/play", PlayHandler)
	r.HandleFunc("/pause", PauseHandler)
	r.HandleFunc("/randomOn", RandomOnHandler)
	r.HandleFunc("/randomOff", RandomOffHandler)
	r.HandleFunc("/files", FileListHandler)
	r.HandleFunc("/playlist", PlayListHandler)

	// The front-end assets are served from a go-bindata file.
	r.PathPrefix("/").Handler(
		http.FileServer(&assetfs.AssetFS{Asset, AssetDir, ""}),
	)
	http.Handle("/", r)
	glog.Infof("Listening on %s.", *port)
	err := http.ListenAndServe(*port, nil)
	if err != nil {
		glog.Errorf("http.ListenAndServe %s failed: %s", *port, err)
		return
	}
}

func mpdStatus() ([]byte, error) {
	data, err := client.c.Status()
	if err != nil {
		return nil, err
	}
	song, err := client.c.CurrentSong()
	if err != nil {
		return nil, err
	}
	for k, v := range song {
		data[k] = v
	}
	b, err := json.Marshal(data)
	return b, err
}

func NextHandler(w http.ResponseWriter, r *http.Request) {
	err := client.c.Next()
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func PreviousHandler(w http.ResponseWriter, r *http.Request) {
	err := client.c.Previous()
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func PlayHandler(w http.ResponseWriter, r *http.Request) {
	err := client.c.Play(-1)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func PauseHandler(w http.ResponseWriter, r *http.Request) {
	err := client.c.Pause(true)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func random(on bool, w http.ResponseWriter) {
	err := client.c.Random(on)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}
func RandomOnHandler(w http.ResponseWriter, r *http.Request) {
	random(true, w)
}
func RandomOffHandler(w http.ResponseWriter, r *http.Request) {
	random(false, w)
}

func FileListHandler(w http.ResponseWriter, r *http.Request) {
	data, err := client.c.ListInfo(r.FormValue("uri"))
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	b, err := json.Marshal(data)
	w.Header().Add("Content-Type", "application/json")
	fmt.Fprint(w, string(b))
}

func PlayListHandler(w http.ResponseWriter, r *http.Request) {

	// Parse the JSON body.
	decoder := json.NewDecoder(r.Body)
	var params map[string]interface{}
	err := decoder.Decode(&params)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	uri := params["uri"].(string)
	replace := params["replace"].(bool)
	play := params["play"].(bool)
	pos := 0
	if uri == "" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Clear the playlist.
	if replace {
		err := client.c.Clear()
		if err != nil {
			glog.Errorln(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

	// To play from the start of the new items in the playlist, we need to get the
	// current playlist position.
	if !replace {
		data, err := client.c.Status()
		if err != nil {
			glog.Errorln(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		pos, err = strconv.Atoi(data["playlistlength"])
		if err != nil {
			glog.Errorln(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		glog.Infof("pos: %d", pos)
	}

	// Add to the playlist.
	err = client.c.Add(uri)
	if err != nil {
		glog.Errorln(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// Play.
	if play {
		err := client.c.Play(pos)
		if err != nil {
			glog.Errorln(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

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