<commit_before>package control

import (
	"crypto/rsa"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/intelsdilabs/gomit"

	"github.com/intelsdilabs/pulse/control/plugin"
	"github.com/intelsdilabs/pulse/control/plugin/client"
	"github.com/intelsdilabs/pulse/control/plugin/cpolicy"
	"github.com/intelsdilabs/pulse/control/routing"
	"github.com/intelsdilabs/pulse/core"
	"github.com/intelsdilabs/pulse/core/cdata"
	"github.com/intelsdilabs/pulse/core/control_event"
	"github.com/intelsdilabs/pulse/pkg/logger"
)

// control private key (RSA private key)
// control public key (RSA public key)
// Plugin token = token generated by plugin and passed to control
// Session token = plugin seed encrypted by control private key, verified by plugin using control public key
//

type executablePlugins []plugin.ExecutablePlugin

type pluginControl struct {
	// TODO, going to need coordination on changing of these
	RunningPlugins executablePlugins
	Started        bool

	controlPrivKey *rsa.PrivateKey
	controlPubKey  *rsa.PublicKey
	eventManager   *gomit.EventController

	pluginManager managesPlugins
	metricCatalog catalogsMetrics
	pluginRunner  runsPlugins

	strategy RoutingStrategy
}

type runsPlugins interface {
	Start() error
	Stop() []error
	AvailablePlugins() *availablePlugins
	AddDelegates(delegates ...gomit.Delegator)
	SetMetricCatalog(c catalogsMetrics)
	SetPluginManager(m managesPlugins)
	Monitor() *monitor
}

type managesPlugins interface {
	LoadPlugin(string) (*loadedPlugin, error)
	UnloadPlugin(CatalogedPlugin) error
	LoadedPlugins() *loadedPlugins
	SetMetricCatalog(catalogsMetrics)
	GenerateArgs() plugin.Arg
}

type catalogsMetrics interface {
	Get([]string, int) (*metricType, error)
	Add(*metricType)
	AddLoadedMetricType(*loadedPlugin, core.MetricType)
	Item() (string, []*metricType)
	Next() bool
	Subscribe([]string, int) error
	Unsubscribe([]string, int) error
	Table() map[string][]*metricType
	GetPlugin([]string, int) (*loadedPlugin, error)
}

// Returns a new pluginControl instance
func New() *pluginControl {

	c := &pluginControl{}
	// Initialize components
	//
	// Event Manager
	c.eventManager = gomit.NewEventController()
	logger.Debug("control.init", "event controller created")

	// Metric Catalog
	c.metricCatalog = newMetricCatalog()
	logger.Debug("control.init", "metric catalog created")

	// Plugin Manager
	c.pluginManager = newPluginManager()
	logger.Debug("control.init", "plugin manager created")
	//    Plugin Manager needs a reference to the metric catalog
	c.pluginManager.SetMetricCatalog(c.metricCatalog)

	// Plugin Runner
	c.pluginRunner = newRunner()
	logger.Debug("control.init", "runner created")
	c.pluginRunner.AddDelegates(c.eventManager)
	c.pluginRunner.SetMetricCatalog(c.metricCatalog)
	c.pluginRunner.SetPluginManager(c.pluginManager)

	// Strategy
	c.strategy = &routing.RoundRobinStrategy{}

	// Wire event manager

	// Start stuff
	err := c.pluginRunner.Start()
	if err != nil {
		panic(err)
	}

	return c
}

// Begin handling load, unload, and inventory
func (p *pluginControl) Start() error {
	// Start pluginManager when pluginControl starts
	p.Started = true
	logger.Debug("control.start", "started")
	return nil
}

func (p *pluginControl) Stop() {
	p.Started = false
	logger.Debug("control.stop", "stopped")
}

// Load is the public method to load a plugin into
// the LoadedPlugins array and issue an event when
// successful.
func (p *pluginControl) Load(path string) error {
	// logger.Debug("control.load", fmt.Sprintf("load called on path: %s", path))
	if !p.Started {
		return errors.New("Must start Controller before calling Load()")
	}

	if _, err := p.pluginManager.LoadPlugin(path); err != nil {
		return err
	}

	// defer sending event
	event := new(control_event.LoadPluginEvent)
	defer p.eventManager.Emit(event)
	return nil
}

func (p *pluginControl) Unload(pl CatalogedPlugin) error {
	err := p.pluginManager.UnloadPlugin(pl)
	if err != nil {
		return err
	}

	event := new(control_event.UnloadPluginEvent)
	defer p.eventManager.Emit(event)
	return nil
}

func (p *pluginControl) SwapPlugins(inPath string, out CatalogedPlugin) error {

	lp, err := p.pluginManager.LoadPlugin(inPath)
	if err != nil {
		return err
	}

	err = p.pluginManager.UnloadPlugin(out)
	if err != nil {
		err2 := p.pluginManager.UnloadPlugin(lp)
		if err2 != nil {
			return errors.New("failed to rollback after error" + err2.Error() + " -- " + err.Error())
		}
		return err
	}

	event := new(control_event.SwapPluginsEvent)
	defer p.eventManager.Emit(event)

	return nil
}

func (p *pluginControl) generateArgs() plugin.Arg {
	a := plugin.Arg{
		ControlPubKey: p.controlPubKey,
		PluginLogPath: "/tmp/pulse-test-plugin.log",
	}
	return a
}

// SubscribeMetricType validates the given config data, and if valid
// returns a MetricType with a config.  On error a collection of errors is returned
// either from config data processing, or the inability to find the metric.
func (p *pluginControl) SubscribeMetricType(mt core.MetricType, cd *cdata.ConfigDataNode) (core.MetricType, []error) {
	logger.Info("control.subscribe", fmt.Sprintf("subscription called with: %s", mt.Namespace()))
	subErrs := make([]error, 0)

	m, err := p.metricCatalog.Get(mt.Namespace(), mt.Version())
	if err != nil {
		subErrs = append(subErrs, err)
		return nil, subErrs
	}

	// No metric found return error.
	if m == nil {
		subErrs = append(subErrs, errors.New(fmt.Sprintf("no metric found cannot subscribe: (%s) version(%d)", mt.Namespace(), mt.Version())))
		return nil, subErrs
	}

	if m.policy == nil {
		m.policy = cpolicy.NewPolicyNode()
	}
	ncdTable, errs := m.policy.Process(cd.Table())
	if errs != nil && errs.HasErrors() {
		return nil, errs.Errors()
	}
	m.config = cdata.FromTable(*ncdTable)

	m.Subscribe()
	e := &control_event.MetricSubscriptionEvent{
		MetricNamespace: m.Namespace(),
		Version:         m.Version(),
	}
	defer p.eventManager.Emit(e)

	return m, nil
}

// UnsubscribeMetricType unsubscribes a MetricType
// If subscriptions fall below zero we will panic.
func (p *pluginControl) UnsubscribeMetricType(mt core.MetricType) {
	logger.Info("control.subscribe", fmt.Sprintf("unsubscription called with: %s", mt.Namespace()))
	err := p.metricCatalog.Unsubscribe(mt.Namespace(), mt.Version())
	if err != nil {
		// panic because if a metric falls below 0, something bad has happened
		panic(err.Error())
	}
	e := &control_event.MetricUnsubscriptionEvent{
		MetricNamespace: mt.Namespace(),
	}
	p.eventManager.Emit(e)
}

// SetMonitorOptions exposes monitors options
func (p *pluginControl) SetMonitorOptions(options ...monitorOption) {
	p.pluginRunner.Monitor().Option(options...)
}

// the public interface for a plugin
// this should be the contract for
// how mgmt modules know a plugin
type CatalogedPlugin interface {
	Name() string
	Version() int
	TypeName() string
	Status() string
	LoadedTimestamp() int64
}

// the collection of cataloged plugins used
// by mgmt modules
type PluginCatalog []CatalogedPlugin

// returns a copy of the plugin catalog
func (p *pluginControl) PluginCatalog() PluginCatalog {
	table := p.pluginManager.LoadedPlugins().Table()
	pc := make([]CatalogedPlugin, len(table))
	for i, lp := range table {
		pc[i] = lp
	}
	return pc
}

func (p *pluginControl) MetricCatalog() []core.MetricType {
	var c []core.MetricType
	for p.metricCatalog.Next() {
		_, mts := p.metricCatalog.Item()
		for _, mt := range mts {
			c = append(c, mt)
		}
	}
	return c
}

func (p *pluginControl) MetricExists(mns []string, ver int) bool {
	_, err := p.metricCatalog.Get(mns, ver)
	if err == nil {
		return true
	}
	return false
}

// Calls collector plugins for the metric types and returns collection response containing metrics. Blocking method.
func (p *pluginControl) CollectMetrics(metricTypes []core.MetricType, config *cdata.ConfigDataNode, deadline time.Time) ([]core.Metric, error) {

	pluginToMetricMap, err := groupMetricTypesByPlugin(p.metricCatalog, metricTypes)
	if err != nil {
		return []core.Metric{}, err
	}

	//
	metrics := []core.Metric{}

	// For each available plugin call available plugin using RPC client and wait for response (goroutines)
	for pluginKey, pmt := range pluginToMetricMap {
		// fmt.Printf("plugin: (%s) has (%d) metrics to gather\n", pluginKey, metrics.Count())

		pool, err := p.getPool(pluginKey)
		if err != nil {
			return nil, err
		}

		ap, err := p.getAvailablePlugin(pool)
		if err != nil {
			return nil, err
		}

		// Attempt collection on selected available plugin
		ap.hitCount++
		ap.lastHitTime = time.Now()

		cli, ok := ap.Client.(client.PluginCollectorClient)
		if !ok {
			return []core.Metric{}, errors.New("unable to cast client to PluginCollectorClient")
		}

		metrics, err = cli.CollectMetrics(pmt.metricTypes)
		if err != nil {
			return nil, err
		}
	}

	return metrics, nil
}

// ------------------- helper struct and function for grouping metrics types ------

// just a tuple of loadedPlugin and metricType slice
type pluginMetricTypes struct {
	plugin      *loadedPlugin
	metricTypes []core.MetricType
}

func (p *pluginMetricTypes) Count() int {
	return len(p.metricTypes)
}

// groupMetricTypesByPlugin groups metricTypes by a plugin.Key() and returns appropriate structure
func groupMetricTypesByPlugin(cat catalogsMetrics, metricTypes []core.MetricType) (map[string]pluginMetricTypes, error) {
	pmts := make(map[string]pluginMetricTypes)
	// For each plugin type select a matching available plugin to call
	for _, mt := range metricTypes {

		// This is set to choose the newest and not pin version. TODO, be sure version is set to -1 if not provided by user on Task creation.
		lp, err := cat.GetPlugin(mt.Namespace(), -1)
		if err != nil {
			return map[string]pluginMetricTypes{}, err
		}
		// if loaded plugin is nil, we have failed.  return error
		if lp == nil {
			return map[string]pluginMetricTypes{}, errors.New(fmt.Sprintf("Metric missing: %s", strings.Join(mt.Namespace(), "/")))
		}

		// fmt.Printf("Found plugin (%s v%d) for metric (%s)\n", lp.Name(), lp.Version(), strings.Join(m.Namespace(), "/"))

		key := lp.Key()

		//
		pmt, _ := pmts[key]
		pmt.plugin = lp
		pmt.metricTypes = append(pmt.metricTypes, mt)
		pmts[key] = pmt

	}
	return pmts, nil
}

// getPool finds a pool for a given pluginKey and checks is not empty
func (p *pluginControl) getPool(pluginKey string) (*availablePluginPool, error) {

	pool := p.pluginRunner.AvailablePlugins().Collectors.GetPluginPool(pluginKey)

	if pool == nil {
		// return error because this plugin has no pool
		return nil, errors.New(fmt.Sprintf("no available plugins for plugin type (%s)", pluginKey))
	}

	// TODO: Lock this apPool so we are the only one operating on it.
	if pool.Count() == 0 {
		// return error indicating we have no available plugins to call for Collect
		return nil, errors.New(fmt.Sprintf("there is no availablePlugins in pool (%s)", pluginKey))
	}
	return pool, nil
}

