<commit_before>package main // import "fknsrs.biz/p/don"

import (
	"crypto/tls"
	"database/sql"
	"encoding/json"
	"encoding/xml"
	"html/template"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/GeertJohan/go.rice"
	"github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/jtacoma/uritemplates"
	_ "github.com/mattn/go-sqlite3"
	"github.com/meatballhat/negroni-logrus"
	"github.com/phyber/negroni-gzip/gzip"
	"github.com/urfave/negroni"
	"gopkg.in/alecthomas/kingpin.v2"
)

var (
	app                   = kingpin.New("don", "Really really small OStatus node.")
	addr                  = app.Flag("addr", "Address to listen on.").Envar("ADDR").Default(":5000").String()
	database              = app.Flag("database", "Where to put the SQLite database.").Envar("DATABASE").Default("don.db").String()
	publicURL             = app.Flag("public_url", "URL to use for callbacks etc.").Envar("PUBLIC_URL").Required().String()
	logLevel              = app.Flag("log_level", "How much to log.").Default("INFO").Envar("LOG_LEVEL").Enum("DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC")
	pubsubRefreshInterval = app.Flag("pubsub_refresh_interval", "PubSub subscription refresh interval.").Default("15m").Envar("PUBSUB_REFRESH_INTERVAL").Duration()
)

func main() {
	kingpin.MustParse(app.Parse(os.Args[1:]))

	ll, err := logrus.ParseLevel(*logLevel)
	if err != nil {
		panic(err)
	}
	logrus.SetLevel(ll)

	logrus.WithFields(logrus.Fields{
		"addr":       *addr,
		"database":   *database,
		"public_url": *publicURL,
		"log_level":  *logLevel,
	}).Info("starting up")

	if http.DefaultClient.Transport == nil {
		http.DefaultClient.Transport = http.DefaultTransport
	}

	if tr, ok := http.DefaultClient.Transport.(*http.Transport); ok {
		logrus.Debug("hack: disabling http2 - POST requests are currently broken")
		tr.TLSNextProto = map[string]func(authority string, c *tls.Conn) http.RoundTripper{}
	} else {
		logrus.Debug("hack: couldn't disable http2 - POST requests may not work")
	}

	db, err := sql.Open("sqlite3", *database)
	if err != nil {
		panic(err)
	}
	defer db.Close()

	if _, err := db.Exec(`
		create table if not exists pubsub_state (id text not null primary key, hub text not null, topic text not null, callback_url text not null, expires_at datetime, unique (hub, topic));
	  create table if not exists people (feed_url text not null primary key, first_seen datetime not null, name text, display_name text, email text, summary text, note text);
		create table if not exists posts (feed_url text not null, id text not null, created_at datetime not null, raw_entry text not null, primary key (feed_url, id));
	`); err != nil {
		panic(err)
	}

	savePerson := func(feedURL string, author *AtomAuthor) error {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()

		var name, displayName, email, summary, note string
		if err := tx.QueryRow("select name, display_name, email, summary, note from people where feed_url = $1", feedURL).Scan(&name, &displayName, &email, &summary, &note); err != nil {
			if err != sql.ErrNoRows {
				return err
			}

			if _, err := tx.Exec("insert into people (feed_url, first_seen, name, display_name, email, summary, note) values ($1, $2, $3, $4, $5, $6, $7)", feedURL, time.Now(), author.Name, author.DisplayName, author.Email, author.Summary, author.Note); err != nil {
				return err
			}
		} else {
			if name == author.Name && displayName == author.DisplayName && email == author.Email && summary == author.Summary && note == author.Note {
				return nil
			}

			if _, err := tx.Exec("update people set name = $1, display_name = $2, email = $3, summary = $4, note = $5 where feed_url = $6", author.Name, author.DisplayName, author.Email, author.Summary, author.Note, feedURL); err != nil {
				return err
			}
		}

		return tx.Commit()
	}

	saveEntry := func(feedURL string, entry *AtomEntry) error {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()

		var exists int
		if err := tx.QueryRow("select count(1) from posts where feed_url = $1 and id = $2", feedURL, entry.ID).Scan(&exists); err != nil {
			return err
		}

		if exists > 0 {
			return nil
		}

		d, err := json.Marshal(entry)
		if err != nil {
			return err
		}

		if _, err := tx.Exec("insert into posts (feed_url, id, created_at, raw_entry) values ($1, $2, $3, $4)", feedURL, entry.ID, entry.Published, string(d)); err != nil {
			return err
		}

		return tx.Commit()
	}

	psc := NewPubSubClient(*publicURL+"/pubsub", NewPubSubSQLState(db), func(id string, s *PubSubSubscription, rd io.ReadCloser) {
		if s == nil {
			logrus.WithField("id", id).Debug("pubsub: unsolicited message")
			return
		}

		l := logrus.WithFields(logrus.Fields{
			"id":    s.ID,
			"hub":   s.Hub,
			"topic": s.Topic,
		})

		l.Debug("pubsub: received message")

		var v AtomFeed
		if err := xml.NewDecoder(rd).Decode(&v); err != nil {
			l.WithError(err).Debug("pubsub: couldn't parse body")
			return
		}

		l.Debug("pubsub: parsed message")

		if v.Author != nil {
			if err := savePerson(s.Topic, v.Author); err != nil {
				l.WithError(err).Debug("pubsub: couldn't save author")
				return
			}
		}

		for _, e := range v.Entry {
			if err := saveEntry(s.Topic, &e); err != nil {
				l.WithError(err).Debug("pubsub: couldn't save entry")
			} else {
				l.Debug("pubsub: saved entry")
			}
		}
	})

	go func() {
		time.Sleep(time.Second * 2)

		for {
			logrus.Debug("refreshing pubsub subscriptions")
			if err := psc.Refresh(false, *pubsubRefreshInterval); err != nil {
				logrus.WithError(err).Error("couldn't refresh pubsub subscriptions")
			} else {
				logrus.Debug("refreshed pubsub subscriptions")
			}

			time.Sleep(*pubsubRefreshInterval)
		}
	}()

	// this has to be here for rice to work
	_, _ = rice.FindBox("public")
	_, _ = rice.FindBox("templates")

	cfg := rice.Config{LocateOrder: []rice.LocateMethod{
		rice.LocateWorkingDirectory,
		rice.LocateFS,
		rice.LocateEmbedded,
	}}

	publicBox := cfg.MustFindBox("public")
	templateBox := cfg.MustFindBox("templates")

	rootTemplate := template.New("root")

	if err := templateBox.Walk("/", func(name string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() || !strings.HasSuffix(name, ".html") || strings.HasPrefix(name, "page_") {
			return nil
		}

		if _, err := rootTemplate.Parse(templateBox.MustString(name)); err != nil {
			return err
		}

		return nil
	}); err != nil {
		panic(err)
	}

	templateHome := template.Must(template.Must(rootTemplate.Clone()).Parse(templateBox.MustString("page_home.html")))
	templateFeed := template.Must(template.Must(rootTemplate.Clone()).Parse(templateBox.MustString("page_feed.html")))

	m := mux.NewRouter()

	m.PathPrefix("/pubsub").Handler(psc.Handler())

	m.Methods("GET").Path("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rows, err := db.Query("select posts.feed_url, posts.raw_entry, people.name, people.display_name, people.email from posts left join people on people.feed_url = posts.feed_url order by posts.created_at desc limit 25")
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		var posts []AtomFeed
		for rows.Next() {
			var feedURL, rawEntry string
			var name, displayName, email sql.NullString
			if err := rows.Scan(&feedURL, &rawEntry, &name, &displayName, &email); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}

			var entry AtomEntry
			if err := json.Unmarshal([]byte(rawEntry), &entry); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}

			post := AtomFeed{
				Entry: []AtomEntry{entry},
				Link:  []AtomLink{AtomLink{Rel: "self", Href: feedURL}},
			}

			if name.Valid {
				post.Author = &AtomAuthor{
					Name:        name.String,
					DisplayName: displayName.String,
					Email:       email.String,
				}
			}

			posts = append(posts, post)
		}

		rw.Header().Set("content-type", "text/html")
		if err := templateHome.Execute(rw, map[string]interface{}{"Posts": posts}); err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	m.Methods("GET").Path("/health").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rw.WriteHeader(http.StatusOK)
	})

	m.Methods("GET").Path("/show-feed").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		feed, err := AtomFetch(r.URL.Query().Get("url"))
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}

		hubLink := feed.GetLink("hub")
		selfLink := feed.GetLink("self")

		if hubLink != nil && selfLink != nil && hubLink.Href != "" && selfLink.Href != "" {
			if err := psc.Subscribe(hubLink.Href, selfLink.Href); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}
		}

		rw.Header().Set("content-type", "text/html")
		rw.WriteHeader(http.StatusOK)

		if feed.Author != nil {
			if err := savePerson(r.URL.Query().Get("url"), feed.Author); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}
		}

		for _, e := range feed.Entry {
			if err := saveEntry(r.URL.Query().Get("url"), &e); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}
		}

		if err := templateFeed.Execute(rw, map[string]interface{}{"Feed": feed}); err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	m.Methods("GET").Path("/find-feed").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		user := r.URL.Query().Get("user")
		if !strings.HasPrefix(user, "acct:") {
			user = "acct:" + user
		}

		acct, err := AcctFromString(user)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}

		hm, err := HostMetaFetch(acct.Host)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}

		lrdd := hm.GetLink("lrdd")
		if lrdd == nil {
			http.Error(rw, "no lrdd link found in host metadata", http.StatusInternalServerError)
			return
		}

		var lrddHref string
		switch {
		case lrdd.Href != "":
			lrddHref = lrdd.Href
		case lrdd.Template != "":
			lrddHrefTemplate, err := uritemplates.Parse(lrdd.Template)
			if err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}

			s, err := lrddHrefTemplate.Expand(map[string]interface{}{"uri": acct.String()})
			if err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}

			lrddHref = s
		}

		wf, err := WebfingerFetch(lrddHref)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}

		feedLink := wf.GetLink("http://schemas.google.com/g/2010#updates-from")
		if feedLink == nil {
			http.Error(rw, "no feed link found in webfinger response", http.StatusInternalServerError)
			return
		}

		rw.Header().Set("location", "/show-feed?"+url.Values{"url": []string{feedLink.Href}}.Encode())
		rw.WriteHeader(http.StatusSeeOther)
	})

	m.NotFoundHandler = http.FileServer(publicBox.HTTPBox())

	n := negroni.New()

	n.Use(negronilogrus.NewMiddleware())
	n.Use(negroni.NewRecovery())
	n.Use(gzip.Gzip(gzip.BestCompression))
	n.UseHandler(m)

	logrus.Info("starting server")

	if err := http.ListenAndServe(*addr, n); err != nil {
		panic(err)
	}
}
<commit_msg>remove gzip middleware - it seems to break some proxies<commit_after>package main // import "fknsrs.biz/p/don"

import (
	"crypto/tls"
	"database/sql"
	"encoding/json"
	"encoding/xml"
	"html/template"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/GeertJohan/go.rice"
	"github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/jtacoma/uritemplates"
	_ "github.com/mattn/go-sqlite3"
	"github.com/meatballhat/negroni-logrus"
	"github.com/urfave/negroni"
	"gopkg.in/alecthomas/kingpin.v2"
)

var (
	app                   = kingpin.New("don", "Really really small OStatus node.")
	addr                  = app.Flag("addr", "Address to listen on.").Envar("ADDR").Default(":5000").String()
	database              = app.Flag("database", "Where to put the SQLite database.").Envar("DATABASE").Default("don.db").String()
	publicURL             = app.Flag("public_url", "URL to use for callbacks etc.").Envar("PUBLIC_URL").Required().String()
	logLevel              = app.Flag("log_level", "How much to log.").Default("INFO").Envar("LOG_LEVEL").Enum("DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC")
	pubsubRefreshInterval = app.Flag("pubsub_refresh_interval", "PubSub subscription refresh interval.").Default("15m").Envar("PUBSUB_REFRESH_INTERVAL").Duration()
)

