<commit_before>package main

import (
	"encoding/xml"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	"golang.org/x/tools/blog/atom"

	"github.com/ChimeraCoder/anaconda"
	"github.com/joho/godotenv"
)

const (
	MaxStatuses = 200
	MaxEntries  = 100
)

type atomEntryArray []*atom.Entry

func (a atomEntryArray) Len() int {
	return len(a)
}

func (a atomEntryArray) Less(i, j int) bool {
	it, err := time.Parse(time.RFC3339, string(a[i].Updated))
	if err != nil {
		return false
	}
	jt, err := time.Parse(time.RFC3339, string(a[j].Updated))
	if err != nil {
		return false
	}
	return it.After(jt)
}

func (a atomEntryArray) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func main() {
	log.SetFlags(log.Lshortfile)

	if len(os.Args) > 0 {
		if err := godotenv.Load(os.Args...); err != nil {
			log.Fatal(err)
		}
	}

	anaconda.SetConsumerKey(os.Getenv("TWITTER_CONSUMER_KEY"))
	anaconda.SetConsumerSecret(os.Getenv("TWITTER_CONSUMER_SECRET"))

	api := anaconda.NewTwitterApi(os.Getenv("TWITTER_OAUTH_TOKEN"), os.Getenv("TWITTER_OAUTH_TOKEN_SECRET"))
	defer api.Close()

	feedUrl := os.Getenv("FEED_URL")
	if feedUrl == "" {
		log.Fatal("FEED_URL is required.")
	}
	template := os.Getenv("TEMPLATE")
	if template == "" {
		log.Fatal("TEMPLATE is required.")
	}

	userId := strings.SplitN(os.Getenv("TWITTER_OAUTH_TOKEN"), "-", 2)[0]
	if _, err := strconv.ParseInt(userId, 10, 64); err != nil {
		log.Fatal(err)
	}

	recentUrls := make(map[string]struct{})
	{
		v := url.Values{}
		v.Set("user_id", userId)
		v.Set("count", strconv.Itoa(MaxStatuses))
		timeline, err := api.GetUserTimeline(v)
		if err != nil {
			log.Fatal(err)
		}

		for _, status := range timeline {
			for _, url := range status.Entities.Urls {
				recentUrls[url.Expanded_url] = struct{}{}
			}
		}
	}

	var atom atom.Feed
	{
		resp, err := http.Get(feedUrl)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}

		if err := xml.Unmarshal(body, &atom); err != nil {
			log.Fatal(err)
		}

	}

	{
		entries := atomEntryArray(atom.Entry)
		sort.Sort(sort.Reverse(entries))
		n := len(entries) - MaxEntries
		if n < 0 {
			n = 0
		}
	entries:
		for _, entry := range entries[n:] {
			if len(entry.Link) == 0 {
				continue
			}
			link := entry.Link[0]
			if _, ok := recentUrls[link.Href]; ok {
				continue
			}

			replacer := strings.NewReplacer("{title}", entry.Title, "{url}", link.Href)
			text := replacer.Replace(template)
			_, err := api.PostTweet(text, url.Values{})
			if err != nil {
				if apiErr, ok := err.(*anaconda.ApiError); ok {
					for _, err := range apiErr.Decoded.Errors {
						if err.Code == anaconda.TwitterErrorStatusIsADuplicate {
							continue entries
						}
					}
					log.Fatal(apiErr)
				} else {
					log.Fatal(err)
				}
			}
		}
	}
}
<commit_msg>Update<commit_after>package main

import (
	"encoding/xml"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	"golang.org/x/tools/blog/atom"

	"github.com/ChimeraCoder/anaconda"
	"github.com/joho/godotenv"
)

const (
	MaxStatuses = 200
	MaxEntries  = 100
)

type atomEntrySlice []*atom.Entry

func (a atomEntrySlice) Len() int {
	return len(a)
}

func (a atomEntrySlice) Less(i, j int) bool {
	it, err := time.Parse(time.RFC3339, string(a[i].Updated))
	if err != nil {
		return false
	}
	jt, err := time.Parse(time.RFC3339, string(a[j].Updated))
	if err != nil {
		return false
	}
	return it.After(jt)
}

