<commit_before>package main

import (
	"bufio"
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"io/ioutil"
	"log"
	"strings"
	"time"
	"github.com/bradfitz/gomemcache/memcache"
	"github.com/BurntSushi/toml"
)

type Config struct {
	Upstream string
	ReqFanFactor int
	TimeoutMS int
	MemcacheHosts []string
	MemcacheSeconds int
}

func snd (c chan string, s string) {
	go func () { c <- s }()
}

// start starts Charm up and returns a done channel for the done message
func start(confPath string) (chan string) {
	done := make(chan string)

	// print a welcome message
	log.Print("Charm is starting up.")
	// read the config file
	log.Printf(".   . Reading %s", confPath)
	tomlData, err := ioutil.ReadFile(confPath)
	if err != nil {
		snd(done, fmt.Sprintf("Could not read file at %s", confPath))
		return done
	}
	// populate the config struct
	log.Print(".   . Loading config")
	var conf Config
	_, err = toml.Decode(string(tomlData), &conf)
	if err != nil {
		snd(done, "Could not decode config")
		return done
	}
	// report on the configuration
	log.Print("Charm is configured!")
	log.Printf(".   . Stabilizing %v", conf.Upstream)
	log.Printf(".   . with %v duplicate requests", conf.ReqFanFactor)
	log.Printf(".   . and a %v milisecond timeout.", conf.TimeoutMS)
	log.Printf(
		".   . memcached at %v for %v seconds.",
		conf.MemcacheHosts,
		conf.MemcacheSeconds,
	)
	go run(conf, done)
	return done
}

type stableTransport struct {
	wrappedTransport http.RoundTripper
	reqFanFactor int
	cacheResponse chan *http.Response
}

// stableTransport.RoundTrip makes many round trips and returns the first
// response
func (t *stableTransport) RoundTrip(r *http.Request) (*http.Response, error) {
	c := make(chan *http.Response)
	if t.wrappedTransport == nil {
		t.wrappedTransport = http.DefaultTransport
	}
	// fan out requests, send responses to the channel, log errors, don't
	// wait very long for someone to recieve our response
	for i := 0; i < t.reqFanFactor; i++ {
		go func () {
			resp, err := t.wrappedTransport.RoundTrip(r)
			if err != nil {
				log.Printf("transport-error: %v", err)
			} else {
				select {
				case c <- resp:
					// they were still waiting for the first
					// response and they recieved it from c
					return
				case <-time.After(1 * time.Millisecond):
					// no one was waiting to recieve from c
					// so this is not the first response
					return
				}
			}
		}()
	}

	// wait to reviece the first response
	first := <-c
	// copy the first respnse for caching
	cacheCopy := new(http.Response)
	*cacheCopy = *first
	if first.Body != nil {
		bodyBytes, err := ioutil.ReadAll(first.Body)
		if err != nil {
			log.Fatal("error reading response body:", err)
		}
		cacheBytes := make([]byte, len(bodyBytes))
		copy(cacheBytes, bodyBytes)
		first.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
		cacheCopy.Body = ioutil.NopCloser(bytes.NewBuffer(cacheBytes))
	}
	// send the copy to be cached if they want it
	go func () {
		select {
		case t.cacheResponse <- cacheCopy:
			return
		case <-time.After(15 * time.Millisecond):
			return
		}
	}()
	// return the first response to the handler
	return first, nil
}

// cacheKey returns a string to be used as the cache key for a request
func cacheKey(r *http.Request) (string, error) {
	// We need to be careful here.
	// There is serious potential to accidently ignore permissions if we
	// cache requests too broadly. For example, if our cache key is the path
	// and a super-admin cache-misses on /some/restricted/path then a
	// restricted user could be given the cached result from that super
	//  admin request.

	keyStr := ""
	keyStr += r.Method
	keyStr += r.URL.Host
	keyStr += r.URL.Path
	keyStr += r.URL.RawQuery

	// TODO: extract which headers to cache on into a config option
	keyStr += r.Header["X-Forwarded-Email"][0]

	key := sha256.Sum224([]byte(keyStr))
	return hex.EncodeToString(key[:sha256.Size224]), nil
}

// copyHeader copies headers to the des from the src
// this code is copied from the reverse proxy library in go
func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// copyTrailer builds up the trailer header from Trailer
// this code is copied from the reverse proxy library in go
func copyTrailer(w http.ResponseWriter, r *http.Response) {
	if len(r.Trailer) > 0 {
		var trailerKeys []string
		for k := range r.Trailer {
			trailerKeys = append(trailerKeys, k)
		}
		w.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
	}
}

// Conf.ServeHTTP checks memcache then proxies/caches with a stable transport
func (conf Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// check memcache
	mc := memcache.New(conf.MemcacheHosts...)
	key, err := cacheKey(r)
	if err != nil {
		log.Println("cache key error for Request:", r)
	} else {
		item, err := mc.Get(key)
		if err == nil { //cache hit
			log.Println(
				"INFO: cache hit!",
				r.Method,
				r.URL.Host,
				r.URL.Path,
				r.URL.RawQuery,
				r.Header["X-Forwarded-Email"],
			)
			// get the cached response
			response, err := http.ReadResponse(
				bufio.NewReader(bytes.NewReader(item.Value)),
				r,
			)
			if err == nil {
				// get the bytes out of the body first so if
				// if there is an error we aren't half way into
				// responding when we call it a cache miss
				bodyBytes, err := ioutil.ReadAll(response.Body)
				if err != nil {
					log.Println("could not read cached response", err)
					return
				}
				// respond with the cached response
				// copy the headers to the response writer
				copyHeader(w.Header(), response.Header)
				copyTrailer(w, response)
				w.WriteHeader(response.StatusCode)
				// from reverse proxy code in go
				if len(response.Trailer) > 0 {
					// Forse chunking if we saw a trailer.
					// Prevents net/http from calculating
					// length for short bodies and adding
					// Content-Length.
					if fl, ok := w.(http.Flusher); ok {
						fl.Flush()
					}
				}
				w.Write(bodyBytes)
				response.Body.Close()
				// copy trailers like in go reverse proxy lib
				copyHeader(w.Header(), response.Trailer)
				return
			}
		}
	}
	// cache miss
	log.Println(
		"INFO: cache miss!",
		r.Method,
		r.URL.Host,
		r.URL.Path,
		r.URL.RawQuery,
		r.Header["X-Forwarded-Email"],
	)
	upstreamURL, err := url.Parse(conf.Upstream)
	if err != nil {
		log.Fatal("error parsing Upstream URL", conf.Upstream)
	}
	responseChan := make(chan *http.Response)
	proxy := httputil.NewSingleHostReverseProxy(upstreamURL)
	proxy.Transport = &stableTransport{
		proxy.Transport,
		conf.ReqFanFactor,
		responseChan,
	}
	proxy.ServeHTTP(w, r)

	// if the transport has a response waiting on the channel, cache it if
	// we have a cache
	if cacheKey != nil {
		select {
		case resp := <- responseChan:
			dump, err := httputil.DumpResponse(resp, true)
			if err != nil {
				log.Println(
					"ERROR: couldn't dump response:",
					err,
				)
				return
			}
			item := &memcache.Item{
				Key: key,
				Value: dump,
				Expiration: int32(conf.MemcacheSeconds),
			}
			err = mc.Set(item)
			if err != nil {
				log.Println("ERROR: memcached set error:", err)
				return
			}
		case <-time.After(1 * time.Millisecond):
			return
		}
	}

}

// run
func run(conf Config, done chan string) {
	// serve the config under a timeout
	timeout := time.Duration(conf.TimeoutMS) * time.Millisecond
	log.Fatal(http.ListenAndServe(
		":8000",
		http.TimeoutHandler(conf, timeout, "upstream timeout"),
	))
}

func main() {
	// start Charm,
	done := start("/secret/charm.conf")
	// when Charm is done, log the message and quit.
        log.Print(<-done)
}
<commit_msg>periodically log the number of goroutines<commit_after>package main

import (
	"bufio"
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"io/ioutil"
	"log"
	"runtime"
	"strings"
	"time"
	"github.com/bradfitz/gomemcache/memcache"
	"github.com/BurntSushi/toml"
)

type Config struct {
	Upstream string
	ReqFanFactor int
	TimeoutMS int
	MemcacheHosts []string
	MemcacheSeconds int
}

func snd (c chan string, s string) {
	go func () { c <- s }()
}

// start starts Charm up and returns a done channel for the done message
func start(confPath string) (chan string) {
	done := make(chan string)

	// print a welcome message
	log.Print("Charm is starting up.")
	// read the config file
	log.Printf(".   . Reading %s", confPath)
	tomlData, err := ioutil.ReadFile(confPath)
	if err != nil {
		snd(done, fmt.Sprintf("Could not read file at %s", confPath))
		return done
	}
	// populate the config struct
	log.Print(".   . Loading config")
	var conf Config
	_, err = toml.Decode(string(tomlData), &conf)
	if err != nil {
		snd(done, "Could not decode config")
		return done
	}
	// report on the configuration
	log.Print("Charm is configured!")
	log.Printf(".   . Stabilizing %v", conf.Upstream)
	log.Printf(".   . with %v duplicate requests", conf.ReqFanFactor)
	log.Printf(".   . and a %v milisecond timeout.", conf.TimeoutMS)
	log.Printf(
		".   . memcached at %v for %v seconds.",
		conf.MemcacheHosts,
		conf.MemcacheSeconds,
	)
	go run(conf, done)
	return done
}

type stableTransport struct {
	wrappedTransport http.RoundTripper
	reqFanFactor int
	cacheResponse chan *http.Response
}

// stableTransport.RoundTrip makes many round trips and returns the first
// response
func (t *stableTransport) RoundTrip(r *http.Request) (*http.Response, error) {
	c := make(chan *http.Response)
	if t.wrappedTransport == nil {
		t.wrappedTransport = http.DefaultTransport
	}
	// fan out requests, send responses to the channel, log errors, don't
	// wait very long for someone to recieve our response
	for i := 0; i < t.reqFanFactor; i++ {
		go func () {
			resp, err := t.wrappedTransport.RoundTrip(r)
			if err != nil {
				log.Printf("transport-error: %v", err)
			} else {
				select {
				case c <- resp:
					// they were still waiting for the first
					// response and they recieved it from c
					return
				case <-time.After(1 * time.Millisecond):
					// no one was waiting to recieve from c
					// so this is not the first response
					return
				}
			}
		}()
	}

	// wait to reviece the first response
	first := <-c
	// copy the first respnse for caching
	cacheCopy := new(http.Response)
	*cacheCopy = *first
	if first.Body != nil {
		bodyBytes, err := ioutil.ReadAll(first.Body)
		if err != nil {
			log.Fatal("error reading response body:", err)
		}
		cacheBytes := make([]byte, len(bodyBytes))
		copy(cacheBytes, bodyBytes)
		first.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
		cacheCopy.Body = ioutil.NopCloser(bytes.NewBuffer(cacheBytes))
	}
	// send the copy to be cached if they want it
	go func () {
		select {
		case t.cacheResponse <- cacheCopy:
			return
		case <-time.After(15 * time.Millisecond):
			return
		}
	}()
	// return the first response to the handler
	return first, nil
}

// cacheKey returns a string to be used as the cache key for a request
func cacheKey(r *http.Request) (string, error) {
	// We need to be careful here.
	// There is serious potential to accidently ignore permissions if we
	// cache requests too broadly. For example, if our cache key is the path
	// and a super-admin cache-misses on /some/restricted/path then a
	// restricted user could be given the cached result from that super
	//  admin request.

	keyStr := ""
	keyStr += r.Method
	keyStr += r.URL.Host
	keyStr += r.URL.Path
	keyStr += r.URL.RawQuery

	// TODO: extract which headers to cache on into a config option
	keyStr += r.Header["X-Forwarded-Email"][0]

	key := sha256.Sum224([]byte(keyStr))
	return hex.EncodeToString(key[:sha256.Size224]), nil
}

