<commit_before>/*
   DNS-over-HTTPS
   Copyright (C) 2017-2018 Star Brilliant <m13253@hotmail.com>

   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 main

import (
	"context"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/cookiejar"
	"strings"
	"sync"
	"time"

	"github.com/m13253/dns-over-https/json-dns"
	"github.com/miekg/dns"
	"golang.org/x/net/http2"
)

type Client struct {
	conf              *config
	bootstrap         []string
	udpServers        []*dns.Server
	tcpServers        []*dns.Server
	bootstrapResolver *net.Resolver
	cookieJar         *cookiejar.Jar
	httpClientMux     *sync.RWMutex
	httpTransport     *http.Transport
	httpClient        *http.Client
}

type DNSRequest struct {
	response          *http.Response
	reply             *dns.Msg
	udpSize           uint16
	ednsClientAddress net.IP
	ednsClientNetmask uint8
	err               error
}

func NewClient(conf *config) (c *Client, err error) {
	c = &Client{
		conf: conf,
	}

	udpHandler := dns.HandlerFunc(c.udpHandlerFunc)
	tcpHandler := dns.HandlerFunc(c.tcpHandlerFunc)
	for _, addr := range conf.Listen {
		c.udpServers = append(c.udpServers, &dns.Server{
			Addr:    addr,
			Net:     "udp",
			Handler: udpHandler,
			UDPSize: dns.DefaultMsgSize,
		})
		c.tcpServers = append(c.tcpServers, &dns.Server{
			Addr:    addr,
			Net:     "tcp",
			Handler: tcpHandler,
		})
	}
	c.bootstrapResolver = net.DefaultResolver
	if len(conf.Bootstrap) != 0 {
		c.bootstrap = make([]string, len(conf.Bootstrap))
		for i, bootstrap := range conf.Bootstrap {
			bootstrapAddr, err := net.ResolveUDPAddr("udp", bootstrap)
			if err != nil {
				bootstrapAddr, err = net.ResolveUDPAddr("udp", "["+bootstrap+"]:53")
			}
			if err != nil {
				return nil, err
			}
			c.bootstrap[i] = bootstrapAddr.String()
		}
		c.bootstrapResolver = &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				var d net.Dialer
				numServers := len(c.bootstrap)
				bootstrap := c.bootstrap[rand.Intn(numServers)]
				conn, err := d.DialContext(ctx, network, bootstrap)
				return conn, err
			},
		}
	}
	// Most CDNs require Cookie support to prevent DDoS attack.
	// Disabling Cookie does not effectively prevent tracking,
	// so I will leave it on to make anti-DDoS services happy.
	if !c.conf.NoCookies {
		c.cookieJar, err = cookiejar.New(nil)
		if err != nil {
			return nil, err
		}
	}
	c.httpClientMux = new(sync.RWMutex)
	err = c.newHTTPClient()
	if err != nil {
		return nil, err
	}
	return c, nil
}

func (c *Client) newHTTPClient() error {
	c.httpClientMux.Lock()
	defer c.httpClientMux.Unlock()
	if c.httpTransport != nil {
		c.httpTransport.CloseIdleConnections()
	}
	c.httpTransport = &http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   time.Duration(c.conf.Timeout) * time.Second,
			KeepAlive: 30 * time.Second,
			DualStack: true,
			Resolver:  c.bootstrapResolver,
		}).DialContext,
		ExpectContinueTimeout: 1 * time.Second,
		IdleConnTimeout:       90 * time.Second,
		MaxIdleConns:          100,
		MaxIdleConnsPerHost:   10,
		Proxy:                 http.ProxyFromEnvironment,
		ResponseHeaderTimeout: time.Duration(c.conf.Timeout) * time.Second,
		TLSHandshakeTimeout:   time.Duration(c.conf.Timeout) * time.Second,
	}
	err := http2.ConfigureTransport(c.httpTransport)
	if err != nil {
		return err
	}
	c.httpClient = &http.Client{
		Transport: c.httpTransport,
		Jar:       c.cookieJar,
	}
	return nil
}

func (c *Client) Start() error {
	results := make(chan error, len(c.udpServers)+len(c.tcpServers))
	for _, srv := range append(c.udpServers, c.tcpServers...) {
		go func(srv *dns.Server) {
			err := srv.ListenAndServe()
			if err != nil {
				log.Println(err)
			}
			results <- err
		}(srv)
	}

	for i := 0; i < cap(results); i++ {
		err := <-results
		if err != nil {
			return err
		}
	}
	close(results)
	return nil
}

func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
	if r.Response == true {
		log.Println("Received a response packet")
		return
	}

	requestType := ""
	if len(c.conf.UpstreamIETF) == 0 {
		requestType = "application/dns-json"
	} else if len(c.conf.UpstreamGoogle) == 0 {
		requestType = "application/dns-message"
	} else {
		numServers := len(c.conf.UpstreamGoogle) + len(c.conf.UpstreamIETF)
		random := rand.Intn(numServers)
		if random < len(c.conf.UpstreamGoogle) {
			requestType = "application/dns-json"
		} else {
			requestType = "application/dns-message"
		}
	}

	var req *DNSRequest
	if requestType == "application/dns-json" {
		req = c.generateRequestGoogle(w, r, isTCP)
	} else if requestType == "application/dns-message" {
		req = c.generateRequestIETF(w, r, isTCP)
	} else {
		panic("Unknown request Content-Type")
	}

	if req.err != nil {
		return
	}

	contentType := ""
	candidateType := strings.SplitN(req.response.Header.Get("Content-Type"), ";", 2)[0]
	if candidateType == "application/json" {
		contentType = "application/json"
	} else if candidateType == "application/dns-message" {
		contentType = "application/dns-message"
	} else if candidateType == "application/dns-udpwireformat" {
		contentType = "application/dns-message"
	} else {
		if requestType == "application/dns-json" {
			contentType = "application/json"
		} else if requestType == "application/dns-message" {
			contentType = "application/dns-message"
		}
	}

	if contentType == "application/json" {
		c.parseResponseGoogle(w, r, isTCP, req)
	} else if contentType == "application/dns-message" {
		c.parseResponseIETF(w, r, isTCP, req)
	} else {
		panic("Unknown response Content-Type")
	}
}

func (c *Client) udpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
	c.handlerFunc(w, r, false)
}

func (c *Client) tcpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
	c.handlerFunc(w, r, true)
}

var (
	ipv4Mask24 = net.IPMask{255, 255, 255, 0}
	ipv6Mask48 = net.CIDRMask(48, 128)
)

func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) {
	ednsClientNetmask = 255
	if c.conf.NoECS {
		return net.IPv4(0, 0, 0, 0), 0
	}
	if opt := r.IsEdns0(); opt != nil {
		for _, option := range opt.Option {
			if option.Option() == dns.EDNS0SUBNET {
				edns0Subnet := option.(*dns.EDNS0_SUBNET)
				ednsClientAddress = edns0Subnet.Address
				ednsClientNetmask = edns0Subnet.SourceNetmask
				return
			}
		}
	}
	remoteAddr, err := net.ResolveUDPAddr("udp", w.RemoteAddr().String())
	if err != nil {
		return
	}
	if ip := remoteAddr.IP; jsonDNS.IsGlobalIP(ip) {
		if ipv4 := ip.To4(); ipv4 != nil {
			ednsClientAddress = ipv4.Mask(ipv4Mask24)
			ednsClientNetmask = 24
		} else {
			ednsClientAddress = ip.Mask(ipv6Mask48)
			ednsClientNetmask = 48
		}
	}
	return
}
<commit_msg>Limit the frequency of creating HTTP client<commit_after>/*
   DNS-over-HTTPS
   Copyright (C) 2017-2018 Star Brilliant <m13253@hotmail.com>

   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 main

import (
	"context"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/cookiejar"
	"strings"
	"sync"
	"time"

	"github.com/m13253/dns-over-https/json-dns"
	"github.com/miekg/dns"
	"golang.org/x/net/http2"
)

type Client struct {
	conf                 *config
	bootstrap            []string
	udpServers           []*dns.Server
	tcpServers           []*dns.Server
	bootstrapResolver    *net.Resolver
	cookieJar            *cookiejar.Jar
	httpClientMux        *sync.RWMutex
	httpTransport        *http.Transport
	httpClient           *http.Client
	httpClientLastCreate time.Time
}

type DNSRequest struct {
	response          *http.Response
	reply             *dns.Msg
	udpSize           uint16
	ednsClientAddress net.IP
	ednsClientNetmask uint8
	err               error
}

func NewClient(conf *config) (c *Client, err error) {
	c = &Client{
		conf: conf,
	}

	udpHandler := dns.HandlerFunc(c.udpHandlerFunc)
	tcpHandler := dns.HandlerFunc(c.tcpHandlerFunc)
	for _, addr := range conf.Listen {
		c.udpServers = append(c.udpServers, &dns.Server{
			Addr:    addr,
			Net:     "udp",
			Handler: udpHandler,
			UDPSize: dns.DefaultMsgSize,
		})
		c.tcpServers = append(c.tcpServers, &dns.Server{
			Addr:    addr,
			Net:     "tcp",
			Handler: tcpHandler,
		})
	}
	c.bootstrapResolver = net.DefaultResolver
	if len(conf.Bootstrap) != 0 {
		c.bootstrap = make([]string, len(conf.Bootstrap))
		for i, bootstrap := range conf.Bootstrap {
			bootstrapAddr, err := net.ResolveUDPAddr("udp", bootstrap)
			if err != nil {
				bootstrapAddr, err = net.ResolveUDPAddr("udp", "["+bootstrap+"]:53")
			}
			if err != nil {
				return nil, err
			}
			c.bootstrap[i] = bootstrapAddr.String()
		}
		c.bootstrapResolver = &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				var d net.Dialer
				numServers := len(c.bootstrap)
				bootstrap := c.bootstrap[rand.Intn(numServers)]
				conn, err := d.DialContext(ctx, network, bootstrap)
				return conn, err
			},
		}
	}
	// Most CDNs require Cookie support to prevent DDoS attack.
	// Disabling Cookie does not effectively prevent tracking,
	// so I will leave it on to make anti-DDoS services happy.
	if !c.conf.NoCookies {
		c.cookieJar, err = cookiejar.New(nil)
		if err != nil {
			return nil, err
		}
	}
	c.httpClientMux = new(sync.RWMutex)
	err = c.newHTTPClient()
	if err != nil {
		return nil, err
	}
	return c, nil
}

func (c *Client) newHTTPClient() error {
	c.httpClientMux.Lock()
	defer c.httpClientMux.Unlock()
	if !c.httpClientLastCreate.IsZero() && time.Now().Sub(c.httpClientLastCreate) < time.Duration(c.conf.Timeout)*time.Second {
		return nil
	}
	if c.httpTransport != nil {
		c.httpTransport.CloseIdleConnections()
	}
	c.httpTransport = &http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   time.Duration(c.conf.Timeout) * time.Second,
			KeepAlive: 30 * time.Second,
			DualStack: true,
			Resolver:  c.bootstrapResolver,
		}).DialContext,
		ExpectContinueTimeout: 1 * time.Second,
		IdleConnTimeout:       90 * time.Second,
		MaxIdleConns:          100,
		MaxIdleConnsPerHost:   10,
		Proxy:                 http.ProxyFromEnvironment,
		ResponseHeaderTimeout: time.Duration(c.conf.Timeout) * time.Second,
		TLSHandshakeTimeout:   time.Duration(c.conf.Timeout) * time.Second,
	}
	err := http2.ConfigureTransport(c.httpTransport)
	if err != nil {
		return err
	}
	c.httpClient = &http.Client{
		Transport: c.httpTransport,
		Jar:       c.cookieJar,
	}
	c.httpClientLastCreate = time.Now()
	return nil
}

func (c *Client) Start() error {
	results := make(chan error, len(c.udpServers)+len(c.tcpServers))
	for _, srv := range append(c.udpServers, c.tcpServers...) {
		go func(srv *dns.Server) {
			err := srv.ListenAndServe()
			if err != nil {
				log.Println(err)
			}
			results <- err
		}(srv)
	}

	for i := 0; i < cap(results); i++ {
		err := <-results
		if err != nil {
			return err
		}
	}
	close(results)
	return nil
}

func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
	if r.Response == true {
		log.Println("Received a response packet")
		return
	}

	requestType := ""
	if len(c.conf.UpstreamIETF) == 0 {
		requestType = "application/dns-json"
	} else if len(c.conf.UpstreamGoogle) == 0 {
		requestType = "application/dns-message"
	} else {
		numServers := len(c.conf.UpstreamGoogle) + len(c.conf.UpstreamIETF)
		random := rand.Intn(numServers)
		if random < len(c.conf.UpstreamGoogle) {
			requestType = "application/dns-json"
		} else {
			requestType = "application/dns-message"
		}
	}

	var req *DNSRequest
	if requestType == "application/dns-json" {
		req = c.generateRequestGoogle(w, r, isTCP)
	} else if requestType == "application/dns-message" {
		req = c.generateRequestIETF(w, r, isTCP)
	} else {
		panic("Unknown request Content-Type")
	}

	if req.err != nil {
		return
	}

	contentType := ""
	candidateType := strings.SplitN(req.response.Header.Get("Content-Type"), ";", 2)[0]
	if candidateType == "application/json" {
		contentType = "application/json"
	} else if candidateType == "application/dns-message" {
		contentType = "application/dns-message"
	} else if candidateType == "application/dns-udpwireformat" {
		contentType = "application/dns-message"
	} else {
		if requestType == "application/dns-json" {
			contentType = "application/json"
		} else if requestType == "application/dns-message" {
			contentType = "application/dns-message"
		}
	}

	if contentType == "application/json" {
		c.parseResponseGoogle(w, r, isTCP, req)
	} else if contentType == "application/dns-message" {
		c.parseResponseIETF(w, r, isTCP, req)
	} else {
		panic("Unknown response Content-Type")
	}
}

func (c *Client) udpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
	c.handlerFunc(w, r, false)
}

func (c *Client) tcpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
	c.handlerFunc(w, r, true)
}

var (
	ipv4Mask24 = net.IPMask{255, 255, 255, 0}
	ipv6Mask48 = net.CIDRMask(48, 128)
)

func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) {
	ednsClientNetmask = 255
	if c.conf.NoECS {
		return net.IPv4(0, 0, 0, 0), 0
	}
	if opt := r.IsEdns0(); opt != nil {
		for _, option := range opt.Option {
			if option.Option() == dns.EDNS0SUBNET {
				edns0Subnet := option.(*dns.EDNS0_SUBNET)
				ednsClientAddress = edns0Subnet.Address
				ednsClientNetmask = edns0Subnet.SourceNetmask
				return
			}
		}
	}
	remoteAddr, err := net.ResolveUDPAddr("udp", w.RemoteAddr().String())
	if err != nil {
		return
	}
	if ip := remoteAddr.IP; jsonDNS.IsGlobalIP(ip) {
		if ipv4 := ip.To4(); ipv4 != nil {
			ednsClientAddress = ipv4.Mask(ipv4Mask24)
			ednsClientNetmask = 24
		} else {
			ednsClientAddress = ip.Mask(ipv6Mask48)
			ednsClientNetmask = 48
		}
	}
	return
}
<|endoftext|>


<commit_before>package middleware

import (
	"bufio"
	"errors"
	"github.com/DeedleFake/Go-PhysicsFS/physfs"
	"github.com/carbonsrv/carbon/modules/glue"
	"github.com/carbonsrv/carbon/modules/helpers"
	"github.com/carbonsrv/carbon/modules/scheduler"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/pmylund/go-cache"
	"github.com/vifino/golua/lua"
	"github.com/vifino/luar"
	"net/http"
	"time"
)

// Cache
var kvstore *cache.Cache
var cbc *cache.Cache
var cfe *cache.Cache
var LDumper *lua.State

func cacheDump(file string) (string, error, bool) {
	data_tmp, found := cbc.Get(file)
	if found == false {
		data, err := fileRead(file)
		if err != nil {
			return "", err, false
		}
		res, err := bcdump(data)
		if err != nil {
			return "", err, true
		}
		cbc.Set(file, res, cache.DefaultExpiration)
		return res, nil, false
	} else {
		//debug("Using Bytecode-cache for " + file)
		return data_tmp.(string), nil, false
	}
}
func bcdump(data string) (string, error) {
	if LDumper.LoadString(data) != 0 {
		return "", errors.New(LDumper.ToString(-1))
	}
	defer LDumper.Pop(1)
	return LDumper.FDump(), nil
}

// FS
var filesystem http.FileSystem

func fileExists(file string) bool {
	data_tmp, found := cfe.Get(file)
	if found == false {
		exists := physfs.Exists(file)
		cfe.Set(file, exists, cache.DefaultExpiration)
		return exists
	} else {
		return data_tmp.(bool)
	}
}

func fileRead(file string) (string, error) {
	f, err := filesystem.Open(file)
	defer f.Close()
	if err != nil {
		return "", err
	}
	fi, err := f.Stat()
	if err != nil {
		return "", err
	}
	r := bufio.NewReader(f)
	buf := make([]byte, fi.Size())
	_, err = r.Read(buf)
	if err != nil {
		return "", err
	}
	return string(buf), err
}

// Preloader/Starter
var jobs int
var Preloaded chan *lua.State

func Preloader() {
	Preloaded = make(chan *lua.State, jobs)
	for {
		//fmt.Println("preloading")
		L := luar.Init()
		Bind(L)
		err := L.DoString(glue.MainGlue())
		if err != nil {
			panic(err)
		}
		err = L.DoString(glue.RouteGlue())
		if err != nil {
			panic(err)
		}
		Preloaded <- L
	}
}
func GetInstance() *lua.State {
	//fmt.Println("grabbing instance")
	L := <-Preloaded
	//fmt.Println("Done")
	return L
}

// Init
func Init(j int, cfe_new *cache.Cache, kvstore_new *cache.Cache) {
	cfe = cfe_new
	kvstore = kvstore_new
	jobs = j
	filesystem = physfs.FileSystem()
	cbc = cache.New(5*time.Minute, 30*time.Second) // Initialize cache with 5 minute lifetime and purge every 30 seconds
	LDumper = luar.Init()
}

// PHP-like lua scripts
func Lua() func(*gin.Context) {
	//LDumper := luar.Init()
	return func(context *gin.Context) {
		file := context.Request.URL.Path
		if fileExists(file) {
			//fmt.Println("start")
			L := GetInstance()
			//fmt.Println("after start")
			defer scheduler.Add(func() {
				L.Close()
			})
			//fmt.Println("after after start")
			BindContext(L, context)
			//fmt.Println("before cache")
			code, err, lerr := cacheDump(file)
			//fmt.Println("after cache")
			if err != nil {
				if lerr == false {
					context.Next()
					return
				} else {
					helpers.HTMLString(context, http.StatusInternalServerError, `
					<title>Syntax Error in `+context.Request.URL.Path+`</title>

Syntax Error in file `+context.Request.URL.Path+`:

						`+string(err.Error())+`

					`)
					context.Abort()
					return
				}
			}
			//fmt.Println("before loadbuffer")
			L.LoadBuffer(code, len(code), file) // This shouldn't error, was checked earlier.
			if L.Pcall(0, 0, 0) != 0 {          // != 0 means error in execution
				helpers.HTMLString(context, http.StatusInternalServerError, `
				<title>Runtime Error in `+context.Request.URL.Path+`</title>

Runtime Error in file `+context.Request.URL.Path+`:

					`+L.ToString(-1)+`

				`)
				context.Abort()
				return
			}
			/*L.DoString("return CONTENT_TO_RETURN")
			v := luar.CopyTableToMap(L, nil, -1)
			m := v.(map[string]interface{})
			i := int(m["code"].(float64))
			if err != nil {
				i = http.StatusOK
			}*/
			//helpers.HTMLString(context, i, m["content"].(string))
		} else {
			context.Next()
		}
	}
}

