<commit_before>package unionpay

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

// AppTrans is client for submit order and get back the TN(transaction number) from
// china unionpay
type AppTrans struct {
	Config *UnionpayConfig
}

// NewApppTrans initial the AppTrans with specific configuration
func NewAppTrans(cfg *UnionpayConfig) *AppTrans {
	return &AppTrans{Config: cfg}
}

// Submit the order to china unionpay and return the TN(transaction number) if success,
// TN is used by mobile app.
// If fail, error is not nil, check error for more information
func (this *AppTrans) Submit(orderId string, amount float64, desc string) (string, error) {

	if this.Config.SignKeyPath == "" ||
		this.Config.SignCertPath == "" ||
		this.Config.VerifyCertPath == "" ||
		this.Config.CallbackUrl == "" ||
		this.Config.MerId == "" ||
		this.Config.AppTransUrl == "" {
		return "", errors.New("Please set key and cert")
	}

	txnTime := time.Now().Format("20060102030405")
	txnAmt := fmt.Sprintf("%.0f", amount) //单位为分，不可以有小数

	param := make(map[string]string)
	param["version"] = "5.0.0"
	param["encoding"] = "UTF-8"                                                //编码方式
	param["txnType"] = "01"                                                    //交易类型
	param["txnSubType"] = "01"                                                 //交易子类
	param["bizType"] = "000201"                                                //业务类型
	param["frontUrl"] = "http://not.exist.com/demo/utf8/FrontRcvResponse.aspx" //前台通知地址 ，控件接入方式无作用
	param["backUrl"] = this.Config.CallbackUrl                                 //后台通知地址
	param["signMethod"] = "01"                                                 //签名方法
	param["channelType"] = "08"                                                //渠道类型，07-PC，08-手机
	param["accessType"] = "0"                                                  //接入类型
	param["merId"] = this.Config.MerId                                         //商户号，请改成自己的商户号
	param["orderId"] = orderId                                                 //商户订单号
	param["txnTime"] = txnTime                                                 //订单发送时间
	param["txnAmt"] = txnAmt                                                   //交易金额，单位分
	param["currencyCode"] = "156"                                              //交易币种
	param["orderDesc"] = desc                                                  //订单描述，可不上送，上送时控件中会显示该信息
	param["reqReserved"] = "透传字段"                                              //请求方保留域，透传字段，查询、通知、对账文件中均会原样出现

	this.Sign(param)

	respMsg, err := doTrans(param, this.Config.AppTransUrl)
	if err != nil {
		return "", err
	}

	respValue, err := ParseResponseMsg(respMsg)
	if err != nil {
		return "", err
	}

	respCode, ok := respValue["respCode"]
	if !ok {
		return "", errors.New("respCode field not found")
	}
	if respCode != "00" {
		PrintMap(respValue)
		return "", errors.New("respCode:" + respCode)
	}

	err = this.Validate(respValue)
	if err != nil {
		return "", err
	}

	tn, ok := respValue["tn"]
	if ok {
		// fmt.Println("TN:", tn)
		return tn, nil
	} else {
		PrintMap(respValue)
		return "", errors.New("tn field not found")
	}
}

// Sign the data to comform with specs,
// more info refer to https://open.unionpay.com/ajweb/help/faq/detail?id=38
func (this *AppTrans) Sign(param map[string]string) error {

	//证书序列号
	certSn, err := certSerialNumberFromFile(this.Config.SignCertPath)
	if err != nil {
		return err
	}

	param["certId"] = certSn.String()

	sortedPairStr := SortAndConcat(param)
	signedDigest := sha1DigestFromString(sortedPairStr)
	hexSignedDigest := fmt.Sprintf("%x", signedDigest)

	byteSign, err := rsaSignBySha1(this.Config.SignKeyPath, []byte(hexSignedDigest))
	if err != nil {
		return err
	}

	//设置签名
	param["signature"] = base64String(byteSign)

	return nil
}

// Validate the response message with verfy certificate,
// more info refer to https://open.unionpay.com/ajweb/help/faq/detail?id=38
func (this *AppTrans) Validate(param map[string]string) error {
	//获取签名
	signature := param["signature"]
	// fmt.Println(signature)
	signByte, err := base64Bytes(signature)
	if err != nil {
		return err
	}

	delete(param, "signature")

	stringData := SortAndConcat(param)
	signedDigest := sha1DigestFromString(stringData)
	hexSignedDigest := fmt.Sprintf("%x", signedDigest)

	//TODO: check serial number of certifcate
	return rsaVerifyBySha1(this.Config.VerifyCertPath, signByte, []byte(hexSignedDigest))
}

func doTrans(param map[string]string, appTransUrl string) ([]byte, error) {
	datagram := ConcatWithUrlEncode(param)

	req, err := http.NewRequest("POST", appTransUrl, &datagram)
	if err != nil {
		return []byte(""), err
	}
	req.Header.Add("Content-type", "application/x-www-form-urlencoded;charset=UTF-8")

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	resp, err := client.Do(req)
	if err != nil {
		return []byte(""), err
	}

	defer resp.Body.Close()
	respData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte(""), err
	}

	return respData, nil
}
<commit_msg>check config before return AppTrans to ensure config is valid<commit_after>package unionpay

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

// AppTrans is client for submit order and get back the TN(transaction number) from
// china unionpay
type AppTrans struct {
	Config *UnionpayConfig
}

// NewApppTrans initial the AppTrans with specific configuration
func NewAppTrans(cfg *UnionpayConfig) (*AppTrans, error) {
	if cfg.SignKeyPath == "" ||
		cfg.SignCertPath == "" ||
		cfg.VerifyCertPath == "" ||
		cfg.MerId == "" ||
		cfg.CallbackUrl == "" ||
		cfg.AppTransUrl == "" {
		return &AppTrans{Config: cfg}, errors.New("config field cannot be empty string")
	}

	return &AppTrans{Config: cfg}, nil
}

// Submit the order to china unionpay and return the TN(transaction number) if success,
// TN is used by mobile app.
// If fail, error is not nil, check error for more information
func (this *AppTrans) Submit(orderId string, amount float64, desc string) (string, error) {

	txnTime := time.Now().Format("20060102030405")
	txnAmt := fmt.Sprintf("%.0f", amount) //单位为分，不可以有小数

	param := make(map[string]string)
	param["version"] = "5.0.0"
	param["encoding"] = "UTF-8"                                                //编码方式
	param["txnType"] = "01"                                                    //交易类型
	param["txnSubType"] = "01"                                                 //交易子类
	param["bizType"] = "000201"                                                //业务类型
	param["frontUrl"] = "http://not.exist.com/demo/utf8/FrontRcvResponse.aspx" //前台通知地址 ，控件接入方式无作用
	param["backUrl"] = this.Config.CallbackUrl                                 //后台通知地址
	param["signMethod"] = "01"                                                 //签名方法
	param["channelType"] = "08"                                                //渠道类型，07-PC，08-手机
	param["accessType"] = "0"                                                  //接入类型
	param["merId"] = this.Config.MerId                                         //商户号，请改成自己的商户号
	param["orderId"] = orderId                                                 //商户订单号
	param["txnTime"] = txnTime                                                 //订单发送时间
	param["txnAmt"] = txnAmt                                                   //交易金额，单位分
	param["currencyCode"] = "156"                                              //交易币种
	param["orderDesc"] = desc                                                  //订单描述，可不上送，上送时控件中会显示该信息
	param["reqReserved"] = "透传字段"                                              //请求方保留域，透传字段，查询、通知、对账文件中均会原样出现

	this.Sign(param)

	respMsg, err := doTrans(param, this.Config.AppTransUrl)
	if err != nil {
		return "", err
	}

	respValue, err := ParseResponseMsg(respMsg)
	if err != nil {
		return "", err
	}

	respCode, ok := respValue["respCode"]
	if !ok {
		return "", errors.New("respCode field not found")
	}
	if respCode != "00" {
		PrintMap(respValue)
		return "", errors.New("respCode:" + respCode)
	}

	err = this.Validate(respValue)
	if err != nil {
		return "", err
	}

	tn, ok := respValue["tn"]
	if ok {
		// fmt.Println("TN:", tn)
		return tn, nil
	} else {
		PrintMap(respValue)
		return "", errors.New("tn field not found")
	}
}

// Sign the data to comform with specs,
// more info refer to https://open.unionpay.com/ajweb/help/faq/detail?id=38
func (this *AppTrans) Sign(param map[string]string) error {

	//证书序列号
	certSn, err := certSerialNumberFromFile(this.Config.SignCertPath)
	if err != nil {
		return err
	}

	param["certId"] = certSn.String()

	sortedPairStr := SortAndConcat(param)
	signedDigest := sha1DigestFromString(sortedPairStr)
	hexSignedDigest := fmt.Sprintf("%x", signedDigest)

	byteSign, err := rsaSignBySha1(this.Config.SignKeyPath, []byte(hexSignedDigest))
	if err != nil {
		return err
	}

	//设置签名
	param["signature"] = base64String(byteSign)

	return nil
}

// Validate the response message with verfy certificate,
// more info refer to https://open.unionpay.com/ajweb/help/faq/detail?id=38
func (this *AppTrans) Validate(param map[string]string) error {
	//获取签名
	signature := param["signature"]
	// fmt.Println(signature)
	signByte, err := base64Bytes(signature)
	if err != nil {
		return err
	}

	delete(param, "signature")

	stringData := SortAndConcat(param)
	signedDigest := sha1DigestFromString(stringData)
	hexSignedDigest := fmt.Sprintf("%x", signedDigest)

	//TODO: check serial number of certifcate
	return rsaVerifyBySha1(this.Config.VerifyCertPath, signByte, []byte(hexSignedDigest))
}

func doTrans(param map[string]string, appTransUrl string) ([]byte, error) {
	datagram := ConcatWithUrlEncode(param)

	req, err := http.NewRequest("POST", appTransUrl, &datagram)
	if err != nil {
		return []byte(""), err
	}
	req.Header.Add("Content-type", "application/x-www-form-urlencoded;charset=UTF-8")

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	resp, err := client.Do(req)
	if err != nil {
		return []byte(""), err
	}

	defer resp.Body.Close()
	respData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte(""), err
	}

	return respData, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Fred Zhou

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package next

import (
	"bytes"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/pprof"
	"net/url"
	"os"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type Server struct {
	Config *Config
	routes *Routes
	Logger *log.Logger
	Env    map[string]interface{}
	//save the listener so it can be closed
	l net.Listener
}

func NewServer() *Server {
	server := &Server{
		Config: NewConfig(),
		routes: NewRoutes(),
		Logger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
		Env:    map[string]interface{}{},
	}

	// Load default config if exists
	file := "config.json"
	if fileExists(file) {
		server.Config.Load(file)
	}

	return server
}

// ServeHTTP is the interface method for Go's http server package
func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) {
	s.Process(c, req)
}

