<commit_before>/* post-anything allows you to send HTTP POST/GET to anything, anywhere.
supports CORS, and can be used as a static file server*/
package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/url"
)

func main() {
	listen := flag.String("listen", "", "interface to listen on e.g. 0.0.0.0:8000")
	dir := flag.String("dir", "", "serve static files from here, will use url of /static/*")
	flag.Parse()
	if *listen == "" {
		log.Fatal("listen is required")
	}

	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		req.ParseForm()
		switch req.Method {
		case "GET":
			logMap(req.Form)
		case "POST":
			logMap(req.Form)
		case "OPTIONS":
			w.Header().Add("Access-Control-Allow-Origin", "*")
		}
		w.WriteHeader(200)
	})
	if *dir != "" {
		http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(*dir))))
	}
	log.Fatal(http.ListenAndServe(*listen, nil))
}

func logMap(m url.Values) {
	fmt.Println("Request Values")
	for k, v := range m {
		fmt.Println(k, v)
	}
}
<commit_msg>Adds file upload route<commit_after>/* post-anything allows you to send HTTP POST/GET to anything, anywhere.
supports CORS, and can be used as a static file server*/
package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"

	"github.com/mholt/binding"
)

type fileRequest struct {
	File *multipart.FileHeader
}

func (f *fileRequest) FieldMap(req *http.Request) binding.FieldMap {
	return binding.FieldMap{
		&f.File: "file",
	}
}

func main() {
	listen := flag.String("listen", "", "interface to listen on e.g. 0.0.0.0:8000")
	dir := flag.String("dir", "", "serve static files from here, will use url of /static/*")
	flag.Parse()
	if *listen == "" {
		log.Fatal("listen is required")
	}

	http.HandleFunc("/sendfile", func(w http.ResponseWriter, req *http.Request) {
		fReq := &fileRequest{}
		binding.MaxMemory = 104857600000
		if errs := binding.Bind(req, fReq); errs.Len() != 0 {
			w.WriteHeader(500)
			return
		}
		if fReq.File == nil {
			w.WriteHeader(400)
			return
		}
		fh, err := fReq.File.Open()
		if err != nil {
			w.WriteHeader(500)
			return
		}
		defer fh.Close()
		fh2, err := os.Create(fReq.File.Filename)
		if err != nil {
			w.WriteHeader(500)
			return
		}
		defer fh2.Close()

		if _, err := io.Copy(fh2, fh); err != nil {
			w.WriteHeader(500)
			return
		}
		w.WriteHeader(200)
	})

	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		req.ParseForm()
		switch req.Method {
		case "GET":
			logMap(req.Form)
		case "POST":
			logMap(req.Form)
		case "OPTIONS":
			w.Header().Add("Access-Control-Allow-Origin", "*")
		}
		w.WriteHeader(200)
	})

	if *dir != "" {
		http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(*dir))))
	}

	log.Fatal(http.ListenAndServe(*listen, nil))
}

func logMap(m url.Values) {
	fmt.Println("Request Values")
	for k, v := range m {
		fmt.Println(k, v)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/fatih/color"
	mkr "github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/logger"
	"gopkg.in/urfave/cli.v1"
)

var commandAlerts = cli.Command{
	Name:      "alerts",
	Usage:     "Retrieve/Close alerts",
	ArgsUsage: "[--with-closed | -w] [--limit | -l]",
	Description: `
    Retrieve/Close alerts. With no subcommand specified, this will show all alerts.
    Requests APIs under "/api/v0/alerts". See https://mackerel.io/api-docs/entry/alerts .
`,
	Action: doAlertsRetrieve,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "with-closed, w", Usage: "Display open alert including close alert. default: false"},
		cli.IntFlag{Name: "limit, l", Value: 100, Usage: "Set the number of alerts to display at once when withClosed is active. default: 100"},
	},
	Subcommands: []cli.Command{
		{
			Name:      "list",
			Usage:     "list alerts",
			ArgsUsage: "[--service | -s <service>] [--host-status | -S <file>] [--color | -c] [--with-closed | -w] [--limit | -l]",
			Description: `
    Shows alerts in human-readable format.
`,
			Action: doAlertsList,
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "service, s",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by service. Multiple choices are allowed.",
				},
				cli.StringSliceFlag{
					Name:  "host-status, S",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by status of each host. Multiple choices are allowed.",
				},
				cli.BoolTFlag{Name: "color, c", Usage: "Colorize output. default: true"},
				cli.BoolFlag{Name: "with-closed, w", Usage: "Display open alert including close alert. default: false"},
				cli.IntFlag{Name: "limit, l", Value: 100, Usage: "Set the number of alerts to display at once when withClosed is active. default: 100"},
			},
		},
		{
			Name:      "close",
			Usage:     "close alerts",
			ArgsUsage: "<alertIds....>",
			Description: `
    Closes alerts. Multiple alert IDs can be specified.
`,
			Action: doAlertsClose,
			Flags: []cli.Flag{
				cli.StringFlag{Name: "reason, r", Value: "", Usage: "Reason of closing alert."},
				cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
			},
		},
	},
}

type alertSet struct {
	Alert   *mkr.Alert
	Host    *mkr.Host
	Monitor mkr.Monitor
}

func joinMonitorsAndHosts(client *mkr.Client, alerts []*mkr.Alert) []*alertSet {
	hostsJSON, err := client.FindHosts(&mkr.FindHostsParam{
		Statuses: []string{"working", "standby", "poweroff", "maintenance"},
	})
	logger.DieIf(err)

	hosts := map[string]*mkr.Host{}
	for _, host := range hostsJSON {
		hosts[host.ID] = host
	}

	monitorsJSON, err := client.FindMonitors()
	logger.DieIf(err)

	monitors := map[string]mkr.Monitor{}
	for _, monitor := range monitorsJSON {
		monitors[monitor.MonitorID()] = monitor
	}

	alertSets := []*alertSet{}
	for _, alert := range alerts {
		alertSets = append(
			alertSets,
			&alertSet{Alert: alert, Host: hosts[alert.HostID], Monitor: monitors[alert.MonitorID]},
		)
	}
	return alertSets
}

func formatJoinedAlert(alertSet *alertSet, colorize bool) string {
	const layout = "2006-01-02 15:04:05"

	host := alertSet.Host
	monitor := alertSet.Monitor
	alert := alertSet.Alert

	hostMsg := ""
	if host != nil {
		statusMsg := host.Status
		if host.IsRetired == true {
			statusMsg = "retired"
		}
		if colorize {
			switch statusMsg {
			case "working":
				statusMsg = color.BlueString("working")
			case "standby":
				statusMsg = color.GreenString("standby")
			case "poweroff":
				statusMsg = "poweroff"
			case "maintenance":
				statusMsg = color.YellowString("maintenance")
			}
		}
		hostMsg = fmt.Sprintf(" %s %s", host.Name, statusMsg)
		roleMsgs := []string{}
		for service, roles := range host.Roles {
			roleMsgs = append(roleMsgs, fmt.Sprintf("%s:%s", service, strings.Join(roles, ",")))
		}
		hostMsg += " [" + strings.Join(roleMsgs, ", ") + "]"
	}

	monitorMsg := ""
	if monitor != nil {
		switch m := monitor.(type) {
		case *mkr.MonitorConnectivity:
			monitorMsg = ""
		case *mkr.MonitorHostMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", m.Metric, alert.Value)
			}
		case *mkr.MonitorServiceMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %s %.2f", m.Service, m.Metric, alert.Value)
			}
		case *mkr.MonitorExternalHTTP:
			statusRegexp, _ := regexp.Compile("^[2345][0-9][0-9]$")
			switch alert.Status {
			case "CRITICAL":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeCritical != nil {
					monitorMsg = fmt.Sprintf("%s %.2f > %.2f msec, status:%s", m.URL, alert.Value, *m.ResponseTimeCritical, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%s %.2f msec, %s", m.URL, alert.Value, alert.Message)
				}
			case "WARNING":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeWarning != nil {
					monitorMsg = fmt.Sprintf("%.2f > %.2f msec, status:%s", alert.Value, *m.ResponseTimeWarning, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%.2f msec, %s", alert.Value, alert.Message)
				}
			default:
				monitorMsg = fmt.Sprintf("%.2f msec, status:%s", alert.Value, alert.Message)
			}
		case *mkr.MonitorExpression:
			expression := formatExpressionOneline(m.Expression)
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Warning)
			} else if alert.Status == "UNKNOWN" {
				monitorMsg = fmt.Sprintf("%s", expression)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", expression, alert.Value)
			}
		default:
			monitorMsg = fmt.Sprintf("%s", monitor.MonitorType())
		}
		if monitorMsg == "" {
			monitorMsg = monitor.MonitorName()
		} else {
			monitorMsg = monitor.MonitorName() + " " + monitorMsg
		}
	}
	// If alert is caused by check monitoring, take monitorMsg from alert.message
	if alert.Type == "check" {
		monitorMsg = formatCheckMessage(alert.Message)
	}

	statusMsg := alert.Status
	if colorize {
		switch alert.Status {
		case "CRITICAL":
			statusMsg = color.RedString("CRITICAL ")
		case "WARNING":
			statusMsg = color.YellowString("WARNING ")
		case "OK":
			statusMsg = color.GreenString("OK ")
		case "UNKNOWN":
			statusMsg = "UNKNOWN "
		}
	}
	return fmt.Sprintf("%s %s %s %s%s", alert.ID, time.Unix(alert.OpenedAt, 0).Format(layout), statusMsg, monitorMsg, hostMsg)
}

var expressionNewlinePattern = regexp.MustCompile(`\s*[\r\n]+\s*`)

func formatExpressionOneline(expr string) string {
	expr = strings.Trim(expressionNewlinePattern.ReplaceAllString(expr, " "), " ")
	return strings.Replace(strings.Replace(expr, "( ", "(", -1), " )", ")", -1)
}

func formatCheckMessage(msg string) string {
	truncated := false
	if index := strings.IndexAny(msg, "\n\r"); index != -1 {
		msg = msg[0:index]
		truncated = true
	}
	if runes := []rune(msg); len(runes) > 100 {
		msg = string(runes[0:100])
		truncated = true
	}
	if truncated {
		msg = msg + "..."
	}
	return msg
}

func doAlertsRetrieve(c *cli.Context) error {
	client := newMackerelFromContext(c)
	withClosed := c.Bool("with-closed")
	limit := c.Int("limit")

	if withClosed {
		alerts, err := client.FindWithClosedAlerts()
		logger.DieIf(err)
		if alerts.NextID != "" {
			for {
				if limit > len(alerts.Alerts) {
					nextAlerts, err := client.FindWithClosedAlertsByNextID(alerts.NextID)
					logger.DieIf(err)
					alerts.Alerts = append(alerts.Alerts, nextAlerts.Alerts...)
					alerts.NextID = nextAlerts.NextID
					if alerts.NextID == "" {
						break
					}
					time.Sleep(1 * time.Second)
				} else {
					break
				}
			}
		}

		if len(alerts.Alerts) > limit {
			alerts.Alerts = alerts.Alerts[:limit]
		}
		PrettyPrintJSON(alerts.Alerts)
	} else {
		alerts, err := client.FindAlerts()
		logger.DieIf(err)
		if alerts.NextID != "" {
			for {
				nextAlerts, err := client.FindAlertsByNextID(alerts.NextID)
				logger.DieIf(err)
				alerts.Alerts = append(alerts.Alerts, nextAlerts.Alerts...)
				alerts.NextID = nextAlerts.NextID
				if alerts.NextID == "" {
					break
				}
				time.Sleep(1 * time.Second)
			}
		}

		if len(alerts.Alerts) > limit {
			alerts.Alerts = alerts.Alerts[:limit]
		}
		PrettyPrintJSON(alerts.Alerts)
	}
	return nil
}

