<commit_before>package main

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

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

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

var shuttingDown int32

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

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

	// setup profiling if enabled
	var prof interface {
		Stop()
	}
	if cfg.ProfileMode != "" {
		var mode func(*profile.Profile)
		switch cfg.ProfileMode {
		case "":
			// do nothing
		case "cpu":
			mode = profile.CPUProfile
		case "mem":
			mode = profile.MemProfile
		case "mutex":
			mode = profile.MutexProfile
		case "block":
			mode = profile.BlockProfile
		default:
			log.Fatalf("[FATAL] Invalid profile mode %q", cfg.ProfileMode)
		}

		prof = profile.Start(mode, profile.ProfilePath(cfg.ProfilePath), profile.NoShutdownHook)
		log.Printf("[INFO] Profile mode %q", cfg.ProfileMode)
		log.Printf("[INFO] Profile path %q", cfg.ProfilePath)
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

func startServers(cfg *config.Config) {
	for _, l := range cfg.Listen {
		l := l // capture loop var for go routines below
		tlscfg, err := makeTLSConfig(l)
		if err != nil {
			exit.Fatal("[FATAL] ", err)
		}

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

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

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

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

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

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

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

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

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

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

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

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

		once sync.Once
	)

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

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

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

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

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

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

	const defFormat = "delta"
	switch format {
	case "detail":
		log.Printf("[INFO] Updated config to\n%s", t.Dump())

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

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

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

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

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

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

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

var shuttingDown int32

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

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

	// setup profiling if enabled
	var prof interface {
		Stop()
	}
	if cfg.ProfileMode != "" {
		var mode func(*profile.Profile)
		switch cfg.ProfileMode {
		case "":
			// do nothing
		case "cpu":
			mode = profile.CPUProfile
		case "mem":
			mode = profile.MemProfile
		case "mutex":
			mode = profile.MutexProfile
		case "block":
			mode = profile.BlockProfile
		default:
			log.Fatalf("[FATAL] Invalid profile mode %q", cfg.ProfileMode)
		}

		prof = profile.Start(mode, profile.ProfilePath(cfg.ProfilePath), profile.NoShutdownHook)
		log.Printf("[INFO] Profile mode %q", cfg.ProfileMode)
		log.Printf("[INFO] Profile path %q", cfg.ProfilePath)
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

func startServers(cfg *config.Config) {
	for _, l := range cfg.Listen {
		l := l // capture loop var for go routines below
		tlscfg, err := makeTLSConfig(l)
		if err != nil {
			exit.Fatal("[FATAL] ", err)
		}

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

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

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

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

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

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

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

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

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

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

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

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

		once sync.Once
	)

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

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

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

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

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

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

	const defFormat = "delta"
	switch format {
	case "detail":
		log.Printf("[INFO] Updated config to\n%s", t.Dump())

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

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

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

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


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"runtime"

	"github.com/codegangsta/cli"
	"github.com/exercism/cli/api"
	"github.com/exercism/cli/cmd"
)

const (
	// Version is the current release of the command-line app.
	// We try to follow Semantic Versioning (http://semver.org),
	// but with the http://exercism.io app being a prototype, a
	// lot of things get out of hand.
	Version = "1.8.1"

	descDebug     = "Outputs useful debug information."
	descConfigure = "Writes config values to a JSON file."
	descDemo      = "Fetches a demo problem for each language track on exercism.io."
	descFetch     = "Fetches your current problems on exercism.io, as well as the next unstarted problem in each language."
	descRestore   = "Restores completed and current problems on from exercism.io, along with your most recent iteration for each."
	descSubmit    = "Submits a new iteration to a problem on exercism.io."
	descUnsubmit  = "Deletes the most recently submitted iteration."
	descTracks    = "List the available language tracks"
	descLogin     = "DEPRECATED: Interactively saves exercism.io api credentials."
	descLogout    = "DEPRECATED: Clear exercism.io api credentials"

	descLongRestore = "Restore will pull the latest revisions of exercises that have already been submitted. It will *not* overwrite existing files. If you have made changes to a file and have not submitted it, and you're trying to restore the last submitted version, first move that file out of the way, then call restore."
)

func main() {
	api.UserAgent = fmt.Sprintf("github.com/exercism/cli v%s (%s/%s)", Version, runtime.GOOS, runtime.GOARCH)

	app := cli.NewApp()
	app.Name = "exercism"
	app.Usage = "A command line tool to interact with http://exercism.io"
	app.Version = Version
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "config, c",
			Usage: "path to config file",
		},
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "turn on verbose logging",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:   "debug",
			Usage:  descDebug,
			Action: cmd.Debug,
		},
		{
			Name:  "configure",
			Usage: descConfigure,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "dir, d",
					Usage: "path to exercises directory",
				},
				cli.StringFlag{
					Name:  "host, u",
					Usage: "exercism api host",
				},
				cli.StringFlag{
					Name:  "key, k",
					Usage: "exercism.io API key (see http://exercism.io/account)",
				},
			},
			Action: cmd.Configure,
		},
		{
			Name:      "demo",
			ShortName: "d",
			Usage:     descDemo,
			Action:    cmd.Demo,
		},
		{
			Name:      "fetch",
			ShortName: "f",
			Usage:     descFetch,
			Action:    cmd.Fetch,
		},
		{
			Name:      "login",
			ShortName: "l",
			Usage:     descLogin,
			Action:    cmd.Login,
		},
		{
			Name:      "logout",
			ShortName: "o",
			Usage:     descLogout,
			Action:    cmd.Logout,
		},
		{
			Name:        "restore",
			ShortName:   "r",
			Usage:       descRestore,
			Description: descLongRestore,
			Action:      cmd.Restore,
		},
		{
			Name:      "submit",
			ShortName: "s",
			Usage:     descSubmit,
			Action:    cmd.Submit,
		},
		{
			Name:      "unsubmit",
			ShortName: "u",
			Usage:     descUnsubmit,
			Action:    cmd.Unsubmit,
		},
		{
			Name:      "tracks",
			ShortName: "t",
			Usage:     descTracks,
			Action:    cmd.Tracks,
		},
	}
	if err := app.Run(os.Args); err != nil {

		log.Fatal(err)
	}
}
<commit_msg>Bump version<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"runtime"

	"github.com/codegangsta/cli"
	"github.com/exercism/cli/api"
	"github.com/exercism/cli/cmd"
)

const (
	// Version is the current release of the command-line app.
	// We try to follow Semantic Versioning (http://semver.org),
	// but with the http://exercism.io app being a prototype, a
	// lot of things get out of hand.
	Version = "1.8.2"

	descDebug     = "Outputs useful debug information."
	descConfigure = "Writes config values to a JSON file."
	descDemo      = "Fetches a demo problem for each language track on exercism.io."
	descFetch     = "Fetches your current problems on exercism.io, as well as the next unstarted problem in each language."
	descRestore   = "Restores completed and current problems on from exercism.io, along with your most recent iteration for each."
	descSubmit    = "Submits a new iteration to a problem on exercism.io."
	descUnsubmit  = "Deletes the most recently submitted iteration."
	descTracks    = "List the available language tracks"
	descLogin     = "DEPRECATED: Interactively saves exercism.io api credentials."
	descLogout    = "DEPRECATED: Clear exercism.io api credentials"

	descLongRestore = "Restore will pull the latest revisions of exercises that have already been submitted. It will *not* overwrite existing files. If you have made changes to a file and have not submitted it, and you're trying to restore the last submitted version, first move that file out of the way, then call restore."
)

func main() {
	api.UserAgent = fmt.Sprintf("github.com/exercism/cli v%s (%s/%s)", Version, runtime.GOOS, runtime.GOARCH)

	app := cli.NewApp()
	app.Name = "exercism"
	app.Usage = "A command line tool to interact with http://exercism.io"
	app.Version = Version
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "config, c",
			Usage: "path to config file",
		},
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "turn on verbose logging",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:   "debug",
			Usage:  descDebug,
			Action: cmd.Debug,
		},
		{
			Name:  "configure",
			Usage: descConfigure,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "dir, d",
					Usage: "path to exercises directory",
				},
				cli.StringFlag{
					Name:  "host, u",
					Usage: "exercism api host",
				},
				cli.StringFlag{
					Name:  "key, k",
					Usage: "exercism.io API key (see http://exercism.io/account)",
				},
			},
			Action: cmd.Configure,
		},
		{
			Name:      "demo",
			ShortName: "d",
			Usage:     descDemo,
			Action:    cmd.Demo,
		},
		{
			Name:      "fetch",
			ShortName: "f",
			Usage:     descFetch,
			Action:    cmd.Fetch,
		},
		{
			Name:      "login",
			ShortName: "l",
			Usage:     descLogin,
			Action:    cmd.Login,
		},
		{
			Name:      "logout",
			ShortName: "o",
			Usage:     descLogout,
			Action:    cmd.Logout,
		},
		{
			Name:        "restore",
			ShortName:   "r",
			Usage:       descRestore,
			Description: descLongRestore,
			Action:      cmd.Restore,
		},
		{
			Name:      "submit",
			ShortName: "s",
			Usage:     descSubmit,
			Action:    cmd.Submit,
		},
		{
			Name:      "unsubmit",
			ShortName: "u",
			Usage:     descUnsubmit,
			Action:    cmd.Unsubmit,
		},
		{
			Name:      "tracks",
			ShortName: "t",
			Usage:     descTracks,
			Action:    cmd.Tracks,
		},
	}
	if err := app.Run(os.Args); err != nil {

		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"github.com/dghubble/go-twitter/twitter"
	"github.com/dghubble/oauth1"
	"github.com/fatih/color"
	_ "github.com/mattn/go-sqlite3"
	"log"
	"os/user"
)

