<commit_before>package main

import (
	"github.com/byrnedo/dockdash/dockerClient"
	goDocker "github.com/fsouza/go-dockerclient"
	ui "github.com/gizak/termui"
	"io"
	"io/ioutil"
	"log"
	"os"
	"sort"
	"strings"
)

type DockerInfoType int

const (
	ImageInfo DockerInfoType = iota
	PortInfo
	VolumesInfo
	TimeInfo
)

const MaxContainers = 1000
const MaxHorizPosition = 3

var (
	Trace   *log.Logger
	Info    *log.Logger
	Warning *log.Logger
	Error   *log.Logger
)

func mapValuesSorted(mapToSort map[string]*goDocker.Container) (sorted []*goDocker.Container) {

	sorted = make([]*goDocker.Container, len(mapToSort))
	var keys []string
	for k := range mapToSort {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// To perform the opertion you want
	count := 0
	for _, k := range keys {
		sorted[count] = mapToSort[k]
		count++
	}
	return
}

func InitLog(
	traceHandle io.Writer,
	infoHandle io.Writer,
	warningHandle io.Writer,
	errorHandle io.Writer) {

	Trace = log.New(traceHandle,
		"TRACE: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	Info = log.New(infoHandle,
		"INFO: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	Warning = log.New(warningHandle,
		"WARNING: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	Error = log.New(errorHandle,
		"ERROR: ",
		log.Ldate|log.Ltime|log.Lshortfile)
}

func createExitBar() (p *ui.Par) {
	p = ui.NewPar(":PRESS q TO QUIT")
	p.Height = 3
	p.TextFgColor = ui.ColorWhite
	p.Border.Label = "Text Box"
	p.Border.FgColor = ui.ColorCyan
	return
}

func createErrorBar() (p *ui.Par) {
	p = ui.NewPar("")
	p.Height = 3
	p.TextFgColor = ui.ColorRed
	p.HasBorder = false
	return
}

func createStatusBar() (p *ui.Par) {
	p = ui.NewPar("")
	p.Height = 3
	p.TextFgColor = ui.ColorGreen
	p.HasBorder = true
	return
}

func createCPUGauge() *ui.Gauge {
	cpuGauge := ui.NewGauge()
	cpuGauge.Percent = 50
	cpuGauge.Height = 3
	cpuGauge.Border.Label = "CPU Usage"
	cpuGauge.BarColor = ui.ColorRed
	cpuGauge.Border.FgColor = ui.ColorWhite
	cpuGauge.Border.LabelFgColor = ui.ColorCyan
	return cpuGauge
}

func createMemGauge() *ui.Gauge {
	memGauge := ui.NewGauge()
	memGauge.Percent = 50
	memGauge.Height = 3
	memGauge.Border.Label = "Mem. Usage"
	memGauge.BarColor = ui.ColorRed
	memGauge.Border.FgColor = ui.ColorWhite
	memGauge.Border.LabelFgColor = ui.ColorCyan
	return memGauge
}

func createContainerList() *ui.List {
	list := ui.NewList()
	list.ItemFgColor = ui.ColorYellow
	list.HasBorder = true
	return list
}

func createDockerLineChart() *ui.LineChart {
	lc := ui.NewLineChart()
	lc.Border.Label = "Container Numbers"
	//lc.Data = sinps
	lc.Height = 10
	lc.AxesColor = ui.ColorWhite
	lc.LineColor = ui.ColorRed | ui.AttrBold
	lc.Mode = "line"
	return lc
}

func getNameAndInfoOfContainers(containers map[string]*goDocker.Container, offset int, infoType DockerInfoType) ([]string, []string) {
	if offset > len(containers) {
		offset = len(containers) - 1
	}

	numContainersSubset := len(containers) - offset

	names := make([]string, numContainersSubset)
	info := make([]string, numContainersSubset)

	containersSorted := mapValuesSorted(containers)
	for index, cont := range containersSorted {
		if index < offset {
			continue
		}

		names[index-offset] = strings.TrimLeft(cont.Name, "/")
		switch infoType {
		case ImageInfo:
			info[index-offset] = strings.Replace(cont.Config.Image, "dockerregistry.pagero.local", "d.p.l", 1)
		case PortInfo:
			portStr := ""
			for intPort, extHostPortList := range cont.NetworkSettings.Ports {
				for _, extHostPort := range extHostPortList {
					portStr = intPort.Port() + "->" + extHostPort.HostIP + ":" + extHostPort.HostPort + ","
				}
			}
			info[index-offset] = portStr
		case VolumesInfo:
		case TimeInfo:
		default:
			Error.Println("Unhandled info type", infoType)
		}
	}
	return names, info
}

func updateContainerList(rightList *ui.List, leftList *ui.List, containers map[string]*goDocker.Container, offset int, horizPosition DockerInfoType) {
	names, info := getNameAndInfoOfContainers(containers, offset, horizPosition)
	rightList.Height = len(containers) + 2
	leftList.Height = len(containers) + 2
	rightList.Items = names
	leftList.Items = info
}

/*
 *func updateStatisticsRoutines(cl *dockerClient.DockerClient, event *goDocker.APIEvents, deadContainerChan chan<- string, startedContainerChan chan<- string, statsChan chan *goDocker.Stats, errChan chan<- string) {
 *    switch event.Status {
 *    case "die":
 *        go stopGettingStats(cl, event.ID, errChan)
 *    case "start":
 *        go startGettingStats(cl, event.ID, statsChan, errChan)
 *    }
 *}
 *
 *func startGettingStats(cl *dockerClient.DockerClient, id string, statsChan chan *goDocker.Stats, errChan chan<- string) {
 *    if err := cl.Stats(goDocker.StatsOptions{id, statsChan, true, nil, 0}); err != nil {
 *        errChan <- err.Error()
 *    }
 *}
 *
 *func stopGettingStats(cl *dockerClient.DockerClient, id string, errChan chan<- string) {
 *
 *}
 */

func main() {
	logPath := "/tmp/dockdash.log"
	file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		panic("Failed to open log file " + logPath + ":" + err.Error())
	}

	InitLog(ioutil.Discard, file, file, file)

	docker, err := dockerClient.NewDockerClient()
	if err != nil {
		panic(err)
	}

	err = ui.Init()
	if err != nil {
		panic(err)
	}

	defer ui.Close()

	exitBar := createExitBar()
	errorBar := createErrorBar()
	statusBar := createStatusBar()

	containerListLeft := createContainerList()
	containerListLeft.Border.Label = "Name"
	containerListRight := createContainerList()
	containerListRight.Border.Label = "Image"

	ui.Body.AddRows(
		ui.NewRow(
			ui.NewCol(12, 0, exitBar),
		),
		ui.NewRow(
			ui.NewCol(12, 0, errorBar),
		),
		ui.NewRow(
			ui.NewCol(12, 0, statusBar),
		),
		ui.NewRow(
			ui.NewCol(3, 0, containerListLeft),
			ui.NewCol(9, 0, containerListRight),
		),
	)

	// calculate layout
	ui.Body.Align()

	drawChan := make(chan bool)
	/*
	 *errChan := make(chan string, 10)
	 */
	dockerEventChan := make(chan *goDocker.APIEvents, 10)
	containersChan := make(chan map[string]*goDocker.Container, 10)
	listStartOffsetChan := make(chan int)
	horizPositionChan := make(chan int)
	maxOffsetChan := make(chan int)
	deadContainerChan := make(chan string, 10)
	startedContainerChan := make(chan string, 10)
	doneChan := make(chan bool)
	evtChan := ui.EventCh()
	/*
	 *statsChan := make(chan *goDocker.Stats)
	 */

	err = docker.AddEventListener(dockerEventChan)
	if err != nil {
		panic("Failed to add event listener: " + err.Error())
	}

	defer func() {
		if err := docker.RemoveEventListener(dockerEventChan); err != nil {
			panic(err)
		}
	}()

	uiRoutine := func() {
		var horizPosition int = 0
		var offset int = 0
		var maxOffset int = 0
		for {
			select {
			case e := <-evtChan:
				Info.Println("Got ui event:", e)
				if e.Type == ui.EventKey {
					switch e.Ch {
					case 'q':
						doneChan <- true
					case 0:
						switch e.Key {
						case ui.KeyArrowLeft:
							if horizPosition > 0 {
								horizPosition--
							}
							horizPositionChan <- offset
						case ui.KeyArrowRight:
							if horizPosition < MaxHorizPosition {
								horizPosition++
							}
							horizPositionChan <- offset
						case ui.KeyArrowDown:
							if offset < maxOffset && offset < MaxContainers {
								offset++
							}
							listStartOffsetChan <- offset
							//shift the list down
						case ui.KeyArrowUp:
							if offset > 0 {
								offset--
							}
							listStartOffsetChan <- offset
							//shift the list up
						default:
							Info.Printf("Got unhandled key %d\n", e.Key)
						}
					}
				}
				if e.Type == ui.EventResize {
					ui.Body.Width = ui.TermWidth()
					ui.Body.Align()
				}
			case max := <-maxOffsetChan:
				maxOffset = max
			case _ = <-drawChan:
				Info.Println("Got draw event")
				ui.Render(ui.Body)
			}
		}
	}
	go uiRoutine()

	Info.Println("Sending initial draw signal")

	//handle container addition/removal
	Info.Println("Spinning off container change routine")
	containerChangeRoutine := func() {
		currentContainers := make(map[string]*goDocker.Container)
		for {
			select {
			case newContainerID := <-startedContainerChan:
				Info.Println("Got new container event")
				cont, err := docker.InspectContainer(newContainerID)
				if err != nil {
					Error.Println("Failed to inspect new container", newContainerID, ":", err)
					continue
				}
				currentContainers[cont.ID] = cont
				maxOffsetChan <- len(currentContainers) - 1
				containersChan <- currentContainers
			case removedContainerID := <-deadContainerChan:
				Info.Println("Got dead container event")
				delete(currentContainers, removedContainerID)
				maxOffsetChan <- len(currentContainers) - 1
				containersChan <- currentContainers
			}
		}
	}
	go containerChangeRoutine()

	Info.Println("Spinning off update widgets routine")
	updateWidgets := func() {
		lastContainersList := make(map[string]*goDocker.Container)
		offset := 0
		horizPosition := 0
		for {
			select {
			case hp := <-horizPositionChan:
				horizPosition = hp
				Info.Println("Got changed horiz position", horizPosition)
			case containers := <-containersChan:
				Info.Println("Got containers changed event")
				updateContainerList(containerListLeft, containerListRight, containers, offset, DockerInfoType(horizPosition))
				lastContainersList = containers
				drawChan <- true
			case offset = <-listStartOffsetChan:
				Info.Println("Got list offset of", offset)
				updateContainerList(containerListLeft, containerListRight, lastContainersList, offset, DockerInfoType(horizPosition))
				drawChan <- true
			}
		}
	}
	go updateWidgets()

	dockerEventRouting := func() {
		for {
			select {
			case e := <-dockerEventChan:
				switch e.Status {
				case "start":
					startedContainerChan <- e.ID
				case "die":
					deadContainerChan <- e.ID
				}
			}
		}
	}

	go dockerEventRouting()

	//setup initial containers
	containers, _ := docker.ListContainers(goDocker.ListContainersOptions{})
	drawChan <- true
	Info.Println("Listing intial", len(containers), "containers as started")
	for _, cont := range containers {
		startedContainerChan <- cont.ID
	}

	<-doneChan

}
<commit_msg>Now shows volumes and created time<commit_after>package main

import (
	"github.com/byrnedo/dockdash/dockerClient"
	goDocker "github.com/fsouza/go-dockerclient"
	ui "github.com/gizak/termui"
	"io"
	"io/ioutil"
	"log"
	"os"
	"sort"
	"strings"
)

type DockerInfoType int

const (
	ImageInfo DockerInfoType = iota
	PortInfo
	VolumesInfo
	TimeInfo
)

var InfoHeaders map[DockerInfoType]string = map[DockerInfoType]string{
	ImageInfo:   "Images",
	PortInfo:    "Ports",
	VolumesInfo: "Volumes",
	TimeInfo:    "Created At",
}

const MaxContainers = 1000
const MaxHorizPosition = int(TimeInfo)

var (
	Trace   *log.Logger
	Info    *log.Logger
	Warning *log.Logger
	Error   *log.Logger
)