// Process invokes the routing system for server s
func (s *Server) Process(c http.ResponseWriter, req *http.Request) {
	route := s.routeHandler(req, c)
	if route != nil {
		route.httpHandler.ServeHTTP(c, req)
	}
}

// Get adds a handler for the 'GET' http method for server s.
func (s *Server) Get(route string, handler interface{}) {
	s.routes.Add(route, "GET", handler)
}

// Post adds a handler for the 'POST' http method for server s.
func (s *Server) Post(route string, handler interface{}) {
	s.routes.Add(route, "POST", handler)
}

// Put adds a handler for the 'PUT' http method for server s.
func (s *Server) Put(route string, handler interface{}) {
	s.routes.Add(route, "PUT", handler)
}

// Delete adds a handler for the 'DELETE' http method for server s.
func (s *Server) Delete(route string, handler interface{}) {
	s.routes.Add(route, "DELETE", handler)
}

// Match adds a handler for an arbitrary http method for server s.
func (s *Server) Match(method string, route string, handler interface{}) {
	s.routes.Add(route, method, handler)
}

//Adds a custom handler. Only for webserver mode. Will have no effect when running as FCGI or SCGI.
func (s *Server) Handler(route string, method string, handler http.Handler) {
	s.routes.Add(route, method, handler)
}

// Run starts the web application and serves HTTP requests for s
func (s *Server) Run(addr string) {
	mux := http.NewServeMux()
	if s.Config.Bool("debug.profiler") {
		mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
		mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
		mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
		mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
	}
	mux.Handle("/", s)

	s.Logger.Printf("next serving %s\n", addr)

	l, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
	s.l = l
	err = http.Serve(s.l, mux)
	s.l.Close()
}

// RunTLS starts the web application and serves HTTPS requests for s.
func (s *Server) RunTLS(addr string, config *tls.Config) error {
	mux := http.NewServeMux()
	mux.Handle("/", s)
	l, err := tls.Listen("tcp", addr, config)
	if err != nil {
		log.Fatal("Listen:", err)
		return err
	}

	s.l = l
	return http.Serve(s.l, mux)
}

// Close stops server s.
func (s *Server) Close() {
	if s.l != nil {
		s.l.Close()
	}
}

// safelyCall invokes `function` in recover block
func (s *Server) safelyCall(function reflect.Value, args []reflect.Value) (resp []reflect.Value, e interface{}) {
	defer func() {
		if err := recover(); err != nil {
			if s.Config.Bool("panic") {
				// go back to panic
				panic(err)
			} else {
				e = err
				resp = nil
				s.Logger.Println("Handler crashed with error", err)
				for i := 1; ; i += 1 {
					_, file, line, ok := runtime.Caller(i)
					if !ok {
						break
					}
					s.Logger.Println(file, line)
				}
			}
		}
	}()
	return function.Call(args), nil
}

// requiresContext determines whether 'handlerType' contains
// an argument to 'web.Ctx' as its first argument
func requiresContext(handlerType reflect.Type) bool {
	//if the method doesn't take arguments, no
	if handlerType.NumIn() == 0 {
		return false
	}

	//if the first argument is not a pointer, no
	a0 := handlerType.In(0)
	if a0.Kind() != reflect.Ptr {
		return false
	}
	//if the first argument is a context, yes
	if a0.Elem() == reflect.TypeOf(Context{}) {
		return true
	}

	return false
}

func (s *Server) logRequest(ctx Context, sTime time.Time) {
	//log the request
	var logEntry bytes.Buffer
	req := ctx.Request
	requestPath := req.URL.Path

	duration := time.Now().Sub(sTime)
	var client string

	// We suppose RemoteAddr is of the form Ip:Port as specified in the Request
	// documentation at http://golang.org/pkg/net/http/#Request
	pos := strings.LastIndex(req.RemoteAddr, ":")
	if pos > 0 {
		client = req.RemoteAddr[0:pos]
	} else {
		client = req.RemoteAddr
	}

	fmt.Fprintf(&logEntry, "%s - \033[32;1m %s %s\033[0m - %v", client, req.Method, requestPath, duration)

	if len(ctx.Params) > 0 {
		fmt.Fprintf(&logEntry, " - \033[37;1mParams: %v\033[0m\n", ctx.Params)
	}

	ctx.Server.Logger.Print(logEntry.String())
}

// the main route handler in next
// Tries to handle the given request.
// Finds the route matching the request, and execute the callback associated
// with it.  In case of custom http handlers, this function returns an "unused"
// route. The caller is then responsible for calling the httpHandler associated
// with the returned route.
func (s *Server) routeHandler(req *http.Request, w http.ResponseWriter) (unused *Route) {
	requestPath := req.URL.Path
	ctx := Context{req, map[string]string{}, s, w}

	//set some default headers
	ctx.SetHeader("Server", "next", true)
	tm := time.Now().UTC()

	//ignore errors from ParseForm because it's usually harmless.
	req.ParseForm()
	if len(req.Form) > 0 {
		for k, v := range req.Form {
			ctx.Params[k] = v[0]
		}
	}
	if len(req.PostForm) > 0 {
		for k, v := range req.PostForm {
			ctx.Params[k] = v[0]
		}
	}

	// Data in body
	// ------------------
	requestbody, _ := ioutil.ReadAll(req.Body)
	req.Body.Close()
	bf := bytes.NewBuffer(requestbody)
	req.Body = ioutil.NopCloser(bf)

	if len(requestbody) > 0 {
		postArray := strings.Split(string(requestbody), "&")
		for _, v := range postArray {
			pair := strings.Split(v, "=")
			if len(pair) > 1 {
				ctx.Params[pair[0]], _ = url.QueryUnescape(pair[1])
			}
		}
	}
	// ------------------

	defer s.logRequest(ctx, tm)

	ctx.SetHeader("Date", webTime(tm), true)

	route := s.routes.Match(requestPath, req.Method)
	if route == nil {
		ctx.Abort(404, "Page not found")
		return
	}
	cr := route.cr

	//Set the default content-type
	ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)

	if route.httpHandler != nil {
		unused = route
		// We can not handle custom http handlers here, give back to the caller.
		return
	}

	var args []reflect.Value
	handlerType := route.handler.Type()
	if requiresContext(handlerType) {
		args = append(args, reflect.ValueOf(&ctx))
	}

	match := cr.FindStringSubmatch(requestPath)
	for _, arg := range match[1:] {
		args = append(args, reflect.ValueOf(arg))
	}

	ret, err := s.safelyCall(route.handler, args)
	if err != nil {
		//there was an error or panic while calling the handler
		ctx.Abort(500, "Server Error")
	}
	if len(ret) == 0 {
		return
	}

	sval := ret[0]

	var content []byte

	if sval.Kind() == reflect.String {
		content = []byte(sval.String())
	} else if sval.Kind() == reflect.Slice && sval.Type().Elem().Kind() == reflect.Uint8 {
		content = sval.Interface().([]byte)
	}
	ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
	_, err = ctx.ResponseWriter.Write(content)
	if err != nil {
		ctx.Server.Logger.Println("Error during write: ", err)
	}

	return
}

// SetLogger sets the logger for server s
func (s *Server) SetLogger(logger *log.Logger) {
	s.Logger = logger
}
<commit_msg>fix xml request issue<commit_after>// Copyright (c) 2015 Fred Zhou

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package next

import (
	"bytes"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/pprof"
	"net/url"
	"os"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type Server struct {
	Config *Config
	routes *Routes
	Logger *log.Logger
	Env    map[string]interface{}
	//save the listener so it can be closed
	l net.Listener
}

func NewServer() *Server {
	server := &Server{
		Config: NewConfig(),
		routes: NewRoutes(),
		Logger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
		Env:    map[string]interface{}{},
	}

	// Load default config if exists
	file := "config.json"
	if fileExists(file) {
		server.Config.Load(file)
	}

	return server
}

// ServeHTTP is the interface method for Go's http server package
func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) {
	s.Process(c, req)
}

// Process invokes the routing system for server s
func (s *Server) Process(c http.ResponseWriter, req *http.Request) {
	route := s.routeHandler(req, c)
	if route != nil {
		route.httpHandler.ServeHTTP(c, req)
	}
}

// Get adds a handler for the 'GET' http method for server s.
func (s *Server) Get(route string, handler interface{}) {
	s.routes.Add(route, "GET", handler)
}

// Post adds a handler for the 'POST' http method for server s.
func (s *Server) Post(route string, handler interface{}) {
	s.routes.Add(route, "POST", handler)
}

// Put adds a handler for the 'PUT' http method for server s.
func (s *Server) Put(route string, handler interface{}) {
	s.routes.Add(route, "PUT", handler)
}

// Delete adds a handler for the 'DELETE' http method for server s.
func (s *Server) Delete(route string, handler interface{}) {
	s.routes.Add(route, "DELETE", handler)
}

// Match adds a handler for an arbitrary http method for server s.
func (s *Server) Match(method string, route string, handler interface{}) {
	s.routes.Add(route, method, handler)
}

//Adds a custom handler. Only for webserver mode. Will have no effect when running as FCGI or SCGI.
func (s *Server) Handler(route string, method string, handler http.Handler) {
	s.routes.Add(route, method, handler)
}

// Run starts the web application and serves HTTP requests for s
func (s *Server) Run(addr string) {
	mux := http.NewServeMux()
	if s.Config.Bool("debug.profiler") {
		mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
		mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
		mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
		mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
	}
	mux.Handle("/", s)

	s.Logger.Printf("next serving %s\n", addr)

	l, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
	s.l = l
	err = http.Serve(s.l, mux)
	s.l.Close()
}

// RunTLS starts the web application and serves HTTPS requests for s.
func (s *Server) RunTLS(addr string, config *tls.Config) error {
	mux := http.NewServeMux()
	mux.Handle("/", s)
	l, err := tls.Listen("tcp", addr, config)
	if err != nil {
		log.Fatal("Listen:", err)
		return err
	}

	s.l = l
	return http.Serve(s.l, mux)
}

// Close stops server s.
func (s *Server) Close() {
	if s.l != nil {
		s.l.Close()
	}
}

// safelyCall invokes `function` in recover block
func (s *Server) safelyCall(function reflect.Value, args []reflect.Value) (resp []reflect.Value, e interface{}) {
	defer func() {
		if err := recover(); err != nil {
			if s.Config.Bool("panic") {
				// go back to panic
				panic(err)
			} else {
				e = err
				resp = nil
				s.Logger.Println("Handler crashed with error", err)
				for i := 1; ; i += 1 {
					_, file, line, ok := runtime.Caller(i)
					if !ok {
						break
					}
					s.Logger.Println(file, line)
				}
			}
		}
	}()
	return function.Call(args), nil
}