// Route creation by lua
func DLR_NS(bcode string, dobind bool, vals map[string]interface{}) (func(*gin.Context), error) {
	/*code, err := bcdump(code)
	if err != nil {
		return func(*gin.Context) {}, err
	}*/
	return func(context *gin.Context) {
		L := GetInstance()
		if dobind {
			luar.Register(L, "", vals)
		}
		defer scheduler.Add(func() {
			L.Close()
		})
		BindContext(L, context)
		//fmt.Println("before loadbuffer")
		/*if L.LoadBuffer(bcode, len(bcode), "route") != 0 {
			helpers.HTMLString(context, http.StatusInternalServerError, `
			<title>Syntax Error in `+context.Request.URL.Path+`</title>

Syntax Error in Lua Route on `+context.Request.URL.Path+`:

				`+L.ToString(-1)+`

			`)
			context.Abort()
			return
		}*/
		L.LoadBuffer(bcode, len(bcode), "route")
		if L.Pcall(0, 0, 0) != 0 { // != 0 means error in execution
			helpers.HTMLString(context, http.StatusInternalServerError, `
			<title>Runtime Error on `+context.Request.URL.Path+`</title>

Runtime Error in Lua Route on `+context.Request.URL.Path+`:

				`+L.ToString(-1)+`

			`)
			context.Abort()
			return
		}
	}, nil
}
func DLR_RUS(bcode string, instances int, dobind bool, vals map[string]interface{}) (func(*gin.Context), error) { // Same as above, but reuses states. Much faster. Higher memory use though, because more states.
	insts := instances
	if instances < 0 {
		insts = 2
		if jobs/2 > 1 {
			insts = jobs
		}
	}
	schan := make(chan *lua.State, insts)
	for i := 0; i < jobs/2; i++ {
		L := GetInstance()
		if dobind {
			luar.Register(L, "", vals)
		}
		if L.LoadBuffer(bcode, len(bcode), "route") != 0 {
			return func(context *gin.Context) {}, errors.New(L.ToString(-1))
		}
		L.PushValue(-1)
		schan <- L
	}
	return func(context *gin.Context) {
		L := <-schan
		BindContext(L, context)
		if L.Pcall(0, 0, 0) != 0 { // != 0 means error in execution
			helpers.HTMLString(context, http.StatusInternalServerError, `
			<title>Runtime Error on `+context.Request.URL.Path+`</title>

Runtime Error in Lua Route on `+context.Request.URL.Path+`:

				`+L.ToString(-1)+`

			`)
			context.Abort()
			return
		}
		L.PushValue(-1)
		schan <- L
	}, nil
}

