<commit_before>/*
Package decimals is a small library of functions for rounding and
formatting base ten numbers. These are functions that are either
missing from the standard libraries or are more convenient for
presenting numbers in a human-readable format.
*/
package decimals

import (
	"strconv"
	"math"
)

// RoundInt rounds a base ten int64 to the given precision. Precision is a
// negative number that represents the nearest power of ten to which the
// integer should be rounded. It is expressed as a negative number to be
// consistent with the decimal precision arguments used in rounding floats.
// If the rounded number falls outside the minimum and maximum for int64
// the minimum or maximum will be returned instead.
func RoundInt(x int64, precision int) int64 {

	var (
		xstr string = strconv.FormatInt(x, 10)
		xslice = []byte(xstr)
		zeroFrom int = -1
		roundFrom int
	)

	// Map for converting decimal bytes to int64
	decimalInts := map[byte]int64{
		'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
		'5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
	}

	// Array for converting decimal ints to bytes
	decimalBytes := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',}

	// If precision is not negative return x
	if precision > -1 {

		return x
	}

	// If x is negative remove the sign
	if x < 0 {

		xslice = xslice[1:]
	}

	// Set the index of the digit to round from
	roundFrom = len(xslice) + precision

	// If rounding to more than one order of magnitude larger than x return 0
	if roundFrom < 0 {

		return 0
	}

	// If rounding to one order of magnitude larger than x round from first digit
	if roundFrom == 0 {

		firstDigit := decimalInts[xslice[0]]

		if firstDigit < 5 {

			return 0

		} else {

			xslice = append([]byte{'1'}, xslice...)
			zeroFrom = 1
		}

	// Otherwise round through the slice from right to left
	} else {

		// Start rounding from the round digit
		roundDigit := decimalInts[xslice[roundFrom]]

		// If less than five round from there
		if roundDigit < 5 {

			zeroFrom = roundFrom

		// Otherwise keep moving left to find the rounding point
		} else {

			for i := roundFrom; i > 0; i-- {

				j := i - 1
				nextDigit := decimalInts[xslice[j]]

				if nextDigit < 9 {

					xslice[j] = decimalBytes[nextDigit + 1]
					zeroFrom = i
					break
				}
			}

			// If not found add a leading one and round from there
			if zeroFrom == -1 {

				xslice = append([]byte{'1'}, xslice...)
				zeroFrom = 1
			}
		}
	}

	// Zero all digits after the rounding point
	for i := zeroFrom; i < len(xslice); i++ {

		xslice[i] = '0'
	}

	// If x is negative add the sign back
	if x < 0 {

		xslice = append([]byte("-"), xslice...)
	}

	// Convert the slice back to an int64
	rstr := string(xslice)
	r, _ := strconv.ParseInt(rstr, 10, 64)

	return r
}

// RoundFloat rounds a base ten float64 to the given decimal precision.
// Precision may be positive, representing the number of decimal places,
// or negative, representing the nearest power of ten to which the float
// should be rounded.
func RoundFloat(x float64, precision int) float64 {

	// Handle negative precision with integer rounding
	if precision < 0 {

		i, _ := math.Modf(x)
		return float64(RoundInt(int64(i), precision))
	}

	// Handle positive precision with strconv.FormatFloat()
	rstr := strconv.FormatFloat(x, 'f', precision, 64)
	r, _ := strconv.ParseFloat(rstr, 64)

	return r
}

// FormatThousands converts an int64 into a string formatted using a comma
// separator for thousands.
func FormatThousands(x int64) string {

	var (
		xstr string
		xslice []byte
		fslice []byte
		lenx int
		lenf int
		commas int
	)

	// Get the number as a byte slice
	xstr = strconv.FormatInt(x, 10)
	xslice = []byte(xstr)
	lenx = len(xslice)

	// Determine the number of commas depending on the sign of x
	if x < 0 {

		commas = (lenx -2) / 3
		lenf = lenx + commas

	} else {

		commas = (lenx -1) / 3
		lenf = lenx + commas

	}

	// Create an empty byte slice for the formatted number
	fslice = make([]byte, lenf)

	// Copy the digits from right to left, adding commas
	i := lenx - 1
	j := lenf - 1

	// Copy the digits in batches of three
	for k := 0; k < commas; k++ {

		for l := 0; l < 3; l++ {

			fslice[j] = xslice[i]
			i--
			j--
		}

		// Add the comma
		fslice[j] = []byte(",")[0]
		j--
	}

	// Copy the remaining digits
	for ; i >= 0; i, j = i - 1, j - 1 {

		fslice[j] = xslice[i]
	}

	return string(fslice)
}

// FormatInt converts an int64 to a formatted string. The int is rounded
// to the given precision and formatted using a comma separator for thousands.
func FormatInt(x int64, precision int) string {

	return FormatThousands(RoundInt(x, precision))
}

// FormatFloat converts a float64 to a formatted string. The float is rounded
// to the given precision and formatted using a comma separator for thousands.
func FormatFloat(x float64, precision int) string {

	// Round the float and get the decimal and fractional parts
	r := RoundFloat(x, precision)
	i, f := math.Modf(r)
	is := FormatThousands(int64(i))

	// If precision is less than one return the formatted integer part
	if precision <= 0 {

		return is
	}

	// Otherwise convert the fractional part to a string
	fs := strconv.FormatFloat(f, 'f', precision, 64)

	// And get the digits after the decimal point
	if x < 0 {

		fs = fs[3:]

	} else {

		fs = fs[2:]
	}

	// Concatenate the decimal and fractional parts and return
	return is + "." + fs
}
<commit_msg>2.0.0<commit_after>/*
Package decimals is a small library of functions for rounding and
formatting base ten numbers. These are functions that are either
missing from the standard libraries or are more convenient for
presenting numbers in a human-readable format.
*/
package decimals

import (
	"strconv"
	"math"
)

// RoundInt rounds a base ten int64 to the given precision. Precision is a
// negative number that represents the nearest power of ten to which the
// integer should be rounded. It is expressed as a negative number to be
// consistent with the decimal precision arguments used in rounding floats.
// If the rounded number falls outside the minimum and maximum for int64
// the minimum or maximum will be returned instead.
func RoundInt(x int64, precision int) int64 {

	var (
		xstr string = strconv.FormatInt(x, 10)
		xslice []byte = []byte(xstr)
		zeroFrom int = -1
		roundFrom int
	)

	// Map for converting decimal bytes to int64
	decimalInts := map[byte]int64{
		'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
		'5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
	}

	// Array for converting decimal ints to bytes
	decimalBytes := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',}

	// If precision is not negative return x
	if precision > -1 {

		return x
	}

	// If x is negative remove the sign
	if x < 0 {

		xslice = xslice[1:]
	}

	// Set the index of the digit to round from
	roundFrom = len(xslice) + precision

	// If rounding to more than one order of magnitude larger than x return 0
	if roundFrom < 0 {

		return 0
	}

	// If rounding to one order of magnitude larger than x round from first digit
	if roundFrom == 0 {

		firstDigit := decimalInts[xslice[0]]

		if firstDigit < 5 {

			return 0

		} else {

			xslice = append([]byte{'1'}, xslice...)
			zeroFrom = 1
		}

	// Otherwise round through the slice from right to left
	} else {

		// Start rounding from the round digit
		roundDigit := decimalInts[xslice[roundFrom]]

		// If less than five round from there
		if roundDigit < 5 {

			zeroFrom = roundFrom

		// Otherwise keep moving left to find the rounding point
		} else {

			for i := roundFrom; i > 0; i-- {

				j := i - 1
				nextDigit := decimalInts[xslice[j]]

				if nextDigit < 9 {

					xslice[j] = decimalBytes[nextDigit + 1]
					zeroFrom = i
					break
				}
			}

			// If not found add a leading one and round from there
			if zeroFrom == -1 {

				xslice = append([]byte{'1'}, xslice...)
				zeroFrom = 1
			}
		}
	}

	// Zero all digits after the rounding point
	for i := zeroFrom; i < len(xslice); i++ {

		xslice[i] = '0'
	}

	// If x is negative add the sign back
	if x < 0 {

		xslice = append([]byte("-"), xslice...)
	}

	// Convert the slice back to an int64
	rstr := string(xslice)
	r, _ := strconv.ParseInt(rstr, 10, 64)

	return r
}

// RoundFloat rounds a base ten float64 to the given decimal precision.
// Precision may be positive, representing the number of decimal places,
// or negative, representing the nearest power of ten to which the float
// should be rounded.
func RoundFloat(x float64, precision int) float64 {

	// Handle negative precision with integer rounding
	if precision < 0 {

		i, _ := math.Modf(x)
		return float64(RoundInt(int64(i), precision))
	}

	// Handle positive precision with strconv.FormatFloat()
	rstr := strconv.FormatFloat(x, 'f', precision, 64)
	r, _ := strconv.ParseFloat(rstr, 64)

	return r
}

// FormatThousands converts an int64 into a string formatted using a comma
// separator for thousands.
func FormatThousands(x int64) string {

	var (
		xstr string
		xslice []byte
		fslice []byte
		lenx int
		lenf int
		commas int
	)

	// Get the number as a byte slice
	xstr = strconv.FormatInt(x, 10)
	xslice = []byte(xstr)
	lenx = len(xslice)

	// Determine the number of commas depending on the sign of x
	if x < 0 {

		commas = (lenx -2) / 3
		lenf = lenx + commas

	} else {

		commas = (lenx -1) / 3
		lenf = lenx + commas

	}

	// Create an empty byte slice for the formatted number
	fslice = make([]byte, lenf)

	// Copy the digits from right to left, adding commas
	i := lenx - 1
	j := lenf - 1

	// Copy the digits in batches of three
	for k := 0; k < commas; k++ {

		for l := 0; l < 3; l++ {

			fslice[j] = xslice[i]
			i--
			j--
		}

		// Add the comma
		fslice[j] = []byte(",")[0]
		j--
	}

	// Copy the remaining digits
	for ; i >= 0; i, j = i - 1, j - 1 {

		fslice[j] = xslice[i]
	}

	return string(fslice)
}

// FormatInt converts an int64 to a formatted string. The int is rounded
// to the given precision and formatted using a comma separator for thousands.
func FormatInt(x int64, precision int) string {

	return FormatThousands(RoundInt(x, precision))
}

// FormatFloat converts a float64 to a formatted string. The float is rounded
// to the given precision and formatted using a comma separator for thousands.
func FormatFloat(x float64, precision int) string {

	// Round the float and get the decimal and fractional parts
	r := RoundFloat(x, precision)
	i, f := math.Modf(r)
	is := FormatThousands(int64(i))

	// If precision is less than one return the formatted integer part
	if precision <= 0 {

		return is
	}

	// Otherwise convert the fractional part to a string
	fs := strconv.FormatFloat(f, 'f', precision, 64)

	// And get the digits after the decimal point
	if x < 0 {

		fs = fs[3:]

	} else {

		fs = fs[2:]
	}

	// Concatenate the decimal and fractional parts and return
	return is + "." + fs
}
<|endoftext|>


<commit_before><commit_msg>clean up integration_test<commit_after><|endoftext|>


<commit_before>package deque

// Deque deque
type Deque interface {
	// Len returns the number of elements of Deque.
	// The complexity is O(1).
	Len() int
	// Clear clears Deque
	Clear()
	// ForEach call fn on all elements which after the offset, stopped if false returned
	ForEach(offset int, fn func(interface{}) bool)
	// Front returns the first element of Deque, false if the list is empty.
	Front() (*Element, bool)
	// Back returns the last element of Deque, false if the list is empty.
	Back() (*Element, bool)
	// PushFront inserts a new element e with value v at the front of deque.
	PushFront(v interface{}) *Element
	// PushBack inserts a new element e with value v at the back of deque.
	PushBack(v interface{}) *Element
	// InsertBefore inserts a new element e with value v immediately before mark and returns e.
	// If mark is not an element of l, the list is not modified.
	// The mark must not be nil.
	InsertBefore(v interface{}, mark *Element) *Element
	// InsertAfter inserts a new element e with value v immediately after mark and returns e.
	// If mark is not an element of l, the list is not modified.
	// The mark must not be nil.
	InsertAfter(v interface{}, mark *Element) *Element
	// MoveToFront moves element e to the front of list l.
	// If e is not an element of l, the list is not modified.
	// The element must not be nil.
	MoveToFront(e *Element)
	// MoveToBack moves element e to the back of list l.
	// If e is not an element of l, the list is not modified.
	// The element must not be nil.
	MoveToBack(e *Element)
	// MoveBefore moves element e to its new position before mark.
	// If e or mark is not an element of l, or e == mark, the list is not modified.
	// The element and mark must not be nil.
	MoveBefore(e, mark *Element)
	// MoveAfter moves element e to its new position after mark.
	// If e or mark is not an element of l, or e == mark, the list is not modified.
	// The element and mark must not be nil.
	MoveAfter(e, mark *Element)
	// Remove removes e from l if e is an element of list l.
	// It returns the element value e.Value.
	// The element must not be nil.
	Remove(e *Element) interface{}
	// Truncate trancate deque, keeping the first size elements
	Truncate(keeping int)
	// Drain removes the specified range in the deque, returns drained
	Drain(from, to int) Deque
}