func mapValuesSorted(mapToSort map[string]*goDocker.Container) (sorted []*goDocker.Container) {

	sorted = make([]*goDocker.Container, len(mapToSort))
	var keys []string
	for k := range mapToSort {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// To perform the opertion you want
	count := 0
	for _, k := range keys {
		sorted[count] = mapToSort[k]
		count++
	}
	return
}

func InitLog(
	traceHandle io.Writer,
	infoHandle io.Writer,
	warningHandle io.Writer,
	errorHandle io.Writer) {

	Trace = log.New(traceHandle,
		"TRACE: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	Info = log.New(infoHandle,
		"INFO: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	Warning = log.New(warningHandle,
		"WARNING: ",
		log.Ldate|log.Ltime|log.Lshortfile)

	Error = log.New(errorHandle,
		"ERROR: ",
		log.Ldate|log.Ltime|log.Lshortfile)
}

func createExitBar() (p *ui.Par) {
	p = ui.NewPar(":PRESS q TO QUIT")
	p.Height = 3
	p.TextFgColor = ui.ColorWhite
	p.Border.Label = "Text Box"
	p.Border.FgColor = ui.ColorCyan
	return
}

func createErrorBar() (p *ui.Par) {
	p = ui.NewPar("")
	p.Height = 3
	p.TextFgColor = ui.ColorRed
	p.HasBorder = false
	return
}

func createStatusBar() (p *ui.Par) {
	p = ui.NewPar("")
	p.Height = 3
	p.TextFgColor = ui.ColorGreen
	p.HasBorder = true
	return
}

func createCPUGauge() *ui.Gauge {
	cpuGauge := ui.NewGauge()
	cpuGauge.Percent = 50
	cpuGauge.Height = 3
	cpuGauge.Border.Label = "CPU Usage"
	cpuGauge.BarColor = ui.ColorRed
	cpuGauge.Border.FgColor = ui.ColorWhite
	cpuGauge.Border.LabelFgColor = ui.ColorCyan
	return cpuGauge
}

func createMemGauge() *ui.Gauge {
	memGauge := ui.NewGauge()
	memGauge.Percent = 50
	memGauge.Height = 3
	memGauge.Border.Label = "Mem. Usage"
	memGauge.BarColor = ui.ColorRed
	memGauge.Border.FgColor = ui.ColorWhite
	memGauge.Border.LabelFgColor = ui.ColorCyan
	return memGauge
}

func createContainerList() *ui.List {
	list := ui.NewList()
	list.ItemFgColor = ui.ColorYellow
	list.HasBorder = true
	return list
}

func createDockerLineChart() *ui.LineChart {
	lc := ui.NewLineChart()
	lc.Border.Label = "Container Numbers"
	//lc.Data = sinps
	lc.Height = 10
	lc.AxesColor = ui.ColorWhite
	lc.LineColor = ui.ColorRed | ui.AttrBold
	lc.Mode = "line"
	return lc
}

func createPortsString(ports map[goDocker.Port][]goDocker.PortBinding) (portsStr string) {

	for intPort, extHostPortList := range ports {
		if len(extHostPortList) == 0 {
			portsStr += intPort.Port() + "->N/A,"
		}
		for _, extHostPort := range extHostPortList {
			portsStr += intPort.Port() + "->" + extHostPort.HostIP + ":" + extHostPort.HostPort + ","
		}
	}
	return strings.TrimLeft(portsStr, ",")
}

func getNameAndInfoOfContainers(containers map[string]*goDocker.Container, offset int, infoType DockerInfoType) ([]string, []string) {
	if offset > len(containers) {
		offset = len(containers) - 1
	}

	numContainersSubset := len(containers) - offset

	names := make([]string, numContainersSubset)
	info := make([]string, numContainersSubset)

	containersSorted := mapValuesSorted(containers)
	for index, cont := range containersSorted {
		if index < offset {
			continue
		}

		names[index-offset] = strings.TrimLeft(cont.Name, "/")
		switch infoType {
		case ImageInfo:
			info[index-offset] = strings.Replace(cont.Config.Image, "dockerregistry.pagero.local", "d.p.l", 1)
		case PortInfo:
			Info.Println("Creating list of", len(cont.NetworkSettings.Ports), "ports")
			info[index-offset] = createPortsString(cont.NetworkSettings.Ports)
		case VolumesInfo:
			Info.Println("Creating list of", len(cont.Volumes), "volumes")
			volStr := ""
			for intVol, hostVol := range cont.Volumes {
				volStr += intVol + ":" + hostVol + ","
			}
			info[index-offset] = strings.TrimRight(volStr, ",")
		case TimeInfo:
			info[index-offset] = cont.Created.String()
		default:
			Error.Println("Unhandled info type", infoType)
		}
	}
	return names, info
}

func updateContainerList(rightList *ui.List, leftList *ui.List, containers map[string]*goDocker.Container, offset int, horizPosition DockerInfoType) {
	names, info := getNameAndInfoOfContainers(containers, offset, horizPosition)
	rightList.Height = len(containers) + 2
	leftList.Height = len(containers) + 2
	rightList.Items = names
	leftList.Border.Label = InfoHeaders[horizPosition]
	leftList.Items = info
}

/*
 *func updateStatisticsRoutines(cl *dockerClient.DockerClient, event *goDocker.APIEvents, deadContainerChan chan<- string, startedContainerChan chan<- string, statsChan chan *goDocker.Stats, errChan chan<- string) {
 *    switch event.Status {
 *    case "die":
 *        go stopGettingStats(cl, event.ID, errChan)
 *    case "start":
 *        go startGettingStats(cl, event.ID, statsChan, errChan)
 *    }
 *}
 *
 *func startGettingStats(cl *dockerClient.DockerClient, id string, statsChan chan *goDocker.Stats, errChan chan<- string) {
 *    if err := cl.Stats(goDocker.StatsOptions{id, statsChan, true, nil, 0}); err != nil {
 *        errChan <- err.Error()
 *    }
 *}
 *
 *func stopGettingStats(cl *dockerClient.DockerClient, id string, errChan chan<- string) {
 *
 *}
 */

func main() {
	logPath := "/tmp/dockdash.log"
	file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		panic("Failed to open log file " + logPath + ":" + err.Error())
	}

	InitLog(ioutil.Discard, file, file, file)

	docker, err := dockerClient.NewDockerClient()
	if err != nil {
		panic(err)
	}

	err = ui.Init()
	if err != nil {
		panic(err)
	}

	defer ui.Close()

	exitBar := createExitBar()
	errorBar := createErrorBar()
	statusBar := createStatusBar()

	containerListLeft := createContainerList()
	containerListLeft.Border.Label = "Name"
	containerListRight := createContainerList()
	containerListRight.Border.Label = "Image"

	ui.Body.AddRows(
		ui.NewRow(
			ui.NewCol(12, 0, exitBar),
		),
		ui.NewRow(
			ui.NewCol(12, 0, errorBar),
		),
		ui.NewRow(
			ui.NewCol(12, 0, statusBar),
		),
		ui.NewRow(
			ui.NewCol(3, 0, containerListLeft),
			ui.NewCol(9, 0, containerListRight),
		),
	)

	// calculate layout
	ui.Body.Align()

	drawChan := make(chan bool)
	/*
	 *errChan := make(chan string, 10)
	 */
	dockerEventChan := make(chan *goDocker.APIEvents, 10)
	containersChan := make(chan map[string]*goDocker.Container, 10)
	listStartOffsetChan := make(chan int)
	horizPositionChan := make(chan int)
	maxOffsetChan := make(chan int)
	deadContainerChan := make(chan string, 10)
	startedContainerChan := make(chan string, 10)
	doneChan := make(chan bool)
	evtChan := ui.EventCh()
	/*
	 *statsChan := make(chan *goDocker.Stats)
	 */

	err = docker.AddEventListener(dockerEventChan)
	if err != nil {
		panic("Failed to add event listener: " + err.Error())
	}

	defer func() {
		if err := docker.RemoveEventListener(dockerEventChan); err != nil {
			panic(err)
		}
	}()

	uiRoutine := func() {
		var horizPosition int = 0
		var offset int = 0
		var maxOffset int = 0
		for {
			select {
			case e := <-evtChan:
				Info.Println("Got ui event:", e)
				if e.Type == ui.EventKey {
					switch e.Ch {
					case 'q':
						doneChan <- true
					case 0:
						switch e.Key {
						case ui.KeyArrowLeft:
							if horizPosition > 0 {
								horizPosition--
							}
							horizPositionChan <- horizPosition
						case ui.KeyArrowRight:
							if horizPosition < MaxHorizPosition {
								horizPosition++
							}
							horizPositionChan <- horizPosition
						case ui.KeyArrowDown:
							if offset < maxOffset && offset < MaxContainers {
								offset++
							}
							listStartOffsetChan <- offset
							//shift the list down
						case ui.KeyArrowUp:
							if offset > 0 {
								offset--
							}
							listStartOffsetChan <- offset
							//shift the list up
						default:
							Info.Printf("Got unhandled key %d\n", e.Key)
						}
					}
				}
				if e.Type == ui.EventResize {
					ui.Body.Width = ui.TermWidth()
					ui.Body.Align()
				}
			case max := <-maxOffsetChan:
				maxOffset = max
			case _ = <-drawChan:
				Info.Println("Got draw event")
				ui.Render(ui.Body)
			}
		}
	}
	go uiRoutine()

	Info.Println("Sending initial draw signal")

	//handle container addition/removal
	Info.Println("Spinning off container change routine")
	containerChangeRoutine := func() {
		currentContainers := make(map[string]*goDocker.Container)
		for {
			select {
			case newContainerID := <-startedContainerChan:
				Info.Println("Got new container event")
				cont, err := docker.InspectContainer(newContainerID)
				if err != nil {
					Error.Println("Failed to inspect new container", newContainerID, ":", err)
					continue
				}
				currentContainers[cont.ID] = cont
				maxOffsetChan <- len(currentContainers) - 1
				containersChan <- currentContainers
			case removedContainerID := <-deadContainerChan:
				Info.Println("Got dead container event")
				delete(currentContainers, removedContainerID)
				maxOffsetChan <- len(currentContainers) - 1
				containersChan <- currentContainers
			}
		}
	}
	go containerChangeRoutine()

	Info.Println("Spinning off update widgets routine")
	updateWidgets := func() {
		lastContainersList := make(map[string]*goDocker.Container)
		offset := 0
		horizPosition := 0
		for {
			select {
			case hp := <-horizPositionChan:
				horizPosition = hp
				Info.Println("Got changed horiz position", horizPosition)
				updateContainerList(containerListLeft, containerListRight, lastContainersList, offset, DockerInfoType(horizPosition))
				drawChan <- true
			case containers := <-containersChan:
				Info.Println("Got containers changed event")
				updateContainerList(containerListLeft, containerListRight, containers, offset, DockerInfoType(horizPosition))
				lastContainersList = containers
				drawChan <- true
			case offset = <-listStartOffsetChan:
				Info.Println("Got list offset of", offset)
				updateContainerList(containerListLeft, containerListRight, lastContainersList, offset, DockerInfoType(horizPosition))
				drawChan <- true
			}
		}
	}
	go updateWidgets()

	dockerEventRouting := func() {
		for {
			select {
			case e := <-dockerEventChan:
				switch e.Status {
				case "start":
					startedContainerChan <- e.ID
				case "die":
					deadContainerChan <- e.ID
				}
			}
		}
	}

	go dockerEventRouting()

	//setup initial containers
	containers, _ := docker.ListContainers(goDocker.ListContainersOptions{})
	drawChan <- true
	Info.Println("Listing intial", len(containers), "containers as started")
	for _, cont := range containers {
		startedContainerChan <- cont.ID
	}

	<-doneChan

}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"net"
	"os"
	"runtime"
	"strings"
	"time"

	log "github.com/cihub/seelog"

	"github.com/mondough/phosphor/util"
	"github.com/mondough/phosphord/forwarder"
	"github.com/mondough/phosphord/transport"
)

const (
	UDP = "udp"
)

var (
	packetSize  = 512
	bindAddress = "0.0.0.0:7760"

	// forwarder defaults
	numForwarders = 20
	bufferSize    = 200

	// logLevel default set to info and above
	defaultLogLevel = "info"
	verboseLogLevel = "debug"

	// verbose logging mode
	verbose = false

	// NSQ settings
	nsqdTCPAddrs = util.StringArray{}
	nsqTopic     = "trace"
)

func init() {
	// Register flags
	flag.BoolVar(&verbose, "verbose", false, "enable verbose logging")

	flag.IntVar(&numForwarders, "num-forwarders", 20, "set the number of workers which buffer and forward traces")
	flag.IntVar(&bufferSize, "buffer-size", 200, "set the maximum number of traces buffered per worker before batch sending")

	flag.Var(&nsqdTCPAddrs, "nsqd-tcp-address", "nsqd TCP address (may be given multiple times)")
	flag.StringVar(&nsqTopic, "nsq-topic", "trace", "nsq topic to forward traces to")
}

func main() {
	flag.Parse()

	// Set up the logger, using the log level set by the environment
	initialiseLogger()
	defer log.Flush()

	log.Infof("Phosphor started at %v using %v CPUs", time.Now(), runtime.NumCPU())

	// Use ALL the CPUs
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Make a channel to pass around trace frames
	ch := make(chan []byte)

	// Initialise our transport
	tr, err := transport.NewNSQTransport(nsqTopic, nsqdTCPAddrs)
	if err != nil {
		log.Criticalf(err.Error())
		os.Exit(1)
	}

	// Fire up a number of forwarders to process inbound messages
	forwarder.Verbose = verbose
	forwarder.Start(ch, tr, numForwarders, bufferSize)

	// Bind and listen to UDP traffic
	if err := listen(ch); err != nil {
		log.Criticalf(err.Error())
		os.Exit(1)
	}
}

// listen on a UDP socket for trace frames
func listen(ch chan []byte) error {

	// Resolve bind address
	address, err := net.ResolveUDPAddr(UDP, bindAddress)
	if err != nil {
		log.Errorf("Failed to resolve address: %s", err.Error())
		return err
	}

	// Take the resolved address and attempt to listen on the UDP socket
	listener, err := net.ListenUDP(UDP, address)
	if err != nil {
		log.Errorf("ListenUDP error: %s", err.Error())
		return err
	}
	defer listener.Close()

	// Listen loop
	log.Infof("Listening on %s for UDP trace frames", address.String())
	for {
		message := make([]byte, packetSize)
		n, _, err := listener.ReadFrom(message)
		if err != nil {
			continue
		}
		buf := bytes.NewBuffer(message[0:n])
		// log.Infof("Packet received from %s: %s", remaddr, string(message[0:n]))

		// Attempt to push into our channel to be processed by a worker
		select {
		case ch <- buf.Bytes():
			// log.Infof("Wrote message to channel")
		default:
			// abort!
			// log.Infof("Dropped message")
		}
	}
}