func (a atomEntrySlice) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func main() {
	log.SetFlags(log.Lshortfile)

	if len(os.Args) > 0 {
		if err := godotenv.Load(os.Args...); err != nil {
			log.Fatal(err)
		}
	}

	anaconda.SetConsumerKey(os.Getenv("TWITTER_CONSUMER_KEY"))
	anaconda.SetConsumerSecret(os.Getenv("TWITTER_CONSUMER_SECRET"))

	api := anaconda.NewTwitterApi(os.Getenv("TWITTER_OAUTH_TOKEN"), os.Getenv("TWITTER_OAUTH_TOKEN_SECRET"))
	defer api.Close()

	feedUrl := os.Getenv("FEED_URL")
	if feedUrl == "" {
		log.Fatal("FEED_URL is required.")
	}
	template := os.Getenv("TEMPLATE")
	if template == "" {
		log.Fatal("TEMPLATE is required.")
	}

	userId := strings.SplitN(os.Getenv("TWITTER_OAUTH_TOKEN"), "-", 2)[0]
	if _, err := strconv.ParseInt(userId, 10, 64); err != nil {
		log.Fatal(err)
	}

	recentUrls := make(map[string]struct{})
	{
		v := url.Values{}
		v.Set("user_id", userId)
		v.Set("count", strconv.Itoa(MaxStatuses))
		timeline, err := api.GetUserTimeline(v)
		if err != nil {
			log.Fatal(err)
		}

		for _, status := range timeline {
			for _, url := range status.Entities.Urls {
				recentUrls[url.Expanded_url] = struct{}{}
			}
		}
	}

	var atom atom.Feed
	{
		resp, err := http.Get(feedUrl)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}

		if err := xml.Unmarshal(body, &atom); err != nil {
			log.Fatal(err)
		}

	}

	{
		entries := atomEntrySlice(atom.Entry)
		sort.Sort(sort.Reverse(entries))
		n := 0
		if len(entries) > MaxEntries {
			n = len(entries) - MaxEntries
		}
	entries:
		for _, entry := range entries[n:] {
			if len(entry.Link) == 0 {
				continue
			}
			link := entry.Link[0]
			if _, ok := recentUrls[link.Href]; ok {
				continue
			}

			replacer := strings.NewReplacer("{title}", entry.Title, "{url}", link.Href)
			text := replacer.Replace(template)
			_, err := api.PostTweet(text, url.Values{})
			if err != nil {
				if apiErr, ok := err.(*anaconda.ApiError); ok {
					for _, err := range apiErr.Decoded.Errors {
						if err.Code == anaconda.TwitterErrorStatusIsADuplicate {
							continue entries
						}
					}
					log.Fatal(apiErr)
				} else {
					log.Fatal(err)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package awsconfig

import (
	"flag"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sts"
)

var (
	logging = flag.Bool("log", false, "Enable logging")
	region  = flag.String("region", "us-east-1", "Set S3 region")
	role    = flag.String("role", "", "Role ARN")
)

// New creates config with credentials for using with AWS SDK for Go
func New() *aws.Config {
	config := aws.NewConfig()
	if *role != "" {
		session := session.New(config)
		client := sts.New(session)
		resp, err := client.GetSessionToken(&sts.GetSessionTokenInput{})
		if err != nil {
			log.Fatal("Can't retrieve temprorarily credentials because of:", err)
		}
		config.WithCredentials(credentials.NewStaticCredentials(
			*resp.Credentials.AccessKeyId,
			*resp.Credentials.SecretAccessKey,
			*resp.Credentials.SessionToken))
	} else {
		config.WithCredentials(credentials.NewEnvCredentials())
	}
	config.WithRegion(*region)
	if *logging {
		config.WithLogLevel(aws.LogDebugWithHTTPBody)
	}
	return config
}
<commit_msg>Fix typo<commit_after>package awsconfig

import (
	"flag"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sts"
)

var (
	logging = flag.Bool("log", false, "Enable logging")
	region  = flag.String("region", "us-east-1", "Set AWS region")
	role    = flag.String("role", "", "Role ARN")
)

// New creates config with credentials for using with AWS SDK for Go
func New() *aws.Config {
	config := aws.NewConfig()
	if *role != "" {
		session := session.New(config)
		client := sts.New(session)
		resp, err := client.GetSessionToken(&sts.GetSessionTokenInput{})
		if err != nil {
			log.Fatal("Can't retrieve temprorarily credentials because of:", err)
		}
		config.WithCredentials(credentials.NewStaticCredentials(
			*resp.Credentials.AccessKeyId,
			*resp.Credentials.SecretAccessKey,
			*resp.Credentials.SessionToken))
	} else {
		config.WithCredentials(credentials.NewEnvCredentials())
	}
	config.WithRegion(*region)
	if *logging {
		config.WithLogLevel(aws.LogDebugWithHTTPBody)
	}
	return config
}
<|endoftext|>


<commit_before>package main

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

	"gopkg.in/yaml.v2"
)

type Command struct {
	Desc   string `yaml:"desc"`
	Exec   string `yaml:"exec`
	Script string `yaml:"script"`
	//Env map[string]string `yaml:"env"`
}

// Config represents the configuration data that are
// loaded from the Supfile YAML file.
type Config struct {
	Hosts    map[string][]string `yaml:"hosts"`
	Env      map[string]string   `yaml:"env"`
	Commands map[string]Command  `yaml:"commands"`
	Targets  map[string][]string `yaml:"targets"`
}

func usage(conf *Config) {
	switch len(os.Args) {
	case 2:
		log.Fatal("Available commands (from Supfile):\n")
		for cmd, _ := range conf.Commands {
			log.Fatal("- %v\n", cmd)
		}
	case 1:
		log.Fatal("Available hosts (from Supfile):\n")
		for group, hosts := range conf.Hosts {
			log.Fatal("- %v\n", group)
			for _, host := range hosts {
				log.Fatal("   - %v\n", host)
			}
		}
	default:
		log.Fatal("Usage:\nsup <host-group> <command-alias>")
	}
	os.Exit(1)
}

func main() {
	var (
		conf           Config
		longestHostLen int
	)

	data, _ := ioutil.ReadFile("./Supfile")
	if err := yaml.Unmarshal(data, &conf); err != nil {
		log.Fatal(err)
	}

	if len(os.Args) != 3 {
		usage(&conf)
	}

	hosts, ok := conf.Hosts[os.Args[1]]
	if !ok || len(hosts) == 0 {
		usage(&conf)
	}

	for _, host := range hosts {
		if len(host) > longestHostLen {
			longestHostLen = len(host)
		}
	}

	// TODO: Targets?

	command, ok := conf.Commands[os.Args[2]]
	if !ok {
		usage(&conf)
	}

	clients := make([]*SSHClient, len(hosts))
	for i, host := range hosts {
		c := &SSHClient{
			Env: map[string]string{"FOO": "sup"},
		}
		if err := c.Connect(host); err != nil {
			log.Fatal(err)
		}

		defer c.Close()
		clients[i] = c
	}

	for _, c := range clients {
		c.Run(command)
		spaces := strings.Repeat(" ", longestHostLen-len(c.Host))
		go func(c *SSHClient) {
			if _, err := io.Copy(os.Stdout, NewPrefixedLineReader(c.RemoteStdout, spaces+c.Host+" | ")); err != nil {
				log.Printf("STDOUT(%v): %v", c.Host, err)
			}
		}(c)
		go func(c *SSHClient) {
			if _, err := io.Copy(os.Stderr, NewPrefixedLineReader(c.RemoteStderr, spaces+c.Host+" | ")); err != nil {
				log.Printf("STERR(%v): %v", c.Host, err)
			}
		}(c)
	}

	for _, c := range clients {
		c.Wait()
		// TODO: check for exit err:
		// err = session.Wait()
		// if err == nil {
		// 	t.Fatalf("expected command to fail but it didn't")
		// }
		// e, ok := err.(*ExitError)
		// if !ok {
		// 	t.Fatalf("expected *ExitError but got %T", err)
		// }
		// if e.ExitStatus() != 15 {
		// 	t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
		// }
	}

	// 1. Parse the command that we're going to run..
	// ie. from:
	// $ sup beta deploy  ===> 1. connect to beta  2. run deploy command
	// $ sup beta stop
	// $ sup beta start
	// command := commands["ping"].(map[interface{}]interface{}) // ..
	// cmd := command["exec"].(string)

	// log.Printf("the command: %s", cmd)

	// log.Println(b)
	// s.Session.Wait()

	// s.Run(cmd)
	// s.Quit()

	// 2. Open connection to each host - all must connect

	// 3.

	//s.Session.Wait()
}
<commit_msg>Fix panic (negative number of prefixed spaces)<commit_after>package main

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

	"gopkg.in/yaml.v2"
)

type Command struct {
	Desc   string `yaml:"desc"`
	Exec   string `yaml:"exec`
	Script string `yaml:"script"`
	//Env map[string]string `yaml:"env"`
}

// Config represents the configuration data that are
// loaded from the Supfile YAML file.
type Config struct {
	Hosts    map[string][]string `yaml:"hosts"`
	Env      map[string]string   `yaml:"env"`
	Commands map[string]Command  `yaml:"commands"`
	Targets  map[string][]string `yaml:"targets"`
}

func usage(conf *Config) {
	switch len(os.Args) {
	case 2:
		log.Fatal("Available commands (from Supfile):\n")
		for cmd, _ := range conf.Commands {
			log.Fatal("- %v\n", cmd)
		}
	case 1:
		log.Fatal("Available hosts (from Supfile):\n")
		for group, hosts := range conf.Hosts {
			log.Fatal("- %v\n", group)
			for _, host := range hosts {
				log.Fatal("   - %v\n", host)
			}
		}
	default:
		log.Fatal("Usage:\nsup <host-group> <command-alias>")
	}
	os.Exit(1)
}

func main() {
	var (
		conf           Config
		longestHostLen int
	)

	data, _ := ioutil.ReadFile("./Supfile")
	if err := yaml.Unmarshal(data, &conf); err != nil {
		log.Fatal(err)
	}

	if len(os.Args) != 3 {
		usage(&conf)
	}

	hosts, ok := conf.Hosts[os.Args[1]]
	if !ok || len(hosts) == 0 {
		usage(&conf)
	}

	// TODO: Targets?

	command, ok := conf.Commands[os.Args[2]]
	if !ok {
		usage(&conf)
	}

	clients := make([]*SSHClient, len(hosts))
	for i, host := range hosts {
		c := &SSHClient{
			Env: map[string]string{"FOO": "sup"},
		}
		if err := c.Connect(host); err != nil {
			log.Fatal(err)
		}
		defer c.Close()

		if len(c.Host) > longestHostLen {
			longestHostLen = len(c.Host)
		}

		clients[i] = c
	}

	for _, c := range clients {
		c.Run(command)
		spaces := strings.Repeat(" ", longestHostLen-len(c.Host))
		go func(c *SSHClient) {
			if _, err := io.Copy(os.Stdout, NewPrefixedLineReader(c.RemoteStdout, spaces+c.Host+" | ")); err != nil {
				log.Printf("STDOUT(%v): %v", c.Host, err)
			}
		}(c)
		go func(c *SSHClient) {
			if _, err := io.Copy(os.Stderr, NewPrefixedLineReader(c.RemoteStderr, spaces+c.Host+" | ")); err != nil {
				log.Printf("STERR(%v): %v", c.Host, err)
			}
		}(c)
	}

	for _, c := range clients {
		c.Wait()
		// TODO: check for exit err:
		// err = session.Wait()
		// if err == nil {
		// 	t.Fatalf("expected command to fail but it didn't")
		// }
		// e, ok := err.(*ExitError)
		// if !ok {
		// 	t.Fatalf("expected *ExitError but got %T", err)
		// }
		// if e.ExitStatus() != 15 {
		// 	t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
		// }
	}

	// 1. Parse the command that we're going to run..
	// ie. from:
	// $ sup beta deploy  ===> 1. connect to beta  2. run deploy command
	// $ sup beta stop
	// $ sup beta start
	// command := commands["ping"].(map[interface{}]interface{}) // ..
	// cmd := command["exec"].(string)

	// log.Printf("the command: %s", cmd)

	// log.Println(b)
	// s.Session.Wait()

	// s.Run(cmd)
	// s.Quit()

	// 2. Open connection to each host - all must connect

	// 3.

	//s.Session.Wait()
}
<|endoftext|>


<commit_before>// A project to simulate traffic navigation in the city of Hastings.
//
// Team members: Frederick Stoddart, Levi Fraser-Daley, Nikolas Burke
package main

import "fmt"

// A location representing a suburb in a city
type funCityLoc struct {
	name        string
	outsideCity bool
}

//
type funStreet struct {
	name string
	exit funExit
}

type funExit string

type funDriver struct {
	number int
	name   string
}

var hastings = []funCityLoc{
	{"Mayfair", false},
	{"Akina", false},
	{"Stortford Lodge", false},
	{"Mahora", false},
	{"Outside City", true},
}

var drivers = []funDriver{
	{1, "Fred"},
	{2, "Caitlyn"},
	{3, "Mason"},
	{4, "Bea"},
	{5, "Tara"},
}

// Main() :p
func main() {

	fmt.Println(hastings)
	fmt.Println(drivers)

}
<commit_msg>Added more structs and Connectors<commit_after>// A project to simulate traffic navigation in the city of Hastings.
//
// Team members: Frederick Stoddart, Levi Fraser-Daley, Nikolas Burke
package main

import (
	"encoding/json"
	"fmt"
)

// A location representing a suburb in a city
type funCityLoc struct {
	Name       string
	Connectors []funConnector
}

// A collection of streets that connect two areas and an exit to leave the city
type funConnector struct {
	Streets []funStreet
	Exit    funExit
}

// A street connecting two locations and an exit.
// Can comprise of multiple physical streets
type funStreet string

// An exit leading to Outside City
type funExit string

// A driver to be used in the simulation
type funDriver struct {
	Number int
	Name   string
}

var akinaStortfordLodgeConnector = funConnector{
	[]funStreet{
		"Southhampton St W",
	},
	"Railway Rd S",
}

var stortfordLodgeMahoraConnector = funConnector{
	[]funStreet{
		"Maraekakaho Rd",
		"Pakowhai Rd",
	},
	"Omahu Rd",
}

var mahoraMayfairConnector = funConnector{
	[]funStreet{
		"Frederick St",
		"Grove Rd",
	},
	"Karamu Rd",
}

var akinaMayfairConnector = funConnector{
	[]funStreet{
		"Willowpark Rd",
	},
	"Havelock Rd",
}

var hastings = []funCityLoc{
	{
		"Mayfair",
		[]funConnector{
			akinaMayfairConnector,
			mahoraMayfairConnector,
		},
	},
	{
		"Akina",
		[]funConnector{
			akinaMayfairConnector,
			akinaStortfordLodgeConnector,
		},
	},
	{
		"Stortford Lodge",
		[]funConnector{
			stortfordLodgeMahoraConnector,
			akinaStortfordLodgeConnector,
		},
	},
	{
		"Mahora",
		[]funConnector{
			mahoraMayfairConnector,
			stortfordLodgeMahoraConnector,
		},
	},
}

var drivers = []funDriver{
	{1, "Fred"},
	{2, "Caitlyn"},
	{3, "Mason"},
	{4, "Bea"},
	{5, "Tara"},
}

// Main() :p
func main() {

	// This code is just to clear the unused variable warnings (and I made it output pretty JSON)
	hastingsJson, _ := json.MarshalIndent(hastings, "", "  ")
	fmt.Println(string(hastingsJson))
	fmt.Println(drivers)

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/funnylookinhat/gas/lib"
	"log"
	"math/rand"
	"time"
)

type GasTest struct {
	bytes  int
	length int
}

// Make sure you've got at least 5 GBs of free space before running this.
// Also - please ask your HDD for forgiveness.
func main() {

	fmt.Printf("Generating random data... ")

	randomData := make(map[int][][]byte)

	testSizes := []int{512, 1024, 2048, 4096}
	testLengths := []int{1000, 5000, 10000, 50000, 100000, 500000, 1000000}

	// Temporary Override
	testSizes = []int{128} //, 256, 512}
	testLengths = []int{1000, 5000, 10000, 50000, 100000, 500000, 1000000, 5000000, 10000000}

	for _, s := range testSizes {
		fmt.Printf("%d ... ", s)
		randomData[s] = make([][]byte, 0)
		for i := 0; i < 100; i++ {
			randomData[s] = append(randomData[s], generateBytes(s))
		}
	}

	fmt.Printf("done!\n")

	fileTestStartTime := time.Now()

	fileService, err := gas.NewService("file", []string{"test/file/"}...)

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

	for _, s := range testSizes {
		for _, length := range testLengths {
			writeDuration, readDuration := testServiceSpeed(fileService, length, randomData[s])
			fmt.Printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - \n")
			fmt.Printf("File service: %d items @ %d bytes -> %s / %s \n\n", length, s, writeDuration, readDuration)
		}
	}

	fileTestDuration := time.Since(fileTestStartTime)

	fmt.Printf("* * * * * * * * * * * * * \n\n\n\n")
	fmt.Printf("All file tests ( including random data generation ): %s\n", fileTestDuration)
	fmt.Printf("\n\n\n* * * * * * * * * * * * * \n")

	fileServiceBucket, err := fileService.GetBucket("test1")

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

	fileServiceBucket.RemoveAllItems()

	bfileTestStartTime := time.Now()

	bfileService, err := gas.NewService("bfile", []string{"test/bfile/"}...)

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

	for _, s := range testSizes {
		for _, length := range testLengths {
			writeDuration, readDuration := testServiceSpeed(bfileService, length, randomData[s])
			fmt.Printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - \n")
			fmt.Printf("BFile service: %d items @ %d bytes -> %s / %s \n\n", length, s, writeDuration, readDuration)
		}
	}

	bfileTestDuration := time.Since(bfileTestStartTime)

	fmt.Printf("* * * * * * * * * * * * * \n\n\n\n")
	fmt.Printf("All bfile tests ( including random data generation ): %s\n", bfileTestDuration)
	fmt.Printf("\n\n\n* * * * * * * * * * * * * \n")

	bfileServiceBucket, err := bfileService.GetBucket("test1")

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

	bfileServiceBucket.RemoveAllItems()

}

// n = number of items in a bucket
// s = size of item data in bytes
func testServiceSpeed(service gas.Service, n int, randomData [][]byte) (writeDuration, readDuration time.Duration) {
	var err error

	if n < 500 {
		log.Fatal("That test isn't worth running: n must be >= 500.")
	}

	s := len(randomData[0])

	b, err := service.GetBucket("test1")

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

	b.RemoveAllItems()

	// r = Number of items we will fetch at a time in read tests.
	r := n / 10

	if r > 2000 {
		r = 2000
	}

	// Number of fetch tests to run ( x2 - first and last )
	q := n / r

	if q < 1 {
		q = 1
	}

	if q > 100 {
		q = 100
	}

	fmt.Printf("Pushing %d items (%d bytes) to %s -> ", n, s, b.GetName())
	startTime := time.Now()
	startAllTime := startTime
	for i := 0; i < n; i++ {
		b.PushItem(randomData[rand.Intn(len(randomData))])
	}
	duration := time.Since(startTime)
	writeDuration = duration
	fmt.Printf("Result: %s \n", duration)

	startAllReadTime := time.Now()

	for i := 0; i < q; i++ {
		// Offset
		o := i * (n / q)

		//fmt.Printf("%d Items @ %d Bytes / Fetching %d first items (offset %d) -> ", n, s, r, o)
		//startTime = time.Now()
		_, _ = b.GetFirstItems(r, o)
		//duration = time.Since(startTime)
		//fmt.Printf("Result: %s \n", duration)

		//fmt.Printf("%d Items @ %d Bytes / Fetching %d  last items (offset %d) -> ", n, s, r, o)
		//startTime = time.Now()
		_, _ = b.GetLastItems(r, o)
		//duration = time.Since(startTime)
		//fmt.Printf("Result: %s \n", duration)
	}

	readDuration = time.Since(startAllReadTime)

	duration = time.Since(startAllTime)

	fmt.Printf("Total time for %d items (%d bytes) : %s\n\n", n, s, duration)

	return
}

func generateBytes(length int) []byte {
	charset := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
	bytes := ""
	for i := 0; i < length; i++ {
		bytes = bytes + string(charset[rand.Intn(len(charset))])
	}

	return []byte(bytes)
}
<commit_msg>Bit cleaner benchmarking.<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/funnylookinhat/gas/lib"
	"log"
	"math/rand"
	"time"
)

type Benchmark struct {
	service       gas.Service
	description   string
	tests         []BenchmarkTest
	writeDuration time.Duration
	readDuration  time.Duration
}

type BenchmarkTest struct {
	bytes         int
	length        int
	writeDuration time.Duration
	readDuration  time.Duration
}

// Make sure you've got at least 5 GBs of free space before running this.
// Also - please ask your HDD for forgiveness.
func main() {

	var quick string
	flag.StringVar(&quick, "quick", "", "Whether or not to run a faster set of tests.")
	flag.Parse()

	smallByteSizes := []int{128, 256, 512}
	smallByteLengths := []int{1000, 5000, 10000, 50000, 100000, 500000}

	largeByteSizes := []int{1024, 2048, 4096}
	largeByteLengths := []int{1000, 5000, 10000}

	// Temporary overrides
	if quick == "yes" {
		fmt.Printf("Running shortened test suite ...\n")
		smallByteSizes = []int{128, 256, 512}
		smallByteLengths = []int{1000, 5000, 10000}
		largeByteSizes = []int{1024}
		largeByteLengths = []int{1000}
	} else {
		fmt.Printf("Running full test suite - to check only a subset of tests run with --quick=yes\n")
	}

	benchmarks := make([]Benchmark, 0)

	fileService, err := gas.NewService("file", []string{"test/file/"}...)

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

	benchmarks = append(benchmarks, Benchmark{
		service:     fileService,
		description: "File",
		tests:       make([]BenchmarkTest, 0),
	})

	bfileService, err := gas.NewService("bfile", []string{"test/bfile/"}...)

	if err != nil {
		log.Fatalf("Error creating bfile service: %s", err)
	}

	benchmarks = append(benchmarks, Benchmark{
		service:     bfileService,
		description: "BFile",
		tests:       make([]BenchmarkTest, 0),
	})

	for i, _ := range benchmarks {
		// Small byte tests up to 10,000,000 items.
		for _, s := range smallByteSizes {
			for _, l := range smallByteLengths {
				benchmarks[i].tests = append(benchmarks[i].tests, BenchmarkTest{
					bytes:  s,
					length: l,
				})
			}
		}

		// Large byte tests up to 100,000 items.
		for _, s := range largeByteSizes {
			for _, l := range largeByteLengths {
				benchmarks[i].tests = append(benchmarks[i].tests, BenchmarkTest{
					bytes:  s,
					length: l,
				})
			}
		}
	}

	fmt.Printf("Generating random data... ")

	randomData := make(map[int][][]byte)

	for _, s := range []int{128, 256, 512, 1024, 2048, 4096} {
		fmt.Printf("%d ... ", s)
		randomData[s] = make([][]byte, 0)
		for i := 0; i < 100; i++ {
			randomData[s] = append(randomData[s], generateBytes(s))
		}
	}

	fmt.Printf("done!\n")

	fmt.Printf("Running tests...\n")

	for i, b := range benchmarks {
		fmt.Printf("\nRunning Service Tests: %s\n", b.description)

		benchmarks[i].readDuration = time.Since(time.Now())
		benchmarks[i].writeDuration = time.Since(time.Now())

		for j, t := range b.tests {
			fmt.Printf("  -> %d Lines @ %d Bytes ... ", t.length, t.bytes)
			w, r, err := testServiceSpeed(b.service, t.length, randomData[t.bytes])
			if err != nil {
				log.Fatal(err)
			}
			benchmarks[i].tests[j].readDuration = r
			benchmarks[i].tests[j].writeDuration = w

			benchmarks[i].readDuration += r
			benchmarks[i].writeDuration += w

			fmt.Printf("write: %s , read: %s", benchmarks[i].tests[j].writeDuration, benchmarks[i].tests[j].readDuration)

			fmt.Printf("\n")
		}
	}

	fmt.Printf("\n")

	for _, b := range benchmarks {
		fmt.Printf("%s Total write: %s , read: %s \n", b.description, b.writeDuration, b.readDuration)
	}

	return
}

// n = number of items in a bucket
// s = size of item data in bytes
func testServiceSpeed(service gas.Service, n int, randomData [][]byte) (writeDuration, readDuration time.Duration, err error) {
	if n < 500 {
		err = fmt.Errorf("That test isn't worth running: n must be >= 500.")
		return
	}

	// s := len(randomData[0])

	b, err := service.GetBucket("test1")

	if err != nil {
		return
	}

	b.RemoveAllItems()

	// r = Number of items we will fetch at a time in read tests.
	r := n / 10

	if r > 500 {
		r = 500
	}

	// Number of fetch tests to run ( x2 - first and last )
	q := 1000

	startWriteTime := time.Now()
	for i := 0; i < n; i++ {
		b.PushItem(randomData[rand.Intn(len(randomData))])
	}
	writeDuration = time.Since(startWriteTime)

	startReadTime := time.Now()
	for i := 0; i < q; i++ {
		// Offset
		o := i * (n / q)
		_, _ = b.GetFirstItems(r, o)
		_, _ = b.GetLastItems(r, o)
	}

	readDuration = time.Since(startReadTime)

	b.RemoveAllItems()

	return
}

func generateBytes(length int) []byte {
	charset := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
	bytes := ""
	for i := 0; i < length; i++ {
		bytes = bytes + string(charset[rand.Intn(len(charset))])
	}

	return []byte(bytes)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"sync/atomic"
	"time"
)

var (
	addr     = flag.String("addr", env("PROXY_ADDR", ":9294"), "Listen address for HTTP requests")
	logstash = flag.String("logstash", env("PROXY_LOGSTASH", "127.0.0.1:9293"), "Input TCP address for logstash")
)

var (
	Success      = new(uint64)
	ConnectError = new(uint64)
	CopyError    = new(uint64)
	Total        = new(uint64)
)

func main() {
	flag.Parse()

	go PrintStats(time.Hour)

	http.HandleFunc("/_status", HandleStatus)
	http.HandleFunc("/", HandleInbound)

	fmt.Printf("I Listening on %s\n", *addr)
	http.ListenAndServe(*addr, nil)
}

func env(key, def string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return def
}

func HandleStatus(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", "application/json")
	fmt.Fprint(w, Stats())
}

func HandleInbound(w http.ResponseWriter, r *http.Request) {
	defer atomic.AddUint64(Total, 1)
	defer r.Body.Close()

	conn, err := net.Dial("tcp", *logstash)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		fmt.Printf("E Failed to connect to %s: %s\n", *logstash, err)
		atomic.AddUint64(ConnectError, 1)
		return
	}
	defer conn.Close()

	n, err := io.Copy(conn, r.Body)
	if err != nil {
		fmt.Printf("E io.Copy: %s\n", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		atomic.AddUint64(CopyError, 1)
		return
	}

	atomic.AddUint64(Success, 1)
	w.Header().Add("X-Bytes-Written", fmt.Sprint(n))
}

func PrintStats(freq time.Duration) {
	for {
		<-time.After(freq)
		fmt.Println("S " + Stats())
	}
}

func Stats() string {
	return fmt.Sprintf(`{ "now": "%s", success": %d, "connect_error": %d, "copy_error": %d, "total": %d }`+"\n",
		time.Now().Format(time.RFC3339),
		atomic.LoadUint64(Success),
		atomic.LoadUint64(ConnectError),
		atomic.LoadUint64(CopyError),
		atomic.LoadUint64(Total),
	)
}
<commit_msg>Removed extra newline in stats JSON<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"sync/atomic"
	"time"
)