// New returns an initialized Deque.
func New() Deque {
	q := new(defaultDeque)
	q.Clear()
	return q
}

// Element is an Element of a linked Deque.
type Element struct {
	// Next and previous pointers in the doubly-linked Deque of elements.
	// To simplify the implementation, internally a Deque l is implemented
	// as a ring, such that &l.root is both the next element of the last
	// Deque element (l.Back()) and the previous element of the first Deque
	// element (l.Front()).
	next, prev *Element

	// The list to which this element belongs.
	list *defaultDeque

	// The value stored with this element.
	Value interface{}
}

// Next returns the next Deque element or nil.
func (e *Element) Next() *Element {
	if p := e.next; e.list != nil && p != &e.list.root {
		return p
	}
	return nil
}

// Prev returns the previous Deque element or nil.
func (e *Element) Prev() *Element {
	if p := e.prev; e.list != nil && p != &e.list.root {
		return p
	}
	return nil
}

type defaultDeque struct {
	root Element // sentinel list element, only &root, root.prev, and root.next are used
	len  int     // current list length excluding (this) sentinel element
}

func (q *defaultDeque) Clear() {
	q.root.next = &q.root
	q.root.prev = &q.root
	q.len = 0
}

func (q *defaultDeque) Truncate(keeping int) {
	if keeping >= q.len {
		return
	}

	q.doRangeRemove(keeping, q.len, false)
}

func (q *defaultDeque) Len() int { return q.len }

func (q *defaultDeque) ForEach(offset int, fn func(interface{}) bool) {
	if q.len == 0 {
		return
	}

	skipped := 0
	v, _ := q.Front()
	for e := v; e != nil; e = e.Next() {
		if skipped < offset {
			skipped++
			continue
		}

		if !fn(e.Value) {
			return
		}
	}
}

func (q *defaultDeque) Front() (*Element, bool) {
	if q.len == 0 {
		return nil, false
	}
	return q.root.next, true
}

func (q *defaultDeque) Back() (*Element, bool) {
	if q.len == 0 {
		return nil, false
	}
	return q.root.prev, true
}

func (q *defaultDeque) PushFront(v interface{}) *Element {
	q.lazyInit()
	return q.insertValue(v, &q.root)
}

func (q *defaultDeque) PushBack(v interface{}) *Element {
	q.lazyInit()
	return q.insertValue(v, q.root.prev)
}

func (q *defaultDeque) Drain(from, to int) Deque {
	return q.doRangeRemove(from, to, true)
}

// lazyInit lazily initializes a zero List value.
func (q *defaultDeque) lazyInit() {
	if q.root.next == nil {
		q.Clear()
	}
}

// insert inserts e after at, increments l.len, and returns e.
func (q *defaultDeque) insert(e, at *Element) *Element {
	e.prev = at
	e.next = at.next
	e.prev.next = e
	e.next.prev = e
	e.list = q
	q.len++
	return e
}

// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (q *defaultDeque) insertValue(v interface{}, at *Element) *Element {
	return q.insert(&Element{Value: v}, at)
}

// remove removes e from its list, decrements l.len, and returns e.
func (q *defaultDeque) remove(e *Element) *Element {
	e.prev.next = e.next
	e.next.prev = e.prev
	e.next = nil // avoid memory leaks
	e.prev = nil // avoid memory leaks
	e.list = nil
	q.len--
	return e
}

// move moves e to next to at and returns e.
func (q *defaultDeque) move(e, at *Element) *Element {
	if e == at {
		return e
	}
	e.prev.next = e.next
	e.next.prev = e.prev

	e.prev = at
	e.next = at.next
	e.prev.next = e
	e.next.prev = e

	return e
}

func (q *defaultDeque) Remove(e *Element) interface{} {
	if e.list == q {
		// if e.list == l, l must have been initialized when e was inserted
		// in l or l == nil (e is a zero Element) and l.remove will crash
		q.remove(e)
	}
	return e.Value
}

func (q *defaultDeque) InsertBefore(v interface{}, mark *Element) *Element {
	if mark.list != q {
		return nil
	}
	// see comment in List.Remove about initialization of l
	return q.insertValue(v, mark.prev)
}

func (q *defaultDeque) InsertAfter(v interface{}, mark *Element) *Element {
	if mark.list != q {
		return nil
	}
	// see comment in List.Remove about initialization of l
	return q.insertValue(v, mark)
}

func (q *defaultDeque) MoveToFront(e *Element) {
	if e.list != q || q.root.next == e {
		return
	}
	// see comment in List.Remove about initialization of l
	q.move(e, &q.root)
}

func (q *defaultDeque) MoveToBack(e *Element) {
	if e.list != q || q.root.prev == e {
		return
	}
	// see comment in List.Remove about initialization of l
	q.move(e, q.root.prev)
}

func (q *defaultDeque) MoveBefore(e, mark *Element) {
	if e.list != q || e == mark || mark.list != q {
		return
	}
	q.move(e, mark.prev)
}

func (q *defaultDeque) MoveAfter(e, mark *Element) {
	if e.list != q || e == mark || mark.list != q {
		return
	}
	q.move(e, mark)
}

func (q *defaultDeque) doRangeRemove(from, to int, withRemoved bool) Deque {
	if from >= to {
		return nil
	}

	q.lazyInit()
	if q.len == 0 {
		return nil
	}

	if to > q.len {
		to = q.len
	}

	i := 0
	var left *Element
	var drainedRight *Element
	right := &q.root
	for e := q.root.next; e != &q.root && e.list != nil; e = e.next {
		if i >= from && i < to {
			if left == nil {
				left = e
			}
			drainedRight = e
		} else if i >= to {
			right = e
			break
		}

		i++
	}

	q.len -= i - from
	left.prev.next = right
	right.prev = left.prev
	if right == &q.root {
		q.root.prev = left.prev
	}

	if !withRemoved {
		return nil
	}

	drained := new(defaultDeque)
	drained.Clear()
	left.prev = &drained.root
	drained.root.next = left
	drained.root.prev = drainedRight
	drainedRight.next = &drained.root
	drained.len = i - from
	for e := left; e != &q.root && e.list != nil; e = e.next {
		e.list = drained
		if e == drainedRight {
			break
		}
	}
	return drained
}
<commit_msg>Add must method for deque<commit_after>package deque

// Deque deque
type Deque interface {
	// Len returns the number of elements of Deque.
	// The complexity is O(1).
	Len() int
	// Clear clears Deque
	Clear()
	// ForEach call fn on all elements which after the offset, stopped if false returned
	ForEach(offset int, fn func(interface{}) bool)
	// Front returns the first element of Deque, false if the list is empty.
	Front() (*Element, bool)
	// Front returns the first element of Deque, panic if the list is empty.
	MustFront() *Element
	// Back returns the last element of Deque, false if the list is empty.
	Back() (*Element, bool)
	// MustBack returns the last element of Deque, panic if the list is empty.
	MustBack() *Element
	// PushFront inserts a new element e with value v at the front of deque.
	PushFront(v interface{}) *Element
	// PushBack inserts a new element e with value v at the back of deque.
	PushBack(v interface{}) *Element
	// InsertBefore inserts a new element e with value v immediately before mark and returns e.
	// If mark is not an element of l, the list is not modified.
	// The mark must not be nil.
	InsertBefore(v interface{}, mark *Element) *Element
	// InsertAfter inserts a new element e with value v immediately after mark and returns e.
	// If mark is not an element of l, the list is not modified.
	// The mark must not be nil.
	InsertAfter(v interface{}, mark *Element) *Element
	// MoveToFront moves element e to the front of list l.
	// If e is not an element of l, the list is not modified.
	// The element must not be nil.
	MoveToFront(e *Element)
	// MoveToBack moves element e to the back of list l.
	// If e is not an element of l, the list is not modified.
	// The element must not be nil.
	MoveToBack(e *Element)
	// MoveBefore moves element e to its new position before mark.
	// If e or mark is not an element of l, or e == mark, the list is not modified.
	// The element and mark must not be nil.
	MoveBefore(e, mark *Element)
	// MoveAfter moves element e to its new position after mark.
	// If e or mark is not an element of l, or e == mark, the list is not modified.
	// The element and mark must not be nil.
	MoveAfter(e, mark *Element)
	// Remove removes e from l if e is an element of list l.
	// It returns the element value e.Value.
	// The element must not be nil.
	Remove(e *Element) interface{}
	// Truncate trancate deque, keeping the first size elements
	Truncate(keeping int)
	// Drain removes the specified range in the deque, returns drained
	Drain(from, to int) Deque
}

// New returns an initialized Deque.
func New() Deque {
	q := new(defaultDeque)
	q.Clear()
	return q
}

// Element is an Element of a linked Deque.
type Element struct {
	// Next and previous pointers in the doubly-linked Deque of elements.
	// To simplify the implementation, internally a Deque l is implemented
	// as a ring, such that &l.root is both the next element of the last
	// Deque element (l.Back()) and the previous element of the first Deque
	// element (l.Front()).
	next, prev *Element

	// The list to which this element belongs.
	list *defaultDeque

	// The value stored with this element.
	Value interface{}
}

// Next returns the next Deque element or nil.
func (e *Element) Next() *Element {
	if p := e.next; e.list != nil && p != &e.list.root {
		return p
	}
	return nil
}

// Prev returns the previous Deque element or nil.
func (e *Element) Prev() *Element {
	if p := e.prev; e.list != nil && p != &e.list.root {
		return p
	}
	return nil
}

type defaultDeque struct {
	root Element // sentinel list element, only &root, root.prev, and root.next are used
	len  int     // current list length excluding (this) sentinel element
}

func (q *defaultDeque) Clear() {
	q.root.next = &q.root
	q.root.prev = &q.root
	q.len = 0
}

func (q *defaultDeque) Truncate(keeping int) {
	if keeping >= q.len {
		return
	}

	q.doRangeRemove(keeping, q.len, false)
}

func (q *defaultDeque) Len() int { return q.len }

func (q *defaultDeque) ForEach(offset int, fn func(interface{}) bool) {
	if q.len == 0 {
		return
	}

	skipped := 0
	v, _ := q.Front()
	for e := v; e != nil; e = e.Next() {
		if skipped < offset {
			skipped++
			continue
		}

		if !fn(e.Value) {
			return
		}
	}
}

func (q *defaultDeque) Front() (*Element, bool) {
	if q.len == 0 {
		return nil, false
	}
	return q.root.next, true
}

func (q *defaultDeque) Back() (*Element, bool) {
	if q.len == 0 {
		return nil, false
	}
	return q.root.prev, true
}

func (q *defaultDeque) PushFront(v interface{}) *Element {
	q.lazyInit()
	return q.insertValue(v, &q.root)
}

func (q *defaultDeque) PushBack(v interface{}) *Element {
	q.lazyInit()
	return q.insertValue(v, q.root.prev)
}

func (q *defaultDeque) Drain(from, to int) Deque {
	return q.doRangeRemove(from, to, true)
}

// lazyInit lazily initializes a zero List value.
func (q *defaultDeque) lazyInit() {
	if q.root.next == nil {
		q.Clear()
	}
}

// insert inserts e after at, increments l.len, and returns e.
func (q *defaultDeque) insert(e, at *Element) *Element {
	e.prev = at
	e.next = at.next
	e.prev.next = e
	e.next.prev = e
	e.list = q
	q.len++
	return e
}

// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (q *defaultDeque) insertValue(v interface{}, at *Element) *Element {
	return q.insert(&Element{Value: v}, at)
}

// remove removes e from its list, decrements l.len, and returns e.
func (q *defaultDeque) remove(e *Element) *Element {
	e.prev.next = e.next
	e.next.prev = e.prev
	e.next = nil // avoid memory leaks
	e.prev = nil // avoid memory leaks
	e.list = nil
	q.len--
	return e
}

// move moves e to next to at and returns e.
func (q *defaultDeque) move(e, at *Element) *Element {
	if e == at {
		return e
	}
	e.prev.next = e.next
	e.next.prev = e.prev

	e.prev = at
	e.next = at.next
	e.prev.next = e
	e.next.prev = e

	return e
}

func (q *defaultDeque) Remove(e *Element) interface{} {
	if e.list == q {
		// if e.list == l, l must have been initialized when e was inserted
		// in l or l == nil (e is a zero Element) and l.remove will crash
		q.remove(e)
	}
	return e.Value
}

func (q *defaultDeque) InsertBefore(v interface{}, mark *Element) *Element {
	if mark.list != q {
		return nil
	}
	// see comment in List.Remove about initialization of l
	return q.insertValue(v, mark.prev)
}

func (q *defaultDeque) InsertAfter(v interface{}, mark *Element) *Element {
	if mark.list != q {
		return nil
	}
	// see comment in List.Remove about initialization of l
	return q.insertValue(v, mark)
}