// getAvailablePlugin finds a "best" availablePlugin to be asked for metrics
func (p *pluginControl) getAvailablePlugin(pool *availablePluginPool) (*availablePlugin, error) {

	// Use a router strategy to select an available plugin from the pool
	ap, err := pool.SelectUsingStrategy(p.strategy)
	if err != nil {
		return nil, err
	}

	if ap == nil {
		return nil, errors.New(fmt.Sprintf("no available plugin selected in pool %v", pool))
	}

	return ap, nil
}
<commit_msg>collectmetrics helper methods transformed to functions<commit_after>package control

import (
	"crypto/rsa"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/intelsdilabs/gomit"

	"github.com/intelsdilabs/pulse/control/plugin"
	"github.com/intelsdilabs/pulse/control/plugin/client"
	"github.com/intelsdilabs/pulse/control/plugin/cpolicy"
	"github.com/intelsdilabs/pulse/control/routing"
	"github.com/intelsdilabs/pulse/core"
	"github.com/intelsdilabs/pulse/core/cdata"
	"github.com/intelsdilabs/pulse/core/control_event"
	"github.com/intelsdilabs/pulse/pkg/logger"
)

// control private key (RSA private key)
// control public key (RSA public key)
// Plugin token = token generated by plugin and passed to control
// Session token = plugin seed encrypted by control private key, verified by plugin using control public key
//

type executablePlugins []plugin.ExecutablePlugin

type pluginControl struct {
	// TODO, going to need coordination on changing of these
	RunningPlugins executablePlugins
	Started        bool

	controlPrivKey *rsa.PrivateKey
	controlPubKey  *rsa.PublicKey
	eventManager   *gomit.EventController

	pluginManager managesPlugins
	metricCatalog catalogsMetrics
	pluginRunner  runsPlugins

	strategy RoutingStrategy
}

type runsPlugins interface {
	Start() error
	Stop() []error
	AvailablePlugins() *availablePlugins
	AddDelegates(delegates ...gomit.Delegator)
	SetMetricCatalog(c catalogsMetrics)
	SetPluginManager(m managesPlugins)
	Monitor() *monitor
}

type managesPlugins interface {
	LoadPlugin(string) (*loadedPlugin, error)
	UnloadPlugin(CatalogedPlugin) error
	LoadedPlugins() *loadedPlugins
	SetMetricCatalog(catalogsMetrics)
	GenerateArgs() plugin.Arg
}

type catalogsMetrics interface {
	Get([]string, int) (*metricType, error)
	Add(*metricType)
	AddLoadedMetricType(*loadedPlugin, core.MetricType)
	Item() (string, []*metricType)
	Next() bool
	Subscribe([]string, int) error
	Unsubscribe([]string, int) error
	Table() map[string][]*metricType
	GetPlugin([]string, int) (*loadedPlugin, error)
}

// Returns a new pluginControl instance
func New() *pluginControl {

	c := &pluginControl{}
	// Initialize components
	//
	// Event Manager
	c.eventManager = gomit.NewEventController()
	logger.Debug("control.init", "event controller created")

	// Metric Catalog
	c.metricCatalog = newMetricCatalog()
	logger.Debug("control.init", "metric catalog created")

	// Plugin Manager
	c.pluginManager = newPluginManager()
	logger.Debug("control.init", "plugin manager created")
	//    Plugin Manager needs a reference to the metric catalog
	c.pluginManager.SetMetricCatalog(c.metricCatalog)

	// Plugin Runner
	c.pluginRunner = newRunner()
	logger.Debug("control.init", "runner created")
	c.pluginRunner.AddDelegates(c.eventManager)
	c.pluginRunner.SetMetricCatalog(c.metricCatalog)
	c.pluginRunner.SetPluginManager(c.pluginManager)

	// Strategy
	c.strategy = &routing.RoundRobinStrategy{}

	// Wire event manager

	// Start stuff
	err := c.pluginRunner.Start()
	if err != nil {
		panic(err)
	}

	return c
}

// Begin handling load, unload, and inventory
func (p *pluginControl) Start() error {
	// Start pluginManager when pluginControl starts
	p.Started = true
	logger.Debug("control.start", "started")
	return nil
}

func (p *pluginControl) Stop() {
	p.Started = false
	logger.Debug("control.stop", "stopped")
}

// Load is the public method to load a plugin into
// the LoadedPlugins array and issue an event when
// successful.
func (p *pluginControl) Load(path string) error {
	// logger.Debug("control.load", fmt.Sprintf("load called on path: %s", path))
	if !p.Started {
		return errors.New("Must start Controller before calling Load()")
	}

	if _, err := p.pluginManager.LoadPlugin(path); err != nil {
		return err
	}

	// defer sending event
	event := new(control_event.LoadPluginEvent)
	defer p.eventManager.Emit(event)
	return nil
}

func (p *pluginControl) Unload(pl CatalogedPlugin) error {
	err := p.pluginManager.UnloadPlugin(pl)
	if err != nil {
		return err
	}

	event := new(control_event.UnloadPluginEvent)
	defer p.eventManager.Emit(event)
	return nil
}

func (p *pluginControl) SwapPlugins(inPath string, out CatalogedPlugin) error {

	lp, err := p.pluginManager.LoadPlugin(inPath)
	if err != nil {
		return err
	}

	err = p.pluginManager.UnloadPlugin(out)
	if err != nil {
		err2 := p.pluginManager.UnloadPlugin(lp)
		if err2 != nil {
			return errors.New("failed to rollback after error" + err2.Error() + " -- " + err.Error())
		}
		return err
	}

	event := new(control_event.SwapPluginsEvent)
	defer p.eventManager.Emit(event)

	return nil
}

func (p *pluginControl) generateArgs() plugin.Arg {
	a := plugin.Arg{
		ControlPubKey: p.controlPubKey,
		PluginLogPath: "/tmp/pulse-test-plugin.log",
	}
	return a
}

// SubscribeMetricType validates the given config data, and if valid
// returns a MetricType with a config.  On error a collection of errors is returned
// either from config data processing, or the inability to find the metric.
func (p *pluginControl) SubscribeMetricType(mt core.MetricType, cd *cdata.ConfigDataNode) (core.MetricType, []error) {
	logger.Info("control.subscribe", fmt.Sprintf("subscription called with: %s", mt.Namespace()))
	subErrs := make([]error, 0)

	m, err := p.metricCatalog.Get(mt.Namespace(), mt.Version())
	if err != nil {
		subErrs = append(subErrs, err)
		return nil, subErrs
	}

	// No metric found return error.
	if m == nil {
		subErrs = append(subErrs, errors.New(fmt.Sprintf("no metric found cannot subscribe: (%s) version(%d)", mt.Namespace(), mt.Version())))
		return nil, subErrs
	}

	if m.policy == nil {
		m.policy = cpolicy.NewPolicyNode()
	}
	ncdTable, errs := m.policy.Process(cd.Table())
	if errs != nil && errs.HasErrors() {
		return nil, errs.Errors()
	}
	m.config = cdata.FromTable(*ncdTable)

	m.Subscribe()
	e := &control_event.MetricSubscriptionEvent{
		MetricNamespace: m.Namespace(),
		Version:         m.Version(),
	}
	defer p.eventManager.Emit(e)

	return m, nil
}

// UnsubscribeMetricType unsubscribes a MetricType
// If subscriptions fall below zero we will panic.
func (p *pluginControl) UnsubscribeMetricType(mt core.MetricType) {
	logger.Info("control.subscribe", fmt.Sprintf("unsubscription called with: %s", mt.Namespace()))
	err := p.metricCatalog.Unsubscribe(mt.Namespace(), mt.Version())
	if err != nil {
		// panic because if a metric falls below 0, something bad has happened
		panic(err.Error())
	}
	e := &control_event.MetricUnsubscriptionEvent{
		MetricNamespace: mt.Namespace(),
	}
	p.eventManager.Emit(e)
}

// SetMonitorOptions exposes monitors options
func (p *pluginControl) SetMonitorOptions(options ...monitorOption) {
	p.pluginRunner.Monitor().Option(options...)
}

// the public interface for a plugin
// this should be the contract for
// how mgmt modules know a plugin
type CatalogedPlugin interface {
	Name() string
	Version() int
	TypeName() string
	Status() string
	LoadedTimestamp() int64
}

// the collection of cataloged plugins used
// by mgmt modules
type PluginCatalog []CatalogedPlugin

// returns a copy of the plugin catalog
func (p *pluginControl) PluginCatalog() PluginCatalog {
	table := p.pluginManager.LoadedPlugins().Table()
	pc := make([]CatalogedPlugin, len(table))
	for i, lp := range table {
		pc[i] = lp
	}
	return pc
}

func (p *pluginControl) MetricCatalog() []core.MetricType {
	var c []core.MetricType
	for p.metricCatalog.Next() {
		_, mts := p.metricCatalog.Item()
		for _, mt := range mts {
			c = append(c, mt)
		}
	}
	return c
}

func (p *pluginControl) MetricExists(mns []string, ver int) bool {
	_, err := p.metricCatalog.Get(mns, ver)
	if err == nil {
		return true
	}
	return false
}

// Calls collector plugins for the metric types and returns collection response containing metrics. Blocking method.
func (p *pluginControl) CollectMetrics(metricTypes []core.MetricType, config *cdata.ConfigDataNode, deadline time.Time) ([]core.Metric, error) {

	pluginToMetricMap, err := groupMetricTypesByPlugin(p.metricCatalog, metricTypes)
	if err != nil {
		return []core.Metric{}, err
	}

	metrics := []core.Metric{}

	// For each available plugin call available plugin using RPC client and wait for response (goroutines)
	for pluginKey, pmt := range pluginToMetricMap {
		// fmt.Printf("plugin: (%s) has (%d) metrics to gather\n", pluginKey, metrics.Count())

		pool, err := getPool(pluginKey, p.pluginRunner.AvailablePlugins())
		if err != nil {
			return nil, err
		}

		ap, err := getAvailablePlugin(pool, p.strategy)
		if err != nil {
			return nil, err
		}

		// Attempt collection on selected available plugin
		ap.hitCount++
		ap.lastHitTime = time.Now()

		cli, ok := ap.Client.(client.PluginCollectorClient)
		if !ok {
			return []core.Metric{}, errors.New("unable to cast client to PluginCollectorClient")
		}

		metrics, err = cli.CollectMetrics(pmt.metricTypes)
		if err != nil {
			return nil, err
		}
	}

	return metrics, nil
}

// ------------------- helper struct and function for grouping metrics types ------

// just a tuple of loadedPlugin and metricType slice
type pluginMetricTypes struct {
	plugin      *loadedPlugin
	metricTypes []core.MetricType
}

func (p *pluginMetricTypes) Count() int {
	return len(p.metricTypes)
}

// groupMetricTypesByPlugin groups metricTypes by a plugin.Key() and returns appropriate structure
func groupMetricTypesByPlugin(cat catalogsMetrics, metricTypes []core.MetricType) (map[string]pluginMetricTypes, error) {
	pmts := make(map[string]pluginMetricTypes)
	// For each plugin type select a matching available plugin to call
	for _, mt := range metricTypes {

		// This is set to choose the newest and not pin version. TODO, be sure version is set to -1 if not provided by user on Task creation.
		lp, err := cat.GetPlugin(mt.Namespace(), -1)
		if err != nil {
			return map[string]pluginMetricTypes{}, err
		}
		// if loaded plugin is nil, we have failed.  return error
		if lp == nil {
			return map[string]pluginMetricTypes{}, errors.New(fmt.Sprintf("Metric missing: %s", strings.Join(mt.Namespace(), "/")))
		}

		// fmt.Printf("Found plugin (%s v%d) for metric (%s)\n", lp.Name(), lp.Version(), strings.Join(m.Namespace(), "/"))

		key := lp.Key()

		//
		pmt, _ := pmts[key]
		pmt.plugin = lp
		pmt.metricTypes = append(pmt.metricTypes, mt)
		pmts[key] = pmt

	}
	return pmts, nil
}

// getPool finds a pool for a given pluginKey and checks is not empty
func getPool(pluginKey string, availablePlugins *availablePlugins) (*availablePluginPool, error) {

	pool := availablePlugins.Collectors.GetPluginPool(pluginKey)

	if pool == nil {
		// return error because this plugin has no pool
		return nil, errors.New(fmt.Sprintf("no available plugins for plugin type (%s)", pluginKey))
	}

	// TODO: Lock this apPool so we are the only one operating on it.
	if pool.Count() == 0 {
		// return error indicating we have no available plugins to call for Collect
		return nil, errors.New(fmt.Sprintf("there is no availablePlugins in pool (%s)", pluginKey))
	}
	return pool, nil
}