var (
	addr     = flag.String("addr", env("PROXY_ADDR", ":9294"), "Listen address for HTTP requests")
	logstash = flag.String("logstash", env("PROXY_LOGSTASH", "127.0.0.1:9293"), "Input TCP address for logstash")
)

var (
	Success      = new(uint64)
	ConnectError = new(uint64)
	CopyError    = new(uint64)
	Total        = new(uint64)
)

func main() {
	flag.Parse()

	go PrintStats(time.Hour)

	http.HandleFunc("/_status", HandleStatus)
	http.HandleFunc("/", HandleInbound)

	fmt.Printf("I Listening on %s\n", *addr)
	http.ListenAndServe(*addr, nil)
}

func env(key, def string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return def
}

func HandleStatus(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", "application/json")
	fmt.Fprint(w, Stats())
}

func HandleInbound(w http.ResponseWriter, r *http.Request) {
	defer atomic.AddUint64(Total, 1)
	defer r.Body.Close()

	conn, err := net.Dial("tcp", *logstash)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		fmt.Printf("E Failed to connect to %s: %s\n", *logstash, err)
		atomic.AddUint64(ConnectError, 1)
		return
	}
	defer conn.Close()

	n, err := io.Copy(conn, r.Body)
	if err != nil {
		fmt.Printf("E io.Copy: %s\n", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		atomic.AddUint64(CopyError, 1)
		return
	}

	atomic.AddUint64(Success, 1)
	w.Header().Add("X-Bytes-Written", fmt.Sprint(n))
}

func PrintStats(freq time.Duration) {
	for {
		<-time.After(freq)
		fmt.Println("S " + Stats())
	}
}

func Stats() string {
	return fmt.Sprintf(`{ "now": "%s", success": %d, "connect_error": %d, "copy_error": %d, "total": %d }`,
		time.Now().Format(time.RFC3339),
		atomic.LoadUint64(Success),
		atomic.LoadUint64(ConnectError),
		atomic.LoadUint64(CopyError),
		atomic.LoadUint64(Total),
	)
}
<|endoftext|>


<commit_before>package main

/*
 * A shiny status page.
 *
 * Want it to combine my existing idle page and tiny-care-terminal.
 *
 * Things to include:
 *  - some twitter accounts
 *      - @tinycarebot, @selfcare_bot and @magicrealismbot. Maybe that boat one instead of magic realism.
 *  - weather
 *  - recent git commits
 *  - system status:
 *      - User/hostname
 *      - Kerberos ticket status
 *      - Current time
 *      - Uptime
 *      - Battery and time left
 *      - Audio status and volume
 *      - Network
 *          - Local, docker, wireless
 *      - Disk
 *          - Mounts, free/used/total/percentage w/ color
 *      - CPU
 *          - Load average w/ color
 *          - Percentage
 *          - Top processes?
 *      - Status of git repos
 *
 * Minimum terminal size to support:
 *  - 189x77 (half monitor with some stacks)
 *  - 104x56ish? (half the laptop screen with some stacks)
 *  - 100x50? (nice and round)
 *  - 80x40? (my default putty)
 *
 */

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/user"
	"strings"
	"syscall"
	"time"
	"unicode/utf8"

	linuxproc "github.com/c9s/goprocinfo/linux"
	ui "github.com/gizak/termui"
)

var timerCounter uint64 = 0
var lastTimer uint64 = 0

////////////////////////////////////////////
// Utility: Formatting
////////////////////////////////////////////

/**
 * Make a string as wide as requested, with stuff left justified and right justified.
 *
 * width:       How wide to get.
 * left:        What text goes on the left?
 * right:       What text goes on the right?
 * fillChar:    What character to use as the filler.
 */
func fitAStringToWidth(width int, left string, right string, fillChar string) string {
	leftLen := utf8.RuneCountInString(left)
	rightLen := utf8.RuneCountInString(right)
	fillCharLen := utf8.RuneCountInString(fillChar) // Usually 1

	// Figure out how many filler chars we need
	fillLen := width - (leftLen + rightLen)
	fillRunes := (fillLen - 1 + fillCharLen) / fillCharLen

	if fillRunes < 0 {
		fillRunes = 0
	}

	fillStr := strings.Repeat(fillChar, fillRunes)

	return fmt.Sprintf("%s %s %s", left, fillStr, right)
}

////////////////////////////////////////////
// Utility: Data gathering
////////////////////////////////////////////

func execAndGetOutput(name string, args ...string) (stdout string, exitCode int, err error) {
	cmd := exec.Command(name, args...)

	var out bytes.Buffer
	cmd.Stdout = &out

	err = cmd.Run()

	// Getting the exit code is platform dependant, this code isn't portable
	exitCode = 0

	if err != nil {
		// Based on: https://stackoverflow.com/questions/10385551/get-exit-code-go
		if exitError, ok := err.(*exec.ExitError); ok {
			ws := exitError.Sys().(syscall.WaitStatus)
			exitCode = ws.ExitStatus()
		} else {
			// Failed, but on a platform where this conversion doesn't work...
			exitCode = 1
		}
	}

	stdout = out.String()

	return
}

func getUsername() string {
	curUser, userErr := user.Current()
	userName := "unknown"
	if userErr == nil {
		userName = curUser.Username
	}

	return userName
}

func getHostname() (string, string) {
	hostName, hostErr := os.Hostname()
	if hostErr != nil {
		hostName = "unknown"
	}

	prettyName, _, prettyNameErr := execAndGetOutput("pretty-hostname")

	if prettyNameErr == nil {
		return hostName, prettyName
	} else {
		return hostName, hostName
	}
}

func getTime() (time.Time, *linuxproc.Uptime) {
	now := time.Now().UTC()
	uptime, err := linuxproc.ReadUptime("/proc/uptime")

	if err != nil {
		uptime = nil
	}

	return now, uptime

}

////////////////////////////////////////////
// Utility: Widgets
////////////////////////////////////////////

type CAHWidget interface {
	getGridWidget() ui.GridBufferer
	update()
	resize()
}

type UpdateInterval interface {
	getUpdateInterval() time.Duration
	getLastUpdated() *time.Time
	setLastUpdated(t time.Time)
}

func shouldUpdate(updater UpdateInterval) bool {
	now := time.Now()
	lastUpdated := updater.getLastUpdated()

	if lastUpdated == nil {
		// First time, execute it
		updater.setLastUpdated(now)
		return true
	} else {
		// Has enough time passed?
		elapsed := now.Sub(*lastUpdated)

		if elapsed.Nanoseconds() > updater.getUpdateInterval().Nanoseconds() {
			updater.setLastUpdated(time.Now())
			return true
		}
	}

	return false
}

type TempWidget struct {
	widget *ui.Par
}

func NewTempWidget(l string) *TempWidget {
	p := ui.NewPar(l)
	p.Height = 5
	p.BorderLabel = l

	// Create our widget
	w := &TempWidget{
		widget: p,
	}

	// Invoke its functions
	w.update()
	w.resize()

	return w
}

