<commit_before>package main

import (
	"errors"
	"fmt"
	"net/http"
	"os"
	"time"
)

const (
	waitTime  = 3 * time.Second
	execDigit = 5
)

func main() {
	c := new(http.Client)
	c.CheckRedirect = func(req *http.Request, via []*http.Request) error { return errors.New("not redirect") }

	runes := []rune{'0'}

	for {
		var uri string
		for _, v := range runes {
			uri += string(v)
		}

		resp, err := c.Get("http://git.io/" + uri)
		if err == nil {
			resp.Body.Close()
		}

		if resp.StatusCode == 404 {
			fmt.Println(uri + " OK!")
		} else {
			fmt.Fprintln(os.Stderr, uri+" NG")
			fmt.Fprintln(os.Stderr, "Location:", resp.Header.Get("Location"))
		}

		if runes[len(runes)-1] == 'Z' {
			// carry over
			addFlg := true
			for i := len(runes) - 1; i > -1; i-- {
				beforeRune := runes[i]
				runes[i] = getNextRune(runes[i])
				if beforeRune != 'Z' {
					addFlg = false
					break
				}
			}

			if addFlg {
				runes = append(runes, '0')
			}
		} else {
			runes[len(runes)-1] = getNextRune(runes[len(runes)-1])
		}

		if len(runes) > execDigit {
			// exit
			break
		}

		time.Sleep(waitTime)
	}
}

func getNextRune(r rune) rune {
	// 0 -> 9, a -> z, A -> Z
	if r == '9' {
		return 'a'
	} else if r == 'z' {
		return 'A'
	} else if r == 'Z' {
		return '0'
	}

	r++
	return r
}
<commit_msg>fix url<commit_after>package main

import (
	"errors"
	"fmt"
	"net/http"
	"os"
	"time"
)

const (
	waitTime  = 3 * time.Second
	execDigit = 5
)

func main() {
	c := new(http.Client)
	c.CheckRedirect = func(req *http.Request, via []*http.Request) error { return errors.New("not redirect") }

	runes := []rune{'0'}

	for {
		var uri string
		for _, v := range runes {
			uri += string(v)
		}

		resp, err := c.Get("https://git.io/" + uri)
		if err == nil {
			resp.Body.Close()
		}

		if resp.StatusCode == 404 {
			fmt.Println(uri + " OK!")
		} else {
			fmt.Fprintln(os.Stderr, uri+" NG")
			fmt.Fprintln(os.Stderr, "Location:", resp.Header.Get("Location"))
		}

		if runes[len(runes)-1] == 'Z' {
			// carry over
			addFlg := true
			for i := len(runes) - 1; i > -1; i-- {
				beforeRune := runes[i]
				runes[i] = getNextRune(runes[i])
				if beforeRune != 'Z' {
					addFlg = false
					break
				}
			}

			if addFlg {
				runes = append(runes, '0')
			}
		} else {
			runes[len(runes)-1] = getNextRune(runes[len(runes)-1])
		}

		if len(runes) > execDigit {
			// exit
			break
		}

		time.Sleep(waitTime)
	}
}

func getNextRune(r rune) rune {
	// 0 -> 9, a -> z, A -> Z
	if r == '9' {
		return 'a'
	} else if r == 'z' {
		return 'A'
	} else if r == 'Z' {
		return '0'
	}

	r++
	return r
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"errors"
	"flag"
	"fmt"
	"github.com/gorilla/mux"
	"github.com/kylelemons/go-gypsy/yaml"
	_ "github.com/mattn/go-sqlite3"
	"github.com/shawnps/gr"
	"github.com/shawnps/rt"
	"github.com/shawnps/sp"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"sync"
	"time"
)

var (
	port       = flag.String("p", "8000", "Port number (default 8000)")
	configFile = flag.String("c", "config.yml", "Config file (default config.yml)")
)

func parseYAML() (rtKey, grKey, grSecret string, err error) {
	config, err := yaml.ReadFile(*configFile)
	if err != nil {
		return
	}
	rtKey, err = config.Get("rt")
	if err != nil {
		return
	}
	grKey, err = config.Get("gr.key")
	if err != nil {
		return
	}
	grSecret, err = config.Get("gr.secret")
	if err != nil {
		return
	}

	return rtKey, grKey, grSecret, nil
}

func createDb() error {
	db, err := sql.Open("sqlite3", "./watchreadlisten.db")
	if err != nil {
		return errors.New("Error opening or creating watchreadlisten.db: " + err.Error())
	}
	defer db.Close()
	sql := `create table if not exists entries (id integer not null primary key autoincrement, title text, link text, media_type text, timestamp datetime default current_timestamp);`
	_, err = db.Exec(sql)
	if err != nil {
		return errors.New("Error creating entries table: " + err.Error())
	}
	return nil
}

func insertEntry(db sql.DB, title, link, mediaType string) error {
	tx, err := db.Begin()
	if err != nil {
		return errors.New("Error connecting to database: " + err.Error())
	}
	stmt, err := tx.Prepare("insert into entries(title, link, media_type) values(?, ?, ?)")
	if err != nil {
		return errors.New("Error inserting to database: " + err.Error())
	}
	defer stmt.Close()
	_, err = stmt.Exec(title, link, mediaType)
	if err != nil {
		return errors.New("Error executing statement on database: " + err.Error())
	}
	tx.Commit()
	return nil
}

func truncate(s, suf string, l int) string {
	if len(s) < l {
		return s
	} else {
		return s[:l] + suf
	}
}

// Search Rotten Tomatoes, Goodreads, and Spotify.
func Search(q string, rtClient rt.RottenTomatoes, grClient gr.Goodreads, spClient sp.Spotify) (m []rt.Movie, g gr.GoodreadsResponse, s sp.SearchAlbumsResponse) {
	var wg sync.WaitGroup
	wg.Add(3)
	go func(q string) {
		defer wg.Done()
		movies, err := rtClient.SearchMovies(q)
		if err != nil {
			fmt.Println("ERROR (rt): ", err.Error())
		}
		for _, mov := range movies {
			mov.Title = truncate(mov.Title, "...", 60)
			m = append(m, mov)
		}
	}(q)
	go func(q string) {
		defer wg.Done()
		books, err := grClient.SearchBooks(q)
		if err != nil {
			fmt.Println("ERROR (gr): ", err.Error())
		}
		for i, w := range books.Search.Works {
			w.BestBook.Title = truncate(w.BestBook.Title, "...", 60)
			books.Search.Works[i] = w
		}
		g = books
	}(q)
	go func(q string) {
		defer wg.Done()
		albums, err := spClient.SearchAlbums(q)
		if err != nil {
			fmt.Println("ERROR (sp): ", err.Error())
		}
		for i, a := range albums.Albums {
			a.Name = truncate(a.Name, "...", 60)
			albums.Albums[i] = a
		}
		s = albums
	}(q)
	wg.Wait()
	return m, g, s
}

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	t, err := template.New("index.html").ParseFiles("templates/index.html", "templates/base.html")
	if err != nil {
		log.Panic(err)
	}
	// Render the template
	err = t.ExecuteTemplate(w, "base", map[string]interface{}{"Page": "home"})
	if err != nil {
		log.Panic(err)
	}
}

func SearchHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	q := vars["query"]
	q, err := url.QueryUnescape(q)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	rtKey, grKey, grSecret, err := parseYAML()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	rtClient := rt.RottenTomatoes{rtKey}
	grClient := gr.Goodreads{grKey, grSecret}
	spClient := sp.Spotify{}
	m, g, s := Search(q, rtClient, grClient, spClient)
	// Since spotify: URIs are not trusted, have to pass a
	// URL function to the template to use in hrefs
	funcMap := template.FuncMap{
		"URL": func(q string) template.URL { return template.URL(q) },
	}
	t, err := template.New("search.html").Funcs(funcMap).ParseFiles("templates/search.html", "templates/base.html")
	if err != nil {
		log.Panic(err)
	}
	// Render the template
	err = t.ExecuteTemplate(w, "base", map[string]interface{}{"Movies": m, "Books": g, "Albums": s.Albums})
	if err != nil {
		log.Panic(err)
	}
}

func SaveHandler(w http.ResponseWriter, r *http.Request) {
	db, err := sql.Open("sqlite3", "./watchreadlisten.db")
	if err != nil {
		log.Println("Error opening sqlite db: " + err.Error())
		return
	}
	defer db.Close()
	t := r.FormValue("title")
	l := r.FormValue("link")
	m := r.FormValue("media_type")
	err = insertEntry(*db, t, l, m)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/", http.StatusFound)
}

func ListHandler(w http.ResponseWriter, r *http.Request) {
	db, err := sql.Open("sqlite3", "./watchreadlisten.db")
	if err != nil {
		log.Println("Error opening sqlite db: " + err.Error())
		return
	}
	defer db.Close()
	q := "select id, title, link, media_type, timestamp from entries"
	rows, err := db.Query(q)
	if err != nil {
		log.Println("Error querying sqlite db: " + err.Error())
		return
	}
	type Entry struct {
		Id        int
		Title     string
		Link      string
		MediaType string `db:"media_type"`
		Timestamp time.Time
	}
	entries := map[string][]Entry{}
	defer rows.Close()
	for rows.Next() {
		e := Entry{}
		var id int
		var title, link, mediaType string
		var timestamp time.Time
		rows.Scan(&id, &title, &link, &mediaType, &timestamp)
		e.Id = id
		e.Title = title
		e.Link = link
		e.MediaType = mediaType
		e.Timestamp = timestamp
		entries[mediaType] = append(entries[mediaType], e)
	}
	// Create and parse Template
	t, err := template.New("list.html").ParseFiles("templates/list.html", "templates/base.html")
	if err != nil {
		log.Panic(err)
	}
	// Render the template
	t.ExecuteTemplate(w, "base", map[string]interface{}{"Entries": entries, "Page": "list"})
}

func main() {
	err := createDb()
	if err != nil {
		log.Fatal(err)
	}
	r := mux.NewRouter()
	r.HandleFunc("/", HomeHandler)
	r.HandleFunc("/search/{query}", SearchHandler)
	r.HandleFunc("/save", SaveHandler)
	r.HandleFunc("/list", ListHandler)
	http.Handle("/", r)
	fmt.Println("Running on localhost:" + *port)

	log.Fatal(http.ListenAndServe(":"+*port, nil))
}
<commit_msg>redirect to /list after saving<commit_after>package main

import (
	"database/sql"
	"errors"
	"flag"
	"fmt"
	"github.com/gorilla/mux"
	"github.com/kylelemons/go-gypsy/yaml"
	_ "github.com/mattn/go-sqlite3"
	"github.com/shawnps/gr"
	"github.com/shawnps/rt"
	"github.com/shawnps/sp"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"sync"
	"time"
)

var (
	port       = flag.String("p", "8000", "Port number (default 8000)")
	configFile = flag.String("c", "config.yml", "Config file (default config.yml)")
)

func parseYAML() (rtKey, grKey, grSecret string, err error) {
	config, err := yaml.ReadFile(*configFile)
	if err != nil {
		return
	}
	rtKey, err = config.Get("rt")
	if err != nil {
		return
	}
	grKey, err = config.Get("gr.key")
	if err != nil {
		return
	}
	grSecret, err = config.Get("gr.secret")
	if err != nil {
		return
	}

	return rtKey, grKey, grSecret, nil
}

func createDb() error {
	db, err := sql.Open("sqlite3", "./watchreadlisten.db")
	if err != nil {
		return errors.New("Error opening or creating watchreadlisten.db: " + err.Error())
	}
	defer db.Close()
	sql := `create table if not exists entries (id integer not null primary key autoincrement, title text, link text, media_type text, timestamp datetime default current_timestamp);`
	_, err = db.Exec(sql)
	if err != nil {
		return errors.New("Error creating entries table: " + err.Error())
	}
	return nil
}