// getAvailablePlugin finds a "best" availablePlugin to be asked for metrics
func getAvailablePlugin(pool *availablePluginPool, strategy RoutingStrategy) (*availablePlugin, error) {

	// Use a router strategy to select an available plugin from the pool
	ap, err := pool.SelectUsingStrategy(strategy)
	if err != nil {
		return nil, err
	}

	if ap == nil {
		return nil, errors.New(fmt.Sprintf("no available plugin selected in pool %v", pool))
	}

	return ap, nil
}
<|endoftext|>


<commit_before>// Convert BlogSum DB (sqlite3) to GOB
package main

import (
	"database/sql"
	"encoding/gob"
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

const timeFormat = "2006-Jan-02"

/*
BlogSum DB Schema:

CREATE TABLE articles (
	id integer primary key,
	date date,
	title text,
	uri text,
	body text,
	tags text,
	enabled boolean,
	author text);

CREATE TABLE comments (
	id integer primary key,
	article_id integer,
	date date,
	name text,
	email text,
	url text,
	comment text,
	enabled boolean);
*/

type Articles []Article

type Article struct {
	Date     time.Time
	Title    string
	Slug     string // Uri
	Body     string
	Tags     Tags
	Enabled  bool
	Author   string
	Comments Comments
}

type Tags []string

type Comments []Comment

type Comment struct {
	Date    time.Time
	Name    string
	Email   string
	URL     string
	Comment string
	Enabled bool
}

var (
	input  string
	output string
)

func (a Articles) Write(fname string) {
	w, err := os.Create(fname)
	if err != nil {
		log.Fatal("write ", err)
	}
	defer w.Close()
	enc := gob.NewEncoder(w)
	err = enc.Encode(a)
	if err != nil {
		log.Fatal("encode ", err)
	}
}

func getTags(tags string) Tags {
	t := strings.Split(tags, ",")
	for i := range t {
		t[i] = strings.TrimSpace(t[i])
	}
	return t
}

func getDate(date string) time.Time {
	d, err := time.Parse("2006-01-02 15:04:05", date)
	if err != nil {
		log.Fatal("parse time ", err)
	}
	return d
}

func (a Article) String() string {
	return fmt.Sprintf("%s %s %s", a.Date.Format(timeFormat), a.Slug, a.Tags)
}

func (c Comment) String() string {
	return fmt.Sprintf("%s Commentar from %s", c.Date.Format(timeFormat), c.Name)
}

func getComments(db *sql.DB, id int) (C Comments) {
	rows, err := db.Query("SELECT date,name,email,url,comment,enabled FROM comments WHERE article_id=?", id)
	if err != nil {
		log.Fatal("query comment ", err)
	}
	defer rows.Close()

	for rows.Next() {
		var (
			date    string
			name    string
			email   []byte
			url     []byte
			comment string
			enabled bool
		)

		err := rows.Scan(&date, &name, &email, &url, &comment, &enabled)
		if err != nil {
			log.Fatal("scan comment ", err)
		}

		c := Comment{
			Date:    getDate(date),
			Name:    name,
			Email:   string(email),
			URL:     string(url),
			Comment: comment,
			Enabled: enabled,
		}

		fmt.Println(c)
		C = append(C, c)
	}

	return C
}

func getArticles(db *sql.DB) (A Articles) {
	rows, err := db.Query("SELECT id,date,title,uri,body,tags,enabled,author FROM articles")
	if err != nil {
		log.Fatal("query article ", err)
	}
	defer rows.Close()

	for rows.Next() {
		var (
			id      int
			date    string
			title   string
			uri     string
			body    string
			tags    string
			enabled bool
			author  string
		)

		err := rows.Scan(&id, &date, &title, &uri, &body, &tags, &enabled, &author)
		if err != nil {
			log.Fatal("scan article ", err)
		}

		a := Article{
			Date:     getDate(date),
			Title:    title,
			Slug:     uri,
			Body:     body,
			Tags:     getTags(tags),
			Enabled:  enabled,
			Author:   author,
			Comments: getComments(db, id),
		}

		fmt.Println(a)
		A = append(A, a)
	}

	return A
}

func init() {
	flag.StringVar(&input, "input", "site.db", "input file (sqlite3)")
	flag.StringVar(&output, "output", "site.gob", "output file (gob)")
	flag.Parse()
}

func main() {
	db, err := sql.Open("sqlite3", input)
	if err != nil {
		log.Fatal("open ", err)
	}
	defer db.Close()
	getArticles(db).Write(output)
}
<commit_msg>Fix time.Time on Mac<commit_after>// Convert BlogSum DB (sqlite3) to GOB
package main

import (
	"database/sql"
	"encoding/gob"
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

const timeFormat = "2006-Jan-02"

/*
BlogSum DB Schema:

CREATE TABLE articles (
	id integer primary key,
	date date,
	title text,
	uri text,
	body text,
	tags text,
	enabled boolean,
	author text);

CREATE TABLE comments (
	id integer primary key,
	article_id integer,
	date date,
	name text,
	email text,
	url text,
	comment text,
	enabled boolean);
*/

type Articles []Article

type Article struct {
	Date     time.Time
	Title    string
	Slug     string // Uri
	Body     string
	Tags     Tags
	Enabled  bool
	Author   string
	Comments Comments
}

type Tags []string

type Comments []Comment

type Comment struct {
	Date    time.Time
	Name    string
	Email   string
	URL     string
	Comment string
	Enabled bool
}

var (
	input  string
	output string
)

func (a Articles) write(fname string) {
	w, err := os.Create(fname)
	if err != nil {
		log.Fatal("create ", err)
	}
	defer w.Close()
	enc := gob.NewEncoder(w)
	err = enc.Encode(a)
	if err != nil {
		log.Fatal("encode ", err)
	}
}

func getTags(tags string) Tags {
	t := strings.Split(tags, ",")
	for i := range t {
		t[i] = strings.TrimSpace(t[i])
	}
	return t
}

func getDate(date string) time.Time {
	d, err := time.Parse("2006-01-02 15:04:05", date)
	if err != nil {
		log.Fatal("parse time ", err)
	}
	return d
}

func (a Article) String() string {
	return fmt.Sprintf("%s %s %s", a.Date.Format(timeFormat), a.Slug, a.Tags)
}

func (c Comment) String() string {
	return fmt.Sprintf("%s Commentar from %s", c.Date.Format(timeFormat), c.Name)
}

func getComments(db *sql.DB, id int) (C Comments) {
	rows, err := db.Query("SELECT date,name,email,url,comment,enabled FROM comments WHERE article_id=?", id)
	if err != nil {
		log.Fatal("query comment ", err)
	}
	defer rows.Close()

	for rows.Next() {
		var (
			date    time.Time
			name    string
			email   []byte
			url     []byte
			comment string
			enabled bool
		)

		err := rows.Scan(&date, &name, &email, &url, &comment, &enabled)
		if err != nil {
			log.Fatal("scan comment ", err)
		}

		c := Comment{
			Date:    date,
			Name:    name,
			Email:   string(email),
			URL:     string(url),
			Comment: comment,
			Enabled: enabled,
		}

		fmt.Println(c)
		C = append(C, c)
	}

	return C
}

func getArticles(db *sql.DB) (A Articles) {
	rows, err := db.Query("SELECT id,date,title,uri,body,tags,enabled,author FROM articles")
	if err != nil {
		log.Fatal("query article ", err)
	}
	defer rows.Close()

	for rows.Next() {
		var (
			id      int
			date    time.Time
			title   string
			uri     string
			body    string
			tags    string
			enabled bool
			author  string
		)

		err := rows.Scan(&id, &date, &title, &uri, &body, &tags, &enabled, &author)
		if err != nil {
			log.Fatal("scan article ", err)
		}

		a := Article{
			Date:     date,
			Title:    title,
			Slug:     uri,
			Body:     body,
			Tags:     getTags(tags),
			Enabled:  enabled,
			Author:   author,
			Comments: getComments(db, id),
		}

		fmt.Println(a)
		A = append(A, a)
	}

	return A
}

func init() {
	flag.StringVar(&input, "input", "site.db", "input file (sqlite3)")
	flag.StringVar(&output, "output", "site.gob", "output file (gob)")
	flag.Parse()
}

func main() {
	db, err := sql.Open("sqlite3", input)
	if err != nil {
		log.Fatal("open ", err)
	}
	defer db.Close()
	getArticles(db).write(output)
}
<|endoftext|>


<commit_before>package memap

import (
	"testing"
	"encoding/binary"
	"fmt"
	"os"
	"github.com/edsrzf/mmap-go"
	"strconv"
	"github.com/BenJoyenConseil/bluckdb/util"
	"errors"
)

type Page []byte

func (p Page) use() int {
	return int(binary.LittleEndian.Uint16(p[4094 : 4096]))
}

func (p Page) add(v...byte) {
	copy(p[p.use():], v)
	use := make([]byte, 2)
	binary.LittleEndian.PutUint16(use, uint16(p.use() + len(v)))
	copy(p[4094:], use)
}

func (p Page) put(k, v string) error{
	headerSize := 4
	lens := make([]byte, headerSize)
	lenK := len(k)
	lenV := len(v)
	binary.LittleEndian.PutUint16(lens, uint16(lenK))
	binary.LittleEndian.PutUint16(lens[2:], uint16(lenV))
	payload := lenK + lenV + headerSize
	if p.left() >= payload {
		p.add(lens...)
		p.add([]byte(k)...)
		p.add([]byte(v)...)
		return nil
	} else {
		return errors.New("The page is full. use = " + strconv.Itoa(p.use()))
	}
}

func (p Page) left() int {
	return 4092 - p.use()
}

func (p Page) ld() int {
	return int(binary.LittleEndian.Uint16(p[4092:]))
}

func (p Page) setLd(v int) {
	binary.LittleEndian.PutUint16(p[4092:], uint16(v))
}

func (p Page) get(k string) string {

	for i := 0; i <= p.use(); {

		lenKey := int(binary.LittleEndian.Uint16(p[i : i + 2]))
		lenVal := int(binary.LittleEndian.Uint16(p[i + 2 : i + 4]))

		currentKey := string(p[i + 4 : i + 4 + lenKey])
		if currentKey == k {
			return string(p[ i + 4 + lenKey : i + 4 + lenKey + lenVal])
		}else {
			i += 4 + lenKey + lenVal
		}
	}
	return ""
}

type PageIterator struct {
	p Page
	current int
}

func (it *PageIterator) next() (k, v string) {
	lenKey := int(binary.LittleEndian.Uint16(it.p[it.current : it.current + 2]))
	lenVal := int(binary.LittleEndian.Uint16(it.p[it.current + 2 : it.current + 4]))

	key := string(it.p[it.current + 4 : it.current + 4 + lenKey])
	value := string(it.p[ it.current + 4 + lenKey : it.current + 4 + lenKey + lenVal])

	it.current += lenKey + lenVal + 4

	return key, value
}

func (it *PageIterator) hasNext() bool {
	if it.current < it.p.use() {
		return true
	}

	return false
}

func TestPut(t *testing.T){
	f, err  := os.OpenFile("/tmp/data.db", os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0644)
	f.Write(make([]byte, 4096))
	m, err := mmap.Map(f, mmap.RDWR, 0)
	defer f.Close()
	if err != nil {
		fmt.Println(err)
	}
	defer m.Unmap()

	var page Page = Page(m[0:4096])
	fill(page)
	fmt.Println(page.left())
	fmt.Println(page.get("key180"))
}

func fill(page Page) {
	for i := 0; i < 185; i++{
		itoa := strconv.Itoa(i)
		page.put("key" + itoa, "value yop yop")
	}
}

type Directory struct {
	table []int
	data mmap.MMap
	gd uint
	dataFile *os.File
	lastPageId int
}

func (dir *Directory) getPageId(k util.Hashable) int {
	return k.Hash() & (( 1 << dir.gd) -1)
}

func (dir *Directory) getPage(k util.Hashable) (Page, int) {
	id := k.Hash() & (( 1 << dir.gd) -1)
	offset := dir.table[id] * 4096
	return Page(dir.data[offset : offset + 4096]), id
}

func (dir *Directory) get(k string) string {
	p, _ := dir.getPage(util.String(k))
	return p.get(k)
}

func (dir *Directory) expand() {
	dir.table = append(dir.table, dir.table...)
	dir.gd ++
}

func (dir *Directory) split(page Page) (p1, p2 Page) {
	p1 = make([]byte, 4096)
	p2 = make([]byte, 4096)

	it := &PageIterator{p: page, current: 0}

	for it.hasNext() {
		k, v := it.next()
		h := util.String(k).Hash() & (( 1 << dir.gd) -1)
		if (h >> uint(page.ld())) & 1 == 1 {
			p2.put(k, v)
		} else {
			p1.put(k, v)
		}
	}
	return p1, p2

}

func (dir *Directory) replace(obsoletePageId int, ld uint) (p1, p2 int) {
	p1Id := obsoletePageId
	p2Id := dir.nextPageId()

	for i := 0; i < len(dir.table); i++ {
		if obsoletePageId != dir.table[i] {
			continue
		}
		if (i >> ld) & 1 == 1 {
			dir.table[i] = p2Id
		} else {
			dir.table[i] = p1Id
		}
	}
	return p1Id, p2Id

}

func (dir *Directory) nextPageId() int{
	dir.lastPageId ++
	return dir.lastPageId
}

func (dir *Directory) put(key, value string) {
	page, id := dir.getPage(util.String(key))
	err := page.put(key, value)

	if err != nil {
		fmt.Println("Page id : " + strconv.Itoa(id))
		fmt.Println(err)
		if dir.gd == uint(page.ld()) {
			dir.expand()
		}

		p1, p2 := dir.split(page)
		id1, id2 := dir.replace(id, uint(page.ld()))
		p1.setLd(page.ld() + 1)
		p2.setLd(page.ld() + 1)

		dir.dataFile.WriteAt(p1, int64(id1 * 4096))
		dir.dataFile.WriteAt(p2, int64(id2 * 4096))
		dir.data.Unmap()
		dir.data, _ = mmap.Map(dir.dataFile, mmap.RDWR, 0)
		//dir.put(key, value)
	}
}

func TestDirectory(t *testing.T){
	f, err := os.OpenFile("/tmp/data.db", os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0644)
	defer f.Close()
	if err != nil {
		fmt.Println(err)
	}
	f.Write(make([]byte, 4096))

	// init
	dir := &Directory{
		dataFile: f,
		gd: 0,
		table: make([]int, 1),
	}
	dir.table[0] = 0
	dir.data, _ = mmap.Map(dir.dataFile, mmap.RDWR, 0)
	defer dir.data.Unmap()
	// given
	var page Page = Page(dir.data[0:4096])
	fill(page)

	//
	key := "key123"
	fmt.Println(dir.get(key))

	for i := 0; i < 2000; i++ {
		dir.put("yolo !! " + strconv.Itoa(i), "mec, elle est où ma caisse ??")
		fmt.Println(dir.table)
	}
}<commit_msg>benchs<commit_after>package memap

import (
	"testing"
	"encoding/binary"
	"fmt"
	"os"
	"github.com/edsrzf/mmap-go"
	"strconv"
	"github.com/BenJoyenConseil/bluckdb/util"
	"errors"
	"github.com/stretchr/testify/assert"
)

type Page []byte

func (p Page) use() int {
	return int(binary.LittleEndian.Uint16(p[4094 : 4096]))
}

func (p Page) add(v...byte) {
	copy(p[p.use():], v)
	use := make([]byte, 2)
	binary.LittleEndian.PutUint16(use, uint16(p.use() + len(v)))
	copy(p[4094:], use)
}

func (p Page) put(k, v string) error{
	headerSize := 4
	lens := make([]byte, headerSize)
	lenK := len(k)
	lenV := len(v)
	binary.LittleEndian.PutUint16(lens, uint16(lenK))
	binary.LittleEndian.PutUint16(lens[2:], uint16(lenV))
	payload := lenK + lenV + headerSize
	if p.left() >= payload {
		p.add(lens...)
		p.add([]byte(k)...)
		p.add([]byte(v)...)
		return nil
	} else {
		return errors.New("The page is full. use = " + strconv.Itoa(p.use()))
	}
}

func (p Page) left() int {
	return 4092 - p.use()
}

func (p Page) ld() int {
	return int(binary.LittleEndian.Uint16(p[4092:]))
}

func (p Page) setLd(v int) {
	binary.LittleEndian.PutUint16(p[4092:], uint16(v))
}

func (p Page) get(k string) string {

	for i := 0; i <= p.use(); {

		lenKey := int(binary.LittleEndian.Uint16(p[i : i + 2]))
		lenVal := int(binary.LittleEndian.Uint16(p[i + 2 : i + 4]))

		currentKey := string(p[i + 4 : i + 4 + lenKey])
		if currentKey == k {
			return string(p[ i + 4 + lenKey : i + 4 + lenKey + lenVal])
		}else {
			i += 4 + lenKey + lenVal
		}
	}
	return ""
}

type PageIterator struct {
	p Page
	current int
}

func (it *PageIterator) next() (k, v string) {
	lenKey := int(binary.LittleEndian.Uint16(it.p[it.current : it.current + 2]))
	lenVal := int(binary.LittleEndian.Uint16(it.p[it.current + 2 : it.current + 4]))

	key := string(it.p[it.current + 4 : it.current + 4 + lenKey])
	value := string(it.p[ it.current + 4 + lenKey : it.current + 4 + lenKey + lenVal])

	it.current += lenKey + lenVal + 4

	return key, value
}

func (it *PageIterator) hasNext() bool {
	if it.current < it.p.use() {
		return true
	}

	return false
}

func TestPut(t *testing.T){
	f, err  := os.OpenFile("/tmp/data.db", os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0644)
	f.Write(make([]byte, 4096))
	m, err := mmap.Map(f, mmap.RDWR, 0)
	defer f.Close()
	if err != nil {
		fmt.Println(err)
	}
	defer m.Unmap()

	var page Page = Page(m[0:4096])
	fill(page)
	fmt.Println(page.left())
	fmt.Println(page.get("key180"))
}

func fill(page Page) {
	for i := 0; i < 185; i++{
		itoa := strconv.Itoa(i)
		page.put("key" + itoa, "value yop yop")
	}
}

type Directory struct {
	table []int
	data mmap.MMap
	gd uint
	dataFile *os.File
	lastPageId int
}

func (dir *Directory) getPageId(k util.Hashable) int {
	return k.Hash() & (( 1 << dir.gd) -1)
}

func (dir *Directory) getPage(k util.Hashable) (Page, int) {
	id := k.Hash() & (( 1 << dir.gd) -1)
	offset := dir.table[id] * 4096
	return Page(dir.data[offset : offset + 4096]), id
}

func (dir *Directory) get(k string) string {
	p, _ := dir.getPage(util.String(k))
	return p.get(k)
}

func (dir *Directory) expand() {
	dir.table = append(dir.table, dir.table...)
	dir.gd ++
}

func (dir *Directory) split(page Page) (p1, p2 Page) {
	p1 = make([]byte, 4096)
	p2 = make([]byte, 4096)

	it := &PageIterator{p: page, current: 0}

	for it.hasNext() {
		k, v := it.next()
		h := util.String(k).Hash() & (( 1 << dir.gd) -1)
		if (h >> uint(page.ld())) & 1 == 1 {
			p2.put(k, v)
		} else {
			p1.put(k, v)
		}
	}
	return p1, p2

}

func (dir *Directory) replace(obsoletePageId int, ld uint) (p1, p2 int) {
	p1Id := obsoletePageId
	p2Id := dir.nextPageId()

	for i := 0; i < len(dir.table); i++ {
		if obsoletePageId != dir.table[i] {
			continue
		}
		if (i >> ld) & 1 == 1 {
			dir.table[i] = p2Id
		} else {
			dir.table[i] = p1Id
		}
	}
	return p1Id, p2Id

}

func TestReplace(t *testing.T)  {
	// Given
	dir := &Directory{
		table:[]int{0, 1, 3, 2, 0, 1, 3, 2},
		gd: 2,
		lastPageId: 4,
	}

	// When
	r1, r2 := dir.replace(2, 2)

	// Then
	assert.Equal(t, 2, r1)
	assert.Equal(t, 5, r2)
}

func (dir *Directory) nextPageId() int {
	dir.lastPageId ++
	return dir.lastPageId
}

func (dir *Directory) put(key, value string) {
	page, id := dir.getPage(util.String(key))
	err := page.put(key, value)

	if err != nil {
		if uint(page.ld()) == dir.gd {
			dir.expand()
		}
		if uint(page.ld()) < dir.gd {

			p1, p2 := dir.split(page)
			id1, id2 := dir.replace(dir.table[id], uint(page.ld()))
			p1.setLd(page.ld() + 1)
			p2.setLd(page.ld() + 1)

			dir.dataFile.WriteAt(p1, int64(id1 * 4096))
			dir.dataFile.WriteAt(p2, int64(id2 * 4096))
			dir.data.Unmap()
			dir.data, _ = mmap.Map(dir.dataFile, mmap.RDWR, 0)
			dir.put(key, value)
		}

	}
}

func BenchmarkMemapPut(b *testing.B){
	f, err := os.OpenFile("/tmp/data.db", os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0644)
	defer f.Close()
	if err != nil {
		fmt.Println(err)
	}
	f.Write(make([]byte, 4096))

	// init
	dir := &Directory{
		dataFile: f,
		gd: 0,
		table: make([]int, 1),
	}
	dir.table[0] = 0
	dir.data, _ = mmap.Map(dir.dataFile, mmap.RDWR, 0)
	defer dir.data.Unmap()
	// given
	var page Page = Page(dir.data[0:4096])
	fill(page)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {

		dir.put("yolo !! " + strconv.Itoa(i), "mec, elle est où ma caisse ??")
	}
	fmt.Println(dir.gd)

}

func BenchmarkMemapGet(b *testing.B){
	f, err := os.OpenFile("/tmp/data.db", os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0644)
	defer f.Close()
	if err != nil {
		fmt.Println(err)
	}
	f.Write(make([]byte, 4096))

	// init
	dir := &Directory{
		dataFile: f,
		gd: 0,
		table: make([]int, 1),
	}
	dir.table[0] = 0
	dir.data, _ = mmap.Map(dir.dataFile, mmap.RDWR, 0)
	defer dir.data.Unmap()
	// given
	var page Page = Page(dir.data[0:4096])
	fill(page)
	for i := 0; i < b.N; i++ {

		dir.put("yolo !! " + strconv.Itoa(i), "mec, elle est où ma caisse ??")
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {

		dir.get("yolo !! " + strconv.Itoa(i))
	}
}<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"."
	"github.com/bmizerany/pat"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/service"
	"github.com/timeredbull/tsuru/api/user"
	. "github.com/timeredbull/tsuru/database"
	"launchpad.net/mgo"
	"log"
	"net/http"
)

func main() {
	session, err := mgo.Dial("localhost:27017")
	if err != nil {
		panic(err)
	}
	Db = session.DB("tsuru")
	defer session.Close()
	m := pat.New()

	m.Post("/services", webserver.Handler(service.CreateHandler))
	m.Get("/services", webserver.Handler(service.ServicesHandler))
	m.Get("/services/types", webserver.Handler(service.ServiceTypesHandler))
	m.Get("/services/:name", webserver.Handler(service.DeleteHandler))
	m.Post("/services/bind", webserver.Handler(service.BindHandler))
	m.Post("/services/unbind", webserver.Handler(service.UnbindHandler))

	m.Get("/apps/:name/delete", webserver.Handler(app.AppDelete))
	m.Get("/apps/:name", webserver.Handler(app.AppInfo))
	m.Post("/apps/:name/application", webserver.Handler(app.Upload))
	m.Get("/apps", webserver.Handler(app.AppList))
	m.Post("/apps", webserver.Handler(app.CreateAppHandler))

	m.Post("/users", webserver.Handler(user.CreateUser))
	m.Post("/users/:email/tokens", webserver.Handler(user.Login))
	m.Get("/users/check-authorization", webserver.Handler(user.CheckAuthorization))

	log.Fatal(http.ListenAndServe(":4000", m))
}
<commit_msg>api/webserver: update main to use the new db package<commit_after>// +build ignore

package main

import (
	"."
	"github.com/bmizerany/pat"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/service"
	"github.com/timeredbull/tsuru/api/user"
	"github.com/timeredbull/tsuru/db"
	"log"
	"net/http"
)

func main() {
	var err error
	db.Session, err = db.Open("127.0.0.1:27017", "tsuru")
	if err != nil {
		panic(err)
	}
	defer db.Session.Close()
	m := pat.New()

	m.Post("/services", webserver.Handler(service.CreateHandler))
	m.Get("/services", webserver.Handler(service.ServicesHandler))
	m.Get("/services/types", webserver.Handler(service.ServiceTypesHandler))
	m.Get("/services/:name", webserver.Handler(service.DeleteHandler))
	m.Post("/services/bind", webserver.Handler(service.BindHandler))
	m.Post("/services/unbind", webserver.Handler(service.UnbindHandler))

	m.Get("/apps/:name/delete", webserver.Handler(app.AppDelete))
	m.Get("/apps/:name", webserver.Handler(app.AppInfo))
	m.Post("/apps/:name/application", webserver.Handler(app.Upload))
	m.Get("/apps", webserver.Handler(app.AppList))
	m.Post("/apps", webserver.Handler(app.CreateAppHandler))

	m.Post("/users", webserver.Handler(user.CreateUser))
	m.Post("/users/:email/tokens", webserver.Handler(user.Login))
	m.Get("/users/check-authorization", webserver.Handler(user.CheckAuthorization))

	log.Fatal(http.ListenAndServe(":4000", m))
}
<|endoftext|>


<commit_before>package user

import (
	"fmt"

	"golang.org/x/net/context"
)

// UserIDContextKey is the key used in contexts to find the userid
const userIDContextKey = "PrismUserID" // TODO dedupe with storage/local

// GetID returns the user
func GetID(ctx context.Context) (string, error) {
	userid, ok := ctx.Value(userIDContextKey).(string)
	if !ok {
		return "", fmt.Errorf("no user id")
	}
	return userid, nil
}

// WithID returns a derived context containing the user ID.
func WithID(ctx context.Context, userID string) context.Context {
	return context.WithValue(ctx, userIDContextKey, userID)
}
<commit_msg>Fix lint<commit_after>package user

import (
	"fmt"

	"golang.org/x/net/context"
)

// UserIDContextKey is the key used in contexts to find the userid
const userIDContextKey = "PrismUserID" // TODO dedupe with storage/local

// GetID returns the user
func GetID(ctx context.Context) (string, error) {
	userid, ok := ctx.Value(userIDContextKey).(string)
	if !ok {
		return "", fmt.Errorf("no user id")
	}
	return userid, nil
}

// WithID returns a derived context containing the user ID.
func WithID(ctx context.Context, userID string) context.Context {
	return context.WithValue(ctx, interface{}(userIDContextKey), userID)
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	"net/http"
	"sort"
	"sync"

	"github.com/gorilla/mux"
	"golang.org/x/net/context"

	"github.com/weaveworks/scope/probe/kubernetes"
	"github.com/weaveworks/scope/render"
	"github.com/weaveworks/scope/report"
)

const apiTopologyURL = "/api/topology/"

var (
	topologyRegistry = &registry{
		items: map[string]APITopologyDesc{},
	}
)

func init() {
	containerFilters := []APITopologyOptionGroup{
		{
			ID:      "system",
			Default: "application",
			Options: []APITopologyOption{
				{"system", "System containers", render.IsSystem},
				{"application", "Application containers", render.IsApplication},
				{"both", "Both", nil},
			},
		},
		{
			ID:      "stopped",
			Default: "running",
			Options: []APITopologyOption{
				{"stopped", "Stopped containers", render.IsStopped},
				{"running", "Running containers", render.IsRunning},
				{"both", "Both", nil},
			},
		},
	}

	unconnectedFilter := []APITopologyOptionGroup{
		{
			ID:      "unconnected",
			Default: "hide",
			Options: []APITopologyOption{
				// Show the user why there are filtered nodes in this view.
				// Don't give them the option to show those nodes.
				{"hide", "Unconnected nodes hidden", nil},
			},
		},
	}

	// Topology option labels should tell the current state. The first item must
	// be the verb to get to that state
	topologyRegistry.add(
		APITopologyDesc{
			id:       "processes",
			renderer: render.FilterUnconnected(render.ProcessWithContainerNameRenderer),
			Name:     "Processes",
			Rank:     1,
			Options:  unconnectedFilter,
		},
		APITopologyDesc{
			id:       "processes-by-name",
			parent:   "processes",
			renderer: render.FilterUnconnected(render.ProcessNameRenderer),
			Name:     "by name",
			Options:  unconnectedFilter,
		},
		APITopologyDesc{
			id:       "containers",
			renderer: render.ContainerWithImageNameRenderer,
			Name:     "Containers",
			Rank:     2,
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:       "containers-by-image",
			parent:   "containers",
			renderer: render.ContainerImageRenderer,
			Name:     "by image",
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:       "containers-by-hostname",
			parent:   "containers",
			renderer: render.ContainerHostnameRenderer,
			Name:     "by DNS name",
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:          "pods",
			renderer:    render.PodRenderer,
			Name:        "Pods",
			Rank:        3,
			HideIfEmpty: true,
		},
		APITopologyDesc{
			id:          "pods-by-service",
			parent:      "pods",
			renderer:    render.PodServiceRenderer,
			Name:        "by service",
			HideIfEmpty: true,
		},
		APITopologyDesc{
			id:       "hosts",
			renderer: render.HostRenderer,
			Name:     "Hosts",
			Rank:     4,
		},
	)
}

// kubernetesFilters generates the current kubernetes filters based on the
// available k8s topologies.
func kubernetesFilters(namespaces ...string) APITopologyOptionGroup {
	options := APITopologyOptionGroup{ID: "namespace", Default: "all"}
	for _, namespace := range namespaces {
		options.Options = append(options.Options, APITopologyOption{namespace, namespace, render.IsNamespace(namespace)})
	}
	options.Options = append(options.Options, APITopologyOption{"all", "All Namespaces", nil})
	return options
}

// updateFilters updates the available filters based on the current report.
// Currently only kubernetes changes.
func updateFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc {
	namespaces := map[string]struct{}{}
	for _, t := range []report.Topology{rpt.Pod, rpt.Service} {
		for _, n := range t.Nodes {
			if namespace, ok := n.Latest.Lookup(kubernetes.Namespace); ok {
				namespaces[namespace] = struct{}{}
			}
		}
	}
	var ns []string
	for namespace := range namespaces {
		ns = append(ns, namespace)
	}
	sort.Strings(ns)
	for i, t := range topologies {
		if t.id == "pods" || t.id == "pods-by-service" {
			topologies[i] = updateTopologyFilters(t, []APITopologyOptionGroup{kubernetesFilters(ns...)})
		}
	}
	return topologies
}

// updateTopologyFilters recursively sets the options on a topology description
func updateTopologyFilters(t APITopologyDesc, options []APITopologyOptionGroup) APITopologyDesc {
	t.Options = options
	for i, sub := range t.SubTopologies {
		t.SubTopologies[i] = updateTopologyFilters(sub, options)
	}
	return t
}

// registry is a threadsafe store of the available topologies
type registry struct {
	sync.RWMutex
	items map[string]APITopologyDesc
}

// APITopologyDesc is returned in a list by the /api/topology handler.
type APITopologyDesc struct {
	id       string
	parent   string
	renderer render.Renderer

	Name        string                   `json:"name"`
	Rank        int                      `json:"rank"`
	HideIfEmpty bool                     `json:"hide_if_empty"`
	Options     []APITopologyOptionGroup `json:"options"`

	URL           string            `json:"url"`
	SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"`
	Stats         topologyStats     `json:"stats,omitempty"`
}

type byName []APITopologyDesc

func (a byName) Len() int           { return len(a) }
func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name }

// APITopologyOptionGroup describes a group of APITopologyOptions
type APITopologyOptionGroup struct {
	ID      string              `json:"id"`
	Default string              `json:"defaultValue,omitempty"`
	Options []APITopologyOption `json:"options,omitempty"`
}

// APITopologyOption describes a &param=value to a given topology.
type APITopologyOption struct {
	Value string `json:"value"`
	Label string `json:"label"`

	filter render.FilterFunc
}

type topologyStats struct {
	NodeCount          int `json:"node_count"`
	NonpseudoNodeCount int `json:"nonpseudo_node_count"`
	EdgeCount          int `json:"edge_count"`
	FilteredNodes      int `json:"filtered_nodes"`
}

func (r *registry) add(ts ...APITopologyDesc) {
	r.Lock()
	defer r.Unlock()
	for _, t := range ts {
		t.URL = apiTopologyURL + t.id

		if t.parent != "" {
			parent := r.items[t.parent]
			parent.SubTopologies = append(parent.SubTopologies, t)
			sort.Sort(byName(parent.SubTopologies))
			r.items[t.parent] = parent
		}

		r.items[t.id] = t
	}
}

func (r *registry) get(name string) (APITopologyDesc, bool) {
	r.RLock()
	defer r.RUnlock()
	t, ok := r.items[name]
	return t, ok
}

func (r *registry) walk(f func(APITopologyDesc)) {
	r.RLock()
	defer r.RUnlock()
	descs := []APITopologyDesc{}
	for _, desc := range r.items {
		if desc.parent != "" {
			continue
		}
		descs = append(descs, desc)
	}
	sort.Sort(byName(descs))
	for _, desc := range descs {
		f(desc)
	}
}

// makeTopologyList returns a handler that yields an APITopologyList.
func (r *registry) makeTopologyList(rep Reporter) CtxHandlerFunc {
	return func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
		report, err := rep.Report(ctx)
		if err != nil {
			respondWith(w, http.StatusInternalServerError, err.Error())
			return
		}
		respondWith(w, http.StatusOK, r.renderTopologies(report, req))
	}
}