func DLRWS_RUS(bcode string, instances int, dobind bool, vals map[string]interface{}) (func(*gin.Context), error) { // Same as above, but for websockets. Not working because?!
	insts := instances
	if instances < 0 {
		insts = 2
		if jobs/2 > 1 {
			insts = jobs
		}
	}
	schan := make(chan *lua.State, insts)
	for i := 0; i < jobs/2; i++ {
		L := GetInstance()
		if dobind {
			luar.Register(L, "", vals)
		}
		if L.LoadBuffer(bcode, len(bcode), "route") != 0 {
			return func(context *gin.Context) {}, errors.New(L.ToString(-1))
		}
		L.PushValue(-1)
		schan <- L
	}
	var upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}
	return func(context *gin.Context) {
		wshandler(context.Writer, context.Request)
	}, nil
}

func wshandler(w http.ResponseWriter, r *http.Request) {
	L := <-schan
	BindContext(L, context)
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return // silent error.
	}
	luar.Register(L, "ws", luar.Map{
		"BinaryMessage": websocket.BinaryMessage,
		"TextMessage":   websocket.TextMessage,
		//"read":          conn.ReadMessage,
		//"send":          conn.SendMessage,
		"read": (func() (int, string, error) {
			messageType, p, err := conn.ReadMessage()
			if err != nil {
				return -1, "", err
			}
			return messageType, string(p), nil
		}),
		"send": (func(t int, cnt string) error {
			return conn.WriteMessage(t, []byte(cnt))
		}),
	})
	if L.Pcall(0, 0, 0) != 0 { // != 0 means error in execution
		fmt.Println("Websocket error: "+L.ToString(-1))
		context.Abort()
		return
	}
	L.PushValue(-1)
	schan <- L
}
<commit_msg>Blargh.<commit_after>package middleware

import (
	"bufio"
	"errors"
	"fmt"
	"github.com/DeedleFake/Go-PhysicsFS/physfs"
	"github.com/carbonsrv/carbon/modules/glue"
	"github.com/carbonsrv/carbon/modules/helpers"
	"github.com/carbonsrv/carbon/modules/scheduler"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/pmylund/go-cache"
	"github.com/vifino/golua/lua"
	"github.com/vifino/luar"
	"net/http"
	"time"
)

// Cache
var kvstore *cache.Cache
var cbc *cache.Cache
var cfe *cache.Cache
var LDumper *lua.State

func cacheDump(file string) (string, error, bool) {
	data_tmp, found := cbc.Get(file)
	if found == false {
		data, err := fileRead(file)
		if err != nil {
			return "", err, false
		}
		res, err := bcdump(data)
		if err != nil {
			return "", err, true
		}
		cbc.Set(file, res, cache.DefaultExpiration)
		return res, nil, false
	} else {
		//debug("Using Bytecode-cache for " + file)
		return data_tmp.(string), nil, false
	}
}
func bcdump(data string) (string, error) {
	if LDumper.LoadString(data) != 0 {
		return "", errors.New(LDumper.ToString(-1))
	}
	defer LDumper.Pop(1)
	return LDumper.FDump(), nil
}

// FS
var filesystem http.FileSystem

func fileExists(file string) bool {
	data_tmp, found := cfe.Get(file)
	if found == false {
		exists := physfs.Exists(file)
		cfe.Set(file, exists, cache.DefaultExpiration)
		return exists
	} else {
		return data_tmp.(bool)
	}
}

func fileRead(file string) (string, error) {
	f, err := filesystem.Open(file)
	defer f.Close()
	if err != nil {
		return "", err
	}
	fi, err := f.Stat()
	if err != nil {
		return "", err
	}
	r := bufio.NewReader(f)
	buf := make([]byte, fi.Size())
	_, err = r.Read(buf)
	if err != nil {
		return "", err
	}
	return string(buf), err
}

// Preloader/Starter
var jobs int
var Preloaded chan *lua.State

func Preloader() {
	Preloaded = make(chan *lua.State, jobs)
	for {
		//fmt.Println("preloading")
		L := luar.Init()
		Bind(L)
		err := L.DoString(glue.MainGlue())
		if err != nil {
			panic(err)
		}
		err = L.DoString(glue.RouteGlue())
		if err != nil {
			panic(err)
		}
		Preloaded <- L
	}
}
func GetInstance() *lua.State {
	//fmt.Println("grabbing instance")
	L := <-Preloaded
	//fmt.Println("Done")
	return L
}

// Init
func Init(j int, cfe_new *cache.Cache, kvstore_new *cache.Cache) {
	cfe = cfe_new
	kvstore = kvstore_new
	jobs = j
	filesystem = physfs.FileSystem()
	cbc = cache.New(5*time.Minute, 30*time.Second) // Initialize cache with 5 minute lifetime and purge every 30 seconds
	LDumper = luar.Init()
}

// PHP-like lua scripts
func Lua() func(*gin.Context) {
	//LDumper := luar.Init()
	return func(context *gin.Context) {
		file := context.Request.URL.Path
		if fileExists(file) {
			//fmt.Println("start")
			L := GetInstance()
			//fmt.Println("after start")
			defer scheduler.Add(func() {
				L.Close()
			})
			//fmt.Println("after after start")
			BindContext(L, context)
			//fmt.Println("before cache")
			code, err, lerr := cacheDump(file)
			//fmt.Println("after cache")
			if err != nil {
				if lerr == false {
					context.Next()
					return
				} else {
					helpers.HTMLString(context, http.StatusInternalServerError, `
					<title>Syntax Error in `+context.Request.URL.Path+`</title>

Syntax Error in file `+context.Request.URL.Path+`:

						`+string(err.Error())+`

					`)
					context.Abort()
					return
				}
			}
			//fmt.Println("before loadbuffer")
			L.LoadBuffer(code, len(code), file) // This shouldn't error, was checked earlier.
			if L.Pcall(0, 0, 0) != 0 {          // != 0 means error in execution
				helpers.HTMLString(context, http.StatusInternalServerError, `
				<title>Runtime Error in `+context.Request.URL.Path+`</title>

Runtime Error in file `+context.Request.URL.Path+`:

					`+L.ToString(-1)+`

				`)
				context.Abort()
				return
			}
			/*L.DoString("return CONTENT_TO_RETURN")
			v := luar.CopyTableToMap(L, nil, -1)
			m := v.(map[string]interface{})
			i := int(m["code"].(float64))
			if err != nil {
				i = http.StatusOK
			}*/
			//helpers.HTMLString(context, i, m["content"].(string))
		} else {
			context.Next()
		}
	}
}