func (w *TempWidget) getGridWidget() ui.GridBufferer {
	return w.widget
}

func (w *TempWidget) update() {
	// Do nothing
}

func (w *TempWidget) resize() {
	// Do nothing
}

////////////////////////////////////////////
// Widget: Header
////////////////////////////////////////////

type HeaderWidget struct {
	widget         *ui.Par
	userHostHeader string
	lastUpdated    uint64
}

func NewHeaderWidget() *HeaderWidget {
	// Create base element
	e := ui.NewPar("")

	// Static information
	userName := getUsername()
	hostName, prettyName := getHostname()
	var userHostHeader string

	if prettyName != hostName {
		// Host/pretty name are different
		userHostHeader = fmt.Sprintf(" %v @ %v (%v)", userName, prettyName, hostName)
	} else {
		// Host/pretty name are the same (or pretty failed)
		userHostHeader = fmt.Sprintf("%v @ %v", userName, hostName)
	}

	// Create our widget
	w := &HeaderWidget{
		widget:         e,
		userHostHeader: userHostHeader,
	}

	// Invoke its functions
	w.update()
	w.resize()

	return w
}

func (w *HeaderWidget) getGridWidget() ui.GridBufferer {
	return w.widget
}

func (w *HeaderWidget) update() {
	now, uptime := getTime()
	nowStr := now.Local().Format(time.RFC1123Z)
	uptimeStr := uptime.GetTotalDuration()

	timeStr := fmt.Sprintf("%v (%v) ", nowStr, uptimeStr)

	w.widget.BorderLabel = fitAStringToWidth(ui.TermWidth()-4, w.userHostHeader, timeStr, "-")
}

func (w *HeaderWidget) resize() {
	// Update header on window resize
	w.widget.X = 0
	w.widget.Y = 0
	w.widget.Width = ui.TermWidth()
	w.widget.Height = ui.TermHeight()

	// Also update dynamic information, since the spacing depends on the window width
	w.update()
}

////////////////////////////////////////////
// Widget: Time
////////////////////////////////////////////

type TimeWidget struct {
	widget      *ui.Par
	tickCounter uint64
}

func NewTimeWidget() *TimeWidget {
	// Create base element
	e := ui.NewPar("Time")
	e.Height = 4

	// Create widget
	w := &TimeWidget{
		widget:      e,
		tickCounter: 0,
	}

	w.update()
	w.resize()

	return w
}

func (w *TimeWidget) getGridWidget() ui.GridBufferer {
	return w.widget
}

func (w *TimeWidget) update() {
	w.widget.BorderLabel = fmt.Sprintf("Time (%v)", w.tickCounter)
	w.tickCounter++

	now, uptime := getTime()
	nowStr := now.Format(time.RFC1123Z)
	uptimeStr := uptime.GetTotalDuration()

	w.widget.Text = fmt.Sprintf("Now: %v\nUptime: %v", nowStr, uptimeStr)
}

func (w *TimeWidget) resize() {
	// Do nothing
}

////////////////////////////////////////////
// Widget: Kerberos
////////////////////////////////////////////

const KerberosUpdateIntervalSeconds = 10

type KerberosWidget struct {
	widget      *ui.Par
	lastUpdated *time.Time
}

func NewKerberosWidget() *KerberosWidget {
	// Create base element
	e := ui.NewPar("Kerberos")
	e.Height = 1
	e.Border = false

	// Create widget
	w := &KerberosWidget{
		widget:      e,
		lastUpdated: nil,
	}

	w.update()
	w.resize()

	return w
}

func (w *KerberosWidget) getGridWidget() ui.GridBufferer {
	return w.widget
}

func (w *KerberosWidget) update() {
	if shouldUpdate(w) {
		// Do we have a ticket?
		_, exitCode, _ := execAndGetOutput("klist", "-s")

		hasTicket := exitCode == 0

		// Get the time left
		timeLeftOutput, _, err := execAndGetOutput("kleft", "")
		var hasTimeLeft = false
		var timeLeft string

		if err == nil {
			timeLeftParts := strings.Split(timeLeftOutput, " ")
			if len(timeLeftParts) > 1 {
				hasTimeLeft = true
				timeLeft = strings.TrimSpace(timeLeftParts[1])
			}
		}

		// Piece it all together
		if hasTicket {
			if hasTimeLeft {
				w.widget.Text = fmt.Sprintf("Krb: OK (%v)", timeLeft)
			} else {
				w.widget.Text = fmt.Sprintf("Krb: OK")
			}
			w.widget.TextFgColor = ui.ColorGreen + ui.AttrBold
		} else {
			w.widget.Text = fmt.Sprintf("Krb: NO TICKET")
			w.widget.TextFgColor = ui.ColorRed + ui.AttrBold
		}
	}
}

func (w *KerberosWidget) resize() {
	// Do nothing
}

func (w *KerberosWidget) getUpdateInterval() time.Duration {
	// Update every 10 seconds
	return time.Second * 5
}

func (w *KerberosWidget) getLastUpdated() *time.Time {
	return w.lastUpdated
}

func (w *KerberosWidget) setLastUpdated(t time.Time) {
	w.lastUpdated = &t
}

////////////////////////////////////////////
// Where the real stuff happens
////////////////////////////////////////////

func main() {
	// Set up the console UI
	err := ui.Init()
	if err != nil {
		panic(err)
	}
	defer log.Printf("Final Timer: %v (%v)", timerCounter, lastTimer)
	defer ui.Close()

	ui.DefaultEvtStream.Merge("timer", ui.NewTimerCh(5*time.Second))

	//
	// Create the widgets
	//
	widgets := make([]CAHWidget, 0)

	header := NewHeaderWidget()
	widgets = append(widgets, header)

	kerberos := NewKerberosWidget()
	widgets = append(widgets, kerberos)

	network := NewTempWidget("network")
	widgets = append(widgets, network)

	time := NewTimeWidget()
	widgets = append(widgets, time)

	battAudio := NewTempWidget("battery/audio")
	widgets = append(widgets, battAudio)

	disk := NewTempWidget("disk")
	widgets = append(widgets, disk)

	cpu := NewTempWidget("cpu")
	widgets = append(widgets, cpu)

	repo := NewTempWidget("repos")
	widgets = append(widgets, repo)

	commits := NewTempWidget("commits")
	widgets = append(widgets, commits)

	twitter1 := NewTempWidget("tinycare")
	widgets = append(widgets, twitter1)

	twitter2 := NewTempWidget("selfcare")
	widgets = append(widgets, twitter2)

	twitter3 := NewTempWidget("a strange voyage")
	widgets = append(widgets, twitter3)

	weather := NewTempWidget("weather")
	widgets = append(widgets, weather)

	//
	// Create the layout
	//

	// Give space around the ui.Body for the header box to wrap all around
	ui.Body.Width = ui.TermWidth() - 2
	ui.Body.X = 1
	ui.Body.Y = 1

	ui.Body.AddRows(
		ui.NewRow(
			ui.NewCol(6, 0, time.getGridWidget()),
			ui.NewCol(6, 0, kerberos.getGridWidget())),
		ui.NewRow(
			ui.NewCol(3, 0, network.getGridWidget()),
			ui.NewCol(3, 0, disk.getGridWidget()),
			ui.NewCol(3, 0, cpu.getGridWidget()),
			ui.NewCol(3, 0, battAudio.getGridWidget())),
		ui.NewRow(
			ui.NewCol(6, 0, repo.getGridWidget()),
			ui.NewCol(6, 0, commits.getGridWidget())),
		ui.NewRow(
			ui.NewCol(3, 0, weather.getGridWidget()),
			ui.NewCol(3, 0, twitter1.getGridWidget()),
			ui.NewCol(3, 0, twitter2.getGridWidget()),
			ui.NewCol(3, 0, twitter3.getGridWidget())))

	ui.Body.Align()

	render := func() {
		ui.Body.Align()
		ui.Clear()
		ui.Render(header.widget, ui.Body)
	}

	//
	//  Activate
	//

	log.Printf("Failed: %v", timerCounter)

	render()

	ui.Handle("/sys/kbd/q", func(ui.Event) {
		// press q to quit
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/C-c", func(ui.Event) {
		// ctrl-c to quit
		ui.StopLoop()
	})

	ui.Handle("/timer/5s", func(e ui.Event) {
		// Call all update funcs
		for _, w := range widgets {
			w.update()
		}

		// Re-render
		render()
	})

	ui.Handle("/sys/wnd/resize", func(ui.Event) {
		// Re-layout on resize
		ui.Body.Width = ui.TermWidth() - 2

		// Call all resize funcs
		for _, w := range widgets {
			w.resize()
		}

		// Re-render
		render()
	})

	ui.Loop()
}
<commit_msg>Experimenting with single-line cells<commit_after>package main

/*
 * A shiny status page.
 *
 * Want it to combine my existing idle page and tiny-care-terminal.
 *
 * Things to include:
 *  - some twitter accounts
 *      - @tinycarebot, @selfcare_bot and @magicrealismbot. Maybe that boat one instead of magic realism.
 *  - weather
 *  - recent git commits
 *  - system status:
 *      - User/hostname
 *      - Kerberos ticket status
 *      - Current time
 *      - Uptime
 *      - Battery and time left
 *      - Audio status and volume
 *      - Network
 *          - Local, docker, wireless
 *      - Disk
 *          - Mounts, free/used/total/percentage w/ color
 *      - CPU
 *          - Load average w/ color
 *          - Percentage
 *          - Top processes?
 *      - Status of git repos
 *
 * Minimum terminal size to support:
 *  - 189x77 (half monitor with some stacks)
 *  - 104x56ish? (half the laptop screen with some stacks)
 *  - 100x50? (nice and round)
 *  - 80x40? (my default putty)
 *
 */

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/user"
	"strings"
	"syscall"
	"time"
	"unicode/utf8"

	linuxproc "github.com/c9s/goprocinfo/linux"
	ui "github.com/gizak/termui"
)

var timerCounter uint64 = 0
var lastTimer uint64 = 0

////////////////////////////////////////////
// Utility: Formatting
////////////////////////////////////////////

/**
 * Make a string as wide as requested, with stuff left justified and right justified.
 *
 * width:       How wide to get.
 * left:        What text goes on the left?
 * right:       What text goes on the right?
 * fillChar:    What character to use as the filler.
 */
func fitAStringToWidth(width int, left string, right string, fillChar string) string {
	leftLen := utf8.RuneCountInString(left)
	rightLen := utf8.RuneCountInString(right)
	fillCharLen := utf8.RuneCountInString(fillChar) // Usually 1

	// Figure out how many filler chars we need
	fillLen := width - (leftLen + rightLen)
	fillRunes := (fillLen - 1 + fillCharLen) / fillCharLen

	if fillRunes < 0 {
		fillRunes = 0
	}

	fillStr := strings.Repeat(fillChar, fillRunes)

	return fmt.Sprintf("%s %s %s", left, fillStr, right)
}

func rightJustify(width int, str string) string {

	rightJustfyLen := width - utf8.RuneCountInString(str)

	log.Printf("Width: %v, Rune: %v, Len: %v, Justify: %v", width, utf8.RuneCountInString(str), len(str), rightJustfyLen)
	var rightJustify = ""
	if rightJustfyLen > 0 {
		rightJustify = strings.Repeat(" ", rightJustfyLen)
	}

	return rightJustify + str
}

////////////////////////////////////////////
// Utility: Data gathering
////////////////////////////////////////////

func execAndGetOutput(name string, args ...string) (stdout string, exitCode int, err error) {
	cmd := exec.Command(name, args...)

	var out bytes.Buffer
	cmd.Stdout = &out

	err = cmd.Run()

	// Getting the exit code is platform dependant, this code isn't portable
	exitCode = 0

	if err != nil {
		// Based on: https://stackoverflow.com/questions/10385551/get-exit-code-go
		if exitError, ok := err.(*exec.ExitError); ok {
			ws := exitError.Sys().(syscall.WaitStatus)
			exitCode = ws.ExitStatus()
		} else {
			// Failed, but on a platform where this conversion doesn't work...
			exitCode = 1
		}
	}

	stdout = out.String()

	return
}

func getUsername() string {
	curUser, userErr := user.Current()
	userName := "unknown"
	if userErr == nil {
		userName = curUser.Username
	}

	return userName
}

func getHostname() (string, string) {
	hostName, hostErr := os.Hostname()
	if hostErr != nil {
		hostName = "unknown"
	}

	prettyName, _, prettyNameErr := execAndGetOutput("pretty-hostname")

	if prettyNameErr == nil {
		return hostName, prettyName
	} else {
		return hostName, hostName
	}
}

func getTime() (time.Time, *linuxproc.Uptime) {
	now := time.Now().UTC()
	uptime, err := linuxproc.ReadUptime("/proc/uptime")

	if err != nil {
		uptime = nil
	}

	return now, uptime

}

////////////////////////////////////////////
// Utility: Widgets
////////////////////////////////////////////

type CAHWidget interface {
	getGridWidget() ui.GridBufferer
	update()
	resize()
}