func doAlertsList(c *cli.Context) error {
	filterServices := c.StringSlice("service")
	filterStatuses := c.StringSlice("host-status")
	client := newMackerelFromContext(c)
	withClosed := c.Bool("with-closed")
	limit := c.Int("limit")
	var alert []*mkr.Alert

	if withClosed {
		alerts, err := client.FindWithClosedAlerts()
		logger.DieIf(err)
		if alerts.NextID != "" {
			for {
				if limit > len(alerts.Alerts) {
					nextAlerts, err := client.FindWithClosedAlertsByNextID(alerts.NextID)
					logger.DieIf(err)
					alerts.Alerts = append(alerts.Alerts, nextAlerts.Alerts...)
					alerts.NextID = nextAlerts.NextID
					if alerts.NextID == "" {
						break
					}
					time.Sleep(1 * time.Second)
				} else {
					break
				}
			}
		}
		alert = alerts.Alerts
		if len(alert) > limit {
			alert = alert[:limit]
		}
	} else {
		alerts, err := client.FindAlerts()
		logger.DieIf(err)
		if alerts.NextID != "" {
			for {
				nextAlerts, err := client.FindAlertsByNextID(alerts.NextID)
				logger.DieIf(err)
				alerts.Alerts = append(alerts.Alerts, nextAlerts.Alerts...)
				alerts.NextID = nextAlerts.NextID
				if alerts.NextID == "" {
					break
				}
				time.Sleep(1 * time.Second)
			}
		}
		alert = alerts.Alerts
		if len(alert) > limit {
			alert = alert[:limit]
		}
	}
	joinedAlerts := joinMonitorsAndHosts(client, alert)
	for _, joinAlert := range joinedAlerts {
		if len(filterServices) > 0 {
			found := false
			for _, filterService := range filterServices {
				if joinAlert.Host != nil {
					if _, ok := joinAlert.Host.Roles[filterService]; ok {
						found = true
					}
				} else {
					var service string
					if m, ok := joinAlert.Monitor.(*mkr.MonitorServiceMetric); ok {
						service = m.Service
					} else if m, ok := joinAlert.Monitor.(*mkr.MonitorExternalHTTP); ok {
						service = m.Service
					}
					found = service == filterService
				}
			}
			if !found {
				continue
			}
		}
		if len(filterStatuses) > 0 {
			found := false
			for _, filterStatus := range filterStatuses {
				if joinAlert.Host != nil && joinAlert.Host.Status == filterStatus {
					found = true
				}
			}
			if !found {
				continue
			}
		}
		fmt.Fprintln(color.Output, formatJoinedAlert(joinAlert, c.BoolT("color")))
	}
	return nil
}

func doAlertsClose(c *cli.Context) error {
	isVerbose := c.Bool("verbose")
	argAlertIDs := c.Args()
	reason := c.String("reason")

	if len(argAlertIDs) < 1 {
		cli.ShowCommandHelp(c, "alerts")
		os.Exit(1)
	}

	client := newMackerelFromContext(c)
	for _, alertID := range argAlertIDs {
		alert, err := client.CloseAlert(alertID, reason)
		logger.DieIf(err)

		logger.Log("Alert closed", alertID)
		if isVerbose == true {
			PrettyPrintJSON(alert)
		}
	}
	return nil
}
<commit_msg>refactor alerts commands<commit_after>package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/fatih/color"
	mkr "github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/logger"
	"gopkg.in/urfave/cli.v1"
)

var commandAlerts = cli.Command{
	Name:      "alerts",
	Usage:     "Retrieve/Close alerts",
	ArgsUsage: "[--with-closed | -w] [--limit | -l]",
	Description: `
    Retrieve/Close alerts. With no subcommand specified, this will show all alerts.
    Requests APIs under "/api/v0/alerts". See https://mackerel.io/api-docs/entry/alerts .
`,
	Action: doAlertsRetrieve,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "with-closed, w", Usage: "Display open alert including close alert. default: false"},
		cli.IntFlag{Name: "limit, l", Value: 100, Usage: "Set the number of alerts to display at once when withClosed is active. default: 100"},
	},
	Subcommands: []cli.Command{
		{
			Name:      "list",
			Usage:     "list alerts",
			ArgsUsage: "[--service | -s <service>] [--host-status | -S <file>] [--color | -c] [--with-closed | -w] [--limit | -l]",
			Description: `
    Shows alerts in human-readable format.
`,
			Action: doAlertsList,
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "service, s",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by service. Multiple choices are allowed.",
				},
				cli.StringSliceFlag{
					Name:  "host-status, S",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by status of each host. Multiple choices are allowed.",
				},
				cli.BoolTFlag{Name: "color, c", Usage: "Colorize output. default: true"},
				cli.BoolFlag{Name: "with-closed, w", Usage: "Display open alert including close alert. default: false"},
				cli.IntFlag{Name: "limit, l", Value: 100, Usage: "Set the number of alerts to display at once when withClosed is active. default: 100"},
			},
		},
		{
			Name:      "close",
			Usage:     "close alerts",
			ArgsUsage: "<alertIds....>",
			Description: `
    Closes alerts. Multiple alert IDs can be specified.
`,
			Action: doAlertsClose,
			Flags: []cli.Flag{
				cli.StringFlag{Name: "reason, r", Value: "", Usage: "Reason of closing alert."},
				cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
			},
		},
	},
}

type alertSet struct {
	Alert   *mkr.Alert
	Host    *mkr.Host
	Monitor mkr.Monitor
}

func joinMonitorsAndHosts(client *mkr.Client, alerts []*mkr.Alert) []*alertSet {
	hostsJSON, err := client.FindHosts(&mkr.FindHostsParam{
		Statuses: []string{"working", "standby", "poweroff", "maintenance"},
	})
	logger.DieIf(err)

	hosts := map[string]*mkr.Host{}
	for _, host := range hostsJSON {
		hosts[host.ID] = host
	}

	monitorsJSON, err := client.FindMonitors()
	logger.DieIf(err)

	monitors := map[string]mkr.Monitor{}
	for _, monitor := range monitorsJSON {
		monitors[monitor.MonitorID()] = monitor
	}

	alertSets := []*alertSet{}
	for _, alert := range alerts {
		alertSets = append(
			alertSets,
			&alertSet{Alert: alert, Host: hosts[alert.HostID], Monitor: monitors[alert.MonitorID]},
		)
	}
	return alertSets
}

func formatJoinedAlert(alertSet *alertSet, colorize bool) string {
	const layout = "2006-01-02 15:04:05"

	host := alertSet.Host
	monitor := alertSet.Monitor
	alert := alertSet.Alert

	hostMsg := ""
	if host != nil {
		statusMsg := host.Status
		if host.IsRetired == true {
			statusMsg = "retired"
		}
		if colorize {
			switch statusMsg {
			case "working":
				statusMsg = color.BlueString("working")
			case "standby":
				statusMsg = color.GreenString("standby")
			case "poweroff":
				statusMsg = "poweroff"
			case "maintenance":
				statusMsg = color.YellowString("maintenance")
			}
		}
		hostMsg = fmt.Sprintf(" %s %s", host.Name, statusMsg)
		roleMsgs := []string{}
		for service, roles := range host.Roles {
			roleMsgs = append(roleMsgs, fmt.Sprintf("%s:%s", service, strings.Join(roles, ",")))
		}
		hostMsg += " [" + strings.Join(roleMsgs, ", ") + "]"
	}

	monitorMsg := ""
	if monitor != nil {
		switch m := monitor.(type) {
		case *mkr.MonitorConnectivity:
			monitorMsg = ""
		case *mkr.MonitorHostMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", m.Metric, alert.Value)
			}
		case *mkr.MonitorServiceMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %s %.2f", m.Service, m.Metric, alert.Value)
			}
		case *mkr.MonitorExternalHTTP:
			statusRegexp, _ := regexp.Compile("^[2345][0-9][0-9]$")
			switch alert.Status {
			case "CRITICAL":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeCritical != nil {
					monitorMsg = fmt.Sprintf("%s %.2f > %.2f msec, status:%s", m.URL, alert.Value, *m.ResponseTimeCritical, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%s %.2f msec, %s", m.URL, alert.Value, alert.Message)
				}
			case "WARNING":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeWarning != nil {
					monitorMsg = fmt.Sprintf("%.2f > %.2f msec, status:%s", alert.Value, *m.ResponseTimeWarning, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%.2f msec, %s", alert.Value, alert.Message)
				}
			default:
				monitorMsg = fmt.Sprintf("%.2f msec, status:%s", alert.Value, alert.Message)
			}
		case *mkr.MonitorExpression:
			expression := formatExpressionOneline(m.Expression)
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Warning)
			} else if alert.Status == "UNKNOWN" {
				monitorMsg = fmt.Sprintf("%s", expression)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", expression, alert.Value)
			}
		default:
			monitorMsg = fmt.Sprintf("%s", monitor.MonitorType())
		}
		if monitorMsg == "" {
			monitorMsg = monitor.MonitorName()
		} else {
			monitorMsg = monitor.MonitorName() + " " + monitorMsg
		}
	}
	// If alert is caused by check monitoring, take monitorMsg from alert.message
	if alert.Type == "check" {
		monitorMsg = formatCheckMessage(alert.Message)
	}

	statusMsg := alert.Status
	if colorize {
		switch alert.Status {
		case "CRITICAL":
			statusMsg = color.RedString("CRITICAL ")
		case "WARNING":
			statusMsg = color.YellowString("WARNING ")
		case "OK":
			statusMsg = color.GreenString("OK ")
		case "UNKNOWN":
			statusMsg = "UNKNOWN "
		}
	}
	return fmt.Sprintf("%s %s %s %s%s", alert.ID, time.Unix(alert.OpenedAt, 0).Format(layout), statusMsg, monitorMsg, hostMsg)
}

var expressionNewlinePattern = regexp.MustCompile(`\s*[\r\n]+\s*`)

func formatExpressionOneline(expr string) string {
	expr = strings.Trim(expressionNewlinePattern.ReplaceAllString(expr, " "), " ")
	return strings.Replace(strings.Replace(expr, "( ", "(", -1), " )", ")", -1)
}

func formatCheckMessage(msg string) string {
	truncated := false
	if index := strings.IndexAny(msg, "\n\r"); index != -1 {
		msg = msg[0:index]
		truncated = true
	}
	if runes := []rune(msg); len(runes) > 100 {
		msg = string(runes[0:100])
		truncated = true
	}
	if truncated {
		msg = msg + "..."
	}
	return msg
}

func doAlertsRetrieve(c *cli.Context) error {
	client := newMackerelFromContext(c)
	withClosed := c.Bool("with-closed")
	limit := c.Int("limit")
	alerts, err := fetchAlerts(client, withClosed, limit)
	logger.DieIf(err)
	PrettyPrintJSON(alerts)
	return nil
}

func doAlertsList(c *cli.Context) error {
	filterServices := c.StringSlice("service")
	filterStatuses := c.StringSlice("host-status")
	client := newMackerelFromContext(c)
	withClosed := c.Bool("with-closed")
	limit := c.Int("limit")
	alerts, err := fetchAlerts(client, withClosed, limit)
	logger.DieIf(err)

	joinedAlerts := joinMonitorsAndHosts(client, alerts)
	for _, joinAlert := range joinedAlerts {
		if len(filterServices) > 0 {
			found := false
			for _, filterService := range filterServices {
				if joinAlert.Host != nil {
					if _, ok := joinAlert.Host.Roles[filterService]; ok {
						found = true
					}
				} else {
					var service string
					if m, ok := joinAlert.Monitor.(*mkr.MonitorServiceMetric); ok {
						service = m.Service
					} else if m, ok := joinAlert.Monitor.(*mkr.MonitorExternalHTTP); ok {
						service = m.Service
					}
					found = service == filterService
				}
			}
			if !found {
				continue
			}
		}
		if len(filterStatuses) > 0 {
			found := false
			for _, filterStatus := range filterStatuses {
				if joinAlert.Host != nil && joinAlert.Host.Status == filterStatus {
					found = true
				}
			}
			if !found {
				continue
			}
		}
		fmt.Fprintln(color.Output, formatJoinedAlert(joinAlert, c.BoolT("color")))
	}
	return nil
}

func fetchAlerts(client *mkr.Client, withClosed bool, limit int) ([]*mkr.Alert, error) {
	var resp *mkr.AlertsResp
	var err error
	if withClosed {
		if resp, err = client.FindWithClosedAlerts(); err != nil {
			return nil, err
		}
		if resp.NextID != "" {
			for {
				if limit <= len(resp.Alerts) {
					break
				}
				nextResp, err := client.FindWithClosedAlertsByNextID(resp.NextID)
				if err != nil {
					return nil, err
				}
				resp.Alerts = append(resp.Alerts, nextResp.Alerts...)
				resp.NextID = nextResp.NextID
				if resp.NextID == "" {
					break
				}
				time.Sleep(1 * time.Second)
			}
		}
	} else {
		if resp, err = client.FindAlerts(); err != nil {
			return nil, err
		}
		if resp.NextID != "" {
			for {
				nextResp, err := client.FindAlertsByNextID(resp.NextID)
				if err != nil {
					return nil, err
				}
				resp.Alerts = append(resp.Alerts, nextResp.Alerts...)
				resp.NextID = nextResp.NextID
				if resp.NextID == "" {
					break
				}
				time.Sleep(1 * time.Second)
			}
		}
	}
	if len(resp.Alerts) > limit {
		resp.Alerts = resp.Alerts[:limit]
	}
	return resp.Alerts, nil
}

