<commit_before>package controller

import (
	"fmt"
	"github.com/eaciit/colony-core/v0"
	"github.com/eaciit/colony-manager/helper"
	"github.com/eaciit/dbox"
	"github.com/eaciit/knot/knot.v1"
	"github.com/eaciit/toolkit"
	"github.com/robfig/cron"
)

var (
	serviceHolder = map[string]*cron.Cron{}
)

type DataGrabberController struct {
	App
}

func CreateDataGrabberController(s *knot.Server) *DataGrabberController {
	var controller = new(DataGrabberController)
	controller.Server = s
	return controller
}

func (d *DataGrabberController) SaveDataGrabber(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	payload := new(colonycore.DataGrabber)
	err := r.GetPayload(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Delete(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Save(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	return helper.CreateResult(true, payload, "")
}

func (d *DataGrabberController) SelectDataGrabber(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	payload := new(colonycore.DataGrabber)
	err := r.GetPayload(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Get(payload, payload.ID)

	return helper.CreateResult(true, payload, "")
}

func (d *DataGrabberController) GetDataGrabber(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	data := []colonycore.DataGrabber{}
	cursor, err := colonycore.Find(new(colonycore.DataGrabber), nil)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = cursor.Fetch(&data, 0, false)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}
	defer cursor.Close()

	return helper.CreateResult(true, data, "")
}

func (d *DataGrabberController) RemoveDataGrabber(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	payload := new(colonycore.DataGrabber)
	err := r.GetPayload(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Delete(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	return helper.CreateResult(true, nil, "")
}

func (d *DataGrabberController) StartTransformation(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	dataGrabber := new(colonycore.DataGrabber)
	err := r.GetPayload(dataGrabber)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Get(dataGrabber, dataGrabber.ID)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	if _, ok := serviceHolder[dataGrabber.ID]; ok {
		process := serviceHolder[dataGrabber.ID]
		process.Stop()
		delete(serviceHolder, dataGrabber.ID)

		fmt.Println("===> Transformation stopped!", dataGrabber.DataSourceOrigin, "->", dataGrabber.DataSourceDestination)
	}

	yo := func() {
		success, data, message := d.Transform(dataGrabber)
		_, _, _ = success, data, message
		// timeout not yet implemented
		// also later, write the logs
	}
	yo()

	process := cron.New()
	serviceHolder[dataGrabber.ID] = process
	duration := fmt.Sprintf("%d%s", dataGrabber.GrabInterval, string(dataGrabber.IntervalType[1]))
	process.AddFunc("@every "+duration, yo)
	process.Start()

	return helper.CreateResult(true, nil, "")
}

func (d *DataGrabberController) StopTransformation(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	dataGrabber := new(colonycore.DataGrabber)
	err := r.GetPayload(dataGrabber)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	if _, ok := serviceHolder[dataGrabber.ID]; ok {
		process := serviceHolder[dataGrabber.ID]
		process.Stop()
		delete(serviceHolder, dataGrabber.ID)

		fmt.Println("===> Transformation stopped!", dataGrabber.DataSourceOrigin, "->", dataGrabber.DataSourceDestination)
	}

	return helper.CreateResult(true, nil, "")
}

func (d *DataGrabberController) Stat(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	dataGrabber := new(colonycore.DataGrabber)
	err := r.GetPayload(dataGrabber)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	_, ok := serviceHolder[dataGrabber.ID]
	return helper.CreateResult(true, ok, "")
}

func (d *DataGrabberController) Transform(dataGrabber *colonycore.DataGrabber) (bool, interface{}, string) {
	fmt.Println("===> Transformation started!", dataGrabber.DataSourceOrigin, "->", dataGrabber.DataSourceDestination, "interval", dataGrabber.GrabInterval, dataGrabber.IntervalType)

	dsOrigin := new(colonycore.DataSource)
	err := colonycore.Get(dsOrigin, dataGrabber.DataSourceOrigin)
	if err != nil {
		return false, nil, err.Error()
	}

	dsDestination := new(colonycore.DataSource)
	err = colonycore.Get(dsDestination, dataGrabber.DataSourceDestination)
	if err != nil {
		return false, nil, err.Error()
	}

	dataDS, _, conn, query, metaSave, err := new(DataSourceController).
		ConnectToDataSource(dataGrabber.DataSourceOrigin)
	if len(dataDS.QueryInfo) == 0 {
		return false, nil, "Data source origin has invalid query"
	}
	if err != nil {
		return false, nil, err.Error()
	}
	defer conn.Close()

	if metaSave.keyword != "" {
		return false, nil, `Data source origin query is not "Select"`
	}

	cursor, err := query.Cursor(nil)
	if err != nil {
		return false, nil, err.Error()
	}
	defer cursor.Close()

	data := []toolkit.M{}
	err = cursor.Fetch(&data, 0, false)
	if err != nil {
		return false, nil, err.Error()
	}

	arrayContains := func(slice []string, key string) bool {
		for _, each := range slice {
			if each == key {
				return true
			}
		}

		return false
	}

	connDesc := new(colonycore.Connection)
	err = colonycore.Get(connDesc, dsDestination.ConnectionID)
	if err != nil {
		return false, nil, err.Error()
	}

	transformedData := []toolkit.M{}

	for _, each := range data {
		eachTransformedData := toolkit.M{}

		for _, eachMeta := range dsDestination.MetaData {
			if arrayContains(dataGrabber.IgnoreFieldsDestination, eachMeta.ID) {
				continue
			}

			fieldFrom := eachMeta.ID
		checkMap:
			for _, eachMap := range dataGrabber.Map {
				if eachMap.FieldDestination == eachMeta.ID {
					fieldFrom = eachMap.FieldOrigin
					break checkMap
				}
			}

			eachTransformedData.Set(eachMeta.ID, each.Get(fieldFrom))
		}

		transformedData = append(transformedData, eachTransformedData)

		tableName := dsDestination.QueryInfo.GetString("from")

		queryWrapper := helper.Query(connDesc.Driver, connDesc.Host, connDesc.Database, connDesc.UserName, connDesc.Password, connDesc.Settings)
		err = queryWrapper.Delete(tableName, dbox.Eq("_id", eachTransformedData.GetString("_id")))

		queryWrapper = helper.Query(connDesc.Driver, connDesc.Host, connDesc.Database, connDesc.UserName, connDesc.Password, connDesc.Settings)
		err = queryWrapper.Save(tableName, eachTransformedData)
		if err != nil {
			return false, nil, err.Error()
		}
	}

	return true, len(transformedData), ""
}
<commit_msg>no message<commit_after>package controller

import (
	"fmt"
	"github.com/eaciit/colony-core/v0"
	"github.com/eaciit/colony-manager/helper"
	"github.com/eaciit/dbox"
	"github.com/eaciit/knot/knot.v1"
	"github.com/eaciit/toolkit"
	"github.com/robfig/cron"
)

var (
	serviceHolder = map[string]*cron.Cron{}
)

type DataGrabberController struct {
	App
}

func CreateDataGrabberController(s *knot.Server) *DataGrabberController {
	var controller = new(DataGrabberController)
	controller.Server = s
	return controller
}

func (d *DataGrabberController) SaveDataGrabber(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	payload := new(colonycore.DataGrabber)
	err := r.GetPayload(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Delete(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Save(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	return helper.CreateResult(true, payload, "")
}

func (d *DataGrabberController) SelectDataGrabber(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	payload := new(colonycore.DataGrabber)
	err := r.GetPayload(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Get(payload, payload.ID)

	return helper.CreateResult(true, payload, "")
}

func (d *DataGrabberController) GetDataGrabber(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	data := []colonycore.DataGrabber{}
	cursor, err := colonycore.Find(new(colonycore.DataGrabber), nil)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = cursor.Fetch(&data, 0, false)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}
	defer cursor.Close()

	return helper.CreateResult(true, data, "")
}

func (d *DataGrabberController) RemoveDataGrabber(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	payload := new(colonycore.DataGrabber)
	err := r.GetPayload(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Delete(payload)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	return helper.CreateResult(true, nil, "")
}

func (d *DataGrabberController) StartTransformation(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	dataGrabber := new(colonycore.DataGrabber)
	err := r.GetPayload(dataGrabber)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	err = colonycore.Get(dataGrabber, dataGrabber.ID)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	if _, ok := serviceHolder[dataGrabber.ID]; ok {
		process := serviceHolder[dataGrabber.ID]
		process.Stop()
		delete(serviceHolder, dataGrabber.ID)

		fmt.Println("===> Transformation stopped!", dataGrabber.DataSourceOrigin, "->", dataGrabber.DataSourceDestination)
	}

	yo := func() {
		success, data, message := d.Transform(dataGrabber)
		_, _, _ = success, data, message
		// timeout not yet implemented
		// also later, write the logs
	}
	yo()

	process := cron.New()
	serviceHolder[dataGrabber.ID] = process
	duration := fmt.Sprintf("%d%s", dataGrabber.GrabInterval, string(dataGrabber.IntervalType[0]))
	process.AddFunc("@every "+duration, yo)
	process.Start()

	return helper.CreateResult(true, nil, "")
}

func (d *DataGrabberController) StopTransformation(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	dataGrabber := new(colonycore.DataGrabber)
	err := r.GetPayload(dataGrabber)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	if _, ok := serviceHolder[dataGrabber.ID]; ok {
		process := serviceHolder[dataGrabber.ID]
		process.Stop()
		delete(serviceHolder, dataGrabber.ID)

		fmt.Println("===> Transformation stopped!", dataGrabber.DataSourceOrigin, "->", dataGrabber.DataSourceDestination)
	}

	return helper.CreateResult(true, nil, "")
}

func (d *DataGrabberController) Stat(r *knot.WebContext) interface{} {
	r.Config.OutputType = knot.OutputJson

	dataGrabber := new(colonycore.DataGrabber)
	err := r.GetPayload(dataGrabber)
	if err != nil {
		return helper.CreateResult(false, nil, err.Error())
	}

	_, ok := serviceHolder[dataGrabber.ID]
	return helper.CreateResult(true, ok, "")
}

func (d *DataGrabberController) Transform(dataGrabber *colonycore.DataGrabber) (bool, interface{}, string) {
	fmt.Println("===> Transformation started!", dataGrabber.DataSourceOrigin, "->", dataGrabber.DataSourceDestination, "interval", dataGrabber.GrabInterval, dataGrabber.IntervalType)

	dsOrigin := new(colonycore.DataSource)
	err := colonycore.Get(dsOrigin, dataGrabber.DataSourceOrigin)
	if err != nil {
		return false, nil, err.Error()
	}

	dsDestination := new(colonycore.DataSource)
	err = colonycore.Get(dsDestination, dataGrabber.DataSourceDestination)
	if err != nil {
		return false, nil, err.Error()
	}

	dataDS, _, conn, query, metaSave, err := new(DataSourceController).
		ConnectToDataSource(dataGrabber.DataSourceOrigin)
	if len(dataDS.QueryInfo) == 0 {
		return false, nil, "Data source origin has invalid query"
	}
	if err != nil {
		return false, nil, err.Error()
	}
	defer conn.Close()

	if metaSave.keyword != "" {
		return false, nil, `Data source origin query is not "Select"`
	}

	cursor, err := query.Cursor(nil)
	if err != nil {
		return false, nil, err.Error()
	}
	defer cursor.Close()

	data := []toolkit.M{}
	err = cursor.Fetch(&data, 0, false)
	if err != nil {
		return false, nil, err.Error()
	}

	arrayContains := func(slice []string, key string) bool {
		for _, each := range slice {
			if each == key {
				return true
			}
		}

		return false
	}

	connDesc := new(colonycore.Connection)
	err = colonycore.Get(connDesc, dsDestination.ConnectionID)
	if err != nil {
		return false, nil, err.Error()
	}

	transformedData := []toolkit.M{}

	for _, each := range data {
		eachTransformedData := toolkit.M{}

		for _, eachMeta := range dsDestination.MetaData {
			if arrayContains(dataGrabber.IgnoreFieldsDestination, eachMeta.ID) {
				continue
			}

			fieldFrom := eachMeta.ID
		checkMap:
			for _, eachMap := range dataGrabber.Map {
				if eachMap.FieldDestination == eachMeta.ID {
					fieldFrom = eachMap.FieldOrigin
					break checkMap
				}
			}

			eachTransformedData.Set(eachMeta.ID, each.Get(fieldFrom))
		}

		transformedData = append(transformedData, eachTransformedData)

		tableName := dsDestination.QueryInfo.GetString("from")

		queryWrapper := helper.Query(connDesc.Driver, connDesc.Host, connDesc.Database, connDesc.UserName, connDesc.Password, connDesc.Settings)
		err = queryWrapper.Delete(tableName, dbox.Eq("_id", eachTransformedData.GetString("_id")))

		queryWrapper = helper.Query(connDesc.Driver, connDesc.Host, connDesc.Database, connDesc.UserName, connDesc.Password, connDesc.Settings)
		err = queryWrapper.Save(tableName, eachTransformedData)
		if err != nil {
			return false, nil, err.Error()
		}
	}

	return true, len(transformedData), ""
}
<|endoftext|>


<commit_before>package controllers

import (
	"bytes"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/eirka/eirka-libs/config"
	"github.com/eirka/eirka-libs/db"
	"github.com/eirka/eirka-libs/user"

	local "github.com/eirka/eirka-post/config"
)

func init() {

	// Database connection settings
	dbase := db.Database{

		User:           local.Settings.Database.User,
		Password:       local.Settings.Database.Password,
		Proto:          local.Settings.Database.Proto,
		Host:           local.Settings.Database.Host,
		Database:       local.Settings.Database.Database,
		MaxIdle:        local.Settings.Database.MaxIdle,
		MaxConnections: local.Settings.Database.MaxConnections,
	}

	// Set up DB connection
	dbase.NewDb()

	// Get limits and stuff from database
	config.GetDatabaseSettings()

	user.Secret = "secret"
}

func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
	req, _ := http.NewRequest(method, path, nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func performJwtFormRequest(r http.Handler, method, path, token string, body bytes.Buffer) *httptest.ResponseRecorder {
	req, _ := http.NewRequest(method, path, &body)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func TestEmailController(t *testing.T) {

	var err error

	gin.SetMode(gin.ReleaseMode)

	router := gin.New()

	router.Use(user.Auth(true))

	router.POST("/email", EmailController)

	first := performRequest(router, "POST", "/email")

	assert.Equal(t, first.Code, 401, "HTTP request code should match")

	u := user.DefaultUser()
	u.SetId(2)
	u.SetAuthenticated()
	u.Password()

	assert.True(t, u.ComparePassword("testpassword"), "Test user password should be set")

	token, err := u.CreateToken()
	if assert.NoError(t, err, "An error was not expected") {
		assert.NotEmpty(t, token, "token should be returned")
	}

	var b bytes.Buffer

	mw := multipart.NewWriter(&b)
	mw.WriteField("ib", "1")
	mw.WriteField("email", "test@cool.com")
	mw.Close()

	second := performJwtFormRequest(router, "POST", "/email", token, b)

	assert.Equal(t, second.Code, 200, "HTTP request code should match")

}
<commit_msg>add email test<commit_after>package controllers

import (
	"bytes"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/eirka/eirka-libs/config"
	"github.com/eirka/eirka-libs/db"
	"github.com/eirka/eirka-libs/user"

	local "github.com/eirka/eirka-post/config"
)

func init() {

	// Database connection settings
	dbase := db.Database{

		User:           local.Settings.Database.User,
		Password:       local.Settings.Database.Password,
		Proto:          local.Settings.Database.Proto,
		Host:           local.Settings.Database.Host,
		Database:       local.Settings.Database.Database,
		MaxIdle:        local.Settings.Database.MaxIdle,
		MaxConnections: local.Settings.Database.MaxConnections,
	}

	// Set up DB connection
	dbase.NewDb()

	// Get limits and stuff from database
	config.GetDatabaseSettings()

	user.Secret = "secret"
}

func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
	req, _ := http.NewRequest(method, path, nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func performJwtFormRequest(r http.Handler, method, path, token string, body bytes.Buffer) *httptest.ResponseRecorder {
	req, _ := http.NewRequest(method, path, &body)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func TestEmailController(t *testing.T) {

	var err error

	gin.SetMode(gin.ReleaseMode)

	router := gin.New()

	router.Use(user.Auth(true))

	router.POST("/email", EmailController)

	first := performRequest(router, "POST", "/email")

	assert.Equal(t, first.Code, 401, "HTTP request code should match")

	u := user.DefaultUser()
	u.SetId(2)
	u.SetAuthenticated()
	u.Password()

	assert.True(t, u.ComparePassword("testpassword"), "Test user password should be set")

	token, err := u.CreateToken()
	if assert.NoError(t, err, "An error was not expected") {
		assert.NotEmpty(t, token, "token should be returned")
	}

	var b bytes.Buffer

	mw := multipart.NewWriter(&b)
	mw.WriteField("ib", "1")
	mw.WriteField("email", "test@cool.com")
	mw.Close()

	second := performJwtFormRequest(router, "POST", "/email", token, b)

	fmt.Println(second)

	assert.Equal(t, second.Code, 200, "HTTP request code should match")

}
<|endoftext|>


<commit_before>// Copyright 2012-2016 Apcera Inc. All rights reserved.

package server

import (
	"time"
)

const (
	// VERSION is the current version for the server.
	VERSION = "0.8.2.beta"

	// DEFAULT_PORT is the default port for client connections.
	DEFAULT_PORT = 4222

	// RANDOM_PORT is the value for port that, when supplied, will cause the
	// server to listen on a randomly-chosen available port. The resolved port
	// is available via the Addr() method.
	RANDOM_PORT = -1

	// DEFAULT_HOST defaults to all interfaces.
	DEFAULT_HOST = "0.0.0.0"

	// MAX_CONTROL_LINE_SIZE is the maximum allowed protocol control line size.
	// 1k should be plenty since payloads sans connect string are separate
	MAX_CONTROL_LINE_SIZE = 1024

	// MAX_PAYLOAD_SIZE is the maximum allowed payload size. Should be using
	// something different if > 1MB payloads are needed.
	MAX_PAYLOAD_SIZE = (1024 * 1024)

	// MAX_PENDING_SIZE is the maximum outbound size (in bytes) per client.
	MAX_PENDING_SIZE = (10 * 1024 * 1024)

	// DEFAULT_MAX_CONNECTIONS is the default maximum connections allowed.
	DEFAULT_MAX_CONNECTIONS = (64 * 1024)

	// TLS_TIMEOUT is the TLS wait time.
	TLS_TIMEOUT = 500 * time.Millisecond

	// AUTH_TIMEOUT is the authorization wait time.
	AUTH_TIMEOUT = 2 * TLS_TIMEOUT

	// DEFAULT_PING_INTERVAL is how often pings are sent to clients and routes.
	DEFAULT_PING_INTERVAL = 2 * time.Minute

	// DEFAULT_PING_MAX_OUT is maximum allowed pings outstanding before disconnect.
	DEFAULT_PING_MAX_OUT = 2

	// CR_LF string
	CR_LF = "\r\n"

	// LEN_CR_LF hold onto the computed size.
	LEN_CR_LF = len(CR_LF)

	// DEFAULT_FLUSH_DEADLINE is the write/flush deadlines.
	DEFAULT_FLUSH_DEADLINE = 2 * time.Second

	// DEFAULT_HTTP_PORT is the default monitoring port.
	DEFAULT_HTTP_PORT = 8222

	// ACCEPT_MIN_SLEEP is the minimum acceptable sleep times on temporary errors.
	ACCEPT_MIN_SLEEP = 10 * time.Millisecond

	// ACCEPT_MAX_SLEEP is the maximum acceptable sleep times on temporary errors
	ACCEPT_MAX_SLEEP = 1 * time.Second

	// DEFAULT_ROUTE_CONNECT Route solicitation intervals.
	DEFAULT_ROUTE_CONNECT = 1 * time.Second

	// DEFAULT_ROUTE_RECONNECT Route reconnect intervals.
	DEFAULT_ROUTE_RECONNECT = 1 * time.Second

	// DEFAULT_ROUTE_DIAL Route dial timeout.
	DEFAULT_ROUTE_DIAL = 1 * time.Second

	// PROTO_SNIPPET_SIZE is the default size of proto to print on parse errors.
	PROTO_SNIPPET_SIZE = 32

	// MAX_MSG_ARGS Maximum possible number of arguments from MSG proto.
	MAX_MSG_ARGS = 4

	// MAX_PUB_ARGS Maximum possible number of arguments from PUB proto.
	MAX_PUB_ARGS = 3
)
<commit_msg>Bump version for multi-user and authorization [ci skip]<commit_after>// Copyright 2012-2016 Apcera Inc. All rights reserved.

package server

import (
	"time"
)

const (
	// VERSION is the current version for the server.
	VERSION = "0.9.0.beta"

	// DEFAULT_PORT is the default port for client connections.
	DEFAULT_PORT = 4222

	// RANDOM_PORT is the value for port that, when supplied, will cause the
	// server to listen on a randomly-chosen available port. The resolved port
	// is available via the Addr() method.
	RANDOM_PORT = -1

	// DEFAULT_HOST defaults to all interfaces.
	DEFAULT_HOST = "0.0.0.0"

	// MAX_CONTROL_LINE_SIZE is the maximum allowed protocol control line size.
	// 1k should be plenty since payloads sans connect string are separate
	MAX_CONTROL_LINE_SIZE = 1024

	// MAX_PAYLOAD_SIZE is the maximum allowed payload size. Should be using
	// something different if > 1MB payloads are needed.
	MAX_PAYLOAD_SIZE = (1024 * 1024)

	// MAX_PENDING_SIZE is the maximum outbound size (in bytes) per client.
	MAX_PENDING_SIZE = (10 * 1024 * 1024)

	// DEFAULT_MAX_CONNECTIONS is the default maximum connections allowed.
	DEFAULT_MAX_CONNECTIONS = (64 * 1024)

	// TLS_TIMEOUT is the TLS wait time.
	TLS_TIMEOUT = 500 * time.Millisecond

	// AUTH_TIMEOUT is the authorization wait time.
	AUTH_TIMEOUT = 2 * TLS_TIMEOUT

	// DEFAULT_PING_INTERVAL is how often pings are sent to clients and routes.
	DEFAULT_PING_INTERVAL = 2 * time.Minute

	// DEFAULT_PING_MAX_OUT is maximum allowed pings outstanding before disconnect.
	DEFAULT_PING_MAX_OUT = 2

	// CR_LF string
	CR_LF = "\r\n"

	// LEN_CR_LF hold onto the computed size.
	LEN_CR_LF = len(CR_LF)

	// DEFAULT_FLUSH_DEADLINE is the write/flush deadlines.
	DEFAULT_FLUSH_DEADLINE = 2 * time.Second

	// DEFAULT_HTTP_PORT is the default monitoring port.
	DEFAULT_HTTP_PORT = 8222

	// ACCEPT_MIN_SLEEP is the minimum acceptable sleep times on temporary errors.
	ACCEPT_MIN_SLEEP = 10 * time.Millisecond

	// ACCEPT_MAX_SLEEP is the maximum acceptable sleep times on temporary errors
	ACCEPT_MAX_SLEEP = 1 * time.Second

	// DEFAULT_ROUTE_CONNECT Route solicitation intervals.
	DEFAULT_ROUTE_CONNECT = 1 * time.Second

	// DEFAULT_ROUTE_RECONNECT Route reconnect intervals.
	DEFAULT_ROUTE_RECONNECT = 1 * time.Second

	// DEFAULT_ROUTE_DIAL Route dial timeout.
	DEFAULT_ROUTE_DIAL = 1 * time.Second

	// PROTO_SNIPPET_SIZE is the default size of proto to print on parse errors.
	PROTO_SNIPPET_SIZE = 32

	// MAX_MSG_ARGS Maximum possible number of arguments from MSG proto.
	MAX_MSG_ARGS = 4

	// MAX_PUB_ARGS Maximum possible number of arguments from PUB proto.
	MAX_PUB_ARGS = 3
)
<|endoftext|>


<commit_before><commit_msg>feat: provide functions for creating mock and public tokenisers<commit_after><|endoftext|>


<commit_before>package server

import (
	"encoding/binary"
	"fmt"
	"net"
	"time"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/golang/protobuf/proto"
)

const (
	deadline = 120 * time.Second
)

// writeMessage writes a *message.Signed to the connection via protobuf.
func writeMessage(conn net.Conn, msgs []*message.Signed) error {
	packets := &message.Packets{
		Packet: msgs,
	}

	reply, err := proto.Marshal(packets)
	if err != nil {
		return err
	}

	if debugMode {
		fmt.Println("[Sent]", packets)
	}

	_, err = conn.Write(frameMessage(reply))
	if err != nil {
		return err
	}

	// Extend the deadline, we just sent a message.
	conn.SetDeadline(time.Now().Add(deadline))

	return nil
}

// frameMessage sets up the message to be sent
// over the wire. This is sent as
// [magicBytes][length][message]
func frameMessage(reply []byte) []byte {
	msg := []byte{}

	msg = append(msg, magicBytes...)

	bs := make([]byte, 4)
	binary.BigEndian.PutUint32(bs, uint32(len(reply)))
	msg = append(msg, bs...)

	msg = append(msg, reply...)

	return msg
}
<commit_msg>Extend pool survival deadline<commit_after>package server

import (
	"encoding/binary"
	"fmt"
	"net"
	"time"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/golang/protobuf/proto"
)

const (
	deadline = 180 * time.Second
)

// writeMessage writes a *message.Signed to the connection via protobuf.
func writeMessage(conn net.Conn, msgs []*message.Signed) error {
	packets := &message.Packets{
		Packet: msgs,
	}

	reply, err := proto.Marshal(packets)
	if err != nil {
		return err
	}

	if debugMode {
		fmt.Println("[Sent]", packets)
	}

	_, err = conn.Write(frameMessage(reply))
	if err != nil {
		return err
	}

	// Extend the deadline, we just sent a message.
	conn.SetDeadline(time.Now().Add(deadline))

	return nil
}

// frameMessage sets up the message to be sent
// over the wire. This is sent as
// [magicBytes][length][message]
func frameMessage(reply []byte) []byte {
	msg := []byte{}

	msg = append(msg, magicBytes...)

	bs := make([]byte, 4)
	binary.BigEndian.PutUint32(bs, uint32(len(reply)))
	msg = append(msg, bs...)

	msg = append(msg, reply...)

	return msg
}
<|endoftext|>


<commit_before>// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package checkmgr provides a check management interace to circonus-gometrics
package checkmgr

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/circonus-labs/circonus-gometrics/api"
)

// Check management offers:
//
// Create a check if one cannot be found matching specific criteria
// Manage metrics in the supplied check (enabling new metrics as they are submitted)
//
// To disable check management, leave Config.Api.Token.Key blank
//
// use cases:
// configure without api token - check management disabled
//  - configuration parameters other than Check.SubmissionUrl, Debug and Log are ignored
//  - note: SubmissionUrl is **required** in this case as there is no way to derive w/o api
// configure with api token - check management enabled
//  - all otehr configuration parameters affect how the trap url is obtained
//    1. provided (Check.SubmissionUrl)
//    2. via check lookup (CheckConfig.Id)
//    3. via a search using CheckConfig.InstanceId + CheckConfig.SearchTag
//    4. a new check is created

const (
	defaultCheckType             = "httptrap"
	defaultTrapMaxURLAge         = "60s"   // 60 seconds
	defaultBrokerMaxResponseTime = "500ms" // 500 milliseconds
	defaultForceMetricActivation = "false"
	statusActive                 = "active"
)

// CheckConfig options for check
type CheckConfig struct {
	// a specific submission url
	SubmissionURL string
	// a specific check id (not check bundle id)
	ID string
	// unique instance id string
	// used to search for a check to use
	// used as check.target when creating a check
	InstanceID string
	// explicitly set check.target (default: instance id)
	TargetHost string
	// a custom display name for the check (as viewed in UI Checks)
	// default: instance id
	DisplayName string
	// unique check searching tag (or tags)
	// used to search for a check to use (combined with instanceid)
	// used as a regular tag when creating a check
	SearchTag string
	// httptrap check secret (for creating a check)
	Secret string
	// additional tags to add to a check (when creating a check)
	// these tags will not be added to an existing check
	Tags string
	// max amount of time to to hold on to a submission url
	// when a given submission fails (due to retries) if the
	// time the url was last updated is > than this, the trap
	// url will be refreshed (e.g. if the broker is changed
	// in the UI) **only relevant when check management is enabled**
	// e.g. 5m, 30m, 1h, etc.
	MaxURLAge string
	// force metric activation - if a metric has been disabled via the UI
	// the default behavior is to *not* re-activate the metric; this setting
	// overrides the behavior and will re-activate the metric when it is
	// encountered. "(true|false)", default "false"
	ForceMetricActivation string
}

// BrokerConfig options for broker
type BrokerConfig struct {
	// a specific broker id (numeric portion of cid)
	ID string
	// one or more tags used to select 1-n brokers from which to select
	// when creating a new check (e.g. datacenter:abc or loc:dfw,dc:abc)
	SelectTag string
	// for a broker to be considered viable it must respond to a
	// connection attempt within this amount of time e.g. 200ms, 2s, 1m
	MaxResponseTime string
}

// Config options
type Config struct {
	Log   *log.Logger
	Debug bool

	// Circonus API config
	API api.Config
	// Check specific configuration options
	Check CheckConfig
	// Broker specific configuration options
	Broker BrokerConfig
}

// CheckTypeType check type
type CheckTypeType string

// CheckInstanceIDType check instance id
type CheckInstanceIDType string

// CheckTargetType check target/host
type CheckTargetType string

// CheckSecretType check secret
type CheckSecretType string

// CheckTagsType check tags
type CheckTagsType string

// CheckDisplayNameType check display name
type CheckDisplayNameType string

// BrokerCNType broker common name
type BrokerCNType string

// CheckManager settings
type CheckManager struct {
	enabled bool
	Log     *log.Logger
	Debug   bool
	apih    *api.API

	initialized   bool
	initializedmu sync.RWMutex

	// check
	checkType             CheckTypeType
	checkID               api.IDType
	checkInstanceID       CheckInstanceIDType
	checkTarget           CheckTargetType
	checkSearchTag        api.TagType
	checkSecret           CheckSecretType
	checkTags             api.TagType
	checkSubmissionURL    api.URLType
	checkDisplayName      CheckDisplayNameType
	forceMetricActivation bool
	forceCheckUpdate      bool

	// metric tags
	metricTags map[string][]string
	mtmu       sync.Mutex

	// broker
	brokerID              api.IDType
	brokerSelectTag       api.TagType
	brokerMaxResponseTime time.Duration

	// state
	checkBundle        *api.CheckBundle
	cbmu               sync.Mutex
	availableMetrics   map[string]bool
	availableMetricsmu sync.Mutex
	trapURL            api.URLType
	trapCN             BrokerCNType
	trapLastUpdate     time.Time
	trapMaxURLAge      time.Duration
	trapmu             sync.Mutex
	certPool           *x509.CertPool
}

// Trap config
type Trap struct {
	URL *url.URL
	TLS *tls.Config
}

// NewCheckManager returns a new check manager
func NewCheckManager(cfg *Config) (*CheckManager, error) {
	return New(cfg)
}

// New returns a new check manager
func New(cfg *Config) (*CheckManager, error) {

	if cfg == nil {
		return nil, errors.New("invalid Check Manager configuration (nil)")
	}

	cm := &CheckManager{enabled: true, initialized: false}

	// Setup logging for check manager
	cm.Debug = cfg.Debug
	cm.Log = cfg.Log
	if cm.Debug && cm.Log == nil {
		cm.Log = log.New(os.Stderr, "", log.LstdFlags)
	}
	if cm.Log == nil {
		cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
	}

	if cfg.Check.SubmissionURL != "" {
		cm.checkSubmissionURL = api.URLType(cfg.Check.SubmissionURL)
	}

	// Blank API Token *disables* check management
	if cfg.API.TokenKey == "" {
		cm.enabled = false
	}

	if !cm.enabled && cm.checkSubmissionURL == "" {
		return nil, errors.New("invalid check manager configuration (no API token AND no submission url)")
	}

	if cm.enabled {
		// initialize api handle
		cfg.API.Debug = cm.Debug
		cfg.API.Log = cm.Log
		apih, err := api.New(&cfg.API)
		if err != nil {
			return nil, err
		}
		cm.apih = apih
	}

	// initialize check related data
	cm.checkType = defaultCheckType

	idSetting := "0"
	if cfg.Check.ID != "" {
		idSetting = cfg.Check.ID
	}
	id, err := strconv.Atoi(idSetting)
	if err != nil {
		return nil, err
	}
	cm.checkID = api.IDType(id)

	cm.checkInstanceID = CheckInstanceIDType(cfg.Check.InstanceID)
	cm.checkTarget = CheckTargetType(cfg.Check.TargetHost)
	cm.checkDisplayName = CheckDisplayNameType(cfg.Check.DisplayName)
	cm.checkSecret = CheckSecretType(cfg.Check.Secret)

	fma := defaultForceMetricActivation
	if cfg.Check.ForceMetricActivation != "" {
		fma = cfg.Check.ForceMetricActivation
	}
	fm, err := strconv.ParseBool(fma)
	if err != nil {
		return nil, err
	}
	cm.forceMetricActivation = fm

	_, an := path.Split(os.Args[0])
	hn, err := os.Hostname()
	if err != nil {
		hn = "unknown"
	}
	if cm.checkInstanceID == "" {
		cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an))
	}
	if cm.checkDisplayName == "" {
		cm.checkDisplayName = CheckDisplayNameType(cm.checkInstanceID)
	}
	if cm.checkTarget == "" {
		cm.checkTarget = CheckTargetType(cm.checkInstanceID)
	}

	if cfg.Check.SearchTag == "" {
		cm.checkSearchTag = []string{fmt.Sprintf("service:%s", an)}
	} else {
		cm.checkSearchTag = strings.Split(strings.Replace(cfg.Check.SearchTag, " ", "", -1), ",")
	}

	if cfg.Check.Tags != "" {
		cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",")
	}

	dur := cfg.Check.MaxURLAge
	if dur == "" {
		dur = defaultTrapMaxURLAge
	}
	maxDur, err := time.ParseDuration(dur)
	if err != nil {
		return nil, err
	}
	cm.trapMaxURLAge = maxDur

	// setup broker
	idSetting = "0"
	if cfg.Broker.ID != "" {
		idSetting = cfg.Broker.ID
	}
	id, err = strconv.Atoi(idSetting)
	if err != nil {
		return nil, err
	}
	cm.brokerID = api.IDType(id)

	if cfg.Broker.SelectTag != "" {
		cm.brokerSelectTag = strings.Split(strings.Replace(cfg.Broker.SelectTag, " ", "", -1), ",")
	}

	dur = cfg.Broker.MaxResponseTime
	if dur == "" {
		dur = defaultBrokerMaxResponseTime
	}
	maxDur, err = time.ParseDuration(dur)
	if err != nil {
		return nil, err
	}
	cm.brokerMaxResponseTime = maxDur

	// metrics
	cm.availableMetrics = make(map[string]bool)
	cm.metricTags = make(map[string][]string)

	return cm, nil
}

// Initialize for sending metrics
func (cm *CheckManager) Initialize() {
	go func() {
		if cm.enabled {
			cm.apih.EnableExponentialBackoff()
		}
		err := cm.initializeTrapURL()
		if err == nil {
			cm.initializedmu.Lock()
			cm.initialized = true
			cm.initializedmu.Unlock()
		} else {
			fmt.Printf("[WARN] error initializing trap %s", err.Error())
		}
		if cm.enabled {
			cm.apih.DisableExponentialBackoff()
		}
	}()
}

// IsReady reflects if the check has been initialied and metrics can be sent to Circonus
func (cm *CheckManager) IsReady() bool {
	cm.initializedmu.RLock()
	defer cm.initializedmu.RUnlock()
	return cm.initialized
}

// GetSubmissionURL returns submission url for circonus
func (cm *CheckManager) GetSubmissionURL() (*Trap, error) {
	if cm.trapURL == "" {
		return nil, fmt.Errorf("[ERROR] no submission url currently available")
		// if err := cm.initializeTrapURL(); err != nil {
		// 	return nil, err
		// }
	}

	trap := &Trap{}

	u, err := url.Parse(string(cm.trapURL))
	if err != nil {
		return nil, err
	}

	trap.URL = u

	if u.Scheme == "https" {
		if cm.certPool == nil {
			cm.loadCACert()
		}
		t := &tls.Config{
			RootCAs: cm.certPool,
		}
		if cm.trapCN != "" {
			t.ServerName = string(cm.trapCN)
		}
		trap.TLS = t
	}

	return trap, nil
}

// ResetTrap URL, force request to the API for the submission URL and broker ca cert
func (cm *CheckManager) ResetTrap() error {
	if cm.trapURL == "" {
		return nil
	}

	cm.trapURL = ""
	cm.certPool = nil
	err := cm.initializeTrapURL()
	return err
}

// RefreshTrap check when the last time the URL was reset, reset if needed
func (cm *CheckManager) RefreshTrap() {
	if cm.trapURL == "" {
		return
	}

	if time.Since(cm.trapLastUpdate) >= cm.trapMaxURLAge {
		cm.ResetTrap()
	}
}
<commit_msg>upd: add short-circuit initialization for the impatient<commit_after>// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package checkmgr provides a check management interace to circonus-gometrics
package checkmgr

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/circonus-labs/circonus-gometrics/api"
)

// Check management offers:
//
// Create a check if one cannot be found matching specific criteria
// Manage metrics in the supplied check (enabling new metrics as they are submitted)
//
// To disable check management, leave Config.Api.Token.Key blank
//
// use cases:
// configure without api token - check management disabled
//  - configuration parameters other than Check.SubmissionUrl, Debug and Log are ignored
//  - note: SubmissionUrl is **required** in this case as there is no way to derive w/o api
// configure with api token - check management enabled
//  - all otehr configuration parameters affect how the trap url is obtained
//    1. provided (Check.SubmissionUrl)
//    2. via check lookup (CheckConfig.Id)
//    3. via a search using CheckConfig.InstanceId + CheckConfig.SearchTag
//    4. a new check is created

const (
	defaultCheckType             = "httptrap"
	defaultTrapMaxURLAge         = "60s"   // 60 seconds
	defaultBrokerMaxResponseTime = "500ms" // 500 milliseconds
	defaultForceMetricActivation = "false"
	statusActive                 = "active"
)

// CheckConfig options for check
type CheckConfig struct {
	// a specific submission url
	SubmissionURL string
	// a specific check id (not check bundle id)
	ID string
	// unique instance id string
	// used to search for a check to use
	// used as check.target when creating a check
	InstanceID string
	// explicitly set check.target (default: instance id)
	TargetHost string
	// a custom display name for the check (as viewed in UI Checks)
	// default: instance id
	DisplayName string
	// unique check searching tag (or tags)
	// used to search for a check to use (combined with instanceid)
	// used as a regular tag when creating a check
	SearchTag string
	// httptrap check secret (for creating a check)
	Secret string
	// additional tags to add to a check (when creating a check)
	// these tags will not be added to an existing check
	Tags string
	// max amount of time to to hold on to a submission url
	// when a given submission fails (due to retries) if the
	// time the url was last updated is > than this, the trap
	// url will be refreshed (e.g. if the broker is changed
	// in the UI) **only relevant when check management is enabled**
	// e.g. 5m, 30m, 1h, etc.
	MaxURLAge string
	// force metric activation - if a metric has been disabled via the UI
	// the default behavior is to *not* re-activate the metric; this setting
	// overrides the behavior and will re-activate the metric when it is
	// encountered. "(true|false)", default "false"
	ForceMetricActivation string
}

// BrokerConfig options for broker
type BrokerConfig struct {
	// a specific broker id (numeric portion of cid)
	ID string
	// one or more tags used to select 1-n brokers from which to select
	// when creating a new check (e.g. datacenter:abc or loc:dfw,dc:abc)
	SelectTag string
	// for a broker to be considered viable it must respond to a
	// connection attempt within this amount of time e.g. 200ms, 2s, 1m
	MaxResponseTime string
}

// Config options
type Config struct {
	Log   *log.Logger
	Debug bool

	// Circonus API config
	API api.Config
	// Check specific configuration options
	Check CheckConfig
	// Broker specific configuration options
	Broker BrokerConfig
}

// CheckTypeType check type
type CheckTypeType string

// CheckInstanceIDType check instance id
type CheckInstanceIDType string

// CheckTargetType check target/host
type CheckTargetType string

// CheckSecretType check secret
type CheckSecretType string

// CheckTagsType check tags
type CheckTagsType string

// CheckDisplayNameType check display name
type CheckDisplayNameType string

// BrokerCNType broker common name
type BrokerCNType string

// CheckManager settings
type CheckManager struct {
	enabled bool
	Log     *log.Logger
	Debug   bool
	apih    *api.API

	initialized   bool
	initializedmu sync.RWMutex

	// check
	checkType             CheckTypeType
	checkID               api.IDType
	checkInstanceID       CheckInstanceIDType
	checkTarget           CheckTargetType
	checkSearchTag        api.TagType
	checkSecret           CheckSecretType
	checkTags             api.TagType
	checkSubmissionURL    api.URLType
	checkDisplayName      CheckDisplayNameType
	forceMetricActivation bool
	forceCheckUpdate      bool

	// metric tags
	metricTags map[string][]string
	mtmu       sync.Mutex

	// broker
	brokerID              api.IDType
	brokerSelectTag       api.TagType
	brokerMaxResponseTime time.Duration

	// state
	checkBundle        *api.CheckBundle
	cbmu               sync.Mutex
	availableMetrics   map[string]bool
	availableMetricsmu sync.Mutex
	trapURL            api.URLType
	trapCN             BrokerCNType
	trapLastUpdate     time.Time
	trapMaxURLAge      time.Duration
	trapmu             sync.Mutex
	certPool           *x509.CertPool
}

// Trap config
type Trap struct {
	URL *url.URL
	TLS *tls.Config
}

// NewCheckManager returns a new check manager
func NewCheckManager(cfg *Config) (*CheckManager, error) {
	return New(cfg)
}

// New returns a new check manager
func New(cfg *Config) (*CheckManager, error) {

	if cfg == nil {
		return nil, errors.New("invalid Check Manager configuration (nil)")
	}

	cm := &CheckManager{enabled: true, initialized: false}

	// Setup logging for check manager
	cm.Debug = cfg.Debug
	cm.Log = cfg.Log
	if cm.Debug && cm.Log == nil {
		cm.Log = log.New(os.Stderr, "", log.LstdFlags)
	}
	if cm.Log == nil {
		cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
	}

	if cfg.Check.SubmissionURL != "" {
		cm.checkSubmissionURL = api.URLType(cfg.Check.SubmissionURL)
	}

	// Blank API Token *disables* check management
	if cfg.API.TokenKey == "" {
		cm.enabled = false
	}

	if !cm.enabled && cm.checkSubmissionURL == "" {
		return nil, errors.New("invalid check manager configuration (no API token AND no submission url)")
	}

	if cm.enabled {
		// initialize api handle
		cfg.API.Debug = cm.Debug
		cfg.API.Log = cm.Log
		apih, err := api.New(&cfg.API)
		if err != nil {
			return nil, err
		}
		cm.apih = apih
	}

	// initialize check related data
	cm.checkType = defaultCheckType

	idSetting := "0"
	if cfg.Check.ID != "" {
		idSetting = cfg.Check.ID
	}
	id, err := strconv.Atoi(idSetting)
	if err != nil {
		return nil, err
	}
	cm.checkID = api.IDType(id)

	cm.checkInstanceID = CheckInstanceIDType(cfg.Check.InstanceID)
	cm.checkTarget = CheckTargetType(cfg.Check.TargetHost)
	cm.checkDisplayName = CheckDisplayNameType(cfg.Check.DisplayName)
	cm.checkSecret = CheckSecretType(cfg.Check.Secret)

	fma := defaultForceMetricActivation
	if cfg.Check.ForceMetricActivation != "" {
		fma = cfg.Check.ForceMetricActivation
	}
	fm, err := strconv.ParseBool(fma)
	if err != nil {
		return nil, err
	}
	cm.forceMetricActivation = fm

	_, an := path.Split(os.Args[0])
	hn, err := os.Hostname()
	if err != nil {
		hn = "unknown"
	}
	if cm.checkInstanceID == "" {
		cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an))
	}
	if cm.checkDisplayName == "" {
		cm.checkDisplayName = CheckDisplayNameType(cm.checkInstanceID)
	}
	if cm.checkTarget == "" {
		cm.checkTarget = CheckTargetType(cm.checkInstanceID)
	}

	if cfg.Check.SearchTag == "" {
		cm.checkSearchTag = []string{fmt.Sprintf("service:%s", an)}
	} else {
		cm.checkSearchTag = strings.Split(strings.Replace(cfg.Check.SearchTag, " ", "", -1), ",")
	}

	if cfg.Check.Tags != "" {
		cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",")
	}

	dur := cfg.Check.MaxURLAge
	if dur == "" {
		dur = defaultTrapMaxURLAge
	}
	maxDur, err := time.ParseDuration(dur)
	if err != nil {
		return nil, err
	}
	cm.trapMaxURLAge = maxDur

	// setup broker
	idSetting = "0"
	if cfg.Broker.ID != "" {
		idSetting = cfg.Broker.ID
	}
	id, err = strconv.Atoi(idSetting)
	if err != nil {
		return nil, err
	}
	cm.brokerID = api.IDType(id)

	if cfg.Broker.SelectTag != "" {
		cm.brokerSelectTag = strings.Split(strings.Replace(cfg.Broker.SelectTag, " ", "", -1), ",")
	}

	dur = cfg.Broker.MaxResponseTime
	if dur == "" {
		dur = defaultBrokerMaxResponseTime
	}
	maxDur, err = time.ParseDuration(dur)
	if err != nil {
		return nil, err
	}
	cm.brokerMaxResponseTime = maxDur

	// metrics
	cm.availableMetrics = make(map[string]bool)
	cm.metricTags = make(map[string][]string)

	return cm, nil
}

// Initialize for sending metrics
func (cm *CheckManager) Initialize() {

	// if not managing the check, quicker initialization
	if !cm.enabled {
		err := cm.initializeTrapURL()
		if err == nil {
			cm.initializedmu.Lock()
			cm.initialized = true
			cm.initializedmu.Unlock()
		} else {
			cm.Log.Printf("[WARN] error initializing trap %s", err.Error())
		}
		return
	}

	// background initialization when we have to reach out to the api
	go func() {
		cm.apih.EnableExponentialBackoff()
		err := cm.initializeTrapURL()
		if err == nil {
			cm.initializedmu.Lock()
			cm.initialized = true
			cm.initializedmu.Unlock()
		} else {
			cm.Log.Printf("[WARN] error initializing trap %s", err.Error())
		}
		cm.apih.DisableExponentialBackoff()
	}()
}

// IsReady reflects if the check has been initialied and metrics can be sent to Circonus
func (cm *CheckManager) IsReady() bool {
	cm.initializedmu.RLock()
	defer cm.initializedmu.RUnlock()
	return cm.initialized
}

// GetSubmissionURL returns submission url for circonus
func (cm *CheckManager) GetSubmissionURL() (*Trap, error) {
	if cm.trapURL == "" {
		return nil, fmt.Errorf("[ERROR] no submission url currently available")
		// if err := cm.initializeTrapURL(); err != nil {
		// 	return nil, err
		// }
	}

	trap := &Trap{}

	u, err := url.Parse(string(cm.trapURL))
	if err != nil {
		return nil, err
	}

	trap.URL = u

	if u.Scheme == "https" {
		if cm.certPool == nil {
			cm.loadCACert()
		}
		t := &tls.Config{
			RootCAs: cm.certPool,
		}
		if cm.trapCN != "" {
			t.ServerName = string(cm.trapCN)
		}
		trap.TLS = t
	}

	return trap, nil
}

// ResetTrap URL, force request to the API for the submission URL and broker ca cert
func (cm *CheckManager) ResetTrap() error {
	if cm.trapURL == "" {
		return nil
	}

	cm.trapURL = ""
	cm.certPool = nil
	err := cm.initializeTrapURL()
	return err
}

// RefreshTrap check when the last time the URL was reset, reset if needed
func (cm *CheckManager) RefreshTrap() {
	if cm.trapURL == "" {
		return
	}

	if time.Since(cm.trapLastUpdate) >= cm.trapMaxURLAge {
		cm.ResetTrap()
	}
}
<|endoftext|>


<commit_before>package observers

import (
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/objects/corpse"
	"github.com/ivan1993spb/snake-server/objects/snake"
	"github.com/ivan1993spb/snake-server/world"
)

type SnakeObserver struct{}

func (SnakeObserver) Observe(stop <-chan struct{}, w *world.World, logger logrus.FieldLogger) {
	go func() {
		// TODO: Create buffer const.
		for event := range w.Events(stop, 32) {
			if event.Type == world.EventTypeObjectDelete {
				if s, ok := event.Payload.(*snake.Snake); ok {
					// TODO: Handle error.
					c, err := corpse.NewCorpse(w, s.GetLocation())
					if err == nil {
						c.Run(stop)
					}
				}
			}
		}
	}()
}
<commit_msg>Create error logging in snake observer<commit_after>package observers

import (
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/objects/corpse"
	"github.com/ivan1993spb/snake-server/objects/snake"
	"github.com/ivan1993spb/snake-server/world"
)

const chanSnakeObserverEventsBuffer = 32

type SnakeObserver struct{}

func (SnakeObserver) Observe(stop <-chan struct{}, w *world.World, logger logrus.FieldLogger) {
	go func() {
		for event := range w.Events(stop, chanSnakeObserverEventsBuffer) {
			if event.Type == world.EventTypeObjectDelete {
				if s, ok := event.Payload.(*snake.Snake); ok {
					if c, err := corpse.NewCorpse(w, s.GetLocation()); err != nil {
						logger.WithError(err).Error("cannot create corpse")
					} else {
						c.Run(stop)
					}
				}
			}
		}
	}()
}
<|endoftext|>


<commit_before>// +build coprocess

package main

import (
	"crypto/md5"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/mitchellh/mapstructure"
	"gopkg.in/xmlpath.v2"

	"github.com/TykTechnologies/tyk/apidef"
	"github.com/TykTechnologies/tyk/user"
)

// IdExtractor is the base interface for an ID extractor.
type IdExtractor interface {
	ExtractAndCheck(*http.Request) (string, ReturnOverrides)
	PostProcess(*http.Request, *user.SessionState, string)
	GenerateSessionID(string, BaseMiddleware) string
}

// BaseExtractor is the base structure for an ID extractor, it implements the IdExtractor interface. Other extractors may override some of its methods.
type BaseExtractor struct {
	Config  *apidef.MiddlewareIdExtractor
	BaseMid BaseMiddleware
	Spec    *APISpec
}

// ExtractAndCheck is called from the CP middleware, if ID extractor is enabled for the current API.
func (e *BaseExtractor) ExtractAndCheck(r *http.Request) (sessionID string, returnOverrides ReturnOverrides) {
	log.WithFields(logrus.Fields{
		"prefix": "idextractor",
	}).Error("This extractor doesn't implement an extraction method, rejecting.")
	return "", ReturnOverrides{ResponseCode: 403, ResponseError: "Key not authorised"}
}

// PostProcess sets context variables and updates the storage.
func (e *BaseExtractor) PostProcess(r *http.Request, session *user.SessionState, sessionID string) {
	sessionLifetime := session.Lifetime(e.Spec.SessionLifetime)
	e.Spec.SessionManager.UpdateSession(sessionID, session, sessionLifetime)

	ctxSetSession(r, session)
	ctxSetAuthToken(r, sessionID)
}

// ExtractHeader is used when a HeaderSource is specified.
func (e *BaseExtractor) ExtractHeader(r *http.Request) (headerValue string, err error) {
	headerName := e.Config.ExtractorConfig["header_name"].(string)
	headerValue = r.Header.Get(headerName)
	if headerValue == "" {
		err = errors.New("Bad header value.")
	}
	return headerValue, err
}

// ExtractForm is used when a FormSource is specified.
func (e *BaseExtractor) ExtractForm(r *http.Request, paramName string) (formValue string, err error) {
	copiedRequest := copyRequest(r)
	copiedRequest.ParseForm()

	if paramName == "" {
		return "", errors.New("no form param name set")
	}

	values := copiedRequest.Form[paramName]
	if len(values) == 0 {
		return "", errors.New("no form value")
	}

	return strings.Join(values, ""), nil
}

func (e *BaseExtractor) ExtractBody(r *http.Request) (bodyValue string, err error) {
	return bodyValue, err
}

// Error is a helper for logging the extractor errors. It always returns HTTP 400 (so we don't expose any details).
func (e *BaseExtractor) Error(r *http.Request, err error, message string) (returnOverrides ReturnOverrides) {
	logEntry := getLogEntryForRequest(r, "", nil)
	logEntry.Info("Extractor error: ", message, ", ", err)

	return ReturnOverrides{
		ResponseCode:  400,
		ResponseError: "Authorization field missing",
	}
}

// GenerateSessionID is a helper for generating session IDs, it takes an input (usually the extractor output) and a middleware pointer.
func (e *BaseExtractor) GenerateSessionID(input string, mw BaseMiddleware) (sessionID string) {
	data := []byte(input)
	tokenID := fmt.Sprintf("%x", md5.Sum(data))
	sessionID = mw.Spec.OrgID + tokenID
	return sessionID
}

type ValueExtractor struct {
	BaseExtractor
}

type ValueExtractorConfig struct {
	HeaderName    string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	FormParamName string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *ValueExtractor) Extract(input interface{}) string {
	headerValue := input.(string)
	return headerValue
}

func (e *ValueExtractor) ExtractAndCheck(r *http.Request) (sessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var config ValueExtractorConfig

	err := mapstructure.Decode(e.Config.ExtractorConfig, &config)
	if err != nil {
		returnOverrides = e.Error(r, err, "Couldn't decode ValueExtractor configuration")
		return sessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case apidef.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case apidef.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "ValueExtractor error")
		return sessionID, returnOverrides
	}

	sessionID = e.GenerateSessionID(extractorOutput, e.BaseMid)

	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(sessionID)

	if keyExists {
		lastUpdated, _ := strconv.Atoi(previousSession.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSession.IdExtractorDeadline
		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, &previousSession, sessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return sessionID, returnOverrides
}

type RegexExtractor struct {
	BaseExtractor
}

type RegexExtractorConfig struct {
	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *RegexExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var config RegexExtractorConfig

	err := mapstructure.Decode(e.Config.ExtractorConfig, &config)
	if err != nil {
		returnOverrides = e.Error(r, err, "Can't decode RegexExtractor configuration")
		return SessionID, returnOverrides
	}

	if e.Config.ExtractorConfig["regex_expression"] == nil {
		returnOverrides = e.Error(r, nil, "RegexExtractor expects an expression")
		return SessionID, returnOverrides
	}

	expression, err := regexp.Compile(config.RegexExpression)

	if err != nil {
		returnOverrides = e.Error(r, nil, "RegexExtractor found an invalid expression")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case apidef.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case apidef.BodySource:
		extractorOutput, err = e.ExtractBody(r)
	case apidef.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "RegexExtractor error")
		return SessionID, returnOverrides
	}

	regexOutput := expression.FindAllString(extractorOutput, -1)

	if config.RegexMatchIndex > len(regexOutput)-1 {
		returnOverrides = e.Error(r, fmt.Errorf("Can't find regexp match group"), "RegexExtractor error")
		return SessionID, returnOverrides
	}

	SessionID = e.GenerateSessionID(regexOutput[config.RegexMatchIndex], e.BaseMid)

	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(SessionID)

	if keyExists {

		lastUpdated, _ := strconv.Atoi(previousSession.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSession.IdExtractorDeadline

		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, &previousSession, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

type XPathExtractor struct {
	BaseExtractor
}

type XPathExtractorConfig struct {
	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *XPathExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string

	var config XPathExtractorConfig
	err := mapstructure.Decode(e.Config.ExtractorConfig, &config)

	if e.Config.ExtractorConfig["xpath_expression"] == nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: no expression set")
		return SessionID, returnOverrides
	}

	expressionString := e.Config.ExtractorConfig["xpath_expression"].(string)

	expression, err := xmlpath.Compile(expressionString)
	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: bad expression")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case apidef.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case apidef.BodySource:
		extractorOutput, err = e.ExtractBody(r)
	case apidef.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}
	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor error")
		return SessionID, returnOverrides
	}

	extractedXml, err := xmlpath.Parse(strings.NewReader(extractorOutput))
	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: couldn't parse input")
		return SessionID, returnOverrides
	}

	output, ok := expression.String(extractedXml)
	if !ok {
		returnOverrides = e.Error(r, err, "XPathExtractor: no input")
		return SessionID, returnOverrides
	}

	SessionID = e.GenerateSessionID(output, e.BaseMid)

	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(SessionID)
	if keyExists {

		lastUpdated, _ := strconv.Atoi(previousSession.LastUpdated)

		deadlineTs := int64(lastUpdated) + previousSession.IdExtractorDeadline

		if deadlineTs > time.Now().Unix() {
			e.PostProcess(r, &previousSession, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

// newExtractor is called from the CP middleware for every API that specifies extractor settings.
func newExtractor(referenceSpec *APISpec, mw BaseMiddleware) {
	var extractor IdExtractor

	baseExtractor := BaseExtractor{&referenceSpec.CustomMiddleware.IdExtractor, mw, referenceSpec}

	// Initialize a extractor based on the API spec.
	switch referenceSpec.CustomMiddleware.IdExtractor.ExtractWith {
	case apidef.ValueExtractor:
		extractor = &ValueExtractor{baseExtractor}
	case apidef.RegexExtractor:
		extractor = &RegexExtractor{baseExtractor}
	case apidef.XPathExtractor:
		extractor = &XPathExtractor{baseExtractor}
	}

	referenceSpec.CustomMiddleware.IdExtractor.Extractor = extractor
}
<commit_msg>Remove lastUpdated field usage as is not used<commit_after>// +build coprocess

package main

import (
	"crypto/md5"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/mitchellh/mapstructure"
	"gopkg.in/xmlpath.v2"

	"github.com/TykTechnologies/tyk/apidef"
	"github.com/TykTechnologies/tyk/user"
)

// IdExtractor is the base interface for an ID extractor.
type IdExtractor interface {
	ExtractAndCheck(*http.Request) (string, ReturnOverrides)
	PostProcess(*http.Request, *user.SessionState, string)
	GenerateSessionID(string, BaseMiddleware) string
}

// BaseExtractor is the base structure for an ID extractor, it implements the IdExtractor interface. Other extractors may override some of its methods.
type BaseExtractor struct {
	Config  *apidef.MiddlewareIdExtractor
	BaseMid BaseMiddleware
	Spec    *APISpec
}

// ExtractAndCheck is called from the CP middleware, if ID extractor is enabled for the current API.
func (e *BaseExtractor) ExtractAndCheck(r *http.Request) (sessionID string, returnOverrides ReturnOverrides) {
	log.WithFields(logrus.Fields{
		"prefix": "idextractor",
	}).Error("This extractor doesn't implement an extraction method, rejecting.")
	return "", ReturnOverrides{ResponseCode: 403, ResponseError: "Key not authorised"}
}

// PostProcess sets context variables and updates the storage.
func (e *BaseExtractor) PostProcess(r *http.Request, session *user.SessionState, sessionID string) {
	sessionLifetime := session.Lifetime(e.Spec.SessionLifetime)
	e.Spec.SessionManager.UpdateSession(sessionID, session, sessionLifetime)

	ctxSetSession(r, session)
	ctxSetAuthToken(r, sessionID)
}

// ExtractHeader is used when a HeaderSource is specified.
func (e *BaseExtractor) ExtractHeader(r *http.Request) (headerValue string, err error) {
	headerName := e.Config.ExtractorConfig["header_name"].(string)
	headerValue = r.Header.Get(headerName)
	if headerValue == "" {
		err = errors.New("Bad header value.")
	}
	return headerValue, err
}

// ExtractForm is used when a FormSource is specified.
func (e *BaseExtractor) ExtractForm(r *http.Request, paramName string) (formValue string, err error) {
	copiedRequest := copyRequest(r)
	copiedRequest.ParseForm()

	if paramName == "" {
		return "", errors.New("no form param name set")
	}

	values := copiedRequest.Form[paramName]
	if len(values) == 0 {
		return "", errors.New("no form value")
	}

	return strings.Join(values, ""), nil
}

func (e *BaseExtractor) ExtractBody(r *http.Request) (bodyValue string, err error) {
	return bodyValue, err
}

// Error is a helper for logging the extractor errors. It always returns HTTP 400 (so we don't expose any details).
func (e *BaseExtractor) Error(r *http.Request, err error, message string) (returnOverrides ReturnOverrides) {
	logEntry := getLogEntryForRequest(r, "", nil)
	logEntry.Info("Extractor error: ", message, ", ", err)

	return ReturnOverrides{
		ResponseCode:  400,
		ResponseError: "Authorization field missing",
	}
}

// GenerateSessionID is a helper for generating session IDs, it takes an input (usually the extractor output) and a middleware pointer.
func (e *BaseExtractor) GenerateSessionID(input string, mw BaseMiddleware) (sessionID string) {
	data := []byte(input)
	tokenID := fmt.Sprintf("%x", md5.Sum(data))
	sessionID = mw.Spec.OrgID + tokenID
	return sessionID
}

type ValueExtractor struct {
	BaseExtractor
}

type ValueExtractorConfig struct {
	HeaderName    string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	FormParamName string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *ValueExtractor) Extract(input interface{}) string {
	headerValue := input.(string)
	return headerValue
}

func (e *ValueExtractor) ExtractAndCheck(r *http.Request) (sessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var config ValueExtractorConfig

	err := mapstructure.Decode(e.Config.ExtractorConfig, &config)
	if err != nil {
		returnOverrides = e.Error(r, err, "Couldn't decode ValueExtractor configuration")
		return sessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case apidef.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case apidef.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "ValueExtractor error")
		return sessionID, returnOverrides
	}

	sessionID = e.GenerateSessionID(extractorOutput, e.BaseMid)

	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(sessionID)

	if keyExists {
		if previousSession.IdExtractorDeadline > time.Now().Unix() {
			e.PostProcess(r, &previousSession, sessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return sessionID, returnOverrides
}

type RegexExtractor struct {
	BaseExtractor
}

type RegexExtractorConfig struct {
	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *RegexExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string
	var config RegexExtractorConfig

	err := mapstructure.Decode(e.Config.ExtractorConfig, &config)
	if err != nil {
		returnOverrides = e.Error(r, err, "Can't decode RegexExtractor configuration")
		return SessionID, returnOverrides
	}

	if e.Config.ExtractorConfig["regex_expression"] == nil {
		returnOverrides = e.Error(r, nil, "RegexExtractor expects an expression")
		return SessionID, returnOverrides
	}

	expression, err := regexp.Compile(config.RegexExpression)

	if err != nil {
		returnOverrides = e.Error(r, nil, "RegexExtractor found an invalid expression")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case apidef.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case apidef.BodySource:
		extractorOutput, err = e.ExtractBody(r)
	case apidef.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}

	if err != nil {
		returnOverrides = e.Error(r, err, "RegexExtractor error")
		return SessionID, returnOverrides
	}

	regexOutput := expression.FindAllString(extractorOutput, -1)

	if config.RegexMatchIndex > len(regexOutput)-1 {
		returnOverrides = e.Error(r, fmt.Errorf("Can't find regexp match group"), "RegexExtractor error")
		return SessionID, returnOverrides
	}

	SessionID = e.GenerateSessionID(regexOutput[config.RegexMatchIndex], e.BaseMid)

	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(SessionID)

	if keyExists {

		if previousSession.IdExtractorDeadline > time.Now().Unix() {
			e.PostProcess(r, &previousSession, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

type XPathExtractor struct {
	BaseExtractor
}

type XPathExtractorConfig struct {
	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
	RegexExpression string `mapstructure:"regex_expression" bson:"regex_expression" json:"regex_expression"`
	RegexMatchIndex int    `mapstructure:"regex_match_index" bson:"regex_match_index" json:"regex_match_index"`
	FormParamName   string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
}

func (e *XPathExtractor) ExtractAndCheck(r *http.Request) (SessionID string, returnOverrides ReturnOverrides) {
	var extractorOutput string

	var config XPathExtractorConfig
	err := mapstructure.Decode(e.Config.ExtractorConfig, &config)

	if e.Config.ExtractorConfig["xpath_expression"] == nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: no expression set")
		return SessionID, returnOverrides
	}

	expressionString := e.Config.ExtractorConfig["xpath_expression"].(string)

	expression, err := xmlpath.Compile(expressionString)
	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: bad expression")
		return SessionID, returnOverrides
	}

	switch e.Config.ExtractFrom {
	case apidef.HeaderSource:
		extractorOutput, err = e.ExtractHeader(r)
	case apidef.BodySource:
		extractorOutput, err = e.ExtractBody(r)
	case apidef.FormSource:
		extractorOutput, err = e.ExtractForm(r, config.FormParamName)
	}
	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor error")
		return SessionID, returnOverrides
	}

	extractedXml, err := xmlpath.Parse(strings.NewReader(extractorOutput))
	if err != nil {
		returnOverrides = e.Error(r, err, "XPathExtractor: couldn't parse input")
		return SessionID, returnOverrides
	}

	output, ok := expression.String(extractedXml)
	if !ok {
		returnOverrides = e.Error(r, err, "XPathExtractor: no input")
		return SessionID, returnOverrides
	}

	SessionID = e.GenerateSessionID(output, e.BaseMid)

	previousSession, keyExists := e.BaseMid.CheckSessionAndIdentityForValidKey(SessionID)
	if keyExists {
		if previousSession.IdExtractorDeadline > time.Now().Unix() {
			e.PostProcess(r, &previousSession, SessionID)
			returnOverrides = ReturnOverrides{
				ResponseCode: 200,
			}
		}
	}

	return SessionID, returnOverrides
}

// newExtractor is called from the CP middleware for every API that specifies extractor settings.
func newExtractor(referenceSpec *APISpec, mw BaseMiddleware) {
	var extractor IdExtractor

	baseExtractor := BaseExtractor{&referenceSpec.CustomMiddleware.IdExtractor, mw, referenceSpec}

	// Initialize a extractor based on the API spec.
	switch referenceSpec.CustomMiddleware.IdExtractor.ExtractWith {
	case apidef.ValueExtractor:
		extractor = &ValueExtractor{baseExtractor}
	case apidef.RegexExtractor:
		extractor = &RegexExtractor{baseExtractor}
	case apidef.XPathExtractor:
		extractor = &XPathExtractor{baseExtractor}
	}

	referenceSpec.CustomMiddleware.IdExtractor.Extractor = extractor
}
<|endoftext|>


<commit_before>// Title：表格及其下属控件
//
// Description:
//
// Author:black
//
// Createtime:2013-08-09 16:48
//
// Version:1.0
//
// 修改历史:版本号 修改日期 修改人 修改说明
//
// 1.0 2013-08-09 16:48 black 创建文档
package lessgo

import (
	"bytes"
	"net/http"
	"text/template"
)

type formPanel struct {
	Entity      string    `xml:"entity,attr"`
	Load        string    `xml:"load,attr"`
	Id          string    `xml:"id,attr"`
	Title       string    `xml:"title,attr"`
	Elements    []element `xml:"element"`
	BeforeSave  string    `xml:"beforeSave"`
	AfterRender string    `xml:"afterRender"`
	AfterSave   string    `xml:"afterSave"`
	Inwindow    string    `xml:"inwindow,attr"`
}

type element struct {
	Field    string `xml:"field,attr"`
	Desc     string `xml:"desc,attr"`
	Type     string `xml:"type,attr"`
	Readonly string `xml:"readonly,attr"`
	Validate string `xml:"validate,attr"`
	Tip      string `xml:"tip,attr"`

	LocalData string `xml:"localData,attr"` //for 本地下拉框

	Url        string `xml:"url,attr"`        //for 远程下拉框
	ValueField string `xml:"valueField,attr"` //for 远程下拉框，多选框控件
	DescField  string `xml:"descField,attr"`  //for 远程下拉框，多选框控件

	DefaultValue string `xml:"defaultValue,attr"` //for 隐藏域

	RefTable string `xml:"refTable,attr"` //for 多选框控件
	SelfId   string `xml:"selfId,attr"`   //for 多选框控件
	RefId    string `xml:"refId,attr"`    //for 多选框控件

	ImageEntity string `xml:"imageEntity,attr"` //for Image控件
	ImagePath   string `xml:"imagePath,attr"`   //for Image控件
	Widths      string `xml:"widths,attr"`      //for Image控件
	MaxWidth    string `xml:"maxWidth,attr"`    //for Image控件
	MaxHeight   string `xml:"maxHeight,attr"`   //for Image控件
	MinWidth    string `xml:"minWidth,attr"`    //for Image控件
	MinHeight   string `xml:"minHeight,attr"`   //for Image控件
	MaxSize     string `xml:"maxSize,attr"`     //for Image控件
	Resolution  string `xml:"resolution,attr"`  //for Image控件
	ImageType   string `xml:"imageType,attr"`   //for Image控件
}

func (formpanel formPanel) generate(entity Entity, terminal, packageName string, r *http.Request) []byte {

	var t *template.Template

	var buf bytes.Buffer

	formpanel.Id = packageName + "." + formpanel.Id

	runtimeComponentContain[formpanel.Id] = formpanel

	t = template.New("formpanel.html")

	t = t.Funcs(template.FuncMap{
		"getComponentId":  getComponentId,
		"compareInt":      CompareInt,
		"compareString":   CompareString,
		"getPropValue":    GetPropValue,
		"dealHTMLEscaper": DealHTMLEscaper,
	})

	t, err := t.ParseFiles("../lessgo/template/component/" + terminal + "/formpanel.html")

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	data := make(map[string]interface{})

	data["Formpanel"] = formpanel
	data["Entity"] = entity
	data["Terminal"] = terminal

	if formpanel.Load == "true" {
		id := r.FormValue("id")
		model, err := findById(entity, id)

		if err != nil {
			Log.Error(err.Error())
			return []byte{}
		}

		data["Model"] = model
	}

	err = t.Execute(&buf, data)

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	return buf.Bytes()

}
<commit_msg>HTML编辑器修改<commit_after>// Title：表格及其下属控件
//
// Description:
//
// Author:black
//
// Createtime:2013-08-09 16:48
//
// Version:1.0
//
// 修改历史:版本号 修改日期 修改人 修改说明
//
// 1.0 2013-08-09 16:48 black 创建文档
package lessgo

import (
	"bytes"
	"net/http"
	"text/template"
)

type formPanel struct {
	Entity      string    `xml:"entity,attr"`
	Load        string    `xml:"load,attr"`
	Id          string    `xml:"id,attr"`
	Title       string    `xml:"title,attr"`
	Elements    []element `xml:"element"`
	BeforeSave  string    `xml:"beforeSave"`
	AfterRender string    `xml:"afterRender"`
	AfterSave   string    `xml:"afterSave"`
	Inwindow    string    `xml:"inwindow,attr"`
}

type element struct {
	Field    string `xml:"field,attr"`
	Desc     string `xml:"desc,attr"`
	Type     string `xml:"type,attr"`
	Readonly string `xml:"readonly,attr"`
	Validate string `xml:"validate,attr"`
	Tip      string `xml:"tip,attr"`

	LocalData string `xml:"localData,attr"` //for 本地下拉框

	Url        string `xml:"url,attr"`        //for 远程下拉框
	ValueField string `xml:"valueField,attr"` //for 远程下拉框，多选框控件
	DescField  string `xml:"descField,attr"`  //for 远程下拉框，多选框控件

	DefaultValue string `xml:"defaultValue,attr"` //for 隐藏域

	RefTable string `xml:"refTable,attr"` //for 多选框控件
	SelfId   string `xml:"selfId,attr"`   //for 多选框控件
	RefId    string `xml:"refId,attr"`    //for 多选框控件

	ImageEntity string `xml:"imageEntity,attr"` //for Image控件
	ImagePath   string `xml:"imagePath,attr"`   //for Image控件
	Widths      string `xml:"widths,attr"`      //for Image控件
	MaxWidth    string `xml:"maxWidth,attr"`    //for Image控件
	MaxHeight   string `xml:"maxHeight,attr"`   //for Image控件
	MinWidth    string `xml:"minWidth,attr"`    //for Image控件
	MinHeight   string `xml:"minHeight,attr"`   //for Image控件
	MaxSize     string `xml:"maxSize,attr"`     //for Image控件
	Resolution  string `xml:"resolution,attr"`  //for Image控件
	ImageType   string `xml:"imageType,attr"`   //for Image控件

	UploadUrl   string `xml:"uploadUrl,attr"`   //for HTML编辑器控件
}

func (formpanel formPanel) generate(entity Entity, terminal, packageName string, r *http.Request) []byte {

	var t *template.Template

	var buf bytes.Buffer

	formpanel.Id = packageName + "." + formpanel.Id

	runtimeComponentContain[formpanel.Id] = formpanel

	t = template.New("formpanel.html")

	t = t.Funcs(template.FuncMap{
		"getComponentId":  getComponentId,
		"compareInt":      CompareInt,
		"compareString":   CompareString,
		"getPropValue":    GetPropValue,
		"dealHTMLEscaper": DealHTMLEscaper,
	})

	t, err := t.ParseFiles("../lessgo/template/component/" + terminal + "/formpanel.html")

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	data := make(map[string]interface{})

	data["Formpanel"] = formpanel
	data["Entity"] = entity
	data["Terminal"] = terminal

	if formpanel.Load == "true" {
		id := r.FormValue("id")
		model, err := findById(entity, id)

		if err != nil {
			Log.Error(err.Error())
			return []byte{}
		}

		data["Model"] = model
	}

	err = t.Execute(&buf, data)

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	return buf.Bytes()

}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strconv"

	"github.com/bouk/httprouter"
	"github.com/influxdata/chronograf"
)

type userRequest struct {
	ID       uint64            `json:"id,string"`
	Name     string            `json:"name"`
	Provider string            `json:"provider"`
	Scheme   string            `json:"scheme"`
	Roles    []chronograf.Role `json:"roles"`
}

func (r *userRequest) ValidCreate() error {
	if r.Name == "" {
		return fmt.Errorf("Name required on Chronograf User request body")
	}
	if r.Provider == "" {
		return fmt.Errorf("Provider required on Chronograf User request body")
	}
	if r.Scheme == "" {
		return fmt.Errorf("Scheme required on Chronograf User request body")
	}

	// TODO: This Scheme value is hard-coded temporarily since we only currently
	// support OAuth2. This hard-coding should be removed whenever we add
	// support for other authentication schemes.
	r.Scheme = "OAuth2"
	return r.ValidRoles()
}

// TODO: Provide detailed error message
// TODO: Reconsider what fields should actually be updateable once this is more robust
func (r *userRequest) ValidUpdate() error {
	if r.Name == "" && r.Provider == "" && r.Scheme == "" && r.Roles == nil {
		return fmt.Errorf("No fields to update")
	}
	return r.ValidRoles()
}

func (r *userRequest) ValidRoles() error {
	if len(r.Roles) > 0 {
		for _, r := range r.Roles {
			switch r.Name {
			// TODO: add SuperAdmin
			case ViewerRoleName, EditorRoleName, AdminRoleName:
				continue
			default:
				return fmt.Errorf("Unknown role %s. Valid roles are 'viewer', 'editor', 'admin', and 'superadmin'", r.Name)
			}
		}
	}
	return nil
}

type userResponse struct {
	Links    selfLinks         `json:"links"`
	ID       uint64            `json:"id,string"`
	Name     string            `json:"name"`
	Provider string            `json:"provider"`
	Scheme   string            `json:"scheme"`
	Roles    []chronograf.Role `json:"roles"`
}

func newUserResponse(u *chronograf.User) *userResponse {
	// This ensures that any user response with no roles returns an empty array instead of
	// null when marshaled into JSON. That way, JavaScript doesn't need any guard on the
	// key existing and it can simply be iterated over.
	if u.Roles == nil {
		u.Roles = []chronograf.Role{}
	}
	return &userResponse{
		ID:       u.ID,
		Name:     u.Name,
		Provider: u.Provider,
		Scheme:   u.Scheme,
		Roles:    u.Roles,
		Links: selfLinks{
			Self: fmt.Sprintf("/chronograf/v1/users/%d", u.ID),
		},
	}
}

type usersResponse struct {
	Links selfLinks       `json:"links"`
	Users []*userResponse `json:"users"`
}

func newUsersResponse(users []chronograf.User) *usersResponse {
	usersResp := make([]*userResponse, len(users))
	for i, user := range users {
		usersResp[i] = newUserResponse(&user)
	}
	sort.Slice(usersResp, func(i, j int) bool {
		return usersResp[i].ID < usersResp[j].ID
	})
	return &usersResponse{
		Users: usersResp,
		Links: selfLinks{
			Self: "/chronograf/v1/users",
		},
	}
}

// Chronograf User Roles
const (
	ViewerRoleName = "viewer"
	EditorRoleName = "editor"
	AdminRoleName  = "admin"
)

var (
	// ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, and Sources
	ViewerRole = chronograf.Role{
		Name: ViewerRoleName,
	}

	// EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, and Sources
	EditorRole = chronograf.Role{
		Name: EditorRoleName,
	}

	// AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Users
	AdminRole = chronograf.Role{
		Name: AdminRoleName,
	}
)

// UserID retrieves a Chronograf user with ID from store
func (s *Service) UserID(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	idStr := httprouter.GetParamFromContext(ctx, "id")
	id, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
		return
	}
	user, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
	if err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	res := newUserResponse(user)
	encodeJSON(w, http.StatusOK, res, s.Logger)
}

// NewUser adds a new Chronograf user to store
func (s *Service) NewUser(w http.ResponseWriter, r *http.Request) {
	var req userRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		invalidJSON(w, s.Logger)
		return
	}

	if err := req.ValidCreate(); err != nil {
		invalidData(w, err, s.Logger)
		return
	}

	ctx := r.Context()
	user := &chronograf.User{
		Name:     req.Name,
		Provider: req.Provider,
		Scheme:   req.Scheme,
		Roles:    req.Roles,
	}

	res, err := s.UsersStore.Add(ctx, user)
	if err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	cu := newUserResponse(res)
	location(w, cu.Links.Self)
	encodeJSON(w, http.StatusCreated, cu, s.Logger)
}

// RemoveUser deletes a Chronograf user from store
func (s *Service) RemoveUser(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	idStr := httprouter.GetParamFromContext(ctx, "id")
	id, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
		return
	}

	u, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
	if err != nil {
		Error(w, http.StatusNotFound, err.Error(), s.Logger)
	}
	if err := s.UsersStore.Delete(ctx, u); err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
	}

	w.WriteHeader(http.StatusNoContent)
}

// UpdateUser updates a Chronograf user in store
func (s *Service) UpdateUser(w http.ResponseWriter, r *http.Request) {
	var req userRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		invalidJSON(w, s.Logger)
		return
	}

	if err := req.ValidUpdate(); err != nil {
		invalidData(w, err, s.Logger)
		return
	}

	ctx := r.Context()
	idStr := httprouter.GetParamFromContext(ctx, "id")
	id, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
		return
	}

	u, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
	if err != nil {
		Error(w, http.StatusNotFound, err.Error(), s.Logger)
	}

	if req.Name != "" {
		u.Name = req.Name
	}
	if req.Provider != "" {
		u.Provider = req.Provider
	}
	if req.Scheme != "" {
		u.Scheme = req.Scheme
	}
	if req.Roles != nil {
		u.Roles = req.Roles
	}

	err = s.UsersStore.Update(ctx, u)
	if err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	cu := newUserResponse(u)
	location(w, cu.Links.Self)
	encodeJSON(w, http.StatusOK, cu, s.Logger)
}

// Users retrieves all Chronograf users from store
func (s *Service) Users(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	users, err := s.UsersStore.All(ctx)
	if err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	res := newUsersResponse(users)
	encodeJSON(w, http.StatusOK, res, s.Logger)
}
<commit_msg>Add missing returns in http hanlder methods<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strconv"

	"github.com/bouk/httprouter"
	"github.com/influxdata/chronograf"
)

type userRequest struct {
	ID       uint64            `json:"id,string"`
	Name     string            `json:"name"`
	Provider string            `json:"provider"`
	Scheme   string            `json:"scheme"`
	Roles    []chronograf.Role `json:"roles"`
}

func (r *userRequest) ValidCreate() error {
	if r.Name == "" {
		return fmt.Errorf("Name required on Chronograf User request body")
	}
	if r.Provider == "" {
		return fmt.Errorf("Provider required on Chronograf User request body")
	}
	if r.Scheme == "" {
		return fmt.Errorf("Scheme required on Chronograf User request body")
	}

	// TODO: This Scheme value is hard-coded temporarily since we only currently
	// support OAuth2. This hard-coding should be removed whenever we add
	// support for other authentication schemes.
	r.Scheme = "OAuth2"
	return r.ValidRoles()
}

// TODO: Provide detailed error message
// TODO: Reconsider what fields should actually be updateable once this is more robust
func (r *userRequest) ValidUpdate() error {
	if r.Name == "" && r.Provider == "" && r.Scheme == "" && r.Roles == nil {
		return fmt.Errorf("No fields to update")
	}
	return r.ValidRoles()
}

func (r *userRequest) ValidRoles() error {
	if len(r.Roles) > 0 {
		for _, r := range r.Roles {
			switch r.Name {
			// TODO: add SuperAdmin
			case ViewerRoleName, EditorRoleName, AdminRoleName:
				continue
			default:
				return fmt.Errorf("Unknown role %s. Valid roles are 'viewer', 'editor', 'admin', and 'superadmin'", r.Name)
			}
		}
	}
	return nil
}

type userResponse struct {
	Links    selfLinks         `json:"links"`
	ID       uint64            `json:"id,string"`
	Name     string            `json:"name"`
	Provider string            `json:"provider"`
	Scheme   string            `json:"scheme"`
	Roles    []chronograf.Role `json:"roles"`
}

func newUserResponse(u *chronograf.User) *userResponse {
	// This ensures that any user response with no roles returns an empty array instead of
	// null when marshaled into JSON. That way, JavaScript doesn't need any guard on the
	// key existing and it can simply be iterated over.
	if u.Roles == nil {
		u.Roles = []chronograf.Role{}
	}
	return &userResponse{
		ID:       u.ID,
		Name:     u.Name,
		Provider: u.Provider,
		Scheme:   u.Scheme,
		Roles:    u.Roles,
		Links: selfLinks{
			Self: fmt.Sprintf("/chronograf/v1/users/%d", u.ID),
		},
	}
}

type usersResponse struct {
	Links selfLinks       `json:"links"`
	Users []*userResponse `json:"users"`
}

func newUsersResponse(users []chronograf.User) *usersResponse {
	usersResp := make([]*userResponse, len(users))
	for i, user := range users {
		usersResp[i] = newUserResponse(&user)
	}
	sort.Slice(usersResp, func(i, j int) bool {
		return usersResp[i].ID < usersResp[j].ID
	})
	return &usersResponse{
		Users: usersResp,
		Links: selfLinks{
			Self: "/chronograf/v1/users",
		},
	}
}

// Chronograf User Roles
const (
	ViewerRoleName = "viewer"
	EditorRoleName = "editor"
	AdminRoleName  = "admin"
)

var (
	// ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, and Sources
	ViewerRole = chronograf.Role{
		Name: ViewerRoleName,
	}

	// EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, and Sources
	EditorRole = chronograf.Role{
		Name: EditorRoleName,
	}

	// AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Users
	AdminRole = chronograf.Role{
		Name: AdminRoleName,
	}
)

// UserID retrieves a Chronograf user with ID from store
func (s *Service) UserID(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	idStr := httprouter.GetParamFromContext(ctx, "id")
	id, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
		return
	}
	user, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
	if err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	res := newUserResponse(user)
	encodeJSON(w, http.StatusOK, res, s.Logger)
}

// NewUser adds a new Chronograf user to store
func (s *Service) NewUser(w http.ResponseWriter, r *http.Request) {
	var req userRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		invalidJSON(w, s.Logger)
		return
	}

	if err := req.ValidCreate(); err != nil {
		invalidData(w, err, s.Logger)
		return
	}

	ctx := r.Context()
	user := &chronograf.User{
		Name:     req.Name,
		Provider: req.Provider,
		Scheme:   req.Scheme,
		Roles:    req.Roles,
	}

	res, err := s.UsersStore.Add(ctx, user)
	if err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	cu := newUserResponse(res)
	location(w, cu.Links.Self)
	encodeJSON(w, http.StatusCreated, cu, s.Logger)
}

// RemoveUser deletes a Chronograf user from store
func (s *Service) RemoveUser(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	idStr := httprouter.GetParamFromContext(ctx, "id")
	id, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
		return
	}

	u, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
	if err != nil {
		Error(w, http.StatusNotFound, err.Error(), s.Logger)
		return
	}
	if err := s.UsersStore.Delete(ctx, u); err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// UpdateUser updates a Chronograf user in store
func (s *Service) UpdateUser(w http.ResponseWriter, r *http.Request) {
	var req userRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		invalidJSON(w, s.Logger)
		return
	}

	if err := req.ValidUpdate(); err != nil {
		invalidData(w, err, s.Logger)
		return
	}

	ctx := r.Context()
	idStr := httprouter.GetParamFromContext(ctx, "id")
	id, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
		return
	}

	u, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
	if err != nil {
		Error(w, http.StatusNotFound, err.Error(), s.Logger)
		return
	}

	if req.Name != "" {
		u.Name = req.Name
	}
	if req.Provider != "" {
		u.Provider = req.Provider
	}
	if req.Scheme != "" {
		u.Scheme = req.Scheme
	}
	if req.Roles != nil {
		u.Roles = req.Roles
	}

	err = s.UsersStore.Update(ctx, u)
	if err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	cu := newUserResponse(u)
	location(w, cu.Links.Self)
	encodeJSON(w, http.StatusOK, cu, s.Logger)
}

// Users retrieves all Chronograf users from store
func (s *Service) Users(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	users, err := s.UsersStore.All(ctx)
	if err != nil {
		Error(w, http.StatusBadRequest, err.Error(), s.Logger)
		return
	}

	res := newUsersResponse(users)
	encodeJSON(w, http.StatusOK, res, s.Logger)
}
<|endoftext|>