func initialiseLogger() {

	// Attempt to pull log level from env, if not set to default level
	logLevel := os.Getenv("LOG_LEVEL")
	if logLevel == "" {
		logLevel = defaultLogLevel
	}

	// Overridden by verbose flag
	if verbose {
		logLevel = verboseLogLevel
	}

	// Build config for seelog
	logConfig := fmt.Sprintf(`<seelog minlevel="%s">`, logLevel)
	logConfig = strings.Join([]string{logConfig, `<outputs formatid="main"><console/></outputs><formats><format id="main" format="%Date %Time [%LEV] %Msg (%File %Line)%n"/></formats></seelog>`}, "")

	// Initialise the logger!
	logger, err := log.LoggerFromConfigAsBytes([]byte(logConfig))
	if err != nil {
		log.Errorf("Couldn't initialise new logger: %s", err)
	}
	log.ReplaceLogger(logger)
}
<commit_msg>Increase UDP packet size<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"net"
	"os"
	"runtime"
	"strings"
	"time"

	log "github.com/cihub/seelog"

	"github.com/mondough/phosphor/util"
	"github.com/mondough/phosphord/forwarder"
	"github.com/mondough/phosphord/transport"
)

const (
	UDP = "udp"
)

var (
	packetSize  = 65536 - 8 - 20 // 8-byte UDP header, 20-byte IP header
	bindAddress = "0.0.0.0:7760"

	// forwarder defaults
	numForwarders = 20
	bufferSize    = 200

	// logLevel default set to info and above
	defaultLogLevel = "info"
	verboseLogLevel = "debug"

	// verbose logging mode
	verbose = false

	// NSQ settings
	nsqdTCPAddrs = util.StringArray{}
	nsqTopic     = "trace"
)

func init() {
	// Register flags
	flag.BoolVar(&verbose, "verbose", false, "enable verbose logging")

	flag.IntVar(&numForwarders, "num-forwarders", 20, "set the number of workers which buffer and forward traces")
	flag.IntVar(&bufferSize, "buffer-size", 200, "set the maximum number of traces buffered per worker before batch sending")

	flag.Var(&nsqdTCPAddrs, "nsqd-tcp-address", "nsqd TCP address (may be given multiple times)")
	flag.StringVar(&nsqTopic, "nsq-topic", "trace", "nsq topic to forward traces to")
}

func main() {
	flag.Parse()

	// Set up the logger, using the log level set by the environment
	initialiseLogger()
	defer log.Flush()

	log.Infof("Phosphor started at %v using %v CPUs", time.Now(), runtime.NumCPU())

	// Use ALL the CPUs
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Make a channel to pass around trace frames
	ch := make(chan []byte)

	// Initialise our transport
	tr, err := transport.NewNSQTransport(nsqTopic, nsqdTCPAddrs)
	if err != nil {
		log.Criticalf(err.Error())
		os.Exit(1)
	}

	// Fire up a number of forwarders to process inbound messages
	forwarder.Verbose = verbose
	forwarder.Start(ch, tr, numForwarders, bufferSize)

	// Bind and listen to UDP traffic
	if err := listen(ch); err != nil {
		log.Criticalf(err.Error())
		os.Exit(1)
	}
}

// listen on a UDP socket for trace frames
func listen(ch chan []byte) error {

	// Resolve bind address
	address, err := net.ResolveUDPAddr(UDP, bindAddress)
	if err != nil {
		log.Errorf("Failed to resolve address: %s", err.Error())
		return err
	}

	// Take the resolved address and attempt to listen on the UDP socket
	listener, err := net.ListenUDP(UDP, address)
	if err != nil {
		log.Errorf("ListenUDP error: %s", err.Error())
		return err
	}
	defer listener.Close()

	// Listen loop
	log.Infof("Listening on %s for UDP trace frames", address.String())
	for {
		message := make([]byte, packetSize)
		n, _, err := listener.ReadFrom(message)
		if err != nil {
			continue
		}
		buf := bytes.NewBuffer(message[0:n])
		// log.Infof("Packet received from %s: %s", remaddr, string(message[0:n]))

		// Attempt to push into our channel to be processed by a worker
		select {
		case ch <- buf.Bytes():
			// log.Infof("Wrote message to channel")
		default:
			// abort!
			// log.Infof("Dropped message")
		}
	}
}

func initialiseLogger() {

	// Attempt to pull log level from env, if not set to default level
	logLevel := os.Getenv("LOG_LEVEL")
	if logLevel == "" {
		logLevel = defaultLogLevel
	}

	// Overridden by verbose flag
	if verbose {
		logLevel = verboseLogLevel
	}

	// Build config for seelog
	logConfig := fmt.Sprintf(`<seelog minlevel="%s">`, logLevel)
	logConfig = strings.Join([]string{logConfig, `<outputs formatid="main"><console/></outputs><formats><format id="main" format="%Date %Time [%LEV] %Msg (%File %Line)%n"/></formats></seelog>`}, "")

	// Initialise the logger!
	logger, err := log.LoggerFromConfigAsBytes([]byte(logConfig))
	if err != nil {
		log.Errorf("Couldn't initialise new logger: %s", err)
	}
	log.ReplaceLogger(logger)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
)

const (
	VERSION = "0.1.0"
)

var options struct {
	Path string
	Port int
}

var services []Service

func main() {
	options.Path = "./config"
	options.Port = 5000

	var err error
	services, err = readServices()

	if err != nil {
		fmt.Println("Error:", err.Error())
		os.Exit(1)
	}

	fmt.Printf("envd v%s\n", VERSION)
	fmt.Println("services detected:", len(services))
	fmt.Println("starting server on port", options.Port)

	startServer()
}
<commit_msg>Add options<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
)

const (
	VERSION = "0.1.0"
)

var options struct {
	Path string
	Port int
}

var services []Service

func main() {
	flag.StringVar(&options.Path, "c", "", "Path to config directory")
	flag.IntVar(&options.Port, "p", 3050, "Port to listen on")
	flag.Parse()

	if options.Path == "" {
		options.Path = "./config"
	}

	var err error
	services, err = readServices()

	if err != nil {
		fmt.Println("Error:", err.Error())
		os.Exit(1)
	}

	fmt.Printf("envd v%s\n", VERSION)
	fmt.Println("config path:", options.Path)
	fmt.Println("services detected:", len(services))
	fmt.Println("starting server on port", options.Port)

	startServer()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
)

type collectdMetric struct {
	Values          []float64
	Dstypes         []string
	Dsnames         []string
	Time            float64
	Interval        float64
	Host            string
	Plugin          string
	Plugin_instance string
	Type            string
	Type_instance   string
}

var (
	addr     = flag.String("listen-address", ":6060", "The address to listen on for HTTP requests.")
	lastPush = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Name: "collectd_last_push",
			Help: "Unixtime the collectd exporter was last pushed to.",
		},
	)
)

func metricName(m collectdMetric, dstype string, dsname string) string {
	result := "collectd"
	if m.Plugin != m.Type && !strings.HasPrefix(m.Type, m.Plugin+"_") {
		result += "_" + m.Plugin
	}
	result += "_" + m.Type
	if dsname != "value" {
		result += "_" + dsname
	}
	if dstype == "counter" {
		result += "_total"
	}
	return result
}

func metricHelp(m collectdMetric, dstype string, dsname string) string {
	return fmt.Sprintf("Collectd Metric Plugin: '%s' Type: '%s' Dstype: '%s' Dsname: '%s'",
		m.Plugin, m.Type, dstype, dsname)
}

type collectdSample struct {
	Name    string
	Labels  map[string]string
	Help    string
	Value   float64
	Gauge   bool
	Expires time.Time
}

type collectdSampleLabelset struct {
	Name           string
	Instance       string
	Type           string
	Plugin         string
	PluginInstance string
}

type CollectdCollector struct {
	samples map[collectdSampleLabelset]*collectdSample
	mu      *sync.Mutex
	ch      chan *collectdSample
}

func newCollectdCollector() *CollectdCollector {
	c := &CollectdCollector{
		ch:      make(chan *collectdSample, 0),
		mu:      &sync.Mutex{},
		samples: map[collectdSampleLabelset]*collectdSample{},
	}
	go c.processSamples()
	return c
}

func (c *CollectdCollector) collectdPost(w http.ResponseWriter, r *http.Request) {
	var postedMetrics []collectdMetric
	err := json.NewDecoder(r.Body).Decode(&postedMetrics)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	now := time.Now()
	lastPush.Set(float64(now.UnixNano()) / 1e9)
	for _, metric := range postedMetrics {
		for i, value := range metric.Values {
			name := metricName(metric, metric.Dstypes[i], metric.Dsnames[i])
			help := metricHelp(metric, metric.Dstypes[i], metric.Dsnames[i])
			labels := prometheus.Labels{}
			if metric.Plugin_instance != "" {
				labels[metric.Plugin] = metric.Plugin_instance
			}
			if metric.Type_instance != "" {
				if metric.Plugin_instance == "" {
					labels[metric.Plugin] = metric.Type_instance
				} else {
					labels["type"] = metric.Type_instance
				}
			}
			labels["instance"] = metric.Host
			c.ch <- &collectdSample{
				Name:    name,
				Labels:  labels,
				Help:    help,
				Value:   value,
				Gauge:   metric.Dstypes[i] != "counter",
				Expires: now.Add(time.Duration(metric.Interval) * time.Second * 2),
			}
		}
	}
}

func (c *CollectdCollector) processSamples() {
	ticker := time.NewTicker(time.Minute).C
	for {
		select {
		case sample := <-c.ch:
			labelset := &collectdSampleLabelset{
				Name: sample.Name,
			}
			for k, v := range sample.Labels {
				switch k {
				case "instance":
					labelset.Instance = v
				case "type":
					labelset.Type = v
				default:
					labelset.Plugin = k
					labelset.PluginInstance = v
				}
			}
			c.mu.Lock()
			c.samples[*labelset] = sample
			c.mu.Unlock()
		case <-ticker:
			// Garbage collect expired samples.
			now := time.Now()
			c.mu.Lock()
			for k, sample := range c.samples {
				if now.After(sample.Expires) {
					delete(c.samples, k)
				}
			}
			c.mu.Unlock()
		}
	}
}

// Implements Collector.
func (c CollectdCollector) Collect(ch chan<- prometheus.Metric) {
	ch <- lastPush
	c.mu.Lock()
	samples := c.samples
	c.mu.Unlock()
	now := time.Now()
	for _, sample := range samples {
		if now.After(sample.Expires) {
			continue
		}
		if sample.Gauge {
			gauge := prometheus.NewGauge(
				prometheus.GaugeOpts{
					Name:        sample.Name,
					Help:        sample.Help,
					ConstLabels: sample.Labels})
			gauge.Set(sample.Value)
			ch <- gauge
		} else {
			counter := prometheus.NewCounter(
				prometheus.CounterOpts{
					Name:        sample.Name,
					Help:        sample.Help,
					ConstLabels: sample.Labels})
			counter.Set(sample.Value)
			ch <- counter
		}
	}
}

// Implements Collector.
func (c CollectdCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- lastPush.Desc()
}

func main() {
	flag.Parse()
	http.Handle("/metrics", prometheus.Handler())
	c := newCollectdCollector()
	http.HandleFunc("/collectd-post", c.collectdPost)
	prometheus.MustRegister(c)
	http.ListenAndServe(*addr, nil)
}
<commit_msg>Rename metrics per suggestion<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
)

type collectdMetric struct {
	Values         []float64
	Dstypes        []string
	Dsnames        []string
	Time           float64
	Interval       float64
	Host           string
	Plugin         string
	PluginInstance string `json:"plugin_instance"`
	Type           string
	TypeInstance   string `json:"type_instance"`
}

var (
	addr     = flag.String("listen-address", ":6060", "The address to listen on for HTTP requests.")
	lastPush = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Name: "collectd_last_push",
			Help: "Unixtime the collectd exporter was last pushed to.",
		},
	)
)

func metricName(m collectdMetric, dstype string, dsname string) string {
	result := "collectd"
	if m.Plugin != m.Type && !strings.HasPrefix(m.Type, m.Plugin+"_") {
		result += "_" + m.Plugin
	}
	result += "_" + m.Type
	if dsname != "value" {
		result += "_" + dsname
	}
	if dstype == "counter" {
		result += "_total"
	}
	return result
}

func metricHelp(m collectdMetric, dstype string, dsname string) string {
	return fmt.Sprintf("Collectd Metric Plugin: '%s' Type: '%s' Dstype: '%s' Dsname: '%s'",
		m.Plugin, m.Type, dstype, dsname)
}

type collectdSample struct {
	Name    string
	Labels  map[string]string
	Help    string
	Value   float64
	Gauge   bool
	Expires time.Time
}

type collectdSampleLabelset struct {
	Name           string
	Instance       string
	Type           string
	Plugin         string
	PluginInstance string
}

type CollectdCollector struct {
	samples map[collectdSampleLabelset]*collectdSample
	mu      *sync.Mutex
	ch      chan *collectdSample
}

func newCollectdCollector() *CollectdCollector {
	c := &CollectdCollector{
		ch:      make(chan *collectdSample, 0),
		mu:      &sync.Mutex{},
		samples: map[collectdSampleLabelset]*collectdSample{},
	}
	go c.processSamples()
	return c
}