func doAlertsClose(c *cli.Context) error {
	isVerbose := c.Bool("verbose")
	argAlertIDs := c.Args()
	reason := c.String("reason")

	if len(argAlertIDs) < 1 {
		cli.ShowCommandHelp(c, "alerts")
		os.Exit(1)
	}

	client := newMackerelFromContext(c)
	for _, alertID := range argAlertIDs {
		alert, err := client.CloseAlert(alertID, reason)
		logger.DieIf(err)

		logger.Log("Alert closed", alertID)
		if isVerbose == true {
			PrettyPrintJSON(alert)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/fatih/color"
	mkr "github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/logger"
	"golang.org/x/exp/utf8string"
	"gopkg.in/urfave/cli.v1"
)

var commandAlerts = cli.Command{
	Name:  "alerts",
	Usage: "Retrieve/Close alerts",
	Description: `
    Retrieve/Close alerts. With no subcommand specified, this will show all alerts.
    Requests APIs under "/api/v0/alerts". See https://mackerel.io/api-docs/entry/alerts .
`,
	Action: doAlertsRetrieve,
	Subcommands: []cli.Command{
		{
			Name:      "list",
			Usage:     "list alerts",
			ArgsUsage: "[--service | -s <service>] [--host-status | -S <file>] [--color | -c]",
			Description: `
    Shows alerts in human-readable format.
`,
			Action: doAlertsList,
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "service, s",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by service. Multiple choices are allowed.",
				},
				cli.StringSliceFlag{
					Name:  "host-status, S",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by status of each host. Multiple choices are allowed.",
				},
				cli.BoolTFlag{Name: "color, c", Usage: "Colorize output. default: true"},
			},
		},
		{
			Name:      "close",
			Usage:     "close alerts",
			ArgsUsage: "<alertIds....>",
			Description: `
    Closes alerts. Multiple alert IDs can be specified.
`,
			Action: doAlertsClose,
			Flags: []cli.Flag{
				cli.StringFlag{Name: "reason, r", Value: "", Usage: "Reason of closing alert."},
				cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
			},
		},
	},
}

type alertSet struct {
	Alert   *mkr.Alert
	Host    *mkr.Host
	Monitor mkr.Monitor
}

func joinMonitorsAndHosts(client *mkr.Client, alerts []*mkr.Alert) []*alertSet {
	hostsJSON, err := client.FindHosts(&mkr.FindHostsParam{
		Statuses: []string{"working", "standby", "poweroff", "maintenance"},
	})
	logger.DieIf(err)

	hosts := map[string]*mkr.Host{}
	for _, host := range hostsJSON {
		hosts[host.ID] = host
	}

	monitorsJSON, err := client.FindMonitors()
	logger.DieIf(err)

	monitors := map[string]mkr.Monitor{}
	for _, monitor := range monitorsJSON {
		monitors[monitor.MonitorID()] = monitor
	}

	alertSets := []*alertSet{}
	for _, alert := range alerts {
		alertSets = append(
			alertSets,
			&alertSet{Alert: alert, Host: hosts[alert.HostID], Monitor: monitors[alert.MonitorID]},
		)
	}
	return alertSets
}

func formatJoinedAlert(alertSet *alertSet, colorize bool) string {
	const layout = "2006-01-02 15:04:05"

	host := alertSet.Host
	monitor := alertSet.Monitor
	alert := alertSet.Alert

	hostMsg := ""
	if host != nil {
		statusMsg := host.Status
		if host.IsRetired == true {
			statusMsg = "retired"
		}
		if colorize {
			switch statusMsg {
			case "working":
				statusMsg = color.BlueString("working")
			case "standby":
				statusMsg = color.GreenString("standby")
			case "poweroff":
				statusMsg = "poweroff"
			case "maintenance":
				statusMsg = color.YellowString("maintenance")
			}
		}
		hostMsg = fmt.Sprintf(" %s %s", host.Name, statusMsg)
		roleMsgs := []string{}
		for service, roles := range host.Roles {
			roleMsgs = append(roleMsgs, fmt.Sprintf("%s:%s", service, strings.Join(roles, ",")))
		}
		hostMsg += " [" + strings.Join(roleMsgs, ", ") + "]"
	}

	monitorMsg := ""
	if monitor != nil {
		switch m := monitor.(type) {
		case *mkr.MonitorConnectivity:
			monitorMsg = ""
		case *mkr.MonitorHostMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", m.Metric, alert.Value)
			}
		case *mkr.MonitorServiceMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %s %.2f", m.Service, m.Metric, alert.Value)
			}
		case *mkr.MonitorExternalHTTP:
			statusRegexp, _ := regexp.Compile("^[2345][0-9][0-9]$")
			switch alert.Status {
			case "CRITICAL":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeCritical != nil {
					monitorMsg = fmt.Sprintf("%s %.2f > %.2f msec, status:%s", m.URL, alert.Value, *m.ResponseTimeCritical, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%s %.2f msec, %s", m.URL, alert.Value, alert.Message)
				}
			case "WARNING":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeWarning != nil {
					monitorMsg = fmt.Sprintf("%.2f > %.2f msec, status:%s", alert.Value, *m.ResponseTimeWarning, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%.2f msec, %s", alert.Value, alert.Message)
				}
			default:
				monitorMsg = fmt.Sprintf("%.2f msec, status:%s", alert.Value, alert.Message)
			}
		case *mkr.MonitorExpression:
			expression := formatExpressionOneline(m.Expression)
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Warning)
			} else if alert.Status == "UNKNOWN" {
				monitorMsg = fmt.Sprintf("%s", expression)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", expression, alert.Value)
			}
		default:
			monitorMsg = fmt.Sprintf("%s", monitor.MonitorType())
		}
		if monitorMsg == "" {
			monitorMsg = monitor.MonitorName()
		} else {
			monitorMsg = monitor.MonitorName() + " " + monitorMsg
		}
	}
	// If alert is caused by check monitoring, take monitorMsg from alert.message
	if alert.Type == "check" {
		monitorMsg = formatCheckMessage(alert.Message)
	}

	statusMsg := alert.Status
	if colorize {
		switch alert.Status {
		case "CRITICAL":
			statusMsg = color.RedString("CRITICAL")
		case "WARNING":
			statusMsg = color.YellowString("WARNING ")
		case "UNKNOWN":
			statusMsg = "UNKNOWN "
		}
	}
	return fmt.Sprintf("%s %s %s %s%s", alert.ID, time.Unix(alert.OpenedAt, 0).Format(layout), statusMsg, monitorMsg, hostMsg)
}

var expressionNewlinePattern = regexp.MustCompile(`\s*[\r\n]+\s*`)

func formatExpressionOneline(expr string) string {
	expr = strings.Trim(expressionNewlinePattern.ReplaceAllString(expr, " "), " ")
	return strings.Replace(strings.Replace(expr, "( ", "(", -1), " )", ")", -1)
}

func formatCheckMessage(msg string) string {
	truncated := false
	if index := strings.IndexAny(msg, "\n\r"); index != -1 {
		msg = msg[0:index]
		truncated = true
	}
	if msgU := utf8string.NewString(msg); msgU.RuneCount() > 100 {
		msg = msgU.Slice(0, 100)
		truncated = true
	}
	if truncated {
		msg = msg + "..."
	}
	return msg
}

func doAlertsRetrieve(c *cli.Context) error {
	client := newMackerelFromContext(c)

	alerts, err := client.FindAlerts()
	logger.DieIf(err)
	PrettyPrintJSON(alerts)
	return nil
}

func doAlertsList(c *cli.Context) error {
	filterServices := c.StringSlice("service")
	filterStatuses := c.StringSlice("host-status")
	client := newMackerelFromContext(c)

	alerts, err := client.FindAlerts()
	logger.DieIf(err)
	joinedAlerts := joinMonitorsAndHosts(client, alerts)

	for _, joinAlert := range joinedAlerts {
		if len(filterServices) > 0 {
			found := false
			for _, filterService := range filterServices {
				if joinAlert.Host != nil {
					if _, ok := joinAlert.Host.Roles[filterService]; ok {
						found = true
					}
				} else {
					var service string
					if m, ok := joinAlert.Monitor.(*mkr.MonitorServiceMetric); ok {
						service = m.Service
					} else if m, ok := joinAlert.Monitor.(*mkr.MonitorExternalHTTP); ok {
						service = m.Service
					}
					found = service == filterService
				}
			}
			if !found {
				continue
			}
		}
		if len(filterStatuses) > 0 {
			found := false
			for _, filterStatus := range filterStatuses {
				if joinAlert.Host != nil && joinAlert.Host.Status == filterStatus {
					found = true
				}
			}
			if !found {
				continue
			}
		}
		fmt.Fprintln(color.Output, formatJoinedAlert(joinAlert, c.BoolT("color")))
	}
	return nil
}

func doAlertsClose(c *cli.Context) error {
	isVerbose := c.Bool("verbose")
	argAlertIDs := c.Args()
	reason := c.String("reason")

	if len(argAlertIDs) < 1 {
		cli.ShowCommandHelp(c, "alerts")
		os.Exit(1)
	}

	client := newMackerelFromContext(c)
	for _, alertID := range argAlertIDs {
		alert, err := client.CloseAlert(alertID, reason)
		logger.DieIf(err)

		logger.Log("Alert closed", alertID)
		if isVerbose == true {
			PrettyPrintJSON(alert)
		}
	}
	return nil
}
<commit_msg>use []rune<commit_after>package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/fatih/color"
	mkr "github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/logger"
	"gopkg.in/urfave/cli.v1"
)

var commandAlerts = cli.Command{
	Name:  "alerts",
	Usage: "Retrieve/Close alerts",
	Description: `
    Retrieve/Close alerts. With no subcommand specified, this will show all alerts.
    Requests APIs under "/api/v0/alerts". See https://mackerel.io/api-docs/entry/alerts .
`,
	Action: doAlertsRetrieve,
	Subcommands: []cli.Command{
		{
			Name:      "list",
			Usage:     "list alerts",
			ArgsUsage: "[--service | -s <service>] [--host-status | -S <file>] [--color | -c]",
			Description: `
    Shows alerts in human-readable format.
`,
			Action: doAlertsList,
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "service, s",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by service. Multiple choices are allowed.",
				},
				cli.StringSliceFlag{
					Name:  "host-status, S",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by status of each host. Multiple choices are allowed.",
				},
				cli.BoolTFlag{Name: "color, c", Usage: "Colorize output. default: true"},
			},
		},
		{
			Name:      "close",
			Usage:     "close alerts",
			ArgsUsage: "<alertIds....>",
			Description: `
    Closes alerts. Multiple alert IDs can be specified.
`,
			Action: doAlertsClose,
			Flags: []cli.Flag{
				cli.StringFlag{Name: "reason, r", Value: "", Usage: "Reason of closing alert."},
				cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
			},
		},
	},
}

type alertSet struct {
	Alert   *mkr.Alert
	Host    *mkr.Host
	Monitor mkr.Monitor
}

func joinMonitorsAndHosts(client *mkr.Client, alerts []*mkr.Alert) []*alertSet {
	hostsJSON, err := client.FindHosts(&mkr.FindHostsParam{
		Statuses: []string{"working", "standby", "poweroff", "maintenance"},
	})
	logger.DieIf(err)

	hosts := map[string]*mkr.Host{}
	for _, host := range hostsJSON {
		hosts[host.ID] = host
	}

	monitorsJSON, err := client.FindMonitors()
	logger.DieIf(err)

	monitors := map[string]mkr.Monitor{}
	for _, monitor := range monitorsJSON {
		monitors[monitor.MonitorID()] = monitor
	}

	alertSets := []*alertSet{}
	for _, alert := range alerts {
		alertSets = append(
			alertSets,
			&alertSet{Alert: alert, Host: hosts[alert.HostID], Monitor: monitors[alert.MonitorID]},
		)
	}
	return alertSets
}