func main() {
	//User infos
	user, err := user.Current()
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	//Config
	config := oauth1.NewConfig("<CONSUMER KEY>", "<CONSUMER SECRET>")
	token := oauth1.NewToken("<ACCESS TOKEN>", "<ACCESS SECRET>")
	httpClient := config.Client(oauth1.NoContext, token)
	//Client
	client := twitter.NewClient(httpClient)
	//Cursor
	var cursor int64
	cursor = -1

	nick := flag.String("nick", "dlion92", "your nickname on twitter")
	url := flag.Bool("url", false, "true if you want to see the url in output")

	flag.Parse()

	db, err := sql.Open("sqlite3", fmt.Sprintf("%s/.goo.db", user.HomeDir))
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer db.Close()

	//Create a new db if not exists
	newDB := `
		CREATE TABLE IF NOT EXISTS users (id integer not null primary key, username text);
		CREATE TABLE IF NOT EXISTS usersTmp (id integer not null primary key, username text);
		`
	_, err = db.Exec(newDB)
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}

	//Delete usersTmp table
	_, err = db.Exec(`DELETE FROM usersTmp`)
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	//Count followers
	var plus int64
	plus = 0
	//Begin
	tx, err := db.Begin()
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}

	insertUsers, err := tx.Prepare("INSERT INTO usersTmp (username) VALUES(?)")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer insertUsers.Close()

	color.Set(color.FgYellow)
	fmt.Println("Getting followers, please wait...")
	color.Unset()
	// Get Followers
	for cursor != 0 {
		//dlion92 followers for now
		io := &twitter.FollowerListParams{ScreenName: *nick, Cursor: int(cursor), Count: 200}
		followers, resp, err := client.Followers.List(io)
		if err != nil || resp.StatusCode != 200 {
			color.Set(color.FgRed, color.BlinkSlow)
			if resp.StatusCode == 429 { //
				log.Fatal("Too much requests in a short period, try again after some minutes (15 minutes should be fine)")
			} else {
				log.Fatal(err)
			}
			color.Unset()

		}
		//Put usernames in the db
		for _, v := range followers.Users {
			_, err = insertUsers.Exec(fmt.Sprintf("%s", v.ScreenName))
			if err != nil {
				color.Set(color.FgRed, color.BlinkSlow)
				log.Fatal(err)
				color.Unset()
			}
			plus++
		}
		//Next Follower
		cursor = followers.NextCursor
	}
	tx.Commit()

	//Check new followers
	var followers []string
	rows, err := db.Query("SELECT username FROM usersTmp WHERE username NOT IN (SELECT username FROM users)")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer rows.Close()

	for rows.Next() {
		var username string
		err = rows.Scan(&username)
		if err != nil {
			color.Set(color.FgRed, color.BlinkSlow)
			log.Fatal(err)
			color.Unset()
		}
		followers = append(followers, username)
	}

	//Check new unfollowers
	var unfollowers []string
	rows, err = db.Query("SELECT username FROM users WHERE username NOT IN (SELECT username FROM usersTmp)")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer rows.Close()

	for rows.Next() {
		var username string
		err = rows.Scan(&username)
		if err != nil {
			color.Set(color.FgRed, color.BlinkSlow)
			log.Fatal(err)
			color.Unset()
		}
		unfollowers = append(unfollowers, username)
	}

	//Delete all users to update the table
	_, err = db.Exec("DELETE FROM users")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	//Get all new followers
	rowsN, err := db.Query("SELECT username FROM usersTmp")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer rowsN.Close()

	tx, err = db.Begin()
	if err != nil {
		panic(err)
	}
	insertUsers, err = tx.Prepare("INSERT INTO users (username) VALUES(?)")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}

	for rowsN.Next() {
		var username string
		err = rowsN.Scan(&username)
		if err != nil {
			panic(err)
		}

		_, err = insertUsers.Exec(fmt.Sprintf("%s", username))
		if err != nil {
			color.Set(color.FgRed, color.BlinkSlow)
			log.Fatal(err)
			color.Unset()
		}
	}
	tx.Commit()

	db.Close()

	color.Set(color.FgHiBlue)
	fmt.Printf("%s has %d followers currently\n", *nick, plus)
	color.Unset()

	if len(followers) > 0 {
		color.Set(color.FgHiCyan)
		fmt.Printf("%s has %d new followers! :)\n", *nick, len(followers))
		color.Unset()
		color.Set(color.FgGreen)
		for i := range followers {
			if *url == true {
				fmt.Printf("https://twitter.com/")
			}

			fmt.Printf("%s welcome!\n", followers[i])
		}
		color.Unset()
	}

	if len(unfollowers) > 0 {
		color.Set(color.FgHiCyan)
		fmt.Printf("%s has %d new unfollowers! :(\n", *nick, len(unfollowers))
		color.Unset()
		color.Set(color.FgHiRed)
		for i := range unfollowers {
			if *url == true {
				fmt.Printf("https://twitter.com/")
			}
			fmt.Printf("%s goodbye!\n", unfollowers[i])
		}
		color.Unset()
	}

	if len(followers) == 0 && len(unfollowers) == 0 {
		color.Set(color.FgMagenta, color.Underline)
		fmt.Printf("%s has no new followers or unfollowers, bye!\n", *nick)
		color.Unset()
	}
}
<commit_msg>ID instead username<commit_after>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"github.com/dghubble/go-twitter/twitter"
	"github.com/dghubble/oauth1"
	"github.com/fatih/color"
	_ "github.com/mattn/go-sqlite3"
	"log"
	"os/user"
)

type fUser struct {
	id       int64
	username string
}

func main() {
	//User infos
	user, err := user.Current()
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	//Config
	config := oauth1.NewConfig("<CONSUMER KEY>", "<CONSUMER SECRET>")
	token := oauth1.NewToken("<ACCESS TOKEN>", "<ACCESS SECRET>")
	httpClient := config.Client(oauth1.NoContext, token)
	//Client
	client := twitter.NewClient(httpClient)
	//Cursor
	var cursor int64
	cursor = -1

	nick := flag.String("nick", "dlion92", "your nickname on twitter")
	url := flag.Bool("url", false, "true if you want to see the url in output")

	flag.Parse()

	db, err := sql.Open("sqlite3", fmt.Sprintf("%s/.goo.db", user.HomeDir))
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer db.Close()

	//Create a new db if not exists
	newDB := `
		CREATE TABLE IF NOT EXISTS users (id integer not null primary key, username text);
		CREATE TABLE IF NOT EXISTS usersTmp (id integer not null primary key, username text);
		`
	_, err = db.Exec(newDB)
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}

	//Delete usersTmp table
	_, err = db.Exec(`DELETE FROM usersTmp`)
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	//Count followers
	var plus int64
	plus = 0
	//Begin
	tx, err := db.Begin()
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}

	insertUsers, err := tx.Prepare("INSERT INTO usersTmp (id, username) VALUES(?, ?)")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer insertUsers.Close()

	color.Set(color.FgYellow)
	fmt.Println("Getting followers, please wait...")
	color.Unset()
	// Get Followers
	for cursor != 0 {
		//dlion92 followers for now
		io := &twitter.FollowerListParams{ScreenName: *nick, Cursor: int(cursor), Count: 200}
		followers, resp, err := client.Followers.List(io)
		if err != nil || resp.StatusCode != 200 {
			color.Set(color.FgRed, color.BlinkSlow)
			if resp.StatusCode == 429 { //
				log.Fatal("Too much requests in a short period, try again after some minutes (15 minutes should be fine)")
			} else {
				log.Fatal(err)
			}
			color.Unset()

		}
		//Put usernames in the db
		for _, v := range followers.Users {
			_, err = insertUsers.Exec(v.ID, fmt.Sprintf("%s", v.ScreenName))
			if err != nil {
				color.Set(color.FgRed, color.BlinkSlow)
				log.Fatal(err)
				color.Unset()
			}
			plus++
		}
		//Next Follower
		cursor = followers.NextCursor
	}
	tx.Commit()

	//Check new followers
	var followers []fUser
	rows, err := db.Query("SELECT id, username FROM usersTmp WHERE id NOT IN (SELECT id FROM users)")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer rows.Close()

	for rows.Next() {
		var username string
		var id int64
		err = rows.Scan(&id, &username)
		if err != nil {
			color.Set(color.FgRed, color.BlinkSlow)
			log.Fatal(err)
			color.Unset()
		}
		u := fUser{
			id:       id,
			username: username,
		}
		followers = append(followers, u)
	}

	//Check new unfollowers
	var unfollowers []fUser
	rows, err = db.Query("SELECT id, username FROM users WHERE id NOT IN (SELECT id FROM usersTmp)")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer rows.Close()

	for rows.Next() {
		var username string
		var id int64
		err = rows.Scan(&id, &username)
		if err != nil {
			color.Set(color.FgRed, color.BlinkSlow)
			log.Fatal(err)
			color.Unset()
		}
		u := fUser{
			id:       id,
			username: username,
		}
		unfollowers = append(unfollowers, u)
	}

	//Delete all users to update the table
	_, err = db.Exec("DELETE FROM users")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	//Get all new followers
	rowsN, err := db.Query("SELECT id, username FROM usersTmp")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}
	defer rowsN.Close()

	tx, err = db.Begin()
	if err != nil {
		panic(err)
	}
	insertUsers, err = tx.Prepare("INSERT INTO users (id, username) VALUES(?, ?)")
	if err != nil {
		color.Set(color.FgRed, color.BlinkSlow)
		log.Fatal(err)
		color.Unset()
	}

	for rowsN.Next() {
		var username string
		var id int64
		err = rowsN.Scan(&id, &username)
		if err != nil {
			panic(err)
		}

		_, err = insertUsers.Exec(id, fmt.Sprintf("%s", username))
		if err != nil {
			color.Set(color.FgRed, color.BlinkSlow)
			log.Fatal(err)
			color.Unset()
		}
	}
	tx.Commit()

	db.Close()

	color.Set(color.FgHiBlue)
	fmt.Printf("%s has %d followers currently\n", *nick, plus)
	color.Unset()

	if len(followers) > 0 {
		color.Set(color.FgHiCyan)
		fmt.Printf("%s has %d new followers! :)\n", *nick, len(followers))
		color.Unset()
		color.Set(color.FgGreen)
		for i := range followers {
			if *url == true {
				fmt.Printf("https://twitter.com/")
			}

			fmt.Printf("%s welcome!\n", followers[i].username)
		}
		color.Unset()
	}

	if len(unfollowers) > 0 {
		color.Set(color.FgHiCyan)
		fmt.Printf("%s has %d new unfollowers! :(\n", *nick, len(unfollowers))
		color.Unset()
		color.Set(color.FgHiRed)
		for i := range unfollowers {
			if *url == true {
				fmt.Printf("https://twitter.com/")
			}
			fmt.Printf("%s goodbye!\n", unfollowers[i].username)
		}
		color.Unset()
	}

	if len(followers) == 0 && len(unfollowers) == 0 {
		color.Set(color.FgMagenta, color.Underline)
		fmt.Printf("%s has no new followers or unfollowers, bye!\n", *nick)
		color.Unset()
	}
}
<|endoftext|>


