<commit_before>package redisc

import (
	"errors"
	"math/rand"
	"strconv"
	"sync"
	"time"

	"github.com/garyburd/redigo/redis"
)

const hashSlots = 16384

// Cluster manages a redis cluster. If the CreatePool field is not nil,
// a redis.Pool is used for each node in the cluster to get connections
// via Get. If it is nil or if Dial is called, redis.Dial
// is used to get the connection.
type Cluster struct {
	// StartupNodes is the list of initial nodes that make up
	// the cluster. The values are expected as "address:port"
	// (e.g.: "127.0.0.1:6379"). Only master nodes should be
	// specified.
	StartupNodes []string

	// DialOptions is the list of options to set on each new connection.
	DialOptions []redis.DialOption

	// CreatePool is the function to call to create a redis.Pool for
	// the specified TCP address, using the provided options
	// as set in DialOptions. If this field is not nil, a
	// redis.Pool is created for each node in the cluster and the
	// pool is used to manage the connections returned by Get.
	CreatePool func(address string, options ...redis.DialOption) (*redis.Pool, error)

	mu         sync.Mutex             // protects following fields
	err        error                  // broken connection error
	pools      map[string]*redis.Pool // created pools per node
	masters    map[string]bool        // set of known active master nodes, kept up-to-date
	replicas   map[string]bool        // set of known active replica nodes, kept up-to-date
	mapping    [hashSlots][]string    // hash slot number to master and replica(s) server addresses, master is always at [0]
	refreshing bool                   // indicates if there's a refresh in progress
}

// Refresh updates the cluster's internal mapping of hash slots
// to redis node. It calls CLUSTER SLOTS on each known node until one
// of them succeeds.
//
// It should typically be called after creating the Cluster and before
// using it. The cluster automatically keeps its mapping up-to-date
// afterwards, based on the redis commands' MOVED responses.
func (c *Cluster) Refresh() error {
	c.mu.Lock()
	err := c.err
	if err == nil {
		c.refreshing = true
	}
	c.mu.Unlock()
	if err != nil {
		return err
	}

	return c.refresh()
}

func (c *Cluster) refresh() error {
	addrs := c.getNodeAddrs(false)
	for _, addr := range addrs {
		m, err := c.getClusterSlots(addr)
		if err == nil {
			// succeeded, save as mapping
			c.mu.Lock()
			// mark all current nodes as false
			for k := range c.masters {
				c.masters[k] = false
			}
			for k := range c.replicas {
				c.replicas[k] = false
			}

			for _, sm := range m {
				for i, node := range sm.nodes {
					if node != "" {
						target := c.masters
						if i > 0 {
							target = c.replicas
						}
						target[node] = true
					}
				}
				for ix := sm.start; ix <= sm.end; ix++ {
					c.mapping[ix] = sm.nodes
				}
			}

			// remove all nodes that are gone from the cluster
			for _, nodes := range []map[string]bool{c.masters, c.replicas} {
				for k, ok := range nodes {
					if !ok {
						delete(nodes, k)

						// close and remove all existing pools for removed nodes
						if p := c.pools[k]; p != nil {
							p.Close()
							delete(c.pools, k)
						}
					}
				}
			}

			// mark that no refresh is needed until another MOVED
			c.refreshing = false
			c.mu.Unlock()

			return nil
		}
	}

	// reset the refreshing flag
	c.mu.Lock()
	c.refreshing = false
	c.mu.Unlock()

	return errors.New("redisc: all nodes failed")
}

// needsRefresh handles automatic update of the mapping.
func (c *Cluster) needsRefresh(re *RedirError) {
	c.mu.Lock()
	if re != nil {
		// update the mapping only if the address has changed, so that if
		// a READONLY replica read returns a MOVED to a master, it doesn't
		// overwrite that slot's replicas by setting just the master (i.e. this
		// is not a MOVED because the cluster is updating, it is a MOVED
		// because the replica cannot serve that key). Same goes for a request
		// to a random connection that gets a MOVED, should not overwrite
		// the moved-to slot's configuration if the master's address is the same.
		if current := c.mapping[re.NewSlot]; len(current) == 0 || current[0] != re.Addr {
			c.mapping[re.NewSlot] = []string{re.Addr}
		}
	}
	if !c.refreshing {
		// refreshing is reset to only once the goroutine has
		// finished updating the mapping, so a new refresh goroutine
		// will only be started if none is running.
		c.refreshing = true
		go c.refresh()
	}
	c.mu.Unlock()
}

type slotMapping struct {
	start, end int
	nodes      []string // master is always at [0]
}

func (c *Cluster) getClusterSlots(addr string) ([]slotMapping, error) {
	conn, err := c.getConnForAddr(addr, false)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	vals, err := redis.Values(conn.Do("CLUSTER", "SLOTS"))
	if err != nil {
		return nil, err
	}

	m := make([]slotMapping, 0, len(vals))
	for len(vals) > 0 {
		var slotRange []interface{}
		vals, err = redis.Scan(vals, &slotRange)
		if err != nil {
			return nil, err
		}

		var start, end int
		slotRange, err = redis.Scan(slotRange, &start, &end)
		if err != nil {
			return nil, err
		}

		sm := slotMapping{start: start, end: end}
		// store the master address and all replicas
		for len(slotRange) > 0 {
			var nodes []interface{}
			slotRange, err = redis.Scan(slotRange, &nodes)
			if err != nil {
				return nil, err
			}

			var addr string
			var port int
			if _, err = redis.Scan(nodes, &addr, &port); err != nil {
				return nil, err
			}
			sm.nodes = append(sm.nodes, addr+":"+strconv.Itoa(port))
		}

		m = append(m, sm)
	}

	return m, nil
}

func (c *Cluster) getConnForAddr(addr string, forceDial bool) (redis.Conn, error) {
	// non-pooled doesn't require a lock
	if c.CreatePool == nil || forceDial {
		return redis.Dial("tcp", addr, c.DialOptions...)
	}

	c.mu.Lock()

	p := c.pools[addr]
	if p == nil {
		c.mu.Unlock()
		pool, err := c.CreatePool(addr, c.DialOptions...)
		if err != nil {
			return nil, err
		}

		c.mu.Lock()
		// check again, concurrent request may have set the pool in the meantime
		if p = c.pools[addr]; p == nil {
			if c.pools == nil {
				c.pools = make(map[string]*redis.Pool, len(c.StartupNodes))
			}
			c.pools[addr] = pool
			p = pool
		} else {
			// Don't assume CreatePool just returned the pool struct, it may have
			// used a connection or something - always match CreatePool with Close.
			// Do it in a defer to keep lock time short.
			defer pool.Close()
		}
	}
	c.mu.Unlock()

	conn := p.Get()
	return conn, conn.Err()
}

var errNoNodeForSlot = errors.New("redisc: no node for slot")

func (c *Cluster) getConnForSlot(slot int, forceDial, readOnly bool) (redis.Conn, string, error) {
	c.mu.Lock()
	addrs := c.mapping[slot]
	c.mu.Unlock()
	if len(addrs) == 0 {
		return nil, "", errNoNodeForSlot
	}

	// mapping slices are never altered, they are replaced when refreshing
	// or on a MOVED response, so it's non-racy to read them outside the lock.
	addr := addrs[0]
	if readOnly && len(addrs) > 1 {
		// get the address of a replica
		if len(addrs) == 2 {
			addr = addrs[1]
		} else {
			rnd.Lock()
			ix := rnd.Intn(len(addrs) - 1)
			rnd.Unlock()
			addr = addrs[ix+1] // +1 because 0 is the master
		}
	} else {
		readOnly = false
	}
	conn, err := c.getConnForAddr(addr, forceDial)
	if err == nil && readOnly {
		conn.Do("READONLY")
	}
	return conn, addr, err
}

// a *rand.Rand is not safe for concurrent access
var rnd = struct {
	sync.Mutex
	*rand.Rand
}{Rand: rand.New(rand.NewSource(time.Now().UnixNano()))}

func (c *Cluster) getRandomConn(forceDial, readOnly bool) (redis.Conn, string, error) {
	addrs := c.getNodeAddrs(readOnly)
	rnd.Lock()
	perms := rnd.Perm(len(addrs))
	rnd.Unlock()

	for _, ix := range perms {
		addr := addrs[ix]
		conn, err := c.getConnForAddr(addr, forceDial)
		if err == nil {
			if readOnly {
				conn.Do("READONLY")
			}
			return conn, addr, nil
		}
	}
	return nil, "", errors.New("redisc: failed to get a connection")
}

func (c *Cluster) getConn(preferredSlot int, forceDial, readOnly bool) (conn redis.Conn, addr string, err error) {
	if preferredSlot >= 0 {
		conn, addr, err = c.getConnForSlot(preferredSlot, forceDial, readOnly)
		if err == errNoNodeForSlot {
			c.needsRefresh(nil)
		}
	}
	if preferredSlot < 0 || err != nil {
		conn, addr, err = c.getRandomConn(forceDial, readOnly)
	}
	return conn, addr, err
}

func (c *Cluster) getNodeAddrs(preferReplicas bool) []string {
	c.mu.Lock()

	// populate nodes lazily, only once
	if c.masters == nil {
		c.masters = make(map[string]bool)
		c.replicas = make(map[string]bool)

		// StartupNodes should be masters
		for _, n := range c.StartupNodes {
			c.masters[n] = true
		}
	}

	from := c.masters
	if preferReplicas && len(c.replicas) > 0 {
		from = c.replicas
	}

	// grab a slice of addresses
	addrs := make([]string, 0, len(from))
	for addr := range from {
		addrs = append(addrs, addr)
	}
	c.mu.Unlock()

	return addrs
}

// Dial returns a connection the same way as Get, but
// it guarantees that the connection will not be managed by the
// pool, even if CreatePool is set. The actual returned
// type is *Conn, see its documentation for details.
func (c *Cluster) Dial() (redis.Conn, error) {
	c.mu.Lock()
	err := c.err
	c.mu.Unlock()

	if err != nil {
		return nil, err
	}

	return &Conn{
		cluster:   c,
		forceDial: true,
	}, nil
}

// Get returns a redis.Conn interface that can be used to call
// redis commands on the cluster. The application must close the
// returned connection. The actual returned type is *Conn,
// see its documentation for details.
func (c *Cluster) Get() redis.Conn {
	c.mu.Lock()
	err := c.err
	c.mu.Unlock()

	return &Conn{
		cluster: c,
		err:     err,
	}
}

// Close releases the resources used by the cluster. It closes all the
// pools that were created, if any.
func (c *Cluster) Close() error {
	c.mu.Lock()
	err := c.err
	if err == nil {
		c.err = errors.New("redisc: closed")
		for _, p := range c.pools {
			if e := p.Close(); e != nil && err == nil {
				err = e
			}
		}
	}
	c.mu.Unlock()

	return err
}
<commit_msg>Stats method added<commit_after>package redisc

import (
	"errors"
	"math/rand"
	"strconv"
	"sync"
	"time"

	"github.com/garyburd/redigo/redis"
)

const hashSlots = 16384

// Cluster manages a redis cluster. If the CreatePool field is not nil,
// a redis.Pool is used for each node in the cluster to get connections
// via Get. If it is nil or if Dial is called, redis.Dial
// is used to get the connection.
type Cluster struct {
	// StartupNodes is the list of initial nodes that make up
	// the cluster. The values are expected as "address:port"
	// (e.g.: "127.0.0.1:6379"). Only master nodes should be
	// specified.
	StartupNodes []string

	// DialOptions is the list of options to set on each new connection.
	DialOptions []redis.DialOption

	// CreatePool is the function to call to create a redis.Pool for
	// the specified TCP address, using the provided options
	// as set in DialOptions. If this field is not nil, a
	// redis.Pool is created for each node in the cluster and the
	// pool is used to manage the connections returned by Get.
	CreatePool func(address string, options ...redis.DialOption) (*redis.Pool, error)

	mu         sync.RWMutex           // protects following fields
	err        error                  // broken connection error
	pools      map[string]*redis.Pool // created pools per node
	masters    map[string]bool        // set of known active master nodes, kept up-to-date
	replicas   map[string]bool        // set of known active replica nodes, kept up-to-date
	mapping    [hashSlots][]string    // hash slot number to master and replica(s) server addresses, master is always at [0]
	refreshing bool                   // indicates if there's a refresh in progress
}