type UpdateInterval interface {
	getUpdateInterval() time.Duration
	getLastUpdated() *time.Time
	setLastUpdated(t time.Time)
}

func shouldUpdate(updater UpdateInterval) bool {
	now := time.Now()
	lastUpdated := updater.getLastUpdated()

	if lastUpdated == nil {
		// First time, execute it
		updater.setLastUpdated(now)
		return true
	} else {
		// Has enough time passed?
		elapsed := now.Sub(*lastUpdated)

		if elapsed.Nanoseconds() > updater.getUpdateInterval().Nanoseconds() {
			updater.setLastUpdated(time.Now())
			return true
		}
	}

	return false
}

type TempWidget struct {
	widget *ui.Par
}

func NewTempWidget(l string) *TempWidget {
	p := ui.NewPar(l)
	p.Height = 5
	p.BorderLabel = l

	// Create our widget
	w := &TempWidget{
		widget: p,
	}

	// Invoke its functions
	w.update()
	w.resize()

	return w
}

func (w *TempWidget) getGridWidget() ui.GridBufferer {
	return w.widget
}

func (w *TempWidget) update() {
	// Do nothing
}

func (w *TempWidget) resize() {
	// Do nothing
}

////////////////////////////////////////////
// Widget: Header
////////////////////////////////////////////

type HeaderWidget struct {
	widget         *ui.Par
	userHostHeader string
	lastUpdated    uint64
}

func NewHeaderWidget() *HeaderWidget {
	// Create base element
	e := ui.NewPar("")

	// Static information
	userName := getUsername()
	hostName, prettyName := getHostname()
	var userHostHeader string

	if prettyName != hostName {
		// Host/pretty name are different
		userHostHeader = fmt.Sprintf(" %v @ %v (%v)", userName, prettyName, hostName)
	} else {
		// Host/pretty name are the same (or pretty failed)
		userHostHeader = fmt.Sprintf("%v @ %v", userName, hostName)
	}

	// Create our widget
	w := &HeaderWidget{
		widget:         e,
		userHostHeader: userHostHeader,
	}

	// Invoke its functions
	w.update()
	w.resize()

	return w
}

func (w *HeaderWidget) getGridWidget() ui.GridBufferer {
	return w.widget
}

func (w *HeaderWidget) update() {
	now, uptime := getTime()
	nowStr := now.Local().Format(time.RFC1123Z)
	uptimeStr := uptime.GetTotalDuration()

	timeStr := fmt.Sprintf("%v (%v) ", nowStr, uptimeStr)

	w.widget.BorderLabel = fitAStringToWidth(ui.TermWidth()-4, w.userHostHeader, timeStr, "-")
}

func (w *HeaderWidget) resize() {
	// Update header on window resize
	w.widget.X = 0
	w.widget.Y = 0
	w.widget.Width = ui.TermWidth()
	w.widget.Height = ui.TermHeight()

	// Also update dynamic information, since the spacing depends on the window width
	w.update()
}

////////////////////////////////////////////
// Widget: Time
////////////////////////////////////////////

type TimeWidget struct {
	widget      *ui.Par
	tickCounter uint64
}

func NewTimeWidget() *TimeWidget {
	// Create base element
	e := ui.NewPar("Time")
	e.Height = 1
	e.Border = false
	e.TextFgColor = ui.ColorGreen

	// Create widget
	w := &TimeWidget{
		widget:      e,
		tickCounter: 0,
	}

	w.update()
	w.resize()

	// Also want to run an update right after things get rendered so we can use the width to right justify correctly
	// TODO: This isn't working right, isn't being called correctly
	time.AfterFunc(time.Second*1, func() {
		w.update()
	})

	return w
}

func (w *TimeWidget) getGridWidget() ui.GridBufferer {
	return w.widget
}

func (w *TimeWidget) update() {
	now, uptime := getTime()
	nowStr := now.Local().Format(time.RFC1123Z)
	uptimeStr := uptime.GetTotalDuration()

	timeStr := fmt.Sprintf("%v (%v) ", nowStr, uptimeStr)

	w.widget.Text = rightJustify(w.widget.Width, timeStr)
}

func (w *TimeWidget) resize() {
	// Update
	w.update()
}

////////////////////////////////////////////
// Widget: Kerberos
////////////////////////////////////////////

const KerberosUpdateIntervalSeconds = 10

type KerberosWidget struct {
	widget      *ui.Par
	lastUpdated *time.Time
}

func NewKerberosWidget() *KerberosWidget {
	// Create base element
	e := ui.NewPar("Kerberos")
	e.Height = 1
	e.Border = false

	// Create widget
	w := &KerberosWidget{
		widget:      e,
		lastUpdated: nil,
	}

	w.update()
	w.resize()

	return w
}

func (w *KerberosWidget) getGridWidget() ui.GridBufferer {
	return w.widget
}

func (w *KerberosWidget) update() {
	if shouldUpdate(w) {
		// Do we have a ticket?
		_, exitCode, _ := execAndGetOutput("klist", "-s")

		hasTicket := exitCode == 0

		// Get the time left
		timeLeftOutput, _, err := execAndGetOutput("kleft", "")
		var hasTimeLeft = false
		var timeLeft string

		if err == nil {
			timeLeftParts := strings.Split(timeLeftOutput, " ")
			if len(timeLeftParts) > 1 {
				hasTimeLeft = true
				timeLeft = strings.TrimSpace(timeLeftParts[1])
			}
		}

		// Piece it all together
		if hasTicket {
			if hasTimeLeft {
				w.widget.Text = fmt.Sprintf("Krb: OK (%v)", timeLeft)
			} else {
				w.widget.Text = fmt.Sprintf("Krb: OK")
			}
			w.widget.TextFgColor = ui.ColorGreen + ui.AttrBold
		} else {
			w.widget.Text = fmt.Sprintf("Krb: NO TICKET")
			w.widget.TextFgColor = ui.ColorRed + ui.AttrBold
		}
	}
}

func (w *KerberosWidget) resize() {
	// Do nothing
}

func (w *KerberosWidget) getUpdateInterval() time.Duration {
	// Update every 10 seconds
	return time.Second * 5
}

func (w *KerberosWidget) getLastUpdated() *time.Time {
	return w.lastUpdated
}

func (w *KerberosWidget) setLastUpdated(t time.Time) {
	w.lastUpdated = &t
}

////////////////////////////////////////////
// Where the real stuff happens
////////////////////////////////////////////