func main() {
	kingpin.MustParse(app.Parse(os.Args[1:]))

	ll, err := logrus.ParseLevel(*logLevel)
	if err != nil {
		panic(err)
	}
	logrus.SetLevel(ll)

	logrus.WithFields(logrus.Fields{
		"addr":       *addr,
		"database":   *database,
		"public_url": *publicURL,
		"log_level":  *logLevel,
	}).Info("starting up")

	if http.DefaultClient.Transport == nil {
		http.DefaultClient.Transport = http.DefaultTransport
	}

	if tr, ok := http.DefaultClient.Transport.(*http.Transport); ok {
		logrus.Debug("hack: disabling http2 - POST requests are currently broken")
		tr.TLSNextProto = map[string]func(authority string, c *tls.Conn) http.RoundTripper{}
	} else {
		logrus.Debug("hack: couldn't disable http2 - POST requests may not work")
	}

	db, err := sql.Open("sqlite3", *database)
	if err != nil {
		panic(err)
	}
	defer db.Close()

	if _, err := db.Exec(`
		create table if not exists pubsub_state (id text not null primary key, hub text not null, topic text not null, callback_url text not null, expires_at datetime, unique (hub, topic));
	  create table if not exists people (feed_url text not null primary key, first_seen datetime not null, name text, display_name text, email text, summary text, note text);
		create table if not exists posts (feed_url text not null, id text not null, created_at datetime not null, raw_entry text not null, primary key (feed_url, id));
	`); err != nil {
		panic(err)
	}

	savePerson := func(feedURL string, author *AtomAuthor) error {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()

		var name, displayName, email, summary, note string
		if err := tx.QueryRow("select name, display_name, email, summary, note from people where feed_url = $1", feedURL).Scan(&name, &displayName, &email, &summary, &note); err != nil {
			if err != sql.ErrNoRows {
				return err
			}

			if _, err := tx.Exec("insert into people (feed_url, first_seen, name, display_name, email, summary, note) values ($1, $2, $3, $4, $5, $6, $7)", feedURL, time.Now(), author.Name, author.DisplayName, author.Email, author.Summary, author.Note); err != nil {
				return err
			}
		} else {
			if name == author.Name && displayName == author.DisplayName && email == author.Email && summary == author.Summary && note == author.Note {
				return nil
			}

			if _, err := tx.Exec("update people set name = $1, display_name = $2, email = $3, summary = $4, note = $5 where feed_url = $6", author.Name, author.DisplayName, author.Email, author.Summary, author.Note, feedURL); err != nil {
				return err
			}
		}

		return tx.Commit()
	}

	saveEntry := func(feedURL string, entry *AtomEntry) error {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()

		var exists int
		if err := tx.QueryRow("select count(1) from posts where feed_url = $1 and id = $2", feedURL, entry.ID).Scan(&exists); err != nil {
			return err
		}

		if exists > 0 {
			return nil
		}

		d, err := json.Marshal(entry)
		if err != nil {
			return err
		}

		if _, err := tx.Exec("insert into posts (feed_url, id, created_at, raw_entry) values ($1, $2, $3, $4)", feedURL, entry.ID, entry.Published, string(d)); err != nil {
			return err
		}

		return tx.Commit()
	}

	psc := NewPubSubClient(*publicURL+"/pubsub", NewPubSubSQLState(db), func(id string, s *PubSubSubscription, rd io.ReadCloser) {
		if s == nil {
			logrus.WithField("id", id).Debug("pubsub: unsolicited message")
			return
		}

		l := logrus.WithFields(logrus.Fields{
			"id":    s.ID,
			"hub":   s.Hub,
			"topic": s.Topic,
		})

		l.Debug("pubsub: received message")

		var v AtomFeed
		if err := xml.NewDecoder(rd).Decode(&v); err != nil {
			l.WithError(err).Debug("pubsub: couldn't parse body")
			return
		}

		l.Debug("pubsub: parsed message")

		if v.Author != nil {
			if err := savePerson(s.Topic, v.Author); err != nil {
				l.WithError(err).Debug("pubsub: couldn't save author")
				return
			}
		}

		for _, e := range v.Entry {
			if err := saveEntry(s.Topic, &e); err != nil {
				l.WithError(err).Debug("pubsub: couldn't save entry")
			} else {
				l.Debug("pubsub: saved entry")
			}
		}
	})

	go func() {
		time.Sleep(time.Second * 2)

		for {
			logrus.Debug("refreshing pubsub subscriptions")
			if err := psc.Refresh(false, *pubsubRefreshInterval); err != nil {
				logrus.WithError(err).Error("couldn't refresh pubsub subscriptions")
			} else {
				logrus.Debug("refreshed pubsub subscriptions")
			}

			time.Sleep(*pubsubRefreshInterval)
		}
	}()

	// this has to be here for rice to work
	_, _ = rice.FindBox("public")
	_, _ = rice.FindBox("templates")

	cfg := rice.Config{LocateOrder: []rice.LocateMethod{
		rice.LocateWorkingDirectory,
		rice.LocateFS,
		rice.LocateEmbedded,
	}}

	publicBox := cfg.MustFindBox("public")
	templateBox := cfg.MustFindBox("templates")

	rootTemplate := template.New("root")

	if err := templateBox.Walk("/", func(name string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() || !strings.HasSuffix(name, ".html") || strings.HasPrefix(name, "page_") {
			return nil
		}

		if _, err := rootTemplate.Parse(templateBox.MustString(name)); err != nil {
			return err
		}

		return nil
	}); err != nil {
		panic(err)
	}

	templateHome := template.Must(template.Must(rootTemplate.Clone()).Parse(templateBox.MustString("page_home.html")))
	templateFeed := template.Must(template.Must(rootTemplate.Clone()).Parse(templateBox.MustString("page_feed.html")))

	m := mux.NewRouter()

	m.PathPrefix("/pubsub").Handler(psc.Handler())

	m.Methods("GET").Path("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rows, err := db.Query("select posts.feed_url, posts.raw_entry, people.name, people.display_name, people.email from posts left join people on people.feed_url = posts.feed_url order by posts.created_at desc limit 25")
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		var posts []AtomFeed
		for rows.Next() {
			var feedURL, rawEntry string
			var name, displayName, email sql.NullString
			if err := rows.Scan(&feedURL, &rawEntry, &name, &displayName, &email); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}

			var entry AtomEntry
			if err := json.Unmarshal([]byte(rawEntry), &entry); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}

			post := AtomFeed{
				Entry: []AtomEntry{entry},
				Link:  []AtomLink{AtomLink{Rel: "self", Href: feedURL}},
			}

			if name.Valid {
				post.Author = &AtomAuthor{
					Name:        name.String,
					DisplayName: displayName.String,
					Email:       email.String,
				}
			}

			posts = append(posts, post)
		}

		rw.Header().Set("content-type", "text/html")
		if err := templateHome.Execute(rw, map[string]interface{}{"Posts": posts}); err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	m.Methods("GET").Path("/health").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rw.WriteHeader(http.StatusOK)
	})

	m.Methods("GET").Path("/show-feed").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		feed, err := AtomFetch(r.URL.Query().Get("url"))
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}

		hubLink := feed.GetLink("hub")
		selfLink := feed.GetLink("self")

		if hubLink != nil && selfLink != nil && hubLink.Href != "" && selfLink.Href != "" {
			if err := psc.Subscribe(hubLink.Href, selfLink.Href); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}
		}

		rw.Header().Set("content-type", "text/html")
		rw.WriteHeader(http.StatusOK)

		if feed.Author != nil {
			if err := savePerson(r.URL.Query().Get("url"), feed.Author); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}
		}

		for _, e := range feed.Entry {
			if err := saveEntry(r.URL.Query().Get("url"), &e); err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}
		}

		if err := templateFeed.Execute(rw, map[string]interface{}{"Feed": feed}); err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	m.Methods("GET").Path("/find-feed").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		user := r.URL.Query().Get("user")
		if !strings.HasPrefix(user, "acct:") {
			user = "acct:" + user
		}

		acct, err := AcctFromString(user)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}

		hm, err := HostMetaFetch(acct.Host)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}

		lrdd := hm.GetLink("lrdd")
		if lrdd == nil {
			http.Error(rw, "no lrdd link found in host metadata", http.StatusInternalServerError)
			return
		}

		var lrddHref string
		switch {
		case lrdd.Href != "":
			lrddHref = lrdd.Href
		case lrdd.Template != "":
			lrddHrefTemplate, err := uritemplates.Parse(lrdd.Template)
			if err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}

			s, err := lrddHrefTemplate.Expand(map[string]interface{}{"uri": acct.String()})
			if err != nil {
				http.Error(rw, err.Error(), http.StatusInternalServerError)
				return
			}

			lrddHref = s
		}

		wf, err := WebfingerFetch(lrddHref)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}

		feedLink := wf.GetLink("http://schemas.google.com/g/2010#updates-from")
		if feedLink == nil {
			http.Error(rw, "no feed link found in webfinger response", http.StatusInternalServerError)
			return
		}

		rw.Header().Set("location", "/show-feed?"+url.Values{"url": []string{feedLink.Href}}.Encode())
		rw.WriteHeader(http.StatusSeeOther)
	})

	m.NotFoundHandler = http.FileServer(publicBox.HTTPBox())

	n := negroni.New()

	n.Use(negronilogrus.NewMiddleware())
	n.Use(negroni.NewRecovery())
	n.UseHandler(m)

	logrus.Info("starting server")

	if err := http.ListenAndServe(*addr, n); err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"mime"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/ONSdigital/florence/assets"
	"github.com/ONSdigital/florence/config"
	"github.com/ONSdigital/florence/healthcheck"
	"github.com/ONSdigital/florence/upload"
	"github.com/ONSdigital/go-ns/handlers/reverseProxy"
	hc "github.com/ONSdigital/go-ns/healthcheck"
	"github.com/ONSdigital/go-ns/server"
	"github.com/ONSdigital/go-ns/vault"
	"github.com/ONSdigital/log.go/log"
	"github.com/gorilla/pat"
	"github.com/gorilla/websocket"
)

var getAsset = assets.Asset
var getAssetETag = assets.GetAssetETag
var upgrader = websocket.Upgrader{}

// Version is set by the make target
var Version string

func main() {
	log.Namespace = "florence"
	log.Event(nil, "florence version", log.Data{"version": Version})

	cfg, err := config.Get()
	if err != nil {
		log.Event(nil, "error getting configuration", log.Error(err))
		os.Exit(1)
	}

	zc := healthcheck.New(cfg.ZebedeeURL, "zebedee")
	rtrc := healthcheck.New(cfg.RouterURL, "router")
	dc := healthcheck.New(cfg.DatasetAPIURL, "dataset-api")
	rc := healthcheck.New(cfg.RecipeAPIURL, "recipe-api")
	ic := healthcheck.New(cfg.ImportAPIURL, "import-api")

	routerURL, err := url.Parse(cfg.RouterURL)
	if err != nil {
		log.Event(nil, "error parsing router URL", log.Error(err))
		os.Exit(1)
	}
	routerProxy := reverseProxy.Create(routerURL, director)

	zebedeeURL, err := url.Parse(cfg.ZebedeeURL)
	if err != nil {
		log.Event(nil, "error parsing zebedee URL", log.Error(err))
		os.Exit(1)
	}
	zebedeeProxy := reverseProxy.Create(zebedeeURL, zebedeeDirector)

	recipeAPIURL, err := url.Parse(cfg.RecipeAPIURL)
	if err != nil {
		log.Event(nil, "error parsing recipe API URL", log.Error(err))
		os.Exit(1)
	}
	recipeAPIProxy := reverseProxy.Create(recipeAPIURL, nil)

	tableURL, err := url.Parse(cfg.TableRendererURL)
	if err != nil {
		log.Event(nil, "error parsing table renderer URL", log.Error(err))
		os.Exit(1)
	}
	tableProxy := reverseProxy.Create(tableURL, tableDirector)

	importAPIURL, err := url.Parse(cfg.ImportAPIURL)
	if err != nil {
		log.Event(nil, "error parsing import API URL", log.Error(err))
		os.Exit(1)
	}
	importAPIProxy := reverseProxy.Create(importAPIURL, importAPIDirector)

	datasetAPIURL, err := url.Parse(cfg.DatasetAPIURL)
	if err != nil {
		log.Event(nil, "error parsing dataset API URL", log.Error(err))
		os.Exit(1)
	}
	datasetAPIProxy := reverseProxy.Create(datasetAPIURL, datasetAPIDirector)

	var vc upload.VaultClient
	if !cfg.EncryptionDisabled {
		vc, err = vault.CreateVaultClient(cfg.VaultToken, cfg.VaultAddr, 3)
		if err != nil {
			log.Event(nil, "error creating vault client", log.Error(err))
			os.Exit(1)
		}
	}

	uploader, err := upload.New(cfg.UploadBucketName, cfg.VaultPath, vc)
	if err != nil {
		log.Event(nil, "error creating file uploader", log.Error(err))
		os.Exit(1)
	}

	router := pat.New()

	router.Path("/healthcheck").HandlerFunc(hc.Do)

	if cfg.SharedConfig.EnableDatasetImport {
		router.Path("/upload").Methods("GET").HandlerFunc(uploader.CheckUploaded)
		router.Path("/upload").Methods("POST").HandlerFunc(uploader.Upload)
		router.Path("/upload/{id}").Methods("GET").HandlerFunc(uploader.GetS3URL)
		router.Handle("/recipes{uri:.*}", recipeAPIProxy)
		router.Handle("/import{uri:.*}", importAPIProxy)
		router.Handle("/dataset/{uri:.*}", datasetAPIProxy)
		router.Handle("/instances/{uri:.*}", datasetAPIProxy)
	}

	router.Handle("/zebedee{uri:/.*}", zebedeeProxy)
	router.Handle("/table/{uri:.*}", tableProxy)
	router.HandleFunc("/florence/dist/{uri:.*}", staticFiles)
	router.HandleFunc("/florence/", redirectToFlorence)
	router.HandleFunc("/florence/index.html", redirectToFlorence)
	router.Path("/florence/publishing-queue").HandlerFunc(legacyIndexFile(cfg))
	router.Path("/florence/reports").HandlerFunc(legacyIndexFile(cfg))
	router.Path("/florence/workspace").HandlerFunc(legacyIndexFile(cfg))
	router.HandleFunc("/florence/websocket", websocketHandler)
	router.Path("/florence{uri:.*}").HandlerFunc(refactoredIndexFile(cfg))
	router.Handle("/{uri:.*}", routerProxy)

	log.Event(nil, "starting server", log.Data{"config": cfg})

	s := server.New(cfg.BindAddr, router)
	// TODO need to reconsider default go-ns server timeouts
	s.Server.IdleTimeout = 120 * time.Second
	s.Server.WriteTimeout = 120 * time.Second
	s.Server.ReadTimeout = 30 * time.Second
	s.HandleOSSignals = false
	s.Middleware["RequestLogger"] = log.Middleware
	s.MiddlewareOrder = []string{"RequestID", "RequestLogger"}

	// FIXME temporary hack to remove timeout middleware (doesn't support hijacker interface)
	mo := s.MiddlewareOrder
	var newMo []string
	for _, mw := range mo {
		if mw != "Timeout" {
			newMo = append(newMo, mw)
		}
	}
	s.MiddlewareOrder = newMo

	go func() {
		if err := s.ListenAndServe(); err != nil {
			log.Event(nil, "error starting http server", log.Error(err))
			os.Exit(2)
		}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, os.Kill)

	for {
		hc.MonitorExternal(rtrc, zc, ic, rc, dc)

		timer := time.NewTimer(time.Second * 60)

		select {
		case <-timer.C:
			continue
		case <-stop:
			log.Event(nil, "shutting down gracefully")
			timer.Stop()
			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
			defer cancel()
			if err := s.Server.Shutdown(ctx); err != nil {
				log.Event(nil, "error shutting down gracefully", log.Error(err))
			}
			return
		}
	}
}