// copyHeader copies headers to the des from the src
// this code is copied from the reverse proxy library in go
func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// copyTrailer builds up the trailer header from Trailer
// this code is copied from the reverse proxy library in go
func copyTrailer(w http.ResponseWriter, r *http.Response) {
	if len(r.Trailer) > 0 {
		var trailerKeys []string
		for k := range r.Trailer {
			trailerKeys = append(trailerKeys, k)
		}
		w.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
	}
}

// Conf.ServeHTTP checks memcache then proxies/caches with a stable transport
func (conf Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// check memcache
	mc := memcache.New(conf.MemcacheHosts...)
	key, err := cacheKey(r)
	if err != nil {
		log.Println("cache key error for Request:", r)
	} else {
		item, err := mc.Get(key)
		if err == nil { //cache hit
			log.Println(
				"INFO: cache hit",
				r.Method,
				r.URL.Host,
				r.URL.Path,
				r.URL.RawQuery,
				r.Header["X-Forwarded-Email"],
			)
			// get the cached response
			response, err := http.ReadResponse(
				bufio.NewReader(bytes.NewReader(item.Value)),
				r,
			)
			if err == nil {
				// get the bytes out of the body first so if
				// if there is an error we aren't half way into
				// responding when we call it a cache miss
				bodyBytes, err := ioutil.ReadAll(response.Body)
				if err != nil {
					log.Println("could not read cached response", err)
					return
				}
				// respond with the cached response
				// copy the headers to the response writer
				copyHeader(w.Header(), response.Header)
				copyTrailer(w, response)
				w.WriteHeader(response.StatusCode)
				// from reverse proxy code in go
				if len(response.Trailer) > 0 {
					// Forse chunking if we saw a trailer.
					// Prevents net/http from calculating
					// length for short bodies and adding
					// Content-Length.
					if fl, ok := w.(http.Flusher); ok {
						fl.Flush()
					}
				}
				w.Write(bodyBytes)
				response.Body.Close()
				// copy trailers like in go reverse proxy lib
				copyHeader(w.Header(), response.Trailer)
				return
			}
		}
	}
	// cache miss
	log.Println(
		"INFO: cache miss",
		r.Method,
		r.URL.Host,
		r.URL.Path,
		r.URL.RawQuery,
		r.Header["X-Forwarded-Email"],
	)
	upstreamURL, err := url.Parse(conf.Upstream)
	if err != nil {
		log.Fatal("error parsing Upstream URL", conf.Upstream)
	}
	responseChan := make(chan *http.Response)
	proxy := httputil.NewSingleHostReverseProxy(upstreamURL)
	proxy.Transport = &stableTransport{
		proxy.Transport,
		conf.ReqFanFactor,
		responseChan,
	}
	proxy.ServeHTTP(w, r)

	// if the transport has a response waiting on the channel, cache it if
	// we have a cache
	if cacheKey != nil {
		select {
		case resp := <- responseChan:
			dump, err := httputil.DumpResponse(resp, true)
			if err != nil {
				log.Println(
					"ERROR: couldn't dump response:",
					err,
				)
				return
			}
			item := &memcache.Item{
				Key: key,
				Value: dump,
				Expiration: int32(conf.MemcacheSeconds),
			}
			err = mc.Set(item)
			if err != nil {
				log.Println("ERROR: memcached set error:", err)
				return
			}
		case <-time.After(1 * time.Millisecond):
			return
		}
	}

}

// run
func run(conf Config, done chan string) {
	// serve the config under a timeout
	timeout := time.Duration(conf.TimeoutMS) * time.Millisecond
	log.Fatal(http.ListenAndServe(
		":8000",
		http.TimeoutHandler(conf, timeout, "upstream timeout"),
	))
}

func main() {
	// start Charm,
	done := start("/secret/charm.conf")
	stop := make(chan bool)
	// start some logging of the number of goroutines
	go func() {
		log.Println(
			"Charm is currently using",
			runtime.NumGoroutine(),
			"goroutines.",
		)
		for {
			select {
			case <-time.After(10 * time.Minute):
				log.Println(
					"Charm is currently using",
					runtime.NumGoroutine(),
					"goroutines.",
				)
			case <-stop:
				return
			}
		}
	}()
	// when Charm is done, log the message and quit.
        log.Print(<-done)
	close(stop)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"io/ioutil"
	"log"
	"time"
	"github.com/BurntSushi/toml"
)

type Config struct {
	Upstream string
	ReqFanFactor int
	TimeoutMS int
}

func snd (c chan string, s string) {
	go func () { c <- s }()
}

// start starts Charm up and returns a done channel for the done message
func start(confPath string) (chan string) {
	done := make(chan string)

	// print a welcome message
	log.Print("Charm is starting up.")
	// read the config file
	log.Printf(".   . Reading %s", confPath)
	tomlData, err := ioutil.ReadFile(confPath)
	if err != nil {
		snd(done, fmt.Sprintf("Could not read file at %s", confPath))
		return done
	}
	// populate the config struct
	log.Print(".   . Loading config")
	var conf Config
	_, err = toml.Decode(string(tomlData), &conf)
	if err != nil {
		snd(done, "Could not decode config")
		return done
	}
	// report on the configuration
	log.Print("Charm is configured!")
	log.Printf(".   . Stabilizing %v", conf.Upstream)
	log.Printf(".   . with %v duplicate requests", conf.ReqFanFactor)
	log.Printf(".   . and a %v milisecond timeout.", conf.TimeoutMS)
	go run(conf, done)
	return done
}

func First() {}

type stableTransport struct {
	wrappedTransport http.RoundTripper
	reqFanFactor int
}

// stableTransport.RoundTrip makes many round trips and returns the first
// response
func (t *stableTransport) RoundTrip(r *http.Request) (*http.Response, error) {
	c := make(chan *http.Response)
	if t.wrappedTransport == nil {
		t.wrappedTransport = http.DefaultTransport
	}
	// fan out requests, send responses to the channel, log errors
	for i := 0; i < t.reqFanFactor; i++ {
		time.Sleep(5)
		go func () {
			resp, err := t.wrappedTransport.RoundTrip(r)
			if err != nil {
//				fmt.Printf("stableTransport.RoundTrip error (%)\n", err)
			} else {
				c <- resp
			}
		}()
	}

	// return the first good response
	return <-c, nil
}

type Stabilizer struct {
	upstreamURL *url.URL
	reqFanFactor int
}

// Stabilizer.ServeHTTP proxies with a stable transport
func (st *Stabilizer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	proxy := httputil.NewSingleHostReverseProxy(st.upstreamURL)
	proxy.Transport = &stableTransport{proxy.Transport, st.reqFanFactor}
	proxy.ServeHTTP(w, r)
}

// run
func run(conf Config, done chan string) {
	// make a stabilizer
	upstreamURL, err := url.Parse(conf.Upstream)
	if err != nil {
		log.Fatal("error parsing Upstream URL", conf.Upstream)
	}
	stabilizer := &Stabilizer{upstreamURL, conf.ReqFanFactor}

	// serve that stabilizer under a timeout
	timeout := time.Duration(conf.TimeoutMS) * time.Millisecond
	log.Fatal(http.ListenAndServe(
		":8000",
		http.TimeoutHandler(stabilizer, timeout, "upstream timeout"),
	))
}

func main() {
	// start Charm,
	done := start("/secret/charm.conf")
	// when Charm is done, log the message and quit.
        log.Print(<-done)
}
<commit_msg>clean up commented out code<commit_after>package main

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"io/ioutil"
	"log"
	"time"
	"github.com/BurntSushi/toml"
)

type Config struct {
	Upstream string
	ReqFanFactor int
	TimeoutMS int
}

func snd (c chan string, s string) {
	go func () { c <- s }()
}

// start starts Charm up and returns a done channel for the done message
func start(confPath string) (chan string) {
	done := make(chan string)

	// print a welcome message
	log.Print("Charm is starting up.")
	// read the config file
	log.Printf(".   . Reading %s", confPath)
	tomlData, err := ioutil.ReadFile(confPath)
	if err != nil {
		snd(done, fmt.Sprintf("Could not read file at %s", confPath))
		return done
	}
	// populate the config struct
	log.Print(".   . Loading config")
	var conf Config
	_, err = toml.Decode(string(tomlData), &conf)
	if err != nil {
		snd(done, "Could not decode config")
		return done
	}
	// report on the configuration
	log.Print("Charm is configured!")
	log.Printf(".   . Stabilizing %v", conf.Upstream)
	log.Printf(".   . with %v duplicate requests", conf.ReqFanFactor)
	log.Printf(".   . and a %v milisecond timeout.", conf.TimeoutMS)
	go run(conf, done)
	return done
}

func First() {}

type stableTransport struct {
	wrappedTransport http.RoundTripper
	reqFanFactor int
}

// stableTransport.RoundTrip makes many round trips and returns the first
// response
func (t *stableTransport) RoundTrip(r *http.Request) (*http.Response, error) {
	c := make(chan *http.Response)
	if t.wrappedTransport == nil {
		t.wrappedTransport = http.DefaultTransport
	}
	// fan out requests, send responses to the channel, log errors
	for i := 0; i < t.reqFanFactor; i++ {
		time.Sleep(5)
		go func () {
			resp, err := t.wrappedTransport.RoundTrip(r)
			if err != nil {
				// TODO: figure out how much logging we want
			} else {
				c <- resp
			}
		}()
	}

	// return the first good response
	return <-c, nil
}

type Stabilizer struct {
	upstreamURL *url.URL
	reqFanFactor int
}

// Stabilizer.ServeHTTP proxies with a stable transport
func (st *Stabilizer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	proxy := httputil.NewSingleHostReverseProxy(st.upstreamURL)
	proxy.Transport = &stableTransport{proxy.Transport, st.reqFanFactor}
	proxy.ServeHTTP(w, r)
}

// run
func run(conf Config, done chan string) {
	// make a stabilizer
	upstreamURL, err := url.Parse(conf.Upstream)
	if err != nil {
		log.Fatal("error parsing Upstream URL", conf.Upstream)
	}
	stabilizer := &Stabilizer{upstreamURL, conf.ReqFanFactor}

	// serve that stabilizer under a timeout
	timeout := time.Duration(conf.TimeoutMS) * time.Millisecond
	log.Fatal(http.ListenAndServe(
		":8000",
		http.TimeoutHandler(stabilizer, timeout, "upstream timeout"),
	))
}

func main() {
	// start Charm,
	done := start("/secret/charm.conf")
	// when Charm is done, log the message and quit.
        log.Print(<-done)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/lowstz/slackhookgo"
)

type alertMessage struct {
	url   string
	text  string
	color string
}

func (am *alertMessage) sentSlack() {
	err := slackhookgo.Send(
		am.url,
		slackhookgo.NewSlackMessage(
			"username",
			"backup",
		).AddAttachment(
			slackhookgo.MessageAttachment{
				Color: am.color,
				Text:  am.text,
				Title: "<!channel>",
			},
		),
	)
	checkIfError(err)
}

func checkIfError(err error) {
	if err == nil {
		return
	}
	log.Printf("error: %s", err)
}

