<commit_before>package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/kardianos/osext"
	sdees "github.com/schollz/sdees/src"
	"github.com/urfave/cli"
)

var (
	Version, BuildTime, Build, OS, LastCommit string
	Debug                                     bool
	DontEncrypt, Clean                        bool
	ResetConfig                               bool
	ImportOldFile, ImportFile                 bool
)

func main() {
	// Delete temp files upon exit
	defer sdees.CleanUp()

	// Handle Ctl+C for cleanUp
	// from http://stackoverflow.com/questions/11268943/golang-is-it-possible-to-capture-a-ctrlc-signal-and-run-a-cleanup-function-in
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		sdees.CleanUp()
		os.Exit(1)
	}()

	// App information
	setBuild()
	app := cli.NewApp()
	app.Name = "sdees"
	app.Version = Version + " " + Build + " " + BuildTime + " " + OS
	app.Usage = `SDEES Does Editing, Encryption, and Synchronization

	 https://github.com/schollz/sdees

FOLDERS:
	'` + sdees.CachePath + `' stores all encrypted files and repositories
	'` + sdees.ConfigPath + `' stores all configuration files

EXAMPLE USAGE:
   sdees new.txt # edit a new document, new.txt
	 sdees UglyDuckling # edit a entry in a document
   sdees --summary -n 5 # list a summary of last five entries
   sdees --search "dogs cats" # find entries that mention 'dogs' or 'cats'`

	app.Action = func(c *cli.Context) error {
		// Set the log level
		if Debug {
			sdees.DebugMode()
		}

		CheckIfGitIsInstalled()

		workingFile := c.Args().Get(0)
		if len(workingFile) > 0 {
			sdees.InputDocument = workingFile
		}

		// Check if its Windows
		if runtime.GOOS == "windows" {
			sdees.Extension = ".exe"
		} else {
			sdees.Extension = ""
		}
		sdees.Version = Version

		// Check new Version
		programPath, _ := osext.Executable()
		sdees.CheckNewVersion(programPath, Version, LastCommit, OS)

		// Process some flags
		if ResetConfig {
			sdees.SetupConfig()
		} else if Clean {
			sdees.CleanAll()
		} else {
			sdees.Run()
		}
		return nil
	}
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:        "debug",
			Usage:       "Turn on debug mode",
			Destination: &Debug,
		},
		cli.BoolFlag{
			Name:        "clean",
			Usage:       "Deletes all sdees files",
			Destination: &Clean,
		},
		cli.StringFlag{
			Name:        "search",
			Usage:       "Search for `word`",
			Destination: &sdees.Search,
		},
		cli.BoolFlag{
			Name:        "importold",
			Usage:       "Import `document` (JRNL-format)",
			Destination: &sdees.ImportOldFlag,
		},
		cli.BoolFlag{
			Name:        "import",
			Usage:       "Import `document`",
			Destination: &sdees.ImportFlag,
		},
		cli.BoolFlag{
			Name:        "export",
			Usage:       "Export `document`",
			Destination: &sdees.Export,
		},
		cli.BoolFlag{
			Name:        "config",
			Usage:       "Configure",
			Destination: &ResetConfig,
		},
		cli.BoolFlag{
			Name:        "all, a",
			Usage:       "Edit all of the document",
			Destination: &sdees.All,
		},
		cli.BoolFlag{
			Name:        "delete",
			Usage:       "Delete `X`, where X is a document or entry",
			Destination: &sdees.DeleteFlag,
		},
		cli.BoolFlag{
			Name:        "summary",
			Usage:       "Gets summary",
			Destination: &sdees.Summarize,
		},
	}
	app.Run(os.Args)
}

func CheckIfGitIsInstalled() {
	cmd := exec.Command("git", "--version")
	stdout, err := cmd.Output()
	versionNums := strings.Split(strings.Split(string(stdout), " ")[2], ".")
	fmt.Println(versionNums)
	os.Exit(1)
	if err != nil {
		fmt.Println("\ngit is not detected.\n\nPlease install git before proceeding. To install, go to \n\n    https://git-scm.com/downloads \n\nand install the version for your operating system.\nPress enter to continue... ")
		var input string
		fmt.Scanln(&input)
		os.Exit(1)
	}
}

func setBuild() {
	if len(Build) == 0 {
		cwd, _ := os.Getwd()
		defer os.Chdir(cwd)
		Build = "dev"
		Version = "dev"
		BuildTime = time.Now().String()
		err := os.Chdir(path.Join(os.Getenv("GOPATH"), "src", "github.com", "schollz", "sdees"))
		if err != nil {
			return
		}
		cmd := exec.Command("git", "log", "-1", "--pretty=format:'%ad'")
		stdout, err := cmd.Output()
		if err != nil {
			return
		}
		LastCommit = strings.Replace(string(stdout), "'", "", -1)
		BuildTime = LastCommit
	} else {
		Build = Build[0:7]
	}
}
<commit_msg>Minimum version of git enforced<commit_after>package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/kardianos/osext"
	sdees "github.com/schollz/sdees/src"
	"github.com/urfave/cli"
)

var (
	Version, BuildTime, Build, OS, LastCommit string
	Debug                                     bool
	DontEncrypt, Clean                        bool
	ResetConfig                               bool
	ImportOldFile, ImportFile                 bool
)

func main() {
	// Delete temp files upon exit
	defer sdees.CleanUp()

	// Handle Ctl+C for cleanUp
	// from http://stackoverflow.com/questions/11268943/golang-is-it-possible-to-capture-a-ctrlc-signal-and-run-a-cleanup-function-in
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		sdees.CleanUp()
		os.Exit(1)
	}()

	// App information
	setBuild()
	app := cli.NewApp()
	app.Name = "sdees"
	app.Version = Version + " " + Build + " " + BuildTime + " " + OS
	app.Usage = `SDEES Does Editing, Encryption, and Synchronization

	 https://github.com/schollz/sdees

FOLDERS:
	'` + sdees.CachePath + `' stores all encrypted files and repositories
	'` + sdees.ConfigPath + `' stores all configuration files

EXAMPLE USAGE:
   sdees new.txt # edit a new document, new.txt
	 sdees UglyDuckling # edit a entry in a document
   sdees --summary -n 5 # list a summary of last five entries
   sdees --search "dogs cats" # find entries that mention 'dogs' or 'cats'`

	app.Action = func(c *cli.Context) error {
		// Set the log level
		if Debug {
			sdees.DebugMode()
		}

		CheckIfGitIsInstalled()

		workingFile := c.Args().Get(0)
		if len(workingFile) > 0 {
			sdees.InputDocument = workingFile
		}

		// Check if its Windows
		if runtime.GOOS == "windows" {
			sdees.Extension = ".exe"
		} else {
			sdees.Extension = ""
		}
		sdees.Version = Version

		// Check new Version
		programPath, _ := osext.Executable()
		sdees.CheckNewVersion(programPath, Version, LastCommit, OS)

		// Process some flags
		if ResetConfig {
			sdees.SetupConfig()
		} else if Clean {
			sdees.CleanAll()
		} else {
			sdees.Run()
		}
		return nil
	}
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:        "debug",
			Usage:       "Turn on debug mode",
			Destination: &Debug,
		},
		cli.BoolFlag{
			Name:        "clean",
			Usage:       "Deletes all sdees files",
			Destination: &Clean,
		},
		cli.StringFlag{
			Name:        "search",
			Usage:       "Search for `word`",
			Destination: &sdees.Search,
		},
		cli.BoolFlag{
			Name:        "importold",
			Usage:       "Import `document` (JRNL-format)",
			Destination: &sdees.ImportOldFlag,
		},
		cli.BoolFlag{
			Name:        "import",
			Usage:       "Import `document`",
			Destination: &sdees.ImportFlag,
		},
		cli.BoolFlag{
			Name:        "export",
			Usage:       "Export `document`",
			Destination: &sdees.Export,
		},
		cli.BoolFlag{
			Name:        "config",
			Usage:       "Configure",
			Destination: &ResetConfig,
		},
		cli.BoolFlag{
			Name:        "all, a",
			Usage:       "Edit all of the document",
			Destination: &sdees.All,
		},
		cli.BoolFlag{
			Name:        "delete",
			Usage:       "Delete `X`, where X is a document or entry",
			Destination: &sdees.DeleteFlag,
		},
		cli.BoolFlag{
			Name:        "summary",
			Usage:       "Gets summary",
			Destination: &sdees.Summarize,
		},
	}
	app.Run(os.Args)
}

func CheckIfGitIsInstalled() {
	cmd := exec.Command("git", "--version")
	stdout, err := cmd.Output()
	versionNums := strings.Split(strings.Split(string(stdout), " ")[2], ".")
	major, _ := strconv.Atoi(versionNums[0])
	fmt.Println(major)
	minor, _ := strconv.Atoi(versionNums[1])
	fmt.Println(minor)
	os.Exit(1)
	if err != nil {
		fmt.Println("\ngit is not detected.\n\nPlease install git before proceeding. To install, go to \n\n    https://git-scm.com/downloads \n\nand install the version for your operating system.\nPress enter to continue... ")
		var input string
		fmt.Scanln(&input)
		os.Exit(1)
	}
}

func setBuild() {
	if len(Build) == 0 {
		cwd, _ := os.Getwd()
		defer os.Chdir(cwd)
		Build = "dev"
		Version = "dev"
		BuildTime = time.Now().String()
		err := os.Chdir(path.Join(os.Getenv("GOPATH"), "src", "github.com", "schollz", "sdees"))
		if err != nil {
			return
		}
		cmd := exec.Command("git", "log", "-1", "--pretty=format:'%ad'")
		stdout, err := cmd.Output()
		if err != nil {
			return
		}
		LastCommit = strings.Replace(string(stdout), "'", "", -1)
		BuildTime = LastCommit
	} else {
		Build = Build[0:7]
	}
}
<|endoftext|>


<commit_before>/*
CDN Test is a program intended to be distributed to the users of a service,
in order to help debug connection issues to a CDN (which frequently seem to
involve ISPs with congested routes)

It is currently based mainly around the debugging of CloudFront requests,
which helpfully already provide a source of helpful information:
http://d7uri8nf7uskq.cloudfront.net/CustomerTesting.html

Rather than require a user to be quite technically competent and install or use
multiple tools, cdntest aims to reduce the work required by the end user to
downloading a single tool, and running it with an endpoint file to download,
then producing output in a file (and on the console) that the user can attach
to a report.
*/
package main

import (
	"bufio"
	"crypto/md5"
	"crypto/tls"
	"crypto/x509"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/urfave/cli"
)

const ipify = "https://api.ipify.org"

var transport = &http.Transport{
	Proxy: http.ProxyFromEnvironment,
	ResponseHeaderTimeout: time.Second * 10,
}

// Use a custom http client to ensure sensible timeouts
var DefaultClient = &http.Client{
	Timeout:   10 * time.Second,
	Transport: transport,
}

func getPort(scheme string) (int, error) {
	switch scheme {
	case "http":
		return 80, nil
	case "https":
		return 443, nil
	default:
		return 0, fmt.Errorf("Expected http or https: \"%v\"", scheme)
	}
}

func getMyPublicIP() (string, error) {
	response, err := DefaultClient.Get(ipify)

	if err != nil {
		return "", fmt.Errorf(
			"Could not contact %v to get your public IP: %v",
			ipify,
			err,
		)
	}

	defer response.Body.Close()

	b, err := ioutil.ReadAll(response.Body)

	if err != nil {
		return "", fmt.Errorf(
			"Could not contact %v to get your public IP: %v",
			ipify,
			err,
		)
	}

	return string(b), nil
}

func main() {
	app := cli.NewApp()
	app.Author = "Daniel Robert Speed"
	app.Usage = `
	Used for testing HTTP(S) endpoints for their SSL certificates and performance.

	See: https://github.com/Redundancy/cdntest
	`
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "ip",
			Usage: "Use an explicit IP address rather than DNS provided",
		},
		cli.StringFlag{
			Name:  "cname",
			Usage: "Use an alternative DNS entry to lookup the IP",
		},
		cli.StringFlag{
			Name:  "out",
			Usage: "Save the content body to a file",
		},
	}

	app.Action = func(c *cli.Context) error {
		if c.NArg() != 1 {
			return fmt.Errorf("Need a URL argument")
		}

		return testHTTP(options{
			url:     c.Args()[0],
			ip:      c.String("ip"),
			cname:   c.String("cname"),
			outfile: c.String("out"),
		})
	}

	if err := app.Run(os.Args); err != nil {
		fmt.Printf("Error: %v", err)
		os.Exit(1)
	}
}

type options struct {
	url     string
	ip      string
	cname   string
	outfile string
}