<commit_before>//go:generate go get -v github.com/josephspurrier/goversioninfo/...
//go:generate goversioninfo -icon=res/app.ico
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"syscall"

	"github.com/crazy-max/nodejs-portable/app/app"
	"github.com/crazy-max/nodejs-portable/app/fs"
	"github.com/crazy-max/nodejs-portable/app/menu"
	"github.com/crazy-max/nodejs-portable/app/nodejs"
	"github.com/crazy-max/nodejs-portable/app/pathu"
	"github.com/crazy-max/nodejs-portable/app/util"
	"github.com/fatih/color"
	version "github.com/mcuadros/go-version"
	"github.com/op/go-logging"
	"golang.org/x/sys/windows/registry"
)

// logger
var (
	log       = logging.MustGetLogger(app.ID)
	logFormat = logging.MustStringFormatter(`%{time:2006-01-02 15:04:05} %{level:.4s} - %{message}`)
)

func init() {
	// set window title
	exec.Command("cmd", "/c", fmt.Sprintf("title %s %s", app.NAME, app.VERSION)).Run()

	// log file
	pathu.CurrentPath = fs.FormatWinPath(pathu.CurrentPath)
	logfile, err := fs.OpenFile(fs.Join(pathu.CurrentPath, app.ID+".log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Error("Log file:", err)
	}

	// init logger
	logBackendFile := logging.NewBackendFormatter(logging.NewLogBackend(logfile, "", 0), logFormat)
	logging.SetBackend(logBackendFile)

	log.Info("--------")
	log.Infof("Starting %s %s...", app.NAME, app.VERSION)
	log.Info("Current path:", pathu.CurrentPath)
}

func main() {
	color.New(color.FgHiWhite).Println(app.NAME + " " + app.VERSION)
	color.New(color.FgHiWhite).Println(app.URL)

	// check for update
	latestVersion, err := util.GetLatestVersion()
	if err != nil {
		log.Error("Cannot contact the update server:", err.Error())
		color.New(color.FgRed).Printf("\n%s can't contact the update server: %s\n", app.NAME, err.Error())
	} else if version.Compare(app.VERSION, latestVersion, "<") {
		log.Info("New release available:", latestVersion)
		color.New(color.FgHiGreen).Print("\nA new release is available : ")
		color.New(color.FgHiGreen, color.Bold).Print(latestVersion)
		color.New(color.FgHiGreen).Print("\nDownload : ")
		color.New(color.FgHiGreen, color.Bold).Print(app.URL + "/releases/latest\n")
	}

	// build menu
	menuCommands := []menu.CommandOption{
		{
			Description: "Install",
			Function:    install,
		},
		{
			Description: "Shell",
			Function:    shell,
		},
	}

	menuOptions := menu.NewOptions("Menu", "'menu' for help> ", 0, "")

	menuN := menu.NewMenu(menuCommands, menuOptions)
	menuN.Start()
}

func install(args ...string) error {
	log.Info(">> INSTALL")
	fmt.Println()

	// check if already installed
	if _, err := fs.Stat(fs.Join(pathu.CurrentPath, "node.exe")); err == nil {
		util.PrintErrorStr("Node.js is already installed...")
		return nil
	}

	// create tmp folder
	fs.CreateSubfolder(pathu.TmpPath)

	// seek latest node.js version
	latestNodejs, err := nodejs.GetLatestVersion()
	if err != nil {
		util.PrintErrorStr(fmt.Sprintf("Seeking latest Node.js version: %s...", err.Error()))
		return nil
	}

	// input version
	nodejsVersion := util.ReadLine(fmt.Sprintf("  Version (default %s): ", latestNodejs))
	if nodejsVersion == "" {
		nodejsVersion = latestNodejs
	}
	log.Info("nodejsVersion:", nodejsVersion)

	// input arch
	nodejsArch := util.ReadLine("  Architecture (default x86): ")
	if nodejsArch == "" {
		nodejsArch = "x86"
	}
	log.Info("nodejsArch:", nodejsArch)

	// check dist
	fmt.Println()
	util.Print(fmt.Sprintf("Checking Node.js version '%s'... ", nodejsVersion))
	distURL, distFilename, err := nodejs.GetDistURL(nodejsVersion, nodejsArch)
	if err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// download dist
	util.Print(fmt.Sprintf("Downloading %s...", distURL))
	distPath := fs.Join(pathu.TmpPath, distFilename)
	if err := util.DownloadFile(pathu.TmpPath, distURL); err != nil {
		fmt.Print(" ")
		util.PrintError(err)
		return nil
	}
	fmt.Print(" ")
	util.PrintOk()

	// extract dist
	util.Print(fmt.Sprintf("Extracting %s... ", distFilename))
	extractPath, err := nodejs.ExtractDist(distPath)
	if err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// move nodejs folder
	util.Print("Moving nodejs folder... ")
	err = fs.CopyDir(extractPath, pathu.CurrentPath)
	if err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// create config
	util.Print("Creating Node.js configuration... ")
	if err := nodejs.CreateConfig(); err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	return nil
}

func shell(args ...string) error {
	log.Info(">> SHELL")
	fmt.Println()

	// check if installed
	util.Print("Checking if Node.js installed... ")
	if _, err := fs.Stat(path.Join(pathu.CurrentPath, "node.exe")); err != nil {
		util.PrintErrorStr("Not installed...")
		return nil
	}
	util.PrintOk()

	// create tmp folder
	fs.CreateSubfolder(pathu.TmpPath)

	// create config
	util.Print("Creating Node.js configuration... ")
	if err := nodejs.CreateConfig(); err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// create launch script
	util.Print("Creating launch script... ")
	launchScript := fs.Join(pathu.TmpPath, "launch.bat")
	err := util.CreateFile(launchScript, app.GetLaunchScriptContent(pathu.CurrentPath))
	if err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// seek Git path
	util.Print("Seeking Git path... ")
	gitPath := ""
	key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1`, registry.QUERY_VALUE)
	if err == nil {
		defer key.Close()
		gitPath, _, err = key.GetStringValue("InstallLocation")
		if err == nil {
			log.Info("Git installation found:", gitPath)
			os.Setenv("PATH", fmt.Sprintf("%s;%s", fs.FormatWinPath(gitPath+"cmd"), os.Getenv("PATH")))
			util.PrintOk()
		} else {
			util.PrintError(err)
		}
	} else {
		util.PrintError(err)
	}

	// add node to path
	util.Print("Adding node to path... ")
	if err := os.Setenv("PATH", fmt.Sprintf("%s;%s", pathu.CurrentPath, os.Getenv("PATH"))); err != nil {
		util.PrintError(err)
	}
	util.PrintOk()

	// clear screen
	util.Println("Clearing screen...")
	clear := exec.Command("cmd", "/c", "cls")
	clear.Stdout = os.Stdout
	clear.Run()

	// transfer stdin, stdout, and stderr to the new process
	// and also set target directory for the shell to start in.
	pa := os.ProcAttr{
		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
		Dir:   fs.RemoveUnc(pathu.WorkPath),
		Sys: &syscall.SysProcAttr{
			CmdLine: fmt.Sprintf(` /k "%s"`, fs.RemoveUnc(launchScript)),
		},
	}

	// start up a new shell.
	log.Info("Starting up the shell... ")
	proc, err := os.StartProcess(os.Getenv("COMSPEC"), []string{}, &pa)
	if err != nil {
		util.PrintError(err)
		return nil
	}

	// wait until user exits the shell
	if _, err = proc.Wait(); err != nil {
		util.PrintError(err)
		return nil
	}

	return nil
}
<commit_msg>Add NODE_PATH to the shell (Issue #29)<commit_after>//go:generate go get -v github.com/josephspurrier/goversioninfo/...
//go:generate goversioninfo -icon=res/app.ico
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"syscall"

	"github.com/crazy-max/nodejs-portable/app/app"
	"github.com/crazy-max/nodejs-portable/app/fs"
	"github.com/crazy-max/nodejs-portable/app/menu"
	"github.com/crazy-max/nodejs-portable/app/nodejs"
	"github.com/crazy-max/nodejs-portable/app/pathu"
	"github.com/crazy-max/nodejs-portable/app/util"
	"github.com/fatih/color"
	version "github.com/mcuadros/go-version"
	"github.com/op/go-logging"
	"golang.org/x/sys/windows/registry"
)

// logger
var (
	log       = logging.MustGetLogger(app.ID)
	logFormat = logging.MustStringFormatter(`%{time:2006-01-02 15:04:05} %{level:.4s} - %{message}`)
)

func init() {
	// set window title
	exec.Command("cmd", "/c", fmt.Sprintf("title %s %s", app.NAME, app.VERSION)).Run()

	// log file
	pathu.CurrentPath = fs.FormatWinPath(pathu.CurrentPath)
	logfile, err := fs.OpenFile(fs.Join(pathu.CurrentPath, app.ID+".log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Error("Log file:", err)
	}

	// init logger
	logBackendFile := logging.NewBackendFormatter(logging.NewLogBackend(logfile, "", 0), logFormat)
	logging.SetBackend(logBackendFile)

	log.Info("--------")
	log.Infof("Starting %s %s...", app.NAME, app.VERSION)
	log.Info("Current path:", pathu.CurrentPath)
}

func main() {
	color.New(color.FgHiWhite).Println(app.NAME + " " + app.VERSION)
	color.New(color.FgHiWhite).Println(app.URL)

	// check for update
	latestVersion, err := util.GetLatestVersion()
	if err != nil {
		log.Error("Cannot contact the update server:", err.Error())
		color.New(color.FgRed).Printf("\n%s can't contact the update server: %s\n", app.NAME, err.Error())
	} else if version.Compare(app.VERSION, latestVersion, "<") {
		log.Info("New release available:", latestVersion)
		color.New(color.FgHiGreen).Print("\nA new release is available : ")
		color.New(color.FgHiGreen, color.Bold).Print(latestVersion)
		color.New(color.FgHiGreen).Print("\nDownload : ")
		color.New(color.FgHiGreen, color.Bold).Print(app.URL + "/releases/latest\n")
	}

	// build menu
	menuCommands := []menu.CommandOption{
		{
			Description: "Install",
			Function:    install,
		},
		{
			Description: "Shell",
			Function:    shell,
		},
	}

	menuOptions := menu.NewOptions("Menu", "'menu' for help> ", 0, "")

	menuN := menu.NewMenu(menuCommands, menuOptions)
	menuN.Start()
}

func install(args ...string) error {
	log.Info(">> INSTALL")
	fmt.Println()

	// check if already installed
	if _, err := fs.Stat(fs.Join(pathu.CurrentPath, "node.exe")); err == nil {
		util.PrintErrorStr("Node.js is already installed...")
		return nil
	}

	// create tmp folder
	fs.CreateSubfolder(pathu.TmpPath)

	// seek latest node.js version
	latestNodejs, err := nodejs.GetLatestVersion()
	if err != nil {
		util.PrintErrorStr(fmt.Sprintf("Seeking latest Node.js version: %s...", err.Error()))
		return nil
	}

	// input version
	nodejsVersion := util.ReadLine(fmt.Sprintf("  Version (default %s): ", latestNodejs))
	if nodejsVersion == "" {
		nodejsVersion = latestNodejs
	}
	log.Info("nodejsVersion:", nodejsVersion)

	// input arch
	nodejsArch := util.ReadLine("  Architecture (default x86): ")
	if nodejsArch == "" {
		nodejsArch = "x86"
	}
	log.Info("nodejsArch:", nodejsArch)

	// check dist
	fmt.Println()
	util.Print(fmt.Sprintf("Checking Node.js version '%s'... ", nodejsVersion))
	distURL, distFilename, err := nodejs.GetDistURL(nodejsVersion, nodejsArch)
	if err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// download dist
	util.Print(fmt.Sprintf("Downloading %s...", distURL))
	distPath := fs.Join(pathu.TmpPath, distFilename)
	if err := util.DownloadFile(pathu.TmpPath, distURL); err != nil {
		fmt.Print(" ")
		util.PrintError(err)
		return nil
	}
	fmt.Print(" ")
	util.PrintOk()

	// extract dist
	util.Print(fmt.Sprintf("Extracting %s... ", distFilename))
	extractPath, err := nodejs.ExtractDist(distPath)
	if err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// move nodejs folder
	util.Print("Moving nodejs folder... ")
	err = fs.CopyDir(extractPath, pathu.CurrentPath)
	if err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// create config
	util.Print("Creating Node.js configuration... ")
	if err := nodejs.CreateConfig(); err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	return nil
}

func shell(args ...string) error {
	log.Info(">> SHELL")
	fmt.Println()

	// check if installed
	util.Print("Checking if Node.js installed... ")
	if _, err := fs.Stat(path.Join(pathu.CurrentPath, "node.exe")); err != nil {
		util.PrintErrorStr("Not installed...")
		return nil
	}
	util.PrintOk()

	// create tmp folder
	fs.CreateSubfolder(pathu.TmpPath)

	// create config
	util.Print("Creating Node.js configuration... ")
	if err := nodejs.CreateConfig(); err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// create launch script
	util.Print("Creating launch script... ")
	launchScript := fs.Join(pathu.TmpPath, "launch.bat")
	err := util.CreateFile(launchScript, app.GetLaunchScriptContent(pathu.CurrentPath))
	if err != nil {
		util.PrintError(err)
		return nil
	}
	util.PrintOk()

	// seek Git path
	util.Print("Seeking Git path... ")
	gitPath := ""
	key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1`, registry.QUERY_VALUE)
	if err == nil {
		defer key.Close()
		gitPath, _, err = key.GetStringValue("InstallLocation")
		if err == nil {
			log.Info("Git installation found:", gitPath)
			os.Setenv("PATH", fmt.Sprintf("%s;%s", fs.FormatWinPath(gitPath+"cmd"), os.Getenv("PATH")))
			util.PrintOk()
		} else {
			util.PrintError(err)
		}
	} else {
		util.PrintError(err)
	}

	// add node to path
	util.Print("Adding node to path... ")
	if err := os.Setenv("PATH", fmt.Sprintf("%s;%s", pathu.CurrentPath, os.Getenv("PATH"))); err != nil {
		util.PrintError(err)
	}
	util.PrintOk()

	// set NODE_PATH
	util.Print("Settings NODE_PATH... ")
	if err := os.Setenv("NODE_PATH", fs.FormatWinPath(path.Join(pathu.CurrentPath, "node_modules"))); err != nil {
		util.PrintError(err)
	}
	util.PrintOk()

	// clear screen
	util.Println("Clearing screen...")
	clear := exec.Command("cmd", "/c", "cls")
	clear.Stdout = os.Stdout
	clear.Run()

	// transfer stdin, stdout, and stderr to the new process
	// and also set target directory for the shell to start in.
	pa := os.ProcAttr{
		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
		Dir:   fs.RemoveUnc(pathu.WorkPath),
		Sys: &syscall.SysProcAttr{
			CmdLine: fmt.Sprintf(` /k "%s"`, fs.RemoveUnc(launchScript)),
		},
	}

	// start up a new shell.
	log.Info("Starting up the shell... ")
	proc, err := os.StartProcess(os.Getenv("COMSPEC"), []string{}, &pa)
	if err != nil {
		util.PrintError(err)
		return nil
	}

	// wait until user exits the shell
	if _, err = proc.Wait(); err != nil {
		util.PrintError(err)
		return nil
	}

	return nil
}
<|endoftext|>