func main() {
	var (
		sentUp   = 0
		sentDown = 0
		protocol = flag.String("protocol", "tcp", "protocol tcp/udp")
		host     = flag.String("host", "ya.ru", "destination host")
		port     = flag.String("port", "80", "destination port")
		interval = flag.Uint("interval", 5, "interval check seconds")
		url      = flag.String("url", "", "hook url")
	)

	flag.Parse()

	address := fmt.Sprintf("%s:%s", *host, *port)

	for {
		conn, err := net.DialTimeout(*protocol, address, 3*time.Second)
		checkIfError(err)
		if err == nil {
			conn.Close()
			if sentUp == 0 {
				am := alertMessage{
					color: "good",
					text:  "Destination host " + *host + ":" + *port + " reachable",
					url:   *url,
				}
				am.sentSlack()
				sentUp = 1
				sentDown = 0
			}
		} else {
			if sentDown == 0 {
				am := alertMessage{
					color: "danger",
					text:  "Destination host " + *host + ":" + *port + " unreachable",
					url:   *url,
				}
				am.sentSlack()
				sentUp = 0
				sentDown = 1
			}
		}
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<commit_msg>ref type connection method conn<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/lowstz/slackhookgo"
)

type alertMessage struct {
	url   string
	text  string
	color string
}

type connection struct {
	protocol string
	host     string
	port     string
	address  string
}

func checkError(err error) {
	if err == nil {
		return
	}
	log.Printf("error: %s", err)
}

func (c *connection) conn() (net.Conn, error) {
	conn, err := net.DialTimeout(c.protocol, c.address, 3*time.Second)
	checkError(err)
	return conn, err
}

func (a *alertMessage) sentSlack() {
	err := slackhookgo.Send(
		a.url,
		slackhookgo.NewSlackMessage(
			"username",
			"backup",
		).AddAttachment(
			slackhookgo.MessageAttachment{
				Color: a.color,
				Text:  a.text,
				Title: "<!channel>",
			},
		),
	)
	checkError(err)
}

func main() {
	var (
		sentUp   = 0
		sentDown = 0
		protocol = flag.String("protocol", "tcp", "protocol tcp/udp")
		host     = flag.String("host", "ya.ru", "destination host")
		port     = flag.String("port", "80", "destination port")
		interval = flag.Uint("interval", 5, "interval check seconds")
		url      = flag.String("url", "", "hook url")
	)

	flag.Parse()

	for {
		c := connection{
			protocol: *protocol,
			address:  fmt.Sprintf("%s:%s", *host, *port),
		}
		conn, err := c.conn()
		if err == nil {
			conn.Close()
			if sentUp == 0 {
				am := alertMessage{
					color: "good",
					text:  "Destination host " + *host + ":" + *port + " reachable",
					url:   *url,
				}
				am.sentSlack()
				sentUp = 1
				sentDown = 0
			}
		} else {
			if sentDown == 0 {
				am := alertMessage{
					color: "danger",
					text:  "Destination host " + *host + ":" + *port + " unreachable",
					url:   *url,
				}
				am.sentSlack()
				sentUp = 0
				sentDown = 1
			}
		}
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<|endoftext|>


<commit_before>package mtree

import (
	"bufio"
	"io"
)

const posixPolynomial uint32 = 0x04C11DB7

func cksum(r io.Reader) (uint32, int, error) {
	in := bufio.NewReader(r)
	count := 0
	var sum uint32
	f := func(b byte) {
		for i := 7; i >= 0; i-- {
			msb := sum & (1 << 31)
			sum = sum << 1
			if msb != 0 {
				sum = sum ^ posixPolynomial
			}
		}
		sum ^= uint32(b)
	}

	for done := false; !done; {
		switch b, err := in.ReadByte(); err {
		case io.EOF:
			done = true
		case nil:
			f(b)
			count++
		default:
			return ^sum, count, err
		}
	}
	for m := count; ; {
		f(byte(m) & 0xff)
		m = m >> 8
		if m == 0 {
			break
		}
	}
	f(0)
	f(0)
	f(0)
	f(0)
	return ^sum, count, nil
}
<commit_msg>cksum: comment<commit_after>package mtree

import (
	"bufio"
	"io"
)

const posixPolynomial uint32 = 0x04C11DB7

// cksum is an implementation of the POSIX CRC algorithm
func cksum(r io.Reader) (uint32, int, error) {
	in := bufio.NewReader(r)
	count := 0
	var sum uint32
	f := func(b byte) {
		for i := 7; i >= 0; i-- {
			msb := sum & (1 << 31)
			sum = sum << 1
			if msb != 0 {
				sum = sum ^ posixPolynomial
			}
		}
		sum ^= uint32(b)
	}

	for done := false; !done; {
		switch b, err := in.ReadByte(); err {
		case io.EOF:
			done = true
		case nil:
			f(b)
			count++
		default:
			return ^sum, count, err
		}
	}
	for m := count; ; {
		f(byte(m) & 0xff)
		m = m >> 8
		if m == 0 {
			break
		}
	}
	f(0)
	f(0)
	f(0)
	f(0)
	return ^sum, count, nil
}
<|endoftext|>


<commit_before>package scripturebot

import botsecrets "github.com/julwrites/BotSecrets"

// Struct definitions for bot

type UserConfig struct {
	Version       string
	Timezone      string
	Subscriptions string
}

type UserData struct {
	Firstname string
	Lastname  string
	Username  string
	Id        string
	Type      string
	Config    UserConfig
}

type MessageData struct {
	User    UserData
	Command string
	Message string
}

type SessionData struct {
	Secrets botsecrets.SecretsData
	Type    string
	Props   MessageData
}
<commit_msg>Flatten UserConfig when storing in datastore<commit_after>package scripturebot

import botsecrets "github.com/julwrites/BotSecrets"

// Struct definitions for bot

type UserConfig struct {
	Version       string
	Timezone      string
	Subscriptions string
}

type UserData struct {
	Firstname string
	Lastname  string
	Username  string
	Id        string
	Type      string
	Config    UserConfig `datastore:",flatten"`
}

type MessageData struct {
	User    UserData
	Command string
	Message string
}

type SessionData struct {
	Secrets botsecrets.SecretsData
	Type    string
	Props   MessageData
}
<|endoftext|>


<commit_before>// Package clock provides the ability to test time-sensitive code.
//
// By passing in Default to production code, you can then use NewFake
// in tests to create Clocks that control what time that production
// code sees.
//
// Be sure to test Time equality in your tests with with Time#Equal,
// not ==.
package clock

import (
	"sync"
	"time"
)

var systemClock Clock = sysClock{}

// Default returns a Clock that matches the actual system time.
func Default() Clock {
	// This is a method instead of a public var to prevent folks from
	// "making things work" by writing to the var instead of passing
	// in a Clock.
	return systemClock
}

// Clock returns the current time. It's main purpose is to allow for
// writing time-dependent tests using FakeClock while passing in
// Default to production code. Remember to test time equality with
// Time#Equal, not ==.
type Clock interface {
	// Now returns the Clock's current view of the time. Mutating the
	// returned Time will not mutate the clock's time.
	Now() time.Time
}

type sysClock struct{}

func (s sysClock) Now() time.Time {
	return time.Now()
}

// NewFake returns a FakeClock to be used in tests that need to
// manipulate time.
func NewFake() FakeClock {
	// We're explicit about this time construction to avoid early user
	// questions about why the time object doesn't have a Location by
	// default.
	return &fake{t: time.Unix(0, 0).UTC()}
}

// FakeClock is a Clock to be passed into time-sensitive code to make
// testing easy. Adjusting the FakeClock's view of time is done with
// Add. Use NewFake to get a FakeClock implementation.
type FakeClock interface {
	Clock
	// Adjust the time that will be returned by Now.
	Add(d time.Duration)
}

// To prevent mistakes with the API, we hide this behind NewFake. It's
// easy forget to create a pointer to a fake since time.Time (and
// sync.Mutex) are also simple values. The code will appear to work
// but the clock's time will never be adjusted. The alternative of
// making it a *time.Time requires folks to know how to initialize a
// time and that's another line of code every where.
type fake struct {
	sync.Mutex
	t time.Time
}

func (f *fake) Now() time.Time {
	f.Lock()
	defer f.Unlock()
	return f.t
}

func (f *fake) Add(d time.Duration) {
	f.Lock()
	defer f.Unlock()
	f.t = f.t.Add(d)
}
<commit_msg>improve package doc summary<commit_after>// Package clock provides an abstraction for system time that enables
// testing of time-sensitive code.
//
// By passing in Default to production code, you can then use NewFake
// in tests to create Clocks that control what time that production
// code sees.
//
// Be sure to test Time equality in your tests with with Time#Equal,
// not ==.
package clock

import (
	"sync"
	"time"
)

var systemClock Clock = sysClock{}

// Default returns a Clock that matches the actual system time.
func Default() Clock {
	// This is a method instead of a public var to prevent folks from
	// "making things work" by writing to the var instead of passing
	// in a Clock.
	return systemClock
}

// Clock returns the current time. It's main purpose is to allow for
// writing time-dependent tests using FakeClock while passing in
// Default to production code. Remember to test time equality with
// Time#Equal, not ==.
type Clock interface {
	// Now returns the Clock's current view of the time. Mutating the
	// returned Time will not mutate the clock's time.
	Now() time.Time
}

type sysClock struct{}

func (s sysClock) Now() time.Time {
	return time.Now()
}

// NewFake returns a FakeClock to be used in tests that need to
// manipulate time.
func NewFake() FakeClock {
	// We're explicit about this time construction to avoid early user
	// questions about why the time object doesn't have a Location by
	// default.
	return &fake{t: time.Unix(0, 0).UTC()}
}

// FakeClock is a Clock to be passed into time-sensitive code to make
// testing easy. Adjusting the FakeClock's view of time is done with
// Add. Use NewFake to get a FakeClock implementation.
type FakeClock interface {
	Clock
	// Adjust the time that will be returned by Now.
	Add(d time.Duration)
}

// To prevent mistakes with the API, we hide this behind NewFake. It's
// easy forget to create a pointer to a fake since time.Time (and
// sync.Mutex) are also simple values. The code will appear to work
// but the clock's time will never be adjusted. The alternative of
// making it a *time.Time requires folks to know how to initialize a
// time and that's another line of code every where.
type fake struct {
	sync.Mutex
	t time.Time
}

func (f *fake) Now() time.Time {
	f.Lock()
	defer f.Unlock()
	return f.t
}

func (f *fake) Add(d time.Duration) {
	f.Lock()
	defer f.Unlock()
	f.t = f.t.Add(d)
}
<|endoftext|>


<commit_before>package goes

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/jetbasrawi/goes/internal/atom"
)

type Client struct {
	client *http.Client

	BaseURL *url.URL
}

/*
An ErrorResponse reports one or more errors caused by an API request.

GitHub API docs: http://developer.github.com/v3/#client-errors
*/
type ErrorResponse struct {
	Response   *http.Response // HTTP response that caused this error
	Message    string
	StatusCode int
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d %+v",
		r.Response.Request.Method, r.Response.Request.URL,
		r.Response.StatusCode, r.Message)
}

func NewClient(httpClient *http.Client, serverURL string) (*Client, error) {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}
	baseURL, err := url.Parse(serverURL)
	if err != nil {
		return nil, err
	}

	c := &Client{client: httpClient, BaseURL: baseURL}

	return c, nil
}

func (c *Client) newRequest(method, urlString string, body interface{}) (*http.Request, error) {

	url, err := url.Parse(urlString)
	if err != nil {
		return nil, err
	}

	if !url.IsAbs() {
		url = c.BaseURL.ResolveReference(url)
	}

	var buf io.ReadWriter
	if body != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, url.String(), buf)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/vnd.eventstore.events+json")

	return req, nil
}

func checkResponse(r *http.Response) error {
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}

	return errorResponse
}