// Refresh updates the cluster's internal mapping of hash slots
// to redis node. It calls CLUSTER SLOTS on each known node until one
// of them succeeds.
//
// It should typically be called after creating the Cluster and before
// using it. The cluster automatically keeps its mapping up-to-date
// afterwards, based on the redis commands' MOVED responses.
func (c *Cluster) Refresh() error {
	c.mu.Lock()
	err := c.err
	if err == nil {
		c.refreshing = true
	}
	c.mu.Unlock()
	if err != nil {
		return err
	}

	return c.refresh()
}

func (c *Cluster) refresh() error {
	addrs := c.getNodeAddrs(false)
	for _, addr := range addrs {
		m, err := c.getClusterSlots(addr)
		if err == nil {
			// succeeded, save as mapping
			c.mu.Lock()
			// mark all current nodes as false
			for k := range c.masters {
				c.masters[k] = false
			}
			for k := range c.replicas {
				c.replicas[k] = false
			}

			for _, sm := range m {
				for i, node := range sm.nodes {
					if node != "" {
						target := c.masters
						if i > 0 {
							target = c.replicas
						}
						target[node] = true
					}
				}
				for ix := sm.start; ix <= sm.end; ix++ {
					c.mapping[ix] = sm.nodes
				}
			}

			// remove all nodes that are gone from the cluster
			for _, nodes := range []map[string]bool{c.masters, c.replicas} {
				for k, ok := range nodes {
					if !ok {
						delete(nodes, k)

						// close and remove all existing pools for removed nodes
						if p := c.pools[k]; p != nil {
							p.Close()
							delete(c.pools, k)
						}
					}
				}
			}

			// mark that no refresh is needed until another MOVED
			c.refreshing = false
			c.mu.Unlock()

			return nil
		}
	}

	// reset the refreshing flag
	c.mu.Lock()
	c.refreshing = false
	c.mu.Unlock()

	return errors.New("redisc: all nodes failed")
}

// needsRefresh handles automatic update of the mapping.
func (c *Cluster) needsRefresh(re *RedirError) {
	c.mu.Lock()
	if re != nil {
		// update the mapping only if the address has changed, so that if
		// a READONLY replica read returns a MOVED to a master, it doesn't
		// overwrite that slot's replicas by setting just the master (i.e. this
		// is not a MOVED because the cluster is updating, it is a MOVED
		// because the replica cannot serve that key). Same goes for a request
		// to a random connection that gets a MOVED, should not overwrite
		// the moved-to slot's configuration if the master's address is the same.
		if current := c.mapping[re.NewSlot]; len(current) == 0 || current[0] != re.Addr {
			c.mapping[re.NewSlot] = []string{re.Addr}
		}
	}
	if !c.refreshing {
		// refreshing is reset to only once the goroutine has
		// finished updating the mapping, so a new refresh goroutine
		// will only be started if none is running.
		c.refreshing = true
		go c.refresh()
	}
	c.mu.Unlock()
}

type slotMapping struct {
	start, end int
	nodes      []string // master is always at [0]
}

func (c *Cluster) getClusterSlots(addr string) ([]slotMapping, error) {
	conn, err := c.getConnForAddr(addr, false)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	vals, err := redis.Values(conn.Do("CLUSTER", "SLOTS"))
	if err != nil {
		return nil, err
	}

	m := make([]slotMapping, 0, len(vals))
	for len(vals) > 0 {
		var slotRange []interface{}
		vals, err = redis.Scan(vals, &slotRange)
		if err != nil {
			return nil, err
		}

		var start, end int
		slotRange, err = redis.Scan(slotRange, &start, &end)
		if err != nil {
			return nil, err
		}

		sm := slotMapping{start: start, end: end}
		// store the master address and all replicas
		for len(slotRange) > 0 {
			var nodes []interface{}
			slotRange, err = redis.Scan(slotRange, &nodes)
			if err != nil {
				return nil, err
			}

			var addr string
			var port int
			if _, err = redis.Scan(nodes, &addr, &port); err != nil {
				return nil, err
			}
			sm.nodes = append(sm.nodes, addr+":"+strconv.Itoa(port))
		}

		m = append(m, sm)
	}

	return m, nil
}

func (c *Cluster) getConnForAddr(addr string, forceDial bool) (redis.Conn, error) {
	// non-pooled doesn't require a lock
	if c.CreatePool == nil || forceDial {
		return redis.Dial("tcp", addr, c.DialOptions...)
	}

	c.mu.Lock()

	p := c.pools[addr]
	if p == nil {
		c.mu.Unlock()
		pool, err := c.CreatePool(addr, c.DialOptions...)
		if err != nil {
			return nil, err
		}

		c.mu.Lock()
		// check again, concurrent request may have set the pool in the meantime
		if p = c.pools[addr]; p == nil {
			if c.pools == nil {
				c.pools = make(map[string]*redis.Pool, len(c.StartupNodes))
			}
			c.pools[addr] = pool
			p = pool
		} else {
			// Don't assume CreatePool just returned the pool struct, it may have
			// used a connection or something - always match CreatePool with Close.
			// Do it in a defer to keep lock time short.
			defer pool.Close()
		}
	}
	c.mu.Unlock()

	conn := p.Get()
	return conn, conn.Err()
}

var errNoNodeForSlot = errors.New("redisc: no node for slot")

func (c *Cluster) getConnForSlot(slot int, forceDial, readOnly bool) (redis.Conn, string, error) {
	c.mu.Lock()
	addrs := c.mapping[slot]
	c.mu.Unlock()
	if len(addrs) == 0 {
		return nil, "", errNoNodeForSlot
	}

	// mapping slices are never altered, they are replaced when refreshing
	// or on a MOVED response, so it's non-racy to read them outside the lock.
	addr := addrs[0]
	if readOnly && len(addrs) > 1 {
		// get the address of a replica
		if len(addrs) == 2 {
			addr = addrs[1]
		} else {
			rnd.Lock()
			ix := rnd.Intn(len(addrs) - 1)
			rnd.Unlock()
			addr = addrs[ix+1] // +1 because 0 is the master
		}
	} else {
		readOnly = false
	}
	conn, err := c.getConnForAddr(addr, forceDial)
	if err == nil && readOnly {
		conn.Do("READONLY")
	}
	return conn, addr, err
}

// a *rand.Rand is not safe for concurrent access
var rnd = struct {
	sync.Mutex
	*rand.Rand
}{Rand: rand.New(rand.NewSource(time.Now().UnixNano()))}

func (c *Cluster) getRandomConn(forceDial, readOnly bool) (redis.Conn, string, error) {
	addrs := c.getNodeAddrs(readOnly)
	rnd.Lock()
	perms := rnd.Perm(len(addrs))
	rnd.Unlock()

	for _, ix := range perms {
		addr := addrs[ix]
		conn, err := c.getConnForAddr(addr, forceDial)
		if err == nil {
			if readOnly {
				conn.Do("READONLY")
			}
			return conn, addr, nil
		}
	}
	return nil, "", errors.New("redisc: failed to get a connection")
}

func (c *Cluster) getConn(preferredSlot int, forceDial, readOnly bool) (conn redis.Conn, addr string, err error) {
	if preferredSlot >= 0 {
		conn, addr, err = c.getConnForSlot(preferredSlot, forceDial, readOnly)
		if err == errNoNodeForSlot {
			c.needsRefresh(nil)
		}
	}
	if preferredSlot < 0 || err != nil {
		conn, addr, err = c.getRandomConn(forceDial, readOnly)
	}
	return conn, addr, err
}

func (c *Cluster) getNodeAddrs(preferReplicas bool) []string {
	c.mu.Lock()

	// populate nodes lazily, only once
	if c.masters == nil {
		c.masters = make(map[string]bool)
		c.replicas = make(map[string]bool)

		// StartupNodes should be masters
		for _, n := range c.StartupNodes {
			c.masters[n] = true
		}
	}

	from := c.masters
	if preferReplicas && len(c.replicas) > 0 {
		from = c.replicas
	}

	// grab a slice of addresses
	addrs := make([]string, 0, len(from))
	for addr := range from {
		addrs = append(addrs, addr)
	}
	c.mu.Unlock()

	return addrs
}

// Dial returns a connection the same way as Get, but
// it guarantees that the connection will not be managed by the
// pool, even if CreatePool is set. The actual returned
// type is *Conn, see its documentation for details.
func (c *Cluster) Dial() (redis.Conn, error) {
	c.mu.Lock()
	err := c.err
	c.mu.Unlock()

	if err != nil {
		return nil, err
	}

	return &Conn{
		cluster:   c,
		forceDial: true,
	}, nil
}

// Get returns a redis.Conn interface that can be used to call
// redis commands on the cluster. The application must close the
// returned connection. The actual returned type is *Conn,
// see its documentation for details.
func (c *Cluster) Get() redis.Conn {
	c.mu.Lock()
	err := c.err
	c.mu.Unlock()

	return &Conn{
		cluster: c,
		err:     err,
	}
}

// Close releases the resources used by the cluster. It closes all the
// pools that were created, if any.
func (c *Cluster) Close() error {
	c.mu.Lock()
	err := c.err
	if err == nil {
		c.err = errors.New("redisc: closed")
		for _, p := range c.pools {
			if e := p.Close(); e != nil && err == nil {
				err = e
			}
		}
	}
	c.mu.Unlock()

	return err
}

// PoolStats contains pool statistics
type PoolStats struct {
	ActiveCount int
	IdleCount   int
}

// Stats returns stats for all pools
func (c *Cluster) Stats() map[string]PoolStats {
	c.mu.RLock()
	defer c.mu.RUnlock()

	stats := make(map[string]PoolStats, len(c.pools))

	for address, pool := range c.pools {
		stats[address] = PoolStats{
			ActiveCount: pool.ActiveCount(),
			IdleCount:   pool.IdleCount(),
		}
	}

	return stats
}
<|endoftext|>


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

package cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net"
	"strings"
	"sync"

	"github.com/google/gnxi/utils/xpath"
	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"google.golang.org/grpc/metadata"
)