func (q *defaultDeque) MoveToFront(e *Element) {
	if e.list != q || q.root.next == e {
		return
	}
	// see comment in List.Remove about initialization of l
	q.move(e, &q.root)
}

func (q *defaultDeque) MoveToBack(e *Element) {
	if e.list != q || q.root.prev == e {
		return
	}
	// see comment in List.Remove about initialization of l
	q.move(e, q.root.prev)
}

func (q *defaultDeque) MoveBefore(e, mark *Element) {
	if e.list != q || e == mark || mark.list != q {
		return
	}
	q.move(e, mark.prev)
}

func (q *defaultDeque) MoveAfter(e, mark *Element) {
	if e.list != q || e == mark || mark.list != q {
		return
	}
	q.move(e, mark)
}

func (q *defaultDeque) doRangeRemove(from, to int, withRemoved bool) Deque {
	if from >= to {
		return nil
	}

	q.lazyInit()
	if q.len == 0 {
		return nil
	}

	if to > q.len {
		to = q.len
	}

	i := 0
	var left *Element
	var drainedRight *Element
	right := &q.root
	for e := q.root.next; e != &q.root && e.list != nil; e = e.next {
		if i >= from && i < to {
			if left == nil {
				left = e
			}
			drainedRight = e
		} else if i >= to {
			right = e
			break
		}

		i++
	}

	q.len -= i - from
	left.prev.next = right
	right.prev = left.prev
	if right == &q.root {
		q.root.prev = left.prev
	}

	if !withRemoved {
		return nil
	}

	drained := new(defaultDeque)
	drained.Clear()
	left.prev = &drained.root
	drained.root.next = left
	drained.root.prev = drainedRight
	drainedRight.next = &drained.root
	drained.len = i - from
	for e := left; e != &q.root && e.list != nil; e = e.next {
		e.list = drained
		if e == drainedRight {
			break
		}
	}
	return drained
}
<|endoftext|>


<commit_before>package common

import (
  "github.com/urfave/cli"
  "os"
  "os/exec"
  "strings"
  "errors"
  "fmt"
)