<commit_before>package main

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

	"gopkg.in/buildkite/go-buildkite.v2/buildkite"
)

const recordsPerPage = 100

// Version is passed in via ldflags
var Version string

var queuePattern *regexp.Regexp

func init() {
	queuePattern = regexp.MustCompile(`(?i)^queue=(.+?)$`)
}

func main() {
	var (
		accessToken = flag.String("token", "", "A Buildkite API Access Token")
		orgSlug     = flag.String("org", "", "A Buildkite Organization Slug")
		interval    = flag.Duration("interval", 0, "Update metrics every interval, rather than once")
		history     = flag.Duration("history", time.Hour*24, "Historical data to use for finished builds")
		debug       = flag.Bool("debug", false, "Show API debugging output")
		version     = flag.Bool("version", false, "Show the version")

		// filters
		queues = flag.String("queue", "", "Filter by list of queues, comma delimited")
	)

	flag.Parse()

	if *version {
		fmt.Printf("buildkite-metrics %s\n", Version)
		os.Exit(0)
	}

	if *accessToken == "" {
		log.Fatal("Must provide a value for -token")
	}

	if *orgSlug == "" {
		log.Fatal("Must provide a value for -org")
	}

	config, err := buildkite.NewTokenConfig(*accessToken, false)
	if err != nil {
		log.Fatalf("client config failed: %s", err)
	}

	client := buildkite.NewClient(config.Client())
	buildkite.SetHttpDebug(*debug)

	f := func() error {
		t := time.Now()

		res, err := collectResults(client, collectOpts{
			OrgSlug:    *orgSlug,
			Historical: *history,
			Queues:     strings.Split(*queues, ","),
		})
		if err != nil {
			return err
		}

		dumpResults(res)

		err = cloudwatchSend(res)
		if err != nil {
			return err
		}

		log.Printf("Finished in %s", time.Now().Sub(t))
		return nil
	}

	if err := f(); err != nil {
		log.Fatal(err)
	}

	if *interval > 0 {
		for _ = range time.NewTicker(*interval).C {
			if err := f(); err != nil {
				log.Println(err)
			}
		}
	}
}

type collectOpts struct {
	OrgSlug    string
	Historical time.Duration
	Queues     []string
}

func collectResults(client *buildkite.Client, opts collectOpts) (*result, error) {
	res := &result{
		totals:    newCounts(),
		queues:    map[string]counts{},
		pipelines: map[string]counts{},
	}

	if len(opts.Queues) == 0 {
		log.Println("Collecting historical metrics")
		if err := res.addHistoricalMetrics(client, opts); err != nil {
			return nil, err
		}
	}

	log.Println("Collecting running and scheduled build and job metrics")
	if err := res.addBuildAndJobMetrics(client, opts); err != nil {
		return nil, err
	}

	log.Println("Collecting agent metrics")
	if err := res.addAgentMetrics(client, opts); err != nil {
		return nil, err
	}

	if len(opts.Queues) == 0 {
		return res, nil
	}

	filteredRes := &result{
		queues: map[string]counts{},
	}

	for _, queue := range opts.Queues {
		filteredRes.queues[queue] = res.queues[queue]
	}

	return filteredRes, nil
}

func dumpResults(res *result) {
	for name, c := range res.totals {
		log.Printf("Buildkite > %s = %d", name, c)
	}

	for name, c := range res.queues {
		for k, v := range c {
			log.Printf("Buildkite > [queue = %s] > %s = %d", name, k, v)
		}
	}

	for name, c := range res.pipelines {
		for k, v := range c {
			log.Printf("Buildkite > [pipeline = %s] > %s = %d", name, k, v)
		}
	}
}

const (
	runningBuildsCount   = "RunningBuildsCount"
	runningJobsCount     = "RunningJobsCount"
	scheduledBuildsCount = "ScheduledBuildsCount"
	scheduledJobsCount   = "ScheduledJobsCount"
	unfinishedJobsCount  = "UnfinishedJobsCount"
	totalAgentCount      = "TotalAgentCount"
	busyAgentCount       = "BusyAgentCount"
	idleAgentCount       = "IdleAgentCount"
)

type counts map[string]int

func newCounts() counts {
	return counts{
		runningBuildsCount:   0,
		scheduledBuildsCount: 0,
		runningJobsCount:     0,
		scheduledJobsCount:   0,
		unfinishedJobsCount:  0,
	}
}

func queue(j *buildkite.Job) string {
	for _, m := range j.AgentQueryRules {
		if match := queuePattern.FindStringSubmatch(m); match != nil {
			return match[1]
		}
	}
	return "default"
}

func uniqueQueues(builds []buildkite.Build) []string {
	queueMap := map[string]struct{}{}
	for _, b := range builds {
		for _, j := range b.Jobs {
			queueMap[queue(j)] = struct{}{}
		}
	}

	queues := []string{}
	for q := range queueMap {
		queues = append(queues, q)
	}

	return queues
}

type result struct {
	totals            counts
	queues, pipelines map[string]counts
}