func main() {
	// Set up logging
	logFile, logErr := os.OpenFile("go.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0660)
	if logErr != nil {
		panic(logErr)
	}
	defer logFile.Close()

	log.SetOutput(logFile)

	// Set up the console UI
	uiErr := ui.Init()
	if uiErr != nil {
		panic(uiErr)
	}
	defer ui.Close()

	ui.DefaultEvtStream.Merge("timer", ui.NewTimerCh(5*time.Second))

	//
	// Create the widgets
	//
	widgets := make([]CAHWidget, 0)

	header := NewHeaderWidget()
	widgets = append(widgets, header)

	kerberos := NewKerberosWidget()
	widgets = append(widgets, kerberos)

	network := NewTempWidget("network")
	widgets = append(widgets, network)

	time := NewTimeWidget()
	widgets = append(widgets, time)

	battAudio := NewTempWidget("battery/audio")
	widgets = append(widgets, battAudio)

	disk := NewTempWidget("disk")
	widgets = append(widgets, disk)

	cpu := NewTempWidget("cpu")
	widgets = append(widgets, cpu)

	repo := NewTempWidget("repos")
	widgets = append(widgets, repo)

	commits := NewTempWidget("commits")
	widgets = append(widgets, commits)

	twitter1 := NewTempWidget("tinycare")
	widgets = append(widgets, twitter1)

	twitter2 := NewTempWidget("selfcare")
	widgets = append(widgets, twitter2)

	twitter3 := NewTempWidget("a strange voyage")
	widgets = append(widgets, twitter3)

	weather := NewTempWidget("weather")
	widgets = append(widgets, weather)

	//
	// Create the layout
	//

	// Give space around the ui.Body for the header box to wrap all around
	ui.Body.Width = ui.TermWidth() - 2
	ui.Body.X = 1
	ui.Body.Y = 1

	ui.Body.AddRows(
		ui.NewRow(
			ui.NewCol(6, 0, kerberos.getGridWidget()),
			ui.NewCol(6, 0, time.getGridWidget())),
		ui.NewRow(
			ui.NewCol(3, 0, network.getGridWidget()),
			ui.NewCol(3, 0, disk.getGridWidget()),
			ui.NewCol(3, 0, cpu.getGridWidget()),
			ui.NewCol(3, 0, battAudio.getGridWidget())),
		ui.NewRow(
			ui.NewCol(6, 0, repo.getGridWidget()),
			ui.NewCol(6, 0, commits.getGridWidget())),
		ui.NewRow(
			ui.NewCol(3, 0, weather.getGridWidget()),
			ui.NewCol(3, 0, twitter1.getGridWidget()),
			ui.NewCol(3, 0, twitter2.getGridWidget()),
			ui.NewCol(3, 0, twitter3.getGridWidget())))

	ui.Body.Align()

	render := func() {
		ui.Body.Align()
		ui.Clear()
		ui.Render(header.widget, ui.Body)
	}

	//
	//  Activate
	//

	log.Printf("Failed: %v", timerCounter)

	render()

	ui.Handle("/sys/kbd/q", func(ui.Event) {
		// press q to quit
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/C-c", func(ui.Event) {
		// ctrl-c to quit
		ui.StopLoop()
	})

	ui.Handle("/timer/5s", func(e ui.Event) {
		// Call all update funcs
		for _, w := range widgets {
			w.update()
		}

		// Re-render
		render()
	})

	ui.Handle("/sys/wnd/resize", func(ui.Event) {
		// Re-layout on resize
		ui.Body.Width = ui.TermWidth() - 2

		// Call all resize funcs
		for _, w := range widgets {
			w.resize()
		}

		// Re-render
		render()
	})

	ui.Loop()
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"crypto/tls"
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"mellium.im/sasl"
	"mellium.im/xmlstream"
	"mellium.im/xmpp"
	"mellium.im/xmpp/dial"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/stanza"
	"net/http"
	"os"
	"strings"
)

func panicOnErr(err error) {
	if err != nil {
		panic(err)
	}
}

type MessageBody struct {
	stanza.Message
	Body string `xml:"body"`
}

func initXMPP(address jid.JID, pass string, skipTLSVerify bool, legacyTLS bool, forceStartTLS bool) (*xmpp.Session, error) {
	tlsConfig := tls.Config{InsecureSkipVerify: skipTLSVerify}
	var dialer dial.Dialer
	// only use the tls config for the dialer if necessary
	if skipTLSVerify {
		dialer = dial.Dialer{NoTLS: !legacyTLS, TLSConfig: &tlsConfig}
	} else {
		dialer = dial.Dialer{NoTLS: !legacyTLS}
	}
	conn, err := dialer.Dial(context.TODO(), "tcp", address)
	if err != nil {
		return nil, err
	}
	// we need the domain in the tls config if we want to verify the cert
	if !skipTLSVerify {
		tlsConfig.ServerName = address.Domainpart()
	}
	return xmpp.NegotiateSession(
		context.TODO(),
		address.Domain(),
		address,
		conn,
		false,
		xmpp.NewNegotiator(xmpp.StreamConfig{Features: []xmpp.StreamFeature{
			xmpp.BindResource(),
			xmpp.StartTLS(forceStartTLS, &tlsConfig),
			xmpp.SASL("", pass, sasl.ScramSha1Plus, sasl.ScramSha1, sasl.Plain),
		}}),
	)
}

func closeXMPP(session *xmpp.Session) {
	session.Close()
	session.Conn().Close()
}

func main() {
	// get xmpp credentials, message receivers
	xi := os.Getenv("XMPP_ID")
	xp := os.Getenv("XMPP_PASS")
	xr := os.Getenv("XMPP_RECEIVERS")

	// get tls settings from env
	_, skipTLSVerify := os.LookupEnv("XMPP_SKIP_VERIFY")
	_, legacyTLS := os.LookupEnv("XMPP_OVER_TLS")
	_, forceStartTLS := os.LookupEnv("XMPP_FORCE_STARTTLS")

	// check if xmpp credentials and receiver list are supplied
	if xi == "" || xp == "1" || xr == "" {
		log.Fatal("XMPP_ID, XMPP_PASS or XMPP_RECEIVERS not set")
	}

	address, err := jid.Parse(xi)
	panicOnErr(err)

	// connect to xmpp server
	xmppSession, err := initXMPP(address, xp, skipTLSVerify, legacyTLS, forceStartTLS)
	panicOnErr(err)
	defer closeXMPP(xmppSession)

	// send initial presence
	panicOnErr(xmppSession.Send(context.TODO(), stanza.WrapPresence(jid.JID{}, stanza.AvailablePresence, nil)))

	// listen for messages and echo them
	go func() {
		err = xmppSession.Serve(xmpp.HandlerFunc(func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error {
			d := xml.NewTokenDecoder(t)
			// ignore elements that aren't messages
			if start.Name.Local != "message" {
				return nil
			}

			// parse message into struct
			msg := MessageBody{}
			err = d.DecodeElement(&msg, start)
			if err != nil && err != io.EOF {
				return nil
			}

			// ignore empty messages and stanzas that aren't messages
			if msg.Body == "" || msg.Type != stanza.ChatMessage {
				return nil
			}

			// create reply with identical contents
			reply := MessageBody{
				Message: stanza.Message{
					To: msg.From.Bare(),
				},
				Body: msg.Body,
			}

			// try to send reply, ignore errors
			_ = t.Encode(reply)
			return nil
		}))
		panicOnErr(err)
	}()

	// create chan for messages (webhooks -> xmpp)
	messages := make(chan string)

	// wait for messages from the webhooks and send them to all receivers
	go func() {
		for m := range messages {
			for _, r := range strings.Split(xr, ",") {
				recipient, err := jid.Parse(r)
				panicOnErr(err)
				fmt.Println(m)
				fmt.Println(recipient)
				// try to send message, ignore errors
				//_ = xmppSession.Send(context.TODO(), stanza.WrapMessage(recipient, stanza.NormalMessage))
			}
		}
	}()

	// initialize handler for grafana alerts
	http.Handle("/grafana", newMessageHandler(messages, grafanaParserFunc))

	// listen for requests
	http.ListenAndServe(":4321", nil)
}
<commit_msg>Handle alerts with mellium-xmpp<commit_after>package main

import (
	"context"
	"crypto/tls"
	"encoding/xml"
	"io"
	"log"
	"mellium.im/sasl"
	"mellium.im/xmlstream"
	"mellium.im/xmpp"
	"mellium.im/xmpp/dial"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/stanza"
	"net/http"
	"os"
	"strings"
)

func panicOnErr(err error) {
	if err != nil {
		panic(err)
	}
}

type MessageBody struct {
	stanza.Message
	Body string `xml:"body"`
}

func initXMPP(address jid.JID, pass string, skipTLSVerify bool, legacyTLS bool, forceStartTLS bool) (*xmpp.Session, error) {
	tlsConfig := tls.Config{InsecureSkipVerify: skipTLSVerify}
	var dialer dial.Dialer
	// only use the tls config for the dialer if necessary
	if skipTLSVerify {
		dialer = dial.Dialer{NoTLS: !legacyTLS, TLSConfig: &tlsConfig}
	} else {
		dialer = dial.Dialer{NoTLS: !legacyTLS}
	}
	conn, err := dialer.Dial(context.TODO(), "tcp", address)
	if err != nil {
		return nil, err
	}
	// we need the domain in the tls config if we want to verify the cert
	if !skipTLSVerify {
		tlsConfig.ServerName = address.Domainpart()
	}
	return xmpp.NegotiateSession(
		context.TODO(),
		address.Domain(),
		address,
		conn,
		false,
		xmpp.NewNegotiator(xmpp.StreamConfig{Features: []xmpp.StreamFeature{
			xmpp.BindResource(),
			xmpp.StartTLS(forceStartTLS, &tlsConfig),
			xmpp.SASL("", pass, sasl.ScramSha1Plus, sasl.ScramSha1, sasl.Plain),
		}}),
	)
}

func closeXMPP(session *xmpp.Session) {
	session.Close()
	session.Conn().Close()
}

func main() {
	// get xmpp credentials, message receivers
	xi := os.Getenv("XMPP_ID")
	xp := os.Getenv("XMPP_PASS")
	xr := os.Getenv("XMPP_RECEIVERS")

	// get tls settings from env
	_, skipTLSVerify := os.LookupEnv("XMPP_SKIP_VERIFY")
	_, legacyTLS := os.LookupEnv("XMPP_OVER_TLS")
	_, forceStartTLS := os.LookupEnv("XMPP_FORCE_STARTTLS")

	// check if xmpp credentials and receiver list are supplied
	if xi == "" || xp == "" || xr == "" {
		log.Fatal("XMPP_ID, XMPP_PASS or XMPP_RECEIVERS not set")
	}

	address, err := jid.Parse(xi)
	panicOnErr(err)

	// connect to xmpp server
	xmppSession, err := initXMPP(address, xp, skipTLSVerify, legacyTLS, forceStartTLS)
	panicOnErr(err)
	defer closeXMPP(xmppSession)

	// send initial presence
	panicOnErr(xmppSession.Send(context.TODO(), stanza.WrapPresence(jid.JID{}, stanza.AvailablePresence, nil)))

	// listen for messages and echo them
	go func() {
		err = xmppSession.Serve(xmpp.HandlerFunc(func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error {
			d := xml.NewTokenDecoder(t)
			// ignore elements that aren't messages
			if start.Name.Local != "message" {
				return nil
			}

			// parse message into struct
			msg := MessageBody{}
			err = d.DecodeElement(&msg, start)
			if err != nil && err != io.EOF {
				return nil
			}

			// ignore empty messages and stanzas that aren't messages
			if msg.Body == "" || msg.Type != stanza.ChatMessage {
				return nil
			}

			// create reply with identical contents
			reply := MessageBody{
				Message: stanza.Message{
					To: msg.From.Bare(),
				},
				Body: msg.Body,
			}

			// try to send reply, ignore errors
			_ = t.Encode(reply)
			return nil
		}))
		panicOnErr(err)
	}()

	// create chan for messages (webhooks -> xmpp)
	messages := make(chan string)

	// wait for messages from the webhooks and send them to all receivers
	go func() {
		for m := range messages {
			for _, r := range strings.Split(xr, ",") {
				recipient, err := jid.Parse(r)
				panicOnErr(err)
				// try to send message, ignore errors
				_ = xmppSession.Encode(MessageBody{
					Message: stanza.Message{
						To: recipient,
					},
					Body: m,
				})
			}
		}
	}()

	// initialize handler for grafana alerts
	http.Handle("/grafana", newMessageHandler(messages, grafanaParserFunc))

	// listen for requests
	http.ListenAndServe(":4321", nil)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/codegangsta/cli"

	"os"
)

func main() {
	app := cli.NewApp()
	app.Name = "clicrypt"
	app.Usage = "encrypt and decrypt files with AES"
	app.Version = "1.0.0"
	app.Commands = []cli.Command{
		encryptCommand,
		decryptCommand,
	}
	app.Run(os.Args)
}
<commit_msg>Help and usage is written to STDERR instead of STDOUT.<commit_after>package main

import (
	"github.com/codegangsta/cli"

	"os"
)

func main() {
	app := cli.NewApp()
	app.Name = "clicrypt"
	app.Usage = "encrypt and decrypt files with AES"
	app.Version = "1.0.0"
	app.Commands = []cli.Command{
		encryptCommand,
		decryptCommand,
	}
	app.Writer = os.Stderr
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"github.com/reportportal/commons-go/commons"
	"github.com/reportportal/commons-go/conf"
	"github.com/reportportal/commons-go/server"
	"github.com/unrolled/secure"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	currDir, e := os.Getwd()
	if nil != e {
		log.Fatalf("Cannot get workdir: %s", e.Error())
	}
	cfg := conf.EmptyConfig()
	rpConf := struct {
		Cfg         *conf.ServerConfig
		StaticsPath string `env:"RP_STATICS_PATH"`
	}{
		Cfg:         cfg,
		StaticsPath: currDir,
	}
	err := conf.LoadConfig(&rpConf)
	if nil != err {
		log.Fatalf("Cannot log app config")
	}
	info := commons.GetBuildInfo()
	info.Name = "Service UI"
	srv := server.New(rpConf.Cfg, info)
	configureRouter(srv, rpConf)
	srv.StartServer()
}

func configureRouter(srv *server.RpServer, rpConf struct {
	Cfg         *conf.ServerConfig
	StaticsPath string `env:"RP_STATICS_PATH"`
}) {
	srv.WithRouter(func(router *chi.Mux) {
		//apply compression
		router.Use(middleware.DefaultCompress)
		router.Use(middleware.Logger)
		//content security policy
		csp := map[string][]string{
			"default-src": {"'self'", "data:", "'unsafe-inline'", "*.uservoice.com"},
			"script-src": {
				"'self'",
				"'unsafe-inline'",
				"'unsafe-eval'",
				"status.reportportal.io",
				"www.google-analytics.com",
				"stats.g.doubleclick.net",
				"*.saucelabs.com",
				"*.epam.com",
				"*.uservoice.com",
				"*.rawgit.com",
			},
			"worker-src":     {"'self'", "blob:"},
			"font-src":       {"'self'", "data:", "fonts.googleapis.com", "fonts.gstatic.com", "*.rawgit.com"},
			"style-src-elem": {"'self'", "data:", "'unsafe-inline'", "*.googleapis.com", "*.rawgit.com"},
			"media-src":      {"'self'", "*.saucelabs.com"},
			"img-src":        {"*", "'self'", "data:", "blob:"},
			"object-src":     {"'self'"},
		}
		//apply content security policies
		router.Use(func(next http.Handler) http.Handler {
			return secure.New(secure.Options{
				ContentTypeNosniff:    true,
				BrowserXssFilter:      true,
				ContentSecurityPolicy: buildCSP(csp),
				STSSeconds:            315360000,
				STSIncludeSubdomains:  true,
				STSPreload:            true,
			}).Handler(next)
		})
		err := os.Chdir(rpConf.StaticsPath)
		if nil != err {
			log.Fatalf("Dir %s not found", rpConf.StaticsPath)
		}
		router.Handle("/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			//trim query params
			ext := filepath.Ext(trimQuery(r.URL.String(), "?"))
			// never cache html
			if "/" == r.URL.String() || ".html" == ext {
				w.Header().Add("Cache-Control", "no-cache")
			}
			http.FileServer(http.Dir(rpConf.StaticsPath)).ServeHTTP(&redirectingRW{ResponseWriter: w, Request: r}, r)
		}))
	})
}

func trimQuery(s string, sep string) string {
	sepIndex := strings.Index(s, sep)
	if -1 != sepIndex {
		return s[:sepIndex]
	}
	return s
}

func buildCSP(csp map[string][]string) string {
	var instr []string
	for k, v := range csp {
		instr = append(instr, k+" "+strings.Join(v, " "))
	}
	return strings.Join(instr, "; ")
}

type redirectingRW struct {
	*http.Request
	http.ResponseWriter
	ignore bool
}

func (hrw *redirectingRW) Header() http.Header {
	return hrw.ResponseWriter.Header()
}

func (hrw *redirectingRW) WriteHeader(status int) {
    var notFoundStatusCode int = 404
	if status == notFoundStatusCode {
		hrw.ignore = true
		http.Redirect(hrw.ResponseWriter, hrw.Request, "/ui/#notfound", http.StatusTemporaryRedirect)
	} else {
		hrw.ResponseWriter.WriteHeader(status)
	}
}

func (hrw *redirectingRW) Write(p []byte) (int, error) {
	if hrw.ignore {
		return len(p), nil
	}
	return hrw.ResponseWriter.Write(p)
}
<commit_msg>Update main.go<commit_after>package main

import (
	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"github.com/reportportal/commons-go/commons"
	"github.com/reportportal/commons-go/conf"
	"github.com/reportportal/commons-go/server"
	"github.com/unrolled/secure"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	currDir, e := os.Getwd()
	if nil != e {
		log.Fatalf("Cannot get workdir: %s", e.Error())
	}
	cfg := conf.EmptyConfig()
	rpConf := struct {
		Cfg         *conf.ServerConfig
		StaticsPath string `env:"RP_STATICS_PATH"`
	}{
		Cfg:         cfg,
		StaticsPath: currDir,
	}
	err := conf.LoadConfig(&rpConf)
	if nil != err {
		log.Fatalf("Cannot log app config")
	}
	info := commons.GetBuildInfo()
	info.Name = "Service UI"
	srv := server.New(rpConf.Cfg, info)
	configureRouter(srv, rpConf)
	srv.StartServer()
}