func cliRun(c *cli.Context) error {
  if c.IsSet("version") {
    fmt.Println(c.App.Version)
    return nil
  }

  if c.IsSet("help") {
    cli.ShowAppHelp(c)
    return nil
  }

  setRemLevel(c.Int("verbosity"))

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Build the ssh command

  sshParts := make([]string, 1, 30)
  sshParts[0] = "ssh"

  args := c.Args()
  at := ""
  for i, arg := range args {
    if strings.Contains(arg, "@") {
      at = arg
      // Cut it out
      args = append(args[:i], args[i + 1:]...)
      break
    }
  }

  if !(at != "" || c.IsSet("host") || c.IsSet("instance") || c.IsSet("group") || c.IsSet("tag")) {
    return errors.New("To whom do I connect? Specify one of: user@host, --host/-h, --instance/-m, --group/-g, --tag/-t")
  }

  dbgf("Resolving ssh_config. Some inferences are sensitive to ssh_config.")
  sshConfigFile := c.String("config-file")
  parsedSshConfig, err := resolveSshConfig(sshConfigFile)
  if err != nil {
    return err
  }

  inff("AWS_REGION=%s", *ec2Svc.Config.Region)

  dbgf("Resolving hosts first. We may need EC2 info to infer other ssh params.")
  host, ec2, err := resolveHost(at, c.String("host"), c.String("instance"), c.String("group"), c.String("tag"), c.Bool("private"))
  if err != nil {
    return err
  }

  dbgf("Resolving user.")
  user, err := resolveUser(at, c.String("user"), host, ec2, parsedSshConfig)
  if err != nil {
    return err
  }

  dbgf("Resolving identity.")
  ident, err := resolveIdent(c.String("identity"), c.Bool("kms"), ec2, parsedSshConfig)
  if err != nil {
    return err
  }

  dbgf("Assembling ssh args.")
  if sshConfigFile != "" {
    sshParts = append(sshParts, "-F", sshConfigFile)
  }

  if ident != "" {
    sshParts = append(sshParts, "-i", ident)
  }

  if user != "" {
    sshParts = append(sshParts, user + "@" + host)
  } else if host != "" {
    sshParts = append(sshParts, host)
  }

  inff("SSH command: %s", sshParts)
  if c.Bool("Agent") {
    inff("  with ssh-agent")
  }
  inff("Remote command: %s", args)

  sshCmd := strings.Join(sshParts, " ") + " " + strings.Join(args, " ")

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Build the script

  scriptLines := make([]string, 1, 10)

  if c.Bool("Agent") {
    scriptLines = append(scriptLines, "eval $(ssh-agent) >> /dev/null", "ssh-add ~/.ssh/{id_rsa,*.pem} 2> /dev/null")
  }

  scriptLines = append(scriptLines, sshCmd)

  if c.Bool("Agent") {
    scriptLines = append(scriptLines, "kill $SSH_AGENT_PID")
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Execution

  cmd := exec.Command("bash", "-c", strings.Join(scriptLines, "\n"))
  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr
  cmd.Stdin = os.Stdin
  return cmd.Run()
  return err
}

func Run() {
  err := InitSshConfig()
  if err != nil {
    erro(err)
    return
  }

  app := cli.NewApp()
  app.Name = "ash"
  app.Version = "0.1.0"
  app.Usage = "AWS EC2 ssh tool"
  app.HideHelp = true  // Help conflicts with that of host flag. Disable it.
  app.HideVersion = true // -v conflicts with verbosity flag. Disabled it.
  app.Flags = []cli.Flag{
    cli.StringFlag{
      Name: "host, h",
      EnvVar: "ASH_HOST",
      Value: "",
      Usage: "ssh by hostname",
    },
    cli.StringFlag{
      Name: "instance, machine, m",
      EnvVar: "ASH_MACHINE",
      Value: "",
      Usage: "ssh by EC2 instance id",
    },
    cli.StringFlag{
      Name: "group, g",
      EnvVar: "ASH_GROUP",
      Value: "",
      Usage: "ssh by auto-scaling group name",
    },
    cli.StringFlag{
      Name: "tag, t",
      EnvVar: "ASH_TAG",
      Value: "",
      Usage: "ssh by EC2 tag",
    },
    cli.BoolFlag{
      Name: "private, p",
      EnvVar: "ASH_PRIVATE_IP",
      Usage: "When resolving host, prefer AWS private IPs. Useful when working from networks peered with AWS VPCs " +
          "where public addresses may be bound but unreachable over the public internet.",
    },
    cli.StringFlag{
      Name: "user, u",
      EnvVar: "ASH_USER",
      Value: "",
      Usage: "ssh as this username",
    },
    cli.StringFlag{
      Name: "identity, i",
      EnvVar: "ASH_IDENTITY",
      Value: "",
      Usage: "ssh identified by this private key file",
    },
    cli.BoolFlag{
      Name: "kms, k",
      EnvVar: "ASH_IDENTITY",
      Usage: "NOT IMPLEMENTED ssh identified by a private key from KMS",
    },
    cli.BoolFlag{
      Name: "Agent, A",
      EnvVar: "ASH_AGENT",
      Usage: "ssh identified by any private key in ~/.ssh/{id_rsa,*.pem} via ssh-agent",
    },
    cli.StringFlag{
      Name: "config-file, F",
      EnvVar: "ASH_CONFIG_FILE",
      Value: "",
      Usage: "ssh -F option: use a config file other than the default (usually ~/.ssh/{ssh_,}config",
    },
    cli.IntFlag{
      Name: "verbosity, v",
      EnvVar: "ASH_VERBOSITY",
      Value: 2,
      Usage: "ash verbosity: 0 - TRACE, 1 - DEBUG, 2 - INFO (default level), 3 - WARN, 4 - ERROR)",
    },
    // Re-instate long version flag.
    cli.BoolFlag{
      Name: "version",
      Usage: "print the version",
    },
    // Re-instate long help flag.
    cli.BoolFlag{
      Name: "help",
      Usage: "show help",
    },
  }
  app.Action = cliRun

  err = app.Run(os.Args)
  if err != nil {
    erro(err)
    return
  }
}
<commit_msg>shorten help string<commit_after>package common

import (
  "github.com/urfave/cli"
  "os"
  "os/exec"
  "strings"
  "errors"
  "fmt"
)

func cliRun(c *cli.Context) error {
  if c.IsSet("version") {
    fmt.Println(c.App.Version)
    return nil
  }

  if c.IsSet("help") {
    cli.ShowAppHelp(c)
    return nil
  }

  setRemLevel(c.Int("verbosity"))

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Build the ssh command

  sshParts := make([]string, 1, 30)
  sshParts[0] = "ssh"

  args := c.Args()
  at := ""
  for i, arg := range args {
    if strings.Contains(arg, "@") {
      at = arg
      // Cut it out
      args = append(args[:i], args[i + 1:]...)
      break
    }
  }

  if !(at != "" || c.IsSet("host") || c.IsSet("instance") || c.IsSet("group") || c.IsSet("tag")) {
    return errors.New("To whom do I connect? Specify one of: user@host, --host/-h, --instance/-m, --group/-g, --tag/-t")
  }

  dbgf("Resolving ssh_config. Some inferences are sensitive to ssh_config.")
  sshConfigFile := c.String("config-file")
  parsedSshConfig, err := resolveSshConfig(sshConfigFile)
  if err != nil {
    return err
  }

  inff("AWS_REGION=%s", *ec2Svc.Config.Region)

  dbgf("Resolving hosts first. We may need EC2 info to infer other ssh params.")
  host, ec2, err := resolveHost(at, c.String("host"), c.String("instance"), c.String("group"), c.String("tag"), c.Bool("private"))
  if err != nil {
    return err
  }

  dbgf("Resolving user.")
  user, err := resolveUser(at, c.String("user"), host, ec2, parsedSshConfig)
  if err != nil {
    return err
  }

  dbgf("Resolving identity.")
  ident, err := resolveIdent(c.String("identity"), c.Bool("kms"), ec2, parsedSshConfig)
  if err != nil {
    return err
  }

  dbgf("Assembling ssh args.")
  if sshConfigFile != "" {
    sshParts = append(sshParts, "-F", sshConfigFile)
  }

  if ident != "" {
    sshParts = append(sshParts, "-i", ident)
  }

  if user != "" {
    sshParts = append(sshParts, user + "@" + host)
  } else if host != "" {
    sshParts = append(sshParts, host)
  }

  inff("SSH command: %s", sshParts)
  if c.Bool("Agent") {
    inff("  with ssh-agent")
  }
  inff("Remote command: %s", args)

  sshCmd := strings.Join(sshParts, " ") + " " + strings.Join(args, " ")

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Build the script

  scriptLines := make([]string, 1, 10)

  if c.Bool("Agent") {
    scriptLines = append(scriptLines, "eval $(ssh-agent) >> /dev/null", "ssh-add ~/.ssh/{id_rsa,*.pem} 2> /dev/null")
  }

  scriptLines = append(scriptLines, sshCmd)

  if c.Bool("Agent") {
    scriptLines = append(scriptLines, "kill $SSH_AGENT_PID")
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Execution

  cmd := exec.Command("bash", "-c", strings.Join(scriptLines, "\n"))
  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr
  cmd.Stdin = os.Stdin
  return cmd.Run()
  return err
}

func Run() {
  err := InitSshConfig()
  if err != nil {
    erro(err)
    return
  }

  app := cli.NewApp()
  app.Name = "ash"
  app.Version = "0.1.0"
  app.Usage = "AWS EC2 ssh tool"
  app.HideHelp = true  // Help conflicts with that of host flag. Disable it.
  app.HideVersion = true // -v conflicts with verbosity flag. Disabled it.
  app.Flags = []cli.Flag{
    cli.StringFlag{
      Name: "host, h",
      EnvVar: "ASH_HOST",
      Value: "",
      Usage: "ssh by hostname",
    },
    cli.StringFlag{
      Name: "instance, machine, m",
      EnvVar: "ASH_MACHINE",
      Value: "",
      Usage: "ssh by EC2 instance id",
    },
    cli.StringFlag{
      Name: "group, g",
      EnvVar: "ASH_GROUP",
      Value: "",
      Usage: "ssh by auto-scaling group name",
    },
    cli.StringFlag{
      Name: "tag, t",
      EnvVar: "ASH_TAG",
      Value: "",
      Usage: "ssh by EC2 tag",
    },
    cli.BoolFlag{
      Name: "private, p",
      EnvVar: "ASH_PRIVATE_IP",
      Usage: "When resolving host, use AWS private DNS name.",
    },
    cli.StringFlag{
      Name: "user, u",
      EnvVar: "ASH_USER",
      Value: "",
      Usage: "ssh as this username",
    },
    cli.StringFlag{
      Name: "identity, i",
      EnvVar: "ASH_IDENTITY",
      Value: "",
      Usage: "ssh identified by this private key file",
    },
    cli.BoolFlag{
      Name: "kms, k",
      EnvVar: "ASH_IDENTITY",
      Usage: "NOT IMPLEMENTED ssh identified by a private key from KMS",
    },
    cli.BoolFlag{
      Name: "Agent, A",
      EnvVar: "ASH_AGENT",
      Usage: "ssh identified by any private key in ~/.ssh/{id_rsa,*.pem} via ssh-agent",
    },
    cli.StringFlag{
      Name: "config-file, F",
      EnvVar: "ASH_CONFIG_FILE",
      Value: "",
      Usage: "ssh -F option: use a config file other than the default (usually ~/.ssh/{ssh_,}config",
    },
    cli.IntFlag{
      Name: "verbosity, v",
      EnvVar: "ASH_VERBOSITY",
      Value: 2,
      Usage: "ash verbosity: 0 - TRACE, 1 - DEBUG, 2 - INFO (default level), 3 - WARN, 4 - ERROR)",
    },
    // Re-instate long version flag.
    cli.BoolFlag{
      Name: "version",
      Usage: "print the version",
    },
    // Re-instate long help flag.
    cli.BoolFlag{
      Name: "help",
      Usage: "show help",
    },
  }
  app.Action = cliRun

  err = app.Run(os.Args)
  if err != nil {
    erro(err)
    return
  }
}
<|endoftext|>


<commit_before>package common

import (
	"github.com/aws/aws-sdk-go/aws/session"
	"log"
	"path"
	"io/ioutil"
	"github.com/aws/aws-sdk-go/service/lambda"
	"encoding/json"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/pkg/errors"
	"fmt"
	"strings"
)

func SshCommand(sess *session.Session, lambdaFunc, funcIdentity, kmsKeyId, instanceArn, username string, encodedVouchers, args []string) []string {
	kp, _ := MyKeyPair()

	ident, err := CallerIdentityUser(sess)
	if err != nil {
		log.Panicf("error getting aws user identity: %+v\n", err)
	}

	vouchers := []VoucherToken{}
	for _, encVoucher := range(encodedVouchers) {
		voucher, err := DecodeVoucherToken(encVoucher)
		if err != nil {
			log.Panicf("couldn't decode voucher: %+v\n", err)
		}
		vouchers = append(vouchers, *voucher)
	}

	token := CreateToken(sess, TokenParams{
		FromId: ident.UserId,
		FromAccount: ident.AccountId,
		FromName: ident.Username,
		To: funcIdentity,
		Type: ident.Type,
		RemoteInstanceArn: instanceArn,
		Vouchers: vouchers,
		SshUsername: username,
	}, kmsKeyId)

	req := UserCertReqJson{
		EventType: "UserCertReq",
		Token: token,
		PublicKey: string(kp.PublicKey),
	}

	signed := UserCertRespJson{}
	err = RequestSignedPayload(sess, lambdaFunc, req, &signed)
	if err != nil {
		log.Panicf("err: %s", err.Error())
	}

	certPath := path.Join(AppDir(), "id_rsa-cert.pub")
	ioutil.WriteFile(certPath, []byte(signed.SignedPublicKey), 0644)

	lkpArgs := []string{
		"ssh",
		"-o",
		"IdentityFile=~/.lkp/id_rsa",
		"-o",
		fmt.Sprintf("HostKeyAlias=%s", instanceArn),
	}

	if len(signed.Jumpboxes) > 0 {
		jumps := []string{}
		for _, jbox := range signed.Jumpboxes {
			jumps = append(jumps, fmt.Sprintf("%s@%s", jbox.User, jbox.Address))
		}
		joinedJumps := strings.Join(jumps, ",")
		lkpArgs = append(lkpArgs, "-J", joinedJumps)
	}

	args = append(lkpArgs, args...)

	if len(signed.TargetAddress) > 0 {
		args = append(args, fmt.Sprintf("%s@%s", username, signed.TargetAddress))
	}

	return args
}

func lambdaClientForKeyId(sess *session.Session, lambdaArn string) *lambda.Lambda {
	if strings.HasPrefix(lambdaArn, "arn:aws:lambda") {
		parts := strings.Split(lambdaArn, ":")
		region := parts[3]
		sess = sess.Copy(aws.NewConfig().WithRegion(region))
	}

	return lambda.New(sess)
}

func RequestSignedPayload(sess *session.Session, lambdaArn string, req interface{}, resp interface{}) error {
	ca := lambdaClientForKeyId(sess, lambdaArn)

	reqPayload, err := json.Marshal(&req)
	if err != nil {
		return errors.Wrap(err, "marshalling lambda req payload")
	}

	input := lambda.InvokeInput{
		FunctionName: aws.String(lambdaArn),
		Payload: reqPayload,
	}

	lambdaResp, err := ca.Invoke(&input)
	if err != nil {
		return errors.Wrap(err, "invoking CA lambda")
	}

	err = json.Unmarshal(lambdaResp.Payload, resp)
	if err != nil {
		return errors.Wrap(err, "unmarshalling lambda resp payload")
	}

	return nil
}
<commit_msg>CLI should throw an error if there's a function-level error (not just AWS errors)<commit_after>package common

import (
	"github.com/aws/aws-sdk-go/aws/session"
	"log"
	"path"
	"io/ioutil"
	"github.com/aws/aws-sdk-go/service/lambda"
	"encoding/json"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/pkg/errors"
	"fmt"
	"strings"
)

func SshCommand(sess *session.Session, lambdaFunc, funcIdentity, kmsKeyId, instanceArn, username string, encodedVouchers, args []string) []string {
	kp, _ := MyKeyPair()

	ident, err := CallerIdentityUser(sess)
	if err != nil {
		log.Panicf("error getting aws user identity: %+v\n", err)
	}

	vouchers := []VoucherToken{}
	for _, encVoucher := range(encodedVouchers) {
		voucher, err := DecodeVoucherToken(encVoucher)
		if err != nil {
			log.Panicf("couldn't decode voucher: %+v\n", err)
		}
		vouchers = append(vouchers, *voucher)
	}

	token := CreateToken(sess, TokenParams{
		FromId: ident.UserId,
		FromAccount: ident.AccountId,
		FromName: ident.Username,
		To: funcIdentity,
		Type: ident.Type,
		RemoteInstanceArn: instanceArn,
		Vouchers: vouchers,
		SshUsername: username,
	}, kmsKeyId)

	req := UserCertReqJson{
		EventType: "UserCertReq",
		Token: token,
		PublicKey: string(kp.PublicKey),
	}

	signed := UserCertRespJson{}
	err = RequestSignedPayload(sess, lambdaFunc, req, &signed)
	if err != nil {
		log.Panicf("err: %s", err.Error())
	}

	certPath := path.Join(AppDir(), "id_rsa-cert.pub")
	ioutil.WriteFile(certPath, []byte(signed.SignedPublicKey), 0644)

	lkpArgs := []string{
		"ssh",
		"-o",
		"IdentityFile=~/.lkp/id_rsa",
		"-o",
		fmt.Sprintf("HostKeyAlias=%s", instanceArn),
	}

	if len(signed.Jumpboxes) > 0 {
		jumps := []string{}
		for _, jbox := range signed.Jumpboxes {
			jumps = append(jumps, fmt.Sprintf("%s@%s", jbox.User, jbox.Address))
		}
		joinedJumps := strings.Join(jumps, ",")
		lkpArgs = append(lkpArgs, "-J", joinedJumps)
	}

	args = append(lkpArgs, args...)

	if len(signed.TargetAddress) > 0 {
		args = append(args, fmt.Sprintf("%s@%s", username, signed.TargetAddress))
	}

	return args
}

func lambdaClientForKeyId(sess *session.Session, lambdaArn string) *lambda.Lambda {
	if strings.HasPrefix(lambdaArn, "arn:aws:lambda") {
		parts := strings.Split(lambdaArn, ":")
		region := parts[3]
		sess = sess.Copy(aws.NewConfig().WithRegion(region))
	}

	return lambda.New(sess)
}

func RequestSignedPayload(sess *session.Session, lambdaArn string, req interface{}, resp interface{}) error {
	ca := lambdaClientForKeyId(sess, lambdaArn)

	reqPayload, err := json.Marshal(&req)
	if err != nil {
		return errors.Wrap(err, "marshalling lambda req payload")
	}

	input := lambda.InvokeInput{
		FunctionName: aws.String(lambdaArn),
		Payload: reqPayload,
	}

	lambdaResp, err := ca.Invoke(&input)
	if err != nil {
		return errors.Wrap(err, "invoking CA lambda")
	}
	if lambdaResp.FunctionError != nil {
		return errors.New(fmt.Sprintf("%s: %s", *lambdaResp.FunctionError, string(lambdaResp.Payload)))
	}

	err = json.Unmarshal(lambdaResp.Payload, resp)
	if err != nil {
		return errors.Wrap(err, "unmarshalling lambda resp payload")
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"mime/multipart"
	"reflect"
	"strconv"
	"strings"
)

type UnsupportedType struct {
	t reflect.Type
}

func (err UnsupportedType) Error() string {
	return fmt.Sprintf("unsupported type: %s", err.t.String())
}

type Marshaler interface {
	MarshalMultipart() string
}

type FileMarshaler interface {
	MarshalMultipart() (string, []byte)
}

type MultipartEncoder struct {
	*multipart.Writer
}

func NewMultipartEncoder(w io.Writer) *MultipartEncoder {
	return &MultipartEncoder{multipart.NewWriter(w)}
}

func (me *MultipartEncoder) Encode(field string, v interface{}) error {
	return me.encode(field, reflect.ValueOf(v))
}

func (me *MultipartEncoder) encode(field string, v reflect.Value) error {
	if v.CanInterface() {
		i := v.Interface()

		if m, ok := i.(Marshaler); ok {
			return me.WriteField(field, m.MarshalMultipart())
		}

		if m, ok := i.(FileMarshaler); ok {
			filename, body := m.MarshalMultipart()
			return me.encodeFile(field, filename, body)
		}
	}

	switch v.Kind() {
	case reflect.Ptr:
		return me.encode(field, v.Elem())
	case reflect.Bool:
		return me.WriteField(field, strconv.FormatBool(v.Bool()))
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return me.WriteField(field, strconv.FormatInt(v.Int(), 10))
	case reflect.Float32, reflect.Float64:
		return me.WriteField(field, strconv.FormatFloat(v.Float(), 'f', -1, 64))
	case reflect.String:
		return me.WriteField(field, v.String())
	case reflect.Slice, reflect.Array:
		return me.encodeSlice(field, v)
	case reflect.Map:
		return me.encodeMap(field, v)
	case reflect.Struct:
		return me.encodeStruct(field, v)
	case reflect.Interface:
		return me.Encode(field, v.Interface())
	default:
		return UnsupportedType{v.Type()}
	}
}

func (me *MultipartEncoder) encodeSlice(field string, v reflect.Value) error {
	for i := 0; i < v.Len(); i++ {
		if err := me.encode(fmt.Sprintf("%s[]", field), v.Index(i)); err != nil {
			return err
		}
	}
	return nil
}

func (me *MultipartEncoder) encodeMap(field string, v reflect.Value) error {
	if v.Type().Key().Kind() != reflect.String {
		return UnsupportedType{v.Type()}
	}

	for _, k := range v.MapKeys() {
		if err := me.encode(joinFields(field, k.String()), v.MapIndex(k)); err != nil {
			return err
		}
	}
	return nil
}

func (me *MultipartEncoder) encodeStruct(field string, v reflect.Value) error {
	t := v.Type()

	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		name, _ := parseTag(f.Tag.Get("multipart"))

		if name == "-" {
			continue
		}

		if len(name) == 0 {
			name = f.Name
		}

		if err := me.encode(joinFields(field, name), v.Field(i)); err != nil {
			return err
		}
	}
	return nil
}

func (me *MultipartEncoder) encodeFile(field, filename string, body []byte) error {
	part, err := me.CreateFormFile(field, filename)

	if err != nil {
		return err
	}
	_, err = part.Write(body)
	return err
}

func joinFields(a, b string) string {
	if len(a) == 0 {
		return b
	}
	return fmt.Sprintf("%s[%s]", a, b)
}

func parseTag(tag string) (name string, opts []string) {
	opts = strings.Split(tag, ",")

	for i, opt := range opts {
		opts[i] = strings.TrimSpace(opt)
	}

	if len(opts) > 0 {
		name = opts[0]
		opts = opts[1:]
	}
	return name, opts
}
<commit_msg>minor cleanup<commit_after>package main

import (
	"fmt"
	"io"
	"mime/multipart"
	"reflect"
	"strconv"
	"strings"
)

type UnsupportedType struct {
	t reflect.Type
}

func (err UnsupportedType) Error() string {
	return fmt.Sprintf("unsupported type: %s", err.t.String())
}

type Marshaler interface {
	MarshalMultipart() string
}

type FileMarshaler interface {
	MarshalMultipart() (string, []byte)
}

type MultipartEncoder struct {
	*multipart.Writer
}

func NewMultipartEncoder(w io.Writer) *MultipartEncoder {
	return &MultipartEncoder{multipart.NewWriter(w)}
}

func (me *MultipartEncoder) Encode(field string, v interface{}) error {
	return me.encode(field, reflect.ValueOf(v))
}

func (me *MultipartEncoder) encode(field string, v reflect.Value) error {
	if v.CanInterface() {
		i := v.Interface()

		if m, ok := i.(Marshaler); ok {
			return me.WriteField(field, m.MarshalMultipart())
		}

		if m, ok := i.(FileMarshaler); ok {
			filename, body := m.MarshalMultipart()
			return me.encodeFile(field, filename, body)
		}
	}

	switch v.Kind() {
	case reflect.Ptr:
		return me.encode(field, v.Elem())
	case reflect.Bool:
		return me.WriteField(field, strconv.FormatBool(v.Bool()))
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return me.WriteField(field, strconv.FormatInt(v.Int(), 10))
	case reflect.Float32, reflect.Float64:
		return me.WriteField(field, strconv.FormatFloat(v.Float(), 'f', -1, 64))
	case reflect.String:
		return me.WriteField(field, v.String())
	case reflect.Slice, reflect.Array:
		return me.encodeSlice(field, v)
	case reflect.Map:
		return me.encodeMap(field, v)
	case reflect.Struct:
		return me.encodeStruct(field, v)
	case reflect.Interface:
		return me.Encode(field, v.Interface())
	default:
		return UnsupportedType{v.Type()}
	}
}

func (me *MultipartEncoder) encodeSlice(field string, v reflect.Value) error {
	for i := 0; i < v.Len(); i++ {
		if err := me.encode(fmt.Sprintf("%s[]", field), v.Index(i)); err != nil {
			return err
		}
	}
	return nil
}

func (me *MultipartEncoder) encodeMap(field string, v reflect.Value) error {
	if v.Type().Key().Kind() != reflect.String {
		return UnsupportedType{v.Type()}
	}

	for _, k := range v.MapKeys() {
		if err := me.encode(joinFields(field, k.String()), v.MapIndex(k)); err != nil {
			return err
		}
	}
	return nil
}

func (me *MultipartEncoder) encodeStruct(field string, v reflect.Value) error {
	t := v.Type()

	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		name, _ := parseTag(f.Tag.Get("multipart"))

		if name == "-" {
			continue
		}

		if len(name) == 0 {
			name = f.Name
		}

		if err := me.encode(joinFields(field, name), v.Field(i)); err != nil {
			return err
		}
	}
	return nil
}

func (me *MultipartEncoder) encodeFile(field, filename string, body []byte) error {
	part, err := me.CreateFormFile(field, filename)

	if err != nil {
		return err
	}
	_, err = part.Write(body)
	return err
}

func joinFields(a, b string) string {
	if len(a) == 0 {
		return b
	}
	return fmt.Sprintf("%s[%s]", a, b)
}

func parseTag(tag string) (string, []string) {
	opts := strings.Split(tag, ",")

	for i, opt := range opts {
		opts[i] = strings.TrimSpace(opt)
	}

	if len(opts) == 0 {
		return "", opts
	}
	return opts[0], opts[1:]
}
<|endoftext|>


<commit_before>// Package transport defines JSON-compatible guardian messages.
package transport

import (
	"time"

	"github.com/scjalliance/resourceful/environment"
	"github.com/scjalliance/resourceful/lease"
)

// HealthResponse reports the health of a guardian server.
type HealthResponse struct {
	OK bool `json:"ok"`
}

// Request represents a request from a resourceful client.
type Request struct {
	Resource    string                  `json:"resource"`
	Consumer    string                  `json:"consumer"`
	Environment environment.Environment `json:"environment,omitempty"`
}

// AcquireResponse reports the result of a resource acquisition attempt.
type AcquireResponse struct {
	Request
	Accepted bool          `json:"accepted"`
	Message  string        `json:"message,omitempty"`
	Duration time.Duration `json:"duration"` // FIXME: JSON duration codec
	Leases   []lease.Lease `json:"lease"`
}

// UpdateResponse represents a lease environment update request.
type UpdateResponse struct {
	Request
	Success bool   `json:"success"`
	Message string `json:"message,omitempty"`
}
<commit_msg>Removed redundant return value in transport<commit_after>// Package transport defines JSON-compatible guardian messages.
package transport

import (
	"github.com/scjalliance/resourceful/environment"
	"github.com/scjalliance/resourceful/lease"
)

// HealthResponse reports the health of a guardian server.
type HealthResponse struct {
	OK bool `json:"ok"`
}

// Request represents a request from a resourceful client.
type Request struct {
	Resource    string                  `json:"resource"`
	Consumer    string                  `json:"consumer"`
	Environment environment.Environment `json:"environment,omitempty"`
}

// AcquireResponse reports the result of a resource acquisition attempt.
type AcquireResponse struct {
	Request
	Accepted bool          `json:"accepted"`
	Message  string        `json:"message,omitempty"`
	Leases   []lease.Lease `json:"lease"`
}

// UpdateResponse represents a lease environment update request.
type UpdateResponse struct {
	Request
	Success bool   `json:"success"`
	Message string `json:"message,omitempty"`
}
<|endoftext|>


<commit_before>package namesys

import (
	"bytes"
	"errors"
	"fmt"
	"time"

	proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
	ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/ipfs/go-datastore"
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"

	key "github.com/ipfs/go-ipfs/blocks/key"
	dag "github.com/ipfs/go-ipfs/merkledag"
	pb "github.com/ipfs/go-ipfs/namesys/pb"
	path "github.com/ipfs/go-ipfs/path"
	pin "github.com/ipfs/go-ipfs/pin"
	routing "github.com/ipfs/go-ipfs/routing"
	dhtpb "github.com/ipfs/go-ipfs/routing/dht/pb"
	record "github.com/ipfs/go-ipfs/routing/record"
	ft "github.com/ipfs/go-ipfs/unixfs"
	u "github.com/ipfs/go-ipfs/util"
	ci "gx/ipfs/QmUBogf4nUefBjmYjn6jfsfPJRkmDGSeMhNj4usRKq69f4/go-libp2p/p2p/crypto"
	peer "gx/ipfs/QmUBogf4nUefBjmYjn6jfsfPJRkmDGSeMhNj4usRKq69f4/go-libp2p/p2p/peer"
)

// ErrExpiredRecord should be returned when an ipns record is
// invalid due to being too old
var ErrExpiredRecord = errors.New("expired record")

// ErrUnrecognizedValidity is returned when an IpnsRecord has an
// unknown validity type.
var ErrUnrecognizedValidity = errors.New("unrecognized validity type")

var PublishPutValTimeout = time.Minute

// ipnsPublisher is capable of publishing and resolving names to the IPFS
// routing system.
type ipnsPublisher struct {
	routing routing.IpfsRouting
	ds      ds.Datastore
}

// NewRoutingPublisher constructs a publisher for the IPFS Routing name system.
func NewRoutingPublisher(route routing.IpfsRouting, ds ds.Datastore) *ipnsPublisher {
	if ds == nil {
		panic("nil datastore")
	}
	return &ipnsPublisher{routing: route, ds: ds}
}

// Publish implements Publisher. Accepts a keypair and a value,
// and publishes it out to the routing system
func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Path) error {
	log.Debugf("Publish %s", value)
	return p.PublishWithEOL(ctx, k, value, time.Now().Add(time.Hour*24))
}