func formatJoinedAlert(alertSet *alertSet, colorize bool) string {
	const layout = "2006-01-02 15:04:05"

	host := alertSet.Host
	monitor := alertSet.Monitor
	alert := alertSet.Alert

	hostMsg := ""
	if host != nil {
		statusMsg := host.Status
		if host.IsRetired == true {
			statusMsg = "retired"
		}
		if colorize {
			switch statusMsg {
			case "working":
				statusMsg = color.BlueString("working")
			case "standby":
				statusMsg = color.GreenString("standby")
			case "poweroff":
				statusMsg = "poweroff"
			case "maintenance":
				statusMsg = color.YellowString("maintenance")
			}
		}
		hostMsg = fmt.Sprintf(" %s %s", host.Name, statusMsg)
		roleMsgs := []string{}
		for service, roles := range host.Roles {
			roleMsgs = append(roleMsgs, fmt.Sprintf("%s:%s", service, strings.Join(roles, ",")))
		}
		hostMsg += " [" + strings.Join(roleMsgs, ", ") + "]"
	}

	monitorMsg := ""
	if monitor != nil {
		switch m := monitor.(type) {
		case *mkr.MonitorConnectivity:
			monitorMsg = ""
		case *mkr.MonitorHostMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", m.Metric, alert.Value)
			}
		case *mkr.MonitorServiceMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %s %.2f", m.Service, m.Metric, alert.Value)
			}
		case *mkr.MonitorExternalHTTP:
			statusRegexp, _ := regexp.Compile("^[2345][0-9][0-9]$")
			switch alert.Status {
			case "CRITICAL":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeCritical != nil {
					monitorMsg = fmt.Sprintf("%s %.2f > %.2f msec, status:%s", m.URL, alert.Value, *m.ResponseTimeCritical, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%s %.2f msec, %s", m.URL, alert.Value, alert.Message)
				}
			case "WARNING":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeWarning != nil {
					monitorMsg = fmt.Sprintf("%.2f > %.2f msec, status:%s", alert.Value, *m.ResponseTimeWarning, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%.2f msec, %s", alert.Value, alert.Message)
				}
			default:
				monitorMsg = fmt.Sprintf("%.2f msec, status:%s", alert.Value, alert.Message)
			}
		case *mkr.MonitorExpression:
			expression := formatExpressionOneline(m.Expression)
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Warning)
			} else if alert.Status == "UNKNOWN" {
				monitorMsg = fmt.Sprintf("%s", expression)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", expression, alert.Value)
			}
		default:
			monitorMsg = fmt.Sprintf("%s", monitor.MonitorType())
		}
		if monitorMsg == "" {
			monitorMsg = monitor.MonitorName()
		} else {
			monitorMsg = monitor.MonitorName() + " " + monitorMsg
		}
	}
	// If alert is caused by check monitoring, take monitorMsg from alert.message
	if alert.Type == "check" {
		monitorMsg = formatCheckMessage(alert.Message)
	}

	statusMsg := alert.Status
	if colorize {
		switch alert.Status {
		case "CRITICAL":
			statusMsg = color.RedString("CRITICAL")
		case "WARNING":
			statusMsg = color.YellowString("WARNING ")
		case "UNKNOWN":
			statusMsg = "UNKNOWN "
		}
	}
	return fmt.Sprintf("%s %s %s %s%s", alert.ID, time.Unix(alert.OpenedAt, 0).Format(layout), statusMsg, monitorMsg, hostMsg)
}

var expressionNewlinePattern = regexp.MustCompile(`\s*[\r\n]+\s*`)

func formatExpressionOneline(expr string) string {
	expr = strings.Trim(expressionNewlinePattern.ReplaceAllString(expr, " "), " ")
	return strings.Replace(strings.Replace(expr, "( ", "(", -1), " )", ")", -1)
}

func formatCheckMessage(msg string) string {
	truncated := false
	if index := strings.IndexAny(msg, "\n\r"); index != -1 {
		msg = msg[0:index]
		truncated = true
	}
	if runes := []rune(msg); len(runes) > 100 {
		msg = string(runes[0:100])
		truncated = true
	}
	if truncated {
		msg = msg + "..."
	}
	return msg
}

func doAlertsRetrieve(c *cli.Context) error {
	client := newMackerelFromContext(c)

	alerts, err := client.FindAlerts()
	logger.DieIf(err)
	PrettyPrintJSON(alerts)
	return nil
}

func doAlertsList(c *cli.Context) error {
	filterServices := c.StringSlice("service")
	filterStatuses := c.StringSlice("host-status")
	client := newMackerelFromContext(c)

	alerts, err := client.FindAlerts()
	logger.DieIf(err)
	joinedAlerts := joinMonitorsAndHosts(client, alerts)

	for _, joinAlert := range joinedAlerts {
		if len(filterServices) > 0 {
			found := false
			for _, filterService := range filterServices {
				if joinAlert.Host != nil {
					if _, ok := joinAlert.Host.Roles[filterService]; ok {
						found = true
					}
				} else {
					var service string
					if m, ok := joinAlert.Monitor.(*mkr.MonitorServiceMetric); ok {
						service = m.Service
					} else if m, ok := joinAlert.Monitor.(*mkr.MonitorExternalHTTP); ok {
						service = m.Service
					}
					found = service == filterService
				}
			}
			if !found {
				continue
			}
		}
		if len(filterStatuses) > 0 {
			found := false
			for _, filterStatus := range filterStatuses {
				if joinAlert.Host != nil && joinAlert.Host.Status == filterStatus {
					found = true
				}
			}
			if !found {
				continue
			}
		}
		fmt.Fprintln(color.Output, formatJoinedAlert(joinAlert, c.BoolT("color")))
	}
	return nil
}

func doAlertsClose(c *cli.Context) error {
	isVerbose := c.Bool("verbose")
	argAlertIDs := c.Args()
	reason := c.String("reason")

	if len(argAlertIDs) < 1 {
		cli.ShowCommandHelp(c, "alerts")
		os.Exit(1)
	}

	client := newMackerelFromContext(c)
	for _, alertID := range argAlertIDs {
		alert, err := client.CloseAlert(alertID, reason)
		logger.DieIf(err)

		logger.Log("Alert closed", alertID)
		if isVerbose == true {
			PrettyPrintJSON(alert)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	path "path/filepath"
	"strings"
)

var cmdApiapp = &Command{
	// CustomFlags: true,
	UsageLine: "api [appname]",
	Short:     "create an api application base on beego framework",
	Long: `
create an api application base on beego framework

In the current path, will create a folder named [appname]

In the appname folder has the follow struct:

	├── conf
	│   └── app.conf
	├── controllers
	│   └── default.go
	├── main.go
	└── models
	    └── default.go

`,
}

var apiconf = `
appname = {{.Appname}}
httpport = 8080
runmode = dev
autorender = false
`
var apiMaingo = `package main

import (
	"{{.Appname}}/controllers"
	"github.com/astaxie/beego"
)

func main() {
	beego.Router("/{{.Version}}/users/:objectId", &controllers.UserController{})
	beego.Router("/{{.Version}}/users", &controllers.UserController{})
	beego.Run()
}
`
var apiModels = `package models

type User struct {
	Id    interface{}
	Name  string
	Email string
}

func (this *User) One(id interface{}) (user User, err error) {
	// get user from DB
	// user, err = query(id)
	user = User{id, "astaxie", "astaxie@gmail.com"}
	return
}

func (this *User) All() (users []User, err error) {
	// get all users from DB
	// users, err = queryAll()
	users = append([]User{}, User{1, "astaxie", "astaxie@gmail.com"})
	users = append(users, User{2, "someone", "someone@gmail.com"})
	return
}

func (this *User) Update(id interface{}) (err error) {
	// user, err = update(id, this)
	return
}

`

var apiControllers = `package controllers

import (
	"encoding/json"
	"{{.Appname}}/models"
	"github.com/astaxie/beego"
	"io/ioutil"
)

type UserController struct {
	beego.Controller
}

func (this *UserController) Get() {
	var user models.User
	objectId := this.Ctx.Params[":objectId"]
	if objectId != "" {
		user, err := user.One(objectId)
		if err != nil {
			this.Data["json"] = err
		} else {
			this.Data["json"] = user
		}
	} else {
		users, err := user.All()
		if err != nil {
			this.Data["json"] = err
		} else {
			this.Data["json"] = users
		}
	}
	this.ServeJson()
}

func (this *UserController) Put() {
	defer this.ServeJson()
	var user models.User
	objectId := this.Ctx.Params[":objectId"]

	body, err := ioutil.ReadAll(this.Ctx.Request.Body)
	if err != nil {
		this.Data["json"] = err
		return
	}
	this.Ctx.Request.Body.Close()

	if err = json.Unmarshal(body, &user); err != nil {
		this.Data["json"] = err
		return
	}

	err = user.Update(objectId)
	if err != nil {
		this.Data["json"] = err
	} else {
		this.Data["json"] = "update success!"
	}
}

`

func init() {
	cmdApiapp.Run = createapi
}

func createapi(cmd *Command, args []string) {
	version := "1"
	if len(args) == 2 {
		version = args[1]
	} else if len(args) != 1 {
		fmt.Println("error args")
		os.Exit(2)
	}
	apppath, packpath, err := checkEnv(args[0])
	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}
	os.MkdirAll(apppath, 0755)
	fmt.Println("create app folder:", apppath)
	os.Mkdir(path.Join(apppath, "conf"), 0755)
	fmt.Println("create conf:", path.Join(apppath, "conf"))
	os.Mkdir(path.Join(apppath, "controllers"), 0755)
	fmt.Println("create controllers:", path.Join(apppath, "controllers"))
	os.Mkdir(path.Join(apppath, "models"), 0755)
	fmt.Println("create models:", path.Join(apppath, "models"))

	fmt.Println("create conf app.conf:", path.Join(apppath, "conf", "app.conf"))
	writetofile(path.Join(apppath, "conf", "app.conf"),
		strings.Replace(apiconf, "{{.Appname}}", args[0], -1))

	fmt.Println("create controllers default.go:", path.Join(apppath, "controllers", "default.go"))
	writetofile(path.Join(apppath, "controllers", "default.go"),
		strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))

	fmt.Println("create models default.go:", path.Join(apppath, "models", "default.go"))
	writetofile(path.Join(apppath, "models", "default.go"), apiModels)

	fmt.Println("create main.go:", path.Join(apppath, "main.go"))
	apiMaingo = strings.Replace(apiMaingo, "{{.Version}}", version, -1)
	writetofile(path.Join(apppath, "main.go"),
		strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
}

func checkEnv(appname string) (apppath, packpath string, err error) {
	curpath, err := os.Getwd()
	if err != nil {
		return
	}

	gopath := os.Getenv("GOPATH")
	Debugf("gopath:%s", gopath)
	if gopath == "" {
		err = fmt.Errorf("you should set GOPATH in the env")
		return
	}

	appsrcpath := ""
	haspath := false
	wgopath := path.SplitList(gopath)
	for _, wg := range wgopath {
		wg = path.Join(wg, "src")

		if path.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
			haspath = true
			appsrcpath = wg
			break
		}
	}

	if !haspath {
		err = fmt.Errorf("can't create application outside of GOPATH `%s`\n"+
			"you first should `cd $GOPATH%ssrc` then use create\n", gopath, string(path.Separator))
		return
	}
	apppath = path.Join(curpath, appname)

	if _, e := os.Stat(apppath); os.IsNotExist(e) == false {
		err = fmt.Errorf("path `%s` exists, can not create app without remove it\n", apppath)
		return
	}
	packpath = strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/")
	return
}
<commit_msg>an example for api application<commit_after>package main

import (
	"fmt"
	"os"
	path "path/filepath"
	"strings"
)

var cmdApiapp = &Command{
	// CustomFlags: true,
	UsageLine: "api [appname]",
	Short:     "create an api application base on beego framework",
	Long: `
create an api application base on beego framework

In the current path, will create a folder named [appname]

In the appname folder has the follow struct:

	├── conf
	│   └── app.conf
	├── controllers
	│   └── default.go
	├── main.go
	└── models
	    └── object.go

`,
}