func (r *result) addHistoricalMetrics(client *buildkite.Client, opts collectOpts) error {
	finishedBuilds := listBuildsByOrg(client.Builds, opts.OrgSlug, buildkite.BuildsListOptions{
		FinishedFrom: time.Now().UTC().Add(opts.Historical * -1),
		ListOptions: buildkite.ListOptions{
			PerPage: recordsPerPage,
		},
	})

	return finishedBuilds.Pages(func(v interface{}) bool {
		for _, queue := range uniqueQueues(v.([]buildkite.Build)) {
			if _, ok := r.queues[queue]; !ok {
				r.queues[queue] = newCounts()
			}
		}
		for _, build := range v.([]buildkite.Build) {
			r.pipelines[*build.Pipeline.Name] = newCounts()
		}
		return true
	})
}

func (r *result) addBuildAndJobMetrics(client *buildkite.Client, opts collectOpts) error {
	currentBuilds := listBuildsByOrg(client.Builds, opts.OrgSlug, buildkite.BuildsListOptions{
		State: []string{"scheduled", "running"},
		ListOptions: buildkite.ListOptions{
			PerPage: recordsPerPage,
		},
	})

	return currentBuilds.Pages(func(v interface{}) bool {
		for _, build := range v.([]buildkite.Build) {
			// log.Printf("Adding build to stats (id=%q, pipeline=%q, branch=%q, state=%q)",
			// 	*build.ID, *build.Pipeline.Name, *build.Branch, *build.State)

			if _, ok := r.pipelines[*build.Pipeline.Name]; !ok {
				r.pipelines[*build.Pipeline.Name] = newCounts()
			}

			switch *build.State {
			case "running":
				r.totals[runningBuildsCount]++
				r.pipelines[*build.Pipeline.Name][runningBuildsCount]++

			case "scheduled":
				r.totals[scheduledBuildsCount]++
				r.pipelines[*build.Pipeline.Name][scheduledBuildsCount]++
			}

			var buildQueues = map[string]int{}

			for _, job := range build.Jobs {
				if job.Type != nil && *job.Type == "waiter" {
					continue
				}

				state := ""
				if job.State != nil {
					state = *job.State
				}

				// log.Printf("Adding job to stats (id=%q, pipeline=%q, queue=%q, type=%q, state=%q)",
				// 	*job.ID, *build.Pipeline.Name, queue(job), *job.Type, state)

				if _, ok := r.queues[queue(job)]; !ok {
					r.queues[queue(job)] = newCounts()
				}

				if state == "running" || state == "scheduled" {
					switch state {
					case "running":
						r.totals[runningJobsCount]++
						r.queues[queue(job)][runningJobsCount]++

					case "scheduled":
						r.totals[scheduledJobsCount]++
						r.queues[queue(job)][scheduledJobsCount]++
					}

					r.totals[unfinishedJobsCount]++
					r.queues[queue(job)][unfinishedJobsCount]++
				}

				buildQueues[queue(job)]++
			}

			// add build metrics to queues
			if len(buildQueues) > 0 {
				for queue := range buildQueues {
					switch *build.State {
					case "running":
						r.queues[queue][runningBuildsCount]++

					case "scheduled":
						r.queues[queue][scheduledBuildsCount]++
					}
				}
			}
		}
		return true
	})
}

func (r *result) addAgentMetrics(client *buildkite.Client, opts collectOpts) error {
	p := &pager{
		lister: func(page int) (interface{}, int, error) {
			agents, resp, err := client.Agents.List(opts.OrgSlug, &buildkite.AgentListOptions{
				ListOptions: buildkite.ListOptions{
					Page: page,
				},
			})
			if err != nil {
				return nil, 0, err
			}
			log.Printf("Agents page %d has %d agents, next page is %d", page, len(agents), resp.NextPage)
			return agents, resp.NextPage, err
		},
	}

	r.totals[busyAgentCount] = 0
	r.totals[idleAgentCount] = 0
	r.totals[totalAgentCount] = 0

	for queue := range r.queues {
		r.queues[queue][busyAgentCount] = 0
		r.queues[queue][idleAgentCount] = 0
		r.queues[queue][totalAgentCount] = 0
	}

	err := p.Pages(func(v interface{}) bool {
		agents := v.([]buildkite.Agent)

		for _, agent := range agents {
			queue := "default"
			for _, m := range agent.Metadata {
				if match := queuePattern.FindStringSubmatch(m); match != nil {
					queue = match[1]
					break
				}
			}

			if _, ok := r.queues[queue]; !ok {
				r.queues[queue] = newCounts()
				r.queues[queue][busyAgentCount] = 0
				r.queues[queue][idleAgentCount] = 0
				r.queues[queue][totalAgentCount] = 0
			}

			// log.Printf("Adding agent to stats (name=%q, queue=%q, job=%#v)",
			// 	*agent.Name, queue, agent.Job != nil)

			if agent.Job != nil {
				r.totals[busyAgentCount]++
				r.queues[queue][busyAgentCount]++
			} else {
				r.totals[idleAgentCount]++
				r.queues[queue][idleAgentCount]++
			}

			r.totals[totalAgentCount]++
			r.queues[queue][totalAgentCount]++
		}

		return true
	})
	if err != nil {
		return err
	}

	return nil
}

type pager struct {
	lister func(page int) (v interface{}, nextPage int, err error)
}

func (p *pager) Pages(f func(v interface{}) bool) error {
	page := 1
	for {
		val, nextPage, err := p.lister(page)
		if err != nil {
			return err
		}
		if !f(val) || nextPage == 0 {
			break
		}
		page = nextPage
	}
	return nil
}

func listBuildsByOrg(builds *buildkite.BuildsService, orgSlug string, opts buildkite.BuildsListOptions) *pager {
	return &pager{
		lister: func(page int) (interface{}, int, error) {
			opts.ListOptions = buildkite.ListOptions{
				Page: page,
			}
			builds, resp, err := builds.ListByOrg(orgSlug, &opts)
			if err != nil {
				return nil, 0, err
			}
			log.Printf("Builds page %d has %d builds, next page is %d", page, len(builds), resp.NextPage)
			return builds, resp.NextPage, err
		},
	}
}
<commit_msg>Revert "Support multiple queues via --queue"<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"time"

	"gopkg.in/buildkite/go-buildkite.v2/buildkite"
)

const recordsPerPage = 100

// Version is passed in via ldflags
var Version string

var queuePattern *regexp.Regexp

func init() {
	queuePattern = regexp.MustCompile(`(?i)^queue=(.+?)$`)
}

func main() {
	var (
		accessToken = flag.String("token", "", "A Buildkite API Access Token")
		orgSlug     = flag.String("org", "", "A Buildkite Organization Slug")
		interval    = flag.Duration("interval", 0, "Update metrics every interval, rather than once")
		history     = flag.Duration("history", time.Hour*24, "Historical data to use for finished builds")
		debug       = flag.Bool("debug", false, "Show API debugging output")
		version     = flag.Bool("version", false, "Show the version")

		// filters
		queue = flag.String("queue", "", "Only include a specific queue")
	)

	flag.Parse()

	if *version {
		fmt.Printf("buildkite-metrics %s\n", Version)
		os.Exit(0)
	}

	if *accessToken == "" {
		log.Fatal("Must provide a value for -token")
	}

	if *orgSlug == "" {
		log.Fatal("Must provide a value for -org")
	}

	config, err := buildkite.NewTokenConfig(*accessToken, false)
	if err != nil {
		log.Fatalf("client config failed: %s", err)
	}

	client := buildkite.NewClient(config.Client())
	buildkite.SetHttpDebug(*debug)

	f := func() error {
		t := time.Now()

		res, err := collectResults(client, collectOpts{
			OrgSlug:    *orgSlug,
			Historical: *history,
			Queue:      *queue,
		})
		if err != nil {
			return err
		}

		dumpResults(res)

		err = cloudwatchSend(res)
		if err != nil {
			return err
		}

		log.Printf("Finished in %s", time.Now().Sub(t))
		return nil
	}

	if err := f(); err != nil {
		log.Fatal(err)
	}

	if *interval > 0 {
		for _ = range time.NewTicker(*interval).C {
			if err := f(); err != nil {
				log.Println(err)
			}
		}
	}
}

type collectOpts struct {
	OrgSlug    string
	Historical time.Duration
	Queue      string
}

func collectResults(client *buildkite.Client, opts collectOpts) (*result, error) {
	res := &result{
		totals:    newCounts(),
		queues:    map[string]counts{},
		pipelines: map[string]counts{},
	}

	if opts.Queue == "" {
		log.Println("Collecting historical metrics")
		if err := res.addHistoricalMetrics(client, opts); err != nil {
			return nil, err
		}
	}

	log.Println("Collecting running and scheduled build and job metrics")
	if err := res.addBuildAndJobMetrics(client, opts); err != nil {
		return nil, err
	}

	log.Println("Collecting agent metrics")
	if err := res.addAgentMetrics(client, opts); err != nil {
		return nil, err
	}

	if opts.Queue != "" {
		if c, ok := res.queues[opts.Queue]; ok {
			return &result{
				queues: map[string]counts{
					opts.Queue: c,
				},
			}, nil
		}
		return &result{}, nil
	}

	return res, nil
}

func dumpResults(res *result) {
	for name, c := range res.totals {
		log.Printf("Buildkite > %s = %d", name, c)
	}

	for name, c := range res.queues {
		for k, v := range c {
			log.Printf("Buildkite > [queue = %s] > %s = %d", name, k, v)
		}
	}

	for name, c := range res.pipelines {
		for k, v := range c {
			log.Printf("Buildkite > [pipeline = %s] > %s = %d", name, k, v)
		}
	}
}

const (
	runningBuildsCount   = "RunningBuildsCount"
	runningJobsCount     = "RunningJobsCount"
	scheduledBuildsCount = "ScheduledBuildsCount"
	scheduledJobsCount   = "ScheduledJobsCount"
	unfinishedJobsCount  = "UnfinishedJobsCount"
	totalAgentCount      = "TotalAgentCount"
	busyAgentCount       = "BusyAgentCount"
	idleAgentCount       = "IdleAgentCount"
)

type counts map[string]int

func newCounts() counts {
	return counts{
		runningBuildsCount:   0,
		scheduledBuildsCount: 0,
		runningJobsCount:     0,
		scheduledJobsCount:   0,
		unfinishedJobsCount:  0,
	}
}

