<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"syscall"
	"unsafe"
)

const (
	CURSOR_UP    = 1000
	CURSOR_DOWN  = 1001
	CURSOR_LEFT  = 1002
	CURSOR_RIGHT = 1003
	PAGE_UP      = 1004
	PAGE_DOWN    = 1005
	HOME_KEY     = 1006
	END_KEY      = 1007
	DEL_KEY      = 1008
)

const (
	INSERT_MODE = 1
	CMD_MODE    = 2
)

type winsize struct {
	height uint16
	width  uint16
	x      uint16
	y      uint16
}

type terminal int

func (t terminal) Read(buf []byte) (int, error) {
	return syscall.Read(int(t), buf)
}

func (t terminal) Write(s string) {
	b := bytes.NewBufferString(s)
	if _, err := syscall.Write(int(t), b.Bytes()); err != nil {
		panic(err)
	}
}

type cursor struct {
	x, y uint16
}

type editor struct {
	reader   terminal
	orignial syscall.Termios
	winsize
	editorUI     *bytes.Buffer
	cursor       cursor
	mode         int
	fileContents []string
	filename     string
}

var goedit editor

func init() {
	goedit = editor{}
	goedit.mode = CMD_MODE

	goedit.reader = terminal(syscall.Stdin)
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), syscall.TCGETS, uintptr(unsafe.Pointer(&goedit.orignial)), 0, 0, 0)
	if err != 0 {
		panic(err)
	}

	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(goedit.reader), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&goedit.winsize))); err != 0 {
		panic(err)
	}

	goedit.editorUI = bytes.NewBufferString("")
}

func openFile(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		goedit.fileContents = append(goedit.fileContents, scanner.Text())
	}
}

func drawRows() {
	for x := 0; x < int(goedit.height); x++ {
		if x >= len(goedit.fileContents) {
			goedit.editorUI.WriteString("~")
		} else {
			goedit.editorUI.WriteString(goedit.fileContents[x])
		}

		goedit.editorUI.WriteString("\x1b[K")
		if x < int(goedit.height)-1 {
			goedit.editorUI.WriteString("\r\n")
		}
	}
}

func rawMode() {
	argp := goedit.orignial
	argp.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	argp.Oflag &^= syscall.OPOST
	argp.Cflag |= syscall.CS8
	argp.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG
	argp.Cc[syscall.VMIN] = 0
	argp.Cc[syscall.VTIME] = 1

	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), 0x5404, uintptr(unsafe.Pointer(&argp)), 0, 0, 0)
	if err != 0 {
		panic(err)
	}
}

func resetMode() {
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), 0x5404, uintptr(unsafe.Pointer(&goedit.orignial)), 0, 0, 0)
	if err != 0 {
		panic(err)
	}
}

func readKey() rune {
	var buf [1]byte

	for {
		n, err := goedit.reader.Read(buf[:])
		if err != nil {
			panic(err)
		}

		if n == 1 {
			break
		}
	}

	if buf[0] == '\x1b' {
		var seq [2]byte
		n, err := goedit.reader.Read(seq[:])
		if err != nil {
			panic(err)
		}

		if n != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				var tilde [1]byte
				n, err := goedit.reader.Read(tilde[:])
				if err != nil {
					panic(err)
				}

				if n != 1 {
					return '\x1b'
				}

				if tilde[0] == '~' {
					switch seq[1] {
					case '1', '7':
						return HOME_KEY
					case '3':
						return DEL_KEY
					case '4', '8':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					}
				}
			} else {
				switch seq[1] {
				case 'A':
					return CURSOR_UP
				case 'B':
					return CURSOR_DOWN
				case 'C':
					return CURSOR_RIGHT
				case 'D':
					return CURSOR_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == 'O' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}

	return bytes.Runes(buf[:])[0]
}

func (e *editor) moveCursor(key rune) {
	switch key {
	case CURSOR_DOWN:
		if e.height != e.cursor.y {
			e.cursor.y++
		}
	case CURSOR_UP:
		if e.cursor.y != 0 {
			e.cursor.y--
		}
	case CURSOR_LEFT:
		if e.cursor.x != 0 {
			e.cursor.x--
		}
	case CURSOR_RIGHT:
		if e.width != e.cursor.x {
			e.cursor.x++
		}
	}
}

func clearScreen() {
	goedit.editorUI.Reset()
	goedit.editorUI.WriteString("\x1b[?25l")
	goedit.editorUI.WriteString("\x1b[H")
	drawRows()
	goedit.editorUI.WriteString(fmt.Sprintf("\x1b[%d;%dH", int(goedit.cursor.y)+1, int(goedit.cursor.x)+1))
	goedit.editorUI.WriteString("\x1b[?25h")

	goedit.reader.Write(goedit.editorUI.String())
	goedit.editorUI.Reset()
}

func processKeyPress() {
	key := readKey()

	switch key {
	case ('q' & 0x1f):
		resetMode()
		os.Exit(0)
	case CURSOR_DOWN, CURSOR_UP, CURSOR_LEFT, CURSOR_RIGHT:
		goedit.moveCursor(key)
	case 'h':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_LEFT)
		}
	case 'j':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_DOWN)
		}
	case 'k':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_UP)
		}
	case 'l':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_RIGHT)
		}
	case 'i':
		if goedit.mode == CMD_MODE {
			goedit.mode = INSERT_MODE
		}
	case '\x1b':
		goedit.mode = CMD_MODE
	case PAGE_UP:
		for x := 0; x < int(goedit.height); x++ {
			goedit.moveCursor(CURSOR_UP)
		}
	case PAGE_DOWN:
		for x := 0; x < int(goedit.height); x++ {
			goedit.moveCursor(CURSOR_DOWN)
		}
	case HOME_KEY:
		goedit.cursor.x = 0
	case END_KEY:
		goedit.cursor.x = goedit.width - 1
	}
}

func main() {
	rawMode()
	openFile("README.md")

	for {
		clearScreen()
		processKeyPress()
	}
}
<commit_msg>opens up file passed to editor<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"syscall"
	"unsafe"
)

const (
	CURSOR_UP    = 1000
	CURSOR_DOWN  = 1001
	CURSOR_LEFT  = 1002
	CURSOR_RIGHT = 1003
	PAGE_UP      = 1004
	PAGE_DOWN    = 1005
	HOME_KEY     = 1006
	END_KEY      = 1007
	DEL_KEY      = 1008
)

const (
	INSERT_MODE = 1
	CMD_MODE    = 2
)

type winsize struct {
	height uint16
	width  uint16
	x      uint16
	y      uint16
}

type terminal int

func (t terminal) Read(buf []byte) (int, error) {
	return syscall.Read(int(t), buf)
}

func (t terminal) Write(s string) {
	b := bytes.NewBufferString(s)
	if _, err := syscall.Write(int(t), b.Bytes()); err != nil {
		panic(err)
	}
}

type cursor struct {
	x, y uint16
}

type editor struct {
	reader   terminal
	orignial syscall.Termios
	winsize
	editorUI     *bytes.Buffer
	cursor       cursor
	mode         int
	fileContents []string
	filename     string
}

var goedit editor

func init() {
	goedit = editor{}
	goedit.mode = CMD_MODE

	goedit.reader = terminal(syscall.Stdin)
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), syscall.TCGETS, uintptr(unsafe.Pointer(&goedit.orignial)), 0, 0, 0)
	if err != 0 {
		panic(err)
	}

	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(goedit.reader), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&goedit.winsize))); err != 0 {
		panic(err)
	}

	goedit.editorUI = bytes.NewBufferString("")
}

func openFile(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		goedit.fileContents = append(goedit.fileContents, scanner.Text())
	}
}

func drawRows() {
	for x := 0; x < int(goedit.height); x++ {
		if x >= len(goedit.fileContents) {
			goedit.editorUI.WriteString("~")
		} else {
			goedit.editorUI.WriteString(goedit.fileContents[x])
		}

		goedit.editorUI.WriteString("\x1b[K")
		if x < int(goedit.height)-1 {
			goedit.editorUI.WriteString("\r\n")
		}
	}
}

func rawMode() {
	argp := goedit.orignial
	argp.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	argp.Oflag &^= syscall.OPOST
	argp.Cflag |= syscall.CS8
	argp.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG
	argp.Cc[syscall.VMIN] = 0
	argp.Cc[syscall.VTIME] = 1

	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), 0x5404, uintptr(unsafe.Pointer(&argp)), 0, 0, 0)
	if err != 0 {
		panic(err)
	}
}

func resetMode() {
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), 0x5404, uintptr(unsafe.Pointer(&goedit.orignial)), 0, 0, 0)
	if err != 0 {
		panic(err)
	}
}

func readKey() rune {
	var buf [1]byte

	for {
		n, err := goedit.reader.Read(buf[:])
		if err != nil {
			panic(err)
		}

		if n == 1 {
			break
		}
	}

	if buf[0] == '\x1b' {
		var seq [2]byte
		n, err := goedit.reader.Read(seq[:])
		if err != nil {
			panic(err)
		}

		if n != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				var tilde [1]byte
				n, err := goedit.reader.Read(tilde[:])
				if err != nil {
					panic(err)
				}

				if n != 1 {
					return '\x1b'
				}

				if tilde[0] == '~' {
					switch seq[1] {
					case '1', '7':
						return HOME_KEY
					case '3':
						return DEL_KEY
					case '4', '8':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					}
				}
			} else {
				switch seq[1] {
				case 'A':
					return CURSOR_UP
				case 'B':
					return CURSOR_DOWN
				case 'C':
					return CURSOR_RIGHT
				case 'D':
					return CURSOR_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == 'O' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}

	return bytes.Runes(buf[:])[0]
}

func (e *editor) moveCursor(key rune) {
	switch key {
	case CURSOR_DOWN:
		if e.height != e.cursor.y {
			e.cursor.y++
		}
	case CURSOR_UP:
		if e.cursor.y != 0 {
			e.cursor.y--
		}
	case CURSOR_LEFT:
		if e.cursor.x != 0 {
			e.cursor.x--
		}
	case CURSOR_RIGHT:
		if e.width != e.cursor.x {
			e.cursor.x++
		}
	}
}

func clearScreen() {
	goedit.editorUI.Reset()
	goedit.editorUI.WriteString("\x1b[?25l")
	goedit.editorUI.WriteString("\x1b[H")
	drawRows()
	goedit.editorUI.WriteString(fmt.Sprintf("\x1b[%d;%dH", int(goedit.cursor.y)+1, int(goedit.cursor.x)+1))
	goedit.editorUI.WriteString("\x1b[?25h")

	goedit.reader.Write(goedit.editorUI.String())
	goedit.editorUI.Reset()
}

func processKeyPress() {
	key := readKey()

	switch key {
	case ('q' & 0x1f):
		resetMode()
		os.Exit(0)
	case CURSOR_DOWN, CURSOR_UP, CURSOR_LEFT, CURSOR_RIGHT:
		goedit.moveCursor(key)
	case 'h':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_LEFT)
		}
	case 'j':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_DOWN)
		}
	case 'k':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_UP)
		}
	case 'l':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_RIGHT)
		}
	case 'i':
		if goedit.mode == CMD_MODE {
			goedit.mode = INSERT_MODE
		}
	case '\x1b':
		goedit.mode = CMD_MODE
	case PAGE_UP:
		for x := 0; x < int(goedit.height); x++ {
			goedit.moveCursor(CURSOR_UP)
		}
	case PAGE_DOWN:
		for x := 0; x < int(goedit.height); x++ {
			goedit.moveCursor(CURSOR_DOWN)
		}
	case HOME_KEY:
		goedit.cursor.x = 0
	case END_KEY:
		goedit.cursor.x = goedit.width - 1
	}
}