// Route creation by lua
func DLR_NS(bcode string, dobind bool, vals map[string]interface{}) (func(*gin.Context), error) {
	/*code, err := bcdump(code)
	if err != nil {
		return func(*gin.Context) {}, err
	}*/
	return func(context *gin.Context) {
		L := GetInstance()
		if dobind {
			luar.Register(L, "", vals)
		}
		defer scheduler.Add(func() {
			L.Close()
		})
		BindContext(L, context)
		//fmt.Println("before loadbuffer")
		/*if L.LoadBuffer(bcode, len(bcode), "route") != 0 {
			helpers.HTMLString(context, http.StatusInternalServerError, `
			<title>Syntax Error in `+context.Request.URL.Path+`</title>

Syntax Error in Lua Route on `+context.Request.URL.Path+`:

				`+L.ToString(-1)+`

			`)
			context.Abort()
			return
		}*/
		L.LoadBuffer(bcode, len(bcode), "route")
		if L.Pcall(0, 0, 0) != 0 { // != 0 means error in execution
			helpers.HTMLString(context, http.StatusInternalServerError, `
			<title>Runtime Error on `+context.Request.URL.Path+`</title>

Runtime Error in Lua Route on `+context.Request.URL.Path+`:

				`+L.ToString(-1)+`

			`)
			context.Abort()
			return
		}
	}, nil
}
func DLR_RUS(bcode string, instances int, dobind bool, vals map[string]interface{}) (func(*gin.Context), error) { // Same as above, but reuses states. Much faster. Higher memory use though, because more states.
	insts := instances
	if instances < 0 {
		insts = 2
		if jobs/2 > 1 {
			insts = jobs
		}
	}
	schan := make(chan *lua.State, insts)
	for i := 0; i < jobs/2; i++ {
		L := GetInstance()
		if dobind {
			luar.Register(L, "", vals)
		}
		if L.LoadBuffer(bcode, len(bcode), "route") != 0 {
			return func(context *gin.Context) {}, errors.New(L.ToString(-1))
		}
		L.PushValue(-1)
		schan <- L
	}
	return func(context *gin.Context) {
		L := <-schan
		BindContext(L, context)
		if L.Pcall(0, 0, 0) != 0 { // != 0 means error in execution
			helpers.HTMLString(context, http.StatusInternalServerError, `
			<title>Runtime Error on `+context.Request.URL.Path+`</title>

Runtime Error in Lua Route on `+context.Request.URL.Path+`:

				`+L.ToString(-1)+`

			`)
			context.Abort()
			return
		}
		L.PushValue(-1)
		schan <- L
	}, nil
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func DLRWS_RUS(bcode string, instances int, dobind bool, vals map[string]interface{}) (func(*gin.Context), error) { // Same as above, but for websockets. Not working because?!
	insts := instances
	if instances < 0 {
		insts = 2
		if jobs/2 > 1 {
			insts = jobs
		}
	}
	schan := make(chan *lua.State, insts)
	for i := 0; i < jobs/2; i++ {
		L := GetInstance()
		if dobind {
			luar.Register(L, "", vals)
		}
		if L.LoadBuffer(bcode, len(bcode), "route") != 0 {
			return func(context *gin.Context) {}, errors.New(L.ToString(-1))
		}
		L.PushValue(-1)
		schan <- L
	}
	return func(context *gin.Context) {
		L := <-schan
		BindContext(L, context)
		r := wshandler(context.Writer, context.Request, L)
		schan <- L
		if r {
			context.Abort()
		}
	}, nil
}

func wshandler(w http.ResponseWriter, r *http.Request, L *lua.State) bool {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return // silent error.
	}
	luar.Register(L, "ws", luar.Map{
		"BinaryMessage": websocket.BinaryMessage,
		"TextMessage":   websocket.TextMessage,
		//"read":          conn.ReadMessage,
		//"send":          conn.SendMessage,
		"read": (func() (int, string, error) {
			messageType, p, err := conn.ReadMessage()
			if err != nil {
				return -1, "", err
			}
			return messageType, string(p), nil
		}),
		"send": (func(t int, cnt string) error {
			return conn.WriteMessage(t, []byte(cnt))
		}),
	})
	if L.Pcall(0, 0, 0) != 0 { // != 0 means error in execution
		fmt.Println("Websocket error: " + L.ToString(-1))
		return false
	}
	L.PushValue(-1)
	return true
}
<|endoftext|>


<commit_before>package resources

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"

	"github.com/Machiel/slugify"
	"github.com/generationtux/brizo/database"
	"github.com/generationtux/brizo/kube"
	"github.com/jinzhu/gorm"
	"github.com/pborman/uuid"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
	metav1 "k8s.io/client-go/pkg/apis/meta/v1"
)

// Volume as defined by Brizo.
type Volume struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

// Container individual container for a version
type Container struct {
	Name         string                 `json:"name"`
	Image        string                 `json:"image"`
	AlwaysPull   bool                   `json:"alwaysPull"`
	Args         []string               `json:"args"`
	VolumeMounts []ContainerVolumeMount `json:"volumeMounts"`
	Ports        []ContainerPort        `json:"ports"`
}

// ContainerPort exposed container port
type ContainerPort struct {
	Protocol string `json:"protocol"`
	Port     int    `json:"port"`
}

// ContainerVolumeMount mount configuration for an available volume
type ContainerVolumeMount struct {
	Name string `json:"name"`
	Path string `json:"path"`
}

// Version as defined by Brizo.
type Version struct {
	database.Model
	UUID            string       `gorm:"not null;unique_index:uix_versions_name_application_uuid" sql:"type:varchar(36)" json:"uuid"`
	Name            string       `gorm:"not null" json:"name"`
	Slug            string       `gorm:"not null" json:"slug"`
	Replicas        int          `gorm:"not null" sql:"DEFAULT:'0'" json:"replicas"`
	ApplicationID   uint64       `gorm:"not null" json:"appliction_id,string"`
	ApplicationUUID string       `gorm:"not null;unique_index:uix_versions_name_application_uuid" json:"application_uuid"`
	Application     Application  `json:"application"`
	EnvironmentID   uint         `gorm:"not null" json:"environment_id,string"`
	EnvironmentUUID string       `gorm:"not null" json:"environment_uuid"`
	Environment     *Environment `json:"environment"`
	Volumes         []Volume     `gorm:"-" json:"volumes"`
	Containers      []Container  `gorm:"-" json:"containers"`
	Spec            string       `gorm:"type:json" json:"-"`
}

// Spec k8s spec information
type Spec struct {
	Name              string
	Labels            []string
	Namespace         string
	creationTimestamp string
}

// BeforeCreate is a hook that runs before inserting a new record into the
// database
func (version *Version) BeforeCreate() (err error) {
	if version.UUID == "" {
		version.UUID = uuid.New()
	}

	return
}

// AllVersions will return all of the Versions
func AllVersions(db *gorm.DB) ([]Version, error) {
	var versions []Version
	result := db.Find(&versions)

	return versions, result.Error
}

// DeployVersion will deploy an existing version
func DeployVersion(client kube.APIInterface, version *Version) (bool, error) {
	deployment := versionDeploymentDefinition(version)
	err := client.CreateOrUpdateDeployment(deployment)
	if err != nil {
		return false, err
	}

	return true, nil
}

// CreateVersion will create and deploy a new version
func CreateVersion(db *gorm.DB, client kube.APIInterface, version *Version) (bool, error) {
	deployment := versionDeploymentDefinition(version)

	err := client.CreateOrUpdateDeployment(deployment)
	if err != nil {
		return false, err
	}

	spec, err := json.Marshal(deployment)
	if err != nil {
		client.DeleteDeployment(deployment)
		return false, err
	}

	version.Spec = string(spec)
	persist := db.Create(&version)

	// update environment service
	ports := gatherContainerPorts(version.Containers)
	UpdateEnvironmentService(db, client, version.Environment, ports)

	return persist.RowsAffected == 1, persist.Error
}

func gatherContainerPorts(containers []Container) []ContainerPort {
	ports := make([]ContainerPort, 0)
	for _, container := range containers {
		ports = append(ports, container.Ports...)
	}
	return ports
}

func convertVolume(volume Volume) v1.Volume {
	var k8sVol v1.Volume

	switch volume.Type {
	case "temp":
		k8sVol = v1.Volume{
			Name: volume.Name,
			VolumeSource: v1.VolumeSource{
				EmptyDir: &v1.EmptyDirVolumeSource{
					Medium: v1.StorageMediumDefault,
				},
			},
		}
	default:
		// @TODO handle unsupported volume error
		panic(volume.Type + " is not a supported volume type")
	}

	return k8sVol
}

func createContainerSpec(container Container, environmentUUID string) v1.Container {
	policy := v1.PullAlways
	if !container.AlwaysPull {
		policy = v1.PullIfNotPresent
	}

	k8sPorts := make([]v1.ContainerPort, len(container.Ports))
	for i, port := range container.Ports {
		protocol := v1.ProtocolTCP
		if port.Protocol == "UDP" {
			protocol = v1.ProtocolUDP
		}
		k8sPorts[i] = v1.ContainerPort{
			Protocol:      protocol,
			ContainerPort: int32(port.Port),
		}
	}

	k8sVolumeMounts := make([]v1.VolumeMount, len(container.VolumeMounts))
	for i, mount := range container.VolumeMounts {
		k8sVolumeMounts[i] = v1.VolumeMount{
			Name:      mount.Name,
			MountPath: mount.Path,
		}
	}

	db, err := database.Connect()
	defer db.Close()
	if err != nil {
		log.Printf("Database error: '%s'\n", err)
	}
	environmentVars, err := GetEnvironmentConfig(db, environmentUUID)
	if err != nil {
		log.Printf("Error retrieving environment configs: '%s'\n", err)
	}

	var k8sEnvVars []v1.EnvVar
	for _, environmentVar := range *environmentVars {
		k8sEnvVars = append(k8sEnvVars, v1.EnvVar{
			Name:  environmentVar.Name,
			Value: environmentVar.Value,
		})
	}

	return v1.Container{
		Name:            container.Name,
		Image:           container.Image,
		ImagePullPolicy: policy,
		Args:            container.Args,
		Ports:           k8sPorts,
		VolumeMounts:    k8sVolumeMounts,
		Env:             k8sEnvVars,
	}
}

