<commit_before>package radix

// This is a fork of https://github.com/armon/go-radix that removes the
// ability to update nodes as well as uses fixed int value type.

import (
	"sort"
	"strings"
	"sync"
)

// WalkFn is used when walking the tree. Takes a
// key and value, returning if iteration should
// be terminated.
type WalkFn func(s string, v int) bool

// leafNode is used to represent a value
type leafNode struct {
	key string
	val int
}

// edge is used to represent an edge node
type edge struct {
	label byte
	node  *node
}

type node struct {
	// leaf is used to store possible leaf
	leaf *leafNode

	// prefix is the common prefix we ignore
	prefix string

	// Edges should be stored in-order for iteration.
	// We avoid a fully materialized slice to save memory,
	// since in most cases we expect to be sparse
	edges edges
}

func (n *node) isLeaf() bool {
	return n.leaf != nil
}

func (n *node) addEdge(e edge) {
	n.edges = append(n.edges, e)
	n.edges.Sort()
}

func (n *node) replaceEdge(e edge) {
	num := len(n.edges)
	idx := sort.Search(num, func(i int) bool {
		return n.edges[i].label >= e.label
	})
	if idx < num && n.edges[idx].label == e.label {
		n.edges[idx].node = e.node
		return
	}
	panic("replacing missing edge")
}

func (n *node) getEdge(label byte) *node {
	num := len(n.edges)
	idx := sort.Search(num, func(i int) bool {
		return n.edges[i].label >= label
	})
	if idx < num && n.edges[idx].label == label {
		return n.edges[idx].node
	}
	return nil
}

func (n *node) delEdge(label byte) {
	num := len(n.edges)
	idx := sort.Search(num, func(i int) bool {
		return n.edges[i].label >= label
	})
	if idx < num && n.edges[idx].label == label {
		copy(n.edges[idx:], n.edges[idx+1:])
		n.edges[len(n.edges)-1] = edge{}
		n.edges = n.edges[:len(n.edges)-1]
	}
}

type edges []edge

func (e edges) Len() int {
	return len(e)
}

func (e edges) Less(i, j int) bool {
	return e[i].label < e[j].label
}

func (e edges) Swap(i, j int) {
	e[i], e[j] = e[j], e[i]
}

func (e edges) Sort() {
	sort.Sort(e)
}

// Tree implements a radix tree. This can be treated as a
// Dictionary abstract data type. The main advantage over
// a standard hash map is prefix-based lookups and
// ordered iteration.  The tree is safe for concurrent access.
type Tree struct {
	mu sync.RWMutex

	root *node
	size int
}

// New returns an empty Tree
func New() *Tree {
	return NewFromMap(nil)
}

// NewFromMap returns a new tree containing the keys
// from an existing map
func NewFromMap(m map[string]int) *Tree {
	t := &Tree{root: &node{}}
	for k, v := range m {
		t.Insert(k, v)
	}
	return t
}

// Len is used to return the number of elements in the tree
func (t *Tree) Len() int {
	return t.size
}

// longestPrefix finds the length of the shared prefix
// of two strings
func longestPrefix(k1, k2 string) int {
	max := len(k1)
	if l := len(k2); l < max {
		max = l
	}
	var i int
	for i = 0; i < max; i++ {
		if k1[i] != k2[i] {
			break
		}
	}
	return i
}

// Insert is used to add a newentry or update
// an existing entry. Returns if inserted.
func (t *Tree) Insert(s string, v int) (int, bool) {
	t.mu.Lock()
	defer t.mu.Unlock()

	var parent *node
	n := t.root
	search := s
	for {
		// Handle key exhaution
		if len(search) == 0 {
			if n.isLeaf() {
				old := n.leaf.val
				return old, false
			}

			n.leaf = &leafNode{
				key: s,
				val: v,
			}
			t.size++
			return v, true
		}

		// Look for the edge
		parent = n
		n = n.getEdge(search[0])

		// No edge, create one
		if n == nil {
			e := edge{
				label: search[0],
				node: &node{
					leaf: &leafNode{
						key: s,
						val: v,
					},
					prefix: search,
				},
			}
			parent.addEdge(e)
			t.size++
			return v, true
		}

		// Determine longest prefix of the search key on match
		commonPrefix := longestPrefix(search, n.prefix)
		if commonPrefix == len(n.prefix) {
			search = search[commonPrefix:]
			continue
		}

		// Split the node
		t.size++
		child := &node{
			prefix: search[:commonPrefix],
		}
		parent.replaceEdge(edge{
			label: search[0],
			node:  child,
		})

		// Restore the existing node
		child.addEdge(edge{
			label: n.prefix[commonPrefix],
			node:  n,
		})
		n.prefix = n.prefix[commonPrefix:]

		// Create a new leaf node
		leaf := &leafNode{
			key: s,
			val: v,
		}

		// If the new key is a subset, add to to this node
		search = search[commonPrefix:]
		if len(search) == 0 {
			child.leaf = leaf
			return v, true
		}

		// Create a new edge for the node
		child.addEdge(edge{
			label: search[0],
			node: &node{
				leaf:   leaf,
				prefix: search,
			},
		})
		return v, true
	}
}

// Delete is used to delete a key, returning the previous
// value and if it was deleted
func (t *Tree) Delete(s string) (int, bool) {
	t.mu.Lock()
	defer t.mu.Unlock()

	var parent *node
	var label byte
	n := t.root
	search := s
	for {
		// Check for key exhaution
		if len(search) == 0 {
			if !n.isLeaf() {
				break
			}
			goto DELETE
		}

		// Look for an edge
		parent = n
		label = search[0]
		n = n.getEdge(label)
		if n == nil {
			break
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]
		} else {
			break
		}
	}
	return 0, false

DELETE:
	// Delete the leaf
	leaf := n.leaf
	n.leaf = nil
	t.size--

	// Check if we should delete this node from the parent
	if parent != nil && len(n.edges) == 0 {
		parent.delEdge(label)
	}

	// Check if we should merge this node
	if n != t.root && len(n.edges) == 1 {
		n.mergeChild()
	}

	// Check if we should merge the parent's other child
	if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() {
		parent.mergeChild()
	}

	return leaf.val, true
}

// DeletePrefix is used to delete the subtree under a prefix
// Returns how many nodes were deleted
// Use this to delete large subtrees efficiently
func (t *Tree) DeletePrefix(s string) int {
	t.mu.Lock()
	defer t.mu.Unlock()

	return t.deletePrefix(nil, t.root, s)
}

// delete does a recursive deletion
func (t *Tree) deletePrefix(parent, n *node, prefix string) int {
	// Check for key exhaustion
	if len(prefix) == 0 {
		// Remove the leaf node
		subTreeSize := 0
		//recursively walk from all edges of the node to be deleted
		recursiveWalk(n, func(s string, v int) bool {
			subTreeSize++
			return false
		})
		if n.isLeaf() {
			n.leaf = nil
		}
		n.edges = nil // deletes the entire subtree

		// Check if we should merge the parent's other child
		if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() {
			parent.mergeChild()
		}
		t.size -= subTreeSize
		return subTreeSize
	}

	// Look for an edge
	label := prefix[0]
	child := n.getEdge(label)
	if child == nil || (!strings.HasPrefix(child.prefix, prefix) && !strings.HasPrefix(prefix, child.prefix)) {
		return 0
	}

	// Consume the search prefix
	if len(child.prefix) > len(prefix) {
		prefix = prefix[len(prefix):]
	} else {
		prefix = prefix[len(child.prefix):]
	}
	return t.deletePrefix(n, child, prefix)
}

func (n *node) mergeChild() {
	e := n.edges[0]
	child := e.node
	n.prefix = n.prefix + child.prefix
	n.leaf = child.leaf
	n.edges = child.edges
}

// Get is used to lookup a specific key, returning
// the value and if it was found
func (t *Tree) Get(s string) (int, bool) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	search := s
	for {
		// Check for key exhaution
		if len(search) == 0 {
			if n.isLeaf() {
				return n.leaf.val, true
			}
			break
		}

		// Look for an edge
		n = n.getEdge(search[0])
		if n == nil {
			break
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]
		} else {
			break
		}
	}
	return 0, false
}

// LongestPrefix is like Get, but instead of an
// exact match, it will return the longest prefix match.
func (t *Tree) LongestPrefix(s string) (string, int, bool) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	var last *leafNode
	n := t.root
	search := s
	for {
		// Look for a leaf node
		if n.isLeaf() {
			last = n.leaf
		}

		// Check for key exhaution
		if len(search) == 0 {
			break
		}

		// Look for an edge
		n = n.getEdge(search[0])
		if n == nil {
			break
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]
		} else {
			break
		}
	}
	if last != nil {
		return last.key, last.val, true
	}
	return "", 0, false
}

// Minimum is used to return the minimum value in the tree
func (t *Tree) Minimum() (string, int, bool) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	for {
		if n.isLeaf() {
			return n.leaf.key, n.leaf.val, true
		}
		if len(n.edges) > 0 {
			n = n.edges[0].node
		} else {
			break
		}
	}
	return "", 0, false
}

// Maximum is used to return the maximum value in the tree
func (t *Tree) Maximum() (string, int, bool) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	for {
		if num := len(n.edges); num > 0 {
			n = n.edges[num-1].node
			continue
		}
		if n.isLeaf() {
			return n.leaf.key, n.leaf.val, true
		}
		break
	}
	return "", 0, false
}

// Walk is used to walk the tree
func (t *Tree) Walk(fn WalkFn) {
	recursiveWalk(t.root, fn)
}

// WalkPrefix is used to walk the tree under a prefix
func (t *Tree) WalkPrefix(prefix string, fn WalkFn) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	search := prefix
	for {
		// Check for key exhaution
		if len(search) == 0 {
			recursiveWalk(n, fn)
			return
		}

		// Look for an edge
		n = n.getEdge(search[0])
		if n == nil {
			break
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]

		} else if strings.HasPrefix(n.prefix, search) {
			// Child may be under our search prefix
			recursiveWalk(n, fn)
			return
		} else {
			break
		}
	}

}

// WalkPath is used to walk the tree, but only visiting nodes
// from the root down to a given leaf. Where WalkPrefix walks
// all the entries *under* the given prefix, this walks the
// entries *above* the given prefix.
func (t *Tree) WalkPath(path string, fn WalkFn) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	search := path
	for {
		// Visit the leaf values if any
		if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
			return
		}

		// Check for key exhaution
		if len(search) == 0 {
			return
		}

		// Look for an edge
		n = n.getEdge(search[0])
		if n == nil {
			return
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]
		} else {
			break
		}
	}
}

// recursiveWalk is used to do a pre-order walk of a node
// recursively. Returns true if the walk should be aborted
func recursiveWalk(n *node, fn WalkFn) bool {
	// Visit the leaf values if any
	if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
		return true
	}

	// Recurse on the children
	for _, e := range n.edges {
		if recursiveWalk(e.node, fn) {
			return true
		}
	}
	return false
}

// ToMap is used to walk the tree and convert it into a map
func (t *Tree) ToMap() map[string]int {
	out := make(map[string]int, t.size)
	t.Walk(func(k string, v int) bool {
		out[k] = v
		return false
	})
	return out
}
<commit_msg>Small performance optimizations<commit_after>package radix

// This is a fork of https://github.com/armon/go-radix that removes the
// ability to update nodes as well as uses fixed int value type.

import (
	"sort"
	"strings"
	"sync"
)

// WalkFn is used when walking the tree. Takes a
// key and value, returning if iteration should
// be terminated.
type WalkFn func(s string, v int) bool

// leafNode is used to represent a value
type leafNode struct {
	key string
	val int
}

// edge is used to represent an edge node
type edge struct {
	label byte
	node  *node
}

type node struct {
	// leaf is used to store possible leaf
	leaf *leafNode

	// prefix is the common prefix we ignore
	prefix string

	// Edges should be stored in-order for iteration.
	// We avoid a fully materialized slice to save memory,
	// since in most cases we expect to be sparse
	edges edges
}