func insertEntry(db sql.DB, title, link, mediaType string) error {
	tx, err := db.Begin()
	if err != nil {
		return errors.New("Error connecting to database: " + err.Error())
	}
	stmt, err := tx.Prepare("insert into entries(title, link, media_type) values(?, ?, ?)")
	if err != nil {
		return errors.New("Error inserting to database: " + err.Error())
	}
	defer stmt.Close()
	_, err = stmt.Exec(title, link, mediaType)
	if err != nil {
		return errors.New("Error executing statement on database: " + err.Error())
	}
	tx.Commit()
	return nil
}

func truncate(s, suf string, l int) string {
	if len(s) < l {
		return s
	} else {
		return s[:l] + suf
	}
}

// Search Rotten Tomatoes, Goodreads, and Spotify.
func Search(q string, rtClient rt.RottenTomatoes, grClient gr.Goodreads, spClient sp.Spotify) (m []rt.Movie, g gr.GoodreadsResponse, s sp.SearchAlbumsResponse) {
	var wg sync.WaitGroup
	wg.Add(3)
	go func(q string) {
		defer wg.Done()
		movies, err := rtClient.SearchMovies(q)
		if err != nil {
			fmt.Println("ERROR (rt): ", err.Error())
		}
		for _, mov := range movies {
			mov.Title = truncate(mov.Title, "...", 60)
			m = append(m, mov)
		}
	}(q)
	go func(q string) {
		defer wg.Done()
		books, err := grClient.SearchBooks(q)
		if err != nil {
			fmt.Println("ERROR (gr): ", err.Error())
		}
		for i, w := range books.Search.Works {
			w.BestBook.Title = truncate(w.BestBook.Title, "...", 60)
			books.Search.Works[i] = w
		}
		g = books
	}(q)
	go func(q string) {
		defer wg.Done()
		albums, err := spClient.SearchAlbums(q)
		if err != nil {
			fmt.Println("ERROR (sp): ", err.Error())
		}
		for i, a := range albums.Albums {
			a.Name = truncate(a.Name, "...", 60)
			albums.Albums[i] = a
		}
		s = albums
	}(q)
	wg.Wait()
	return m, g, s
}

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	t, err := template.New("index.html").ParseFiles("templates/index.html", "templates/base.html")
	if err != nil {
		log.Panic(err)
	}
	// Render the template
	err = t.ExecuteTemplate(w, "base", map[string]interface{}{"Page": "home"})
	if err != nil {
		log.Panic(err)
	}
}

func SearchHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	q := vars["query"]
	q, err := url.QueryUnescape(q)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	rtKey, grKey, grSecret, err := parseYAML()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	rtClient := rt.RottenTomatoes{rtKey}
	grClient := gr.Goodreads{grKey, grSecret}
	spClient := sp.Spotify{}
	m, g, s := Search(q, rtClient, grClient, spClient)
	// Since spotify: URIs are not trusted, have to pass a
	// URL function to the template to use in hrefs
	funcMap := template.FuncMap{
		"URL": func(q string) template.URL { return template.URL(q) },
	}
	t, err := template.New("search.html").Funcs(funcMap).ParseFiles("templates/search.html", "templates/base.html")
	if err != nil {
		log.Panic(err)
	}
	// Render the template
	err = t.ExecuteTemplate(w, "base", map[string]interface{}{"Movies": m, "Books": g, "Albums": s.Albums})
	if err != nil {
		log.Panic(err)
	}
}

func SaveHandler(w http.ResponseWriter, r *http.Request) {
	db, err := sql.Open("sqlite3", "./watchreadlisten.db")
	if err != nil {
		log.Println("Error opening sqlite db: " + err.Error())
		return
	}
	defer db.Close()
	t := r.FormValue("title")
	l := r.FormValue("link")
	m := r.FormValue("media_type")
	err = insertEntry(*db, t, l, m)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/list", http.StatusFound)
}

func ListHandler(w http.ResponseWriter, r *http.Request) {
	db, err := sql.Open("sqlite3", "./watchreadlisten.db")
	if err != nil {
		log.Println("Error opening sqlite db: " + err.Error())
		return
	}
	defer db.Close()
	q := "select id, title, link, media_type, timestamp from entries"
	rows, err := db.Query(q)
	if err != nil {
		log.Println("Error querying sqlite db: " + err.Error())
		return
	}
	type Entry struct {
		Id        int
		Title     string
		Link      string
		MediaType string `db:"media_type"`
		Timestamp time.Time
	}
	entries := map[string][]Entry{}
	defer rows.Close()
	for rows.Next() {
		e := Entry{}
		var id int
		var title, link, mediaType string
		var timestamp time.Time
		rows.Scan(&id, &title, &link, &mediaType, &timestamp)
		e.Id = id
		e.Title = title
		e.Link = link
		e.MediaType = mediaType
		e.Timestamp = timestamp
		entries[mediaType] = append(entries[mediaType], e)
	}
	// Create and parse Template
	t, err := template.New("list.html").ParseFiles("templates/list.html", "templates/base.html")
	if err != nil {
		log.Panic(err)
	}
	// Render the template
	t.ExecuteTemplate(w, "base", map[string]interface{}{"Entries": entries, "Page": "list"})
}

func main() {
	err := createDb()
	if err != nil {
		log.Fatal(err)
	}
	r := mux.NewRouter()
	r.HandleFunc("/", HomeHandler)
	r.HandleFunc("/search/{query}", SearchHandler)
	r.HandleFunc("/save", SaveHandler)
	r.HandleFunc("/list", ListHandler)
	http.Handle("/", r)
	fmt.Println("Running on localhost:" + *port)

	log.Fatal(http.ListenAndServe(":"+*port, nil))
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"reflect"
)

type Config map[string]interface{}

type Response struct {
	Success  bool
	Errors   []string
	Messages []string
	Result   []ResponseSetting
}

type ResponseSetting struct {
	ID         string
	Value      interface{}
	ModifiedOn string `json:"modified_on"`
	Editable   bool
}

const rootURL = "https://api.cloudflare.com/v4"

var (
	httpClient = &http.Client{}
	authEmail  = flag.String("email", "", "Authentication email address")
	authKey    = flag.String("key", "", "Authentication key")
	zoneID     = flag.String("zone", "", "Zone ID")
)

func main() {
	var (
		configFile = flag.String("file", "cloudflare_zone.json", "Config file")
		download   = flag.Bool("download", false, "Download configuration")
	)

	flag.Parse()

	settings := getSettings()
	config := convertToConfig(settings)

	if *download {
		log.Println("Saving configuration..")
		writeConfig(config, *configFile)
	} else {
		log.Println("Comparing and updating configuration..")
		configDesired := readConfig(*configFile)
		compareConfig(config, configDesired)
	}
}

func getSettings() []ResponseSetting {
	url := fmt.Sprintf("%s/zones/%s/settings", rootURL, *zoneID)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatalln("Constructing request failed:", err)
	}

	resp := makeRequest(req)
	return resp.Result
}

func makeRequest(req *http.Request) Response {
	req.Header.Set("X-Auth-Email", *authEmail)
	req.Header.Set("X-Auth-Key", *authKey)

	resp, err := httpClient.Do(req)
	if err != nil {
		log.Fatalln("Request failed:", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Fatalln("Incorrect HTTP response code:", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalln("Reading response body failed:", err)
	}

	var parsedResp Response
	err = json.Unmarshal(body, &parsedResp)
	if err != nil {
		log.Fatalln("Parsing response body as JSON failed", err)
	}

	if !parsedResp.Success || len(parsedResp.Errors) > 0 {
		log.Fatalln("Response body indicated that request failed:", parsedResp)
	}

	return parsedResp
}

func convertToConfig(settings []ResponseSetting) Config {
	config := make(Config)
	for _, setting := range settings {
		config[setting.ID] = setting.Value
	}

	return config
}

func writeConfig(config Config, file string) {
	bs, err := json.MarshalIndent(config, "", "    ")
	if err != nil {
		log.Fatalln("Parsing config to JSON failed:", err)
	}

	err = ioutil.WriteFile(file, bs, 0644)
	if err != nil {
		log.Fatalln("Writing config file failed:", err)
	}
}

func readConfig(file string) Config {
	bs, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatalln("Reading config file failed:", err)
	}

	var config Config
	err = json.Unmarshal(bs, &config)
	if err != nil {
		log.Fatalln("Parsing config file as JSON failed:", err)
	}

	return config
}

func compareConfig(configActual, configDesired Config) {
	if reflect.DeepEqual(configActual, configDesired) {
		log.Println("No config changes to make")
		return
	}

	for key, valActual := range configActual {
		if valDesired, ok := configDesired[key]; ok {
			if reflect.DeepEqual(valActual, valDesired) {
				continue
			}

			log.Println("Change:", key, valActual, "->", valDesired)
		} else {
			log.Println("Not in config:", key)
		}
	}
}
<commit_msg>Log response body on non-200 response codes<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"reflect"
)

type Config map[string]interface{}

type Response struct {
	Success  bool
	Errors   []string
	Messages []string
	Result   []ResponseSetting
}

type ResponseSetting struct {
	ID         string
	Value      interface{}
	ModifiedOn string `json:"modified_on"`
	Editable   bool
}

const rootURL = "https://api.cloudflare.com/v4"

var (
	httpClient = &http.Client{}
	authEmail  = flag.String("email", "", "Authentication email address")
	authKey    = flag.String("key", "", "Authentication key")
	zoneID     = flag.String("zone", "", "Zone ID")
)

func main() {
	var (
		configFile = flag.String("file", "cloudflare_zone.json", "Config file")
		download   = flag.Bool("download", false, "Download configuration")
	)

	flag.Parse()

	settings := getSettings()
	config := convertToConfig(settings)

	if *download {
		log.Println("Saving configuration..")
		writeConfig(config, *configFile)
	} else {
		log.Println("Comparing and updating configuration..")
		configDesired := readConfig(*configFile)
		compareConfig(config, configDesired)
	}
}

func getSettings() []ResponseSetting {
	url := fmt.Sprintf("%s/zones/%s/settings", rootURL, *zoneID)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatalln("Constructing request failed:", err)
	}

	resp := makeRequest(req)
	return resp.Result
}

func makeRequest(req *http.Request) Response {
	req.Header.Set("X-Auth-Email", *authEmail)
	req.Header.Set("X-Auth-Key", *authKey)

	resp, err := httpClient.Do(req)
	if err != nil {
		log.Fatalln("Request failed:", err)
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalln("Reading response body failed:", err)
	}

	if resp.StatusCode != http.StatusOK {
		log.Fatalln("Incorrect HTTP response code", resp.StatusCode, ":", string(body))
	}

	var parsedResp Response
	err = json.Unmarshal(body, &parsedResp)
	if err != nil {
		log.Fatalln("Parsing response body as JSON failed", err)
	}

	if !parsedResp.Success || len(parsedResp.Errors) > 0 {
		log.Fatalln("Response body indicated that request failed:", parsedResp)
	}

	return parsedResp
}

func convertToConfig(settings []ResponseSetting) Config {
	config := make(Config)
	for _, setting := range settings {
		config[setting.ID] = setting.Value
	}

	return config
}

func writeConfig(config Config, file string) {
	bs, err := json.MarshalIndent(config, "", "    ")
	if err != nil {
		log.Fatalln("Parsing config to JSON failed:", err)
	}

	err = ioutil.WriteFile(file, bs, 0644)
	if err != nil {
		log.Fatalln("Writing config file failed:", err)
	}
}

func readConfig(file string) Config {
	bs, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatalln("Reading config file failed:", err)
	}

	var config Config
	err = json.Unmarshal(bs, &config)
	if err != nil {
		log.Fatalln("Parsing config file as JSON failed:", err)
	}

	return config
}