// requiresContext determines whether 'handlerType' contains
// an argument to 'web.Ctx' as its first argument
func requiresContext(handlerType reflect.Type) bool {
	//if the method doesn't take arguments, no
	if handlerType.NumIn() == 0 {
		return false
	}

	//if the first argument is not a pointer, no
	a0 := handlerType.In(0)
	if a0.Kind() != reflect.Ptr {
		return false
	}
	//if the first argument is a context, yes
	if a0.Elem() == reflect.TypeOf(Context{}) {
		return true
	}

	return false
}

func (s *Server) logRequest(ctx Context, sTime time.Time) {
	//log the request
	var logEntry bytes.Buffer
	req := ctx.Request
	requestPath := req.URL.Path

	duration := time.Now().Sub(sTime)
	var client string

	// We suppose RemoteAddr is of the form Ip:Port as specified in the Request
	// documentation at http://golang.org/pkg/net/http/#Request
	pos := strings.LastIndex(req.RemoteAddr, ":")
	if pos > 0 {
		client = req.RemoteAddr[0:pos]
	} else {
		client = req.RemoteAddr
	}

	fmt.Fprintf(&logEntry, "%s - \033[32;1m %s %s\033[0m - %v", client, req.Method, requestPath, duration)

	if len(ctx.Params) > 0 {
		fmt.Fprintf(&logEntry, " - \033[37;1mParams: %v\033[0m\n", ctx.Params)
	}

	ctx.Server.Logger.Print(logEntry.String())
}

// the main route handler in next
// Tries to handle the given request.
// Finds the route matching the request, and execute the callback associated
// with it.  In case of custom http handlers, this function returns an "unused"
// route. The caller is then responsible for calling the httpHandler associated
// with the returned route.
func (s *Server) routeHandler(req *http.Request, w http.ResponseWriter) (unused *Route) {
	requestPath := req.URL.Path
	ctx := Context{req, map[string]string{}, s, w}

	//set some default headers
	ctx.SetHeader("Server", "next", true)
	tm := time.Now().UTC()

	//ignore errors from ParseForm because it's usually harmless.
	req.ParseForm()
	if len(req.Form) > 0 {
		for k, v := range req.Form {
			ctx.Params[k] = v[0]
		}
	}
	if len(req.PostForm) > 0 {
		for k, v := range req.PostForm {
			ctx.Params[k] = v[0]
		}
	}

	// Data in body
	// ------------------
	requestbody, _ := ioutil.ReadAll(req.Body)
	req.Body.Close()
	bf := bytes.NewBuffer(requestbody)
	req.Body = ioutil.NopCloser(bf)

	if len(requestbody) > 0 {
		postArray := strings.Split(string(requestbody), "&")
		for _, v := range postArray {
			pair := strings.Split(v, "=")
			if len(pair) > 1 {
				ctx.Params[pair[0]], _ = url.QueryUnescape(pair[1])
			}
		}
	}
	if resp, err := NewXml().ToMap(req.Body); err == nil {
		for k, v := range resp {
			ctx.Params[k] = v
		}
	}
	// ------------------

	defer s.logRequest(ctx, tm)

	ctx.SetHeader("Date", webTime(tm), true)

	route := s.routes.Match(requestPath, req.Method)
	if route == nil {
		ctx.Abort(404, "Page not found")
		return
	}
	cr := route.cr

	//Set the default content-type
	ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)

	if route.httpHandler != nil {
		unused = route
		// We can not handle custom http handlers here, give back to the caller.
		return
	}

	var args []reflect.Value
	handlerType := route.handler.Type()
	if requiresContext(handlerType) {
		args = append(args, reflect.ValueOf(&ctx))
	}

	match := cr.FindStringSubmatch(requestPath)
	for _, arg := range match[1:] {
		args = append(args, reflect.ValueOf(arg))
	}

	ret, err := s.safelyCall(route.handler, args)
	if err != nil {
		//there was an error or panic while calling the handler
		ctx.Abort(500, "Server Error")
	}
	if len(ret) == 0 {
		return
	}

	sval := ret[0]

	var content []byte

	if sval.Kind() == reflect.String {
		content = []byte(sval.String())
	} else if sval.Kind() == reflect.Slice && sval.Type().Elem().Kind() == reflect.Uint8 {
		content = sval.Interface().([]byte)
	}
	ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
	_, err = ctx.ResponseWriter.Write(content)
	if err != nil {
		ctx.Server.Logger.Println("Error during write: ", err)
	}

	return
}

// SetLogger sets the logger for server s
func (s *Server) SetLogger(logger *log.Logger) {
	s.Logger = logger
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"errors"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/httputil"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/flynn/go-discover/discover"
)

type HTTPFrontend struct {
	Addr      string
	TLSAddr   string
	TLSConfig *tls.Config

	mtx      sync.RWMutex
	domains  map[string]*httpServer
	services map[string]*httpServer

	etcdPrefix string

	etcd     *etcd.Client
	discover *discover.Client
}

func NewHTTPFrontend(addr string) (*HTTPFrontend, error) {
	f := &HTTPFrontend{
		Addr:       addr,
		etcd:       etcd.NewClient(nil),
		etcdPrefix: "/strowger/http/",
		domains:    make(map[string]*httpServer),
		services:   make(map[string]*httpServer),
	}
	var err error
	f.discover, err = discover.NewClient()
	return f, err
}

func (s *HTTPFrontend) AddHTTPDomain(domain string, service string, certs [][]byte, key []byte) error {
	return s.addDomain(domain, service, true)
}

func (s *HTTPFrontend) addDomain(domain string, service string, persist bool) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	if server, ok := s.domains[domain]; ok {
		if server.name != service {
			return errors.New("domain exists with different service")
		}
		return nil
	}

	server := s.services[service]
	if server == nil {
		services, err := s.discover.Services(service)
		if err != nil {
			return err
		}
		server = &httpServer{name: service, services: services}
	}
	if persist {
		if _, err := s.etcd.Create(s.etcdPrefix+domain+"/service", service, 0); err != nil {
			return err
		}
	}
	// TODO: set cert/key data if provided

	server.refs++
	s.domains[domain] = server
	s.services[service] = server
	// TODO: TLS config

	log.Println("Add service", service, "to domain", domain)

	return nil
}

func (s *HTTPFrontend) RemoveHTTPDomain(domain string) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	server := s.domains[domain]
	if server == nil {
		return
	}
	delete(s.domains, domain)
	server.refs++
	if server.refs <= 0 {
		// TODO: close service set stream
		delete(s.services, server.name)
	}
	// TODO: persist
}

func (s *HTTPFrontend) syncDatabase() {
	var since uint64
	data, err := s.etcd.Get(s.etcdPrefix, false, true)
	if e, ok := err.(etcd.EtcdError); ok && e.ErrorCode == 100 {
		// key not found, ignore
		goto watch
	}
	if err != nil {
		log.Fatal(err)
		return
	}
	since = data.ModifiedIndex
	for _, res := range data.Kvs {
		if !res.Dir {
			continue
		}
		domain := path.Base(res.Key)
		serviceRes, err := s.etcd.Get(res.Key+"/service", false, false)
		if err != nil {
			log.Fatal(err)
		}
		service := serviceRes.Value
		if err := s.addDomain(domain, service, false); err != nil {
			log.Fatal(err)
		}
	}

watch:
	stream := make(chan *etcd.Response)
	stop := make(chan bool)
	// TODO: store stop
	go s.etcd.Watch(s.etcdPrefix, since, false, stream, stop)
	for res := range stream {
		if !res.Dir && res.NewKey && path.Base(res.Key) == "service" {
			domain := path.Base(path.Dir(res.Key))
			if err := s.addDomain(domain, res.Value, false); err != nil {
				// TODO: log error
			}
		}
		// TODO: handle delete
	}
	log.Println("done watching etcd")
}

func (s *HTTPFrontend) serve() {
	l, err := net.Listen("tcp", s.Addr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) serveTLS() {
	l, err := net.Listen("tcp", s.TLSAddr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) handle(conn net.Conn) {
	defer conn.Close()
	sc := httputil.NewServerConn(conn, nil)
	req, err := sc.Read()
	if err != nil {
		if err != httputil.ErrPersistEOF {
			// TODO: log error
		}
		return
	}

	s.mtx.RLock()
	// TODO: handle wildcard domains
	backend := s.domains[req.Host]
	s.mtx.RUnlock()
	log.Println(req, backend)
	if backend == nil {
		// TODO: return 404
		return
	}
	_, tls := conn.(*tls.Conn)
	backend.handle(req, sc, tls)
}

type httpServer struct {
	name     string
	services *discover.ServiceSet
	refs     int
}

func (s *httpServer) getBackend() *httputil.ClientConn {
	for _, addr := range shuffle(s.services.OnlineAddrs()) {
		// TODO: set connection timeout
		backend, err := net.Dial("tcp", addr)
		if err != nil {
			// TODO: log error
			// TODO: limit number of backends tried
			// TODO: temporarily quarantine failing backends
			log.Println("backend error", err)
			continue
		}
		return httputil.NewClientConn(backend, nil)
	}
	// TODO: log no backends found error
	return nil
}

func (s *httpServer) handle(req *http.Request, sc *httputil.ServerConn, tls bool) {
	req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	backend := s.getBackend()
	if backend == nil {
		// TODO: Return 503
		log.Println("no backend found")
		return
	}
	defer backend.Close()

	for {
		if req.Method != "GET" && req.Method != "POST" && req.Method != "HEAD" &&
			req.Method != "OPTIONS" && req.Method != "PUT" && req.Method != "DELETE" && req.Method != "TRACE" {
			// TODO: return 405
		}

		req.Proto = "HTTP/1.1"
		req.ProtoMajor = 1
		req.ProtoMinor = 1
		delete(req.Header, "Te")
		delete(req.Header, "Transfer-Encoding")

		if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
			// If we aren't the first proxy retain prior
			// X-Forwarded-For information as a comma+space
			// separated list and fold multiple headers into one.
			if prior, ok := req.Header["X-Forwarded-For"]; ok {
				clientIP = strings.Join(prior, ", ") + ", " + clientIP
			}
			req.Header.Set("X-Forwarded-For", clientIP)
		}
		if tls {
			req.Header.Set("X-Forwarded-Proto", "https")
		} else {
			req.Header.Set("X-Forwarded-Proto", "http")
		}
		// TODO: Set X-Forwarded-Port

		if err := backend.Write(req); err != nil {
			log.Println("server write err:", err)
			return
		}
		res, err := backend.Read(req)
		if res != nil {
			if err := sc.Write(req, res); err != nil {
				if err != io.EOF && err != httputil.ErrPersistEOF {
					log.Println("client write err:", err)
					// TODO: log error
				}
				return
			}
		}
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("server read err:", err)
				// TODO: log error
				// TODO: Return 502
			}
			return
		}

		// TODO: Proxy HTTP CONNECT? (example: Go RPC over HTTP)
		if res.StatusCode == http.StatusSwitchingProtocols {
			serverW, serverR := backend.Hijack()
			clientW, clientR := sc.Hijack()
			defer serverW.Close()
			done := make(chan struct{})
			go func() {
				serverR.WriteTo(clientW)
				close(done)
			}()
			clientR.WriteTo(serverW)
			<-done
			return
		}

		// TODO: http pipelining
		req, err = sc.Read()
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("client read err:", err)
			}
			return
		}
		req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	}
}

