<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2015 Romain LÉTENDART
//
// See LICENSE file.
package main

import (
	"flag"
	"fmt"
	"github.com/thoj/go-ircevent"
	"golang.org/x/net/html"
	"golang.org/x/net/idna"
	"net/http"
	"net/url"
	"os"
	"regexp"
)

func getOptions() (nick, server, channel, channelKey string, success bool) {
	flag.StringVar(&channel, "channel", "", "IRC channel name")
	flag.StringVar(&channelKey, "key", "", "IRC channel key (optional)")
	flag.StringVar(&nick, "nick", "goxxx", "the bot's nickname (optional)")
	flag.StringVar(&server, "server", "chat.freenode.net:6697", "IRC_SERVER[:PORT] (optional)")
	flag.Usage = func() {
		fmt.Println("Usage:", os.Args[0], "-channel CHANNEL [ARGUMENTS]")
		fmt.Println()
		fmt.Println("Arguments description:")
		flag.PrintDefaults()
	}
	flag.Parse()

	if channel == "" {
		flag.Usage()
		success = false
	} else {
		success = true
	}

	return
}

func getTitleFromHTML(document *html.Node) (title string, found bool) {
	if document.Type != html.DocumentNode {
		// Didn't find a document node as first node, exit
		return
	}

	// Try to find the <html> inside the document
	child := document.FirstChild
	for child != nil && !(child.Type == html.ElementNode && child.Data == "html") {
		child = child.NextSibling
	}
	if child == nil {
		// Didn't find <html>, exit
		return
	}

	// Try to find the <head> inside the document
	currentNode := child
	for child = currentNode.FirstChild; child != nil && !(child.Type == html.ElementNode && child.Data == "head"); child = child.NextSibling {
	}
	if child == nil {
		// Didn't find <head>, exit
		return
	}

	// Try to find the <title> inside the <head>
	currentNode = child
	for child = currentNode.FirstChild; child != nil && !(child.Type == html.ElementNode && child.Data == "title"); child = child.NextSibling {
	}
	if child == nil || child.FirstChild == nil {
		// Didn't find <title> or it is empty, exit
		return
	}

	// Retrieve the content inside the <title>
	title = child.FirstChild.Data
	found = true

	return
}

func findUrls(message string) (urls []*url.URL) {
	const maxUrlsCount int = 10

	// Source of the regular expression:
	// http://daringfireball.net/2010/07/improved_regex_for_matching_urls
	re := regexp.MustCompile("(?:https?://|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’])")
	urlCandidates := re.FindAllString(message, maxUrlsCount)

	for _, candidate := range urlCandidates {
		url, err := url.Parse(candidate)
		if err != nil {
			break
		}
		// Scheme is required to query a URL
		if url.Scheme == "" {
			url.Scheme = "https"
		}
		// Conversion to ASCII is needed for Unicode hostnames
		asciiHost, err := idna.ToASCII(url.Host)
		if err == nil {
			url.Host = asciiHost
		}
		urls = append(urls, url)
	}

	return
}

func main() {
	nick, server, channel, channelKey, success := getOptions()

	if !success {
		return
	}

	ircConn := irc.IRC(nick, nick)
	ircConn.UseTLS = true
	ircConn.Connect(server)
	ircConn.Join(channel + " " + channelKey)
	ircConn.AddCallback("PRIVMSG", func(event *irc.Event) {
		allUrls := findUrls(event.Message())
		for _, url := range allUrls {
			fmt.Println("Detected URL:", url.String())
			response, err := http.Get(url.String())
			if err != nil {
				// TODO Do proper logging
				fmt.Println(err)
				return
			}
			doc, err := html.Parse(response.Body)
			response.Body.Close()
			if err != nil {
				// TODO Do proper logging
				fmt.Println(err)
				return
			}
			title, found := getTitleFromHTML(doc)
			if found {
				ircConn.Privmsg(channel, title)
			}
		}
	})

	ircConn.Loop()
}
<commit_msg>Added Bot struct<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2015 Romain LÉTENDART
//
// See LICENSE file.
package main

import (
	"flag"
	"fmt"
	"github.com/thoj/go-ircevent"
	"golang.org/x/net/html"
	"golang.org/x/net/idna"
	"net/http"
	"net/url"
	"os"
	"regexp"
)

func getOptions() (nick, server, channel, channelKey string, success bool) {
	flag.StringVar(&channel, "channel", "", "IRC channel name")
	flag.StringVar(&channelKey, "key", "", "IRC channel key (optional)")
	flag.StringVar(&nick, "nick", "goxxx", "the bot's nickname (optional)")
	flag.StringVar(&server, "server", "chat.freenode.net:6697", "IRC_SERVER[:PORT] (optional)")
	flag.Usage = func() {
		fmt.Println("Usage:", os.Args[0], "-channel CHANNEL [ARGUMENTS]")
		fmt.Println()
		fmt.Println("Arguments description:")
		flag.PrintDefaults()
	}
	flag.Parse()

	if channel == "" {
		flag.Usage()
		success = false
	} else {
		success = true
	}

	return
}

func getTitleFromHTML(document *html.Node) (title string, found bool) {
	if document.Type != html.DocumentNode {
		// Didn't find a document node as first node, exit
		return
	}

	// Try to find the <html> inside the document
	child := document.FirstChild
	for child != nil && !(child.Type == html.ElementNode && child.Data == "html") {
		child = child.NextSibling
	}
	if child == nil {
		// Didn't find <html>, exit
		return
	}

	// Try to find the <head> inside the document
	currentNode := child
	for child = currentNode.FirstChild; child != nil && !(child.Type == html.ElementNode && child.Data == "head"); child = child.NextSibling {
	}
	if child == nil {
		// Didn't find <head>, exit
		return
	}

	// Try to find the <title> inside the <head>
	currentNode = child
	for child = currentNode.FirstChild; child != nil && !(child.Type == html.ElementNode && child.Data == "title"); child = child.NextSibling {
	}
	if child == nil || child.FirstChild == nil {
		// Didn't find <title> or it is empty, exit
		return
	}

	// Retrieve the content inside the <title>
	title = child.FirstChild.Data
	found = true

	return
}

func findUrls(message string) (urls []*url.URL) {
	const maxUrlsCount int = 10

	// Source of the regular expression:
	// http://daringfireball.net/2010/07/improved_regex_for_matching_urls
	re := regexp.MustCompile("(?:https?://|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’])")
	urlCandidates := re.FindAllString(message, maxUrlsCount)

	for _, candidate := range urlCandidates {
		url, err := url.Parse(candidate)
		if err != nil {
			break
		}
		// Scheme is required to query a URL
		if url.Scheme == "" {
			url.Scheme = "https"
		}
		// Conversion to ASCII is needed for Unicode hostnames
		asciiHost, err := idna.ToASCII(url.Host)
		if err == nil {
			url.Host = asciiHost
		}
		urls = append(urls, url)
	}

	return
}

type Bot struct {
	nick       string
	server     string
	channel    string
	channelKey string
	ircConn    *irc.Connection
}

func (bot *Bot) Init() {
	bot.ircConn = irc.IRC(bot.nick, bot.nick)
	bot.ircConn.UseTLS = true
	bot.ircConn.Connect(bot.server)
	bot.ircConn.Join(bot.channel + " " + bot.channelKey)
	bot.ircConn.AddCallback("PRIVMSG", func(event *irc.Event) {
		allUrls := findUrls(event.Message())
		for _, url := range allUrls {
			fmt.Println("Detected URL:", url.String())
			response, err := http.Get(url.String())
			if err != nil {
				// TODO Do proper logging
				fmt.Println(err)
				return
			}
			doc, err := html.Parse(response.Body)
			response.Body.Close()
			if err != nil {
				// TODO Do proper logging
				fmt.Println(err)
				return
			}
			title, found := getTitleFromHTML(doc)
			if found {
				bot.ReplyToAll(title)
			}
		}
	})
}

func (bot *Bot) Run() {
	bot.ircConn.Loop()
}

func (bot *Bot) ReplyToAll(message string) {
	bot.ircConn.Privmsg(bot.channel, message)
}

func main() {
	nick, server, channel, channelKey, success := getOptions()
	if !success {
		return
	}

	bot := Bot{
		nick:       nick,
		server:     server,
		channel:    channel,
		channelKey: channelKey,
	}
	bot.Init()
	bot.Run()
}
<|endoftext|>


<commit_before>package agent

import (
	"encoding/json"
	"fmt"
	"net/http"
	"unicode"
	"unicode/utf8"

	"code.google.com/p/go-uuid/uuid"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/mistifyio/kvite"
	"github.com/mistifyio/mistify-agent/client"
	"github.com/mistifyio/mistify-agent/rpc"
)

type (

	// Guest is a "helper struct"
	Guest struct {
		context *Context
		*client.Guest
	}
)

const requestGuestKey = "requestGuest"

// PersistGuest writes guest data to the data store
func (ctx *Context) PersistGuest(g *client.Guest) error {
	return ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		data, err := json.Marshal(g)
		if err != nil {
			return err
		}
		return b.Put(g.Id, data)
	})
}

// DeleteGuest removes a guest from the data store
func (ctx *Context) DeleteGuest(g *client.Guest) error {
	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.Delete(g.Id)
	})

	if err != nil {
		return err
	}
	ctx.DeleteGuestRunner(g.Id)
	return nil
}

// prefixedActionName creates the appropriate action name for the guest type
func prefixedActionName(gType, actionName string) string {
	if gType != "container" || actionName == "" {
		return actionName
	}
	r, n := utf8.DecodeRuneInString(actionName)
	return "container" + string(unicode.ToUpper(r)) + actionName[n:]
}

func listGuests(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	ctx := GetContext(r)
	var guests []*client.Guest

	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.ForEach(func(k string, v []byte) error {
			var g client.Guest
			if err := json.Unmarshal(v, &g); err != nil {
				return err
			}
			// Do we want to actually verify this information or trust the pipelines??
			guests = append(guests, &g)
			return nil
		})
	})

	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}

	hr.JSON(http.StatusOK, guests)
}