func compareConfig(configActual, configDesired Config) {
	if reflect.DeepEqual(configActual, configDesired) {
		log.Println("No config changes to make")
		return
	}

	for key, valActual := range configActual {
		if valDesired, ok := configDesired[key]; ok {
			if reflect.DeepEqual(valActual, valDesired) {
				continue
			}

			log.Println("Change:", key, valActual, "->", valDesired)
		} else {
			log.Println("Not in config:", key)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"os/exec"
	"runtime"
	"syscall"
)

const VERSION = "1.1"

func init() {
	// make sure we only have one process and that it runs on the main thread (so that ideally, when we Exec, we keep our user switches and stuff)
	runtime.GOMAXPROCS(1)
	runtime.LockOSThread()
}

func main() {
	log.SetFlags(0) // no timestamps on our logs

	if len(os.Args) <= 2 {
		log.Printf("Usage: %s user-spec command [args]", os.Args[0])
		log.Printf("   ie: %s tianon bash", os.Args[0])
		log.Printf("       %s nobody:root bash -c 'whoami && id'", os.Args[0])
		log.Printf("       %s 1000:1 id", os.Args[0])
		log.Println()
		log.Printf("%s version: %s (%s on %s/%s; %s)", os.Args[0], VERSION, runtime.Version(), runtime.GOOS, runtime.GOARCH, runtime.Compiler)
		log.Println()
		os.Exit(1)
	}

	err := SetupUser(os.Args[1])
	if err != nil {
		log.Fatalf("error: failed switching to %q: %v", os.Args[1], err)
	}

	name, err := exec.LookPath(os.Args[2])
	if err != nil {
		log.Fatalf("error: %v", err)
	}

	err = syscall.Exec(name, os.Args[2:], os.Environ())
	if err != nil {
		log.Fatalf("error: exec failed: %v", err)
	}
}
<commit_msg>Bump to version 1.2<commit_after>package main

import (
	"log"
	"os"
	"os/exec"
	"runtime"
	"syscall"
)

const VERSION = "1.2"

func init() {
	// make sure we only have one process and that it runs on the main thread (so that ideally, when we Exec, we keep our user switches and stuff)
	runtime.GOMAXPROCS(1)
	runtime.LockOSThread()
}

func main() {
	log.SetFlags(0) // no timestamps on our logs

	if len(os.Args) <= 2 {
		log.Printf("Usage: %s user-spec command [args]", os.Args[0])
		log.Printf("   ie: %s tianon bash", os.Args[0])
		log.Printf("       %s nobody:root bash -c 'whoami && id'", os.Args[0])
		log.Printf("       %s 1000:1 id", os.Args[0])
		log.Println()
		log.Printf("%s version: %s (%s on %s/%s; %s)", os.Args[0], VERSION, runtime.Version(), runtime.GOOS, runtime.GOARCH, runtime.Compiler)
		log.Println()
		os.Exit(1)
	}

	err := SetupUser(os.Args[1])
	if err != nil {
		log.Fatalf("error: failed switching to %q: %v", os.Args[1], err)
	}

	name, err := exec.LookPath(os.Args[2])
	if err != nil {
		log.Fatalf("error: %v", err)
	}

	err = syscall.Exec(name, os.Args[2:], os.Environ())
	if err != nil {
		log.Fatalf("error: exec failed: %v", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/loadimpact/speedboat/loadtest"
	"github.com/loadimpact/speedboat/runner"
	"github.com/loadimpact/speedboat/util"
	"io/ioutil"
	"os"
	"path"
)

func makeTest(c *cli.Context) (test loadtest.LoadTest, err error) {
	base := ""
	conf := loadtest.NewConfig()
	if len(c.Args()) > 0 {
		filename := c.Args()[0]
		base = path.Dir(filename)
		data, err := ioutil.ReadFile(filename)
		if err != nil {
			return test, err
		}

		loadtest.ParseConfig(data, &conf)
	}

	if c.IsSet("script") {
		conf.Script = c.String("script")
		base = ""
	}
	if c.IsSet("duration") {
		conf.Duration = c.String("duration")
	}
	if c.IsSet("vus") {
		conf.VUs = c.Int("vus")
	}

	test, err = conf.Compile()
	if err != nil {
		return test, err
	}

	if err = test.Load(base); err != nil {
		return test, err
	}

	return test, nil
}

func action(c *cli.Context) {
	test, err := makeTest(c)
	if err != nil {
		log.WithError(err).Fatal("Configuration error")
	}

	r, err := util.GetRunner(test.Script)
	if err != nil {
		log.WithError(err).Fatal("Couldn't get a runner")
	}
	log.WithField("r", r).Info("Runner")

	err = r.Load(test.Script, test.Source)
	if err != nil {
		log.WithError(err).Fatal("Couldn't load script")
	}

	controlChannel := make(chan int, 1)
	currentVUs := test.Stages[0].VUs.Start
	controlChannel <- currentVUs

	for res := range runner.Run(r, controlChannel) {
		switch res := res.(type) {
		case runner.LogEntry:
			log.WithField("text", res.Text).Info("Test Log")
		case runner.Metric:
			log.WithField("d", res.Duration).Info("Test Metric")
		case error:
			log.WithError(res).Error("Test Error")
		}
	}
}

// Configure the global logger.
func configureLogging(c *cli.Context) {
	if c.GlobalBool("verbose") {
		log.SetLevel(log.DebugLevel)
	}
}

func main() {
	// Free up -v and -h for our own flags
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help, ?"

	// Bootstrap using action-registered commandline flags
	app := cli.NewApp()
	app.Name = "speedboat"
	app.Usage = "A next-generation load generator"
	app.Version = "0.0.1a1"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "More verbose output",
		},
	}
	app.Before = func(c *cli.Context) error {
		configureLogging(c)
		return nil
	}
	app.Action = action
	app.Run(os.Args)
}

// func main() {
// 	// Free up -v and -h for our own flags
// 	cli.VersionFlag.Name = "version"
// 	cli.HelpFlag.Name = "help, ?"

// 	// Bootstrap using action-registered commandline flags
// 	app := cli.NewApp()
// 	app.Name = "speedboat"
// 	app.Usage = "A next-generation load generator"
// 	app.Version = "0.0.1a1"
// 	app.Flags = []cli.Flag{
// 		cli.BoolFlag{
// 			Name:  "verbose, v",
// 			Usage: "More verbose output",
// 		},
// 	}
// 	app.Commands = client.GlobalCommands
// 	app.Before = func(c *cli.Context) error {
// 		configureLogging(c)
// 		return nil
// 	}
// 	app.Run(os.Args)
// }
<commit_msg>I can run full tests<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/loadimpact/speedboat/loadtest"
	"github.com/loadimpact/speedboat/runner"
	"github.com/loadimpact/speedboat/util"
	"io/ioutil"
	"os"
	"path"
	"time"
)

func makeTest(c *cli.Context) (test loadtest.LoadTest, err error) {
	base := ""
	conf := loadtest.NewConfig()
	if len(c.Args()) > 0 {
		filename := c.Args()[0]
		base = path.Dir(filename)
		data, err := ioutil.ReadFile(filename)
		if err != nil {
			return test, err
		}

		loadtest.ParseConfig(data, &conf)
	}

	if c.IsSet("script") {
		conf.Script = c.String("script")
		base = ""
	}
	if c.IsSet("duration") {
		conf.Duration = c.String("duration")
	}
	if c.IsSet("vus") {
		conf.VUs = c.Int("vus")
	}

	test, err = conf.Compile()
	if err != nil {
		return test, err
	}

	if err = test.Load(base); err != nil {
		return test, err
	}

	return test, nil
}

func action(c *cli.Context) {
	test, err := makeTest(c)
	if err != nil {
		log.WithError(err).Fatal("Configuration error")
	}

	r, err := util.GetRunner(test.Script)
	if err != nil {
		log.WithError(err).Fatal("Couldn't get a runner")
	}
	log.WithField("r", r).Info("Runner")

	err = r.Load(test.Script, test.Source)
	if err != nil {
		log.WithError(err).Fatal("Couldn't load script")
	}

	controlChannel := make(chan int, 1)
	currentVUs := test.Stages[0].VUs.Start
	controlChannel <- currentVUs

	startTime := time.Now()
	intervene := time.Tick(time.Duration(1) * time.Second)
	results := runner.Run(r, controlChannel)
runLoop:
	for {
		select {
		case res := <-results:
			switch res := res.(type) {
			case runner.LogEntry:
				log.WithField("text", res.Text).Info("Test Log")
			case runner.Metric:
				log.WithField("d", res.Duration).Info("Test Metric")
			case error:
				log.WithError(res).Error("Test Error")
			}
		case <-intervene:
			vus, stop := test.VUsAt(time.Since(startTime))
			if stop {
				break runLoop
			}
			if vus != currentVUs {
				delta := vus - currentVUs
				controlChannel <- delta
				currentVUs = vus
			}
		}
	}
}

// Configure the global logger.
func configureLogging(c *cli.Context) {
	if c.GlobalBool("verbose") {
		log.SetLevel(log.DebugLevel)
	}
}

func main() {
	// Free up -v and -h for our own flags
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help, ?"

	// Bootstrap using action-registered commandline flags
	app := cli.NewApp()
	app.Name = "speedboat"
	app.Usage = "A next-generation load generator"
	app.Version = "0.0.1a1"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "More verbose output",
		},
	}
	app.Before = func(c *cli.Context) error {
		configureLogging(c)
		return nil
	}
	app.Action = action
	app.Run(os.Args)
}

// func main() {
// 	// Free up -v and -h for our own flags
// 	cli.VersionFlag.Name = "version"
// 	cli.HelpFlag.Name = "help, ?"

// 	// Bootstrap using action-registered commandline flags
// 	app := cli.NewApp()
// 	app.Name = "speedboat"
// 	app.Usage = "A next-generation load generator"
// 	app.Version = "0.0.1a1"
// 	app.Flags = []cli.Flag{
// 		cli.BoolFlag{
// 			Name:  "verbose, v",
// 			Usage: "More verbose output",
// 		},
// 	}
// 	app.Commands = client.GlobalCommands
// 	app.Before = func(c *cli.Context) error {
// 		configureLogging(c)
// 		return nil
// 	}
// 	app.Run(os.Args)
// }
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"encoding/base64"
  "io/ioutil"
	"fmt"
  "os"
  "bytes"
	"github.com/howeyc/gopass"
  "code.google.com/p/go.crypto/pbkdf2"
  "crypto/sha1"
  "crypto/md5"
  "crypto/aes"
  "crypto/cipher"
)

func clear(b []byte) {
  for i := 0; i < len(b); i++ {
    b[i] = 0;
  }
}

// Pad applies the PKCS #7 padding scheme on the buffer.
func Pad(in []byte) []byte {
	padding := 16 - (len(in) % 16)
	if padding == 0 {
		padding = 16
	}
	for i := 0; i < padding; i++ {
		in = append(in, byte(padding))
	}
	return in
}

// Unpad strips the PKCS #7 padding on a buffer. If the padding is
// invalid, nil is returned.
func Unpad(in []byte) []byte {
	if len(in) == 0 {
    fmt.Println("x1")
		return nil
	}

	padding := in[len(in)-1]
  fmt.Printf("len %s\n", len(in))
  fmt.Printf("padding %s\n", padding)
	if int(padding) > len(in) || padding > aes.BlockSize {
    fmt.Println("x2")
		return nil
	} else if padding == 0 {
    fmt.Println("x3")
		return nil
	}

	for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
		if in[i] != padding {
    fmt.Println("x4")
			return nil
		}
	}
	return in[:len(in)-int(padding)]
}