func shuffle(s []string) []string {
	for i := len(s) - 1; i > 0; i-- {
		j := rand.Intn(i + 1)
		s[i], s[j] = s[j], s[i]
	}
	return s
}

func init() {
	rand.Seed(time.Now().UnixNano())
}
<commit_msg>Use our fork of go-etcd<commit_after>package main

import (
	"crypto/tls"
	"errors"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/httputil"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/flynn/go-discover/discover"
	"github.com/flynn/go-etcd/etcd"
)

type HTTPFrontend struct {
	Addr      string
	TLSAddr   string
	TLSConfig *tls.Config

	mtx      sync.RWMutex
	domains  map[string]*httpServer
	services map[string]*httpServer

	etcdPrefix string

	etcd     *etcd.Client
	discover *discover.Client
}

func NewHTTPFrontend(addr string) (*HTTPFrontend, error) {
	f := &HTTPFrontend{
		Addr:       addr,
		etcd:       etcd.NewClient(nil),
		etcdPrefix: "/strowger/http/",
		domains:    make(map[string]*httpServer),
		services:   make(map[string]*httpServer),
	}
	var err error
	f.discover, err = discover.NewClient()
	return f, err
}

func (s *HTTPFrontend) AddHTTPDomain(domain string, service string, certs [][]byte, key []byte) error {
	return s.addDomain(domain, service, true)
}

func (s *HTTPFrontend) addDomain(domain string, service string, persist bool) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	if server, ok := s.domains[domain]; ok {
		if server.name != service {
			return errors.New("domain exists with different service")
		}
		return nil
	}

	server := s.services[service]
	if server == nil {
		services, err := s.discover.Services(service)
		if err != nil {
			return err
		}
		server = &httpServer{name: service, services: services}
	}
	if persist {
		if _, err := s.etcd.Create(s.etcdPrefix+domain+"/service", service, 0); err != nil {
			return err
		}
	}
	// TODO: set cert/key data if provided

	server.refs++
	s.domains[domain] = server
	s.services[service] = server
	// TODO: TLS config

	log.Println("Add service", service, "to domain", domain)

	return nil
}

func (s *HTTPFrontend) RemoveHTTPDomain(domain string) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	server := s.domains[domain]
	if server == nil {
		return
	}
	delete(s.domains, domain)
	server.refs++
	if server.refs <= 0 {
		// TODO: close service set stream
		delete(s.services, server.name)
	}
	// TODO: persist
}

func (s *HTTPFrontend) syncDatabase() {
	var since uint64
	data, err := s.etcd.Get(s.etcdPrefix, false, true)
	if e, ok := err.(etcd.EtcdError); ok && e.ErrorCode == 100 {
		// key not found, ignore
		goto watch
	}
	if err != nil {
		log.Fatal(err)
		return
	}
	since = data.ModifiedIndex
	for _, res := range data.Kvs {
		if !res.Dir {
			continue
		}
		domain := path.Base(res.Key)
		serviceRes, err := s.etcd.Get(res.Key+"/service", false, false)
		if err != nil {
			log.Fatal(err)
		}
		service := serviceRes.Value
		if err := s.addDomain(domain, service, false); err != nil {
			log.Fatal(err)
		}
	}

watch:
	stream := make(chan *etcd.Response)
	stop := make(chan bool)
	// TODO: store stop
	go s.etcd.Watch(s.etcdPrefix, since, false, stream, stop)
	for res := range stream {
		if !res.Dir && res.NewKey && path.Base(res.Key) == "service" {
			domain := path.Base(path.Dir(res.Key))
			if err := s.addDomain(domain, res.Value, false); err != nil {
				// TODO: log error
			}
		}
		// TODO: handle delete
	}
	log.Println("done watching etcd")
}

func (s *HTTPFrontend) serve() {
	l, err := net.Listen("tcp", s.Addr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) serveTLS() {
	l, err := net.Listen("tcp", s.TLSAddr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) handle(conn net.Conn) {
	defer conn.Close()
	sc := httputil.NewServerConn(conn, nil)
	req, err := sc.Read()
	if err != nil {
		if err != httputil.ErrPersistEOF {
			// TODO: log error
		}
		return
	}

	s.mtx.RLock()
	// TODO: handle wildcard domains
	backend := s.domains[req.Host]
	s.mtx.RUnlock()
	log.Println(req, backend)
	if backend == nil {
		// TODO: return 404
		return
	}
	_, tls := conn.(*tls.Conn)
	backend.handle(req, sc, tls)
}

type httpServer struct {
	name     string
	services *discover.ServiceSet
	refs     int
}

func (s *httpServer) getBackend() *httputil.ClientConn {
	for _, addr := range shuffle(s.services.OnlineAddrs()) {
		// TODO: set connection timeout
		backend, err := net.Dial("tcp", addr)
		if err != nil {
			// TODO: log error
			// TODO: limit number of backends tried
			// TODO: temporarily quarantine failing backends
			log.Println("backend error", err)
			continue
		}
		return httputil.NewClientConn(backend, nil)
	}
	// TODO: log no backends found error
	return nil
}

func (s *httpServer) handle(req *http.Request, sc *httputil.ServerConn, tls bool) {
	req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	backend := s.getBackend()
	if backend == nil {
		// TODO: Return 503
		log.Println("no backend found")
		return
	}
	defer backend.Close()

	for {
		if req.Method != "GET" && req.Method != "POST" && req.Method != "HEAD" &&
			req.Method != "OPTIONS" && req.Method != "PUT" && req.Method != "DELETE" && req.Method != "TRACE" {
			// TODO: return 405
		}

		req.Proto = "HTTP/1.1"
		req.ProtoMajor = 1
		req.ProtoMinor = 1
		delete(req.Header, "Te")
		delete(req.Header, "Transfer-Encoding")

		if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
			// If we aren't the first proxy retain prior
			// X-Forwarded-For information as a comma+space
			// separated list and fold multiple headers into one.
			if prior, ok := req.Header["X-Forwarded-For"]; ok {
				clientIP = strings.Join(prior, ", ") + ", " + clientIP
			}
			req.Header.Set("X-Forwarded-For", clientIP)
		}
		if tls {
			req.Header.Set("X-Forwarded-Proto", "https")
		} else {
			req.Header.Set("X-Forwarded-Proto", "http")
		}
		// TODO: Set X-Forwarded-Port

		if err := backend.Write(req); err != nil {
			log.Println("server write err:", err)
			return
		}
		res, err := backend.Read(req)
		if res != nil {
			if err := sc.Write(req, res); err != nil {
				if err != io.EOF && err != httputil.ErrPersistEOF {
					log.Println("client write err:", err)
					// TODO: log error
				}
				return
			}
		}
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("server read err:", err)
				// TODO: log error
				// TODO: Return 502
			}
			return
		}

		// TODO: Proxy HTTP CONNECT? (example: Go RPC over HTTP)
		if res.StatusCode == http.StatusSwitchingProtocols {
			serverW, serverR := backend.Hijack()
			clientW, clientR := sc.Hijack()
			defer serverW.Close()
			done := make(chan struct{})
			go func() {
				serverR.WriteTo(clientW)
				close(done)
			}()
			clientR.WriteTo(serverW)
			<-done
			return
		}

		// TODO: http pipelining
		req, err = sc.Read()
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("client read err:", err)
			}
			return
		}
		req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	}
}

func shuffle(s []string) []string {
	for i := len(s) - 1; i > 0; i-- {
		j := rand.Intn(i + 1)
		s[i], s[j] = s[j], s[i]
	}
	return s
}

func init() {
	rand.Seed(time.Now().UnixNano())
}
<|endoftext|>


<commit_before>// go2hal v0.1.0
// Copyright (c) 2016 Patrick Moule
// License: MIT

package hal

import (
	"testing"
	"fmt"
	"reflect"
)

func TestAddLinkObject(t *testing.T) {
	want, _ := NewLinkRelation("relation")
	want.SetLink(&LinkObject{})
	wantedName := "_links"

	resourceObject := NewResourceObject()
	resourceObject.AddLink(want)

	namedMap := resourceObject.Links()

	if namedMap.Name != wantedName {
		t.Errorf("Map is named %s, want %s", namedMap.Name, wantedName)
	}

	_, ok := namedMap.Content[want.Name()]

	if !ok {
		t.Errorf("LinkRelation %q does not exist", want)
	}
}

func TestAddResourceObject(t *testing.T) {
	want, _ := NewResourceRelation("relation")
	want.SetResource(NewResourceObject())
	wantedName := "_embedded"

	resourceObject := NewResourceObject()
	resourceObject.AddResource(want)

	namedMap := resourceObject.EmbeddedResources()

	if namedMap.Name != wantedName {
		t.Errorf("Map is named %s, want %s", namedMap.Name, wantedName)
	}

	val, ok := namedMap.Content[want.Name()]

	if !ok {
		t.Errorf("LinkRelation %q does not exist", want)
	}

	_, isPropertyMap := val.(PropertyMap)

	if !isPropertyMap {
		t.Errorf("LinkRelation value is %[1]T(%[1]p), want %[2]T(%[2]p)", val, PropertyMap{})
	}
}

func TestAddCurieLink(t *testing.T) {
	resourceObject := NewResourceObject()
	curieName := "doc"
	curieHref := "http://doc/{rel}"
	curieLink, _ := NewCurieLink(curieName, curieHref)

	resourceObject.AddCurieLinks([]*LinkObject {curieLink})

	val := resourceObject.Links().Content["curies"]
	result, ok := val.([]*LinkObject)

	if !ok {
		t.Errorf("CurieLink type == %q, want %q", reflect.TypeOf(val), reflect.TypeOf(result))
	}

	if count := len(result); count != 1 {
		t.Errorf("CurieLink count == %d, want %d", count, 1)
	}

	if curieLink != result[0] {
		t.Errorf("CurieLink == %q, want %q", val, curieLink)
	}

	curieLink2, _ := NewCurieLink(curieName, curieHref)
	resourceObject.AddCurieLinks([]*LinkObject {curieLink, curieLink2})

	fmt.Println("length: " + string(len(resourceObject.Links().Content)))
	val2 := resourceObject.Links().Content["curies"]
	result2, _ := val2.([]*LinkObject)

	if count := len(result2); count != 2 {
		t.Errorf("CurieLink count == %d, want %d", count, 2)
	}
}<commit_msg>Added tests for Resource<commit_after>// go2hal v0.1.0
// Copyright (c) 2016 Patrick Moule
// License: MIT

package hal

import (
	"testing"
	"fmt"
	"reflect"
)