// TODO: A lot of the duplicated code between here and the guest action wrapper
// will go away when we fix our middlewares. The initial setup here can be a
// simple middleware called first before the guest and runner retrieval
// middlewares
// NOTE: The config for create should include stages for startup
func createGuest(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	ctx := GetContext(r)

	g := &client.Guest{}
	if err := json.NewDecoder(r.Body).Decode(g); err != nil {
		hr.JSONError(http.StatusBadRequest, err)
		return
	}
	if g.Id != "" {
		if uuid.Parse(g.Id) == nil {
			hr.JSONError(http.StatusBadRequest, fmt.Errorf("id must be uuid"))
			return
		}
	} else {
		g.Id = uuid.New()
	}

	// TODO: make sure it's actually unique
	g.State = "create"

	// TODO: general validations, like memory, disks look sane, etc

	action, err := ctx.GetAction(prefixedActionName(g.Type, "create"))
	if err != nil {
		hr.JSONError(http.StatusNotFound, err)
		return
	}

	if err := ctx.PersistGuest(g); err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}

	runner := ctx.NewGuestRunner(g.Id, 100, 5)

	response := &rpc.GuestResponse{}
	request := &rpc.GuestRequest{
		Guest:  g,
		Action: action.Name,
	}
	pipeline := action.GeneratePipeline(request, response, hr, nil)
	// PreStageFunc copies the stage args into the request
	pipeline.PreStageFunc = func(p *Pipeline, s *Stage) error {
		request.Args = s.Args
		return nil
	}
	// PostStageFunc saves the guest and uses it for the next request
	pipeline.PostStageFunc = func(p *Pipeline, s *Stage) error {
		request.Guest = response.Guest
		return ctx.PersistGuest(response.Guest)
	}

	hr.Header().Set("X-Guest-Job-ID", pipeline.ID)
	err = runner.Process(pipeline)
	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}
	hr.JSON(http.StatusAccepted, g)
}

// GetGuestMiddleware retrieves guest information into the request context
func GetGuestMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hr := &HTTPResponse{w}
		ctx := GetContext(r)
		vars := mux.Vars(r)

		id := vars["id"]
		var g client.Guest
		err := ctx.db.Transaction(func(tx *kvite.Tx) error {
			b, err := tx.Bucket("guests")
			if err != nil {
				return err
			}
			data, err := b.Get(id)
			if err != nil {
				return err
			}
			if data == nil {
				return ErrNotFound
			}

			return json.Unmarshal(data, &g)
		})

		if err != nil {
			code := http.StatusInternalServerError
			if err == ErrNotFound {
				code = http.StatusNotFound
			}
			hr.JSONError(code, err)
			return
		}

		context.Set(r, requestGuestKey, &g)
		h.ServeHTTP(w, r)
	})
}

func getGuest(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	hr.JSON(http.StatusOK, GetRequestGuest(r))
}

func deleteGuest(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	ctx := GetContext(r)
	g := GetRequestGuest(r)

	err := ctx.PersistGuest(g)
	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}
	hr.JSON(http.StatusAccepted, g)
}

func getGuestMetadata(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	g := GetRequestGuest(r)
	hr.JSON(http.StatusOK, g.Metadata)
}

func setGuestMetadata(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	ctx := GetContext(r)
	g := GetRequestGuest(r)

	var metadata map[string]string
	err := json.NewDecoder(r.Body).Decode(&metadata)
	if err != nil {
		hr.JSONError(http.StatusBadRequest, err)
		return
	}

	for key, value := range metadata {
		if value == "" {
			delete(g.Metadata, key)
		} else {
			g.Metadata[key] = value
		}
	}

	err = ctx.PersistGuest(g)
	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}
	hr.JSON(http.StatusOK, g.Metadata)
}

// GuestRunnerMiddleware gets and places the runner into the request context
func GuestRunnerMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hr := &HTTPResponse{w}
		ctx := GetContext(r)
		guest := GetRequestGuest(r)
		runner, err := ctx.GetGuestRunner(guest.Id)
		if err != nil {
			hr.JSONError(http.StatusInternalServerError, err)
			return
		}

		context.Set(r, requestRunnerKey, runner)
		h.ServeHTTP(w, r)
	})
}

// GenerateGuestAction creates a handler function for a particular guest action
func GenerateGuestAction(actionName string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		hr := &HTTPResponse{w}
		ctx := GetContext(r)
		g := GetRequestGuest(r)
		runner := GetRequestRunner(r)

		actionName := prefixedActionName(g.Type, actionName)
		action, err := ctx.GetAction(actionName)
		if err != nil {
			hr.JSONError(http.StatusNotFound, err)
			return
		}

		response := &rpc.GuestResponse{}
		request := &rpc.GuestRequest{
			Guest:  g,
			Action: action.Name,
		}
		doneChan := make(chan error)
		pipeline := action.GeneratePipeline(request, response, hr, doneChan)
		// PreStageFunc copies the stage args into the request
		pipeline.PreStageFunc = func(p *Pipeline, s *Stage) error {
			request.Args = s.Args
			return nil
		}
		// PostStageFunc saves the guest and uses it for the next request
		pipeline.PostStageFunc = func(p *Pipeline, s *Stage) error {
			request.Guest = response.Guest
			return ctx.PersistGuest(response.Guest)
		}

		hr.Header().Set("X-Guest-Job-ID", pipeline.ID)
		err = runner.Process(pipeline)
		if err != nil {
			hr.JSONError(http.StatusInternalServerError, err)
			return
		}
		// Extra processing after the pipeline finishes
		go func() {
			err := <-doneChan
			if err != nil {
				return
			}
			if actionName == prefixedActionName(g.Type, "delete") {
				if err := ctx.DeleteGuest(g); err != nil {
					log.WithFields(log.Fields{
						"guest": g.Id,
						"error": err,
						"func":  "agent.Context.DeleteGuest",
					}).Error("Delete Error:", err)
				}
				return
			}
		}()
		hr.JSON(http.StatusAccepted, g)
	}
}

// GetRequestGuest retrieves the guest from the request context
func GetRequestGuest(r *http.Request) *client.Guest {
	if value := context.Get(r, requestGuestKey); value != nil {
		return value.(*client.Guest)
	}
	return nil
}
<commit_msg>hr only used in error case, move it there<commit_after>package agent

import (
	"encoding/json"
	"fmt"
	"net/http"
	"unicode"
	"unicode/utf8"

	"code.google.com/p/go-uuid/uuid"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/mistifyio/kvite"
	"github.com/mistifyio/mistify-agent/client"
	"github.com/mistifyio/mistify-agent/rpc"
)

type (

	// Guest is a "helper struct"
	Guest struct {
		context *Context
		*client.Guest
	}
)

const requestGuestKey = "requestGuest"

// PersistGuest writes guest data to the data store
func (ctx *Context) PersistGuest(g *client.Guest) error {
	return ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		data, err := json.Marshal(g)
		if err != nil {
			return err
		}
		return b.Put(g.Id, data)
	})
}

// DeleteGuest removes a guest from the data store
func (ctx *Context) DeleteGuest(g *client.Guest) error {
	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.Delete(g.Id)
	})

	if err != nil {
		return err
	}
	ctx.DeleteGuestRunner(g.Id)
	return nil
}

// prefixedActionName creates the appropriate action name for the guest type
func prefixedActionName(gType, actionName string) string {
	if gType != "container" || actionName == "" {
		return actionName
	}
	r, n := utf8.DecodeRuneInString(actionName)
	return "container" + string(unicode.ToUpper(r)) + actionName[n:]
}

func listGuests(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	ctx := GetContext(r)
	var guests []*client.Guest

	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.ForEach(func(k string, v []byte) error {
			var g client.Guest
			if err := json.Unmarshal(v, &g); err != nil {
				return err
			}
			// Do we want to actually verify this information or trust the pipelines??
			guests = append(guests, &g)
			return nil
		})
	})

	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}

	hr.JSON(http.StatusOK, guests)
}

// TODO: A lot of the duplicated code between here and the guest action wrapper
// will go away when we fix our middlewares. The initial setup here can be a
// simple middleware called first before the guest and runner retrieval
// middlewares
// NOTE: The config for create should include stages for startup
func createGuest(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	ctx := GetContext(r)

	g := &client.Guest{}
	if err := json.NewDecoder(r.Body).Decode(g); err != nil {
		hr.JSONError(http.StatusBadRequest, err)
		return
	}
	if g.Id != "" {
		if uuid.Parse(g.Id) == nil {
			hr.JSONError(http.StatusBadRequest, fmt.Errorf("id must be uuid"))
			return
		}
	} else {
		g.Id = uuid.New()
	}

	// TODO: make sure it's actually unique
	g.State = "create"

	// TODO: general validations, like memory, disks look sane, etc

	action, err := ctx.GetAction(prefixedActionName(g.Type, "create"))
	if err != nil {
		hr.JSONError(http.StatusNotFound, err)
		return
	}

	if err := ctx.PersistGuest(g); err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}

	runner := ctx.NewGuestRunner(g.Id, 100, 5)

	response := &rpc.GuestResponse{}
	request := &rpc.GuestRequest{
		Guest:  g,
		Action: action.Name,
	}
	pipeline := action.GeneratePipeline(request, response, hr, nil)
	// PreStageFunc copies the stage args into the request
	pipeline.PreStageFunc = func(p *Pipeline, s *Stage) error {
		request.Args = s.Args
		return nil
	}
	// PostStageFunc saves the guest and uses it for the next request
	pipeline.PostStageFunc = func(p *Pipeline, s *Stage) error {
		request.Guest = response.Guest
		return ctx.PersistGuest(response.Guest)
	}

	hr.Header().Set("X-Guest-Job-ID", pipeline.ID)
	err = runner.Process(pipeline)
	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}
	hr.JSON(http.StatusAccepted, g)
}

// GetGuestMiddleware retrieves guest information into the request context
func GetGuestMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hr := &HTTPResponse{w}
		ctx := GetContext(r)
		vars := mux.Vars(r)

		id := vars["id"]
		var g client.Guest
		err := ctx.db.Transaction(func(tx *kvite.Tx) error {
			b, err := tx.Bucket("guests")
			if err != nil {
				return err
			}
			data, err := b.Get(id)
			if err != nil {
				return err
			}
			if data == nil {
				return ErrNotFound
			}

			return json.Unmarshal(data, &g)
		})

		if err != nil {
			code := http.StatusInternalServerError
			if err == ErrNotFound {
				code = http.StatusNotFound
			}
			hr.JSONError(code, err)
			return
		}

		context.Set(r, requestGuestKey, &g)
		h.ServeHTTP(w, r)
	})
}

func getGuest(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	hr.JSON(http.StatusOK, GetRequestGuest(r))
}

func deleteGuest(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	ctx := GetContext(r)
	g := GetRequestGuest(r)

	err := ctx.PersistGuest(g)
	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}
	hr.JSON(http.StatusAccepted, g)
}

func getGuestMetadata(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	g := GetRequestGuest(r)
	hr.JSON(http.StatusOK, g.Metadata)
}

func setGuestMetadata(w http.ResponseWriter, r *http.Request) {
	hr := &HTTPResponse{w}
	ctx := GetContext(r)
	g := GetRequestGuest(r)

	var metadata map[string]string
	err := json.NewDecoder(r.Body).Decode(&metadata)
	if err != nil {
		hr.JSONError(http.StatusBadRequest, err)
		return
	}

	for key, value := range metadata {
		if value == "" {
			delete(g.Metadata, key)
		} else {
			g.Metadata[key] = value
		}
	}

	err = ctx.PersistGuest(g)
	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}
	hr.JSON(http.StatusOK, g.Metadata)
}