func main() {
	fmt.Printf("Password: ")
	pass := gopass.GetPasswd()
  defer clear(pass)

  type PassKey struct {
    Data string
    Identifier string
    Iterations int
    Level string
    Validation string
  }
  type Keys struct {
    List []PassKey
  }
  file, err := ioutil.ReadFile("./encryptionKeys.js")
  if err != nil {
    fmt.Println(err)
    os.Exit(1)
  }

  var keys Keys
  err = json.Unmarshal(file, &keys)
  if err != nil {
    fmt.Println(err)
    os.Exit(1)
  }

  fmt.Printf("%+v\n", keys)

  data := keys.List[1].Data

  // seems to be a null terminated string, cut off the last character
  encryptionKey, er := base64.StdEncoding.DecodeString(data[0:len(data)-1])

  if er != nil {
    fmt.Println(er)
    os.Exit(1)
  }

  saltMarker := []byte("Salted__")
  salted := (bytes.Equal([]byte(encryptionKey[0:len(saltMarker)]), saltMarker))
  fmt.Println(encryptionKey[0:30])
  fmt.Println(len(encryptionKey))

  var salt []byte
  if salted {
    // salt marker (8 bytes) | salt (8 bytes) | data
    skip := len(saltMarker) + 8
    salt = []byte(encryptionKey[len(saltMarker):skip])
    //encryptionKey = encryptionKey[skip:len(encryptionKey)-skip]
    encryptionKey = encryptionKey[skip:]
  }

  fmt.Println(encryptionKey[0:30])
  fmt.Println(len(encryptionKey))
  fmt.Println(len(salt))

  bytez := pbkdf2.Key(pass, salt, keys.List[1].Iterations, aes.BlockSize * 2, sha1.New)
  fmt.Println(len(bytez))
  fmt.Println(bytez)

  //b, e := Decrypt(bytez[:16], bytez[16:], encryptionKey)
  b, e := Decrypt(bytez, encryptionKey)
  if !e {
    fmt.Println("decrypt error")
    os.Exit(1)
  }

  fmt.Println(len(b))

  validationData := keys.List[1].Validation
  validation, er := base64.StdEncoding.DecodeString(validationData[0:len(validationData)-1])
  fmt.Println(validation[:8])
  v := decryptData(b, validation)
  fmt.Println(len(v))

  fmt.Println(bytes.Equal(b, v))

  fmt.Println("done")
}

func Decrypt(k, in []byte) ([]byte, bool) {
  if len(in) == 0 || len(in)%aes.BlockSize != 0 {
    fmt.Println("meh")
    return nil, false
  }

  c, err := aes.NewCipher(k)
  if err != nil {
    fmt.Println("meh2")
    return nil, false
  }

  cbc := cipher.NewCBCDecrypter(c, in[:aes.BlockSize])
  cbc.CryptBlocks(in[aes.BlockSize:], in[aes.BlockSize:])
  out := in[aes.BlockSize:]
  if out == nil {
    fmt.Println("meh3")
    return nil, false
  }
  return out, true

}

func decryptData(key, data []byte) []byte {
  salt := data[8:8]
  data = data[16:]
  nkey := md5.Sum(append(key, salt...))
  iv := md5.Sum(append(append(nkey[:], key...), salt...))

  result, _ := Decrypt(append(nkey[:], iv[:]...), data)
  return result
}

// https://leanpub.com/gocrypto/read#leanpub-auto-encrypting-and-decrypting-data-with-aes-cbc
// https://github.com/kisom/gocrypto/blob/master/chapter2/aescbc/aescbc.go
// Decrypt decrypts the message and removes any padding.
// func Decrypt(k, iv, in []byte) ([]byte, bool) {
// 	c, err := aes.NewCipher(k)
// 	if err != nil {
//     fmt.Println("meh2")
// 		return nil, false
// 	}

// 	cbc := cipher.NewCBCDecrypter(c, iv)
// 	cbc.CryptBlocks(in, in)
//   fmt.Println("decrypted")
//   fmt.Println(len(in))
// 	out := Unpad(in)
// 	if out == nil {
//     fmt.Println("meh3")
// 		return nil, false
// 	}
// 	return out, true

// }
<commit_msg>encryption key validation passes<commit_after>package main

import (
	"encoding/json"
	"encoding/base64"
  "io/ioutil"
	"fmt"
  "os"
  "bytes"
	"github.com/howeyc/gopass"
  "code.google.com/p/go.crypto/pbkdf2"
  "crypto/sha1"
  "crypto/md5"
  "crypto/aes"
  "crypto/cipher"
)

func clear(b []byte) {
  for i := 0; i < len(b); i++ {
    b[i] = 0;
  }
}

// Pad applies the PKCS #7 padding scheme on the buffer.
func Pad(in []byte) []byte {
	padding := 16 - (len(in) % 16)
	if padding == 0 {
		padding = 16
	}
	for i := 0; i < padding; i++ {
		in = append(in, byte(padding))
	}
	return in
}

// Unpad strips the PKCS #7 padding on a buffer. If the padding is
// invalid, nil is returned.
func Unpad(in []byte) []byte {
	if len(in) == 0 {
    fmt.Println("x1")
		return nil
	}

	padding := in[len(in)-1]
  fmt.Printf("len %s\n", len(in))
  fmt.Printf("padding %s\n", padding)
	if int(padding) > len(in) || padding > aes.BlockSize {
    fmt.Println("x2")
		return nil
	} else if padding == 0 {
    fmt.Println("x3")
		return nil
	}

	for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
		if in[i] != padding {
    fmt.Println("x4")
			return nil
		}
	}
	return in[:len(in)-int(padding)]
}

func main() {
	fmt.Printf("Password: ")
	pass := gopass.GetPasswd()
  defer clear(pass)

  type PassKey struct {
    Data string
    Identifier string
    Iterations int
    Level string
    Validation string
  }
  type Keys struct {
    List []PassKey
  }
  file, err := ioutil.ReadFile("./encryptionKeys.js")
  if err != nil {
    fmt.Println(err)
    os.Exit(1)
  }

  var keys Keys
  err = json.Unmarshal(file, &keys)
  if err != nil {
    fmt.Println(err)
    os.Exit(1)
  }

  fmt.Printf("%+v\n", keys)

  data := keys.List[1].Data

  // seems to be a null terminated string, cut off the last character
  encryptionKey, er := base64.StdEncoding.DecodeString(data[0:len(data)-1])

  if er != nil {
    fmt.Println(er)
    os.Exit(1)
  }

  saltMarker := []byte("Salted__")
  salted := (bytes.Equal([]byte(encryptionKey[0:len(saltMarker)]), saltMarker))
  //fmt.Println(encryptionKey[0:30])
  //fmt.Println(len(encryptionKey))

  var salt []byte
  if salted {
    // salt marker (8 bytes) | salt (8 bytes) | data
    skip := len(saltMarker) + 8
    salt = []byte(encryptionKey[len(saltMarker):skip])
    //encryptionKey = encryptionKey[skip:len(encryptionKey)-skip]
    encryptionKey = encryptionKey[skip:]
  }

  //fmt.Println(encryptionKey[0:30])
  //fmt.Println(len(encryptionKey))
  //fmt.Println(len(salt))

  // first 16 bytes are the key, last 16 bytes are the IV
  bytez := pbkdf2.Key(pass, salt, keys.List[1].Iterations, aes.BlockSize * 2, sha1.New)
  key := bytez[:16]
  iv := bytez[16:]

  b, e := Decryptr(key, iv, encryptionKey)
  //b, e := Decrypt(bytez, encryptionKey)
  if !e {
    fmt.Println("decrypt error")
    os.Exit(1)
  }

  fmt.Println(len(b))
  fmt.Println(b)

  validationData := keys.List[1].Validation
  validation, er := base64.StdEncoding.DecodeString(validationData[0:len(validationData)-1])
  //fmt.Println(validation[:8])
  v := decryptData(b, validation)
  fmt.Println(len(v))
  fmt.Println(v)

  fmt.Println(bytes.Equal(b, v))

  fmt.Println("done")
}

func Decrypt2(k, in []byte) ([]byte, bool) {
  if len(in) == 0 || len(in)%aes.BlockSize != 0 {
    fmt.Println("meh")
    return nil, false
  }

  c, err := aes.NewCipher(k)
  if err != nil {
    fmt.Println("meh2")
    return nil, false
  }

  cbc := cipher.NewCBCDecrypter(c, in[:aes.BlockSize])
  cbc.CryptBlocks(in[aes.BlockSize:], in[aes.BlockSize:])
  out := in[aes.BlockSize:]
  if out == nil {
    fmt.Println("meh3")
    return nil, false
  }
  return out, true

}
func Decrypt(k, in []byte) ([]byte, bool) {
  if len(in) == 0 || len(in)%aes.BlockSize != 0 {
    fmt.Println("meh")
    return nil, false
  }

  c, err := aes.NewCipher(k)
  if err != nil {
    fmt.Println("meh2")
    return nil, false
  }

  cbc := cipher.NewCBCDecrypter(c, in[:aes.BlockSize])
  cbc.CryptBlocks(in[aes.BlockSize:], in[aes.BlockSize:])
  out := in[aes.BlockSize:]
  if out == nil {
    fmt.Println("meh3")
    return nil, false
  }
  return out, true

}

func decryptData(key, data []byte) []byte {
  salt := data[8:16]
  data = data[16:]
  nkey := md5.Sum(append(key, salt...))
  iv := md5.Sum(append(append(nkey[:], key...), salt...))

  //result, _ := Decrypt(append(nkey[:], iv[:]...), data)
  result, _ := Decryptr(nkey[:], iv[:], data)
  return result
}

// https://leanpub.com/gocrypto/read#leanpub-auto-encrypting-and-decrypting-data-with-aes-cbc
// https://github.com/kisom/gocrypto/blob/master/chapter2/aescbc/aescbc.go
// Decrypt decrypts the message and removes any padding.
func Decryptr(k, iv, in []byte) ([]byte, bool) {
	c, err := aes.NewCipher(k)
	if err != nil {
    fmt.Println("meh2")
		return nil, false
	}

	cbc := cipher.NewCBCDecrypter(c, iv)
	cbc.CryptBlocks(in, in)
  fmt.Println("decrypted")
  fmt.Println(len(in))
	out := Unpad(in)
	if out == nil {
    fmt.Println("meh3")
		return nil, false
	}
	return out, true

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/GeertJohan/go.rice"
	"github.com/boltdb/bolt"
	"github.com/cswank/quimby/controllers"
	"github.com/cswank/quimby/models"
	"github.com/cswank/quimby/utils"
	"github.com/gorilla/mux"
	"gopkg.in/alecthomas/kingpin.v2"
)

const (
	version = "0.0.1"
)

var (
	app          = kingpin.New("quimby", "An interface to gogadets")
	users        = app.Command("users", "User management")
	userAdd      = users.Command("add", "Add a new user.")
	userList     = users.Command("list", "List users.")
	userEdit     = users.Command("edit", "Update a user.")
	cert         = app.Command("cert", "Make an tls cert.")
	domain       = cert.Flag("domain", "The domain for the tls cert.").Required().Short('d').String()
	pth          = cert.Flag("path", "The directory where the cert files will be written").Required().Short('p').String()
	serve        = app.Command("serve", "Start the server.")
	command      = app.Command("command", "Send a command.")
	method       = app.Command("method", "Send a method.")
	gadgets      = app.Command("gadgets", "Commands for managing gadgets")
	gadgetAdd    = gadgets.Command("add", "Add a gadget.")
	gadgetList   = gadgets.Command("list", "List the gadgets.")
	gadgetEdit   = gadgets.Command("edit", "List the gadgets.")
	gadgetDelete = gadgets.Command("delete", "Delete a gadget.")
	token        = app.Command("token", "Generate a jwt token")
	version      = app.Command("version", "Quimby version")
	bootstrap    = app.Command("bootstrap", "Set up a bunch of stuff")

	keyPath  = os.Getenv("QUIMBY_TLS_KEY")
	certPath = os.Getenv("QUIMBY_TLS_CERT")
	iface    = os.Getenv("QUIMBY_INTERFACE")
)

func main() {
	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
	case cert.FullCommand():
		utils.GenerateCert(*domain, *pth)
	case userAdd.FullCommand():
		addDB(utils.AddUser)
	case userList.FullCommand():
		addDB(utils.ListUsers)
	case userEdit.FullCommand():
		addDB(utils.EditUser)
	case gadgetAdd.FullCommand():
		addDB(utils.AddGadget)
	case gadgetList.FullCommand():
		addDB(utils.ListGadgets)
	case gadgetEdit.FullCommand():
		addDB(utils.EditGadget)
	case gadgetDelete.FullCommand():
		addDB(utils.DeleteGadget)
	case command.FullCommand():
		addDB(utils.SendCommand)
	case token.FullCommand():
		utils.GetToken()
	case version.FullCommand():
		fmt.Println("quimby", version)
	case bootstrap.FullCommand():
		utils.Bootstrap()
	case serve.FullCommand():
		addDB(startServer)
	}

}