func (r *registry) renderTopologies(rpt report.Report, req *http.Request) []APITopologyDesc {
	topologies := []APITopologyDesc{}
	req.ParseForm()
	values := map[string]string{}
	for k, vs := range req.Form {
		values[k] = vs[0]
	}
	r.walk(func(desc APITopologyDesc) {
		renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
		desc.Stats = decorateWithStats(rpt, renderer, decorator)
		for i := range desc.SubTopologies {
			renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
			desc.SubTopologies[i].Stats = decorateWithStats(rpt, renderer, decorator)
		}
		topologies = append(topologies, desc)
	})
	return updateFilters(rpt, topologies)
}

func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator render.Decorator) topologyStats {
	var (
		nodes     int
		realNodes int
		edges     int
	)
	for _, n := range renderer.Render(rpt, decorator) {
		nodes++
		if n.Topology != render.Pseudo {
			realNodes++
		}
		edges += len(n.Adjacency)
	}
	renderStats := renderer.Stats(rpt, decorator)
	return topologyStats{
		NodeCount:          nodes,
		NonpseudoNodeCount: realNodes,
		EdgeCount:          edges,
		FilteredNodes:      renderStats.FilteredNodes,
	}
}

func (r *registry) rendererForTopology(id string, values map[string]string, rpt report.Report) (render.Renderer, render.Decorator, error) {
	topology, ok := r.get(id)
	if !ok {
		return nil, nil, fmt.Errorf("topology not found: %s", id)
	}
	topology = updateFilters(rpt, []APITopologyDesc{topology})[0]

	var filters []render.FilterFunc
	for _, group := range topology.Options {
		value := values[group.ID]
		for _, opt := range group.Options {
			if opt.filter == nil {
				continue
			}
			if (value == "" && group.Default == opt.Value) || (opt.Value != "" && opt.Value == value) {
				filters = append(filters, opt.filter)
			}
		}
	}
	var decorator render.Decorator
	if len(filters) > 0 {
		decorator = func(renderer render.Renderer) render.Renderer {
			return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
		}
	}
	return topology.renderer, decorator, nil
}