// GuestRunnerMiddleware gets and places the runner into the request context
func GuestRunnerMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := GetContext(r)
		guest := GetRequestGuest(r)
		runner, err := ctx.GetGuestRunner(guest.Id)
		if err != nil {
			hr := &HTTPResponse{w}
			hr.JSONError(http.StatusInternalServerError, err)
			return
		}

		context.Set(r, requestRunnerKey, runner)
		h.ServeHTTP(w, r)
	})
}

// GenerateGuestAction creates a handler function for a particular guest action
func GenerateGuestAction(actionName string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		hr := &HTTPResponse{w}
		ctx := GetContext(r)
		g := GetRequestGuest(r)
		runner := GetRequestRunner(r)

		actionName := prefixedActionName(g.Type, actionName)
		action, err := ctx.GetAction(actionName)
		if err != nil {
			hr.JSONError(http.StatusNotFound, err)
			return
		}

		response := &rpc.GuestResponse{}
		request := &rpc.GuestRequest{
			Guest:  g,
			Action: action.Name,
		}
		doneChan := make(chan error)
		pipeline := action.GeneratePipeline(request, response, hr, doneChan)
		// PreStageFunc copies the stage args into the request
		pipeline.PreStageFunc = func(p *Pipeline, s *Stage) error {
			request.Args = s.Args
			return nil
		}
		// PostStageFunc saves the guest and uses it for the next request
		pipeline.PostStageFunc = func(p *Pipeline, s *Stage) error {
			request.Guest = response.Guest
			return ctx.PersistGuest(response.Guest)
		}

		hr.Header().Set("X-Guest-Job-ID", pipeline.ID)
		err = runner.Process(pipeline)
		if err != nil {
			hr.JSONError(http.StatusInternalServerError, err)
			return
		}
		// Extra processing after the pipeline finishes
		go func() {
			err := <-doneChan
			if err != nil {
				return
			}
			if actionName == prefixedActionName(g.Type, "delete") {
				if err := ctx.DeleteGuest(g); err != nil {
					log.WithFields(log.Fields{
						"guest": g.Id,
						"error": err,
						"func":  "agent.Context.DeleteGuest",
					}).Error("Delete Error:", err)
				}
				return
			}
		}()
		hr.JSON(http.StatusAccepted, g)
	}
}

// GetRequestGuest retrieves the guest from the request context
func GetRequestGuest(r *http.Request) *client.Guest {
	if value := context.Get(r, requestGuestKey); value != nil {
		return value.(*client.Guest)
	}
	return nil
}
<|endoftext|>


<commit_before>package mackerel

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Host host information
type Host struct {
	ID            string      `json:"id,omitempty"`
	Name          string      `json:"name,omitempty"`
	DisplayName   string      `json:"displayName,omitempty"`
	Type          string      `json:"type,omitempty"`
	Status        string      `json:"status,omitempty"`
	Memo          string      `json:"memo,omitempty"`
	Roles         Roles       `json:"roles,omitempty"`
	RoleFullnames []string    `json:"roleFullnames,omitempty"`
	IsRetired     bool        `json:"isRetired,omitempty"`
	CreatedAt     int32       `json:"createdAt,omitempty"`
	Meta          HostMeta    `json:"meta,omitempty"`
	Interfaces    []Interface `json:"interfaces,omitempty"`
}

// Roles host role maps
type Roles map[string][]string

// HostMeta host meta informations
type HostMeta struct {
	AgentRevision string      `json:"agent-revision,omitempty"`
	AgentVersion  string      `json:"agent-version,omitempty"`
	BlockDevice   BlockDevice `json:"block_device,omitempty"`
	CPU           CPU         `json:"cpu,omitempty"`
	Filesystem    FileSystem  `json:"filesystem,omitempty"`
	Kernel        Kernel      `json:"kernel,omitempty"`
	Memory        Memory      `json:"memory,omitempty"`
	Cloud         Cloud       `json:"cloud,omitempty"`
}

// BlockDevice blockdevice
type BlockDevice map[string]map[string]interface{}

// CPU cpu
type CPU []map[string]interface{}

// FileSystem filesystem
type FileSystem map[string]interface{}

// Kernel kernel
type Kernel map[string]string

// Memory memory
type Memory map[string]string

// Cloud cloud
type Cloud struct {
	Provider string            `json:"provider,omitempty"`
	MetaData map[string]string `json:"metadata,omitempty"`
}

// Interface network interface
type Interface struct {
	Name       string `json:"name,omitempty"`
	IPAddress  string `json:"ipAddress,omitempty"`
	MacAddress string `json:"macAddress,omitempty"`
}

// FindHostsParam parameters for FindHosts
type FindHostsParam struct {
	Service          string
	Roles            []string
	Name             string
	Statuses         []string
	CustomIdentifier string
}

// CreateHostParam parameters for CreateHost
type CreateHostParam struct {
	Name             string      `json:"name,omitempty"`
	DisplayName      string      `json:"displayName,omitempty"`
	Meta             HostMeta    `json:"meta,omitempty"`
	Interfaces       []Interface `json:"interfaces,omitempty"`
	RoleFullnames    []string    `json:"roleFullnames,omitempty"`
	CustomIdentifier string      `json:"customIdentifier,omitempty"`
}

// UpdateHostParam parameters for UpdateHost
type UpdateHostParam CreateHostParam

// GetRoleFullnames getrolefullnames
func (h *Host) GetRoleFullnames() []string {
	if len(h.Roles) < 1 {
		return nil
	}

	var fullnames []string
	for service, roles := range h.Roles {
		for _, role := range roles {
			fullname := strings.Join([]string{service, role}, ":")
			fullnames = append(fullnames, fullname)
		}
	}

	return fullnames
}

// DateFromCreatedAt returns time.Time
func (h *Host) DateFromCreatedAt() time.Time {
	return time.Unix(int64(h.CreatedAt), 0)
}

// DateStringFromCreatedAt returns date string
func (h *Host) DateStringFromCreatedAt() string {
	const layout = "Jan 2, 2006 at 3:04pm (MST)"
	return h.DateFromCreatedAt().Format(layout)
}

// IPAddresses returns ipaddresses
func (h *Host) IPAddresses() map[string]string {
	if len(h.Interfaces) < 1 {
		return nil
	}

	ipAddresses := make(map[string]string, 0)
	for _, iface := range h.Interfaces {
		ipAddresses[iface.Name] = iface.IPAddress
	}
	return ipAddresses
}

// FindHost find the host
func (c *Client) FindHost(id string) (*Host, error) {
	req, err := http.NewRequest("GET", c.urlFor(fmt.Sprintf("/api/v0/hosts/%s", id)).String(), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}

	var data struct {
		Host *Host `json:"host"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return nil, err
	}
	return data.Host, err
}

// FindHosts find hosts
func (c *Client) FindHosts(param *FindHostsParam) ([]*Host, error) {
	v := url.Values{}
	if param.Service != "" {
		v.Set("service", param.Service)
	}
	if len(param.Roles) >= 1 {
		for _, role := range param.Roles {
			v.Add("role", role)
		}
	}
	if param.Name != "" {
		v.Set("name", param.Name)
	}
	if len(param.Statuses) >= 1 {
		for _, status := range param.Statuses {
			v.Add("status", status)
		}
	}
	if param.CustomIdentifier != "" {
		v.Set("customIdentifier", param.CustomIdentifier)
	}

	req, err := http.NewRequest("GET", fmt.Sprintf("%s?%s", c.urlFor("/api/v0/hosts.json").String(), v.Encode()), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}

	var data struct {
		Hosts []*(Host) `json:"hosts"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return nil, err
	}

	return data.Hosts, err
}

// CreateHost creating host
func (c *Client) CreateHost(param *CreateHostParam) (string, error) {
	resp, err := c.PostJSON("/api/v0/hosts", param)
	defer closeResponse(resp)
	if err != nil {
		return "", err
	}

	var data struct {
		ID string `json:"id"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return "", err
	}
	return data.ID, nil
}

// UpdateHost update host
func (c *Client) UpdateHost(hostID string, param *UpdateHostParam) (string, error) {
	resp, err := c.PutJSON(fmt.Sprintf("/api/v0/hosts/%s", hostID), param)
	defer closeResponse(resp)
	if err != nil {
		return "", err
	}

	var data struct {
		ID string `json:"id"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return "", err
	}

	return data.ID, nil
}

// UpdateHostStatus update host status
func (c *Client) UpdateHostStatus(hostID string, status string) error {
	resp, err := c.PostJSON(fmt.Sprintf("/api/v0/hosts/%s/status", hostID), map[string]string{
		"status": status,
	})
	defer closeResponse(resp)
	if err != nil {
		return err
	}
	return nil
}

// RetireHost retuire the host
func (c *Client) RetireHost(id string) error {
	resp, err := c.PostJSON(fmt.Sprintf("/api/v0/hosts/%s/retire", id), "{}")
	defer closeResponse(resp)
	return err
}
<commit_msg>make the value of the metadata interface{}<commit_after>package mackerel

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Host host information
type Host struct {
	ID            string      `json:"id,omitempty"`
	Name          string      `json:"name,omitempty"`
	DisplayName   string      `json:"displayName,omitempty"`
	Type          string      `json:"type,omitempty"`
	Status        string      `json:"status,omitempty"`
	Memo          string      `json:"memo,omitempty"`
	Roles         Roles       `json:"roles,omitempty"`
	RoleFullnames []string    `json:"roleFullnames,omitempty"`
	IsRetired     bool        `json:"isRetired,omitempty"`
	CreatedAt     int32       `json:"createdAt,omitempty"`
	Meta          HostMeta    `json:"meta,omitempty"`
	Interfaces    []Interface `json:"interfaces,omitempty"`
}

// Roles host role maps
type Roles map[string][]string

// HostMeta host meta informations
type HostMeta struct {
	AgentRevision string      `json:"agent-revision,omitempty"`
	AgentVersion  string      `json:"agent-version,omitempty"`
	BlockDevice   BlockDevice `json:"block_device,omitempty"`
	CPU           CPU         `json:"cpu,omitempty"`
	Filesystem    FileSystem  `json:"filesystem,omitempty"`
	Kernel        Kernel      `json:"kernel,omitempty"`
	Memory        Memory      `json:"memory,omitempty"`
	Cloud         Cloud       `json:"cloud,omitempty"`
}

// BlockDevice blockdevice
type BlockDevice map[string]map[string]interface{}

// CPU cpu
type CPU []map[string]interface{}

// FileSystem filesystem
type FileSystem map[string]interface{}

// Kernel kernel
type Kernel map[string]string

// Memory memory
type Memory map[string]string

// Cloud cloud
type Cloud struct {
	Provider string                 `json:"provider,omitempty"`
	MetaData map[string]interface{} `json:"metadata,omitempty"`
}