func (c *CollectdCollector) collectdPost(w http.ResponseWriter, r *http.Request) {
	var postedMetrics []collectdMetric
	err := json.NewDecoder(r.Body).Decode(&postedMetrics)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	now := time.Now()
	lastPush.Set(float64(now.UnixNano()) / 1e9)
	for _, metric := range postedMetrics {
		for i, value := range metric.Values {
			name := metricName(metric, metric.Dstypes[i], metric.Dsnames[i])
			help := metricHelp(metric, metric.Dstypes[i], metric.Dsnames[i])
			labels := prometheus.Labels{}
			if metric.PluginInstance != "" {
				labels[metric.Plugin] = metric.PluginInstance
			}
			if metric.TypeInstance != "" {
				if metric.PluginInstance == "" {
					labels[metric.Plugin] = metric.TypeInstance
				} else {
					labels["type"] = metric.TypeInstance
				}
			}
			labels["instance"] = metric.Host
			c.ch <- &collectdSample{
				Name:    name,
				Labels:  labels,
				Help:    help,
				Value:   value,
				Gauge:   metric.Dstypes[i] != "counter",
				Expires: now.Add(time.Duration(metric.Interval) * time.Second * 2),
			}
		}
	}
}

func (c *CollectdCollector) processSamples() {
	ticker := time.NewTicker(time.Minute).C
	for {
		select {
		case sample := <-c.ch:
			labelset := &collectdSampleLabelset{
				Name: sample.Name,
			}
			for k, v := range sample.Labels {
				switch k {
				case "instance":
					labelset.Instance = v
				case "type":
					labelset.Type = v
				default:
					labelset.Plugin = k
					labelset.PluginInstance = v
				}
			}
			c.mu.Lock()
			c.samples[*labelset] = sample
			c.mu.Unlock()
		case <-ticker:
			// Garbage collect expired samples.
			now := time.Now()
			c.mu.Lock()
			for k, sample := range c.samples {
				if now.After(sample.Expires) {
					delete(c.samples, k)
				}
			}
			c.mu.Unlock()
		}
	}
}

// Implements Collector.
func (c CollectdCollector) Collect(ch chan<- prometheus.Metric) {
	ch <- lastPush
	c.mu.Lock()
	samples := c.samples
	c.mu.Unlock()
	now := time.Now()
	for _, sample := range samples {
		if now.After(sample.Expires) {
			continue
		}
		if sample.Gauge {
			gauge := prometheus.NewGauge(
				prometheus.GaugeOpts{
					Name:        sample.Name,
					Help:        sample.Help,
					ConstLabels: sample.Labels})
			gauge.Set(sample.Value)
			ch <- gauge
		} else {
			counter := prometheus.NewCounter(
				prometheus.CounterOpts{
					Name:        sample.Name,
					Help:        sample.Help,
					ConstLabels: sample.Labels})
			counter.Set(sample.Value)
			ch <- counter
		}
	}
}

// Implements Collector.
func (c CollectdCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- lastPush.Desc()
}

func main() {
	flag.Parse()
	http.Handle("/metrics", prometheus.Handler())
	c := newCollectdCollector()
	http.HandleFunc("/collectd-post", c.collectdPost)
	prometheus.MustRegister(c)
	http.ListenAndServe(*addr, nil)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"encoding/gob"
	"encoding/xml"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/http/fcgi"
	"net/url"
	"path"
	"strings"
	"time"

	"code.google.com/p/cascadia"
	"github.com/PuerkitoBio/goquery"
	"github.com/bkaradzic/go-lz4"
	"github.com/bradfitz/gomemcache/memcache"
	"github.com/thatguystone/swan"
)

type article struct {
	FinalURL string
	Content  string
}

type tracking struct {
	ip    string
	cid   uint32
	url   string
	title string
}

type feedRequest struct {
	baseURL *url.URL
	t       tracking
}

const (
	googleFavicon = "https://www.google.com/s2/favicons?domain=%s&alt=feed"
)

var (
	runFcgi         = false
	httpPort        = 8080
	memcacheServers = ""

	errNoMc        = errors.New("memcache disabled")
	errInvalidPage = errors.New("could not find a feed on this page")

	linkAlt = cascadia.MustCompile(
		"link[rel=alternate][type=\"application/rss+xml\"][href], " +
			"link[rel=alternate][type=\"application/atom+xml\"][href]")

	mc *memcache.Client
)

func init() {
	flag.BoolVar(&runFcgi, "fcgi", false, "run as a fastcgi server")
	flag.IntVar(&httpPort, "httpPort", 8080, "run a debug server")
	flag.StringVar(&memcacheServers, "mcServers", "", "comma-separated list of memcache servers")
}

func main() {
	flag.Parse()
	httpDisableLocal()
	rand.Seed(time.Now().UnixNano())

	if memcacheServers != "" {
		mc = memcache.New(strings.Split(memcacheServers, ",")...)
	}

	http.HandleFunc("/", feedHandler)

	if runFcgi {
		fcgi.Serve(nil, nil)
	} else {
		http.ListenAndServe(fmt.Sprintf(":%d", httpPort), nil)
	}
}

func hitCache(key string) (*article, error) {
	if mc == nil {
		return nil, errNoMc
	}

	item, err := mc.Get(key)
	if err != nil {
		return nil, err
	}

	lzdc, err := lz4.Decode(nil, item.Value)
	if err != nil {
		return nil, err
	}

	var b bytes.Buffer
	var a *article

	dec := gob.NewDecoder(&b)
	b.Write(lzdc)
	err = dec.Decode(&a)

	return a, err
}

func cacheArticle(key string, a *article, expire int32) {
	if mc == nil {
		return
	}

	var b []byte
	if a != nil {
		var bf bytes.Buffer
		enc := gob.NewEncoder(&bf)
		err := enc.Encode(a)
		if err != nil {
			return
		}

		b, err = lz4.Encode(nil, bf.Bytes())
		if err != nil {
			return
		}
	}

	mc.Set(&memcache.Item{
		Key:        key,
		Value:      b,
		Expiration: expire,
	})
}

func getArticle(url string) *article {
	if url == "" {
		return nil
	}

	sum := sha1.Sum([]byte(url))
	key := "ohmyrss_" + base64.StdEncoding.EncodeToString(sum[:])

	art, err := hitCache(key)
	if err == nil {
		return art
	}

	sa, err := swan.FromURL(url)
	cacheTime := int32(60 * 3)
	if err == nil {
		cacheTime = 60 * 60 * 24 * 7
	}

	if sa != nil && sa.TopNode != nil {
		html, _ := sa.TopNode.Html()
		art = &article{
			FinalURL: sa.URL,
			Content:  strings.TrimSpace(html),
		}
	}

	cacheArticle(key, art, cacheTime)
	return art
}

func feedHandler(w http.ResponseWriter, req *http.Request) {
	req.Body.Close()

	feedURL := req.FormValue("url")
	if feedURL == "" {
		http.Error(w, "missing url parameter", http.StatusBadRequest)
		return
	}

	u, err := url.Parse(feedURL)
	if err != nil {
		http.Error(w, "invalid url", http.StatusBadRequest)
		return
	}

	// For schemeless, just add an HTTP and retry
	if u.Scheme != "http" && u.Scheme != "https" {
		u, err = url.Parse("http://" + feedURL)
		if err != nil {
			http.Error(w, "invalid url", http.StatusBadRequest)
			return
		}
	}

	fr := feedRequest{
		baseURL: u,
		t: tracking{
			ip:  httpGetRemoteIP(req),
			cid: rand.Uint32(),
		},
	}

	feed, redirectURL, err := handleFeed(fr)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if redirectURL != "" {
		req.URL.RawQuery = fmt.Sprintf("url=%s", redirectURL)
		http.Redirect(w, req, req.URL.String(), http.StatusMovedPermanently)
		return
	}

	w.Header().Add("Content-Type", "text/xml; charset=utf-8")
	w.Write([]byte(feed))
}

func handleFeed(fr feedRequest) (feed string, redirectURL string, err error) {
	body, err := httpGetURL(fr.baseURL)
	if err != nil {
		return
	}
	defer body.Close()

	in, err := ioutil.ReadAll(body)
	if err != nil {
		return
	}

	var rss Rss
	err = xml.Unmarshal(in, &rss)
	if err == nil {
		feed, err = handleRss(&rss, fr)
		return
	}

	var atom Atom
	err = xml.Unmarshal(in, &atom)
	if err == nil {
		feed, err = handleAtom(&atom, fr)
		return
	}

	redirectURL = checkLandingPage(fr.baseURL, string(in))
	if redirectURL != "" {
		err = nil
	} else {
		err = errInvalidPage
	}

	return
}

func handleRss(rss *Rss, fr feedRequest) (string, error) {
	ch := rss.Channel
	fr.t.title = ch.Title
	track(fr)

	if ch.Image == nil {
		ch.Image = &RssImage{
			Title: ch.Title,
			Link:  ch.Link,
		}
	}

	if ch.Image.Url == "" {
		ch.Image.Url = fmt.Sprintf(googleFavicon, fr.baseURL.Host)
	}

	for _, item := range ch.Items {
		a := getArticle(item.Link)

		// Don't modify if something went wrong
		if a == nil {
			continue
		}

		if a.FinalURL != "" {
			item.Link = a.FinalURL
		}

		if a.Content != "" {
			item.Description = a.Content
		}

		fr.t.title = item.Title
		fr.t.url = item.Link
		addTracking(&item.Description, fr)
	}

	return xmlEncode(rss)
}

func handleAtom(atom *Atom, fr feedRequest) (string, error) {
	fr.t.title = atom.Title
	track(fr)

	if atom.Icon == "" {
		atom.Icon = fmt.Sprintf(googleFavicon, fr.baseURL.Host)
	}

	for _, item := range atom.Entries {
		if item.Link == nil {
			continue
		}

		a := getArticle(item.Link.Href)

		// Don't modify if something went wrong
		if a == nil {
			continue
		}

		if a.FinalURL != "" {
			item.Link.Href = a.FinalURL
		}

		if a.Content != "" {
			if item.Content == nil {
				item.Content = &AtomContent{}
			}

			item.Content.Type = "html"
			item.Content.Content = a.Content
		}

		fr.t.title = item.Title
		fr.t.url = item.Link.Href
		addTracking(&item.Content.Content, fr)
	}

	return xmlEncode(atom)
}

func xmlEncode(v interface{}) (string, error) {
	res, err := xml.Marshal(v)
	if err != nil {
		return "", err
	}

	return string(res), nil
}

func urlAsPath(u url.URL) string {
	u.Scheme = ""
	u.Opaque = ""
	u.User = nil

	return path.Clean(path.Join("/", u.String()))
}

func getTrackingURL(fr feedRequest, includeIP bool, isFeedHit bool) string {
	ip := ""
	if includeIP {
		ip = fmt.Sprintf("&uip=%s", url.QueryEscape(fr.t.ip))
	}

	var u *url.URL
	if fr.t.url != "" {
		u, _ = url.Parse(fr.t.url)
	}

	if u == nil {
		u = fr.baseURL
	}

	dp := urlAsPath(*u)
	if isFeedHit {
		dp = "/hit" + dp
	} else {
		dp = "/read" + dp
	}

	return fmt.Sprintf(
		"https://www.google-analytics.com/collect?v=1&tid=UA-6408039-10&cid=%d&t=pageview&dh=ohmyrss.com&dp=%s&dt=%s%s",
		fr.t.cid,
		url.QueryEscape(dp),
		url.QueryEscape(fr.t.title),
		ip)
}

func track(fr feedRequest) {
	go func() {
		body, err := httpGet(getTrackingURL(fr, true, true))
		if err == nil {
			body.Close()
		}
	}()
}

func addTracking(content *string, fr feedRequest) {
	*content += fmt.Sprintf("", getTrackingURL(fr, false, false))
}

func checkLandingPage(u *url.URL, content string) (redirectURL string) {
	// Well, maybe we're looking at a landing page...
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
	if err != nil {
		return
	}

	feedHref, _ := doc.FindMatcher(linkAlt).Attr("href")
	if feedHref == "" {
		return
	}

	feedURL, err := url.Parse(feedHref)
	if err != nil {
		return
	}

	redirectURL = u.ResolveReference(feedURL).String()
	return
}
<commit_msg>Update cascadia to import from github<commit_after>package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"encoding/gob"
	"encoding/xml"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/http/fcgi"
	"net/url"
	"path"
	"strings"
	"time"

	"github.com/andybalholm/cascadia"
	"github.com/PuerkitoBio/goquery"
	"github.com/bkaradzic/go-lz4"
	"github.com/bradfitz/gomemcache/memcache"
	"github.com/thatguystone/swan"
)

type article struct {
	FinalURL string
	Content  string
}

type tracking struct {
	ip    string
	cid   uint32
	url   string
	title string
}

type feedRequest struct {
	baseURL *url.URL
	t       tracking
}

const (
	googleFavicon = "https://www.google.com/s2/favicons?domain=%s&alt=feed"
)

var (
	runFcgi         = false
	httpPort        = 8080
	memcacheServers = ""

	errNoMc        = errors.New("memcache disabled")
	errInvalidPage = errors.New("could not find a feed on this page")

	linkAlt = cascadia.MustCompile(
		"link[rel=alternate][type=\"application/rss+xml\"][href], " +
			"link[rel=alternate][type=\"application/atom+xml\"][href]")

	mc *memcache.Client
)

func init() {
	flag.BoolVar(&runFcgi, "fcgi", false, "run as a fastcgi server")
	flag.IntVar(&httpPort, "httpPort", 8080, "run a debug server")
	flag.StringVar(&memcacheServers, "mcServers", "", "comma-separated list of memcache servers")
}

func main() {
	flag.Parse()
	httpDisableLocal()
	rand.Seed(time.Now().UnixNano())

	if memcacheServers != "" {
		mc = memcache.New(strings.Split(memcacheServers, ",")...)
	}

	http.HandleFunc("/", feedHandler)

	if runFcgi {
		fcgi.Serve(nil, nil)
	} else {
		http.ListenAndServe(fmt.Sprintf(":%d", httpPort), nil)
	}
}