type Response struct {
	*http.Response
	StatusCode    int
	StatusMessage string
}

// newResponse creates a new Response for the provided http.Response.
func newResponse(r *http.Response) *Response {
	response := &Response{Response: r}
	response.StatusMessage = r.Status
	response.StatusCode = r.StatusCode
	return response
}

func (c *Client) do(req *http.Request, v interface{}) (*Response, error) {

	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	response := newResponse(resp)

	err = checkResponse(resp)
	if err != nil {
		// even though there was an error, we still return the response
		// in case the caller wants to inspect it further
		return response, err
	}

	if v != nil {
		if v, ok := v.(io.Writer); ok {
			io.Copy(v, resp.Body)

		} else {
			i, _ := ioutil.ReadAll(resp.Body)
			fmt.Println("Decoding %s", string(i))
			//TODO
			//err := json.NewDecoder(bytes.NewReader(i)).Decode(v)
			err := json.Unmarshal(i, v)
			if err == io.EOF {
				log.Println(err)
				err = nil // ignore EOF errors caused by empty response body

			}
		}
	}
	return response, err
}

func (c *Client) NewEvent(eventId, eventType string, data interface{}) *Event {

	e := &Event{EventType: eventType}

	if eventId == "" {
		e.EventID, _ = NewUUID()
	} else {
		e.EventID = eventId
	}

	e.Data = data

	return e
}

func (c *Client) AppendToStream(streamName string, expectedVersion *StreamVersion, events ...*Event) (*Response, error) {

	u := fmt.Sprintf("/streams/%s", streamName)

	req, err := c.newRequest("POST", u, events)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/vnd.eventstore.events+json")

	if expectedVersion != nil {
		req.Header.Set("ES-ExpectedVersion", strconv.Itoa(expectedVersion.Number))
	}

	resp, err := c.do(req, nil)
	if err != nil {
		return resp, err
	}

	return resp, nil
}

func (c *Client) UpdateMetaData(stream string, metadata ...*MetaData) (*Response, error) {

	mURL, err := c.getMetadataURL(stream)
	if err != nil {
		return nil, err
	}
	req, err := c.newRequest("POST", mURL, metadata)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/vnd.eventstore.events+json")

	resp, err := c.do(req, nil)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

func (c *Client) GetStreamMetaData(stream string) (*EventResponse, *Response, error) {

	url, err := c.getMetadataURL(stream)
	if err != nil {
		return nil, nil, err
	}
	er, resp, err := c.GetEvent(url)
	if err != nil {
		return nil, resp, err
	}

	return er, resp, nil
}

func (c *Client) getMetadataURL(stream string) (string, error) {

	url, err := getFeedURL(stream, "backward", nil, nil)
	if err != nil {
		return "", err
	}

	f, _, err := c.readFeed(url)
	if err != nil {
		return "", err
	}
	for _, v := range f.Link {
		if v.Rel == "metadata" {
			return v.Href, nil
		}
	}
	return "", nil
}

func (c *Client) ReadFeedForward(stream string, version *StreamVersion, take *Take) ([]*EventResponse, *Response, error) {

	url, err := getFeedURL(stream, "forward", version, take)
	if err != nil {
		return nil, nil, err
	}

	urls := []string{}

	for {
		f, resp, err := c.readFeed(url)
		if err != nil {
			return nil, resp, err
		}

		u, err := getEventURLs(f)
		if err != nil {
			return nil, nil, err
		}
		if len(u) <= 0 {
			break
		}

		var rev []string
		for i := len(u) - 1; i >= 0; i-- {
			rev = append(rev, u[i])
		}

		urls = append(urls, rev...)

		if take != nil && len(urls) >= take.Number {
			break
		}

		p := ""
		for _, v := range f.Link {
			if v.Rel == "previous" {
				p = v.Href
				break
			}
		}
		url = p
	}

	e, _, err := c.GetEvents(urls)
	if err != nil {
		return nil, nil, err
	}
	if take != nil {
		d := len(e) - take.Number
		if d < 0 {
			return e, nil, nil
		}
		return e[:take.Number], nil, nil
	}

	return e, nil, nil
}

func (c *Client) ReadFeedBackward(stream string, version *StreamVersion, take *Take) ([]*EventResponse, *Response, error) {

	url, err := getFeedURL(stream, "backward", version, take)
	if err != nil {
		return nil, nil, err
	}

	urls := []string{}
	for {

		f, resp, err := c.readFeed(url)
		if err != nil {
			return nil, resp, err
		}

		u, err := getEventURLs(f)
		if err != nil {
			log.Printf("Error getting event URLs %#v\n", err)
			return nil, nil, err
		}
		if len(u) <= 0 {
			break
		}
		urls = append(urls, u...)

		if take != nil && len(urls) >= take.Number {
			break
		}

		n := ""
		for _, v := range f.Link {
			if v.Rel == "next" {
				n = v.Href
				break
			}
		}
		if n == "" {
			break
		} else {
			url = n
		}
	}

	e, resp, err := c.GetEvents(urls)
	if err != nil {
		log.Printf("Error getting events %#v\n", err)
		return nil, resp, err
	}

	if take != nil {
		d := len(e) - take.Number
		if d < 0 {
			return e, nil, nil
		}
		return e[:take.Number], nil, nil
	}

	return e, nil, nil
}

func (c *Client) GetEvents(urls []string) ([]*EventResponse, *Response, error) {

	s := make([]*EventResponse, len(urls))

	for i := 0; i < len(urls); i++ {
		e, resp, err := c.GetEvent(urls[i])
		if err != nil {
			return nil, resp, err
		}
		//fmt.Printf("EVENT: %+v\n", e)
		s[i] = e
	}
	return s, nil, nil
}

func (c *Client) GetEvent(url string) (*EventResponse, *Response, error) {

	r, err := c.newRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	r.Header.Set("Accept", "application/vnd.eventstore.atom+json")

	var b bytes.Buffer
	resp, err := c.do(r, &b)
	if err != nil {
		log.Println("Error processing request")
		return nil, resp, err
	}

	if b.String() == "{}" {
		return nil, resp, nil
	}
	var raw json.RawMessage
	er := &eventAtomResponse{Content: &raw}
	err = json.NewDecoder(bytes.NewReader(b.Bytes())).Decode(er)
	if err == io.EOF {
		return nil, resp, nil
	}

	if err != nil {
		log.Printf("unmarshallEventResponse Error: %s", err)
		return nil, resp, err
	}

	var d json.RawMessage
	var m json.RawMessage
	ev := &Event{Data: &d, MetaData: &m}

	err = json.Unmarshal(raw, ev)
	if err == io.EOF {
		log.Println(err)
		err = nil
	}
	if err != nil {
		log.Printf("GetEvent: %#v\n", err)
	}

	e := EventResponse{}
	e.Title = er.Title
	e.ID = er.ID
	e.Updated = er.Updated
	e.Summary = er.Summary
	e.Event = ev

	return &e, resp, nil
}

func (c *Client) readFeed(url string) (*atom.Feed, *Response, error) {

	req, err := c.newRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	req.Header.Set("Accept", "application/atom+xml")

	var b bytes.Buffer
	resp, err := c.do(req, &b)
	if err != nil {
		return nil, resp, err
	}

	feed, err := unmarshalFeed(bytes.NewReader(b.Bytes()))
	if err != nil {
		return nil, resp, err
	}

	return feed, resp, nil
}

func unmarshalFeed(r io.Reader) (*atom.Feed, error) {

	f := &atom.Feed{}
	err := xml.NewDecoder(r).Decode(f)
	if err != nil {
		return nil, err

	}
	return f, nil

}

func getEventURLs(f *atom.Feed) ([]string, error) {
	s := make([]string, len(f.Entry))

	for i := 0; i < len(f.Entry); i++ {
		e := f.Entry[i]
		s[i] = strings.TrimRight(e.Link[1].Href, "/")
	}
	return s, nil
}

func getFeedURL(stream, direction string, version *StreamVersion, take *Take) (string, error) {

	ps := 100
	if take != nil && take.Number < ps {
		ps = take.Number
	}
	dir := ""
	switch direction {
	case "":
		if version != nil && version.Number == 0 {
			dir = "forward"
		} else {
			dir = "backward"
		}

	case "forward", "backward":
		dir = direction
	default:
		return "", errors.New("Invalid Direction")
	}

	ver := "head"
	if version != nil {
		if version.Number < 0 {
			return "", invalidVersionError(version.Number)
		}
		ver = strconv.Itoa(version.Number)
	} else if dir == "forward" {
		ver = "0"
	}

	return fmt.Sprintf("/streams/%s/%s/%s/%d", stream, ver, dir, ps), nil

}

type StreamVersion struct {
	Number int
}

type Take struct {
	Number int
}
<commit_msg>Fixed missing response<commit_after>package goes

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/jetbasrawi/goes/internal/atom"
)

type Client struct {
	client *http.Client

	BaseURL *url.URL
}

/*
An ErrorResponse reports one or more errors caused by an API request.

GitHub API docs: http://developer.github.com/v3/#client-errors
*/
type ErrorResponse struct {
	Response   *http.Response // HTTP response that caused this error
	Message    string
	StatusCode int
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d %+v",
		r.Response.Request.Method, r.Response.Request.URL,
		r.Response.StatusCode, r.Message)
}

func NewClient(httpClient *http.Client, serverURL string) (*Client, error) {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}
	baseURL, err := url.Parse(serverURL)
	if err != nil {
		return nil, err
	}

	c := &Client{client: httpClient, BaseURL: baseURL}

	return c, nil
}

func (c *Client) newRequest(method, urlString string, body interface{}) (*http.Request, error) {

	url, err := url.Parse(urlString)
	if err != nil {
		return nil, err
	}

	if !url.IsAbs() {
		url = c.BaseURL.ResolveReference(url)
	}

	var buf io.ReadWriter
	if body != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, url.String(), buf)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/vnd.eventstore.events+json")

	return req, nil
}

func checkResponse(r *http.Response) error {
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}

	return errorResponse
}

type Response struct {
	*http.Response
	StatusCode    int
	StatusMessage string
}

// newResponse creates a new Response for the provided http.Response.
func newResponse(r *http.Response) *Response {
	response := &Response{Response: r}
	response.StatusMessage = r.Status
	response.StatusCode = r.StatusCode
	return response
}

func (c *Client) do(req *http.Request, v interface{}) (*Response, error) {

	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	response := newResponse(resp)

	err = checkResponse(resp)
	if err != nil {
		// even though there was an error, we still return the response
		// in case the caller wants to inspect it further
		return response, err
	}

	if v != nil {
		if v, ok := v.(io.Writer); ok {
			io.Copy(v, resp.Body)

		} else {
			i, _ := ioutil.ReadAll(resp.Body)
			fmt.Println("Decoding %s", string(i))
			//TODO
			//err := json.NewDecoder(bytes.NewReader(i)).Decode(v)
			err := json.Unmarshal(i, v)
			if err == io.EOF {
				log.Println(err)
				err = nil // ignore EOF errors caused by empty response body

			}
		}
	}
	return response, err
}

func (c *Client) NewEvent(eventId, eventType string, data interface{}) *Event {

	e := &Event{EventType: eventType}

	if eventId == "" {
		e.EventID, _ = NewUUID()
	} else {
		e.EventID = eventId
	}

	e.Data = data

	return e
}

func (c *Client) AppendToStream(streamName string, expectedVersion *StreamVersion, events ...*Event) (*Response, error) {

	u := fmt.Sprintf("/streams/%s", streamName)

	req, err := c.newRequest("POST", u, events)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/vnd.eventstore.events+json")

	if expectedVersion != nil {
		req.Header.Set("ES-ExpectedVersion", strconv.Itoa(expectedVersion.Number))
	}

	resp, err := c.do(req, nil)
	if err != nil {
		return resp, err
	}

	return resp, nil
}