// versionDeploymentDefinition builds a deployment spec for the provided version
func versionDeploymentDefinition(version *Version) *v1beta1.Deployment {
	replicas := int32(version.Replicas)
	name := fmt.Sprintf(
		"%v-%v",
		version.Environment.Application.Slug,
		version.Environment.Slug,
	)

	var k8sVolumes []v1.Volume
	for index := 0; index < len(version.Volumes); index++ {
		k8sVolumes = append(k8sVolumes, convertVolume(version.Volumes[index]))
	}

	var k8sContainers []v1.Container
	for index := 0; index < len(version.Containers); index++ {
		k8sContainers = append(k8sContainers, createContainerSpec(version.Containers[index], version.Environment.UUID))
	}

	deployment := &v1beta1.Deployment{
		ObjectMeta: v1.ObjectMeta{
			Name:      name,
			Namespace: "brizo",
			Labels: map[string]string{
				"brizoManaged": "true",
				"appUUID":      version.Application.UUID,
				"envUUID":      version.Environment.UUID,
				"versionUUID":  version.UUID,
			},
		},
		Spec: v1beta1.DeploymentSpec{
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"appUUID":     version.Application.UUID,
					"envUUID":     version.Environment.UUID,
					"versionUUID": version.UUID,
				},
			},
			Replicas: &replicas,
			Template: v1.PodTemplateSpec{
				ObjectMeta: v1.ObjectMeta{
					Labels: map[string]string{
						"brizoManaged": "true",
						"appUUID":      version.Application.UUID,
						"envUUID":      version.Environment.UUID,
						"versionUUID":  version.UUID,
					},
				},
				Spec: v1.PodSpec{
					Volumes:    k8sVolumes,
					Containers: k8sContainers,
				},
			},
		},
	}

	v1beta1.SetObjectDefaults_Deployment(deployment)
	return deployment
}

// UpdateVersion will update an existing Version
func UpdateVersion(db *gorm.DB, version *Version) (bool, error) {
	version.Slug = slugify.Slugify(version.Name)
	result := db.Model(version).Where("uuid = ?", version.UUID).
		UpdateColumns(version)

	return result.RowsAffected == 1, result.Error
}

// GetVersion will get an existing Version by id
func GetVersion(db *gorm.DB, id string, client *kube.Client, includeContainers bool) (*Version, error) {
	version := new(Version)
	if err := db.Preload("Application.Environments").Where("uuid = ?", id).First(version).Error; err != nil {
		return version, err
	}

	if version.ID == 0 {
		return new(Version), errors.New("not-found")
	}

	if includeContainers {
		err := getVersionContainers(version, client)
		if err != nil {
			return new(Version), err
		}
	}

	return version, nil
}

func getVersionContainers(version *Version, client *kube.Client) error {
	var spec map[string]map[string]interface{}
	err := json.Unmarshal([]byte(version.Spec), &spec)
	if err != nil {
		return err
	}

	specName := spec["metadata"]["name"].(string)
	specNS := spec["metadata"]["namespace"].(string)

	deployment, err := client.FindDeploymentByName(specName, specNS)
	if err != nil {
		return err
	}

	for i := 0; i < len(deployment.Spec.Template.Spec.Containers); i++ {
		// determine pull policy
		pullPolicy := true
		if deployment.Spec.Template.Spec.Containers[i].ImagePullPolicy != "Always" {
			pullPolicy = false
		}

		container := Container{
			Name:       deployment.Spec.Template.Spec.Containers[i].Name,
			Image:      deployment.Spec.Template.Spec.Containers[i].Image,
			AlwaysPull: pullPolicy,
			Args:       deployment.Spec.Template.Spec.Containers[i].Args,
		}
		version.Containers = append(version.Containers, container)
	}

	// gather volumes information
	for i := 0; i < len(deployment.Spec.Template.Spec.Volumes); i++ {
		volume := Volume{
			Name: deployment.Spec.Template.Spec.Volumes[i].Name,
		}
		version.Volumes = append(version.Volumes, volume)
	}

	return nil
}

// GetVersionsByEnvironmentUUID will get an existing version using an
// environment's UUID
func GetVersionsByEnvironmentUUID(db *gorm.DB, uuid string) (*[]Version, error) {
	var versions []Version
	environment, err := GetEnvironment(db, uuid)
	if err != nil {
		return &versions, err
	}

	if err := db.Preload("Application.Environments").Where("environment_id = ?", environment.ID).Find(&versions).Error; err != nil {
		return &versions, err
	}

	return &versions, nil
}

// DeleteVersion will delete an existing Version by name
func DeleteVersion(db *gorm.DB, name string) (bool, error) {
	result := db.Delete(Version{}, "name = ?", name)

	return result.RowsAffected == 1, result.Error
}
<commit_msg>fix field access from recent refactor<commit_after>package resources

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"

	"github.com/Machiel/slugify"
	"github.com/generationtux/brizo/database"
	"github.com/generationtux/brizo/kube"
	"github.com/jinzhu/gorm"
	"github.com/pborman/uuid"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
	metav1 "k8s.io/client-go/pkg/apis/meta/v1"
)

// Volume as defined by Brizo.
type Volume struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

// Container individual container for a version
type Container struct {
	Name         string                 `json:"name"`
	Image        string                 `json:"image"`
	AlwaysPull   bool                   `json:"alwaysPull"`
	Args         []string               `json:"args"`
	VolumeMounts []ContainerVolumeMount `json:"volumeMounts"`
	Ports        []ContainerPort        `json:"ports"`
}

// ContainerPort exposed container port
type ContainerPort struct {
	Protocol string `json:"protocol"`
	Port     int    `json:"port"`
}

// ContainerVolumeMount mount configuration for an available volume
type ContainerVolumeMount struct {
	Name string `json:"name"`
	Path string `json:"path"`
}

// Version as defined by Brizo.
type Version struct {
	database.Model
	UUID            string       `gorm:"not null;unique_index:uix_versions_name_application_uuid" sql:"type:varchar(36)" json:"uuid"`
	Name            string       `gorm:"not null" json:"name"`
	Slug            string       `gorm:"not null" json:"slug"`
	Replicas        int          `gorm:"not null" sql:"DEFAULT:'0'" json:"replicas"`
	ApplicationID   uint64       `gorm:"not null" json:"appliction_id,string"`
	ApplicationUUID string       `gorm:"not null;unique_index:uix_versions_name_application_uuid" json:"application_uuid"`
	Application     Application  `json:"application"`
	EnvironmentID   uint         `gorm:"not null" json:"environment_id,string"`
	EnvironmentUUID string       `gorm:"not null" json:"environment_uuid"`
	Environment     *Environment `json:"environment"`
	Volumes         []Volume     `gorm:"-" json:"volumes"`
	Containers      []Container  `gorm:"-" json:"containers"`
	Spec            string       `gorm:"type:json" json:"-"`
}

// Spec k8s spec information
type Spec struct {
	Name              string
	Labels            []string
	Namespace         string
	creationTimestamp string
}

// BeforeCreate is a hook that runs before inserting a new record into the
// database
func (version *Version) BeforeCreate() (err error) {
	if version.UUID == "" {
		version.UUID = uuid.New()
	}

	return
}

// AllVersions will return all of the Versions
func AllVersions(db *gorm.DB) ([]Version, error) {
	var versions []Version
	result := db.Find(&versions)

	return versions, result.Error
}

// DeployVersion will deploy an existing version
func DeployVersion(client kube.APIInterface, version *Version) (bool, error) {
	deployment := versionDeploymentDefinition(version)
	err := client.CreateOrUpdateDeployment(deployment)
	if err != nil {
		return false, err
	}

	return true, nil
}

// CreateVersion will create and deploy a new version
func CreateVersion(db *gorm.DB, client kube.APIInterface, version *Version) (bool, error) {
	deployment := versionDeploymentDefinition(version)

	err := client.CreateOrUpdateDeployment(deployment)
	if err != nil {
		return false, err
	}

	spec, err := json.Marshal(deployment)
	if err != nil {
		client.DeleteDeployment(deployment)
		return false, err
	}

	version.Spec = string(spec)
	persist := db.Create(&version)

	// update environment service
	ports := gatherContainerPorts(version.Containers)
	UpdateEnvironmentService(db, client, version.Environment, ports)

	return persist.RowsAffected == 1, persist.Error
}

func gatherContainerPorts(containers []Container) []ContainerPort {
	ports := make([]ContainerPort, 0)
	for _, container := range containers {
		ports = append(ports, container.Ports...)
	}
	return ports
}

func convertVolume(volume Volume) v1.Volume {
	var k8sVol v1.Volume

	switch volume.Type {
	case "temp":
		k8sVol = v1.Volume{
			Name: volume.Name,
			VolumeSource: v1.VolumeSource{
				EmptyDir: &v1.EmptyDirVolumeSource{
					Medium: v1.StorageMediumDefault,
				},
			},
		}
	default:
		// @TODO handle unsupported volume error
		panic(volume.Type + " is not a supported volume type")
	}

	return k8sVol
}

func createContainerSpec(container Container, environmentUUID string) v1.Container {
	policy := v1.PullAlways
	if !container.AlwaysPull {
		policy = v1.PullIfNotPresent
	}

	k8sPorts := make([]v1.ContainerPort, len(container.Ports))
	for i, port := range container.Ports {
		protocol := v1.ProtocolTCP
		if port.Protocol == "UDP" {
			protocol = v1.ProtocolUDP
		}
		k8sPorts[i] = v1.ContainerPort{
			Protocol:      protocol,
			ContainerPort: int32(port.Port),
		}
	}

	k8sVolumeMounts := make([]v1.VolumeMount, len(container.VolumeMounts))
	for i, mount := range container.VolumeMounts {
		k8sVolumeMounts[i] = v1.VolumeMount{
			Name:      mount.Name,
			MountPath: mount.Path,
		}
	}

	db, err := database.Connect()
	defer db.Close()
	if err != nil {
		log.Printf("Database error: '%s'\n", err)
	}
	environmentVars, err := GetEnvironmentConfig(db, environmentUUID)
	if err != nil {
		log.Printf("Error retrieving environment configs: '%s'\n", err)
	}

	var k8sEnvVars []v1.EnvVar
	for _, environmentVar := range *environmentVars {
		k8sEnvVars = append(k8sEnvVars, v1.EnvVar{
			Name:  environmentVar.Name,
			Value: environmentVar.Value,
		})
	}

	return v1.Container{
		Name:            container.Name,
		Image:           container.Image,
		ImagePullPolicy: policy,
		Args:            container.Args,
		Ports:           k8sPorts,
		VolumeMounts:    k8sVolumeMounts,
		Env:             k8sEnvVars,
	}
}