type dbNeeder func(*bolt.DB)

func addDB(f dbNeeder) {
	pth := os.Getenv("QUIMBY_DB")
	if pth == "" {
		log.Fatal("you must specify a db location with QUIMBY_DB")
	}
	db, err := models.GetDB(pth)
	if err != nil {
		log.Fatalf("could not open db at %s - %v", pth, err)
	}
	f(db)
	defer db.Close()
}

func startServer(db *bolt.DB) {
	port := os.Getenv("QUIMBY_PORT")
	if port == "" {
		log.Fatal("you must specify a port with QUIMBY_PORT")
	}

	internalPort := os.Getenv("QUIMBY_INTERNAL_PORT")
	if port == "" {
		log.Fatal("you must specify a port with QUIMBY_INTERNAL_PORT")
	}
	lg := log.New(os.Stdout, "quimby ", log.Ltime)
	clients := models.NewClientHolder()
	start(db, port, internalPort, "/", "/api", lg, clients)
}

func start(db *bolt.DB, port, internalPort, root string, iRoot string, lg models.Logger, clients *models.ClientHolder) {
	models.Clients = clients
	models.DB = db
	models.LG = lg
	controllers.DB = db
	controllers.LG = lg

	go startInternal(iRoot, lg, internalPort)

	r := mux.NewRouter()
	r.HandleFunc("/api/login", controllers.Login).Methods("POST")
	r.HandleFunc("/api/logout", controllers.Logout).Methods("POST")
	r.HandleFunc("/api/ping", Ping).Methods("GET")
	r.HandleFunc("/api/users/current", GetUser).Methods("GET")
	r.HandleFunc("/api/gadgets", GetGadgets).Methods("GET")
	r.HandleFunc("/api/gadgets", AddGadget).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}", GetGadget).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}", SendCommand).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}", DeleteGadget).Methods("DELETE")
	r.HandleFunc("/api/gadgets/{id}/method", SendMethod).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}/websocket", Connect).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/values", GetValues).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/status", GetStatus).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/notes", AddNote).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}/notes", GetNotes).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/locations/{location}/devices/{device}/status", GetDevice).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/locations/{location}/devices/{device}/status", UpdateDevice).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}/sources/{name}", GetDataPoints).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/sources/{name}/csv", GetDataPointsCSV).Methods("GET")
	r.HandleFunc("/admin/clients", GetClients).Methods("GET")

	r.PathPrefix("/").Handler(http.FileServer(rice.MustFindBox("www/dist").HTTPBox()))

	http.Handle(root, r)

	addr := fmt.Sprintf("%s:%s", iface, port)
	lg.Printf("listening on %s\n", addr)
	if keyPath == "" {
		lg.Println(http.ListenAndServe(addr, r))
	} else {
		lg.Println(http.ListenAndServeTLS(fmt.Sprintf("%s:443", iface), certPath, keyPath, nil))
	}
}

//This is the endpoint that the gadgets report to. It is
//served on a separate port so it doesn't have to be exposed
//publicly if the main port is exposed.
func startInternal(iRoot string, lg models.Logger, port string) {
	r := mux.NewRouter()
	r.HandleFunc("/internal/updates", Relay).Methods("POST")
	r.HandleFunc("/internal/gadgets/{id}/sources/{name}", AddDataPoint).Methods("POST")
	http.Handle(iRoot, r)
	a := fmt.Sprintf(":%s", port)
	lg.Printf("listening on %s", a)
	err := http.ListenAndServe(a, r)
	if err != nil {
		log.Fatal(err)
	}
}

func Ping(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.Ping, controllers.Read)
}

func GetUser(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetUser, controllers.Read)
}

func GetGadgets(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetGadgets, controllers.Read)
}

func GadgetOptions(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GadgetOptions, controllers.Read)
}

func GetGadget(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetGadget, controllers.Read)
}

func AddGadget(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.AddGadget, controllers.Write)
}

func SendCommand(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.SendCommand, controllers.Write)
}

func SendMethod(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.SendMethod, controllers.Write)
}

func DeleteGadget(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.DeleteGadget, controllers.Write)
}

func GetStatus(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetStatus, controllers.Read)
}

func AddNote(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.AddNote, controllers.Write)
}

func GetNotes(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetNotes, controllers.Read)
}

func AddDataPoint(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.AddDataPoint, controllers.Write)
}

func GetDataPoints(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetDataPoints, controllers.Read)
}

func GetDataPointsCSV(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetDataPointsCSV, controllers.Read)
}

func GetValues(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetValues, controllers.Read)
}

func Connect(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.Connect, controllers.Read)
}

func Relay(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.RelayMessage, controllers.Write)
}
func UpdateDevice(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.UpdateDevice, controllers.Write)
}

func GetDevice(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetDevice, controllers.Write)
}

func GetClients(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetClients, controllers.Admin)
}
<commit_msg>added a version command<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/GeertJohan/go.rice"
	"github.com/boltdb/bolt"
	"github.com/cswank/quimby/controllers"
	"github.com/cswank/quimby/models"
	"github.com/cswank/quimby/utils"
	"github.com/gorilla/mux"
	"gopkg.in/alecthomas/kingpin.v2"
)

const (
	v = "0.0.1"
)

var (
	app          = kingpin.New("quimby", "An interface to gogadets")
	users        = app.Command("users", "User management")
	userAdd      = users.Command("add", "Add a new user.")
	userList     = users.Command("list", "List users.")
	userEdit     = users.Command("edit", "Update a user.")
	cert         = app.Command("cert", "Make an tls cert.")
	domain       = cert.Flag("domain", "The domain for the tls cert.").Required().Short('d').String()
	pth          = cert.Flag("path", "The directory where the cert files will be written").Required().Short('p').String()
	serve        = app.Command("serve", "Start the server.")
	command      = app.Command("command", "Send a command.")
	method       = app.Command("method", "Send a method.")
	gadgets      = app.Command("gadgets", "Commands for managing gadgets")
	gadgetAdd    = gadgets.Command("add", "Add a gadget.")
	gadgetList   = gadgets.Command("list", "List the gadgets.")
	gadgetEdit   = gadgets.Command("edit", "List the gadgets.")
	gadgetDelete = gadgets.Command("delete", "Delete a gadget.")
	token        = app.Command("token", "Generate a jwt token")
	version      = app.Command("version", "Quimby version")
	bootstrap    = app.Command("bootstrap", "Set up a bunch of stuff")

	keyPath  = os.Getenv("QUIMBY_TLS_KEY")
	certPath = os.Getenv("QUIMBY_TLS_CERT")
	iface    = os.Getenv("QUIMBY_INTERFACE")
)

func main() {
	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
	case cert.FullCommand():
		utils.GenerateCert(*domain, *pth)
	case userAdd.FullCommand():
		addDB(utils.AddUser)
	case userList.FullCommand():
		addDB(utils.ListUsers)
	case userEdit.FullCommand():
		addDB(utils.EditUser)
	case gadgetAdd.FullCommand():
		addDB(utils.AddGadget)
	case gadgetList.FullCommand():
		addDB(utils.ListGadgets)
	case gadgetEdit.FullCommand():
		addDB(utils.EditGadget)
	case gadgetDelete.FullCommand():
		addDB(utils.DeleteGadget)
	case command.FullCommand():
		addDB(utils.SendCommand)
	case token.FullCommand():
		utils.GetToken()
	case version.FullCommand():
		fmt.Println("quimby", v)
	case bootstrap.FullCommand():
		utils.Bootstrap()
	case serve.FullCommand():
		addDB(startServer)
	}

}

type dbNeeder func(*bolt.DB)

func addDB(f dbNeeder) {
	pth := os.Getenv("QUIMBY_DB")
	if pth == "" {
		log.Fatal("you must specify a db location with QUIMBY_DB")
	}
	db, err := models.GetDB(pth)
	if err != nil {
		log.Fatalf("could not open db at %s - %v", pth, err)
	}
	f(db)
	defer db.Close()
}

func startServer(db *bolt.DB) {
	port := os.Getenv("QUIMBY_PORT")
	if port == "" {
		log.Fatal("you must specify a port with QUIMBY_PORT")
	}

	internalPort := os.Getenv("QUIMBY_INTERNAL_PORT")
	if port == "" {
		log.Fatal("you must specify a port with QUIMBY_INTERNAL_PORT")
	}
	lg := log.New(os.Stdout, "quimby ", log.Ltime)
	clients := models.NewClientHolder()
	start(db, port, internalPort, "/", "/api", lg, clients)
}

func start(db *bolt.DB, port, internalPort, root string, iRoot string, lg models.Logger, clients *models.ClientHolder) {
	models.Clients = clients
	models.DB = db
	models.LG = lg
	controllers.DB = db
	controllers.LG = lg

	go startInternal(iRoot, lg, internalPort)

	r := mux.NewRouter()
	r.HandleFunc("/api/login", controllers.Login).Methods("POST")
	r.HandleFunc("/api/logout", controllers.Logout).Methods("POST")
	r.HandleFunc("/api/ping", Ping).Methods("GET")
	r.HandleFunc("/api/users/current", GetUser).Methods("GET")
	r.HandleFunc("/api/gadgets", GetGadgets).Methods("GET")
	r.HandleFunc("/api/gadgets", AddGadget).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}", GetGadget).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}", SendCommand).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}", DeleteGadget).Methods("DELETE")
	r.HandleFunc("/api/gadgets/{id}/method", SendMethod).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}/websocket", Connect).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/values", GetValues).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/status", GetStatus).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/notes", AddNote).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}/notes", GetNotes).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/locations/{location}/devices/{device}/status", GetDevice).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/locations/{location}/devices/{device}/status", UpdateDevice).Methods("POST")
	r.HandleFunc("/api/gadgets/{id}/sources/{name}", GetDataPoints).Methods("GET")
	r.HandleFunc("/api/gadgets/{id}/sources/{name}/csv", GetDataPointsCSV).Methods("GET")
	r.HandleFunc("/admin/clients", GetClients).Methods("GET")

	r.PathPrefix("/").Handler(http.FileServer(rice.MustFindBox("www/dist").HTTPBox()))

	http.Handle(root, r)

	addr := fmt.Sprintf("%s:%s", iface, port)
	lg.Printf("listening on %s\n", addr)
	if keyPath == "" {
		lg.Println(http.ListenAndServe(addr, r))
	} else {
		lg.Println(http.ListenAndServeTLS(fmt.Sprintf("%s:443", iface), certPath, keyPath, nil))
	}
}

//This is the endpoint that the gadgets report to. It is
//served on a separate port so it doesn't have to be exposed
//publicly if the main port is exposed.
func startInternal(iRoot string, lg models.Logger, port string) {
	r := mux.NewRouter()
	r.HandleFunc("/internal/updates", Relay).Methods("POST")
	r.HandleFunc("/internal/gadgets/{id}/sources/{name}", AddDataPoint).Methods("POST")
	http.Handle(iRoot, r)
	a := fmt.Sprintf(":%s", port)
	lg.Printf("listening on %s", a)
	err := http.ListenAndServe(a, r)
	if err != nil {
		log.Fatal(err)
	}
}

func Ping(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.Ping, controllers.Read)
}

func GetUser(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetUser, controllers.Read)
}

func GetGadgets(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetGadgets, controllers.Read)
}

func GadgetOptions(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GadgetOptions, controllers.Read)
}

func GetGadget(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetGadget, controllers.Read)
}

func AddGadget(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.AddGadget, controllers.Write)
}

func SendCommand(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.SendCommand, controllers.Write)
}

func SendMethod(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.SendMethod, controllers.Write)
}

func DeleteGadget(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.DeleteGadget, controllers.Write)
}

func GetStatus(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetStatus, controllers.Read)
}

func AddNote(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.AddNote, controllers.Write)
}

func GetNotes(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetNotes, controllers.Read)
}

func AddDataPoint(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.AddDataPoint, controllers.Write)
}

func GetDataPoints(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetDataPoints, controllers.Read)
}

func GetDataPointsCSV(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetDataPointsCSV, controllers.Read)
}