// PublishWithEOL is a temporary stand in for the ipns records implementation
// see here for more details: https://github.com/ipfs/specs/tree/master/records
func (p *ipnsPublisher) PublishWithEOL(ctx context.Context, k ci.PrivKey, value path.Path, eol time.Time) error {

	id, err := peer.IDFromPrivateKey(k)
	if err != nil {
		return err
	}

	_, ipnskey := IpnsKeysForID(id)

	// get previous records sequence number
	seqnum, err := p.getPreviousSeqNo(ctx, ipnskey)
	if err != nil {
		return err
	}

	// increment it
	seqnum++

	return PutRecordToRouting(ctx, k, value, seqnum, eol, p.routing, id)
}

func (p *ipnsPublisher) getPreviousSeqNo(ctx context.Context, ipnskey key.Key) (uint64, error) {
	prevrec, err := p.ds.Get(ipnskey.DsKey())
	if err != nil && err != ds.ErrNotFound {
		// None found, lets start at zero!
		return 0, err
	}
	var val []byte
	if err == nil {
		prbytes, ok := prevrec.([]byte)
		if !ok {
			return 0, fmt.Errorf("unexpected type returned from datastore: %#v", prevrec)
		}
		dhtrec := new(dhtpb.Record)
		err := proto.Unmarshal(prbytes, dhtrec)
		if err != nil {
			return 0, err
		}

		val = dhtrec.GetValue()
	} else {
		// try and check the dht for a record
		ctx, cancel := context.WithTimeout(ctx, time.Second*30)
		defer cancel()

		rv, err := p.routing.GetValue(ctx, ipnskey)
		if err != nil {
			// no such record found, start at zero!
			return 0, nil
		}

		val = rv
	}

	e := new(pb.IpnsEntry)
	err = proto.Unmarshal(val, e)
	if err != nil {
		return 0, err
	}

	return e.GetSequence(), nil
}

// setting the TTL on published records is an experimental feature.
// as such, i'm using the context to wire it through to avoid changing too
// much code along the way.
func checkCtxTTL(ctx context.Context) (time.Duration, bool) {
	v := ctx.Value("ipns-publish-ttl")
	if v == nil {
		return 0, false
	}

	d, ok := v.(time.Duration)
	return d, ok
}

func PutRecordToRouting(ctx context.Context, k ci.PrivKey, value path.Path, seqnum uint64, eol time.Time, r routing.IpfsRouting, id peer.ID) error {
	namekey, ipnskey := IpnsKeysForID(id)
	entry, err := CreateRoutingEntryData(k, value, seqnum, eol)
	if err != nil {
		return err
	}

	ttl, ok := checkCtxTTL(ctx)
	if ok {
		entry.Ttl = proto.Uint64(uint64(ttl.Nanoseconds()))
	}

	err = PublishEntry(ctx, r, ipnskey, entry)
	if err != nil {
		return err
	}

	err = PublishPublicKey(ctx, r, namekey, k.GetPublic())
	if err != nil {
		return err
	}

	return nil
}

func PublishPublicKey(ctx context.Context, r routing.IpfsRouting, k key.Key, pubk ci.PubKey) error {
	log.Debugf("Storing pubkey at: %s", k)
	pkbytes, err := pubk.Bytes()
	if err != nil {
		return err
	}

	// Store associated public key
	timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout)
	defer cancel()
	err = r.PutValue(timectx, k, pkbytes)
	if err != nil {
		return err
	}

	return nil
}

func PublishEntry(ctx context.Context, r routing.IpfsRouting, ipnskey key.Key, rec *pb.IpnsEntry) error {
	timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout)
	defer cancel()

	data, err := proto.Marshal(rec)
	if err != nil {
		return err
	}

	log.Debugf("Storing ipns entry at: %s", ipnskey)
	// Store ipns entry at "/ipns/"+b58(h(pubkey))
	if err := r.PutValue(timectx, ipnskey, data); err != nil {
		return err
	}

	return nil
}

func CreateRoutingEntryData(pk ci.PrivKey, val path.Path, seq uint64, eol time.Time) (*pb.IpnsEntry, error) {
	entry := new(pb.IpnsEntry)

	entry.Value = []byte(val)
	typ := pb.IpnsEntry_EOL
	entry.ValidityType = &typ
	entry.Sequence = proto.Uint64(seq)
	entry.Validity = []byte(u.FormatRFC3339(eol))

	sig, err := pk.Sign(ipnsEntryDataForSig(entry))
	if err != nil {
		return nil, err
	}
	entry.Signature = sig
	return entry, nil
}

func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
	return bytes.Join([][]byte{
		e.Value,
		e.Validity,
		[]byte(fmt.Sprint(e.GetValidityType())),
	},
		[]byte{})
}

var IpnsRecordValidator = &record.ValidChecker{
	Func: ValidateIpnsRecord,
	Sign: true,
}

func IpnsSelectorFunc(k key.Key, vals [][]byte) (int, error) {
	var recs []*pb.IpnsEntry
	for _, v := range vals {
		e := new(pb.IpnsEntry)
		err := proto.Unmarshal(v, e)
		if err == nil {
			recs = append(recs, e)
		} else {
			recs = append(recs, nil)
		}
	}

	return selectRecord(recs, vals)
}

func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
	var best_seq uint64
	best_i := -1

	for i, r := range recs {
		if r == nil || r.GetSequence() < best_seq {
			continue
		}

		if best_i == -1 || r.GetSequence() > best_seq {
			best_seq = r.GetSequence()
			best_i = i
		} else if r.GetSequence() == best_seq {
			rt, err := u.ParseRFC3339(string(r.GetValidity()))
			if err != nil {
				continue
			}

			bestt, err := u.ParseRFC3339(string(recs[best_i].GetValidity()))
			if err != nil {
				continue
			}

			if rt.After(bestt) {
				best_i = i
			} else if rt == bestt {
				if bytes.Compare(vals[i], vals[best_i]) > 0 {
					best_i = i
				}
			}
		}
	}
	if best_i == -1 {
		return 0, errors.New("no usable records in given set")
	}

	return best_i, nil
}