// getCmd represents the get command
var getCmd = &cobra.Command{
	Use:   "get",
	Short: "run gnmi get on targets",

	RunE: func(cmd *cobra.Command, args []string) error {
		debug := viper.GetBool("debug")
		var err error
		addresses := viper.GetStringSlice("address")
		if len(addresses) == 0 {
			fmt.Println("no grpc server address specified")
			return nil
		}
		username := viper.GetString("username")
		if username == "" {
			if username, err = readUsername(); err != nil {
				return err
			}
		}
		password := viper.GetString("password")
		if password == "" {
			if password, err = readPassword(); err != nil {
				return err
			}
		}
		encodingVal, ok := gnmi.Encoding_value[strings.Replace(strings.ToUpper(viper.GetString("encoding")), "-", "_", -1)]
		if !ok {
			return fmt.Errorf("invalid encoding type '%s'", viper.GetString("encoding"))
		}
		req := &gnmi.GetRequest{
			UseModels: make([]*gnmi.ModelData, 0),
			Path:      make([]*gnmi.Path, 0),
			Encoding:  gnmi.Encoding(encodingVal),
		}
		model := viper.GetString("get-model")
		if model != "" {
			req.UseModels = append(req.UseModels, &gnmi.ModelData{Name: model})
		}
		prefix := viper.GetString("get-prefix")
		if prefix != "" {
			gnmiPrefix, err := xpath.ToGNMIPath(prefix)
			if err != nil {
				return fmt.Errorf("prefix parse error: %v", err)
			}
			req.Prefix = gnmiPrefix
		}
		paths := viper.GetStringSlice("get-path")
		for _, p := range paths {
			gnmiPath, err := xpath.ToGNMIPath(p)
			if err != nil {
				return fmt.Errorf("path parse error: %v", err)
			}
			req.Path = append(req.Path, gnmiPath)
		}
		dataType := viper.GetString("get-type")
		if dataType != "" {
			dti, ok := gnmi.GetRequest_DataType_value[dataType]
			if !ok {
				return fmt.Errorf("unknown data type %s", dataType)
			}
			req.Type = gnmi.GetRequest_DataType(dti)
		}
		wg := new(sync.WaitGroup)
		wg.Add(len(addresses))
		lock := new(sync.Mutex)
		for _, addr := range addresses {
			go func(address string) {
				defer wg.Done()
				ipa, _, err := net.SplitHostPort(address)
				if err != nil {
					if strings.Contains(err.Error(), "missing port in address") {
						address = net.JoinHostPort(ipa, defaultGrpcPort)
					} else {
						log.Printf("error parsing address '%s': %v", address, err)
						return
					}
				}
				conn, err := createGrpcConn(address)
				if err != nil {
					log.Printf("connection to %s failed: %v", address, err)
					return
				}
				client := gnmi.NewGNMIClient(conn)
				ctx, cancel := context.WithCancel(context.Background())
				defer cancel()
				ctx = metadata.AppendToOutgoingContext(ctx, "username", username, "password", password)

				response, err := client.Get(ctx, req)
				if err != nil {
					log.Printf("error sending get request: %v", err)
					return
				}
				printPrefix := fmt.Sprintf("[%s] ", address)
				lock.Lock()
				for _, notif := range response.Notification {
					fmt.Printf("%stimestamp: %d\n", printPrefix, notif.Timestamp)
					fmt.Printf("%sprefix: %s\n", printPrefix, gnmiPathToXPath(notif.Prefix))
					fmt.Printf("%salias: %s\n", printPrefix, notif.Alias)
					for _, upd := range notif.Update {
						if debug {
							log.Printf("DEBUG: update: %+v", upd)
						}
						if upd.Val == nil {
							if debug {
								log.Printf("DEBUG: got a nil val update: %+v", upd)
							}
							continue
						}
						var value interface{}
						var jsondata []byte
						switch val := upd.Val.Value.(type) {
						case *gnmi.TypedValue_AsciiVal:
							value = val.AsciiVal
						case *gnmi.TypedValue_BoolVal:
							value = val.BoolVal
						case *gnmi.TypedValue_BytesVal:
							value = val.BytesVal
						case *gnmi.TypedValue_DecimalVal:
							value = val.DecimalVal
						case *gnmi.TypedValue_FloatVal:
							value = val.FloatVal
						case *gnmi.TypedValue_IntVal:
							value = val.IntVal
						case *gnmi.TypedValue_StringVal:
							value = val.StringVal
						case *gnmi.TypedValue_UintVal:
							value = val.UintVal
						case *gnmi.TypedValue_JsonIetfVal:
							jsondata = val.JsonIetfVal
						case *gnmi.TypedValue_JsonVal:
							jsondata = val.JsonVal
						}
						if debug {
							log.Printf("DEBUG: value read from update msg")
							log.Printf("DEBUG: value: (%T) %v", value, value)
							log.Printf("DEBUG: jsonData: (%T) %v", jsondata, jsondata)
						}
						if len(jsondata) > 0 {
							err = json.Unmarshal(jsondata, &value)
							if err != nil {
								log.Printf("error unmarshling jsonVal '%s'", string(jsondata))
								continue
							}
							data, err := json.MarshalIndent(value, printPrefix, "  ")
							if err != nil {
								log.Printf("error marshling jsonVal '%s'", value)
								continue
							}
							fmt.Printf("%s%s: (%T) %s\n", printPrefix, gnmiPathToXPath(upd.Path), upd.Val.Value, data)
						} else if value != nil {
							fmt.Printf("%s%s: (%T) %s\n", printPrefix, gnmiPathToXPath(upd.Path), upd.Val.Value, value)
						}
					}
				}
				fmt.Println()
				lock.Unlock()
			}(addr)
		}
		wg.Wait()
		return nil
	},
}

func init() {
	rootCmd.AddCommand(getCmd)

	getCmd.Flags().StringSliceP("path", "", []string{"/"}, "get request paths")
	getCmd.Flags().StringP("prefix", "", "", "get request prefix")
	getCmd.Flags().StringP("model", "", "", "get request model")
	getCmd.Flags().StringP("type", "t", "ALL", "the type of data that is requested from the target. one of: ALL, CONFIG, STATE, OPERATIONAL")
	viper.BindPFlag("get-path", getCmd.Flags().Lookup("path"))
	viper.BindPFlag("get-prefix", getCmd.Flags().Lookup("prefix"))
	viper.BindPFlag("get-model", getCmd.Flags().Lookup("model"))
	viper.BindPFlag("get-type", getCmd.Flags().Lookup("type"))
}
<commit_msg>better debug formatting<commit_after>// Copyright © 2020 Karim Radhouani <medkarimrdi@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net"
	"strings"
	"sync"

	"github.com/google/gnxi/utils/xpath"
	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"google.golang.org/grpc/metadata"
)

// getCmd represents the get command
var getCmd = &cobra.Command{
	Use:   "get",
	Short: "run gnmi get on targets",

	RunE: func(cmd *cobra.Command, args []string) error {
		debug := viper.GetBool("debug")
		var err error
		addresses := viper.GetStringSlice("address")
		if len(addresses) == 0 {
			fmt.Println("no grpc server address specified")
			return nil
		}
		username := viper.GetString("username")
		if username == "" {
			if username, err = readUsername(); err != nil {
				return err
			}
		}
		password := viper.GetString("password")
		if password == "" {
			if password, err = readPassword(); err != nil {
				return err
			}
		}
		encodingVal, ok := gnmi.Encoding_value[strings.Replace(strings.ToUpper(viper.GetString("encoding")), "-", "_", -1)]
		if !ok {
			return fmt.Errorf("invalid encoding type '%s'", viper.GetString("encoding"))
		}
		req := &gnmi.GetRequest{
			UseModels: make([]*gnmi.ModelData, 0),
			Path:      make([]*gnmi.Path, 0),
			Encoding:  gnmi.Encoding(encodingVal),
		}
		model := viper.GetString("get-model")
		if model != "" {
			req.UseModels = append(req.UseModels, &gnmi.ModelData{Name: model})
		}
		prefix := viper.GetString("get-prefix")
		if prefix != "" {
			gnmiPrefix, err := xpath.ToGNMIPath(prefix)
			if err != nil {
				return fmt.Errorf("prefix parse error: %v", err)
			}
			req.Prefix = gnmiPrefix
		}
		paths := viper.GetStringSlice("get-path")
		for _, p := range paths {
			gnmiPath, err := xpath.ToGNMIPath(p)
			if err != nil {
				return fmt.Errorf("path parse error: %v", err)
			}
			req.Path = append(req.Path, gnmiPath)
		}
		dataType := viper.GetString("get-type")
		if dataType != "" {
			dti, ok := gnmi.GetRequest_DataType_value[dataType]
			if !ok {
				return fmt.Errorf("unknown data type %s", dataType)
			}
			req.Type = gnmi.GetRequest_DataType(dti)
		}
		wg := new(sync.WaitGroup)
		wg.Add(len(addresses))
		lock := new(sync.Mutex)
		for _, addr := range addresses {
			go func(address string) {
				defer wg.Done()
				ipa, _, err := net.SplitHostPort(address)
				if err != nil {
					if strings.Contains(err.Error(), "missing port in address") {
						address = net.JoinHostPort(ipa, defaultGrpcPort)
					} else {
						log.Printf("error parsing address '%s': %v", address, err)
						return
					}
				}
				conn, err := createGrpcConn(address)
				if err != nil {
					log.Printf("connection to %s failed: %v", address, err)
					return
				}
				client := gnmi.NewGNMIClient(conn)
				ctx, cancel := context.WithCancel(context.Background())
				defer cancel()
				ctx = metadata.AppendToOutgoingContext(ctx, "username", username, "password", password)

				response, err := client.Get(ctx, req)
				if err != nil {
					log.Printf("error sending get request: %v", err)
					return
				}
				printPrefix := fmt.Sprintf("[%s] ", address)
				lock.Lock()
				for _, notif := range response.Notification {
					fmt.Printf("%stimestamp: %d\n", printPrefix, notif.Timestamp)
					fmt.Printf("%sprefix: %s\n", printPrefix, gnmiPathToXPath(notif.Prefix))
					fmt.Printf("%salias: %s\n", printPrefix, notif.Alias)
					for _, upd := range notif.Update {
						if debug {
							log.Printf("DEBUG: update: %+v", upd)
						}
						if upd.Val == nil {
							if debug {
								log.Printf("DEBUG: got a nil val update: %+v", upd)
							}
							continue
						}
						var value interface{}
						var jsondata []byte
						switch val := upd.Val.Value.(type) {
						case *gnmi.TypedValue_AsciiVal:
							value = val.AsciiVal
						case *gnmi.TypedValue_BoolVal:
							value = val.BoolVal
						case *gnmi.TypedValue_BytesVal:
							value = val.BytesVal
						case *gnmi.TypedValue_DecimalVal:
							value = val.DecimalVal
						case *gnmi.TypedValue_FloatVal:
							value = val.FloatVal
						case *gnmi.TypedValue_IntVal:
							value = val.IntVal
						case *gnmi.TypedValue_StringVal:
							value = val.StringVal
						case *gnmi.TypedValue_UintVal:
							value = val.UintVal
						case *gnmi.TypedValue_JsonIetfVal:
							jsondata = val.JsonIetfVal
						case *gnmi.TypedValue_JsonVal:
							jsondata = val.JsonVal
						}
						if debug {
							log.Printf("DEBUG: value read from update msg")
							log.Printf("DEBUG: value: (%T) '%v'", value, value)
							log.Printf("DEBUG: jsonData: (%T) '%v'", jsondata, jsondata)
						}
						if len(jsondata) > 0 {
							err = json.Unmarshal(jsondata, &value)
							if err != nil {
								log.Printf("error unmarshling jsonVal '%s'", string(jsondata))
								continue
							}
							data, err := json.MarshalIndent(value, printPrefix, "  ")
							if err != nil {
								log.Printf("error marshling jsonVal '%s'", value)
								continue
							}
							fmt.Printf("%s%s: (%T) %s\n", printPrefix, gnmiPathToXPath(upd.Path), upd.Val.Value, data)
						} else if value != nil {
							fmt.Printf("%s%s: (%T) %s\n", printPrefix, gnmiPathToXPath(upd.Path), upd.Val.Value, value)
						}
					}
				}
				fmt.Println()
				lock.Unlock()
			}(addr)
		}
		wg.Wait()
		return nil
	},
}

func init() {
	rootCmd.AddCommand(getCmd)

	getCmd.Flags().StringSliceP("path", "", []string{"/"}, "get request paths")
	getCmd.Flags().StringP("prefix", "", "", "get request prefix")
	getCmd.Flags().StringP("model", "", "", "get request model")
	getCmd.Flags().StringP("type", "t", "ALL", "the type of data that is requested from the target. one of: ALL, CONFIG, STATE, OPERATIONAL")
	viper.BindPFlag("get-path", getCmd.Flags().Lookup("path"))
	viper.BindPFlag("get-prefix", getCmd.Flags().Lookup("prefix"))
	viper.BindPFlag("get-model", getCmd.Flags().Lookup("model"))
	viper.BindPFlag("get-type", getCmd.Flags().Lookup("type"))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"time"

	"github.com/NebulousLabs/Sia/api"
	"github.com/NebulousLabs/Sia/types"
)