func hitCache(key string) (*article, error) {
	if mc == nil {
		return nil, errNoMc
	}

	item, err := mc.Get(key)
	if err != nil {
		return nil, err
	}

	lzdc, err := lz4.Decode(nil, item.Value)
	if err != nil {
		return nil, err
	}

	var b bytes.Buffer
	var a *article

	dec := gob.NewDecoder(&b)
	b.Write(lzdc)
	err = dec.Decode(&a)

	return a, err
}

func cacheArticle(key string, a *article, expire int32) {
	if mc == nil {
		return
	}

	var b []byte
	if a != nil {
		var bf bytes.Buffer
		enc := gob.NewEncoder(&bf)
		err := enc.Encode(a)
		if err != nil {
			return
		}

		b, err = lz4.Encode(nil, bf.Bytes())
		if err != nil {
			return
		}
	}

	mc.Set(&memcache.Item{
		Key:        key,
		Value:      b,
		Expiration: expire,
	})
}

func getArticle(url string) *article {
	if url == "" {
		return nil
	}

	sum := sha1.Sum([]byte(url))
	key := "ohmyrss_" + base64.StdEncoding.EncodeToString(sum[:])

	art, err := hitCache(key)
	if err == nil {
		return art
	}

	sa, err := swan.FromURL(url)
	cacheTime := int32(60 * 3)
	if err == nil {
		cacheTime = 60 * 60 * 24 * 7
	}

	if sa != nil && sa.TopNode != nil {
		html, _ := sa.TopNode.Html()
		art = &article{
			FinalURL: sa.URL,
			Content:  strings.TrimSpace(html),
		}
	}

	cacheArticle(key, art, cacheTime)
	return art
}

func feedHandler(w http.ResponseWriter, req *http.Request) {
	req.Body.Close()

	feedURL := req.FormValue("url")
	if feedURL == "" {
		http.Error(w, "missing url parameter", http.StatusBadRequest)
		return
	}

	u, err := url.Parse(feedURL)
	if err != nil {
		http.Error(w, "invalid url", http.StatusBadRequest)
		return
	}

	// For schemeless, just add an HTTP and retry
	if u.Scheme != "http" && u.Scheme != "https" {
		u, err = url.Parse("http://" + feedURL)
		if err != nil {
			http.Error(w, "invalid url", http.StatusBadRequest)
			return
		}
	}

	fr := feedRequest{
		baseURL: u,
		t: tracking{
			ip:  httpGetRemoteIP(req),
			cid: rand.Uint32(),
		},
	}

	feed, redirectURL, err := handleFeed(fr)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if redirectURL != "" {
		req.URL.RawQuery = fmt.Sprintf("url=%s", redirectURL)
		http.Redirect(w, req, req.URL.String(), http.StatusMovedPermanently)
		return
	}

	w.Header().Add("Content-Type", "text/xml; charset=utf-8")
	w.Write([]byte(feed))
}

func handleFeed(fr feedRequest) (feed string, redirectURL string, err error) {
	body, err := httpGetURL(fr.baseURL)
	if err != nil {
		return
	}
	defer body.Close()

	in, err := ioutil.ReadAll(body)
	if err != nil {
		return
	}

	var rss Rss
	err = xml.Unmarshal(in, &rss)
	if err == nil {
		feed, err = handleRss(&rss, fr)
		return
	}

	var atom Atom
	err = xml.Unmarshal(in, &atom)
	if err == nil {
		feed, err = handleAtom(&atom, fr)
		return
	}

	redirectURL = checkLandingPage(fr.baseURL, string(in))
	if redirectURL != "" {
		err = nil
	} else {
		err = errInvalidPage
	}

	return
}

func handleRss(rss *Rss, fr feedRequest) (string, error) {
	ch := rss.Channel
	fr.t.title = ch.Title
	track(fr)

	if ch.Image == nil {
		ch.Image = &RssImage{
			Title: ch.Title,
			Link:  ch.Link,
		}
	}

	if ch.Image.Url == "" {
		ch.Image.Url = fmt.Sprintf(googleFavicon, fr.baseURL.Host)
	}

	for _, item := range ch.Items {
		a := getArticle(item.Link)

		// Don't modify if something went wrong
		if a == nil {
			continue
		}

		if a.FinalURL != "" {
			item.Link = a.FinalURL
		}

		if a.Content != "" {
			item.Description = a.Content
		}

		fr.t.title = item.Title
		fr.t.url = item.Link
		addTracking(&item.Description, fr)
	}

	return xmlEncode(rss)
}

func handleAtom(atom *Atom, fr feedRequest) (string, error) {
	fr.t.title = atom.Title
	track(fr)

	if atom.Icon == "" {
		atom.Icon = fmt.Sprintf(googleFavicon, fr.baseURL.Host)
	}

	for _, item := range atom.Entries {
		if item.Link == nil {
			continue
		}

		a := getArticle(item.Link.Href)

		// Don't modify if something went wrong
		if a == nil {
			continue
		}

		if a.FinalURL != "" {
			item.Link.Href = a.FinalURL
		}

		if a.Content != "" {
			if item.Content == nil {
				item.Content = &AtomContent{}
			}

			item.Content.Type = "html"
			item.Content.Content = a.Content
		}

		fr.t.title = item.Title
		fr.t.url = item.Link.Href
		addTracking(&item.Content.Content, fr)
	}

	return xmlEncode(atom)
}

func xmlEncode(v interface{}) (string, error) {
	res, err := xml.Marshal(v)
	if err != nil {
		return "", err
	}

	return string(res), nil
}

func urlAsPath(u url.URL) string {
	u.Scheme = ""
	u.Opaque = ""
	u.User = nil

	return path.Clean(path.Join("/", u.String()))
}

func getTrackingURL(fr feedRequest, includeIP bool, isFeedHit bool) string {
	ip := ""
	if includeIP {
		ip = fmt.Sprintf("&uip=%s", url.QueryEscape(fr.t.ip))
	}

	var u *url.URL
	if fr.t.url != "" {
		u, _ = url.Parse(fr.t.url)
	}

	if u == nil {
		u = fr.baseURL
	}

	dp := urlAsPath(*u)
	if isFeedHit {
		dp = "/hit" + dp
	} else {
		dp = "/read" + dp
	}

	return fmt.Sprintf(
		"https://www.google-analytics.com/collect?v=1&tid=UA-6408039-10&cid=%d&t=pageview&dh=ohmyrss.com&dp=%s&dt=%s%s",
		fr.t.cid,
		url.QueryEscape(dp),
		url.QueryEscape(fr.t.title),
		ip)
}

func track(fr feedRequest) {
	go func() {
		body, err := httpGet(getTrackingURL(fr, true, true))
		if err == nil {
			body.Close()
		}
	}()
}

func addTracking(content *string, fr feedRequest) {
	*content += fmt.Sprintf("", getTrackingURL(fr, false, false))
}

func checkLandingPage(u *url.URL, content string) (redirectURL string) {
	// Well, maybe we're looking at a landing page...
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
	if err != nil {
		return
	}

	feedHref, _ := doc.FindMatcher(linkAlt).Attr("href")
	if feedHref == "" {
		return
	}

	feedURL, err := url.Parse(feedHref)
	if err != nil {
		return
	}

	redirectURL = u.ResolveReference(feedURL).String()
	return
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os/exec"
	"regexp"
	"strings"
	"text/template"

	"github.com/russross/blackfriday"
)

const layout = `
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Changelog</title>
  </head>
  <body>
	  {{.Content}}
	</body>
</html>
`

// ChangeLog represents a collection of changes between versions
type ChangeLog struct {
	NameTags string
	Commits  []CommitMessage
}

// CommitMessage contains the commit information
type CommitMessage struct {
	ShortCommit string `json:"shortcommit"`
	Commit      string `json:"commit"`
	Author      string `json:"author"`
	Email       string `json:"email"`
	Date        string `json:"date"`
	Message     string `json:"message"`
}

// Template for html format
type Template struct {
	Content string
}

// Report full report structure
type Report struct {
	pattern   string
	originURL string
	ChangeLog []ChangeLog
	JSON      string
	Markdown  string
	HTML      string
}

func fetchTags(releasePattern string) []string {
	out, _ := exec.Command("git", "log", "--tags", "--simplify-by-decoration", "--pretty=\"%ai @%d\"").Output()
	uncuratedTags := strings.Split(string(out), "\n")

	var tags []string
	validTag := regexp.MustCompile(fmt.Sprintf(`(.*\s)@\s.*tag:\s(%v)`, releasePattern))

	var match [][]string
	for _, tag := range uncuratedTags {
		match = validTag.FindAllStringSubmatch(tag, -1)
		if len(match) > 0 && len(match[0]) > 2 {
			// fmt.Printf("%v => %v \n", match[0][1], match[0][2])
			tags = append(tags, strings.Replace(match[0][2], ",", "", -1))
		}
	}
	return tags
}

func fetchChanges(releasePattern string) []ChangeLog {
	var changeLogs []ChangeLog
	tags := fetchTags(releasePattern)

	var diffs []string
	for i := 0; i < len(tags)-1; i++ {
		diffs = append(diffs, fmt.Sprintf("%v..%v", tags[i+1], tags[i]))
	}

	format := "--pretty=format:{\"shortcommit\":\"%h\", \"commit\":\"%H\", \"author\":\"%an\", \"email\":\"%ae\", \"date\":\"%ad\", \"message\":\"%f\"},"

	var out []byte
	var outs string
	var data []CommitMessage
	re := regexp.MustCompile(`,$`)
	for _, diff := range diffs {
		out, _ = exec.Command("git", "log", format, diff).Output()
		outs = fmt.Sprintf("[%v]", re.ReplaceAllString(string(out), ""))
		// fmt.Printf("%v\n", outs)
		if err := json.Unmarshal([]byte(outs), &data); err != nil {
			panic(err)
		}
		changeLogs = append(changeLogs, ChangeLog{NameTags: diff, Commits: data})
	}

	return changeLogs
}

func (c *CommitMessage) toMarkdown(originURL string) string {
	space := regexp.MustCompile(`-`)
	commitURL := fmt.Sprintf("%vcommit/%v", originURL, c.Commit)
	return fmt.Sprintf("* [%v](%v) %v [%v](mailto:%v)\n", c.ShortCommit, commitURL, space.ReplaceAllString(c.Message, " "), c.Author, c.Email)
}

func fetchProjectURL() string {
	originURL, _ := exec.Command("git", "config", "--get", "remote.origin.url").Output()
	re := regexp.MustCompile(`:`)
	originURL = []byte(re.ReplaceAllString(string(originURL), `/`))
	re = regexp.MustCompile(`\.git\n$`)
	originURL = []byte(re.ReplaceAllString(string(originURL), `/`))
	re = regexp.MustCompile(`^git@`)
	originURL = []byte(re.ReplaceAllString(string(originURL), `https://`))

	return string(originURL)
}

func buildReport(releasePattern string) Report {
	report := Report{pattern: releasePattern, originURL: fetchProjectURL()}
	report.ChangeLog = fetchChanges(releasePattern)

	byteJSON, _ := json.Marshal(report.ChangeLog)
	report.JSON = string(byteJSON)

	return report
}

func (r *Report) getMarkdown() string {
	if len(r.Markdown) > 0 {
		return r.Markdown
	}
	var mdBuffer bytes.Buffer
	mdBuffer.WriteString("# Changelog\n")

	for _, change := range r.ChangeLog {
		mdBuffer.WriteString(fmt.Sprintf("\n## %v \n\n", change.NameTags))
		for _, commit := range change.Commits {
			mdBuffer.WriteString(commit.toMarkdown(string(r.originURL)))
		}
	}
	r.Markdown = mdBuffer.String()
	return r.Markdown
}

func (r *Report) getHTML() string {
	if len(r.HTML) > 0 {
		return r.HTML
	}
	out := bytes.NewBuffer(nil)

	html := blackfriday.MarkdownCommon([]byte(r.getMarkdown()))

	t := template.Must(template.New("layout").Parse(layout))
	err := t.Execute(out, Template{Content: string(html)})
	if err != nil {
		panic(err)
	}
	r.HTML = out.String()
	return r.HTML
}

func main() {
	releasePattern := `v[\d{1,4}\.]{1,}`
	// releasePattern := `release-v?[\d{1,4}\.]{1,}`
	report := buildReport(releasePattern)
	fmt.Println(report.JSON)
	fmt.Println(report.getMarkdown())
	fmt.Print(report.getHTML())
}
<commit_msg>implement cli commands and options<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"text/template"

	"github.com/codegangsta/cli"
	"github.com/russross/blackfriday"
)

const layout = `
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Changelog</title>
  </head>
  <body>
	  {{.Content}}
	</body>
</html>
`

// ChangeLog represents a collection of changes between versions
type ChangeLog struct {
	NameTags string
	Commits  []CommitMessage
}

// CommitMessage contains the commit information
type CommitMessage struct {
	ShortCommit string `json:"shortcommit"`
	Commit      string `json:"commit"`
	Author      string `json:"author"`
	Email       string `json:"email"`
	Date        string `json:"date"`
	Message     string `json:"message"`
}

// Template for html format
type Template struct {
	Content string
}

// Report full report structure
type Report struct {
	pattern   string
	originURL string
	ChangeLog []ChangeLog
	JSON      string
	Markdown  string
	HTML      string
}