type reportRenderHandler func(context.Context, Reporter, http.ResponseWriter, *http.Request)

func (r *registry) rendererForRequest(req *http.Request, rpt report.Report) (render.Renderer, render.Decorator, error) {
	req.ParseForm()
	values := map[string]string{}
	for k, vs := range req.Form {
		values[k] = vs[0]
	}
	return r.rendererForTopology(mux.Vars(req)["topology"], values, rpt)
}

func captureReporter(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		f(ctx, rep, w, r)
	}
}
<commit_msg>filter out deleted pods when calculating available namespaces<commit_after>package app

import (
	"fmt"
	"net/http"
	"sort"
	"sync"

	"github.com/gorilla/mux"
	"golang.org/x/net/context"

	"github.com/weaveworks/scope/probe/kubernetes"
	"github.com/weaveworks/scope/render"
	"github.com/weaveworks/scope/report"
)

const apiTopologyURL = "/api/topology/"

var (
	topologyRegistry = &registry{
		items: map[string]APITopologyDesc{},
	}
)

func init() {
	containerFilters := []APITopologyOptionGroup{
		{
			ID:      "system",
			Default: "application",
			Options: []APITopologyOption{
				{"system", "System containers", render.IsSystem},
				{"application", "Application containers", render.IsApplication},
				{"both", "Both", nil},
			},
		},
		{
			ID:      "stopped",
			Default: "running",
			Options: []APITopologyOption{
				{"stopped", "Stopped containers", render.IsStopped},
				{"running", "Running containers", render.IsRunning},
				{"both", "Both", nil},
			},
		},
	}

	unconnectedFilter := []APITopologyOptionGroup{
		{
			ID:      "unconnected",
			Default: "hide",
			Options: []APITopologyOption{
				// Show the user why there are filtered nodes in this view.
				// Don't give them the option to show those nodes.
				{"hide", "Unconnected nodes hidden", nil},
			},
		},
	}

	// Topology option labels should tell the current state. The first item must
	// be the verb to get to that state
	topologyRegistry.add(
		APITopologyDesc{
			id:       "processes",
			renderer: render.FilterUnconnected(render.ProcessWithContainerNameRenderer),
			Name:     "Processes",
			Rank:     1,
			Options:  unconnectedFilter,
		},
		APITopologyDesc{
			id:       "processes-by-name",
			parent:   "processes",
			renderer: render.FilterUnconnected(render.ProcessNameRenderer),
			Name:     "by name",
			Options:  unconnectedFilter,
		},
		APITopologyDesc{
			id:       "containers",
			renderer: render.ContainerWithImageNameRenderer,
			Name:     "Containers",
			Rank:     2,
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:       "containers-by-image",
			parent:   "containers",
			renderer: render.ContainerImageRenderer,
			Name:     "by image",
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:       "containers-by-hostname",
			parent:   "containers",
			renderer: render.ContainerHostnameRenderer,
			Name:     "by DNS name",
			Options:  containerFilters,
		},
		APITopologyDesc{
			id:          "pods",
			renderer:    render.PodRenderer,
			Name:        "Pods",
			Rank:        3,
			HideIfEmpty: true,
		},
		APITopologyDesc{
			id:          "pods-by-service",
			parent:      "pods",
			renderer:    render.PodServiceRenderer,
			Name:        "by service",
			HideIfEmpty: true,
		},
		APITopologyDesc{
			id:       "hosts",
			renderer: render.HostRenderer,
			Name:     "Hosts",
			Rank:     4,
		},
	)
}