func testHTTP(o options) error {
	cdnURL, err := url.Parse(o.url)

	if err != nil {
		return fmt.Errorf("Could not parse url: \"%v\" - %v\n", cdnURL, err)
	}

	port, err := getPort(cdnURL.Scheme)
	if err != nil {
		return err
	}

	fmt.Println("Running CDN test at", time.Now())
	myPublicIP, err := getMyPublicIP()

	if err != nil {
		return err
	}

	fmt.Println("My public IP is:", myPublicIP)

	var addrs []string

	host := cdnURL.Host
	if o.cname != "" {
		host = o.cname
	}

	beforeLookup := time.Now()
	addrs, err = net.LookupHost(host)
	fmt.Printf("Host lookup of %v took %v\n", host, time.Now().Sub(beforeLookup))

	if err != nil {
		return fmt.Errorf("Could not look up host: %v", err)
	}

	fmt.Println("DNS returned:", addrs)

	/*
		resolver-identity.cloudfront.net reflects back the IP of the dns server that
		looked it up.
	*/
	resolverIdentities, _ := net.LookupHost("resolver-identity.cloudfront.net")
	if len(resolverIdentities) > 0 {
		resolver := ""
		if resolverNames, _ := net.LookupAddr(resolverIdentities[0]); len(resolverNames) > 0 {
			resolver = resolverNames[0]
		}

		fmt.Printf(
			"Your DNS resolver is %v (%v)\n",
			resolver,
			resolverIdentities[0],
		)
	} else {
		fmt.Printf("Could not identify your DNS resolver\n")
	}

	request, _ := http.NewRequest("GET", cdnURL.String(), nil)

	// We cannot use http.Transport because it does not provide hooks
	// to get the timing metrics that we require
	// TODO: Proxy support based on environment variables
	var conn net.Conn
	var dialErr error

	ip := addrs[0]
	if o.ip != "" {
		ip = o.ip
	}

	beforeDialTime := time.Now()
	conn, dialErr = net.DialTimeout(
		"tcp",
		fmt.Sprintf("%v:%v", ip, port),
		10*time.Second,
	)
	afterDialTime := time.Now()

	defer func() {
		if conn != nil {
			conn.Close()
		}
	}()

	if dialErr != nil {
		return fmt.Errorf("Could not contact server: %v", dialErr)
	}

	fmt.Println(
		"Connected to",
		conn.RemoteAddr().String(),
		"in",
		afterDialTime.Sub(beforeDialTime),
	)

	if cdnURL.Scheme == "https" {
		config := &tls.Config{
			ServerName: cdnURL.Host,
		}

		beforeHandshakeTime := time.Now()
		tlsCon := tls.Client(conn, config)
		tlsCon.SetDeadline(time.Now().Add(10 * time.Second))
		if hsErr := tlsCon.Handshake(); hsErr != nil {
			fmt.Println("SSL handshake error:", hsErr)
		}
		fmt.Println("TLS handshake in", time.Now().Sub(beforeHandshakeTime))

		if e := tlsCon.VerifyHostname(cdnURL.Host); e != nil {
			fmt.Println("SSL was unable to verify the hostname!", e)
		}
		printableCertChain(
			tlsCon.ConnectionState().PeerCertificates,
		).Write(os.Stdout)

		conn = tlsCon
	}

	request.WriteProxy(conn)
	beforeRead := time.Now()
	response, err := http.ReadResponse(bufio.NewReader(conn), request)

	if err != nil || response == nil {
		if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
			return fmt.Errorf(
				"Timeout reading response from %v",
				cdnURL.String(),
			)
		}
		return fmt.Errorf(
			"Did not get a response (after establishing connection), server behind loadbalancer / CDN may be down: %v",
			err,
		)
	}

	if response.Body != nil {
		defer response.Body.Close()
	}

	fmt.Println("Response was", response.Status)

	if response.Body != nil {
		var bodyWriters []io.Writer
		md5 := md5.New()
		var writtenBytes int64

		bodyWriters = append(bodyWriters, md5)

		if o.outfile != "" {
			f, openFileErr := os.OpenFile(o.outfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
			if openFileErr != nil {
				return fmt.Errorf("Unable to open %v for writing: %v", o.outfile, openFileErr)
			}
			defer func() {
				errClose := f.Close()
				if err != nil {
					fmt.Println(errClose)
					return
				}
				fmt.Println("Wrote", o.outfile)
			}()
			bodyWriters = append(bodyWriters, f)
		}

		writtenBytes, err = io.Copy(io.MultiWriter(bodyWriters...), response.Body)
		if err != nil {
			return fmt.Errorf("Error reading content body: %v", err)
		}

		timeTaken := time.Now().Sub(beforeRead)
		bps := bytesPerSecond((float64(time.Second) / float64(timeTaken)) * float64(writtenBytes))

		fmt.Printf(
			"Reading %v bytes took %v (%v)\n",
			writtenBytes,
			timeTaken,
			bps,
		)
		fmt.Printf("MD5 of Content %v\n", hex.EncodeToString(md5.Sum(nil)))
	}

	if response.Header.Get("X-Amz-Cf-Id") != "" {
		fmt.Println("Amazon CloudFront Request ID:", response.Header.Get("X-Amz-Cf-Id"))

		// lookup AWS DNS server
		nameserver, _ := net.LookupHost("identity.cloudfront.net")
		names, _ := net.LookupAddr(nameserver[0])

		fmt.Printf(
			"The CloudFront DNS server you query is %v (%v)\n",
			names[0],
			nameserver[0],
		)
	}

	return nil
}

type printableCertChain []*x509.Certificate

func (cc printableCertChain) Write(o io.Writer) {
	fmt.Fprintln(o, "SSL Certificate Information")

	for _, cert := range cc {
		if cert.IsCA {
			fmt.Fprintf(
				o,
				"\t%v - Valid Until: %v\n",
				cert.Subject.CommonName,
				cert.NotAfter,
			)
		} else {
			fmt.Fprintf(
				o,
				"\t%v - Issued by: %v, Valid Until: %v\n",
				cert.Subject.CommonName,
				cert.Issuer.CommonName,
				cert.NotAfter,
			)
		}
	}

}

type bytesPerSecond int64

func (i bytesPerSecond) String() string {
	switch {
	case i < 1000:
		return fmt.Sprintf("%v bytes/s", int64(i))
	case i < 1000*1000:
		return fmt.Sprintf("%v KiB/s", int64(i)/1000)
	default:
		return fmt.Sprintf("%v MiB/s", int64(i)/1000000)
	}
}
<commit_msg>Use %x format string instead of hex package<commit_after>/*
CDN Test is a program intended to be distributed to the users of a service,
in order to help debug connection issues to a CDN (which frequently seem to
involve ISPs with congested routes)

It is currently based mainly around the debugging of CloudFront requests,
which helpfully already provide a source of helpful information:
http://d7uri8nf7uskq.cloudfront.net/CustomerTesting.html

Rather than require a user to be quite technically competent and install or use
multiple tools, cdntest aims to reduce the work required by the end user to
downloading a single tool, and running it with an endpoint file to download,
then producing output in a file (and on the console) that the user can attach
to a report.
*/
package main

import (
	"bufio"
	"crypto/md5"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/urfave/cli"
)

const ipify = "https://api.ipify.org"

var transport = &http.Transport{
	Proxy: http.ProxyFromEnvironment,
	ResponseHeaderTimeout: time.Second * 10,
}

// Use a custom http client to ensure sensible timeouts
var DefaultClient = &http.Client{
	Timeout:   10 * time.Second,
	Transport: transport,
}

func getPort(scheme string) (int, error) {
	switch scheme {
	case "http":
		return 80, nil
	case "https":
		return 443, nil
	default:
		return 0, fmt.Errorf("Expected http or https: \"%v\"", scheme)
	}
}

func getMyPublicIP() (string, error) {
	response, err := DefaultClient.Get(ipify)

	if err != nil {
		return "", fmt.Errorf(
			"Could not contact %v to get your public IP: %v",
			ipify,
			err,
		)
	}

	defer response.Body.Close()

	b, err := ioutil.ReadAll(response.Body)

	if err != nil {
		return "", fmt.Errorf(
			"Could not contact %v to get your public IP: %v",
			ipify,
			err,
		)
	}

	return string(b), nil
}

func main() {
	app := cli.NewApp()
	app.Author = "Daniel Robert Speed"
	app.Usage = `
	Used for testing HTTP(S) endpoints for their SSL certificates and performance.

	See: https://github.com/Redundancy/cdntest
	`
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "ip",
			Usage: "Use an explicit IP address rather than DNS provided",
		},
		cli.StringFlag{
			Name:  "cname",
			Usage: "Use an alternative DNS entry to lookup the IP",
		},
		cli.StringFlag{
			Name:  "out",
			Usage: "Save the content body to a file",
		},
	}

	app.Action = func(c *cli.Context) error {
		if c.NArg() != 1 {
			return fmt.Errorf("Need a URL argument")
		}

		return testHTTP(options{
			url:     c.Args()[0],
			ip:      c.String("ip"),
			cname:   c.String("cname"),
			outfile: c.String("out"),
		})
	}

	if err := app.Run(os.Args); err != nil {
		fmt.Printf("Error: %v", err)
		os.Exit(1)
	}
}

type options struct {
	url     string
	ip      string
	cname   string
	outfile string
}

func testHTTP(o options) error {
	cdnURL, err := url.Parse(o.url)

	if err != nil {
		return fmt.Errorf("Could not parse url: \"%v\" - %v\n", cdnURL, err)
	}

	port, err := getPort(cdnURL.Scheme)
	if err != nil {
		return err
	}

	fmt.Println("Running CDN test at", time.Now())
	myPublicIP, err := getMyPublicIP()

	if err != nil {
		return err
	}

	fmt.Println("My public IP is:", myPublicIP)

	var addrs []string

	host := cdnURL.Host
	if o.cname != "" {
		host = o.cname
	}

	beforeLookup := time.Now()
	addrs, err = net.LookupHost(host)
	fmt.Printf("Host lookup of %v took %v\n", host, time.Now().Sub(beforeLookup))

	if err != nil {
		return fmt.Errorf("Could not look up host: %v", err)
	}

	fmt.Println("DNS returned:", addrs)

	/*
		resolver-identity.cloudfront.net reflects back the IP of the dns server that
		looked it up.
	*/
	resolverIdentities, _ := net.LookupHost("resolver-identity.cloudfront.net")
	if len(resolverIdentities) > 0 {
		resolver := ""
		if resolverNames, _ := net.LookupAddr(resolverIdentities[0]); len(resolverNames) > 0 {
			resolver = resolverNames[0]
		}

		fmt.Printf(
			"Your DNS resolver is %v (%v)\n",
			resolver,
			resolverIdentities[0],
		)
	} else {
		fmt.Printf("Could not identify your DNS resolver\n")
	}

	request, _ := http.NewRequest("GET", cdnURL.String(), nil)

	// We cannot use http.Transport because it does not provide hooks
	// to get the timing metrics that we require
	// TODO: Proxy support based on environment variables
	var conn net.Conn
	var dialErr error

	ip := addrs[0]
	if o.ip != "" {
		ip = o.ip
	}

	beforeDialTime := time.Now()
	conn, dialErr = net.DialTimeout(
		"tcp",
		fmt.Sprintf("%v:%v", ip, port),
		10*time.Second,
	)
	afterDialTime := time.Now()

	defer func() {
		if conn != nil {
			conn.Close()
		}
	}()

	if dialErr != nil {
		return fmt.Errorf("Could not contact server: %v", dialErr)
	}

	fmt.Println(
		"Connected to",
		conn.RemoteAddr().String(),
		"in",
		afterDialTime.Sub(beforeDialTime),
	)

	if cdnURL.Scheme == "https" {
		config := &tls.Config{
			ServerName: cdnURL.Host,
		}

		beforeHandshakeTime := time.Now()
		tlsCon := tls.Client(conn, config)
		tlsCon.SetDeadline(time.Now().Add(10 * time.Second))
		if hsErr := tlsCon.Handshake(); hsErr != nil {
			fmt.Println("SSL handshake error:", hsErr)
		}
		fmt.Println("TLS handshake in", time.Now().Sub(beforeHandshakeTime))

		if e := tlsCon.VerifyHostname(cdnURL.Host); e != nil {
			fmt.Println("SSL was unable to verify the hostname!", e)
		}
		printableCertChain(
			tlsCon.ConnectionState().PeerCertificates,
		).Write(os.Stdout)

		conn = tlsCon
	}

	request.WriteProxy(conn)
	beforeRead := time.Now()
	response, err := http.ReadResponse(bufio.NewReader(conn), request)

	if err != nil || response == nil {
		if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
			return fmt.Errorf(
				"Timeout reading response from %v",
				cdnURL.String(),
			)
		}
		return fmt.Errorf(
			"Did not get a response (after establishing connection), server behind loadbalancer / CDN may be down: %v",
			err,
		)
	}

	if response.Body != nil {
		defer response.Body.Close()
	}

	fmt.Println("Response was", response.Status)

	if response.Body != nil {
		var bodyWriters []io.Writer
		md5 := md5.New()
		var writtenBytes int64

		bodyWriters = append(bodyWriters, md5)

		if o.outfile != "" {
			f, openFileErr := os.OpenFile(o.outfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
			if openFileErr != nil {
				return fmt.Errorf("Unable to open %v for writing: %v", o.outfile, openFileErr)
			}
			defer func() {
				errClose := f.Close()
				if err != nil {
					fmt.Println(errClose)
					return
				}
				fmt.Println("Wrote", o.outfile)
			}()
			bodyWriters = append(bodyWriters, f)
		}

		writtenBytes, err = io.Copy(io.MultiWriter(bodyWriters...), response.Body)
		if err != nil {
			return fmt.Errorf("Error reading content body: %v", err)
		}

		timeTaken := time.Now().Sub(beforeRead)
		bps := bytesPerSecond((float64(time.Second) / float64(timeTaken)) * float64(writtenBytes))

		fmt.Printf(
			"Reading %v bytes took %v (%v)\n",
			writtenBytes,
			timeTaken,
			bps,
		)
		fmt.Printf("MD5 of Content: %x\n", md5.Sum(nil))
	}

	if response.Header.Get("X-Amz-Cf-Id") != "" {
		fmt.Println("Amazon CloudFront Request ID:", response.Header.Get("X-Amz-Cf-Id"))

		// lookup AWS DNS server
		nameserver, _ := net.LookupHost("identity.cloudfront.net")
		names, _ := net.LookupAddr(nameserver[0])

		fmt.Printf(
			"The CloudFront DNS server you query is %v (%v)\n",
			names[0],
			nameserver[0],
		)
	}

	return nil
}

type printableCertChain []*x509.Certificate