func fetchTags(releasePattern string) []string {
	out, _ := exec.Command("git", "log", "--tags", "--simplify-by-decoration", "--pretty=\"%ai @%d\"").Output()
	uncuratedTags := strings.Split(string(out), "\n")

	var tags []string
	validTag := regexp.MustCompile(fmt.Sprintf(`(.*\s)@\s.*tag:\s(%v)`, releasePattern))

	var match [][]string
	for _, tag := range uncuratedTags {
		match = validTag.FindAllStringSubmatch(tag, -1)
		if len(match) > 0 && len(match[0]) > 2 {
			// fmt.Printf("%v => %v \n", match[0][1], match[0][2])
			tags = append(tags, strings.Replace(match[0][2], ",", "", -1))
		}
	}
	return tags
}

func fetchChanges(releasePattern string) []ChangeLog {
	var changeLogs []ChangeLog
	tags := fetchTags(releasePattern)

	var diffs []string
	for i := 0; i < len(tags)-1; i++ {
		diffs = append(diffs, fmt.Sprintf("%v..%v", tags[i+1], tags[i]))
	}

	format := "--pretty=format:{\"shortcommit\":\"%h\", \"commit\":\"%H\", \"author\":\"%an\", \"email\":\"%ae\", \"date\":\"%ad\", \"message\":\"%f\"},"

	var out []byte
	var outs string
	var data []CommitMessage
	re := regexp.MustCompile(`,$`)
	for _, diff := range diffs {
		out, _ = exec.Command("git", "log", format, diff).Output()
		outs = fmt.Sprintf("[%v]", re.ReplaceAllString(string(out), ""))
		// fmt.Printf("%v\n", outs)
		if err := json.Unmarshal([]byte(outs), &data); err != nil {
			panic(err)
		}
		changeLogs = append(changeLogs, ChangeLog{NameTags: diff, Commits: data})
	}

	return changeLogs
}

func (c *CommitMessage) toMarkdown(originURL string) string {
	space := regexp.MustCompile(`-`)
	commitURL := fmt.Sprintf("%vcommit/%v", originURL, c.Commit)
	return fmt.Sprintf("* [%v](%v) %v [%v](mailto:%v)\n", c.ShortCommit, commitURL, space.ReplaceAllString(c.Message, " "), c.Author, c.Email)
}

func fetchProjectURL() string {
	originURL, _ := exec.Command("git", "config", "--get", "remote.origin.url").Output()
	re := regexp.MustCompile(`:`)
	originURL = []byte(re.ReplaceAllString(string(originURL), `/`))
	re = regexp.MustCompile(`\.git\n$`)
	originURL = []byte(re.ReplaceAllString(string(originURL), `/`))
	re = regexp.MustCompile(`^git@`)
	originURL = []byte(re.ReplaceAllString(string(originURL), `https://`))

	return string(originURL)
}

func buildReport(releasePattern string) Report {
	report := Report{pattern: releasePattern, originURL: fetchProjectURL()}
	report.ChangeLog = fetchChanges(releasePattern)

	byteJSON, _ := json.Marshal(report.ChangeLog)
	report.JSON = string(byteJSON)

	return report
}

func (r *Report) getMarkdown() string {
	if len(r.Markdown) > 0 {
		return r.Markdown
	}
	var mdBuffer bytes.Buffer
	mdBuffer.WriteString("# Changelog\n")

	for _, change := range r.ChangeLog {
		mdBuffer.WriteString(fmt.Sprintf("\n## %v \n\n", change.NameTags))
		for _, commit := range change.Commits {
			mdBuffer.WriteString(commit.toMarkdown(string(r.originURL)))
		}
	}
	r.Markdown = mdBuffer.String()
	return r.Markdown
}

func (r *Report) getHTML() string {
	if len(r.HTML) > 0 {
		return r.HTML
	}
	out := bytes.NewBuffer(nil)

	html := blackfriday.MarkdownCommon([]byte(r.getMarkdown()))

	t := template.Must(template.New("layout").Parse(layout))
	err := t.Execute(out, Template{Content: string(html)})
	if err != nil {
		panic(err)
	}
	r.HTML = out.String()
	return r.HTML
}

func main() {
	app := cli.NewApp()
	app.Name = "Changes reporter"
	app.Usage = "Generate changelog report from git commits and tag releases"
	app.Version = "0.0.2"
	app.Author = "Luis Ezcurdia"
	app.Email = "ing.ezcurdia@gmail.com"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "pattern, p",
			Value: `v[\d{1,4}\.]{1,}`,
			Usage: "Regular expresion for release tags",
		},
		cli.StringFlag{
			Name:  "format, f",
			Value: "markdown",
			Usage: "Output format for report",
		},
	}
	app.Action = func(c *cli.Context) {
		report := buildReport(c.String("pattern"))
		switch strings.ToLower(c.String("format")) {
		case "json":
			fmt.Println(report.JSON)
		case "html":
			fmt.Print(report.getHTML())
		default:
			fmt.Println(report.getMarkdown())
		}
	}
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go-uuid/uuid"
	"encoding/json"
	"fmt"
	"github.com/gorilla/mux"
	gorillasession "github.com/gorilla/sessions"
	"github.com/ory-am/common/env"
	"github.com/ory-am/common/mgopath"
	"github.com/ory-am/event"
	gde "github.com/ory-am/gitdeploy/event"
	"github.com/ory-am/gitdeploy/job"
	gdLog "github.com/ory-am/gitdeploy/log"
	"github.com/ory-am/gitdeploy/sse"
	"github.com/ory-am/gitdeploy/storage"
	"github.com/ory-am/gitdeploy/storage/mongo"
	"github.com/ory-am/google-json-style-response/responder"
	"gopkg.in/mgo.v2"
	"log"
	"net/http"
	"os/exec"
	"regexp"
	"time"
	"runtime"
	"os"
    "strings"
)

const (
	sessionCurrentDeployment = "cdid"
	sessionName = "gdp"
)

var (
// API Version
	ApiVersion = "1.0"

// Generic configuration
	host = env.Getenv("HOST", "")
	port = env.Getenv("PORT", "7654")

	envAppTtl = env.Getenv("APP_TTL", "30m")
    envClusterConf = env.Getenv("FLYNN_CLUSTER_CONFIG", "")

// Configuration for CORS
	corsAllowOrigin = env.Getenv("CORS_ALLOW_ORIGIN", "http://localhost:9000")

	sessionStore = gorillasession.NewCookieStore([]byte(env.Getenv("SESSION_SECRET", "changme")))

// MongoDB
	envMongoPath = env.Getenv("MONGODB", "mongodb://localhost:27017/gitdeploy")
)

type deployRequest struct {
	Repository string `json:"repository"`
}

type appResponse struct {
	App string `json:"app"`
}

func main() {
	checkDependencies()
	eventManager := event.New()

	// mgo
	mongoDatabase, err := mgopath.Connect(envMongoPath)
	if err != nil {
		log.Fatal(err)
	}

	storage := mongo.New(mongoDatabase)
	eventManager.AttachListenerAggregate(storage)

	// SSE broker
	sseBroker := sse.New(storage)

	// Log listener
	eventManager.AttachListenerAggregate(new(gdLog.Listener))

	// Mux router
	r := mux.NewRouter()
	r.HandleFunc("/config", configHandler).Methods("GET")
	r.HandleFunc("/deployments", deployWrapperAction(sseBroker, eventManager, storage)).Methods("POST")
	r.HandleFunc("/deployments", setCORSHeaders).Methods("OPTIONS")
	r.HandleFunc("/deployments/{app:.+}/events", eventWrapperAction(sseBroker)).Methods("GET")
	r.HandleFunc("/apps/{app:.+}", getAppHandler(storage)).Methods("GET")
	r.PathPrefix("/").HandlerFunc(publicHandler("./app/dist"))
	http.Handle("/", r)

	go job.KillAppsOnHitList(storage)

	listen := fmt.Sprintf("%s:%s", host, port)
	log.Printf("Listening on %s", listen)
	log.Fatal(http.ListenAndServe(listen, nil))
}

func publicHandler(dir string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		path := dir + r.URL.Path
		if r.URL.Path == "/" {
			path = dir + "/index.html"
		}
		pattern := `!\.html|\.js|\.svg|\.css|\.png|\.jpg$`

		if f, err := os.Stat(path); err == nil {
			if !f.IsDir() {
				http.ServeFile(w, r, path)
				return
			} else {
                http.NotFound(w, r)
				return
			}
		}

		if matched, err := regexp.MatchString(pattern, path); err != nil {
			log.Printf("Could not exec regex: %s", err.Error())
		} else if !matched {
            http.ServeFile(w, r, dir + "/index.html")
            return
		} else {
			http.NotFound(w, r)
		}
	}
}

func configHandler(w http.ResponseWriter, r *http.Request) {
	setCORSHeaders(w, r)
	responseSuccess(w, struct {
		Now time.Time `json:"now"`
	}{
		time.Now(),
	})
}

func getAppHandler(store *mongo.MongoStorage) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		if cleanUpSession(w, r) != nil {
			return
		}
		setCORSHeaders(w, r)
		vars := mux.Vars(r)
		id := vars["app"]
		app, err := store.GetApp(id)
		if err == mgo.ErrNotFound {
			responseError(w, http.StatusNotFound, fmt.Sprintf("App %s does not exist.", id))
			return
		} else if app.Killed {
			responseError(w, http.StatusNotFound, "App timed out and is no longer available.")
			return
		} else if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}
		logs, err := job.GetLogs(app.ID)
		if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}

		ps, err := job.GetPS(app.ID)
		if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}

		deployLogs, err := store.FindDeployLogsForApp(id)
		if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}

		responseSuccess(w, struct {
			*storage.App
			Logs       string                 `json:"logs"`
			DeployLogs []*storage.DeployEvent `json:"deployLogs"`
			PS         string                 `json:"ps"`
		}{app, logs, deployLogs, ps})
	}
}

func cleanUpSession(w http.ResponseWriter, r *http.Request) error {
	session, err := sessionStore.Get(r, sessionName)
	if err != nil {
		responseError(w, http.StatusBadRequest, "Please delete your cookies.")
		return err
	}
	delete(session.Values, sessionCurrentDeployment)
	if err := session.Save(r, w); err != nil {
		responseError(w, http.StatusInternalServerError, err.Error())
		return err
	}
	return nil
}

func eventWrapperAction(sseBroker *sse.Broker) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		setCORSHeaders(w, r)
		sseBroker.EventHandler(w, r)
	}
}

func deployWrapperAction(sseBroker *sse.Broker, em *event.EventManager, store *mongo.MongoStorage) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		setCORSHeaders(w, r)
		deployAction(w, r, sseBroker, em, store)
	}
}

func deployAction(w http.ResponseWriter, r *http.Request, sseBroker *sse.Broker, em *event.EventManager, store *mongo.MongoStorage) {
	session, err := sessionStore.Get(r, sessionName)
	if err != nil {
		responseError(w, http.StatusBadRequest, "Please delete your cookies.")
		return
	}

	// Check if the user is currently deploying an application and switch to that one.
	if v, ok := session.Values[sessionCurrentDeployment].(string); ok && len(v) > 0 {
		app, err := store.GetApp(v)
		if err != nil {
			cleanUpSession(w, r)
			log.Printf("Could not fetch app from cookie: %s", err.Error())
		} else {
			responseSuccess(w, app)
		}
	}

	// Parse body
	dr := new(deployRequest)
	decoder := json.NewDecoder(r.Body)
	decoder.Decode(dr)

	// Validate URL
	regExpression := "(https|http):\\/\\/github\\.com\\/[a-zA-Z0-9\\-\\_]+/[a-zA-Z0-9\\-\\_]+\\.git"
	if match, _ := regexp.MatchString(regExpression, dr.Repository); !match {
		responseError(w, http.StatusBadRequest, "I only support GitHub.")
		return
	}

	app := uuid.NewRandom().String()
	if ttl, err := time.ParseDuration(envAppTtl); err != nil {
		log.Println(err.Error())
		responseError(w, http.StatusInternalServerError, err.Error())
		return
	} else {
		appEntity, err := store.AddApp(app, time.Now().Add(ttl), dr.Repository, getIP(r))
		if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}
		session.Values[sessionCurrentDeployment] = appEntity.ID
		if err := session.Save(r, w); err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}
		go runJobs(w, r, em, dr, appEntity, sseBroker, session, store)
		responseSuccess(w, appEntity)
	}
}

func runJobs(w http.ResponseWriter, r *http.Request, em *event.EventManager, dr *deployRequest, app *storage.App, sseBroker *sse.Broker, session *gorillasession.Session, store *mongo.MongoStorage) {
	sseBroker.OpenChannel(app.ID)
	sseBroker.Start(app.ID)
	defer func() {
		// Give the client the chance to read the output...
		time.Sleep(2 * time.Minute)
		sseBroker.CloseChannel(app.ID)
	}()

	em.Trigger("app.created", gde.New(app.ID, app.ID))

	destination, err := job.Clone(em, app.ID, dr.Repository)
	if err != nil {
		log.Printf("Error in job.clone %s: %s", app.ID, err.Error())
		return
	}

	if err = job.Parse(em, app.ID, destination); err != nil {
		log.Printf("Error in job.parse %s: %s", app.ID, err.Error())
		return
	}

	if err = job.Deploy(em, app.ID, destination); err != nil {
		log.Printf("Error in job.deploy %s: %s", app.ID, err.Error())
		return
	}

	cluster, err := job.GetCluster(em, app.ID)
	if err != nil {
		log.Printf("Error in job.deploy %s: %s", app.ID, err.Error())
		return
	}

	app.URL = fmt.Sprintf("%s.%s", app.ID, cluster.Host)
	if err := store.UpdateApp(app); err != nil {
		responseError(w, http.StatusInternalServerError, err.Error())
		log.Printf("Error %s: %s", app.ID, err.Error())
		return
	}

	log.Println("Deployment successful.")
	em.Trigger("app.deployed", gde.New(app.ID, app.URL))
}