func GetValues(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetValues, controllers.Read)
}

func Connect(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.Connect, controllers.Read)
}

func Relay(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.RelayMessage, controllers.Write)
}
func UpdateDevice(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.UpdateDevice, controllers.Write)
}

func GetDevice(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetDevice, controllers.Write)
}

func GetClients(w http.ResponseWriter, r *http.Request) {
	controllers.Handle(w, r, controllers.GetClients, controllers.Admin)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	_ "net/http/pprof"
	"os"
	"runtime/pprof"
	"strconv"
	"strings"
	"time"

	clog "github.com/cenkalti/log"
	"github.com/cenkalti/rain/internal/clientversion"
	"github.com/cenkalti/rain/internal/console"
	"github.com/cenkalti/rain/internal/jsonutil"
	"github.com/cenkalti/rain/internal/logger"
	"github.com/cenkalti/rain/rainrpc"
	"github.com/cenkalti/rain/torrent"
	"github.com/cenkalti/rain/torrent/resumer/boltdbresumer"
	"github.com/cenkalti/rain/torrent/storage/filestorage"
	"github.com/hokaccha/go-prettyjson"
	"github.com/jroimartin/gocui"
	"github.com/mitchellh/go-homedir"
	"github.com/urfave/cli"
	"gopkg.in/yaml.v2"
)

var (
	app = cli.NewApp()
	clt *rainrpc.Client
	log = logger.New("rain")
)

func main() {
	app.Version = clientversion.Version
	app.Usage = "BitTorrent client"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "config, c",
			Usage: "read config from `FILE`",
		},
		cli.StringFlag{
			Name:  "cpuprofile",
			Usage: "write cpu profile to `FILE`",
		},
		cli.StringFlag{
			Name:  "pprof",
			Usage: "run pprof server on `ADDR`",
		},
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "enable debug log",
		},
		cli.StringFlag{
			Name:  "logfile",
			Usage: "write log to `FILE`",
		},
	}
	app.Before = handleBeforeCommand
	app.After = handleAfterCommand
	app.Commands = []cli.Command{
		{
			Name:      "download",
			Usage:     "download torrent or magnet",
			ArgsUsage: "[torrent path or magnet link]",
			Action:    handleDownload,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "dest",
					Usage: "save files under `DIR`",
					Value: ".",
				},
				cli.IntFlag{
					Name:  "port",
					Usage: "peer listen port",
				},
				cli.BoolFlag{
					Name:  "seed",
					Usage: "continue seeding after download finishes",
				},
			},
		},
		{
			Name:  "client",
			Usage: "run torrent client",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "config",
					Usage: "path to the config file",
					Value: "~/.rain/config.yaml",
				},
			},
			Action: handleClient,
		},
		{
			Name:  "rpc",
			Usage: "send rpc request to client",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "url",
					Usage: "URL of RPC server",
					Value: "localhost:" + strconv.Itoa(rainrpc.DefaultServerConfig.Port),
				},
			},
			Before: handleBeforeRPC,
			Subcommands: []cli.Command{
				{
					Name:   "list",
					Usage:  "list torrents",
					Action: handleList,
				},
				{
					Name:   "add",
					Usage:  "add torrent or magnet",
					Action: handleAdd,
				},
				{
					Name:   "remove",
					Usage:  "remove torrent",
					Action: handleRemove,
				},
				{
					Name:   "stats",
					Usage:  "get stats of torrent",
					Action: handleStats,
				},
				{
					Name:   "start",
					Usage:  "start torrent",
					Action: handleStart,
				},
				{
					Name:   "stop",
					Usage:  "stop",
					Action: handleStop,
				},
				{
					Name:   "console",
					Usage:  "show client console",
					Action: handleConsole,
				},
			},
		},
	}
	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

func handleBeforeCommand(c *cli.Context) error {
	cpuprofile := c.GlobalString("cpuprofile")
	if cpuprofile != "" {
		f, err := os.Create(cpuprofile)
		if err != nil {
			log.Fatal("could not create CPU profile: ", err)
		}
		if err := pprof.StartCPUProfile(f); err != nil {
			log.Fatal("could not start CPU profile: ", err)
		}
	}
	pprofFlag := c.GlobalString("pprof")
	if pprofFlag != "" {
		go func() {
			log.Notice(http.ListenAndServe(pprofFlag, nil))
		}()
	}
	logFile := c.GlobalString("logfile")
	if logFile != "" {
		f, err := os.Create(logFile)
		if err != nil {
			log.Fatal("could not create log file: ", err)
		}
		logger.SetHandler(clog.NewFileHandler(f))
	}
	if c.GlobalBool("debug") {
		logger.SetLevel(clog.DEBUG)
	}
	return nil
}

func handleAfterCommand(c *cli.Context) error {
	if c.GlobalString("cpuprofile") != "" {
		pprof.StopCPUProfile()
	}
	return nil
}

func handleDownload(c *cli.Context) error {
	path := c.Args().Get(0)
	if path == "" {
		return errors.New("first argument must be a torrent file or magnet link")
	}

	sto, err := filestorage.New(c.String("dest"))
	if err != nil {
		return err
	}
	var t *torrent.Torrent
	if strings.HasPrefix(path, "magnet:") {
		t, err = torrent.NewMagnet(path, c.Int("port"), sto, torrent.DefaultConfig)
	} else {
		f, err2 := os.Open(path) // nolint: gosec
		if err2 != nil {
			return err
		}
		t, err = torrent.New(f, c.Int("port"), sto, torrent.DefaultConfig)
		_ = f.Close()
	}
	if err != nil {
		return err
	}
	defer t.Close()

	res, err := boltdbresumer.Open(t.Name()+"."+t.InfoHash()+".resume", []byte(t.InfoHash()))
	if err != nil {
		return err
	}
	defer func() { _ = res.Close() }()

	err = t.SetResume(res)
	if err != nil {
		return err
	}

	g, err := gocui.NewGui(gocui.OutputNormal)
	if err != nil {
		return err
	}
	defer g.Close()

	g.SetManagerFunc(func(g *gocui.Gui) error {
		x, y := g.Size()
		if v, err2 := g.SetView("stats", 0, 0, x-1, y-1); err != nil {
			if err2 != gocui.ErrUnknownView {
				return err2
			}
			v.Wrap = true
		}
		return nil
	})

	_ = g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		t.Stop()
		return nil
	})

	go func() {
		err = g.MainLoop()
		if err == gocui.ErrQuit {
			err = nil
		}
		if err != nil {
			log.Fatal(err)
		}
	}()

	go func() {
		for range time.Tick(100 * time.Millisecond) {
			s := t.Stats()
			b, err2 := jsonutil.MarshalCompactPretty(s)
			if err2 != nil {
				log.Fatal(err2)
			}
			g.Update(func(g *gocui.Gui) error {
				v, err2 := g.View("stats")
				if err2 != nil {
					return err2
				}
				v.Clear()
				_, _ = fmt.Fprintln(v, string(b))
				return nil
			})
		}
	}()

	t.Start()
	completeC := t.NotifyComplete()
	errC := t.NotifyError()
	for {
		select {
		case <-completeC:
			completeC = nil
			if !c.Bool("seed") {
				t.Stop()
				continue
			}
		case err = <-errC:
			if err != nil {
				err = res.Close()
			}
			return err
		}
	}
}

func handleClient(c *cli.Context) error {
	cfg := rainrpc.DefaultServerConfig

	configPath := c.String("config")
	if configPath != "" {
		cp, err := homedir.Expand(configPath)
		if err != nil {
			return err
		}
		b, err := ioutil.ReadFile(cp) // nolint: gosec
		if os.IsNotExist(err) {
			log.Noticef("config file not found at %q, using default config", cp)
		} else if err != nil {
			return err
		} else {
			err = yaml.Unmarshal(b, &cfg)
			if err != nil {
				return err
			}
			log.Infoln("config loaded from:", cp)
		}
	}

	srv, err := rainrpc.NewServer(cfg)
	if err != nil {
		return err
	}
	return srv.ListenAndServe()
}

func handleBeforeRPC(c *cli.Context) error {
	var err error
	clt, err = rainrpc.NewClient(c.String("url"))
	return err
}

func handleList(c *cli.Context) error {
	resp, err := clt.ListTorrents()
	if err != nil {
		return err
	}
	b, err := prettyjson.Marshal(resp)
	if err != nil {
		return err
	}
	_, _ = os.Stdout.Write(b)
	_, _ = os.Stdout.WriteString("\n")
	return nil
}

func handleAdd(c *cli.Context) error {
	var b []byte
	var marshalErr error
	arg := c.Args().Get(0)
	if strings.HasPrefix(arg, "magnet:") {
		resp, err := clt.AddMagnet(arg)
		if err != nil {
			return err
		}
		b, marshalErr = prettyjson.Marshal(resp)
	} else {
		f, err := os.Open(arg) // nolint: gosec
		if err != nil {
			return err
		}
		resp, err := clt.AddTorrent(f)
		_ = f.Close()
		if err != nil {
			return err
		}
		b, marshalErr = prettyjson.Marshal(resp)
	}
	if marshalErr != nil {
		return marshalErr
	}
	_, _ = os.Stdout.Write(b)
	_, _ = os.Stdout.WriteString("\n")
	return nil
}

func handleRemove(c *cli.Context) error {
	id, err := strconv.ParseUint(c.Args().Get(0), 10, 64)
	if err != nil {
		return err
	}
	_, err = clt.RemoveTorrent(id)
	return err
}

func handleStats(c *cli.Context) error {
	id, err := strconv.ParseUint(c.Args().Get(0), 10, 64)
	if err != nil {
		return err
	}
	resp, err := clt.GetTorrentStats(id)
	if err != nil {
		return err
	}
	b, err := prettyjson.Marshal(resp)
	if err != nil {
		return err
	}
	_, _ = os.Stdout.Write(b)
	_, _ = os.Stdout.WriteString("\n")
	return nil
}

func handleStart(c *cli.Context) error {
	id, err := strconv.ParseUint(c.Args().Get(0), 10, 64)
	if err != nil {
		return err
	}
	_, err = clt.StartTorrent(id)
	return err
}

func handleStop(c *cli.Context) error {
	id, err := strconv.ParseUint(c.Args().Get(0), 10, 64)
	if err != nil {
		return err
	}
	_, err = clt.StopTorrent(id)
	return err
}

func handleConsole(c *cli.Context) error {
	con := console.New(clt)
	return con.Run()
}
<commit_msg>rename cli commands<commit_after>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	_ "net/http/pprof"
	"os"
	"runtime/pprof"
	"strconv"
	"strings"
	"time"

	clog "github.com/cenkalti/log"
	"github.com/cenkalti/rain/internal/clientversion"
	"github.com/cenkalti/rain/internal/console"
	"github.com/cenkalti/rain/internal/jsonutil"
	"github.com/cenkalti/rain/internal/logger"
	"github.com/cenkalti/rain/rainrpc"
	"github.com/cenkalti/rain/torrent"
	"github.com/cenkalti/rain/torrent/resumer/boltdbresumer"
	"github.com/cenkalti/rain/torrent/storage/filestorage"
	"github.com/hokaccha/go-prettyjson"
	"github.com/jroimartin/gocui"
	"github.com/mitchellh/go-homedir"
	"github.com/urfave/cli"
	"gopkg.in/yaml.v2"
)

var (
	app = cli.NewApp()
	clt *rainrpc.Client
	log = logger.New("rain")
)