func main() {
	rawMode()
	if len(os.Args) == 2 {
		openFile(os.Args[1])
	}

	for {
		clearScreen()
		processKeyPress()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"os"

	"code.cloudfoundry.org/cli/plugin"
	"code.cloudfoundry.org/cli/plugin/models"
	"github.com/bluemixgaragelondon/cf-blue-green-deploy/from-cf-codebase/manifest"
	"strings"
)

var PluginVersion string

type CfPlugin struct {
	Connection plugin.CliConnection
	Deployer   BlueGreenDeployer
}

func (p *CfPlugin) Run(cliConnection plugin.CliConnection, args []string) {
	if len(args) > 0 && args[0] == "CLI-MESSAGE-UNINSTALL" {
		return
	}

	argsStruct := NewArgs(args)

	p.Connection = cliConnection

	defaultCfDomain, err := p.DefaultCfDomain()
	if err != nil {
		fmt.Println("Failed to get default shared domain")
		os.Exit(1)
	}

	p.Deployer.Setup(cliConnection)

	if argsStruct.AppName == "" {
		fmt.Println("App name must be provided")
		os.Exit(1)
	}

	if !p.Deploy(defaultCfDomain, manifest.DiskRepository{}, argsStruct) {
		fmt.Println("Smoke tests failed")
		os.Exit(1)
	}
}

func (p *CfPlugin) Deploy(defaultCfDomain string, repo manifest.Repository, args Args) bool {
	appName := args.AppName

	p.Deployer.DeleteAllAppsExceptLiveApp(appName)
	liveAppName, liveAppRoutes := p.Deployer.LiveApp(appName)

	newAppName := appName + "-new"
	tempRoute := plugin_models.GetApp_RouteSummary{Host: newAppName, Domain: plugin_models.GetApp_DomainFields{Name: defaultCfDomain}}
	p.Deployer.PushNewApp(newAppName, tempRoute, args.ManifestPath)

	promoteNewApp := true
	smokeTestScript := args.SmokeTestPath
	if smokeTestScript != "" {
		promoteNewApp = p.Deployer.RunSmokeTests(smokeTestScript, FQDN(tempRoute))
	}

	newAppRoutes := p.GetNewAppRoutes(appName, defaultCfDomain, repo, liveAppRoutes)

	p.Deployer.UnmapRoutesFromApp(newAppName, tempRoute)

	if promoteNewApp {
		if liveAppName != "" {
			p.Deployer.MapRoutesToApp(newAppName, newAppRoutes...)
			p.Deployer.RenameApp(liveAppName, appName+"-old")
			p.Deployer.RenameApp(newAppName, appName)
			p.Deployer.UnmapRoutesFromApp(appName+"-old", liveAppRoutes...)
		} else {
			p.Deployer.MapRoutesToApp(newAppName, newAppRoutes...)
			p.Deployer.RenameApp(newAppName, appName)
		}
		return true
	} else {
		p.Deployer.RenameApp(newAppName, appName+"-failed")
		return false
	}
}

func (p *CfPlugin) GetNewAppRoutes(appName string, defaultCfDomain string, repo manifest.Repository, liveAppRoutes []plugin_models.GetApp_RouteSummary) []plugin_models.GetApp_RouteSummary {
	newAppRoutes := []plugin_models.GetApp_RouteSummary{}
	f := ManifestAppFinder{AppName: appName, Repo: repo, DefaultDomain: defaultCfDomain}
	if manifestRoutes := f.RoutesFromManifest(); manifestRoutes != nil {
		newAppRoutes = append(newAppRoutes, manifestRoutes...)
	}
	uniqueRoutes := p.UnionRouteLists(newAppRoutes, liveAppRoutes)

	if len(uniqueRoutes) == 0 {
		uniqueRoutes = append(uniqueRoutes, plugin_models.GetApp_RouteSummary{Host: appName, Domain: plugin_models.GetApp_DomainFields{Name: defaultCfDomain}})
	}
	return uniqueRoutes
}

func (p *CfPlugin) UnionRouteLists(listA []plugin_models.GetApp_RouteSummary, listB []plugin_models.GetApp_RouteSummary) []plugin_models.GetApp_RouteSummary {
	duplicateList := append(listA, listB...)

	routesSet := make(map[plugin_models.GetApp_RouteSummary]struct{})

	for _, route := range duplicateList {
		routesSet[route] = struct{}{}
	}

	uniqueRoutes := []plugin_models.GetApp_RouteSummary{}
	for route := range routesSet {
		uniqueRoutes = append(uniqueRoutes, route)
	}
	return uniqueRoutes
}

func (p *CfPlugin) GetMetadata() plugin.PluginMetadata {
	var major, minor, build int
	fmt.Sscanf(PluginVersion, "%d.%d.%d", &major, &minor, &build)

	return plugin.PluginMetadata{
		Name: "blue-green-deploy",
		Version: plugin.VersionType{
			Major: major,
			Minor: minor,
			Build: build,
		},
		Commands: []plugin.Command{
			{
				Name:     "blue-green-deploy",
				Alias:    "bgd",
				HelpText: "Zero-downtime deploys with smoke tests",
				UsageDetails: plugin.Usage{
					Usage: "blue-green-deploy APP_NAME [--smoke-test TEST_SCRIPT] [-f MANIFEST_FILE]",
					Options: map[string]string{
						"smoke-test": "The test script to run.",
						"f":          "Path to manifest",
					},
				},
			},
		},
	}
}

func (p *CfPlugin) DefaultCfDomain() (domain string, err error) {
	var res []string
	if res, err = p.Connection.CliCommandWithoutTerminalOutput("curl", "/v2/shared_domains"); err != nil {
		return
	}

	response := struct {
		Resources []struct {
			Entity struct {
				Name string
			}
		}
	}{}

	var json_string string
	json_string = strings.Join(res, "\n")

	if err = json.Unmarshal([]byte(json_string), &response); err != nil {
		return
	}

	domain = response.Resources[0].Entity.Name
	return
}

func FQDN(r plugin_models.GetApp_RouteSummary) string {
	return fmt.Sprintf("%v.%v", r.Host, r.Domain.Name)
}

func main() {

	p := CfPlugin{
		Deployer: &BlueGreenDeploy{
			ErrorFunc: func(message string, err error) {
				fmt.Printf("%v - %v\n", message, err)
				os.Exit(1)
			},
			Out: os.Stdout,
		},
	}

	plugin.Start(&p)
}
<commit_msg>Add clarifying comments<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"os"

	"code.cloudfoundry.org/cli/plugin"
	"code.cloudfoundry.org/cli/plugin/models"
	"github.com/bluemixgaragelondon/cf-blue-green-deploy/from-cf-codebase/manifest"
	"strings"
)

var PluginVersion string

type CfPlugin struct {
	Connection plugin.CliConnection
	Deployer   BlueGreenDeployer
}

func (p *CfPlugin) Run(cliConnection plugin.CliConnection, args []string) {
	if len(args) > 0 && args[0] == "CLI-MESSAGE-UNINSTALL" {
		return
	}

	argsStruct := NewArgs(args)

	p.Connection = cliConnection

	defaultCfDomain, err := p.DefaultCfDomain()
	if err != nil {
		// TODO issue #11 - replace occurrences of the pattern below with
		// the single log.Fatalf("error: %v", err) line which does the same thing
		// and does not discard the error which is sometimes generated (e.g. above).
		fmt.Println("Failed to get default shared domain")
		os.Exit(1)
	}

	p.Deployer.Setup(cliConnection)

	if argsStruct.AppName == "" {
		fmt.Println("App name must be provided")
		os.Exit(1)
	}

	if !p.Deploy(defaultCfDomain, manifest.DiskRepository{}, argsStruct) {
		fmt.Println("Smoke tests failed")
		os.Exit(1)
	}
}

func (p *CfPlugin) Deploy(defaultCfDomain string, repo manifest.Repository, args Args) bool {
	appName := args.AppName

	p.Deployer.DeleteAllAppsExceptLiveApp(appName)
	liveAppName, liveAppRoutes := p.Deployer.LiveApp(appName)

	newAppName := appName + "-new"

	// Add route so that we can run the smoke tests
	tempRoute := plugin_models.GetApp_RouteSummary{Host: newAppName, Domain: plugin_models.GetApp_DomainFields{Name: defaultCfDomain}}
	// If deploy is unsuccessful, p.ErrorFunc will be called which exits.
	p.Deployer.PushNewApp(newAppName, tempRoute, args.ManifestPath)

	promoteNewApp := true
	smokeTestScript := args.SmokeTestPath
	if smokeTestScript != "" {
		promoteNewApp = p.Deployer.RunSmokeTests(smokeTestScript, FQDN(tempRoute))
	}

	// TODO We're overloading 'new' here for both the staging app and the 'finished' app, which is confusing
	newAppRoutes := p.GetNewAppRoutes(appName, defaultCfDomain, repo, liveAppRoutes)

	p.Deployer.UnmapRoutesFromApp(newAppName, tempRoute)

	if promoteNewApp {
		// If there is a live app, we want to disassociate the routes with the old version of the app
		// and instead update the routes to use the new version.
		if liveAppName != "" {
			p.Deployer.MapRoutesToApp(newAppName, newAppRoutes...)
			p.Deployer.RenameApp(liveAppName, appName+"-old")
			p.Deployer.RenameApp(newAppName, appName)
			p.Deployer.UnmapRoutesFromApp(appName+"-old", liveAppRoutes...)
		} else {
			// If there is no live app, we only need to add our new routes.
			p.Deployer.MapRoutesToApp(newAppName, newAppRoutes...)
			p.Deployer.RenameApp(newAppName, appName)
		}
		return true
	} else {
		// We don't want to promote. Instead mark it as failed.
		p.Deployer.RenameApp(newAppName, appName+"-failed")
		return false
	}
}

func (p *CfPlugin) GetNewAppRoutes(appName string, defaultCfDomain string, repo manifest.Repository, liveAppRoutes []plugin_models.GetApp_RouteSummary) []plugin_models.GetApp_RouteSummary {
	newAppRoutes := []plugin_models.GetApp_RouteSummary{}
	f := ManifestAppFinder{AppName: appName, Repo: repo, DefaultDomain: defaultCfDomain}
	if manifestRoutes := f.RoutesFromManifest(); manifestRoutes != nil {
		newAppRoutes = append(newAppRoutes, manifestRoutes...)
	}
	uniqueRoutes := p.UnionRouteLists(newAppRoutes, liveAppRoutes)

	if len(uniqueRoutes) == 0 {
		uniqueRoutes = append(uniqueRoutes, plugin_models.GetApp_RouteSummary{Host: appName, Domain: plugin_models.GetApp_DomainFields{Name: defaultCfDomain}})
	}
	return uniqueRoutes
}

func (p *CfPlugin) UnionRouteLists(listA []plugin_models.GetApp_RouteSummary, listB []plugin_models.GetApp_RouteSummary) []plugin_models.GetApp_RouteSummary {
	duplicateList := append(listA, listB...)

	routesSet := make(map[plugin_models.GetApp_RouteSummary]struct{})

	for _, route := range duplicateList {
		routesSet[route] = struct{}{}
	}

	uniqueRoutes := []plugin_models.GetApp_RouteSummary{}
	for route := range routesSet {
		uniqueRoutes = append(uniqueRoutes, route)
	}
	return uniqueRoutes
}

func (p *CfPlugin) GetMetadata() plugin.PluginMetadata {
	var major, minor, build int
	fmt.Sscanf(PluginVersion, "%d.%d.%d", &major, &minor, &build)

	return plugin.PluginMetadata{
		Name: "blue-green-deploy",
		Version: plugin.VersionType{
			Major: major,
			Minor: minor,
			Build: build,
		},
		Commands: []plugin.Command{
			{
				Name:     "blue-green-deploy",
				Alias:    "bgd",
				HelpText: "Zero-downtime deploys with smoke tests",
				UsageDetails: plugin.Usage{
					Usage: "blue-green-deploy APP_NAME [--smoke-test TEST_SCRIPT] [-f MANIFEST_FILE]",
					Options: map[string]string{
						"smoke-test": "The test script to run.",
						"f":          "Path to manifest",
					},
				},
			},
		},
	}
}

func (p *CfPlugin) DefaultCfDomain() (domain string, err error) {
	var res []string
	if res, err = p.Connection.CliCommandWithoutTerminalOutput("curl", "/v2/shared_domains"); err != nil {
		return
	}

	response := struct {
		Resources []struct {
			Entity struct {
				Name string
			}
		}
	}{}

	var json_string string
	json_string = strings.Join(res, "\n")

	if err = json.Unmarshal([]byte(json_string), &response); err != nil {
		return
	}

	domain = response.Resources[0].Entity.Name
	return
}

func FQDN(r plugin_models.GetApp_RouteSummary) string {
	return fmt.Sprintf("%v.%v", r.Host, r.Domain.Name)
}

func main() {

	p := CfPlugin{
		Deployer: &BlueGreenDeploy{
			ErrorFunc: func(message string, err error) {
				fmt.Printf("%v - %v\n", message, err)
				os.Exit(1)
			},
			Out: os.Stdout,
		},
	}

	// TODO issue #24 - (Rufus) - not sure if I'm using the plugin correctly, but if I build (go build) and run without arguments
	// I expected to see available arguments but instead the code panics.
	plugin.Start(&p)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gorilla/mux"
	"github.com/sokil/go-statsd-client"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

const defaultHTTPHost = "127.0.0.1"
const defaultHTTPPort = 80

const defaultStatsDHost = "127.0.0.1"
const defaultStatsDPort = 8125

const jwtHeaderName = "X-JWT-Token"

// declare command line options
var httpHost = flag.String("http-host", defaultHTTPHost, "HTTP Host")
var httpPort = flag.Int("http-port", defaultHTTPPort, "HTTP Port")
var statsdHost = flag.String("statsd-host", defaultStatsDHost, "StatsD Host")
var statsdPort = flag.Int("statsd-port", defaultStatsDPort, "StatsD Port")
var tokenSecret = flag.String("jwt-secret", "", "Secret to encrypt JWT")
var verbose = flag.Bool("verbose", false, "Verbose")

// statsd client
var statsdClient *statsd.Client

func main() {
	// get flags
	flag.Parse()

	// configure verbosity of logging
	if *verbose == true {
		log.SetOutput(os.Stderr)
	} else {
		log.SetOutput(ioutil.Discard)
	}

	// create HTTP router
	router := mux.NewRouter().StrictSlash(true)

	// register http request handlers
	router.Handle(
		"/heartbeat",
		validateCORS(http.HandlerFunc(handleHeartbeatRequest)),
	).Methods("GET")

	router.Handle(
		"/count/{key}",
		validateCORS(validateJWT(http.HandlerFunc(handleCountRequest))),
	).Methods("POST")

	router.Handle(
		"/gauge/{key}",
		validateCORS(validateJWT(http.HandlerFunc(handleGaugeRequest))),
	).Methods("POST")

	router.Handle(
		"/timing/{key}",
		validateCORS(validateJWT(http.HandlerFunc(handleTimingRequest))),
	).Methods("POST")

	router.Handle(
		"/set/{key}",
		validateCORS(validateJWT(http.HandlerFunc(handleSetRequest))),
	).Methods("POST")

	// Create a new StatsD connection
	statsdClient = statsd.NewClient(*statsdHost, *statsdPort)
	statsdClient.SetAutoflush(true)
	statsdClient.Open()
	defer statsdClient.Close()

	// get server address to bind
	httpAddress := fmt.Sprintf("%s:%d", *httpHost, *httpPort)
	log.Printf("Starting HTTP server %s", httpAddress)

	// create http server
	s := &http.Server{
		Addr:           httpAddress,
		Handler:        router,
		ReadTimeout:    1 * time.Second,
		WriteTimeout:   1 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	// start http server
	err := s.ListenAndServe()
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}

// validate CORS headers
func validateCORS(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		origin := r.Header.Get("Origin")
		if origin != "" {
			w.Header().Add("Access-Control-Allow-Headers", jwtHeaderName + ", X-Requested-With, Origin, Accept, Content-Type, Authentication")
			w.Header().Add("Access-Control-Allow-Methods", "GET, POST, HEAD, OPTIONS")
			w.Header().Add("Access-Control-Allow-Origin", origin)
		}
		next.ServeHTTP(w, r)
	})
}