// kubernetesFilters generates the current kubernetes filters based on the
// available k8s topologies.
func kubernetesFilters(namespaces ...string) APITopologyOptionGroup {
	options := APITopologyOptionGroup{ID: "namespace", Default: "all"}
	for _, namespace := range namespaces {
		options.Options = append(options.Options, APITopologyOption{namespace, namespace, render.IsNamespace(namespace)})
	}
	options.Options = append(options.Options, APITopologyOption{"all", "All Namespaces", nil})
	return options
}

// updateFilters updates the available filters based on the current report.
// Currently only kubernetes changes.
func updateFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc {
	namespaces := map[string]struct{}{}
	for _, t := range []report.Topology{rpt.Pod, rpt.Service} {
		for _, n := range t.Nodes {
			if state, ok := n.Latest.Lookup(kubernetes.PodState); ok && state == kubernetes.StateDeleted {
				continue
			}
			if namespace, ok := n.Latest.Lookup(kubernetes.Namespace); ok {
				namespaces[namespace] = struct{}{}
			}
		}
	}
	var ns []string
	for namespace := range namespaces {
		ns = append(ns, namespace)
	}
	sort.Strings(ns)
	for i, t := range topologies {
		if t.id == "pods" || t.id == "pods-by-service" {
			topologies[i] = updateTopologyFilters(t, []APITopologyOptionGroup{kubernetesFilters(ns...)})
		}
	}
	return topologies
}

// updateTopologyFilters recursively sets the options on a topology description
func updateTopologyFilters(t APITopologyDesc, options []APITopologyOptionGroup) APITopologyDesc {
	t.Options = options
	for i, sub := range t.SubTopologies {
		t.SubTopologies[i] = updateTopologyFilters(sub, options)
	}
	return t
}

// registry is a threadsafe store of the available topologies
type registry struct {
	sync.RWMutex
	items map[string]APITopologyDesc
}

// APITopologyDesc is returned in a list by the /api/topology handler.
type APITopologyDesc struct {
	id       string
	parent   string
	renderer render.Renderer

	Name        string                   `json:"name"`
	Rank        int                      `json:"rank"`
	HideIfEmpty bool                     `json:"hide_if_empty"`
	Options     []APITopologyOptionGroup `json:"options"`

	URL           string            `json:"url"`
	SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"`
	Stats         topologyStats     `json:"stats,omitempty"`
}

type byName []APITopologyDesc

func (a byName) Len() int           { return len(a) }
func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name }

// APITopologyOptionGroup describes a group of APITopologyOptions
type APITopologyOptionGroup struct {
	ID      string              `json:"id"`
	Default string              `json:"defaultValue,omitempty"`
	Options []APITopologyOption `json:"options,omitempty"`
}

// APITopologyOption describes a &param=value to a given topology.
type APITopologyOption struct {
	Value string `json:"value"`
	Label string `json:"label"`

	filter render.FilterFunc
}

type topologyStats struct {
	NodeCount          int `json:"node_count"`
	NonpseudoNodeCount int `json:"nonpseudo_node_count"`
	EdgeCount          int `json:"edge_count"`
	FilteredNodes      int `json:"filtered_nodes"`
}

func (r *registry) add(ts ...APITopologyDesc) {
	r.Lock()
	defer r.Unlock()
	for _, t := range ts {
		t.URL = apiTopologyURL + t.id

		if t.parent != "" {
			parent := r.items[t.parent]
			parent.SubTopologies = append(parent.SubTopologies, t)
			sort.Sort(byName(parent.SubTopologies))
			r.items[t.parent] = parent
		}

		r.items[t.id] = t
	}
}

func (r *registry) get(name string) (APITopologyDesc, bool) {
	r.RLock()
	defer r.RUnlock()
	t, ok := r.items[name]
	return t, ok
}

func (r *registry) walk(f func(APITopologyDesc)) {
	r.RLock()
	defer r.RUnlock()
	descs := []APITopologyDesc{}
	for _, desc := range r.items {
		if desc.parent != "" {
			continue
		}
		descs = append(descs, desc)
	}
	sort.Sort(byName(descs))
	for _, desc := range descs {
		f(desc)
	}
}

// makeTopologyList returns a handler that yields an APITopologyList.
func (r *registry) makeTopologyList(rep Reporter) CtxHandlerFunc {
	return func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
		report, err := rep.Report(ctx)
		if err != nil {
			respondWith(w, http.StatusInternalServerError, err.Error())
			return
		}
		respondWith(w, http.StatusOK, r.renderTopologies(report, req))
	}
}

func (r *registry) renderTopologies(rpt report.Report, req *http.Request) []APITopologyDesc {
	topologies := []APITopologyDesc{}
	req.ParseForm()
	values := map[string]string{}
	for k, vs := range req.Form {
		values[k] = vs[0]
	}
	r.walk(func(desc APITopologyDesc) {
		renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
		desc.Stats = decorateWithStats(rpt, renderer, decorator)
		for i := range desc.SubTopologies {
			renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
			desc.SubTopologies[i].Stats = decorateWithStats(rpt, renderer, decorator)
		}
		topologies = append(topologies, desc)
	})
	return updateFilters(rpt, topologies)
}

func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator render.Decorator) topologyStats {
	var (
		nodes     int
		realNodes int
		edges     int
	)
	for _, n := range renderer.Render(rpt, decorator) {
		nodes++
		if n.Topology != render.Pseudo {
			realNodes++
		}
		edges += len(n.Adjacency)
	}
	renderStats := renderer.Stats(rpt, decorator)
	return topologyStats{
		NodeCount:          nodes,
		NonpseudoNodeCount: realNodes,
		EdgeCount:          edges,
		FilteredNodes:      renderStats.FilteredNodes,
	}
}

func (r *registry) rendererForTopology(id string, values map[string]string, rpt report.Report) (render.Renderer, render.Decorator, error) {
	topology, ok := r.get(id)
	if !ok {
		return nil, nil, fmt.Errorf("topology not found: %s", id)
	}
	topology = updateFilters(rpt, []APITopologyDesc{topology})[0]

	var filters []render.FilterFunc
	for _, group := range topology.Options {
		value := values[group.ID]
		for _, opt := range group.Options {
			if opt.filter == nil {
				continue
			}
			if (value == "" && group.Default == opt.Value) || (opt.Value != "" && opt.Value == value) {
				filters = append(filters, opt.filter)
			}
		}
	}
	var decorator render.Decorator
	if len(filters) > 0 {
		decorator = func(renderer render.Renderer) render.Renderer {
			return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
		}
	}
	return topology.renderer, decorator, nil
}

type reportRenderHandler func(context.Context, Reporter, http.ResponseWriter, *http.Request)

func (r *registry) rendererForRequest(req *http.Request, rpt report.Report) (render.Renderer, render.Decorator, error) {
	req.ParseForm()
	values := map[string]string{}
	for k, vs := range req.Form {
		values[k] = vs[0]
	}
	return r.rendererForTopology(mux.Vars(req)["topology"], values, rpt)
}

func captureReporter(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		f(ctx, rep, w, r)
	}
}
<|endoftext|>


<commit_before>// Package totango provides bindings for the Totango server side integration API
package totango

import (
	"errors"
	"net/http"
	"net/url"
)

const (
	// The base URL for all Totango API requests
	baseURL = "https://sdr.totango.com/pixel.gif/?sdr_s="
)

// The data for an API request
type request struct {
	accountID   string
	accountName string
	userName    string
	activity    string
	module      string
	attributes  map[string]string
}

func encode(s string) string {
	return url.QueryEscape(s)
}

// Construct a URL query param string from the provided fields
func (r *request) String() string {
	var url string

	switch {
	case r.accountID != "":
		url += "&sdr_o=" + encode(r.accountID)
	case r.accountName != "":
		url += "&sdr_odn=" + encode(r.accountName)
	case r.userName != "":
		url += "&sdr_u=" + encode(r.userName)
	case r.activity != "":
		url += "&sdr_a=" + encode(r.activity)
	case r.module != "":
		url += "&sdr_m=" + encode(r.module)
	case len(r.attributes) > 0:
		for name, val := range r.attributes {
			url += "&sdr_o." + encode(name) + "=" + encode(val)
		}
	}

	return url
}

type Tracker struct {
	serviceID string
}

// Construct a Totango API request from a request type
func (t *Tracker) getURL(r *request) string {
	return baseURL + t.serviceID + r.String()
}

func NewTracker(serviceID string) (*Tracker, error) {
	if serviceID == "" {
		return nil, errors.New("Tracker requires a valid Totango Service ID")
	}

	return &Tracker{serviceID: serviceID}, nil
}

func (t *Tracker) Track(accountID, accountName, userName, activity, module string) (*http.Response, error) {
	r := &request{
		accountID:   accountID,
		accountName: accountName,
		userName:    userName,
		activity:    activity,
		module:      module,
	}

	return http.Get(t.getURL(r))
}

func (t *Tracker) TrackAttribute(accountID, userName, name, value string) (*http.Response, error) {
	r := &request{
		accountID:  accountID,
		userName:   userName,
		attributes: map[string]string{name: value},
	}

	return http.Get(t.getURL(r))
}