var apiconf = `
appname = {{.Appname}}
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
`
var apiMaingo = `package main

import (
	"github.com/astaxie/beego"
	"{{.Appname}}/controllers"
)

//		Objects

//	URL					HTTP Verb				Functionality
//	/object				POST					Creating Objects
//	/object/<objectId>	GET						Retrieving Objects
//	/object/<objectId>	PUT						Updating Objects
//	/object				GET						Queries
//	/object/<objectId>	DELETE					Deleting Objects

func main() {
	beego.RESTRouter("/object", &controllers.ObejctController{})
	beego.Run()
}
`
var apiModels = `package models

import (
	"errors"
	"strconv"
	"time"
)

var (
	Objects map[string]*Object
)

type Object struct {
	ObjectId   string
	Score      int64
	PlayerName string
}

func init() {
	Objects = make(map[string]*Object)
	Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
	Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
}

func AddOne(object Object) (ObjectId string) {
	object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
	Objects[object.ObjectId] = &object
	return object.ObjectId
}

func GetOne(ObjectId string) (object *Object, err error) {
	if v, ok := Objects[ObjectId]; ok {
		return v, nil
	}
	return nil, errors.New("ObjectId Not Exist")
}

func GetAll() map[string]*Object {
	return Objects
}

func Update(ObjectId string, Score int64) (err error) {
	if v, ok := Objects[ObjectId]; ok {
		v.Score = Score
		return nil
	}
	return errors.New("ObjectId Not Exist")
}

func Delete(ObjectId string) {
	delete(Objects, ObjectId)
}
`

var apiControllers = `package controllers

import (
	"encoding/json"
	"github.com/astaxie/beego"
	"{{.Appname}}/models"
)

type ResponseInfo struct {
}

type ObejctController struct {
	beego.Controller
}

func (this *ObejctController) Post() {
	var ob models.Object
	json.Unmarshal(this.Ctx.RequestBody, &ob)
	objectid := models.AddOne(ob)
	this.Data["json"] = "{\"ObjectId\":\"" + objectid + "\"}"
	this.ServeJson()
}

func (this *ObejctController) Get() {
	objectId := this.Ctx.Params[":objectId"]
	if objectId != "" {
		ob, err := models.GetOne(objectId)
		if err != nil {
			this.Data["json"] = err
		} else {
			this.Data["json"] = ob
		}
	} else {
		obs := models.GetAll()
		this.Data["json"] = obs
	}
	this.ServeJson()
}

func (this *ObejctController) Put() {
	objectId := this.Ctx.Params[":objectId"]
	var ob models.Object
	json.Unmarshal(this.Ctx.RequestBody, &ob)

	err := models.Update(objectId, ob.Score)
	if err != nil {
		this.Data["json"] = err
	} else {
		this.Data["json"] = "update success!"
	}
	this.ServeJson()
}

func (this *ObejctController) Delete() {
	objectId := this.Ctx.Params[":objectId"]
	models.Delete(objectId)
	this.Data["json"] = "delete success!"
	this.ServeJson()
}
`

func init() {
	cmdApiapp.Run = createapi
}

func createapi(cmd *Command, args []string) {
	if len(args) != 1 {
		fmt.Println("error args")
		os.Exit(2)
	}
	apppath, packpath, err := checkEnv(args[0])
	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}
	os.MkdirAll(apppath, 0755)
	fmt.Println("create app folder:", apppath)
	os.Mkdir(path.Join(apppath, "conf"), 0755)
	fmt.Println("create conf:", path.Join(apppath, "conf"))
	os.Mkdir(path.Join(apppath, "controllers"), 0755)
	fmt.Println("create controllers:", path.Join(apppath, "controllers"))
	os.Mkdir(path.Join(apppath, "models"), 0755)
	fmt.Println("create models:", path.Join(apppath, "models"))

	fmt.Println("create conf app.conf:", path.Join(apppath, "conf", "app.conf"))
	writetofile(path.Join(apppath, "conf", "app.conf"),
		strings.Replace(apiconf, "{{.Appname}}", args[0], -1))

	fmt.Println("create controllers default.go:", path.Join(apppath, "controllers", "default.go"))
	writetofile(path.Join(apppath, "controllers", "default.go"),
		strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))

	fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go"))
	writetofile(path.Join(apppath, "models", "object.go"), apiModels)

	fmt.Println("create main.go:", path.Join(apppath, "main.go"))
	writetofile(path.Join(apppath, "main.go"),
		strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
}

func checkEnv(appname string) (apppath, packpath string, err error) {
	curpath, err := os.Getwd()
	if err != nil {
		return
	}

	gopath := os.Getenv("GOPATH")
	Debugf("gopath:%s", gopath)
	if gopath == "" {
		err = fmt.Errorf("you should set GOPATH in the env")
		return
	}

	appsrcpath := ""
	haspath := false
	wgopath := path.SplitList(gopath)
	for _, wg := range wgopath {
		wg = path.Join(wg, "src")

		if path.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
			haspath = true
			appsrcpath = wg
			break
		}
	}

	if !haspath {
		err = fmt.Errorf("can't create application outside of GOPATH `%s`\n"+
			"you first should `cd $GOPATH%ssrc` then use create\n", gopath, string(path.Separator))
		return
	}
	apppath = path.Join(curpath, appname)

	if _, e := os.Stat(apppath); os.IsNotExist(e) == false {
		err = fmt.Errorf("path `%s` exists, can not create app without remove it\n", apppath)
		return
	}
	packpath = strings.Join(strings.Split(apppath[len(appsrcpath)+1:], string(path.Separator)), "/")
	return
}
<|endoftext|>


<commit_before>package spotify

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"
)

// SimpleArtist contains basic info about an artist.
type SimpleArtist struct {
	Name string `json:"name"`
	ID   ID     `json:"id"`
	// The Spotify URI for the artist.
	URI URI `json:"uri"`
	// A link to the Web API enpoint providing full details of the artist.
	Endpoint     string            `json:"href"`
	ExternalURLs map[string]string `json:"external_urls"`
}

// FullArtist provides extra artist data in addition to what is provided by SimpleArtist.
type FullArtist struct {
	SimpleArtist
	// The popularity of the artist, expressed as an integer between 0 and 100.
	// The artist's popularity is calculated from the popularity of the artist's tracks.
	Popularity int `json:"popularity"`
	// A list of genres the artist is associated with.  For example, "Prog Rock"
	// or "Post-Grunge".  If not yet classified, the slice is empty.
	Genres    []string  `json:"genres"`
	Followers Followers `json:"followers"`
	// Images of the artist in various sizes, widest first.
	Images []Image `json:"images"`
}

// GetArtist gets Spotify catalog information for a single artist, given its Spotify ID.
func (c *Client) GetArtist(id ID) (*FullArtist, error) {
	spotifyURL := fmt.Sprintf("%sartists/%s", c.baseURL, id)

	var a FullArtist
	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return &a, nil
}

// GetArtists gets spotify catalog information for several artists based on their
// Spotify IDs.  It supports up to 50 artists in a single call.  Artists are
// returned in the order requested.  If an artist is not found, that position
// in the result will be nil.  Duplicate IDs will result in duplicate artists
// in the result.
func (c *Client) GetArtists(ids ...ID) ([]*FullArtist, error) {
	spotifyURL := fmt.Sprintf("%sartists?ids=%s", c.baseURL, strings.Join(toStringSlice(ids), ","))

	var a struct {
		Artists []*FullArtist
	}

	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return a.Artists, nil
}

// GetArtistsTopTracks gets Spotify catalog information about an artist's top
// tracks in a particular country.  It returns a maximum of 10 tracks.  The
// country is specified as an ISO 3166-1 alpha-2 country code.
func (c *Client) GetArtistsTopTracks(artistID ID, country string) ([]FullTrack, error) {
	spotifyURL := fmt.Sprintf("%sartists/%s/top-tracks?country=%s", c.baseURL, artistID, country)

	var t struct {
		Tracks []FullTrack `json:"tracks"`
	}

	err := c.get(spotifyURL, &t)
	if err != nil {
		return nil, err
	}

	return t.Tracks, nil
}

// GetRelatedArtists gets Spotify catalog information about artists similar to a
// given artist.  Similarity is based on analysis of the Spotify community's
// listening history.  This function returns up to 20 artists that are considered
// related to the specified artist.
func (c *Client) GetRelatedArtists(id ID) ([]FullArtist, error) {
	spotifyURL := fmt.Sprintf("%sartists/%s/related-artists", c.baseURL, id)

	var a struct {
		Artists []FullArtist `json:"artists"`
	}

	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return a.Artists, nil
}

// GetArtistAlbums gets Spotify catalog information about an artist's albums.
// It is equivalent to GetArtistAlbumsOpt(artistID, nil).
func (c *Client) GetArtistAlbums(artistID ID) (*SimpleAlbumPage, error) {
	return c.GetArtistAlbumsOpt(artistID, nil, nil)
}

// GetArtistAlbumsOpt is just like GetArtistAlbums, but it accepts optional
// parameters used to filter and sort the result.
//
// The AlbumType argument can be used to find a particular type of album.  Search
// for multiple types by OR-ing the types together.
func (c *Client) GetArtistAlbumsOpt(artistID ID, options *Options, t *AlbumType) (*SimpleAlbumPage, error) {
	spotifyURL := fmt.Sprintf("%sartists/%s/albums", c.baseURL, artistID)
	// add optional query string if options were specified
	values := url.Values{}
	if t != nil {
		values.Set("album_type", t.encode())
	}
	if options != nil {
		if options.Country != nil {
			values.Set("market", *options.Country)
		} else {
			// if the market is not specified, Spotify will likely return a lot
			// of duplicates (one for each market in which the album is available)
			// - prevent this behavior by falling back to the US by default
			// TODO: would this ever be the desired behavior?
			values.Set("market", CountryUSA)
		}
		if options.Limit != nil {
			values.Set("limit", strconv.Itoa(*options.Limit))
		}
		if options.Offset != nil {
			values.Set("offset", strconv.Itoa(*options.Offset))
		}
	}
	if query := values.Encode(); query != "" {
		spotifyURL += "?" + query
	}

	var p SimpleAlbumPage

	err := c.get(spotifyURL, &p)
	if err != nil {
		return nil, err
	}

	return &p, nil
}
<commit_msg>feat: Enable option to not sent market using GetArtistAlbumsOpt<commit_after>package spotify

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"
)

// SimpleArtist contains basic info about an artist.
type SimpleArtist struct {
	Name string `json:"name"`
	ID   ID     `json:"id"`
	// The Spotify URI for the artist.
	URI URI `json:"uri"`
	// A link to the Web API enpoint providing full details of the artist.
	Endpoint     string            `json:"href"`
	ExternalURLs map[string]string `json:"external_urls"`
}

// FullArtist provides extra artist data in addition to what is provided by SimpleArtist.
type FullArtist struct {
	SimpleArtist
	// The popularity of the artist, expressed as an integer between 0 and 100.
	// The artist's popularity is calculated from the popularity of the artist's tracks.
	Popularity int `json:"popularity"`
	// A list of genres the artist is associated with.  For example, "Prog Rock"
	// or "Post-Grunge".  If not yet classified, the slice is empty.
	Genres    []string  `json:"genres"`
	Followers Followers `json:"followers"`
	// Images of the artist in various sizes, widest first.
	Images []Image `json:"images"`
}

// GetArtist gets Spotify catalog information for a single artist, given its Spotify ID.
func (c *Client) GetArtist(id ID) (*FullArtist, error) {
	spotifyURL := fmt.Sprintf("%sartists/%s", c.baseURL, id)

	var a FullArtist
	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return &a, nil
}

// GetArtists gets spotify catalog information for several artists based on their
// Spotify IDs.  It supports up to 50 artists in a single call.  Artists are
// returned in the order requested.  If an artist is not found, that position
// in the result will be nil.  Duplicate IDs will result in duplicate artists
// in the result.
func (c *Client) GetArtists(ids ...ID) ([]*FullArtist, error) {
	spotifyURL := fmt.Sprintf("%sartists?ids=%s", c.baseURL, strings.Join(toStringSlice(ids), ","))

	var a struct {
		Artists []*FullArtist
	}

	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return a.Artists, nil
}

// GetArtistsTopTracks gets Spotify catalog information about an artist's top
// tracks in a particular country.  It returns a maximum of 10 tracks.  The
// country is specified as an ISO 3166-1 alpha-2 country code.
func (c *Client) GetArtistsTopTracks(artistID ID, country string) ([]FullTrack, error) {
	spotifyURL := fmt.Sprintf("%sartists/%s/top-tracks?country=%s", c.baseURL, artistID, country)

	var t struct {
		Tracks []FullTrack `json:"tracks"`
	}

	err := c.get(spotifyURL, &t)
	if err != nil {
		return nil, err
	}

	return t.Tracks, nil
}