func configureRouter(srv *server.RpServer, rpConf struct {
	Cfg         *conf.ServerConfig
	StaticsPath string `env:"RP_STATICS_PATH"`
}) {
	srv.WithRouter(func(router *chi.Mux) {
		//apply compression
		router.Use(middleware.DefaultCompress)
		router.Use(middleware.Logger)
		//content security policy
		csp := map[string][]string{
			"default-src": {"'self'", "data:", "'unsafe-inline'", "*.uservoice.com"},
			"script-src": {
				"'self'",
				"'unsafe-inline'",
				"'unsafe-eval'",
				"status.reportportal.io",
				"www.google-analytics.com",
				"stats.g.doubleclick.net",
				"*.saucelabs.com",
				"*.epam.com",
				"*.uservoice.com",
				"*.rawgit.com",
			},
			"worker-src":     {"'self'", "blob:"},
			"font-src":       {"'self'", "data:", "fonts.googleapis.com", "fonts.gstatic.com", "*.rawgit.com"},
			"style-src-elem": {"'self'", "data:", "'unsafe-inline'", "*.googleapis.com", "*.rawgit.com"},
			"media-src":      {"'self'", "*.saucelabs.com"},
			"img-src":        {"*", "'self'", "data:", "blob:"},
			"object-src":     {"'self'"},
		}
		//apply content security policies
		router.Use(func(next http.Handler) http.Handler {
			return secure.New(secure.Options{
				ContentTypeNosniff:    true,
				BrowserXssFilter:      true,
				ContentSecurityPolicy: buildCSP(csp),
				STSSeconds:            315360000,
				STSIncludeSubdomains:  true,
				STSPreload:            true,
			}).Handler(next)
		})
		err := os.Chdir(rpConf.StaticsPath)
		if nil != err {
			log.Fatalf("Dir %s not found", rpConf.StaticsPath)
		}
		router.Handle("/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			//trim query params
			ext := filepath.Ext(trimQuery(r.URL.String(), "?"))
			// never cache html
			if "/" == r.URL.String() || ".html" == ext {
				w.Header().Add("Cache-Control", "no-cache")
			}
			http.FileServer(http.Dir(rpConf.StaticsPath)).ServeHTTP(&redirectingRW{ResponseWriter: w, Request: r}, r)
		}))
	})
}

func trimQuery(s string, sep string) string {
	sepIndex := strings.Index(s, sep)
	if -1 != sepIndex {
		return s[:sepIndex]
	}
	return s
}

func buildCSP(csp map[string][]string) string {
	var instr []string
	for k, v := range csp {
		instr = append(instr, k+" "+strings.Join(v, " "))
	}
	return strings.Join(instr, "; ")
}

type redirectingRW struct {
	*http.Request
	http.ResponseWriter
	ignore bool
}

func (hrw *redirectingRW) Header() http.Header {
	return hrw.ResponseWriter.Header()
}

func (hrw *redirectingRW) WriteHeader(status int) {
    var notFoundStatusCode int = 404
	if notFoundStatusCode == status {
		hrw.ignore = true
		http.Redirect(hrw.ResponseWriter, hrw.Request, "/ui/#notfound", http.StatusTemporaryRedirect)
	} else {
		hrw.ResponseWriter.WriteHeader(status)
	}
}

func (hrw *redirectingRW) Write(p []byte) (int, error) {
	if hrw.ignore {
		return len(p), nil
	}
	return hrw.ResponseWriter.Write(p)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/michaelsauter/crane/print"
	"os"
	"os/exec"
	"strings"
)

func main() {
	// On panic, recover the error and display it
	defer func() {
		if err := recover(); err != nil {
			print.Error("ERROR: %s\n", err)
		}
	}()

	handleCmd()
}

func executeCommand(name string, args []string) {
	if isVerbose() {
		fmt.Printf("\n--> %s %s\n", name, strings.Join(args, " "))
	}
	cmd := exec.Command(name, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Run()
	if !cmd.ProcessState.Success() {
		panic(cmd.ProcessState.String()) // pass the error?
	}
}

func commandOutput(name string, args []string) (string, error) {
	out, err := exec.Command(name, args...).CombinedOutput()
	return strings.TrimSpace(string(out)), err
}

// from https://gist.github.com/dagoof/1477401
func pipedCommandOutput(pipedCommandArgs ...[]string) ([]byte, error) {
	var commands []exec.Cmd
	for _, commandArgs := range pipedCommandArgs {
		cmd := exec.Command(commandArgs[0], commandArgs[1:]...)
		commands = append(commands, *cmd)
	}
	for i, command := range commands[:len(commands)-1] {
		out, err := command.StdoutPipe()
		if err != nil {
			return nil, err
		}
		command.Start()
		commands[i+1].Stdin = out
	}
	final, err := commands[len(commands)-1].Output()
	if err != nil {
		return nil, err
	}
	return final, nil
}
<commit_msg>support status code hint in panic()<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/michaelsauter/crane/print"
	"os"
	"os/exec"
	"strings"
)

type StatusError struct {
	error  error
	status int
}

func main() {
	// On panic, recover the error, display it and return the given status code if any
	defer func() {
		var statusError StatusError

		switch err := recover().(type) {
		case StatusError:
			statusError = err
		case error:
			statusError = StatusError{err, 1}
		case string:
			statusError = StatusError{errors.New(err), 1}
		default:
			statusError = StatusError{}
		}

		if statusError.error != nil {
			print.Error("ERROR: %s\n", statusError.error)
		}
		os.Exit(statusError.status)
	}()

	handleCmd()
}

func executeCommand(name string, args []string) {
	if isVerbose() {
		fmt.Printf("\n--> %s %s\n", name, strings.Join(args, " "))
	}
	cmd := exec.Command(name, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Run()
	if !cmd.ProcessState.Success() {
		panic(cmd.ProcessState.String()) // pass the error?
	}
}

func commandOutput(name string, args []string) (string, error) {
	out, err := exec.Command(name, args...).CombinedOutput()
	return strings.TrimSpace(string(out)), err
}

// from https://gist.github.com/dagoof/1477401
func pipedCommandOutput(pipedCommandArgs ...[]string) ([]byte, error) {
	var commands []exec.Cmd
	for _, commandArgs := range pipedCommandArgs {
		cmd := exec.Command(commandArgs[0], commandArgs[1:]...)
		commands = append(commands, *cmd)
	}
	for i, command := range commands[:len(commands)-1] {
		out, err := command.StdoutPipe()
		if err != nil {
			return nil, err
		}
		command.Start()
		commands[i+1].Stdin = out
	}
	final, err := commands[len(commands)-1].Output()
	if err != nil {
		return nil, err
	}
	return final, nil
}
<|endoftext|>


<commit_before>// Copyright 2015-2016 trivago GmbH
//
// 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"
	"github.com/sirupsen/logrus"
	_ "github.com/trivago/gollum/consumer"
	"github.com/trivago/gollum/core"
	_ "github.com/trivago/gollum/filter"
	_ "github.com/trivago/gollum/format"
	"github.com/trivago/gollum/logger"
	_ "github.com/trivago/gollum/producer"
	_ "github.com/trivago/gollum/router"
	"github.com/trivago/tgo"
	"github.com/trivago/tgo/thealthcheck"
	"github.com/trivago/tgo/tnet"
	"github.com/trivago/tgo/tos"
	"io/ioutil"
	"os"
	"runtime"
	"runtime/pprof"
	"runtime/trace"
	"sort"
	"strconv"
	"strings"
	"time"
)

// logrusHookBuffer is our single instance of LogrusHookBuffer
var logrusHookBuffer logger.LogrusHookBuffer

func main() {
	exitCode := mainWithExitCode()
	os.Exit(exitCode)
}

func mainWithExitCode() int {
	parseFlags()

	if *flagHelp || len(os.Args) == 1 {
		printFlags()
		return tos.ExitSuccess // ### return, help screen ###
	}

	if *flagExtVersion {
		printExtVersion()
		return tos.ExitSuccess // ### return, version only ###
	}

	if *flagVersion {
		printVersion()
		return tos.ExitSuccess // ### return, version only ###
	}

	if *flagModules {
		printModules()
		return tos.ExitSuccess // ### return, modules only ###
	}

	if stop := initLogrus(); stop != nil {
		defer stop()
	}

	logrus.Debug("GOLLUM STARTING")
	defer logrus.Debug("GOLLUM STOPPED")

	config := readConfig()
	if config == nil {
		return tos.ExitError // ### exit, config failed to parse ###
	}

	configureRuntime()

	if stop := startMetricsService(); stop != nil {
		defer stop()
	}

	if stop := startHealthCheckService(); stop != nil {
		defer stop()
	}

	if stop := startCPUProfiler(); stop != nil {
		defer stop()
	}

	if stop := startMemoryProfiler(); stop != nil {
		defer stop()
	}

	if stop := startTracer(); stop != nil {
		defer stop()
	}

	coordinator := NewCoordinator()
	defer coordinator.Shutdown()

	if err := coordinator.Configure(config); err != nil {
		logrus.WithError(err).Error("Config validation failed")
		return tos.ExitError // ### exit, config failed to parse ###
	}

	coordinator.StartPlugins()
	coordinator.Run()
	return tos.ExitSuccess
}

// initLogrus initializes the logging framework
func initLogrus() func() {
	// Initialize logger.LogrusHookBuffer
	logrusHookBuffer = logger.NewLogrusHookBuffer()

	// Initialize logging. All logging is done via logrusHookBuffer;
	// logrus's output writer is always set to ioutil.Discard.
	logrus.AddHook(&logrusHookBuffer)
	logrus.SetOutput(ioutil.Discard)
	logrus.SetLevel(getLogrusLevel(*flagLoglevel))

	switch *flagLogColors {
	case "never", "auto", "always":
	default:
		fmt.Printf("Invalid parameter for -log-colors: '%s'\n", *flagLogColors)
		*flagLogColors = "auto"
	}

	if *flagLogColors == "always" ||
		(*flagLogColors == "auto" && logrus.IsTerminal(logger.FallbackLogDevice)) {
		// Logrus doesn't know the final log device, so we hint the color option here
		logrus.SetFormatter(logger.NewConsoleFormatter())
	}

	// make sure logs are purged at exit
	return func() {
		logrusHookBuffer.SetTargetWriter(logger.FallbackLogDevice)
		logrusHookBuffer.Purge()
	}
}

// readConfig reads and checks the config file for errors.
func readConfig() *core.Config {
	configFile := *flagConfigFile
	testAndExit := false

	if *flagTestConfigFile != "" {
		configFile = *flagTestConfigFile
		testAndExit = true
	}

	if *flagHelp || configFile == "" {
		logrus.Error("Please provide a config file")
		return nil
	}

	if testAndExit {
		logrus.SetLevel(logrus.WarnLevel)
		fmt.Println("Testing config", configFile)
	}

	config, err := core.ReadConfigFromFile(configFile)
	if err != nil {
		logrus.WithError(err).Error("Failed to read config")
		return nil
	}

	if err := config.Validate(); err != nil {
		logrus.WithError(err).Error("Config validation failed")
		return nil
	}

	if testAndExit {
		coordinator := NewCoordinator()
		if err := coordinator.Configure(config); err != nil {
			logrus.WithError(err).Error("Configure pass failed.")
		} else {
			fmt.Println("Config OK.")
		}
		coordinator.Shutdown()
		return nil
	}

	return config
}

// configureRuntime does various different settings that affect runtime
// behavior or enables global functionality
func configureRuntime() {
	if *flagPidFile != "" {
		ioutil.WriteFile(*flagPidFile, []byte(strconv.Itoa(os.Getpid())), 0644)
	}

	if *flagNumCPU == 0 {
		runtime.GOMAXPROCS(runtime.NumCPU())
	} else {
		runtime.GOMAXPROCS(*flagNumCPU)
	}

	if *flagProfile {
		time.AfterFunc(time.Second*3, printProfile)
	}

	if *flagTrace {
		core.ActivateMessageTrace()
	}
}

// startMetricsService creates a metric endpoint if requested.
// The returned function should be deferred if not nil.
func startMetricsService() func() {
	if *flagMetricsAddress == "" {
		return nil
	}

	server := tgo.NewMetricServer()
	address, err := parseAddress(*flagMetricsAddress)
	if err != nil {
		logrus.WithError(err).Error("Failed to start metrics service")
		return nil
	}

	logrus.WithField("address", address).Info("Starting metric service")
	go server.Start(address)
	return server.Stop
}

// startHealthCheckService creates a health check endpoint if requested.
// The returned function should be deferred if not nil.
func startHealthCheckService() func() {
	if *flagHealthCheck == "" {
		return nil
	}
	address, err := parseAddress(*flagHealthCheck)
	if err != nil {
		logrus.WithError(err).Error("Failed to start health check service")
		return nil
	}
	thealthcheck.Configure(address)

	logrus.WithField("address", address).Info("Starting health check service")
	go thealthcheck.Start()

	// Add a static "ping" endpoint
	thealthcheck.AddEndpoint("/_PING_", func() (code int, body string) {
		return thealthcheck.StatusOK, "PONG"
	})
	return thealthcheck.Stop
}

// startCPUProfiler enables the golang CPU profiling process.
// The resulting file can be viewed with `go tool pprof ./gollum file`.
// The returned function should be deferred if not nil.
func startCPUProfiler() func() {
	if *flagCPUProfile == "" {
		return nil
	}

	file, err := os.Create(*flagCPUProfile)
	if err != nil {
		logrus.WithError(err).Error("Failed to create profiling results file")
		return nil
	}

	if err := pprof.StartCPUProfile(file); err != nil {
		file.Close()
		logrus.WithError(err).Error("Failed to start CPU profler")
		return nil
	}

	logrus.WithField("file", *flagCPUProfile).Info("Started CPU profiling")

	return func() {
		pprof.StopCPUProfile()
		file.Close()
	}
}