func queue(j *buildkite.Job) string {
	for _, m := range j.AgentQueryRules {
		if match := queuePattern.FindStringSubmatch(m); match != nil {
			return match[1]
		}
	}
	return "default"
}

func uniqueQueues(builds []buildkite.Build) []string {
	queueMap := map[string]struct{}{}
	for _, b := range builds {
		for _, j := range b.Jobs {
			queueMap[queue(j)] = struct{}{}
		}
	}

	queues := []string{}
	for q := range queueMap {
		queues = append(queues, q)
	}

	return queues
}

type result struct {
	totals            counts
	queues, pipelines map[string]counts
}

func (r *result) addHistoricalMetrics(client *buildkite.Client, opts collectOpts) error {
	finishedBuilds := listBuildsByOrg(client.Builds, opts.OrgSlug, buildkite.BuildsListOptions{
		FinishedFrom: time.Now().UTC().Add(opts.Historical * -1),
		ListOptions: buildkite.ListOptions{
			PerPage: recordsPerPage,
		},
	})

	return finishedBuilds.Pages(func(v interface{}) bool {
		for _, queue := range uniqueQueues(v.([]buildkite.Build)) {
			if _, ok := r.queues[queue]; !ok {
				r.queues[queue] = newCounts()
			}
		}
		for _, build := range v.([]buildkite.Build) {
			r.pipelines[*build.Pipeline.Name] = newCounts()
		}
		return true
	})
}

func (r *result) addBuildAndJobMetrics(client *buildkite.Client, opts collectOpts) error {
	currentBuilds := listBuildsByOrg(client.Builds, opts.OrgSlug, buildkite.BuildsListOptions{
		State: []string{"scheduled", "running"},
		ListOptions: buildkite.ListOptions{
			PerPage: recordsPerPage,
		},
	})

	return currentBuilds.Pages(func(v interface{}) bool {
		for _, build := range v.([]buildkite.Build) {
			// log.Printf("Adding build to stats (id=%q, pipeline=%q, branch=%q, state=%q)",
			// 	*build.ID, *build.Pipeline.Name, *build.Branch, *build.State)

			if _, ok := r.pipelines[*build.Pipeline.Name]; !ok {
				r.pipelines[*build.Pipeline.Name] = newCounts()
			}

			switch *build.State {
			case "running":
				r.totals[runningBuildsCount]++
				r.pipelines[*build.Pipeline.Name][runningBuildsCount]++

			case "scheduled":
				r.totals[scheduledBuildsCount]++
				r.pipelines[*build.Pipeline.Name][scheduledBuildsCount]++
			}

			var buildQueues = map[string]int{}

			for _, job := range build.Jobs {
				if job.Type != nil && *job.Type == "waiter" {
					continue
				}

				state := ""
				if job.State != nil {
					state = *job.State
				}

				// log.Printf("Adding job to stats (id=%q, pipeline=%q, queue=%q, type=%q, state=%q)",
				// 	*job.ID, *build.Pipeline.Name, queue(job), *job.Type, state)

				if _, ok := r.queues[queue(job)]; !ok {
					r.queues[queue(job)] = newCounts()
				}

				if state == "running" || state == "scheduled" {
					switch state {
					case "running":
						r.totals[runningJobsCount]++
						r.queues[queue(job)][runningJobsCount]++

					case "scheduled":
						r.totals[scheduledJobsCount]++
						r.queues[queue(job)][scheduledJobsCount]++
					}

					r.totals[unfinishedJobsCount]++
					r.queues[queue(job)][unfinishedJobsCount]++
				}

				buildQueues[queue(job)]++
			}

			// add build metrics to queues
			if len(buildQueues) > 0 {
				for queue := range buildQueues {
					switch *build.State {
					case "running":
						r.queues[queue][runningBuildsCount]++

					case "scheduled":
						r.queues[queue][scheduledBuildsCount]++
					}
				}
			}
		}
		return true
	})
}

func (r *result) addAgentMetrics(client *buildkite.Client, opts collectOpts) error {
	p := &pager{
		lister: func(page int) (interface{}, int, error) {
			agents, resp, err := client.Agents.List(opts.OrgSlug, &buildkite.AgentListOptions{
				ListOptions: buildkite.ListOptions{
					Page: page,
				},
			})
			if err != nil {
				return nil, 0, err
			}
			log.Printf("Agents page %d has %d agents, next page is %d", page, len(agents), resp.NextPage)
			return agents, resp.NextPage, err
		},
	}

	r.totals[busyAgentCount] = 0
	r.totals[idleAgentCount] = 0
	r.totals[totalAgentCount] = 0

	for queue := range r.queues {
		r.queues[queue][busyAgentCount] = 0
		r.queues[queue][idleAgentCount] = 0
		r.queues[queue][totalAgentCount] = 0
	}

	err := p.Pages(func(v interface{}) bool {
		agents := v.([]buildkite.Agent)

		for _, agent := range agents {
			queue := "default"
			for _, m := range agent.Metadata {
				if match := queuePattern.FindStringSubmatch(m); match != nil {
					queue = match[1]
					break
				}
			}

			if _, ok := r.queues[queue]; !ok {
				r.queues[queue] = newCounts()
				r.queues[queue][busyAgentCount] = 0
				r.queues[queue][idleAgentCount] = 0
				r.queues[queue][totalAgentCount] = 0
			}

			// log.Printf("Adding agent to stats (name=%q, queue=%q, job=%#v)",
			// 	*agent.Name, queue, agent.Job != nil)

			if agent.Job != nil {
				r.totals[busyAgentCount]++
				r.queues[queue][busyAgentCount]++
			} else {
				r.totals[idleAgentCount]++
				r.queues[queue][idleAgentCount]++
			}

			r.totals[totalAgentCount]++
			r.queues[queue][totalAgentCount]++
		}

		return true
	})
	if err != nil {
		return err
	}

	return nil
}

type pager struct {
	lister func(page int) (v interface{}, nextPage int, err error)
}

func (p *pager) Pages(f func(v interface{}) bool) error {
	page := 1
	for {
		val, nextPage, err := p.lister(page)
		if err != nil {
			return err
		}
		if !f(val) || nextPage == 0 {
			break
		}
		page = nextPage
	}
	return nil
}

func listBuildsByOrg(builds *buildkite.BuildsService, orgSlug string, opts buildkite.BuildsListOptions) *pager {
	return &pager{
		lister: func(page int) (interface{}, int, error) {
			opts.ListOptions = buildkite.ListOptions{
				Page: page,
			}
			builds, resp, err := builds.ListByOrg(orgSlug, &opts)
			if err != nil {
				return nil, 0, err
			}
			log.Printf("Builds page %d has %d builds, next page is %d", page, len(builds), resp.NextPage)
			return builds, resp.NextPage, err
		},
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

const (
	ConfigDirName = ".gocfg"
	Debug         = false
)

const usageMessage = `wgo is a tool for managing Go workspaces.

usage: wgo init [--set-primary=PRIMARY_GOPATH] [ADDITIONAL_GOPATH+]
       wgo save
       wgo restore
       wgo <go command>
`

func usage() {
	fmt.Print(usageMessage)
	os.Exit(1)
}

func init() {
	log.SetFlags(0)
	if !Debug {
		log.SetOutput(ioutil.Discard)
	}
}

func orExit(err error) {
	if err == nil {
		return
	}
	fmt.Fprintf(os.Stderr, "%s\n", err)
	os.Exit(1)
}

func main() {
	if len(os.Args) == 1 {
		w, err := getCurrentWorkspace()
		orExit(err)
		fmt.Println(usageMessage)
		w.shellOutToGo(os.Args)
	}
	var err error
	switch os.Args[1] {
	case "init":
		if err = initWgo(os.Args[2:]); err != nil {
			fmt.Fprintf(os.Stderr, "%s\n", err)
		}
	case "restore":
		if len(os.Args) != 2 {
			usage()
		}
		w, err := getCurrentWorkspace()
		orExit(err)
		restore(w)
	case "save":
		if len(os.Args) != 2 {
			usage()
		}
		w, err := getCurrentWorkspace()
		orExit(err)
		save(w)
	default:
		w, err := getCurrentWorkspace()
		orExit(err)
		w.shellOutToGo(os.Args)
	}
}

func initWgo(args []string) error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}

	w, err := getCurrentWorkspace()
	if err == nil && (w.root != wd || len(args) == 0) {
		return fmt.Errorf("%q is already a workspace", w.root)
	}

	fi, err := os.Stat(wd)
	if err != nil {
		return err
	}

	if err := os.MkdirAll(filepath.Join(wd, ConfigDirName), fi.Mode()); err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Join(wd, "src"), fi.Mode()); err != nil {
		return err
	}

	if w, err = getCurrentWorkspace(); err != nil {
		return err
	}

	gopathsPath := filepath.Join(wd, ConfigDirName, "gopaths")
	// if there is no gopaths yet, stick '.' in there.
	if _, err := os.Stat(gopathsPath); err != nil {
		args = append([]string{"."}, args...)
	}

	var fout *os.File
	if fout, err = os.Create(gopathsPath); err != nil {
		return err
	}
	defer fout.Close()

	var gopathArgs []string
	for i := 0; i < len(args); i++ {
		if args[i] == "--set-primary" {
			if i+1 >= len(args) {
				usage()
			}
			fmt.Fprintln(fout, args[i+1])
			i++
			continue
		}
		if strings.HasPrefix(args[i], "--set-primary=") {
			fmt.Fprintln(fout, args[i][len("--set-primary="):])
			continue
		}
		gopathArgs = append(gopathArgs, args[i])
	}

	for _, gopath := range w.gopaths {
		fmt.Fprintln(fout, gopath)
	}
	for _, gopath := range gopathArgs {
		fmt.Fprintln(fout, gopath)
	}

	return nil
}

func clone(args []string) error {
	return nil
}
<commit_msg>Added check to make sure only relative paths are used with wgo init<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

const (
	ConfigDirName = ".gocfg"
	Debug         = false
)

const usageMessage = `wgo is a tool for managing Go workspaces.

usage: wgo init [--set-primary=PRIMARY_GOPATH] [ADDITIONAL_GOPATH+]
       wgo save
       wgo restore
       wgo <go command>
`

func usage() {
	fmt.Print(usageMessage)
	os.Exit(1)
}