// GetRelatedArtists gets Spotify catalog information about artists similar to a
// given artist.  Similarity is based on analysis of the Spotify community's
// listening history.  This function returns up to 20 artists that are considered
// related to the specified artist.
func (c *Client) GetRelatedArtists(id ID) ([]FullArtist, error) {
	spotifyURL := fmt.Sprintf("%sartists/%s/related-artists", c.baseURL, id)

	var a struct {
		Artists []FullArtist `json:"artists"`
	}

	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return a.Artists, nil
}

// GetArtistAlbums gets Spotify catalog information about an artist's albums.
// It is equivalent to GetArtistAlbumsOpt(artistID, nil).
func (c *Client) GetArtistAlbums(artistID ID) (*SimpleAlbumPage, error) {
	return c.GetArtistAlbumsOpt(artistID, nil, nil)
}

// GetArtistAlbumsOpt is just like GetArtistAlbums, but it accepts optional
// parameters used to filter and sort the result.
//
// The AlbumType argument can be used to find a particular type of album.  Search
// for multiple types by OR-ing the types together. If the market (Options) is
// not specified, Spotify will likely return a lot of duplicates (one for each
// market in which the album is available)
func (c *Client) GetArtistAlbumsOpt(artistID ID, options *Options, t *AlbumType) (*SimpleAlbumPage, error) {
	spotifyURL := fmt.Sprintf("%sartists/%s/albums", c.baseURL, artistID)
	// add optional query string if options were specified
	values := url.Values{}
	if t != nil {
		values.Set("album_type", t.encode())
	}
	if options != nil {
		if options.Country != nil {
			values.Set("market", *options.Country)
		}
		if options.Limit != nil {
			values.Set("limit", strconv.Itoa(*options.Limit))
		}
		if options.Offset != nil {
			values.Set("offset", strconv.Itoa(*options.Offset))
		}
	}
	if query := values.Encode(); query != "" {
		spotifyURL += "?" + query
	}

	var p SimpleAlbumPage

	err := c.get(spotifyURL, &p)
	if err != nil {
		return nil, err
	}

	return &p, nil
}
<|endoftext|>


<commit_before>package main

import (
	"compress/gzip"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/dustin/go-hashset"
	"github.com/dustin/gomemcached"

	"encoding/hex"
	"github.com/couchbaselabs/cbfs/config"
	"sync"
)

const backupKey = "/@backup"

type backupItem struct {
	Fn   string                `json:"filename"`
	Oid  string                `json:"oid"`
	When time.Time             `json:"when"`
	Conf cbfsconfig.CBFSConfig `json:"conf"`
}

type backups struct {
	Latest  backupItem   `json:"latest"`
	Backups []backupItem `json:"backups"`
}

func logDuration(m string, startTime time.Time) {
	log.Printf("Completed %v in %v", m, time.Since(startTime))
}

func streamFileMeta(w io.Writer,
	fch chan *namedFile,
	ech chan error) error {

	enc := json.NewEncoder(w)
	for {
		select {
		case f, ok := <-fch:
			if !ok {
				return nil
			}
			err := enc.Encode(map[string]interface{}{
				"path": f.name,
				"meta": f.meta,
			})
			if err != nil {
				return err
			}
		case e, ok := <-ech:
			if ok {
				return e
			}
			ech = nil
		}
	}
}

func backupTo(w io.Writer) (err error) {
	fch := make(chan *namedFile)
	ech := make(chan error)
	qch := make(chan bool)

	defer close(qch)

	defer logDuration("backup", time.Now())

	go pathGenerator("", fch, ech, qch)

	gz := gzip.NewWriter(w)
	defer func() {
		e := gz.Close()
		if err != nil {
			err = e
		}
	}()

	return streamFileMeta(gz, fch, ech)
}

func recordBackupObject() error {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil {
		return err
	}

	removeDeadBackups(&b)

	f, err := os.Create(filepath.Join(*root, ".backup.json"))
	if err != nil {
		return err
	}
	defer f.Close()
	e := json.NewEncoder(f)
	return e.Encode(&b)
}

func recordRemoteBackupObjects() {
	rn, err := findRemoteNodes()
	if err != nil {
		log.Printf("Error getting remote nodes for recording backup: %v",
			err)
		return
	}
	for _, n := range rn {
		u := fmt.Sprintf("http://%s%s",
			n.Address(), markBackupPrefix)
		c := n.Client()
		res, err := c.Post(u, "application/octet-stream", nil)
		if err != nil {
			log.Printf("Error posting to %v: %v", u, err)
			continue
		}
		res.Body.Close()
		if res.StatusCode != 204 {
			log.Printf("HTTP Error posting to %v: %v", u, res.Status)
		}
	}
}

func removeDeadBackups(b *backups) {
	// Keep only backups we're pretty sure still exist.
	obn := b.Backups
	b.Backups = nil
	for _, bi := range obn {
		fm := fileMeta{}
		err := couchbase.Get(shortName(bi.Fn), &fm)
		if gomemcached.IsNotFound(err) {
			log.Printf("Dropping previous (deleted) backup: %v",
				bi.Fn)
		} else {
			b.Backups = append(b.Backups, bi)
		}
	}

}

func storeBackupObject(fn, h string) error {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil && !gomemcached.IsNotFound(err) {
		log.Printf("Weird: %v", err)
		// return err
	}

	removeDeadBackups(&b)

	ob := backupItem{fn, h, time.Now().UTC(), *globalConfig}

	b.Latest = ob
	b.Backups = append(b.Backups, ob)

	return couchbase.Set(backupKey, 0, &b)
}

func backupToCBFS(fn string) error {
	f, err := NewHashRecord(*root, "")
	if err != nil {
		return err
	}
	defer f.Close()

	pr, pw := io.Pipe()

	go func() { pw.CloseWithError(backupTo(pw)) }()

	h, length, err := f.Process(pr)
	if err != nil {
		return err
	}

	err = recordBlobOwnership(h, length, true)
	if err != nil {
		return err
	}

	fm := fileMeta{
		OID:      h,
		Length:   length,
		Modified: time.Now().UTC(),
	}

	err = storeMeta(fn, 0, fm, 1, nil)
	if err != nil {
		return err
	}

	err = storeBackupObject(fn, h)
	if err != nil {
		return err
	}

	err = recordBackupObject()
	if err != nil {
		log.Printf("Failed to record backup OID: %v", err)
	}

	go recordRemoteBackupObjects()

	log.Printf("Replicating backup %v.", h)
	go increaseReplicaCount(h, length, globalConfig.MinReplicas-1)

	return nil
}

func doMarkBackup(w http.ResponseWriter, req *http.Request) {
	if req.FormValue("all") == "true" {
		go recordRemoteBackupObjects()
	}
	err := recordBackupObject()
	if err != nil {
		http.Error(w, fmt.Sprintf("Error marking backup; %v", err), 500)
		return
	}
	w.WriteHeader(204)
}

func doBackupDocs(w http.ResponseWriter, req *http.Request) {
	fn := req.FormValue("fn")
	if fn == "" {
		http.Error(w, "Missing fn parameter", 400)
		return
	}

	if bg, _ := strconv.ParseBool(req.FormValue("bg")); bg {
		go func() {
			err := backupToCBFS(fn)
			if err != nil {
				log.Printf("Error performing bg backup: %v", err)
			}
		}()
		w.WriteHeader(202)
		return
	}

	err := backupToCBFS(fn)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error performing backup: %v", err), 500)
		return
	}

	w.WriteHeader(201)
}

func doGetBackupInfo(w http.ResponseWriter, req *http.Request) {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil {
		code := 500
		if gomemcached.IsNotFound(err) {
			code = 404
		}
		http.Error(w, err.Error(), code)
		return
	}

	removeDeadBackups(&b)

	sendJson(w, req, &b)
}

var errExists = errors.New("item exists")

func maybeStoreMeta(k string, fm fileMeta, exp int, force bool) error {
	if force {
		return couchbase.Set(k, exp, fm)
	}
	added, err := couchbase.Add(k, exp, fm)
	if err == nil && !added {
		err = errExists
	}
	return err
}

func doRestoreDocument(w http.ResponseWriter, req *http.Request, fn string) {
	d := json.NewDecoder(req.Body)
	fm := fileMeta{}
	err := d.Decode(&fm)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	for len(fn) > 0 && fn[0] == '/' {
		fn = fn[1:]
	}

	if fn == "" {
		http.Error(w, "No filename", 400)
		return
	}

	if strings.Contains(fn, "//") {
		http.Error(w,
			fmt.Sprintf("Too many slashes in the path name: %v", fn), 400)
		return
	}

	_, err = referenceBlob(fm.OID)
	if err != nil {
		log.Printf("Missing blob %v while restoring %v - restoring anyway",
			fm.OID, fn)
	}

	exp := getExpiration(req.Header)
	if exp == -1 {
		exp = getExpiration(fm.Headers)
		if exp > 0 && exp < 60*60*24*30 {
			exp = int(fm.Modified.Add(time.Second * time.Duration(exp)).Unix())
		}
	}

	if exp < 0 {
		log.Printf("Attempt to restore expired file: %v", fn)
		w.WriteHeader(201)
		return
	}

	force := false
	err = maybeStoreMeta(fn, fm, exp, force)
	switch err {
	case errExists:
		http.Error(w, err.Error(), 409)
		return
	case nil:
	default:
		log.Printf("Error storing file meta of %v -> %v: %v",
			fn, fm.OID, err)
		http.Error(w,
			fmt.Sprintf("Error recording file meta: %v", err), 500)
		return
	}

	log.Printf("Restored %v -> %v (exp=%v)", fn, fm.OID, exp)

	w.WriteHeader(201)
}

func loadBackupHashes(oid string) (*hashset.Hashset, int, error) {
	rv := &hashset.Hashset{}

	r := blobReader(oid)
	defer r.Close()
	gz, err := gzip.NewReader(r)
	if err != nil {
		return nil, 0, err
	}
	defer gz.Close()

	d := json.NewDecoder(gz)

	hashlen := getHash().Size()

	visited := 0
	for {
		ob := struct {
			Meta struct {
				OID   string
				Older []struct {
					OID string
				}
			}
		}{}

		err := d.Decode(&ob)
		switch err {
		case nil:
			oid, err := hex.DecodeString(ob.Meta.OID)
			if err != nil {
				return nil, visited, err
			}
			if len(oid) != hashlen {
				log.Printf("Invalid hash from %#v", ob)
				continue
			}
			rv.Add(oid)
			visited++
			for _, obs := range ob.Meta.Older {
				oid, err = hex.DecodeString(obs.OID)
				if err != nil {
					return nil, visited, err
				}
				rv.Add(oid)
				visited++
			}
		case io.EOF:
			return rv, visited, nil
		default:
			return nil, visited, err
		}
	}
}

func loadExistingHashes() (*hashset.Hashset, error) {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil && !gomemcached.IsNotFound(err) {
		return nil, err
	}

	oids := make(chan string)
	hsch := make(chan *hashset.Hashset)
	visitch := make(chan int)
	errch := make(chan error)

	wg := sync.WaitGroup{}

	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for o := range oids {
				h, v, e := loadBackupHashes(o)
				if e == nil {
					hsch <- h
				} else {
					errch <- e
				}
				visitch <- v
			}
		}()
	}
	go func() {
		wg.Wait()
		close(hsch)
		close(visitch)
		close(errch)
	}()

	go func() {
		for _, i := range b.Backups {
			log.Printf("Loading backups from %v / %v", i.Oid, i.Fn)
			oids <- i.Oid
		}
		close(oids)
	}()

	visited := 0
	hs := &hashset.Hashset{}
	for {
		// Done getting all the things
		if hsch == nil && visitch == nil && errch == nil {
			break
		}
		select {
		case v, ok := <-visitch:
			visited += v
			if !ok {
				visitch = nil
			}
		case e, ok := <-errch:
			err = e
			if !ok {
				errch = nil
			}
		case h, ok := <-hsch:
			if ok {
				log.Printf("Got %v hashes from a backup",
					h.Len())
				hs.AddAll(h)
			} else {
				hsch = nil
			}
		}
	}

	log.Printf("Visited %v obs, kept %v", visited, hs.Len())

	return hs, err
}
<commit_msg>Something weirded up my imports<commit_after>package main