func (t *Tracker) TrackAttributes(accountID, userName string, attributes map[string]string) (*http.Response, error) {
	r := &request{
		accountID:  accountID,
		userName:   userName,
		attributes: attributes,
	}

	return http.Get(t.getURL(r))
}
<commit_msg>Add error<commit_after>// Package totango provides bindings for the Totango server side integration API
package totango

import (
	"errors"
	"net/http"
	"net/url"
)

const (
	// The base URL for all Totango API requests
	baseURL = "https://sdr.totango.com/pixel.gif/?sdr_s="
)

// The data for an API request
type request struct {
	accountID   string
	accountName string
	userName    string
	activity    string
	module      string
	attributes  map[string]string
}

func encode(s string) string {
	return url.QueryEscape(s)
}

// Construct a URL query param string from the provided fields
func (r *request) String() string {
	var url string

	switch {
	case r.accountID != "":
		url += "&sdr_o=" + encode(r.accountID)
	case r.accountName != "":
		url += "&sdr_odn=" + encode(r.accountName)
	case r.userName != "":
		url += "&sdr_u=" + encode(r.userName)
	case r.activity != "":
		url += "&sdr_a=" + encode(r.activity)
	case r.module != "":
		url += "&sdr_m=" + encode(r.module)
	case len(r.attributes) > 0:
		for name, val := range r.attributes {
			url += "&sdr_o." + encode(name) + "=" + encode(val)
		}
	}

	return url
}

type Tracker struct {
	serviceID string
}

// Construct a Totango API request from a request type
func (t *Tracker) getURL(r *request) string {
	return baseURL + t.serviceID + r.String()
}

func NewTracker(serviceID string) (*Tracker, error) {
	if serviceID == "" {
		return nil, errors.New("Tracker requires a valid Totango Service ID")
	}

	return &Tracker{serviceID: serviceID}, nil
}

func (t *Tracker) Track(accountID, accountName, userName, activity, module string) (*http.Response, error) {
	r := &request{
		accountID:   accountID,
		accountName: accountName,
		userName:    userName,
		activity:    activity,
		module:      module,
	}

	return http.Get(t.getURL(r))
}

func (t *Tracker) TrackAttribute(accountID, userName, name, value string) (*http.Response, error) {
	if accountID == "" {
		return nil, errors.New("An account ID is required to track an attribute")
	}

	r := &request{
		accountID:  accountID,
		userName:   userName,
		attributes: map[string]string{name: value},
	}

	return http.Get(t.getURL(r))
}

func (t *Tracker) TrackAttributes(accountID, userName string, attributes map[string]string) (*http.Response, error) {
	if accountID == "" {
		return nil, errors.New("An account ID is required to track attributes")
	}

	r := &request{
		accountID:  accountID,
		userName:   userName,
		attributes: attributes,
	}

	return http.Get(t.getURL(r))
}
<|endoftext|>


<commit_before>package proboscis

type Request struct {
  Method string
  Format string
  Length uint32
  Data   []byte
}

type Response struct {
  Status string
  Format string
  Length uint32
  Data   []byte
}

func NewRequest() *Request {
  var req *Request
  req = &Request{"", "", 0, []byte{}}
  return req
}
func (req *Request) MakeResonse() *Response {
  rep := NewResponse()
  rep.Status = "200"
  rep.Format = req.Format
  return rep
}

func NewResponse() *Response {
  var rep *Response
  rep = &Response{"", "", 0, []byte{}}
  return rep
}
<commit_msg>Add encoding and decoding<commit_after>package proboscis

type Request struct {
  Method string
  Format string
  Length uint32
  Data   []byte
}

type Response struct {
  Status string
  Format string
  Length uint32
  Data   []byte
}

func NewRequest() *Request {
  var req *Request
  req = &Request{"", "", 0, []byte{}}
  return req
}
func (req *Request) MakeResonse() *Response {
  rep := NewResponse()
func EncodeRequest(req *Request, w io.Writer) error {
  w.Write([]byte(req.Method))
  w.Write(period_byte_slice)
  w.Write([]byte(req.Format))
  w.Write(colon_byte_slice)
  w.Write([]byte(strconv.Itoa(len(req.Data))))
  w.Write(colon_byte_slice)
  w.Write(req.Data)
  return nil
}
func DecodeRequest(r bufio.Reader) (*Request, error) {
  var req *Request
  req = NewRequest()

  // TODO: At least some sanity-checking
  method, _ := r.ReadString(period_byte)
  method = method[0:len(method) - 2]

  format, _ := r.ReadString(colon_byte)
  format = format[0:len(format) - 2]

  length_string, _ := r.ReadString(colon_byte)
  length_string = length_string[0:len(length_string) - 2]

  length, _ := strconv.Atoi(length_string)

  data := make([]byte, length)
  read, _ := r.Read(data)

  if read != length {
    return nil, fmt.Errorf(
      "Error reading data (expected %d bytes, read %d)", length, read,
    )
  }

  req.Method = method
  req.Format = format
  req.Length = uint32(length)
  req.Data   = data
  return req, nil
}
  rep.Status = "200"
  rep.Format = req.Format
  return rep
}

func NewResponse() *Response {
  var rep *Response
  rep = &Response{"", "", 0, []byte{}}
  return rep
}
<|endoftext|>


<commit_before>package main

import (
	"strings"

	"github.com/codegangsta/martini"
	ct "github.com/flynn/flynn-controller/types"
	"github.com/flynn/flynn-host/types"
	"github.com/flynn/go-flynn/cluster"
	"github.com/martini-contrib/render"
)

type clusterClient interface {
	ListHosts() (map[string]host.Host, error)
	ConnectHost(string) (cluster.Host, error)
}

func processList(app *ct.App, cc clusterClient, r render.Render) {
	hosts, err := cc.ListHosts()
	if err != nil {
		// TODO: 500/handle error
	}
	var processes []ct.Process
	for _, h := range hosts {
		for _, job := range h.Jobs {
			if job.Attributes["flynn-controller.app"] != app.ID {
				continue
			}

			proc := ct.Process{
				ID:        h.ID + ":" + job.ID,
				Type:      job.Attributes["flynn-controller.type"],
				ReleaseID: job.Attributes["flynn-controller.release"],
			}
			if proc.Type == "" {
				proc.Cmd = job.Config.Cmd
			}
			processes = append(processes, proc)
		}
	}

	r.JSON(200, processes)
}

func killProcess(app *ct.App, params martini.Params, cl clusterClient) {
	id := strings.SplitN(params["proc_id"], ":", 2)
	if len(id) != 2 {
		// TODO: error
	}
	client, err := cl.ConnectHost(id[0])
	if err != nil {
		// TODO: 500/log error
	}
	if err := client.StopJob(id[1]); err != nil {
		// TODO: 500/log error
	}
}
<commit_msg>Add process id helper<commit_after>package main

import (
	"strings"

	"github.com/codegangsta/martini"
	ct "github.com/flynn/flynn-controller/types"
	"github.com/flynn/flynn-host/types"
	"github.com/flynn/go-flynn/cluster"
	"github.com/martini-contrib/render"
)

type clusterClient interface {
	ListHosts() (map[string]host.Host, error)
	ConnectHost(string) (cluster.Host, error)
}

func processList(app *ct.App, cc clusterClient, r render.Render) {
	hosts, err := cc.ListHosts()
	if err != nil {
		// TODO: 500/handle error
	}
	var processes []ct.Process
	for _, h := range hosts {
		for _, job := range h.Jobs {
			if job.Attributes["flynn-controller.app"] != app.ID {
				continue
			}

			proc := ct.Process{
				ID:        h.ID + ":" + job.ID,
				Type:      job.Attributes["flynn-controller.type"],
				ReleaseID: job.Attributes["flynn-controller.release"],
			}
			if proc.Type == "" {
				proc.Cmd = job.Config.Cmd
			}
			processes = append(processes, proc)
		}
	}

	r.JSON(200, processes)
}

func parseProcessID(params martini.Params) (string, string) {
	id := strings.SplitN(params["proc_id"], ":", 2)
	if len(id) != 2 || id[0] == "" || id[1] == "" {
		return "", ""
	}
	return id[0], id[1]
}

func killProcess(app *ct.App, params martini.Params, cl clusterClient) {
	hostID, jobID := parseProcessID(params)
	if hostID == "" {
		// TODO: error
	}
	client, err := cl.ConnectHost(hostID)
	if err != nil {
		// TODO: 500/log error
	}
	if err := client.StopJob(jobID); err != nil {
		// TODO: 500/log error
	}
}
<|endoftext|>


<commit_before>package main

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

	"github.com/martinlindhe/trasher/osx"
)

func main() {

	forcePtr := flag.Bool("f", false, "force (noop)")
	recursePtr := flag.Bool("r", false, "recurse (noop)")
	verbosePtr := flag.Bool("v", false, "verbose")
	flag.Parse()

	if *forcePtr {
		// NOOP
	}

	if *recursePtr {
		// NOOP, since we move root folder...
		os.Exit(1)
	}

	if len(flag.Args()) < 1 {
		fmt.Printf("Usage: trash <file>\n")
	}

	args := flag.Args()

	trashPath := osx.CurrentTrashPath()

	for _, arg := range args {
		if !Exists(arg) {
			fmt.Printf("File not found: %s\n", arg)
			continue
		}

		cnt := 1
		base := filepath.Base(arg)
		tname := filepath.Join(trashPath, base)

		for Exists(tname) {
			// trashed file exists, come up with a new name
			tname = filepath.Join(trashPath, base+fmt.Sprintf(" (copy %d)", cnt))
			cnt++
		}

		if *verbosePtr {
			fmt.Printf("Trashing %s => %s\n", arg, tname)
		}

		err := os.Rename(arg, tname)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}
}

// Exists reports whether the named file or directory exists.
func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}
<commit_msg>fix usage<commit_after>package main

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

	"github.com/martinlindhe/trasher/osx"
)

func main() {

	forcePtr := flag.Bool("f", false, "force (noop)")
	recursePtr := flag.Bool("r", false, "recurse (noop)")
	verbosePtr := flag.Bool("v", false, "verbose")
	flag.Parse()

	if *forcePtr {
		// NOOP
	}

	if *recursePtr {
		// NOOP, since we just move each argument
		os.Exit(1)
	}

	if len(flag.Args()) < 1 {
		fmt.Printf("Usage: trasher <file>\n")
	}

	args := flag.Args()

	trashPath := osx.CurrentTrashPath()

	for _, arg := range args {
		if !Exists(arg) {
			fmt.Printf("File not found: %s\n", arg)
			continue
		}

		cnt := 1
		base := filepath.Base(arg)
		tname := filepath.Join(trashPath, base)

		for Exists(tname) {
			// trashed file exists, come up with a new name
			tname = filepath.Join(trashPath, base+fmt.Sprintf(" (copy %d)", cnt))
			cnt++
		}

		if *verbosePtr {
			fmt.Printf("Trashing %s => %s\n", arg, tname)
		}

		err := os.Rename(arg, tname)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}
}

// Exists reports whether the named file or directory exists.
func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package bigcommerce

import (
	"context"
	"fmt"
	"net/http"

	"github.com/dghubble/sling"
)