// versionDeploymentDefinition builds a deployment spec for the provided version
func versionDeploymentDefinition(version *Version) *v1beta1.Deployment {
	replicas := int32(version.Replicas)
	name := fmt.Sprintf(
		"%v-%v",
		version.Environment.Application.Slug,
		version.Environment.Slug,
	)

	var k8sVolumes []v1.Volume
	for index := 0; index < len(version.Volumes); index++ {
		k8sVolumes = append(k8sVolumes, convertVolume(version.Volumes[index]))
	}

	var k8sContainers []v1.Container
	for index := 0; index < len(version.Containers); index++ {
		k8sContainers = append(k8sContainers, createContainerSpec(version.Containers[index], version.Environment.UUID))
	}

	deployment := &v1beta1.Deployment{
		ObjectMeta: v1.ObjectMeta{
			Name:      name,
			Namespace: "brizo",
			Labels: map[string]string{
				"brizoManaged": "true",
				"appUUID":      version.Environment.Application.UUID,
				"envUUID":      version.Environment.UUID,
				"versionUUID":  version.UUID,
			},
		},
		Spec: v1beta1.DeploymentSpec{
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"appUUID":     version.Environment.Application.UUID,
					"envUUID":     version.Environment.UUID,
					"versionUUID": version.UUID,
				},
			},
			Replicas: &replicas,
			Template: v1.PodTemplateSpec{
				ObjectMeta: v1.ObjectMeta{
					Labels: map[string]string{
						"brizoManaged": "true",
						"appUUID":      version.Environment.Application.UUID,
						"envUUID":      version.Environment.UUID,
						"versionUUID":  version.UUID,
					},
				},
				Spec: v1.PodSpec{
					Volumes:    k8sVolumes,
					Containers: k8sContainers,
				},
			},
		},
	}

	v1beta1.SetObjectDefaults_Deployment(deployment)
	return deployment
}

// UpdateVersion will update an existing Version
func UpdateVersion(db *gorm.DB, version *Version) (bool, error) {
	version.Slug = slugify.Slugify(version.Name)
	result := db.Model(version).Where("uuid = ?", version.UUID).
		UpdateColumns(version)

	return result.RowsAffected == 1, result.Error
}

// GetVersion will get an existing Version by id
func GetVersion(db *gorm.DB, id string, client *kube.Client, includeContainers bool) (*Version, error) {
	version := new(Version)
	if err := db.Preload("Application.Environments").Where("uuid = ?", id).First(version).Error; err != nil {
		return version, err
	}

	if version.ID == 0 {
		return new(Version), errors.New("not-found")
	}

	if includeContainers {
		err := getVersionContainers(version, client)
		if err != nil {
			return new(Version), err
		}
	}

	return version, nil
}

func getVersionContainers(version *Version, client *kube.Client) error {
	var spec map[string]map[string]interface{}
	err := json.Unmarshal([]byte(version.Spec), &spec)
	if err != nil {
		return err
	}

	specName := spec["metadata"]["name"].(string)
	specNS := spec["metadata"]["namespace"].(string)

	deployment, err := client.FindDeploymentByName(specName, specNS)
	if err != nil {
		return err
	}

	for i := 0; i < len(deployment.Spec.Template.Spec.Containers); i++ {
		// determine pull policy
		pullPolicy := true
		if deployment.Spec.Template.Spec.Containers[i].ImagePullPolicy != "Always" {
			pullPolicy = false
		}

		container := Container{
			Name:       deployment.Spec.Template.Spec.Containers[i].Name,
			Image:      deployment.Spec.Template.Spec.Containers[i].Image,
			AlwaysPull: pullPolicy,
			Args:       deployment.Spec.Template.Spec.Containers[i].Args,
		}
		version.Containers = append(version.Containers, container)
	}

	// gather volumes information
	for i := 0; i < len(deployment.Spec.Template.Spec.Volumes); i++ {
		volume := Volume{
			Name: deployment.Spec.Template.Spec.Volumes[i].Name,
		}
		version.Volumes = append(version.Volumes, volume)
	}

	return nil
}

// GetVersionsByEnvironmentUUID will get an existing version using an
// environment's UUID
func GetVersionsByEnvironmentUUID(db *gorm.DB, uuid string) (*[]Version, error) {
	var versions []Version
	environment, err := GetEnvironment(db, uuid)
	if err != nil {
		return &versions, err
	}

	if err := db.Preload("Application.Environments").Where("environment_id = ?", environment.ID).Find(&versions).Error; err != nil {
		return &versions, err
	}

	return &versions, nil
}

// DeleteVersion will delete an existing Version by name
func DeleteVersion(db *gorm.DB, name string) (bool, error) {
	result := db.Delete(Version{}, "name = ?", name)

	return result.RowsAffected == 1, result.Error
}
<|endoftext|>


<commit_before>package redis

import (
	"container/list"
	"sync"
)

// connPool is a stack-like structure that holds the connections of a Client.
type connPool struct {
	available int
	free      list.List
	lock      sync.Mutex
	emptyCond *sync.Cond
	config    *Config
	closed    bool
}

func newConnPool(config *Config) *connPool {
	cp := new(connPool)
	cp.available = config.PoolCapacity
	cp.config = config
	cp.closed = false
	cp.emptyCond = sync.NewCond(&cp.lock)
	return cp
}

func (cp *connPool) push(c *conn) {
	cp.lock.Lock()
	defer cp.lock.Unlock()
	if cp.closed {
		return
	}

	if !c.closed() {
		cp.free.PushFront(c)
	}

	cp.available++
	cp.emptyCond.Signal()
}

func (cp *connPool) pull() (c *conn, err *Error) {
	cp.lock.Lock()
	defer cp.lock.Unlock()
	if cp.closed {
		return nil, newError("connection pool is closed", ErrorConnection)
	}

	for cp.available == 0 {
		cp.emptyCond.Wait()
	}

	if cp.free.Len() > 0 {
		c, _ = cp.free.Remove(cp.free.Back()).(*conn)
	} else {
		// Lazy creation of a connection
		c, err = newConn(cp.config)

		if err != nil {
			cp.emptyCond.Signal()
			return nil, err
		}
	}

	cp.available--
	return c, nil
}

func (cp *connPool) close() {
	cp.lock.Lock()
	defer cp.lock.Unlock()
	for e := cp.free.Front(); e != nil; e = e.Next() {
		c, _ := e.Value.(*conn)
		c.close()
	}

	cp.free.Init()
	cp.closed = true
}
<commit_msg>refactoring in connpool.<commit_after>package redis

import (
	"container/list"
	"sync"
)

// connPool is a stack-like structure that holds the connections of a Client.
type connPool struct {
	sync.Mutex
	available int
	free      list.List
	emptyCond *sync.Cond
	config    *Config
	closed    bool
}

func newConnPool(config *Config) *connPool {
	cp := new(connPool)
	cp.available = config.PoolCapacity
	cp.config = config
	cp.closed = false
	cp.emptyCond = sync.NewCond(cp)
	return cp
}

func (cp *connPool) push(c *conn) {
	cp.Lock()
	defer cp.Unlock()
	if cp.closed {
		return
	}

	if !c.closed() {
		cp.free.PushFront(c)
	}

	cp.available++
	cp.emptyCond.Signal()
}

func (cp *connPool) pull() (c *conn, err *Error) {
	cp.Lock()
	defer cp.Unlock()
	if cp.closed {
		return nil, newError("connection pool is closed", ErrorConnection)
	}

	for cp.available == 0 {
		cp.emptyCond.Wait()
	}

	if cp.free.Len() > 0 {
		c, _ = cp.free.Remove(cp.free.Back()).(*conn)
	} else {
		// Lazy creation of a connection
		c, err = newConn(cp.config)

		if err != nil {
			cp.emptyCond.Signal()
			return nil, err
		}
	}

	cp.available--
	return c, nil
}

func (cp *connPool) close() {
	cp.Lock()
	defer cp.Unlock()
	for e := cp.free.Front(); e != nil; e = e.Next() {
		c, _ := e.Value.(*conn)
		c.close()
	}

	cp.free.Init()
	cp.closed = true
}
<|endoftext|>


<commit_before>package resource

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
	"testing"
)

//
// Test
//
func TestResource(t *testing.T) {
	a := A{
		Name: "Testing",
		X:    X{Test: "Tested"},
		Bs:   BList{B{Name: "Started"}},
	}

	resource := NewResource(a, "recurso")

	server := NewServer()

	err := server.Add(resource)
	if err != nil {
		log.Println(err)
	}

	//server.BuildRoutes()

	fmt.Println("\n1 ----------\n")

	printResource(resource, 0)

	fmt.Println("\n2 ----------\n")

	printRoute(server.Route, 0)

	fmt.Println("\n3 ----------\n")

	res := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/recurso/bs/123", nil)

	server.ServeHTTP(res, req)

	fmt.Printf("RETURN: %v\n", res.Body)

	fmt.Println("\nEND --------\n")

}

//
// Resources used in test
//

type A struct {
	Id   string
	Name string
	Bs   BList "Tag de Bs"
	X
	//C C // conflit, name 'c' already used
}

type BList []B

func (b *BList) Init(c *C, d D) {
	log.Println("*** BList Received", d)
}

func (b *BList) GET() *BList {
	return b
}

type B struct {
	Id   int
	Name string
	//d    D
	//Cs CList "Tag de C"
}

func (b *B) Init(id ID) *B {
	log.Println("*** ID Received", id)
	b.Name = id.String()
	b.Id, _ = id.Int()
	return b
}
func (b *B) GET(id ID) *B {
	return b
}

func (b *B) PUT() {}

type C struct {
	Nothing string
}

func (c *C) Init() {
	c.Nothing = "Initialized ok"
	//log.Println("*** C Received", d)
}

type CList []B

func (c *CList) Init(id ID) {
	log.Println("*** CList Received ID:", id)
}

func (c *CList) GET() *CList {
	return c
}

//func (c *C) PUT(d *D) {}

type X struct {
	Test string
	Gogo int
	C    C
}

//func (b *X) PUT() {}

func (a *A) Init(b BList) *A {
	return &A{}
}

func (a *A) GET(x A, i *BList) *A {
	return a
}

func (a *A) doSomething() {}

type InterfaceA interface {
	doSomething()
}