// jobHost unlocks the wallet, mines some currency, and starts a host offering
// storage to the ant farm.
func (j *JobRunner) jobHost() {
	done := make(chan struct{})
	defer close(done)

	j.tg.OnStop(func() {
		<-done
	})

	err := j.client.Post("/wallet/unlock", fmt.Sprintf("encryptionpassword=%s&dictionary=%s", j.walletPassword, "english"), nil)
	if err != nil {
		log.Printf("[%v jobHost ERROR: %v\n", j.siaDirectory, err)
		return
	}

	err = j.client.Get("/miner/start", nil)
	if err != nil {
		log.Printf("[%v jobHost ERROR: %v\n", j.siaDirectory, err)
		return
	}

	// Mine at least 50,000 SC
	desiredbalance := types.NewCurrency64(50000).Mul(types.SiacoinPrecision)
	success := false
	for start := time.Now(); time.Since(start) < 5*time.Minute; time.Sleep(time.Second) {
		var walletInfo api.WalletGET
		err = j.client.Get("/wallet", &walletInfo)
		if err != nil {
			log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
			return
		}
		if walletInfo.ConfirmedSiacoinBalance.Cmp(desiredbalance) > 0 {
			success = true
			break
		}
	}
	if !success {
		log.Printf("[%v jobHost ERROR]: timeout: could not mine enough currency after 5 minutes\n", j.siaDirectory)
		return
	}

	// Create a temporary folder for hosting
	hostdir, err := ioutil.TempDir("", "hostdata")
	if err != nil {
		log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		return
	}
	defer os.RemoveAll(hostdir)

	// Add the storage folder.
	err = j.client.Post("/host/storage/folders/add", fmt.Sprintf("path=%s&size=30000000000", hostdir), nil)
	if err != nil {
		log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		return
	}

	// Announce the host to the network, retrying up to 5 times before reporting
	// failure and returning.
	success = false
	for try := 0; try < 5; try++ {
		err = j.client.Post("/host/announce", "", nil)
		if err != nil {
			log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		} else {
			success = true
			break
		}
		time.Sleep(time.Second * 5)
	}
	if !success {
		log.Printf("[%v jobHost ERROR]: Could not announce after 5 tries.\n", j.siaDirectory)
		return
	}

	// Accept contracts
	err = j.client.Post("/host", "acceptingcontracts=true", nil)
	if err != nil {
		log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		return
	}

	// Poll the API for host settings, logging them out with `INFO` tags.  If
	// `StorageRevenue` decreases, log an ERROR.
	maxRevenue := types.NewCurrency64(0)
	for {
		select {
		case <-j.tg.StopChan():
			return
		case <-time.After(time.Second * 15):
		}

		var hostInfo api.HostGET
		err = j.client.Get("/host", &hostInfo)
		if err != nil {
			log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		}

		// Print an error if storage revenue has decreased
		if hostInfo.FinancialMetrics.StorageRevenue.Cmp(maxRevenue) >= 0 {
			maxRevenue = hostInfo.FinancialMetrics.StorageRevenue
		} else {
			// Storage revenue has decreased!
			log.Printf("[%v jobHost ERROR]: StorageRevenue decreased!  was %v is now %v\n", j.siaDirectory, maxRevenue, hostInfo.FinancialMetrics.StorageRevenue)
		}
	}
}
<commit_msg>add announcement success log<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"time"

	"github.com/NebulousLabs/Sia/api"
	"github.com/NebulousLabs/Sia/types"
)

// jobHost unlocks the wallet, mines some currency, and starts a host offering
// storage to the ant farm.
func (j *JobRunner) jobHost() {
	done := make(chan struct{})
	defer close(done)

	j.tg.OnStop(func() {
		<-done
	})

	err := j.client.Post("/wallet/unlock", fmt.Sprintf("encryptionpassword=%s&dictionary=%s", j.walletPassword, "english"), nil)
	if err != nil {
		log.Printf("[%v jobHost ERROR: %v\n", j.siaDirectory, err)
		return
	}

	err = j.client.Get("/miner/start", nil)
	if err != nil {
		log.Printf("[%v jobHost ERROR: %v\n", j.siaDirectory, err)
		return
	}

	// Mine at least 50,000 SC
	desiredbalance := types.NewCurrency64(50000).Mul(types.SiacoinPrecision)
	success := false
	for start := time.Now(); time.Since(start) < 5*time.Minute; time.Sleep(time.Second) {
		var walletInfo api.WalletGET
		err = j.client.Get("/wallet", &walletInfo)
		if err != nil {
			log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
			return
		}
		if walletInfo.ConfirmedSiacoinBalance.Cmp(desiredbalance) > 0 {
			success = true
			break
		}
	}
	if !success {
		log.Printf("[%v jobHost ERROR]: timeout: could not mine enough currency after 5 minutes\n", j.siaDirectory)
		return
	}

	// Create a temporary folder for hosting
	hostdir, err := ioutil.TempDir("", "hostdata")
	if err != nil {
		log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		return
	}
	defer os.RemoveAll(hostdir)

	// Add the storage folder.
	err = j.client.Post("/host/storage/folders/add", fmt.Sprintf("path=%s&size=30000000000", hostdir), nil)
	if err != nil {
		log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		return
	}

	// Announce the host to the network, retrying up to 5 times before reporting
	// failure and returning.
	success = false
	for try := 0; try < 5; try++ {
		err = j.client.Post("/host/announce", "", nil)
		if err != nil {
			log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		} else {
			success = true
			break
		}
		time.Sleep(time.Second * 5)
	}
	if !success {
		log.Printf("[%v jobHost ERROR]: could not announce after 5 tries.\n", j.siaDirectory)
		return
	}
	log.Printf("[%v jobHost INFO]: succesfully performed host announcement\n", j.siaDirectory)

	// Accept contracts
	err = j.client.Post("/host", "acceptingcontracts=true", nil)
	if err != nil {
		log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		return
	}

	// Poll the API for host settings, logging them out with `INFO` tags.  If
	// `StorageRevenue` decreases, log an ERROR.
	maxRevenue := types.NewCurrency64(0)
	for {
		select {
		case <-j.tg.StopChan():
			return
		case <-time.After(time.Second * 15):
		}

		var hostInfo api.HostGET
		err = j.client.Get("/host", &hostInfo)
		if err != nil {
			log.Printf("[%v jobHost ERROR]: %v\n", j.siaDirectory, err)
		}

		// Print an error if storage revenue has decreased
		if hostInfo.FinancialMetrics.StorageRevenue.Cmp(maxRevenue) >= 0 {
			maxRevenue = hostInfo.FinancialMetrics.StorageRevenue
		} else {
			// Storage revenue has decreased!
			log.Printf("[%v jobHost ERROR]: StorageRevenue decreased!  was %v is now %v\n", j.siaDirectory, maxRevenue, hostInfo.FinancialMetrics.StorageRevenue)
		}
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"

	"bufio"
	"github.com/emc-advanced-dev/pkg/errors"
	"github.com/emc-advanced-dev/unik/pkg/client"
	"net"
)

var instanceName, imageName string
var volumes, envPairs []string
var instanceMemory, debugPort int

var runCmd = &cobra.Command{
	Use:   "run",
	Short: "Run a unikernel instance from a compiled image",
	Long: `Deploys a running instance from a unik-compiled unikernel disk image.
The instance will be deployed on the provider the image was compiled for.
e.g. if the image was compiled for virtualbox, unik will attempt to deploy
the image on the configured virtualbox environment.

'unik run' requires a unik-managed volume (see 'unik volumes' and 'unik create volume')
to be attached and mounted to each mount point specified at image compilation time.
This means that if the image was compiled with two mount points, /data1 and /data2,
'unik run' requires 2 available volumes to be attached to the instance at runtime, which
must be specified with the flags --vol SOME_VOLUME_NAME:/data1 --vol ANOTHER_VOLUME_NAME:/data2
If no mount points are required for the image, volumes cannot be attached.

environment variables can be set at runtime through the use of the -env flag.

Example usage:
	unik run --instanceName newInstance --imageName myImage --vol myVol:/mount1 --vol yourVol:/mount2 --env foo=bar --env another=one --instanceMemory 1234

	# will create and run an instance of myImage on the provider environment myImage is compiled for
	# instance will be named newInstance
	# instance will attempt to mount unik-managed volume myVol to /mount1
	# instance will attempt to mount unik-managed volume yourVol to /mount2
	# instance will boot with env variable 'foo' set to 'bar'
	# instance will boot with env variable 'another' set to 'one'
	# instance will get 1234 MB of memory

	# note that run must take exactly one --vol argument for each mount point defined in the image specification
`,
	Run: func(cmd *cobra.Command, args []string) {
		if err := func() error {
			if instanceName == "" {
				return errors.New("--instanceName must be set", nil)
			}
			if imageName == "" {
				return errors.New("--imageName must be set", nil)
			}
			if err := readClientConfig(); err != nil {
				return err
			}
			if host == "" {
				host = clientConfig.Host
			}

			mountPointsToVols := make(map[string]string)
			for _, vol := range volumes {
				pair := strings.Split(vol, ":")
				if len(pair) != 2 {
					return errors.New(fmt.Sprintf("invalid format for vol flag: %s", vol), nil)
				}
				volId := pair[0]
				mnt := pair[1]
				mountPointsToVols[mnt] = volId
			}

			env := make(map[string]string)
			for _, e := range envPairs {
				pair := strings.Split(e, "=")
				if len(pair) != 2 {
					return errors.New(fmt.Sprintf("invalid format for env flag: %s", e), nil)
				}
				key := pair[0]
				val := pair[1]
				env[key] = val
			}

			logrus.WithFields(logrus.Fields{
				"instanceName": instanceName,
				"imageName":    imageName,
				"env":          env,
				"mounts":       mountPointsToVols,
				"host":         host,
			}).Infof("running unik run")
			instance, err := client.UnikClient(host).Instances().Run(instanceName, imageName, mountPointsToVols, env, instanceMemory, noCleanup, debugMode)
			if err != nil {
				return errors.New(fmt.Sprintf("running image failed: %v", err), nil)
			}
			printInstances(instance)
			if debugMode {
				logrus.Infof("attaching debugger to instance %s ...", instance.Name)
				connectDebugger()
			}
			return nil
		}(); err != nil {
			logrus.Errorf("failed running instance: %v", err)
			os.Exit(-1)
		}
	},
}

func init() {
	RootCmd.AddCommand(runCmd)
	runCmd.Flags().StringVar(&instanceName, "instanceName", "", "<string,required> name to give the instance. must be unique")
	runCmd.Flags().StringVar(&imageName, "imageName", "", "<string,required> image to use")
	runCmd.Flags().StringSliceVar(&envPairs, "env", []string{}, "<string,repeated> set any number of environment variables for the instance. must be in the format KEY=VALUE")
	runCmd.Flags().StringSliceVar(&volumes, "vol", []string{}, `<string,repeated> each --vol flag specifies one volume id and the corresponding mount point to attach
	to the instance at boot time. volumes must be attached to the instance for each mount point expected by the image.
	run 'unik image <image_name>' to see the mount points required for the image.
	specified in the format 'volume_id:mount_point'`)
	runCmd.Flags().IntVar(&instanceMemory, "instanceMemory", 0, "<int, optional> amount of memory (in MB) to assign to the instance. if none is given, the provider default will be used")
	runCmd.Flags().BoolVar(&noCleanup, "no-cleanup", false, "<bool, optional> for debugging; do not clean up artifacts for instances that fail to launch")
	runCmd.Flags().BoolVar(&debugMode, "debug-mode", false, "<bool, optional> runs the instance in Debug mode so GDB can be attached. Currently only supported on QEMU provider")
	runCmd.Flags().IntVar(&debugPort, "debug-port", 3001, "<int, optional> target port for debugger tcp connections. used in conjunction with --debug-mode")
}

func connectDebugger() {
	addr := fmt.Sprintf("%v:%v", strings.Split(host, ":")[0], debugPort)
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		logrus.Errorf("failed to initiate tcp connection: %v", err)
		os.Exit(-1)
	}
	if _, err := conn.Write([]byte("GET / HTTP/1.0\r\n\r\n")); err != nil {
		logrus.Errorf("failed to initialize debgger connection: %v", err)
		os.Exit(-1)
	}

	go func() {
		reader := bufio.NewReader(conn)
		for {
			data, err := reader.ReadBytes('\n')
			if err != nil {
				logrus.Errorf("disconnected from debugger: %v", err)
				os.Exit(0)
			}
			fmt.Print(string(data))
		}
	}()

	reader := bufio.NewReader(os.Stdin)
	logrus.Infof("Connected to %s", host)
	for {
		data, err := reader.ReadBytes('\n')
		if err != nil {
			logrus.Errorf("failed reading stdin: %v", err)
			os.Exit(-1)
		}
		if _, err := conn.Write(data); err != nil {
			logrus.Errorf("writing to tcp connection: %v", err)
			os.Exit(-1)
		}
	}
}
<commit_msg>clean up syntax<commit_after>package cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"

	"bufio"
	"github.com/emc-advanced-dev/pkg/errors"
	"github.com/emc-advanced-dev/unik/pkg/client"
	"net"
)