func (c *Client) UpdateMetaData(stream string, metadata ...*MetaData) (*Response, error) {

	mURL, resp, err := c.getMetadataURL(stream)
	if err != nil {
		return resp, err
	}
	req, err := c.newRequest("POST", mURL, metadata)
	if err != nil {
		return resp, err
	}

	req.Header.Set("Content-Type", "application/vnd.eventstore.events+json")

	resp, err = c.do(req, nil)
	if err != nil {
		return resp, err
	}

	return resp, nil
}

// Response may be nil
func (c *Client) GetStreamMetaData(stream string) (*EventResponse, *Response, error) {

	url, resp, err := c.getMetadataURL(stream)
	if err != nil {
		return nil, resp, err
	}
	er, resp, err := c.GetEvent(url)
	if err != nil {
		return nil, resp, err
	}

	return er, resp, nil
}

func (c *Client) getMetadataURL(stream string) (string, *Response, error) {

	url, err := getFeedURL(stream, "backward", nil, nil)
	if err != nil {
		return "", nil, err
	}

	f, resp, err := c.readFeed(url)
	if err != nil {
		return "", resp, err
	}
	for _, v := range f.Link {
		if v.Rel == "metadata" {
			return v.Href, resp, nil
		}
	}
	return "", resp, nil
}

func (c *Client) ReadFeedForward(stream string, version *StreamVersion, take *Take) ([]*EventResponse, *Response, error) {

	url, err := getFeedURL(stream, "forward", version, take)
	if err != nil {
		return nil, nil, err
	}

	urls := []string{}

	for {
		f, resp, err := c.readFeed(url)
		if err != nil {
			return nil, resp, err
		}

		u, err := getEventURLs(f)
		if err != nil {
			return nil, nil, err
		}
		if len(u) <= 0 {
			break
		}

		var rev []string
		for i := len(u) - 1; i >= 0; i-- {
			rev = append(rev, u[i])
		}

		urls = append(urls, rev...)

		if take != nil && len(urls) >= take.Number {
			break
		}

		p := ""
		for _, v := range f.Link {
			if v.Rel == "previous" {
				p = v.Href
				break
			}
		}
		url = p
	}

	e, _, err := c.GetEvents(urls)
	if err != nil {
		return nil, nil, err
	}
	if take != nil {
		d := len(e) - take.Number
		if d < 0 {
			return e, nil, nil
		}
		return e[:take.Number], nil, nil
	}

	return e, nil, nil
}

func (c *Client) ReadFeedBackward(stream string, version *StreamVersion, take *Take) ([]*EventResponse, *Response, error) {

	url, err := getFeedURL(stream, "backward", version, take)
	if err != nil {
		return nil, nil, err
	}

	urls := []string{}
	for {

		f, resp, err := c.readFeed(url)
		if err != nil {
			return nil, resp, err
		}

		u, err := getEventURLs(f)
		if err != nil {
			log.Printf("Error getting event URLs %#v\n", err)
			return nil, nil, err
		}
		if len(u) <= 0 {
			break
		}
		urls = append(urls, u...)

		if take != nil && len(urls) >= take.Number {
			break
		}

		n := ""
		for _, v := range f.Link {
			if v.Rel == "next" {
				n = v.Href
				break
			}
		}
		if n == "" {
			break
		} else {
			url = n
		}
	}

	e, resp, err := c.GetEvents(urls)
	if err != nil {
		log.Printf("Error getting events %#v\n", err)
		return nil, resp, err
	}

	if take != nil {
		d := len(e) - take.Number
		if d < 0 {
			return e, nil, nil
		}
		return e[:take.Number], nil, nil
	}

	return e, nil, nil
}

func (c *Client) GetEvents(urls []string) ([]*EventResponse, *Response, error) {

	s := make([]*EventResponse, len(urls))

	for i := 0; i < len(urls); i++ {
		e, resp, err := c.GetEvent(urls[i])
		if err != nil {
			return nil, resp, err
		}
		//fmt.Printf("EVENT: %+v\n", e)
		s[i] = e
	}
	return s, nil, nil
}

func (c *Client) GetEvent(url string) (*EventResponse, *Response, error) {

	r, err := c.newRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	r.Header.Set("Accept", "application/vnd.eventstore.atom+json")

	var b bytes.Buffer
	resp, err := c.do(r, &b)
	if err != nil {
		log.Println("Error processing request")
		return nil, resp, err
	}

	if b.String() == "{}" {
		return nil, resp, nil
	}
	var raw json.RawMessage
	er := &eventAtomResponse{Content: &raw}
	err = json.NewDecoder(bytes.NewReader(b.Bytes())).Decode(er)
	if err == io.EOF {
		return nil, resp, nil
	}

	if err != nil {
		log.Printf("unmarshallEventResponse Error: %s", err)
		return nil, resp, err
	}

	var d json.RawMessage
	var m json.RawMessage
	ev := &Event{Data: &d, MetaData: &m}

	err = json.Unmarshal(raw, ev)
	if err == io.EOF {
		log.Println(err)
		err = nil
	}
	if err != nil {
		log.Printf("GetEvent: %#v\n", err)
	}

	e := EventResponse{}
	e.Title = er.Title
	e.ID = er.ID
	e.Updated = er.Updated
	e.Summary = er.Summary
	e.Event = ev

	return &e, resp, nil
}

func (c *Client) readFeed(url string) (*atom.Feed, *Response, error) {

	req, err := c.newRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	req.Header.Set("Accept", "application/atom+xml")

	var b bytes.Buffer
	resp, err := c.do(req, &b)
	if err != nil {
		return nil, resp, err
	}

	feed, err := unmarshalFeed(bytes.NewReader(b.Bytes()))
	if err != nil {
		return nil, resp, err
	}

	return feed, resp, nil
}

func unmarshalFeed(r io.Reader) (*atom.Feed, error) {

	f := &atom.Feed{}
	err := xml.NewDecoder(r).Decode(f)
	if err != nil {
		return nil, err

	}
	return f, nil

}

func getEventURLs(f *atom.Feed) ([]string, error) {
	s := make([]string, len(f.Entry))

	for i := 0; i < len(f.Entry); i++ {
		e := f.Entry[i]
		s[i] = strings.TrimRight(e.Link[1].Href, "/")
	}
	return s, nil
}

func getFeedURL(stream, direction string, version *StreamVersion, take *Take) (string, error) {

	ps := 100
	if take != nil && take.Number < ps {
		ps = take.Number
	}
	dir := ""
	switch direction {
	case "":
		if version != nil && version.Number == 0 {
			dir = "forward"
		} else {
			dir = "backward"
		}

	case "forward", "backward":
		dir = direction
	default:
		return "", errors.New("Invalid Direction")
	}

	ver := "head"
	if version != nil {
		if version.Number < 0 {
			return "", invalidVersionError(version.Number)
		}
		ver = strconv.Itoa(version.Number)
	} else if dir == "forward" {
		ver = "0"
	}

	return fmt.Sprintf("/streams/%s/%s/%s/%d", stream, ver, dir, ps), nil

}

type StreamVersion struct {
	Number int
}

type Take struct {
	Number int
}
<|endoftext|>


<commit_before>package gofigure

import (
	"fmt"
	"github.com/droundy/goopt"
	"log"
	"os"
)

// This function is a convenience function. It displays the default values for us but handles them
// in the way we prefer it.
func GooptFigureString(names []string, def string, help string) *string {
	s := new(string)
	*s = ""
	f := func (ss string) error {
		*s = ss
		return nil
	}
	goopt.ReqArg(names, def, help, f)
	return s
}

// Config contains the configuration options that may be set by
// command line flags and environment variables.
type Config struct {
	Description			string
	DescribeEnvironment bool	// if true, the environment variable is automatically added to the flag description
	DisableCommandLine	bool
	EnvPrefix			string
	EnvOverridesFile	bool
	FileParser			File
	Version				string
	options				map[string]*Option
	flags				map[string]*string
	values				map[string]*string
}

// Returns a new Config instance.
func New() *Config {
	return &Config{
		DescribeEnvironment: false,
		DisableCommandLine:	 false,
		EnvOverridesFile:    false,
		options:			 make(map[string]*Option),
		flags:				 make(map[string]*string),
		values:				 make(map[string]*string),
	}
}

// Adds a configuration option, returns an Option instance for
// easily setting the corresponding environment variable, default
// value, and description.
func (c *Config) Add(name string) *Option {
	c.options[name] = &Option{
		name:    name,
		envVar:  "",
		def:     "",
		desc:    "",
		longOpt: name,
	}
	return c.options[name]
}

// Returns a configuration option by flag name.
func (c Config) Get(name string) *string {
	return c.values[name]
}