func init() {
	log.SetFlags(0)
	if !Debug {
		log.SetOutput(ioutil.Discard)
	}
}

func orExit(err error) {
	if err == nil {
		return
	}
	fmt.Fprintf(os.Stderr, "%s\n", err)
	os.Exit(1)
}

func main() {
	if len(os.Args) == 1 {
		w, err := getCurrentWorkspace()
		orExit(err)
		fmt.Println(usageMessage)
		w.shellOutToGo(os.Args)
	}
	var err error
	switch os.Args[1] {
	case "init":
		if err = initWgo(os.Args[2:]); err != nil {
			fmt.Fprintf(os.Stderr, "%s\n", err)
		}
	case "restore":
		if len(os.Args) != 2 {
			usage()
		}
		w, err := getCurrentWorkspace()
		orExit(err)
		restore(w)
	case "save":
		if len(os.Args) != 2 {
			usage()
		}
		w, err := getCurrentWorkspace()
		orExit(err)
		save(w)
	default:
		w, err := getCurrentWorkspace()
		orExit(err)
		w.shellOutToGo(os.Args)
	}
}

func initWgo(args []string) error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}

	w, err := getCurrentWorkspace()
	if err == nil && (w.root != wd || len(args) == 0) {
		return fmt.Errorf("%q is already a workspace", w.root)
	}

	fi, err := os.Stat(wd)
	if err != nil {
		return err
	}

	if err := os.MkdirAll(filepath.Join(wd, ConfigDirName), fi.Mode()); err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Join(wd, "src"), fi.Mode()); err != nil {
		return err
	}

	if w, err = getCurrentWorkspace(); err != nil {
		return err
	}

	gopathsPath := filepath.Join(wd, ConfigDirName, "gopaths")
	// if there is no gopaths yet, stick '.' in there.
	if _, err := os.Stat(gopathsPath); err != nil {
		args = append([]string{"."}, args...)
	}

	var fout *os.File
	if fout, err = os.Create(gopathsPath); err != nil {
		return err
	}
	defer fout.Close()

	checkGopath := func(gopath string) {
		if filepath.IsAbs(gopath) {
			fmt.Fprintf(os.Stderr, "%q is not a relative path\n", gopath)
		}
	}

	var gopathArgs []string
	for i := 0; i < len(args); i++ {
		if args[i] == "--set-primary" {
			if i+1 >= len(args) {
				usage()
			}
			checkGopath(args[i+1])
			fmt.Fprintln(fout, args[i+1])
			i++
			continue
		}

		checkGopath(args[i])

		if strings.HasPrefix(args[i], "--set-primary=") {
			fmt.Fprintln(fout, args[i][len("--set-primary="):])
			continue
		}

		gopathArgs = append(gopathArgs, args[i])
	}

	for _, gopath := range w.gopaths {
		fmt.Fprintln(fout, gopath)
	}
	for _, gopath := range gopathArgs {
		fmt.Fprintln(fout, gopath)
	}

	return nil
}

func clone(args []string) error {
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"

	"github.com/rs/cors"
	log "github.com/sirupsen/logrus"

	"flag"

	"fmt"

	"github.com/fgruchala/twall-middle-go/service"
	"github.com/fgruchala/twall-middle-go/webservice"
	"github.com/gorilla/mux"
)

func main() {
	// LOG
	log.SetFormatter(&log.TextFormatter{
		ForceColors:   true,
		FullTimestamp: true,
	})

	// ARGS
	port := flag.Int("port", 3002, "Port on which Twall is listening")
	consumerKey := flag.String("consumerKey", "", "Consumer key generated by Twitter. Go https://apps.twitter.com")
	consumerSecret := flag.String("consumerSecret", "", "Consumer secret generated by Twitter. Go https://apps.twitter.com")
	corsOrigin := flag.String("cors", "http://localhost:*", "Access-Control-Allow-Origin")
	flag.Parse()

	// SERVICE
	tweetService := service.NewTweetService(consumerKey, consumerSecret)

	// WEBSERVICE
	router := mux.NewRouter().StrictSlash(true)
	webservice.NewTweetWebservice(router, tweetService)

	// CORS
	c := cors.New(cors.Options{
		AllowedOrigins: []string{*corsOrigin},
		AllowedHeaders: []string{"Origin"}})

	mux := http.NewServeMux()
	mux.Handle("/", router)
	handler := c.Handler(mux)
	log.WithFields(log.Fields{"port": *port}).Info("Starting server ...")
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *port), handler))
}
<commit_msg>CHORE(Cors) Only GET<commit_after>package main

import (
	"net/http"

	"github.com/rs/cors"
	log "github.com/sirupsen/logrus"

	"flag"

	"fmt"

	"github.com/fgruchala/twall-middle-go/service"
	"github.com/fgruchala/twall-middle-go/webservice"
	"github.com/gorilla/mux"
)

func main() {
	// LOG
	log.SetFormatter(&log.TextFormatter{
		ForceColors:   true,
		FullTimestamp: true,
	})

	// ARGS
	port := flag.Int("port", 3002, "Port on which Twall is listening")
	consumerKey := flag.String("consumerKey", "", "Consumer key generated by Twitter. Go https://apps.twitter.com")
	consumerSecret := flag.String("consumerSecret", "", "Consumer secret generated by Twitter. Go https://apps.twitter.com")
	corsOrigin := flag.String("cors", "http://localhost:*", "Access-Control-Allow-Origin")
	flag.Parse()

	// SERVICE
	tweetService := service.NewTweetService(consumerKey, consumerSecret)

	// WEBSERVICE
	router := mux.NewRouter().StrictSlash(true)
	webservice.NewTweetWebservice(router, tweetService)

	// CORS
	c := cors.New(cors.Options{
		AllowedOrigins: []string{*corsOrigin},
		AllowedMethods: []string{"GET"}})

	mux := http.NewServeMux()
	mux.Handle("/", router)
	handler := c.Handler(mux)
	log.WithFields(log.Fields{"port": *port}).Info("Starting server ...")
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", *port), handler))
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"
)

var router *gin.Engine
var client *github.Client
var CACHE_PATH = "./.cache/"

func setupGithub() {
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{
			AccessToken: os.Getenv("GITHUB_TOKEN"),
		},
	)

	tc := oauth2.NewClient(oauth2.NoContext, ts)

	client = github.NewClient(tc)
}

func main() {

	// Set the router as the default one provided by Gin
	router = gin.Default()

	// Setup template folder
	router.LoadHTMLGlob("templates/*")

	router.Static("/assets", "./assets")

	// Setup Github client
	setupGithub()

	// Cache repo data
	CachedRepo := make(map[string]Repo)

	// Default landing page
	router.GET("/", func(c *gin.Context) {
		// Call the HTML method of the Context to render a template
		c.HTML(
			// HTTP status
			200,
			// Use the index.html template
			"index.html",
			gin.H{},
		)
	})

	// User handler
	router.GET("user/:username", func(c *gin.Context) {

		username := c.Param("username")
		responseType := c.Query("response_type")
		cachePath := concat(CACHE_PATH, username)

		var resultData Response

		if _, err := os.Stat(cachePath); err == nil {
			cacheData, err := ioutil.ReadFile(cachePath)
			// Error reading the file
			if err != nil {
				log.Println(err)
			}
			data := Response{}
			err = json.Unmarshal(cacheData, &data)
			// Error unmarshaling the file
			if err != nil {
				log.Println(err)
			}

			// Update the last synced at string
			data.LastSyncedAtString = getTimeAgo(data.LastSyncedAt)

			resultData = data
		} else {
			// Search options to override the default 30 and fetch max 100 per page
			opt := &github.SearchOptions{
				ListOptions: github.ListOptions{
					PerPage: 100,
				},
			}

			var allRepos []Repo
			var allPrs []PullRequest

			// Continuously fetch all PR's
			for {
				prs, resp, _ := client.Search.Issues(
					// Search query to find PR's
					fmt.Sprintf("type:pr author:%s is:public", username),
					opt,
				)

				// Iterate over all closed pull requests to
				// see which of them is merged and which one isn't
				// Also for each PR we are going to fetch the actual
				// repo's stats such as stars, pr's etc.
				for _, githubPrObject := range prs.Issues {
					parsedPrUrl := parseGithubPrUrl(*githubPrObject.HTMLURL)

					// Get stats
					repoUrl := strings.Join(
						[]string{
							"https://api.github.com",
							parsedPrUrl.Owner,
							parsedPrUrl.Repo,
						},
						"/",
					)

					// Cache repo stats and only make calls for new ones
					var repo Repo
					if _, isCached := CachedRepo[repoUrl]; isCached == false {
						repoData, _, _ := client.Repositories.Get(parsedPrUrl.Owner, parsedPrUrl.Repo)
						repo = Repo{
							Stars:        *repoData.StargazersCount,
							Forks:        *repoData.ForksCount,
							Name:         *repoData.FullName,
							Url:          *repoData.HTMLURL,
							PullRequests: []PullRequest{},
						}
						CachedRepo[repoUrl] = repo
					} else {
						repo = CachedRepo[repoUrl]
					}

					// Get merged status
					if *githubPrObject.State == "closed" {
						isPrMerged, _, _ := client.PullRequests.IsMerged(
							parsedPrUrl.Owner,
							parsedPrUrl.Repo,
							parsedPrUrl.Number,
						)

						if isPrMerged {
							*githubPrObject.State = "merged"
						}
					}

					pr := PullRequest{
						Url:     *githubPrObject.HTMLURL,
						Title:   *githubPrObject.Title,
						State:   *githubPrObject.State,
						RepoUrl: repoUrl,
					}

					allPrs = append(allPrs, pr)

				}

				if resp.NextPage == 0 {
					break
				}

				opt.ListOptions.Page = resp.NextPage
			}

			allRepos = groupByRepo(allPrs, CachedRepo)

			currentTime := time.Now()

			data := Response{
				LastSyncedAt:              currentTime.Unix(),
				LastSyncedAtString:        getTimeAgo(currentTime.Unix()),
				LastSyncedAtStringVerbose: currentTime.Format("Jan 2, 2006 at 3:04pm (MST)"),
				Username:                  username,
				TotalRepos:                len(allRepos),
				TotalPrs:                  len(allPrs),
				AllRepos:                  allRepos,
			}

			resultData = data

			go SaveDataAsJson(data, username)
		}

		if responseType == "json" {
			c.JSON(
				200,
				resultData,
			)
		} else {
			// Call the HTML method of the Context to render a template
			c.HTML(
				// HTTP status
				200,
				// Use the user.html template
				"user.html",
				// Pass the data that the page uses (in this case, 'title')
				resultData,
			)
		}

	})

	// Start serving the application
	router.Run()

}
<commit_msg>Update github package to support updated go-github library (#14)<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"
)