// Set the different CORS headers required for CORS request
func setCORSHeaders(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
	w.Header().Set("Access-Control-Allow-Origin", corsAllowOrigin)
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
}

func responseError(w http.ResponseWriter, code int, message string) {
	log.Printf("Error %d: %s", code, message)
	response := responder.New(ApiVersion)
	response.Write(w, response.Error(code, message))
}

func responseSuccess(w http.ResponseWriter, data interface{}) {
	response := responder.New(ApiVersion)
	response.Write(w, response.Success(data))
}

func checkDependencies() {
	go func() {
		_, err := exec.LookPath("git")
		if err != nil {
			log.Fatal("Git CLI is required but not installed or not in path.")
		}
	}()
	go checkIfFlynnExists()
}

func checkIfFlynnExists() {
	func() {
		_, err := exec.LookPath("flynn")
		if err != nil {
			if runtime.GOOS == "windows" {
				log.Fatal("Flynn CLI is required but not installed or not in path.")
			}
			log.Println("Could not find Flynn CLI, trying to install...")
			if o, err := exec.Command("sh", "bin/flynn-install.sh").CombinedOutput(); err != nil {
				log.Printf("Could not install Flynn CLI (%s): %s", err.Error(), o)
			} else if _, err := exec.LookPath("flynn"); err != nil {
				log.Fatal("Could not install Flynn CLI.")
			}
            log.Println("Flynn installed successfully!")
            log.Println("Adding flynn cluster...")
            args := append([]string{"cluster", "add"}, strings.Split(envClusterConf, " ")...)
            if o, err := exec.Command("flynn", args...).CombinedOutput(); err != nil {
                log.Fatalf("Could not add cluster (status: %s) (output: %s) (args: %s)", err.Error(), o, args)
            }
		}
	}()
}

func getIP(r *http.Request) string {
    ip := removePort(r.RemoteAddr)
    if len(r.Header.Get("X-FORWARDED-FOR")) > 0 {
        ip = r.Header.Get("X-FORWARDED-FOR")
    }
    return ip
}

func removePort(ip string) string {
    split := strings.Split(ip, ":")
    if len(split) < 2 {
        return ip
    }
    return strings.Join(split[:len(split)-1], ":")
}
<commit_msg>Cluster adding improved<commit_after>package main

import (
	"code.google.com/p/go-uuid/uuid"
	"encoding/json"
	"fmt"
	"github.com/gorilla/mux"
	gorillasession "github.com/gorilla/sessions"
	"github.com/ory-am/common/env"
	"github.com/ory-am/common/mgopath"
	"github.com/ory-am/event"
	gde "github.com/ory-am/gitdeploy/event"
	"github.com/ory-am/gitdeploy/job"
	gdLog "github.com/ory-am/gitdeploy/log"
	"github.com/ory-am/gitdeploy/sse"
	"github.com/ory-am/gitdeploy/storage"
	"github.com/ory-am/gitdeploy/storage/mongo"
	"github.com/ory-am/google-json-style-response/responder"
	"gopkg.in/mgo.v2"
	"log"
	"net/http"
	"os/exec"
	"regexp"
	"time"
	"runtime"
	"os"
    "strings"
)

const (
	sessionCurrentDeployment = "cdid"
	sessionName = "gdp"
)

var (
// API Version
	ApiVersion = "1.0"

// Generic configuration
	host = env.Getenv("HOST", "")
	port = env.Getenv("PORT", "7654")

	envAppTtl = env.Getenv("APP_TTL", "30m")
    envClusterConf = env.Getenv("FLYNN_CLUSTER_CONFIG", "")

// Configuration for CORS
	corsAllowOrigin = env.Getenv("CORS_ALLOW_ORIGIN", "http://localhost:9000")

	sessionStore = gorillasession.NewCookieStore([]byte(env.Getenv("SESSION_SECRET", "changme")))

// MongoDB
	envMongoPath = env.Getenv("MONGODB", "mongodb://localhost:27017/gitdeploy")
)

type deployRequest struct {
	Repository string `json:"repository"`
}

type appResponse struct {
	App string `json:"app"`
}

func main() {
	checkDependencies()
	eventManager := event.New()

	// mgo
	mongoDatabase, err := mgopath.Connect(envMongoPath)
	if err != nil {
		log.Fatal(err)
	}

	storage := mongo.New(mongoDatabase)
	eventManager.AttachListenerAggregate(storage)

	// SSE broker
	sseBroker := sse.New(storage)

	// Log listener
	eventManager.AttachListenerAggregate(new(gdLog.Listener))

	// Mux router
	r := mux.NewRouter()
	r.HandleFunc("/config", configHandler).Methods("GET")
	r.HandleFunc("/deployments", deployWrapperAction(sseBroker, eventManager, storage)).Methods("POST")
	r.HandleFunc("/deployments", setCORSHeaders).Methods("OPTIONS")
	r.HandleFunc("/deployments/{app:.+}/events", eventWrapperAction(sseBroker)).Methods("GET")
	r.HandleFunc("/apps/{app:.+}", getAppHandler(storage)).Methods("GET")
	r.PathPrefix("/").HandlerFunc(publicHandler("./app/dist"))
	http.Handle("/", r)

	go job.KillAppsOnHitList(storage)

	listen := fmt.Sprintf("%s:%s", host, port)
	log.Printf("Listening on %s", listen)
	log.Fatal(http.ListenAndServe(listen, nil))
}

func publicHandler(dir string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		path := dir + r.URL.Path
		if r.URL.Path == "/" {
			path = dir + "/index.html"
		}
		pattern := `!\.html|\.js|\.svg|\.css|\.png|\.jpg$`

		if f, err := os.Stat(path); err == nil {
			if !f.IsDir() {
				http.ServeFile(w, r, path)
				return
			} else {
                http.NotFound(w, r)
				return
			}
		}

		if matched, err := regexp.MatchString(pattern, path); err != nil {
			log.Printf("Could not exec regex: %s", err.Error())
		} else if !matched {
            http.ServeFile(w, r, dir + "/index.html")
            return
		} else {
			http.NotFound(w, r)
		}
	}
}

func configHandler(w http.ResponseWriter, r *http.Request) {
	setCORSHeaders(w, r)
	responseSuccess(w, struct {
		Now time.Time `json:"now"`
	}{
		time.Now(),
	})
}

func getAppHandler(store *mongo.MongoStorage) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		if cleanUpSession(w, r) != nil {
			return
		}
		setCORSHeaders(w, r)
		vars := mux.Vars(r)
		id := vars["app"]
		app, err := store.GetApp(id)
		if err == mgo.ErrNotFound {
			responseError(w, http.StatusNotFound, fmt.Sprintf("App %s does not exist.", id))
			return
		} else if app.Killed {
			responseError(w, http.StatusNotFound, "App timed out and is no longer available.")
			return
		} else if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}
		logs, err := job.GetLogs(app.ID)
		if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}

		ps, err := job.GetPS(app.ID)
		if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}

		deployLogs, err := store.FindDeployLogsForApp(id)
		if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}

		responseSuccess(w, struct {
			*storage.App
			Logs       string                 `json:"logs"`
			DeployLogs []*storage.DeployEvent `json:"deployLogs"`
			PS         string                 `json:"ps"`
		}{app, logs, deployLogs, ps})
	}
}

func cleanUpSession(w http.ResponseWriter, r *http.Request) error {
	session, err := sessionStore.Get(r, sessionName)
	if err != nil {
		responseError(w, http.StatusBadRequest, "Please delete your cookies.")
		return err
	}
	delete(session.Values, sessionCurrentDeployment)
	if err := session.Save(r, w); err != nil {
		responseError(w, http.StatusInternalServerError, err.Error())
		return err
	}
	return nil
}

func eventWrapperAction(sseBroker *sse.Broker) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		setCORSHeaders(w, r)
		sseBroker.EventHandler(w, r)
	}
}

func deployWrapperAction(sseBroker *sse.Broker, em *event.EventManager, store *mongo.MongoStorage) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		setCORSHeaders(w, r)
		deployAction(w, r, sseBroker, em, store)
	}
}

func deployAction(w http.ResponseWriter, r *http.Request, sseBroker *sse.Broker, em *event.EventManager, store *mongo.MongoStorage) {
	session, err := sessionStore.Get(r, sessionName)
	if err != nil {
		responseError(w, http.StatusBadRequest, "Please delete your cookies.")
		return
	}

	// Check if the user is currently deploying an application and switch to that one.
	if v, ok := session.Values[sessionCurrentDeployment].(string); ok && len(v) > 0 {
		app, err := store.GetApp(v)
		if err != nil {
			cleanUpSession(w, r)
			log.Printf("Could not fetch app from cookie: %s", err.Error())
		} else {
			responseSuccess(w, app)
		}
	}

	// Parse body
	dr := new(deployRequest)
	decoder := json.NewDecoder(r.Body)
	decoder.Decode(dr)

	// Validate URL
	regExpression := "(https|http):\\/\\/github\\.com\\/[a-zA-Z0-9\\-\\_]+/[a-zA-Z0-9\\-\\_]+\\.git"
	if match, _ := regexp.MatchString(regExpression, dr.Repository); !match {
		responseError(w, http.StatusBadRequest, "I only support GitHub.")
		return
	}

	app := uuid.NewRandom().String()
	if ttl, err := time.ParseDuration(envAppTtl); err != nil {
		log.Println(err.Error())
		responseError(w, http.StatusInternalServerError, err.Error())
		return
	} else {
		appEntity, err := store.AddApp(app, time.Now().Add(ttl), dr.Repository, getIP(r))
		if err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}
		session.Values[sessionCurrentDeployment] = appEntity.ID
		if err := session.Save(r, w); err != nil {
			responseError(w, http.StatusInternalServerError, err.Error())
			return
		}
		go runJobs(w, r, em, dr, appEntity, sseBroker, session, store)
		responseSuccess(w, appEntity)
	}
}

func runJobs(w http.ResponseWriter, r *http.Request, em *event.EventManager, dr *deployRequest, app *storage.App, sseBroker *sse.Broker, session *gorillasession.Session, store *mongo.MongoStorage) {
	sseBroker.OpenChannel(app.ID)
	sseBroker.Start(app.ID)
	defer func() {
		// Give the client the chance to read the output...
		time.Sleep(2 * time.Minute)
		sseBroker.CloseChannel(app.ID)
	}()

	em.Trigger("app.created", gde.New(app.ID, app.ID))

	destination, err := job.Clone(em, app.ID, dr.Repository)
	if err != nil {
		log.Printf("Error in job.clone %s: %s", app.ID, err.Error())
		return
	}

	if err = job.Parse(em, app.ID, destination); err != nil {
		log.Printf("Error in job.parse %s: %s", app.ID, err.Error())
		return
	}

	if err = job.Deploy(em, app.ID, destination); err != nil {
		log.Printf("Error in job.deploy %s: %s", app.ID, err.Error())
		return
	}

	cluster, err := job.GetCluster(em, app.ID)
	if err != nil {
		log.Printf("Error in job.deploy %s: %s", app.ID, err.Error())
		return
	}

	app.URL = fmt.Sprintf("%s.%s", app.ID, cluster.Host)
	if err := store.UpdateApp(app); err != nil {
		responseError(w, http.StatusInternalServerError, err.Error())
		log.Printf("Error %s: %s", app.ID, err.Error())
		return
	}

	log.Println("Deployment successful.")
	em.Trigger("app.deployed", gde.New(app.ID, app.URL))
}

// Set the different CORS headers required for CORS request
func setCORSHeaders(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
	w.Header().Set("Access-Control-Allow-Origin", corsAllowOrigin)
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
}

func responseError(w http.ResponseWriter, code int, message string) {
	log.Printf("Error %d: %s", code, message)
	response := responder.New(ApiVersion)
	response.Write(w, response.Error(code, message))
}

func responseSuccess(w http.ResponseWriter, data interface{}) {
	response := responder.New(ApiVersion)
	response.Write(w, response.Success(data))
}

func checkDependencies() {
	go func() {
		_, err := exec.LookPath("git")
		if err != nil {
			log.Fatal("Git CLI is required but not installed or not in path.")
		}
	}()
	go checkIfFlynnExists()
}

func checkIfFlynnExists() {
	func() {
		_, err := exec.LookPath("flynn")
		if err != nil {
			if runtime.GOOS == "windows" {
				log.Fatal("Flynn CLI is required but not installed or not in path.")
			}
			log.Println("Could not find Flynn CLI, trying to install...")
			if o, err := exec.Command("sh", "bin/flynn-install.sh").CombinedOutput(); err != nil {
				log.Printf("Could not install Flynn CLI (%s): %s", err.Error(), o)
			} else if _, err := exec.LookPath("flynn"); err != nil {
				log.Fatal("Could not install Flynn CLI.")
			}
            log.Println("Flynn installed successfully!")
            log.Println("Adding flynn cluster...")
            log.Println(envClusterConf)
            args := append([]string{"cluster", "add"}, strings.Split(envClusterConf, " ")...)
            log.Printf("%s", args)
            if o, err := exec.Command("flynn", args...).CombinedOutput(); err != nil {
                log.Fatalf("Could not add cluster (status: %s) (output: %s) (args: %s)", err.Error(), o, args)
            }
		}
	}()
}

func getIP(r *http.Request) string {
    ip := removePort(r.RemoteAddr)
    if len(r.Header.Get("X-FORWARDED-FOR")) > 0 {
        ip = r.Header.Get("X-FORWARDED-FOR")
    }
    return ip
}