// validate JWT middleware
func validateJWT(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if *tokenSecret == "" {
			next.ServeHTTP(w, r)
		} else {
			// get JWT
			tokenString := r.Header.Get(jwtHeaderName)
			if tokenString == "" {
				http.Error(w, "Token not specified", 401)
				return
			}

			// parse JWT
			_, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
				}
				return []byte(*tokenSecret), nil
			})

			if err != nil {
				http.Error(w, "Error parsing token", 403)
				return
			}

			// accept request
			next.ServeHTTP(w, r)
		}

	})
}

// Handle heartbeat request
func handleHeartbeatRequest(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "OK")
}

// Handle StatsD Count request
func handleCountRequest(w http.ResponseWriter, r *http.Request) {
	// get key
	vars := mux.Vars(r)
	key := vars["key"]

	// get delta
	var delta = 1
	deltaPostFormValue := r.PostFormValue("delta")
	if deltaPostFormValue != "" {
		var err error
		delta, err = strconv.Atoi(deltaPostFormValue)
		if err != nil {
			http.Error(w, "Invalid delta specified", 400)
		}
	}

	// get sample rate
	var sampleRate float64 = 1
	sampleRatePostFormValue := r.PostFormValue("sampleRate")
	if sampleRatePostFormValue != "" {
		var err error
		sampleRate, err = strconv.ParseFloat(sampleRatePostFormValue, 32)
		if err != nil {
			http.Error(w, "Invalid sample rate specified", 400)
		}

	}

	// send request
	statsdClient.Count(key, delta, float32(sampleRate))
}

// Handle StatsD Gauge request
func handleGaugeRequest(w http.ResponseWriter, r *http.Request) {
	// get key
	vars := mux.Vars(r)
	key := vars["key"]

	// get delta
	var value = 1
	valuePostFormValue := r.PostFormValue("value")
	if valuePostFormValue != "" {
		var err error
		value, err = strconv.Atoi(valuePostFormValue)
		if err != nil {
			http.Error(w, "Invalid delta specified", 400)
		}
	}

	// send request
	statsdClient.Gauge(key, value)
}

// Handle StatsD Timing request
func handleTimingRequest(w http.ResponseWriter, r *http.Request) {
	// get key
	vars := mux.Vars(r)
	key := vars["key"]

	// get timing
	time, err := strconv.ParseInt(r.PostFormValue("time"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid time specified", 400)
	}

	// get sample rate
	var sampleRate float64 = 1
	sampleRatePostFormValue := r.PostFormValue("sampleRate")
	if sampleRatePostFormValue != "" {
		var err error
		sampleRate, err = strconv.ParseFloat(sampleRatePostFormValue, 32)
		if err != nil {
			http.Error(w, "Invalid sample rate specified", 400)
		}
	}

	// send request
	statsdClient.Timing(key, time, float32(sampleRate))
}

// Handle StatsD Set request
func handleSetRequest(w http.ResponseWriter, r *http.Request) {
	// get key
	vars := mux.Vars(r)
	key := vars["key"]

	// get delta
	var value = 1
	valuePostFormValue := r.PostFormValue("value")
	if valuePostFormValue != "" {
		var err error
		value, err = strconv.Atoi(valuePostFormValue)
		if err != nil {
			http.Error(w, "Invalid delta specified", 400)
		}
	}

	// send request
	statsdClient.Set(key, value)
}
<commit_msg>rename value<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gorilla/mux"
	"github.com/sokil/go-statsd-client"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

const defaultHTTPHost = "127.0.0.1"
const defaultHTTPPort = 80

const defaultStatsDHost = "127.0.0.1"
const defaultStatsDPort = 8125

const jwtHeaderName = "X-JWT-Token"

// declare command line options
var httpHost = flag.String("http-host", defaultHTTPHost, "HTTP Host")
var httpPort = flag.Int("http-port", defaultHTTPPort, "HTTP Port")
var statsdHost = flag.String("statsd-host", defaultStatsDHost, "StatsD Host")
var statsdPort = flag.Int("statsd-port", defaultStatsDPort, "StatsD Port")
var tokenSecret = flag.String("jwt-secret", "", "Secret to encrypt JWT")
var verbose = flag.Bool("verbose", false, "Verbose")

// statsd client
var statsdClient *statsd.Client

func main() {
	// get flags
	flag.Parse()

	// configure verbosity of logging
	if *verbose == true {
		log.SetOutput(os.Stderr)
	} else {
		log.SetOutput(ioutil.Discard)
	}

	// create HTTP router
	router := mux.NewRouter().StrictSlash(true)

	// register http request handlers
	router.Handle(
		"/heartbeat",
		validateCORS(http.HandlerFunc(handleHeartbeatRequest)),
	).Methods("GET")

	router.Handle(
		"/count/{key}",
		validateCORS(validateJWT(http.HandlerFunc(handleCountRequest))),
	).Methods("POST")

	router.Handle(
		"/gauge/{key}",
		validateCORS(validateJWT(http.HandlerFunc(handleGaugeRequest))),
	).Methods("POST")

	router.Handle(
		"/timing/{key}",
		validateCORS(validateJWT(http.HandlerFunc(handleTimingRequest))),
	).Methods("POST")

	router.Handle(
		"/set/{key}",
		validateCORS(validateJWT(http.HandlerFunc(handleSetRequest))),
	).Methods("POST")

	// Create a new StatsD connection
	statsdClient = statsd.NewClient(*statsdHost, *statsdPort)
	statsdClient.SetAutoflush(true)
	statsdClient.Open()
	defer statsdClient.Close()

	// get server address to bind
	httpAddress := fmt.Sprintf("%s:%d", *httpHost, *httpPort)
	log.Printf("Starting HTTP server %s", httpAddress)

	// create http server
	s := &http.Server{
		Addr:           httpAddress,
		Handler:        router,
		ReadTimeout:    1 * time.Second,
		WriteTimeout:   1 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	// start http server
	err := s.ListenAndServe()
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}

// validate CORS headers
func validateCORS(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		origin := r.Header.Get("Origin")
		if origin != "" {
			w.Header().Add("Access-Control-Allow-Headers", jwtHeaderName + ", X-Requested-With, Origin, Accept, Content-Type, Authentication")
			w.Header().Add("Access-Control-Allow-Methods", "GET, POST, HEAD, OPTIONS")
			w.Header().Add("Access-Control-Allow-Origin", origin)
		}
		next.ServeHTTP(w, r)
	})
}

// validate JWT middleware
func validateJWT(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if *tokenSecret == "" {
			next.ServeHTTP(w, r)
		} else {
			// get JWT
			tokenString := r.Header.Get(jwtHeaderName)
			if tokenString == "" {
				http.Error(w, "Token not specified", 401)
				return
			}

			// parse JWT
			_, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
				}
				return []byte(*tokenSecret), nil
			})

			if err != nil {
				http.Error(w, "Error parsing token", 403)
				return
			}

			// accept request
			next.ServeHTTP(w, r)
		}

	})
}

// Handle heartbeat request
func handleHeartbeatRequest(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "OK")
}

// Handle StatsD Count request
func handleCountRequest(w http.ResponseWriter, r *http.Request) {
	// get key
	vars := mux.Vars(r)
	key := vars["key"]

	// get count value
	var value = 1
	valuePostFormValue := r.PostFormValue("value")
	if valuePostFormValue != "" {
		var err error
		value, err = strconv.Atoi(valuePostFormValue)
		if err != nil {
			http.Error(w, "Invalid value specified", 400)
		}
	}

	// get sample rate
	var sampleRate float64 = 1
	sampleRatePostFormValue := r.PostFormValue("sampleRate")
	if sampleRatePostFormValue != "" {
		var err error
		sampleRate, err = strconv.ParseFloat(sampleRatePostFormValue, 32)
		if err != nil {
			http.Error(w, "Invalid sample rate specified", 400)
		}

	}

	// send request
	statsdClient.Count(key, value, float32(sampleRate))
}

// Handle StatsD Gauge request
func handleGaugeRequest(w http.ResponseWriter, r *http.Request) {
	// get key
	vars := mux.Vars(r)
	key := vars["key"]

	// get delta
	var value = 1
	valuePostFormValue := r.PostFormValue("value")
	if valuePostFormValue != "" {
		var err error
		value, err = strconv.Atoi(valuePostFormValue)
		if err != nil {
			http.Error(w, "Invalid delta specified", 400)
		}
	}

	// send request
	statsdClient.Gauge(key, value)
}

// Handle StatsD Timing request
func handleTimingRequest(w http.ResponseWriter, r *http.Request) {
	// get key
	vars := mux.Vars(r)
	key := vars["key"]

	// get timing
	time, err := strconv.ParseInt(r.PostFormValue("time"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid time specified", 400)
	}

	// get sample rate
	var sampleRate float64 = 1
	sampleRatePostFormValue := r.PostFormValue("sampleRate")
	if sampleRatePostFormValue != "" {
		var err error
		sampleRate, err = strconv.ParseFloat(sampleRatePostFormValue, 32)
		if err != nil {
			http.Error(w, "Invalid sample rate specified", 400)
		}
	}

	// send request
	statsdClient.Timing(key, time, float32(sampleRate))
}

// Handle StatsD Set request
func handleSetRequest(w http.ResponseWriter, r *http.Request) {
	// get key
	vars := mux.Vars(r)
	key := vars["key"]

	// get delta
	var value = 1
	valuePostFormValue := r.PostFormValue("value")
	if valuePostFormValue != "" {
		var err error
		value, err = strconv.Atoi(valuePostFormValue)
		if err != nil {
			http.Error(w, "Invalid delta specified", 400)
		}
	}

	// send request
	statsdClient.Set(key, value)
}
<|endoftext|>


<commit_before>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
)

var (
	flagAll    = flag.Bool("all", false, "build for all GOOS/GOARCH platforms")
	flagBase   = flag.String("base", "", "revision to compare against")
	flagRace   = flag.Bool("race", false, "build with -race")
	flagRemake = flag.Bool("remake", false, "build new toolchain with make.bash instead of go install std cmd")
	flagWork   = flag.Bool("work", false, "build with -work")
)

func usage() {
	fmt.Fprintln(os.Stderr, "usage: toolstash-check [options] [commit]")
	flag.PrintDefaults()
}