func TestNewResource(t *testing.T) {
	wanted := []interface{}{"_links", 0, "_embedded", 0, 0}

	resource := NewResourceObject()
	links := resource.Links()

	if name := links.Name; name != wanted[0] {
		t.Errorf("Links name is %s, want %s", name, wanted[0])
	}

	if count := len(links.Content); count != wanted[1] {
		t.Errorf("Initial link amount %d, want %d", count, wanted[1])
	}

	embeddedResources := resource.EmbeddedResources()

	if name := embeddedResources.Name; name != wanted[2] {
		t.Errorf("Embedded resource name is %s, want %s", name, wanted[2])
	}

	if count := len(embeddedResources.Content); count != wanted[3] {
		t.Errorf("Initial embeddedResources amount %d, want %d", count, wanted[3])
	}

	data := resource.Data()

	if count := len(data); count != wanted[4] {
		t.Errorf("Initial data amount %d, want %d", count, wanted[4])
	}
}

func TestAddLinkObject(t *testing.T) {
	want, _ := NewLinkRelation("relation")
	want.SetLink(&LinkObject{})
	wantedName := "_links"

	resourceObject := NewResourceObject()
	resourceObject.AddLink(want)

	namedMap := resourceObject.Links()

	if namedMap.Name != wantedName {
		t.Errorf("Map is named %s, want %s", namedMap.Name, wantedName)
	}

	_, ok := namedMap.Content[want.Name()]

	if !ok {
		t.Errorf("LinkRelation %q does not exist", want)
	}
}

func TestAddResourceObject(t *testing.T) {
	want, _ := NewResourceRelation("relation")
	want.SetResource(NewResourceObject())
	wantedName := "_embedded"

	resourceObject := NewResourceObject()
	resourceObject.AddResource(want)

	namedMap := resourceObject.EmbeddedResources()

	if namedMap.Name != wantedName {
		t.Errorf("Map is named %s, want %s", namedMap.Name, wantedName)
	}

	val, ok := namedMap.Content[want.Name()]

	if !ok {
		t.Errorf("LinkRelation %q does not exist", want)
	}

	_, isPropertyMap := val.(PropertyMap)

	if !isPropertyMap {
		t.Errorf("LinkRelation value is %[1]T(%[1]p), want %[2]T(%[2]p)", val, PropertyMap{})
	}
}

func TestAddCurieLink(t *testing.T) {
	resourceObject := NewResourceObject()
	curieName := "doc"
	curieHref := "http://doc/{rel}"
	curieLink, _ := NewCurieLink(curieName, curieHref)

	resourceObject.AddCurieLinks([]*LinkObject{curieLink})

	val := resourceObject.Links().Content["curies"]
	result, ok := val.([]*LinkObject)

	if !ok {
		t.Errorf("CurieLink type == %q, want %q", reflect.TypeOf(val), reflect.TypeOf(result))
	}

	if count := len(result); count != 1 {
		t.Errorf("CurieLink count == %d, want %d", count, 1)
	}

	if curieLink != result[0] {
		t.Errorf("CurieLink == %q, want %q", val, curieLink)
	}

	curieLink2, _ := NewCurieLink(curieName, curieHref)
	resourceObject.AddCurieLinks([]*LinkObject{curieLink, curieLink2})

	fmt.Println("length: " + string(len(resourceObject.Links().Content)))
	val2 := resourceObject.Links().Content["curies"]
	result2, _ := val2.([]*LinkObject)

	if count := len(result2); count != 2 {
		t.Errorf("CurieLink count == %d, want %d", count, 2)
	}
}<|endoftext|>


<commit_before>// Copyright 2015-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// bbramfs builds a simple initramfs given an existing built bb; see bb.go
// You have to run bb first, which creates cmds/bb/bbsh. cd to that directory,
// and run bbramfs, and you have a single binary which does all u-root commands.
package main

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

	"github.com/u-root/u-root/pkg/cpio"
	_ "github.com/u-root/u-root/pkg/cpio/newc"
	"github.com/u-root/u-root/pkg/ldd"
)

var (
	// Paths contains the paths to put into the initramfs.
	// The index is a root directory, and the value is the place from which to
	// walk. The only required root is the bbsh dir itself, and the starting
	// walk is init -- i.e. we grab only one file. Should you wish to bring in,
	// e.g., /lib/modules/4.04, you would do add the root as / and the
	// starting point for the walk as lib/modules/4.04. That way we only preserve
	// as much of the path as we need, but we can preserve it all.
	paths      = map[string][]string{}
	extraPaths = flag.String("extra", "", `Extra paths to add in the form root:start, e.g. /:etc/hosts.
The path before the : is used as a starting point for a walk; the path after the : selects what things to put
into the initramfs starting at /. E.g., /tmp/prototype:/ will install the prototype file system into / of the initramfs`)
	extraCmds = flag.String("cmds", "", "Extra commands to add (full path, comma-separated string)")
	extraCpio = flag.String("cpio", "", "A list of cpio archives to include in the output")
)

func sanity() {
	goBinGo := filepath.Join(config.Goroot, "bin/go")
	_, err := os.Stat(goBinGo)
	if err == nil {
		config.Go = goBinGo
	}
	// but does the one in go/bin/OS_ARCH exist too?
	goBinGo = filepath.Join(config.Goroot, fmt.Sprintf("bin/%s_%s/go", config.Goos, config.Arch))
	_, err = os.Stat(goBinGo)
	if err == nil {
		config.Go = goBinGo
	}
	if config.Go == "" {
		log.Fatalf("Can't find a go binary! Is GOROOT set correctly?")
	}
}

// dirComponents takes a string and returns an array of strings,
// such that we can create the directory records for intermediate
// directories.
func dirComponents(dir string) []string {
	var dirlist []string
	if filepath.Dir(dir) == "." {
		return []string{}
	}
	for d := filepath.Dir(dir); d != "/"; d = filepath.Dir(d) {
		dirlist = append([]string{d}, dirlist...)
	}
	dirlist = append(dirlist, dir)
	return dirlist
}

// copyCommands takes a list of commands, generates the list of libs,
// and creates cpio records, including directory records.
func copyCommands(w cpio.Writer, cmds []string) {
	debug("copyCommands: start with %v", cmds)
	var recs []cpio.Record
	libs, err := uroot.LddList(cmds)
	if err != nil {
		log.Fatalf("%v\n", err)
	}
	cmds = append(cmds, libs...)
	for _, n := range cmds {
		debug("copyCommands: file %v", n)
		for _, n := range dirComponents(n) {
			debug("copyCommands: %v", n)
			r, err := cpio.GetRecord(n)
			if err != nil {
				log.Fatalf("%v: %v", n, err)
			}
			recs = append(recs, r)
		}
	}
	cpio.MakeReproducible(recs)
	if err := w.WriteRecords(recs); err != nil {
		log.Fatalf("%v\n", err)
	}
}

func ramfs() {
	archiver, err := cpio.Format("newc")
	if err != nil {
		log.Fatalf("Creating newc archiver: %v", err)
	}

	oname := fmt.Sprintf("/tmp/initramfs.%v_%v.cpio", config.Goos, config.Arch)
	f, err := os.Create(oname)
	if err != nil {
		log.Fatalf("%v\n", err)
	}

	w := archiver.Writer(f)
	cpio.MakeReproducible(devCPIO[:])
	if err := w.WriteRecords(devCPIO[:]); err != nil {
		log.Fatalf("%v\n", err)
	}

	paths[filepath.Join(config.Gopath, "src/github.com/u-root/u-root/bb/bbsh")] = []string{"init", "ubin"}

	if *extraPaths != "" {
		extras := strings.Split(strings.TrimSpace(*extraPaths), " ")
		for _, x := range extras {
			p := strings.Split(x, ":")
			if len(p) != 2 {
				p = append([]string{"/"}, p...)
			}
			paths[p[0]] = append(paths[p[0]], p[1])
		}
	}

	if *extraCmds != "" {
		copyCommands(w, strings.Split(strings.TrimSpace(*extraCmds), " "))
	}

	if *extraCpio != "" {
		extras := strings.Split(strings.TrimSpace(*extraCpio), " ")
		for _, x := range extras {
			a, err := cpio.Format("newc")
			if err != nil {
				log.Fatalf("Creating archiver: %v", err)
			}
			f, err := os.Open(x)
			if err != nil {
				log.Fatalf("%v: %v", x, err)
			}
			defer f.Close()
			rr := a.Reader(f)
			recs, err := rr.ReadRecords()
			if err != nil {
				log.Fatalf("read records: %v", err)
			}
			cpio.MakeReproducible(recs)
			if err := w.WriteRecords(recs); err != nil {
				log.Fatalf("%v\n", err)
			}
		}
	}

	// For all the 'roots' in paths, start walking at the name.
	debug("PATHS: %v", paths)
	for r, list := range paths {
		debug("PATHS: root %v", r)
		// we need to make all the path prefix directories.
		for _, n := range list {
			debug("\troot %v, name %v", r, n)
			for _, d := range dirComponents(n) {
				debug("\t\troot %v, name %v, component %v", r, n, d)
				rec, err := cpio.GetRecord(d)
				if err != nil {
					log.Fatalf("Getting record of %q failed: %v", d, err)
				}
				recs := []cpio.Record{rec}
				cpio.MakeReproducible(recs)
				if err := w.WriteRecords(recs); err != nil {
					log.Fatalf("%v\n", err)
				}
			}
		}
		for _, n := range list {
			if err := filepath.Walk(filepath.Join(r, n), func(name string, fi os.FileInfo, err error) error {
				if err != nil {
					return err
				}
				cn, err := filepath.Rel(r, name)
				if err != nil {
					log.Fatalf("filepath.Rel(%v, %v): %v", r, name, err)
				}
				debug("%v\n", cn)
				rec, err := cpio.GetRecord(name)
				if err != nil {
					log.Fatalf("Getting record of %q failed: %v", cn, err)
				}
				// the name in the cpio is relative to our starting point.
				rec.Name = cn
				recs := []cpio.Record{rec}
				cpio.MakeReproducible(recs)
				if err := w.WriteRecords(recs); err != nil {
					log.Fatalf("%v\n", err)
				}
				return nil
			}); err != nil {
				log.Fatalf("bbsh walk failed: %v", err)
			}
		}
	}

	if err := w.WriteTrailer(); err != nil {
		log.Fatalf("Error writing trailer record: %v", err)
	}
	fmt.Printf("Output file is in %v\n", oname)
}
<commit_msg>Changed the structure so there is only the -cmds flag, not -extra. Now that flag checks if the inputted path is a file or directory. Using string.Fields to parse removed the issues of multiple spaces between arguments. Gofmt and adding comments Note: absolute paths needed for cmds option<commit_after>// Copyright 2015-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSiD-style
// license that can be found in the LICENSE file.