// ValidateIpnsRecord implements ValidatorFunc and verifies that the
// given 'val' is an IpnsEntry and that that entry is valid.
func ValidateIpnsRecord(k key.Key, val []byte) error {
	entry := new(pb.IpnsEntry)
	err := proto.Unmarshal(val, entry)
	if err != nil {
		return err
	}
	switch entry.GetValidityType() {
	case pb.IpnsEntry_EOL:
		t, err := u.ParseRFC3339(string(entry.GetValidity()))
		if err != nil {
			log.Debug("Failed parsing time for ipns record EOL")
			return err
		}
		if time.Now().After(t) {
			return ErrExpiredRecord
		}
	default:
		return ErrUnrecognizedValidity
	}
	return nil
}

// InitializeKeyspace sets the ipns record for the given key to
// point to an empty directory.
// TODO: this doesnt feel like it belongs here
func InitializeKeyspace(ctx context.Context, ds dag.DAGService, pub Publisher, pins pin.Pinner, key ci.PrivKey) error {
	emptyDir := &dag.Node{Data: ft.FolderPBData()}
	nodek, err := ds.Add(emptyDir)
	if err != nil {
		return err
	}

	// pin recursively because this might already be pinned
	// and doing a direct pin would throw an error in that case
	err = pins.Pin(ctx, emptyDir, true)
	if err != nil {
		return err
	}

	err = pins.Flush()
	if err != nil {
		return err
	}

	err = pub.Publish(ctx, key, path.FromKey(nodek))
	if err != nil {
		return err
	}

	return nil
}

func IpnsKeysForID(id peer.ID) (name, ipns key.Key) {
	namekey := key.Key("/pk/" + id)
	ipnskey := key.Key("/ipns/" + id)

	return namekey, ipnskey
}
<commit_msg>put pubkey and ipns entry to dht in parallel<commit_after>package namesys

import (
	"bytes"
	"errors"
	"fmt"
	"time"

	proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
	ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/ipfs/go-datastore"
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"

	key "github.com/ipfs/go-ipfs/blocks/key"
	dag "github.com/ipfs/go-ipfs/merkledag"
	pb "github.com/ipfs/go-ipfs/namesys/pb"
	path "github.com/ipfs/go-ipfs/path"
	pin "github.com/ipfs/go-ipfs/pin"
	routing "github.com/ipfs/go-ipfs/routing"
	dhtpb "github.com/ipfs/go-ipfs/routing/dht/pb"
	record "github.com/ipfs/go-ipfs/routing/record"
	ft "github.com/ipfs/go-ipfs/unixfs"
	u "github.com/ipfs/go-ipfs/util"
	ci "gx/ipfs/QmUBogf4nUefBjmYjn6jfsfPJRkmDGSeMhNj4usRKq69f4/go-libp2p/p2p/crypto"
	peer "gx/ipfs/QmUBogf4nUefBjmYjn6jfsfPJRkmDGSeMhNj4usRKq69f4/go-libp2p/p2p/peer"
)

// ErrExpiredRecord should be returned when an ipns record is
// invalid due to being too old
var ErrExpiredRecord = errors.New("expired record")

// ErrUnrecognizedValidity is returned when an IpnsRecord has an
// unknown validity type.
var ErrUnrecognizedValidity = errors.New("unrecognized validity type")

var PublishPutValTimeout = time.Minute

// ipnsPublisher is capable of publishing and resolving names to the IPFS
// routing system.
type ipnsPublisher struct {
	routing routing.IpfsRouting
	ds      ds.Datastore
}

// NewRoutingPublisher constructs a publisher for the IPFS Routing name system.
func NewRoutingPublisher(route routing.IpfsRouting, ds ds.Datastore) *ipnsPublisher {
	if ds == nil {
		panic("nil datastore")
	}
	return &ipnsPublisher{routing: route, ds: ds}
}

// Publish implements Publisher. Accepts a keypair and a value,
// and publishes it out to the routing system
func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Path) error {
	log.Debugf("Publish %s", value)
	return p.PublishWithEOL(ctx, k, value, time.Now().Add(time.Hour*24))
}

// PublishWithEOL is a temporary stand in for the ipns records implementation
// see here for more details: https://github.com/ipfs/specs/tree/master/records
func (p *ipnsPublisher) PublishWithEOL(ctx context.Context, k ci.PrivKey, value path.Path, eol time.Time) error {

	id, err := peer.IDFromPrivateKey(k)
	if err != nil {
		return err
	}

	_, ipnskey := IpnsKeysForID(id)

	// get previous records sequence number
	seqnum, err := p.getPreviousSeqNo(ctx, ipnskey)
	if err != nil {
		return err
	}

	// increment it
	seqnum++

	return PutRecordToRouting(ctx, k, value, seqnum, eol, p.routing, id)
}

func (p *ipnsPublisher) getPreviousSeqNo(ctx context.Context, ipnskey key.Key) (uint64, error) {
	prevrec, err := p.ds.Get(ipnskey.DsKey())
	if err != nil && err != ds.ErrNotFound {
		// None found, lets start at zero!
		return 0, err
	}
	var val []byte
	if err == nil {
		prbytes, ok := prevrec.([]byte)
		if !ok {
			return 0, fmt.Errorf("unexpected type returned from datastore: %#v", prevrec)
		}
		dhtrec := new(dhtpb.Record)
		err := proto.Unmarshal(prbytes, dhtrec)
		if err != nil {
			return 0, err
		}

		val = dhtrec.GetValue()
	} else {
		// try and check the dht for a record
		ctx, cancel := context.WithTimeout(ctx, time.Second*30)
		defer cancel()

		rv, err := p.routing.GetValue(ctx, ipnskey)
		if err != nil {
			// no such record found, start at zero!
			return 0, nil
		}

		val = rv
	}

	e := new(pb.IpnsEntry)
	err = proto.Unmarshal(val, e)
	if err != nil {
		return 0, err
	}

	return e.GetSequence(), nil
}

// setting the TTL on published records is an experimental feature.
// as such, i'm using the context to wire it through to avoid changing too
// much code along the way.
func checkCtxTTL(ctx context.Context) (time.Duration, bool) {
	v := ctx.Value("ipns-publish-ttl")
	if v == nil {
		return 0, false
	}

	d, ok := v.(time.Duration)
	return d, ok
}

func PutRecordToRouting(ctx context.Context, k ci.PrivKey, value path.Path, seqnum uint64, eol time.Time, r routing.IpfsRouting, id peer.ID) error {
	namekey, ipnskey := IpnsKeysForID(id)
	entry, err := CreateRoutingEntryData(k, value, seqnum, eol)
	if err != nil {
		return err
	}

	ttl, ok := checkCtxTTL(ctx)
	if ok {
		entry.Ttl = proto.Uint64(uint64(ttl.Nanoseconds()))
	}

	errs := make(chan error)

	go func() {
		errs <- PublishEntry(ctx, r, ipnskey, entry)
	}()

	go func() {
		errs <- PublishPublicKey(ctx, r, namekey, k.GetPublic())
	}()

	err = waitOnErrChan(ctx, errs)
	if err != nil {
		return err
	}

	err = waitOnErrChan(ctx, errs)
	if err != nil {
		return err
	}

	return nil
}

func waitOnErrChan(ctx context.Context, errs chan error) error {
	select {
	case err := <-errs:
		if err != nil {
			return err
		}
	case <-ctx.Done():
		return ctx.Err()
	}

	return nil
}

func PublishPublicKey(ctx context.Context, r routing.IpfsRouting, k key.Key, pubk ci.PubKey) error {
	log.Debugf("Storing pubkey at: %s", k)
	pkbytes, err := pubk.Bytes()
	if err != nil {
		return err
	}

	// Store associated public key
	timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout)
	defer cancel()
	err = r.PutValue(timectx, k, pkbytes)
	if err != nil {
		return err
	}

	return nil
}

func PublishEntry(ctx context.Context, r routing.IpfsRouting, ipnskey key.Key, rec *pb.IpnsEntry) error {
	timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout)
	defer cancel()

	data, err := proto.Marshal(rec)
	if err != nil {
		return err
	}

	log.Debugf("Storing ipns entry at: %s", ipnskey)
	// Store ipns entry at "/ipns/"+b58(h(pubkey))
	if err := r.PutValue(timectx, ipnskey, data); err != nil {
		return err
	}

	return nil
}

func CreateRoutingEntryData(pk ci.PrivKey, val path.Path, seq uint64, eol time.Time) (*pb.IpnsEntry, error) {
	entry := new(pb.IpnsEntry)

	entry.Value = []byte(val)
	typ := pb.IpnsEntry_EOL
	entry.ValidityType = &typ
	entry.Sequence = proto.Uint64(seq)
	entry.Validity = []byte(u.FormatRFC3339(eol))

	sig, err := pk.Sign(ipnsEntryDataForSig(entry))
	if err != nil {
		return nil, err
	}
	entry.Signature = sig
	return entry, nil
}

func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
	return bytes.Join([][]byte{
		e.Value,
		e.Validity,
		[]byte(fmt.Sprint(e.GetValidityType())),
	},
		[]byte{})
}

var IpnsRecordValidator = &record.ValidChecker{
	Func: ValidateIpnsRecord,
	Sign: true,
}

func IpnsSelectorFunc(k key.Key, vals [][]byte) (int, error) {
	var recs []*pb.IpnsEntry
	for _, v := range vals {
		e := new(pb.IpnsEntry)
		err := proto.Unmarshal(v, e)
		if err == nil {
			recs = append(recs, e)
		} else {
			recs = append(recs, nil)
		}
	}

	return selectRecord(recs, vals)
}

func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
	var best_seq uint64
	best_i := -1

	for i, r := range recs {
		if r == nil || r.GetSequence() < best_seq {
			continue
		}

		if best_i == -1 || r.GetSequence() > best_seq {
			best_seq = r.GetSequence()
			best_i = i
		} else if r.GetSequence() == best_seq {
			rt, err := u.ParseRFC3339(string(r.GetValidity()))
			if err != nil {
				continue
			}

			bestt, err := u.ParseRFC3339(string(recs[best_i].GetValidity()))
			if err != nil {
				continue
			}

			if rt.After(bestt) {
				best_i = i
			} else if rt == bestt {
				if bytes.Compare(vals[i], vals[best_i]) > 0 {
					best_i = i
				}
			}
		}
	}
	if best_i == -1 {
		return 0, errors.New("no usable records in given set")
	}

	return best_i, nil
}

// ValidateIpnsRecord implements ValidatorFunc and verifies that the
// given 'val' is an IpnsEntry and that that entry is valid.
func ValidateIpnsRecord(k key.Key, val []byte) error {
	entry := new(pb.IpnsEntry)
	err := proto.Unmarshal(val, entry)
	if err != nil {
		return err
	}
	switch entry.GetValidityType() {
	case pb.IpnsEntry_EOL:
		t, err := u.ParseRFC3339(string(entry.GetValidity()))
		if err != nil {
			log.Debug("Failed parsing time for ipns record EOL")
			return err
		}
		if time.Now().After(t) {
			return ErrExpiredRecord
		}
	default:
		return ErrUnrecognizedValidity
	}
	return nil
}

// InitializeKeyspace sets the ipns record for the given key to
// point to an empty directory.
// TODO: this doesnt feel like it belongs here
func InitializeKeyspace(ctx context.Context, ds dag.DAGService, pub Publisher, pins pin.Pinner, key ci.PrivKey) error {
	emptyDir := &dag.Node{Data: ft.FolderPBData()}
	nodek, err := ds.Add(emptyDir)
	if err != nil {
		return err
	}

	// pin recursively because this might already be pinned
	// and doing a direct pin would throw an error in that case
	err = pins.Pin(ctx, emptyDir, true)
	if err != nil {
		return err
	}

	err = pins.Flush()
	if err != nil {
		return err
	}

	err = pub.Publish(ctx, key, path.FromKey(nodek))
	if err != nil {
		return err
	}

	return nil
}

func IpnsKeysForID(id peer.ID) (name, ipns key.Key) {
	namekey := key.Key("/pk/" + id)
	ipnskey := key.Key("/ipns/" + id)

	return namekey, ipnskey
}
<|endoftext|>


<commit_before>package command

import (
	"encoding/json"
	"fmt"
	"net/url"
	"strings"

	"github.com/mitchellh/cli"
	v1auth "github.com/nerdalize/nerd/nerd/client/auth/v1"
	"github.com/nerdalize/nerd/nerd/jwt"
	"github.com/nerdalize/nerd/nerd/oauth"
	"github.com/pkg/errors"
)

//WorkloadStartOpts describes command options
type WorkloadStartOpts struct {
	Env          []string `long:"env" short:"e" description:"Environment variables"`
	InputDataset string   `long:"input-dataset" short:"d" description:"Input dataset ID, will be available in /input in your container"`
	Instances    int      `long:"instances" short:"i" default:"1" description:"Number of working instances"`
	PullSecret   string   `long:"pull-secret" short:"p" description:"The pull secret will be used to fetch the private image"`
}