// Interface network interface
type Interface struct {
	Name       string `json:"name,omitempty"`
	IPAddress  string `json:"ipAddress,omitempty"`
	MacAddress string `json:"macAddress,omitempty"`
}

// FindHostsParam parameters for FindHosts
type FindHostsParam struct {
	Service          string
	Roles            []string
	Name             string
	Statuses         []string
	CustomIdentifier string
}

// CreateHostParam parameters for CreateHost
type CreateHostParam struct {
	Name             string      `json:"name,omitempty"`
	DisplayName      string      `json:"displayName,omitempty"`
	Meta             HostMeta    `json:"meta,omitempty"`
	Interfaces       []Interface `json:"interfaces,omitempty"`
	RoleFullnames    []string    `json:"roleFullnames,omitempty"`
	CustomIdentifier string      `json:"customIdentifier,omitempty"`
}

// UpdateHostParam parameters for UpdateHost
type UpdateHostParam CreateHostParam

// GetRoleFullnames getrolefullnames
func (h *Host) GetRoleFullnames() []string {
	if len(h.Roles) < 1 {
		return nil
	}

	var fullnames []string
	for service, roles := range h.Roles {
		for _, role := range roles {
			fullname := strings.Join([]string{service, role}, ":")
			fullnames = append(fullnames, fullname)
		}
	}

	return fullnames
}

// DateFromCreatedAt returns time.Time
func (h *Host) DateFromCreatedAt() time.Time {
	return time.Unix(int64(h.CreatedAt), 0)
}

// DateStringFromCreatedAt returns date string
func (h *Host) DateStringFromCreatedAt() string {
	const layout = "Jan 2, 2006 at 3:04pm (MST)"
	return h.DateFromCreatedAt().Format(layout)
}

// IPAddresses returns ipaddresses
func (h *Host) IPAddresses() map[string]string {
	if len(h.Interfaces) < 1 {
		return nil
	}

	ipAddresses := make(map[string]string, 0)
	for _, iface := range h.Interfaces {
		ipAddresses[iface.Name] = iface.IPAddress
	}
	return ipAddresses
}

// FindHost find the host
func (c *Client) FindHost(id string) (*Host, error) {
	req, err := http.NewRequest("GET", c.urlFor(fmt.Sprintf("/api/v0/hosts/%s", id)).String(), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}

	var data struct {
		Host *Host `json:"host"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return nil, err
	}
	return data.Host, err
}

// FindHosts find hosts
func (c *Client) FindHosts(param *FindHostsParam) ([]*Host, error) {
	v := url.Values{}
	if param.Service != "" {
		v.Set("service", param.Service)
	}
	if len(param.Roles) >= 1 {
		for _, role := range param.Roles {
			v.Add("role", role)
		}
	}
	if param.Name != "" {
		v.Set("name", param.Name)
	}
	if len(param.Statuses) >= 1 {
		for _, status := range param.Statuses {
			v.Add("status", status)
		}
	}
	if param.CustomIdentifier != "" {
		v.Set("customIdentifier", param.CustomIdentifier)
	}

	req, err := http.NewRequest("GET", fmt.Sprintf("%s?%s", c.urlFor("/api/v0/hosts.json").String(), v.Encode()), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}

	var data struct {
		Hosts []*(Host) `json:"hosts"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return nil, err
	}

	return data.Hosts, err
}

// CreateHost creating host
func (c *Client) CreateHost(param *CreateHostParam) (string, error) {
	resp, err := c.PostJSON("/api/v0/hosts", param)
	defer closeResponse(resp)
	if err != nil {
		return "", err
	}

	var data struct {
		ID string `json:"id"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return "", err
	}
	return data.ID, nil
}

// UpdateHost update host
func (c *Client) UpdateHost(hostID string, param *UpdateHostParam) (string, error) {
	resp, err := c.PutJSON(fmt.Sprintf("/api/v0/hosts/%s", hostID), param)
	defer closeResponse(resp)
	if err != nil {
		return "", err
	}

	var data struct {
		ID string `json:"id"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return "", err
	}

	return data.ID, nil
}

// UpdateHostStatus update host status
func (c *Client) UpdateHostStatus(hostID string, status string) error {
	resp, err := c.PostJSON(fmt.Sprintf("/api/v0/hosts/%s/status", hostID), map[string]string{
		"status": status,
	})
	defer closeResponse(resp)
	if err != nil {
		return err
	}
	return nil
}

// RetireHost retuire the host
func (c *Client) RetireHost(id string) error {
	resp, err := c.PostJSON(fmt.Sprintf("/api/v0/hosts/%s/retire", id), "{}")
	defer closeResponse(resp)
	return err
}
<|endoftext|>


<commit_before>/*
   Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package client

import (
	"code.google.com/p/go.net/websocket"
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/percona/cloud-protocol/proto"
	"github.com/percona/percona-agent/pct"
	"net"
	"strings"
	"sync"
	"time"
)

const (
	SEND_BUFFER_SIZE = 10
	RECV_BUFFER_SIZE = 10
)

type WebsocketClient struct {
	logger *pct.Logger
	api    pct.APIConnector
	link   string
	// --
	conn        *websocket.Conn
	recvChan    chan *proto.Cmd
	sendChan    chan *proto.Reply
	connectChan chan bool
	errChan     chan error
	backoff     *pct.Backoff
	started     bool
	sendSync    *pct.SyncChan
	recvSync    *pct.SyncChan
	mux         *sync.Mutex
	name        string
	status      *pct.Status
	connected   bool
}

func NewWebsocketClient(logger *pct.Logger, api pct.APIConnector, link string) (*WebsocketClient, error) {
	name := logger.Service()
	c := &WebsocketClient{
		logger: logger,
		api:    api,
		link:   link,
		// --
		conn:        nil,
		recvChan:    make(chan *proto.Cmd, RECV_BUFFER_SIZE),
		sendChan:    make(chan *proto.Reply, SEND_BUFFER_SIZE),
		connectChan: make(chan bool, 1),
		errChan:     make(chan error, 2),
		backoff:     pct.NewBackoff(5 * time.Minute),
		sendSync:    pct.NewSyncChan(),
		recvSync:    pct.NewSyncChan(),
		mux:         new(sync.Mutex),
		name:        name,
		status:      pct.NewStatus([]string{name, name + "-link"}),
	}
	return c, nil
}

func (c *WebsocketClient) Start() {
	// Start send() and recv() goroutines, but they wait for successful Connect().
	if !c.started {
		go c.send()
		go c.recv()
		c.started = true
	}
}

func (c *WebsocketClient) Stop() {
	if c.started {
		c.sendSync.Stop()
		c.recvSync.Stop()
		c.sendSync.Wait()
		c.recvSync.Wait()
		c.started = false
	}
}

func (c *WebsocketClient) Connect() {
	c.logger.Debug("Connect:call")
	defer c.logger.Debug("Connect:return")

	for {
		// Wait before attempt to avoid DDoS'ing the API
		// (there are many other agents in the world).
		c.logger.Debug("Connect:backoff.Wait")
		c.status.Update(c.name, "Connect wait")
		time.Sleep(c.backoff.Wait())

		if err := c.ConnectOnce(); err != nil {
			c.logger.Warn(err)
			continue
		}

		// Start/resume send() and recv() goroutines if Start() was called.
		if c.started {
			c.recvSync.Start()
			c.sendSync.Start()
		}

		c.backoff.Success()
		c.notifyConnect(true)
		return // success
	}
}

func TlsDialTimeout(network, addr string, config *tls.Config, timeout uint) (*tls.Conn, error) {
	raddr := addr
	c, err := net.DialTimeout(network, raddr, time.Duration(timeout)*time.Second)
	if err != nil {
		return nil, err
	}

	colonPos := strings.LastIndex(raddr, ":")
	if colonPos == -1 {
		colonPos = len(raddr)
	}
	hostname := raddr[:colonPos]

	if config == nil {
		return nil, fmt.Errorf("No config passed to TlsDial")
	}
	// If no ServerName is set, infer the ServerName
	// from the hostname we're connecting to.
	if config.ServerName == "" {
		// Make a copy to avoid polluting argument or default.
		c := *config
		c.ServerName = hostname
		config = &c
	}
	conn := tls.Client(c, config)
	if err = conn.Handshake(); err != nil {
		c.Close()
		return nil, err
	}
	return conn, nil
}

// We need this function, because websocket.DialConfig is limited to connection without timeout.
// DialConfigTimeout opens a new client connection to a WebSocket with a config and timeout.
func DialConfigTimeout(config *websocket.Config, timeout uint) (ws *websocket.Conn, err error) {
	var client net.Conn
	if config.Location == nil {
		return nil, &websocket.DialError{config, websocket.ErrBadWebSocketLocation}
	}
	if config.Origin == nil {
		return nil, &websocket.DialError{config, websocket.ErrBadWebSocketOrigin}
	}
	switch config.Location.Scheme {
	case "ws":
		client, err = net.DialTimeout("tcp", config.Location.Host, time.Duration(timeout)*time.Second)

	case "wss":
		client, err = TlsDialTimeout("tcp", config.Location.Host, config.TlsConfig, timeout)

	default:
		err = websocket.ErrBadScheme
	}
	if err != nil {
		goto Error
	}

	ws, err = websocket.NewClient(config, client)
	if err != nil {
		goto Error
	}
	return

Error:
	return nil, &websocket.DialError{config, err}
}

func (c *WebsocketClient) ConnectOnce() error {
	c.logger.Debug("ConnectOnce:call")
	defer c.logger.Debug("ConnectOnce:return")

	// Make websocket connection.  If this fails, either API is down or the ws
	// address is wrong.
	link := c.api.AgentLink(c.link)
	c.logger.Debug("ConnectOnce:link:" + link)
	config, err := websocket.NewConfig(link, c.api.Origin())
	if err != nil {
		return err
	}
	config.Header.Add("X-Percona-API-Key", c.api.ApiKey())

	c.logger.Debug("ConnectOnce:websocket.DialConfig")
	c.status.Update(c.name, "Connecting "+link)
	conn, err := DialConfigTimeout(config, 10) // 10 seconds timeout
	if err != nil {
		return err
	}

	c.mux.Lock()
	defer c.mux.Unlock()
	c.connected = true
	c.conn = conn
	c.status.Update(c.name, "Connected "+link)

	return nil
}

func (c *WebsocketClient) Disconnect() error {
	c.logger.DebugOffline("Disconnect:call")
	defer c.logger.DebugOffline("Disconnect:return")

	/**
	 * Must guard c.conn here to prevent duplicate notifyConnect() because Close()
	 * causes recv() to error which calls Disconnect(), and normally we want this:
	 * to call Disconnect() on recv error so that notifyConnect(false) is called
	 * to let user know that remote end hung up.  However, when user hangs up
	 * the Disconnect() call from recv() is duplicate and not needed.
	 */
	c.mux.Lock()
	defer c.mux.Unlock()

	if c.connected {
		c.logger.DebugOffline("Disconnect:websocket.Conn.Close")
		if err := c.conn.Close(); err != nil {
			// Example: write tcp 127.0.0.1:8000: i/o timeout
			// That ^ can happen if remote end hangs up, then we call Close().
			// Since there's nothing we can do about errors here, we ignore them.
			c.logger.DebugOffline("Disconnect:websocket.Conn.Close:err:" + err.Error())
		}
		/**
		 * Do not set c.conn = nil to indicate that connection is closed because
		 * unless we also guard c.conn in Send() and Recv() c.conn.Set*Deadline()
		 * will panic.  If the underlying websocket.Conn is closed, then
		 * Set*Deadline() will do nothing and websocket.JSON.Send/Receive() will
		 * just return an error, which is a lot better than a panic.
		 */
		c.connected = false
		c.logger.DebugOffline("Disconnect:disconnected")
		c.status.Update(c.name, "Disconnected")
		c.notifyConnect(false)
	}

	return nil
}