func main() {
	app.Version = clientversion.Version
	app.Usage = "BitTorrent client"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "config, c",
			Usage: "read config from `FILE`",
		},
		cli.StringFlag{
			Name:  "cpuprofile",
			Usage: "write cpu profile to `FILE`",
		},
		cli.StringFlag{
			Name:  "pprof",
			Usage: "run pprof server on `ADDR`",
		},
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "enable debug log",
		},
		cli.StringFlag{
			Name:  "logfile",
			Usage: "write log to `FILE`",
		},
	}
	app.Before = handleBeforeCommand
	app.After = handleAfterCommand
	app.Commands = []cli.Command{
		{
			Name:      "download",
			Usage:     "download torrent or magnet",
			ArgsUsage: "[torrent path or magnet link]",
			Action:    handleDownload,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "dest",
					Usage: "save files under `DIR`",
					Value: ".",
				},
				cli.IntFlag{
					Name:  "port",
					Usage: "peer listen port",
				},
				cli.BoolFlag{
					Name:  "seed",
					Usage: "continue seeding after download finishes",
				},
			},
		},
		{
			Name:  "server",
			Usage: "run rpc server and torrent client",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "config",
					Usage: "path to the config file",
					Value: "~/.rain/config.yaml",
				},
			},
			Action: handleServer,
		},
		{
			Name:  "client",
			Usage: "send rpc request to server",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "url",
					Usage: "URL of RPC server",
					Value: "localhost:" + strconv.Itoa(rainrpc.DefaultServerConfig.Port),
				},
			},
			Before: handleBeforeClient,
			Subcommands: []cli.Command{
				{
					Name:   "list",
					Usage:  "list torrents",
					Action: handleList,
				},
				{
					Name:   "add",
					Usage:  "add torrent or magnet",
					Action: handleAdd,
				},
				{
					Name:   "remove",
					Usage:  "remove torrent",
					Action: handleRemove,
				},
				{
					Name:   "stats",
					Usage:  "get stats of torrent",
					Action: handleStats,
				},
				{
					Name:   "start",
					Usage:  "start torrent",
					Action: handleStart,
				},
				{
					Name:   "stop",
					Usage:  "stop",
					Action: handleStop,
				},
				{
					Name:   "console",
					Usage:  "show client console",
					Action: handleConsole,
				},
			},
		},
	}
	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

func handleBeforeCommand(c *cli.Context) error {
	cpuprofile := c.GlobalString("cpuprofile")
	if cpuprofile != "" {
		f, err := os.Create(cpuprofile)
		if err != nil {
			log.Fatal("could not create CPU profile: ", err)
		}
		if err := pprof.StartCPUProfile(f); err != nil {
			log.Fatal("could not start CPU profile: ", err)
		}
	}
	pprofFlag := c.GlobalString("pprof")
	if pprofFlag != "" {
		go func() {
			log.Notice(http.ListenAndServe(pprofFlag, nil))
		}()
	}
	logFile := c.GlobalString("logfile")
	if logFile != "" {
		f, err := os.Create(logFile)
		if err != nil {
			log.Fatal("could not create log file: ", err)
		}
		logger.SetHandler(clog.NewFileHandler(f))
	}
	if c.GlobalBool("debug") {
		logger.SetLevel(clog.DEBUG)
	}
	return nil
}

func handleAfterCommand(c *cli.Context) error {
	if c.GlobalString("cpuprofile") != "" {
		pprof.StopCPUProfile()
	}
	return nil
}

func handleDownload(c *cli.Context) error {
	path := c.Args().Get(0)
	if path == "" {
		return errors.New("first argument must be a torrent file or magnet link")
	}

	sto, err := filestorage.New(c.String("dest"))
	if err != nil {
		return err
	}
	var t *torrent.Torrent
	if strings.HasPrefix(path, "magnet:") {
		t, err = torrent.NewMagnet(path, c.Int("port"), sto, torrent.DefaultConfig)
	} else {
		f, err2 := os.Open(path) // nolint: gosec
		if err2 != nil {
			return err
		}
		t, err = torrent.New(f, c.Int("port"), sto, torrent.DefaultConfig)
		_ = f.Close()
	}
	if err != nil {
		return err
	}
	defer t.Close()

	res, err := boltdbresumer.Open(t.Name()+"."+t.InfoHash()+".resume", []byte(t.InfoHash()))
	if err != nil {
		return err
	}
	defer func() { _ = res.Close() }()

	err = t.SetResume(res)
	if err != nil {
		return err
	}

	g, err := gocui.NewGui(gocui.OutputNormal)
	if err != nil {
		return err
	}
	defer g.Close()

	g.SetManagerFunc(func(g *gocui.Gui) error {
		x, y := g.Size()
		if v, err2 := g.SetView("stats", 0, 0, x-1, y-1); err != nil {
			if err2 != gocui.ErrUnknownView {
				return err2
			}
			v.Wrap = true
		}
		return nil
	})

	_ = g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
		t.Stop()
		return nil
	})

	go func() {
		err = g.MainLoop()
		if err == gocui.ErrQuit {
			err = nil
		}
		if err != nil {
			log.Fatal(err)
		}
	}()

	go func() {
		for range time.Tick(100 * time.Millisecond) {
			s := t.Stats()
			b, err2 := jsonutil.MarshalCompactPretty(s)
			if err2 != nil {
				log.Fatal(err2)
			}
			g.Update(func(g *gocui.Gui) error {
				v, err2 := g.View("stats")
				if err2 != nil {
					return err2
				}
				v.Clear()
				_, _ = fmt.Fprintln(v, string(b))
				return nil
			})
		}
	}()

	t.Start()
	completeC := t.NotifyComplete()
	errC := t.NotifyError()
	for {
		select {
		case <-completeC:
			completeC = nil
			if !c.Bool("seed") {
				t.Stop()
				continue
			}
		case err = <-errC:
			if err != nil {
				err = res.Close()
			}
			return err
		}
	}
}

func handleServer(c *cli.Context) error {
	cfg := rainrpc.DefaultServerConfig

	configPath := c.String("config")
	if configPath != "" {
		cp, err := homedir.Expand(configPath)
		if err != nil {
			return err
		}
		b, err := ioutil.ReadFile(cp) // nolint: gosec
		if os.IsNotExist(err) {
			log.Noticef("config file not found at %q, using default config", cp)
		} else if err != nil {
			return err
		} else {
			err = yaml.Unmarshal(b, &cfg)
			if err != nil {
				return err
			}
			log.Infoln("config loaded from:", cp)
		}
	}

	srv, err := rainrpc.NewServer(cfg)
	if err != nil {
		return err
	}
	return srv.ListenAndServe()
}

func handleBeforeClient(c *cli.Context) error {
	var err error
	clt, err = rainrpc.NewClient(c.String("url"))
	return err
}

func handleList(c *cli.Context) error {
	resp, err := clt.ListTorrents()
	if err != nil {
		return err
	}
	b, err := prettyjson.Marshal(resp)
	if err != nil {
		return err
	}
	_, _ = os.Stdout.Write(b)
	_, _ = os.Stdout.WriteString("\n")
	return nil
}

func handleAdd(c *cli.Context) error {
	var b []byte
	var marshalErr error
	arg := c.Args().Get(0)
	if strings.HasPrefix(arg, "magnet:") {
		resp, err := clt.AddMagnet(arg)
		if err != nil {
			return err
		}
		b, marshalErr = prettyjson.Marshal(resp)
	} else {
		f, err := os.Open(arg) // nolint: gosec
		if err != nil {
			return err
		}
		resp, err := clt.AddTorrent(f)
		_ = f.Close()
		if err != nil {
			return err
		}
		b, marshalErr = prettyjson.Marshal(resp)
	}
	if marshalErr != nil {
		return marshalErr
	}
	_, _ = os.Stdout.Write(b)
	_, _ = os.Stdout.WriteString("\n")
	return nil
}

func handleRemove(c *cli.Context) error {
	id, err := strconv.ParseUint(c.Args().Get(0), 10, 64)
	if err != nil {
		return err
	}
	_, err = clt.RemoveTorrent(id)
	return err
}

func handleStats(c *cli.Context) error {
	id, err := strconv.ParseUint(c.Args().Get(0), 10, 64)
	if err != nil {
		return err
	}
	resp, err := clt.GetTorrentStats(id)
	if err != nil {
		return err
	}
	b, err := prettyjson.Marshal(resp)
	if err != nil {
		return err
	}
	_, _ = os.Stdout.Write(b)
	_, _ = os.Stdout.WriteString("\n")
	return nil
}

func handleStart(c *cli.Context) error {
	id, err := strconv.ParseUint(c.Args().Get(0), 10, 64)
	if err != nil {
		return err
	}
	_, err = clt.StartTorrent(id)
	return err
}

func handleStop(c *cli.Context) error {
	id, err := strconv.ParseUint(c.Args().Get(0), 10, 64)
	if err != nil {
		return err
	}
	_, err = clt.StopTorrent(id)
	return err
}

func handleConsole(c *cli.Context) error {
	con := console.New(clt)
	return con.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"sync"

	"github.com/zrob/boshler/archiver"
	"github.com/zrob/boshler/bosh_cli"
	"github.com/zrob/boshler/bosh_file"
	"github.com/zrob/boshler/boshio"
)

func main() {
	boshfile, err := bosh_file.ParseFile("BOSHFILE")
	if err != nil {
		panic(err.Error())
	}

	target, err := bosh_cli.GetTarget()
	if err != nil {
		panic(err.Error())
	}
	fmt.Println(target)

	var wg sync.WaitGroup
	wg.Add(len(boshfile.Releases))

	for _, release := range boshfile.Releases {
		go func(release bosh_file.Release) {
			fetcher := boshio.NewMetadataFetcher()
			archiver := archiver.NewArchiver("/tmp/blah")

			metadata, err := fetcher.Fetch(release)
			if err != nil {
				panic(err.Error())
			}

			var releaseVersion boshio.ReleaseVersion
			if release.Version == "" {
				releaseVersion = metadata.Latest()
			} else {
				releaseVersion, err = metadata.Version(release.Version)
				if err != nil {
					panic(err.Error())
				}
			}

			path, err := archiver.Store(releaseVersion)
			if err != nil {
				panic(err.Error())
			}

			err = bosh_cli.UploadRelease(path)
			if err != nil {
				println(err.Error())
				panic(err.Error())
			}

			wg.Done()
		}(release)
	}

	wg.Wait()
}
<commit_msg>store releases in user homedir<commit_after>package main

import (
	"fmt"
	"os/user"
	"path/filepath"
	"sync"

	"github.com/zrob/boshler/archiver"
	"github.com/zrob/boshler/bosh_cli"
	"github.com/zrob/boshler/bosh_file"
	"github.com/zrob/boshler/boshio"
)

func main() {
	boshfile, err := bosh_file.ParseFile("BOSHFILE")
	if err != nil {
		panic(err.Error())
	}

	target, err := bosh_cli.GetTarget()
	if err != nil {
		panic(err.Error())
	}
	fmt.Println(target)

	usr, err := user.Current()
	if err != nil {
		panic(err.Error())
	}
	archiveDir := filepath.Join(usr.HomeDir, ".boshler", "releases")

	var wg sync.WaitGroup
	wg.Add(len(boshfile.Releases))

	for _, release := range boshfile.Releases {
		go func(release bosh_file.Release) {
			fetcher := boshio.NewMetadataFetcher()
			archiver := archiver.NewArchiver(archiveDir)

			metadata, err := fetcher.Fetch(release)
			if err != nil {
				panic(err.Error())
			}

			var releaseVersion boshio.ReleaseVersion
			if release.Version == "" {
				releaseVersion = metadata.Latest()
			} else {
				releaseVersion, err = metadata.Version(release.Version)
				if err != nil {
					panic(err.Error())
				}
			}

			path, err := archiver.Store(releaseVersion)
			if err != nil {
				panic(err.Error())
			}

			err = bosh_cli.UploadRelease(path)
			if err != nil {
				println(err.Error())
				panic(err.Error())
			}

			wg.Done()
		}(release)
	}

	wg.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"runtime"
	"time"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"

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

var (
	version   string
	branch    string
	revision  string
	buildDate string
	goVersion = runtime.Version()
)

var (
	prometheusAddress     = flag.String("metrics-listen-address", ":9001", "The address to listen on for Prometheus metrics requests.")
	prometheusMetricsPath = flag.String("metrics-path", "/metrics", "The path to listen for Prometheus metrics requests.")
	apiAddress            = flag.String("api-listen-address", ":5000", "The address to listen on for api HTTP requests.")

	// seed random number
	r = rand.New(rand.NewSource(time.Now().UnixNano()))

	// define prometheus counter
	webhookTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "estafette_ci_api_webhook_totals",
			Help: "Total of received webhooks.",
		},
		[]string{"event", "source"},
	)
)