func redirectToFlorence(w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, "/florence", 301)
}

func staticFiles(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Query().Get(":uri")
	assetPath := "../dist/" + path

	etag, err := getAssetETag(assetPath)

	if hdr := req.Header.Get("If-None-Match"); len(hdr) > 0 && hdr == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	b, err := getAsset(assetPath)
	if err != nil {
		log.Event(req.Context(), "error getting asset", log.Error(err))
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`ETag`, etag)
	w.Header().Set(`Cache-Control`, "no-cache")
	w.Header().Set(`Content-Type`, mime.TypeByExtension(filepath.Ext(path)))
	w.WriteHeader(200)
	w.Write(b)
}

func legacyIndexFile(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		log.Event(req.Context(), "getting legacy html file")

		b, err := getAsset("../dist/legacy-assets/index.html")
		if err != nil {
			log.Event(req.Context(), "error getting legacy html file", log.Error(err))
			w.WriteHeader(404)
			return
		}

		cfgJSON, err := json.Marshal(cfg.SharedConfig)
		if err != nil {
			log.Event(req.Context(), "error marshalling shared configuration", log.Error(err))
			w.WriteHeader(500)
			return
		}
		b = []byte(strings.Replace(string(b), "/* environment variables placeholder */", "/* server generated shared config */ "+string(cfgJSON), 1))

		w.Header().Set(`Content-Type`, "text/html")
		w.WriteHeader(200)
		w.Write(b)
	}
}

func refactoredIndexFile(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		log.Event(req.Context(), "getting refactored html file")

		b, err := getAsset("../dist/refactored.html")
		if err != nil {
			log.Event(req.Context(), "error getting refactored html file", log.Error(err))
			w.WriteHeader(404)
			return
		}

		cfgJSON, err := json.Marshal(cfg.SharedConfig)
		if err != nil {
			log.Event(req.Context(), "error marshalling shared configuration", log.Error(err))
			w.WriteHeader(500)
			return
		}
		b = []byte(strings.Replace(string(b), "/* environment variables placeholder */", "/* server generated shared config */ "+string(cfgJSON), 1))

		w.Header().Set(`Content-Type`, "text/html")
		w.WriteHeader(200)
		w.Write(b)
	}
}

func zebedeeDirector(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/zebedee")
}

func director(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
}

func importAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/import")
}

func tableDirector(req *http.Request) {
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/table")
}

func datasetAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/dataset")
}

func websocketHandler(w http.ResponseWriter, req *http.Request) {
	c, err := upgrader.Upgrade(w, req, nil)
	if err != nil {
		log.Event(req.Context(), "error upgrading connection to websocket", log.Error(err))
		return
	}

	defer c.Close()

	err = c.WriteJSON(florenceServerEvent{"version", florenceVersionPayload{Version: Version}})
	if err != nil {
		log.Event(req.Context(), "error writing version message", log.Error(err))
		return
	}

	for {
		_, message, err := c.ReadMessage()
		if err != nil {
			log.Event(req.Context(), "error reading websocket message", log.Error(err))
			break
		}

		rdr := bufio.NewReader(bytes.NewReader(message))
		b, err := rdr.ReadBytes('{')
		if err != nil {
			log.Event(req.Context(), "error reading websocket bytes", log.Error(err), log.Data{"bytes": string(b)})
			continue
		}

		tags := strings.Split(string(b), ":")
		eventID := tags[0]
		eventType := tags[1]
		eventData := message[len(eventID)+len(eventType)+2:]

		switch eventType {
		case "log":
			var e florenceLogEvent
			e.ServerTimestamp = time.Now().UTC().Format("2006-01-02T15:04:05.000-0700Z")
			err = json.Unmarshal(eventData, &e)
			if err != nil {
				log.Event(req.Context(), "error unmarshalling websocket message", log.Error(err), log.Data{"data": string(eventData)})
				continue
			}
			log.Event(req.Context(), "client log", log.Data{"data": e})

			err = c.WriteJSON(florenceServerEvent{"ack", eventID})
			if err != nil {
				log.Event(req.Context(), "error writing websocket ack", log.Error(err))
			}
		default:
			log.Event(req.Context(), "unknown websocket event type", log.Data{"type": eventType, "data": string(eventData)})
		}
	}
}

type florenceLogEvent struct {
	ServerTimestamp string      `json:"-"`
	ClientTimestamp time.Time   `json:"clientTimestamp"`
	Type            string      `json:"type"`
	Location        string      `json:"location"`
	InstanceID      string      `json:"instanceID"`
	Payload         interface{} `json:"payload"`
}

type florenceServerEvent struct {
	Type    string      `json:"type"`
	Payload interface{} `json:"payload"`
}

type florenceVersionPayload struct {
	Version string `json:"version"`
}
<commit_msg>If collection cookei present set header<commit_after>package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"mime"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/ONSdigital/florence/assets"
	"github.com/ONSdigital/florence/config"
	"github.com/ONSdigital/florence/healthcheck"
	"github.com/ONSdigital/florence/upload"
	"github.com/ONSdigital/go-ns/common"
	"github.com/ONSdigital/go-ns/handlers/reverseProxy"
	hc "github.com/ONSdigital/go-ns/healthcheck"
	"github.com/ONSdigital/go-ns/server"
	"github.com/ONSdigital/go-ns/vault"
	"github.com/ONSdigital/log.go/log"
	"github.com/gorilla/pat"
	"github.com/gorilla/websocket"
)

var getAsset = assets.Asset
var getAssetETag = assets.GetAssetETag
var upgrader = websocket.Upgrader{}

// Version is set by the make target
var Version string

func main() {
	log.Namespace = "florence"
	log.Event(nil, "florence version", log.Data{"version": Version})

	cfg, err := config.Get()
	if err != nil {
		log.Event(nil, "error getting configuration", log.Error(err))
		os.Exit(1)
	}

	zc := healthcheck.New(cfg.ZebedeeURL, "zebedee")
	rtrc := healthcheck.New(cfg.RouterURL, "router")
	dc := healthcheck.New(cfg.DatasetAPIURL, "dataset-api")
	rc := healthcheck.New(cfg.RecipeAPIURL, "recipe-api")
	ic := healthcheck.New(cfg.ImportAPIURL, "import-api")

	routerURL, err := url.Parse(cfg.RouterURL)
	if err != nil {
		log.Event(nil, "error parsing router URL", log.Error(err))
		os.Exit(1)
	}
	routerProxy := reverseProxy.Create(routerURL, director)

	zebedeeURL, err := url.Parse(cfg.ZebedeeURL)
	if err != nil {
		log.Event(nil, "error parsing zebedee URL", log.Error(err))
		os.Exit(1)
	}
	zebedeeProxy := reverseProxy.Create(zebedeeURL, zebedeeDirector)

	recipeAPIURL, err := url.Parse(cfg.RecipeAPIURL)
	if err != nil {
		log.Event(nil, "error parsing recipe API URL", log.Error(err))
		os.Exit(1)
	}
	recipeAPIProxy := reverseProxy.Create(recipeAPIURL, nil)

	tableURL, err := url.Parse(cfg.TableRendererURL)
	if err != nil {
		log.Event(nil, "error parsing table renderer URL", log.Error(err))
		os.Exit(1)
	}
	tableProxy := reverseProxy.Create(tableURL, tableDirector)

	importAPIURL, err := url.Parse(cfg.ImportAPIURL)
	if err != nil {
		log.Event(nil, "error parsing import API URL", log.Error(err))
		os.Exit(1)
	}
	importAPIProxy := reverseProxy.Create(importAPIURL, importAPIDirector)

	datasetAPIURL, err := url.Parse(cfg.DatasetAPIURL)
	if err != nil {
		log.Event(nil, "error parsing dataset API URL", log.Error(err))
		os.Exit(1)
	}
	datasetAPIProxy := reverseProxy.Create(datasetAPIURL, datasetAPIDirector)

	var vc upload.VaultClient
	if !cfg.EncryptionDisabled {
		vc, err = vault.CreateVaultClient(cfg.VaultToken, cfg.VaultAddr, 3)
		if err != nil {
			log.Event(nil, "error creating vault client", log.Error(err))
			os.Exit(1)
		}
	}

	uploader, err := upload.New(cfg.UploadBucketName, cfg.VaultPath, vc)
	if err != nil {
		log.Event(nil, "error creating file uploader", log.Error(err))
		os.Exit(1)
	}

	router := pat.New()

	router.Path("/healthcheck").HandlerFunc(hc.Do)

	if cfg.SharedConfig.EnableDatasetImport {
		router.Path("/upload").Methods("GET").HandlerFunc(uploader.CheckUploaded)
		router.Path("/upload").Methods("POST").HandlerFunc(uploader.Upload)
		router.Path("/upload/{id}").Methods("GET").HandlerFunc(uploader.GetS3URL)
		router.Handle("/recipes{uri:.*}", recipeAPIProxy)
		router.Handle("/import{uri:.*}", importAPIProxy)
		router.Handle("/dataset/{uri:.*}", datasetAPIProxy)
		router.Handle("/instances/{uri:.*}", datasetAPIProxy)
	}

	router.Handle("/zebedee{uri:/.*}", zebedeeProxy)
	router.Handle("/table/{uri:.*}", tableProxy)
	router.HandleFunc("/florence/dist/{uri:.*}", staticFiles)
	router.HandleFunc("/florence/", redirectToFlorence)
	router.HandleFunc("/florence/index.html", redirectToFlorence)
	router.Path("/florence/publishing-queue").HandlerFunc(legacyIndexFile(cfg))
	router.Path("/florence/reports").HandlerFunc(legacyIndexFile(cfg))
	router.Path("/florence/workspace").HandlerFunc(legacyIndexFile(cfg))
	router.HandleFunc("/florence/websocket", websocketHandler)
	router.Path("/florence{uri:.*}").HandlerFunc(refactoredIndexFile(cfg))
	router.Handle("/{uri:.*}", routerProxy)

	log.Event(nil, "starting server", log.Data{"config": cfg})

	s := server.New(cfg.BindAddr, router)
	// TODO need to reconsider default go-ns server timeouts
	s.Server.IdleTimeout = 120 * time.Second
	s.Server.WriteTimeout = 120 * time.Second
	s.Server.ReadTimeout = 30 * time.Second
	s.HandleOSSignals = false
	s.Middleware["RequestLogger"] = log.Middleware
	s.MiddlewareOrder = []string{"RequestID", "RequestLogger"}

	// FIXME temporary hack to remove timeout middleware (doesn't support hijacker interface)
	mo := s.MiddlewareOrder
	var newMo []string
	for _, mw := range mo {
		if mw != "Timeout" {
			newMo = append(newMo, mw)
		}
	}
	s.MiddlewareOrder = newMo

	go func() {
		if err := s.ListenAndServe(); err != nil {
			log.Event(nil, "error starting http server", log.Error(err))
			os.Exit(2)
		}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, os.Kill)

	for {
		hc.MonitorExternal(rtrc, zc, ic, rc, dc)

		timer := time.NewTimer(time.Second * 60)

		select {
		case <-timer.C:
			continue
		case <-stop:
			log.Event(nil, "shutting down gracefully")
			timer.Stop()
			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
			defer cancel()
			if err := s.Server.Shutdown(ctx); err != nil {
				log.Event(nil, "error shutting down gracefully", log.Error(err))
			}
			return
		}
	}
}