import (
	"compress/gzip"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/dustin/go-hashset"
	"github.com/dustin/gomemcached"

	"github.com/couchbaselabs/cbfs/config"
)

const backupKey = "/@backup"

type backupItem struct {
	Fn   string                `json:"filename"`
	Oid  string                `json:"oid"`
	When time.Time             `json:"when"`
	Conf cbfsconfig.CBFSConfig `json:"conf"`
}

type backups struct {
	Latest  backupItem   `json:"latest"`
	Backups []backupItem `json:"backups"`
}

func logDuration(m string, startTime time.Time) {
	log.Printf("Completed %v in %v", m, time.Since(startTime))
}

func streamFileMeta(w io.Writer,
	fch chan *namedFile,
	ech chan error) error {

	enc := json.NewEncoder(w)
	for {
		select {
		case f, ok := <-fch:
			if !ok {
				return nil
			}
			err := enc.Encode(map[string]interface{}{
				"path": f.name,
				"meta": f.meta,
			})
			if err != nil {
				return err
			}
		case e, ok := <-ech:
			if ok {
				return e
			}
			ech = nil
		}
	}
}

func backupTo(w io.Writer) (err error) {
	fch := make(chan *namedFile)
	ech := make(chan error)
	qch := make(chan bool)

	defer close(qch)

	defer logDuration("backup", time.Now())

	go pathGenerator("", fch, ech, qch)

	gz := gzip.NewWriter(w)
	defer func() {
		e := gz.Close()
		if err != nil {
			err = e
		}
	}()

	return streamFileMeta(gz, fch, ech)
}

func recordBackupObject() error {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil {
		return err
	}

	removeDeadBackups(&b)

	f, err := os.Create(filepath.Join(*root, ".backup.json"))
	if err != nil {
		return err
	}
	defer f.Close()
	e := json.NewEncoder(f)
	return e.Encode(&b)
}

func recordRemoteBackupObjects() {
	rn, err := findRemoteNodes()
	if err != nil {
		log.Printf("Error getting remote nodes for recording backup: %v",
			err)
		return
	}
	for _, n := range rn {
		u := fmt.Sprintf("http://%s%s",
			n.Address(), markBackupPrefix)
		c := n.Client()
		res, err := c.Post(u, "application/octet-stream", nil)
		if err != nil {
			log.Printf("Error posting to %v: %v", u, err)
			continue
		}
		res.Body.Close()
		if res.StatusCode != 204 {
			log.Printf("HTTP Error posting to %v: %v", u, res.Status)
		}
	}
}

func removeDeadBackups(b *backups) {
	// Keep only backups we're pretty sure still exist.
	obn := b.Backups
	b.Backups = nil
	for _, bi := range obn {
		fm := fileMeta{}
		err := couchbase.Get(shortName(bi.Fn), &fm)
		if gomemcached.IsNotFound(err) {
			log.Printf("Dropping previous (deleted) backup: %v",
				bi.Fn)
		} else {
			b.Backups = append(b.Backups, bi)
		}
	}

}

func storeBackupObject(fn, h string) error {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil && !gomemcached.IsNotFound(err) {
		log.Printf("Weird: %v", err)
		// return err
	}

	removeDeadBackups(&b)

	ob := backupItem{fn, h, time.Now().UTC(), *globalConfig}

	b.Latest = ob
	b.Backups = append(b.Backups, ob)

	return couchbase.Set(backupKey, 0, &b)
}

func backupToCBFS(fn string) error {
	f, err := NewHashRecord(*root, "")
	if err != nil {
		return err
	}
	defer f.Close()

	pr, pw := io.Pipe()

	go func() { pw.CloseWithError(backupTo(pw)) }()

	h, length, err := f.Process(pr)
	if err != nil {
		return err
	}

	err = recordBlobOwnership(h, length, true)
	if err != nil {
		return err
	}

	fm := fileMeta{
		OID:      h,
		Length:   length,
		Modified: time.Now().UTC(),
	}

	err = storeMeta(fn, 0, fm, 1, nil)
	if err != nil {
		return err
	}

	err = storeBackupObject(fn, h)
	if err != nil {
		return err
	}

	err = recordBackupObject()
	if err != nil {
		log.Printf("Failed to record backup OID: %v", err)
	}

	go recordRemoteBackupObjects()

	log.Printf("Replicating backup %v.", h)
	go increaseReplicaCount(h, length, globalConfig.MinReplicas-1)

	return nil
}

func doMarkBackup(w http.ResponseWriter, req *http.Request) {
	if req.FormValue("all") == "true" {
		go recordRemoteBackupObjects()
	}
	err := recordBackupObject()
	if err != nil {
		http.Error(w, fmt.Sprintf("Error marking backup; %v", err), 500)
		return
	}
	w.WriteHeader(204)
}

func doBackupDocs(w http.ResponseWriter, req *http.Request) {
	fn := req.FormValue("fn")
	if fn == "" {
		http.Error(w, "Missing fn parameter", 400)
		return
	}

	if bg, _ := strconv.ParseBool(req.FormValue("bg")); bg {
		go func() {
			err := backupToCBFS(fn)
			if err != nil {
				log.Printf("Error performing bg backup: %v", err)
			}
		}()
		w.WriteHeader(202)
		return
	}

	err := backupToCBFS(fn)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error performing backup: %v", err), 500)
		return
	}

	w.WriteHeader(201)
}

func doGetBackupInfo(w http.ResponseWriter, req *http.Request) {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil {
		code := 500
		if gomemcached.IsNotFound(err) {
			code = 404
		}
		http.Error(w, err.Error(), code)
		return
	}

	removeDeadBackups(&b)

	sendJson(w, req, &b)
}

var errExists = errors.New("item exists")

func maybeStoreMeta(k string, fm fileMeta, exp int, force bool) error {
	if force {
		return couchbase.Set(k, exp, fm)
	}
	added, err := couchbase.Add(k, exp, fm)
	if err == nil && !added {
		err = errExists
	}
	return err
}

func doRestoreDocument(w http.ResponseWriter, req *http.Request, fn string) {
	d := json.NewDecoder(req.Body)
	fm := fileMeta{}
	err := d.Decode(&fm)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	for len(fn) > 0 && fn[0] == '/' {
		fn = fn[1:]
	}

	if fn == "" {
		http.Error(w, "No filename", 400)
		return
	}

	if strings.Contains(fn, "//") {
		http.Error(w,
			fmt.Sprintf("Too many slashes in the path name: %v", fn), 400)
		return
	}

	_, err = referenceBlob(fm.OID)
	if err != nil {
		log.Printf("Missing blob %v while restoring %v - restoring anyway",
			fm.OID, fn)
	}

	exp := getExpiration(req.Header)
	if exp == -1 {
		exp = getExpiration(fm.Headers)
		if exp > 0 && exp < 60*60*24*30 {
			exp = int(fm.Modified.Add(time.Second * time.Duration(exp)).Unix())
		}
	}

	if exp < 0 {
		log.Printf("Attempt to restore expired file: %v", fn)
		w.WriteHeader(201)
		return
	}

	force := false
	err = maybeStoreMeta(fn, fm, exp, force)
	switch err {
	case errExists:
		http.Error(w, err.Error(), 409)
		return
	case nil:
	default:
		log.Printf("Error storing file meta of %v -> %v: %v",
			fn, fm.OID, err)
		http.Error(w,
			fmt.Sprintf("Error recording file meta: %v", err), 500)
		return
	}

	log.Printf("Restored %v -> %v (exp=%v)", fn, fm.OID, exp)

	w.WriteHeader(201)
}

func loadBackupHashes(oid string) (*hashset.Hashset, int, error) {
	rv := &hashset.Hashset{}

	r := blobReader(oid)
	defer r.Close()
	gz, err := gzip.NewReader(r)
	if err != nil {
		return nil, 0, err
	}
	defer gz.Close()

	d := json.NewDecoder(gz)

	hashlen := getHash().Size()

	visited := 0
	for {
		ob := struct {
			Meta struct {
				OID   string
				Older []struct {
					OID string
				}
			}
		}{}

		err := d.Decode(&ob)
		switch err {
		case nil:
			oid, err := hex.DecodeString(ob.Meta.OID)
			if err != nil {
				return nil, visited, err
			}
			if len(oid) != hashlen {
				log.Printf("Invalid hash from %#v", ob)
				continue
			}
			rv.Add(oid)
			visited++
			for _, obs := range ob.Meta.Older {
				oid, err = hex.DecodeString(obs.OID)
				if err != nil {
					return nil, visited, err
				}
				rv.Add(oid)
				visited++
			}
		case io.EOF:
			return rv, visited, nil
		default:
			return nil, visited, err
		}
	}
}

func loadExistingHashes() (*hashset.Hashset, error) {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil && !gomemcached.IsNotFound(err) {
		return nil, err
	}

	oids := make(chan string)
	hsch := make(chan *hashset.Hashset)
	visitch := make(chan int)
	errch := make(chan error)

	wg := sync.WaitGroup{}

	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for o := range oids {
				h, v, e := loadBackupHashes(o)
				if e == nil {
					hsch <- h
				} else {
					errch <- e
				}
				visitch <- v
			}
		}()
	}
	go func() {
		wg.Wait()
		close(hsch)
		close(visitch)
		close(errch)
	}()

	go func() {
		for _, i := range b.Backups {
			log.Printf("Loading backups from %v / %v", i.Oid, i.Fn)
			oids <- i.Oid
		}
		close(oids)
	}()

	visited := 0
	hs := &hashset.Hashset{}
	for {
		// Done getting all the things
		if hsch == nil && visitch == nil && errch == nil {
			break
		}
		select {
		case v, ok := <-visitch:
			visited += v
			if !ok {
				visitch = nil
			}
		case e, ok := <-errch:
			err = e
			if !ok {
				errch = nil
			}
		case h, ok := <-hsch:
			if ok {
				log.Printf("Got %v hashes from a backup",
					h.Len())
				hs.AddAll(h)
			} else {
				hsch = nil
			}
		}
	}

	log.Printf("Visited %v obs, kept %v", visited, hs.Len())

	return hs, err
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/gorilla/mux"
	"github.com/unixvoid/glogger"
	"golang.org/x/crypto/sha3"
	"gopkg.in/gcfg.v1"
	"gopkg.in/redis.v3"
)

type Config struct {
	Beacon struct {
		Port            int
		TokenSize       int
		TokenDictionary string
		Loglevel        string
	}
	SSL struct {
		UseTLS     bool
		ServerCert string
		ServerKey  string
	}
	Redis struct {
		Host string
		Port int
	}
}

var (
	config = Config{}
)

func main() {
	err := gcfg.ReadFileInto(&config, "config.gcfg")
	if err != nil {
		fmt.Printf("Could not load config.gcfg, error: %s\n", err)
		return
	}

	// init logger
	if config.Beacon.Loglevel == "debug" {
		glogger.LogInit(os.Stdout, os.Stdout, os.Stdout, os.Stderr)
	} else if config.Beacon.Loglevel == "cluster" {
		glogger.LogInit(os.Stdout, os.Stdout, ioutil.Discard, os.Stderr)
	} else if config.Beacon.Loglevel == "info" {
		glogger.LogInit(os.Stdout, ioutil.Discard, ioutil.Discard, os.Stderr)
	} else {
		glogger.LogInit(ioutil.Discard, ioutil.Discard, ioutil.Discard, os.Stderr)
	}

	redisaddr := fmt.Sprint(config.Redis.Host, ":", config.Redis.Port)
	bitport := fmt.Sprint(":", config.Beacon.Port)
	glogger.Info.Println("beacon running on", config.Beacon.Port)
	glogger.Info.Println("link to redis on", redisaddr)
	// initialize redis connection
	client := redis.NewClient(&redis.Options{
		Addr:     redisaddr,
		Password: "",
		DB:       0,
	})

	// all handlers. lookin funcy casue i have to pass redis handler
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/provision", func(w http.ResponseWriter, r *http.Request) {
		provision(w, r, client, "tmp")
	}).Methods("POST")
	router.HandleFunc("/update", func(w http.ResponseWriter, r *http.Request) {
		update(w, r, client, "tmp")
	}).Methods("POST")
	router.HandleFunc("/remove", func(w http.ResponseWriter, r *http.Request) {
		remove(w, r, client, "tmp")
	}).Methods("POST")
	router.HandleFunc("/{fdata}", func(w http.ResponseWriter, r *http.Request) {
		handlerdynamic(w, r, client)
	}).Methods("GET")
	if config.SSL.UseTLS {
		log.Fatal(http.ListenAndServeTLS(bitport, config.SSL.ServerCert, config.SSL.ServerKey, router))
	} else {
		log.Fatal(http.ListenAndServe(bitport, router))
	}
}