func removePort(ip string) string {
    split := strings.Split(ip, ":")
    if len(split) < 2 {
        return ip
    }
    return strings.Join(split[:len(split)-1], ":")
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	"github.com/codegangsta/cli"
	"github.com/vinceprignano/orchestra/commands"
)

var app *cli.App

func main() {
	app = cli.NewApp()
	app.Name = "Orchestra"
	app.Usage = "Orchestrate Go Services"
	app.Commands = []cli.Command{
		*commands.StartCommand,
		*commands.StopCommand,
	}
	app.Version = "0.1"
	app.Run(os.Args)
}
<commit_msg>Add app information<commit_after>package main

import (
	"os"

	"github.com/codegangsta/cli"
	"github.com/vinceprignano/orchestra/commands"
)

var app *cli.App

func main() {
	app = cli.NewApp()
	app.Name = "Orchestra"
	app.Usage = "Orchestrate Go Services"
	app.Author = "Vincenzo Prignano"
	app.Email = ""
	app.Commands = []cli.Command{
		*commands.StartCommand,
		*commands.StopCommand,
	}
	app.Version = "0.1"
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"time"

	"github.com/appleboy/gin-jwt"
	"github.com/gin-gonic/gin"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"github.com/whatdacode/GoREST/config"
	"github.com/whatdacode/GoREST/controllers"
	"github.com/whatdacode/GoREST/database"
	"github.com/whatdacode/GoREST/models"
	"golang.org/x/crypto/bcrypt"
)

func main() {
	database.Migrations()

	router := gin.Default()

	authMiddleware := &jwt.GinJWTMiddleware{
		Realm:      "OurRealm",
		Key:        []byte("OurSecretKey"),
		Timeout:    time.Hour,
		MaxRefresh: time.Hour,
		Authenticator: func(email string, password string, c *gin.Context) (string, bool) {
			var user models.User
			db := config.Connect()
			db.First(&user, "email = ?", email)

			if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
				return email, false
			}

			return email, true
		},
		// Authorizator: func(userId string, c *gin.Context) bool {
		// 	if userId == "admin" {
		// 		return true
		// 	}

		// 	return false
		// },
		Unauthorized: func(c *gin.Context, code int, message string) {
			c.JSON(code, gin.H{
				"code":    code,
				"message": message,
			})
		},
		TokenLookup: "header:Authorization",
		TimeFunc:    time.Now,
	}

	api := router.Group("/api/v1/")
	{
		api.POST("/login", authMiddleware.LoginHandler)
		usersWithAuth := api.Group("/users")
		usersWithAuth.Use(authMiddleware.MiddlewareFunc())
		{
			usersWithAuth.GET("/", controllers.GetUsers)
			usersWithAuth.GET("/:id", controllers.GetUserDetail)
			usersWithAuth.PATCH("/:id", controllers.UpdateUserDetail)
			usersWithAuth.DELETE("/:id", controllers.DeleteUser)
		}

		users := api.Group("/users")
		{
			users.POST("/", controllers.CreateUser)
		}

	}
	router.Run()

}
<commit_msg>log request body<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"time"

	"github.com/appleboy/gin-jwt"
	"github.com/gin-gonic/gin"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"github.com/whatdacode/GoREST/config"
	"github.com/whatdacode/GoREST/controllers"
	"github.com/whatdacode/GoREST/database"
	"github.com/whatdacode/GoREST/models"
	"golang.org/x/crypto/bcrypt"
)

func main() {
	database.Migrations()

	router := gin.Default()

	router.Use(RequestLogger())

	authMiddleware := &jwt.GinJWTMiddleware{
		Realm:      "OurRealm",
		Key:        []byte("OurSecretKey"),
		Timeout:    time.Hour,
		MaxRefresh: time.Hour,
		Authenticator: func(email string, password string, c *gin.Context) (string, bool) {
			var user models.User
			db := config.Connect()
			db.First(&user, "email = ?", email)

			if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
				return email, false
			}

			return email, true
		},
		// Authorizator: func(userId string, c *gin.Context) bool {
		// 	if userId == "admin" {
		// 		return true
		// 	}

		// 	return false
		// },
		Unauthorized: func(c *gin.Context, code int, message string) {
			c.JSON(code, gin.H{
				"code":    code,
				"message": message,
			})
		},
		TokenLookup: "header:Authorization",
		TimeFunc:    time.Now,
	}

	api := router.Group("/api/v1/")
	{
		api.POST("/login", authMiddleware.LoginHandler)
		usersWithAuth := api.Group("/users")
		usersWithAuth.Use(authMiddleware.MiddlewareFunc())
		{
			usersWithAuth.GET("/", controllers.GetUsers)
			usersWithAuth.GET("/:id", controllers.GetUserDetail)
			usersWithAuth.PATCH("/:id", controllers.UpdateUserDetail)
			usersWithAuth.DELETE("/:id", controllers.DeleteUser)
		}

		users := api.Group("/users")
		{
			users.POST("/", controllers.CreateUser)
		}

	}
	router.Run()

}

// RequestLogger is used for logging each http request in our API.
// thanks to https://stackoverflow.com/users/3011570/emb
func RequestLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		buf, _ := ioutil.ReadAll(c.Request.Body)
		rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf))
		rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf)) //We have to create a new Buffer, because rdr1 will be read.

		fmt.Println(readBody(rdr1)) // Print request body

		c.Request.Body = rdr2
		c.Next()
	}
}

func readBody(reader io.Reader) string {
	buf := new(bytes.Buffer)
	buf.ReadFrom(reader)

	s := buf.String()
	return s
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go.net/websocket"
	"net/http"
	"time"
)

var msg = make(chan []byte)

func wsHandler(ws *websocket.Conn) {
	for {
		wsMsg := <-msg
		ws.Write(wsMsg)
	}
}

func buttonPressed(w http.ResponseWriter, r *http.Request) {
	for {
		time.Sleep(time.Second)
		msg <- []byte("hello, world")
	}
}

func main() {
	http.HandleFunc("/button", buttonPressed)
	http.Handle("/socket", websocket.Handler(wsHandler))
	http.Handle("/", http.FileServer(http.Dir(".")))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic("ListenAndServe: " + err.Error())
	}
}
<commit_msg>Update websocket dependency<commit_after>package main

import (
	"golang.org/x/net/websocket"
	"net/http"
	"time"
)

var msg = make(chan []byte)

func wsHandler(ws *websocket.Conn) {
	for {
		wsMsg := <-msg
		ws.Write(wsMsg)
	}
}

func buttonPressed(w http.ResponseWriter, r *http.Request) {
	for {
		time.Sleep(time.Second)
		msg <- []byte("hello, world")
	}
}

func main() {
	http.HandleFunc("/button", buttonPressed)
	http.Handle("/socket", websocket.Handler(wsHandler))
	http.Handle("/", http.FileServer(http.Dir(".")))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic("ListenAndServe: " + err.Error())
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"

	flag "github.com/spf13/pflag"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/tools/clientcmd"
)

var (
	kubeconfig    string
	namespace     string
	labels        string
	version       bool
	podColumns    = []string{"NAME", "IMAGE", "STATUS", "RESTARTS", "START", "NAMESPACE"}
	deployColumns = []string{"NAME", "IMAGE", "NAMESPACE"}
)

func main() {

	flags := flag.NewFlagSet("kubeps", flag.ExitOnError)

	flags.Usage = func() {
		flags.PrintDefaults()
	}

	flags.StringVar(&kubeconfig, "kubeconfig", "", "Path of kubeconfig")
	flags.StringVar(&labels, "labels", "", "Label filter query")
	flags.StringVar(&namespace, "namespace", "", "Kubernetes namespace")
	flags.BoolVarP(&version, "version", "v", false, "Print version")

	// uses the current context in kubeconfig
	if kubeconfig == "" {
		if os.Getenv("KUBECONFIG") != "" {
			kubeconfig = os.Getenv("KUBECONFIG")
		} else {
			kubeconfig = clientcmd.RecommendedHomeFile
		}
	}

	if err := flags.Parse(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if version {
		printVersion()
		os.Exit(0)
	}

	clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
		&clientcmd.ConfigOverrides{})

	config, err := clientConfig.ClientConfig()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	// creates the clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	fmt.Printf("Namespace: %s\n", namespace)
	fmt.Printf("Labels:    %s\n\n", labels)

	deployments, err := clientset.Deployments(namespace).List(v1.ListOptions{
		LabelSelector: labels,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	deploymentPrint := new(tabwriter.Writer)
	deploymentPrint.Init(os.Stdout, 0, 8, 1, '\t', 0)

	fmt.Fprintln(deploymentPrint, strings.Join(deployColumns, "\t"))

	for _, deployment := range deployments.Items {
		for _, containers := range deployment.Spec.Template.Spec.Containers {
			fmt.Fprintln(deploymentPrint, strings.Join(
				[]string{deployment.Name, containers.Image, deployment.Namespace}, "\t",
			))
		}
	}
	fmt.Println("=== Deployment ===")
	deploymentPrint.Flush()
	fmt.Println()

	podList, err := clientset.Core().Pods(namespace).List(v1.ListOptions{
		LabelSelector: labels,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	podPrint := new(tabwriter.Writer)
	podPrint.Init(os.Stdout, 0, 8, 1, '\t', 0)

	fmt.Fprintln(podPrint, strings.Join(podColumns, "\t"))

	for _, pod := range podList.Items {
		for _, container := range pod.Spec.Containers {
			if pod.Status.ContainerStatuses != nil {
				fmt.Fprintln(podPrint, strings.Join(
					[]string{
						pod.Name,
						container.Image,
						string(pod.Status.Phase),
						strconv.FormatInt(int64(pod.Status.ContainerStatuses[0].RestartCount), 10),
						pod.Status.StartTime.String(),
						pod.Namespace,
					}, "\t",
				))
			} else {
				fmt.Fprintln(podPrint, strings.Join(
					[]string{
						pod.Name,
						container.Image,
						string(pod.Status.Phase),
						"<none>",
						"<none>",
						pod.Namespace,
					}, "\t",
				))
			}
		}
	}
	fmt.Println("=== Pod ===")
	podPrint.Flush()
	fmt.Println()

}
<commit_msg>Count readyContainers<commit_after>package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"

	flag "github.com/spf13/pflag"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/tools/clientcmd"
)

var (
	kubeconfig    string
	namespace     string
	labels        string
	version       bool
	podColumns    = []string{"NAME", "IMAGE", "STATUS", "RESTARTS", "START", "NAMESPACE"}
	deployColumns = []string{"NAME", "IMAGE", "NAMESPACE"}
)

func main() {

	flags := flag.NewFlagSet("kubeps", flag.ExitOnError)

	flags.Usage = func() {
		flags.PrintDefaults()
	}

	flags.StringVar(&kubeconfig, "kubeconfig", "", "Path of kubeconfig")
	flags.StringVar(&labels, "labels", "", "Label filter query")
	flags.StringVar(&namespace, "namespace", "", "Kubernetes namespace")
	flags.BoolVarP(&version, "version", "v", false, "Print version")

	// uses the current context in kubeconfig
	if kubeconfig == "" {
		if os.Getenv("KUBECONFIG") != "" {
			kubeconfig = os.Getenv("KUBECONFIG")
		} else {
			kubeconfig = clientcmd.RecommendedHomeFile
		}
	}

	if err := flags.Parse(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if version {
		printVersion()
		os.Exit(0)
	}

	clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
		&clientcmd.ConfigOverrides{})

	config, err := clientConfig.ClientConfig()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	// creates the clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	fmt.Printf("Namespace: %s\n", namespace)
	fmt.Printf("Labels:    %s\n\n", labels)

	deployments, err := clientset.Deployments(namespace).List(v1.ListOptions{
		LabelSelector: labels,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	deploymentPrint := new(tabwriter.Writer)
	deploymentPrint.Init(os.Stdout, 0, 8, 1, '\t', 0)

	fmt.Fprintln(deploymentPrint, strings.Join(deployColumns, "\t"))

	for _, deployment := range deployments.Items {
		for _, containers := range deployment.Spec.Template.Spec.Containers {
			fmt.Fprintln(deploymentPrint, strings.Join(
				[]string{deployment.Name, containers.Image, deployment.Namespace}, "\t",
			))
		}
	}
	fmt.Println("=== Deployment ===")
	deploymentPrint.Flush()
	fmt.Println()

	podList, err := clientset.Core().Pods(namespace).List(v1.ListOptions{
		LabelSelector: labels,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	podPrint := new(tabwriter.Writer)
	podPrint.Init(os.Stdout, 0, 8, 1, '\t', 0)

	fmt.Fprintln(podPrint, strings.Join(podColumns, "\t"))

	for _, pod := range podList.Items {
		readyContainers := 0
		for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
			container := pod.Status.ContainerStatuses[i]
			if container.Ready && container.State.Running != nil {
				readyContainers++
			}
		}
		for _, container := range pod.Spec.Containers {
			if pod.Status.ContainerStatuses != nil {
				fmt.Fprintln(podPrint, strings.Join(
					[]string{
						pod.Name,
						container.Image,
						string(pod.Status.Phase),
						strconv.FormatInt(int64(pod.Status.ContainerStatuses[0].RestartCount), 10),
						pod.Status.StartTime.String(),
						pod.Namespace,
					}, "\t",
				))
			} else {
				fmt.Fprintln(podPrint, strings.Join(
					[]string{
						pod.Name,
						container.Image,
						string(pod.Status.Phase),
						"<none>",
						"<none>",
						pod.Namespace,
					}, "\t",
				))
			}
		}
	}
	fmt.Println("=== Pod ===")
	podPrint.Flush()
	fmt.Println()

}
<|endoftext|>