func main() {
	flag.Usage = usage
	flag.Parse()

	if *flagAll {
		if *flagRace {
			log.Fatal("-all and -race are incompatible")
			os.Exit(2)
		}
		if *flagWork {
			log.Fatal("-all and -work are incompatible")
			os.Exit(2)
		}
	}

	spec := "HEAD"
	switch flag.NArg() {
	case 0:
	case 1:
		spec = flag.Arg(0)
	default:
		flag.Usage()
		os.Exit(2)
	}

	goroot := runtime.GOROOT()

	commit, err := revParse(goroot, spec)
	must(err)

	base := *flagBase
	if base == "" {
		base = commit + "^"
	}
	base, err = revParse(goroot, base)
	must(err)

	pkg, err := build.Import("golang.org/x/tools/cmd/toolstash", "", build.FindOnly)
	must(err)

	tmpdir, err := ioutil.TempDir("", "toolstash-check-")
	must(err)
	defer os.RemoveAll(tmpdir)

	tmproot := filepath.Join(tmpdir, "go")
	must(command("git", "clone", goroot, tmproot).Run())

	cmd := command("git", "checkout", base)
	cmd.Dir = tmproot
	must(cmd.Run())

	must(ioutil.WriteFile(filepath.Join(tmproot, "VERSION"), []byte("devel"), 0666))

	cmd = command("./make.bash")
	cmd.Dir = filepath.Join(tmproot, "src")
	must(cmd.Run())

	envPath := os.Getenv("PATH")
	if envPath != "" {
		envPath = string(os.PathListSeparator) + envPath
	}
	must(os.Setenv("PATH", filepath.Join(tmproot, "bin")+envPath))
	must(os.Setenv("GOROOT", tmproot))

	must(command("toolstash", "save").Run())

	cmd = command("git", "checkout", commit)
	cmd.Dir = tmproot
	must(cmd.Run())

	if *flagRemake {
		cmd = command("./make.bash")
		cmd.Dir = filepath.Join(tmproot, "src")
	} else {
		cmd = command("go", "install", "std", "cmd")
	}
	must(cmd.Run())

	if *flagAll {
		must(command(filepath.Join(pkg.Dir, "buildall")).Run())
	} else {
		buildArgs := []string{"build", "-a"}
		if *flagRace {
			buildArgs = append(buildArgs, "-race")
		}
		if *flagWork {
			buildArgs = append(buildArgs, "-work")
		}
		buildArgs = append(buildArgs, "-toolexec", "toolstash -cmp", "std", "cmd")
		must(command("go", buildArgs...).Run())
	}

	revs := commit
	if *flagBase != "" {
		revs = base + ".." + commit
	}
	fmt.Println("toolstash-check passed for", revs)
}

// revParse runs "git rev-parse $spec" in $GOROOT to parse a Git
// revision specifier.
func revParse(dir, spec string) (string, error) {
	cmd := exec.Command("git", "rev-parse", "--short", spec)
	cmd.Dir = dir
	out, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSuffix(string(out), "\n"), nil
}