func redirectToFlorence(w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, "/florence", 301)
}

func staticFiles(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Query().Get(":uri")
	assetPath := "../dist/" + path

	etag, err := getAssetETag(assetPath)

	if hdr := req.Header.Get("If-None-Match"); len(hdr) > 0 && hdr == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	b, err := getAsset(assetPath)
	if err != nil {
		log.Event(req.Context(), "error getting asset", log.Error(err))
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`ETag`, etag)
	w.Header().Set(`Cache-Control`, "no-cache")
	w.Header().Set(`Content-Type`, mime.TypeByExtension(filepath.Ext(path)))
	w.WriteHeader(200)
	w.Write(b)
}

func legacyIndexFile(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		log.Event(req.Context(), "getting legacy html file")

		b, err := getAsset("../dist/legacy-assets/index.html")
		if err != nil {
			log.Event(req.Context(), "error getting legacy html file", log.Error(err))
			w.WriteHeader(404)
			return
		}

		cfgJSON, err := json.Marshal(cfg.SharedConfig)
		if err != nil {
			log.Event(req.Context(), "error marshalling shared configuration", log.Error(err))
			w.WriteHeader(500)
			return
		}
		b = []byte(strings.Replace(string(b), "/* environment variables placeholder */", "/* server generated shared config */ "+string(cfgJSON), 1))

		w.Header().Set(`Content-Type`, "text/html")
		w.WriteHeader(200)
		w.Write(b)
	}
}

func refactoredIndexFile(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		log.Event(req.Context(), "getting refactored html file")

		b, err := getAsset("../dist/refactored.html")
		if err != nil {
			log.Event(req.Context(), "error getting refactored html file", log.Error(err))
			w.WriteHeader(404)
			return
		}

		cfgJSON, err := json.Marshal(cfg.SharedConfig)
		if err != nil {
			log.Event(req.Context(), "error marshalling shared configuration", log.Error(err))
			w.WriteHeader(500)
			return
		}
		b = []byte(strings.Replace(string(b), "/* environment variables placeholder */", "/* server generated shared config */ "+string(cfgJSON), 1))

		w.Header().Set(`Content-Type`, "text/html")
		w.WriteHeader(200)
		w.Write(b)
	}
}

func zebedeeDirector(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/zebedee")
}

func director(req *http.Request) {
	if accessTokenCookie, err := req.Cookie(`access_token`); err == nil && len(accessTokenCookie.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, accessTokenCookie.Value)
	}

	if colletionCookie, err := req.Cookie(common.CollectionIDCookieKey); err == nil && len(colletionCookie.Value) > 0 {
		req.Header.Set(common.CollectionIDHeaderKey, colletionCookie.Value)
	}
}

func importAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/import")
}

func tableDirector(req *http.Request) {
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/table")
}

func datasetAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/dataset")
}

func websocketHandler(w http.ResponseWriter, req *http.Request) {
	c, err := upgrader.Upgrade(w, req, nil)
	if err != nil {
		log.Event(req.Context(), "error upgrading connection to websocket", log.Error(err))
		return
	}

	defer c.Close()

	err = c.WriteJSON(florenceServerEvent{"version", florenceVersionPayload{Version: Version}})
	if err != nil {
		log.Event(req.Context(), "error writing version message", log.Error(err))
		return
	}

	for {
		_, message, err := c.ReadMessage()
		if err != nil {
			log.Event(req.Context(), "error reading websocket message", log.Error(err))
			break
		}

		rdr := bufio.NewReader(bytes.NewReader(message))
		b, err := rdr.ReadBytes('{')
		if err != nil {
			log.Event(req.Context(), "error reading websocket bytes", log.Error(err), log.Data{"bytes": string(b)})
			continue
		}

		tags := strings.Split(string(b), ":")
		eventID := tags[0]
		eventType := tags[1]
		eventData := message[len(eventID)+len(eventType)+2:]

		switch eventType {
		case "log":
			var e florenceLogEvent
			e.ServerTimestamp = time.Now().UTC().Format("2006-01-02T15:04:05.000-0700Z")
			err = json.Unmarshal(eventData, &e)
			if err != nil {
				log.Event(req.Context(), "error unmarshalling websocket message", log.Error(err), log.Data{"data": string(eventData)})
				continue
			}
			log.Event(req.Context(), "client log", log.Data{"data": e})

			err = c.WriteJSON(florenceServerEvent{"ack", eventID})
			if err != nil {
				log.Event(req.Context(), "error writing websocket ack", log.Error(err))
			}
		default:
			log.Event(req.Context(), "unknown websocket event type", log.Data{"type": eventType, "data": string(eventData)})
		}
	}
}

type florenceLogEvent struct {
	ServerTimestamp string      `json:"-"`
	ClientTimestamp time.Time   `json:"clientTimestamp"`
	Type            string      `json:"type"`
	Location        string      `json:"location"`
	InstanceID      string      `json:"instanceID"`
	Payload         interface{} `json:"payload"`
}

type florenceServerEvent struct {
	Type    string      `json:"type"`
	Payload interface{} `json:"payload"`
}

type florenceVersionPayload struct {
	Version string `json:"version"`
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"os/signal"
	"sync"

	"github.com/Shopify/sarama"
)

func main() {

	consumerBrokers := []string{"kafka-broker-01.ana.kochava.com:9092", "kafka-broker-02.ana.kochava.com:9092", "kafka-broker-03.ana.kochava.com:9092"}
	producerBrokers := []string{"analytics-kafka-v000-9lzw:9092", "analytics-kafka-v000-u40u:9092", "analytics-kafka-v000-wrdq:9092"}

	logFile, err := os.OpenFile("/var/log/firehose/firehose.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalln("Failed to open log file:", err)
	}
	defer logFile.Close()

	log.SetOutput(logFile)

	metricsFile, err := os.OpenFile("/var/log/firehose/metrics.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalln("Failed to open log file:", err)
	}
	defer metricsFile.Close()

	log.Println("Getting the Kafka consumer")
	consumer, err := GetKafkaConsumer(consumerBrokers, metricsFile)
	if err != nil {
		log.Println("Unable to create consumer", err)
	}

	log.Println("Getting the Kafka producer")
	producer, err := GetKafkaProducer(producerBrokers, metricsFile)
	if err != nil {
		log.Println("Unable to create producer", err)
	}

	defer CloseConsumer(consumer)
	defer CloseProducer(producer)

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)

	var wg sync.WaitGroup

	topic := "events"

	transferChan := make(chan sarama.ProducerMessage, 100000)
	for partition := 0; partition < 24; partition++ {

		partitionConsumer, err := consumer.ConsumePartition(topic, int32(partition), sarama.OffsetNewest)
		if err != nil {
			panic(err)
		}

		wg.Add(1)

		go PullFromTopic(partitionConsumer, transferChan, signals, &wg)
		log.Println("Started consumer for partition ", partition)

		wg.Add(2)
		go PushToTopic(producer, transferChan, signals, &wg)
		go PushToTopic(producer, transferChan, signals, &wg)
		log.Println("Started producer")
	}

	go MonitorChan(transferChan, signals)

	wg.Wait()
}
<commit_msg>Log start for every producer thread<commit_after>package main

import (
	"log"
	"os"
	"os/signal"
	"sync"

	"github.com/Shopify/sarama"
)

func main() {

	consumerBrokers := []string{"kafka-broker-01.ana.kochava.com:9092", "kafka-broker-02.ana.kochava.com:9092", "kafka-broker-03.ana.kochava.com:9092"}
	producerBrokers := []string{"analytics-kafka-v000-9lzw:9092", "analytics-kafka-v000-u40u:9092", "analytics-kafka-v000-wrdq:9092"}

	logFile, err := os.OpenFile("/var/log/firehose/firehose.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalln("Failed to open log file:", err)
	}
	defer logFile.Close()

	log.SetOutput(logFile)

	metricsFile, err := os.OpenFile("/var/log/firehose/metrics.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalln("Failed to open log file:", err)
	}
	defer metricsFile.Close()

	log.Println("Getting the Kafka consumer")
	consumer, err := GetKafkaConsumer(consumerBrokers, metricsFile)
	if err != nil {
		log.Println("Unable to create consumer", err)
	}

	log.Println("Getting the Kafka producer")
	producer, err := GetKafkaProducer(producerBrokers, metricsFile)
	if err != nil {
		log.Println("Unable to create producer", err)
	}

	defer CloseConsumer(consumer)
	defer CloseProducer(producer)

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)

	var wg sync.WaitGroup

	topic := "events"

	transferChan := make(chan sarama.ProducerMessage, 100000)
	for partition := 0; partition < 24; partition++ {

		partitionConsumer, err := consumer.ConsumePartition(topic, int32(partition), sarama.OffsetNewest)
		if err != nil {
			panic(err)
		}

		wg.Add(1)

		go PullFromTopic(partitionConsumer, transferChan, signals, &wg)
		log.Println("Started consumer for partition ", partition)

		wg.Add(2)
		go PushToTopic(producer, transferChan, signals, &wg)
		log.Println("Started producer")
		go PushToTopic(producer, transferChan, signals, &wg)
		log.Println("Started producer")
	}

	go MonitorChan(transferChan, signals)

	wg.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/codegangsta/cli"

	"github.com/yudai/gotty/app"
)

func main() {
	cmd := cli.NewApp()
	cmd.Version = "0.0.3"
	cmd.Name = "gotty"
	cmd.Usage = "Share your terminal as a web application"
	cmd.HideHelp = true
	cmd.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "addr, a",
			Value:  "",
			Usage:  "IP address to listen",
			EnvVar: "GOTTY_ADDR",
		},
		cli.StringFlag{
			Name:   "port, p",
			Value:  "8080",
			Usage:  "Port number to listen",
			EnvVar: "GOTTY_PORT",
		},
		cli.BoolFlag{
			Name:   "permit-write, w",
			Usage:  "Permit clients to write to the TTY (BE CAREFUL)",
			EnvVar: "GOTTY_PERMIT_WRITE",
		},
		cli.StringFlag{
			Name:   "credential, c",
			Usage:  "Credential for Basic Authentication (ex: user:pass)",
			EnvVar: "GOTTY_CREDENTIAL",
		},
		cli.BoolFlag{
			Name:   "random-url, r",
			Usage:  "Add a random string to the URL",
			EnvVar: "GOTTY_RANDOM_URL",
		},
		cli.StringFlag{
			Name:   "title-format",
			Value:  "GoTTY - {{ .Command }} ({{ .Hostname }})",
			Usage:  "Title format of browser window",
			EnvVar: "GOTTY_title-format",
		},
	}
	cmd.Action = func(c *cli.Context) {
		if len(c.Args()) == 0 {
			fmt.Println("Error: No command given.\n")
			cli.ShowAppHelp(c)
			os.Exit(1)
		}

		app, err := app.New(
			app.Options{
				c.String("addr"),
				c.String("port"),
				c.Bool("permit-write"),
				c.String("credential"),
				c.Bool("random-url"),
				c.String("title-format"),
				c.Args(),
			},
		)
		if err != nil {
			fmt.Println(err)
			os.Exit(2)
		}

		err = app.Run()
		if err != nil {
			fmt.Println(err)
			os.Exit(3)
		}
	}

	cli.AppHelpTemplate = helpTemplate

	cmd.Run(os.Args)
}
<commit_msg>Fix GOTTY_TITLE_FORMAT<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/codegangsta/cli"

	"github.com/yudai/gotty/app"
)

func main() {
	cmd := cli.NewApp()
	cmd.Version = "0.0.3"
	cmd.Name = "gotty"
	cmd.Usage = "Share your terminal as a web application"
	cmd.HideHelp = true
	cmd.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "addr, a",
			Value:  "",
			Usage:  "IP address to listen",
			EnvVar: "GOTTY_ADDR",
		},
		cli.StringFlag{
			Name:   "port, p",
			Value:  "8080",
			Usage:  "Port number to listen",
			EnvVar: "GOTTY_PORT",
		},
		cli.BoolFlag{
			Name:   "permit-write, w",
			Usage:  "Permit clients to write to the TTY (BE CAREFUL)",
			EnvVar: "GOTTY_PERMIT_WRITE",
		},
		cli.StringFlag{
			Name:   "credential, c",
			Usage:  "Credential for Basic Authentication (ex: user:pass)",
			EnvVar: "GOTTY_CREDENTIAL",
		},
		cli.BoolFlag{
			Name:   "random-url, r",
			Usage:  "Add a random string to the URL",
			EnvVar: "GOTTY_RANDOM_URL",
		},
		cli.StringFlag{
			Name:   "title-format",
			Value:  "GoTTY - {{ .Command }} ({{ .Hostname }})",
			Usage:  "Title format of browser window",
			EnvVar: "GOTTY_TITLE_FORMAT",
		},
	}
	cmd.Action = func(c *cli.Context) {
		if len(c.Args()) == 0 {
			fmt.Println("Error: No command given.\n")
			cli.ShowAppHelp(c)
			os.Exit(1)
		}

		app, err := app.New(
			app.Options{
				c.String("addr"),
				c.String("port"),
				c.Bool("permit-write"),
				c.String("credential"),
				c.Bool("random-url"),
				c.String("title-format"),
				c.Args(),
			},
		)
		if err != nil {
			fmt.Println(err)
			os.Exit(2)
		}

		err = app.Run()
		if err != nil {
			fmt.Println(err)
			os.Exit(3)
		}
	}

	cli.AppHelpTemplate = helpTemplate

	cmd.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"go-nikto"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"strings"

	"github.com/lair-framework/api-server/client"
	"github.com/lair-framework/go-lair"
)