func (cc printableCertChain) Write(o io.Writer) {
	fmt.Fprintln(o, "SSL Certificate Information")

	for _, cert := range cc {
		if cert.IsCA {
			fmt.Fprintf(
				o,
				"\t%v - Valid Until: %v\n",
				cert.Subject.CommonName,
				cert.NotAfter,
			)
		} else {
			fmt.Fprintf(
				o,
				"\t%v - Issued by: %v, Valid Until: %v\n",
				cert.Subject.CommonName,
				cert.Issuer.CommonName,
				cert.NotAfter,
			)
		}
	}

}

type bytesPerSecond int64

func (i bytesPerSecond) String() string {
	switch {
	case i < 1000:
		return fmt.Sprintf("%v bytes/s", int64(i))
	case i < 1000*1000:
		return fmt.Sprintf("%v KiB/s", int64(i)/1000)
	default:
		return fmt.Sprintf("%v MiB/s", int64(i)/1000000)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	msgpack "gopkg.in/vmihailenco/msgpack.v2"

	"github.com/go-redis/cache"
	"github.com/go-redis/redis"
	"github.com/gorilla/mux"
	"github.com/nuveo/gofn"
	"github.com/nuveo/gofn/provision"
	"github.com/urfave/negroni"
)

const (
	DEFAULT_TAG = "golang-1.10"
)

func imageSupported(tag string) bool {
	switch tag {
	case
		"golang-1.10",
		"golang-1.9",
		"golang-1.8":
		return true
	}
	return false
}

type Object struct {
	Repo   string
	Tag    string
	Cover  string
	Output bool
}

func redisConn() (ring *redis.Ring, codec *cache.Codec) {
	ring = redis.NewRing(&redis.RingOptions{
		Addrs: map[string]string{
			"server1": "redis:6379",
		},
	})
	codec = &cache.Codec{
		Redis: ring,

		Marshal: func(v interface{}) ([]byte, error) {
			return msgpack.Marshal(v)
		},
		Unmarshal: func(b []byte, v interface{}) error {
			return msgpack.Unmarshal(b, v)
		},
	}
	return
}

func repoCover(repo, imageTag string) (obj Object) {
	_, codec := redisConn()
	cacheKey := fmt.Sprintf("%s-%s", repo, imageTag)
	obj.Repo = repo
	obj.Tag = imageTag
	if !imageSupported(imageTag) {
		obj.Cover = fmt.Sprintf("Sorry, not found docker image avelino/cover.run:%s, see Supported languages: https://github.com/avelino/cover.run#supported", imageTag)
		return
	}
	if err := codec.Get(cacheKey, &obj); err != nil {
		StdOut, StdErr := run("avelino/cover.run", imageTag, repo)
		stdOut := strings.Trim(StdOut, " \n")
		obj.Cover = StdErr
		obj.Output = false
		if stdOut != "" {
			obj.Cover = stdOut
			obj.Output = true
		}
		codec.Set(&cache.Item{
			Key:        cacheKey,
			Object:     obj,
			Expiration: time.Hour,
		})
	}
	return
}

func HandlerRepoJSON(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	tag := r.URL.Query().Get("tag")
	if tag == "" {
		tag = DEFAULT_TAG
	}
	obj := repoCover(vars["repo"], tag)
	json.NewEncoder(w).Encode(obj)
}

type copier struct {
	transport http.RoundTripper
}

func (c copier) RoundTrip(request *http.Request) (*http.Response, error) {
	response, err := c.transport.RoundTrip(request)
	return response, err
}

type Repository struct {
	Repo  string
	Tag   string
	Cover string
}

func repoLatest() (repos []Repository) {
	ring, codec := redisConn()
	keys, err := ring.Keys("*").Result()
	if err != nil {
		log.Println(err)
	}

	var obj Object
	for _, key := range keys {
		if len(repos) == 5 {
			return
		}
		if err := codec.Get(key, &obj); err == nil {
			if obj.Output {
				repos = append(repos, Repository{obj.Repo, obj.Tag, obj.Cover})
			}
		}
	}
	return
}

func HandlerRepoSVG(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("X-CoverRunProxy", "CoverRunProxy")
	w.Header().Set("cache-control", "priviate, max-age=0, no-cache")
	w.Header().Set("pragma", "no-cache")
	w.Header().Set("expires", "-1")

	vars := mux.Vars(r)
	tag := r.URL.Query().Get("tag")
	if tag == "" {
		tag = DEFAULT_TAG
	}
	obj := repoCover(vars["repo"], tag)
	cover, _ := strconv.ParseFloat(strings.Replace(obj.Cover, "%", "", -1), 64)
	var color string
	if cover >= 70 {
		color = "green"
	} else if cover >= 45 {
		color = "yellow"
	} else {
		color = "red"
	}

	SHIELDS := "https://img.shields.io/badge/cover.run-%s-%s.svg?style=flat-square"
	badge := strings.Replace(fmt.Sprintf(SHIELDS, obj.Cover, color), "%", "%25", 1)

	http.Redirect(w, r, badge, http.StatusTemporaryRedirect)
	return
}

func HandlerRepo(w http.ResponseWriter, r *http.Request) {
	Body := map[string]interface{}{}
	vars := mux.Vars(r)
	repo := vars["repo"]
	tag := r.URL.Query().Get("tag")
	if tag == "" {
		tag = DEFAULT_TAG
	}
	Body["Repo"] = repo
	obj := repoCover(repo, tag)
	Body["Cover"] = obj.Cover
	Body["Tag"] = obj.Tag
	Body["repositories"] = repoLatest()
	t := template.Must(template.ParseFiles("./templates/layout.tmpl", "./templates/repo.tmpl"))
	t.Execute(w, Body)
	return
}

func Handler(w http.ResponseWriter, r *http.Request) {
	t := template.Must(template.ParseFiles("./templates/layout.tmpl", "./templates/home.tmpl"))

	Body := map[string]interface{}{}
	Body["repositories"] = repoLatest()

	t.Execute(w, Body)
	return
}

func main() {
	n := negroni.Classic()
	r := mux.NewRouter()
	r.HandleFunc("/", Handler)
	r.HandleFunc("/go/{repo:.*}.json", HandlerRepoJSON)
	r.HandleFunc("/go/{repo:.*}.svg", HandlerRepoSVG)
	r.HandleFunc("/go/{repo:.*}", HandlerRepo)
	r.PathPrefix("/assets").Handler(
		http.StripPrefix("/assets", http.FileServer(http.Dir("./assets/"))))
	n.UseHandler(r)
	n.Run(":3000")
}

func run(imageRepoName, dockerTag, repo string) (StdOut, StdErr string) {
	buildOpts := &provision.BuildOptions{
		DoNotUsePrefixImageName: true,
		ImageName:               strings.ToLower(fmt.Sprintf("%s:%s", imageRepoName, dockerTag)),
		StdIN:                   fmt.Sprintf("sh /run.sh %s", repo),
	}
	containerOpts := &provision.ContainerOptions{}
	StdOut, StdErr, err := gofn.Run(
		buildOpts, containerOpts)
	if err != nil {
		log.Println(err)
	}

	return
}
<commit_msg>added ability to decide the style of the badge. Defaults to flat-square<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	msgpack "gopkg.in/vmihailenco/msgpack.v2"

	"github.com/go-redis/cache"
	"github.com/go-redis/redis"
	"github.com/gorilla/mux"
	"github.com/nuveo/gofn"
	"github.com/nuveo/gofn/provision"
	"github.com/urfave/negroni"
)

const (
	DEFAULT_TAG = "golang-1.10"
)

func imageSupported(tag string) bool {
	switch tag {
	case
		"golang-1.10",
		"golang-1.9",
		"golang-1.8":
		return true
	}
	return false
}

type Object struct {
	Repo   string
	Tag    string
	Cover  string
	Output bool
}

func redisConn() (ring *redis.Ring, codec *cache.Codec) {
	ring = redis.NewRing(&redis.RingOptions{
		Addrs: map[string]string{
			"server1": "redis:6379",
		},
	})
	codec = &cache.Codec{
		Redis: ring,

		Marshal: func(v interface{}) ([]byte, error) {
			return msgpack.Marshal(v)
		},
		Unmarshal: func(b []byte, v interface{}) error {
			return msgpack.Unmarshal(b, v)
		},
	}
	return
}

func repoCover(repo, imageTag string) (obj Object) {
	_, codec := redisConn()
	cacheKey := fmt.Sprintf("%s-%s", repo, imageTag)
	obj.Repo = repo
	obj.Tag = imageTag
	if !imageSupported(imageTag) {
		obj.Cover = fmt.Sprintf("Sorry, not found docker image avelino/cover.run:%s, see Supported languages: https://github.com/avelino/cover.run#supported", imageTag)
		return
	}
	if err := codec.Get(cacheKey, &obj); err != nil {
		StdOut, StdErr := run("avelino/cover.run", imageTag, repo)
		stdOut := strings.Trim(StdOut, " \n")
		obj.Cover = StdErr
		obj.Output = false
		if stdOut != "" {
			obj.Cover = stdOut
			obj.Output = true
		}
		codec.Set(&cache.Item{
			Key:        cacheKey,
			Object:     obj,
			Expiration: time.Hour,
		})
	}
	return
}

func HandlerRepoJSON(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	tag := r.URL.Query().Get("tag")
	if tag == "" {
		tag = DEFAULT_TAG
	}
	obj := repoCover(vars["repo"], tag)
	json.NewEncoder(w).Encode(obj)
}

type copier struct {
	transport http.RoundTripper
}

func (c copier) RoundTrip(request *http.Request) (*http.Response, error) {
	response, err := c.transport.RoundTrip(request)
	return response, err
}

type Repository struct {
	Repo  string
	Tag   string
	Cover string
}

func repoLatest() (repos []Repository) {
	ring, codec := redisConn()
	keys, err := ring.Keys("*").Result()
	if err != nil {
		log.Println(err)
	}

	var obj Object
	for _, key := range keys {
		if len(repos) == 5 {
			return
		}
		if err := codec.Get(key, &obj); err == nil {
			if obj.Output {
				repos = append(repos, Repository{obj.Repo, obj.Tag, obj.Cover})
			}
		}
	}
	return
}

func HandlerRepoSVG(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("X-CoverRunProxy", "CoverRunProxy")
	w.Header().Set("cache-control", "priviate, max-age=0, no-cache")
	w.Header().Set("pragma", "no-cache")
	w.Header().Set("expires", "-1")

	vars := mux.Vars(r)
	tag := r.URL.Query().Get("tag")
	if tag == "" {
		tag = DEFAULT_TAG
	}

	badgeStyle := r.URL.Query().Get("style")
	if badgeStyle != "curve" {
		badgeStyle = "flat-square"
	}

	obj := repoCover(vars["repo"], tag)
	cover, _ := strconv.ParseFloat(strings.Replace(obj.Cover, "%", "", -1), 64)
	var color string
	if cover >= 70 {
		color = "green"
	} else if cover >= 45 {
		color = "yellow"
	} else {
		color = "red"
	}

	SHIELDS := "https://img.shields.io/badge/cover.run-%s-%s.svg?style=%s"
	badge := strings.Replace(fmt.Sprintf(SHIELDS, obj.Cover, color, badgeStyle), "%", "%25", 1)

	http.Redirect(w, r, badge, http.StatusTemporaryRedirect)
	return
}

func HandlerRepo(w http.ResponseWriter, r *http.Request) {
	Body := map[string]interface{}{}
	vars := mux.Vars(r)
	repo := vars["repo"]
	tag := r.URL.Query().Get("tag")
	if tag == "" {
		tag = DEFAULT_TAG
	}
	Body["Repo"] = repo
	obj := repoCover(repo, tag)
	Body["Cover"] = obj.Cover
	Body["Tag"] = obj.Tag
	Body["repositories"] = repoLatest()
	t := template.Must(template.ParseFiles("./templates/layout.tmpl", "./templates/repo.tmpl"))
	t.Execute(w, Body)
	return
}

func Handler(w http.ResponseWriter, r *http.Request) {
	t := template.Must(template.ParseFiles("./templates/layout.tmpl", "./templates/home.tmpl"))

	Body := map[string]interface{}{}
	Body["repositories"] = repoLatest()

	t.Execute(w, Body)
	return
}

func main() {
	n := negroni.Classic()
	r := mux.NewRouter()
	r.HandleFunc("/", Handler)
	r.HandleFunc("/go/{repo:.*}.json", HandlerRepoJSON)
	r.HandleFunc("/go/{repo:.*}.svg", HandlerRepoSVG)
	r.HandleFunc("/go/{repo:.*}", HandlerRepo)
	r.PathPrefix("/assets").Handler(
		http.StripPrefix("/assets", http.FileServer(http.Dir("./assets/"))))
	n.UseHandler(r)
	n.Run(":3000")
}

func run(imageRepoName, dockerTag, repo string) (StdOut, StdErr string) {
	buildOpts := &provision.BuildOptions{
		DoNotUsePrefixImageName: true,
		ImageName:               strings.ToLower(fmt.Sprintf("%s:%s", imageRepoName, dockerTag)),
		StdIN:                   fmt.Sprintf("sh /run.sh %s", repo),
	}
	containerOpts := &provision.ContainerOptions{}
	StdOut, StdErr, err := gofn.Run(
		buildOpts, containerOpts)
	if err != nil {
		log.Println(err)
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/codegangsta/cli"
	"os"
)

func newContext(c *cli.Context) {
}

func info(c *cli.Context) {
}

func main() {
	app := cli.NewApp()
	app.Name = "ctx"
	app.Usage = "CLI command to manage your working context"
	app.Commands = []cli.Command{
		{
			Name:    "new",
			Aliases: []string{"n"},
			Action:  newContext,
		},
		{
			Name:    "info",
			Aliases: []string{"i"},
			Action:  info,
		},
	}
	app.Run(os.Args)
}
<commit_msg>Simple timer<commit_after>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"io/ioutil"
	"os"
	"strconv"
	"time"
)