// bbramfs builds a simple initramfs given an existing built bb; see bb.go
// You have to run bb first, which creates cmds/bb/bbsh. cd to that directory,
// and run bbramfs, and you have a single binary which does all u-root commands.
package main

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

	"github.com/u-root/u-root/pkg/cpio"
	_ "github.com/u-root/u-root/pkg/cpio/newc"
	"github.com/u-root/u-root/pkg/ldd"
)

var (
	// Paths contains the paths to put into the initramfs.
	// The index is a root directory, and the value is the place from which to
	// walk. The only required root is the bbsh dir itself, and the starting
	// walk is init -- i.e. we grab only one file. Should you wish to bring in,
	// e.g., /lib/modules/4.04, you would do add the root as / and the
	// starting point for the walk as lib/modules/4.04. That way we only preserve
	// as much of the path as we need, but we can preserve it all.
	paths     = map[string][]string{}
	extraCmds = flag.String("cmds", "", "Extra commands to add (full path, comma-separated string)")
	extraCpio = flag.String("cpio", "", "A list of cpio archives to include in the output")
)

func sanity() {
	goBinGo := filepath.Join(config.Goroot, "bin/go")
	_, err := os.Stat(goBinGo)
	if err == nil {
		config.Go = goBinGo
	}
	// but does the one in go/bin/OS_ARCH exist too?
	goBinGo = filepath.Join(config.Goroot, fmt.Sprintf("bin/%s_%s/go", config.Goos, config.Arch))
	_, err = os.Stat(goBinGo)
	if err == nil {
		config.Go = goBinGo
	}
	if config.Go == "" {
		log.Fatalf("Can't find a go binary! Is GOROOT set correctly?")
	}
}

// dirComponents takes a string and returns an array of strings,
// such that we can create the directory records for intermediate
// directories.
func dirComponents(dir string) []string {
	var dirlist []string
	if filepath.Dir(dir) == "." {
		return []string{}
	}
	for d := filepath.Dir(dir); d != "/"; d = filepath.Dir(d) {
		dirlist = append([]string{d}, dirlist...)
	}
	dirlist = append(dirlist, dir)
	return dirlist
}

// copyCommands takes a list of commands, generates the list of libs,
// and creates cpio records, including directory records.
func copyCommands(w cpio.Writer, cmd string) {
	debug("copyCommands: start with %v", cmd)
	var recs []cpio.Record
	//introduced the tmpSlice variable to be the dependency slice for the cmd string
	tmpSlice := []string{cmd}
	libs, err := uroot.LddList(tmpSlice)
	if err != nil {
		log.Fatalf("%v\n", err)
	}
	tmpSlice = append(tmpSlice, libs...)
	for _, n := range tmpSlice {
		debug("copyCommands: file %v", n)
		for _, n := range dirComponents(n) {
			debug("copyCommands: %v", n)
			r, err := cpio.GetRecord(n)
			if err != nil {
				log.Fatalf("%v: %v", n, err)
			}
			recs = append(recs, r)
		}
	}
	cpio.MakeReproducible(recs)
	if err := w.WriteRecords(recs); err != nil {
		log.Fatalf("%v\n", err)
	}
}

func ramfs() {
	archiver, err := cpio.Format("newc")
	if err != nil {
		log.Fatalf("Creating newc archiver: %v", err)
	}

	oname := fmt.Sprintf("/tmp/initramfs.%v_%v.cpio", config.Goos, config.Arch)
	f, err := os.Create(oname)
	if err != nil {
		log.Fatalf("%v\n", err)
	}

	w := archiver.Writer(f)
	cpio.MakeReproducible(devCPIO[:])
	if err := w.WriteRecords(devCPIO[:]); err != nil {
		log.Fatalf("%v\n", err)
	}

	paths[filepath.Join(config.Gopath, "src/github.com/u-root/u-root/bb/bbsh")] = []string{"init", "ubin"}

	if *extraCmds != "" {
		copyc := strings.Fields(*extraCmds)
		for _, eachPath := range copyc {
			// eachPath must be an absolute filepath for os.Stat
			statval, err := os.Stat(eachPath)
			if err != nil {
				log.Fatalf("%v\n", err)
			}
			if statval.IsDir() {
				// If the file is a directory, append all the files in the directory to the path
				p := strings.Split(eachPath, ":")
				if len(p) != 2 {
					p = append([]string{"/"}, p...)
				}
				paths[p[0]] = append(paths[p[0]], p[1])
			} else {
				copyCommands(w, eachPath)
			}
		}
	}

	if *extraCpio != "" {
		extras := strings.Fields(*extraCpio)
		for _, x := range extras {
			a, err := cpio.Format("newc")
			if err != nil {
				log.Fatalf("Creating archiver: %v", err)
			}
			f, err := os.Open(x)
			if err != nil {
				log.Fatalf("%v: %v", x, err)
			}
			defer f.Close()
			rr := a.Reader(f)
			recs, err := rr.ReadRecords()
			if err != nil {
				log.Fatalf("read records: %v", err)
			}
			cpio.MakeReproducible(recs)
			if err := w.WriteRecords(recs); err != nil {
				log.Fatalf("%v\n", err)
			}
		}
	}

	// For all the 'roots' in paths, start walking at the name.
	debug("PATHS: %v", paths)
	for r, list := range paths {
		debug("PATHS: root %v", r)
		// we need to make all the path prefix directories.
		for _, n := range list {
			debug("\troot %v, name %v", r, n)
			for _, d := range dirComponents(n) {
				debug("\t\troot %v, name %v, component %v", r, n, d)
				rec, err := cpio.GetRecord(d)
				if err != nil {
					log.Fatalf("Getting record of %q failed: %v", d, err)
				}
				recs := []cpio.Record{rec}
				cpio.MakeReproducible(recs)
				if err := w.WriteRecords(recs); err != nil {
					log.Fatalf("%v\n", err)
				}
			}
		}
		for _, n := range list {
			if err := filepath.Walk(filepath.Join(r, n), func(name string, fi os.FileInfo, err error) error {
				if err != nil {
					return err
				}
				cn, err := filepath.Rel(r, name)
				if err != nil {
					log.Fatalf("filepath.Rel(%v, %v): %v", r, name, err)
				}
				debug("%v\n", cn)
				rec, err := cpio.GetRecord(name)
				if err != nil {
					log.Fatalf("Getting record of %q failed: %v", cn, err)
				}
				// the name in the cpio is relative to our starting point.
				rec.Name = cn
				recs := []cpio.Record{rec}
				cpio.MakeReproducible(recs)
				if err := w.WriteRecords(recs); err != nil {
					log.Fatalf("%v\n", err)
				}
				return nil
			}); err != nil {
				log.Fatalf("bbsh walk failed: %v", err)
			}
		}
	}

	if err := w.WriteTrailer(); err != nil {
		log.Fatalf("Error writing trailer record: %v", err)
	}
	fmt.Printf("Output file is in %v\n", oname)
}
<|endoftext|>


<commit_before>package orm

import (
	"gondola/orm/driver"
)

type Iter struct {
	q     *Query
	limit int
	driver.Iter
	err error
}

// Next advances the iter to the next result,
// filling the fields in the out parameter. It
// returns true iff there was a result.
func (i *Iter) Next(out interface{}) bool {
	if i.err != nil {
		return false
	}
	if i.Iter == nil {
		if i.q.model == nil {
			i.q.model, i.err = i.q.orm.model(out)
			if i.q.model == nil {
				return false
			}
		}
		i.q.orm.numQueries++
		i.Iter = i.q.orm.driver.Query(i.q.model, i.q.q, i.limit, i.q.offset, i.q.sortDir, i.q.sortField)
	}
	ok := i.Iter.Next(out)
	if ok {
		i.err = i.q.model.fields.Methods.Load(out)
	}
	return i.err == nil
}

// Err returns the first error returned by the iterator. Once
// there's an error, Next() will return false.
func (i *Iter) Err() error {
	if i.err != nil {
		return i.err
	}
	if i.Iter != nil {
		return i.Iter.Err()
	}
	return nil
}

// P panics if the iter has an error. It's intended as a shorthand
// to save a few lines of code. For iterating over
// the results, a common pattern is:
//
//     for iter.Next(&obj) {
//     ... do something with obj...
//     }
//     if err := iter.Err(); err != nil {
//         panic(err)
//     }
//
// With P() you can instead use this code and save a few keystrokes:
//
//     for iter.Next(&obj) {
//     ... do something with obj...
//     }
//     iter.P()
func (i *Iter) P() {
	if err := i.Err(); err != nil {
		panic(err)
	}
}

// Close closes the iter. It's automatically called when the results
// are exhausted, but if you're ignoring some results you must call
// Close manually to avoid leaking resources. Close is idempotent.
func (i *Iter) Close() error {
	if i.Iter != nil {
		err := i.Iter.Close()
		i.Iter = nil
		return err
	}
	return nil
}
<commit_msg>Fix infinite look on iter<commit_after>package orm

import (
	"gondola/orm/driver"
)

type Iter struct {
	q     *Query
	limit int
	driver.Iter
	err error
}

// Next advances the iter to the next result,
// filling the fields in the out parameter. It
// returns true iff there was a result.
func (i *Iter) Next(out interface{}) bool {
	if i.err != nil {
		return false
	}
	if i.Iter == nil {
		if i.q.model == nil {
			i.q.model, i.err = i.q.orm.model(out)
			if i.q.model == nil {
				return false
			}
		}
		i.q.orm.numQueries++
		i.Iter = i.q.orm.driver.Query(i.q.model, i.q.q, i.limit, i.q.offset, i.q.sortDir, i.q.sortField)
	}
	ok := i.Iter.Next(out)
	if ok {
		i.err = i.q.model.fields.Methods.Load(out)
	}
	return ok && i.err == nil
}

// Err returns the first error returned by the iterator. Once
// there's an error, Next() will return false.
func (i *Iter) Err() error {
	if i.err != nil {
		return i.err
	}
	if i.Iter != nil {
		return i.Iter.Err()
	}
	return nil
}

// P panics if the iter has an error. It's intended as a shorthand
// to save a few lines of code. For iterating over
// the results, a common pattern is:
//
//     for iter.Next(&obj) {
//     ... do something with obj...
//     }
//     if err := iter.Err(); err != nil {
//         panic(err)
//     }
//
// With P() you can instead use this code and save a few keystrokes:
//
//     for iter.Next(&obj) {
//     ... do something with obj...
//     }
//     iter.P()
func (i *Iter) P() {
	if err := i.Err(); err != nil {
		panic(err)
	}
}