var instanceName, imageName string
var volumes, envPairs []string
var instanceMemory, debugPort int

var runCmd = &cobra.Command{
	Use:   "run",
	Short: "Run a unikernel instance from a compiled image",
	Long: `Deploys a running instance from a unik-compiled unikernel disk image.
The instance will be deployed on the provider the image was compiled for.
e.g. if the image was compiled for virtualbox, unik will attempt to deploy
the image on the configured virtualbox environment.

'unik run' requires a unik-managed volume (see 'unik volumes' and 'unik create volume')
to be attached and mounted to each mount point specified at image compilation time.
This means that if the image was compiled with two mount points, /data1 and /data2,
'unik run' requires 2 available volumes to be attached to the instance at runtime, which
must be specified with the flags --vol SOME_VOLUME_NAME:/data1 --vol ANOTHER_VOLUME_NAME:/data2
If no mount points are required for the image, volumes cannot be attached.

environment variables can be set at runtime through the use of the -env flag.

Example usage:
	unik run --instanceName newInstance --imageName myImage --vol myVol:/mount1 --vol yourVol:/mount2 --env foo=bar --env another=one --instanceMemory 1234

	# will create and run an instance of myImage on the provider environment myImage is compiled for
	# instance will be named newInstance
	# instance will attempt to mount unik-managed volume myVol to /mount1
	# instance will attempt to mount unik-managed volume yourVol to /mount2
	# instance will boot with env variable 'foo' set to 'bar'
	# instance will boot with env variable 'another' set to 'one'
	# instance will get 1234 MB of memory

	# note that run must take exactly one --vol argument for each mount point defined in the image specification
`,
	Run: func(cmd *cobra.Command, args []string) {
		if err := func() error {
			if instanceName == "" {
				return errors.New("--instanceName must be set", nil)
			}
			if imageName == "" {
				return errors.New("--imageName must be set", nil)
			}
			if err := readClientConfig(); err != nil {
				return err
			}
			if host == "" {
				host = clientConfig.Host
			}

			mountPointsToVols := make(map[string]string)
			for _, vol := range volumes {
				pair := strings.Split(vol, ":")
				if len(pair) != 2 {
					return errors.New(fmt.Sprintf("invalid format for vol flag: %s", vol), nil)
				}
				volId := pair[0]
				mnt := pair[1]
				mountPointsToVols[mnt] = volId
			}

			env := make(map[string]string)
			for _, e := range envPairs {
				pair := strings.Split(e, "=")
				if len(pair) != 2 {
					return errors.New(fmt.Sprintf("invalid format for env flag: %s", e), nil)
				}
				key := pair[0]
				val := pair[1]
				env[key] = val
			}

			logrus.WithFields(logrus.Fields{
				"instanceName": instanceName,
				"imageName":    imageName,
				"env":          env,
				"mounts":       mountPointsToVols,
				"host":         host,
			}).Infof("running unik run")
			instance, err := client.UnikClient(host).Instances().Run(instanceName, imageName, mountPointsToVols, env, instanceMemory, noCleanup, debugMode)
			if err != nil {
				return errors.New("running image failed: %v", err)
			}
			printInstances(instance)
			if debugMode {
				logrus.Infof("attaching debugger to instance %s ...", instance.Name)
				connectDebugger()
			}
			return nil
		}(); err != nil {
			logrus.Errorf("failed running instance: %v", err)
			os.Exit(-1)
		}
	},
}

func init() {
	RootCmd.AddCommand(runCmd)
	runCmd.Flags().StringVar(&instanceName, "instanceName", "", "<string,required> name to give the instance. must be unique")
	runCmd.Flags().StringVar(&imageName, "imageName", "", "<string,required> image to use")
	runCmd.Flags().StringSliceVar(&envPairs, "env", []string{}, "<string,repeated> set any number of environment variables for the instance. must be in the format KEY=VALUE")
	runCmd.Flags().StringSliceVar(&volumes, "vol", []string{}, `<string,repeated> each --vol flag specifies one volume id and the corresponding mount point to attach
	to the instance at boot time. volumes must be attached to the instance for each mount point expected by the image.
	run 'unik image <image_name>' to see the mount points required for the image.
	specified in the format 'volume_id:mount_point'`)
	runCmd.Flags().IntVar(&instanceMemory, "instanceMemory", 0, "<int, optional> amount of memory (in MB) to assign to the instance. if none is given, the provider default will be used")
	runCmd.Flags().BoolVar(&noCleanup, "no-cleanup", false, "<bool, optional> for debugging; do not clean up artifacts for instances that fail to launch")
	runCmd.Flags().BoolVar(&debugMode, "debug-mode", false, "<bool, optional> runs the instance in Debug mode so GDB can be attached. Currently only supported on QEMU provider")
	runCmd.Flags().IntVar(&debugPort, "debug-port", 3001, "<int, optional> target port for debugger tcp connections. used in conjunction with --debug-mode")
}

func connectDebugger() {
	addr := fmt.Sprintf("%v:%v", strings.Split(host, ":")[0], debugPort)
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		logrus.Errorf("failed to initiate tcp connection: %v", err)
		os.Exit(-1)
	}
	if _, err := conn.Write([]byte("GET / HTTP/1.0\r\n\r\n")); err != nil {
		logrus.Errorf("failed to initialize debgger connection: %v", err)
		os.Exit(-1)
	}

	go func() {
		reader := bufio.NewReader(conn)
		for {
			data, err := reader.ReadBytes('\n')
			if err != nil {
				logrus.Errorf("disconnected from debugger: %v", err)
				os.Exit(0)
			}
			fmt.Print(string(data))
		}
	}()

	reader := bufio.NewReader(os.Stdin)
	logrus.Infof("Connected to %s", host)
	for {
		data, err := reader.ReadBytes('\n')
		if err != nil {
			logrus.Errorf("failed reading stdin: %v", err)
			os.Exit(-1)
		}
		if _, err := conn.Write(data); err != nil {
			logrus.Errorf("writing to tcp connection: %v", err)
			os.Exit(-1)
		}
	}
}
<|endoftext|>


<commit_before>// +build !confonly

package http

import (
	"bufio"
	"context"
	"encoding/base64"
	"io"
	"net/http"
	"net/url"
	"sync"

	"golang.org/x/net/http2"

	"v2ray.com/core"
	"v2ray.com/core/common"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/bytespool"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/protocol"
	"v2ray.com/core/common/retry"
	"v2ray.com/core/common/session"
	"v2ray.com/core/common/signal"
	"v2ray.com/core/common/task"
	"v2ray.com/core/features/policy"
	"v2ray.com/core/transport"
	"v2ray.com/core/transport/internet"
	"v2ray.com/core/transport/internet/tls"
)

type Client struct {
	serverPicker  protocol.ServerPicker
	policyManager policy.Manager
}

type h2Conn struct {
	rawConn net.Conn
	h2Conn  *http2.ClientConn
}

var (
	cachedH2Mutex sync.Mutex
	cachedH2Conns map[net.Destination]h2Conn
)

// NewClient create a new http client based on the given config.
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
	serverList := protocol.NewServerList()
	for _, rec := range config.Server {
		s, err := protocol.NewServerSpecFromPB(rec)
		if err != nil {
			return nil, newError("failed to get server spec").Base(err)
		}
		serverList.AddServer(s)
	}
	if serverList.Size() == 0 {
		return nil, newError("0 target server")
	}

	v := core.MustFromContext(ctx)
	return &Client{
		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
	}, nil
}

// Process implements proxy.Outbound.Process. We first create a socket tunnel via HTTP CONNECT method, then redirect all inbound traffic to that tunnel.
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
	outbound := session.OutboundFromContext(ctx)
	if outbound == nil || !outbound.Target.IsValid() {
		return newError("target not specified.")
	}
	target := outbound.Target
	targetAddr := target.NetAddr()

	if target.Network == net.Network_UDP {
		return newError("UDP is not supported by HTTP outbound")
	}

	var user *protocol.MemoryUser
	var conn internet.Connection

	mbuf, _ := link.Reader.ReadMultiBuffer()
	len := mbuf.Len()
	firstPayload := bytespool.Alloc(len)
	mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
	firstPayload = firstPayload[:len]

	buf.ReleaseMulti(mbuf)
	defer bytespool.Free(firstPayload)

	if err := retry.ExponentialBackoff(5, 100).On(func() error {
		server := c.serverPicker.PickServer()
		dest := server.Destination()
		user = server.PickUser()

		netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, firstPayload)
		if netConn != nil {
			conn = internet.Connection(netConn)
		}
		return err
	}); err != nil {
		return newError("failed to find an available destination").Base(err)
	}

	defer func() {
		if err := conn.Close(); err != nil {
			newError("failed to closed connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
		}
	}()

	p := c.policyManager.ForLevel(0)
	if user != nil {
		p = c.policyManager.ForLevel(user.Level)
	}

	ctx, cancel := context.WithCancel(ctx)
	timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle)

	requestFunc := func() error {
		defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
		return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
	}
	responseFunc := func() error {
		defer timer.SetTimeout(p.Timeouts.UplinkOnly)
		return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
	}

	var responseDonePost = task.OnSuccess(responseFunc, task.Close(link.Writer))
	if err := task.Run(ctx, requestFunc, responseDonePost); err != nil {
		return newError("connection ends").Base(err)
	}

	return nil
}

// setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method
func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, firstPayload []byte) (net.Conn, error) {
	req := &http.Request{
		Method: http.MethodConnect,
		URL:    &url.URL{Host: target},
		Header: make(http.Header),
		Host:   target,
	}

	if user != nil && user.Account != nil {
		account := user.Account.(*Account)
		auth := account.GetUsername() + ":" + account.GetPassword()
		req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
	}

	connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {
		req.Header.Set("Proxy-Connection", "Keep-Alive")

		err := req.Write(rawConn)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		if _, err := rawConn.Write(firstPayload); err != nil {
			rawConn.Close()
			return nil, err
		}

		resp, err := http.ReadResponse(bufio.NewReader(rawConn), req)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		if resp.StatusCode != http.StatusOK {
			rawConn.Close()
			return nil, newError("Proxy responded with non 200 code: " + resp.Status)
		}
		return rawConn, nil
	}

	connectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) {
		pr, pw := io.Pipe()
		req.Body = pr

		var pErr error
		var wg sync.WaitGroup
		wg.Add(1)

		go func() {
			_, pErr = pw.Write(firstPayload)
			wg.Done()
		}()

		resp, err := h2clientConn.RoundTrip(req)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		wg.Wait()
		if pErr != nil {
			rawConn.Close()
			return nil, pErr
		}

		if resp.StatusCode != http.StatusOK {
			rawConn.Close()
			return nil, newError("Proxy responded with non 200 code: " + resp.Status)
		}
		return newHTTP2Conn(rawConn, pw, resp.Body), nil
	}

	cachedH2Mutex.Lock()
	defer cachedH2Mutex.Unlock()

	if cachedConn, found := cachedH2Conns[dest]; found {
		rc, cc := cachedConn.rawConn, cachedConn.h2Conn
		if cc.CanTakeNewRequest() {
			proxyConn, err := connectHTTP2(rc, cc)
			if err != nil {
				return nil, err
			}

			return proxyConn, nil
		}
	}

	rawConn, err := dialer.Dial(ctx, dest)
	if err != nil {
		return nil, err
	}

	iConn := rawConn
	if statConn, ok := iConn.(*internet.StatCouterConnection); ok {
		iConn = statConn.Connection
	}

	nextProto := ""
	if tlsConn, ok := iConn.(*tls.Conn); ok {
		if err := tlsConn.Handshake(); err != nil {
			rawConn.Close()
			return nil, err
		}
		nextProto = tlsConn.ConnectionState().NegotiatedProtocol
	}

	switch nextProto {
	case "", "http/1.1":
		return connectHTTP1(rawConn)
	case "h2":
		t := http2.Transport{}
		h2clientConn, err := t.NewClientConn(rawConn)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		proxyConn, err := connectHTTP2(rawConn, h2clientConn)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		if cachedH2Conns == nil {
			cachedH2Conns = make(map[net.Destination]h2Conn)
		}

		cachedH2Conns[dest] = h2Conn{
			rawConn: rawConn,
			h2Conn:  h2clientConn,
		}

		return proxyConn, err
	default:
		return nil, newError("negotiated unsupported application layer protocol: " + nextProto)
	}
}

func newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn {
	return &http2Conn{Conn: c, in: pipedReqBody, out: respBody}
}

type http2Conn struct {
	net.Conn
	in  *io.PipeWriter
	out io.ReadCloser
}

func (h *http2Conn) Read(p []byte) (n int, err error) {
	return h.out.Read(p)
}

func (h *http2Conn) Write(p []byte) (n int, err error) {
	return h.in.Write(p)
}

func (h *http2Conn) Close() error {
	h.in.Close()
	return h.out.Close()
}

func init() {
	common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
		return NewClient(ctx, config.(*ClientConfig))
	}))
}
<commit_msg>Fix the coarse-grained mutex in http outbound<commit_after>// +build !confonly

package http

import (
	"bufio"
	"context"
	"encoding/base64"
	"io"
	"net/http"
	"net/url"
	"sync"

	"golang.org/x/net/http2"

	"v2ray.com/core"
	"v2ray.com/core/common"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/bytespool"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/protocol"
	"v2ray.com/core/common/retry"
	"v2ray.com/core/common/session"
	"v2ray.com/core/common/signal"
	"v2ray.com/core/common/task"
	"v2ray.com/core/features/policy"
	"v2ray.com/core/transport"
	"v2ray.com/core/transport/internet"
	"v2ray.com/core/transport/internet/tls"
)

type Client struct {
	serverPicker  protocol.ServerPicker
	policyManager policy.Manager
}

type h2Conn struct {
	rawConn net.Conn
	h2Conn  *http2.ClientConn
}

var (
	cachedH2Mutex sync.Mutex
	cachedH2Conns map[net.Destination]h2Conn
)

// NewClient create a new http client based on the given config.
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
	serverList := protocol.NewServerList()
	for _, rec := range config.Server {
		s, err := protocol.NewServerSpecFromPB(rec)
		if err != nil {
			return nil, newError("failed to get server spec").Base(err)
		}
		serverList.AddServer(s)
	}
	if serverList.Size() == 0 {
		return nil, newError("0 target server")
	}

	v := core.MustFromContext(ctx)
	return &Client{
		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
	}, nil
}

// Process implements proxy.Outbound.Process. We first create a socket tunnel via HTTP CONNECT method, then redirect all inbound traffic to that tunnel.
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
	outbound := session.OutboundFromContext(ctx)
	if outbound == nil || !outbound.Target.IsValid() {
		return newError("target not specified.")
	}
	target := outbound.Target
	targetAddr := target.NetAddr()

	if target.Network == net.Network_UDP {
		return newError("UDP is not supported by HTTP outbound")
	}

	var user *protocol.MemoryUser
	var conn internet.Connection

	mbuf, _ := link.Reader.ReadMultiBuffer()
	len := mbuf.Len()
	firstPayload := bytespool.Alloc(len)
	mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
	firstPayload = firstPayload[:len]

	buf.ReleaseMulti(mbuf)
	defer bytespool.Free(firstPayload)

	if err := retry.ExponentialBackoff(5, 100).On(func() error {
		server := c.serverPicker.PickServer()
		dest := server.Destination()
		user = server.PickUser()

		netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, firstPayload)
		if netConn != nil {
			conn = internet.Connection(netConn)
		}
		return err
	}); err != nil {
		return newError("failed to find an available destination").Base(err)
	}

	defer func() {
		if err := conn.Close(); err != nil {
			newError("failed to closed connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
		}
	}()

	p := c.policyManager.ForLevel(0)
	if user != nil {
		p = c.policyManager.ForLevel(user.Level)
	}

	ctx, cancel := context.WithCancel(ctx)
	timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle)

	requestFunc := func() error {
		defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
		return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
	}
	responseFunc := func() error {
		defer timer.SetTimeout(p.Timeouts.UplinkOnly)
		return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
	}

	var responseDonePost = task.OnSuccess(responseFunc, task.Close(link.Writer))
	if err := task.Run(ctx, requestFunc, responseDonePost); err != nil {
		return newError("connection ends").Base(err)
	}

	return nil
}

// setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method
func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, firstPayload []byte) (net.Conn, error) {
	req := &http.Request{
		Method: http.MethodConnect,
		URL:    &url.URL{Host: target},
		Header: make(http.Header),
		Host:   target,
	}

	if user != nil && user.Account != nil {
		account := user.Account.(*Account)
		auth := account.GetUsername() + ":" + account.GetPassword()
		req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
	}

	connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {
		req.Header.Set("Proxy-Connection", "Keep-Alive")

		err := req.Write(rawConn)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		if _, err := rawConn.Write(firstPayload); err != nil {
			rawConn.Close()
			return nil, err
		}

		resp, err := http.ReadResponse(bufio.NewReader(rawConn), req)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		if resp.StatusCode != http.StatusOK {
			rawConn.Close()
			return nil, newError("Proxy responded with non 200 code: " + resp.Status)
		}
		return rawConn, nil
	}

	connectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) {
		pr, pw := io.Pipe()
		req.Body = pr

		var pErr error
		var wg sync.WaitGroup
		wg.Add(1)

		go func() {
			_, pErr = pw.Write(firstPayload)
			wg.Done()
		}()

		resp, err := h2clientConn.RoundTrip(req)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		wg.Wait()
		if pErr != nil {
			rawConn.Close()
			return nil, pErr
		}

		if resp.StatusCode != http.StatusOK {
			rawConn.Close()
			return nil, newError("Proxy responded with non 200 code: " + resp.Status)
		}
		return newHTTP2Conn(rawConn, pw, resp.Body), nil
	}

	cachedH2Mutex.Lock()
	cachedConn, cachedConnFound := cachedH2Conns[dest]
	cachedH2Mutex.Unlock()

	if cachedConnFound {
		rc, cc := cachedConn.rawConn, cachedConn.h2Conn
		if cc.CanTakeNewRequest() {
			proxyConn, err := connectHTTP2(rc, cc)
			if err != nil {
				return nil, err
			}

			return proxyConn, nil
		}
	}

	rawConn, err := dialer.Dial(ctx, dest)
	if err != nil {
		return nil, err
	}

	iConn := rawConn
	if statConn, ok := iConn.(*internet.StatCouterConnection); ok {
		iConn = statConn.Connection
	}

	nextProto := ""
	if tlsConn, ok := iConn.(*tls.Conn); ok {
		if err := tlsConn.Handshake(); err != nil {
			rawConn.Close()
			return nil, err
		}
		nextProto = tlsConn.ConnectionState().NegotiatedProtocol
	}

	switch nextProto {
	case "", "http/1.1":
		return connectHTTP1(rawConn)
	case "h2":
		t := http2.Transport{}
		h2clientConn, err := t.NewClientConn(rawConn)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		proxyConn, err := connectHTTP2(rawConn, h2clientConn)
		if err != nil {
			rawConn.Close()
			return nil, err
		}

		cachedH2Mutex.Lock()
		if cachedH2Conns == nil {
			cachedH2Conns = make(map[net.Destination]h2Conn)
		}

		cachedH2Conns[dest] = h2Conn{
			rawConn: rawConn,
			h2Conn:  h2clientConn,
		}
		cachedH2Mutex.Unlock()

		return proxyConn, err
	default:
		return nil, newError("negotiated unsupported application layer protocol: " + nextProto)
	}
}

func newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn {
	return &http2Conn{Conn: c, in: pipedReqBody, out: respBody}
}

type http2Conn struct {
	net.Conn
	in  *io.PipeWriter
	out io.ReadCloser
}

func (h *http2Conn) Read(p []byte) (n int, err error) {
	return h.out.Read(p)
}

func (h *http2Conn) Write(p []byte) (n int, err error) {
	return h.in.Write(p)
}

func (h *http2Conn) Close() error {
	h.in.Close()
	return h.out.Close()
}

func init() {
	common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
		return NewClient(ctx, config.(*ClientConfig))
	}))
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"gopkg.in/mgo.v2/bson"
	"io"
	"log"
	"net/http"
)

func main() {
	app := NewApp()
	defer app.DisconnectDatabase()

	router := gin.Default()
	router.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})
	router.POST("/client/upload", func(c *gin.Context) {
		reader, err := c.Request.MultipartReader()
		hashsum := ""
		if err != nil {
			c.JSON(http.StatusInternalServerError, err.Error())
			return
		}
		for true {
			part, err := reader.NextPart()
			if err == io.EOF {
				break
			} else if err != nil {
				c.JSON(http.StatusBadRequest, err.Error())
				return
			}
			switch part.FormName() {
			case "payload":
				hashsum, err = app.Store.AddFile(part)
				if err != nil {
					c.JSON(http.StatusInternalServerError,
						err.Error())
					return
				}
				break
			default:
				log.Println("ignoring unknown part: ",
					part.FormName())
			}
		}

		if hashsum != "" {
			c.JSON(http.StatusOK, hashsum)
		} else {
			c.JSON(http.StatusBadRequest, "file part missing")
		}

	})
	repo := router.Group("/client/repo")
	{
		repo.POST("/:name", func(c *gin.Context) {
			repoName := c.Params.ByName("name")
			//TODO validate param
			newRepo := Repo{repoName, 0}
			repoCol, err := app.GetRepoCol()
			if err != nil {
				c.String(500, err.Error())
				return
			}

			err = repoCol.Insert(&newRepo)
			if err != nil {
				log.Println(err)
				c.String(500, "nok")
			} else {
				c.String(200, "OK")
			}
		})
		repo.GET("/", func(c *gin.Context) {
			result := []Repo{}
			repoCol, err := app.GetRepoCol()
			if err != nil {
				c.String(500, err.Error())
				return
			}
			err = repoCol.Find(bson.M{}).All(&result)
			if err != nil {
				log.Println(err)
				c.String(500, "nok")
			} else {
				c.JSON(200, result)
			}
		})
	}
	cluster := router.Group("/cluster")
	{
		cluster.GET("/fetch/:sha3sum", func(c *gin.Context) {
			sha3sum := c.Params.ByName("sha3sum")
			//TODO validate param
			file, err := app.Store.GetFile(sha3sum)
			if err != nil {
				log.Println(err)
				c.String(500, "nok")
			} else {
				io.Copy(c.Writer, file)
			}
		})
		cluster.POST("/init", func(c *gin.Context) {
			initMsg := NodeInitMsg{}
			ok := c.BindWith(&initMsg, binding.JSON)
			if !ok {
				c.String(500, "could not parse body")
				return
			}
			err := initiateCluster(initMsg.Host)
			if err != nil {
				c.String(500, err.Error())
				return
			}
			c.String(200, "ok")
		})
	}

	router.Run(":8080")
}
<commit_msg>move cluster init method to admin endpoint<commit_after>package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"gopkg.in/mgo.v2/bson"
	"io"
	"log"
	"net/http"
)