// Parses the configuration options into defined flags, sets the value
// accordingly. Options are read first from command line flags, then
// from environment variables, and falls back to the default value if
// neither are set.
//
// See https://github.com/rakyll/globalconf/blob/master/globalconf.go
func (c *Config) Parse() {
	goopt.Description = func() string {
		return c.Description
	}

	goopt.Version = c.Version

	// Sets the flags from the configuration options.
	for name, o := range c.options {
		cmdline := []string{}
		if o.shortOpt != "" {
			cmdline = append(cmdline, "-" + o.shortOpt)
		}
		cmdline = append(cmdline, "--"+o.longOpt)
		desc := o.desc
		if c.DescribeEnvironment && o.envVar != "" {
			desc += fmt.Sprintf(" Environment variable: %s_%s.", c.EnvPrefix, o.envVar)
		}
		c.flags[name] = GooptFigureString(cmdline, o.def, desc)
		defcopy := o.def
		c.values[name] = &defcopy
	}

	passed := make(map[string]bool)

	if !c.DisableCommandLine {
		goopt.Parse(nil)

		// Gather the options passed through command line.
		for name, f := range c.flags {
			if *f != "" {
				passed[name] = true
				*c.values[name] = *f
			}
		}
	}

	if (c.EnvOverridesFile) {
		c.ParseEnv(passed)
		err := c.ParseFile(passed)
		if err != nil {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
	} else {
		err := c.ParseFile(passed)
		if err != nil {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		}
		c.ParseEnv(passed)
	}
}

func (c *Config) ParseEnv(passed map[string]bool) {
	for name, f := range c.options {

		// Skip flags passed through the command line as the option is
		// already set and takes precedence over environment variables.
		if passed[name] {
			continue
		}

		// Some options shouldn't be set via environment variables.
		if f.envVar == "" {
			continue
		}

		// If the configuration option was not passed via the command line,
		// check the corresponding environment variable.
		envVar := c.EnvPrefix + f.envVar
		if val := os.Getenv(envVar); val != "" {
			*c.values[name] = val
			passed[name] = true
		}
	}
}

func (c Config) parseFileToMap(handler File) (ValueMap, error) {
	root, err := handler.Parse()
	if err != nil {
		return nil, err
	}
	ret := ValueMap{}
	for _, opt := range c.options {
		if val, ok := root.FindOption(opt); ok {
			ret.Set(opt.Name(), val)
		}
	}
	return ret, nil
}

func (c *Config) ParseFile(passed map[string]bool) error {
	if (c.FileParser == nil) {
		return nil
	}

	values, err := c.parseFileToMap(c.FileParser)
	if err != nil {
		return err
	}

	for k, v := range values {
		if passed[k] {
			continue
		}

		if p, ok := c.values[k]; ok {
			*p = v
			passed[k] = true
		}
	}
	return nil
}

// Option contains the details of a configuration options,
// e.g. corresponding environment variable, default value,
// description.
type Option struct {
	name, envVar, shortOpt, def, desc, longOpt string
	fileSpec				string              // The file spec is of the form "(CATEGORY.)*NAME", eg. for 'foo' under the category 'bar', it would be foo.bar
}

func (o Option) Name() string {
	return o.name
}

func (o *Option) FileSpec(spec string) *Option {
	o.fileSpec = spec
	return o
}

func (o *Option) ShortOpt(opt string) *Option {
	o.shortOpt = opt
	return o
}

func (o *Option) LongOpt(opt string) *Option {
	o.longOpt = opt
	return o
}

// Sets the configuration option's default value.
func (o *Option) Default(def string) *Option {
	o.def = def
	return o
}

// Sets the configuration option's corresponding environment variable.
func (o *Option) EnvVar(envVar string) *Option {
	o.envVar = envVar
	return o
}

// Sets the configuration options long description.
func (o *Option) Description(desc string) *Option {
	o.desc = desc
	return o
}
<commit_msg>added the option to not panic if file is not found<commit_after>package gofigure

import (
	"fmt"
	"github.com/droundy/goopt"
	"log"
	"os"
)

// This function is a convenience function. It displays the default values for us but handles them
// in the way we prefer it.
func GooptFigureString(names []string, def string, help string) *string {
	s := new(string)
	*s = ""
	f := func (ss string) error {
		*s = ss
		return nil
	}
	goopt.ReqArg(names, def, help, f)
	return s
}

// Config contains the configuration options that may be set by
// command line flags and environment variables.
type Config struct {
	Description			string
	DescribeEnvironment bool	// if true, the environment variable is automatically added to the flag description
	DisableCommandLine	bool
	EnvPrefix			string
	EnvOverridesFile	bool
	FileParser			File
	RequireFile			bool    // application will panic if RequireFile == true, FileParser != nil and file doesn't exist
	Version				string
	options				map[string]*Option
	flags				map[string]*string
	values				map[string]*string
}

// Returns a new Config instance.
func New() *Config {
	return &Config{
		DescribeEnvironment: false,
		DisableCommandLine:	 false,
		EnvOverridesFile:    false,
		RequireFile:         true,
		options:			 make(map[string]*Option),
		flags:				 make(map[string]*string),
		values:				 make(map[string]*string),
	}
}

// Adds a configuration option, returns an Option instance for
// easily setting the corresponding environment variable, default
// value, and description.
func (c *Config) Add(name string) *Option {
	c.options[name] = &Option{
		name:    name,
		envVar:  "",
		def:     "",
		desc:    "",
		longOpt: name,
	}
	return c.options[name]
}

// Returns a configuration option by flag name.
func (c Config) Get(name string) *string {
	return c.values[name]
}

// Parses the configuration options into defined flags, sets the value
// accordingly. Options are read first from command line flags, then
// from environment variables, and falls back to the default value if
// neither are set.
//
// See https://github.com/rakyll/globalconf/blob/master/globalconf.go
func (c *Config) Parse() {
	goopt.Description = func() string {
		return c.Description
	}

	goopt.Version = c.Version

	// Sets the flags from the configuration options.
	for name, o := range c.options {
		cmdline := []string{}
		if o.shortOpt != "" {
			cmdline = append(cmdline, "-" + o.shortOpt)
		}
		cmdline = append(cmdline, "--"+o.longOpt)
		desc := o.desc
		if c.DescribeEnvironment && o.envVar != "" {
			desc += fmt.Sprintf(" Environment variable: %s_%s.", c.EnvPrefix, o.envVar)
		}
		c.flags[name] = GooptFigureString(cmdline, o.def, desc)
		defcopy := o.def
		c.values[name] = &defcopy
	}

	passed := make(map[string]bool)

	if !c.DisableCommandLine {
		goopt.Parse(nil)

		// Gather the options passed through command line.
		for name, f := range c.flags {
			if *f != "" {
				passed[name] = true
				*c.values[name] = *f
			}
		}
	}

	if (c.EnvOverridesFile) {
		c.ParseEnv(passed)
		err := c.ParseFile(passed)
		if err != nil && c.RequireFile {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		} else if err != nil {
			log.Printf("Could not parse file: %s", err.Error())
		}
	} else {
		err := c.ParseFile(passed)
		if err != nil && c.RequireFile {
			log.Panicf("File defined but could not be parsed: %s", err.Error())
		} else if err != nil {
			log.Printf("Could not parse file: %s", err.Error())
		}
		c.ParseEnv(passed)
	}
}

func (c *Config) ParseEnv(passed map[string]bool) {
	for name, f := range c.options {

		// Skip flags passed through the command line as the option is
		// already set and takes precedence over environment variables.
		if passed[name] {
			continue
		}

		// Some options shouldn't be set via environment variables.
		if f.envVar == "" {
			continue
		}

		// If the configuration option was not passed via the command line,
		// check the corresponding environment variable.
		envVar := c.EnvPrefix + f.envVar
		if val := os.Getenv(envVar); val != "" {
			*c.values[name] = val
			passed[name] = true
		}
	}
}

func (c Config) parseFileToMap(handler File) (ValueMap, error) {
	root, err := handler.Parse()
	if err != nil {
		return nil, err
	}
	ret := ValueMap{}
	for _, opt := range c.options {
		if val, ok := root.FindOption(opt); ok {
			ret.Set(opt.Name(), val)
		}
	}
	return ret, nil
}

func (c *Config) ParseFile(passed map[string]bool) error {
	if (c.FileParser == nil) {
		return nil
	}

	values, err := c.parseFileToMap(c.FileParser)
	if err != nil {
		return err
	}

	for k, v := range values {
		if passed[k] {
			continue
		}

		if p, ok := c.values[k]; ok {
			*p = v
			passed[k] = true
		}
	}
	return nil
}

// Option contains the details of a configuration options,
// e.g. corresponding environment variable, default value,
// description.
type Option struct {
	name, envVar, shortOpt, def, desc, longOpt string
	fileSpec				string              // The file spec is of the form "(CATEGORY.)*NAME", eg. for 'foo' under the category 'bar', it would be foo.bar
}

func (o Option) Name() string {
	return o.name
}

func (o *Option) FileSpec(spec string) *Option {
	o.fileSpec = spec
	return o
}

func (o *Option) ShortOpt(opt string) *Option {
	o.shortOpt = opt
	return o
}

func (o *Option) LongOpt(opt string) *Option {
	o.longOpt = opt
	return o
}

// Sets the configuration option's default value.
func (o *Option) Default(def string) *Option {
	o.def = def
	return o
}

// Sets the configuration option's corresponding environment variable.
func (o *Option) EnvVar(envVar string) *Option {
	o.envVar = envVar
	return o
}

// Sets the configuration options long description.
func (o *Option) Description(desc string) *Option {
	o.desc = desc
	return o
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"strings"

	"github.com/google/uuid"
)

type peerKind int

const (
	client peerKind = iota
	server
)

type peerInfo struct {
	Kind    peerKind
	Name    string
	ID      string
	Channel chan string
}

const PEER_ID_PARAM_NAME string = "peer_id"
const TO_PARAM_NAME string = "to"

const PEER_MESSAGES_BUFFER_SIZE int = 100

var peers = make(map[string]peerInfo)

func printReqHandler(res http.ResponseWriter, req *http.Request) {
	reqDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(reqDump))
}

func registerHandler(path string, handlerFunc func(http.ResponseWriter, *http.Request)) {
	if path != "" {
		fmt.Printf("Registering handler for %s", path)
		fmt.Println()
		http.HandleFunc(path, handlerFunc)
	}
}

func setConnectionHeader(header http.Header, close bool) {
	if close {
		header.Set("Connection", "close")
	} else {
		header.Set("Connection", "keep-alive")
	}
}

func setVersionHeader(header http.Header) {
	header.Set("Server", "PeerConnectionTestServer/0.1g")
}

func setNoCacheHeader(header http.Header) {
	header.Set("Cache-Control", "no-cache")
}

func addCorsHeaders(header http.Header) {
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Access-Control-Allow-Credentials", "true")
	header.Set("Access-Control-Allow-Methods", strings.Join([]string{"GET", "POST", "OPTIONS"}, ","))
	header.Set("Access-Control-Allow-Headers", strings.Join([]string{"Content-Type", "Content-Length", "Cache-Control", "Connection"}, ","))
	header.Set("Access-Control-Expose-Headers", strings.Join([]string{"Content-Length", "X-Peer-Id"}, ","))
}

func signinHandler(res http.ResponseWriter, req *http.Request) {
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	var name string
	// Parse out peer name
	for k, v := range req.URL.Query() {
		if v[0] == "" {
			name = k
			break
		}
	}

	if name == "" {
		http.Error(res, "No name", http.StatusBadRequest)
		return
	}

	var peerInfo peerInfo
	peerInfo.Name = name
	peerInfo.Channel = make(chan string, PEER_MESSAGES_BUFFER_SIZE)

	// Determine peer type
	if strings.Index(name, "renderingserver_") == 0 {
		peerInfo.Kind = server
	}

	// Generate id
	uuid, err := uuid.NewRandom()
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}

	peerInfo.ID = uuid.String()

	peers[peerInfo.ID] = peerInfo

	peerInfoString := fmt.Sprintf("%s,%s,1", peerInfo.Name, peerInfo.ID)
	peerInfoString += fmt.Sprintln()
	responseString := peerInfoString

	// Return above + current peers (filtered for oppositing type)
	for pID, pInfo := range peers {
		if pID != peerInfo.ID && pInfo.Kind != peerInfo.Kind {
			responseString += fmt.Sprintf("%s,%s,1", pInfo.Name, pInfo.ID)
			responseString += fmt.Sprintln()

			// Also notify these peers that the new one exists
			if len(pInfo.Channel) < cap(pInfo.Channel) {
				pInfo.Channel <- peerInfoString
			} else {
				// TODO: Figure out what to do when peeer message buffer fills up
			}
		}
	}
	res.WriteHeader(http.StatusOK)
	fmt.Fprintf(res, responseString)
	// http.Error(res, "Not implemented "+name+" "+uuid.String(), http.statusadd)
}

func signoutHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())
	var peerID string
	// Parse out peers id
	for k, v := range req.URL.Query() {
		if k == PEER_ID_PARAM_NAME {
			peerID = v[0]
		}
	}
	_, exists := peers[peerID]
	if !exists {
		http.Error(res, "Unknown peer", http.StatusBadRequest)
		return
	}
	delete(peers, peerID)
	res.WriteHeader(http.StatusOK)
}

func messageHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out from id
	// Parse out to id
	peerID, peerExists := req.URL.Query()[PEER_ID_PARAM_NAME]
	toID, toExists := req.URL.Query()[TO_PARAM_NAME]

	if !peerExists || !toExists {
		http.Error(res, "Missing Peer or To ID", http.StatusBadRequest)
		return
	}

	_, peerInfoExists := peers[peerID[0]]
	to, toInfoExists := peers[toID[0]]

	if !peerInfoExists || !toInfoExists {
		http.Error(res, "Invalid Peer or To ID", http.StatusBadRequest)
		return
	}

	requestData, err := ioutil.ReadAll(req.Body)
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}
	requestString := string(requestData)
	defer req.Body.Close()
	// Look up channel for to id
	if len(to.Channel) == cap(to.Channel) {
		http.Error(res, "Invalid Peer or To ID", http.StatusServiceUnavailable)
		return
	}
	to.Channel <- requestString

	// Send message to channel for to id
	res.WriteHeader(http.StatusOK)
}