//WorkloadStart command
type WorkloadStart struct {
	*command
	opts *WorkloadStartOpts
}

//WorkloadStartFactory returns a factory method for the join command
func WorkloadStartFactory() (cli.Command, error) {
	opts := &WorkloadStartOpts{}
	comm, err := newCommand("nerd workload start <image>", "Provision a new workload to provide compute.", "", opts)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create command")
	}
	cmd := &WorkloadStart{
		command: comm,
		opts:    opts,
	}
	cmd.runFunc = cmd.DoRun

	return cmd, nil
}

//DoRun is called by run and allows an error to be returned
func (cmd *WorkloadStart) DoRun(args []string) (err error) {
	if len(args) < 1 {
		return errShowHelp("Not enough arguments, see below for usage.")
	}

	//fetching a worker JWT
	authbase, err := url.Parse(cmd.config.Auth.APIEndpoint)
	if err != nil {
		return HandleError(errors.Wrapf(err, "auth endpoint '%v' is not a valid URL", cmd.config.Auth.APIEndpoint))
	}

	authOpsClient := v1auth.NewOpsClient(v1auth.OpsClientConfig{
		Base:   authbase,
		Logger: cmd.outputter.Logger,
	})

	authclient := v1auth.NewClient(v1auth.ClientConfig{
		Base:               authbase,
		Logger:             cmd.outputter.Logger,
		OAuthTokenProvider: oauth.NewConfigProvider(authOpsClient, cmd.config.Auth.ClientID, cmd.session),
	})

	ss, err := cmd.session.Read()
	if err != nil {
		return HandleError(err)
	}

	projectID, err := ss.RequireProjectID()
	if err != nil {
		return HandleError(err)
	}

	workerJWT, err := authclient.GetWorkerJWT(projectID, v1auth.NCEScope)
	if err != nil {
		return HandleError(errors.Wrap(err, "failed to get worker JWT"))
	}

	bclient, err := NewClient(cmd.config, cmd.session, cmd.outputter)
	if err != nil {
		return HandleError(err)
	}

	wenv := map[string]string{}
	for _, l := range cmd.opts.Env {
		split := strings.SplitN(l, "=", 2)
		if len(split) < 2 {
			return HandleError(fmt.Errorf("invalid environment variable format, expected 'FOO=bar' fromat, got: %v", l))
		}
		wenv[split[0]] = split[1]
	}

	wenv[jwt.NerdTokenEnvVar] = workerJWT.Token
	wenv[jwt.NerdSecretEnvVar] = workerJWT.Secret
	configJSON, err := json.Marshal(cmd.config)
	if err != nil {
		return HandleError(errors.Wrap(err, "failed to marshal config"))
	}
	wenv[EnvConfigJSON] = string(configJSON)
	wenv[EnvNerdProject] = ss.Project.Name

	workload, err := bclient.CreateWorkload(ss.Project.Name, args[0], cmd.opts.InputDataset, cmd.opts.PullSecret, wenv, cmd.opts.Instances, true)
	if err != nil {
		return HandleError(err)
	}

	cmd.outputter.Logger.Printf("Workload created with ID: %s", workload.WorkloadID)
	return nil
}
<commit_msg>add size option to nerd workload start<commit_after>package command

import (
	"encoding/json"
	"fmt"
	"net/url"
	"strings"

	"github.com/mitchellh/cli"
	v1auth "github.com/nerdalize/nerd/nerd/client/auth/v1"
	"github.com/nerdalize/nerd/nerd/jwt"
	"github.com/nerdalize/nerd/nerd/oauth"
	"github.com/pkg/errors"
)

//WorkloadStartOpts describes command options
type WorkloadStartOpts struct {
	Env          []string `long:"env" short:"e" description:"Environment variables"`
	InputDataset string   `long:"input-dataset" short:"d" description:"Input dataset ID, will be available in /input in your container"`
	Instances    int      `long:"instances" short:"i" default:"1" description:"Number of working instances"`
	PullSecret   string   `long:"pull-secret" short:"p" description:"The pull secret will be used to fetch the private image"`
	Size         uint64   `long:"size" short:"s" description:"The amount of resources you want to use for your workload, in compute units."`
}

//WorkloadStart command
type WorkloadStart struct {
	*command
	opts *WorkloadStartOpts
}

//WorkloadStartFactory returns a factory method for the join command
func WorkloadStartFactory() (cli.Command, error) {
	opts := &WorkloadStartOpts{}
	comm, err := newCommand("nerd workload start <image>", "Provision a new workload to provide compute.", "", opts)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create command")
	}
	cmd := &WorkloadStart{
		command: comm,
		opts:    opts,
	}
	cmd.runFunc = cmd.DoRun

	return cmd, nil
}

//DoRun is called by run and allows an error to be returned
func (cmd *WorkloadStart) DoRun(args []string) (err error) {
	if len(args) < 1 {
		return errShowHelp("Not enough arguments, see below for usage.")
	}

	//fetching a worker JWT
	authbase, err := url.Parse(cmd.config.Auth.APIEndpoint)
	if err != nil {
		return HandleError(errors.Wrapf(err, "auth endpoint '%v' is not a valid URL", cmd.config.Auth.APIEndpoint))
	}

	authOpsClient := v1auth.NewOpsClient(v1auth.OpsClientConfig{
		Base:   authbase,
		Logger: cmd.outputter.Logger,
	})

	authclient := v1auth.NewClient(v1auth.ClientConfig{
		Base:               authbase,
		Logger:             cmd.outputter.Logger,
		OAuthTokenProvider: oauth.NewConfigProvider(authOpsClient, cmd.config.Auth.ClientID, cmd.session),
	})

	ss, err := cmd.session.Read()
	if err != nil {
		return HandleError(err)
	}

	projectID, err := ss.RequireProjectID()
	if err != nil {
		return HandleError(err)
	}

	workerJWT, err := authclient.GetWorkerJWT(projectID, v1auth.NCEScope)
	if err != nil {
		return HandleError(errors.Wrap(err, "failed to get worker JWT"))
	}

	bclient, err := NewClient(cmd.config, cmd.session, cmd.outputter)
	if err != nil {
		return HandleError(err)
	}

	wenv := map[string]string{}
	for _, l := range cmd.opts.Env {
		split := strings.SplitN(l, "=", 2)
		if len(split) < 2 {
			return HandleError(fmt.Errorf("invalid environment variable format, expected 'FOO=bar' fromat, got: %v", l))
		}
		wenv[split[0]] = split[1]
	}

	wenv[jwt.NerdTokenEnvVar] = workerJWT.Token
	wenv[jwt.NerdSecretEnvVar] = workerJWT.Secret
	configJSON, err := json.Marshal(cmd.config)
	if err != nil {
		return HandleError(errors.Wrap(err, "failed to marshal config"))
	}
	wenv[EnvConfigJSON] = string(configJSON)
	wenv[EnvNerdProject] = ss.Project.Name

	workload, err := bclient.CreateWorkload(ss.Project.Name, args[0], cmd.opts.InputDataset, cmd.opts.PullSecret, cmd.opts.Size, wenv, cmd.opts.Instances, true)
	if err != nil {
		return HandleError(err)
	}

	cmd.outputter.Logger.Printf("Workload created with ID: %s", workload.WorkloadID)
	return nil
}
<|endoftext|>


<commit_before>package rest

import (
	"time"

	"github.com/freeznet/tomato/cache"
	"github.com/freeznet/tomato/errs"
	"github.com/freeznet/tomato/types"
	"github.com/freeznet/tomato/utils"
)

// Auth 保存当前请求的用户权限信息
type Auth struct {
	IsMaster       bool
	IsReadOnly     bool
	InstallationID string
	User           types.M
	UserRoles      []string
	FetchedRoles   bool
	RolePromise    []string
	Info           *types.RequestInfo
}

// Master 生成 Master 级别用户
func Master(info ...*types.RequestInfo) *Auth {
	if info != nil {
		return &Auth{IsMaster: true, IsReadOnly: false, Info: info[0]}
	}
	return &Auth{IsMaster: true, IsReadOnly: false, Info: &types.RequestInfo{}}
}

// ReadOnly 生成 Master 级别的只读用户
func ReadOnly(info ...*types.RequestInfo) *Auth {
	if info != nil {
		return &Auth{IsMaster: true, IsReadOnly: true, Info: info[0]}
	}
	return &Auth{IsMaster: true, IsReadOnly: true, Info: &types.RequestInfo{}}
}

// Nobody 生成空用户
func Nobody(info ...*types.RequestInfo) *Auth {
	if info != nil {
		return &Auth{IsMaster: false, IsReadOnly: false, Info: info[0]}
	}
	return &Auth{IsMaster: false, IsReadOnly: false, Info: &types.RequestInfo{}}
}

// GetAuthForSessionToken 返回 sessionToken 对应的用户权限信息
func GetAuthForSessionToken(sessionToken string, installationID string, info *types.RequestInfo) (*Auth, error) {
	// 从缓存获取用户信息
	cachedUser := cache.User.Get(sessionToken)
	if u := utils.M(cachedUser); u != nil {
		return &Auth{
			IsMaster:       false,
			IsReadOnly:     false,
			InstallationID: installationID,
			User:           u,
		}, nil
	}
	// 缓存中不存在时，从数据库中查询
	restOptions := types.M{
		"limit":   1,
		"include": "user",
	}
	restWhere := types.M{
		"sessionToken": sessionToken,
	}

	sessionErr := errs.E(errs.InvalidSessionToken, "invalid session token")
	query, err := NewQuery(Master(info), "_Session", restWhere, restOptions, nil)
	if err != nil {
		return nil, sessionErr
	}
	response, err := query.Execute()
	if err != nil {
		return nil, sessionErr
	}

	if response == nil || response["results"] == nil {
		return nil, sessionErr
	}
	results := utils.A(response["results"])
	if results == nil || len(results) != 1 {
		return nil, sessionErr
	}
	result := utils.M(results[0])
	if result == nil || result["user"] == nil {
		return nil, sessionErr
	}

	now := time.Now().UTC()
	if result["expiresAt"] == nil {
		return nil, errs.E(errs.InvalidSessionToken, "Session token is expired.")
	}
	expiresAtDate := utils.M(result["expiresAt"])
	expiresAtString := utils.S(expiresAtDate["iso"])
	expiresAt, err := utils.StringtoTime(expiresAtString)
	if err != nil {
		return nil, errs.E(errs.InvalidSessionToken, "Session token is expired.")
	}
	if expiresAt.UnixNano() < now.UnixNano() {
		return nil, errs.E(errs.InvalidSessionToken, "Session token is expired.")
	}

	user := utils.M(result["user"])
	delete(user, "password")
	user["className"] = "_User"
	user["sessionToken"] = sessionToken
	// 写入缓存
	cache.User.Put(sessionToken, user, 0)

	return &Auth{
		IsMaster:       false,
		IsReadOnly:     false,
		InstallationID: installationID,
		User:           user,
		Info:           info,
	}, nil
}

// GetAuthForLegacySessionToken 处理保存在 _User 中的 sessionToken。
// 该方法处理从 parse 中迁移过来的用户数据，在 tomato 中其实不需要处理这种类型的数据，以后考虑删除
func GetAuthForLegacySessionToken(sessionToken, installationID string, info *types.RequestInfo) (*Auth, error) {
	restOptions := types.M{"limit": 1}
	query, err := NewQuery(Master(info), "_User", types.M{"sessionToken": sessionToken}, restOptions, nil)
	if err != nil {
		return nil, err
	}
	response, err := query.Execute()
	if err != nil {
		return nil, err
	}
	sessionErr := errs.E(errs.InvalidSessionToken, "invalid legacy session token")
	if response == nil || response["results"] == nil {
		return nil, sessionErr
	}
	results := utils.A(response["results"])
	if results == nil || len(results) != 1 {
		return nil, sessionErr
	}
	userObject := utils.M(results[0])
	if userObject == nil {
		return nil, sessionErr
	}
	userObject["className"] = "_User"
	return &Auth{
		IsMaster:       false,
		IsReadOnly:     false,
		InstallationID: installationID,
		User:           userObject,
		Info:           info,
	}, nil
}

// CouldUpdateUserID Master 与当前用户可进行修改
func (a *Auth) CouldUpdateUserID(objectID string) bool {
	if a.IsMaster {
		return true
	}
	if a.User != nil && utils.S(a.User["objectId"]) == objectID {
		return true
	}
	return false
}

// GetUserRoles 获取用户所属的角色列表
func (a *Auth) GetUserRoles() []string {
	if a.IsMaster || a.User == nil {
		return []string{}
	}
	if a.FetchedRoles {
		return a.UserRoles
	}
	if a.RolePromise != nil {
		return a.RolePromise
	}
	a.RolePromise = a.loadRoles()
	return a.RolePromise
}