func handlerdynamic(w http.ResponseWriter, r *http.Request, client *redis.Client) {
	vars := mux.Vars(r)
	fdata := vars["fdata"]

	// hash the token that is passed
	clientIdHash := sha3.Sum512([]byte(fdata))

	val, err := client.Get(fmt.Sprintf("ip:%x", clientIdHash)).Result()
	if err != nil {
		log.Printf("data does not exist")
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w, "token not found")
	} else {
		//log.Printf("data exists")
		fmt.Fprintf(w, "%s", val)
	}
}

func provision(w http.ResponseWriter, r *http.Request, client *redis.Client, state string) {
	// get file POST from index
	r.ParseForm()
	clientId := strings.TrimSpace(r.FormValue("id"))

	// sha3:512 hash the id
	clientIdHash := sha3.Sum512([]byte(clientId))

	// check if the id exists (sec:<clientId>)
	_, err := client.Get(fmt.Sprintf("sec:%x", clientIdHash)).Result()
	if err != redis.Nil {
		fmt.Println("DEBUG :: COLLISION")
		w.WriteHeader(http.StatusBadRequest)
		return
	} else {
		// generate token, store hashed token in db
		token := randStr(config.Beacon.TokenSize)
		tokenHash := sha3.Sum512([]byte(token))

		// return token to client
		w.Header().Set("token", token)
		fmt.Fprintf(w, "%s", token)

		// done with client, rest is server side
		// sec:<hashed clientId> : hashed password
		client.Set(fmt.Sprintf("sec:%x", clientIdHash), fmt.Sprintf("%x", tokenHash), 0).Err()

		// if temp objects, set an expire link on them
		if strings.EqualFold(state, "tmp") {
		}
	}
}

func update(w http.ResponseWriter, r *http.Request, client *redis.Client, state string) {
	// get file POST from index
	r.ParseForm()
	clientId := strings.TrimSpace(r.FormValue("id"))
	clientSec := strings.TrimSpace(r.FormValue("sec"))
	clientAddress := strings.TrimSpace(r.FormValue("address"))

	// sha3:512 hash the id and sec
	clientIdHash := sha3.Sum512([]byte(clientId))
	clientSecHash := sha3.Sum512([]byte(clientSec))

	// check if id exists
	storedSecHash, err := client.Get(fmt.Sprintf("sec:%x", clientIdHash)).Result()
	if err != redis.Nil {
		// id exists, make sure clientSecHash is the same as the stored version
		if fmt.Sprintf("%x", clientSecHash) == storedSecHash {
			// client is authed, update clientAddress
			w.WriteHeader(http.StatusOK)
			client.Set(fmt.Sprintf("ip:%x", clientIdHash), clientAddress, 0).Err()
		} else {
			// client auth failed
			w.WriteHeader(http.StatusForbidden)
		}
	} else {
		w.WriteHeader(http.StatusBadRequest)
		// id does not exist
	}
}

func remove(w http.ResponseWriter, r *http.Request, client *redis.Client, state string) {
	// get file POST from index
	r.ParseForm()
	clientId := strings.TrimSpace(r.FormValue("id"))
	clientSec := strings.TrimSpace(r.FormValue("sec"))

	// sha3:512 hash the id and sec
	clientIdHash := sha3.Sum512([]byte(clientId))
	clientSecHash := sha3.Sum512([]byte(clientSec))

	// check if id exists
	storedSecHash, err := client.Get(fmt.Sprintf("sec:%x", clientIdHash)).Result()
	if err != redis.Nil {
		// id exists, make sure clientSecHash is the same as the stored version
		if fmt.Sprintf("%x", clientSecHash) == storedSecHash {
			// client is authed
			w.WriteHeader(http.StatusOK)
			client.Del(fmt.Sprintf("ip:%x", clientIdHash))
			client.Del(fmt.Sprintf("sec:%x", clientIdHash))
		} else {
			// client auth failed
			w.WriteHeader(http.StatusForbidden)
		}
	} else {
		w.WriteHeader(http.StatusBadRequest)
		// id does not exist
	}
}

func randStr(strSize int) string {
	dictionary := config.Beacon.TokenDictionary

	var bytes = make([]byte, strSize)
	rand.Read(bytes)
	for k, v := range bytes {
		bytes[k] = dictionary[v%byte(len(dictionary))]
	}

	return string(bytes)
}
<commit_msg>Add SSL settings<commit_after>package main

import (
	"crypto/rand"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/gorilla/mux"
	"github.com/unixvoid/glogger"
	"golang.org/x/crypto/sha3"
	"gopkg.in/gcfg.v1"
	"gopkg.in/redis.v3"
)

type Config struct {
	Beacon struct {
		Port            int
		TokenSize       int
		TokenDictionary string
		Loglevel        string
	}
	SSL struct {
		UseTLS     bool
		ServerCert string
		ServerKey  string
	}
	Redis struct {
		Host string
		Port int
	}
}

var (
	config = Config{}
)

func main() {
	err := gcfg.ReadFileInto(&config, "config.gcfg")
	if err != nil {
		fmt.Printf("Could not load config.gcfg, error: %s\n", err)
		return
	}

	// init logger
	if config.Beacon.Loglevel == "debug" {
		glogger.LogInit(os.Stdout, os.Stdout, os.Stdout, os.Stderr)
	} else if config.Beacon.Loglevel == "cluster" {
		glogger.LogInit(os.Stdout, os.Stdout, ioutil.Discard, os.Stderr)
	} else if config.Beacon.Loglevel == "info" {
		glogger.LogInit(os.Stdout, ioutil.Discard, ioutil.Discard, os.Stderr)
	} else {
		glogger.LogInit(ioutil.Discard, ioutil.Discard, ioutil.Discard, os.Stderr)
	}

	// TODO error checking on redis instance (ping)
	redisaddr := fmt.Sprint(config.Redis.Host, ":", config.Redis.Port)
	bitport := fmt.Sprint(":", config.Beacon.Port)
	glogger.Info.Println("link to redis on", redisaddr)
	// initialize redis connection
	client := redis.NewClient(&redis.Options{
		Addr:     redisaddr,
		Password: "",
		DB:       0,
	})

	// router routes/handlers
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/provision", func(w http.ResponseWriter, r *http.Request) {
		provision(w, r, client, "tmp")
	}).Methods("POST")
	router.HandleFunc("/update", func(w http.ResponseWriter, r *http.Request) {
		update(w, r, client, "tmp")
	}).Methods("POST")
	router.HandleFunc("/remove", func(w http.ResponseWriter, r *http.Request) {
		remove(w, r, client, "tmp")
	}).Methods("POST")
	router.HandleFunc("/{fdata}", func(w http.ResponseWriter, r *http.Request) {
		handlerdynamic(w, r, client)
	}).Methods("GET")

	if config.SSL.UseTLS {
		tlsConfig := &tls.Config{
			MinVersion: tls.VersionTLS12,
			MaxVersion: tls.VersionTLS12,
			CipherSuites: []uint16{
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
				tls.TLS_RSA_WITH_AES_128_CBC_SHA,
				tls.TLS_RSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
				tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
			},
			PreferServerCipherSuites: true,
			ClientSessionCache:       tls.NewLRUClientSessionCache(128),
		}
		glogger.Info.Println("beacon running https on", config.Beacon.Port)
		tlsServer := &http.Server{Addr: fmt.Sprintf(":%d", config.Beacon.Port), Handler: router, TLSConfig: tlsConfig}
		log.Fatal(tlsServer.ListenAndServeTLS(config.SSL.ServerCert, config.SSL.ServerKey))
	} else {
		glogger.Info.Println("beacon running http on", config.Beacon.Port)
		log.Fatal(http.ListenAndServe(bitport, router))
	}
}

func handlerdynamic(w http.ResponseWriter, r *http.Request, client *redis.Client) {
	vars := mux.Vars(r)
	fdata := vars["fdata"]

	// hash the token that is passed
	clientIdHash := sha3.Sum512([]byte(fdata))

	val, err := client.Get(fmt.Sprintf("ip:%x", clientIdHash)).Result()
	if err != nil {
		glogger.Debug.Printf("data does not exist")
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w, "token not found")
	} else {
		//log.Printf("data exists")
		fmt.Fprintf(w, "%s", val)
	}
}

func provision(w http.ResponseWriter, r *http.Request, client *redis.Client, state string) {
	// get file POST from index
	r.ParseForm()
	clientId := strings.TrimSpace(r.FormValue("id"))

	// sha3:512 hash the id
	clientIdHash := sha3.Sum512([]byte(clientId))

	// check if the id exists (sec:<clientId>)
	_, err := client.Get(fmt.Sprintf("sec:%x", clientIdHash)).Result()
	if err != redis.Nil {
		fmt.Println("DEBUG :: COLLISION")
		w.WriteHeader(http.StatusBadRequest)
		return
	} else {
		// generate token, store hashed token in db
		token := randStr(config.Beacon.TokenSize)
		tokenHash := sha3.Sum512([]byte(token))

		// return token to client
		w.Header().Set("token", token)
		fmt.Fprintf(w, "%s", token)

		// done with client, rest is server side
		// sec:<hashed clientId> : hashed password
		client.Set(fmt.Sprintf("sec:%x", clientIdHash), fmt.Sprintf("%x", tokenHash), 0).Err()

		// if temp objects, set an expire link on them
		if strings.EqualFold(state, "tmp") {
		}
	}
}

func update(w http.ResponseWriter, r *http.Request, client *redis.Client, state string) {
	// get file POST from index
	r.ParseForm()
	clientId := strings.TrimSpace(r.FormValue("id"))
	clientSec := strings.TrimSpace(r.FormValue("sec"))
	clientAddress := strings.TrimSpace(r.FormValue("address"))

	// sha3:512 hash the id and sec
	clientIdHash := sha3.Sum512([]byte(clientId))
	clientSecHash := sha3.Sum512([]byte(clientSec))

	// check if id exists
	storedSecHash, err := client.Get(fmt.Sprintf("sec:%x", clientIdHash)).Result()
	if err != redis.Nil {
		// id exists, make sure clientSecHash is the same as the stored version
		if fmt.Sprintf("%x", clientSecHash) == storedSecHash {
			// client is authed, update clientAddress
			w.WriteHeader(http.StatusOK)
			client.Set(fmt.Sprintf("ip:%x", clientIdHash), clientAddress, 0).Err()
		} else {
			// client auth failed
			w.WriteHeader(http.StatusForbidden)
		}
	} else {
		w.WriteHeader(http.StatusBadRequest)
		// id does not exist
	}
}

func remove(w http.ResponseWriter, r *http.Request, client *redis.Client, state string) {
	// get file POST from index
	r.ParseForm()
	clientId := strings.TrimSpace(r.FormValue("id"))
	clientSec := strings.TrimSpace(r.FormValue("sec"))

	// sha3:512 hash the id and sec
	clientIdHash := sha3.Sum512([]byte(clientId))
	clientSecHash := sha3.Sum512([]byte(clientSec))

	// check if id exists
	storedSecHash, err := client.Get(fmt.Sprintf("sec:%x", clientIdHash)).Result()
	if err != redis.Nil {
		// id exists, make sure clientSecHash is the same as the stored version
		if fmt.Sprintf("%x", clientSecHash) == storedSecHash {
			// client is authed
			w.WriteHeader(http.StatusOK)
			client.Del(fmt.Sprintf("ip:%x", clientIdHash))
			client.Del(fmt.Sprintf("sec:%x", clientIdHash))
		} else {
			// client auth failed
			w.WriteHeader(http.StatusForbidden)
		}
	} else {
		w.WriteHeader(http.StatusBadRequest)
		// id does not exist
	}
}

func randStr(strSize int) string {
	dictionary := config.Beacon.TokenDictionary

	var bytes = make([]byte, strSize)
	rand.Read(bytes)
	for k, v := range bytes {
		bytes[k] = dictionary[v%byte(len(dictionary))]
	}

	return string(bytes)
}
<|endoftext|>