func newContext(c *cli.Context) {
	localFileName := os.ExpandEnv("${HOME}/.ctx")
	now := time.Now()
	ioutil.WriteFile(localFileName, []byte(strconv.FormatInt(now.UnixNano(), 10)), 0644)
}

func info(c *cli.Context) {
	localFileName := os.ExpandEnv("${HOME}/.ctx")
	now := time.Now()
	content, e1 := ioutil.ReadFile(localFileName)
	if e1 != nil {
		fmt.Printf("Cannot read %s", localFileName)
		return
	}

	then, e2 := strconv.ParseUint(string(content), 10, 64)
	if e2 != nil {
		fmt.Printf("Cannot convert %s to a timestamp", content)
		return
	}

	duration := time.Duration(uint64(now.UnixNano()) - then)

	fmt.Println(duration)
}

func main() {
	app := cli.NewApp()
	app.Name = "ctx"
	app.Usage = "CLI command to manage your working context"
	app.Commands = []cli.Command{
		{
			Name:    "new",
			Aliases: []string{"n"},
			Action:  newContext,
		},
		{
			Name:    "info",
			Aliases: []string{"i"},
			Action:  info,
		},
	}
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"code.google.com/p/go-netrc/netrc"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
)

const (
	Version = "0.0.1"
)

var (
	apiURL = "https://api.heroku.com"
	hkHome = os.Getenv("HOME") + "/.hk"
)

var stdin = bufio.NewReader(os.Stdin)

var updater = Updater{
	url: "https://github.com/downloads/kr/hk/",
	dir: hkHome + "/update/",
}

type Command struct {
	// args does not include the command name
	Run func(cmd *Command, args []string)

	Usage string // first word is the command name
	Short string // `hk help` output
	Long  string // `hk help <cmd>` output
}