func waitHandler(res http.ResponseWriter, req *http.Request) {
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out peer id
	peerID, peerExists := req.URL.Query()[PEER_ID_PARAM_NAME]

	if !peerExists {
		http.Error(res, "Missing Peer ID", http.StatusBadRequest)
		return
	}

	peerInfo, peerInfoExists := peers[peerID[0]]

	if !peerInfoExists {
		http.Error(res, "Peer is backed up", http.StatusBadRequest)
		return
	}

	// Look up message channel for peers id
	// Wait for message to reply
	responseString := <-peerInfo.Channel

	res.WriteHeader(http.StatusOK)
	fmt.Fprint(res, responseString)
}

func main() {
	fmt.Println("gosigsrv starting")
	fmt.Println()
	// Register handlers
	registerHandler("/sign_in", signinHandler)
	registerHandler("/sign_out", signoutHandler)
	registerHandler("/message", messageHandler)
	registerHandler("/wait", waitHandler)
	registerHandler("/", printReqHandler)

	// Start listening
	http.ListenAndServe(":8087", nil)
	fmt.Println()
	fmt.Println("gosigsrv exiting")
}
<commit_msg>Fix some warnings, accidental exports<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"strings"

	"github.com/google/uuid"
)

type peerKind int

const (
	client peerKind = iota
	server
)

type peerInfo struct {
	Kind    peerKind
	Name    string
	ID      string
	Channel chan string
}

const peerIDParamName string = "peer_id"
const toParamName string = "to"

const peerMessageBufferSize int = 100

var peers = make(map[string]peerInfo)

func printReqHandler(res http.ResponseWriter, req *http.Request) {
	reqDump, err := httputil.DumpRequest(req, true)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(reqDump))
}

func registerHandler(path string, handlerFunc func(http.ResponseWriter, *http.Request)) {
	if path != "" {
		fmt.Printf("Registering handler for %s", path)
		fmt.Println()
		http.HandleFunc(path, handlerFunc)
	}
}

func setConnectionHeader(header http.Header, close bool) {
	if close {
		header.Set("Connection", "close")
	} else {
		header.Set("Connection", "keep-alive")
	}
}

func setVersionHeader(header http.Header) {
	header.Set("Server", "PeerConnectionTestServer/0.1g")
}

func setNoCacheHeader(header http.Header) {
	header.Set("Cache-Control", "no-cache")
}

func addCorsHeaders(header http.Header) {
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Access-Control-Allow-Credentials", "true")
	header.Set("Access-Control-Allow-Methods", strings.Join([]string{"GET", "POST", "OPTIONS"}, ","))
	header.Set("Access-Control-Allow-Headers", strings.Join([]string{"Content-Type", "Content-Length", "Cache-Control", "Connection"}, ","))
	header.Set("Access-Control-Expose-Headers", strings.Join([]string{"Content-Length", "X-Peer-Id"}, ","))
}

func signinHandler(res http.ResponseWriter, req *http.Request) {
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	var name string
	// Parse out peer name
	for k, v := range req.URL.Query() {
		if v[0] == "" {
			name = k
			break
		}
	}

	if name == "" {
		http.Error(res, "No name", http.StatusBadRequest)
		return
	}

	var peerInfo peerInfo
	peerInfo.Name = name
	peerInfo.Channel = make(chan string, peerMessageBufferSize)

	// Determine peer type
	if strings.Index(name, "renderingserver_") == 0 {
		peerInfo.Kind = server
	}

	// Generate id
	uuid, err := uuid.NewRandom()
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}

	peerInfo.ID = uuid.String()

	peers[peerInfo.ID] = peerInfo

	peerInfoString := fmt.Sprintf("%s,%s,1", peerInfo.Name, peerInfo.ID)
	peerInfoString += fmt.Sprintln()
	responseString := peerInfoString

	// Return above + current peers (filtered for oppositing type)
	for pID, pInfo := range peers {
		if pID != peerInfo.ID && pInfo.Kind != peerInfo.Kind {
			responseString += fmt.Sprintf("%s,%s,1", pInfo.Name, pInfo.ID)
			responseString += fmt.Sprintln()

			// Also notify these peers that the new one exists
			if len(pInfo.Channel) < cap(pInfo.Channel) {
				pInfo.Channel <- peerInfoString
			} else {
				// TODO: Figure out what to do when peeer message buffer fills up
			}
		}
	}
	res.WriteHeader(http.StatusOK)
	fmt.Fprintf(res, responseString)
	// http.Error(res, "Not implemented "+name+" "+uuid.String(), http.statusadd)
}

func signoutHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())
	var peerID string
	// Parse out peers id
	for k, v := range req.URL.Query() {
		if k == peerIDParamName {
			peerID = v[0]
		}
	}
	_, exists := peers[peerID]
	if !exists {
		http.Error(res, "Unknown peer", http.StatusBadRequest)
		return
	}
	delete(peers, peerID)
	res.WriteHeader(http.StatusOK)
}

func messageHandler(res http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out from id
	// Parse out to id
	peerID, peerExists := req.URL.Query()[peerIDParamName]
	toID, toExists := req.URL.Query()[toParamName]

	if !peerExists || !toExists {
		http.Error(res, "Missing Peer or To ID", http.StatusBadRequest)
		return
	}

	_, peerInfoExists := peers[peerID[0]]
	to, toInfoExists := peers[toID[0]]

	if !peerInfoExists || !toInfoExists {
		http.Error(res, "Invalid Peer or To ID", http.StatusBadRequest)
		return
	}

	requestData, err := ioutil.ReadAll(req.Body)
	if err != nil {
		http.Error(res, err.Error(), http.StatusInternalServerError)
	}
	requestString := string(requestData)
	defer req.Body.Close()
	// Look up channel for to id
	if len(to.Channel) == cap(to.Channel) {
		http.Error(res, "Invalid Peer or To ID", http.StatusServiceUnavailable)
		return
	}
	to.Channel <- requestString

	// Send message to channel for to id
	res.WriteHeader(http.StatusOK)
}

func waitHandler(res http.ResponseWriter, req *http.Request) {
	setConnectionHeader(res.Header(), true)
	setNoCacheHeader(res.Header())
	setVersionHeader(res.Header())
	addCorsHeaders(res.Header())

	if req.Method != "GET" {
		http.Error(res, "Bad request", http.StatusBadRequest)
		return
	}

	// Parse out peer id
	peerID, peerExists := req.URL.Query()[peerIDParamName]

	if !peerExists {
		http.Error(res, "Missing Peer ID", http.StatusBadRequest)
		return
	}

	peerInfo, peerInfoExists := peers[peerID[0]]

	if !peerInfoExists {
		http.Error(res, "Peer is backed up", http.StatusBadRequest)
		return
	}

	// Look up message channel for peers id
	// Wait for message to reply
	responseString := <-peerInfo.Channel

	res.WriteHeader(http.StatusOK)
	fmt.Fprint(res, responseString)
}

func main() {
	fmt.Println("gosigsrv starting")
	fmt.Println()
	// Register handlers
	registerHandler("/sign_in", signinHandler)
	registerHandler("/sign_out", signoutHandler)
	registerHandler("/message", messageHandler)
	registerHandler("/wait", waitHandler)
	registerHandler("/", printReqHandler)

	// Start listening
	http.ListenAndServe(":8087", nil)
	fmt.Println()
	fmt.Println("gosigsrv exiting")
}
<|endoftext|>


<commit_before>package gows

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

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-tools/gows/config"
)

// PrepareEnvironmentAndRunCommand ...
// Returns the exit code of the command and any error occured in the function
func PrepareEnvironmentAndRunCommand(cmdName string, cmdArgs ...string) (int, error) {
	gowsConfig, err := config.LoadGOWSConfigFromFile()
	if err != nil {
		return 0, fmt.Errorf("Failed to read gows configs: %s", err)
	}
	currWorkDir, err := os.Getwd()
	if err != nil {
		return 0, fmt.Errorf("[PrepareEnvironmentAndRunCommand] Failed to get current working directory: %s", err)
	}

	wsConfig, isFound := gowsConfig.WorkspaceForProjectLocation(currWorkDir)
	if !isFound {
		return 0, fmt.Errorf("No Workspace configuration found for the current project / working directory: %s", currWorkDir)
	}

	projectConfig, err := config.LoadProjectConfigFromFile()
	if err != nil {
		return 0, fmt.Errorf("Failed to read Project Config: %s", err)
	}

	origGOPATH := os.Getenv("GOPATH")
	if origGOPATH == "" {
		return 0, fmt.Errorf("You don't have a GOPATH environment - please set it, GOPATH/bin will be symlinked")
	}

	fullPackageWorkspacePath, err := prepareGoWorkspaceEnvironment(origGOPATH, currWorkDir, wsConfig, projectConfig)
	if err != nil {
		return 0, fmt.Errorf("Failed to prepare the Workspace, error: %s", err)
	}

	// Sync project into workspace
	log.Debugf("=> Sync project content into workspace: (%s) -> (%s)", currWorkDir, fullPackageWorkspacePath)
	if err := oneWaySyncDirs(currWorkDir, fullPackageWorkspacePath); err != nil {
		return 0, fmt.Errorf("Failed to sync the project path / workdir into the Workspace, error: %s", err)
	}
	log.Debugf(" [DONE] Sync project content into workspace")

	// Run the command, in the prepared Workspace
	exitCode, cmdErr := runCommand(origGOPATH, fullPackageWorkspacePath, wsConfig, cmdName, cmdArgs...)

	// Sync back from workspace into project
	log.Debugf("=> Sync workspace content into project: (%s) -> (%s)", fullPackageWorkspacePath, currWorkDir)
	if err := oneWaySyncDirs(fullPackageWorkspacePath, currWorkDir); err != nil {
		// we should return the command's exit code and error (if any)
		// maybe if the exitCode==0 and cmdErr==nil only then we could return an error here ...
		// for now we'll just print an error log, but it won't change the "output" of this function
		log.Errorf("Failed to sync back the project content from the Workspace, error: %s", err)
	} else {
		log.Debugf(" [DONE] Sync back project content from workspace")
	}

	return exitCode, cmdErr
}

func oneWaySyncDirs(syncContentOf, syncIntoDir string) error {
	syncContentOf = filepath.Clean(syncContentOf)
	syncIntoDir = filepath.Clean(syncIntoDir)

	if err := pathutil.EnsureDirExist(syncIntoDir); err != nil {
		return fmt.Errorf("Failed to create target (at: %s), error: %s", syncIntoDir, err)
	}

	cmd := exec.Command("rsync", "-avhP", syncContentOf+"/", syncIntoDir+"/")
	cmd.Stdin = os.Stdin

	out, err := cmd.Output()
	if err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			log.Error("[oneWaySyncDirs] Sync Error")
			log.Errorf("[oneWaySyncDirs] Output (Stdout) was: %s", out)
			log.Errorf("[oneWaySyncDirs] Error Output (Stderr) was: %s", exitError.Stderr)
		} else {
			log.Error("[oneWaySyncDirs] Failed to convert error to ExitError")
		}
		return fmt.Errorf("Failed to rsync between (%s) and (%s), error: %s", syncContentOf, syncIntoDir, err)
	}
	return nil
}