// Close closes the iter. It's automatically called when the results
// are exhausted, but if you're ignoring some results you must call
// Close manually to avoid leaking resources. Close is idempotent.
func (i *Iter) Close() error {
	if i.Iter != nil {
		err := i.Iter.Close()
		i.Iter = nil
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strings"
	"time"

	"github.com/hashicorp/go-version"
	"github.com/urfave/cli"
	"gopkg.in/yaml.v2"
)

type ProjectSync struct {
	BaseCommand
}

// Minimal compose file struct to discover volumes
type ComposeFile struct {
	Volumes map[string]Volume
}

type Volume struct {
	External bool
}

const UNISON_PORT = 5000
const MAX_WATCHES = "100000"

func (cmd *ProjectSync) Commands() []cli.Command {
	start := cli.Command{
		Name:        "sync:start",
		Aliases:     []string{"sync"},
		Usage:       "Start a unison sync on local project directory. Optionally provide a volume name.",
		ArgsUsage:   "[optional volume name]",
		Description: "Volume name will be discovered in the following order: argument to this command > outrigger project config > docker-compose file > current directory name",
		Flags: []cli.Flag{
			cli.IntFlag{
				Name:   "initial-sync-timeout",
				Value:  60,
				Usage:  "Maximum amount of time in seconds to allow for detecting each of start of the unison container and start of initial sync",
				EnvVar: "RIG_PROJECT_SYNC_TIMEOUT",
			},
			cli.IntFlag{
				Name:   "initial-sync-wait",
				Value:  5,
				Usage:  "Time in seconds to wait between checks to see if initial sync has finished.",
				EnvVar: "RIG_PROJECT_INITIAL_SYNC_WAIT",
			},
		},
		Before: cmd.Before,
		Action: cmd.RunStart,
	}
	stop := cli.Command{
		Name:        "sync:stop",
		Usage:       "Stops a unison sync on local project directory. Optionally provide a volume name.",
		ArgsUsage:   "[optional volume name]",
		Description: "Volume name will be discovered in the following order: argument to this command > outrigger project config > docker-compose file > current directory name",
		Before:      cmd.Before,
		Action:      cmd.RunStop,
	}

	return []cli.Command{start, stop}
}

// Start the unison sync process
func (cmd *ProjectSync) RunStart(ctx *cli.Context) error {
	config := NewProjectConfig()
	volumeName := cmd.GetVolumeName(ctx, config)
	cmd.out.Verbose.Printf("Starting sync with volume: %s", volumeName)

	// Ensure the processes can handle a large number of watches
	if err := cmd.machine.SetSysctl("fs.inotify.max_user_watches", MAX_WATCHES); err != nil {
		cmd.out.Error.Fatalf("Error configuring file watches on Docker Machine: %v", err)
	}

	cmd.out.Info.Printf("Starting sync volume: %s", volumeName)
	exec.Command("docker", "volume", "create", volumeName).Run()

	cmd.out.Info.Println("Starting unison container")
	unisonMinorVersion := cmd.GetUnisonMinorVersion()
	cmd.out.Verbose.Printf("Local unison version for compatibilty: %s", unisonMinorVersion)
	exec.Command("docker", "container", "stop", volumeName).Run()
	err := exec.Command("docker", "container", "run", "--detach", "--rm",
		"-v", fmt.Sprintf("%s:/unison", volumeName),
		"-e", "UNISON_DIR=/unison",
		"-l", fmt.Sprintf("com.dnsdock.name=%s", volumeName),
		"-l", "com.dnsdock.image=volume.outrigger",
		"--name", volumeName,
		fmt.Sprintf("outrigger/unison:%s", unisonMinorVersion),
	).Run()
	if err != nil {
		cmd.out.Error.Fatalf("Error starting sync container %s: %v", volumeName, err)
	}

	var ip = cmd.WaitForUnisonContainer(volumeName, ctx.Int("initial-sync-timeout"))

	cmd.out.Info.Println("Initializing sync")

	// Remove the log file, the existence of the log file will mean that sync is up and running
	var logFile = fmt.Sprintf("%s.log", volumeName)
	exec.Command("rm", "-f", logFile).Run()

	unisonArgs := []string{
		".",
		fmt.Sprintf("socket://%s:%d/", ip, UNISON_PORT),
		"-auto", "-batch", "-silent", "-contactquietly",
		"-repeat", "watch",
		"-prefer", ".",
		"-logfile", logFile,
		"-ignore", fmt.Sprintf("Name %s", logFile),
	}
	// Append ProjectConfig ignores here
	if config.Sync != nil {
		for _, ignore := range config.Sync.Ignore {
			unisonArgs = append(unisonArgs, "-ignore", ignore)
		}
	}

	cmd.out.Verbose.Printf("Unison Args: %s", strings.Join(unisonArgs[:], " "))
	if err = exec.Command("unison", unisonArgs...).Start(); err != nil {
		cmd.out.Error.Fatalf("Error starting local unison process: %v", err)
	}

	cmd.WaitForSyncInit(logFile, ctx.Int("initial-sync-timeout"), ctx.Int("initial-sync-wait"))

	return nil
}

// Start the unison sync process
func (cmd *ProjectSync) RunStop(ctx *cli.Context) error {
	config := NewProjectConfig()
	volumeName := cmd.GetVolumeName(ctx, config)
	cmd.out.Verbose.Printf("Stopping sync with volume: %s", volumeName)

	cmd.out.Info.Println("Stopping unison container")
	exec.Command("docker", "container", "stop", volumeName).Run()

	return nil
}

// Find the volume name through a variety of fall backs
func (cmd *ProjectSync) GetVolumeName(ctx *cli.Context, config *ProjectConfig) string {
	// 1. Check for argument
	if ctx.Args().Present() {
		return ctx.Args().First()
	}

	// 2. Check for config
	if config.Sync != nil && config.Sync.Volume != "" {
		return config.Sync.Volume
	}

	// 3. Parse compose file looking for an external volume named *-sync
	if composeConfig, err := cmd.LoadComposeFile(); err == nil {
		for name, volume := range composeConfig.Volumes {
			if strings.HasSuffix(name, "-sync") && volume.External {
				return name
			}
		}
	}

	// 4. Use local dir for the volume name
	if dir, err := os.Getwd(); err == nil {
		var _, folder = path.Split(dir)
		return fmt.Sprintf("%s-sync", folder)
	} else {
		cmd.out.Error.Println(err)
	}

	cmd.out.Error.Fatal("Unable to determine a name for the sync volume")
	return ""
}

// Load the proper compose file
func (cmd *ProjectSync) LoadComposeFile() (*ComposeFile, error) {
	yamlFile, err := ioutil.ReadFile("./docker-compose.yml")

	if err == nil {
		var config ComposeFile
		if err := yaml.Unmarshal(yamlFile, &config); err != nil {
			cmd.out.Error.Fatalf("YAML Parsing Error: %s", err)
		}
		return &config, nil
	}

	return nil, err
}

// Wait for the unison container port to allow connections
// Due to the fact that we don't compile with -cgo (so we can build using Docker),
// we need to discover the IP address of the container instead of using the DNS name
// when compiled without -cgo this executable will not use the native mac dns resolution
// which is how we have configured dnsdock to provide names for containers.
func (cmd *ProjectSync) WaitForUnisonContainer(containerName string, timeoutSeconds int) string {
	cmd.out.Info.Println("Waiting for container to start")

	var timeoutLoopSleep = time.Duration(100) * time.Millisecond
	// * 10 here because we loop once every 100 ms and we want to get to seconds
	var timeoutLoops = timeoutSeconds * 10

	output, err := exec.Command("docker", "inspect", "--format", "{{.NetworkSettings.IPAddress}}", containerName).Output()
	if err != nil {
		cmd.out.Error.Fatalf("Error inspecting sync container %s: %v", containerName, err)
	}
	ip := strings.Trim(string(output), "\n")

	cmd.out.Verbose.Printf("Checking for unison network connection on %s %d", ip, UNISON_PORT)
	for i := 1; i <= timeoutLoops; i++ {
		if conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, UNISON_PORT)); err == nil {
			conn.Close()
			return ip
		} else {
			cmd.out.Info.Printf("Error: %v", err)
			time.Sleep(timeoutLoopSleep)
		}
	}
	cmd.out.Error.Fatal("Sync container failed to start!")
	return ""
}

// The local unison process is finished initializing when the log file exists
// and has stopped growing in size
func (cmd *ProjectSync) WaitForSyncInit(logFile string, timeoutSeconds int, syncWaitSeconds int) {
	cmd.out.Info.Print("Waiting for initial sync detection")

	var tempFile = fmt.Sprintf(".%s.tmp", logFile)
	var timeoutLoopSleep = time.Duration(100) * time.Millisecond
	// * 10 here because we loop once every 100 ms and we want to get to seconds
	var timeoutLoops = timeoutSeconds * 10

	// Create a temp file to cause a sync action
	exec.Command("touch", tempFile).Run()

	for i := 1; i <= timeoutLoops; i++ {
		if i%10 == 0 {
			os.Stdout.WriteString(".")
		}
		if statInfo, err := os.Stat(logFile); err == nil {
			os.Stdout.WriteString(" initial sync detected\n")
			// Remove the temp file now that we are running
			exec.Command("rm", "-f", tempFile).Run()

			cmd.out.Info.Print("Waiting for initial sync to finish")
			// Arbitrary sleep length but anything less than 3 wasn't catching
			// ongoing very quick file updates during a test
			var statSleep = time.Duration(syncWaitSeconds) * time.Second
			// Initialize at -2 to force at least one loop
			var lastSize = int64(-2)
			for lastSize != statInfo.Size() {
				os.Stdout.WriteString(".")
				time.Sleep(statSleep)
				lastSize = statInfo.Size()
				if statInfo, err = os.Stat(logFile); err != nil {
					cmd.out.Info.Print(err.Error())
					lastSize = -1
				}
			}
			os.Stdout.WriteString(" done\n")
			return
		} else {
			time.Sleep(timeoutLoopSleep)
		}
	}

	// The log file was not created, the sync has not started yet
	exec.Command("rm", "-f", tempFile).Run()
	cmd.out.Error.Fatal("Failed to detect start of initial sync!")
}

// Get the local Unison version to try to load a compatible unison image
// This function discovers a semver like 2.48.4 and return 2.48
func (cmd *ProjectSync) GetUnisonMinorVersion() string {
	output, _ := exec.Command("unison", "-version").Output()
	re := regexp.MustCompile("unison version (\\d+\\.\\d+\\.\\d+)")
	rawVersion := re.FindAllStringSubmatch(string(output), -1)[0][1]
	v := version.Must(version.NewVersion(rawVersion))
	segments := v.Segments()
	return fmt.Sprintf("%d.%d", segments[0], segments[1])
}
<commit_msg>Move comment about wait time to portion where it is relevant now that it is configurable.<commit_after>package commands

import (
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strings"
	"time"

	"github.com/hashicorp/go-version"
	"github.com/urfave/cli"
	"gopkg.in/yaml.v2"
)

type ProjectSync struct {
	BaseCommand
}

// Minimal compose file struct to discover volumes
type ComposeFile struct {
	Volumes map[string]Volume
}

type Volume struct {
	External bool
}

const UNISON_PORT = 5000
const MAX_WATCHES = "100000"