const (
	version = "2.0.1"
	tool    = "nikto"
	usage   = `
Usage:
  drone-nikto <id> <filename>
  export LAIR_ID=<id>; drone-nikto <filename>
Options:
  -v              show version and exit
  -h              show usage and exit
  -k              allow insecure SSL connections
  -force-ports    disable data protection in the API server for excessive ports
  -tags           a comma separated list of tags to add to every host that is imported
`
)

func buildProject(nikto *nikto.NiktoData, exproject *lair.Project, tags []string) (map[string]bool, error) {
	bNotFound := make(map[string]bool)
	exproject.Tool = tool
	var command string

	for _, scan := range nikto.NiktoScan {
		for _, item := range scan.ScanDetails {
			command = scan.Options

			// Confirm if Host and Port existing in current project
			found := false
			hname := false
			for x, h := range exproject.Hosts {
				if item.TargetIP == h.IPv4 {
					for y, p := range h.Services {
						if item.TargetPort == p.Port {
							found = true

							// Append host tags
							exproject.Hosts[x].Tags = append(h.Tags, tags...)

							// Update Modified By
							h.LastModifiedBy = tool
							p.LastModifiedBy = tool

							// Append Note information to Service
							note := &lair.Note{
								Title:          fmt.Sprintf("Nikto v%v (%v:%v)", scan.Version, item.HostHeader, item.TargetPort),
								LastModifiedBy: tool,
							}

							scheme, err := url.Parse(item.TargetHostname)
							if err != nil {
								return bNotFound, err
							}
							if scheme.Scheme == "https" {
								note.Content = fmt.Sprintf("SSL Information:\nSubject: %v\nCiphers: %v\nIssuer: %v\n\n", item.SSL.Info, item.SSL.Ciphers, item.SSL.Issuers)
							}

							for _, item := range item.Items {
								if item.OSVDBID > 0 {
									note.Content += fmt.Sprintf("%v URI: %v OSVDBID: %v\n", item.Description, item.URI, item.OSVDBID)
								} else {
									note.Content += fmt.Sprintf("%v URI: %v\n", item.Description, item.URI)
								}
							}
							note.Content += fmt.Sprintf("\nStart: %s End: %s", scan.ScanStart, scan.ScanEnd)
							exproject.Hosts[x].Services[y].Notes = append(p.Notes, *note)
						}
					}
					// Check for existing hostname and append
					for _, n := range h.Hostnames {
						if n == item.TargetHostname {
							hname = true
						}
					}
					if !hname && (item.TargetHostname != item.TargetIP) {
						exproject.Hosts[x].Hostnames = append(h.Hostnames, item.TargetHostname)
					}
				}
			}
			if !found {
				bNotFound[fmt.Sprintf("%s (%s:%v)", item.TargetHostname, item.TargetIP, item.TargetPort)] = true
				continue
			}
		}
	}
	// Update project with Nikto command
	var com []lair.Command
	com = append(com, lair.Command{
		Tool:    tool,
		Command: command,
	})
	exproject.Commands = com

	return bNotFound, nil
}

func main() {
	showVersion := flag.Bool("v", false, "")
	insecureSSL := flag.Bool("k", false, "")
	forcePorts := flag.Bool("force-ports", false, "")
	tags := flag.String("tags", "", "")
	flag.Usage = func() {
		fmt.Println(usage)
	}
	flag.Parse()
	if *showVersion {
		log.Println(version)
		os.Exit(0)
	}
	lairURL := os.Getenv("LAIR_API_SERVER")
	if lairURL == "" {
		log.Fatal("Fatal: Missing LAIR_API_SERVER environment variable")
	}
	lairPID := os.Getenv("LAIR_ID")
	var filename string
	switch len(flag.Args()) {
	case 2:
		lairPID = flag.Arg(0)
		filename = flag.Arg(1)
	case 1:
		filename = flag.Arg(0)
	default:
		log.Fatal("Fatal: Missing required argument")
	}

	u, err := url.Parse(lairURL)
	if err != nil {
		log.Fatalf("Fatal: Error parsing LAIR_API_SERVER URL. Error %s", err.Error())
	}
	if u.User == nil {
		log.Fatal("Fatal: Missing username and/or password")
	}
	user := u.User.Username()
	pass, _ := u.User.Password()
	if user == "" || pass == "" {
		log.Fatal("Fatal: Missing username and/or password")
	}
	c, err := client.New(&client.COptions{
		User:               user,
		Password:           pass,
		Host:               u.Host,
		Scheme:             u.Scheme,
		InsecureSkipVerify: *insecureSSL,
	})
	if err != nil {
		log.Fatalf("Fatal: Error setting up client. Error %s", err.Error())
	}

	buf, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatalf("Fatal: Could not open file. Error %s", err.Error())
	}

	niktoData, err := nikto.Parse(buf)
	if err != nil {
		log.Fatalf("Fatal: Error parsing nikto data. Error %s", err.Error())
	}

	hostTags := []string{}
	if *tags != "" {
		hostTags = strings.Split(*tags, ",")
	}

	project, err := c.ExportProject(lairPID)
	if err != nil {
		log.Fatalf("Fatal: Unable to export project. Error %s", err.Error())
	}
	bNotFound, err := buildProject(niktoData, &project, hostTags)
	if err != nil {
		log.Fatal(err.Error())
	}

	res, err := c.ImportProject(&client.DOptions{ForcePorts: *forcePorts}, &project)
	if err != nil {
		log.Fatalf("Fatal: Unable to import project. Error %s", err)
	}

	defer res.Body.Close()
	droneRes := &client.Response{}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatalf("Fatal: Error %s", err.Error())
	}
	if err := json.Unmarshal(body, droneRes); err != nil {
		log.Fatalf("Fatal: Could not unmarshal JSON. Error %s", err.Error())
	}
	if droneRes.Status == "Error" {
		log.Fatalf("Fatal: Import failed. Error %s", droneRes.Message)
	}

	if len(bNotFound) > 0 {
		log.Printf("Info: The following host ports contained Nikto results but did not exist in project %s", lairPID)
	}
	for k := range bNotFound {
		fmt.Println(k)
	}

	log.Println("Success: Operation completed successfully")
}
<commit_msg>Correct imports<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"strings"

	"github.com/lair-framework/api-server/client"
	"github.com/lair-framework/go-lair"
	"github.com/mattburch/go-nikto"
)

const (
	version = "2.0.1"
	tool    = "nikto"
	usage   = `
Usage:
  drone-nikto <id> <filename>
  export LAIR_ID=<id>; drone-nikto <filename>
Options:
  -v              show version and exit
  -h              show usage and exit
  -k              allow insecure SSL connections
  -force-ports    disable data protection in the API server for excessive ports
  -tags           a comma separated list of tags to add to every host that is imported
`
)

func buildProject(nikto *nikto.NiktoData, exproject *lair.Project, tags []string) (map[string]bool, error) {
	bNotFound := make(map[string]bool)
	exproject.Tool = tool
	var command string

	for _, scan := range nikto.NiktoScan {
		for _, item := range scan.ScanDetails {
			command = scan.Options

			// Confirm if Host and Port existing in current project
			found := false
			hname := false
			for x, h := range exproject.Hosts {
				if item.TargetIP == h.IPv4 {
					for y, p := range h.Services {
						if item.TargetPort == p.Port {
							found = true

							// Append host tags
							exproject.Hosts[x].Tags = append(h.Tags, tags...)

							// Update Modified By
							h.LastModifiedBy = tool
							p.LastModifiedBy = tool

							// Append Note information to Service
							note := &lair.Note{
								Title:          fmt.Sprintf("Nikto v%v (%v:%v)", scan.Version, item.HostHeader, item.TargetPort),
								LastModifiedBy: tool,
							}

							scheme, err := url.Parse(item.TargetHostname)
							if err != nil {
								return bNotFound, err
							}
							if scheme.Scheme == "https" {
								note.Content = fmt.Sprintf("SSL Information:\nSubject: %v\nCiphers: %v\nIssuer: %v\n\n", item.SSL.Info, item.SSL.Ciphers, item.SSL.Issuers)
							}

							for _, item := range item.Items {
								if item.OSVDBID > 0 {
									note.Content += fmt.Sprintf("%v URI: %v OSVDBID: %v\n", item.Description, item.URI, item.OSVDBID)
								} else {
									note.Content += fmt.Sprintf("%v URI: %v\n", item.Description, item.URI)
								}
							}
							note.Content += fmt.Sprintf("\nStart: %s End: %s", scan.ScanStart, scan.ScanEnd)
							exproject.Hosts[x].Services[y].Notes = append(p.Notes, *note)
						}
					}
					// Check for existing hostname and append
					for _, n := range h.Hostnames {
						if n == item.TargetHostname {
							hname = true
						}
					}
					if !hname && (item.TargetHostname != item.TargetIP) {
						exproject.Hosts[x].Hostnames = append(h.Hostnames, item.TargetHostname)
					}
				}
			}
			if !found {
				bNotFound[fmt.Sprintf("%s (%s:%v)", item.TargetHostname, item.TargetIP, item.TargetPort)] = true
				continue
			}
		}
	}
	// Update project with Nikto command
	var com []lair.Command
	com = append(com, lair.Command{
		Tool:    tool,
		Command: command,
	})
	exproject.Commands = com

	return bNotFound, nil
}

func main() {
	showVersion := flag.Bool("v", false, "")
	insecureSSL := flag.Bool("k", false, "")
	forcePorts := flag.Bool("force-ports", false, "")
	tags := flag.String("tags", "", "")
	flag.Usage = func() {
		fmt.Println(usage)
	}
	flag.Parse()
	if *showVersion {
		log.Println(version)
		os.Exit(0)
	}
	lairURL := os.Getenv("LAIR_API_SERVER")
	if lairURL == "" {
		log.Fatal("Fatal: Missing LAIR_API_SERVER environment variable")
	}
	lairPID := os.Getenv("LAIR_ID")
	var filename string
	switch len(flag.Args()) {
	case 2:
		lairPID = flag.Arg(0)
		filename = flag.Arg(1)
	case 1:
		filename = flag.Arg(0)
	default:
		log.Fatal("Fatal: Missing required argument")
	}

	u, err := url.Parse(lairURL)
	if err != nil {
		log.Fatalf("Fatal: Error parsing LAIR_API_SERVER URL. Error %s", err.Error())
	}
	if u.User == nil {
		log.Fatal("Fatal: Missing username and/or password")
	}
	user := u.User.Username()
	pass, _ := u.User.Password()
	if user == "" || pass == "" {
		log.Fatal("Fatal: Missing username and/or password")
	}
	c, err := client.New(&client.COptions{
		User:               user,
		Password:           pass,
		Host:               u.Host,
		Scheme:             u.Scheme,
		InsecureSkipVerify: *insecureSSL,
	})
	if err != nil {
		log.Fatalf("Fatal: Error setting up client. Error %s", err.Error())
	}

	buf, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatalf("Fatal: Could not open file. Error %s", err.Error())
	}

	niktoData, err := nikto.Parse(buf)
	if err != nil {
		log.Fatalf("Fatal: Error parsing nikto data. Error %s", err.Error())
	}

	hostTags := []string{}
	if *tags != "" {
		hostTags = strings.Split(*tags, ",")
	}

	project, err := c.ExportProject(lairPID)
	if err != nil {
		log.Fatalf("Fatal: Unable to export project. Error %s", err.Error())
	}
	bNotFound, err := buildProject(niktoData, &project, hostTags)
	if err != nil {
		log.Fatal(err.Error())
	}

	res, err := c.ImportProject(&client.DOptions{ForcePorts: *forcePorts}, &project)
	if err != nil {
		log.Fatalf("Fatal: Unable to import project. Error %s", err)
	}

	defer res.Body.Close()
	droneRes := &client.Response{}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatalf("Fatal: Error %s", err.Error())
	}
	if err := json.Unmarshal(body, droneRes); err != nil {
		log.Fatalf("Fatal: Could not unmarshal JSON. Error %s", err.Error())
	}
	if droneRes.Status == "Error" {
		log.Fatalf("Fatal: Import failed. Error %s", droneRes.Message)
	}

	if len(bNotFound) > 0 {
		log.Printf("Info: The following host ports contained Nikto results but did not exist in project %s", lairPID)
	}
	for k := range bNotFound {
		fmt.Println(k)
	}

	log.Println("Success: Operation completed successfully")
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	log "github.com/Sirupsen/logrus"
	"github.com/joho/godotenv"
	"github.com/urfave/cli"
)