func main() {
	app := NewApp()
	defer app.DisconnectDatabase()

	router := gin.Default()
	router.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})
	router.POST("/client/upload", func(c *gin.Context) {
		reader, err := c.Request.MultipartReader()
		hashsum := ""
		if err != nil {
			c.JSON(http.StatusInternalServerError, err.Error())
			return
		}
		for true {
			part, err := reader.NextPart()
			if err == io.EOF {
				break
			} else if err != nil {
				c.JSON(http.StatusBadRequest, err.Error())
				return
			}
			switch part.FormName() {
			case "payload":
				hashsum, err = app.Store.AddFile(part)
				if err != nil {
					c.JSON(http.StatusInternalServerError,
						err.Error())
					return
				}
				break
			default:
				log.Println("ignoring unknown part: ",
					part.FormName())
			}
		}

		if hashsum != "" {
			c.JSON(http.StatusOK, hashsum)
		} else {
			c.JSON(http.StatusBadRequest, "file part missing")
		}

	})
	repo := router.Group("/client/repo")
	{
		repo.POST("/:name", func(c *gin.Context) {
			repoName := c.Params.ByName("name")
			//TODO validate param
			newRepo := Repo{repoName, 0}
			repoCol, err := app.GetRepoCol()
			if err != nil {
				c.String(500, err.Error())
				return
			}

			err = repoCol.Insert(&newRepo)
			if err != nil {
				log.Println(err)
				c.String(500, "nok")
			} else {
				c.String(200, "OK")
			}
		})
		repo.GET("/", func(c *gin.Context) {
			result := []Repo{}
			repoCol, err := app.GetRepoCol()
			if err != nil {
				c.String(500, err.Error())
				return
			}
			err = repoCol.Find(bson.M{}).All(&result)
			if err != nil {
				log.Println(err)
				c.String(500, "nok")
			} else {
				c.JSON(200, result)
			}
		})
	}
	cluster := router.Group("/cluster")
	{
		cluster.GET("/fetch/:sha3sum", func(c *gin.Context) {
			sha3sum := c.Params.ByName("sha3sum")
			//TODO validate param
			file, err := app.Store.GetFile(sha3sum)
			if err != nil {
				log.Println(err)
				c.String(500, "nok")
			} else {
				io.Copy(c.Writer, file)
			}
		})
	}
	admin := router.Group("/admin")
	{
		admin.POST("/init", func(c *gin.Context) {
			initMsg := NodeInitMsg{}
			ok := c.BindWith(&initMsg, binding.JSON)
			if !ok {
				c.String(500, "could not parse body")
				return
			}
			err := initiateCluster(initMsg.Host)
			if err != nil {
				c.String(500, err.Error())
				return
			}
			c.String(200, "ok")
		})
	}

	router.Run(":8080")
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/robfig/revel"
	"github.com/robfig/revel/harness"
	"strconv"
)

var cmdRun = &Command{
	UsageLine: "run [import path] [run mode] [port]",
	Short:     "run a Revel application",
	Long: `
Run the Revel web application named by the given import path.

For example, to run the chat room sample application:

    revel run github.com/robfig/revel/samples/chat dev

The run mode is used to select which set of app.conf configuration should
apply and may be used to determine logic in the application itself.

Run mode defaults to "dev".

You can set a port as an optional third parameter.  For example:

    revel run github.com/robfig/revel/samples/chat prod 8080`,
}

func init() {
	cmdRun.Run = runApp
}

func runApp(args []string) {
	if len(args) == 0 {
		errorf("No import path given.\nRun 'revel help run' for usage.\n")
	}

	// Determine the run mode.
	mode := "dev"
	if len(args) >= 2 {
		mode = args[1]
	}

	// Determine the override port, if any.
	port := 0
	if len(args) == 3 {
		var err error
		if port, err = strconv.Atoi(args[2]); err != nil {
			errorf("Failed to parse port as integer: %s", args[2])
		}
	}

	// Find and parse app.conf
	rev.Init(mode, args[0], "")

	rev.INFO.Printf("Running %s (%s) in %s mode\n", rev.AppName, rev.ImportPath, mode)
	rev.TRACE.Println("Base path:", rev.BasePath)

	// If the app is run in "watched" mode, use the harness to run it.
	if rev.Config.BoolDefault("watch", true) && rev.Config.BoolDefault("watch.code", true) {
		if port != 0 {
			rev.HttpPort = port
		}
		harness.NewHarness().Run() // Never returns.
	}

	// Else, just build and run the app.
	app, err := harness.Build()
	if err != nil {
		errorf("Failed to build app: %s", err)
	}
	if port != 0 {
		app.Port = port
	}
	app.Cmd().Run()
}
<commit_msg>Revert "Fix: Don't override the port if no override is specified!"<commit_after>package main

import (
	"github.com/robfig/revel"
	"github.com/robfig/revel/harness"
	"strconv"
)

var cmdRun = &Command{
	UsageLine: "run [import path] [run mode] [port]",
	Short:     "run a Revel application",
	Long: `
Run the Revel web application named by the given import path.

For example, to run the chat room sample application:

    revel run github.com/robfig/revel/samples/chat dev

The run mode is used to select which set of app.conf configuration should
apply and may be used to determine logic in the application itself.

Run mode defaults to "dev".

You can set a port as an optional third parameter.  For example:

    revel run github.com/robfig/revel/samples/chat prod 8080`,
}

func init() {
	cmdRun.Run = runApp
}

func runApp(args []string) {
	if len(args) == 0 {
		errorf("No import path given.\nRun 'revel help run' for usage.\n")
	}

	// Determine the run mode.
	mode := "dev"
	if len(args) >= 2 {
		mode = args[1]
	}

	// Determine the override port, if any.
	port := 0
	if len(args) == 3 {
		var err error
		if port, err = strconv.Atoi(args[2]); err != nil {
			errorf("Failed to parse port as integer: %s", args[2])
		}
	}

	// Find and parse app.conf
	rev.Init(mode, args[0], "")

	rev.INFO.Printf("Running %s (%s) in %s mode\n", rev.AppName, rev.ImportPath, mode)
	rev.TRACE.Println("Base path:", rev.BasePath)

	// If the app is run in "watched" mode, use the harness to run it.
	if rev.Config.BoolDefault("watch", true) && rev.Config.BoolDefault("watch.code", true) {
		rev.HttpPort = port
		harness.NewHarness().Run() // Never returns.
	}

	// Else, just build and run the app.
	app, err := harness.Build()
	if err != nil {
		errorf("Failed to build app: %s", err)
	}
	app.Port = port
	app.Cmd().Run()
}
<|endoftext|>


<commit_before>package proxystorage

import (
	"fmt"
	"time"

	"github.com/prometheus/prometheus/promql"
)

// MultiVisitor runs a set of visitors on the same pass over the node tree
type MultiVisitor struct {
	visitors []promql.Visitor
}

// Visit runs on each node in the tree
func (v *MultiVisitor) Visit(node promql.Node, path []promql.Node) (promql.Visitor, error) {
	var visitorErr error
	for i, visitor := range v.visitors {
		if visitor == nil {
			continue
		}
		visitorRet, err := visitor.Visit(node, path)
		if err != nil {
			visitorErr = err
		}
		v.visitors[i] = visitorRet
	}

	return v, visitorErr

}

// OffsetFinder finds the offset (if any) within the tree
type OffsetFinder struct {
	Found  bool
	Offset time.Duration
	Error  error
}

// Visit runs on each node in the tree
func (o *OffsetFinder) Visit(node promql.Node, _ []promql.Node) (promql.Visitor, error) {
	switch n := node.(type) {
	case *promql.VectorSelector:
		if !o.Found {
			o.Offset = n.Offset
			o.Found = true
		} else {
			if n.Offset != o.Offset {
				o.Error = fmt.Errorf("Mismatched offsets %v %v", n.Offset, o.Offset)
			}
		}

	case *promql.MatrixSelector:
		if !o.Found {
			o.Offset = n.Offset
			o.Found = true
		} else {
			if n.Offset != o.Offset {
				o.Error = fmt.Errorf("Mismatched offsets %v %v", n.Offset, o.Offset)
			}
		}
	}
	if o.Error == nil {
		return o, nil
	} else {
		return nil, nil
	}
}

// OffsetRemover removes any offset found in the node tree
// This is required when we send the queries below as we want to actually *remove* the offset.
type OffsetRemover struct{}

// Visit runs on each node in the tree
func (o *OffsetRemover) Visit(node promql.Node, _ []promql.Node) (promql.Visitor, error) {
	switch n := node.(type) {
	case *promql.VectorSelector:
		n.Offset = 0

	case *promql.MatrixSelector:
		n.Offset = 0
	}
	return o, nil
}

// BooleanFinder uses the given func to determine if something is in there or notret := &promql.VectorSelector{Offset: offset}
type BooleanFinder struct {
	Func  func(promql.Node) bool
	Found int
}

// Visit runs on each node in the tree
func (f *BooleanFinder) Visit(node promql.Node, _ []promql.Node) (promql.Visitor, error) {
	if f.Func(node) {
		f.Found++
		return f, nil
	}
	return f, nil
}
<commit_msg>More lint fixes<commit_after>package proxystorage

import (
	"fmt"
	"time"

	"github.com/prometheus/prometheus/promql"
)

// MultiVisitor runs a set of visitors on the same pass over the node tree
type MultiVisitor struct {
	visitors []promql.Visitor
}

// Visit runs on each node in the tree
func (v *MultiVisitor) Visit(node promql.Node, path []promql.Node) (promql.Visitor, error) {
	var visitorErr error
	for i, visitor := range v.visitors {
		if visitor == nil {
			continue
		}
		visitorRet, err := visitor.Visit(node, path)
		if err != nil {
			visitorErr = err
		}
		v.visitors[i] = visitorRet
	}

	return v, visitorErr

}

// OffsetFinder finds the offset (if any) within the tree
type OffsetFinder struct {
	Found  bool
	Offset time.Duration
	Error  error
}

// Visit runs on each node in the tree
func (o *OffsetFinder) Visit(node promql.Node, _ []promql.Node) (promql.Visitor, error) {
	switch n := node.(type) {
	case *promql.VectorSelector:
		if !o.Found {
			o.Offset = n.Offset
			o.Found = true
		} else {
			if n.Offset != o.Offset {
				o.Error = fmt.Errorf("Mismatched offsets %v %v", n.Offset, o.Offset)
			}
		}

	case *promql.MatrixSelector:
		if !o.Found {
			o.Offset = n.Offset
			o.Found = true
		} else {
			if n.Offset != o.Offset {
				o.Error = fmt.Errorf("Mismatched offsets %v %v", n.Offset, o.Offset)
			}
		}
	}
	if o.Error == nil {
		return o, nil
	}
	return nil, nil
}

// OffsetRemover removes any offset found in the node tree
// This is required when we send the queries below as we want to actually *remove* the offset.
type OffsetRemover struct{}

// Visit runs on each node in the tree
func (o *OffsetRemover) Visit(node promql.Node, _ []promql.Node) (promql.Visitor, error) {
	switch n := node.(type) {
	case *promql.VectorSelector:
		n.Offset = 0

	case *promql.MatrixSelector:
		n.Offset = 0
	}
	return o, nil
}

// BooleanFinder uses the given func to determine if something is in there or notret := &promql.VectorSelector{Offset: offset}
type BooleanFinder struct {
	Func  func(promql.Node) bool
	Found int
}

// Visit runs on each node in the tree
func (f *BooleanFinder) Visit(node promql.Node, _ []promql.Node) (promql.Visitor, error) {
	if f.Func(node) {
		f.Found++
		return f, nil
	}
	return f, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"html/template"
	"math/rand"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/mux"
	"github.com/paked/down"
	"github.com/paked/models"
	"gopkg.in/mgo.v2/bson"
)

func init() {
	var err error

	rand.Seed(time.Now().UnixNano())

	url := os.Getenv("DOWN_MONGODB_URL")
	if url == "" {
		url = "localhost"
	}
	fmt.Println("DB url is: ", url)

	err = models.Init(url, "downbase")
	if err != nil {
		panic(err)
	}
}

func main() {
	r := mux.NewRouter()

	r.HandleFunc("/new_content", postRegisterContentHandler).Methods("POST")
	r.HandleFunc("/view/{key}", viewContentHandler)

	r.PathPrefix("/").Handler(http.FileServer(http.Dir("static/")))

	http.Handle("/", r)

	fmt.Println("Listening on :8080...")
	fmt.Println(http.ListenAndServe(":8080", nil))
}