func (cmd *ProjectSync) Commands() []cli.Command {
	start := cli.Command{
		Name:        "sync:start",
		Aliases:     []string{"sync"},
		Usage:       "Start a unison sync on local project directory. Optionally provide a volume name.",
		ArgsUsage:   "[optional volume name]",
		Description: "Volume name will be discovered in the following order: argument to this command > outrigger project config > docker-compose file > current directory name",
		Flags: []cli.Flag{
			cli.IntFlag{
				Name:   "initial-sync-timeout",
				Value:  60,
				Usage:  "Maximum amount of time in seconds to allow for detecting each of start of the unison container and start of initial sync",
				EnvVar: "RIG_PROJECT_SYNC_TIMEOUT",
			},
			// Arbitrary sleep length but anything less than 3 wasn't catching
			// ongoing very quick file updates during a test
			cli.IntFlag{
				Name:   "initial-sync-wait",
				Value:  5,
				Usage:  "Time in seconds to wait between checks to see if initial sync has finished.",
				EnvVar: "RIG_PROJECT_INITIAL_SYNC_WAIT",
			},
		},
		Before: cmd.Before,
		Action: cmd.RunStart,
	}
	stop := cli.Command{
		Name:        "sync:stop",
		Usage:       "Stops a unison sync on local project directory. Optionally provide a volume name.",
		ArgsUsage:   "[optional volume name]",
		Description: "Volume name will be discovered in the following order: argument to this command > outrigger project config > docker-compose file > current directory name",
		Before:      cmd.Before,
		Action:      cmd.RunStop,
	}

	return []cli.Command{start, stop}
}

// Start the unison sync process
func (cmd *ProjectSync) RunStart(ctx *cli.Context) error {
	config := NewProjectConfig()
	volumeName := cmd.GetVolumeName(ctx, config)
	cmd.out.Verbose.Printf("Starting sync with volume: %s", volumeName)

	// Ensure the processes can handle a large number of watches
	if err := cmd.machine.SetSysctl("fs.inotify.max_user_watches", MAX_WATCHES); err != nil {
		cmd.out.Error.Fatalf("Error configuring file watches on Docker Machine: %v", err)
	}

	cmd.out.Info.Printf("Starting sync volume: %s", volumeName)
	exec.Command("docker", "volume", "create", volumeName).Run()

	cmd.out.Info.Println("Starting unison container")
	unisonMinorVersion := cmd.GetUnisonMinorVersion()
	cmd.out.Verbose.Printf("Local unison version for compatibilty: %s", unisonMinorVersion)
	exec.Command("docker", "container", "stop", volumeName).Run()
	err := exec.Command("docker", "container", "run", "--detach", "--rm",
		"-v", fmt.Sprintf("%s:/unison", volumeName),
		"-e", "UNISON_DIR=/unison",
		"-l", fmt.Sprintf("com.dnsdock.name=%s", volumeName),
		"-l", "com.dnsdock.image=volume.outrigger",
		"--name", volumeName,
		fmt.Sprintf("outrigger/unison:%s", unisonMinorVersion),
	).Run()
	if err != nil {
		cmd.out.Error.Fatalf("Error starting sync container %s: %v", volumeName, err)
	}

	var ip = cmd.WaitForUnisonContainer(volumeName, ctx.Int("initial-sync-timeout"))

	cmd.out.Info.Println("Initializing sync")

	// Remove the log file, the existence of the log file will mean that sync is up and running
	var logFile = fmt.Sprintf("%s.log", volumeName)
	exec.Command("rm", "-f", logFile).Run()

	unisonArgs := []string{
		".",
		fmt.Sprintf("socket://%s:%d/", ip, UNISON_PORT),
		"-auto", "-batch", "-silent", "-contactquietly",
		"-repeat", "watch",
		"-prefer", ".",
		"-logfile", logFile,
		"-ignore", fmt.Sprintf("Name %s", logFile),
	}
	// Append ProjectConfig ignores here
	if config.Sync != nil {
		for _, ignore := range config.Sync.Ignore {
			unisonArgs = append(unisonArgs, "-ignore", ignore)
		}
	}

	cmd.out.Verbose.Printf("Unison Args: %s", strings.Join(unisonArgs[:], " "))
	if err = exec.Command("unison", unisonArgs...).Start(); err != nil {
		cmd.out.Error.Fatalf("Error starting local unison process: %v", err)
	}

	cmd.WaitForSyncInit(logFile, ctx.Int("initial-sync-timeout"), ctx.Int("initial-sync-wait"))

	return nil
}

// Start the unison sync process
func (cmd *ProjectSync) RunStop(ctx *cli.Context) error {
	config := NewProjectConfig()
	volumeName := cmd.GetVolumeName(ctx, config)
	cmd.out.Verbose.Printf("Stopping sync with volume: %s", volumeName)

	cmd.out.Info.Println("Stopping unison container")
	exec.Command("docker", "container", "stop", volumeName).Run()

	return nil
}

// Find the volume name through a variety of fall backs
func (cmd *ProjectSync) GetVolumeName(ctx *cli.Context, config *ProjectConfig) string {
	// 1. Check for argument
	if ctx.Args().Present() {
		return ctx.Args().First()
	}

	// 2. Check for config
	if config.Sync != nil && config.Sync.Volume != "" {
		return config.Sync.Volume
	}

	// 3. Parse compose file looking for an external volume named *-sync
	if composeConfig, err := cmd.LoadComposeFile(); err == nil {
		for name, volume := range composeConfig.Volumes {
			if strings.HasSuffix(name, "-sync") && volume.External {
				return name
			}
		}
	}

	// 4. Use local dir for the volume name
	if dir, err := os.Getwd(); err == nil {
		var _, folder = path.Split(dir)
		return fmt.Sprintf("%s-sync", folder)
	} else {
		cmd.out.Error.Println(err)
	}

	cmd.out.Error.Fatal("Unable to determine a name for the sync volume")
	return ""
}

// Load the proper compose file
func (cmd *ProjectSync) LoadComposeFile() (*ComposeFile, error) {
	yamlFile, err := ioutil.ReadFile("./docker-compose.yml")

	if err == nil {
		var config ComposeFile
		if err := yaml.Unmarshal(yamlFile, &config); err != nil {
			cmd.out.Error.Fatalf("YAML Parsing Error: %s", err)
		}
		return &config, nil
	}

	return nil, err
}

// Wait for the unison container port to allow connections
// Due to the fact that we don't compile with -cgo (so we can build using Docker),
// we need to discover the IP address of the container instead of using the DNS name
// when compiled without -cgo this executable will not use the native mac dns resolution
// which is how we have configured dnsdock to provide names for containers.
func (cmd *ProjectSync) WaitForUnisonContainer(containerName string, timeoutSeconds int) string {
	cmd.out.Info.Println("Waiting for container to start")

	var timeoutLoopSleep = time.Duration(100) * time.Millisecond
	// * 10 here because we loop once every 100 ms and we want to get to seconds
	var timeoutLoops = timeoutSeconds * 10

	output, err := exec.Command("docker", "inspect", "--format", "{{.NetworkSettings.IPAddress}}", containerName).Output()
	if err != nil {
		cmd.out.Error.Fatalf("Error inspecting sync container %s: %v", containerName, err)
	}
	ip := strings.Trim(string(output), "\n")

	cmd.out.Verbose.Printf("Checking for unison network connection on %s %d", ip, UNISON_PORT)
	for i := 1; i <= timeoutLoops; i++ {
		if conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, UNISON_PORT)); err == nil {
			conn.Close()
			return ip
		} else {
			cmd.out.Info.Printf("Error: %v", err)
			time.Sleep(timeoutLoopSleep)
		}
	}
	cmd.out.Error.Fatal("Sync container failed to start!")
	return ""
}

// The local unison process is finished initializing when the log file exists
// and has stopped growing in size
func (cmd *ProjectSync) WaitForSyncInit(logFile string, timeoutSeconds int, syncWaitSeconds int) {
	cmd.out.Info.Print("Waiting for initial sync detection")

	var tempFile = fmt.Sprintf(".%s.tmp", logFile)
	var timeoutLoopSleep = time.Duration(100) * time.Millisecond
	// * 10 here because we loop once every 100 ms and we want to get to seconds
	var timeoutLoops = timeoutSeconds * 10

	// Create a temp file to cause a sync action
	exec.Command("touch", tempFile).Run()

	for i := 1; i <= timeoutLoops; i++ {
		if i%10 == 0 {
			os.Stdout.WriteString(".")
		}
		if statInfo, err := os.Stat(logFile); err == nil {
			os.Stdout.WriteString(" initial sync detected\n")
			// Remove the temp file now that we are running
			exec.Command("rm", "-f", tempFile).Run()

			cmd.out.Info.Print("Waiting for initial sync to finish")
			var statSleep = time.Duration(syncWaitSeconds) * time.Second
			// Initialize at -2 to force at least one loop
			var lastSize = int64(-2)
			for lastSize != statInfo.Size() {
				os.Stdout.WriteString(".")
				time.Sleep(statSleep)
				lastSize = statInfo.Size()
				if statInfo, err = os.Stat(logFile); err != nil {
					cmd.out.Info.Print(err.Error())
					lastSize = -1
				}
			}
			os.Stdout.WriteString(" done\n")
			return
		} else {
			time.Sleep(timeoutLoopSleep)
		}
	}

	// The log file was not created, the sync has not started yet
	exec.Command("rm", "-f", tempFile).Run()
	cmd.out.Error.Fatal("Failed to detect start of initial sync!")
}

// Get the local Unison version to try to load a compatible unison image
// This function discovers a semver like 2.48.4 and return 2.48
func (cmd *ProjectSync) GetUnisonMinorVersion() string {
	output, _ := exec.Command("unison", "-version").Output()
	re := regexp.MustCompile("unison version (\\d+\\.\\d+\\.\\d+)")
	rawVersion := re.FindAllStringSubmatch(string(output), -1)[0][1]
	v := version.Must(version.NewVersion(rawVersion))
	segments := v.Segments()
	return fmt.Sprintf("%d.%d", segments[0], segments[1])
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

const version = "v0.8.9"

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print the version number of Terraformer",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Terraformer " + version)
	},
}
<commit_msg>version 0.8.10<commit_after>package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

const version = "v0.8.10"

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print the version number of Terraformer",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Terraformer " + version)
	},
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// Version
// Printed on subcommand `version` or flag `--version`
const Version = "v0.2.0"

func init() {
	root.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print version",
	Run:   version,
}

func version(cmd *cobra.Command, args []string) {
	fmt.Println(Version)
}
<commit_msg>v0.2.1<commit_after>package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// Version
// Printed on subcommand `version` or flag `--version`
const Version = "v0.2.1"

func init() {
	root.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print version",
	Run:   version,
}

func version(cmd *cobra.Command, args []string) {
	fmt.Println(Version)
}
<|endoftext|>