// startMemoryProfile enables the golang heap profiling process.
// The returned function should be deferred if not nil.
func startMemoryProfiler() func() {
	if *flagMemProfile == "" {
		return nil
	}

	return func() {
		file, err := os.Create(*flagMemProfile)
		if err != nil {
			logrus.WithError(err).Error("Failed to create memory profile results file")
			return
		}
		defer file.Close()

		logrus.WithField("file", *flagMemProfile).Info("Dumping memory profile")
		if err := pprof.WriteHeapProfile(file); err != nil {
			logrus.WithError(err).Error("Failed to write heap profile")
		}
	}
}

// startTracer enables the golang tracing process.
// The resulting file can be viewed with `go tool trace -http=':3333' file` or
// converted to pprof with `go tool trace -pprof=TYPE trace.out > TYPE.pprof`
// where TYPE can be net, sync, syscall or sched.
// The returned function should be deferred if not nil.
func startTracer() func() {
	if *flagProfileTrace == "" {
		return nil
	}

	file, err := os.Create(*flagProfileTrace)
	if err != nil {
		logrus.WithError(err).Error("Failed to create tracing results file")
		return nil
	}

	if err := trace.Start(file); err != nil {
		file.Close()
		logrus.WithError(err).Error("Failed to start tracer")
		return nil
	}

	return func() {
		trace.Stop()
		file.Close()
	}
}

func parseAddress(address string) (string, error) {
	_, host, port, err := tnet.SplitAddress(address, "")
	if err != nil {
		return address, fmt.Errorf("Incorrect address %q: %s", address, err)
	}

	return host + ":" + port, nil
}

func printVersion() {
	fmt.Println(core.GetVersionString())
}

func printExtVersion() {
	fmt.Printf("%6s: %s\n", "Gollum", core.GetVersionString()[1:])
	fmt.Printf("%6s: %s\n", "Go", runtime.Version()[2:])
	fmt.Printf("%6s: %s\n", "Arch", runtime.GOARCH)
}

func printModules() {
	namespaces := []string{"consumer", "producer", "filter", "format", "router", "contrib"}
	allMods := []string{}
	for _, pkg := range namespaces {
		modules := core.TypeRegistry.GetRegistered(pkg)
		for _, typeName := range modules {
			allMods = append(allMods, typeName)
		}
	}

	sort.Strings(allMods)
	lastCategory := ""

	for _, name := range allMods {
		pkgIdx := strings.LastIndex(name, ".")
		category := name[:pkgIdx]

		if category != lastCategory {
			fmt.Printf("\n-- %s\n", category)
		}

		fmt.Println(name)
		lastCategory = category
	}
}

func printProfile() {
	msgSec, err := tgo.Metric.Get(core.MetricMessagesRoutedAvg)
	if err == nil {
		fmt.Printf("Processed %d msg/sec\n", msgSec)
	}
	time.AfterFunc(time.Second*3, printProfile)
}
<commit_msg>fixed exit code fur successfully config tests<commit_after>// Copyright 2015-2016 trivago GmbH
//
// 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"
	"github.com/sirupsen/logrus"
	_ "github.com/trivago/gollum/consumer"
	"github.com/trivago/gollum/core"
	_ "github.com/trivago/gollum/filter"
	_ "github.com/trivago/gollum/format"
	"github.com/trivago/gollum/logger"
	_ "github.com/trivago/gollum/producer"
	_ "github.com/trivago/gollum/router"
	"github.com/trivago/tgo"
	"github.com/trivago/tgo/thealthcheck"
	"github.com/trivago/tgo/tnet"
	"github.com/trivago/tgo/tos"
	"io/ioutil"
	"os"
	"runtime"
	"runtime/pprof"
	"runtime/trace"
	"sort"
	"strconv"
	"strings"
	"time"
)

// logrusHookBuffer is our single instance of LogrusHookBuffer
var logrusHookBuffer logger.LogrusHookBuffer

func main() {
	exitCode := mainWithExitCode()
	os.Exit(exitCode)
}

func mainWithExitCode() int {
	parseFlags()

	if *flagHelp || len(os.Args) == 1 {
		printFlags()
		return tos.ExitSuccess // ### return, help screen ###
	}

	if *flagExtVersion {
		printExtVersion()
		return tos.ExitSuccess // ### return, version only ###
	}

	if *flagVersion {
		printVersion()
		return tos.ExitSuccess // ### return, version only ###
	}

	if *flagModules {
		printModules()
		return tos.ExitSuccess // ### return, modules only ###
	}

	if stop := initLogrus(); stop != nil {
		defer stop()
	}

	logrus.Debug("GOLLUM STARTING")
	defer logrus.Debug("GOLLUM STOPPED")

	configFile := *flagConfigFile
	if *flagTestConfigFile != "" {
		configFile = *flagTestConfigFile
	}

	config := readConfig(configFile)
	if config == nil {
		return tos.ExitError // ### exit, config failed to parse ###
	}

	if *flagTestConfigFile != "" {
		logrus.SetLevel(logrus.WarnLevel)
		fmt.Println("Testing config", configFile)

		if !testConfig(config) {
			return tos.ExitError // ### exit, config test failed ###
		}

		fmt.Println("Config OK.")
		return tos.ExitSuccess // ### exit, config test successfully ###
	}

	configureRuntime()

	if stop := startMetricsService(); stop != nil {
		defer stop()
	}

	if stop := startHealthCheckService(); stop != nil {
		defer stop()
	}

	if stop := startCPUProfiler(); stop != nil {
		defer stop()
	}

	if stop := startMemoryProfiler(); stop != nil {
		defer stop()
	}

	if stop := startTracer(); stop != nil {
		defer stop()
	}

	coordinator := NewCoordinator()
	defer coordinator.Shutdown()

	if err := coordinator.Configure(config); err != nil {
		logrus.WithError(err).Error("Config validation failed")
		return tos.ExitError // ### exit, config failed to parse ###
	}

	coordinator.StartPlugins()
	coordinator.Run()
	return tos.ExitSuccess
}

// testConfig test and validate config object
func testConfig(config *core.Config) bool {
	coordinator := NewCoordinator()
	defer coordinator.Shutdown()

	if err := coordinator.Configure(config); err != nil {
		logrus.WithError(err).Error("Configure pass failed.")
		return false
	}

	return true
}

// initLogrus initializes the logging framework
func initLogrus() func() {
	// Initialize logger.LogrusHookBuffer
	logrusHookBuffer = logger.NewLogrusHookBuffer()

	// Initialize logging. All logging is done via logrusHookBuffer;
	// logrus's output writer is always set to ioutil.Discard.
	logrus.AddHook(&logrusHookBuffer)
	logrus.SetOutput(ioutil.Discard)
	logrus.SetLevel(getLogrusLevel(*flagLoglevel))

	switch *flagLogColors {
	case "never", "auto", "always":
	default:
		fmt.Printf("Invalid parameter for -log-colors: '%s'\n", *flagLogColors)
		*flagLogColors = "auto"
	}

	if *flagLogColors == "always" ||
		(*flagLogColors == "auto" && logrus.IsTerminal(logger.FallbackLogDevice)) {
		// Logrus doesn't know the final log device, so we hint the color option here
		logrus.SetFormatter(logger.NewConsoleFormatter())
	}

	// make sure logs are purged at exit
	return func() {
		logrusHookBuffer.SetTargetWriter(logger.FallbackLogDevice)
		logrusHookBuffer.Purge()
	}
}

// readConfig reads and checks the config file for errors.
func readConfig(configFile string) *core.Config {
	if *flagHelp || configFile == "" {
		logrus.Error("Please provide a config file")
		return nil
	}

	config, err := core.ReadConfigFromFile(configFile)
	if err != nil {
		logrus.WithError(err).Error("Failed to read config")
		return nil
	}

	if err := config.Validate(); err != nil {
		logrus.WithError(err).Error("Config validation failed")
		return nil
	}

	return config
}

// configureRuntime does various different settings that affect runtime
// behavior or enables global functionality
func configureRuntime() {
	if *flagPidFile != "" {
		ioutil.WriteFile(*flagPidFile, []byte(strconv.Itoa(os.Getpid())), 0644)
	}

	if *flagNumCPU == 0 {
		runtime.GOMAXPROCS(runtime.NumCPU())
	} else {
		runtime.GOMAXPROCS(*flagNumCPU)
	}

	if *flagProfile {
		time.AfterFunc(time.Second*3, printProfile)
	}

	if *flagTrace {
		core.ActivateMessageTrace()
	}
}

// startMetricsService creates a metric endpoint if requested.
// The returned function should be deferred if not nil.
func startMetricsService() func() {
	if *flagMetricsAddress == "" {
		return nil
	}

	server := tgo.NewMetricServer()
	address, err := parseAddress(*flagMetricsAddress)
	if err != nil {
		logrus.WithError(err).Error("Failed to start metrics service")
		return nil
	}

	logrus.WithField("address", address).Info("Starting metric service")
	go server.Start(address)
	return server.Stop
}

// startHealthCheckService creates a health check endpoint if requested.
// The returned function should be deferred if not nil.
func startHealthCheckService() func() {
	if *flagHealthCheck == "" {
		return nil
	}
	address, err := parseAddress(*flagHealthCheck)
	if err != nil {
		logrus.WithError(err).Error("Failed to start health check service")
		return nil
	}
	thealthcheck.Configure(address)

	logrus.WithField("address", address).Info("Starting health check service")
	go thealthcheck.Start()

	// Add a static "ping" endpoint
	thealthcheck.AddEndpoint("/_PING_", func() (code int, body string) {
		return thealthcheck.StatusOK, "PONG"
	})
	return thealthcheck.Stop
}

// startCPUProfiler enables the golang CPU profiling process.
// The resulting file can be viewed with `go tool pprof ./gollum file`.
// The returned function should be deferred if not nil.
func startCPUProfiler() func() {
	if *flagCPUProfile == "" {
		return nil
	}

	file, err := os.Create(*flagCPUProfile)
	if err != nil {
		logrus.WithError(err).Error("Failed to create profiling results file")
		return nil
	}

	if err := pprof.StartCPUProfile(file); err != nil {
		file.Close()
		logrus.WithError(err).Error("Failed to start CPU profler")
		return nil
	}

	logrus.WithField("file", *flagCPUProfile).Info("Started CPU profiling")

	return func() {
		pprof.StopCPUProfile()
		file.Close()
	}
}

// startMemoryProfile enables the golang heap profiling process.
// The returned function should be deferred if not nil.
func startMemoryProfiler() func() {
	if *flagMemProfile == "" {
		return nil
	}

	return func() {
		file, err := os.Create(*flagMemProfile)
		if err != nil {
			logrus.WithError(err).Error("Failed to create memory profile results file")
			return
		}
		defer file.Close()

		logrus.WithField("file", *flagMemProfile).Info("Dumping memory profile")
		if err := pprof.WriteHeapProfile(file); err != nil {
			logrus.WithError(err).Error("Failed to write heap profile")
		}
	}
}

// startTracer enables the golang tracing process.
// The resulting file can be viewed with `go tool trace -http=':3333' file` or
// converted to pprof with `go tool trace -pprof=TYPE trace.out > TYPE.pprof`
// where TYPE can be net, sync, syscall or sched.
// The returned function should be deferred if not nil.
func startTracer() func() {
	if *flagProfileTrace == "" {
		return nil
	}

	file, err := os.Create(*flagProfileTrace)
	if err != nil {
		logrus.WithError(err).Error("Failed to create tracing results file")
		return nil
	}

	if err := trace.Start(file); err != nil {
		file.Close()
		logrus.WithError(err).Error("Failed to start tracer")
		return nil
	}

	return func() {
		trace.Stop()
		file.Close()
	}
}

func parseAddress(address string) (string, error) {
	_, host, port, err := tnet.SplitAddress(address, "")
	if err != nil {
		return address, fmt.Errorf("Incorrect address %q: %s", address, err)
	}

	return host + ":" + port, nil
}

func printVersion() {
	fmt.Println(core.GetVersionString())
}

func printExtVersion() {
	fmt.Printf("%6s: %s\n", "Gollum", core.GetVersionString()[1:])
	fmt.Printf("%6s: %s\n", "Go", runtime.Version()[2:])
	fmt.Printf("%6s: %s\n", "Arch", runtime.GOARCH)
}

func printModules() {
	namespaces := []string{"consumer", "producer", "filter", "format", "router", "contrib"}
	allMods := []string{}
	for _, pkg := range namespaces {
		modules := core.TypeRegistry.GetRegistered(pkg)
		for _, typeName := range modules {
			allMods = append(allMods, typeName)
		}
	}

	sort.Strings(allMods)
	lastCategory := ""

	for _, name := range allMods {
		pkgIdx := strings.LastIndex(name, ".")
		category := name[:pkgIdx]

		if category != lastCategory {
			fmt.Printf("\n-- %s\n", category)
		}

		fmt.Println(name)
		lastCategory = category
	}
}

func printProfile() {
	msgSec, err := tgo.Metric.Get(core.MetricMessagesRoutedAvg)
	if err == nil {
		fmt.Printf("Processed %d msg/sec\n", msgSec)
	}
	time.AfterFunc(time.Second*3, printProfile)
}
<|endoftext|>