func (n *node) isLeaf() bool {
	return n.leaf != nil
}

func (n *node) addEdge(e edge) {
	n.edges = append(n.edges, e)
	n.edges.Sort()
}

func (n *node) replaceEdge(e edge) {
	num := len(n.edges)
	idx := sort.Search(num, func(i int) bool {
		return n.edges[i].label >= e.label
	})
	if idx < num && n.edges[idx].label == e.label {
		n.edges[idx].node = e.node
		return
	}
	panic("replacing missing edge")
}

func (n *node) getEdge(label byte) *node {
	num := len(n.edges)
	// Use linear search for smaller arrays
	if num < 64 {
		for i := 0; i < num; i++ {
			if n.edges[i].label == label {
				return n.edges[i].node
			}
		}

		return nil
	}

	idx := sort.Search(num, func(i int) bool {
		return n.edges[i].label >= label
	})
	if idx < num && n.edges[idx].label == label {
		return n.edges[idx].node
	}
	return nil
}

func (n *node) delEdge(label byte) {
	num := len(n.edges)
	idx := sort.Search(num, func(i int) bool {
		return n.edges[i].label >= label
	})
	if idx < num && n.edges[idx].label == label {
		copy(n.edges[idx:], n.edges[idx+1:])
		n.edges[len(n.edges)-1] = edge{}
		n.edges = n.edges[:len(n.edges)-1]
	}
}

type edges []edge

func (e edges) Len() int {
	return len(e)
}

func (e edges) Less(i, j int) bool {
	return e[i].label < e[j].label
}

func (e edges) Swap(i, j int) {
	e[i], e[j] = e[j], e[i]
}

func (e edges) Sort() {
	sort.Sort(e)
}

// Tree implements a radix tree. This can be treated as a
// Dictionary abstract data type. The main advantage over
// a standard hash map is prefix-based lookups and
// ordered iteration.  The tree is safe for concurrent access.
type Tree struct {
	mu sync.RWMutex

	root *node
	size int
}

// New returns an empty Tree
func New() *Tree {
	return NewFromMap(nil)
}

// NewFromMap returns a new tree containing the keys
// from an existing map
func NewFromMap(m map[string]int) *Tree {
	t := &Tree{root: &node{}}
	for k, v := range m {
		t.Insert(k, v)
	}
	return t
}

// Len is used to return the number of elements in the tree
func (t *Tree) Len() int {
	return t.size
}

// longestPrefix finds the length of the shared prefix
// of two strings
func longestPrefix(k1, k2 string) int {
	max := len(k1)
	if l := len(k2); l < max {
		max = l
	}
	var i int
	for i = 0; i < max; i++ {
		if k1[i] != k2[i] {
			break
		}
	}
	return i
}

// Insert is used to add a newentry or update
// an existing entry. Returns if inserted.
func (t *Tree) Insert(s string, v int) (int, bool) {
	t.mu.Lock()
	defer t.mu.Unlock()

	var parent *node
	n := t.root
	search := s
	for {
		// Handle key exhaution
		if len(search) == 0 {
			if n.isLeaf() {
				old := n.leaf.val
				return old, false
			}

			n.leaf = &leafNode{
				key: s,
				val: v,
			}
			t.size++
			return v, true
		}

		// Look for the edge
		parent = n
		n = n.getEdge(search[0])

		// No edge, create one
		if n == nil {
			e := edge{
				label: search[0],
				node: &node{
					leaf: &leafNode{
						key: s,
						val: v,
					},
					prefix: search,
				},
			}
			parent.addEdge(e)
			t.size++
			return v, true
		}

		// Determine longest prefix of the search key on match
		commonPrefix := longestPrefix(search, n.prefix)
		if commonPrefix == len(n.prefix) {
			search = search[commonPrefix:]
			continue
		}

		// Split the node
		t.size++
		child := &node{
			prefix: search[:commonPrefix],
		}
		parent.replaceEdge(edge{
			label: search[0],
			node:  child,
		})

		// Restore the existing node
		child.addEdge(edge{
			label: n.prefix[commonPrefix],
			node:  n,
		})
		n.prefix = n.prefix[commonPrefix:]

		// Create a new leaf node
		leaf := &leafNode{
			key: s,
			val: v,
		}

		// If the new key is a subset, add to to this node
		search = search[commonPrefix:]
		if len(search) == 0 {
			child.leaf = leaf
			return v, true
		}

		// Create a new edge for the node
		child.addEdge(edge{
			label: search[0],
			node: &node{
				leaf:   leaf,
				prefix: search,
			},
		})
		return v, true
	}
}

// Delete is used to delete a key, returning the previous
// value and if it was deleted
func (t *Tree) Delete(s string) (int, bool) {
	t.mu.Lock()
	defer t.mu.Unlock()

	var parent *node
	var label byte
	n := t.root
	search := s
	for {
		// Check for key exhaution
		if len(search) == 0 {
			if !n.isLeaf() {
				break
			}
			goto DELETE
		}

		// Look for an edge
		parent = n
		label = search[0]
		n = n.getEdge(label)
		if n == nil {
			break
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]
		} else {
			break
		}
	}
	return 0, false

DELETE:
	// Delete the leaf
	leaf := n.leaf
	n.leaf = nil
	t.size--

	// Check if we should delete this node from the parent
	if parent != nil && len(n.edges) == 0 {
		parent.delEdge(label)
	}

	// Check if we should merge this node
	if n != t.root && len(n.edges) == 1 {
		n.mergeChild()
	}

	// Check if we should merge the parent's other child
	if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() {
		parent.mergeChild()
	}

	return leaf.val, true
}

// DeletePrefix is used to delete the subtree under a prefix
// Returns how many nodes were deleted
// Use this to delete large subtrees efficiently
func (t *Tree) DeletePrefix(s string) int {
	t.mu.Lock()
	defer t.mu.Unlock()

	return t.deletePrefix(nil, t.root, s)
}

// delete does a recursive deletion
func (t *Tree) deletePrefix(parent, n *node, prefix string) int {
	// Check for key exhaustion
	if len(prefix) == 0 {
		// Remove the leaf node
		subTreeSize := 0
		//recursively walk from all edges of the node to be deleted
		recursiveWalk(n, func(s string, v int) bool {
			subTreeSize++
			return false
		})
		if n.isLeaf() {
			n.leaf = nil
		}
		n.edges = nil // deletes the entire subtree

		// Check if we should merge the parent's other child
		if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() {
			parent.mergeChild()
		}
		t.size -= subTreeSize
		return subTreeSize
	}

	// Look for an edge
	label := prefix[0]
	child := n.getEdge(label)
	if child == nil || (!strings.HasPrefix(child.prefix, prefix) && !strings.HasPrefix(prefix, child.prefix)) {
		return 0
	}

	// Consume the search prefix
	if len(child.prefix) > len(prefix) {
		prefix = prefix[len(prefix):]
	} else {
		prefix = prefix[len(child.prefix):]
	}
	return t.deletePrefix(n, child, prefix)
}

func (n *node) mergeChild() {
	e := n.edges[0]
	child := e.node
	n.prefix = n.prefix + child.prefix
	n.leaf = child.leaf
	n.edges = child.edges
}

// Get is used to lookup a specific key, returning
// the value and if it was found
func (t *Tree) Get(s string) (int, bool) {
	t.mu.RLock()

	n := t.root
	search := s
	for {
		// Check for key exhaution
		if len(search) == 0 {
			if n.isLeaf() {
				t.mu.RUnlock()
				return n.leaf.val, true
			}
			break
		}

		// Look for an edge
		n = n.getEdge(search[0])
		if n == nil {
			break
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]
		} else {
			break
		}
	}
	t.mu.RUnlock()
	return 0, false
}

// LongestPrefix is like Get, but instead of an
// exact match, it will return the longest prefix match.
func (t *Tree) LongestPrefix(s string) (string, int, bool) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	var last *leafNode
	n := t.root
	search := s
	for {
		// Look for a leaf node
		if n.isLeaf() {
			last = n.leaf
		}

		// Check for key exhaution
		if len(search) == 0 {
			break
		}

		// Look for an edge
		n = n.getEdge(search[0])
		if n == nil {
			break
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]
		} else {
			break
		}
	}
	if last != nil {
		return last.key, last.val, true
	}
	return "", 0, false
}

// Minimum is used to return the minimum value in the tree
func (t *Tree) Minimum() (string, int, bool) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	for {
		if n.isLeaf() {
			return n.leaf.key, n.leaf.val, true
		}
		if len(n.edges) > 0 {
			n = n.edges[0].node
		} else {
			break
		}
	}
	return "", 0, false
}

// Maximum is used to return the maximum value in the tree
func (t *Tree) Maximum() (string, int, bool) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	for {
		if num := len(n.edges); num > 0 {
			n = n.edges[num-1].node
			continue
		}
		if n.isLeaf() {
			return n.leaf.key, n.leaf.val, true
		}
		break
	}
	return "", 0, false
}

// Walk is used to walk the tree
func (t *Tree) Walk(fn WalkFn) {
	recursiveWalk(t.root, fn)
}

// WalkPrefix is used to walk the tree under a prefix
func (t *Tree) WalkPrefix(prefix string, fn WalkFn) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	search := prefix
	for {
		// Check for key exhaution
		if len(search) == 0 {
			recursiveWalk(n, fn)
			return
		}

		// Look for an edge
		n = n.getEdge(search[0])
		if n == nil {
			break
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]

		} else if strings.HasPrefix(n.prefix, search) {
			// Child may be under our search prefix
			recursiveWalk(n, fn)
			return
		} else {
			break
		}
	}

}

// WalkPath is used to walk the tree, but only visiting nodes
// from the root down to a given leaf. Where WalkPrefix walks
// all the entries *under* the given prefix, this walks the
// entries *above* the given prefix.
func (t *Tree) WalkPath(path string, fn WalkFn) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	n := t.root
	search := path
	for {
		// Visit the leaf values if any
		if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
			return
		}

		// Check for key exhaution
		if len(search) == 0 {
			return
		}

		// Look for an edge
		n = n.getEdge(search[0])
		if n == nil {
			return
		}

		// Consume the search prefix
		if strings.HasPrefix(search, n.prefix) {
			search = search[len(n.prefix):]
		} else {
			break
		}
	}
}

// recursiveWalk is used to do a pre-order walk of a node
// recursively. Returns true if the walk should be aborted
func recursiveWalk(n *node, fn WalkFn) bool {
	// Visit the leaf values if any
	if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
		return true
	}

	// Recurse on the children
	for _, e := range n.edges {
		if recursiveWalk(e.node, fn) {
			return true
		}
	}
	return false
}

// ToMap is used to walk the tree and convert it into a map
func (t *Tree) ToMap() map[string]int {
	out := make(map[string]int, t.size)
	t.Walk(func(k string, v int) bool {
		out[k] = v
		return false
	})
	return out
}
<|endoftext|>


<commit_before>// Copyright 2013 SteelSeries ApS. All rights reserved.
// No license is given for the use of this source code.

// This package impliments a basic LISP interpretor for embedding in a go program for scripting.
// This file provides a repl

package golisp

import (
    "fmt"
)

func Repl() {
    fmt.Printf("Welcome to GoLisp\n")
    fmt.Printf("Copyright 2013 SteelSeries\n")
    fmt.Printf("Evaluate '(quit)' to exit.\n\n")
    prompt := "> "
    LoadHistoryFromFile(".golisp_history")
    for true {
        for true {
            input := *ReadLine(&prompt)
            if input != "" {
                AddHistory(input)
                code, err := Parse(input)
                if err != nil {
                    fmt.Printf("Error: %s\n", err)
                } else {
                    d, err := Eval(code, Global)
                    if err != nil {
                        fmt.Printf("Error in evaluation: %s\n", err)
                    } else {
                        fmt.Printf("==> %s\n", String(d))
                    }
                }
            }
        }
    }
}
<commit_msg>Don't add repeated commands to history<commit_after>// Copyright 2013 SteelSeries ApS. All rights reserved.
// No license is given for the use of this source code.