func main() {
	// Load env-file if it exists first
	if env := os.Getenv("PLUGIN_ENV_FILE"); env != "" {
		godotenv.Load(env)
	}

	app := cli.NewApp()
	app.Name = "email plugin"
	app.Usage = "email plugin"
	app.Action = run
	app.Version = "2.0.2"
	app.Flags = []cli.Flag{
		// Plugin environment
		cli.StringFlag{
			Name:   "from",
			Usage:  "from address",
			EnvVar: "PLUGIN_FROM",
		},
		cli.StringFlag{
			Name:   "host",
			Usage:  "smtp host",
			EnvVar: "EMAIL_HOST,PLUGIN_HOST",
		},
		cli.IntFlag{
			Name:   "port",
			Value:  DefaultPort,
			Usage:  "smtp port",
			EnvVar: "EMAIL_PORT,PLUGIN_PORT",
		},
		cli.StringFlag{
			Name:   "username",
			Usage:  "smtp server username",
			EnvVar: "EMAIL_USERNAME,PLUGIN_USERNAME",
		},
		cli.StringFlag{
			Name:   "password",
			Usage:  "smtp server password",
			EnvVar: "EMAIL_PASSWORD,PLUGIN_PASSWORD",
		},
		cli.BoolFlag{
			Name:   "skip.verify",
			Usage:  "skip tls verify",
			EnvVar: "PLUGIN_SKIP_VERIFY",
		},
		cli.StringSliceFlag{
			Name:   "recipients",
			Usage:  "recipient addresses",
			EnvVar: "EMAIL_RECIPIENTS,PLUGIN_RECIPIENTS",
		},
		cli.BoolFlag{
			Name:   "recipients.only",
			Usage:  "send to recipients only",
			EnvVar: "PLUGIN_RECIPIENTS_ONLY",
		},
		cli.StringFlag{
			Name:   "template.subject",
			Value:  DefaultSubject,
			Usage:  "subject template",
			EnvVar: "PLUGIN_SUBJECT",
		},
		cli.StringFlag{
			Name:   "template.body",
			Value:  DefaultTemplate,
			Usage:  "body template",
			EnvVar: "PLUGIN_BODY",
		},
		cli.StringFlag{
			Name:   "attachment",
			Usage:  "attachment filename",
			EnvVar: "PLUGIN_ATTACHMENT",
		},
		cli.StringSliceFlag{
			Name:   "attachments",
			Usage:  "attachment filename(s)",
			EnvVar: "PLUGIN_ATTACHMENTS",
		},
    cli.StringFlag{
			Name:   "clienthostname",
			Value:  DefaultClientHostname,
			Usage:  "smtp client hostname",
			EnvVar: "EMAIL_CLIENTHOSTNAME,PLUGIN_CLIENTHOSTNAME",
		},

		// Drone environment
		// Repo
		cli.StringFlag{
			Name:   "repo.fullName",
			Usage:  "repository full name",
			EnvVar: "DRONE_REPO",
		},
		cli.StringFlag{
			Name:   "repo.owner",
			Usage:  "repository owner",
			EnvVar: "DRONE_REPO_OWNER",
		},
		cli.StringFlag{
			Name:   "repo.name",
			Usage:  "repository name",
			EnvVar: "DRONE_REPO_NAME",
		},
		cli.StringFlag{
			Name:   "repo.scm",
			Value:  "git",
			Usage:  "respository scm",
			EnvVar: "DRONE_REPO_SCM",
		},
		cli.StringFlag{
			Name:   "repo.link",
			Usage:  "repository link",
			EnvVar: "DRONE_REPO_LINK",
		},
		cli.StringFlag{
			Name:   "repo.avatar",
			Usage:  "repository avatar",
			EnvVar: "DRONE_REPO_AVATAR",
		},
		cli.StringFlag{
			Name:   "repo.branch",
			Value:  "master",
			Usage:  "repository default branch",
			EnvVar: "DRONE_REPO_BRANCH",
		},
		cli.BoolFlag{
			Name:   "repo.private",
			Usage:  "repository is private",
			EnvVar: "DRONE_REPO_PRIVATE",
		},
		cli.BoolFlag{
			Name:   "repo.trusted",
			Usage:  "repository is trusted",
			EnvVar: "DRONE_REPO_TRUSTED",
		},

		// Remote
		cli.StringFlag{
			Name:   "remote.url",
			Usage:  "repository clone url",
			EnvVar: "DRONE_REMOTE_URL",
		},

		// Commit
		cli.StringFlag{
			Name:   "commit.sha",
			Usage:  "git commit sha",
			EnvVar: "DRONE_COMMIT_SHA",
		},
		cli.StringFlag{
			Name:   "commit.ref",
			Value:  "refs/heads/master",
			Usage:  "git commit ref",
			EnvVar: "DRONE_COMMIT_REF",
		},
		cli.StringFlag{
			Name:   "commit.branch",
			Value:  "master",
			Usage:  "git commit branch",
			EnvVar: "DRONE_COMMIT_BRANCH",
		},
		cli.StringFlag{
			Name:   "commit.link",
			Usage:  "commit link",
			EnvVar: "DRONE_COMMIT_LINK",
		},
		cli.StringFlag{
			Name:   "commit.message",
			Usage:  "git commit message",
			EnvVar: "DRONE_COMMIT_MESSAGE",
		},
		cli.StringFlag{
			Name:   "commit.author.name",
			Usage:  "git author name",
			EnvVar: "DRONE_COMMIT_AUTHOR",
		},
		cli.StringFlag{
			Name:   "commit.author.email",
			Usage:  "git author email",
			EnvVar: "DRONE_COMMIT_AUTHOR_EMAIL",
		},
		cli.StringFlag{
			Name:   "commit.author.avatar",
			Usage:  "git author avatar",
			EnvVar: "DRONE_COMMIT_AUTHOR_AVATAR",
		},

		// Build
		cli.IntFlag{
			Name:   "build.number",
			Usage:  "build number",
			EnvVar: "DRONE_BUILD_NUMBER",
		},
		cli.StringFlag{
			Name:   "build.event",
			Value:  "push",
			Usage:  "build event",
			EnvVar: "DRONE_BUILD_EVENT",
		},
		cli.StringFlag{
			Name:   "build.status",
			Usage:  "build status",
			Value:  "success",
			EnvVar: "DRONE_BUILD_STATUS",
		},
		cli.StringFlag{
			Name:   "build.link",
			Usage:  "build link",
			EnvVar: "DRONE_BUILD_LINK",
		},
		cli.Int64Flag{
			Name:   "build.created",
			Usage:  "build created",
			EnvVar: "DRONE_BUILD_CREATED",
		},
		cli.Int64Flag{
			Name:   "build.started",
			Usage:  "build started",
			EnvVar: "DRONE_BUILD_STARTED",
		},
		cli.Int64Flag{
			Name:   "build.finished",
			Usage:  "build finished",
			EnvVar: "DRONE_BUILD_FINISHED",
		},

		// Prev
		cli.StringFlag{
			Name:   "prev.build.status",
			Usage:  "prior build status",
			EnvVar: "DRONE_PREV_BUILD_STATUS",
		},
		cli.IntFlag{
			Name:   "prev.build.number",
			Usage:  "prior build number",
			EnvVar: "DRONE_PREV_BUILD_NUMBER",
		},
		cli.StringFlag{
			Name:   "prev.commit.sha",
			Usage:  "prior commit sha",
			EnvVar: "DRONE_PREV_COMMIT_SHA",
		},

		// Job
		cli.IntFlag{
			Name:   "job.number",
			Usage:  "job number",
			EnvVar: "DRONE_JOB_NUMBER",
		},
		cli.StringFlag{
			Name:   "job.status",
			Usage:  "job status",
			EnvVar: "DRONE_JOB_STATUS",
		},
		cli.IntFlag{
			Name:   "job.exitCode",
			Usage:  "job exit code",
			EnvVar: "DRONE_JOB_EXIT_CODE",
		},
		cli.Int64Flag{
			Name:   "job.started",
			Usage:  "job started",
			EnvVar: "DRONE_JOB_STARTED",
		},
		cli.Int64Flag{
			Name:   "job.finished",
			Usage:  "job finished",
			EnvVar: "DRONE_JOB_FINISHED",
		},

		// Yaml
		cli.BoolFlag{
			Name:   "yaml.signed",
			Usage:  "yaml is signed",
			EnvVar: "DRONE_YAML_SIGNED",
		},
		cli.BoolFlag{
			Name:   "yaml.verified",
			Usage:  "yaml is signed and verified",
			EnvVar: "DRONE_YAML_VERIFIED",
		},

		// Tag
		cli.StringFlag{
			Name:   "tag",
			Usage:  "git tag",
			EnvVar: "DRONE_TAG",
		},

		// PullRequest
		cli.IntFlag{
			Name:   "pullRequest",
			Usage:  "pull request number",
			EnvVar: "DRONE_PULL_REQUEST",
		},

		// DeployTo
		cli.StringFlag{
			Name:   "deployTo",
			Usage:  "deployment target",
			EnvVar: "DRONE_DEPLOY_TO",
		},
	}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}

func run(c *cli.Context) error {
	plugin := Plugin{
		Repo: Repo{
			FullName: c.String("repo.fullName"),
			Owner:    c.String("repo.owner"),
			Name:     c.String("repo.name"),
			SCM:      c.String("repo.scm"),
			Link:     c.String("repo.link"),
			Avatar:   c.String("repo.avatar"),
			Branch:   c.String("repo.branch"),
			Private:  c.Bool("repo.private"),
			Trusted:  c.Bool("repo.trusted"),
		},
		Remote: Remote{
			URL: c.String("remote.url"),
		},
		Commit: Commit{
			Sha:     c.String("commit.sha"),
			Ref:     c.String("commit.ref"),
			Branch:  c.String("commit.branch"),
			Link:    c.String("commit.link"),
			Message: c.String("commit.message"),
			Author: Author{
				Name:   c.String("commit.author.name"),
				Email:  c.String("commit.author.email"),
				Avatar: c.String("commit.author.avatar"),
			},
		},
		Build: Build{
			Number:   c.Int("build.number"),
			Event:    c.String("build.event"),
			Status:   c.String("build.status"),
			Link:     c.String("build.link"),
			Created:  c.Int64("build.created"),
			Started:  c.Int64("build.started"),
			Finished: c.Int64("build.finished"),
		},
		Prev: Prev{
			Build: PrevBuild{
				Status: c.String("prev.build.status"),
				Number: c.Int("prev.build.number"),
			},
			Commit: PrevCommit{
				Sha: c.String("prev.commit.sha"),
			},
		},
		Job: Job{
			Status:   c.String("job.status"),
			ExitCode: c.Int("job.exitCode"),
			Started:  c.Int64("job.started"),
			Finished: c.Int64("job.finished"),
		},
		Yaml: Yaml{
			Signed:   c.Bool("yaml.signed"),
			Verified: c.Bool("yaml.verified"),
		},
		Tag:         c.String("tag"),
		PullRequest: c.Int("pullRequest"),
		DeployTo:    c.String("deployTo"),
		Config: Config{
			From:           c.String("from"),
			Host:           c.String("host"),
			Port:           c.Int("port"),
			Username:       c.String("username"),
			Password:       c.String("password"),
			SkipVerify:     c.Bool("skip.verify"),
			Recipients:     c.StringSlice("recipients"),
			RecipientsOnly: c.Bool("recipients.only"),
			Subject:        c.String("template.subject"),
			Body:           c.String("template.body"),
			Attachment:     c.String("attachment"),
			Attachments:    c.StringSlice("attachments"),
			ClientHostname:   c.String("clienthostname"),
		},
	}

	return plugin.Exec()
}
<commit_msg>Use /run/drone/env as default PLUGIN_ENV_FILE, overwrite env vars with it<commit_after>package main

import (
	"os"

	log "github.com/Sirupsen/logrus"
	"github.com/joho/godotenv"
	"github.com/urfave/cli"
)