var router *gin.Engine
var client *github.Client
var CACHE_PATH = "./.cache/"

func setupGithub() {
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{
			AccessToken: os.Getenv("GITHUB_TOKEN"),
		},
	)

	tc := oauth2.NewClient(oauth2.NoContext, ts)

	client = github.NewClient(tc)
}

func main() {

	// Set the router as the default one provided by Gin
	router = gin.Default()

	// Setup template folder
	router.LoadHTMLGlob("templates/*")

	router.Static("/assets", "./assets")

	// Setup Github client
	setupGithub()

	// Cache repo data
	CachedRepo := make(map[string]Repo)

	// Default landing page
	router.GET("/", func(c *gin.Context) {
		// Call the HTML method of the Context to render a template
		c.HTML(
			// HTTP status
			200,
			// Use the index.html template
			"index.html",
			gin.H{},
		)
	})

	// User handler
	router.GET("user/:username", func(c *gin.Context) {

		username := c.Param("username")
		responseType := c.Query("response_type")
		cachePath := concat(CACHE_PATH, username)

		var resultData Response

		if _, err := os.Stat(cachePath); err == nil {
			cacheData, err := ioutil.ReadFile(cachePath)
			// Error reading the file
			if err != nil {
				log.Println(err)
			}
			data := Response{}
			err = json.Unmarshal(cacheData, &data)
			// Error unmarshaling the file
			if err != nil {
				log.Println(err)
			}

			// Update the last synced at string
			data.LastSyncedAtString = getTimeAgo(data.LastSyncedAt)

			resultData = data
		} else {
			// Search options to override the default 30 and fetch max 100 per page
			opt := &github.SearchOptions{
				ListOptions: github.ListOptions{
					PerPage: 100,
				},
			}

			var allRepos []Repo
			var allPrs []PullRequest

			// Continuously fetch all PR's
			for {
				prs, resp, _ := client.Search.Issues(
					// Search query to find PR's
					c,
					fmt.Sprintf("type:pr author:%s is:public", username),
					opt,
				)

				// Iterate over all closed pull requests to
				// see which of them is merged and which one isn't
				// Also for each PR we are going to fetch the actual
				// repo's stats such as stars, pr's etc.
				for _, githubPrObject := range prs.Issues {
					parsedPrUrl := parseGithubPrUrl(*githubPrObject.HTMLURL)

					// Get stats
					repoUrl := strings.Join(
						[]string{
							"https://api.github.com",
							parsedPrUrl.Owner,
							parsedPrUrl.Repo,
						},
						"/",
					)

					// Cache repo stats and only make calls for new ones
					var repo Repo
					if _, isCached := CachedRepo[repoUrl]; isCached == false {
						repoData, _, _ := client.Repositories.Get(c, parsedPrUrl.Owner, parsedPrUrl.Repo)
						repo = Repo{
							Stars:        *repoData.StargazersCount,
							Forks:        *repoData.ForksCount,
							Name:         *repoData.FullName,
							Url:          *repoData.HTMLURL,
							PullRequests: []PullRequest{},
						}
						CachedRepo[repoUrl] = repo
					} else {
						repo = CachedRepo[repoUrl]
					}

					// Get merged status
					if *githubPrObject.State == "closed" {
						isPrMerged, _, _ := client.PullRequests.IsMerged(
							c,
							parsedPrUrl.Owner,
							parsedPrUrl.Repo,
							parsedPrUrl.Number,
						)

						if isPrMerged {
							*githubPrObject.State = "merged"
						}
					}

					pr := PullRequest{
						Url:     *githubPrObject.HTMLURL,
						Title:   *githubPrObject.Title,
						State:   *githubPrObject.State,
						RepoUrl: repoUrl,
					}

					allPrs = append(allPrs, pr)

				}

				if resp.NextPage == 0 {
					break
				}

				opt.ListOptions.Page = resp.NextPage
			}

			allRepos = groupByRepo(allPrs, CachedRepo)

			currentTime := time.Now()

			data := Response{
				LastSyncedAt:              currentTime.Unix(),
				LastSyncedAtString:        getTimeAgo(currentTime.Unix()),
				LastSyncedAtStringVerbose: currentTime.Format("Jan 2, 2006 at 3:04pm (MST)"),
				Username:                  username,
				TotalRepos:                len(allRepos),
				TotalPrs:                  len(allPrs),
				AllRepos:                  allRepos,
			}

			resultData = data

			go SaveDataAsJson(data, username)
		}

		if responseType == "json" {
			c.JSON(
				200,
				resultData,
			)
		} else {
			// Call the HTML method of the Context to render a template
			c.HTML(
				// HTTP status
				200,
				// Use the user.html template
				"user.html",
				// Pass the data that the page uses (in this case, 'title')
				resultData,
			)
		}

	})

	// Start serving the application
	router.Run()

}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"time"
)

const version string = "0.6.0"

var (
	showVersion     = flag.Bool("version", false, "Show version information")
	ip              = flag.String("ip", "", "IP or net address to remove from DNS")
	newIpStr        = flag.String("new_ip", "", "IP to set instead of removed IP")
	gcloudProject   = flag.String("gcloud.project", "", "Project ID for Google Cloud DNS")
	dry             = flag.Bool("dry", false, "Do not modify DNS records (simulation only)")
	zoneFilter      = flag.String("zone", "", "Apply only to zones matching the specifed regex")
	skipFilter      = flag.String("skip", "", "Skip zones matching the specified regex")
	typeFilter      = flag.String("type", "", "Record type to change")
	file            = flag.String("file", "drain.json", "File containing changes (for log or undrain)")
	shouldUndrain   = flag.Bool("undrain", false, "Use file to revert changes")
	value           = flag.String("value", "", "Value to replace/replace in DNS data")
	regexString     = flag.String("regex", "", "Regex to find data in DNS records to remove/replace")
	newValue        = flag.String("new_value", "", "Value to replace with in DNS data")
	zoneFilterRegex *regexp.Regexp
	skipFilterRegex *regexp.Regexp
)

func main() {
	flag.Parse()

	if *showVersion {
		printVersionInfo()
		return
	}

	err := parseFilterArgs()
	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	if *dry {
		log.Println("Using dry run. No records will be changed.")
	}

	start := time.Now()

	if *shouldUndrain {
		err = undrain(*file)
	} else {
		err = drain()
	}

	if err != nil {
		log.Printf("ERROR - %s\n", err)
		os.Exit(1)
	}

	log.Printf("Finished after %v", time.Since(start))
}

func printVersionInfo() {
	fmt.Println("dns-drain")
	fmt.Printf("Version: %s\n", version)
}

func parseFilterArgs() error {
	var err error = nil

	if len(*zoneFilter) > 0 {
		zoneFilterRegex, err = regexp.Compile(*zoneFilter)
		if err != nil {
			return err
		}
	}

	if len(*skipFilter) > 0 {
		skipFilterRegex, err = regexp.Compile(*skipFilter)
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>improved --help<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"time"
)

const version string = "0.6.1"

var (
	showVersion     = flag.Bool("version", false, "Show version information")
	ip              = flag.String("ip", "", "IP or net address to remove from DNS")
	newIpStr        = flag.String("new_ip", "", "IP to set instead of removed IP")
	gcloudProject   = flag.String("gcloud.project", "", "Project ID for Google Cloud DNS")
	dry             = flag.Bool("dry", false, "Do not modify DNS records (simulation only)")
	zoneFilter      = flag.String("zone", "", "Apply only to zones matching the specifed regex")
	skipFilter      = flag.String("skip", "", "Skip zones matching the specified regex")
	typeFilter      = flag.String("type", "", "Record type to change")
	file            = flag.String("file", "drain.json", "File containing changes (for log or undrain)")
	shouldUndrain   = flag.Bool("undrain", false, "Use file to revert changes")
	value           = flag.String("value", "", "Value to replace/replace in DNS data")
	regexString     = flag.String("regex", "", "Regex to find data in DNS records to remove/replace")
	newValue        = flag.String("new_value", "", "Value to replace with in DNS data")
	zoneFilterRegex *regexp.Regexp
	skipFilterRegex *regexp.Regexp
)

func init() {
	flag.Usage = func() {
		fmt.Println("Usage: dns-drain [ ... ]\n\nParameters:\n")
		flag.PrintDefaults()
		fmt.Println("\n\nExamples:\n")
		fmt.Println("Drain IP 1.2.3.4 in project api-project-xxx by removing IP from records\n")
		fmt.Println("  dns-drain -gcloud.project=api-project-xxx -ip=1.2.3.4/32\n")
		fmt.Println("Drain IP 1.2.3.4 in project api-project-xxx by replacing IP with 1.2.3.5\n")
		fmt.Println("  dns-drain -gcloud.project=api-project-xxx -ip=1.2.3.4/32 -new_ip=1.2.3.5\n")
		fmt.Println("Undrain by using json file written in drain process\n")
		fmt.Println("  dns-drain -undrain -gcloud.project=api-project-xxx")
	}
}

func main() {
	flag.Parse()

	if *showVersion {
		printVersionInfo()
		return
	}

	err := parseFilterArgs()
	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	if *dry {
		log.Println("Using dry run. No records will be changed.")
	}

	start := time.Now()

	if *shouldUndrain {
		err = undrain(*file)
	} else {
		err = drain()
	}

	if err != nil {
		log.Printf("ERROR - %s\n", err)
		os.Exit(1)
	}

	log.Printf("Finished after %v", time.Since(start))
}

func printVersionInfo() {
	fmt.Println("dns-drain")
	fmt.Printf("Version: %s\n", version)
}

func parseFilterArgs() error {
	var err error = nil

	if len(*zoneFilter) > 0 {
		zoneFilterRegex, err = regexp.Compile(*zoneFilter)
		if err != nil {
			return err
		}
	}

	if len(*skipFilter) > 0 {
		skipFilterRegex, err = regexp.Compile(*skipFilter)
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>