func command(name string, args ...string) *exec.Cmd {
	cmd := exec.Command(name, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

func must(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>toolstash-check: add -gcflags flag<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
)

var (
	flagAll     = flag.Bool("all", false, "build for all GOOS/GOARCH platforms")
	flagBase    = flag.String("base", "", "revision to compare against")
	flagGcflags = flag.String("gcflags", "", "additional flags to pass to compile")
	flagRace    = flag.Bool("race", false, "build with -race")
	flagRemake  = flag.Bool("remake", false, "build new toolchain with make.bash instead of go install std cmd")
	flagWork    = flag.Bool("work", false, "build with -work")
)

func usage() {
	fmt.Fprintln(os.Stderr, "usage: toolstash-check [options] [commit]")
	flag.PrintDefaults()
}

func main() {
	flag.Usage = usage
	flag.Parse()

	if *flagAll {
		if *flagRace {
			log.Fatal("-all and -race are incompatible")
			os.Exit(2)
		}
		if *flagWork {
			log.Fatal("-all and -work are incompatible")
			os.Exit(2)
		}
		if *flagGcflags != "" {
			log.Fatal("-all and -gcflags are incompatible")
			os.Exit(2)
		}
	}

	spec := "HEAD"
	switch flag.NArg() {
	case 0:
	case 1:
		spec = flag.Arg(0)
	default:
		flag.Usage()
		os.Exit(2)
	}

	goroot := runtime.GOROOT()

	commit, err := revParse(goroot, spec)
	must(err)

	base := *flagBase
	if base == "" {
		base = commit + "^"
	}
	base, err = revParse(goroot, base)
	must(err)

	pkg, err := build.Import("golang.org/x/tools/cmd/toolstash", "", build.FindOnly)
	must(err)

	tmpdir, err := ioutil.TempDir("", "toolstash-check-")
	must(err)
	defer os.RemoveAll(tmpdir)

	tmproot := filepath.Join(tmpdir, "go")
	must(command("git", "clone", goroot, tmproot).Run())

	cmd := command("git", "checkout", base)
	cmd.Dir = tmproot
	must(cmd.Run())

	must(ioutil.WriteFile(filepath.Join(tmproot, "VERSION"), []byte("devel"), 0666))

	cmd = command("./make.bash")
	cmd.Dir = filepath.Join(tmproot, "src")
	must(cmd.Run())

	envPath := os.Getenv("PATH")
	if envPath != "" {
		envPath = string(os.PathListSeparator) + envPath
	}
	must(os.Setenv("PATH", filepath.Join(tmproot, "bin")+envPath))
	must(os.Setenv("GOROOT", tmproot))

	must(command("toolstash", "save").Run())

	cmd = command("git", "checkout", commit)
	cmd.Dir = tmproot
	must(cmd.Run())

	if *flagRemake {
		cmd = command("./make.bash")
		cmd.Dir = filepath.Join(tmproot, "src")
	} else {
		cmd = command("go", "install", "std", "cmd")
	}
	must(cmd.Run())

	if *flagAll {
		must(command(filepath.Join(pkg.Dir, "buildall")).Run())
	} else {
		buildArgs := []string{"build", "-a"}
		if *flagRace {
			buildArgs = append(buildArgs, "-race")
		}
		if *flagWork {
			buildArgs = append(buildArgs, "-work")
		}
		if *flagGcflags != "" {
			buildArgs = append(buildArgs, "-gcflags", *flagGcflags)
		}
		buildArgs = append(buildArgs, "-toolexec", "toolstash -cmp", "std", "cmd")
		must(command("go", buildArgs...).Run())
	}

	revs := commit
	if *flagBase != "" {
		revs = base + ".." + commit
	}
	fmt.Println("toolstash-check passed for", revs)
}

// revParse runs "git rev-parse $spec" in $GOROOT to parse a Git
// revision specifier.
func revParse(dir, spec string) (string, error) {
	cmd := exec.Command("git", "rev-parse", "--short", spec)
	cmd.Dir = dir
	out, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSuffix(string(out), "\n"), nil
}

func command(name string, args ...string) *exec.Cmd {
	cmd := exec.Command(name, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

func must(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package scribble

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/nanobox-core/utils"
)

const (
	DefaultDir = "./tmp/db"
)

//
type (

	// Driver represents
	Driver struct {
		channels map[string]chan int
		dir      string
	}

	// Transaction represents
	Transaction struct {
		Action     string
		Collection string
		Resource   string
		Container  interface{}
	}
)

//
var (
	debugging bool
)

// Init
func (d *Driver) Init(opts map[string]string) int {
	fmt.Printf("Creating database directory at '%v'...\n", opts["db_dir"])

	debugging = (opts["debugging"] == "true")

	d.dir = opts["db_dir"]

	//
	d.channels = make(map[string]chan int)

	// make a ping channel
	ping := make(chan int)
	d.channels["ping"] = ping

	//
	if err := mkDir(d.dir); err != nil {
		fmt.Printf("Unable to create dir '%v': %v", d.dir, err)
		return 1
	}

	//
	return 0
}

// Transact
func (d *Driver) Transact(trans Transaction) {

	//
	done := d.getOrCreateChan(trans.Collection)

	//
	switch trans.Action {
	case "write":
		go d.write(trans, done)
	case "read":
		go d.read(trans, done)
	case "readall":
		go d.readAll(trans, done)
	case "delete":
		go d.delete(trans, done)
	default:
		fmt.Println("Unsupported action ", trans.Action)
	}

	// wait...
	<-done
}

// private

// write
func (d *Driver) write(trans Transaction, done chan<- int) {

	//
	dir := d.dir + "/" + trans.Collection

	//
	if err := mkDir(dir); err != nil {
		fmt.Println("Unable to create dir '%v': %v", dir, err)
		os.Exit(1)
	}

	//
	file, err := os.Create(dir + "/" + trans.Resource)
	if err != nil {
		fmt.Printf("Unable to create file %v/%v: %v", trans.Collection, trans.Resource, err)
		os.Exit(1)
	}

	defer file.Close()

	//
	b := utils.ToJSONIndent(trans.Container)

	_, err = file.WriteString(string(b))
	if err != nil {
		fmt.Printf("Unable to write to file %v: %v", trans.Resource, err)
		os.Exit(1)
	}

	// release...
	done <- 0
}

// read
func (d *Driver) read(trans Transaction, done chan<- int) interface{} {

	dir := d.dir + "/" + trans.Collection

	b, err := ioutil.ReadFile(dir + "/" + trans.Resource)
	if err != nil {
		fmt.Printf("Unable to read file %v/%v: %v", trans.Collection, trans.Resource, err)
		os.Exit(1)
	}

	if err := utils.FromJSON(b, trans.Container); err != nil {
		panic(err)
	}

	// release...
	done <- 0

	return trans.Container
}

// readAll
func (d *Driver) readAll(trans Transaction, done chan<- int) {

	dir := d.dir + "/" + trans.Collection

	//
	files, err := ioutil.ReadDir(dir)

	// if there is an error here it just means there are no evars so dont do anything
	if err != nil {
	}

	var f []string

	for _, file := range files {
		b, err := ioutil.ReadFile(dir + "/" + file.Name())
		if err != nil {
			panic(err)
		}

		f = append(f, string(b))
	}

	//
	if err := utils.FromJSON([]byte("["+strings.Join(f, ",")+"]"), trans.Container); err != nil {
		panic(err)
	}

	// release...
	done <- 0
}

// delete
func (d *Driver) delete(trans Transaction, done chan<- int) {

	dir := d.dir + "/" + trans.Collection

	err := os.Remove(dir + "/" + trans.Resource)
	if err != nil {
		fmt.Printf("Unable to delete file %v/%v: %v", trans.Collection, trans.Resource, err)
		os.Exit(1)
	}

	// release...
	done <- 0
}

// helpers

// getChan
func (d *Driver) getOrCreateChan(channel string) chan int {

	c, ok := d.channels[channel]

	// if the chan doesn't exist make it
	if !ok {
		d.channels[channel] = make(chan int)
		return d.channels[channel]
	}

	return c
}

// mkDir
func mkDir(d string) error {

	//
	dir, _ := os.Stat(d)

	if dir == nil {
		err := os.MkdirAll(d, 0755)
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>adding some consts<commit_after>package scribble

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/nanobox-core/utils"
)

const (
	DefaultDir = "./tmp/db"
	Version    = "0.0.1"
)

//
type (

	// Driver represents
	Driver struct {
		channels map[string]chan int
		dir      string
	}

	// Transaction represents
	Transaction struct {
		Action     string
		Collection string
		Resource   string
		Container  interface{}
	}
)

//
var (
	debugging bool
)

// Init
func (d *Driver) Init(opts map[string]string) int {
	fmt.Printf("Creating database directory at '%v'...\n", opts["db_dir"])

	debugging = (opts["debugging"] == "true")

	d.dir = opts["db_dir"]

	//
	d.channels = make(map[string]chan int)

	// make a ping channel
	ping := make(chan int)
	d.channels["ping"] = ping

	//
	if err := mkDir(d.dir); err != nil {
		fmt.Printf("Unable to create dir '%v': %v", d.dir, err)
		return 1
	}

	//
	return 0
}

// Transact
func (d *Driver) Transact(trans Transaction) {

	//
	done := d.getOrCreateChan(trans.Collection)

	//
	switch trans.Action {
	case "write":
		go d.write(trans, done)
	case "read":
		go d.read(trans, done)
	case "readall":
		go d.readAll(trans, done)
	case "delete":
		go d.delete(trans, done)
	default:
		fmt.Println("Unsupported action ", trans.Action)
	}

	// wait...
	<-done
}

// private

// write
func (d *Driver) write(trans Transaction, done chan<- int) {

	//
	dir := d.dir + "/" + trans.Collection

	//
	if err := mkDir(dir); err != nil {
		fmt.Println("Unable to create dir '%v': %v", dir, err)
		os.Exit(1)
	}

	//
	file, err := os.Create(dir + "/" + trans.Resource)
	if err != nil {
		fmt.Printf("Unable to create file %v/%v: %v", trans.Collection, trans.Resource, err)
		os.Exit(1)
	}

	defer file.Close()

	//
	b := utils.ToJSONIndent(trans.Container)

	_, err = file.WriteString(string(b))
	if err != nil {
		fmt.Printf("Unable to write to file %v: %v", trans.Resource, err)
		os.Exit(1)
	}

	// release...
	done <- 0
}

// read
func (d *Driver) read(trans Transaction, done chan<- int) interface{} {

	dir := d.dir + "/" + trans.Collection

	b, err := ioutil.ReadFile(dir + "/" + trans.Resource)
	if err != nil {
		fmt.Printf("Unable to read file %v/%v: %v", trans.Collection, trans.Resource, err)
		os.Exit(1)
	}

	if err := utils.FromJSON(b, trans.Container); err != nil {
		panic(err)
	}

	// release...
	done <- 0

	return trans.Container
}

// readAll
func (d *Driver) readAll(trans Transaction, done chan<- int) {

	dir := d.dir + "/" + trans.Collection

	//
	files, err := ioutil.ReadDir(dir)

	// if there is an error here it just means there are no evars so dont do anything
	if err != nil {
	}

	var f []string

	for _, file := range files {
		b, err := ioutil.ReadFile(dir + "/" + file.Name())
		if err != nil {
			panic(err)
		}

		f = append(f, string(b))
	}

	//
	if err := utils.FromJSON([]byte("["+strings.Join(f, ",")+"]"), trans.Container); err != nil {
		panic(err)
	}

	// release...
	done <- 0
}

// delete
func (d *Driver) delete(trans Transaction, done chan<- int) {

	dir := d.dir + "/" + trans.Collection

	err := os.Remove(dir + "/" + trans.Resource)
	if err != nil {
		fmt.Printf("Unable to delete file %v/%v: %v", trans.Collection, trans.Resource, err)
		os.Exit(1)
	}

	// release...
	done <- 0
}

// helpers

// getChan
func (d *Driver) getOrCreateChan(channel string) chan int {

	c, ok := d.channels[channel]

	// if the chan doesn't exist make it
	if !ok {
		d.channels[channel] = make(chan int)
		return d.channels[channel]
	}

	return c
}

// mkDir
func mkDir(d string) error {

	//
	dir, _ := os.Stat(d)

	if dir == nil {
		err := os.MkdirAll(d, 0755)
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>/*
* IBM jStart team cf download cli Plugin
* A plugin for downloading contents of a running app's file directory
*
* Authors: Miguel Clement, Jake Eden
* Date: 3/5/2015
*
* for cross platform compiling use gox (https://github.com/mitchellh/gox)
* gox compile command: gox -output="binaries/{{.OS}}/{{.Arch}}/cf-download" -osarch="linux/amd64 darwin/amd64 windows/amd64"
 */

package main

import (
	"flag"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/cloudfoundry/cli/plugin"
	"github.com/ibmjstart/cf-download/cmd_exec"
	"github.com/ibmjstart/cf-download/dir_parser"
	"github.com/ibmjstart/cf-download/downloader"
	"github.com/ibmjstart/cf-download/filter"
	"github.com/mgutz/ansi"
)

/*
*	This is the struct implementing the interface defined by the core CLI. It can
*	be found at  "github.com/cloudfoundry/cli/plugin/plugin.go"
 */
type DownloadPlugin struct{}

// contains flag values
type flagVal struct {
	Omit_flag      string
	OverWrite_flag bool
	Instance_flag  string
	Verbose_flag   bool
}

var (
	rootWorkingDirectory string
	appName              string
	filesDownloaded      int
	failedDownloads      []string
	parser               dir_parser.Parser
	dloader              downloader.Downloader
)

// global wait group for all download threads
var wg sync.WaitGroup

/*
*	This function must be implemented by any plugin because it is part of the
*	plugin interface defined by the core CLI.
*
*	Run(....) is the entry point when the core CLI is invoking a command defined
*	by a plugin. The first parameter, plugin.CliConnection, is a struct that can
*	be used to invoke cli commands. The second paramter, args, is a slice of
*	strings. args[0] will be the name of the command, and will be followed by
*	any additional arguments a cli user typed in.
*
*	Any error handling should be handled with the plugin itself (this means printing
*	user facing errors). The CLI will exit 0 if the plugin exits 0 and will exit
*	1 should the plugin exits nonzero.
 */

func (c *DownloadPlugin) Run(cliConnection plugin.CliConnection, args []string) {
	if args[0] != "download" {
		os.Exit(0)
	}

	// start time for download timer
	start := time.Now()

	// disables ansi text color on windows
	onWindows := IsWindows()

	if len(args) < 2 {
		fmt.Println(createMessage("\nError: Missing App Name", "red+b", onWindows))
		printHelp()
		os.Exit(1)
	}

	flagVals := ParseFlags(args)

	// flag variables
	filterList := filter.GetFilterList(flagVals.Omit_flag, flagVals.Verbose_flag) // get list of things to not download

	workingDir, err := os.Getwd()
	check(err, "Called by: Getwd")
	rootWorkingDirectory, startingPath := GetDirectoryContext(workingDir, args)

	// ensure cf_trace is disabled, otherwise parsing breaks
	if os.Getenv("CF_TRACE") == "true" {
		fmt.Println("\nError: environment variable CF_TRACE is set to true. This prevents download from succeeding.")
		return
	}

	// prevent overwriting files
	if Exists(rootWorkingDirectory) && flagVals.OverWrite_flag == false {
		fmt.Println("\nError: destination path", rootWorkingDirectory, "already Exists and is not an empty directory.\n\nDelete it or use 'cf download APP_NAME --overwrite'")
		os.Exit(1)
	}

	cmdExec := cmd_exec.NewCmdExec()
	parser = dir_parser.NewParser(cmdExec, appName, flagVals.Instance_flag, onWindows, flagVals.Verbose_flag)
	dloader = downloader.NewDownloader(cmdExec, &wg, appName, flagVals.Instance_flag, rootWorkingDirectory, flagVals.Verbose_flag, onWindows)

	// parse the directory
	files, dirs := parser.ExecParseDir(startingPath)

	// stop consoleWriter
	quit := make(chan int)

	// disable consoleWriter if verbose
	if flagVals.Verbose_flag == false {
		go consoleWriter(quit)
	}

	// Start the download
	wg.Add(1)
	dloader.Download(files, dirs, startingPath, rootWorkingDirectory, filterList)

	// Wait for download goRoutines
	wg.Wait()

	// stop console writer
	if flagVals.Verbose_flag == false {
		quit <- 0
	}

	getFailedDownloads()
	PrintCompletionInfo(start, onWindows)

}

/*
*	-----------------------------------------------------------------------------------------------
* 	------------------------------------- Helper Functions ----------------------------------------
* 	-----------------------------------------------------------------------------------------------
 */

func getFailedDownloads() {
	failedDownloads = append(parser.GetFailedDownloads(), dloader.GetFailedDownloads()...)
}

func GetDirectoryContext(workingDir string, copyOfArgs []string) (string, string) {
	rootWorkingDirectory := workingDir + "/" + appName + "-download/"

	// append path if provided as arguement
	startingPath := "/"
	if len(copyOfArgs) > 2 && !strings.HasPrefix(copyOfArgs[2], "-") {
		startingPath = copyOfArgs[2]
		if !strings.HasSuffix(startingPath, "/") {
			startingPath += "/"
		}
		if strings.HasPrefix(startingPath, "/") {
			startingPath = strings.TrimPrefix(startingPath, "/")
		}
		rootWorkingDirectory += startingPath
		startingPath = "/" + startingPath
	}

	return rootWorkingDirectory, startingPath
}

func ParseFlags(args []string) flagVal {

	// Create flagSet f1
	f1 := flag.NewFlagSet("f1", flag.ContinueOnError)

	// Create flags
	omitp := f1.String("omit", "", "--omit path/to/some/file")
	overWritep := f1.Bool("overwrite", false, "--overwrite")
	instancep := f1.Int("i", 0, "-i [instanceNum]")
	verbosep := f1.Bool("verbose", false, "--verbose")

	var err error
	if len(args) > 2 && !strings.HasPrefix(args[2], "-") { // if there is a path as in 'cf download path' vs. 'cf download'
		err = f1.Parse(args[3:])
	} else {
		err = f1.Parse(args[2:])
	}

	// check for misplaced flags
	appName = args[1]
	if strings.HasPrefix(appName, "-") || strings.HasPrefix(appName, "--") {
		fmt.Println(createMessage("\nError: App name begins with '-' or '--'. correct flag usage: 'cf download APP_NAME [--flags]'", "red+b", IsWindows()))
		printHelp()
		os.Exit(1)
	}

	// Check for parsing errors, display usage
	if err != nil {
		fmt.Println("\nError: ", err, "\n")
		printHelp()
		os.Exit(1)
	}

	flagVals := flagVal{
		Omit_flag:      string(*omitp),
		OverWrite_flag: bool(*overWritep),
		Instance_flag:  strconv.Itoa(*instancep),
		Verbose_flag:   *verbosep,
	}

	return flagVals
}

/*
*	consoleWriter prints the current number of files downloaded. It is polled every 350 milleseconds
* 	disabled if using verbose flag.
 */
func consoleWriter(quit chan int) {
	count := 0
	for {
		filesDownloaded := dloader.GetFilesDownloadedCount()
		select {
		case <-quit:
			fmt.Printf("\rFiles completed: %d ", filesDownloaded)
			return
		default:
			switch count = (count + 1) % 4; count {
			case 0:
				fmt.Printf("\rFiles completed: %d \\ ", filesDownloaded)
			case 1:
				fmt.Printf("\rFiles completed: %d | ", filesDownloaded)
			case 2:
				fmt.Printf("\rFiles completed: %d / ", filesDownloaded)
			case 3:
				fmt.Printf("\rFiles completed: %d --", filesDownloaded)
			}
			time.Sleep(350 * time.Millisecond)
		}
	}
}

// prints all the info you see at program finish
func PrintCompletionInfo(start time.Time, onWindows bool) {
	// let user know if any files were inaccessible
	if len(failedDownloads) == 1 {
		fmt.Println("")
		fmt.Println(len(failedDownloads), "file or directory was not downloaded (permissions issue or corrupt):")
		PrintSlice(failedDownloads)
	} else if len(failedDownloads) > 1 {
		fmt.Println("")
		fmt.Println(len(failedDownloads), "files or directories were not downloaded (permissions issue or corrupt):")
		PrintSlice(failedDownloads)
	}

	if len(failedDownloads) > 100 {
		fmt.Println("\nYou had over 100 failed downloads, we highly recommend you omit the failed file'sopen parent directories using the omit flag.\n")
	}

	// display runtime
	elapsed := time.Since(start)
	elapsedString := strings.Split(elapsed.String(), ".")[0]
	elapsedString = strings.TrimSuffix(elapsedString, ".") + "s"
	fmt.Println("\nDownload time: " + elapsedString)

	msg := ansi.Color(appName+" Successfully Downloaded!", "green+b")
	if onWindows == true {
		msg = "Successfully Downloaded!"
	}
	fmt.Println(msg)
}

// error check function
func check(e error, errMsg string) {
	if e != nil {
		fmt.Println("\nError: ", e)
		if errMsg != "" {
			fmt.Println("Message: ", errMsg)
		}
		os.Exit(1)
	}
}

// prints slices in readable format
func PrintSlice(slice []string) error {
	for index, val := range slice {
		fmt.Println(index+1, ": ", val)
	}
	return nil
}

func IsWindows() bool {
	return runtime.GOOS == "windows"
}

// Exists returns whether the given file or directory Exists or not
func Exists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	check(err, "Error E0.")
	return false
}

func createMessage(message, color string, onWindows bool) string {
	errmsg := ansi.Color(message, color)
	if onWindows == true {
		errmsg = message
	}

	return errmsg
}

func printHelp() {
	cmd := exec.Command("cf", "help", "download")
	output, _ := cmd.CombinedOutput()
	fmt.Printf("%s", output)
}

/*
*	This function must be implemented as part of the	plugin interface
*	defined by the core CLI.
*
*	GetMetadata() returns a PluginMetadata struct. The first field, Name,
*	determines the name of the plugin which should generally be without spaces.
*	If there are spaces in the name a user will need to properly quote the name
*	during uninstall otherwise the name will be treated as seperate arguments.
*	The second value is a slice of Command structs. Our slice only contains one
*	Command Struct, but could contain any number of them. The first field Name
*	defines the command `cf basic-plugin-command` once installed into the CLI. The
*	second field, HelpText, is used by the core CLI to display help information
*	to the user in the core commands `cf help`, `cf`, or `cf -h`.
 */
func (c *DownloadPlugin) GetMetadata() plugin.PluginMetadata {
	return plugin.PluginMetadata{
		Name: "download",
		Version: plugin.VersionType{
			Major: 0,
			Minor: 1,
			Build: 4,
		},
		Commands: []plugin.Command{
			plugin.Command{
				Name:     "download",
				HelpText: "Download contents of a running app's file directory",

				// UsageDetails is optional
				// It is used to show help of usage of each command
				UsageDetails: plugin.Usage{
					Usage: "cf download APP_NAME [PATH] [--overwrite] [--verbose] [--omit ommited_paths] [-i instance_num]",
					Options: map[string]string{
						"overwrite":             "Overwrite existing files",
						"verbose":               "Verbose output",
						"omit \"path/to/file\"": "Omit directories or files (delimited by semicolons)",
						"i": "Instance",
					},
				},
			},
		},
	}
}

/*
* Unlike most Go programs, the `Main()` function will not be used to run all of the
* commands provided in your plugin. Main will be used to initialize the plugin
* process, as well as any dependencies you might require for your
* plugin.
 */
func main() {

	// Any initialization for your plugin can be handled here

	// Note: The plugin's main() method is invoked at install time to collect
	// metadata. The plugin will exit 0 and the Run([]string) method will not be
	// invoked.

	// About debug Locally:
	// The plugin interface hides panics from stdout, so in order to get panic info,
	// you can run this plugin outside of the plugin architecture by setting debuglocally = true.

	// example usage for locall run: go run main.go download APP_NAME --overwrite 2> err.txt
	// note the lack of 'cf'

	debugLocally := true
	if debugLocally {
		var run DownloadPlugin
		run.Run(nil, os.Args[1:])
	} else {
		plugin.Start(new(DownloadPlugin))
	}

	// Plugin code should be written in the Run([]string) method,
	// ensuring the plugin environment is bootstrapped.
}
<commit_msg>release ready, changed to version1.0.0<commit_after>/*
* IBM jStart team cf download cli Plugin
* A plugin for downloading contents of a running app's file directory
*
* Authors: Miguel Clement, Jake Eden
* Date: 3/5/2015
*
* for cross platform compiling use gox (https://github.com/mitchellh/gox)
* gox compile command: gox -output="binaries/{{.OS}}/{{.Arch}}/cf-download" -osarch="linux/amd64 darwin/amd64 windows/amd64"
 */

package main

import (
	"flag"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/cloudfoundry/cli/plugin"
	"github.com/ibmjstart/cf-download/cmd_exec"
	"github.com/ibmjstart/cf-download/dir_parser"
	"github.com/ibmjstart/cf-download/downloader"
	"github.com/ibmjstart/cf-download/filter"
	"github.com/mgutz/ansi"
)

/*
*	This is the struct implementing the interface defined by the core CLI. It can
*	be found at  "github.com/cloudfoundry/cli/plugin/plugin.go"
 */
type DownloadPlugin struct{}

// contains flag values
type flagVal struct {
	Omit_flag      string
	OverWrite_flag bool
	Instance_flag  string
	Verbose_flag   bool
}

var (
	rootWorkingDirectory string
	appName              string
	filesDownloaded      int
	failedDownloads      []string
	parser               dir_parser.Parser
	dloader              downloader.Downloader
)

// global wait group for all download threads
var wg sync.WaitGroup

/*
*	This function must be implemented by any plugin because it is part of the
*	plugin interface defined by the core CLI.
*
*	Run(....) is the entry point when the core CLI is invoking a command defined
*	by a plugin. The first parameter, plugin.CliConnection, is a struct that can
*	be used to invoke cli commands. The second paramter, args, is a slice of
*	strings. args[0] will be the name of the command, and will be followed by
*	any additional arguments a cli user typed in.
*
*	Any error handling should be handled with the plugin itself (this means printing
*	user facing errors). The CLI will exit 0 if the plugin exits 0 and will exit
*	1 should the plugin exits nonzero.
 */

func (c *DownloadPlugin) Run(cliConnection plugin.CliConnection, args []string) {
	if args[0] != "download" {
		os.Exit(0)
	}

	// start time for download timer
	start := time.Now()

	// disables ansi text color on windows
	onWindows := IsWindows()

	if len(args) < 2 {
		fmt.Println(createMessage("\nError: Missing App Name", "red+b", onWindows))
		printHelp()
		os.Exit(1)
	}

	flagVals := ParseFlags(args)

	// flag variables
	filterList := filter.GetFilterList(flagVals.Omit_flag, flagVals.Verbose_flag) // get list of things to not download

	workingDir, err := os.Getwd()
	check(err, "Called by: Getwd")
	rootWorkingDirectory, startingPath := GetDirectoryContext(workingDir, args)

	// ensure cf_trace is disabled, otherwise parsing breaks
	if os.Getenv("CF_TRACE") == "true" {
		fmt.Println("\nError: environment variable CF_TRACE is set to true. This prevents download from succeeding.")
		return
	}

	// prevent overwriting files
	if Exists(rootWorkingDirectory) && flagVals.OverWrite_flag == false {
		fmt.Println("\nError: destination path", rootWorkingDirectory, "already Exists and is not an empty directory.\n\nDelete it or use 'cf download APP_NAME --overwrite'")
		os.Exit(1)
	}

	cmdExec := cmd_exec.NewCmdExec()
	parser = dir_parser.NewParser(cmdExec, appName, flagVals.Instance_flag, onWindows, flagVals.Verbose_flag)
	dloader = downloader.NewDownloader(cmdExec, &wg, appName, flagVals.Instance_flag, rootWorkingDirectory, flagVals.Verbose_flag, onWindows)

	// parse the directory
	files, dirs := parser.ExecParseDir(startingPath)

	// stop consoleWriter
	quit := make(chan int)

	// disable consoleWriter if verbose
	if flagVals.Verbose_flag == false {
		go consoleWriter(quit)
	}

	// Start the download
	wg.Add(1)
	dloader.Download(files, dirs, startingPath, rootWorkingDirectory, filterList)

	// Wait for download goRoutines
	wg.Wait()

	// stop console writer
	if flagVals.Verbose_flag == false {
		quit <- 0
	}

	getFailedDownloads()
	PrintCompletionInfo(start, onWindows)

}

/*
*	-----------------------------------------------------------------------------------------------
* 	------------------------------------- Helper Functions ----------------------------------------
* 	-----------------------------------------------------------------------------------------------
 */

func getFailedDownloads() {
	failedDownloads = append(parser.GetFailedDownloads(), dloader.GetFailedDownloads()...)
}

func GetDirectoryContext(workingDir string, copyOfArgs []string) (string, string) {
	rootWorkingDirectory := workingDir + "/" + appName + "-download/"

	// append path if provided as arguement
	startingPath := "/"
	if len(copyOfArgs) > 2 && !strings.HasPrefix(copyOfArgs[2], "-") {
		startingPath = copyOfArgs[2]
		if !strings.HasSuffix(startingPath, "/") {
			startingPath += "/"
		}
		if strings.HasPrefix(startingPath, "/") {
			startingPath = strings.TrimPrefix(startingPath, "/")
		}
		rootWorkingDirectory += startingPath
		startingPath = "/" + startingPath
	}

	return rootWorkingDirectory, startingPath
}

func ParseFlags(args []string) flagVal {

	// Create flagSet f1
	f1 := flag.NewFlagSet("f1", flag.ContinueOnError)

	// Create flags
	omitp := f1.String("omit", "", "--omit path/to/some/file")
	overWritep := f1.Bool("overwrite", false, "--overwrite")
	instancep := f1.Int("i", 0, "-i [instanceNum]")
	verbosep := f1.Bool("verbose", false, "--verbose")

	var err error
	if len(args) > 2 && !strings.HasPrefix(args[2], "-") { // if there is a path as in 'cf download path' vs. 'cf download'
		err = f1.Parse(args[3:])
	} else {
		err = f1.Parse(args[2:])
	}

	// check for misplaced flags
	appName = args[1]
	if strings.HasPrefix(appName, "-") || strings.HasPrefix(appName, "--") {
		fmt.Println(createMessage("\nError: App name begins with '-' or '--'. correct flag usage: 'cf download APP_NAME [--flags]'", "red+b", IsWindows()))
		printHelp()
		os.Exit(1)
	}

	// Check for parsing errors, display usage
	if err != nil {
		fmt.Println("\nError: ", err, "\n")
		printHelp()
		os.Exit(1)
	}

	flagVals := flagVal{
		Omit_flag:      string(*omitp),
		OverWrite_flag: bool(*overWritep),
		Instance_flag:  strconv.Itoa(*instancep),
		Verbose_flag:   *verbosep,
	}

	return flagVals
}

/*
*	consoleWriter prints the current number of files downloaded. It is polled every 350 milleseconds
* 	disabled if using verbose flag.
 */
func consoleWriter(quit chan int) {
	count := 0
	for {
		filesDownloaded := dloader.GetFilesDownloadedCount()
		select {
		case <-quit:
			fmt.Printf("\rFiles completed: %d ", filesDownloaded)
			return
		default:
			switch count = (count + 1) % 4; count {
			case 0:
				fmt.Printf("\rFiles completed: %d \\ ", filesDownloaded)
			case 1:
				fmt.Printf("\rFiles completed: %d | ", filesDownloaded)
			case 2:
				fmt.Printf("\rFiles completed: %d / ", filesDownloaded)
			case 3:
				fmt.Printf("\rFiles completed: %d --", filesDownloaded)
			}
			time.Sleep(350 * time.Millisecond)
		}
	}
}

// prints all the info you see at program finish
func PrintCompletionInfo(start time.Time, onWindows bool) {
	// let user know if any files were inaccessible
	if len(failedDownloads) == 1 {
		fmt.Println("")
		fmt.Println(len(failedDownloads), "file or directory was not downloaded (permissions issue or corrupt):")
		PrintSlice(failedDownloads)
	} else if len(failedDownloads) > 1 {
		fmt.Println("")
		fmt.Println(len(failedDownloads), "files or directories were not downloaded (permissions issue or corrupt):")
		PrintSlice(failedDownloads)
	}

	if len(failedDownloads) > 100 {
		fmt.Println("\nYou had over 100 failed downloads, we highly recommend you omit the failed file'sopen parent directories using the omit flag.\n")
	}

	// display runtime
	elapsed := time.Since(start)
	elapsedString := strings.Split(elapsed.String(), ".")[0]
	elapsedString = strings.TrimSuffix(elapsedString, ".") + "s"
	fmt.Println("\nDownload time: " + elapsedString)

	msg := ansi.Color(appName+" Successfully Downloaded!", "green+b")
	if onWindows == true {
		msg = "Successfully Downloaded!"
	}
	fmt.Println(msg)
}

// error check function
func check(e error, errMsg string) {
	if e != nil {
		fmt.Println("\nError: ", e)
		if errMsg != "" {
			fmt.Println("Message: ", errMsg)
		}
		os.Exit(1)
	}
}

// prints slices in readable format
func PrintSlice(slice []string) error {
	for index, val := range slice {
		fmt.Println(index+1, ": ", val)
	}
	return nil
}

func IsWindows() bool {
	return runtime.GOOS == "windows"
}

// Exists returns whether the given file or directory Exists or not
func Exists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	check(err, "Error E0.")
	return false
}

func createMessage(message, color string, onWindows bool) string {
	errmsg := ansi.Color(message, color)
	if onWindows == true {
		errmsg = message
	}

	return errmsg
}

func printHelp() {
	cmd := exec.Command("cf", "help", "download")
	output, _ := cmd.CombinedOutput()
	fmt.Printf("%s", output)
}

/*
*	This function must be implemented as part of the	plugin interface
*	defined by the core CLI.
*
*	GetMetadata() returns a PluginMetadata struct. The first field, Name,
*	determines the name of the plugin which should generally be without spaces.
*	If there are spaces in the name a user will need to properly quote the name
*	during uninstall otherwise the name will be treated as seperate arguments.
*	The second value is a slice of Command structs. Our slice only contains one
*	Command Struct, but could contain any number of them. The first field Name
*	defines the command `cf basic-plugin-command` once installed into the CLI. The
*	second field, HelpText, is used by the core CLI to display help information
*	to the user in the core commands `cf help`, `cf`, or `cf -h`.
 */
func (c *DownloadPlugin) GetMetadata() plugin.PluginMetadata {
	return plugin.PluginMetadata{
		Name: "download",
		Version: plugin.VersionType{
			Major: 1,
			Minor: 0,
			Build: 0,
		},
		Commands: []plugin.Command{
			plugin.Command{
				Name:     "download",
				HelpText: "Download contents of a running app's file directory",

				// UsageDetails is optional
				// It is used to show help of usage of each command
				UsageDetails: plugin.Usage{
					Usage: "cf download APP_NAME [PATH] [--overwrite] [--verbose] [--omit ommited_paths] [-i instance_num]",
					Options: map[string]string{
						"overwrite":             "Overwrite existing files",
						"verbose":               "Verbose output",
						"omit \"path/to/file\"": "Omit directories or files (delimited by semicolons)",
						"i": "Instance",
					},
				},
			},
		},
	}
}

/*
* Unlike most Go programs, the `Main()` function will not be used to run all of the
* commands provided in your plugin. Main will be used to initialize the plugin
* process, as well as any dependencies you might require for your
* plugin.
 */
func main() {

	// Any initialization for your plugin can be handled here

	// Note: The plugin's main() method is invoked at install time to collect
	// metadata. The plugin will exit 0 and the Run([]string) method will not be
	// invoked.

	// About debug Locally:
	// The plugin interface hides panics from stdout, so in order to get panic info,
	// you can run this plugin outside of the plugin architecture by setting debuglocally = true.

	// example usage for locall run: go run main.go download APP_NAME --overwrite 2> err.txt
	// note the lack of 'cf'

	debugLocally := true
	if debugLocally {
		var run DownloadPlugin
		run.Run(nil, os.Args[1:])
	} else {
		plugin.Start(new(DownloadPlugin))
	}

	// Plugin code should be written in the Run([]string) method,
	// ensuring the plugin environment is bootstrapped.
}
<|endoftext|>


<commit_before>/*
   Copyright 2009-2013 Phil Pennock

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

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

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

package sks_spider

import (
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"
)

var (
	flSpiderStartHost    = flag.String("spider-start-host", "sks-peer.spodhuis.org", "Host to query to start things rolling")
	flListen             = flag.String("listen", "localhost:8001", "port to listen on with web-server")
	flMaintEmail         = flag.String("maint-email", "webmaster@spodhuis.org", "Email address of local maintainer")
	flHostname           = flag.String("hostname", "sks.spodhuis.org", "Hostname to use in generated pages")
	flMyStylesheet       = flag.String("stylesheet", "/styles/sks-peers.css", "CSS Style sheet to use")
	flSksMembershipFile  = flag.String("sks-membership-file", "/var/sks/membership", "SKS Membership file")
	flSksPortRecon       = flag.Int("sks-port-recon", 11370, "Default SKS recon port")
	flSksPortHkp         = flag.Int("sks-port-hkp", 11371, "Default SKS HKP port")
	flTimeoutStatsFetch  = flag.Int("timeout-stats-fetch", 30, "Timeout for fetching stats from a remote server")
	flCountriesZone      = flag.String("countries-zone", "zz.countries.nerd.dk.", "DNS zone for determining IP locations")
	flKeysSanityMin      = flag.Int("keys-sanity-min", 3100000, "Minimum number of keys that's sane, or we're broken")
	flKeysDailyJitter    = flag.Int("keys-daily-jitter", 500, "Max daily jitter in key count")
	flScanIntervalSecs   = flag.Int("scan-interval", 3600*8, "How often to trigger a scan")
	flScanIntervalJitter = flag.Int("scan-interval-jitter", 120, "Jitter in scan interval")
	flLogFile            = flag.String("log-file", "sksdaemon.log", "Where to write logfiles")
	flLogStdout          = flag.Bool("log-stdout", false, "Log to stdout instead of log-file")
	flJsonDump           = flag.String("json-dump", "", "File to dump JSON of spidered hosts to")
	flJsonLoad           = flag.String("json-load", "", "File to load JSON hosts from instead of spidering")
	flJsonPersistPath    = flag.String("json-persist", "", "File to load at startup if exists, and write to at SIGUSR1")
	flStartedFlagfile    = flag.String("started-file", "", "Create this file after started and running")
	flHttpFetchTimeout   = flag.Duration("http-fetch-timeout", 2*time.Minute, "Timeout for HTTP fetch from SKS servers")
)

var serverHeadersNative = map[string]bool{
	"sks_www": true,
	"gnuks":   true,
}
var defaultSoftware = "SKS"

// People put dumb things in their membership files
var blacklistedQueryHosts = []string{
	"localhost",
	"127.0.0.1",
	"::1",
}

var Log *log.Logger

func setupLogging() {
	if *flLogStdout {
		Log = log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile)
		return
	}
	fh, err := os.OpenFile(*flLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Unable to open logfile \"%s\": %s\n", *flLogFile, err)
		os.Exit(1)
	}
	Log = log.New(fh, "", log.LstdFlags|log.Lshortfile)
}

type PersistedHostInfo struct {
	HostMap      HostMap
	AliasMap     AliasMap
	IPCountryMap IPCountryMap
	Sorted       []string
	DepthSorted  []string
	Graph        *HostGraph
	Timestamp    time.Time
}

var (
	currentHostInfo    *PersistedHostInfo
	currentHostMapLock sync.RWMutex
)

func GetCurrentPersisted() *PersistedHostInfo {
	currentHostMapLock.RLock()
	defer currentHostMapLock.RUnlock()
	return currentHostInfo
}

func GetCurrentHosts() HostMap {
	currentHostMapLock.RLock()
	defer currentHostMapLock.RUnlock()
	if currentHostInfo == nil {
		return nil
	}
	return currentHostInfo.HostMap
}

func GetCurrentHostlist() []string {
	currentHostMapLock.RLock()
	defer currentHostMapLock.RUnlock()
	if currentHostInfo == nil {
		return nil
	}
	return currentHostInfo.Sorted
}

func SetCurrentPersisted(p *PersistedHostInfo) {
	p.Timestamp = time.Now()
	p.LogInformation()
	currentHostMapLock.Lock()
	defer currentHostMapLock.Unlock()
	currentHostInfo = p
}

func normaliseMeshAndSet(spider *Spider, dumpJson bool) {
	go func(s *Spider) {
		persisted := GeneratePersistedInformation(s)
		SetCurrentPersisted(persisted)
		persisted.UpdateStatsCounters(spider)
		runtime.GC()
		if dumpJson && *flJsonDump != "" {
			Log.Printf("Saving JSON to \"%s\"", *flJsonDump)
			err := persisted.HostMap.DumpJSONToFile(*flJsonDump)
			if err != nil {
				Log.Printf("Error saving JSON to \"%s\": %s", *flJsonDump, err)
				// continue anyway
			}
			runtime.GC()
		}
	}(spider)
}

func respiderPeriodically() {
	for {
		var delay time.Duration = time.Duration(*flScanIntervalSecs) * time.Second
		if *flScanIntervalJitter > 0 {
			jitter := rand.Int63n(int64(*flScanIntervalJitter) * int64(time.Second))
			jitter -= int64(*flScanIntervalJitter) * int64(time.Second) / 2
			delay += time.Duration(jitter)
		}
		minDelay := time.Minute * 30
		if delay < minDelay {
			Log.Printf("respider period too low, capping %d up to %d", delay, minDelay)
			delay = minDelay
		}
		Log.Printf("Sleeping %s before next respider", delay)
		time.Sleep(delay)
		Log.Printf("Awoken!  Time to spider.")
		var spider *Spider
		func() {
			spider = StartSpider()
			defer func(sp *Spider) {
				if r := recover(); r != nil {
					Log.Printf("Spider paniced: %s", r)
				}
				sp.Terminate()
			}(spider)
			spider.AddHost(*flSpiderStartHost, 0)
			spider.Wait()
		}()
		normaliseMeshAndSet(spider, false)
	}
}

var httpServing sync.WaitGroup

func startHttpServing() {
	Log.Printf("Will Listen on <%s>", *flListen)
	server := setupHttpServer(*flListen)
	err := server.ListenAndServe()
	if err != nil {
		Log.Printf("ListenAndServe(%s): %s", *flListen, err)
	}
	httpServing.Done()
}

func shutdownRunner(ch <-chan os.Signal) {
	signal, ok := <-ch
	if !ok {
		return
	}
	persisted := GetCurrentPersisted()
	if persisted != nil {
		Log.Printf("Received signal %s; saving JSON to \"%s\"", signal, *flJsonPersistPath)
		err := persisted.HostMap.DumpJSONToFile(*flJsonPersistPath)
		if err != nil {
			Log.Printf("Error saving shutdown JSON: %s", err)
		} else {
			Log.Print("Wrote shutdown JSON")
		}
	}
	httpServing.Done()
}

func Main() {
	flag.Parse()

	if *flScanIntervalJitter < 0 {
		fmt.Fprintf(os.Stderr, "Bad jitter, must be >= 0 [got: %d]\n", *flScanIntervalJitter)
		os.Exit(1)
	}

	setupLogging()
	Log.Printf("started")

	httpServing.Add(1)
	go startHttpServing()

	if *flJsonPersistPath != "" {
		if _, err := os.Stat(*flJsonPersistPath); err == nil {
			if *flJsonLoad == "" {
				*flJsonLoad = *flJsonPersistPath
			}
		}
	}

	var doneRespider bool

	if *flJsonLoad != "" {
		Log.Printf("Loading hosts from \"%s\" instead of spidering", *flJsonLoad)
		hostmap, err := LoadJSONFromFile(*flJsonLoad)
		if err != nil {
			Log.Fatalf("Failed to load JSON from \"%s\": %s", *flJsonLoad, err)
		}
		Log.Printf("Loaded %d hosts from JSON", len(hostmap))
		hostnames := GenerateHostlistSorted(hostmap)
		countryMap := GetFreshCountryForHostmap(hostmap)
		aliasMap := GetAliasMapForHostmap(hostmap)
		SetCurrentPersisted(&PersistedHostInfo{
			HostMap:      hostmap,
			AliasMap:     aliasMap,
			IPCountryMap: countryMap,
			Sorted:       hostnames,
			DepthSorted:  GenerateDepthSorted(hostmap),
			Graph:        GenerateGraph(hostnames, hostmap, aliasMap),
		})
	} else {
		spider := StartSpider()
		spider.AddHost(*flSpiderStartHost, 0)
		spider.Wait()
		spider.Terminate()
		Log.Printf("Start-up initial spidering complete")
		normaliseMeshAndSet(spider, true)
		go respiderPeriodically()
		doneRespider = true
	}

	if *flJsonPersistPath != "" {
		signalChan := make(chan os.Signal)
		if !doneRespider {
			go respiderPeriodically()
		}
		go shutdownRunner(signalChan)
		// Warning: Unix-specific, need to figure out how to make this signal-handling
		// replacable with another notification mechanism which is system-local and easily
		// triggered from an rc script
		signal.Notify(signalChan, syscall.SIGUSR1)
	}

	if *flStartedFlagfile != "" {
		fh, err := os.Create(*flStartedFlagfile)
		if err == nil {
			fmt.Fprintf(fh, "Started %s\n", os.Args[0])
			err = fh.Close()
			if err != nil {
				Log.Printf("Error in close(%s): %s", *flStartedFlagfile, err)
			}
		} else {
			Log.Printf("Failed to create -started-file: %s", err)
		}
	}

	httpServing.Wait()
}
<commit_msg>Change default -keys-daily-jitter 500 => 800<commit_after>/*
   Copyright 2009-2013 Phil Pennock

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

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

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

package sks_spider

import (
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"
)

var (
	flSpiderStartHost    = flag.String("spider-start-host", "sks-peer.spodhuis.org", "Host to query to start things rolling")
	flListen             = flag.String("listen", "localhost:8001", "port to listen on with web-server")
	flMaintEmail         = flag.String("maint-email", "webmaster@spodhuis.org", "Email address of local maintainer")
	flHostname           = flag.String("hostname", "sks.spodhuis.org", "Hostname to use in generated pages")
	flMyStylesheet       = flag.String("stylesheet", "/styles/sks-peers.css", "CSS Style sheet to use")
	flSksMembershipFile  = flag.String("sks-membership-file", "/var/sks/membership", "SKS Membership file")
	flSksPortRecon       = flag.Int("sks-port-recon", 11370, "Default SKS recon port")
	flSksPortHkp         = flag.Int("sks-port-hkp", 11371, "Default SKS HKP port")
	flTimeoutStatsFetch  = flag.Int("timeout-stats-fetch", 30, "Timeout for fetching stats from a remote server")
	flCountriesZone      = flag.String("countries-zone", "zz.countries.nerd.dk.", "DNS zone for determining IP locations")
	flKeysSanityMin      = flag.Int("keys-sanity-min", 3100000, "Minimum number of keys that's sane, or we're broken")
	flKeysDailyJitter    = flag.Int("keys-daily-jitter", 800, "Max daily jitter in key count")
	flScanIntervalSecs   = flag.Int("scan-interval", 3600*8, "How often to trigger a scan")
	flScanIntervalJitter = flag.Int("scan-interval-jitter", 120, "Jitter in scan interval")
	flLogFile            = flag.String("log-file", "sksdaemon.log", "Where to write logfiles")
	flLogStdout          = flag.Bool("log-stdout", false, "Log to stdout instead of log-file")
	flJsonDump           = flag.String("json-dump", "", "File to dump JSON of spidered hosts to")
	flJsonLoad           = flag.String("json-load", "", "File to load JSON hosts from instead of spidering")
	flJsonPersistPath    = flag.String("json-persist", "", "File to load at startup if exists, and write to at SIGUSR1")
	flStartedFlagfile    = flag.String("started-file", "", "Create this file after started and running")
	flHttpFetchTimeout   = flag.Duration("http-fetch-timeout", 2*time.Minute, "Timeout for HTTP fetch from SKS servers")
)

var serverHeadersNative = map[string]bool{
	"sks_www": true,
	"gnuks":   true,
}
var defaultSoftware = "SKS"

// People put dumb things in their membership files
var blacklistedQueryHosts = []string{
	"localhost",
	"127.0.0.1",
	"::1",
}

var Log *log.Logger

func setupLogging() {
	if *flLogStdout {
		Log = log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile)
		return
	}
	fh, err := os.OpenFile(*flLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Unable to open logfile \"%s\": %s\n", *flLogFile, err)
		os.Exit(1)
	}
	Log = log.New(fh, "", log.LstdFlags|log.Lshortfile)
}

type PersistedHostInfo struct {
	HostMap      HostMap
	AliasMap     AliasMap
	IPCountryMap IPCountryMap
	Sorted       []string
	DepthSorted  []string
	Graph        *HostGraph
	Timestamp    time.Time
}

var (
	currentHostInfo    *PersistedHostInfo
	currentHostMapLock sync.RWMutex
)

func GetCurrentPersisted() *PersistedHostInfo {
	currentHostMapLock.RLock()
	defer currentHostMapLock.RUnlock()
	return currentHostInfo
}

func GetCurrentHosts() HostMap {
	currentHostMapLock.RLock()
	defer currentHostMapLock.RUnlock()
	if currentHostInfo == nil {
		return nil
	}
	return currentHostInfo.HostMap
}

func GetCurrentHostlist() []string {
	currentHostMapLock.RLock()
	defer currentHostMapLock.RUnlock()
	if currentHostInfo == nil {
		return nil
	}
	return currentHostInfo.Sorted
}

func SetCurrentPersisted(p *PersistedHostInfo) {
	p.Timestamp = time.Now()
	p.LogInformation()
	currentHostMapLock.Lock()
	defer currentHostMapLock.Unlock()
	currentHostInfo = p
}

func normaliseMeshAndSet(spider *Spider, dumpJson bool) {
	go func(s *Spider) {
		persisted := GeneratePersistedInformation(s)
		SetCurrentPersisted(persisted)
		persisted.UpdateStatsCounters(spider)
		runtime.GC()
		if dumpJson && *flJsonDump != "" {
			Log.Printf("Saving JSON to \"%s\"", *flJsonDump)
			err := persisted.HostMap.DumpJSONToFile(*flJsonDump)
			if err != nil {
				Log.Printf("Error saving JSON to \"%s\": %s", *flJsonDump, err)
				// continue anyway
			}
			runtime.GC()
		}
	}(spider)
}

func respiderPeriodically() {
	for {
		var delay time.Duration = time.Duration(*flScanIntervalSecs) * time.Second
		if *flScanIntervalJitter > 0 {
			jitter := rand.Int63n(int64(*flScanIntervalJitter) * int64(time.Second))
			jitter -= int64(*flScanIntervalJitter) * int64(time.Second) / 2
			delay += time.Duration(jitter)
		}
		minDelay := time.Minute * 30
		if delay < minDelay {
			Log.Printf("respider period too low, capping %d up to %d", delay, minDelay)
			delay = minDelay
		}
		Log.Printf("Sleeping %s before next respider", delay)
		time.Sleep(delay)
		Log.Printf("Awoken!  Time to spider.")
		var spider *Spider
		func() {
			spider = StartSpider()
			defer func(sp *Spider) {
				if r := recover(); r != nil {
					Log.Printf("Spider paniced: %s", r)
				}
				sp.Terminate()
			}(spider)
			spider.AddHost(*flSpiderStartHost, 0)
			spider.Wait()
		}()
		normaliseMeshAndSet(spider, false)
	}
}

var httpServing sync.WaitGroup

func startHttpServing() {
	Log.Printf("Will Listen on <%s>", *flListen)
	server := setupHttpServer(*flListen)
	err := server.ListenAndServe()
	if err != nil {
		Log.Printf("ListenAndServe(%s): %s", *flListen, err)
	}
	httpServing.Done()
}

func shutdownRunner(ch <-chan os.Signal) {
	signal, ok := <-ch
	if !ok {
		return
	}
	persisted := GetCurrentPersisted()
	if persisted != nil {
		Log.Printf("Received signal %s; saving JSON to \"%s\"", signal, *flJsonPersistPath)
		err := persisted.HostMap.DumpJSONToFile(*flJsonPersistPath)
		if err != nil {
			Log.Printf("Error saving shutdown JSON: %s", err)
		} else {
			Log.Print("Wrote shutdown JSON")
		}
	}
	httpServing.Done()
}

func Main() {
	flag.Parse()

	if *flScanIntervalJitter < 0 {
		fmt.Fprintf(os.Stderr, "Bad jitter, must be >= 0 [got: %d]\n", *flScanIntervalJitter)
		os.Exit(1)
	}

	setupLogging()
	Log.Printf("started")

	httpServing.Add(1)
	go startHttpServing()

	if *flJsonPersistPath != "" {
		if _, err := os.Stat(*flJsonPersistPath); err == nil {
			if *flJsonLoad == "" {
				*flJsonLoad = *flJsonPersistPath
			}
		}
	}

	var doneRespider bool

	if *flJsonLoad != "" {
		Log.Printf("Loading hosts from \"%s\" instead of spidering", *flJsonLoad)
		hostmap, err := LoadJSONFromFile(*flJsonLoad)
		if err != nil {
			Log.Fatalf("Failed to load JSON from \"%s\": %s", *flJsonLoad, err)
		}
		Log.Printf("Loaded %d hosts from JSON", len(hostmap))
		hostnames := GenerateHostlistSorted(hostmap)
		countryMap := GetFreshCountryForHostmap(hostmap)
		aliasMap := GetAliasMapForHostmap(hostmap)
		SetCurrentPersisted(&PersistedHostInfo{
			HostMap:      hostmap,
			AliasMap:     aliasMap,
			IPCountryMap: countryMap,
			Sorted:       hostnames,
			DepthSorted:  GenerateDepthSorted(hostmap),
			Graph:        GenerateGraph(hostnames, hostmap, aliasMap),
		})
	} else {
		spider := StartSpider()
		spider.AddHost(*flSpiderStartHost, 0)
		spider.Wait()
		spider.Terminate()
		Log.Printf("Start-up initial spidering complete")
		normaliseMeshAndSet(spider, true)
		go respiderPeriodically()
		doneRespider = true
	}

	if *flJsonPersistPath != "" {
		signalChan := make(chan os.Signal)
		if !doneRespider {
			go respiderPeriodically()
		}
		go shutdownRunner(signalChan)
		// Warning: Unix-specific, need to figure out how to make this signal-handling
		// replacable with another notification mechanism which is system-local and easily
		// triggered from an rc script
		signal.Notify(signalChan, syscall.SIGUSR1)
	}

	if *flStartedFlagfile != "" {
		fh, err := os.Create(*flStartedFlagfile)
		if err == nil {
			fmt.Fprintf(fh, "Started %s\n", os.Args[0])
			err = fh.Close()
			if err != nil {
				Log.Printf("Error in close(%s): %s", *flStartedFlagfile, err)
			}
		} else {
			Log.Printf("Failed to create -started-file: %s", err)
		}
	}

	httpServing.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/jessevdk/go-flags"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"net/http"
	"regexp"
	"strings"
	"sync"
	"time"
)

const (
	expectingVersion = "1.1.1 DISABLED"                                  // number being expected. must be changed manually (for now).
	changeURLBase    = "https://code.google.com/p/go/source/detail?r=go" // base url to poll the tag
	updateInterval   = 6 * time.Second                                   // Update interval for the expected number
)

var defaultPage = "http://isgo" + strings.Replace(expectingVersion, ".", "point", -1) + ".outyet.org" //++ TODO(GeertJohan): strings replace "." to "point" ?

var (
	versions     = make(map[string]*version) // map with all versions by number(string)
	versionsLock sync.RWMutex                // map lock
)

var regexpNumber = regexp.MustCompile(`^[1-9](?:\.[0-9]){0,2}$`)

var colVersions *mgo.Collection
var colNV *mgo.Collection

var options struct {
	Listen string `short:"l" long:"listen" default:"141.138.139.6:80" description:"IP:post to listen on"`
}

func main() {
	args, err := flags.Parse(&options)
	if err != nil {
		log.Fatalln(err)
	}
	if len(args) > 0 {
		log.Fatalln("Unexpected arguments.")
	}

	mgoSess, err := mgo.Dial("localhost")
	if err != nil {
		log.Fatalln(err)
	}
	colVersions = mgoSess.DB("outyet").C("versions")
	colVersions.EnsureIndex(mgo.Index{
		Key:      []string{"number"},
		Unique:   true,
		DropDups: true,
	})
	colNV = mgoSess.DB("outyet").C("namevalue")
	colNV.EnsureIndex(mgo.Index{
		Key:      []string{"name"},
		Unique:   true,
		DropDups: true,
	})

	http.HandleFunc("/", rootHandler)
	if err := http.ListenAndServe(options.Listen, nil); err != nil {
		log.Fatalln(err)
	}
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	// handler for stats page
	if r.Host == "stats.outyet.org" {
		statsHandler(w, r)
		return
	}

	// redirect for 'old' domain
	if r.Host == "isgo1point2outyet.com" {
		http.Redirect(w, r, "http://isgo1point2.outyet.org", http.StatusTemporaryRedirect)
		return
	}

	// only handle requests on /
	if r.RequestURI != "/" {
		http.NotFound(w, r)
		return
	}

	// check if Host header matches isgo*.outyet.org
	if !strings.HasSuffix(r.Host, ".outyet.org") || !strings.HasPrefix(r.Host, "isgo") {
		log.Printf("Invalid host format detected. %s\n", r.Host)
		http.Redirect(w, r, defaultPage, http.StatusTemporaryRedirect)
		return
	}

	number := strings.Replace(r.Host[4:len(r.Host)-11], "point", ".", -1)
	log.Println(number)

	if !regexpNumber.MatchString(number) {
		http.Error(w, "invalid request format", http.StatusBadRequest)
		return
	}

	// get right version in a safe way
	o := getVersion(number)

	// add hitCount's
	colVersions.Upsert(bson.M{"number": o.number}, bson.M{"$inc": bson.M{"hits": 1}})
	colNV.Upsert(bson.M{"name": "counts"}, bson.M{"$inc": bson.M{"hits": 1}})

	// execute template
	data := dataOutyet{
		Outyet: <-o.isOutyetChan, //retrieve outyet directly from channel
		Number: number,
	}
	err := tmplOutyet.Execute(w, data)
	if err != nil {
		log.Print(err)
	}
}

func statsHandler(w http.ResponseWriter, r *http.Request) {
	data := &dataStats{}

	colNV.Find(bson.M{"name": "counts"}).One(data)
	colVersions.Find(nil).Sort("number").All(&data.Versions)

	for _, v := range data.Versions {
		v.Outyet = <-getVersion(v.Number).isOutyetChan
	}

	err := tmplStats.Execute(w, data)
	if err != nil {
		log.Print(err)
	}
}
<commit_msg>Added hit for each version on stats request.<commit_after>package main

import (
	"github.com/jessevdk/go-flags"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"net/http"
	"regexp"
	"strings"
	"sync"
	"time"
)

const (
	expectingVersion = "1.1.1 DISABLED"                                  // number being expected. must be changed manually (for now).
	changeURLBase    = "https://code.google.com/p/go/source/detail?r=go" // base url to poll the tag
	updateInterval   = 6 * time.Second                                   // Update interval for the expected number
)

var defaultPage = "http://isgo" + strings.Replace(expectingVersion, ".", "point", -1) + ".outyet.org" //++ TODO(GeertJohan): strings replace "." to "point" ?

var (
	versions     = make(map[string]*version) // map with all versions by number(string)
	versionsLock sync.RWMutex                // map lock
)

var regexpNumber = regexp.MustCompile(`^[1-9](?:\.[0-9]){0,2}$`)

var colVersions *mgo.Collection
var colNV *mgo.Collection

var options struct {
	Listen string `short:"l" long:"listen" default:"141.138.139.6:80" description:"IP:post to listen on"`
}

func main() {
	args, err := flags.Parse(&options)
	if err != nil {
		log.Fatalln(err)
	}
	if len(args) > 0 {
		log.Fatalln("Unexpected arguments.")
	}

	mgoSess, err := mgo.Dial("localhost")
	if err != nil {
		log.Fatalln(err)
	}
	colVersions = mgoSess.DB("outyet").C("versions")
	colVersions.EnsureIndex(mgo.Index{
		Key:      []string{"number"},
		Unique:   true,
		DropDups: true,
	})
	colNV = mgoSess.DB("outyet").C("namevalue")
	colNV.EnsureIndex(mgo.Index{
		Key:      []string{"name"},
		Unique:   true,
		DropDups: true,
	})

	http.HandleFunc("/", rootHandler)
	if err := http.ListenAndServe(options.Listen, nil); err != nil {
		log.Fatalln(err)
	}
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	// handler for stats page
	if r.Host == "stats.outyet.org" {
		statsHandler(w, r)
		return
	}

	// redirect for 'old' domain
	if r.Host == "isgo1point2outyet.com" {
		http.Redirect(w, r, "http://isgo1point2.outyet.org", http.StatusTemporaryRedirect)
		return
	}

	// only handle requests on /
	if r.RequestURI != "/" {
		http.NotFound(w, r)
		return
	}

	// check if Host header matches isgo*.outyet.org
	if !strings.HasSuffix(r.Host, ".outyet.org") || !strings.HasPrefix(r.Host, "isgo") {
		log.Printf("Invalid host format detected. %s\n", r.Host)
		http.Redirect(w, r, defaultPage, http.StatusTemporaryRedirect)
		return
	}

	number := strings.Replace(r.Host[4:len(r.Host)-11], "point", ".", -1)
	log.Println(number)

	if !regexpNumber.MatchString(number) {
		http.Error(w, "invalid request format", http.StatusBadRequest)
		return
	}

	// get right version in a safe way
	o := getVersion(number)

	// add hitCount's
	colVersions.Upsert(bson.M{"number": o.number}, bson.M{"$inc": bson.M{"hits": 1}})
	colNV.Upsert(bson.M{"name": "counts"}, bson.M{"$inc": bson.M{"hits": 1}})

	// execute template
	data := dataOutyet{
		Outyet: <-o.isOutyetChan, //retrieve outyet directly from channel
		Number: number,
	}
	err := tmplOutyet.Execute(w, data)
	if err != nil {
		log.Print(err)
	}
}

func statsHandler(w http.ResponseWriter, r *http.Request) {
	data := &dataStats{}

	colNV.Find(bson.M{"name": "counts"}).One(data)
	colVersions.Find(nil).Sort("number").All(&data.Versions)

	for _, v := range data.Versions {
		// get outyet for given version number
		v.Outyet = <-getVersion(v.Number).isOutyetChan

	    // add hitCount's
    	colVersions.Upsert(bson.M{"number": v.Number}, bson.M{"$inc": bson.M{"hits": 1}})
	}

	err := tmplStats.Execute(w, data)
	if err != nil {
		log.Print(err)
	}
}
<|endoftext|>