func (c *Command) Name() string {
	name := c.Usage
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

// Running `hk help` will list commands in this order.
var commands = []*Command{
	cmdCreate,
	cmdCreds,
	cmdEnv,
	cmdFetchUpdate,
	cmdGet,
	cmdSet,
	cmdInfo,
	cmdList,
	cmdOpen,
	cmdPs,
	cmdVersion,
	cmdHelp,
}

var flagApp = flag.String("a", "", "app")

func main() {
	defer updater.run() // doesn't run if os.Exit is called

	if s := os.Getenv("HEROKU_API_URL"); s != "" {
		apiURL = strings.TrimRight(s, "/")
	}

	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	name := args[0]
	os.Args = args
	flag.Parse()
	args = flag.Args()

	for _, cmd := range commands {
		if cmd.Name() == name {
			cmd.Run(cmd, args)
			return
		}
	}

	fmt.Fprintf(os.Stderr, "Unknown command: %s\n", name)
	usage()
}

func getCreds(u *url.URL) (user, pass string) {
	if u.User != nil {
		pw, _ := u.User.Password()
		return u.User.Username(), pw
	}

	m, err := netrc.FindMachine(os.Getenv("HOME")+"/.netrc", u.Host)
	if err != nil {
		log.Fatal(err)
	}

	return m.Login, m.Password
}

func apiReqJson(v interface{}, meth, url string, data interface{}) {
	body, err := json.Marshal(data)
	if err != nil {
		log.Fatal(err)
	}
	apiReq(v, meth, url, "application/json", bytes.NewReader(body))
}

func apiReqForm(v interface{}, meth, url string, data url.Values) {
	body := strings.NewReader(data.Encode())
	apiReq(v, meth, url, "application/x-www-form-urlencoded", body)
}

func getApiReq(v interface{}, url string) {
	apiReq(v, "GET", url, "", nil)
}

func apiReq(v interface{}, meth, url, contentType string, body io.Reader) {
	req, err := http.NewRequest(meth, url, body)
	if err != nil {
		log.Fatal(err)
	}

	req.SetBasicAuth(getCreds(req.URL))
	req.Header.Add("User-Agent", "hk/"+Version)
	req.Header.Add("Accept", "application/json")
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	fmt.Fprintf(os.Stderr, "%#v\n", req)
	fmt.Fprintf(os.Stderr, "%#v\n", req.URL)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	if res.StatusCode == 401 {
		log.Fatal("Unauthorized")
	}
	if res.StatusCode == 403 {
		log.Fatal("Unauthorized")
	}
	if res.StatusCode/100 != 2 { // 200, 201, 202, etc
		log.Fatal("Unexpected error: ", res.Status)
	}

	if msg := res.Header.Get("X-Heroku-Warning"); msg != "" {
		fmt.Fprintln(os.Stderr, strings.TrimSpace(msg))
	}

	if v != nil {
		err = json.NewDecoder(res.Body).Decode(v)
		if err != nil {
			log.Fatal(err)
		}
	}
}

func app() string {
	if *flagApp != "" {
		return *flagApp
	}
	out, err := exec.Command("git", "remote", "show", "-n", "heroku").Output()
	if err != nil {
		log.Fatal(err)
	}
	s := string(out)
	const sign = "Fetch URL: "
	i := strings.Index(s, sign)
	if i < 0 {
		log.Fatal("could not find git remote named 'heroku'")
	}
	s = s[i+len(sign):]
	i = strings.Index(s, "\n")
	if i >= 0 {
		s = s[:i]
	}
	const pre = "git@heroku.com:"
	const suf = ".git"
	if !strings.HasPrefix(s, pre) || !strings.HasSuffix(s, suf) {
		log.Fatal("could not find app name in heroku git remote")
	}
	return s[len(pre) : len(s)-len(suf)]
}
<commit_msg>remove debugging code<commit_after>package main

import (
	"bufio"
	"bytes"
	"code.google.com/p/go-netrc/netrc"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
)

const (
	Version = "0.0.1"
)

var (
	apiURL = "https://api.heroku.com"
	hkHome = os.Getenv("HOME") + "/.hk"
)

var stdin = bufio.NewReader(os.Stdin)

var updater = Updater{
	url: "https://github.com/downloads/kr/hk/",
	dir: hkHome + "/update/",
}

type Command struct {
	// args does not include the command name
	Run func(cmd *Command, args []string)

	Usage string // first word is the command name
	Short string // `hk help` output
	Long  string // `hk help <cmd>` output
}

func (c *Command) Name() string {
	name := c.Usage
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

// Running `hk help` will list commands in this order.
var commands = []*Command{
	cmdCreate,
	cmdCreds,
	cmdEnv,
	cmdFetchUpdate,
	cmdGet,
	cmdSet,
	cmdInfo,
	cmdList,
	cmdOpen,
	cmdPs,
	cmdVersion,
	cmdHelp,
}

var flagApp = flag.String("a", "", "app")

func main() {
	defer updater.run() // doesn't run if os.Exit is called

	if s := os.Getenv("HEROKU_API_URL"); s != "" {
		apiURL = strings.TrimRight(s, "/")
	}

	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	name := args[0]
	os.Args = args
	flag.Parse()
	args = flag.Args()

	for _, cmd := range commands {
		if cmd.Name() == name {
			cmd.Run(cmd, args)
			return
		}
	}

	fmt.Fprintf(os.Stderr, "Unknown command: %s\n", name)
	usage()
}

func getCreds(u *url.URL) (user, pass string) {
	if u.User != nil {
		pw, _ := u.User.Password()
		return u.User.Username(), pw
	}

	m, err := netrc.FindMachine(os.Getenv("HOME")+"/.netrc", u.Host)
	if err != nil {
		log.Fatal(err)
	}

	return m.Login, m.Password
}

func apiReqJson(v interface{}, meth, url string, data interface{}) {
	body, err := json.Marshal(data)
	if err != nil {
		log.Fatal(err)
	}
	apiReq(v, meth, url, "application/json", bytes.NewReader(body))
}

func apiReqForm(v interface{}, meth, url string, data url.Values) {
	body := strings.NewReader(data.Encode())
	apiReq(v, meth, url, "application/x-www-form-urlencoded", body)
}

func getApiReq(v interface{}, url string) {
	apiReq(v, "GET", url, "", nil)
}

func apiReq(v interface{}, meth, url, contentType string, body io.Reader) {
	req, err := http.NewRequest(meth, url, body)
	if err != nil {
		log.Fatal(err)
	}

	req.SetBasicAuth(getCreds(req.URL))
	req.Header.Add("User-Agent", "hk/"+Version)
	req.Header.Add("Accept", "application/json")
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	if res.StatusCode == 401 {
		log.Fatal("Unauthorized")
	}
	if res.StatusCode == 403 {
		log.Fatal("Unauthorized")
	}
	if res.StatusCode/100 != 2 { // 200, 201, 202, etc
		log.Fatal("Unexpected error: ", res.Status)
	}

	if msg := res.Header.Get("X-Heroku-Warning"); msg != "" {
		fmt.Fprintln(os.Stderr, strings.TrimSpace(msg))
	}

	if v != nil {
		err = json.NewDecoder(res.Body).Decode(v)
		if err != nil {
			log.Fatal(err)
		}
	}
}

func app() string {
	if *flagApp != "" {
		return *flagApp
	}
	out, err := exec.Command("git", "remote", "show", "-n", "heroku").Output()
	if err != nil {
		log.Fatal(err)
	}
	s := string(out)
	const sign = "Fetch URL: "
	i := strings.Index(s, sign)
	if i < 0 {
		log.Fatal("could not find git remote named 'heroku'")
	}
	s = s[i+len(sign):]
	i = strings.Index(s, "\n")
	if i >= 0 {
		s = s[:i]
	}
	const pre = "git@heroku.com:"
	const suf = ".git"
	if !strings.HasPrefix(s, pre) || !strings.HasSuffix(s, suf) {
		log.Fatal("could not find app name in heroku git remote")
	}
	return s[len(pre) : len(s)-len(suf)]
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"sort"
	"sync"
	"time"
)

var (
	version   = "0.2.0"
	github    = "https://github.com/ekalinin/awsping"
	useragent = fmt.Sprintf("AwsPing/%s (+%s)", version, github)
)

var (
	repeats = flag.Int("repeats", 1, "Number of repeats")
)

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func mkRandoString(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

// AWSRegion description of the AWS EC2 region
type AWSRegion struct {
	Name      string
	Code      string
	Latencies []time.Duration
	Error     error
}

// CheckLatencyHTTP Test Latency via HTTP
func (r *AWSRegion) CheckLatencyHTTP(wg *sync.WaitGroup) {
	defer wg.Done()
	url := fmt.Sprintf("http://dynamodb.%s.amazonaws.com/ping?x=%s",
		r.Code, mkRandoString(13))
	client := &http.Client{}

	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("User-Agent", useragent)

	start := time.Now()
	resp, err := client.Do(req)
	r.Latencies = append(r.Latencies, time.Since(start))
	defer resp.Body.Close()

	r.Error = err
}

// CheckLatencyTCP Test Latency via TCP
func (r *AWSRegion) CheckLatencyTCP(wg *sync.WaitGroup) {
	defer wg.Done()
	tcpAddr, err := net.ResolveTCPAddr("tcp4",
		fmt.Sprintf("dynamodb.%s.amazonaws.com:80", r.Code))

	start := time.Now()
	conn, _ := net.DialTCP("tcp", nil, tcpAddr)
	r.Latencies = append(r.Latencies, time.Since(start))
	defer conn.Close()
	_, _ = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
	_, _ = ioutil.ReadAll(conn)

	r.Error = err
}

// GetLatency returns Latency in ms
func (r *AWSRegion) GetLatency() float64 {
	sum := float64(0)
	for _, l := range r.Latencies {
		sum += float64(l.Nanoseconds()) / 1000 / 1000
	}
	return sum / float64(len(r.Latencies))
}

// AWSRegions slice of the AWSRegion
type AWSRegions []AWSRegion

func (rs AWSRegions) Len() int {
	return len(rs)
}

func (rs AWSRegions) Less(i, j int) bool {
	return rs[i].GetLatency() < rs[j].GetLatency()
}

func (rs AWSRegions) Swap(i, j int) {
	rs[i], rs[j] = rs[j], rs[i]
}

// CalcLatency returns list of aws regions sorted by Latency
func CalcLatency(repeats int) *AWSRegions {
	regions := AWSRegions{
		{Name: "US-East (Virginia)", Code: "us-east-1"},
		{Name: "US-West (California)", Code: "us-west-1"},
		{Name: "US-West (Oregon)", Code: "us-west-2"},
		{Name: "Asia Pacific (Mumbai)", Code: "ap-south-1"},
		{Name: "Asia Pacific (Seoul)", Code: "ap-northeast-2"},
		{Name: "Asia Pacific (Singapore)", Code: "ap-southeast-1"},
		{Name: "Asia Pacific (Sydney)", Code: "ap-southeast-2"},
		{Name: "Asia Pacific (Tokyo)", Code: "ap-northeast-1"},
		{Name: "Europe (Ireland)", Code: "eu-west-1"},
		{Name: "Europe (Frankfurt)", Code: "eu-central-1"},
		{Name: "South America (São Paulo)", Code: "sa-east-1"},
		//{Name: "China (Beijing)", Code: "cn-north-1"},
	}
	var wg sync.WaitGroup

	for n := 1; n <= repeats; n++ {

		wg.Add(len(regions))

		for i := range regions {
			go regions[i].CheckLatencyTCP(&wg)
		}

		wg.Wait()
	}

	sort.Sort(regions)
	return &regions
}

func main() {

	flag.Parse()

	regions := *CalcLatency(*repeats)

	outFmt := "%5v %-30s %20s\n"
	fmt.Printf(outFmt, "", "Region", "Latency")
	for i, r := range regions {
		ms := fmt.Sprintf("%.2f ms", r.GetLatency())
		fmt.Printf(outFmt, i, r.Name, ms)
	}
}

func init() {
	rand.Seed(time.Now().UnixNano())
}
<commit_msg>added option to test via tcp or http<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"sort"
	"sync"
	"time"
)

var (
	version   = "0.2.0"
	github    = "https://github.com/ekalinin/awsping"
	useragent = fmt.Sprintf("AwsPing/%s (+%s)", version, github)
)

var (
	repeats = flag.Int("repeats", 1, "Number of repeats")
	useHTTP = flag.Bool("http", false, "Use http transport (default is tcp)")
)

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func mkRandoString(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

// AWSRegion description of the AWS EC2 region
type AWSRegion struct {
	Name      string
	Code      string
	Latencies []time.Duration
	Error     error
}

// CheckLatencyHTTP Test Latency via HTTP
func (r *AWSRegion) CheckLatencyHTTP(wg *sync.WaitGroup) {
	defer wg.Done()
	url := fmt.Sprintf("http://dynamodb.%s.amazonaws.com/ping?x=%s",
		r.Code, mkRandoString(13))
	client := &http.Client{}

	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("User-Agent", useragent)

	start := time.Now()
	resp, err := client.Do(req)
	r.Latencies = append(r.Latencies, time.Since(start))
	defer resp.Body.Close()

	r.Error = err
}

// CheckLatencyTCP Test Latency via TCP
func (r *AWSRegion) CheckLatencyTCP(wg *sync.WaitGroup) {
	defer wg.Done()
	tcpAddr, err := net.ResolveTCPAddr("tcp4",
		fmt.Sprintf("dynamodb.%s.amazonaws.com:80", r.Code))

	start := time.Now()
	conn, _ := net.DialTCP("tcp", nil, tcpAddr)
	r.Latencies = append(r.Latencies, time.Since(start))
	defer conn.Close()
	_, _ = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
	_, _ = ioutil.ReadAll(conn)

	r.Error = err
}

// GetLatency returns Latency in ms
func (r *AWSRegion) GetLatency() float64 {
	sum := float64(0)
	for _, l := range r.Latencies {
		sum += float64(l.Nanoseconds()) / 1000 / 1000
	}
	return sum / float64(len(r.Latencies))
}

// AWSRegions slice of the AWSRegion
type AWSRegions []AWSRegion

func (rs AWSRegions) Len() int {
	return len(rs)
}

func (rs AWSRegions) Less(i, j int) bool {
	return rs[i].GetLatency() < rs[j].GetLatency()
}

func (rs AWSRegions) Swap(i, j int) {
	rs[i], rs[j] = rs[j], rs[i]
}

// CalcLatency returns list of aws regions sorted by Latency
func CalcLatency(repeats int, useHTTP bool) *AWSRegions {
	regions := AWSRegions{
		{Name: "US-East (Virginia)", Code: "us-east-1"},
		{Name: "US-West (California)", Code: "us-west-1"},
		{Name: "US-West (Oregon)", Code: "us-west-2"},
		{Name: "Asia Pacific (Mumbai)", Code: "ap-south-1"},
		{Name: "Asia Pacific (Seoul)", Code: "ap-northeast-2"},
		{Name: "Asia Pacific (Singapore)", Code: "ap-southeast-1"},
		{Name: "Asia Pacific (Sydney)", Code: "ap-southeast-2"},
		{Name: "Asia Pacific (Tokyo)", Code: "ap-northeast-1"},
		{Name: "Europe (Ireland)", Code: "eu-west-1"},
		{Name: "Europe (Frankfurt)", Code: "eu-central-1"},
		{Name: "South America (São Paulo)", Code: "sa-east-1"},
		//{Name: "China (Beijing)", Code: "cn-north-1"},
	}
	var wg sync.WaitGroup

	for n := 1; n <= repeats; n++ {

		wg.Add(len(regions))

		for i := range regions {
			if useHTTP {
				go regions[i].CheckLatencyHTTP(&wg)
			} else {
				go regions[i].CheckLatencyTCP(&wg)
			}

		}

		wg.Wait()
	}

	sort.Sort(regions)
	return &regions
}

func main() {

	flag.Parse()

	regions := *CalcLatency(*repeats, *useHTTP)

	outFmt := "%5v %-30s %20s\n"
	fmt.Printf(outFmt, "", "Region", "Latency")
	for i, r := range regions {
		ms := fmt.Sprintf("%.2f ms", r.GetLatency())
		fmt.Printf(outFmt, i, r.Name, ms)
	}
}

func init() {
	rand.Seed(time.Now().UnixNano())
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"container/list"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"path"
	"strconv"
	"strings"
	"text/template"
	"time"
)

const (
	httpListenPort                   = 8081
	refreshRate                      = 60 //in seconds
	wikiURI                          = "https://wiki.tox.chat/users/nodes?do=export_raw"
	maxUDPPacketSize                 = 2048
	getNodesPacketID                 = 2
	sendNodesIpv6PacketID            = 4
	bootstrapInfoPacketID            = 240
	bootstrapInfoPacketLength        = 78
	tcpHandshakePacketLength         = 128
	tcpHandshakeResponsePacketLength = 96
	maxMOTDLength                    = 256
	queryTimeout                     = 4 //in seconds
	dialerTimeout                    = 4 //in seconds
)

var (
	lastScan     int64
	nodesList    = list.New()
	crypto, _    = NewCrypto()
	tcpPorts     = []int{443, 3389, 33445}
	lowerFuncMap = template.FuncMap{"lower": strings.ToLower}
)

type tcpHandshakeResult struct {
	Port  int
	Error error
}

type toxStatus struct {
	LastScan       int64     `json:"last_scan"`
	LastScanString string    `json:"last_scan_string"`
	Nodes          []toxNode `json:"nodes"`
}

type toxNode struct {
	Ipv4Address    string `json:"ipv4"`
	Ipv6Address    string `json:"ipv6"`
	Port           int    `json:"port"`
	TCPPorts       []int  `json:"tcp_ports"`
	PublicKey      string `json:"public_key"`
	Maintainer     string `json:"maintainer"`
	Location       string `json:"location"`
	Status         bool   `json:"status"`
	Version        string `json:"version"`
	MOTD           string `json:"motd"`
	LastPing       int64  `json:"last_ping"`
	LastPingString string `json:"last_ping_string"`
}

func main() {
	if crypto == nil {
		log.Fatalf("Could not generate keypair")
	}

	go probeLoop()

	http.HandleFunc("/", handleHTTPRequest)
	http.HandleFunc("/json", handleJSONRequest)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", httpListenPort), nil))
}

func handleHTTPRequest(w http.ResponseWriter, r *http.Request) {
	urlPath := r.URL.Path[1:]
	if r.URL.Path == "/" {
		renderMainPage(w, "index.html")
		return
	}

	//TODO: make this more efficient
	data, err := ioutil.ReadFile(path.Join("./assets/", string(urlPath)))
	if err != nil {
		http.Error(w, http.StatusText(404), 404)
	} else {
		w.Write(data)
	}
}

func renderMainPage(w http.ResponseWriter, urlPath string) {
	tmpl, err := template.New("index.html").
		Funcs(lowerFuncMap).
		ParseFiles(path.Join("./assets/", string(urlPath)))

	if err != nil {
		http.Error(w, http.StatusText(500), 500)
		log.Printf("Internal server error while trying to serve index: %s", err.Error())
	} else {
		nodes := nodesListToSlice(nodesList)
		response := toxStatus{lastScan, time.Unix(lastScan, 0).String(), nodes}
		tmpl.Execute(w, response)
	}
}

func handleJSONRequest(w http.ResponseWriter, r *http.Request) {
	nodes := nodesListToSlice(nodesList)
	response := toxStatus{lastScan, time.Unix(lastScan, 0).String(), nodes}

	bytes, err := json.Marshal(response)
	if err != nil {
		http.Error(w, http.StatusText(500), 500)
		return
	}

	w.Write(bytes)
}

func probeLoop() {
	for {
		nodes, err := parseNodes()
		if err != nil {
			log.Printf("Error while trying to parse nodes: %s", err.Error())
		} else {
			c := make(chan *toxNode)
			for e := nodes.Front(); e != nil; e = e.Next() {
				node, _ := e.Value.(*toxNode)
				go func() { c <- probeNode(node) }()
			}

			for i := 0; i < nodes.Len(); i++ {
				_ = <-c
			}

			nodesList = nodes
			lastScan = time.Now().Unix()
		}

		time.Sleep(refreshRate * time.Second)
	}
}

func probeNode(node *toxNode) *toxNode {
	conn, err := newNodeConn(node, node.Port, "udp")
	if err != nil {
		return node
	}

	err = getBootstrapInfo(node, conn)
	if err != nil {
		fmt.Printf("%s\n", err.Error())
	}

	conn.Close()
	conn, err = newNodeConn(node, node.Port, "udp")
	if err != nil {
		return node
	}

	err = getNodes(node, conn)
	if err != nil {
		fmt.Printf("%s\n", err.Error())
		return node
	}
	conn.Close()

	ports := tcpPorts
	if !contains(tcpPorts, node.Port) {
		ports = append(ports, node.Port)
	}

	c := make(chan tcpHandshakeResult)
	for _, port := range ports {
		go func(p int) {
			conn, err = newNodeConn(node, p, "tcp")
			if err != nil {
				fmt.Printf("%s\n", err.Error())
				c <- tcpHandshakeResult{p, err}
			} else {
				c <- tryTCPHandshake(node, conn, p)
			}
		}(port)
	}

	for i := 0; i < len(ports); i++ {
		result := <-c
		if result.Error != nil {
			fmt.Printf("%s\n", result.Error.Error())
		} else {
			node.TCPPorts = append(node.TCPPorts, result.Port)
		}
	}

	node.LastPing = time.Now().Unix()
	node.Status = true
	return node
}

func getNodes(node *toxNode, conn net.Conn) error {
	nodePublicKey, err := hex.DecodeString(node.PublicKey)
	if err != nil {
		return err
	}

	plain := make([]byte, len(crypto.PublicKey)+8)
	copy(plain, crypto.PublicKey)
	copy(plain[len(crypto.PublicKey):], nextBytes(8)) //ping id

	nonce := nextNonce()
	sharedKey := crypto.CreateSharedKey(nodePublicKey)
	encrypted := encryptData(plain, sharedKey, nonce)[16:]

	payload := make([]byte, 1+len(crypto.PublicKey)+len(nonce)+len(encrypted))
	payload[0] = getNodesPacketID
	copy(payload[1:], crypto.PublicKey)
	copy(payload[1+len(crypto.PublicKey):], nonce)
	copy(payload[1+len(crypto.PublicKey)+len(nonce):], encrypted)
	conn.Write(payload)

	buffer := make([]byte, maxUDPPacketSize)
	_, err = conn.Read(buffer)

	if err != nil {
		return err
	} /*else if payload[0] != sendNodesIpv6PacketID {
		return fmt.Errorf("packet id: %d is not a sendnodesipv6 packet", payload[0])
	}

	right now we're happy if a node responds to our 'getnodes' request, without even validating the response
	this needs some more work

	on a side note: it looks like nodes are sending a 'getnodes' packet before 'sendnodesipv6',
	*/

	return nil
}

func getBootstrapInfo(node *toxNode, conn net.Conn) error {
	payload := make([]byte, bootstrapInfoPacketLength)
	payload[0] = bootstrapInfoPacketID
	conn.Write(payload)

	buffer := make([]byte, 1+4+maxMOTDLength)
	read, err := conn.Read(buffer)

	if err != nil {
		return err
	} else if buffer[0] != bootstrapInfoPacketID {
		return fmt.Errorf("packet id: %d is not a bootstrap info packet", buffer[0])
	}

	buffer = buffer[:read]
	if len(buffer) < 1+4 {
		return errors.New("bootstrap info packet too small")
	}

	node.Version = fmt.Sprintf("%d", binary.BigEndian.Uint32(buffer[1:1+4]))
	node.MOTD = string(bytes.Trim(buffer[1+4:], "\x00"))
	return nil
}

func tryTCPHandshake(node *toxNode, conn net.Conn, port int) tcpHandshakeResult {
	/* NOTE: conn is closed at the end of this function */
	nodePublicKey, err := hex.DecodeString(node.PublicKey)
	if err != nil {
		return tcpHandshakeResult{port, err}
	}

	nonce := nextNonce()
	baseNonce := nextNonce()
	plain := make([]byte, len(crypto.PublicKey)+len(baseNonce))
	tempCrypto, _ := NewCrypto()

	copy(plain, tempCrypto.PublicKey)
	copy(plain[len(tempCrypto.PublicKey):], baseNonce)
	sharedKey := crypto.CreateSharedKey(nodePublicKey)
	encrypted := encryptData(plain, sharedKey, nonce)[16:]

	payload := make([]byte, tcpHandshakePacketLength)
	copy(payload, crypto.PublicKey)
	copy(payload[len(crypto.PublicKey):], nonce)
	copy(payload[len(crypto.PublicKey)+len(nonce):], encrypted)
	conn.Write(payload)

	buffer := make([]byte, tcpHandshakeResponsePacketLength)
	read, err := conn.Read(buffer)

	var result tcpHandshakeResult

	if err != nil {
		result = tcpHandshakeResult{port, err}
	} else if read != tcpHandshakeResponsePacketLength {
		result = tcpHandshakeResult{
			port,
			errors.New("tcp handshake response has an incorrect length"),
		}
	} else {
		result = tcpHandshakeResult{port, nil}
	}

	conn.Close()
	return result
}

func newNodeConn(node *toxNode, port int, network string) (net.Conn, error) {
	dialer := net.Dialer{}
	dialer.Deadline = time.Now().Add(dialerTimeout * time.Second)

	conn, err := dialer.Dial(network, fmt.Sprintf("%s:%d", node.Ipv4Address, port))
	if err != nil {
		return nil, err
	}

	conn.SetReadDeadline(time.Now().Add(queryTimeout * time.Second))
	return conn, nil
}

func parseNode(nodeString string) *toxNode {
	nodeString = stripSpaces(nodeString)
	if !strings.HasPrefix(nodeString, "|") {
		return nil
	}

	lineParts := strings.Split(nodeString, "|")
	if port, err := strconv.Atoi(strings.TrimSpace(lineParts[3])); err == nil && len(lineParts) == 8 {
		node := toxNode{
			strings.TrimSpace(lineParts[1]),
			strings.TrimSpace(lineParts[2]),
			port,
			[]int{},
			strings.TrimSpace(lineParts[4]),
			strings.TrimSpace(lineParts[5]),
			strings.TrimSpace(lineParts[6]),
			false,
			"",
			"",
			0,
			"Never",
		}

		if node.Ipv6Address == "NONE" {
			node.Ipv6Address = "-"
		}

		return &node
	}

	return nil
}

func parseNodes() (*list.List, error) {
	res, err := http.Get(wikiURI)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	nodes := list.New()
	content, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	lines := strings.Split(string(content), "\n")
	for _, line := range lines {
		node := parseNode(line)
		if node == nil {
			continue
		}

		oldNode := getOldNode(node.PublicKey)
		if oldNode != nil { //transfer last ping info
			node.LastPing = oldNode.LastPing
			node.LastPingString = oldNode.LastPingString
		}

		nodes.PushBack(node)
	}
	return nodes, nil
}

func getOldNode(publicKey string) *toxNode {
	for e := nodesList.Front(); e != nil; e = e.Next() {
		node, _ := e.Value.(*toxNode)
		if node.PublicKey == publicKey {
			return node
		}
	}
	return nil
}
<commit_msg>Some work on adding command line tool functionality to this thing<commit_after>package main

import (
	"bytes"
	"container/list"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"text/template"
	"time"
)

const (
	httpListenPort                   = 8081
	refreshRate                      = 60 //in seconds
	wikiURI                          = "https://wiki.tox.chat/users/nodes?do=export_raw"
	maxUDPPacketSize                 = 2048
	getNodesPacketID                 = 2
	sendNodesIpv6PacketID            = 4
	bootstrapInfoPacketID            = 240
	bootstrapInfoPacketLength        = 78
	tcpHandshakePacketLength         = 128
	tcpHandshakeResponsePacketLength = 96
	maxMOTDLength                    = 256
	queryTimeout                     = 4 //in seconds
	dialerTimeout                    = 4 //in seconds
)

var (
	lastScan     int64
	nodesList    = list.New()
	crypto, _    = NewCrypto()
	tcpPorts     = []int{443, 3389, 33445}
	lowerFuncMap = template.FuncMap{"lower": strings.ToLower}
)

//flags
var (
	networkFlag = flag.String("net", "udp", "network type, either 'udp' or 'tcp'")
	ipFlag      = flag.String("ip", "127.0.0.1", "ip address to scan, can only be ipv4 for now")
	portFlag    = flag.Int("port", 33445, "port(s) to scan")
	keyFlag     = flag.String("key", "", "public key of the node")
)

type tcpHandshakeResult struct {
	Port  int
	Error error
}

type toxStatus struct {
	LastScan       int64     `json:"last_scan"`
	LastScanString string    `json:"last_scan_string"`
	Nodes          []toxNode `json:"nodes"`
}

type toxNode struct {
	Ipv4Address    string `json:"ipv4"`
	Ipv6Address    string `json:"ipv6"`
	Port           int    `json:"port"`
	TCPPorts       []int  `json:"tcp_ports"`
	PublicKey      string `json:"public_key"`
	Maintainer     string `json:"maintainer"`
	Location       string `json:"location"`
	Status         bool   `json:"status"`
	Version        string `json:"version"`
	MOTD           string `json:"motd"`
	LastPing       int64  `json:"last_ping"`
	LastPingString string `json:"last_ping_string"`
}

func main() {
	if crypto == nil {
		log.Fatalf("Could not generate keypair")
	}

	if handleFlags() {
		return
	}

	go probeLoop()

	http.HandleFunc("/", handleHTTPRequest)
	http.HandleFunc("/json", handleJSONRequest)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", httpListenPort), nil))
}

func handleFlags() bool {
	if len(os.Args) < 2 {
		return false
	}

	flag.Parse()

	if len(*keyFlag) != 64 {
		log.Fatalln("error: public key must have a lenght of 64 hex characters")
	}

	node := toxNode{}
	node.Ipv4Address = *ipFlag
	node.PublicKey = *keyFlag
	node.Port = *portFlag

	if *networkFlag == "udp" {
		probeNode(&node)
	} else if *networkFlag == "tcp" {

	} else {
		log.Fatalf("error: unsupported network specified: %s", *networkFlag)
	}

	return true
}

func handleHTTPRequest(w http.ResponseWriter, r *http.Request) {
	urlPath := r.URL.Path[1:]
	if r.URL.Path == "/" {
		renderMainPage(w, "index.html")
		return
	}

	//TODO: make this more efficient
	data, err := ioutil.ReadFile(path.Join("./assets/", string(urlPath)))
	if err != nil {
		http.Error(w, http.StatusText(404), 404)
	} else {
		w.Write(data)
	}
}

func renderMainPage(w http.ResponseWriter, urlPath string) {
	tmpl, err := template.New("index.html").
		Funcs(lowerFuncMap).
		ParseFiles(path.Join("./assets/", string(urlPath)))

	if err != nil {
		http.Error(w, http.StatusText(500), 500)
		log.Printf("Internal server error while trying to serve index: %s", err.Error())
	} else {
		nodes := nodesListToSlice(nodesList)
		response := toxStatus{lastScan, time.Unix(lastScan, 0).String(), nodes}
		tmpl.Execute(w, response)
	}
}

func handleJSONRequest(w http.ResponseWriter, r *http.Request) {
	nodes := nodesListToSlice(nodesList)
	response := toxStatus{lastScan, time.Unix(lastScan, 0).String(), nodes}

	bytes, err := json.Marshal(response)
	if err != nil {
		http.Error(w, http.StatusText(500), 500)
		return
	}

	w.Write(bytes)
}

func probeLoop() {
	for {
		nodes, err := parseNodes()
		if err != nil {
			log.Printf("Error while trying to parse nodes: %s", err.Error())
		} else {
			c := make(chan *toxNode)
			for e := nodes.Front(); e != nil; e = e.Next() {
				node, _ := e.Value.(*toxNode)
				go func() { c <- probeNode(node) }()
			}

			for i := 0; i < nodes.Len(); i++ {
				_ = <-c
			}

			nodesList = nodes
			lastScan = time.Now().Unix()
		}

		time.Sleep(refreshRate * time.Second)
	}
}

func probeNode(node *toxNode) *toxNode {
	conn, err := newNodeConn(node, node.Port, "udp")
	if err != nil {
		return node
	}

	err = getBootstrapInfo(node, conn)
	if err != nil {
		fmt.Printf("%s\n", err.Error())
	}

	conn.Close()
	conn, err = newNodeConn(node, node.Port, "udp")
	if err != nil {
		return node
	}

	err = getNodes(node, conn)
	if err != nil {
		fmt.Printf("%s\n", err.Error())
		return node
	}
	conn.Close()

	ports := tcpPorts
	if !contains(tcpPorts, node.Port) {
		ports = append(ports, node.Port)
	}

	c := make(chan tcpHandshakeResult)
	for _, port := range ports {
		go func(p int) {
			conn, err = newNodeConn(node, p, "tcp")
			if err != nil {
				fmt.Printf("%s\n", err.Error())
				c <- tcpHandshakeResult{p, err}
			} else {
				c <- tryTCPHandshake(node, conn, p)
			}
		}(port)
	}

	for i := 0; i < len(ports); i++ {
		result := <-c
		if result.Error != nil {
			fmt.Printf("%s\n", result.Error.Error())
		} else {
			node.TCPPorts = append(node.TCPPorts, result.Port)
		}
	}

	node.LastPing = time.Now().Unix()
	node.Status = true
	return node
}

func getNodes(node *toxNode, conn net.Conn) error {
	nodePublicKey, err := hex.DecodeString(node.PublicKey)
	if err != nil {
		return err
	}

	plain := make([]byte, len(crypto.PublicKey)+8)
	copy(plain, crypto.PublicKey)
	copy(plain[len(crypto.PublicKey):], nextBytes(8)) //ping id

	nonce := nextNonce()
	sharedKey := crypto.CreateSharedKey(nodePublicKey)
	encrypted := encryptData(plain, sharedKey, nonce)[16:]

	payload := make([]byte, 1+len(crypto.PublicKey)+len(nonce)+len(encrypted))
	payload[0] = getNodesPacketID
	copy(payload[1:], crypto.PublicKey)
	copy(payload[1+len(crypto.PublicKey):], nonce)
	copy(payload[1+len(crypto.PublicKey)+len(nonce):], encrypted)
	conn.Write(payload)

	buffer := make([]byte, maxUDPPacketSize)
	_, err = conn.Read(buffer)

	if err != nil {
		return err
	} /*else if payload[0] != sendNodesIpv6PacketID {
		return fmt.Errorf("packet id: %d is not a sendnodesipv6 packet", payload[0])
	}

	right now we're happy if a node responds to our 'getnodes' request, without even validating the response
	this needs some more work

	on a side note: it looks like nodes are sending a 'getnodes' packet before 'sendnodesipv6',
	*/

	return nil
}

func getBootstrapInfo(node *toxNode, conn net.Conn) error {
	payload := make([]byte, bootstrapInfoPacketLength)
	payload[0] = bootstrapInfoPacketID
	conn.Write(payload)

	buffer := make([]byte, 1+4+maxMOTDLength)
	read, err := conn.Read(buffer)

	if err != nil {
		return err
	} else if buffer[0] != bootstrapInfoPacketID {
		return fmt.Errorf("packet id: %d is not a bootstrap info packet", buffer[0])
	}

	buffer = buffer[:read]
	if len(buffer) < 1+4 {
		return errors.New("bootstrap info packet too small")
	}

	node.Version = fmt.Sprintf("%d", binary.BigEndian.Uint32(buffer[1:1+4]))
	node.MOTD = string(bytes.Trim(buffer[1+4:], "\x00"))
	return nil
}

func tryTCPHandshake(node *toxNode, conn net.Conn, port int) tcpHandshakeResult {
	/* NOTE: conn is closed at the end of this function */
	nodePublicKey, err := hex.DecodeString(node.PublicKey)
	if err != nil {
		return tcpHandshakeResult{port, err}
	}

	nonce := nextNonce()
	baseNonce := nextNonce()
	plain := make([]byte, len(crypto.PublicKey)+len(baseNonce))
	tempCrypto, _ := NewCrypto()

	copy(plain, tempCrypto.PublicKey)
	copy(plain[len(tempCrypto.PublicKey):], baseNonce)
	sharedKey := crypto.CreateSharedKey(nodePublicKey)
	encrypted := encryptData(plain, sharedKey, nonce)[16:]

	payload := make([]byte, tcpHandshakePacketLength)
	copy(payload, crypto.PublicKey)
	copy(payload[len(crypto.PublicKey):], nonce)
	copy(payload[len(crypto.PublicKey)+len(nonce):], encrypted)
	conn.Write(payload)

	buffer := make([]byte, tcpHandshakeResponsePacketLength)
	read, err := conn.Read(buffer)

	var result tcpHandshakeResult

	if err != nil {
		result = tcpHandshakeResult{port, err}
	} else if read != tcpHandshakeResponsePacketLength {
		result = tcpHandshakeResult{
			port,
			errors.New("tcp handshake response has an incorrect length"),
		}
	} else {
		result = tcpHandshakeResult{port, nil}
	}

	conn.Close()
	return result
}

func newNodeConn(node *toxNode, port int, network string) (net.Conn, error) {
	dialer := net.Dialer{}
	dialer.Deadline = time.Now().Add(dialerTimeout * time.Second)

	conn, err := dialer.Dial(network, fmt.Sprintf("%s:%d", node.Ipv4Address, port))
	if err != nil {
		return nil, err
	}

	conn.SetReadDeadline(time.Now().Add(queryTimeout * time.Second))
	return conn, nil
}

func parseNode(nodeString string) *toxNode {
	nodeString = stripSpaces(nodeString)
	if !strings.HasPrefix(nodeString, "|") {
		return nil
	}

	lineParts := strings.Split(nodeString, "|")
	if port, err := strconv.Atoi(strings.TrimSpace(lineParts[3])); err == nil && len(lineParts) == 8 {
		node := toxNode{
			strings.TrimSpace(lineParts[1]),
			strings.TrimSpace(lineParts[2]),
			port,
			[]int{},
			strings.TrimSpace(lineParts[4]),
			strings.TrimSpace(lineParts[5]),
			strings.TrimSpace(lineParts[6]),
			false,
			"",
			"",
			0,
			"Never",
		}

		if node.Ipv6Address == "NONE" {
			node.Ipv6Address = "-"
		}

		return &node
	}

	return nil
}

func parseNodes() (*list.List, error) {
	res, err := http.Get(wikiURI)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	nodes := list.New()
	content, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	lines := strings.Split(string(content), "\n")
	for _, line := range lines {
		node := parseNode(line)
		if node == nil {
			continue
		}

		oldNode := getOldNode(node.PublicKey)
		if oldNode != nil { //transfer last ping info
			node.LastPing = oldNode.LastPing
			node.LastPingString = oldNode.LastPingString
		}

		nodes.PushBack(node)
	}
	return nodes, nil
}

func getOldNode(publicKey string) *toxNode {
	for e := nodesList.Front(); e != nil; e = e.Next() {
		node, _ := e.Value.(*toxNode)
		if node.PublicKey == publicKey {
			return node
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"sync"

	"gopkg.in/yaml.v2"

	mtr "github.com/Shinzu/go-mtr"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/version"
)

type Exporter struct {
	mutex    sync.Mutex
	sent     *prometheus.CounterVec
	received *prometheus.CounterVec
	dropped  *prometheus.CounterVec
	lost     *prometheus.CounterVec
	latency     *prometheus.SummaryVec
	failed     *prometheus.CounterVec
}

type Config struct {
	Arguments    []string `yaml:"args"`
	Hosts        []Host   `yaml:"hosts"`
}

type Host struct {
	Name  string `yaml:"name"`
	Alias string `yaml:"alias"`
}

type TargetFeedback struct {
	Target string
	Alias  string
	Hosts  []*mtr.Host
}

var config Config

const (
	Namespace = "mtr"
)

func NewExporter() *Exporter {
	var (
		alias  = "alias"
		server = "server"
		hop_id = "hop_id"
		hop_ip = "hop_ip"
	)

	return &Exporter{
		sent: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "sent",
				Help:      "packets sent",
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		received: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "received",
				Help:      "packets received",
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		dropped: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "dropped",
				Help:      "packets dropped",
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		lost: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "lost",
				Help:      "packets lost in percent",
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		latency: prometheus.NewSummaryVec(
			prometheus.SummaryOpts{
				Namespace:  Namespace,
				Name:       "latency",
				Help:       "packet latency in microseconds",
				Objectives: map[float64]float64{
					0.5: 0.05,
					0.9: 0.01,
					0.99: 0.001,
				},
				MaxAge: prometheus.DefMaxAge,
				AgeBuckets: prometheus.DefAgeBuckets,
				BufCap: prometheus.DefBufCap,
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		failed: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "failed",
				Help:      "MTR runs failed",
			},
			[]string{alias, server},
		),
	}
}

func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	e.sent.Describe(ch)
	e.received.Describe(ch)
	e.dropped.Describe(ch)
	e.lost.Describe(ch)
	e.latency.Describe(ch)
	e.failed.Describe(ch)
}

func (e *Exporter) collect() error {
	for {
		results := make(chan *TargetFeedback)
		wg := &sync.WaitGroup{}
		wg.Add(len(config.Hosts))

		for w, host := range config.Hosts {
			go func(w int, host Host) {
				log.Infoln("worker", w, "processing job", host.Name, "aliased as", host.Alias)
				err := worker(w, host, results, wg)
				if err != nil {
				  log.Errorf("worker %d failed job %v aliased as %v: %v\n", w, host.Name, host.Alias, err)
					e.failed.WithLabelValues(host.Alias, host.Name).Inc()
				} else {
					log.Infoln("worker", w, "finished job", host.Name, "aliased as", host.Alias)
				}
			}(w, host)
		}

		go func() {
			wg.Wait()
			close(results)
		}()

		for tf := range results {
			for _, host := range tf.Hosts {
				e.sent.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Add(float64(host.Sent))
				e.received.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Add(float64(host.Received))
				e.dropped.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Add(float64(host.Dropped))
				e.lost.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Add(host.LostPercent * 100)
				e.latency.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Observe(host.Mean)
			}
		}
	}
}

func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	e.sent.Collect(ch)
	e.received.Collect(ch)
	e.dropped.Collect(ch)
	e.lost.Collect(ch)
	e.latency.Collect(ch)
	e.failed.Collect(ch)
	return
}

func trace(host Host, results chan<- *TargetFeedback) error {
	// run MTR and wait for it to complete
	a := mtr.New(1, host.Name, config.Arguments...)
	<-a.Done

	// output result
	if a.Error == nil {
		results <- &TargetFeedback{
			Target: host.Name,
			Alias:  host.Alias,
			Hosts:  a.Hosts,
		}
	}
	return a.Error
}

func worker(id int, host Host, results chan<- *TargetFeedback, wg *sync.WaitGroup) error {
	defer wg.Done()
	return trace(host, results)
}

func main() {
	var (
		configFile    = flag.String("config.file", "mtr.yaml", "MTR exporter configuration file.")
		listenAddress = flag.String("web.listen-address", ":9116", "The address to listen on for HTTP requests.")
		showVersion   = flag.Bool("version", false, "Print version information.")
	)

	flag.Parse()

	if *showVersion {
		fmt.Fprintln(os.Stdout, version.Print("mtr_exporter"))
		os.Exit(0)
	}

	log.Infoln("Starting mtr_exporter", version.Info())
	log.Infoln("Build context", version.BuildContext())

	yamlFile, err := ioutil.ReadFile(*configFile)

	if err != nil {
		log.Fatalf("Error reading config file: %s", err)
	}

	err = yaml.Unmarshal(yamlFile, &config)
	if err != nil {
		log.Fatalf("Error parsing config file: %s", err)
	}

	prometheus.MustRegister(version.NewCollector("mtr_exporter"))
	exporter := NewExporter()
	prometheus.MustRegister(exporter)

	go exporter.collect()

	http.Handle("/metrics", prometheus.Handler())
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
            <title>MTR Exporter</title>

MTR Exporter

Metrics

            `))
	})

	log.Infoln("Listening on", *listenAddress)
	if err := http.ListenAndServe(*listenAddress, nil); err != nil {
		log.Fatalf("Error starting HTTP server: %s", err)
	}
}
<commit_msg>Correct lost packets values<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"sync"

	"gopkg.in/yaml.v2"

	mtr "github.com/Shinzu/go-mtr"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/version"
)

type Exporter struct {
	mutex    sync.Mutex
	sent     *prometheus.CounterVec
	received *prometheus.CounterVec
	dropped  *prometheus.CounterVec
	lost     *prometheus.CounterVec
	latency     *prometheus.SummaryVec
	failed     *prometheus.CounterVec
}

type Config struct {
	Arguments    []string `yaml:"args"`
	Hosts        []Host   `yaml:"hosts"`
}

type Host struct {
	Name  string `yaml:"name"`
	Alias string `yaml:"alias"`
}

type TargetFeedback struct {
	Target string
	Alias  string
	Hosts  []*mtr.Host
}

var config Config

const (
	Namespace = "mtr"
)

func NewExporter() *Exporter {
	var (
		alias  = "alias"
		server = "server"
		hop_id = "hop_id"
		hop_ip = "hop_ip"
	)

	return &Exporter{
		sent: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "sent",
				Help:      "packets sent",
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		received: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "received",
				Help:      "packets received",
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		dropped: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "dropped",
				Help:      "packets dropped",
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		lost: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "lost",
				Help:      "packets lost",
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		latency: prometheus.NewSummaryVec(
			prometheus.SummaryOpts{
				Namespace:  Namespace,
				Name:       "latency",
				Help:       "packet latency in microseconds",
				Objectives: map[float64]float64{
					0.5: 0.05,
					0.9: 0.01,
					0.99: 0.001,
				},
				MaxAge: prometheus.DefMaxAge,
				AgeBuckets: prometheus.DefAgeBuckets,
				BufCap: prometheus.DefBufCap,
			},
			[]string{alias, server, hop_id, hop_ip},
		),
		failed: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: Namespace,
				Name:      "failed",
				Help:      "MTR runs failed",
			},
			[]string{alias, server},
		),
	}
}

func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	e.sent.Describe(ch)
	e.received.Describe(ch)
	e.dropped.Describe(ch)
	e.lost.Describe(ch)
	e.latency.Describe(ch)
	e.failed.Describe(ch)
}

func (e *Exporter) collect() error {
	for {
		results := make(chan *TargetFeedback)
		wg := &sync.WaitGroup{}
		wg.Add(len(config.Hosts))

		for w, host := range config.Hosts {
			go func(w int, host Host) {
				log.Infoln("worker", w, "processing job", host.Name, "aliased as", host.Alias)
				err := worker(w, host, results, wg)
				if err != nil {
				  log.Errorf("worker %d failed job %v aliased as %v: %v\n", w, host.Name, host.Alias, err)
					e.failed.WithLabelValues(host.Alias, host.Name).Inc()
				} else {
					log.Infoln("worker", w, "finished job", host.Name, "aliased as", host.Alias)
				}
			}(w, host)
		}

		go func() {
			wg.Wait()
			close(results)
		}()

		for tf := range results {
			for _, host := range tf.Hosts {
				e.sent.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Add(float64(host.Sent))
				e.received.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Add(float64(host.Received))
				e.dropped.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Add(float64(host.Dropped))
				e.lost.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Add(host.LostPercent * float64(host.Sent))
				e.latency.WithLabelValues(tf.Alias, tf.Target, strconv.Itoa(host.Hop), host.IP.String()).Observe(host.Mean)
			}
		}
	}
}

func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	e.sent.Collect(ch)
	e.received.Collect(ch)
	e.dropped.Collect(ch)
	e.lost.Collect(ch)
	e.latency.Collect(ch)
	e.failed.Collect(ch)
	return
}

func trace(host Host, results chan<- *TargetFeedback) error {
	// run MTR and wait for it to complete
	a := mtr.New(1, host.Name, config.Arguments...)
	<-a.Done

	// output result
	if a.Error == nil {
		results <- &TargetFeedback{
			Target: host.Name,
			Alias:  host.Alias,
			Hosts:  a.Hosts,
		}
	}
	return a.Error
}

func worker(id int, host Host, results chan<- *TargetFeedback, wg *sync.WaitGroup) error {
	defer wg.Done()
	return trace(host, results)
}

func main() {
	var (
		configFile    = flag.String("config.file", "mtr.yaml", "MTR exporter configuration file.")
		listenAddress = flag.String("web.listen-address", ":9116", "The address to listen on for HTTP requests.")
		showVersion   = flag.Bool("version", false, "Print version information.")
	)

	flag.Parse()

	if *showVersion {
		fmt.Fprintln(os.Stdout, version.Print("mtr_exporter"))
		os.Exit(0)
	}

	log.Infoln("Starting mtr_exporter", version.Info())
	log.Infoln("Build context", version.BuildContext())

	yamlFile, err := ioutil.ReadFile(*configFile)

	if err != nil {
		log.Fatalf("Error reading config file: %s", err)
	}

	err = yaml.Unmarshal(yamlFile, &config)
	if err != nil {
		log.Fatalf("Error parsing config file: %s", err)
	}

	prometheus.MustRegister(version.NewCollector("mtr_exporter"))
	exporter := NewExporter()
	prometheus.MustRegister(exporter)

	go exporter.collect()

	http.Handle("/metrics", prometheus.Handler())
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
            <title>MTR Exporter</title>

MTR Exporter

Metrics

            `))
	})

	log.Infoln("Listening on", *listenAddress)
	if err := http.ListenAndServe(*listenAddress, nil); err != nil {
		log.Fatalf("Error starting HTTP server: %s", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"flag"
	"time"

	"golang.org/x/text/encoding/charmap"
	"golang.org/x/text/transform"
)

const dateFormat string = "02.01.2006"

type payeeSubstitution interface {
	substitute(t *Transaction)
}

/**
 * Input: "Buchungstag";"Wertstellung (Valuta)";"Vorgang";"Buchungstext";"Umsatz in EUR";
 * Output: Date;Payee;Category;Memo;Outflow;Inflow
 */

func main() {

	inputFileName := flag.String("in", "", "input file (mandatory)")
	outputFileName := flag.String("out", "", "output file (mandatory)")
	fromDate := flag.String("fromDate", "01.01.1900", "only parse from this day and after (Format: DD.MM.YYY)")

	flag.Parse()

	if *inputFileName == "" {
		panic("No input file declared. Check usage with -h")
	}

	if *outputFileName == "" {
		panic("No output file declared. Check usage with -h")
	}

	date, err := time.Parse(dateFormat, *fromDate)
	if err != nil {
		panic(err)
	}

	rewriteCSV(*inputFileName, *outputFileName, date, personalPayees{})
}

func rewriteCSV(inputFileName string, outputFileName string, fromDate time.Time, substitution payeeSubstitution) {

	inputFile, err := os.Open(inputFileName)
	if err != nil {
		panic(err)
	}
	defer inputFile.Close()
	outputFile, err := os.Create(outputFileName)
	if err != nil {
		panic(err)
	}
	defer inputFile.Close()
	dec := transform.NewReader(inputFile, charmap.ISO8859_15.NewDecoder())
	scanner := bufio.NewScanner(dec)
	writer := bufio.NewWriter(outputFile)
	defer writer.Flush()

	var inProc InputProcessor = NewComdirectInput(fromDate, substitution)
	var outProc OutputProcessor = YNABOutput{}
	outProc.WriteHeader(writer)
	for scanner.Scan() {
		t := inProc.processLine(scanner.Text())
		outProc.Process(writer, t)
	}
}
<commit_msg>Fixed imports<commit_after>package main

import (
	"bufio"
	"os"

	"flag"
	"time"

	"golang.org/x/text/encoding/charmap"
	"golang.org/x/text/transform"
)

const dateFormat string = "02.01.2006"

type payeeSubstitution interface {
	substitute(t *Transaction)
}

/**
 * Input: "Buchungstag";"Wertstellung (Valuta)";"Vorgang";"Buchungstext";"Umsatz in EUR";
 * Output: Date;Payee;Category;Memo;Outflow;Inflow
 */

func main() {

	inputFileName := flag.String("in", "", "input file (mandatory)")
	outputFileName := flag.String("out", "", "output file (mandatory)")
	fromDate := flag.String("fromDate", "01.01.1900", "only parse from this day and after (Format: DD.MM.YYY)")

	flag.Parse()

	if *inputFileName == "" {
		panic("No input file declared. Check usage with -h")
	}

	if *outputFileName == "" {
		panic("No output file declared. Check usage with -h")
	}

	date, err := time.Parse(dateFormat, *fromDate)
	if err != nil {
		panic(err)
	}

	rewriteCSV(*inputFileName, *outputFileName, date, personalPayees{})
}

func rewriteCSV(inputFileName string, outputFileName string, fromDate time.Time, substitution payeeSubstitution) {

	inputFile, err := os.Open(inputFileName)
	if err != nil {
		panic(err)
	}
	defer inputFile.Close()
	outputFile, err := os.Create(outputFileName)
	if err != nil {
		panic(err)
	}
	defer inputFile.Close()
	dec := transform.NewReader(inputFile, charmap.ISO8859_15.NewDecoder())
	scanner := bufio.NewScanner(dec)
	writer := bufio.NewWriter(outputFile)
	defer writer.Flush()

	var inProc InputProcessor = NewComdirectInput(fromDate, substitution)
	var outProc OutputProcessor = YNABOutput{}
	outProc.WriteHeader(writer)
	for scanner.Scan() {
		t := inProc.processLine(scanner.Text())
		outProc.Process(writer, t)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/kybin/tor/cell"
	"github.com/kybin/tor/syntax"
	term "github.com/nsf/termbox-go"
)

var usage = `
  tor [flag...] file

file
  filename[:line[:offset]]

flag
`

func printUsage(f *flag.FlagSet) {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprint(os.Stderr, usage)
	f.PrintDefaults()
}

// sortArgs sorts args to make flags always placed ahead of file arg.
func sortArgs(args []string) {
	sort.Slice(args, func(i, j int) bool {
		iIsFlag := strings.HasPrefix(args[i], "-")
		jIsFlag := strings.HasPrefix(args[j], "-")
		if iIsFlag && !jIsFlag {
			return true
		}
		return false
	})
}

// parseFileArg parses farg. Which looks like "filepath:linenum(1):offset(1)"
// The correspond return values are (filepath, linenum(0), offset(0)).
//
// Offset from the arg treated as 1 based offsets.
// They will changed to 0 based offsets, means they are subtracted by 1.
//
// If linenum or offset is given but invalid, it's return value will be 0.
// When both of them are ungiven, their return value will be -1, -1.
func parseFileArg(farg string) (string, int, int) {
	// final ":" is invalid but ignore it is sufficient.
	if strings.HasSuffix(farg, ":") {
		farg = farg[:len(farg)-1]
	}

	finfo := strings.Split(farg, ":")
	f := finfo[0]
	if len(finfo) == 1 {
		return f, -1, -1
	}

	l, err := strconv.Atoi(finfo[1])
	if err != nil || l < 1 {
		l = 1
	}
	l -= 1 // to base 0.
	if len(finfo) == 2 {
		return f, l, 0
	}

	o, err := strconv.Atoi(finfo[2])
	if err != nil || o < 1 {
		o = 1
	}
	o -= 1 // to base 0.
	return f, l, o
}

func main() {
	flagset := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	var newFlag bool
	flagset.BoolVar(&newFlag, "new", false, "let tor to edit a new file.")

	args := os.Args[1:]
	sortArgs(args)
	flagset.Parse(args)

	fileArgs := flagset.Args()
	if len(fileArgs) != 1 {
		printUsage(flagset)
		os.Exit(1)
	}
	farg := fileArgs[0]

	f, initL, initB := parseFileArg(farg)
	if initL == -1 {
		initL, initB = loadLastPosition(f)
	}

	// get text from file or make new.
	var text *Text
	if _, err := os.Stat(f); os.IsNotExist(err) {
		if !newFlag {
			fmt.Fprintln(os.Stderr, "file not exist. please retry with -new flag.")
			os.Exit(1)
		}
		lines := make([]Line, 0)
		lines = append(lines, Line{""})
		text = &Text{lines: lines, tabToSpace: false, tabWidth: 4, edited: false}
	} else {
		var err error
		text, err = open(f)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}

	err := term.Init()
	if err != nil {
		panic(err)
	}
	defer term.Close()
	term.SetInputMode(term.InputAlt)

	termw, termh := term.Size()
	win := NewWindow(cell.Pt{termh - 1, termw})
	cursor := NewCursor(text)
	cursor.GotoLine(initL)
	cursor.SetCloseToB(initB)
	selection := NewSelection(text)
	history := NewHistory()

	// create modes for handling events.
	mode := &ModeSelector{}
	mode.normal = &NormalMode{
		text:      text,
		cursor:    cursor,
		selection: selection,
		history:   history,
		f:         f,
		mode:      mode,
		copied:    loadConfig("copy"),
	}
	mode.find = &FindMode{
		mode: mode,
		str:  loadConfig("find"),
	}
	mode.replace = &ReplaceMode{
		mode: mode,
		str:  loadConfig("replace"),
	}
	mode.gotoline = &GotoLineMode{
		cursor: cursor,
		mode:   mode,
	}
	mode.exit = &ExitMode{
		f:      f,
		cursor: cursor,
		mode:   mode,
	}
	mode.current = mode.normal // start as normal mode.

	// get events from termbox.
	events := make(chan term.Event, 20)
	go func() {
		for {
			events <- term.PollEvent()
		}
	}()

	// mutex for commucation with termbox.
	mu := &sync.Mutex{}

	// Sync redraws terminal from buffer.
	// sometimes Flush is insufficient,
	// it is better to Sync frequently.
	go func() {
		ticker := time.NewTicker(time.Second)
		for {
			select {
			case <-ticker.C:
				mu.Lock()
				term.Sync()
				mu.Unlock()
			}
		}
	}()

	// parse syntax
	ext := filepath.Ext(f)
	var lang *syntax.Language
	if ext != "" {
		lang = syntax.Languages[ext[1:]]
	}
	var matches []syntax.Match
	if lang != nil {
		matches = lang.Parse(mode.normal.text.Bytes())
	}

	// main loop
	for {
		moved := win.Follow(cursor, 3)
		if lang != nil {
			if moved || (mode.current == mode.normal && mode.normal.dirty) {
				// recalculate syntax matches from window's top.
				// it will better to recalculate from edited position,
				// but seems little harder to implement.
				matches = lang.ParseRange(matches, mode.normal.text.Bytes(), cell.Pt{L: win.min.L, O: 0}, cell.Pt{L: win.Max().L + 1, O: 0})
				mode.normal.dirty = false
			}
		}

		mu.Lock()
		term.Clear(term.ColorDefault, term.ColorDefault)
		drawScreen(win, mode.normal.text, selection, lang, matches)
		if mode.current.Error() != "" {
			drawErrorStatus(mode.current.Error())
		} else {
			drawStatus(mode.current.Status())
		}
		if mode.current == mode.normal {
			winP := cursor.Position().Sub(win.min)
			term.SetCursor(winP.O, winP.L)
		} else {
			term.SetCursor(vlen(mode.current.Status(), mode.normal.text.tabWidth), termh)
		}
		term.Flush()
		mu.Unlock()

		// wait for keyboard input
		select {
		case ev := <-events:
			switch ev.Type {
			case term.EventKey:
				mode.current.Handle(ev)
			case term.EventResize:
				mu.Lock()
				term.Clear(term.ColorDefault, term.ColorDefault)
				termw, termh = term.Size()
				win.size = cell.Pt{termh - 1, termw}
				mu.Unlock()
			}
		}
	}
}
<commit_msg>simplify create a new text part from main<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/kybin/tor/cell"
	"github.com/kybin/tor/syntax"
	term "github.com/nsf/termbox-go"
)

var usage = `
  tor [flag...] file

file
  filename[:line[:offset]]

flag
`

func printUsage(f *flag.FlagSet) {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprint(os.Stderr, usage)
	f.PrintDefaults()
}

// sortArgs sorts args to make flags always placed ahead of file arg.
func sortArgs(args []string) {
	sort.Slice(args, func(i, j int) bool {
		iIsFlag := strings.HasPrefix(args[i], "-")
		jIsFlag := strings.HasPrefix(args[j], "-")
		if iIsFlag && !jIsFlag {
			return true
		}
		return false
	})
}

// parseFileArg parses farg. Which looks like "filepath:linenum(1):offset(1)"
// The correspond return values are (filepath, linenum(0), offset(0)).
//
// Offset from the arg treated as 1 based offsets.
// They will changed to 0 based offsets, means they are subtracted by 1.
//
// If linenum or offset is given but invalid, it's return value will be 0.
// When both of them are ungiven, their return value will be -1, -1.
func parseFileArg(farg string) (string, int, int) {
	// final ":" is invalid but ignore it is sufficient.
	if strings.HasSuffix(farg, ":") {
		farg = farg[:len(farg)-1]
	}

	finfo := strings.Split(farg, ":")
	f := finfo[0]
	if len(finfo) == 1 {
		return f, -1, -1
	}

	l, err := strconv.Atoi(finfo[1])
	if err != nil || l < 1 {
		l = 1
	}
	l -= 1 // to base 0.
	if len(finfo) == 2 {
		return f, l, 0
	}

	o, err := strconv.Atoi(finfo[2])
	if err != nil || o < 1 {
		o = 1
	}
	o -= 1 // to base 0.
	return f, l, o
}

func main() {
	flagset := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	var newFlag bool
	flagset.BoolVar(&newFlag, "new", false, "let tor to edit a new file.")

	args := os.Args[1:]
	sortArgs(args)
	flagset.Parse(args)

	fileArgs := flagset.Args()
	if len(fileArgs) != 1 {
		printUsage(flagset)
		os.Exit(1)
	}
	farg := fileArgs[0]

	f, initL, initB := parseFileArg(farg)
	if initL == -1 {
		initL, initB = loadLastPosition(f)
	}

	// get text from file or make new.
	var text *Text
	if _, err := os.Stat(f); os.IsNotExist(err) {
		if !newFlag {
			fmt.Fprintln(os.Stderr, "file not exist. please retry with -new flag.")
			os.Exit(1)
		}
		text = &Text{lines: []Line{{""}}, tabToSpace: false, tabWidth: 4, edited: false}
	} else {
		var err error
		text, err = open(f)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}

	err := term.Init()
	if err != nil {
		panic(err)
	}
	defer term.Close()
	term.SetInputMode(term.InputAlt)

	termw, termh := term.Size()
	win := NewWindow(cell.Pt{termh - 1, termw})
	cursor := NewCursor(text)
	cursor.GotoLine(initL)
	cursor.SetCloseToB(initB)
	selection := NewSelection(text)
	history := NewHistory()

	// create modes for handling events.
	mode := &ModeSelector{}
	mode.normal = &NormalMode{
		text:      text,
		cursor:    cursor,
		selection: selection,
		history:   history,
		f:         f,
		mode:      mode,
		copied:    loadConfig("copy"),
	}
	mode.find = &FindMode{
		mode: mode,
		str:  loadConfig("find"),
	}
	mode.replace = &ReplaceMode{
		mode: mode,
		str:  loadConfig("replace"),
	}
	mode.gotoline = &GotoLineMode{
		cursor: cursor,
		mode:   mode,
	}
	mode.exit = &ExitMode{
		f:      f,
		cursor: cursor,
		mode:   mode,
	}
	mode.current = mode.normal // start as normal mode.

	// get events from termbox.
	events := make(chan term.Event, 20)
	go func() {
		for {
			events <- term.PollEvent()
		}
	}()

	// mutex for commucation with termbox.
	mu := &sync.Mutex{}

	// Sync redraws terminal from buffer.
	// sometimes Flush is insufficient,
	// it is better to Sync frequently.
	go func() {
		ticker := time.NewTicker(time.Second)
		for {
			select {
			case <-ticker.C:
				mu.Lock()
				term.Sync()
				mu.Unlock()
			}
		}
	}()

	// parse syntax
	ext := filepath.Ext(f)
	var lang *syntax.Language
	if ext != "" {
		lang = syntax.Languages[ext[1:]]
	}
	var matches []syntax.Match
	if lang != nil {
		matches = lang.Parse(mode.normal.text.Bytes())
	}

	// main loop
	for {
		moved := win.Follow(cursor, 3)
		if lang != nil {
			if moved || (mode.current == mode.normal && mode.normal.dirty) {
				// recalculate syntax matches from window's top.
				// it will better to recalculate from edited position,
				// but seems little harder to implement.
				matches = lang.ParseRange(matches, mode.normal.text.Bytes(), cell.Pt{L: win.min.L, O: 0}, cell.Pt{L: win.Max().L + 1, O: 0})
				mode.normal.dirty = false
			}
		}

		mu.Lock()
		term.Clear(term.ColorDefault, term.ColorDefault)
		drawScreen(win, mode.normal.text, selection, lang, matches)
		if mode.current.Error() != "" {
			drawErrorStatus(mode.current.Error())
		} else {
			drawStatus(mode.current.Status())
		}
		if mode.current == mode.normal {
			winP := cursor.Position().Sub(win.min)
			term.SetCursor(winP.O, winP.L)
		} else {
			term.SetCursor(vlen(mode.current.Status(), mode.normal.text.tabWidth), termh)
		}
		term.Flush()
		mu.Unlock()

		// wait for keyboard input
		select {
		case ev := <-events:
			switch ev.Type {
			case term.EventKey:
				mode.current.Handle(ev)
			case term.EventResize:
				mu.Lock()
				term.Clear(term.ColorDefault, term.ColorDefault)
				termw, termh = term.Size()
				win.size = cell.Pt{termh - 1, termw}
				mu.Unlock()
			}
		}
	}
}
<|endoftext|>