// This package impliments a basic LISP interpretor for embedding in a go program for scripting.
// This file provides a repl

package golisp

import (
    "fmt"
)

func Repl() {
    fmt.Printf("Welcome to GoLisp\n")
    fmt.Printf("Copyright 2013 SteelSeries\n")
    fmt.Printf("Evaluate '(quit)' to exit.\n\n")
    prompt := "> "
    LoadHistoryFromFile(".golisp_history")
    lastInput := ""
    for true {
        input := *ReadLine(&prompt)
        if input != "" {
            if input != lastInput {
                AddHistory(input)
            }
            lastInput = input
            code, err := Parse(input)
            if err != nil {
                fmt.Printf("Error: %s\n", err)
            } else {
                d, err := Eval(code, Global)
                if err != nil {
                    fmt.Printf("Error in evaluation: %s\n", err)
                } else {
                    fmt.Printf("==> %s\n", String(d))
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package api

import (
	"github.com/fsouza/go-dockerclient"
)

// Common string formats
// ---------------------
// Many fields in this API have formatting requirements.  The commonly used
// formats are defined here.
//
// C_IDENTIFIER:  This is a string that conforms the definition of an "identifier"
//     in the C language.  This is captured by the following regex:
//         [A-Za-z_][A-Za-z0-9_]*
//     This defines the format, but not the length restriction, which should be
//     specified at the definition of any field of this type.
//
// DNS_LABEL:  This is a string that conforms to the definition of a "label"
//     in RFCs 1035 and 1123.  This is captured by the following regex:
//         [a-z0-9]([-a-z0-9]*[a-z0-9])?
//     This defines the format, but not the length restriction, which should be
//     specified at the definition of any field of this type.
//
//  DNS_SUBDOMAIN:  This is a string that conforms to the definition of a
//      "subdomain" in RFCs 1035 and 1123.  This is captured by the following regex:
//         [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//     or more simply:
//         DNS_LABEL(\.DNS_LABEL)*
//     This defines the format, but not the length restriction, which should be
//     specified at the definition of any field of this type.

// ContainerManifest corresponds to the Container Manifest format, documented at:
// https://developers.google.com/compute/docs/containers/container_vms#container_manifest
// This is used as the representation of Kubernetes workloads.
type ContainerManifest struct {
	// Required: This must be a supported version string, such as "v1beta1".
	Version string `yaml:"version" json:"version"`
	// Required: This must be a DNS_SUBDOMAIN, 255 characters or less.
	ID         string      `yaml:"id" json:"id"`
	Volumes    []Volume    `yaml:"volumes" json:"volumes"`
	Containers []Container `yaml:"containers" json:"containers"`
}

// Volume represents a named volume in a pod that may be accessed by any containers in the pod.
type Volume struct {
	// Required: This must be a DNS_LABEL, 63 characters or less.  Each volume in a pod
	// must have a unique name.
	Name string `yaml:"name" json:"name"`
}

// Port represents a network port in a single container
type Port struct {
	// Optional: If specified, this must be a DNS_LABEL, 63 characters or less.  Each
	// container in a pod must have a unique name.
	Name string `yaml:"name,omitempty" json:"name,omitempty"`
	// Optional: Defaults to ContainerPort.
	HostPort int `yaml:"hostPort,omitempty" json:"hostPort,omitempty"`
	// Required: This must be a valid port number, 0 < x < 65536.
	ContainerPort int `yaml:"containerPort" json:"containerPort"`
	// Optional: Defaults to "TCP".
	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
}

// VolumeMount describes a mounting of a Volume within a container
type VolumeMount struct {
	// Required: This must match the Name of a Volume [above].
	Name string `yaml:"name" json:"name"`
	// Optional: Defaults to false (read-write).
	ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
	// Required.
	MountPath string `yaml:"mountPath,omitempty" json:"mountPath,omitempty"`
	// One of: "LOCAL" (local volume) or "HOST" (external mount from the host). Default: LOCAL.
	MountType string `yaml:"mountType,omitempty" json:"mountType,omitempty"`
}

// EnvVar represents an environment variable present in a Container
type EnvVar struct {
	// Required: This must be a C_IDENTIFIER.
	// Exactly one of the following must be set.  If both are set, prefer Name.
	// DEPRECATED: EnvVar.Key will be removed in a future version of the API.
	Name string `yaml:"name" json:"name"`
	Key  string `yaml:"key,omitempty" json:"name,omitempty"`
	// Optional: defaults to "".
	Value string `yaml:"value,omitempty" json:"value,omitempty"`
}

// Container represents a single container that is expected to be run on the host.
type Container struct {
	// Required: This must be a DNS_LABEL, 63 characters or less.  Each container in a
	// pod must have a unique name.
	Name string `yaml:"name" json:"name"`
	// Required.
	Image string `yaml:"image" json:"image"`
	// Optional: Defaults to whatever is defined in the image.
	Command []string `yaml:"command,omitempty" json:"command,omitempty"`
	// Optional: Defaults to Docker's default.
	WorkingDir string   `yaml:"workingDir,omitempty" json:"workingDir,omitempty"`
	Ports      []Port   `yaml:"ports,omitempty" json:"ports,omitempty"`
	Env        []EnvVar `yaml:"env,omitempty" json:"env,omitempty"`
	// Optional: Defaults to unlimited.
	Memory int `yaml:"memory,omitempty" json:"memory,omitempty"`
	// Optional: Defaults to unlimited.
	CPU          int           `yaml:"cpu,omitempty" json:"cpu,omitempty"`
	VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"`
}

// Percentile represents a pair which contains a percentage from 0 to 100 and
// its corresponding value.
type Percentile struct {
	Percentage int    `json:"percentage,omitempty"`
	Value      uint64 `json:"value,omitempty"`
}

// ContainerStats represents statistical information of a container
type ContainerStats struct {
	CpuUsagePercentiles    []Percentile `json:"cpu_usage_percentiles,omitempty"`
	MemoryUsagePercentiles []Percentile `json:"memory_usage_percentiles,omitempty"`
	MaxMemoryUsage         uint64       `json:"max_memory_usage,omitempty"`
}

// Event is the representation of an event logged to etcd backends
type Event struct {
	Event     string             `json:"event,omitempty"`
	Manifest  *ContainerManifest `json:"manifest,omitempty"`
	Container *Container         `json:"container,omitempty"`
	Timestamp int64              `json:"timestamp"`
}

// The below types are used by kube_client and api_server.

// JSONBase is shared by all objects sent to, or returned from the client
type JSONBase struct {
	Kind              string `json:"kind,omitempty" yaml:"kind,omitempty"`
	ID                string `json:"id,omitempty" yaml:"id,omitempty"`
	CreationTimestamp string `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"`
	SelfLink          string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"`
}

type PodStatus string

const (
	PodRunning PodStatus = "Running"
	PodPending PodStatus = "Pending"
	PodStopped PodStatus = "Stopped"
)

// PodInfo contains one entry for every container with available info.
type PodInfo map[string]docker.Container

// PodState is the state of a pod, used as either input (desired state) or output (current state)
type PodState struct {
	Manifest ContainerManifest `json:"manifest,omitempty" yaml:"manifest,omitempty"`
	Status   PodStatus         `json:"status,omitempty" yaml:"status,omitempty"`
	Host     string            `json:"host,omitempty" yaml:"host,omitempty"`
	HostIP   string            `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`

	// The key of this map is the *name* of the container within the manifest; it has one
	// entry per container in the manifest. The value of this map is currently the output
	// of `docker inspect`. This output format is *not* final and should not be relied
	// upon.
	// TODO: Make real decisions about what our info should look like.
	Info PodInfo `json:"info,omitempty" yaml:"info,omitempty"`
}

type PodList struct {
	JSONBase `json:",inline" yaml:",inline"`
	Items    []Pod `json:"items" yaml:"items,omitempty"`
}

// Pod is a collection of containers, used as either input (create, update) or as output (list, get)
type Pod struct {
	JSONBase     `json:",inline" yaml:",inline"`
	Labels       map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
	DesiredState PodState          `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
	CurrentState PodState          `json:"currentState,omitempty" yaml:"currentState,omitempty"`
}

// ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get)
type ReplicationControllerState struct {
	Replicas        int               `json:"replicas" yaml:"replicas"`
	ReplicaSelector map[string]string `json:"replicaSelector,omitempty" yaml:"replicaSelector,omitempty"`
	PodTemplate     PodTemplate       `json:"podTemplate,omitempty" yaml:"podTemplate,omitempty"`
}

type ReplicationControllerList struct {
	JSONBase `json:",inline" yaml:",inline"`
	Items    []ReplicationController `json:"items,omitempty" yaml:"items,omitempty"`
}

// ReplicationController represents the configuration of a replication controller
type ReplicationController struct {
	JSONBase     `json:",inline" yaml:",inline"`
	DesiredState ReplicationControllerState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
	Labels       map[string]string          `json:"labels,omitempty" yaml:"labels,omitempty"`
}

// PodTemplate holds the information used for creating pods
type PodTemplate struct {
	DesiredState PodState          `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
	Labels       map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
}

// ServiceList holds a list of services
type ServiceList struct {
	JSONBase `json:",inline" yaml:",inline"`
	Items    []Service `json:"items" yaml:"items"`
}

// Defines a service abstraction by a name (for example, mysql) consisting of local port
// (for example 3306) that the proxy listens on, and the selector that determines which pods
// will answer requests sent through the proxy.
type Service struct {
	JSONBase `json:",inline" yaml:",inline"`
	Port     int `json:"port,omitempty" yaml:"port,omitempty"`

	// This service's labels.
	Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`

	// This service will route traffic to pods having labels matching this selector.
	Selector                   map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"`
	CreateExternalLoadBalancer bool              `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"`
}

// Defines the endpoints that implement the actual service, for example:
// Name: "mysql", Endpoints: ["10.10.1.1:1909", "10.10.2.2:8834"]
type Endpoints struct {
	Name      string
	Endpoints []string
}

// Information about a single Minion; the name of the minion according to etcd
// is in JSONBase.ID.
type Minion struct {
	JSONBase `json:",inline" yaml:",inline"`
	// Queried from cloud provider, if available.
	HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
}

// A list of minions.
type MinionList struct {
	JSONBase `json:",inline" yaml:",inline"`
	Items    []Minion `json:"minions,omitempty" yaml:"minions,omitempty"`
}

// Status is a return value for calls that don't return other objects.
// Arguably, this could go in apiserver, but I'm including it here so clients needn't
// import both.
type Status struct {
	JSONBase `json:",inline" yaml:",inline"`
	// One of: "success", "failure", "working" (for operations not yet completed)
	// TODO: if "working", include an operation identifier so final status can be
	// checked.
	Status  string `json:"status,omitempty" yaml:"status,omitempty"`
	Details string `json:"details,omitempty" yaml:"details,omitempty"`
}

// Values of Status.Status
const (
	StatusSuccess = "success"
	StatusFailure = "failure"
	StatusWorking = "working"
)

// Operation information, as delivered to API clients.
type ServerOp struct {
	JSONBase `yaml:",inline" json:",inline"`
}

// Operation list, as delivered to API clients.
type ServerOpList struct {
	JSONBase `yaml:",inline" json:",inline"`
	Items    []ServerOp `yaml:"items,omitempty" json:"items,omitempty"`
}
<commit_msg>Fix wrong json name for Key field<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package api

import (
	"github.com/fsouza/go-dockerclient"
)

// Common string formats
// ---------------------
// Many fields in this API have formatting requirements.  The commonly used
// formats are defined here.
//
// C_IDENTIFIER:  This is a string that conforms the definition of an "identifier"
//     in the C language.  This is captured by the following regex:
//         [A-Za-z_][A-Za-z0-9_]*
//     This defines the format, but not the length restriction, which should be
//     specified at the definition of any field of this type.
//
// DNS_LABEL:  This is a string that conforms to the definition of a "label"
//     in RFCs 1035 and 1123.  This is captured by the following regex:
//         [a-z0-9]([-a-z0-9]*[a-z0-9])?
//     This defines the format, but not the length restriction, which should be
//     specified at the definition of any field of this type.
//
//  DNS_SUBDOMAIN:  This is a string that conforms to the definition of a
//      "subdomain" in RFCs 1035 and 1123.  This is captured by the following regex:
//         [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//     or more simply:
//         DNS_LABEL(\.DNS_LABEL)*
//     This defines the format, but not the length restriction, which should be
//     specified at the definition of any field of this type.

// ContainerManifest corresponds to the Container Manifest format, documented at:
// https://developers.google.com/compute/docs/containers/container_vms#container_manifest
// This is used as the representation of Kubernetes workloads.
type ContainerManifest struct {
	// Required: This must be a supported version string, such as "v1beta1".
	Version string `yaml:"version" json:"version"`
	// Required: This must be a DNS_SUBDOMAIN, 255 characters or less.
	ID         string      `yaml:"id" json:"id"`
	Volumes    []Volume    `yaml:"volumes" json:"volumes"`
	Containers []Container `yaml:"containers" json:"containers"`
}

// Volume represents a named volume in a pod that may be accessed by any containers in the pod.
type Volume struct {
	// Required: This must be a DNS_LABEL, 63 characters or less.  Each volume in a pod
	// must have a unique name.
	Name string `yaml:"name" json:"name"`
}

// Port represents a network port in a single container
type Port struct {
	// Optional: If specified, this must be a DNS_LABEL, 63 characters or less.  Each
	// container in a pod must have a unique name.
	Name string `yaml:"name,omitempty" json:"name,omitempty"`
	// Optional: Defaults to ContainerPort.
	HostPort int `yaml:"hostPort,omitempty" json:"hostPort,omitempty"`
	// Required: This must be a valid port number, 0 < x < 65536.
	ContainerPort int `yaml:"containerPort" json:"containerPort"`
	// Optional: Defaults to "TCP".
	Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
}

// VolumeMount describes a mounting of a Volume within a container
type VolumeMount struct {
	// Required: This must match the Name of a Volume [above].
	Name string `yaml:"name" json:"name"`
	// Optional: Defaults to false (read-write).
	ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
	// Required.
	MountPath string `yaml:"mountPath,omitempty" json:"mountPath,omitempty"`
	// One of: "LOCAL" (local volume) or "HOST" (external mount from the host). Default: LOCAL.
	MountType string `yaml:"mountType,omitempty" json:"mountType,omitempty"`
}

// EnvVar represents an environment variable present in a Container
type EnvVar struct {
	// Required: This must be a C_IDENTIFIER.
	// Exactly one of the following must be set.  If both are set, prefer Name.
	// DEPRECATED: EnvVar.Key will be removed in a future version of the API.
	Name string `yaml:"name" json:"name"`
	Key  string `yaml:"key,omitempty" json:"key,omitempty"`
	// Optional: defaults to "".
	Value string `yaml:"value,omitempty" json:"value,omitempty"`
}

// Container represents a single container that is expected to be run on the host.
type Container struct {
	// Required: This must be a DNS_LABEL, 63 characters or less.  Each container in a
	// pod must have a unique name.
	Name string `yaml:"name" json:"name"`
	// Required.
	Image string `yaml:"image" json:"image"`
	// Optional: Defaults to whatever is defined in the image.
	Command []string `yaml:"command,omitempty" json:"command,omitempty"`
	// Optional: Defaults to Docker's default.
	WorkingDir string   `yaml:"workingDir,omitempty" json:"workingDir,omitempty"`
	Ports      []Port   `yaml:"ports,omitempty" json:"ports,omitempty"`
	Env        []EnvVar `yaml:"env,omitempty" json:"env,omitempty"`
	// Optional: Defaults to unlimited.
	Memory int `yaml:"memory,omitempty" json:"memory,omitempty"`
	// Optional: Defaults to unlimited.
	CPU          int           `yaml:"cpu,omitempty" json:"cpu,omitempty"`
	VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"`
}

// Percentile represents a pair which contains a percentage from 0 to 100 and
// its corresponding value.
type Percentile struct {
	Percentage int    `json:"percentage,omitempty"`
	Value      uint64 `json:"value,omitempty"`
}

// ContainerStats represents statistical information of a container
type ContainerStats struct {
	CpuUsagePercentiles    []Percentile `json:"cpu_usage_percentiles,omitempty"`
	MemoryUsagePercentiles []Percentile `json:"memory_usage_percentiles,omitempty"`
	MaxMemoryUsage         uint64       `json:"max_memory_usage,omitempty"`
}

// Event is the representation of an event logged to etcd backends
type Event struct {
	Event     string             `json:"event,omitempty"`
	Manifest  *ContainerManifest `json:"manifest,omitempty"`
	Container *Container         `json:"container,omitempty"`
	Timestamp int64              `json:"timestamp"`
}

// The below types are used by kube_client and api_server.

// JSONBase is shared by all objects sent to, or returned from the client
type JSONBase struct {
	Kind              string `json:"kind,omitempty" yaml:"kind,omitempty"`
	ID                string `json:"id,omitempty" yaml:"id,omitempty"`
	CreationTimestamp string `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"`
	SelfLink          string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"`
}

type PodStatus string

const (
	PodRunning PodStatus = "Running"
	PodPending PodStatus = "Pending"
	PodStopped PodStatus = "Stopped"
)

// PodInfo contains one entry for every container with available info.
type PodInfo map[string]docker.Container

// PodState is the state of a pod, used as either input (desired state) or output (current state)
type PodState struct {
	Manifest ContainerManifest `json:"manifest,omitempty" yaml:"manifest,omitempty"`
	Status   PodStatus         `json:"status,omitempty" yaml:"status,omitempty"`
	Host     string            `json:"host,omitempty" yaml:"host,omitempty"`
	HostIP   string            `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`

	// The key of this map is the *name* of the container within the manifest; it has one
	// entry per container in the manifest. The value of this map is currently the output
	// of `docker inspect`. This output format is *not* final and should not be relied
	// upon.
	// TODO: Make real decisions about what our info should look like.
	Info PodInfo `json:"info,omitempty" yaml:"info,omitempty"`
}

type PodList struct {
	JSONBase `json:",inline" yaml:",inline"`
	Items    []Pod `json:"items" yaml:"items,omitempty"`
}

// Pod is a collection of containers, used as either input (create, update) or as output (list, get)
type Pod struct {
	JSONBase     `json:",inline" yaml:",inline"`
	Labels       map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
	DesiredState PodState          `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
	CurrentState PodState          `json:"currentState,omitempty" yaml:"currentState,omitempty"`
}

// ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get)
type ReplicationControllerState struct {
	Replicas        int               `json:"replicas" yaml:"replicas"`
	ReplicaSelector map[string]string `json:"replicaSelector,omitempty" yaml:"replicaSelector,omitempty"`
	PodTemplate     PodTemplate       `json:"podTemplate,omitempty" yaml:"podTemplate,omitempty"`
}

type ReplicationControllerList struct {
	JSONBase `json:",inline" yaml:",inline"`
	Items    []ReplicationController `json:"items,omitempty" yaml:"items,omitempty"`
}

// ReplicationController represents the configuration of a replication controller
type ReplicationController struct {
	JSONBase     `json:",inline" yaml:",inline"`
	DesiredState ReplicationControllerState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
	Labels       map[string]string          `json:"labels,omitempty" yaml:"labels,omitempty"`
}

// PodTemplate holds the information used for creating pods
type PodTemplate struct {
	DesiredState PodState          `json:"desiredState,omitempty" yaml:"desiredState,omitempty"`
	Labels       map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
}

// ServiceList holds a list of services
type ServiceList struct {
	JSONBase `json:",inline" yaml:",inline"`
	Items    []Service `json:"items" yaml:"items"`
}

// Defines a service abstraction by a name (for example, mysql) consisting of local port
// (for example 3306) that the proxy listens on, and the selector that determines which pods
// will answer requests sent through the proxy.
type Service struct {
	JSONBase `json:",inline" yaml:",inline"`
	Port     int `json:"port,omitempty" yaml:"port,omitempty"`

	// This service's labels.
	Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`

	// This service will route traffic to pods having labels matching this selector.
	Selector                   map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"`
	CreateExternalLoadBalancer bool              `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"`
}

// Defines the endpoints that implement the actual service, for example:
// Name: "mysql", Endpoints: ["10.10.1.1:1909", "10.10.2.2:8834"]
type Endpoints struct {
	Name      string
	Endpoints []string
}

// Information about a single Minion; the name of the minion according to etcd
// is in JSONBase.ID.
type Minion struct {
	JSONBase `json:",inline" yaml:",inline"`
	// Queried from cloud provider, if available.
	HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"`
}

// A list of minions.
type MinionList struct {
	JSONBase `json:",inline" yaml:",inline"`
	Items    []Minion `json:"minions,omitempty" yaml:"minions,omitempty"`
}

// Status is a return value for calls that don't return other objects.
// Arguably, this could go in apiserver, but I'm including it here so clients needn't
// import both.
type Status struct {
	JSONBase `json:",inline" yaml:",inline"`
	// One of: "success", "failure", "working" (for operations not yet completed)
	// TODO: if "working", include an operation identifier so final status can be
	// checked.
	Status  string `json:"status,omitempty" yaml:"status,omitempty"`
	Details string `json:"details,omitempty" yaml:"details,omitempty"`
}

// Values of Status.Status
const (
	StatusSuccess = "success"
	StatusFailure = "failure"
	StatusWorking = "working"
)

// Operation information, as delivered to API clients.
type ServerOp struct {
	JSONBase `yaml:",inline" json:",inline"`
}

// Operation list, as delivered to API clients.
type ServerOpList struct {
	JSONBase `yaml:",inline" json:",inline"`
	Items    []ServerOp `yaml:"items,omitempty" json:"items,omitempty"`
}
<|endoftext|>


<commit_before>// The Ruby package is used to provision ruby on a host.
//
// Ruby will be downloaded, extracted, configured, built, and installed to `/opt/ruby-{{ .Version }}`. If the `Bundle`
// flag is set, bundler will be installed.
package ruby

import (
	"fmt"
	"github.com/dynport/urknall"
	"github.com/dynport/urknall/cmd"
	"strings"
)

func New(version string) *Package {
	return &Package{
		Version: version,
	}
}

type Package struct {
	Version     string `urknall:"default=2.0.0-p247"`
	WithBundler bool
}

func (ruby *Package) Package(r *urknall.Runlist) {
	r.Add(
		cmd.InstallPackages("curl", "build-essential",
			"libyaml-dev", "libxml2-dev", "libxslt1-dev",
			"libreadline-dev", "libssl-dev", "zlib1g-dev"))

	r.Add(
		cmd.DownloadAndExtract(ruby.downloadURL(), "/opt/src"))

	r.Add(
		cmd.And("cd {{ .SourcePath }}",
			"./configure --disable-install-doc --prefix={{ .InstallPath }}",
			"make",
			"make install"))

	if ruby.WithBundler {
		r.Add("{{ .InstallPath }}/bin/gem install bundler")
	}
}

func (ruby *Package) downloadURL() string {
	majorVersion := strings.Join(strings.Split(ruby.Version, ".")[0:2], ".")
	return fmt.Sprintf("http://ftp.ruby-lang.org/pub/ruby/%s/ruby-%s.tar.gz", majorVersion, ruby.Version)
}

func (ruby *Package) InstallPath() string {
	return fmt.Sprintf("/opt/ruby-%s", ruby.Version)
}

func (ruby *Package) SourcePath() string {
	return fmt.Sprintf("/opt/src/ruby-%s", ruby.Version)
}
<commit_msg>add option to install ruby to /usr/local (better for docker)<commit_after>// The Ruby package is used to provision ruby on a host.
//
// Ruby will be downloaded, extracted, configured, built, and installed to `/opt/ruby-{{ .Version }}`. If the `Bundle`
// flag is set, bundler will be installed.
package ruby

import (
	"fmt"
	"github.com/dynport/urknall"
	"github.com/dynport/urknall/cmd"
	"strings"
)

func New(version string) *Package {
	return &Package{
		Version: version,
	}
}

type Package struct {
	Version     string `urknall:"default=2.0.0-p247"`
	WithBundler bool
	Local       bool // install to /usr/local/bin
}

func (ruby *Package) Package(r *urknall.Runlist) {
	r.Add(
		cmd.InstallPackages("curl", "build-essential",
			"libyaml-dev", "libxml2-dev", "libxslt1-dev",
			"libreadline-dev", "libssl-dev", "zlib1g-dev"))

	r.Add(
		cmd.DownloadAndExtract(ruby.downloadURL(), "/opt/src"))

	r.Add(
		cmd.And("cd {{ .SourcePath }}",
			"./configure --disable-install-doc --prefix={{ .InstallPath }}",
			"make",
			"make install"))

	if ruby.WithBundler {
		r.Add("{{ .InstallPath }}/bin/gem install bundler")
	}
}

func (ruby *Package) downloadURL() string {
	majorVersion := strings.Join(strings.Split(ruby.Version, ".")[0:2], ".")
	return fmt.Sprintf("http://ftp.ruby-lang.org/pub/ruby/%s/ruby-%s.tar.gz", majorVersion, ruby.Version)
}

func (ruby *Package) InstallPath() string {
	if ruby.Local {
		return "/usr/local"
	}
	return fmt.Sprintf("/opt/ruby-%s", ruby.Version)
}

func (ruby *Package) SourcePath() string {
	return fmt.Sprintf("/opt/src/ruby-%s", ruby.Version)
}
<|endoftext|>


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

package v1

import (
	"encoding/json"
	"io"
	"time"
)

// ConfigFile is the configuration file that holds the metadata describing
// how to launch a container. See:
// https://github.com/opencontainers/image-spec/blob/master/config.md
type ConfigFile struct {
	Architecture  string    `json:"architecture"`
	Author        string    `json:"author,omitempty"`
	Container     string    `json:"container,omitempty"`
	Created       Time      `json:"created,omitempty"`
	DockerVersion string    `json:"docker_version,omitempty"`
	History       []History `json:"history,omitempty"`
	OS            string    `json:"os"`
	RootFS        RootFS    `json:"rootfs"`
	Config        Config    `json:"config"`
	OSVersion     string    `json:"osversion,omitempty"`
}

// History is one entry of a list recording how this container image was built.
type History struct {
	Author     string `json:"author,omitempty"`
	Created    Time   `json:"created,omitempty"`
	CreatedBy  string `json:"created_by,omitempty"`
	Comment    string `json:"comment,omitempty"`
	EmptyLayer bool   `json:"empty_layer,omitempty"`
}

// Time is a wrapper around time.Time to help with deep copying
type Time struct {
	time.Time
}

// DeepCopyInto creates a deep-copy of the Time value.  The underlying time.Time
// type is effectively immutable in the time API, so it is safe to
// copy-by-assign, despite the presence of (unexported) Pointer fields.
func (t *Time) DeepCopyInto(out *Time) {
	*out = *t
}

// RootFS holds the ordered list of file system deltas that comprise the
// container image's root filesystem.
type RootFS struct {
	Type    string `json:"type"`
	DiffIDs []Hash `json:"diff_ids"`
}

// HealthConfig holds configuration settings for the HEALTHCHECK feature.
type HealthConfig struct {
	// Test is the test to perform to check that the container is healthy.
	// An empty slice means to inherit the default.
	// The options are:
	// {} : inherit healthcheck
	// {"NONE"} : disable healthcheck
	// {"CMD", args...} : exec arguments directly
	// {"CMD-SHELL", command} : run command with system's default shell
	Test []string `json:",omitempty"`

	// Zero means to inherit. Durations are expressed as integer nanoseconds.
	Interval    time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
	Timeout     time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
	StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.

	// Retries is the number of consecutive failures needed to consider a container as unhealthy.
	// Zero means inherit.
	Retries int `json:",omitempty"`
}

// Config is a submessage of the config file described as:
//   The execution parameters which SHOULD be used as a base when running
//   a container using the image.
// The names of the fields in this message are chosen to reflect the JSON
// payload of the Config as defined here:
// https://git.io/vrAET
// and
// https://github.com/opencontainers/image-spec/blob/master/config.md
type Config struct {
	AttachStderr    bool                `json:"AttachStderr,omitempty"`
	AttachStdin     bool                `json:"AttachStdin,omitempty"`
	AttachStdout    bool                `json:"AttachStdout,omitempty"`
	Cmd             []string            `json:"Cmd,omitempty"`
	Healthcheck     *HealthConfig       `json:"Healthcheck,omitempty"`
	Domainname      string              `json:"Domainname,omitempty"`
	Entrypoint      []string            `json:"Entrypoint,omitempty"`
	Env             []string            `json:"Env,omitempty"`
	Hostname        string              `json:"Hostname,omitempty"`
	Image           string              `json:"Image,omitempty"`
	Labels          map[string]string   `json:"Labels,omitempty"`
	OnBuild         []string            `json:"OnBuild,omitempty"`
	OpenStdin       bool                `json:"OpenStdin,omitempty"`
	StdinOnce       bool                `json:"StdinOnce,omitempty"`
	Tty             bool                `json:"Tty,omitempty"`
	User            string              `json:"User,omitempty"`
	Volumes         map[string]struct{} `json:"Volumes,omitempty"`
	WorkingDir      string              `json:"WorkingDir,omitempty"`
	ExposedPorts    map[string]struct{} `json:"ExposedPorts,omitempty"`
	ArgsEscaped     bool                `json:"ArgsEscaped,omitempty"`
	NetworkDisabled bool                `json:"NetworkDisabled,omitempty"`
	MacAddress      string              `json:"MacAddress,omitempty"`
	StopSignal      string              `json:"StopSignal,omitempty"`
	Shell           []string            `json:"Shell,omitempty"`
}

// ParseConfigFile parses the io.Reader's contents into a ConfigFile.
func ParseConfigFile(r io.Reader) (*ConfigFile, error) {
	cf := ConfigFile{}
	if err := json.NewDecoder(r).Decode(&cf); err != nil {
		return nil, err
	}
	return &cf, nil
}
<commit_msg>Fix OSVersion json key in v1.Config (#676)<commit_after>// Copyright 2018 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1

import (
	"encoding/json"
	"io"
	"time"
)

// ConfigFile is the configuration file that holds the metadata describing
// how to launch a container. See:
// https://github.com/opencontainers/image-spec/blob/master/config.md
//
// docker_version and os.version are not part of the spec but included
// for backwards compatibility.
type ConfigFile struct {
	Architecture  string    `json:"architecture"`
	Author        string    `json:"author,omitempty"`
	Container     string    `json:"container,omitempty"`
	Created       Time      `json:"created,omitempty"`
	DockerVersion string    `json:"docker_version,omitempty"`
	History       []History `json:"history,omitempty"`
	OS            string    `json:"os"`
	RootFS        RootFS    `json:"rootfs"`
	Config        Config    `json:"config"`
	OSVersion     string    `json:"os.version,omitempty"`
}

// History is one entry of a list recording how this container image was built.
type History struct {
	Author     string `json:"author,omitempty"`
	Created    Time   `json:"created,omitempty"`
	CreatedBy  string `json:"created_by,omitempty"`
	Comment    string `json:"comment,omitempty"`
	EmptyLayer bool   `json:"empty_layer,omitempty"`
}

// Time is a wrapper around time.Time to help with deep copying
type Time struct {
	time.Time
}

// DeepCopyInto creates a deep-copy of the Time value.  The underlying time.Time
// type is effectively immutable in the time API, so it is safe to
// copy-by-assign, despite the presence of (unexported) Pointer fields.
func (t *Time) DeepCopyInto(out *Time) {
	*out = *t
}

// RootFS holds the ordered list of file system deltas that comprise the
// container image's root filesystem.
type RootFS struct {
	Type    string `json:"type"`
	DiffIDs []Hash `json:"diff_ids"`
}

// HealthConfig holds configuration settings for the HEALTHCHECK feature.
type HealthConfig struct {
	// Test is the test to perform to check that the container is healthy.
	// An empty slice means to inherit the default.
	// The options are:
	// {} : inherit healthcheck
	// {"NONE"} : disable healthcheck
	// {"CMD", args...} : exec arguments directly
	// {"CMD-SHELL", command} : run command with system's default shell
	Test []string `json:",omitempty"`

	// Zero means to inherit. Durations are expressed as integer nanoseconds.
	Interval    time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
	Timeout     time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
	StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.

	// Retries is the number of consecutive failures needed to consider a container as unhealthy.
	// Zero means inherit.
	Retries int `json:",omitempty"`
}

// Config is a submessage of the config file described as:
//   The execution parameters which SHOULD be used as a base when running
//   a container using the image.
// The names of the fields in this message are chosen to reflect the JSON
// payload of the Config as defined here:
// https://git.io/vrAET
// and
// https://github.com/opencontainers/image-spec/blob/master/config.md
type Config struct {
	AttachStderr    bool                `json:"AttachStderr,omitempty"`
	AttachStdin     bool                `json:"AttachStdin,omitempty"`
	AttachStdout    bool                `json:"AttachStdout,omitempty"`
	Cmd             []string            `json:"Cmd,omitempty"`
	Healthcheck     *HealthConfig       `json:"Healthcheck,omitempty"`
	Domainname      string              `json:"Domainname,omitempty"`
	Entrypoint      []string            `json:"Entrypoint,omitempty"`
	Env             []string            `json:"Env,omitempty"`
	Hostname        string              `json:"Hostname,omitempty"`
	Image           string              `json:"Image,omitempty"`
	Labels          map[string]string   `json:"Labels,omitempty"`
	OnBuild         []string            `json:"OnBuild,omitempty"`
	OpenStdin       bool                `json:"OpenStdin,omitempty"`
	StdinOnce       bool                `json:"StdinOnce,omitempty"`
	Tty             bool                `json:"Tty,omitempty"`
	User            string              `json:"User,omitempty"`
	Volumes         map[string]struct{} `json:"Volumes,omitempty"`
	WorkingDir      string              `json:"WorkingDir,omitempty"`
	ExposedPorts    map[string]struct{} `json:"ExposedPorts,omitempty"`
	ArgsEscaped     bool                `json:"ArgsEscaped,omitempty"`
	NetworkDisabled bool                `json:"NetworkDisabled,omitempty"`
	MacAddress      string              `json:"MacAddress,omitempty"`
	StopSignal      string              `json:"StopSignal,omitempty"`
	Shell           []string            `json:"Shell,omitempty"`
}

// ParseConfigFile parses the io.Reader's contents into a ConfigFile.
func ParseConfigFile(r io.Reader) (*ConfigFile, error) {
	cf := ConfigFile{}
	if err := json.NewDecoder(r).Decode(&cf); err != nil {
		return nil, err
	}
	return &cf, nil
}
<|endoftext|>


<commit_before>package yaml

import (
	"bytes"
	"errors"
	"fmt"
	"gopkg.in/yaml.v1"
	"io/ioutil"
	"log"
	"os"
	"sort"
	"strconv"
	"text/template"
)

func (this Context) copy_from(other Context) {
	for k, v := range other {
		this[k] = v
	}
}

func (this Context) eval(f *string) string {
	old := *f
	s := eval(*f, this)
	*f = s
	return old
}

const LIVE_MODE = "LIVE_MODE"

func (this Context) test_mode() bool {
	test := true
	if v, ok := this[LIVE_MODE].(string); ok {
		if b, err := strconv.ParseBool(v); err == nil {
			test = !b
		}
	} else if v, ok := this[LIVE_MODE].(int); ok {
		return v == 0
	}
	return test
}

func eval(tpl string, m map[string]interface{}) string {
	var buff bytes.Buffer
	t := template.Must(template.New(tpl).Parse(tpl))
	err := t.Execute(&buff, m)
	if err != nil {
		return tpl
	} else {
		return os.ExpandEnv(buff.String())
	}
}

func (this *MaestroDoc) String() string {
	bytes, err := yaml.Marshal(this)
	if err != nil {
		return err.Error()
	} else {
		return string(bytes)
	}
}

func (this *MaestroDoc) LoadFromFile(filename string) error {
	this.init()

	file, err := os.Open(filename)
	if err != nil {
		return err
	}

	buff, err := ioutil.ReadAll(file)
	if err != nil {
		return err
	}
	return this.LoadFromBytes(buff)
}

func (this *MaestroDoc) LoadFromBytes(buff []byte) error {
	this.init()

	err := yaml.Unmarshal(buff, this)
	if err != nil {
		return err
	}

	// Do imports
	for _, file := range this.Imports {
		path := os.ExpandEnv(string(file))
		imported := &MaestroDoc{}
		if err := imported.LoadFromFile(path); err != nil {
			return err
		} else {
			imported.merge(this)
		}
	}
	return err
}

func (this *MaestroDoc) init() {
	if this.Imports == nil {
		this.Imports = []YmlFilePath{}
	}
	if this.Deploys == nil {
		this.Deploys = []ServiceKey{}
	}
	if this.Vars == nil {
		this.Vars = make(map[string]string)
	}
	if this.ServiceSection == nil {
		this.ServiceSection = make(map[ServiceKey][]map[JobKey]JobPortList)
	}
	if this.Artifacts == nil {
		this.Artifacts = make(map[ArtifactKey]*Artifact)
	}
	if this.Images == nil {
		this.Images = make(map[ImageKey]*Image)
	}
	if this.Containers == nil {
		this.Containers = make(map[ContainerKey]*Container)
	}
	if this.Disks == nil {
		this.Disks = make(map[DiskKey]*Disk)
	}
	if this.Instances == nil {
		this.Instances = make(map[InstanceKey]*Instance)
	}
	if this.services == nil {
		this.services = make(map[ServiceKey]*Service)
	}
}

func (this *MaestroDoc) merge(other *MaestroDoc) error {
	from := this
	to := other

	for _, d := range from.Deploys {
		to.Deploys = append(to.Deploys, d)
	}
	for k, v := range from.Vars {
		if _, has := to.Vars[k]; has {
			log.Println("Warning: Var[", k, "] to be overriden.")
		}
		to.Vars[k] = v
	}
	for k, v := range from.Artifacts {
		if _, has := to.Artifacts[k]; has {
			log.Println("Warning: Artifact[", k, "] to be overriden.")
		}
		to.Artifacts[k] = v
	}
	for k, v := range from.Images {
		if _, has := to.Images[k]; has {
			log.Println("Warning: Docker[", k, "] to be overriden.")
		}
		to.Images[k] = v
	}
	for k, v := range from.Containers {
		if _, has := to.Containers[k]; has {
			log.Println("Warning: Container[", k, "] to be overriden.")
		}
		to.Containers[k] = v
	}
	for k, v := range from.Disks {
		if _, has := to.Disks[k]; has {
			log.Println("Warning: Disk[", k, "] to be overriden.")
		}
		to.Disks[k] = v
	}
	for k, v := range from.Instances {
		if _, has := to.Instances[k]; has {
			log.Println("Warning: Instance[", k, "] to be overriden.")
		}
		to.Instances[k] = v
	}
	for k, v := range from.Jobs {
		if _, has := to.Jobs[k]; has {
			log.Println("Warning: Job[", k, "] to be overriden.")
		}
		to.Jobs[k] = v
	}
	for k, v := range from.ServiceSection {
		if _, has := to.ServiceSection[k]; has {
			log.Println("Warning: Service[", k, "] to be overriden.")
		}
		to.ServiceSection[k] = v
	}

	return nil
}

func (this *Container) clone_from(other *Container) {
	*this = *other
	// we need clone the ssh commands since they are stored as array of pointers
	ssh := make([]*string, len(other.Ssh))
	for i, c := range other.Ssh {
		copy := *c
		ssh[i] = &copy
	}
	this.Ssh = ssh
}

func (this *MaestroDoc) process_jobs() error {
	for k, job := range this.Jobs {

		container, has := this.Containers[job.ContainerKey]
		if !has {
			return errors.New(fmt.Sprintf("container-%s-not-found", job.ContainerKey))
		}
		job.name = JobKey(k)
		job.container = container
		job.instance_labels = job.InstanceLabels.parse()

		if job.container == nil {
			return errors.New(fmt.Sprintf("job-no-container-template:%s", job.name))
		}

		if len(this.Instances) == 0 {
			return errors.New("no-instances-specified-in-doc")
		}

		// Go through the instances known to this doc and see if an instance can take the job
		matches := []*Instance{}
		for _, instance := range this.Instances {
			if instance.can_take(job) {
				matches = append(matches, instance)
			}
		}
		sort.Sort(ByInstanceKey(matches))
		job.instances = matches

		if len(job.instances) == 0 {
			return errors.New(fmt.Sprintf("job-no-matched-instances:%s", job.name))
		}

		// now create container instances that are to be run on each machine instance
		job.container_instances = []*Container{}
		for _, instance := range job.instances {
			// Spawn a container instance
			copy := &Container{}
			copy.clone_from(job.container)
			copy.targetInstance = instance
			job.container_instances = append(job.container_instances, copy)
		}
		if len(job.container_instances) == 0 {
			return errors.New(fmt.Sprintf("job-no-matched-container-instances:%s", job.name))
		}

	}
	return nil
}

func (this *MaestroDoc) process_services() error {
	// Build the services.  Each service is a composition of a list of container and instance label pair.
	// It means for a given service, there are 1 or more containers to run and they are to be run in sequence.
	// For a given container, it is to be run over a number of instances, as identified by the instance label.
	this.services = make(map[ServiceKey]*Service)
	for service_key, job_key_port_list := range this.ServiceSection {

		service := &Service{
			name:     service_key,
			jobs:     []*Job{},
			port_map: map[JobKey][]ExposedPort{},
		}
		this.services[service_key] = service

		// Here is an array of map (job name : port list) pairs
		for _, job_and_ports := range job_key_port_list {

			for job_key, job_port_list := range job_and_ports {

				// Get the job to determine which containers to run
				job, has := this.Jobs[job_key]

				if !has {
					return errors.New(fmt.Sprintf("job-not-found%s", job_key))
				}

				exposed_ports, err := job_port_list.parse()
				if err != nil {
					return errors.New("bad-port-list:" + err.Error())
				}

				service.jobs = append(service.jobs, job)
				service.port_map[job_key] = exposed_ports
			}
		}
	}
	return nil
}

func (this *MaestroDoc) process_images() error {
	for k, artifact := range this.Artifacts {
		artifact.name = ArtifactKey(k)
	}
	for k, image := range this.Images {
		image.name = k
		for _, ak := range this.Images[k].ArtifactKeys {
			if artifact, has := this.Artifacts[ak]; has {
				if image.artifacts == nil {
					image.artifacts = make([]*Artifact, 0)
				}
				image.artifacts = append(image.artifacts, artifact)
			}
		}
	}
	return nil
}

func (this *MaestroDoc) process_containers() error {
	for k, container := range this.Containers {
		container.name = ContainerKey(k)
		// containers either reference images or builds

		if image, has := this.Images[ImageKey(container.ImageRef)]; has {
			container.targetImage = image
		} else {
			// assumes that the image references a docker hub image
			container.targetImage = nil
		}
		// Containers also reference instances which are bound later at the time
		// when we launch container in instances in parallel.
		// The instances are bound via definition of services.
	}
	return nil
}

func (this *MaestroDoc) process_resources() error {
	for k, a := range this.Disks {
		a.name = DiskKey(k)
	}
	for k, instance := range this.Instances {
		instance.name = InstanceKey(k)
		instance.disks = make(map[VolumeLabel]*Volume)
		instance.labels = instance.InstanceLabels.parse()
		for vl, m := range instance.VolumeSection {
			for dk, mp := range m {
				if _, has := this.Disks[dk]; !has {
					return errors.New(fmt.Sprint("No disk '", dk, "' found."))
				}
				instance.disks[vl] = &Volume{
					Disk:       dk,
					MountPoint: string(mp),
				}
			}
		}
	}

	return nil
}

func (this *MaestroDoc) process_config() error {
	// What to run
	if err := this.process_images(); err != nil {
		return err
	}
	// Where to run things
	if err := this.process_resources(); err != nil {
		return err
	}
	// Containers to run
	if err := this.process_containers(); err != nil {
		return err
	}
	// Jobs to run
	if err := this.process_jobs(); err != nil {
		return err
	}
	// Match what to where:
	if err := this.process_services(); err != nil {
		return err
	}
	return nil
}

func (this *MaestroDoc) new_context() Context {
	context := make(Context)
	for k, v := range this.Vars {
		// Substitutes any $ENV with value from the environment
		context[k] = os.ExpandEnv(v)
	}
	return context
}

func (this *MaestroDoc) InDesiredState(c Context) (bool, error) {
	return true, nil
}

func (this *MaestroDoc) Validate(c Context) error {
	// Validate the doc
	// TODO The resources and artifacts are independent so we can run in parallel

	var err error
	for k, disk := range this.Disks {
		log.Print("Validating disk " + k)
		err = disk.Validate(c)
		if err != nil {
			log.Println(err)
			return err
		}
	}
	for k, instance := range this.Instances {
		log.Print("Validating instance " + k)
		err = instance.Validate(c)
		if err != nil {
			log.Println(err)
			return err
		}
	}
	for k, image := range this.Images {
		log.Print("Validating image " + k)
		err = image.Validate(c)
		if err != nil {
			log.Println(err)
			return err
		}
	}
	log.Println("Image validation done.")
	for k, service := range this.services {
		log.Print("Validating service " + k)
		err = service.Validate(c)
		if err != nil {
			log.Println(err)
			return err
		}
	}
	return err
}

func (this *MaestroDoc) all_runnables() []func() Runnable {
	return []func() Runnable{
		this.runnableImages,
	}
}

func (this *MaestroDoc) runnableImages() Runnable {
	m := make(runnableMap)
	for k, v := range this.Images {
		m[k] = v
	}
	return runnableMap(m)
}

func (this *MaestroDoc) runnableServices() Runnable {
	m := make(runnableMap)
	for k, v := range this.services {
		m[k] = v
	}
	return runnableMap(m)
}

func (this *MaestroDoc) runnableDeployments() Runnable {
	m := make(runnableMap)
	for k, v := range this.services {
		m[k] = v
	}
	return runnableMap(m)
}

func (this *MaestroDoc) Prepare(c Context) error {
	for _, prepare := range this.all_runnables() {
		if err := prepare().Prepare(c); err != nil {
			return err
		}
	}
	return nil
}

func (this *MaestroDoc) Execute(c Context) error {
	for _, execute := range this.all_runnables() {
		if err := execute().Execute(c); err != nil {
			return err
		}
	}
	return nil
}

func (this *MaestroDoc) Finish(c Context) error {
	for _, finish := range this.all_runnables() {
		if err := finish().Finish(c); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Small refactor to break up larger functions<commit_after>package yaml

import (
	"bytes"
	"errors"
	"fmt"
	"gopkg.in/yaml.v1"
	"io/ioutil"
	"log"
	"os"
	"sort"
	"strconv"
	"text/template"
)

func (this Context) copy_from(other Context) {
	for k, v := range other {
		this[k] = v
	}
}

func (this Context) eval(f *string) string {
	old := *f
	s := eval(*f, this)
	*f = s
	return old
}

const LIVE_MODE = "LIVE_MODE"

func (this Context) test_mode() bool {
	test := true
	if v, ok := this[LIVE_MODE].(string); ok {
		if b, err := strconv.ParseBool(v); err == nil {
			test = !b
		}
	} else if v, ok := this[LIVE_MODE].(int); ok {
		return v == 0
	}
	return test
}

func eval(tpl string, m map[string]interface{}) string {
	var buff bytes.Buffer
	t := template.Must(template.New(tpl).Parse(tpl))
	err := t.Execute(&buff, m)
	if err != nil {
		return tpl
	} else {
		return os.ExpandEnv(buff.String())
	}
}

func (this *MaestroDoc) String() string {
	bytes, err := yaml.Marshal(this)
	if err != nil {
		return err.Error()
	} else {
		return string(bytes)
	}
}

func (this *MaestroDoc) LoadFromFile(filename string) error {
	this.init()

	file, err := os.Open(filename)
	if err != nil {
		return err
	}

	buff, err := ioutil.ReadAll(file)
	if err != nil {
		return err
	}
	return this.LoadFromBytes(buff)
}

func (this *MaestroDoc) LoadFromBytes(buff []byte) error {
	this.init()

	err := yaml.Unmarshal(buff, this)
	if err != nil {
		return err
	}

	// Do imports
	for _, file := range this.Imports {
		path := os.ExpandEnv(string(file))
		imported := &MaestroDoc{}
		if err := imported.LoadFromFile(path); err != nil {
			return err
		} else {
			imported.merge(this)
		}
	}
	return err
}

func (this *MaestroDoc) init() {
	if this.Imports == nil {
		this.Imports = []YmlFilePath{}
	}
	if this.Deploys == nil {
		this.Deploys = []ServiceKey{}
	}
	if this.Vars == nil {
		this.Vars = make(map[string]string)
	}
	if this.ServiceSection == nil {
		this.ServiceSection = make(map[ServiceKey][]map[JobKey]JobPortList)
	}
	if this.Artifacts == nil {
		this.Artifacts = make(map[ArtifactKey]*Artifact)
	}
	if this.Images == nil {
		this.Images = make(map[ImageKey]*Image)
	}
	if this.Containers == nil {
		this.Containers = make(map[ContainerKey]*Container)
	}
	if this.Disks == nil {
		this.Disks = make(map[DiskKey]*Disk)
	}
	if this.Instances == nil {
		this.Instances = make(map[InstanceKey]*Instance)
	}
	if this.services == nil {
		this.services = make(map[ServiceKey]*Service)
	}
}

func (this *MaestroDoc) merge(other *MaestroDoc) error {
	from := this
	to := other

	for _, d := range from.Deploys {
		to.Deploys = append(to.Deploys, d)
	}
	for k, v := range from.Vars {
		if _, has := to.Vars[k]; has {
			log.Println("Warning: Var[", k, "] to be overriden.")
		}
		to.Vars[k] = v
	}
	for k, v := range from.Artifacts {
		if _, has := to.Artifacts[k]; has {
			log.Println("Warning: Artifact[", k, "] to be overriden.")
		}
		to.Artifacts[k] = v
	}
	for k, v := range from.Images {
		if _, has := to.Images[k]; has {
			log.Println("Warning: Docker[", k, "] to be overriden.")
		}
		to.Images[k] = v
	}
	for k, v := range from.Containers {
		if _, has := to.Containers[k]; has {
			log.Println("Warning: Container[", k, "] to be overriden.")
		}
		to.Containers[k] = v
	}
	for k, v := range from.Disks {
		if _, has := to.Disks[k]; has {
			log.Println("Warning: Disk[", k, "] to be overriden.")
		}
		to.Disks[k] = v
	}
	for k, v := range from.Instances {
		if _, has := to.Instances[k]; has {
			log.Println("Warning: Instance[", k, "] to be overriden.")
		}
		to.Instances[k] = v
	}
	for k, v := range from.Jobs {
		if _, has := to.Jobs[k]; has {
			log.Println("Warning: Job[", k, "] to be overriden.")
		}
		to.Jobs[k] = v
	}
	for k, v := range from.ServiceSection {
		if _, has := to.ServiceSection[k]; has {
			log.Println("Warning: Service[", k, "] to be overriden.")
		}
		to.ServiceSection[k] = v
	}

	return nil
}

func (this *Container) clone_from(other *Container) {
	*this = *other
	// we need clone the ssh commands since they are stored as array of pointers
	ssh := make([]*string, len(other.Ssh))
	for i, c := range other.Ssh {
		copy := *c
		ssh[i] = &copy
	}
	this.Ssh = ssh
}

func (this *MaestroDoc) process_jobs() error {
	for k, job := range this.Jobs {

		container, has := this.Containers[job.ContainerKey]
		if !has {
			return errors.New(fmt.Sprintf("container-%s-not-found", job.ContainerKey))
		}
		job.name = JobKey(k)
		job.container = container
		job.instance_labels = job.InstanceLabels.parse()

		if job.container == nil {
			return errors.New(fmt.Sprintf("job-no-container-template:%s", job.name))
		}

		if len(this.Instances) == 0 {
			return errors.New("no-instances-specified-in-doc")
		}

		// Go through the instances known to this doc and see if an instance can take the job
		matches := []*Instance{}
		for _, instance := range this.Instances {
			if instance.can_take(job) {
				matches = append(matches, instance)
			}
		}
		sort.Sort(ByInstanceKey(matches))
		job.instances = matches

		if len(job.instances) == 0 {
			return errors.New(fmt.Sprintf("job-no-matched-instances:%s", job.name))
		}

		// now create container instances that are to be run on each machine instance
		job.container_instances = []*Container{}
		for _, instance := range job.instances {
			// Spawn a container instance
			copy := &Container{}
			copy.clone_from(job.container)
			copy.targetInstance = instance
			job.container_instances = append(job.container_instances, copy)
		}
		if len(job.container_instances) == 0 {
			return errors.New(fmt.Sprintf("job-no-matched-container-instances:%s", job.name))
		}

	}
	return nil
}

func (this *MaestroDoc) process_services() error {
	// Build the services.  Each service is a composition of a list of container and instance label pair.
	// It means for a given service, there are 1 or more containers to run and they are to be run in sequence.
	// For a given container, it is to be run over a number of instances, as identified by the instance label.
	this.services = make(map[ServiceKey]*Service)
	for service_key, job_key_port_list := range this.ServiceSection {

		service := &Service{
			name:     service_key,
			jobs:     []*Job{},
			port_map: map[JobKey][]ExposedPort{},
		}
		this.services[service_key] = service

		// Here is an array of map (job name : port list) pairs
		for _, job_and_ports := range job_key_port_list {

			for job_key, job_port_list := range job_and_ports {

				// Get the job to determine which containers to run
				job, has := this.Jobs[job_key]

				if !has {
					return errors.New(fmt.Sprintf("job-not-found%s", job_key))
				}

				exposed_ports, err := job_port_list.parse()
				if err != nil {
					return errors.New("bad-port-list:" + err.Error())
				}

				service.jobs = append(service.jobs, job)
				service.port_map[job_key] = exposed_ports
			}
		}
	}
	return nil
}

func (this *MaestroDoc) process_artifacts() error {
	for k, artifact := range this.Artifacts {
		artifact.name = ArtifactKey(k)
	}
	return nil
}

func (this *MaestroDoc) process_images() error {
	for k, image := range this.Images {
		image.name = k
		for _, ak := range this.Images[k].ArtifactKeys {
			if artifact, has := this.Artifacts[ak]; has {
				if image.artifacts == nil {
					image.artifacts = make([]*Artifact, 0)
				}
				image.artifacts = append(image.artifacts, artifact)
			}
		}
	}
	return nil
}

func (this *MaestroDoc) process_containers() error {
	for k, container := range this.Containers {
		container.name = ContainerKey(k)
		// containers either reference images or builds

		if image, has := this.Images[ImageKey(container.ImageRef)]; has {
			container.targetImage = image
		} else {
			// assumes that the image references a docker hub image
			container.targetImage = nil
		}
		// Containers also reference instances which are bound later at the time
		// when we launch container in instances in parallel.
		// The instances are bound via definition of services.
	}
	return nil
}

func (this *MaestroDoc) process_disks() error {
	for k, a := range this.Disks {
		a.name = DiskKey(k)
	}
	return nil
}

func (this *MaestroDoc) process_instances() error {
	for k, instance := range this.Instances {
		instance.name = InstanceKey(k)
		instance.disks = make(map[VolumeLabel]*Volume)
		instance.labels = instance.InstanceLabels.parse()
		for vl, m := range instance.VolumeSection {
			for dk, mp := range m {
				if _, has := this.Disks[dk]; !has {
					return errors.New(fmt.Sprint("No disk '", dk, "' found."))
				}
				instance.disks[vl] = &Volume{
					Disk:       dk,
					MountPoint: string(mp),
				}
			}
		}
	}

	return nil
}

// Performs any parsing and conversion of the yml; sets up the relationships
// and parent-child relationships.
func (this *MaestroDoc) process_config() error {
	// Resources available
	if err := this.process_disks(); err != nil {
		return err
	}
	if err := this.process_instances(); err != nil {
		return err
	}
	// What to run
	if err := this.process_artifacts(); err != nil {
		return err
	}
	if err := this.process_images(); err != nil {
		return err
	}
	// Containers to run
	if err := this.process_containers(); err != nil {
		return err
	}
	// Jobs to run
	if err := this.process_jobs(); err != nil {
		return err
	}
	// Match what to where:
	if err := this.process_services(); err != nil {
		return err
	}
	return nil
}

func (this *MaestroDoc) new_context() Context {
	context := make(Context)
	for k, v := range this.Vars {
		// Substitutes any $ENV with value from the environment
		context[k] = os.ExpandEnv(v)
	}
	return context
}

func (this *MaestroDoc) InDesiredState(c Context) (bool, error) {
	return true, nil
}

func (this *MaestroDoc) Validate(c Context) error {
	// Validate the doc
	// TODO The resources and artifacts are independent so we can run in parallel

	var err error
	for k, disk := range this.Disks {
		log.Print("Validating disk " + k)
		err = disk.Validate(c)
		if err != nil {
			log.Println(err)
			return err
		}
	}
	for k, instance := range this.Instances {
		log.Print("Validating instance " + k)
		err = instance.Validate(c)
		if err != nil {
			log.Println(err)
			return err
		}
	}
	for k, image := range this.Images {
		log.Print("Validating image " + k)
		err = image.Validate(c)
		if err != nil {
			log.Println(err)
			return err
		}
	}
	log.Println("Image validation done.")
	for k, service := range this.services {
		log.Print("Validating service " + k)
		err = service.Validate(c)
		if err != nil {
			log.Println(err)
			return err
		}
	}
	return err
}

func (this *MaestroDoc) all_runnables() []func() Runnable {
	return []func() Runnable{
		this.runnableImages,
	}
}

func (this *MaestroDoc) runnableImages() Runnable {
	m := make(runnableMap)
	for k, v := range this.Images {
		m[k] = v
	}
	return runnableMap(m)
}

func (this *MaestroDoc) runnableServices() Runnable {
	m := make(runnableMap)
	for k, v := range this.services {
		m[k] = v
	}
	return runnableMap(m)
}

func (this *MaestroDoc) runnableDeployments() Runnable {
	m := make(runnableMap)
	for k, v := range this.services {
		m[k] = v
	}
	return runnableMap(m)
}

func (this *MaestroDoc) Prepare(c Context) error {
	for _, prepare := range this.all_runnables() {
		if err := prepare().Prepare(c); err != nil {
			return err
		}
	}
	return nil
}

func (this *MaestroDoc) Execute(c Context) error {
	for _, execute := range this.all_runnables() {
		if err := execute().Execute(c); err != nil {
			return err
		}
	}
	return nil
}

func (this *MaestroDoc) Finish(c Context) error {
	for _, finish := range this.all_runnables() {
		if err := finish().Finish(c); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package heroku

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

	log "github.com/Sirupsen/logrus"

	cc "github.com/commonchat/commonchat-go"
	"github.com/grokify/glip-go-webhook"
	"github.com/grokify/webhook-proxy-go/src/adapters"
	"github.com/grokify/webhook-proxy-go/src/config"
	"github.com/grokify/webhook-proxy-go/src/util"
	"github.com/valyala/fasthttp"
)

const (
	DisplayName = "Heroku"
	HandlerKey  = "heroku"
	IconURL     = "https://a.slack-edge.com/ae7f/plugins/heroku/assets/service_512.png"
)

// FastHttp request handler for Heroku outbound webhook
// https://devcenter.heroku.com/articles/deploy-hooks#http-post-hook
type HerokuOutToGlipHandler struct {
	Config     config.Configuration
	GlipClient glipwebhook.GlipWebhookClient
	Adapter    adapters.Adapter
}

// FastHttp request handler constructor for Confluence outbound webhook
func NewHerokuOutToGlipHandler(cfg config.Configuration, adapter adapters.Adapter) HerokuOutToGlipHandler {
	return HerokuOutToGlipHandler{Config: cfg, Adapter: adapter}
}

// HandleFastHTTP is the method to respond to a fasthttp request.
func (h *HerokuOutToGlipHandler) HandleFastHTTP(ctx *fasthttp.RequestCtx) {
	srcMsg, err := BuildInboundMessage(ctx)
	if err != nil {
		ctx.SetStatusCode(fasthttp.StatusNotAcceptable)
		log.WithFields(log.Fields{
			"type":   "http.response",
			"status": fasthttp.StatusNotAcceptable,
		}).Info(fmt.Sprintf("%v request is not acceptable.", DisplayName))
		return
	}

	ccMsg, err := NormalizeHerokuMessage(srcMsg)
	if err != nil {
		ctx.SetStatusCode(fasthttp.StatusNotAcceptable)
		log.WithFields(log.Fields{
			"type":   "http.response",
			"status": fasthttp.StatusNotAcceptable,
		}).Info(fmt.Sprintf("%v request is not acceptable.", DisplayName))
		return
	}

	util.SendWebhook(ctx, h.Adapter, ccMsg)
}

func BuildInboundMessage(ctx *fasthttp.RequestCtx) (HerokuOutMessage, error) {
	return HerokuOutMessage{
		App:      string(ctx.FormValue("app")),
		User:     string(ctx.FormValue("user")),
		URL:      string(ctx.FormValue("url")),
		Head:     string(ctx.FormValue("head")),
		HeadLong: string(ctx.FormValue("head_long")),
		PrevHead: string(ctx.FormValue("prev_head")),
		GitLog:   string(ctx.FormValue("git_log")),
		Release:  string(ctx.FormValue("release"))}, nil
}

//func Normalize(src HerokuOutMessage) glipwebhook.GlipWebhookMessage {
func Normalize(bytes []byte) (cc.Message, error) {
	src, err := HerokuOutMessageFromQueryString(string(bytes))
	if err != nil {
		return cc.Message{}, err
	}
	return NormalizeHerokuMessage(src)
}

func NormalizeHerokuMessage(src HerokuOutMessage) (cc.Message, error) {
	message := cc.NewMessage()
	message.IconURL = IconURL

	if len(strings.TrimSpace(src.App)) > 0 {
		message.Activity = fmt.Sprintf("%v deployed on %v", src.App, DisplayName)
	}

	if 1 == 0 {
		if len(strings.TrimSpace(src.User)) > 0 {
			message.Activity = fmt.Sprintf("%v deployed an app on %v", src.User, DisplayName)
		} else {
			if len(strings.TrimSpace(src.App)) > 0 {
				message.Activity = fmt.Sprintf("%v deployed on %v", src.App, DisplayName)
			} else {
				message.Activity = fmt.Sprintf("An app has been deployed on %v", DisplayName)
			}
		}
	}

	attachment := cc.NewAttachment()

	if len(strings.TrimSpace(src.App)) > 0 {
		field := cc.Field{Title: "Application"}
		if len(src.App) < 35 {
			field.Short = true
		}
		if len(strings.TrimSpace(src.URL)) > 0 {
			field.Value = fmt.Sprintf("[%s](%s)", src.App, src.URL)
		} else {
			field.Value = src.App
		}
		attachment.AddField(field)
	}
	if len(strings.TrimSpace(src.Release)) > 0 {
		attachment.AddField(cc.Field{Title: "Release", Value: src.Release, Short: true})
	}

	message.AddAttachment(attachment)
	return message, nil
}

type HerokuOutMessage struct {
	App      string `json:"app,omitempty"`
	User     string `json:"user,omitempty"`
	URL      string `json:"url,omitempty"`
	Head     string `json:"head,omitempty"`
	HeadLong string `json:"head_long,omitempty"`
	PrevHead string `json:"prev_head,omitempty"`
	GitLog   string `json:"git_log,omitempty"`
	Release  string `json:"release,omitempty"`
}

func HerokuOutMessageFromQueryString(queryString string) (HerokuOutMessage, error) {
	msg := HerokuOutMessage{}
	values, err := url.ParseQuery(queryString)
	if err != nil {
		return msg, err
	}
	if len(strings.TrimSpace(values.Get("app"))) > 0 {
		msg.App = strings.TrimSpace(values.Get("app"))
	}
	if len(strings.TrimSpace(values.Get("user"))) > 0 {
		msg.User = strings.TrimSpace(values.Get("user"))
	}
	if len(strings.TrimSpace(values.Get("url"))) > 0 {
		msg.URL = strings.TrimSpace(values.Get("url"))
	}
	if len(strings.TrimSpace(values.Get("head"))) > 0 {
		msg.Head = strings.TrimSpace(values.Get("head"))
	}
	if len(strings.TrimSpace(values.Get("head_long"))) > 0 {
		msg.HeadLong = strings.TrimSpace(values.Get("head_long"))
	}
	if len(strings.TrimSpace(values.Get("prev_head"))) > 0 {
		msg.PrevHead = strings.TrimSpace(values.Get("prev_head"))
	}
	if len(strings.TrimSpace(values.Get("git_log"))) > 0 {
		msg.GitLog = strings.TrimSpace(values.Get("git_log"))
	}
	if len(strings.TrimSpace(values.Get("release"))) > 0 {
		msg.Release = strings.TrimSpace(values.Get("release"))
	}
	return msg, nil
}
<commit_msg>update heroku<commit_after>package heroku

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

	log "github.com/Sirupsen/logrus"

	cc "github.com/commonchat/commonchat-go"
	"github.com/grokify/glip-go-webhook"
	"github.com/grokify/webhook-proxy-go/src/adapters"
	"github.com/grokify/webhook-proxy-go/src/config"
	"github.com/grokify/webhook-proxy-go/src/util"
	"github.com/valyala/fasthttp"
)

const (
	DisplayName = "Heroku"
	HandlerKey  = "heroku"
	IconURL     = "https://a.slack-edge.com/ae7f/plugins/heroku/assets/service_512.png"
)

// FastHttp request handler for Heroku outbound webhook
// https://devcenter.heroku.com/articles/deploy-hooks#http-post-hook
type HerokuOutToGlipHandler struct {
	Config     config.Configuration
	GlipClient glipwebhook.GlipWebhookClient
	Adapter    adapters.Adapter
}

// FastHttp request handler constructor for Confluence outbound webhook
func NewHerokuOutToGlipHandler(cfg config.Configuration, adapter adapters.Adapter) HerokuOutToGlipHandler {
	return HerokuOutToGlipHandler{Config: cfg, Adapter: adapter}
}

// HandleFastHTTP is the method to respond to a fasthttp request.
func (h *HerokuOutToGlipHandler) HandleFastHTTP(ctx *fasthttp.RequestCtx) {
	srcMsg, err := BuildInboundMessage(ctx)
	if err != nil {
		ctx.SetStatusCode(fasthttp.StatusNotAcceptable)
		log.WithFields(log.Fields{
			"type":   "http.response",
			"status": fasthttp.StatusNotAcceptable,
		}).Info(fmt.Sprintf("%v request is not acceptable.", DisplayName))
		return
	}

	ccMsg, err := NormalizeHerokuMessage(srcMsg)
	if err != nil {
		ctx.SetStatusCode(fasthttp.StatusNotAcceptable)
		log.WithFields(log.Fields{
			"type":   "http.response",
			"status": fasthttp.StatusNotAcceptable,
		}).Info(fmt.Sprintf("%v request is not acceptable.", DisplayName))
		return
	}

	util.SendWebhook(ctx, h.Adapter, ccMsg)
}

func BuildInboundMessage(ctx *fasthttp.RequestCtx) (HerokuOutMessage, error) {
	return HerokuOutMessage{
		App:      string(ctx.FormValue("app")),
		User:     string(ctx.FormValue("user")),
		URL:      string(ctx.FormValue("url")),
		Head:     string(ctx.FormValue("head")),
		HeadLong: string(ctx.FormValue("head_long")),
		PrevHead: string(ctx.FormValue("prev_head")),
		GitLog:   string(ctx.FormValue("git_log")),
		Release:  string(ctx.FormValue("release"))}, nil
}

//func Normalize(src HerokuOutMessage) glipwebhook.GlipWebhookMessage {
func Normalize(bytes []byte) (cc.Message, error) {
	src, err := HerokuOutMessageFromQueryString(string(bytes))
	if err != nil {
		return cc.Message{}, err
	}
	return NormalizeHerokuMessage(src)
}

func NormalizeHerokuMessage(src HerokuOutMessage) (cc.Message, error) {
	message := cc.NewMessage()
	message.IconURL = IconURL

	if len(strings.TrimSpace(src.App)) > 0 {
		message.Activity = fmt.Sprintf("%v deployed on %v", src.App, DisplayName)
	} else {
		message.Activity = fmt.Sprintf("An app has been deployed on %v", DisplayName)
	}

	attachment := cc.NewAttachment()

	if len(strings.TrimSpace(src.App)) > 0 {
		field := cc.Field{Title: "Application"}
		if len(src.App) < 35 {
			field.Short = true
		}
		if len(strings.TrimSpace(src.URL)) > 0 {
			field.Value = fmt.Sprintf("[%s](%s)", src.App, src.URL)
		} else {
			field.Value = src.App
		}
		attachment.AddField(field)
	}
	if len(strings.TrimSpace(src.Release)) > 0 {
		attachment.AddField(cc.Field{Title: "Release", Value: src.Release, Short: true})
	}
	if len(strings.TrimSpace(src.User)) > 0 {
		attachment.AddField(cc.Field{Title: "User", Value: src.User, Short: true})
	}

	message.AddAttachment(attachment)
	return message, nil
}

type HerokuOutMessage struct {
	App      string `json:"app,omitempty"`
	User     string `json:"user,omitempty"`
	URL      string `json:"url,omitempty"`
	Head     string `json:"head,omitempty"`
	HeadLong string `json:"head_long,omitempty"`
	PrevHead string `json:"prev_head,omitempty"`
	GitLog   string `json:"git_log,omitempty"`
	Release  string `json:"release,omitempty"`
}

func HerokuOutMessageFromQueryString(queryString string) (HerokuOutMessage, error) {
	msg := HerokuOutMessage{}
	values, err := url.ParseQuery(queryString)
	if err != nil {
		return msg, err
	}
	if len(strings.TrimSpace(values.Get("app"))) > 0 {
		msg.App = strings.TrimSpace(values.Get("app"))
	}
	if len(strings.TrimSpace(values.Get("user"))) > 0 {
		msg.User = strings.TrimSpace(values.Get("user"))
	}
	if len(strings.TrimSpace(values.Get("url"))) > 0 {
		msg.URL = strings.TrimSpace(values.Get("url"))
	}
	if len(strings.TrimSpace(values.Get("head"))) > 0 {
		msg.Head = strings.TrimSpace(values.Get("head"))
	}
	if len(strings.TrimSpace(values.Get("head_long"))) > 0 {
		msg.HeadLong = strings.TrimSpace(values.Get("head_long"))
	}
	if len(strings.TrimSpace(values.Get("prev_head"))) > 0 {
		msg.PrevHead = strings.TrimSpace(values.Get("prev_head"))
	}
	if len(strings.TrimSpace(values.Get("git_log"))) > 0 {
		msg.GitLog = strings.TrimSpace(values.Get("git_log"))
	}
	if len(strings.TrimSpace(values.Get("release"))) > 0 {
		msg.Release = strings.TrimSpace(values.Get("release"))
	}
	return msg, nil
}
<|endoftext|>