func init() {
	// Metrics have to be registered to be exposed:
	prometheus.MustRegister(webhookTotal)
}

func main() {

	// set some default fields added to all logs
	log := zerolog.New(os.Stdout).With().
		Timestamp().
		Str("app", "estafette-ci-api").
		Str("version", version).
		Logger()

	// log startup message
	log.Info().
		Str("branch", branch).
		Str("revision", revision).
		Str("buildDate", buildDate).
		Str("goVersion", goVersion).
		Msg("Starting estafette-ci-api...")

	// start prometheus
	go func() {
		log.Info().
			Str("port", *prometheusAddress).
			Str("path", *prometheusMetricsPath).
			Msg("Serving Prometheus metrics...")

		http.Handle(*prometheusMetricsPath, promhttp.Handler())

		if err := http.ListenAndServe(*prometheusAddress, nil); err != nil {
			log.Fatal().Err(err).Msg("Starting Prometheus listener failed")
		}
	}()

	log.Info().
		Str("port", *apiAddress).
		Msg("Serving api calls...")

	http.HandleFunc("/webhook/github", githubWebhookHandler)
	http.HandleFunc("/webhook/bitbucket", bitbucketWebhookHandler)
	http.HandleFunc("/liveness", livenessHandler)
	http.HandleFunc("/readiness", readinessHandler)

	if err := http.ListenAndServe(*apiAddress, nil); err != nil {
		log.Fatal().Err(err).Msg("Starting api listener failed")
	}
}

func githubWebhookHandler(w http.ResponseWriter, r *http.Request) {

	// https://developer.github.com/webhooks/
	eventType := r.Header.Get("X-GitHub-Event")
	webhookTotal.With(prometheus.Labels{"event": eventType, "source": "github"}).Inc()

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Error().Err(err).Msg("Reading body from Github webhook failed")
		http.Error(w, "Reading body from Github webhook failed", 500)
		return
	}

	// unmarshal json body
	var b interface{}
	err = json.Unmarshal(body, &b)
	if err != nil {
		log.Error().Err(err).Str("body", string(body)).Msg("Deserializing body from Github webhook failed")
		http.Error(w, "Deserializing body from Github webhook failed", 500)
		return
	}

	log.Info().
		Str("method", r.Method).
		Str("url", r.URL.String()).
		Interface("headers", r.Header).
		Interface("body", b).
		Msgf("Received webhook event of type '%v' from GitHub...", eventType)

	switch eventType {
	case "push":
		handleGithubPush(body)
	default:
		log.Warn().Str("event", eventType).Msgf("Unsupported Github webhook event of type '%v'", eventType)
	}

	fmt.Fprintf(w, "Aye aye!")
}

func bitbucketWebhookHandler(w http.ResponseWriter, r *http.Request) {

	// https://confluence.atlassian.com/bitbucket/manage-webhooks-735643732.html

	eventType := r.Header.Get("X-Event-Key")
	webhookTotal.With(prometheus.Labels{"event": eventType, "source": "bitbucket"}).Inc()

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Error().Err(err).Msg("Reading body from Bitbucket webhook failed")
		http.Error(w, "Reading body from Bitbucket webhook failed", 500)
		return
	}

	// unmarshal json body
	var b interface{}
	err = json.Unmarshal(body, &b)
	if err != nil {
		log.Error().Err(err).Str("body", string(body)).Msg("Deserializing body from Bitbucket webhook failed")
		http.Error(w, "Deserializing body from Bitbucket webhook failed", 500)
		return
	}

	log.Info().
		Str("method", r.Method).
		Str("url", r.URL.String()).
		Interface("headers", r.Header).
		Interface("body", string(body)).
		Msgf("Received webhook event of type '%v' from Bitbucket", eventType)

	switch eventType {
	case "repo:push":
		handleBitbucketPush(body)
	default:
		log.Warn().Str("event", eventType).Msgf("Unsupported Bitbucket webhook event of type '%v'", eventType)
	}

	fmt.Fprintf(w, "Aye aye!")
}

// GithubPushEvent represents a Github webhook push event
type GithubPushEvent struct {
	After      string           `json:"after"`
	Commits    []GithubCommit   `json:"commits"`
	HeadCommit GithubCommit     `json:"head_commit"`
	Pusher     GithubPusher     `json:"pusher"`
	Repository GithubRepository `json:"repository"`
}

// GithubCommit represents a Github commit
type GithubCommit struct {
	Author  GithubAuthor `json:"author"`
	Message string       `json:"message"`
	ID      string       `json:"id"`
}

// GithubAuthor represents a Github author
type GithubAuthor struct {
	Email    string `json:"email"`
	Name     string `json:"name"`
	UserName string `json:"username"`
}

// GithubPusher represents a Github pusher
type GithubPusher struct {
	Email string `json:"email"`
	Name  string `json:"name"`
}

// GithubRepository represents a Github repository
type GithubRepository struct {
	GitURL   string `json:"git_url"`
	HTMLURL  string `json:"html_url"`
	Name     string `json:"name"`
	FullName string `json:"full_name"`
}

func handleGithubPush(body []byte) {

	// unmarshal json body
	var pushEvent GithubPushEvent
	err := json.Unmarshal(body, &pushEvent)
	if err != nil {
		log.Error().Err(err).Str("body", string(body)).Msg("Deserializing body to GithubPushEvent failed")
		return
	}

	log.Info().Interface("pushEvent", pushEvent).Msg("Deserialized GitHub push event")
}

func handleBitbucketPush(body []byte) {

}

func livenessHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "I'm alive!")
}

func readinessHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "I'm ready!")
}
<commit_msg>parse command line parameters<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"runtime"
	"time"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"

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

var (
	version   string
	branch    string
	revision  string
	buildDate string
	goVersion = runtime.Version()
)

var (
	prometheusAddress     = flag.String("metrics-listen-address", ":9001", "The address to listen on for Prometheus metrics requests.")
	prometheusMetricsPath = flag.String("metrics-path", "/metrics", "The path to listen for Prometheus metrics requests.")
	apiAddress            = flag.String("api-listen-address", ":5000", "The address to listen on for api HTTP requests.")

	// seed random number
	r = rand.New(rand.NewSource(time.Now().UnixNano()))

	// define prometheus counter
	webhookTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "estafette_ci_api_webhook_totals",
			Help: "Total of received webhooks.",
		},
		[]string{"event", "source"},
	)
)

func init() {
	// Metrics have to be registered to be exposed:
	prometheus.MustRegister(webhookTotal)
}

func main() {

	// parse command line parameters
	flag.Parse()

	// set some default fields added to all logs
	log := zerolog.New(os.Stdout).With().
		Timestamp().
		Str("app", "estafette-ci-api").
		Str("version", version).
		Logger()

	// log startup message
	log.Info().
		Str("branch", branch).
		Str("revision", revision).
		Str("buildDate", buildDate).
		Str("goVersion", goVersion).
		Msg("Starting estafette-ci-api...")

	// start prometheus
	go func() {
		log.Info().
			Str("port", *prometheusAddress).
			Str("path", *prometheusMetricsPath).
			Msg("Serving Prometheus metrics...")

		http.Handle(*prometheusMetricsPath, promhttp.Handler())

		if err := http.ListenAndServe(*prometheusAddress, nil); err != nil {
			log.Fatal().Err(err).Msg("Starting Prometheus listener failed")
		}
	}()

	log.Info().
		Str("port", *apiAddress).
		Msg("Serving api calls...")

	http.HandleFunc("/webhook/github", githubWebhookHandler)
	http.HandleFunc("/webhook/bitbucket", bitbucketWebhookHandler)
	http.HandleFunc("/liveness", livenessHandler)
	http.HandleFunc("/readiness", readinessHandler)

	if err := http.ListenAndServe(*apiAddress, nil); err != nil {
		log.Fatal().Err(err).Msg("Starting api listener failed")
	}
}

func githubWebhookHandler(w http.ResponseWriter, r *http.Request) {

	// https://developer.github.com/webhooks/
	eventType := r.Header.Get("X-GitHub-Event")
	webhookTotal.With(prometheus.Labels{"event": eventType, "source": "github"}).Inc()

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Error().Err(err).Msg("Reading body from Github webhook failed")
		http.Error(w, "Reading body from Github webhook failed", 500)
		return
	}

	// unmarshal json body
	var b interface{}
	err = json.Unmarshal(body, &b)
	if err != nil {
		log.Error().Err(err).Str("body", string(body)).Msg("Deserializing body from Github webhook failed")
		http.Error(w, "Deserializing body from Github webhook failed", 500)
		return
	}

	log.Info().
		Str("method", r.Method).
		Str("url", r.URL.String()).
		Interface("headers", r.Header).
		Interface("body", b).
		Msgf("Received webhook event of type '%v' from GitHub...", eventType)

	switch eventType {
	case "push":
		handleGithubPush(body)
	default:
		log.Warn().Str("event", eventType).Msgf("Unsupported Github webhook event of type '%v'", eventType)
	}

	fmt.Fprintf(w, "Aye aye!")
}

func bitbucketWebhookHandler(w http.ResponseWriter, r *http.Request) {

	// https://confluence.atlassian.com/bitbucket/manage-webhooks-735643732.html

	eventType := r.Header.Get("X-Event-Key")
	webhookTotal.With(prometheus.Labels{"event": eventType, "source": "bitbucket"}).Inc()

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Error().Err(err).Msg("Reading body from Bitbucket webhook failed")
		http.Error(w, "Reading body from Bitbucket webhook failed", 500)
		return
	}

	// unmarshal json body
	var b interface{}
	err = json.Unmarshal(body, &b)
	if err != nil {
		log.Error().Err(err).Str("body", string(body)).Msg("Deserializing body from Bitbucket webhook failed")
		http.Error(w, "Deserializing body from Bitbucket webhook failed", 500)
		return
	}

	log.Info().
		Str("method", r.Method).
		Str("url", r.URL.String()).
		Interface("headers", r.Header).
		Interface("body", string(body)).
		Msgf("Received webhook event of type '%v' from Bitbucket", eventType)

	switch eventType {
	case "repo:push":
		handleBitbucketPush(body)
	default:
		log.Warn().Str("event", eventType).Msgf("Unsupported Bitbucket webhook event of type '%v'", eventType)
	}

	fmt.Fprintf(w, "Aye aye!")
}

// GithubPushEvent represents a Github webhook push event
type GithubPushEvent struct {
	After      string           `json:"after"`
	Commits    []GithubCommit   `json:"commits"`
	HeadCommit GithubCommit     `json:"head_commit"`
	Pusher     GithubPusher     `json:"pusher"`
	Repository GithubRepository `json:"repository"`
}

// GithubCommit represents a Github commit
type GithubCommit struct {
	Author  GithubAuthor `json:"author"`
	Message string       `json:"message"`
	ID      string       `json:"id"`
}

// GithubAuthor represents a Github author
type GithubAuthor struct {
	Email    string `json:"email"`
	Name     string `json:"name"`
	UserName string `json:"username"`
}

// GithubPusher represents a Github pusher
type GithubPusher struct {
	Email string `json:"email"`
	Name  string `json:"name"`
}

// GithubRepository represents a Github repository
type GithubRepository struct {
	GitURL   string `json:"git_url"`
	HTMLURL  string `json:"html_url"`
	Name     string `json:"name"`
	FullName string `json:"full_name"`
}

func handleGithubPush(body []byte) {

	// unmarshal json body
	var pushEvent GithubPushEvent
	err := json.Unmarshal(body, &pushEvent)
	if err != nil {
		log.Error().Err(err).Str("body", string(body)).Msg("Deserializing body to GithubPushEvent failed")
		return
	}

	log.Info().Interface("pushEvent", pushEvent).Msg("Deserialized GitHub push event")
}

func handleBitbucketPush(body []byte) {

}

func livenessHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "I'm alive!")
}

func readinessHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "I'm ready!")
}
<|endoftext|>