func prepareGoWorkspaceEnvironment(origGOPATH, currWorkDir string, wsConfig config.WorkspaceConfigModel, projectConfig config.ProjectConfigModel) (string, error) {
	if wsConfig.WorkspaceRootPath == "" {
		return "", fmt.Errorf("No gows Workspace root path found for the current project / working directory: %s", currWorkDir)
	}
	if projectConfig.PackageName == "" {
		return "", errors.New("No Package Name specified - make sure you initialized the workspace (with: gows init)")
	}

	fullWorkspaceBinPath := filepath.Join(wsConfig.WorkspaceRootPath, "bin")
	originalGopathBinPath, err := pathutil.AbsPath(filepath.Join(origGOPATH, "bin"))
	if err != nil {
		return "", fmt.Errorf("Failed to get the path of 'bin' dir inside your GOPATH (%s), error: %s", origGOPATH, err)
	}

	fullPackageWorkspacePath := filepath.Join(wsConfig.WorkspaceRootPath, "src", projectConfig.PackageName)

	log.Debugf("=> Creating Symlink: (%s) -> (%s)", originalGopathBinPath, fullWorkspaceBinPath)

	// create symlink for GOPATH/bin, if not yet created
	fileInfo, isExists, err := pathutil.PathCheckAndInfos(fullWorkspaceBinPath)
	if err != nil {
		return "", fmt.Errorf("Failed to check Symlink status (at: %s), error: %s", fullWorkspaceBinPath, err)
	}
	isSymlinkAlreadyInPlace := false
	if isExists && fileInfo.Mode()&os.ModeSymlink != 0 {
		log.Debug(" Symlink already exists")
		originPth, err := os.Readlink(fullWorkspaceBinPath)
		if err != nil {
			return "", fmt.Errorf("Symlink found (at: %s), but failed to open: %s", fullWorkspaceBinPath, err)
		}

		if originPth == originalGopathBinPath {
			isSymlinkAlreadyInPlace = true
		} else {
			// remove
			log.Warning("Symlink already exists (at: %s), but target (%s) is not the current one (%s)", fullWorkspaceBinPath, originPth, originalGopathBinPath)
			log.Warning("Removing and re-creating the symlink ...")
			if err := os.Remove(fullWorkspaceBinPath); err != nil {
				return "", fmt.Errorf("Failed to remove Symlink (at: %s), error: %s", fullWorkspaceBinPath, err)
			}
		}
	}

	if !isSymlinkAlreadyInPlace {
		log.Debug(" Creating symlink ...")
		// create the parent directory
		if err := os.MkdirAll(filepath.Dir(fullWorkspaceBinPath), 0777); err != nil {
			return "", fmt.Errorf("Failed to create base directory for symlink into: %s", fullWorkspaceBinPath)
		}
		// create symlink
		if err := os.Symlink(originalGopathBinPath, fullWorkspaceBinPath); err != nil {
			return "", fmt.Errorf("Failed to create symlink from project directory (%s) into gows Workspace directory (%s), error: %s", originalGopathBinPath, fullWorkspaceBinPath, err)
		}
	}

	log.Debugf(" [DONE] Symlink is in place")

	return fullPackageWorkspacePath, nil
}

// runCommand runs the command with it's arguments
// Returns the exit code of the command and any error occured in the function
func runCommand(originalGOPATH, cmdWorkdir string, wsConfig config.WorkspaceConfigModel, cmdName string, cmdArgs ...string) (int, error) {
	log.Debugf("[RunCommand] Command Name: %s", cmdName)
	log.Debugf("[RunCommand] Command Args: %#v", cmdArgs)
	log.Debugf("[RunCommand] Command Work Dir: %#v", cmdWorkdir)

	cmd := exec.Command(cmdName, cmdArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = cmdWorkdir
	cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%s", wsConfig.WorkspaceRootPath))

	cmdExitCode := 0
	if err := cmd.Run(); err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			waitStatus, ok := exitError.Sys().(syscall.WaitStatus)
			if !ok {
				return 0, errors.New("Failed to cast exit status")
			}
			cmdExitCode = waitStatus.ExitStatus()
		}
		return cmdExitCode, err
	}

	return 0, nil
}
<commit_msg>RUN: sync dirs now runs `rsync` with `--delete`<commit_after>package gows

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

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-tools/gows/config"
)

// PrepareEnvironmentAndRunCommand ...
// Returns the exit code of the command and any error occured in the function
func PrepareEnvironmentAndRunCommand(cmdName string, cmdArgs ...string) (int, error) {
	gowsConfig, err := config.LoadGOWSConfigFromFile()
	if err != nil {
		return 0, fmt.Errorf("Failed to read gows configs: %s", err)
	}
	currWorkDir, err := os.Getwd()
	if err != nil {
		return 0, fmt.Errorf("[PrepareEnvironmentAndRunCommand] Failed to get current working directory: %s", err)
	}

	wsConfig, isFound := gowsConfig.WorkspaceForProjectLocation(currWorkDir)
	if !isFound {
		return 0, fmt.Errorf("No Workspace configuration found for the current project / working directory: %s", currWorkDir)
	}

	projectConfig, err := config.LoadProjectConfigFromFile()
	if err != nil {
		return 0, fmt.Errorf("Failed to read Project Config: %s", err)
	}

	origGOPATH := os.Getenv("GOPATH")
	if origGOPATH == "" {
		return 0, fmt.Errorf("You don't have a GOPATH environment - please set it, GOPATH/bin will be symlinked")
	}

	fullPackageWorkspacePath, err := prepareGoWorkspaceEnvironment(origGOPATH, currWorkDir, wsConfig, projectConfig)
	if err != nil {
		return 0, fmt.Errorf("Failed to prepare the Workspace, error: %s", err)
	}

	// Sync project into workspace
	log.Debugf("=> Sync project content into workspace: (%s) -> (%s)", currWorkDir, fullPackageWorkspacePath)
	if err := syncDirWithDir(currWorkDir, fullPackageWorkspacePath); err != nil {
		return 0, fmt.Errorf("Failed to sync the project path / workdir into the Workspace, error: %s", err)
	}
	log.Debugf(" [DONE] Sync project content into workspace")

	// Run the command, in the prepared Workspace
	exitCode, cmdErr := runCommand(origGOPATH, fullPackageWorkspacePath, wsConfig, cmdName, cmdArgs...)

	// Sync back from workspace into project
	log.Debugf("=> Sync workspace content into project: (%s) -> (%s)", fullPackageWorkspacePath, currWorkDir)
	if err := syncDirWithDir(fullPackageWorkspacePath, currWorkDir); err != nil {
		// we should return the command's exit code and error (if any)
		// maybe if the exitCode==0 and cmdErr==nil only then we could return an error here ...
		// for now we'll just print an error log, but it won't change the "output" of this function
		log.Errorf("Failed to sync back the project content from the Workspace, error: %s", err)
	} else {
		log.Debugf(" [DONE] Sync back project content from workspace")
	}

	return exitCode, cmdErr
}

func syncDirWithDir(syncContentOf, syncIntoDir string) error {
	syncContentOf = filepath.Clean(syncContentOf)
	syncIntoDir = filepath.Clean(syncIntoDir)

	if err := pathutil.EnsureDirExist(syncIntoDir); err != nil {
		return fmt.Errorf("Failed to create target (at: %s), error: %s", syncIntoDir, err)
	}

	cmd := exec.Command("rsync", "-avhP", "--delete", syncContentOf+"/", syncIntoDir+"/")
	cmd.Stdin = os.Stdin

	out, err := cmd.Output()
	if err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			log.Error("[syncDirWithDir] Sync Error")
			log.Errorf("[syncDirWithDir] Output (Stdout) was: %s", out)
			log.Errorf("[syncDirWithDir] Error Output (Stderr) was: %s", exitError.Stderr)
		} else {
			log.Error("[syncDirWithDir] Failed to convert error to ExitError")
		}
		return fmt.Errorf("Failed to rsync between (%s) and (%s), error: %s", syncContentOf, syncIntoDir, err)
	}
	return nil
}

func prepareGoWorkspaceEnvironment(origGOPATH, currWorkDir string, wsConfig config.WorkspaceConfigModel, projectConfig config.ProjectConfigModel) (string, error) {
	if wsConfig.WorkspaceRootPath == "" {
		return "", fmt.Errorf("No gows Workspace root path found for the current project / working directory: %s", currWorkDir)
	}
	if projectConfig.PackageName == "" {
		return "", errors.New("No Package Name specified - make sure you initialized the workspace (with: gows init)")
	}

	fullWorkspaceBinPath := filepath.Join(wsConfig.WorkspaceRootPath, "bin")
	originalGopathBinPath, err := pathutil.AbsPath(filepath.Join(origGOPATH, "bin"))
	if err != nil {
		return "", fmt.Errorf("Failed to get the path of 'bin' dir inside your GOPATH (%s), error: %s", origGOPATH, err)
	}

	fullPackageWorkspacePath := filepath.Join(wsConfig.WorkspaceRootPath, "src", projectConfig.PackageName)

	log.Debugf("=> Creating Symlink: (%s) -> (%s)", originalGopathBinPath, fullWorkspaceBinPath)

	// create symlink for GOPATH/bin, if not yet created
	fileInfo, isExists, err := pathutil.PathCheckAndInfos(fullWorkspaceBinPath)
	if err != nil {
		return "", fmt.Errorf("Failed to check Symlink status (at: %s), error: %s", fullWorkspaceBinPath, err)
	}
	isSymlinkAlreadyInPlace := false
	if isExists && fileInfo.Mode()&os.ModeSymlink != 0 {
		log.Debug(" Symlink already exists")
		originPth, err := os.Readlink(fullWorkspaceBinPath)
		if err != nil {
			return "", fmt.Errorf("Symlink found (at: %s), but failed to open: %s", fullWorkspaceBinPath, err)
		}

		if originPth == originalGopathBinPath {
			isSymlinkAlreadyInPlace = true
		} else {
			// remove
			log.Warning("Symlink already exists (at: %s), but target (%s) is not the current one (%s)", fullWorkspaceBinPath, originPth, originalGopathBinPath)
			log.Warning("Removing and re-creating the symlink ...")
			if err := os.Remove(fullWorkspaceBinPath); err != nil {
				return "", fmt.Errorf("Failed to remove Symlink (at: %s), error: %s", fullWorkspaceBinPath, err)
			}
		}
	}

	if !isSymlinkAlreadyInPlace {
		log.Debug(" Creating symlink ...")
		// create the parent directory
		if err := os.MkdirAll(filepath.Dir(fullWorkspaceBinPath), 0777); err != nil {
			return "", fmt.Errorf("Failed to create base directory for symlink into: %s", fullWorkspaceBinPath)
		}
		// create symlink
		if err := os.Symlink(originalGopathBinPath, fullWorkspaceBinPath); err != nil {
			return "", fmt.Errorf("Failed to create symlink from project directory (%s) into gows Workspace directory (%s), error: %s", originalGopathBinPath, fullWorkspaceBinPath, err)
		}
	}

	log.Debugf(" [DONE] Symlink is in place")

	return fullPackageWorkspacePath, nil
}

// runCommand runs the command with it's arguments
// Returns the exit code of the command and any error occured in the function
func runCommand(originalGOPATH, cmdWorkdir string, wsConfig config.WorkspaceConfigModel, cmdName string, cmdArgs ...string) (int, error) {
	log.Debugf("[RunCommand] Command Name: %s", cmdName)
	log.Debugf("[RunCommand] Command Args: %#v", cmdArgs)
	log.Debugf("[RunCommand] Command Work Dir: %#v", cmdWorkdir)

	cmd := exec.Command(cmdName, cmdArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = cmdWorkdir
	cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%s", wsConfig.WorkspaceRootPath))

	cmdExitCode := 0
	if err := cmd.Run(); err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			waitStatus, ok := exitError.Sys().(syscall.WaitStatus)
			if !ok {
				return 0, errors.New("Failed to cast exit status")
			}
			cmdExitCode = waitStatus.ExitStatus()
		}
		return cmdExitCode, err
	}

	return 0, nil
}
<|endoftext|>