func (c *WebsocketClient) send() {
	/**
	 * Send Reply from agent to API.
	 */

	c.logger.DebugOffline("send:call")
	defer c.logger.DebugOffline("send:return")
	defer c.sendSync.Done()

	for {
		// Wait to start (connect) or be told to stop.
		c.logger.DebugOffline("send:start")
		select {
		case <-c.sendSync.StartChan:
			c.sendSync.StartChan <- true
		case <-c.sendSync.StopChan:
			return
		}

	SEND_LOOP:
		for {
			c.logger.DebugOffline("send:wait")
			select {
			case reply := <-c.sendChan:
				// Got Reply from agent, send to API.
				c.logger.DebugOffline("send:reply:", reply)
				if err := c.Send(reply, 10); err != nil {
					c.logger.DebugOffline("send:err:", err)
					select {
					case c.errChan <- err:
					default:
					}
					break SEND_LOOP
				}
			case <-c.sendSync.StopChan:
				c.logger.DebugOffline("send:stop")
				return
			}
		}

		c.logger.DebugOffline("send:Disconnect")
		c.Disconnect()
	}
}

func (c *WebsocketClient) recv() {
	/**
	 * Receive Cmd from API, forward to agent.
	 */

	c.logger.DebugOffline("recv:call")
	defer c.logger.DebugOffline("recv:return")
	defer c.recvSync.Done()

	for {
		// Wait to start (connect) or be told to stop.
		c.logger.DebugOffline("recv:start")
		select {
		case <-c.recvSync.StartChan:
			c.recvSync.StartChan <- true
		case <-c.recvSync.StopChan:
			return
		}

	RECV_LOOP:
		for {
			// Before blocking on Recv, see if we're supposed to stop.
			c.logger.DebugOffline("recv:wait")
			select {
			case <-c.recvSync.StopChan:
				c.logger.DebugOffline("recv:stop")
				return
			default:
			}

			// Wait for Cmd from API.
			cmd := &proto.Cmd{}
			if err := c.Recv(cmd, 0); err != nil {
				c.logger.DebugOffline("recv:err:", err)
				select {
				case c.errChan <- err:
				default:
				}
				break RECV_LOOP
			}

			// Forward Cmd to agent.
			c.logger.DebugOffline("recv:cmd:", cmd)
			c.recvChan <- cmd
		}

		c.logger.DebugOffline("recv:Disconnect")
		c.Disconnect()
	}
}

func (c *WebsocketClient) SendChan() chan *proto.Reply {
	return c.sendChan
}

func (c *WebsocketClient) RecvChan() chan *proto.Cmd {
	return c.recvChan
}

func (c *WebsocketClient) Send(data interface{}, timeout uint) error {
	// These make the debug output a little too verbose:
	// c.logger.DebugOffline("Send:call")
	// defer c.logger.DebugOffline("Send:return")

	/**
	 * I cannot provoke an EOF error on websocket.Send(), only Receive().
	 * Perhaps EOF errors are only reported on recv?  This only affects
	 * the logger since it's ws send-only: it will need a goroutine blocking
	 * on Recieve() that, upon error, notifies the sending goroutine
	 * to reconnect.
	 */
	if timeout > 0 {
		c.conn.SetWriteDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
	} else {
		c.conn.SetWriteDeadline(time.Time{})
	}
	if err := websocket.JSON.Send(c.conn, data); err != nil {
		c.logger.DebugOffline("Send:err:", err)
		return fmt.Errorf("Send:", err)
	}
	return nil
}

func (c *WebsocketClient) SendBytes(data []byte) error {
	c.logger.DebugOffline("SendBytes:call")
	defer c.logger.DebugOffline("SendBytes:return")
	c.conn.SetWriteDeadline(time.Now().Add(20 * time.Second))
	return websocket.Message.Send(c.conn, data)
}

func (c *WebsocketClient) Recv(data interface{}, timeout uint) error {
	c.logger.DebugOffline("Recv:call")
	defer c.logger.DebugOffline("Recv:return")
	if timeout > 0 {
		t := time.Now().Add(time.Duration(timeout) * time.Second)
		c.conn.SetReadDeadline(t)
	} else {
		c.conn.SetReadDeadline(time.Time{})
	}
	if err := websocket.JSON.Receive(c.conn, data); err != nil {
		c.logger.DebugOffline("Recv:err:", err)
		return errors.New(fmt.Sprint("Recv:", err))
	}
	return nil
}

func (c *WebsocketClient) ConnectChan() chan bool {
	return c.connectChan
}

func (c *WebsocketClient) ErrorChan() chan error {
	return c.errChan
}

func (c *WebsocketClient) Conn() *websocket.Conn {
	return c.conn
}

func (c *WebsocketClient) Status() map[string]string {
	c.status.Update(c.name+"-link", c.api.AgentLink(c.link))
	return c.status.All()
}

func (c *WebsocketClient) notifyConnect(state bool) {
	c.logger.DebugOffline(fmt.Sprintf("notifyConnect:call:%t", state))
	defer c.logger.DebugOffline("notifyConnect:return")
	select {
	case c.connectChan <- state:
	case <-time.After(20 * time.Second):
		c.logger.Error("notifyConnect timeout")
	}
}
<commit_msg>error descr fix<commit_after>/*
   Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package client

import (
	"code.google.com/p/go.net/websocket"
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/percona/cloud-protocol/proto"
	"github.com/percona/percona-agent/pct"
	"net"
	"strings"
	"sync"
	"time"
)

const (
	SEND_BUFFER_SIZE = 10
	RECV_BUFFER_SIZE = 10
)

type WebsocketClient struct {
	logger *pct.Logger
	api    pct.APIConnector
	link   string
	// --
	conn        *websocket.Conn
	recvChan    chan *proto.Cmd
	sendChan    chan *proto.Reply
	connectChan chan bool
	errChan     chan error
	backoff     *pct.Backoff
	started     bool
	sendSync    *pct.SyncChan
	recvSync    *pct.SyncChan
	mux         *sync.Mutex
	name        string
	status      *pct.Status
	connected   bool
}

func NewWebsocketClient(logger *pct.Logger, api pct.APIConnector, link string) (*WebsocketClient, error) {
	name := logger.Service()
	c := &WebsocketClient{
		logger: logger,
		api:    api,
		link:   link,
		// --
		conn:        nil,
		recvChan:    make(chan *proto.Cmd, RECV_BUFFER_SIZE),
		sendChan:    make(chan *proto.Reply, SEND_BUFFER_SIZE),
		connectChan: make(chan bool, 1),
		errChan:     make(chan error, 2),
		backoff:     pct.NewBackoff(5 * time.Minute),
		sendSync:    pct.NewSyncChan(),
		recvSync:    pct.NewSyncChan(),
		mux:         new(sync.Mutex),
		name:        name,
		status:      pct.NewStatus([]string{name, name + "-link"}),
	}
	return c, nil
}

func (c *WebsocketClient) Start() {
	// Start send() and recv() goroutines, but they wait for successful Connect().
	if !c.started {
		go c.send()
		go c.recv()
		c.started = true
	}
}

func (c *WebsocketClient) Stop() {
	if c.started {
		c.sendSync.Stop()
		c.recvSync.Stop()
		c.sendSync.Wait()
		c.recvSync.Wait()
		c.started = false
	}
}

func (c *WebsocketClient) Connect() {
	c.logger.Debug("Connect:call")
	defer c.logger.Debug("Connect:return")

	for {
		// Wait before attempt to avoid DDoS'ing the API
		// (there are many other agents in the world).
		c.logger.Debug("Connect:backoff.Wait")
		c.status.Update(c.name, "Connect wait")
		time.Sleep(c.backoff.Wait())

		if err := c.ConnectOnce(); err != nil {
			c.logger.Warn(err)
			continue
		}

		// Start/resume send() and recv() goroutines if Start() was called.
		if c.started {
			c.recvSync.Start()
			c.sendSync.Start()
		}

		c.backoff.Success()
		c.notifyConnect(true)
		return // success
	}
}

func TlsDialTimeout(network, addr string, config *tls.Config, timeout uint) (*tls.Conn, error) {
	raddr := addr
	c, err := net.DialTimeout(network, raddr, time.Duration(timeout)*time.Second)
	if err != nil {
		return nil, err
	}

	colonPos := strings.LastIndex(raddr, ":")
	if colonPos == -1 {
		colonPos = len(raddr)
	}
	hostname := raddr[:colonPos]

	if config == nil {
		return nil, fmt.Errorf("No config passed to TlsDialTimeout")
	}
	// If no ServerName is set, infer the ServerName
	// from the hostname we're connecting to.
	if config.ServerName == "" {
		// Make a copy to avoid polluting argument or default.
		c := *config
		c.ServerName = hostname
		config = &c
	}
	conn := tls.Client(c, config)
	if err = conn.Handshake(); err != nil {
		c.Close()
		return nil, err
	}
	return conn, nil
}

// We need this function, because websocket.DialConfig is limited to connection without timeout.
// DialConfigTimeout opens a new client connection to a WebSocket with a config and timeout.
func DialConfigTimeout(config *websocket.Config, timeout uint) (ws *websocket.Conn, err error) {
	var client net.Conn
	if config.Location == nil {
		return nil, &websocket.DialError{config, websocket.ErrBadWebSocketLocation}
	}
	if config.Origin == nil {
		return nil, &websocket.DialError{config, websocket.ErrBadWebSocketOrigin}
	}
	switch config.Location.Scheme {
	case "ws":
		client, err = net.DialTimeout("tcp", config.Location.Host, time.Duration(timeout)*time.Second)

	case "wss":
		client, err = TlsDialTimeout("tcp", config.Location.Host, config.TlsConfig, timeout)

	default:
		err = websocket.ErrBadScheme
	}
	if err != nil {
		goto Error
	}

	ws, err = websocket.NewClient(config, client)
	if err != nil {
		goto Error
	}
	return

Error:
	return nil, &websocket.DialError{config, err}
}

func (c *WebsocketClient) ConnectOnce() error {
	c.logger.Debug("ConnectOnce:call")
	defer c.logger.Debug("ConnectOnce:return")

	// Make websocket connection.  If this fails, either API is down or the ws
	// address is wrong.
	link := c.api.AgentLink(c.link)
	c.logger.Debug("ConnectOnce:link:" + link)
	config, err := websocket.NewConfig(link, c.api.Origin())
	if err != nil {
		return err
	}
	config.Header.Add("X-Percona-API-Key", c.api.ApiKey())

	c.logger.Debug("ConnectOnce:websocket.DialConfig")
	c.status.Update(c.name, "Connecting "+link)
	conn, err := DialConfigTimeout(config, 10) // 10 seconds timeout
	if err != nil {
		return err
	}

	c.mux.Lock()
	defer c.mux.Unlock()
	c.connected = true
	c.conn = conn
	c.status.Update(c.name, "Connected "+link)

	return nil
}

func (c *WebsocketClient) Disconnect() error {
	c.logger.DebugOffline("Disconnect:call")
	defer c.logger.DebugOffline("Disconnect:return")

	/**
	 * Must guard c.conn here to prevent duplicate notifyConnect() because Close()
	 * causes recv() to error which calls Disconnect(), and normally we want this:
	 * to call Disconnect() on recv error so that notifyConnect(false) is called
	 * to let user know that remote end hung up.  However, when user hangs up
	 * the Disconnect() call from recv() is duplicate and not needed.
	 */
	c.mux.Lock()
	defer c.mux.Unlock()

	if c.connected {
		c.logger.DebugOffline("Disconnect:websocket.Conn.Close")
		if err := c.conn.Close(); err != nil {
			// Example: write tcp 127.0.0.1:8000: i/o timeout
			// That ^ can happen if remote end hangs up, then we call Close().
			// Since there's nothing we can do about errors here, we ignore them.
			c.logger.DebugOffline("Disconnect:websocket.Conn.Close:err:" + err.Error())
		}
		/**
		 * Do not set c.conn = nil to indicate that connection is closed because
		 * unless we also guard c.conn in Send() and Recv() c.conn.Set*Deadline()
		 * will panic.  If the underlying websocket.Conn is closed, then
		 * Set*Deadline() will do nothing and websocket.JSON.Send/Receive() will
		 * just return an error, which is a lot better than a panic.
		 */
		c.connected = false
		c.logger.DebugOffline("Disconnect:disconnected")
		c.status.Update(c.name, "Disconnected")
		c.notifyConnect(false)
	}

	return nil
}