// Order describes the product resource
type Order struct {
	ID                   int           `json:"id"`
	CustomerID           int           `json:"customer_id"`
	DateCreated          BCTime        `json:"date_created"`
	DateModified         BCTime        `json:"date_modified"`
	DateShipped          BCTime        `json:"date_shipped"`
	StatusID             int           `json:"status_id"`
	Status               string        `json:"status"`
	HandlingCostExTax    float64       `json:"handling_cost_ex_tax,string"`
	HandlingCostIncTax   float64       `json:"handling_cost_inc_tax,string"`
	HandlingCostTax      float64       `json:"handling_cost_tax,string"`
	ShippingCostExTax    float64       `json:"shipping_cost_ex_tax,string"`
	ShippingCostIncTax   float64       `json:"shipping_cost_inc_tax,string"`
	ShippingCostTax      float64       `json:"shipping_cost_tax,string"`
	SubTotalExTax        float64       `json:"subtotal_ex_tax,string"`
	SubTotalIncTax       float64       `json:"subtotal_inc_tax,string"`
	SubTotalTax          float64       `json:"subtotal_tax,string"`
	TotalExTax           float64       `json:"total_ex_tax,string"`
	TotalIncTax          float64       `json:"total_inc_tax,string"`
	TotalTax             float64       `json:"total_tax,string"`
	BaseShippingCost     float64       `json:"base_shipping_cost,string"`
	ItemsTotal           int           `json:"items_total"`
	PaymentMethod        string        `json:"payment_method"`
	PaymentStatus        string        `json:"payment_status"`
	IPAddress            string        `json:"ip_address"`
	CurrencyID           int           `json:"currency_id"`
	CurrencyCode         string        `json:"currency_code"`
	StaffNotes           string        `json:"staff_notes"`
	CustomerMessage      string        `json:"customer_message"`
	DiscountAmount       string        `json:"discount_amount"`
	CouponDiscount       string        `json:"counpon_discount"`
	ShippingAddressCount int           `json:"shipping_address_count"`
	BillingAddress       AddressEntity `json:"billing_address"`
}

// OrderService adds the APIs for the Order resource.
type OrderService struct {
	sling      *sling.Sling
	httpClient *http.Client
}

func newOrderService(sling *sling.Sling, httpClient *http.Client) *OrderService {
	return &OrderService{
		sling:      sling.Path("orders/"),
		httpClient: httpClient,
	}
}

// OrderListParams are the parameters for OrderService.List
type OrderListParams struct {
	Page          int     `url:"page,omitempty"`
	Limit         int     `url:"limit,omitempty"`
	Sort          string  `url:"sort,omitempty"`
	MinID         int     `url:"min_id,omitempty"`
	MaxID         int     `url:"max_id,omitempty"`
	MinTotal      float64 `url:"min_total,omitempty"`
	MaxTotal      float64 `url:"max_total,omitempty"`
	CustomerID    *int    `url:"customer_id,omitempty"`
	Email         string  `url:"email,omitempty"`
	StatusID      *int    `url:"status_id,omitempty"`
	PaymentMethod string  `url:"payment_method,omitempty"`
	//TODO: add date and boolean based params.
}

// List returns a list of Orders matching the given OrderListParams.
func (s *OrderService) List(ctx context.Context, params *OrderListParams) ([]Order, *http.Response, error) {
	var orders []Order
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().QueryStruct(params), s.httpClient, &orders, apiError)
	return orders, resp, relevantError(err, *apiError)
}

// Count returns an OrderCount for Orders that matches the given OrderListParams.
func (s *OrderService) Count(ctx context.Context, params *OrderListParams) (int, *http.Response, error) {
	var count count
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.Get("count").QueryStruct(params), s.httpClient, &count, apiError)
	return count.Count, resp, relevantError(err, *apiError)
}

// Show returns the requested Order.
func (s *OrderService) Show(ctx context.Context, id int32) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Get(fmt.Sprintf("%d", id)), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}

// OrderProduct defines a product to be included in the OrderBody.
// Regular Products require: ProductID and Quantity
// Custom Products require: Name, Quantity and PriceIncTax / PriceExTax
type OrderProduct struct {
	ProductID   int     `json:"product_id,omitempty"`
	ProductName string  `json:"name,omitempty"`
	Quantity    int     `json:"quantity"`
	PriceIncTax float64 `json:"price_inc_tax,omitempty"`
	PriceExTax  float64 `json:"price_ex_tax,omitempty"`
}

// OrderBody describes the order information given when creating a new Order.
type OrderBody struct {
	ExternalSource     string          `json:"external_source"`
	CustomerID         *int            `json:"customer_id"`
	StatusID           *int            `json:"status_id"`
	BillingAddress     AddressEntity   `json:"billing_address"`
	Products           []OrderProduct  `json:"products"`
	ShippingCostIncTax float64         `json:"shipping_cost_inc_tax,omitempty"`
	ShippingCostExTax  float64         `json:"shipping_cost_ex_tax,omitempty"`
	HandlingCostIncTax float64         `json:"handling_cost_inc_tax,omitempty"`
	HandlingCostExTax  float64         `json:"handling_cost_ex_tax,omitempty"`
	DiscountAmount     float64         `json:"discount_amount"`
	ShippingAddresses  AddressEntities `json:"shipping_addresses,omitempty"`
	CustomerMessage    string          `json:"customer_message"`
	StaffNotes         string          `json:"staff_notes"`
	PaymentMethod      string          `json:"payment_method"`
}

// New creates a new Order with the specified information and returns the new order.
func (s *OrderService) New(ctx context.Context, body *OrderBody) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Post("").BodyJSON(body), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}

// OrderEditParams describes the fields that are editable on an Order.
type OrderEditParams struct {
	CustomerID      *int           `json:"customer_id,omitempty"`
	StatusID        *int           `json:"status_id,omitempty"`
	IPAddress       string         `json:"ip_address,omitempty"`
	StaffNotes      string         `json:"staff_notes,omitempty"`
	CustomerMessage string         `json:"customer_message,omitempty"`
	BillingAddress  *AddressEntity `json:"billing_address,omitempty"`
}

// Edit updates the given OrderEditParams of the given Order.
func (s *OrderService) Edit(ctx context.Context, id int, params *OrderEditParams) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Put(fmt.Sprintf("%d", id)).BodyJSON(params), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}
<commit_msg>feat(Orders Service): allow override of totals and subtotals on order creation<commit_after>package bigcommerce

import (
	"context"
	"fmt"
	"net/http"

	"github.com/dghubble/sling"
)

// Order describes the product resource
type Order struct {
	ID                   int           `json:"id"`
	CustomerID           int           `json:"customer_id"`
	DateCreated          BCTime        `json:"date_created"`
	DateModified         BCTime        `json:"date_modified"`
	DateShipped          BCTime        `json:"date_shipped"`
	StatusID             int           `json:"status_id"`
	Status               string        `json:"status"`
	HandlingCostExTax    float64       `json:"handling_cost_ex_tax,string"`
	HandlingCostIncTax   float64       `json:"handling_cost_inc_tax,string"`
	HandlingCostTax      float64       `json:"handling_cost_tax,string"`
	ShippingCostExTax    float64       `json:"shipping_cost_ex_tax,string"`
	ShippingCostIncTax   float64       `json:"shipping_cost_inc_tax,string"`
	ShippingCostTax      float64       `json:"shipping_cost_tax,string"`
	SubTotalExTax        float64       `json:"subtotal_ex_tax,string"`
	SubTotalIncTax       float64       `json:"subtotal_inc_tax,string"`
	SubTotalTax          float64       `json:"subtotal_tax,string"`
	TotalExTax           float64       `json:"total_ex_tax,string"`
	TotalIncTax          float64       `json:"total_inc_tax,string"`
	TotalTax             float64       `json:"total_tax,string"`
	BaseShippingCost     float64       `json:"base_shipping_cost,string"`
	ItemsTotal           int           `json:"items_total"`
	PaymentMethod        string        `json:"payment_method"`
	PaymentStatus        string        `json:"payment_status"`
	IPAddress            string        `json:"ip_address"`
	CurrencyID           int           `json:"currency_id"`
	CurrencyCode         string        `json:"currency_code"`
	StaffNotes           string        `json:"staff_notes"`
	CustomerMessage      string        `json:"customer_message"`
	DiscountAmount       string        `json:"discount_amount"`
	CouponDiscount       string        `json:"counpon_discount"`
	ShippingAddressCount int           `json:"shipping_address_count"`
	BillingAddress       AddressEntity `json:"billing_address"`
}

// OrderService adds the APIs for the Order resource.
type OrderService struct {
	sling      *sling.Sling
	httpClient *http.Client
}

func newOrderService(sling *sling.Sling, httpClient *http.Client) *OrderService {
	return &OrderService{
		sling:      sling.Path("orders/"),
		httpClient: httpClient,
	}
}

// OrderListParams are the parameters for OrderService.List
type OrderListParams struct {
	Page          int     `url:"page,omitempty"`
	Limit         int     `url:"limit,omitempty"`
	Sort          string  `url:"sort,omitempty"`
	MinID         int     `url:"min_id,omitempty"`
	MaxID         int     `url:"max_id,omitempty"`
	MinTotal      float64 `url:"min_total,omitempty"`
	MaxTotal      float64 `url:"max_total,omitempty"`
	CustomerID    *int    `url:"customer_id,omitempty"`
	Email         string  `url:"email,omitempty"`
	StatusID      *int    `url:"status_id,omitempty"`
	PaymentMethod string  `url:"payment_method,omitempty"`
	//TODO: add date and boolean based params.
}

// List returns a list of Orders matching the given OrderListParams.
func (s *OrderService) List(ctx context.Context, params *OrderListParams) ([]Order, *http.Response, error) {
	var orders []Order
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().QueryStruct(params), s.httpClient, &orders, apiError)
	return orders, resp, relevantError(err, *apiError)
}

// Count returns an OrderCount for Orders that matches the given OrderListParams.
func (s *OrderService) Count(ctx context.Context, params *OrderListParams) (int, *http.Response, error) {
	var count count
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.Get("count").QueryStruct(params), s.httpClient, &count, apiError)
	return count.Count, resp, relevantError(err, *apiError)
}

// Show returns the requested Order.
func (s *OrderService) Show(ctx context.Context, id int32) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Get(fmt.Sprintf("%d", id)), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}

// OrderProduct defines a product to be included in the OrderBody.
// Regular Products require: ProductID and Quantity
// Custom Products require: Name, Quantity and PriceIncTax / PriceExTax
type OrderProduct struct {
	ProductID   int     `json:"product_id,omitempty"`
	ProductName string  `json:"name,omitempty"`
	Quantity    int     `json:"quantity"`
	PriceIncTax float64 `json:"price_inc_tax,omitempty"`
	PriceExTax  float64 `json:"price_ex_tax,omitempty"`
}

// OrderBody describes the order information given when creating a new Order.
type OrderBody struct {
	ExternalSource     string          `json:"external_source"`
	CustomerID         *int            `json:"customer_id"`
	StatusID           *int            `json:"status_id"`
	BillingAddress     AddressEntity   `json:"billing_address"`
	Products           []OrderProduct  `json:"products"`
	ShippingCostIncTax float64         `json:"shipping_cost_inc_tax,omitempty"`
	ShippingCostExTax  float64         `json:"shipping_cost_ex_tax,omitempty"`
	HandlingCostIncTax float64         `json:"handling_cost_inc_tax,omitempty"`
	HandlingCostExTax  float64         `json:"handling_cost_ex_tax,omitempty"`
	DiscountAmount     float64         `json:"discount_amount"`
	ShippingAddresses  AddressEntities `json:"shipping_addresses,omitempty"`
	CustomerMessage    string          `json:"customer_message"`
	StaffNotes         string          `json:"staff_notes"`
	PaymentMethod      string          `json:"payment_method"`
	SubtotalExTax      *float64        `json:"subtotal_ex_tax,omitempty"`
	SubtotalIncTax     *float64        `json:"subtotal_inc_tax,omitempty"`
	TotalExTax         *float64        `json:"total_ex_tax,omitempty"`
	TotalIncTax        *float64        `json:"total_inc_tax,omitempty"`
}

// New creates a new Order with the specified information and returns the new order.
func (s *OrderService) New(ctx context.Context, body *OrderBody) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Post("").BodyJSON(body), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}

// OrderEditParams describes the fields that are editable on an Order.
type OrderEditParams struct {
	CustomerID      *int           `json:"customer_id,omitempty"`
	StatusID        *int           `json:"status_id,omitempty"`
	IPAddress       string         `json:"ip_address,omitempty"`
	StaffNotes      string         `json:"staff_notes,omitempty"`
	CustomerMessage string         `json:"customer_message,omitempty"`
	BillingAddress  *AddressEntity `json:"billing_address,omitempty"`
}

// Edit updates the given OrderEditParams of the given Order.
func (s *OrderService) Edit(ctx context.Context, id int, params *OrderEditParams) (*Order, *http.Response, error) {
	order := new(Order)
	apiError := new(APIError)

	resp, err := performRequest(ctx, s.sling.New().Put(fmt.Sprintf("%d", id)).BodyJSON(params), s.httpClient, order, apiError)
	return order, resp, relevantError(err, *apiError)
}
<|endoftext|>