// loadRoles 从数据库加载用户角色列表
func (a *Auth) loadRoles() []string {
	cachedRoles, ok := cache.Role.Get(utils.S(a.User["objectId"])).([]string)
	if ok && cachedRoles != nil {
		a.FetchedRoles = true
		a.UserRoles = cachedRoles
		return cachedRoles
	}

	users := types.M{
		"__type":    "Pointer",
		"className": "_User",
		"objectId":  a.User["objectId"],
	}
	restWhere := types.M{
		"users": users,
	}
	// 取出当前用户直接对应的所有角色
	// TODO 处理错误，处理结果大于100的情况
	query, err := NewQuery(Master(a.Info), "_Role", restWhere, types.M{}, nil)
	if err != nil {
		a.UserRoles = []string{}
		a.FetchedRoles = true
		a.RolePromise = nil
		cache.Role.Put(utils.S(a.User["objectId"]), a.UserRoles, 0)
		return a.UserRoles
	}

	response, err := query.Execute()
	if err != nil || utils.HasResults(response) == false {
		a.UserRoles = []string{}
		a.FetchedRoles = true
		a.RolePromise = nil
		cache.Role.Put(utils.S(a.User["objectId"]), a.UserRoles, 0)
		return a.UserRoles
	}

	results := utils.A(response["results"])
	ids := []string{}
	names := []string{}
	for _, v := range results {
		roleObj := utils.M(v)
		if roleObj == nil {
			continue
		}
		ids = append(ids, utils.S(roleObj["objectId"]))
		names = append(names, utils.S(roleObj["name"]))
	}

	queriedRoles := map[string]bool{} // 记录查询过的 role ，避免多次查询
	roleNames := a.getAllRolesNamesForRoleIds(ids, names, queriedRoles)

	a.UserRoles = []string{}
	for _, v := range roleNames {
		a.UserRoles = append(a.UserRoles, "role:"+v)
	}
	a.FetchedRoles = true
	a.RolePromise = nil

	cache.Role.Put(utils.S(a.User["objectId"]), a.UserRoles, 0)
	return a.UserRoles
}

// getAllRolesNamesForRoleIds 取出角色 id 对应的父角色
func (a *Auth) getAllRolesNamesForRoleIds(roleIDs, names []string, queriedRoles map[string]bool) []string {
	if names == nil {
		names = []string{}
	}
	if queriedRoles == nil {
		queriedRoles = map[string]bool{}
	}
	ins := types.S{}
	for _, roleID := range roleIDs {
		if _, ok := queriedRoles[roleID]; ok {
			continue
		}
		// 标记该 roleID 已经获取过一次父角色了
		queriedRoles[roleID] = true
		object := types.M{
			"__type":    "Pointer",
			"className": "_Role",
			"objectId":  roleID,
		}
		ins = append(ins, object)
	}

	// 已经没有待获取父角色的 roleID，返回 names
	if len(ins) == 0 {
		return names
	}

	restWhere := types.M{}
	if len(ins) == 1 {
		restWhere["roles"] = ins[0]
	} else {
		restWhere["roles"] = types.M{"$in": ins}
	}

	query, err := NewQuery(Master(a.Info), "_Role", restWhere, types.M{}, nil)
	if err != nil {
		return names
	}

	// 未找到角色
	response, err := query.Execute()
	if err != nil || utils.HasResults(response) == false {
		return names
	}

	results := utils.A(response["results"])
	ids := []string{}
	pnames := []string{}
	for _, v := range results {
		roleObj := utils.M(v)
		if roleObj == nil {
			continue
		}
		ids = append(ids, utils.S(roleObj["objectId"]))
		pnames = append(pnames, utils.S(roleObj["name"]))
	}

	// 存储找到的角色名
	names = append(names, pnames...)

	// 继续查找最新角色的父角色
	return a.getAllRolesNamesForRoleIds(ids, names, queriedRoles)
}
<commit_msg>fix auth not pass info to Auth<commit_after>package rest

import (
	"time"

	"github.com/freeznet/tomato/cache"
	"github.com/freeznet/tomato/errs"
	"github.com/freeznet/tomato/types"
	"github.com/freeznet/tomato/utils"
)

// Auth 保存当前请求的用户权限信息
type Auth struct {
	IsMaster       bool
	IsReadOnly     bool
	InstallationID string
	User           types.M
	UserRoles      []string
	FetchedRoles   bool
	RolePromise    []string
	Info           *types.RequestInfo
}

// Master 生成 Master 级别用户
func Master(info ...*types.RequestInfo) *Auth {
	if info != nil {
		return &Auth{IsMaster: true, IsReadOnly: false, Info: info[0]}
	}
	return &Auth{IsMaster: true, IsReadOnly: false, Info: &types.RequestInfo{}}
}

// ReadOnly 生成 Master 级别的只读用户
func ReadOnly(info ...*types.RequestInfo) *Auth {
	if info != nil {
		return &Auth{IsMaster: true, IsReadOnly: true, Info: info[0]}
	}
	return &Auth{IsMaster: true, IsReadOnly: true, Info: &types.RequestInfo{}}
}

// Nobody 生成空用户
func Nobody(info ...*types.RequestInfo) *Auth {
	if info != nil {
		return &Auth{IsMaster: false, IsReadOnly: false, Info: info[0]}
	}
	return &Auth{IsMaster: false, IsReadOnly: false, Info: &types.RequestInfo{}}
}

// GetAuthForSessionToken 返回 sessionToken 对应的用户权限信息
func GetAuthForSessionToken(sessionToken string, installationID string, info *types.RequestInfo) (*Auth, error) {
	// 从缓存获取用户信息
	cachedUser := cache.User.Get(sessionToken)
	if u := utils.M(cachedUser); u != nil {
		return &Auth{
			IsMaster:       false,
			IsReadOnly:     false,
			InstallationID: installationID,
			User:           u,
			Info:           info,
		}, nil
	}
	// 缓存中不存在时，从数据库中查询
	restOptions := types.M{
		"limit":   1,
		"include": "user",
	}
	restWhere := types.M{
		"sessionToken": sessionToken,
	}

	sessionErr := errs.E(errs.InvalidSessionToken, "invalid session token")
	query, err := NewQuery(Master(info), "_Session", restWhere, restOptions, nil)
	if err != nil {
		return nil, sessionErr
	}
	response, err := query.Execute()
	if err != nil {
		return nil, sessionErr
	}

	if response == nil || response["results"] == nil {
		return nil, sessionErr
	}
	results := utils.A(response["results"])
	if results == nil || len(results) != 1 {
		return nil, sessionErr
	}
	result := utils.M(results[0])
	if result == nil || result["user"] == nil {
		return nil, sessionErr
	}

	now := time.Now().UTC()
	if result["expiresAt"] == nil {
		return nil, errs.E(errs.InvalidSessionToken, "Session token is expired.")
	}
	expiresAtDate := utils.M(result["expiresAt"])
	expiresAtString := utils.S(expiresAtDate["iso"])
	expiresAt, err := utils.StringtoTime(expiresAtString)
	if err != nil {
		return nil, errs.E(errs.InvalidSessionToken, "Session token is expired.")
	}
	if expiresAt.UnixNano() < now.UnixNano() {
		return nil, errs.E(errs.InvalidSessionToken, "Session token is expired.")
	}

	user := utils.M(result["user"])
	delete(user, "password")
	user["className"] = "_User"
	user["sessionToken"] = sessionToken
	// 写入缓存
	cache.User.Put(sessionToken, user, 0)

	return &Auth{
		IsMaster:       false,
		IsReadOnly:     false,
		InstallationID: installationID,
		User:           user,
		Info:           info,
	}, nil
}

// GetAuthForLegacySessionToken 处理保存在 _User 中的 sessionToken。
// 该方法处理从 parse 中迁移过来的用户数据，在 tomato 中其实不需要处理这种类型的数据，以后考虑删除
func GetAuthForLegacySessionToken(sessionToken, installationID string, info *types.RequestInfo) (*Auth, error) {
	restOptions := types.M{"limit": 1}
	query, err := NewQuery(Master(info), "_User", types.M{"sessionToken": sessionToken}, restOptions, nil)
	if err != nil {
		return nil, err
	}
	response, err := query.Execute()
	if err != nil {
		return nil, err
	}
	sessionErr := errs.E(errs.InvalidSessionToken, "invalid legacy session token")
	if response == nil || response["results"] == nil {
		return nil, sessionErr
	}
	results := utils.A(response["results"])
	if results == nil || len(results) != 1 {
		return nil, sessionErr
	}
	userObject := utils.M(results[0])
	if userObject == nil {
		return nil, sessionErr
	}
	userObject["className"] = "_User"
	return &Auth{
		IsMaster:       false,
		IsReadOnly:     false,
		InstallationID: installationID,
		User:           userObject,
		Info:           info,
	}, nil
}

// CouldUpdateUserID Master 与当前用户可进行修改
func (a *Auth) CouldUpdateUserID(objectID string) bool {
	if a.IsMaster {
		return true
	}
	if a.User != nil && utils.S(a.User["objectId"]) == objectID {
		return true
	}
	return false
}

// GetUserRoles 获取用户所属的角色列表
func (a *Auth) GetUserRoles() []string {
	if a.IsMaster || a.User == nil {
		return []string{}
	}
	if a.FetchedRoles {
		return a.UserRoles
	}
	if a.RolePromise != nil {
		return a.RolePromise
	}
	a.RolePromise = a.loadRoles()
	return a.RolePromise
}

// loadRoles 从数据库加载用户角色列表
func (a *Auth) loadRoles() []string {
	cachedRoles, ok := cache.Role.Get(utils.S(a.User["objectId"])).([]string)
	if ok && cachedRoles != nil {
		a.FetchedRoles = true
		a.UserRoles = cachedRoles
		return cachedRoles
	}

	users := types.M{
		"__type":    "Pointer",
		"className": "_User",
		"objectId":  a.User["objectId"],
	}
	restWhere := types.M{
		"users": users,
	}
	// 取出当前用户直接对应的所有角色
	// TODO 处理错误，处理结果大于100的情况
	query, err := NewQuery(Master(a.Info), "_Role", restWhere, types.M{}, nil)
	if err != nil {
		a.UserRoles = []string{}
		a.FetchedRoles = true
		a.RolePromise = nil
		cache.Role.Put(utils.S(a.User["objectId"]), a.UserRoles, 0)
		return a.UserRoles
	}

	response, err := query.Execute()
	if err != nil || utils.HasResults(response) == false {
		a.UserRoles = []string{}
		a.FetchedRoles = true
		a.RolePromise = nil
		cache.Role.Put(utils.S(a.User["objectId"]), a.UserRoles, 0)
		return a.UserRoles
	}

	results := utils.A(response["results"])
	ids := []string{}
	names := []string{}
	for _, v := range results {
		roleObj := utils.M(v)
		if roleObj == nil {
			continue
		}
		ids = append(ids, utils.S(roleObj["objectId"]))
		names = append(names, utils.S(roleObj["name"]))
	}

	queriedRoles := map[string]bool{} // 记录查询过的 role ，避免多次查询
	roleNames := a.getAllRolesNamesForRoleIds(ids, names, queriedRoles)

	a.UserRoles = []string{}
	for _, v := range roleNames {
		a.UserRoles = append(a.UserRoles, "role:"+v)
	}
	a.FetchedRoles = true
	a.RolePromise = nil

	cache.Role.Put(utils.S(a.User["objectId"]), a.UserRoles, 0)
	return a.UserRoles
}

// getAllRolesNamesForRoleIds 取出角色 id 对应的父角色
func (a *Auth) getAllRolesNamesForRoleIds(roleIDs, names []string, queriedRoles map[string]bool) []string {
	if names == nil {
		names = []string{}
	}
	if queriedRoles == nil {
		queriedRoles = map[string]bool{}
	}
	ins := types.S{}
	for _, roleID := range roleIDs {
		if _, ok := queriedRoles[roleID]; ok {
			continue
		}
		// 标记该 roleID 已经获取过一次父角色了
		queriedRoles[roleID] = true
		object := types.M{
			"__type":    "Pointer",
			"className": "_Role",
			"objectId":  roleID,
		}
		ins = append(ins, object)
	}

	// 已经没有待获取父角色的 roleID，返回 names
	if len(ins) == 0 {
		return names
	}

	restWhere := types.M{}
	if len(ins) == 1 {
		restWhere["roles"] = ins[0]
	} else {
		restWhere["roles"] = types.M{"$in": ins}
	}

	query, err := NewQuery(Master(a.Info), "_Role", restWhere, types.M{}, nil)
	if err != nil {
		return names
	}

	// 未找到角色
	response, err := query.Execute()
	if err != nil || utils.HasResults(response) == false {
		return names
	}

	results := utils.A(response["results"])
	ids := []string{}
	pnames := []string{}
	for _, v := range results {
		roleObj := utils.M(v)
		if roleObj == nil {
			continue
		}
		ids = append(ids, utils.S(roleObj["objectId"]))
		pnames = append(pnames, utils.S(roleObj["name"]))
	}

	// 存储找到的角色名
	names = append(names, pnames...)

	// 继续查找最新角色的父角色
	return a.getAllRolesNamesForRoleIds(ids, names, queriedRoles)
}
<|endoftext|>