func (c *WebsocketClient) send() {
	/**
	 * Send Reply from agent to API.
	 */

	c.logger.DebugOffline("send:call")
	defer c.logger.DebugOffline("send:return")
	defer c.sendSync.Done()

	for {
		// Wait to start (connect) or be told to stop.
		c.logger.DebugOffline("send:start")
		select {
		case <-c.sendSync.StartChan:
			c.sendSync.StartChan <- true
		case <-c.sendSync.StopChan:
			return
		}

	SEND_LOOP:
		for {
			c.logger.DebugOffline("send:wait")
			select {
			case reply := <-c.sendChan:
				// Got Reply from agent, send to API.
				c.logger.DebugOffline("send:reply:", reply)
				if err := c.Send(reply, 10); err != nil {
					c.logger.DebugOffline("send:err:", err)
					select {
					case c.errChan <- err:
					default:
					}
					break SEND_LOOP
				}
			case <-c.sendSync.StopChan:
				c.logger.DebugOffline("send:stop")
				return
			}
		}

		c.logger.DebugOffline("send:Disconnect")
		c.Disconnect()
	}
}

func (c *WebsocketClient) recv() {
	/**
	 * Receive Cmd from API, forward to agent.
	 */

	c.logger.DebugOffline("recv:call")
	defer c.logger.DebugOffline("recv:return")
	defer c.recvSync.Done()

	for {
		// Wait to start (connect) or be told to stop.
		c.logger.DebugOffline("recv:start")
		select {
		case <-c.recvSync.StartChan:
			c.recvSync.StartChan <- true
		case <-c.recvSync.StopChan:
			return
		}

	RECV_LOOP:
		for {
			// Before blocking on Recv, see if we're supposed to stop.
			c.logger.DebugOffline("recv:wait")
			select {
			case <-c.recvSync.StopChan:
				c.logger.DebugOffline("recv:stop")
				return
			default:
			}

			// Wait for Cmd from API.
			cmd := &proto.Cmd{}
			if err := c.Recv(cmd, 0); err != nil {
				c.logger.DebugOffline("recv:err:", err)
				select {
				case c.errChan <- err:
				default:
				}
				break RECV_LOOP
			}

			// Forward Cmd to agent.
			c.logger.DebugOffline("recv:cmd:", cmd)
			c.recvChan <- cmd
		}

		c.logger.DebugOffline("recv:Disconnect")
		c.Disconnect()
	}
}

func (c *WebsocketClient) SendChan() chan *proto.Reply {
	return c.sendChan
}

func (c *WebsocketClient) RecvChan() chan *proto.Cmd {
	return c.recvChan
}

func (c *WebsocketClient) Send(data interface{}, timeout uint) error {
	// These make the debug output a little too verbose:
	// c.logger.DebugOffline("Send:call")
	// defer c.logger.DebugOffline("Send:return")

	/**
	 * I cannot provoke an EOF error on websocket.Send(), only Receive().
	 * Perhaps EOF errors are only reported on recv?  This only affects
	 * the logger since it's ws send-only: it will need a goroutine blocking
	 * on Recieve() that, upon error, notifies the sending goroutine
	 * to reconnect.
	 */
	if timeout > 0 {
		c.conn.SetWriteDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
	} else {
		c.conn.SetWriteDeadline(time.Time{})
	}
	if err := websocket.JSON.Send(c.conn, data); err != nil {
		c.logger.DebugOffline("Send:err:", err)
		return fmt.Errorf("Send:", err)
	}
	return nil
}

func (c *WebsocketClient) SendBytes(data []byte) error {
	c.logger.DebugOffline("SendBytes:call")
	defer c.logger.DebugOffline("SendBytes:return")
	c.conn.SetWriteDeadline(time.Now().Add(20 * time.Second))
	return websocket.Message.Send(c.conn, data)
}

func (c *WebsocketClient) Recv(data interface{}, timeout uint) error {
	c.logger.DebugOffline("Recv:call")
	defer c.logger.DebugOffline("Recv:return")
	if timeout > 0 {
		t := time.Now().Add(time.Duration(timeout) * time.Second)
		c.conn.SetReadDeadline(t)
	} else {
		c.conn.SetReadDeadline(time.Time{})
	}
	if err := websocket.JSON.Receive(c.conn, data); err != nil {
		c.logger.DebugOffline("Recv:err:", err)
		return errors.New(fmt.Sprint("Recv:", err))
	}
	return nil
}

func (c *WebsocketClient) ConnectChan() chan bool {
	return c.connectChan
}

func (c *WebsocketClient) ErrorChan() chan error {
	return c.errChan
}

func (c *WebsocketClient) Conn() *websocket.Conn {
	return c.conn
}

func (c *WebsocketClient) Status() map[string]string {
	c.status.Update(c.name+"-link", c.api.AgentLink(c.link))
	return c.status.All()
}