type D struct {
	Test string
}

func (d *D) Init() {
	d.Test = "TESTED"
}

// Set the value passed by user on creation
//ptrValue.Elem().Set(value)
<commit_msg>Testing ok<commit_after>package resource

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
	"testing"
)

//
// Test
//
func TestResource(t *testing.T) {
	a := A{
		Name: "Testing",
		X:    X{Test: "Tested"},
		Bs:   BList{B{Name: "Started"}},
	}

	resource := NewResource(a, "recurso")

	server := NewServer()

	err := server.Add(resource)
	if err != nil {
		log.Println(err)
	}

	//server.BuildRoutes()

	fmt.Println("\n1 ----------\n")

	printResource(resource, 0)

	fmt.Println("\n2 ----------\n")

	printRoute(server.Route, 0)

	fmt.Println("\n3 ----------\n")

	res := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/recurso/bs/123/cs/321", nil)

	server.ServeHTTP(res, req)

	fmt.Printf("RETURN: %v\n", res.Body)

	fmt.Println("\nEND --------\n")

}

//
// Resources used in test
//

type A struct {
	Id   string
	Name string
	Bs   BList "Tag de Bs"
	X
	//C C // conflit, name 'c' already used
}

type BList []B

func (b *BList) Init(c *C, d D) {
	log.Println("*** BList Received", d)
}

func (b *BList) GET() *BList {
	return b
}

type B struct {
	Id   int
	Name string
	//d    D
	Cs CList "Tag de C"
}

func (b *B) Init(id ID) *B {
	log.Println("*** B ID Received", id)
	b.Name = id.String()
	b.Id, _ = id.Int()
	return b
}
func (b *B) GET(id ID) *B {
	return b
}

func (b *B) PUT() {}

type C struct {
	Id      int
	BId     int
	Nothing string
}

func (c *C) Init(id ID, b B) {
	log.Println("*** C Received ID:", id)
	c.Id, _ = id.Int()
	c.BId = b.Id
}

func (c *C) GET() *C {
	return c
}

type CList []C

func (c *CList) Init() {
	//log.Println("*** C Received", d)
}

//func (c *C) PUT(d *D) {}

type X struct {
	Test string
	Gogo int
	C    C
}

//func (b *X) PUT() {}

func (a *A) Init(b BList) *A {
	return &A{}
}

func (a *A) GET(x A, i *BList) *A {
	return a
}

func (a *A) doSomething() {}

type InterfaceA interface {
	doSomething()
}

type D struct {
	Test string
}

func (d *D) Init() {
	d.Test = "TESTED"
}

// Set the value passed by user on creation
//ptrValue.Elem().Set(value)
<|endoftext|>


<commit_before>package neo

import (
	"github.com/ivpusic/httpcheck"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"testing"
)

type testPerson struct {
	Name string
	Age  int
}

func getTestApp(t *testing.T) *httpcheck.Checker {
	app := App()
	app.Templates("./testassets/templates/*")

	app.Get("/json", func(this *Ctx) {
		this.Res.Json(&testPerson{"Some", 30}, 200)
	})

	app.Get("/xml", func(this *Ctx) {
		this.Res.Xml(&testPerson{"Some", 30}, 200)
	})

	app.Get("/text", func(this *Ctx) {
		this.Res.Text("some text", 200)
	})

	app.Get("/tpl", func(this *Ctx) {
		this.Res.Tpl("testindex", testPerson{"Some", 30})
	})

	app.Get("/cookie", func(this *Ctx) {
		this.Res.Cookie.Set("key", "value")
		this.Res.Cookie.Set("key1", "value1")
		this.Res.Status = 200
	})

	app.Get("/header", func(this *Ctx) {
		this.Res.Header.Set("key", "value")
		this.Res.Header.Set("key1", "value1")
		this.Res.Status = 200
	})

	app.Get("/status400", func(this *Ctx) {
		this.Res.Status = 400
	})

	app.Get("/status200", func(this *Ctx) {
		this.Res.Status = 200
	})

	app.Get("/file", func(this *Ctx) {
		this.Res.File("./testassets/test.txt")
	})

	app.Get("/fileunknown", func(this *Ctx) {
		this.Res.File("./testassets/test_unkonown_file.txt")
	})

	app.Get("/err", func(this *Ctx) {
		Assert(false, 400, []byte("some error"))
	})

	app.Serve("/assets", "./testassets")

	app.flush()

	return httpcheck.New(t, app, ":4449")
}

func TestStatus(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/status400").
		Check().
		HasStatus(400)

	server.Test("GET", "/status200").
		Check().
		HasStatus(200)
}

func TestJsonResponse(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/json").
		Check().
		HasHeader("Content-Type", "application/json").
		HasStatus(200).
		HasJson(&testPerson{"Some", 30})
}

func TestXmlResponse(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/xml").
		Check().
		HasHeader("Content-Type", "application/xml").
		HasStatus(200).
		HasXml(&testPerson{"Some", 30})
}

func TestTplResponse(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/tpl").
		Check().
		HasStatus(200).
		Cb(func(response *http.Response) {
		body, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err)

		html := "\n\n" +
			"\n" +
			"    <title>Test</title>\n" +
			"\n" +
			"\n" +
			"    \n" +
			"Header\n" +
			"\n" +
			"    Body Some\n" +
			"    \n" +
			"Footer\n" +
			"\n" +
			"\n" +
			"\n"

		assert.Equal(t, html, string(body))
	})
}

func TestTextResponse(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/text").
		Check().
		HasHeader("Content-Type", "text/plain").
		HasStatus(200).
		Cb(func(response *http.Response) {
		body, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err)
		assert.Equal(t, "some text", string(body))
	})
}

func TestCookie(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/cookie").
		Check().HasStatus(200).
		HasCookie("key", "value").
		HasCookie("key1", "value1")
}

func TestHeader(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/header").
		Check().HasStatus(200).
		HasHeader("key", "value").
		HasHeader("key1", "value1")
}

func TestServingFile(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/assets/test.txt").Check().
		Cb(func(response *http.Response) {
		body, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err)
		assert.Equal(t, "file content\n", string(body))
	})

	server.Test("GET", "/assets/test_unknown_file.txt").Check().
		HasStatus(404)
}

func TestSendFile(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/file").Check().
		HasStatus(200).
		Cb(func(response *http.Response) {
		body, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err)
		assert.Equal(t, "file content\n", string(body))
	})

	server.Test("GET", "/fileunknown").Check().
		HasStatus(404)
}

func TestRouteNotFound(t *testing.T) {
	server := getTestApp(t)
	server.Test("PUT", "/unknown_route").
		Check().
		HasStatus(404)
}

func TestNeoAssert(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/err").
		Check().
		HasStatus(400).
		HasBody([]byte("some error"))
}
<commit_msg>fixed test<commit_after>package neo

import (
	"github.com/ivpusic/httpcheck"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"testing"
)

type testPerson struct {
	Name string
	Age  int
}

func getTestApp(t *testing.T) *httpcheck.Checker {
	app := App()
	app.Templates("./testassets/templates/*")

	app.Get("/json", func(this *Ctx) {
		this.Res.Json(&testPerson{"Some", 30}, 200)
	})

	app.Get("/xml", func(this *Ctx) {
		this.Res.Xml(&testPerson{"Some", 30}, 200)
	})

	app.Get("/text", func(this *Ctx) {
		this.Res.Text("some text", 200)
	})

	app.Get("/tpl", func(this *Ctx) {
		this.Res.Tpl("testindex", testPerson{"Some", 30})
	})

	app.Get("/cookie", func(this *Ctx) {
		this.Res.Cookie.Set("key", "value")
		this.Res.Cookie.Set("key1", "value1")
		this.Res.Status = 200
	})

	app.Get("/header", func(this *Ctx) {
		this.Res.Header.Set("key", "value")
		this.Res.Header.Set("key1", "value1")
		this.Res.Status = 200
	})

	app.Get("/status400", func(this *Ctx) {
		this.Res.Status = 400
	})

	app.Get("/status200", func(this *Ctx) {
		this.Res.Status = 200
	})

	app.Get("/file", func(this *Ctx) {
		this.Res.File("./testassets/test.txt")
	})

	app.Get("/fileunknown", func(this *Ctx) {
		this.Res.File("./testassets/test_unkonown_file.txt")
	})

	app.Get("/err", func(this *Ctx) {
		Assert(false, 400, []byte("some error"))
	})

	app.Serve("/assets", "./testassets")

	app.flush()

	return httpcheck.New(t, app, ":4449")
}

func TestStatus(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/status400").
		Check().
		HasStatus(400)

	server.Test("GET", "/status200").
		Check().
		HasStatus(200)
}

func TestJsonResponse(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/json").
		Check().
		HasHeader("Content-Type", "application/json").
		HasStatus(200).
		HasJson(&testPerson{"Some", 30})
}

func TestXmlResponse(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/xml").
		Check().
		HasHeader("Content-Type", "application/xml").
		HasStatus(200).
		HasXml(&testPerson{"Some", 30})
}

func TestTplResponse(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/tpl").
		Check().
		HasStatus(200).
		Cb(func(response *http.Response) {
		body, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err)

		html := "\n\n" +
			"\n" +
			"    <title>Test</title>\n" +
			"\n" +
			"\n" +
			"    \n" +
			"Header\n" +
			"\n" +
			"    Body Some\n" +
			"    \n" +
			"Footer\n" +
			"\n" +
			"\n" +
			"\n"

		assert.Equal(t, html, string(body))
	})
}

func TestTextResponse(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/text").
		Check().
		HasHeader("Content-Type", "text/plain; charset=utf-8").
		HasStatus(200).
		Cb(func(response *http.Response) {
		body, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err)
		assert.Equal(t, "some text", string(body))
	})
}

func TestCookie(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/cookie").
		Check().HasStatus(200).
		HasCookie("key", "value").
		HasCookie("key1", "value1")
}

func TestHeader(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/header").
		Check().HasStatus(200).
		HasHeader("key", "value").
		HasHeader("key1", "value1")
}