type Content struct {
	ID   bson.ObjectId `bson:"_id"`
	Down string        `bson: "down"`
	Key  string        `bson: "key"`
}

func (c Content) BID() bson.ObjectId {
	return c.ID
}

func (c Content) C() string {
	return "contents"
}

func postRegisterContentHandler(w http.ResponseWriter, r *http.Request) {
	content := Content{bson.NewObjectId(), r.FormValue("content"), randomString(64)}
	models.Persist(content)

	http.Redirect(w, r, fmt.Sprintf("/view/%v", content.Key), http.StatusFound)
}

func viewContentHandler(w http.ResponseWriter, r *http.Request) {
	var c Content
	key := mux.Vars(r)["key"]

	models.Restore(&c, bson.M{"key": key})

	fmt.Println(c.Down)
	t, err := template.ParseFiles("templates/view.html")
	if err != nil {
		fmt.Println(err)
		return
	}
	t.Execute(w, template.HTML(down.Parse(c.Down)))
}

// Random key generation
func randomString(length int) string {
	bytes := []byte("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	var end []byte
	for i := 0; i < length; i++ {
		r := rand.Intn(len(bytes))
		end = append(end, bytes[r])
	}

	return string(end)
}
<commit_msg>cmd/web: use Content.ID for key<commit_after>package main

import (
	"fmt"
	"html/template"
	"math/rand"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/mux"
	"github.com/paked/down"
	"github.com/paked/models"
	"gopkg.in/mgo.v2/bson"
)

func init() {
	var err error

	rand.Seed(time.Now().UnixNano())

	url := os.Getenv("DOWN_MONGODB_URL")
	if url == "" {
		url = "localhost"
	}
	fmt.Println("DB url is: ", url)

	err = models.Init(url, "downbase")
	if err != nil {
		panic(err)
	}
}

func main() {
	r := mux.NewRouter()

	r.HandleFunc("/new_content", postRegisterContentHandler).Methods("POST")
	r.HandleFunc("/view/{key}", viewContentHandler)

	r.PathPrefix("/").Handler(http.FileServer(http.Dir("static/")))

	http.Handle("/", r)

	fmt.Println("Listening on :8080...")
	fmt.Println(http.ListenAndServe(":8080", nil))
}

type Content struct {
	ID   bson.ObjectId `bson:"_id"`
	Down string        `bson: "down"`
}

func (c Content) BID() bson.ObjectId {
	return c.ID
}

func (c Content) C() string {
	return "contents"
}

func postRegisterContentHandler(w http.ResponseWriter, r *http.Request) {
	content := Content{bson.NewObjectId(), r.FormValue("content")}

	models.Persist(content)

	http.Redirect(w, r, fmt.Sprintf("/view/%x", string(content.ID)), http.StatusFound)
}

func viewContentHandler(w http.ResponseWriter, r *http.Request) {
	var c Content
	id := mux.Vars(r)["key"]

	models.RestoreByID(&c, bson.ObjectIdHex(id))

	fmt.Println(c.Down)
	t, err := template.ParseFiles("templates/view.html")
	if err != nil {
		fmt.Println(err)
		return
	}
	t.Execute(w, template.HTML(down.Parse(c.Down)))
}

// Random key generation
func randomString(length int) string {
	bytes := []byte("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	var end []byte
	for i := 0; i < length; i++ {
		r := rand.Intn(len(bytes))
		end = append(end, bytes[r])
	}

	return string(end)
}
<|endoftext|>


<commit_before>package meta

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/eyecuelab/kit/flect"
	"github.com/google/jsonapi"
	"github.com/jinzhu/inflection"
	"github.com/spf13/viper"
)

type (
	method       string
	inputType    string
	actionHolder []*JsonAPIAction

	JsonAPIAction struct {
		Method string         `json:"method"`
		Name   string         `json:"name"`
		URL    string         `json:"url"`
		Fields []JsonAPIField `json:"fields"`
	}

	JsonAPIField struct {
		Name      string        `json:"name"`
		InputType string        `json:"type"`
		Value     interface{}   `json:"value,omitempty"`
		Required  bool          `json:"required"`
		Options   []FieldOption `json:"options,omitempty"`
		Data      Pagination    `json:"data,omitempty"`
	}

	FieldOption struct {
		Label string      `json:"label"`
		Value interface{} `json:"value"`
	}

	Pagination struct {
		Count int `json:"item_count"`
		Max   int `json:"max"`
		Page  int `json:"page"`
	}
)

const (
	DELETE method = "DELETE"
	GET    method = "GET"
	POST   method = "POST"
	PATCH  method = "PATCH"
	PUT    method = "PUT"

	InputText   inputType = "text"
	InputPass   inputType = "password"
	InputBool   inputType = "bool"
	InputNumber inputType = "number"
	InputSelect inputType = "select"
)

var ah actionHolder

func AddAction(m method, name, urlHelper string) *JsonAPIAction {
	a := JsonAPIAction{
		Method: string(m),
		Name:   name,
		URL:    APIURL(urlHelper),
		Fields: make([]JsonAPIField, 0),
	}
	ah = append(ah, &a)
	return &a
}

func (a *JsonAPIAction) Field(name string, inputType inputType, value interface{}, requred bool) *JsonAPIAction {
	f := JsonAPIField{
		Name:      name,
		InputType: string(inputType),
		Value:     value,
		Required:  requred,
	}
	a.Fields = append(a.Fields, f)
	return a
}

func (a *JsonAPIAction) FieldWithOpts(name string, inputType inputType, value interface{}, requred bool, options []FieldOption) *JsonAPIAction {
	f := JsonAPIField{
		Name:      name,
		InputType: string(inputType),
		Value:     value,
		Required:  requred,
		Options:   options,
	}
	a.Fields = append(a.Fields, f)
	return a
}

func (a *JsonAPIAction) Pagination(data Pagination) *JsonAPIAction {
	f := JsonAPIField{
		Name:      "page",
		InputType: string(InputNumber),
		Value:     data.Page,
		Required:  false,
		Data:      data,
	}
	a.Fields = append(a.Fields, f)
	return a
}

func RenderActions() *jsonapi.Meta {
	clone := ah
	ah = []*JsonAPIAction{}
	return &jsonapi.Meta{"actions": clone}
}

// APIURL full api url for the path
func APIURL(path string) string {
	return fmt.Sprintf("%s%s", viper.GetString("root_url"), path)
}

// JSONApiSelfLink self link helper
func JSONApiSelfLink(i interface{}) *jsonapi.Links {
	name := reflect.TypeOf(i).Name()
	name = inflection.Plural(strings.ToLower(name))

	id := reflect.ValueOf(i).FieldByName("ID").Interface()

	return &jsonapi.Links{
		"self": jsonapi.Link{
			Href: fmt.Sprintf("/%v/%v", name, id)},
	}
}

// JSONApiRefLink ref link helper
func JSONApiRefLink(i interface{}, rel string) *jsonapi.Links {
	fields := flect.Fields(reflect.ValueOf(i))
	name := reflect.TypeOf(i).Name()
	id := reflect.ValueOf(i).FieldByName("ID").Interface()
	for _, f := range fields {
		value, opts, ok := flect.TagValues(f.Tag, "jsonapi")
		if ok && value == "relation" && opts.HasOption(rel) {
			if _, _, ok := flect.TagValues(f.Tag, "link"); ok {
				return &jsonapi.Links{
					"related": fmt.Sprintf(
						"/%v/%v/%v",
						strings.ToLower(inflection.Plural(name)),
						id,
						strings.ToLower(inflection.Plural(f.Name)),
					),
				}
			}
		}
	}
	return &jsonapi.Links{}
}
<commit_msg>Add meta to field option<commit_after>package meta

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/eyecuelab/kit/flect"
	"github.com/google/jsonapi"
	"github.com/jinzhu/inflection"
	"github.com/spf13/viper"
)

type (
	method       string
	inputType    string
	actionHolder []*JsonAPIAction

	JsonAPIAction struct {
		Method string         `json:"method"`
		Name   string         `json:"name"`
		URL    string         `json:"url"`
		Fields []JsonAPIField `json:"fields"`
	}

	JsonAPIField struct {
		Name      string        `json:"name"`
		InputType string        `json:"type"`
		Value     interface{}   `json:"value,omitempty"`
		Required  bool          `json:"required"`
		Options   []FieldOption `json:"options,omitempty"`
		Data      Pagination    `json:"data,omitempty"`
	}

	FieldOption struct {
		Label string                 `json:"label"`
		Value interface{}            `json:"value"`
		Meta  map[string]interface{} `json:"meta"`
	}

	Pagination struct {
		Count int `json:"item_count"`
		Max   int `json:"max"`
		Page  int `json:"page"`
	}
)

const (
	DELETE method = "DELETE"
	GET    method = "GET"
	POST   method = "POST"
	PATCH  method = "PATCH"
	PUT    method = "PUT"

	InputText   inputType = "text"
	InputPass   inputType = "password"
	InputBool   inputType = "bool"
	InputNumber inputType = "number"
	InputSelect inputType = "select"
)

var ah actionHolder

func AddAction(m method, name, urlHelper string) *JsonAPIAction {
	a := JsonAPIAction{
		Method: string(m),
		Name:   name,
		URL:    APIURL(urlHelper),
		Fields: make([]JsonAPIField, 0),
	}
	ah = append(ah, &a)
	return &a
}

func (a *JsonAPIAction) Field(name string, inputType inputType, value interface{}, requred bool) *JsonAPIAction {
	f := JsonAPIField{
		Name:      name,
		InputType: string(inputType),
		Value:     value,
		Required:  requred,
	}
	a.Fields = append(a.Fields, f)
	return a
}

func (a *JsonAPIAction) FieldWithOpts(name string, inputType inputType, value interface{}, requred bool, options []FieldOption) *JsonAPIAction {
	f := JsonAPIField{
		Name:      name,
		InputType: string(inputType),
		Value:     value,
		Required:  requred,
		Options:   options,
	}
	a.Fields = append(a.Fields, f)
	return a
}

func (a *JsonAPIAction) Pagination(data Pagination) *JsonAPIAction {
	f := JsonAPIField{
		Name:      "page",
		InputType: string(InputNumber),
		Value:     data.Page,
		Required:  false,
		Data:      data,
	}
	a.Fields = append(a.Fields, f)
	return a
}

func RenderActions() *jsonapi.Meta {
	clone := ah
	ah = []*JsonAPIAction{}
	return &jsonapi.Meta{"actions": clone}
}

// APIURL full api url for the path
func APIURL(path string) string {
	return fmt.Sprintf("%s%s", viper.GetString("root_url"), path)
}

// JSONApiSelfLink self link helper
func JSONApiSelfLink(i interface{}) *jsonapi.Links {
	name := reflect.TypeOf(i).Name()
	name = inflection.Plural(strings.ToLower(name))

	id := reflect.ValueOf(i).FieldByName("ID").Interface()

	return &jsonapi.Links{
		"self": jsonapi.Link{
			Href: fmt.Sprintf("/%v/%v", name, id)},
	}
}

// JSONApiRefLink ref link helper
func JSONApiRefLink(i interface{}, rel string) *jsonapi.Links {
	fields := flect.Fields(reflect.ValueOf(i))
	name := reflect.TypeOf(i).Name()
	id := reflect.ValueOf(i).FieldByName("ID").Interface()
	for _, f := range fields {
		value, opts, ok := flect.TagValues(f.Tag, "jsonapi")
		if ok && value == "relation" && opts.HasOption(rel) {
			if _, _, ok := flect.TagValues(f.Tag, "link"); ok {
				return &jsonapi.Links{
					"related": fmt.Sprintf(
						"/%v/%v/%v",
						strings.ToLower(inflection.Plural(name)),
						id,
						strings.ToLower(inflection.Plural(f.Name)),
					),
				}
			}
		}
	}
	return &jsonapi.Links{}
}
<|endoftext|>