func (c *WebsocketClient) notifyConnect(state bool) {
	c.logger.DebugOffline(fmt.Sprintf("notifyConnect:call:%t", state))
	defer c.logger.DebugOffline("notifyConnect:return")
	select {
	case c.connectChan <- state:
	case <-time.After(20 * time.Second):
		c.logger.Error("notifyConnect timeout")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"os"

	"github.com/goph/fxt/debug"
	"github.com/goph/fxt/log"
	"github.com/kelseyhightower/envconfig"
)

// NewConfig creates the application Config from flags and the environment.
func NewConfig() (*Config, error) {
	config := new(Config)

	// Load Config from environment
	err := envconfig.Process("", config)
	if err != nil {
		return nil, err
	}

	// Load Config from flags
	flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	config.flags(flags)
	flags.Parse(os.Args[1:])

	return config, nil
}

// NewLoggerConfig creates a logger config constructor.
func NewLoggerConfig(config *Config) (*log.Config, error) {
	c := log.NewConfig()
	f, err := log.ParseFormat(config.LogFormat)
	if err != nil {
		return nil, err
	}

	c.Format = f
	c.Debug = config.Debug
	c.Context = []interface{}{
		"environment", config.Environment,
		"service", ServiceName,
		"tag", LogTag,
	}

	return c, nil
}

// NewDebugConfig creates a debug config constructor.
func NewDebugConfig(config *Config) *debug.Config {
	c := debug.NewConfig(config.DebugAddr)
	c.Debug = config.Debug

	return c
}
<commit_msg>Fix type<commit_after>package main

import (
	"flag"
	"os"

	"github.com/goph/fxt/debug"
	"github.com/goph/fxt/log"
	"github.com/kelseyhightower/envconfig"
)

// NewConfig creates the application Config from flags and the environment.
func NewConfig() (*Config, error) {
	config := new(Config)

	// Load Config from environment
	err := envconfig.Process("", config)
	if err != nil {
		return nil, err
	}

	// Load Config from flags
	flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	config.flags(flags)
	flags.Parse(os.Args[1:])

	return config, nil
}

// NewLoggerConfig creates a logger config constructor.
func NewLoggerConfig(config *Config) (*log.Config, error) {
	c := log.NewConfig()
	f, err := log.ParseFormat(config.LogFormat)
	if err != nil {
		return nil, err
	}

	c.Format = f
	c.Debug = config.Debug
	c.Context = []interface{}{
		"environment", config.Environment,
		"service", ServiceName,
		"tag", LogTag,
	}

	return c, nil
}

// NewDebugConfig creates a debug config.
func NewDebugConfig(config *Config) *debug.Config {
	c := debug.NewConfig(config.DebugAddr)
	c.Debug = config.Debug

	return c
}
<|endoftext|>


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

package cmd

import (
	"bytes"
	"errors"
	"log"
	"time"

	"net"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/rhuss/puffer/pkg/api"
	"github.com/rhuss/puffer/pkg/speak"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var buttonMac = "ac:63:be:fb:13:9d"

// watchCmd represents the watch command
var watchCmd = &cobra.Command{
	Use:   "watch",
	Short: "Watch for the press of a Amazon Dash button",
	Long: `Watch the press of a Amazon Dash button

	`,
	Run: watch,
}

func watch(cmd *cobra.Command, args []string) {
	netInterface := viper.GetString("interface")
	if netInterface == "" {
		netInterface = "en3"
	}

	iface, err := net.InterfaceByName(netInterface)
	if err != nil {
		panic(err)
	}

	addr, err := extractAddress(iface)
	if err != nil {
		panic(err)
	}

	log.Printf("Using network range %v for interface %v", addr, iface.Name)

	// Start up a goroutine to read in packet data.
	for {
		// Open up a pcap handle for packet reads/writes.
		handle, err := pcap.OpenLive(iface.Name, 65536, true, pcap.BlockForever)
		if err != nil {
			panic(err)
		}
		watchForButton(handle, iface)
		log.Printf("Closing")
		handle.Close()
		log.Printf("Sleeping")
		time.Sleep(2 * time.Second)
		buttonPushed()
	}
}

func buttonPushed() {
	log.Printf("Button PUSHED !")
	pufferData := api.FetchPufferData()
	log.Printf("Puffer info fetched")
	speak.Speak(PufferMessage(pufferData), SpeakOptions())
}

var lastPushed = time.Time{}

// watchForButton watches a handle for incoming ARP responses we might care about, and prints them.
//
// watchForButton loops until 'stop' is closed.
func watchForButton(handle *pcap.Handle, iface *net.Interface) {
	src := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
	in := src.Packets()
	for {
		var packet gopacket.Packet
		select {
		case packet = <-in:
			arpLayer := packet.Layer(layers.LayerTypeARP)
			if arpLayer == nil {
				continue
			}
			arp := arpLayer.(*layers.ARP)
			if arp.Operation == layers.ARPRequest {
				log.Printf("ARP request")
				log.Printf("IP %v Dst %v Src %v", net.IP(arp.SourceProtAddress), net.HardwareAddr(arp.DstHwAddress), net.HardwareAddr(arp.SourceHwAddress))
				if addressEquals("00:00:00:00:00:00", arp.DstHwAddress) && addressEquals(buttonMac, arp.SourceHwAddress) {
					log.Print("--> Received ARP request")
					var now = time.Now()
					if now.Sub(lastPushed).Seconds() > 5 {
						lastPushed = now
						return
					}
				}
			}
		}
	}
}

func addressEquals(mac string, addr []byte) bool {
	macParsed, error := net.ParseMAC(mac)
	if error != nil {
		return false
	}
	return bytes.Equal(macParsed, addr)
}

func extractAddress(iface *net.Interface) (*net.IPNet, error) {
	var addr *net.IPNet
	if addrs, err := iface.Addrs(); err != nil {
		return nil, err
	} else {
		for _, a := range addrs {
			if ipnet, ok := a.(*net.IPNet); ok {
				if ip4 := ipnet.IP.To4(); ip4 != nil {
					addr = &net.IPNet{
						IP:   ip4,
						Mask: ipnet.Mask[len(ipnet.Mask)-4:],
					}
					break
				}
			}
		}
	}
	// Sanity-check that the interface has a good address.
	if addr == nil {
		return nil, errors.New("no good IP network found")
	} else if addr.IP[0] == 127 {
		return nil, errors.New("skipping localhost")
	} else if addr.Mask[0] != 0xff || addr.Mask[1] != 0xff {
		return nil, errors.New("mask means network is too large")
	}
	return addr, nil
}

func init() {
	RootCmd.AddCommand(watchCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// watchCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// watchCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

}
<commit_msg>Fixed timeout<commit_after>// Copyright © 2016 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"bytes"
	"errors"
	"log"
	"time"

	"net"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/rhuss/puffer/pkg/api"
	"github.com/rhuss/puffer/pkg/speak"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var buttonMac = "ac:63:be:fb:13:9d"

// watchCmd represents the watch command
var watchCmd = &cobra.Command{
	Use:   "watch",
	Short: "Watch for the press of a Amazon Dash button",
	Long: `Watch the press of a Amazon Dash button

	`,
	Run: watch,
}

func watch(cmd *cobra.Command, args []string) {
	netInterface := viper.GetString("interface")
	if netInterface == "" {
		netInterface = "en3"
	}

	iface, err := net.InterfaceByName(netInterface)
	if err != nil {
		panic(err)
	}

	addr, err := extractAddress(iface)
	if err != nil {
		panic(err)
	}

	log.Printf("Using network range %v for interface %v", addr, iface.Name)

	// Start up a goroutine to read in packet data.
	for {
		// Open up a pcap handle for packet reads/writes.
		handle, err := pcap.OpenLive(iface.Name, 65536, true, 10 * time.Millisecond)
		if err != nil {
			panic(err)
		}
		watchForButton(handle, iface)
		handle.Close()
		buttonPushed()
	}
}

func buttonPushed() {
	log.Printf("Button PUSHED !")
	pufferData := api.FetchPufferData()
	log.Printf("Puffer info fetched")
	speak.Speak(PufferMessage(pufferData), SpeakOptions())
}

var lastPushed = time.Time{}

// watchForButton watches a handle for incoming ARP responses we might care about, and prints them.
//
// watchForButton loops until 'stop' is closed.
func watchForButton(handle *pcap.Handle, iface *net.Interface) {
	src := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
	in := src.Packets()
	for {
		var packet gopacket.Packet
		select {
		case packet = <-in:
			arpLayer := packet.Layer(layers.LayerTypeARP)
			if arpLayer == nil {
				continue
			}
			arp := arpLayer.(*layers.ARP)
			if arp.Operation == layers.ARPRequest {
				log.Printf("ARP request")
				log.Printf("IP %v Dst %v Src %v", net.IP(arp.SourceProtAddress), net.HardwareAddr(arp.DstHwAddress), net.HardwareAddr(arp.SourceHwAddress))
				if addressEquals("00:00:00:00:00:00", arp.DstHwAddress) && addressEquals(buttonMac, arp.SourceHwAddress) {
					log.Print("--> Received ARP request")
					var now = time.Now()
					if now.Sub(lastPushed).Seconds() > 5 {
						lastPushed = now
						return
					}
				}
			}
		}
	}
}

func addressEquals(mac string, addr []byte) bool {
	macParsed, error := net.ParseMAC(mac)
	if error != nil {
		return false
	}
	return bytes.Equal(macParsed, addr)
}

func extractAddress(iface *net.Interface) (*net.IPNet, error) {
	var addr *net.IPNet
	if addrs, err := iface.Addrs(); err != nil {
		return nil, err
	} else {
		for _, a := range addrs {
			if ipnet, ok := a.(*net.IPNet); ok {
				if ip4 := ipnet.IP.To4(); ip4 != nil {
					addr = &net.IPNet{
						IP:   ip4,
						Mask: ipnet.Mask[len(ipnet.Mask)-4:],
					}
					break
				}
			}
		}
	}
	// Sanity-check that the interface has a good address.
	if addr == nil {
		return nil, errors.New("no good IP network found")
	} else if addr.IP[0] == 127 {
		return nil, errors.New("skipping localhost")
	} else if addr.Mask[0] != 0xff || addr.Mask[1] != 0xff {
		return nil, errors.New("mask means network is too large")
	}
	return addr, nil
}

func init() {
	RootCmd.AddCommand(watchCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// watchCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// watchCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/gonuts/commander"
	gocfg "github.com/gonuts/config"
	"github.com/gonuts/flag"
	"github.com/hwaf/hwaf/hwaflib"
)

func hwaf_make_cmd_setup() *commander.Command {
	cmd := &commander.Command{
		Run:       hwaf_run_cmd_setup,
		UsageLine: "setup [options] <workarea>",
		Short:     "setup an existing workarea",
		Long: `
setup sets up an existing workarea.

ex:
 $ hwaf setup
 $ hwaf setup .
 $ hwaf setup my-work-area
 $ hwaf setup -p=/opt/sw/mana/mana-core/20121207 my-work-area
 $ hwaf setup -p=/path1:/path2 my-work-area
 $ hwaf setup -cfg=${HWAF_CFG}/usr.cfg my-work-area
`,
		Flag: *flag.NewFlagSet("hwaf-setup", flag.ExitOnError),
	}
	cmd.Flag.String("p", "", "List of paths to projects to setup against")
	cmd.Flag.String("cfg", "", "Path to a configuration file")
	cmd.Flag.String("pkgdir", "src", "Directory under which to checkout packages")
	cmd.Flag.String("variant", "", "quadruplet (e.g. x86_64-slc6-gcc47-opt) identifying the target to build for")
	cmd.Flag.Bool("v", false, "enable verbose output")

	return cmd
}

func hwaf_run_cmd_setup(cmd *commander.Command, args []string) {
	var err error
	n := "hwaf-" + cmd.Name()
	dirname := "."
	switch len(args) {
	case 0:
		dirname = "."
	case 1:
		dirname = args[0]
	default:
		err = fmt.Errorf("%s: you need to give a directory name", n)
		handle_err(err)
	}

	dirname = os.ExpandEnv(dirname)
	dirname = filepath.Clean(dirname)

	verbose := cmd.Flag.Lookup("v").Value.Get().(bool)
	cfg_fname := cmd.Flag.Lookup("cfg").Value.Get().(string)
	pkgdir := cmd.Flag.Lookup("pkgdir").Value.Get().(string)
	variant := cmd.Flag.Lookup("variant").Value.Get().(string)

	projdirs := []string{}
	const pathsep = string(os.PathListSeparator)
	for _, v := range strings.Split(cmd.Flag.Lookup("p").Value.Get().(string), pathsep) {
		if v != "" {
			v = os.ExpandEnv(v)
			v = filepath.Clean(v)
			projdirs = append(projdirs, v)
		}
	}

	if verbose {
		fmt.Printf("%s: setup workarea [%s]...\n", n, dirname)
		fmt.Printf("%s: projects=%v\n", n, projdirs)
		if cfg_fname != "" {
			fmt.Printf("%s: cfg-file=%s\n", n, cfg_fname)
		}
	}

	if cfg_fname != "" && !path_exists(cfg_fname) {
		err = fmt.Errorf("configuration file [%s] does not exist (or is not readable)", cfg_fname)
		handle_err(err)
	}

	for _, projdir := range projdirs {
		if !path_exists(projdir) {
			err = fmt.Errorf("no such directory: [%s]", projdir)
			handle_err(err)
		}

		pinfo := filepath.Join(projdir, "project.info")
		if !path_exists(pinfo) {
			err = fmt.Errorf("no such file: [%s]", pinfo)
			handle_err(err)
		}
	}

	pwd, err := os.Getwd()
	handle_err(err)
	defer os.Chdir(pwd)

	err = os.Chdir(dirname)
	handle_err(err)

	if verbose {
		fmt.Printf("%s: create local config...\n", n)
	}

	var lcfg *gocfg.Config
	lcfg_fname := "local.conf"

	// if the user provided a configuration file use that as a default
	if cfg_fname != "" && path_exists(cfg_fname) {
		lcfg, err = gocfg.ReadDefault(cfg_fname)
		handle_err(err)
	} else {
		if path_exists(lcfg_fname) {
			lcfg, err = gocfg.ReadDefault(lcfg_fname)
			handle_err(err)
		} else {
			lcfg = gocfg.NewDefault()
		}
	}

	section := "hwaf-cfg"
	if !lcfg.HasSection(section) && !lcfg.AddSection(section) {
		err = fmt.Errorf("%s: could not create section [%s] in file [%s]",
			n, section, lcfg_fname)
		handle_err(err)
	}

	// fetch a few informations from the first project.info
	cmtcfg := g_ctx.Cmtcfg()
	//projvers := time.Now().Format("20060102")
	if len(projdirs) > 0 {
		pinfo, err := hwaflib.NewProjectInfo(filepath.Join(projdirs[0], "project.info"))
		handle_err(err)
		cmtcfg, err = pinfo.Get("CMTCFG")
		handle_err(err)
	}

	if variant != "" {
		cmtcfg = variant
	}

	for k, v := range map[string]string{
		"projects": strings.Join(projdirs, pathsep),
		"pkgdir":   pkgdir,
		"cmtcfg":   cmtcfg,
	} {
		if lcfg.HasOption(section, k) {
			lcfg.RemoveOption(section, k)
		}
		if !lcfg.AddOption(section, k, v) {
			err := fmt.Errorf("%s: could not add option [%s] to section [%s]",
				n, k, section,
			)
			handle_err(err)
		}
	}

	err = lcfg.WriteFile(lcfg_fname, 0600, "")
	handle_err(err)

	if verbose {
		fmt.Printf("%s: setup workarea [%s]... [ok]\n", n, dirname)
	}
}

// EOF
<commit_msg>cmd-setup: add support for specifying tags from command line<commit_after>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/gonuts/commander"
	gocfg "github.com/gonuts/config"
	"github.com/gonuts/flag"
	"github.com/hwaf/hwaf/hwaflib"
)

func hwaf_make_cmd_setup() *commander.Command {
	cmd := &commander.Command{
		Run:       hwaf_run_cmd_setup,
		UsageLine: "setup [options] <workarea>",
		Short:     "setup an existing workarea",
		Long: `
setup sets up an existing workarea.

ex:
 $ hwaf setup
 $ hwaf setup .
 $ hwaf setup my-work-area
 $ hwaf setup -p=/opt/sw/mana/mana-core/20121207 my-work-area
 $ hwaf setup -p=/path1:/path2 my-work-area
 $ hwaf setup -cfg=${HWAF_CFG}/usr.cfg my-work-area
 $ hwaf setup -tags=ATLAS,NEED_PYCOOL my-work-area
 $ hwaf setup -variant=x86_64-slc6-gcc47-opt my-work-area
`,
		Flag: *flag.NewFlagSet("hwaf-setup", flag.ExitOnError),
	}
	cmd.Flag.String("p", "", "List of paths to projects to setup against")
	cmd.Flag.String("cfg", "", "Path to a configuration file")
	cmd.Flag.String("pkgdir", "src", "Directory under which to checkout packages")
	cmd.Flag.String("variant", "", "quadruplet (e.g. x86_64-slc6-gcc47-opt) identifying the target to build for")
	cmd.Flag.String("tags", "", "comma-separated list of tags to enable for this build")
	cmd.Flag.Bool("v", false, "enable verbose output")

	return cmd
}

func hwaf_run_cmd_setup(cmd *commander.Command, args []string) {
	var err error
	n := "hwaf-" + cmd.Name()
	dirname := "."
	switch len(args) {
	case 0:
		dirname = "."
	case 1:
		dirname = args[0]
	default:
		err = fmt.Errorf("%s: you need to give a directory name", n)
		handle_err(err)
	}

	dirname = os.ExpandEnv(dirname)
	dirname = filepath.Clean(dirname)

	verbose := cmd.Flag.Lookup("v").Value.Get().(bool)
	cfg_fname := cmd.Flag.Lookup("cfg").Value.Get().(string)
	pkgdir := cmd.Flag.Lookup("pkgdir").Value.Get().(string)
	variant := cmd.Flag.Lookup("variant").Value.Get().(string)
	tags := cmd.Flag.Lookup("tags").Value.Get().(string)

	projdirs := []string{}
	const pathsep = string(os.PathListSeparator)
	for _, v := range strings.Split(cmd.Flag.Lookup("p").Value.Get().(string), pathsep) {
		if v != "" {
			v = os.ExpandEnv(v)
			v = filepath.Clean(v)
			projdirs = append(projdirs, v)
		}
	}

	if verbose {
		fmt.Printf("%s: setup workarea [%s]...\n", n, dirname)
		fmt.Printf("%s: projects=%v\n", n, projdirs)
		if cfg_fname != "" {
			fmt.Printf("%s: cfg-file=%s\n", n, cfg_fname)
		}
	}

	if cfg_fname != "" && !path_exists(cfg_fname) {
		err = fmt.Errorf("configuration file [%s] does not exist (or is not readable)", cfg_fname)
		handle_err(err)
	}

	for _, projdir := range projdirs {
		if !path_exists(projdir) {
			err = fmt.Errorf("no such directory: [%s]", projdir)
			handle_err(err)
		}

		pinfo := filepath.Join(projdir, "project.info")
		if !path_exists(pinfo) {
			err = fmt.Errorf("no such file: [%s]", pinfo)
			handle_err(err)
		}
	}

	pwd, err := os.Getwd()
	handle_err(err)
	defer os.Chdir(pwd)

	err = os.Chdir(dirname)
	handle_err(err)

	if verbose {
		fmt.Printf("%s: create local config...\n", n)
	}

	var lcfg *gocfg.Config
	lcfg_fname := "local.conf"

	// if the user provided a configuration file use that as a default
	if cfg_fname != "" && path_exists(cfg_fname) {
		lcfg, err = gocfg.ReadDefault(cfg_fname)
		handle_err(err)
	} else {
		if path_exists(lcfg_fname) {
			lcfg, err = gocfg.ReadDefault(lcfg_fname)
			handle_err(err)
		} else {
			lcfg = gocfg.NewDefault()
		}
	}

	section := "hwaf-cfg"
	if !lcfg.HasSection(section) && !lcfg.AddSection(section) {
		err = fmt.Errorf("%s: could not create section [%s] in file [%s]",
			n, section, lcfg_fname)
		handle_err(err)
	}

	// fetch a few informations from the first project.info
	cmtcfg := g_ctx.Cmtcfg()
	//projvers := time.Now().Format("20060102")
	if len(projdirs) > 0 {
		pinfo, err := hwaflib.NewProjectInfo(filepath.Join(projdirs[0], "project.info"))
		handle_err(err)
		cmtcfg, err = pinfo.Get("CMTCFG")
		handle_err(err)
	}

	if variant != "" {
		cmtcfg = variant
	}

	if tags != "" {
		tags_slice := strings.Split(tags, ",")
		tags = strings.Join(tags_slice, " ")
	}

	for k, v := range map[string]string{
		"projects": strings.Join(projdirs, pathsep),
		"pkgdir":   pkgdir,
		"cmtcfg":   cmtcfg,
		"tags":     tags,
	} {
		if lcfg.HasOption(section, k) {
			lcfg.RemoveOption(section, k)
		}
		if !lcfg.AddOption(section, k, v) {
			err := fmt.Errorf("%s: could not add option [%s] to section [%s]",
				n, k, section,
			)
			handle_err(err)
		}
	}

	err = lcfg.WriteFile(lcfg_fname, 0600, "")
	handle_err(err)

	if verbose {
		fmt.Printf("%s: setup workarea [%s]... [ok]\n", n, dirname)
	}
}

// EOF
<|endoftext|>


<commit_before>package dns

// MsgAcceptFunc is used early in the server code to accept or reject a message with RcodeFormatError.
// There are to booleans to be returned, once signaling the rejection and another to signal if
// a reply is to be send back (you want to prevent DNS ping-pong and not reply to a response for instance).
type MsgAcceptFunc func(dh Header) MsgAcceptAction

// DefaultMsgAcceptFunc checks the request and will reject if:
//
// * isn't a request (don't respond in that case).
// * opcode isn't OpcodeQuery or OpcodeNotify
// * Zero bit isn't zero
// * has more than 1 question in the question section
// * has more than 0 RRs in the Answer section
// * has more than 0 RRs in the Authority section
// * has more than 2 RRs in the Additional section
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc

// MsgAcceptAction represents the action to be taken.
type MsgAcceptAction int

const (
	MsgAccept MsgAcceptAction = iota
	MsgReject
	MsgIgnore
)

var defaultMsgAcceptFunc = func(dh Header) MsgAcceptAction {
	if isResponse := dh.Bits&_QR != 0; isResponse {
		return MsgIgnore
	}

	// Don't allow dynamic updates, because then the sections can contain a whole bunch of RRs.
	opcode := int(dh.Bits>>11) & 0xF
	if opcode != OpcodeQuery && opcode != OpcodeNotify {
		return MsgReject
	}

	if isZero := dh.Bits&_Z != 0; isZero {
		return MsgReject
	}
	if dh.Qdcount != 1 {
		return MsgReject
	}
	if dh.Ancount != 0 {
		return MsgReject
	}
	if dh.Nscount != 0 {
		return MsgReject
	}
	if dh.Arcount > 2 {
		return MsgReject
	}
	return MsgAccept
}
<commit_msg>fix docs<commit_after>package dns

// MsgAcceptFunc is used early in the server code to accept or reject a message with RcodeFormatError.
// It returns a MsgAcceptAction to indicate what should happen with the message.
type MsgAcceptFunc func(dh Header) MsgAcceptAction

// DefaultMsgAcceptFunc checks the request and will reject if:
//
// * isn't a request (don't respond in that case).
// * opcode isn't OpcodeQuery or OpcodeNotify
// * Zero bit isn't zero
// * has more than 1 question in the question section
// * has more than 0 RRs in the Answer section
// * has more than 0 RRs in the Authority section
// * has more than 2 RRs in the Additional section
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc

// MsgAcceptAction represents the action to be taken.
type MsgAcceptAction int

const (
	MsgAccept MsgAcceptAction = iota // Accept the message
	MsgReject                        // Reject the message with a RcodeFormatError
	MsgIgnore                        // Ignore the error and send nothing back.
)

var defaultMsgAcceptFunc = func(dh Header) MsgAcceptAction {
	if isResponse := dh.Bits&_QR != 0; isResponse {
		return MsgIgnore
	}

	// Don't allow dynamic updates, because then the sections can contain a whole bunch of RRs.
	opcode := int(dh.Bits>>11) & 0xF
	if opcode != OpcodeQuery && opcode != OpcodeNotify {
		return MsgReject
	}

	if isZero := dh.Bits&_Z != 0; isZero {
		return MsgReject
	}
	if dh.Qdcount != 1 {
		return MsgReject
	}
	if dh.Ancount != 0 {
		return MsgReject
	}
	if dh.Nscount != 0 {
		return MsgReject
	}
	if dh.Arcount > 2 {
		return MsgReject
	}
	return MsgAccept
}
<|endoftext|>