func main() {
	// Load env-file if it exists first
	envFile, envFileSet := os.LookupEnv("PLUGIN_ENV_FILE")
	if !envFileSet {
		envFile = "/run/drone/env"
	}
	if _, err := os.Stat(envFile); err == nil {
		godotenv.Overload(envFile)
	}

	app := cli.NewApp()
	app.Name = "email plugin"
	app.Usage = "email plugin"
	app.Action = run
	app.Version = "2.0.2"
	app.Flags = []cli.Flag{
		// Plugin environment
		cli.StringFlag{
			Name:   "from",
			Usage:  "from address",
			EnvVar: "PLUGIN_FROM",
		},
		cli.StringFlag{
			Name:   "host",
			Usage:  "smtp host",
			EnvVar: "EMAIL_HOST,PLUGIN_HOST",
		},
		cli.IntFlag{
			Name:   "port",
			Value:  DefaultPort,
			Usage:  "smtp port",
			EnvVar: "EMAIL_PORT,PLUGIN_PORT",
		},
		cli.StringFlag{
			Name:   "username",
			Usage:  "smtp server username",
			EnvVar: "EMAIL_USERNAME,PLUGIN_USERNAME",
		},
		cli.StringFlag{
			Name:   "password",
			Usage:  "smtp server password",
			EnvVar: "EMAIL_PASSWORD,PLUGIN_PASSWORD",
		},
		cli.BoolFlag{
			Name:   "skip.verify",
			Usage:  "skip tls verify",
			EnvVar: "PLUGIN_SKIP_VERIFY",
		},
		cli.StringSliceFlag{
			Name:   "recipients",
			Usage:  "recipient addresses",
			EnvVar: "EMAIL_RECIPIENTS,PLUGIN_RECIPIENTS",
		},
		cli.BoolFlag{
			Name:   "recipients.only",
			Usage:  "send to recipients only",
			EnvVar: "PLUGIN_RECIPIENTS_ONLY",
		},
		cli.StringFlag{
			Name:   "template.subject",
			Value:  DefaultSubject,
			Usage:  "subject template",
			EnvVar: "PLUGIN_SUBJECT",
		},
		cli.StringFlag{
			Name:   "template.body",
			Value:  DefaultTemplate,
			Usage:  "body template",
			EnvVar: "PLUGIN_BODY",
		},
		cli.StringFlag{
			Name:   "attachment",
			Usage:  "attachment filename",
			EnvVar: "PLUGIN_ATTACHMENT",
		},
		cli.StringSliceFlag{
			Name:   "attachments",
			Usage:  "attachment filename(s)",
			EnvVar: "PLUGIN_ATTACHMENTS",
		},
		cli.StringFlag{
			Name:   "clienthostname",
			Value:  DefaultClientHostname,
			Usage:  "smtp client hostname",
			EnvVar: "EMAIL_CLIENTHOSTNAME,PLUGIN_CLIENTHOSTNAME",
		},

		// Drone environment
		// Repo
		cli.StringFlag{
			Name:   "repo.fullName",
			Usage:  "repository full name",
			EnvVar: "DRONE_REPO",
		},
		cli.StringFlag{
			Name:   "repo.owner",
			Usage:  "repository owner",
			EnvVar: "DRONE_REPO_OWNER",
		},
		cli.StringFlag{
			Name:   "repo.name",
			Usage:  "repository name",
			EnvVar: "DRONE_REPO_NAME",
		},
		cli.StringFlag{
			Name:   "repo.scm",
			Value:  "git",
			Usage:  "respository scm",
			EnvVar: "DRONE_REPO_SCM",
		},
		cli.StringFlag{
			Name:   "repo.link",
			Usage:  "repository link",
			EnvVar: "DRONE_REPO_LINK",
		},
		cli.StringFlag{
			Name:   "repo.avatar",
			Usage:  "repository avatar",
			EnvVar: "DRONE_REPO_AVATAR",
		},
		cli.StringFlag{
			Name:   "repo.branch",
			Value:  "master",
			Usage:  "repository default branch",
			EnvVar: "DRONE_REPO_BRANCH",
		},
		cli.BoolFlag{
			Name:   "repo.private",
			Usage:  "repository is private",
			EnvVar: "DRONE_REPO_PRIVATE",
		},
		cli.BoolFlag{
			Name:   "repo.trusted",
			Usage:  "repository is trusted",
			EnvVar: "DRONE_REPO_TRUSTED",
		},

		// Remote
		cli.StringFlag{
			Name:   "remote.url",
			Usage:  "repository clone url",
			EnvVar: "DRONE_REMOTE_URL",
		},

		// Commit
		cli.StringFlag{
			Name:   "commit.sha",
			Usage:  "git commit sha",
			EnvVar: "DRONE_COMMIT_SHA",
		},
		cli.StringFlag{
			Name:   "commit.ref",
			Value:  "refs/heads/master",
			Usage:  "git commit ref",
			EnvVar: "DRONE_COMMIT_REF",
		},
		cli.StringFlag{
			Name:   "commit.branch",
			Value:  "master",
			Usage:  "git commit branch",
			EnvVar: "DRONE_COMMIT_BRANCH",
		},
		cli.StringFlag{
			Name:   "commit.link",
			Usage:  "commit link",
			EnvVar: "DRONE_COMMIT_LINK",
		},
		cli.StringFlag{
			Name:   "commit.message",
			Usage:  "git commit message",
			EnvVar: "DRONE_COMMIT_MESSAGE",
		},
		cli.StringFlag{
			Name:   "commit.author.name",
			Usage:  "git author name",
			EnvVar: "DRONE_COMMIT_AUTHOR",
		},
		cli.StringFlag{
			Name:   "commit.author.email",
			Usage:  "git author email",
			EnvVar: "DRONE_COMMIT_AUTHOR_EMAIL",
		},
		cli.StringFlag{
			Name:   "commit.author.avatar",
			Usage:  "git author avatar",
			EnvVar: "DRONE_COMMIT_AUTHOR_AVATAR",
		},

		// Build
		cli.IntFlag{
			Name:   "build.number",
			Usage:  "build number",
			EnvVar: "DRONE_BUILD_NUMBER",
		},
		cli.StringFlag{
			Name:   "build.event",
			Value:  "push",
			Usage:  "build event",
			EnvVar: "DRONE_BUILD_EVENT",
		},
		cli.StringFlag{
			Name:   "build.status",
			Usage:  "build status",
			Value:  "success",
			EnvVar: "DRONE_BUILD_STATUS",
		},
		cli.StringFlag{
			Name:   "build.link",
			Usage:  "build link",
			EnvVar: "DRONE_BUILD_LINK",
		},
		cli.Int64Flag{
			Name:   "build.created",
			Usage:  "build created",
			EnvVar: "DRONE_BUILD_CREATED",
		},
		cli.Int64Flag{
			Name:   "build.started",
			Usage:  "build started",
			EnvVar: "DRONE_BUILD_STARTED",
		},
		cli.Int64Flag{
			Name:   "build.finished",
			Usage:  "build finished",
			EnvVar: "DRONE_BUILD_FINISHED",
		},

		// Prev
		cli.StringFlag{
			Name:   "prev.build.status",
			Usage:  "prior build status",
			EnvVar: "DRONE_PREV_BUILD_STATUS",
		},
		cli.IntFlag{
			Name:   "prev.build.number",
			Usage:  "prior build number",
			EnvVar: "DRONE_PREV_BUILD_NUMBER",
		},
		cli.StringFlag{
			Name:   "prev.commit.sha",
			Usage:  "prior commit sha",
			EnvVar: "DRONE_PREV_COMMIT_SHA",
		},

		// Job
		cli.IntFlag{
			Name:   "job.number",
			Usage:  "job number",
			EnvVar: "DRONE_JOB_NUMBER",
		},
		cli.StringFlag{
			Name:   "job.status",
			Usage:  "job status",
			EnvVar: "DRONE_JOB_STATUS",
		},
		cli.IntFlag{
			Name:   "job.exitCode",
			Usage:  "job exit code",
			EnvVar: "DRONE_JOB_EXIT_CODE",
		},
		cli.Int64Flag{
			Name:   "job.started",
			Usage:  "job started",
			EnvVar: "DRONE_JOB_STARTED",
		},
		cli.Int64Flag{
			Name:   "job.finished",
			Usage:  "job finished",
			EnvVar: "DRONE_JOB_FINISHED",
		},

		// Yaml
		cli.BoolFlag{
			Name:   "yaml.signed",
			Usage:  "yaml is signed",
			EnvVar: "DRONE_YAML_SIGNED",
		},
		cli.BoolFlag{
			Name:   "yaml.verified",
			Usage:  "yaml is signed and verified",
			EnvVar: "DRONE_YAML_VERIFIED",
		},

		// Tag
		cli.StringFlag{
			Name:   "tag",
			Usage:  "git tag",
			EnvVar: "DRONE_TAG",
		},

		// PullRequest
		cli.IntFlag{
			Name:   "pullRequest",
			Usage:  "pull request number",
			EnvVar: "DRONE_PULL_REQUEST",
		},

		// DeployTo
		cli.StringFlag{
			Name:   "deployTo",
			Usage:  "deployment target",
			EnvVar: "DRONE_DEPLOY_TO",
		},
	}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}

func run(c *cli.Context) error {
	plugin := Plugin{
		Repo: Repo{
			FullName: c.String("repo.fullName"),
			Owner:    c.String("repo.owner"),
			Name:     c.String("repo.name"),
			SCM:      c.String("repo.scm"),
			Link:     c.String("repo.link"),
			Avatar:   c.String("repo.avatar"),
			Branch:   c.String("repo.branch"),
			Private:  c.Bool("repo.private"),
			Trusted:  c.Bool("repo.trusted"),
		},
		Remote: Remote{
			URL: c.String("remote.url"),
		},
		Commit: Commit{
			Sha:     c.String("commit.sha"),
			Ref:     c.String("commit.ref"),
			Branch:  c.String("commit.branch"),
			Link:    c.String("commit.link"),
			Message: c.String("commit.message"),
			Author: Author{
				Name:   c.String("commit.author.name"),
				Email:  c.String("commit.author.email"),
				Avatar: c.String("commit.author.avatar"),
			},
		},
		Build: Build{
			Number:   c.Int("build.number"),
			Event:    c.String("build.event"),
			Status:   c.String("build.status"),
			Link:     c.String("build.link"),
			Created:  c.Int64("build.created"),
			Started:  c.Int64("build.started"),
			Finished: c.Int64("build.finished"),
		},
		Prev: Prev{
			Build: PrevBuild{
				Status: c.String("prev.build.status"),
				Number: c.Int("prev.build.number"),
			},
			Commit: PrevCommit{
				Sha: c.String("prev.commit.sha"),
			},
		},
		Job: Job{
			Status:   c.String("job.status"),
			ExitCode: c.Int("job.exitCode"),
			Started:  c.Int64("job.started"),
			Finished: c.Int64("job.finished"),
		},
		Yaml: Yaml{
			Signed:   c.Bool("yaml.signed"),
			Verified: c.Bool("yaml.verified"),
		},
		Tag:         c.String("tag"),
		PullRequest: c.Int("pullRequest"),
		DeployTo:    c.String("deployTo"),
		Config: Config{
			From:           c.String("from"),
			Host:           c.String("host"),
			Port:           c.Int("port"),
			Username:       c.String("username"),
			Password:       c.String("password"),
			SkipVerify:     c.Bool("skip.verify"),
			Recipients:     c.StringSlice("recipients"),
			RecipientsOnly: c.Bool("recipients.only"),
			Subject:        c.String("template.subject"),
			Body:           c.String("template.body"),
			Attachment:     c.String("attachment"),
			Attachments:    c.StringSlice("attachments"),
			ClientHostname: c.String("clienthostname"),
		},
	}

	return plugin.Exec()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"

	"io/ioutil"

	"regexp"

	"github.com/davecgh/go-spew/spew"
)

var configFilePath string
var logFilePath string
var cmd string
var field string

func init() {
	flag.StringVar(&configFilePath, "config", "", "config file path")
	flag.StringVar(&logFilePath, "log", "", "unity log file path")
	flag.StringVar(&cmd, "cmd", "dump", "command")
	flag.StringVar(&field, "field", "", "field to show")
}

func main() {
	flag.Parse()

	config, err := loadConfig(configFilePath, logFilePath)
	if err != nil {
		panic(err)
	}

	switch cmd {
	case "dump":
		cmdDump(&config)
	case "show":
		cmdShow(&config)
	case "build":
		cmdBuild(&config)
	default:
		fmt.Println("unknown command:", cmd)
		os.Exit(-1)
	}
}

func cmdDump(c *Config) {
	spew.Dump(c)
	fmt.Println("")
	fmt.Println("BuildPath\t:", c.MakeBuildPath())
	fmt.Println("ConfigPath\t:", c.FilePath)
	fmt.Println("LogFilePath\t:", c.LogFilePath())
	fmt.Println("UnityPath\t:", c.MakeUnityPath())
	fmt.Println("ProjectPath\t:", c.MakeProjectPath())
	fmt.Println("Args\t:", c.Args())
}

func cmdShow(c *Config) {
	switch field {
	case "build_path":
		fmt.Print(c.MakeProjectPath())
	case "config_path":
		fmt.Print(c.FilePath)
	case "log_file_path":
		fmt.Print(c.LogFilePath())
	case "unity_path":
		fmt.Print(c.MakeUnityPath())
	case "project_path":
		fmt.Print(c.MakeProjectPath())
	case "minamo_version":
		tmpfile, err := ioutil.TempFile("", "minamo")
		if err != nil {
			panic(err)
		}
		fp := tmpfile.Name()
		defer os.Remove(tmpfile.Name())
		tmpfile.Close()

		c.logFilePath = tmpfile.Name()

		method := "Assets.Minamo.Editor.EntryPoint.VersionName"
		_, _, err = c.ExecuteMethod(method)
		if err != nil {
			panic(err)
		}

		data, err := ioutil.ReadFile(fp)
		if err != nil {
			panic(err)
		}

		re := regexp.MustCompile(`MinamoVersion=(\d+.\d+.\d+)`)
		founds := re.FindAllSubmatch(data, -1)
		if len(founds) > 0 {
			f := founds[0][1]
			v := string(f)
			fmt.Print(v)
		}
	}
}