func TestServingFile(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/assets/test.txt").Check().
		Cb(func(response *http.Response) {
		body, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err)
		assert.Equal(t, "file content\n", string(body))
	})

	server.Test("GET", "/assets/test_unknown_file.txt").Check().
		HasStatus(404)
}

func TestSendFile(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/file").Check().
		HasStatus(200).
		Cb(func(response *http.Response) {
		body, err := ioutil.ReadAll(response.Body)
		assert.Nil(t, err)
		assert.Equal(t, "file content\n", string(body))
	})

	server.Test("GET", "/fileunknown").Check().
		HasStatus(404)
}

func TestRouteNotFound(t *testing.T) {
	server := getTestApp(t)
	server.Test("PUT", "/unknown_route").
		Check().
		HasStatus(404)
}

func TestNeoAssert(t *testing.T) {
	server := getTestApp(t)
	server.Test("GET", "/err").
		Check().
		HasStatus(400).
		HasBody([]byte("some error"))
}
<|endoftext|>


<commit_before>package download

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/subutai-io/base/agent/log"
	"github.com/subutai-io/gorjun/db"
)

var (
	path = "/tmp/"
)

type AptItem struct {
	Architecture string `json:"architecture,omitempty"`
	Description  string `json:"description,omitempty"`
	Filename     string `json:"filename,omitempty"`
	Md5Sum       string `json:"md5Sum,omitempty"`
	Name         string `json:"name,omitempty"`
	Version      string `json:"version,omitempty"`
	Size         string `json:"size"`
}

type RawItem struct {
	Md5Sum  string `json:"md5Sum,omitempty"`
	Name    string `json:"name,omitempty"`
	Package string `json:"package,omitempty"`
	Version string `json:"version,omitempty"`
	Size    int64  `json:"size"`
	ID      string `json:"id"`
}

type ListItem struct {
	Architecture     string   `json:"architecture"`
	ConfigContents   string   `json:"configContents"`
	ID               string   `json:"id"`
	Md5Sum           string   `json:"md5Sum"`
	Name             string   `json:"name"`
	OwnerFprint      string   `json:"ownerFprint"`
	Package          string   `json:"package"`
	PackagesContents string   `json:"packagesContents"`
	Parent           string   `json:"parent"`
	Size             int64    `json:"size"`
	Version          string   `json:"version"`
	Owner            []string `json:"owner"`
}

func Handler(w http.ResponseWriter, r *http.Request) {
	hash := r.URL.Query().Get("hash")
	name := r.URL.Query().Get("name")
	if len(r.URL.Query().Get("id")) > 0 {
		hash = r.URL.Query().Get("id")
		if tmp := strings.Split(hash, "."); len(tmp) > 1 {
			hash = tmp[1]
		}
	}
	if len(hash) == 0 && len(name) == 0 {
		w.Write([]byte("Please specify hash or name"))
		return
	} else if len(name) != 0 {
		hash = db.LastHash(name)
	}

	f, err := os.Open(path + hash)
	defer f.Close()
	if log.Check(log.WarnLevel, "Opening file "+path+hash, err) {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	fi, _ := f.Stat()

	if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && fi.ModTime().Unix() <= t.Unix() {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	w.Header().Set("Content-Length", fmt.Sprint(fi.Size()))
	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
	w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
	w.Header().Set("Content-Disposition", "attachment; filename="+db.Read(hash))

	io.Copy(w, f)
}

func Info(repo string, r *http.Request) []byte {
	var item, js []byte

	id := r.URL.Query().Get("id")
	name := r.URL.Query().Get("name")
	rtype := r.URL.Query().Get("type")
	version := r.URL.Query().Get("version")

	if len(strings.Split(id, ".")) > 1 {
		name = db.Read(strings.Split(id, ".")[1])
	}

	counter := 0
	for k, _ := range db.Search(name) {
		info := db.Info(k)
		if info["type"] == repo {
			counter++
			if rtype == "text" && repo == "template" {
				if name == strings.Split(info["name"], "-subutai-template")[0] && (len(version) == 0 || info["version"] == version) {
					return ([]byte("public." + k))
				}
				continue
			}
			size, _ := strconv.ParseInt(info["size"], 10, 64)
			switch repo {
			case "template":
				item, _ = json.Marshal(ListItem{
					Name:         strings.Split(info["name"], "-")[0],
					ID:           info["owner"] + "." + k,
					OwnerFprint:  info["owner"],
					Parent:       info["parent"],
					Version:      info["version"],
					Architecture: strings.ToUpper(info["arch"]),
					Md5Sum:       k,
					Size:         size,
				})
			case "apt":
				item, _ = json.Marshal(AptItem{
					Name:         info["name"],
					Md5Sum:       info["MD5sum"],
					Description:  info["Description"],
					Architecture: info["Architecture"],
					Version:      info["Version"],
					Size:         info["Size"],
				})
			case "raw":
				item, _ = json.Marshal(RawItem{
					Name:    info["name"],
					ID:      k,
					Md5Sum:  k,
					Package: info["package"],
					Version: info["version"],
					Size:    size,
				})
			}
			if counter > 1 {
				js = append(js, []byte(",")[0])
			}
			js = append(js, item...)
			if name == strings.Split(info["name"], "-subutai-template")[0] || name == info["name"] {
				if len(version) == 0 || info["version"] == version {
					if k == db.LastHash(info["name"]) {
						return item
					}
				}
			}
		}
	}
	if counter > 1 {
		js = append([]byte("["), js...)
		js = append(js, []byte("]")[0])
	}
	return js
}
<commit_msg>Fixed wrong info by id. #12<commit_after>package download

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/subutai-io/base/agent/log"
	"github.com/subutai-io/gorjun/db"
)

var (
	path = "/tmp/"
)

type AptItem struct {
	Architecture string `json:"architecture,omitempty"`
	Description  string `json:"description,omitempty"`
	Filename     string `json:"filename,omitempty"`
	Md5Sum       string `json:"md5Sum,omitempty"`
	Name         string `json:"name,omitempty"`
	Version      string `json:"version,omitempty"`
	Size         string `json:"size"`
}

type RawItem struct {
	Md5Sum  string `json:"md5Sum,omitempty"`
	Name    string `json:"name,omitempty"`
	Package string `json:"package,omitempty"`
	Version string `json:"version,omitempty"`
	Size    int64  `json:"size"`
	ID      string `json:"id"`
}

type ListItem struct {
	Architecture     string   `json:"architecture"`
	ConfigContents   string   `json:"configContents"`
	ID               string   `json:"id"`
	Md5Sum           string   `json:"md5Sum"`
	Name             string   `json:"name"`
	OwnerFprint      string   `json:"ownerFprint"`
	Package          string   `json:"package"`
	PackagesContents string   `json:"packagesContents"`
	Parent           string   `json:"parent"`
	Size             int64    `json:"size"`
	Version          string   `json:"version"`
	Owner            []string `json:"owner"`
}

func Handler(w http.ResponseWriter, r *http.Request) {
	hash := r.URL.Query().Get("hash")
	name := r.URL.Query().Get("name")
	if len(r.URL.Query().Get("id")) > 0 {
		hash = r.URL.Query().Get("id")
		if tmp := strings.Split(hash, "."); len(tmp) > 1 {
			hash = tmp[1]
		}
	}
	if len(hash) == 0 && len(name) == 0 {
		w.Write([]byte("Please specify hash or name"))
		return
	} else if len(name) != 0 {
		hash = db.LastHash(name)
	}

	f, err := os.Open(path + hash)
	defer f.Close()
	if log.Check(log.WarnLevel, "Opening file "+path+hash, err) {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	fi, _ := f.Stat()

	if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && fi.ModTime().Unix() <= t.Unix() {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	w.Header().Set("Content-Length", fmt.Sprint(fi.Size()))
	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
	w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
	w.Header().Set("Content-Disposition", "attachment; filename="+db.Read(hash))

	io.Copy(w, f)
}

func Info(repo string, r *http.Request) []byte {
	var item, js []byte

	id := r.URL.Query().Get("id")
	name := r.URL.Query().Get("name")
	rtype := r.URL.Query().Get("type")
	version := r.URL.Query().Get("version")

	list := db.Search(name)
	if len(id) > 0 {
		if len(strings.Split(id, ".")) > 1 {
			id = strings.Split(id, ".")[1]
		}
		list = map[string]string{id: ""}
	}

	counter := 0
	for k, _ := range list {
		info := db.Info(k)
		if info["type"] == repo {
			counter++
			if rtype == "text" && repo == "template" {
				if name == strings.Split(info["name"], "-subutai-template")[0] && (len(version) == 0 || info["version"] == version) {
					return ([]byte("public." + k))
				}
				continue
			}
			size, _ := strconv.ParseInt(info["size"], 10, 64)
			switch repo {
			case "template":
				item, _ = json.Marshal(ListItem{
					Name:         strings.Split(info["name"], "-")[0],
					ID:           info["owner"] + "." + k,
					OwnerFprint:  info["owner"],
					Parent:       info["parent"],
					Version:      info["version"],
					Architecture: strings.ToUpper(info["arch"]),
					Md5Sum:       k,
					Size:         size,
				})
			case "apt":
				item, _ = json.Marshal(AptItem{
					Name:         info["name"],
					Md5Sum:       info["MD5sum"],
					Description:  info["Description"],
					Architecture: info["Architecture"],
					Version:      info["Version"],
					Size:         info["Size"],
				})
			case "raw":
				item, _ = json.Marshal(RawItem{
					Name:    info["name"],
					ID:      k,
					Md5Sum:  k,
					Package: info["package"],
					Version: info["version"],
					Size:    size,
				})
			}
			if counter > 1 {
				js = append(js, []byte(",")[0])
			}
			js = append(js, item...)
			if name == strings.Split(info["name"], "-subutai-template")[0] || name == info["name"] {
				if len(version) == 0 || info["version"] == version {
					if k == db.LastHash(info["name"]) {
						return item
					}
				}
			}
		}
	}
	if counter > 1 {
		js = append([]byte("["), js...)
		js = append(js, []byte("]")[0])
	}
	return js
}
<|endoftext|>