func cmdBuild(c *Config) {
	output, buildTime, err := c.Execute()
	if err != nil {
		panic(err)
	}

	fmt.Println(output)
	fmt.Println("BuildTime\t:", buildTime)
}
<commit_msg>fix bug on 'show'<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"regexp"

	"github.com/davecgh/go-spew/spew"
)

var configFilePath string
var logFilePath string
var cmd string
var field string

func init() {
	flag.StringVar(&configFilePath, "config", "", "config file path")
	flag.StringVar(&logFilePath, "log", "", "unity log file path")
	flag.StringVar(&cmd, "cmd", "dump", "command")
	flag.StringVar(&field, "field", "", "field to show")
}

func main() {
	flag.Parse()

	config, err := loadConfig(configFilePath, logFilePath)
	if err != nil {
		log.Fatalf("Failed to load config: %s", err)
	}

	switch cmd {
	case "dump":
		cmdDump(&config)
	case "show":
		cmdShow(&config)
	case "build":
		cmdBuild(&config)
	default:
		log.Fatalf("Unknown command: %s", cmd)
	}
}

func cmdDump(c *Config) {
	spew.Dump(c)
	fmt.Println("")
	fmt.Println("BuildPath\t:", c.MakeBuildPath())
	fmt.Println("ConfigPath\t:", c.FilePath)
	fmt.Println("LogFilePath\t:", c.LogFilePath())
	fmt.Println("UnityPath\t:", c.MakeUnityPath())
	fmt.Println("ProjectPath\t:", c.MakeProjectPath())
	fmt.Println("Args\t:", c.Args())
}

func cmdShow(c *Config) {
	switch field {
	case "build_path":
		fmt.Println(c.MakeBuildPath())
	case "config_path":
		fmt.Println(c.FilePath)
	case "log_file_path":
		fmt.Println(c.LogFilePath())
	case "unity_path":
		fmt.Println(c.MakeUnityPath())
	case "project_path":
		fmt.Println(c.MakeProjectPath())
	case "minamo_version":
		tmpfile, err := ioutil.TempFile("", "minamo")
		if err != nil {
			panic(err)
		}
		fp := tmpfile.Name()
		defer os.Remove(tmpfile.Name())
		tmpfile.Close()

		c.logFilePath = tmpfile.Name()

		method := "Assets.Minamo.Editor.EntryPoint.VersionName"
		_, _, err = c.ExecuteMethod(method)
		if err != nil {
			panic(err)
		}

		data, err := ioutil.ReadFile(fp)
		if err != nil {
			panic(err)
		}

		re := regexp.MustCompile(`MinamoVersion=(\d+.\d+.\d+)`)
		founds := re.FindAllSubmatch(data, -1)
		if len(founds) > 0 {
			f := founds[0][1]
			v := string(f)
			fmt.Println(v)
		}
	default:
		log.Fatalf("unknown field: %s", field)
	}
}

func cmdBuild(c *Config) {
	output, buildTime, err := c.Execute()
	if err != nil {
		panic(err)
	}

	fmt.Println(output)
	fmt.Println("BuildTime\t:", buildTime)
}
<|endoftext|>


<commit_before>package main

import (
  "os"
  "path"
  "fmt"
  "github.com/codegangsta/cli"
  "github.com/buildboxhq/agent-go/buildbox"
)

var AppHelpTemplate = `The agent performs builds and sends the results back to Buildbox.

Usage:

  {{.Name}} command [arguments]

The comamnds are:

  {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
  {{end}}
Use "buildbox-agent help [command]" for more information about a command.

`

var CommandHelpTemplate = `Usage: buildbox-agent {{.Name}} [command options] [arguments...]

{{.Description}}

Options:
   {{range .Flags}}{{.}}
   {{end}}
`

var StartHelpDescription = `When a job is ready to run it will call the "bootstrap-script"
and pass it all the environment variables required for the job to run.
This script is responsible for checking out the code, and running the
actual build script defined in the project.

The agent will run any jobs within a PTY (Pseudo terminal) if available.

Example:

buildbox-agent start --access-token [access-token] \
                     --bootstrap-script ~/.buildbox/bootstrap.sh`

var RunHelpDescription = `Manually run a scheduled job. If the job has already assigned
to another agent, it will not run.

Example:

buildbox-agent run [job-id] --access-token [access-token]`

var BootstrapScriptDefault = "$HOME/.buildbox/bootstrap.sh"

func main() {
  cli.AppHelpTemplate = AppHelpTemplate
  cli.CommandHelpTemplate = CommandHelpTemplate

  app := cli.NewApp()
  app.Name = "buildbox-agent"
  app.Version = buildbox.Version

  // Define the actions for our CLI
  app.Commands = []cli.Command{
    {
      Name:  "start",
      Usage: "Starts the Buildbox agent",
      Description: StartHelpDescription,
      Flags: []cli.Flag {
        cli.StringFlag{"access-token", "", "The access token used to identify the agent."},
        cli.StringFlag{"bootstrap-script", BootstrapScriptDefault, "Path to the bootstrap script."},
        cli.StringFlag{"url", "https://agent.buildbox.io/v1", "The Agent API endpoint."},
        cli.BoolFlag{"debug", "Enable debug mode."},
      },
      Action: func(c *cli.Context) {
        // Create the agent from the CLI options
        agent := setupAgentFromCli(c, "start")

        // Start the agent
        agent.Start()
      },
    },
    {
      Name:  "run",
      Usage: "Manually run a scheduled job",
      Description: RunHelpDescription,
      Flags: []cli.Flag {
        cli.StringFlag{"access-token", "", "The access token used to identify the agent."},
        cli.StringFlag{"bootstrap-script", BootstrapScriptDefault, "Path to the bootstrap script."},
        cli.StringFlag{"url", "https://agent.buildbox.io/v1", "The Agent API endpoint."},
        cli.BoolFlag{"debug", "Enable debug mode."},
      },
      Action: func(c *cli.Context) {
        // Create the agent from the CLI options
        agent := setupAgentFromCli(c, "run")

        // Grab the first argument and use as the job id
        id := c.Args().First()

        // Validate the job id
        if id == "" {
          fmt.Printf("buildbox-agent: no job id specified.\nSee 'buildbox-agent help run'")
          os.Exit(1)
        }

        agent.Run(id)
      },
    },
  }

  // Default the default action
  app.Action = func(c *cli.Context) {
    cli.ShowAppHelp(c)
    os.Exit(1)
  }

  app.Run(os.Args)
}

func setupAgentFromCli(c *cli.Context, command string) buildbox.Agent {
  if c.String("access-token") == "" {
    fmt.Printf("buildbox-agent: missing access token\nSee 'buildbox-agent help %s'\n", command)
    os.Exit(1)
  }

  bootstrapScript := c.String("bootstrap-script")

  // Go doesn't provide an a mechanism to turn $HOME into an absolute
  // path, so if they're using the default, I'll remake it using real
  // values.
  if c.String("bootstrap-script") == BootstrapScriptDefault {
    homeDir := os.Getenv("HOME")

    bootstrapScript = path.Join(homeDir, ".buildbox", "bootstrap.sh")
  }

  // Make sure the boostrap script exists.
  if _, err := os.Stat(bootstrapScript); os.IsNotExist(err) {
    print("buildbox-agent: no such file " + bootstrapScript + "\n")
    os.Exit(1)
  }

  // Set the agent options
  var agent buildbox.Agent;
  agent.Debug = c.Bool("debug")
  agent.BootstrapScript = bootstrapScript

  // Client specific options
  agent.Client.AgentAccessToken = c.String("access-token")
  agent.Client.URL = c.String("url")
  agent.Client.Debug = agent.Debug

  // Tell the user that debug mode has been enabled
  if agent.Debug {
    log.Printf("Debug mode enabled")
  }

  // Setup the agent
  agent.Setup()

  // A nice welcome message
  log.Printf("Started buildbox-agent `%s` (version %s)\n", agent.Name, buildbox.Version)

  return agent
}
<commit_msg>Adding log to the import list.<commit_after>package main

import (
  "os"
  "path"
  "fmt"
  "log"
  "github.com/codegangsta/cli"
  "github.com/buildboxhq/agent-go/buildbox"
)

var AppHelpTemplate = `The agent performs builds and sends the results back to Buildbox.

Usage:

  {{.Name}} command [arguments]

The comamnds are:

  {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
  {{end}}
Use "buildbox-agent help [command]" for more information about a command.

`

var CommandHelpTemplate = `Usage: buildbox-agent {{.Name}} [command options] [arguments...]

{{.Description}}

Options:
   {{range .Flags}}{{.}}
   {{end}}
`

var StartHelpDescription = `When a job is ready to run it will call the "bootstrap-script"
and pass it all the environment variables required for the job to run.
This script is responsible for checking out the code, and running the
actual build script defined in the project.

The agent will run any jobs within a PTY (Pseudo terminal) if available.

Example:

buildbox-agent start --access-token [access-token] \
                     --bootstrap-script ~/.buildbox/bootstrap.sh`

var RunHelpDescription = `Manually run a scheduled job. If the job has already assigned
to another agent, it will not run.

Example:

buildbox-agent run [job-id] --access-token [access-token]`

var BootstrapScriptDefault = "$HOME/.buildbox/bootstrap.sh"

func main() {
  cli.AppHelpTemplate = AppHelpTemplate
  cli.CommandHelpTemplate = CommandHelpTemplate

  app := cli.NewApp()
  app.Name = "buildbox-agent"
  app.Version = buildbox.Version

  // Define the actions for our CLI
  app.Commands = []cli.Command{
    {
      Name:  "start",
      Usage: "Starts the Buildbox agent",
      Description: StartHelpDescription,
      Flags: []cli.Flag {
        cli.StringFlag{"access-token", "", "The access token used to identify the agent."},
        cli.StringFlag{"bootstrap-script", BootstrapScriptDefault, "Path to the bootstrap script."},
        cli.StringFlag{"url", "https://agent.buildbox.io/v1", "The Agent API endpoint."},
        cli.BoolFlag{"debug", "Enable debug mode."},
      },
      Action: func(c *cli.Context) {
        // Create the agent from the CLI options
        agent := setupAgentFromCli(c, "start")

        // Start the agent
        agent.Start()
      },
    },
    {
      Name:  "run",
      Usage: "Manually run a scheduled job",
      Description: RunHelpDescription,
      Flags: []cli.Flag {
        cli.StringFlag{"access-token", "", "The access token used to identify the agent."},
        cli.StringFlag{"bootstrap-script", BootstrapScriptDefault, "Path to the bootstrap script."},
        cli.StringFlag{"url", "https://agent.buildbox.io/v1", "The Agent API endpoint."},
        cli.BoolFlag{"debug", "Enable debug mode."},
      },
      Action: func(c *cli.Context) {
        // Create the agent from the CLI options
        agent := setupAgentFromCli(c, "run")

        // Grab the first argument and use as the job id
        id := c.Args().First()

        // Validate the job id
        if id == "" {
          fmt.Printf("buildbox-agent: no job id specified.\nSee 'buildbox-agent help run'")
          os.Exit(1)
        }

        agent.Run(id)
      },
    },
  }

  // Default the default action
  app.Action = func(c *cli.Context) {
    cli.ShowAppHelp(c)
    os.Exit(1)
  }

  app.Run(os.Args)
}

func setupAgentFromCli(c *cli.Context, command string) buildbox.Agent {
  if c.String("access-token") == "" {
    fmt.Printf("buildbox-agent: missing access token\nSee 'buildbox-agent help %s'\n", command)
    os.Exit(1)
  }

  bootstrapScript := c.String("bootstrap-script")

  // Go doesn't provide an a mechanism to turn $HOME into an absolute
  // path, so if they're using the default, I'll remake it using real
  // values.
  if c.String("bootstrap-script") == BootstrapScriptDefault {
    homeDir := os.Getenv("HOME")

    bootstrapScript = path.Join(homeDir, ".buildbox", "bootstrap.sh")
  }

  // Make sure the boostrap script exists.
  if _, err := os.Stat(bootstrapScript); os.IsNotExist(err) {
    print("buildbox-agent: no such file " + bootstrapScript + "\n")
    os.Exit(1)
  }

  // Set the agent options
  var agent buildbox.Agent;
  agent.Debug = c.Bool("debug")
  agent.BootstrapScript = bootstrapScript

  // Client specific options
  agent.Client.AgentAccessToken = c.String("access-token")
  agent.Client.URL = c.String("url")
  agent.Client.Debug = agent.Debug

  // Tell the user that debug mode has been enabled
  if agent.Debug {
    log.Printf("Debug mode enabled")
  }

  // Setup the agent
  agent.Setup()

  // A nice welcome message
  log.Printf("Started buildbox-agent `%s` (version %s)\n", agent.Name, buildbox.Version)

  return agent
}
<|endoftext|>