<commit_before>// Package energy provides a concurrent energy system, useful for games and other applications.
//
// XXX(Liru): Implement these, you idiot https://golang.org/pkg/bytes/
package energy

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

// ErrMaxed is returned when stuff happens.
var ErrMaxed = errors.New("energy is at or over maximum")

// Energy is a consumable and recoverable resource in games.
type Energy struct {
	mtx sync.RWMutex

	current    int
	usedEnergy int
	usedAt     time.Time
	max        int

	recoveryInterval time.Duration
	recoveryQuantity int
}

// New creates and returns a new Energy instance.
func New(quantity int, max int, interval time.Duration) *Energy {
	return &Energy{
		max:              max,
		recoveryInterval: interval,
		recoveryQuantity: quantity,
	}
}

// CurrentEnergy returns the current energy available for use.
func (e *Energy) CurrentEnergy() int {
	return e.current
}

// use is a backend helper that is wrapped by Use and UseAmount.
func (e *Energy) use(i int, force bool) bool {
	e.mtx.Lock()
	defer e.mtx.Unlock()

	if e.current < i && !force {
		return false
	}

	if ((e.current-i < e.max) && (e.max <= e.current)) || force {
		e.usedEnergy = i - e.current + e.max
		e.usedAt = time.Now()
	} else {
		e.usedEnergy = e.max - e.current + i
	}

	return true
}

// Use uses a single instance of energy.
func (e *Energy) Use() bool {
	return e.use(1, false)
}

// UseAmount uses a specified amount of energy.
func (e *Energy) UseAmount(i int) bool {
	return e.use(i, false)
}

// RecoversIn calculates when the next instance of energy will be recovered.
//
// If energy cannot be recovered, RecoversIn returns 0 and an error
// explaining why it cannot be recovered.
func (e *Energy) RecoversIn() time.Duration {
	p := e.passed()
	if p == 0 {
		return 0
	}

	ticks := p / e.recoveryInterval
	if int(ticks) >= e.usedEnergy {
		return 0
	}

	return e.recoveryInterval - (p % e.recoveryInterval)

}

// FullyRecoversIn calculates when the energy instance's capacity will be reached.
//
// If energy cannot be recovered, RecoversIn returns 0 and an error
// explaining why it cannot be recovered.
func (e *Energy) FullyRecoversIn() time.Duration {
	nextRecover := e.RecoversIn()
	if nextRecover == 0 {
		return 0
	}

	ttr := e.max - e.current - 1
	return nextRecover + e.recoveryInterval*time.Duration(ttr)
}

// String satisfies the fmt.Stringer interface.
func (e *Energy) String() string {
	s := fmt.Sprintf("<Energy %d/%d", e.current, e.max)
	if e.current < e.max {
		//TODO: get recovery time and input below
		nextRecover := e.RecoversIn()
		mins, secs := nextRecover.Minutes(), int(nextRecover.Seconds())%60
		s += fmt.Sprintf(" recover in %02d:%02d", mins, secs)
	}
	s += ">"

	return s
}

// SetEnergy sets the current energy to the given value.
//
// It can be used to set the energy over the given maximum.
func (e *Energy) SetEnergy(i int) {
	e.mtx.Lock()
	defer e.mtx.Unlock()

	if i >= e.max {
		e.usedEnergy = e.max - i
		e.usedAt = time.Time{}
	} else {
		e.use(e.current-i, false)
	}
}

func (e *Energy) ResetEnergy() {
	e.SetEnergy(e.max)
}

func (e *Energy) SetMax(i int) {
	e.mtx.Lock()
	defer e.mtx.Unlock()

	e.max = i
	if e.max < e.current {
		e.usedAt = time.Time{}
	}

}

// SetInterval sets the interval at which energy is regained.
func (e *Energy) SetInterval(i time.Duration) {
	e.mtx.Lock()
	defer e.mtx.Unlock()

	e.recoveryInterval = i
}

// ===== Helper functions ahoy.  =====

// passed indicates how much time has passed since the last time this energy was used.
func (e *Energy) passed() time.Duration {
	if e.usedAt.IsZero() {
		return 0
	}
	return time.Since(e.usedAt)
}

// recovered indicates how much energy has been recovered since the last use.
func (e *Energy) recovered() int {
	p := e.passed()
	if p == 0 {
		return 0
	}

	intervals := int(e.passed() / e.recoveryInterval)
	rec := intervals * e.recoveryQuantity

	if rec > e.usedEnergy {
		return e.usedEnergy
	}
	return rec
}
<commit_msg>Fix minor things<commit_after>// Package energy provides a concurrent energy system, useful for games and other applications.
//
// XXX(Liru): Implement these, you idiot https://golang.org/pkg/bytes/
package energy

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

// ErrMaxed is returned when stuff happens.
var ErrMaxed = errors.New("energy is at or over maximum")

// Energy is a consumable and recoverable resource in games.
type Energy struct {
	mtx sync.RWMutex

	usedEnergy int
	usedAt     time.Time
	max        int

	recoveryInterval time.Duration
	recoveryQuantity int
}

// New creates and returns a new Energy instance.
func New(initEnergy int, maxEnergy int, interval time.Duration) *Energy {
	return &Energy{
		max:        maxEnergy,
		usedEnergy: maxEnergy - initEnergy,

		recoveryInterval: interval,
		// recoveryQuantity: quantity,
	}
}

// CurrentEnergy returns the current energy available for use.
func (e *Energy) CurrentEnergy() int {
	if e.usedAt.IsZero() {
		return e.max
	}
	return e.max - e.usedEnergy + e.recovered()
}

// Use uses a single instance of energy.
func (e *Energy) Use() bool {
	return e.use(1, false)
}

// UseAmount uses a specified amount of energy.
func (e *Energy) UseAmount(i int) bool {
	return e.use(i, false)
}

// RecoversIn calculates when the next instance of energy will be recovered.
//
// If energy cannot be recovered, RecoversIn returns 0 and an error
// explaining why it cannot be recovered.
func (e *Energy) RecoversIn() time.Duration {
	p := e.passed()
	if p == 0 {
		return 0
	}

	ticks := p / e.recoveryInterval
	if int(ticks) >= e.usedEnergy {
		return 0
	}

	return e.recoveryInterval - (p % e.recoveryInterval)

}

// FullyRecoversIn calculates when the energy instance's capacity will be reached.
//
// If energy cannot be recovered, RecoversIn returns 0 and an error
// explaining why it cannot be recovered.
func (e *Energy) FullyRecoversIn() time.Duration {
	nextRecover := e.RecoversIn()
	if nextRecover == 0 {
		return 0
	}

	ttr := e.max - e.CurrentEnergy() - 1
	return nextRecover + e.recoveryInterval*time.Duration(ttr)
}

// String satisfies the fmt.Stringer interface.
func (e *Energy) String() string {
	s := fmt.Sprintf("<Energy %d/%d", e.CurrentEnergy, e.max)
	if e.CurrentEnergy() < e.max {
		//TODO: get recovery time and input below
		nextRecover := e.RecoversIn()
		mins, secs := nextRecover.Minutes(), int(nextRecover.Seconds())%60
		s += fmt.Sprintf(" recover in %02d:%02d", mins, secs)
	}
	s += ">"

	return s
}

// SetEnergy sets the current energy to the given value.
//
// It can be used to set the energy over the given maximum.
func (e *Energy) SetEnergy(i int) {
	e.mtx.Lock()
	defer e.mtx.Unlock()

	if i >= e.max {
		e.usedEnergy = e.max - i
		e.usedAt = time.Time{}
	} else {
		e.use(e.CurrentEnergy()-i, false)
	}
}

func (e *Energy) ResetEnergy() {
	e.SetEnergy(e.max)
}

func (e *Energy) SetMax(i int) {
	e.mtx.Lock()
	defer e.mtx.Unlock()

	e.max = i
	if e.max < e.CurrentEnergy() {
		e.usedAt = time.Time{}
	}

}

// SetInterval sets the interval at which energy is regained.
func (e *Energy) SetInterval(i time.Duration) {
	e.mtx.Lock()
	defer e.mtx.Unlock()

	e.recoveryInterval = i
}

// ===== Helper functions ahoy.  =====

// passed indicates how much time has passed since the last time this energy was used.
func (e *Energy) passed() time.Duration {
	if e.usedAt.IsZero() {
		return 0
	}
	return time.Since(e.usedAt)
}

// recovered indicates how much energy has been recovered since the last use.
func (e *Energy) recovered() int {
	p := e.passed()
	if p == 0 {
		return 0
	}

	intervals := int(e.passed() / e.recoveryInterval)
	rec := intervals * e.recoveryQuantity

	if rec > e.usedEnergy {
		return e.usedEnergy
	}
	return rec
}

// use is a backend helper that is wrapped by Use and UseAmount.
func (e *Energy) use(i int, force bool) bool {
	e.mtx.Lock()
	defer e.mtx.Unlock()

	if e.CurrentEnergy() < i && !force {
		return false
	}

	if ((e.CurrentEnergy()-i < e.max) && (e.max <= e.CurrentEnergy())) || force {
		e.usedEnergy = i - e.CurrentEnergy() + e.max
		e.usedAt = time.Now()
	} else {
		e.usedEnergy = e.max - e.CurrentEnergy() + i
	}

	return true
}
<|endoftext|>


<commit_before>package jig

import (
	//"errors"
	"fmt"
	"github.com/bitly/go-simplejson"
	"github.com/nsf/termbox-go"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

const (
	PROMPT              = "[jig]>> "
	DefaultY     int    = 1
	FilterPrompt string = "[Filter]> "
)

var (
	f        *[]rune
	complete *[]rune
)

type Engine struct {
	json       *simplejson.Json
	orgJson    *simplejson.Json
	currenKeys []string
	query      bool
	pretty     bool
}

func NewEngine(s *os.File, q bool, p bool) *Engine {
	j := parse(s)
	e := &Engine{
		json:       j,
		orgJson:    j,
		currenKeys: []string{},
		query:      q,
		pretty:     p,
	}
	return e
}

func (e Engine) Run() int {

	if !e.render(e.json) {
		return 2
	}
	if e.query {
		fmt.Printf("%s", string(*f))
	} else if e.pretty {
		s, err := e.json.EncodePretty()
		if err != nil {
			return 1
		}
		fmt.Printf("%s", string(s))
	} else {
		s, err := e.json.Encode()
		if err != nil {
			return 1
		}
		fmt.Printf("%s", s)
	}
	return 0
}

func parse(content *os.File) *simplejson.Json {
	buf, err := ioutil.ReadAll(content)

	if err != nil {
		log.Fatal(err)
	}

	js, err := simplejson.NewJson(buf)

	if err != nil {
		log.Fatal(err)
	}

	return js
}

// fix:me
func (e *Engine) render(json *simplejson.Json) bool {

	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()

	f = &[]rune{}
	complete = &[]rune{}

	contents := e.prettyContents()
	keymode := false

	for {
		e.filterJson(string(*f))
		e.suggest()
		if keymode {
			contents = e.currenKeys
		} else {
			contents = e.prettyContents()
		}
		draw(contents)
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			switch ev.Key {
			case termbox.KeyEsc, termbox.KeyCtrlC:
				return false
			case termbox.KeyCtrlK:
				keymode = !keymode
			case termbox.KeySpace:
				*f = append(*f, rune(' '))
			case termbox.KeyCtrlW:
				//delete whole word to period
				s := string(*f)
				kws := strings.Split(s, ".")
				lki := len(kws) - 1
				_, kws = kws[lki], kws[:lki]
				s = strings.Join(kws, ".")
				*f = []rune(s[0:len(s)])
			case termbox.KeyBackspace, termbox.KeyBackspace2:
				if i := len(*f) - 1; i >= 0 {
					slice := *f
					*f = slice[0:i]
				}
			case termbox.KeyTab:
				if len(*complete) > 0 {
					e.autoComplete()
				} else {
					e.suggest()
				}
			case termbox.KeyEnter:
				return true
			case 0:
				*f = append(*f, rune(ev.Ch))
			default:
			}
		case termbox.EventError:
			panic(ev.Err)
			break
		default:
		}
	}
}

func (e *Engine) autoComplete() {
	*f = append(*f, *complete...)
	*complete = []rune("")
}

func (e *Engine) suggest() bool {
	s := string(*f)
	if arr, _ := e.json.Array(); arr != nil && len(arr) == 1 {
		if s[len(s)-1:] == "." {
			*complete = []rune("")
			return false
		}
		ds := regexp.MustCompile("\\[0?\\]?$").FindString(s)
		cs := strings.Replace("[0]", ds, "", -1)
		*complete = []rune(cs)
		return true
	}
	kws := strings.Split(s, ".")
	lki := len(kws) - 1
	if lki == 0 {
		return false
	}
	lkw, tkws := kws[lki], kws[:lki]

	var c []int
	re, err := regexp.Compile("(?i)^" + lkw)
	if err != nil {
		return false
	}

	for i, k := range e.currenKeys {
		if str := re.FindString(k); str != "" {
			c = append(c, i)
			lkw = str
		}
	}

	if len(c) == 1 {
		kw := re.ReplaceAllString(e.currenKeys[c[0]], "")
		*complete = []rune(kw)
		s = strings.Join(tkws, ".") + "." + lkw
		*f = []rune(s)
		return true
	}
	*complete = []rune("")
	return false
}

func (e *Engine) prettyContents() []string {
	s, _ := e.json.EncodePretty()
	return strings.Split(string(s), "\n")
}

func (e *Engine) filterJson(q string) {
	json := e.orgJson
	if len(q) < 1 {
		e.json = json
		e.setCurrentKeys()
		return
	}
	keywords := strings.Split(q, ".")

	// check start "."
	if keywords[0] != "" {
		e.json = json
		return
	}

	keywords = keywords[1:]

	re := regexp.MustCompile("\\[[0-9]*\\]")
	delre := regexp.MustCompile("\\[([0-9]+)?")

	j := json
	lastIdx := len(keywords) - 1

	//eachFlg := false
	for ki, keyword := range keywords {
		if len(keyword) == 0 {
			if ki != lastIdx {
				e.json = simplejson.New()
			}
			break
		}

		// abc[0]
		if keyword[:1] == "[" {
			break
		}
		if keyword[len(keyword)-1:] == "]" {
			matchIndexes := re.FindAllStringIndex(keyword, -1)
			kw := re.ReplaceAllString(keyword, "")

			tj := j.Get(kw)
			if ki != lastIdx {
				j = tj
			} else if !isEmptyJson(tj) {
				j = tj
			}
			lmi := len(matchIndexes) - 1
			for idx, m := range matchIndexes {
				i, _ := strconv.Atoi(keyword[m[0]+1 : m[1]-1])
				if idx == lmi && m[1]-m[0] == 2 {
					//eachFlg = true
				} else if tj := j.GetIndex(i); !isEmptyJson(tj) {
					j = tj
				}
			}
		} else {
			kw := delre.ReplaceAllString(keyword, "")
			if tj := j.Get(kw); !isEmptyJson(tj) {
				j = tj
			}
		}
	}
	json = j
	e.json = json
	e.setCurrentKeys()
}

func (e *Engine) setCurrentKeys() {
	m, err := e.json.Map()
	if err != nil {
		// is array
	}
	keys := []string{}
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	e.currenKeys = keys
}

func isEmptyJson(j *simplejson.Json) bool {
	switch j.Interface().(type) {
	case nil:
		return true
	default:
		return false
	}
}

func draw(rows []string) {

	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)

	fs := FilterPrompt + string(*f)
	cs := string(*complete)
	drawln(0, 0, fs+cs, []([]int){[]int{len(fs), len(fs) + len(cs)}})
	termbox.SetCursor(len(fs), 0)

	for idx, row := range rows {
		drawln(0, idx+DefaultY, row, nil)
	}

	termbox.Flush()
}

func drawln(x int, y int, str string, matches [][]int) {
	color := termbox.ColorDefault
	backgroundColor := termbox.ColorDefault

	var c termbox.Attribute
	for i, s := range str {
		c = color
		for _, match := range matches {
			if i >= match[0] && i < match[1] {
				c = termbox.ColorGreen
			}
		}
		termbox.SetCell(x+i, y, s, c, backgroundColor)
	}
}
<commit_msg>0.2.2 improve suggest interface<commit_after>package jig

import (
	//"errors"
	"fmt"
	"github.com/bitly/go-simplejson"
	"github.com/nsf/termbox-go"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

const (
	PROMPT              = "[jig]>> "
	DefaultY     int    = 1
	FilterPrompt string = "[Filter]> "
)

var (
	f        *[]rune
	complete *[]rune
)

type Engine struct {
	json       *simplejson.Json
	orgJson    *simplejson.Json
	currenKeys []string
	query      bool
	pretty     bool
}

func NewEngine(s *os.File, q bool, p bool) *Engine {
	j := parse(s)
	e := &Engine{
		json:       j,
		orgJson:    j,
		currenKeys: []string{},
		query:      q,
		pretty:     p,
	}
	return e
}

func (e Engine) Run() int {

	if !e.render(e.json) {
		return 2
	}
	if e.query {
		fmt.Printf("%s", string(*f))
	} else if e.pretty {
		s, err := e.json.EncodePretty()
		if err != nil {
			return 1
		}
		fmt.Printf("%s", string(s))
	} else {
		s, err := e.json.Encode()
		if err != nil {
			return 1
		}
		fmt.Printf("%s", s)
	}
	return 0
}

func parse(content *os.File) *simplejson.Json {
	buf, err := ioutil.ReadAll(content)

	if err != nil {
		log.Fatal(err)
	}

	js, err := simplejson.NewJson(buf)

	if err != nil {
		log.Fatal(err)
	}

	return js
}

// fix:me
func (e *Engine) render(json *simplejson.Json) bool {

	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()

	f = &[]rune{}
	complete = &[]rune{}

	contents := e.prettyContents()
	keymode := false

	for {
		e.filterJson(string(*f))
		e.suggest()
		if keymode {
			contents = e.currenKeys
		} else {
			contents = e.prettyContents()
		}
		draw(contents)
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			switch ev.Key {
			case termbox.KeyEsc, termbox.KeyCtrlC:
				return false
			case termbox.KeyCtrlK:
				keymode = !keymode
			case termbox.KeySpace:
				*f = append(*f, rune(' '))
			case termbox.KeyCtrlW:
				//delete whole word to period
				s := string(*f)
				kws := strings.Split(s, ".")
				lki := len(kws) - 1
				_, kws = kws[lki], kws[:lki]
				s = strings.Join(kws, ".")
				*f = []rune(s[0:len(s)])
			case termbox.KeyBackspace, termbox.KeyBackspace2:
				if i := len(*f) - 1; i >= 0 {
					slice := *f
					*f = slice[0:i]
				}
			case termbox.KeyTab:
				if len(*complete) > 0 {
					e.autoComplete()
				} else {
					e.suggest()
				}
			case termbox.KeyEnter:
				return true
			case 0:
				*f = append(*f, rune(ev.Ch))
			default:
			}
		case termbox.EventError:
			panic(ev.Err)
			break
		default:
		}
	}
}

func (e *Engine) autoComplete() {
	*f = append(*f, *complete...)
	*complete = []rune("")
}

func (e *Engine) suggest() bool {
	s := string(*f)
	if arr, _ := e.json.Array(); arr != nil {
		le := s[len(s)-1:]
		if le == "." {
			*complete = []rune("")
			return false
		}
		var rs string
		ds := regexp.MustCompile("\\[([0-9]*)?\\]?$").FindString(s)
		if len(arr) > 1 {
			if ds == "" {
				rs = "["
			} else if le != "]" {
				rs = "]"
			}
		} else {
			rs = "[0]"
		}
		cs := strings.Replace(rs, ds, "", -1)
		*complete = []rune(cs)
		return true
	}
	kws := strings.Split(s, ".")
	lki := len(kws) - 1
	if lki == 0 {
		return false
	}
	lkw, tkws := kws[lki], kws[:lki]

	re, err := regexp.Compile("(?i)^" + lkw)
	if err != nil {
		return false
	}
	m := e.getFilteredCurrentKeys(lkw)

	if len(m) == 1 {
		for k := range m {
			kw := re.ReplaceAllString(e.currenKeys[k], "")
			*complete = []rune(kw)
			s = strings.Join(tkws, ".") + "." + m[k]
		}
		*f = []rune(s)
		return true
	}
	//	else {
	//		km := map[string]int{}
	//		var sw string
	//		for k, v := range m {
	//			sw = v
	//			km[v] = k
	//		}
	//		if len(km) == 1 {
	//			kw := re.ReplaceAllString(sw, "")
	//			*complete = []rune(kw)
	//			s = strings.Join(tkws, ".") + "." + lkw
	//		}
	//	}
	*complete = []rune("")
	return false
}

func (e *Engine) getFilteredCurrentKeys(kw string) map[int]string {
	m := map[int]string{}

	re, err := regexp.Compile("(?i)^" + kw)
	if err != nil {
		return m
	}

	for i, k := range e.currenKeys {
		if str := re.FindString(k); str != "" {
			m[i] = str
		}
	}

	return m
}

func (e *Engine) prettyContents() []string {
	s, _ := e.json.EncodePretty()
	return strings.Split(string(s), "\n")
}

func (e *Engine) filterJson(q string) {
	e.json = e.orgJson
	e.setCurrentKeys()
	if len(q) < 1 {
		return
	}
	keywords := strings.Split(q, ".")

	// check start "."
	if keywords[0] != "" {
		return
	}

	keywords = keywords[1:]

	re := regexp.MustCompile("\\[[0-9]*\\]")
	delre := regexp.MustCompile("\\[([0-9]+)?")

	j := e.json
	lastIdx := len(keywords) - 1

	//eachFlg := false
	for ki, keyword := range keywords {
		if len(keyword) == 0 {
			if ki != lastIdx {
				e.json = simplejson.New()
			}
			break
		}
		// abc[0]
		if keyword[:1] == "[" {
			break
		}
		if keyword[len(keyword)-1:] == "]" {
			matchIndexes := re.FindAllStringIndex(keyword, -1)
			kw := re.ReplaceAllString(keyword, "")

			tj := j.Get(kw)
			if ki != lastIdx {
				j = tj
			} else if !isEmptyJson(tj) {
				j = tj
			}
			lmi := len(matchIndexes) - 1
			for idx, m := range matchIndexes {
				i, _ := strconv.Atoi(keyword[m[0]+1 : m[1]-1])
				if idx == lmi && m[1]-m[0] == 2 {
					//eachFlg = true
				} else if tj := j.GetIndex(i); !isEmptyJson(tj) {
					j = tj
				}
			}
		} else {
			kw := delre.ReplaceAllString(keyword, "")
			tj := j.Get(kw)
			if ki != lastIdx {
				j = tj
				// kokoni
			} else if len(e.getFilteredCurrentKeys(kw)) < 1 {
				j = tj
			}
		}
	}
	e.json = j
	e.setCurrentKeys()
}

func (e *Engine) setCurrentKeys() {
	m, err := e.json.Map()
	if err != nil {
		// is array
	}
	keys := []string{}
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	e.currenKeys = keys
}

func isEmptyJson(j *simplejson.Json) bool {
	switch j.Interface().(type) {
	case nil:
		return true
	default:
		return false
	}
}

func draw(rows []string) {

	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)

	fs := FilterPrompt + string(*f)
	cs := string(*complete)
	drawln(0, 0, fs+cs, []([]int){[]int{len(fs), len(fs) + len(cs)}})
	termbox.SetCursor(len(fs), 0)

	for idx, row := range rows {
		drawln(0, idx+DefaultY, row, nil)
	}

	termbox.Flush()
}

func drawln(x int, y int, str string, matches [][]int) {
	color := termbox.ColorDefault
	backgroundColor := termbox.ColorDefault

	var c termbox.Attribute
	for i, s := range str {
		c = color
		for _, match := range matches {
			if i >= match[0] && i < match[1] {
				c = termbox.ColorGreen
			}
		}
		termbox.SetCell(x+i, y, s, c, backgroundColor)
	}
}
<|endoftext|>


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

package logs

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"regexp"
	"strconv"
	"time"
)

// importTimeFromLine import a datetime information from the beginning
// of kontext applog. Because KonText does not log a timezone information
// it must be passed here to produce proper datetime.
//
// In case of an error, -1 is returned
func importTimeFromLine(lineStr string, timezoneStr string) int64 {
	rg := regexp.MustCompile("^(\\d{4}-\\d{2}-\\d{2}\\s[012]\\d:[0-5]\\d:[0-5]\\d)[\\.,]\\d+")
	srch := rg.FindStringSubmatch(lineStr)
	if len(srch) > 0 {
		if t, err := time.Parse("2006-01-02 15:04:05-07:00", srch[1]+timezoneStr); err == nil {
			return t.Unix()
		}
	}
	return -1
}

// getFileMtime returns file's UNIX mtime (in secods).
// In case of an error, -1 is returned
func getFileMtime(filePath string) int64 {
	f, err := os.Open(filePath)
	if err != nil {
		return -1
	}
	finfo, err := f.Stat()
	if err == nil {
		return finfo.ModTime().Unix()
	}
	return -1
}

// LogFileMatches tests whether the log file specified by filePath matches
// in terms of its first record (whether it is older than the 'minTimestamp').
// If strictMatch is false then in case of non matching file, also its mtime
// is tested.
//
// The function expects that the first line on any log file contains proper
// log record which should be OK (KonText also writes multi-line error dumps
// to the log but it always starts with a proper datetime information).
func LogFileMatches(filePath string, minTimestamp int64, strictMatch bool, timezoneStr string) (bool, error) {
	f, err := os.Open(filePath)
	if err != nil {
		return false, err
	}
	rd := bufio.NewScanner(f)
	rd.Scan()
	line := rd.Text()
	startTime := importTimeFromLine(line, timezoneStr)

	if startTime < minTimestamp && !strictMatch {
		startTime = getFileMtime(filePath)
	}

	return startTime >= minTimestamp, nil
}

// GetFilesInDir lists all the matching log files
func GetFilesInDir(dirPath string, minTimestamp int64, strictMatch bool, timezoneStr string) []string {
	tmp, err := ioutil.ReadDir(dirPath)
	var ans []string
	if err == nil {
		ans = make([]string, len(tmp))
		i := 0
		for _, item := range tmp {
			logPath := path.Join(dirPath, item.Name())
			matches, merr := LogFileMatches(logPath, minTimestamp, strictMatch, timezoneStr)
			if merr != nil {
				log.Println("Failed to check log file ", logPath)

			} else if matches {
				ans[i] = logPath
				i++
			}
		}
		return ans[:i]
	}
	return []string{}
}

// ------------------------ worklog ---------

func worklogExists(filePath string) bool {
	_, err := os.Stat(filePath)
	return !os.IsNotExist(err)
}

// Worklog represents an object keeping track
// of the last time the Save() has been called.
// It is used to find new log files which have
// not been yet processed.
type Worklog struct {
	filePath string
}

// GetLastRecord returns a last UNIX timestamp
// generated by the Save() method.
//
// In case no real log exists in filesystem,
// -1 is returned.
func (w *Worklog) GetLastRecord() int64 {
	if worklogExists(w.filePath) {
		f, err := os.Open(w.filePath)
		if err != nil {
			panic(fmt.Sprintf("Failed to open existing worklog file: %s", err))
		}
		defer f.Close()
		reader := bufio.NewScanner(f)
		lastLine := ""
		if reader != nil {
			for reader.Scan() {
				lastLine = reader.Text()
			}
		}
		if lastLine != "" {
			ans, err := strconv.ParseInt(lastLine, 10, 64)
			if err == nil {
				return ans
			}
		}

	} else {
		log.Println("No worklog file present - all the found log files will be processed")
	}
	return -1
}

// Save saves the worklog by writing actual UNIX timestamp
// to the log file.
func (w *Worklog) Save() error {
	f, err := os.OpenFile(w.filePath, os.O_APPEND, 0) // TODO FileMode
	if err != nil {
		panic(fmt.Sprintf("Failed to open worklog file for writing: %s", err))
	}
	defer f.Close()
	writer := bufio.NewWriter(f)
	defer writer.Flush()
	_, err = writer.WriteString(fmt.Sprintf("%d\n", time.Now().Unix()))
	return err
}

// NewWorklog creates an instance of Worklog with
// defined path. No file access attempts are made.
func NewWorklog(path string) *Worklog {
	return &Worklog{filePath: path}
}
<commit_msg>Fix file open mode<commit_after>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package logs

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"regexp"
	"strconv"
	"time"
)

// importTimeFromLine import a datetime information from the beginning
// of kontext applog. Because KonText does not log a timezone information
// it must be passed here to produce proper datetime.
//
// In case of an error, -1 is returned
func importTimeFromLine(lineStr string, timezoneStr string) int64 {
	rg := regexp.MustCompile("^(\\d{4}-\\d{2}-\\d{2}\\s[012]\\d:[0-5]\\d:[0-5]\\d)[\\.,]\\d+")
	srch := rg.FindStringSubmatch(lineStr)
	if len(srch) > 0 {
		if t, err := time.Parse("2006-01-02 15:04:05-07:00", srch[1]+timezoneStr); err == nil {
			return t.Unix()
		}
	}
	return -1
}

// getFileMtime returns file's UNIX mtime (in secods).
// In case of an error, -1 is returned
func getFileMtime(filePath string) int64 {
	f, err := os.Open(filePath)
	if err != nil {
		return -1
	}
	finfo, err := f.Stat()
	if err == nil {
		return finfo.ModTime().Unix()
	}
	return -1
}

// LogFileMatches tests whether the log file specified by filePath matches
// in terms of its first record (whether it is older than the 'minTimestamp').
// If strictMatch is false then in case of non matching file, also its mtime
// is tested.
//
// The function expects that the first line on any log file contains proper
// log record which should be OK (KonText also writes multi-line error dumps
// to the log but it always starts with a proper datetime information).
func LogFileMatches(filePath string, minTimestamp int64, strictMatch bool, timezoneStr string) (bool, error) {
	f, err := os.Open(filePath)
	if err != nil {
		return false, err
	}
	rd := bufio.NewScanner(f)
	rd.Scan()
	line := rd.Text()
	startTime := importTimeFromLine(line, timezoneStr)

	if startTime < minTimestamp && !strictMatch {
		startTime = getFileMtime(filePath)
	}

	return startTime >= minTimestamp, nil
}

// GetFilesInDir lists all the matching log files
func GetFilesInDir(dirPath string, minTimestamp int64, strictMatch bool, timezoneStr string) []string {
	tmp, err := ioutil.ReadDir(dirPath)
	var ans []string
	if err == nil {
		ans = make([]string, len(tmp))
		i := 0
		for _, item := range tmp {
			logPath := path.Join(dirPath, item.Name())
			matches, merr := LogFileMatches(logPath, minTimestamp, strictMatch, timezoneStr)
			if merr != nil {
				log.Println("Failed to check log file ", logPath)

			} else if matches {
				ans[i] = logPath
				i++
			}
		}
		return ans[:i]
	}
	return []string{}
}

// ------------------------ worklog ---------

func worklogExists(filePath string) bool {
	_, err := os.Stat(filePath)
	return !os.IsNotExist(err)
}

// Worklog represents an object keeping track
// of the last time the Save() has been called.
// It is used to find new log files which have
// not been yet processed.
type Worklog struct {
	filePath string
}

// GetLastRecord returns a last UNIX timestamp
// generated by the Save() method.
//
// In case no real log exists in filesystem,
// -1 is returned.
func (w *Worklog) GetLastRecord() int64 {
	if worklogExists(w.filePath) {
		f, err := os.Open(w.filePath)
		if err != nil {
			panic(fmt.Sprintf("Failed to open existing worklog file: %s", err))
		}
		defer f.Close()
		reader := bufio.NewScanner(f)
		lastLine := ""
		if reader != nil {
			for reader.Scan() {
				lastLine = reader.Text()
			}
		}
		if lastLine != "" {
			ans, err := strconv.ParseInt(lastLine, 10, 64)
			if err == nil {
				return ans
			}
		}

	} else {
		log.Println("No worklog file present - all the found log files will be processed")
	}
	return -1
}

// Save saves the worklog by writing actual UNIX timestamp
// to the log file.
func (w *Worklog) Save() error {
	f, err := os.OpenFile(w.filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		panic(fmt.Sprintf("Failed to open worklog file for writing: %s", err))
	}
	defer f.Close()
	writer := bufio.NewWriter(f)
	defer writer.Flush()
	_, err = writer.WriteString(fmt.Sprintf("%d\n", time.Now().Unix()))
	return err
}

// NewWorklog creates an instance of Worklog with
// defined path. No file access attempts are made.
func NewWorklog(path string) *Worklog {
	return &Worklog{filePath: path}
}
<|endoftext|>


<commit_before>package tricorder

import (
	"github.com/Symantec/tricorder/go/tricorder/units"
	"github.com/Symantec/tricorder/go/tricorder/wrapper"
	"os"
	"runtime"
	"strings"
	"syscall"
	"time"
)

func getProgramArgs() string {
	return strings.Join(os.Args[1:], "|")
}

func timeValToDuration(val *wrapper.Timeval) time.Duration {
	return time.Duration(val.Sec)*time.Second + time.Duration(val.Usec)*time.Nanosecond
}

func initDefaultMetrics() {
	programArgs := getProgramArgs()
	var totalMemory uint64
	memStatsGroup := NewGroup()
	memStatsGroup.RegisterUpdateFunc(func() time.Time {
		var memStats runtime.MemStats
		runtime.ReadMemStats(&memStats)
		if memStats.Sys >= memStats.HeapReleased {
			totalMemory = memStats.Sys - memStats.HeapReleased
		}
		return time.Now()
	})
	RegisterMetricInGroup(
		"/proc/memory/total",
		&totalMemory,
		memStatsGroup,
		units.Byte,
		"System memory currently allocated to process")
	var numGoroutines int
	var resourceUsage wrapper.Rusage
	var userTime time.Duration
	var sysTime time.Duration
	var maxResidentSetSize int64
	resourceUsageGroup := NewGroup()
	resourceUsageGroup.RegisterUpdateFunc(func() time.Time {
		numGoroutines = runtime.NumGoroutine()
		wrapper.Getrusage(syscall.RUSAGE_SELF, &resourceUsage)
		userTime = timeValToDuration(&resourceUsage.Utime)
		sysTime = timeValToDuration(&resourceUsage.Stime)
		maxResidentSetSize = resourceUsage.Maxrss
		return time.Now()
	})
	RegisterMetricInGroup(
		"/proc/cpu/user",
		&userTime,
		resourceUsageGroup,
		units.Second,
		"User CPU time used")
	RegisterMetricInGroup(
		"/proc/cpu/sys",
		&sysTime,
		resourceUsageGroup,
		units.Second,
		"User CPU time used")
	RegisterMetricInGroup(
		"/proc/go/num-goroutines",
		&numGoroutines,
		resourceUsageGroup,
		units.None,
		"Number of goroutines")
	RegisterMetricInGroup(
		"/proc/memory/max-resident-set-size",
		&maxResidentSetSize,
		resourceUsageGroup,
		units.Byte,
		"Maximum resident set size")
	RegisterMetricInGroup(
		"/proc/memory/shared",
		&resourceUsage.Ixrss,
		resourceUsageGroup,
		units.Byte,
		"Integral shared memory size")
	RegisterMetricInGroup(
		"/proc/memory/unshared-data",
		&resourceUsage.Idrss,
		resourceUsageGroup,
		units.Byte,
		"Integral unshared data size")
	RegisterMetricInGroup(
		"/proc/memory/unshared-stack",
		&resourceUsage.Isrss,
		resourceUsageGroup,
		units.Byte,
		"Integral unshared stack size")
	RegisterMetricInGroup(
		"/proc/memory/soft-page-fault",
		&resourceUsage.Minflt,
		resourceUsageGroup,
		units.None,
		"Soft page faults")
	RegisterMetricInGroup(
		"/proc/memory/hard-page-fault",
		&resourceUsage.Majflt,
		resourceUsageGroup,
		units.None,
		"Hard page faults")
	RegisterMetricInGroup(
		"/proc/memory/swaps",
		&resourceUsage.Nswap,
		resourceUsageGroup,
		units.None,
		"Swaps")
	RegisterMetricInGroup(
		"/proc/io/input",
		&resourceUsage.Inblock,
		resourceUsageGroup,
		units.None,
		"Block input operations")
	RegisterMetricInGroup(
		"/proc/io/output",
		&resourceUsage.Oublock,
		resourceUsageGroup,
		units.None,
		"Block output operations")
	RegisterMetricInGroup(
		"/proc/ipc/sent",
		&resourceUsage.Msgsnd,
		resourceUsageGroup,
		units.None,
		"IPC messages sent")
	RegisterMetricInGroup(
		"/proc/ipc/received",
		&resourceUsage.Msgrcv,
		resourceUsageGroup,
		units.None,
		"IPC messages received")
	RegisterMetricInGroup(
		"/proc/signals/received",
		&resourceUsage.Nsignals,
		resourceUsageGroup,
		units.None,
		"Signals received")
	RegisterMetricInGroup(
		"/proc/scheduler/involuntary-switches",
		&resourceUsage.Nivcsw,
		resourceUsageGroup,
		units.None,
		"Involuntary context switches")
	RegisterMetricInGroup(
		"/proc/scheduler/voluntary-switches",
		&resourceUsage.Nvcsw,
		resourceUsageGroup,
		units.None,
		"Voluntary context switches")
	RegisterMetric("/proc/name", &os.Args[0], units.None, "Program name")
	RegisterMetric("/proc/args", &programArgs, units.None, "Program args")
	RegisterMetric("/proc/start-time", &appStartTime, units.None, "Program start time")
}

func init() {
	initDefaultMetrics()
	initHttpFramework()
	initHtmlHandlers()
	initJsonHandlers()
	initRpcHandlers()
}
<commit_msg>Add /proc/io/num-open-file-descriptors built-in metric.<commit_after>package tricorder

import (
	"github.com/Symantec/tricorder/go/tricorder/units"
	"github.com/Symantec/tricorder/go/tricorder/wrapper"
	"os"
	"runtime"
	"strings"
	"syscall"
	"time"
)

func getProgramArgs() string {
	return strings.Join(os.Args[1:], "|")
}

func timeValToDuration(val *wrapper.Timeval) time.Duration {
	return time.Duration(val.Sec)*time.Second + time.Duration(val.Usec)*time.Nanosecond
}

func initDefaultMetrics() {
	programArgs := getProgramArgs()
	var totalMemory uint64
	memStatsGroup := NewGroup()
	memStatsGroup.RegisterUpdateFunc(func() time.Time {
		var memStats runtime.MemStats
		runtime.ReadMemStats(&memStats)
		if memStats.Sys >= memStats.HeapReleased {
			totalMemory = memStats.Sys - memStats.HeapReleased
		}
		return time.Now()
	})
	RegisterMetricInGroup(
		"/proc/memory/total",
		&totalMemory,
		memStatsGroup,
		units.Byte,
		"System memory currently allocated to process")
	var numGoroutines int
	numOpenFileDescriptors := countOpenFileDescriptors()
	var resourceUsage wrapper.Rusage
	var userTime time.Duration
	var sysTime time.Duration
	var maxResidentSetSize int64
	resourceUsageGroup := NewGroup()
	resourceUsageGroup.RegisterUpdateFunc(func() time.Time {
		numGoroutines = runtime.NumGoroutine()
		numOpenFileDescriptors = countOpenFileDescriptors()
		wrapper.Getrusage(syscall.RUSAGE_SELF, &resourceUsage)
		userTime = timeValToDuration(&resourceUsage.Utime)
		sysTime = timeValToDuration(&resourceUsage.Stime)
		maxResidentSetSize = resourceUsage.Maxrss
		return time.Now()
	})
	RegisterMetricInGroup(
		"/proc/cpu/user",
		&userTime,
		resourceUsageGroup,
		units.Second,
		"User CPU time used")
	RegisterMetricInGroup(
		"/proc/cpu/sys",
		&sysTime,
		resourceUsageGroup,
		units.Second,
		"User CPU time used")
	RegisterMetricInGroup(
		"/proc/go/num-goroutines",
		&numGoroutines,
		resourceUsageGroup,
		units.None,
		"Number of goroutines")
	RegisterMetricInGroup(
		"/proc/memory/max-resident-set-size",
		&maxResidentSetSize,
		resourceUsageGroup,
		units.Byte,
		"Maximum resident set size")
	RegisterMetricInGroup(
		"/proc/memory/shared",
		&resourceUsage.Ixrss,
		resourceUsageGroup,
		units.Byte,
		"Integral shared memory size")
	RegisterMetricInGroup(
		"/proc/memory/unshared-data",
		&resourceUsage.Idrss,
		resourceUsageGroup,
		units.Byte,
		"Integral unshared data size")
	RegisterMetricInGroup(
		"/proc/memory/unshared-stack",
		&resourceUsage.Isrss,
		resourceUsageGroup,
		units.Byte,
		"Integral unshared stack size")
	RegisterMetricInGroup(
		"/proc/memory/soft-page-fault",
		&resourceUsage.Minflt,
		resourceUsageGroup,
		units.None,
		"Soft page faults")
	RegisterMetricInGroup(
		"/proc/memory/hard-page-fault",
		&resourceUsage.Majflt,
		resourceUsageGroup,
		units.None,
		"Hard page faults")
	RegisterMetricInGroup(
		"/proc/memory/swaps",
		&resourceUsage.Nswap,
		resourceUsageGroup,
		units.None,
		"Swaps")
	RegisterMetricInGroup(
		"/proc/io/input",
		&resourceUsage.Inblock,
		resourceUsageGroup,
		units.None,
		"Block input operations")
	if numOpenFileDescriptors >= 0 {
		RegisterMetricInGroup(
			"/proc/io/num-open-file-descriptors",
			&numOpenFileDescriptors,
			resourceUsageGroup,
			units.None,
			"Number of open file descriptors")
	}
	RegisterMetricInGroup(
		"/proc/io/output",
		&resourceUsage.Oublock,
		resourceUsageGroup,
		units.None,
		"Block output operations")
	RegisterMetricInGroup(
		"/proc/ipc/sent",
		&resourceUsage.Msgsnd,
		resourceUsageGroup,
		units.None,
		"IPC messages sent")
	RegisterMetricInGroup(
		"/proc/ipc/received",
		&resourceUsage.Msgrcv,
		resourceUsageGroup,
		units.None,
		"IPC messages received")
	RegisterMetricInGroup(
		"/proc/signals/received",
		&resourceUsage.Nsignals,
		resourceUsageGroup,
		units.None,
		"Signals received")
	RegisterMetricInGroup(
		"/proc/scheduler/involuntary-switches",
		&resourceUsage.Nivcsw,
		resourceUsageGroup,
		units.None,
		"Involuntary context switches")
	RegisterMetricInGroup(
		"/proc/scheduler/voluntary-switches",
		&resourceUsage.Nvcsw,
		resourceUsageGroup,
		units.None,
		"Voluntary context switches")
	RegisterMetric("/proc/name", &os.Args[0], units.None, "Program name")
	RegisterMetric("/proc/args", &programArgs, units.None, "Program args")
	RegisterMetric("/proc/start-time", &appStartTime, units.None, "Program start time")
}

func init() {
	initDefaultMetrics()
	initHttpFramework()
	initHtmlHandlers()
	initJsonHandlers()
	initRpcHandlers()
}

func countOpenFileDescriptors() int {
	fdDir, err := os.Open("/proc/self/fd")
	if err != nil {
		return -1
	}
	defer fdDir.Close()
	if dirNames, err := fdDir.Readdirnames(0); err != nil {
		return -1
	} else {
		return len(dirNames)
	}
}
<|endoftext|>


<commit_before>// Copyright 2019 The LUCI Authors.
//
// 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 exe provides a client for the luciexe protocol which allows you to
// write a luciexe-compliant binary.
package exe
<commit_msg>[luciexe/exe] Add some more useful package docs.<commit_after>// Copyright 2019 The LUCI Authors.
//
// 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 exe implements a client for the LUCI Executable ("luciexe") protocol.
//
// The simplest luciexe is:
//
//   import (
//     "context"
//
//     "go.chromium.org/luci/luciexe/exe"
//
//     bbpb "go.chromium.org/luci/buildbucket/proto"
//   )
//
//   func main() {
//     exe.Run(func(ctx context.Context, input *bbpb.Build, send exe.BuildSender) error {
//       ... do whatever you want here ...
//       return nil // nil error indicates successful build.
//     })
//   }
//
// See Also: https://go.chromium.org/luci/luciexe
package exe
<|endoftext|>


<commit_before>package rbac

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"gopkg.in/macaroon-bakery.v2/bakery"
	"gopkg.in/macaroon-bakery.v2/httpbakery"
	"gopkg.in/macaroon-bakery.v2/httpbakery/agent"

	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
)

type rbacResource struct {
	Identifier string `json:"identifier"`
	Name       string `json:"name"`
}

type rbacResourcePost struct {
	LastSyncID *string        `json:"last-sync-id"`
	Updates    []rbacResource `json:"updates,omitempty"`
	Removals   []string       `json:"removals,omitempty"`
}

type rbacResourcePostResponse struct {
	SyncID string `json:"sync-id"`
}

type rbacStatus struct {
	LastChange string `json:"last-change"`
}

// Errors.
var errUnknownUser = fmt.Errorf("Unknown RBAC user")

// UserAccess struct for permission checks.
type UserAccess struct {
	Admin    bool
	Projects map[string][]string
}

// Server represents an RBAC server.
type Server struct {
	apiURL string
	apiKey string

	lastSyncID string
	client     *httpbakery.Client
	lastChange string

	ctx       context.Context
	ctxCancel context.CancelFunc

	resources     map[string]string // Maps name to identifier
	resourcesLock sync.Mutex

	permissions map[string]map[string][]string

	permissionsLock *sync.Mutex

	ProjectsFunc func() (map[int64]string, error)
}

// NewServer returns a new RBAC server instance.
func NewServer(apiURL string, apiKey string, agentAuthURL string, agentUsername string, agentPrivateKey string, agentPublicKey string) (*Server, error) {
	r := Server{
		apiURL:          apiURL,
		apiKey:          apiKey,
		lastSyncID:      "",
		lastChange:      "",
		resources:       make(map[string]string),
		permissions:     make(map[string]map[string][]string),
		permissionsLock: &sync.Mutex{},
	}

	// Setup context
	r.ctx, r.ctxCancel = context.WithCancel(context.Background())

	var keyPair bakery.KeyPair
	err := keyPair.Private.UnmarshalText([]byte(agentPrivateKey))
	if err != nil {
		return nil, err
	}

	err = keyPair.Public.UnmarshalText([]byte(agentPublicKey))
	if err != nil {
		return nil, err
	}

	r.client = httpbakery.NewClient()
	authInfo := agent.AuthInfo{
		Key: &keyPair,
		Agents: []agent.Agent{
			{
				URL:      agentAuthURL,
				Username: agentUsername,
			},
		},
	}

	err = agent.SetUpAuth(r.client, &authInfo)
	if err != nil {
		return nil, err
	}

	r.client.Client.Jar, err = cookiejar.New(nil)
	if err != nil {
		return nil, err
	}

	return &r, nil
}

// StartStatusCheck runs a status checking loop.
func (r *Server) StartStatusCheck() {
	var status rbacStatus

	// Figure out the new URL.
	u, err := url.Parse(r.apiURL)
	if err != nil {
		logger.Errorf("Failed to parse RBAC url: %v", err)
		return
	}
	u.Path = path.Join(u.Path, "/api/service/v1/changes")

	go func() {
		for {
			if status.LastChange != "" {
				values := url.Values{}
				values.Set("last-change", status.LastChange)
				u.RawQuery = values.Encode()
			}

			req, err := http.NewRequestWithContext(r.ctx, "GET", u.String(), nil)
			if err != nil {
				if err == context.Canceled {
					return
				}

				logger.Errorf("Failed to prepare RBAC query: %v", err)
				return
			}

			resp, err := r.client.Do(req)
			if err != nil {
				if err == context.Canceled {
					return
				}

				// Handle server/load-balancer timeouts, errors aren't properly wrapped so checking string.
				if strings.HasSuffix(err.Error(), "EOF") {
					continue
				}

				logger.Errorf("Failed to connect to RBAC, re-trying: %v", err)
				time.Sleep(5 * time.Second)
				continue
			}

			if resp.StatusCode == 504 {
				// 504 indicates the server timed out the background connection, just re-connect.
				_ = resp.Body.Close()
				continue
			}

			if resp.StatusCode != 200 {
				// For other errors we assume a server restart and give it a few seconds.
				_ = resp.Body.Close()
				logger.Debugf("RBAC server disconnected, re-connecting. (code=%v)", resp.StatusCode)
				time.Sleep(5 * time.Second)
				continue
			}

			err = json.NewDecoder(resp.Body).Decode(&status)
			_ = resp.Body.Close()
			if err != nil {
				logger.Errorf("Failed to parse RBAC response, re-trying: %v", err)
				time.Sleep(5 * time.Second)
				continue
			}

			r.lastChange = status.LastChange
			logger.Debugf("RBAC change detected, flushing cache")
			r.flushCache()
		}
	}()
}

// StopStatusCheck stops the periodic status checker.
func (r *Server) StopStatusCheck() {
	r.ctxCancel()
}

// SyncProjects updates the list of projects in RBAC.
func (r *Server) SyncProjects() error {
	if r.ProjectsFunc == nil {
		return fmt.Errorf("ProjectsFunc isn't configured yet, cannot sync")
	}

	resources := []rbacResource{}
	resourcesMap := map[string]string{}

	// Get all projects
	projects, err := r.ProjectsFunc()
	if err != nil {
		return err
	}

	// Convert to RBAC format
	for id, name := range projects {
		resources = append(resources, rbacResource{
			Name:       name,
			Identifier: strconv.FormatInt(id, 10),
		})

		resourcesMap[name] = strconv.FormatInt(id, 10)
	}

	// Update RBAC
	err = r.postResources(resources, nil, true)
	if err != nil {
		return err
	}

	// Update project map
	r.resourcesLock.Lock()
	r.resources = resourcesMap
	r.resourcesLock.Unlock()

	return nil
}

// AddProject adds a new project resource to RBAC.
func (r *Server) AddProject(id int64, name string) error {
	resource := rbacResource{
		Name:       name,
		Identifier: strconv.FormatInt(id, 10),
	}

	// Update RBAC
	err := r.postResources([]rbacResource{resource}, nil, false)
	if err != nil {
		return err
	}

	// Update project map
	r.resourcesLock.Lock()
	r.resources[name] = strconv.FormatInt(id, 10)
	r.resourcesLock.Unlock()

	return nil
}

// DeleteProject adds a new project resource to RBAC.
func (r *Server) DeleteProject(id int64) error {
	// Update RBAC
	err := r.postResources(nil, []string{strconv.FormatInt(id, 10)}, false)
	if err != nil {
		return err
	}

	// Update project map
	r.resourcesLock.Lock()
	for k, v := range r.resources {
		if v == strconv.FormatInt(id, 10) {
			delete(r.resources, k)
			break
		}
	}
	r.resourcesLock.Unlock()

	return nil
}

// RenameProject renames an existing project resource in RBAC.
func (r *Server) RenameProject(id int64, name string) error {
	return r.AddProject(id, name)
}

// UserAccess returns a UserAccess struct for the user.
func (r *Server) UserAccess(username string) (*UserAccess, error) {
	r.permissionsLock.Lock()
	defer r.permissionsLock.Unlock()

	// Check whether the permissions are cached.
	_, cached := r.permissions[username]

	if !cached {
		_ = r.syncPermissions(username)
	}

	// Checked if the user exists.
	permissions, ok := r.permissions[username]
	if !ok {
		return nil, errUnknownUser
	}

	// Prepare the response.
	access := UserAccess{
		Admin:    shared.StringInSlice("admin", permissions[""]),
		Projects: map[string][]string{},
	}

	for k, v := range permissions {
		// Skip the global permissions.
		if k == "" {
			continue
		}

		// Look for project name.
		for projectName, resourceID := range r.resources {
			if k != resourceID {
				continue
			}

			access.Projects[projectName] = v
			break
		}

		// Ignore unknown projects.
	}

	return &access, nil
}

func (r *Server) flushCache() {
	r.permissionsLock.Lock()
	defer r.permissionsLock.Unlock()

	logger.Info("Flushing RBAC permissions cache")

	for k, v := range r.permissions {
		for k := range v {
			delete(v, k)
		}

		delete(r.permissions, k)
	}

	logger.Info("Flushed RBAC permissions cache")
}

func (r *Server) syncAdmin(username string) bool {
	u, err := url.Parse(r.apiURL)
	if err != nil {
		return false
	}

	values := url.Values{}
	values.Set("u", username)
	u.RawQuery = values.Encode()
	u.Path = path.Join(u.Path, "/api/service/v1/resources/lxd/permissions-for-user")

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return false
	}

	resp, err := r.client.Do(req)
	if err != nil {
		return false
	}
	defer func() { _ = resp.Body.Close() }()

	var permissions map[string][]string

	err = json.NewDecoder(resp.Body).Decode(&permissions)
	if err != nil {
		return false
	}

	return shared.StringInSlice("admin", permissions[""])
}

func (r *Server) syncPermissions(username string) error {
	u, err := url.Parse(r.apiURL)
	if err != nil {
		return err
	}

	values := url.Values{}
	values.Set("u", username)
	u.RawQuery = values.Encode()
	u.Path = path.Join(u.Path, "/api/service/v1/resources/project/permissions-for-user")

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return err
	}

	resp, err := r.client.Do(req)
	if err != nil {
		return err
	}
	defer func() { _ = resp.Body.Close() }()

	var permissions map[string][]string

	err = json.NewDecoder(resp.Body).Decode(&permissions)
	if err != nil {
		return err
	}

	if r.syncAdmin(username) {
		permissions[""] = []string{"admin"}
	}

	// No need to acquire the lock since the caller (HasPermission) already has it.
	r.permissions[username] = permissions

	return nil
}

func (r *Server) postResources(updates []rbacResource, removals []string, force bool) error {
	// Make sure that we have a baseline sync in place
	if !force && r.lastSyncID == "" {
		return r.SyncProjects()
	}

	// Generate the URL
	u, err := url.Parse(r.apiURL)
	if err != nil {
		return err
	}

	u.Path = path.Join(u.Path, "/api/service/v1/resources/project")

	// Prepare the request body
	resourcePost := rbacResourcePost{
		Updates:  updates,
		Removals: removals,
	}

	if force {
		resourcePost.LastSyncID = nil
	} else {
		resourcePost.LastSyncID = &r.lastSyncID
	}

	body, err := json.Marshal(&resourcePost)
	if err != nil {
		return err
	}

	// Perform the request
	req, err := http.NewRequest("POST", u.String(), bytes.NewReader(body))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")

	resp, err := r.client.Do(req)
	if err != nil {
		return err
	}
	defer func() { _ = resp.Body.Close() }()

	// Handle errors
	if resp.StatusCode == 409 {
		// Sync IDs don't match, force sync
		return r.SyncProjects()
	} else if resp.StatusCode != http.StatusOK {
		// Something went wrong
		return errors.New(resp.Status)
	}

	// Extract the new SyncID
	var postRespose rbacResourcePostResponse
	err = json.NewDecoder(resp.Body).Decode(&postRespose)
	if err != nil {
		return err
	}

	r.lastSyncID = postRespose.SyncID

	return nil
}
<commit_msg>lxd/rbac: Inserts newlines after blocks.<commit_after>package rbac

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"gopkg.in/macaroon-bakery.v2/bakery"
	"gopkg.in/macaroon-bakery.v2/httpbakery"
	"gopkg.in/macaroon-bakery.v2/httpbakery/agent"

	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
)

type rbacResource struct {
	Identifier string `json:"identifier"`
	Name       string `json:"name"`
}

type rbacResourcePost struct {
	LastSyncID *string        `json:"last-sync-id"`
	Updates    []rbacResource `json:"updates,omitempty"`
	Removals   []string       `json:"removals,omitempty"`
}

type rbacResourcePostResponse struct {
	SyncID string `json:"sync-id"`
}

type rbacStatus struct {
	LastChange string `json:"last-change"`
}

// Errors.
var errUnknownUser = fmt.Errorf("Unknown RBAC user")

// UserAccess struct for permission checks.
type UserAccess struct {
	Admin    bool
	Projects map[string][]string
}

// Server represents an RBAC server.
type Server struct {
	apiURL string
	apiKey string

	lastSyncID string
	client     *httpbakery.Client
	lastChange string

	ctx       context.Context
	ctxCancel context.CancelFunc

	resources     map[string]string // Maps name to identifier
	resourcesLock sync.Mutex

	permissions map[string]map[string][]string

	permissionsLock *sync.Mutex

	ProjectsFunc func() (map[int64]string, error)
}

// NewServer returns a new RBAC server instance.
func NewServer(apiURL string, apiKey string, agentAuthURL string, agentUsername string, agentPrivateKey string, agentPublicKey string) (*Server, error) {
	r := Server{
		apiURL:          apiURL,
		apiKey:          apiKey,
		lastSyncID:      "",
		lastChange:      "",
		resources:       make(map[string]string),
		permissions:     make(map[string]map[string][]string),
		permissionsLock: &sync.Mutex{},
	}

	// Setup context
	r.ctx, r.ctxCancel = context.WithCancel(context.Background())

	var keyPair bakery.KeyPair
	err := keyPair.Private.UnmarshalText([]byte(agentPrivateKey))
	if err != nil {
		return nil, err
	}

	err = keyPair.Public.UnmarshalText([]byte(agentPublicKey))
	if err != nil {
		return nil, err
	}

	r.client = httpbakery.NewClient()
	authInfo := agent.AuthInfo{
		Key: &keyPair,
		Agents: []agent.Agent{
			{
				URL:      agentAuthURL,
				Username: agentUsername,
			},
		},
	}

	err = agent.SetUpAuth(r.client, &authInfo)
	if err != nil {
		return nil, err
	}

	r.client.Client.Jar, err = cookiejar.New(nil)
	if err != nil {
		return nil, err
	}

	return &r, nil
}

// StartStatusCheck runs a status checking loop.
func (r *Server) StartStatusCheck() {
	var status rbacStatus

	// Figure out the new URL.
	u, err := url.Parse(r.apiURL)
	if err != nil {
		logger.Errorf("Failed to parse RBAC url: %v", err)
		return
	}

	u.Path = path.Join(u.Path, "/api/service/v1/changes")

	go func() {
		for {
			if status.LastChange != "" {
				values := url.Values{}
				values.Set("last-change", status.LastChange)
				u.RawQuery = values.Encode()
			}

			req, err := http.NewRequestWithContext(r.ctx, "GET", u.String(), nil)
			if err != nil {
				if err == context.Canceled {
					return
				}

				logger.Errorf("Failed to prepare RBAC query: %v", err)
				return
			}

			resp, err := r.client.Do(req)
			if err != nil {
				if err == context.Canceled {
					return
				}

				// Handle server/load-balancer timeouts, errors aren't properly wrapped so checking string.
				if strings.HasSuffix(err.Error(), "EOF") {
					continue
				}

				logger.Errorf("Failed to connect to RBAC, re-trying: %v", err)
				time.Sleep(5 * time.Second)
				continue
			}

			if resp.StatusCode == 504 {
				// 504 indicates the server timed out the background connection, just re-connect.
				_ = resp.Body.Close()
				continue
			}

			if resp.StatusCode != 200 {
				// For other errors we assume a server restart and give it a few seconds.
				_ = resp.Body.Close()
				logger.Debugf("RBAC server disconnected, re-connecting. (code=%v)", resp.StatusCode)
				time.Sleep(5 * time.Second)
				continue
			}

			err = json.NewDecoder(resp.Body).Decode(&status)
			_ = resp.Body.Close()
			if err != nil {
				logger.Errorf("Failed to parse RBAC response, re-trying: %v", err)
				time.Sleep(5 * time.Second)
				continue
			}

			r.lastChange = status.LastChange
			logger.Debugf("RBAC change detected, flushing cache")
			r.flushCache()
		}
	}()
}

// StopStatusCheck stops the periodic status checker.
func (r *Server) StopStatusCheck() {
	r.ctxCancel()
}

// SyncProjects updates the list of projects in RBAC.
func (r *Server) SyncProjects() error {
	if r.ProjectsFunc == nil {
		return fmt.Errorf("ProjectsFunc isn't configured yet, cannot sync")
	}

	resources := []rbacResource{}
	resourcesMap := map[string]string{}

	// Get all projects
	projects, err := r.ProjectsFunc()
	if err != nil {
		return err
	}

	// Convert to RBAC format
	for id, name := range projects {
		resources = append(resources, rbacResource{
			Name:       name,
			Identifier: strconv.FormatInt(id, 10),
		})

		resourcesMap[name] = strconv.FormatInt(id, 10)
	}

	// Update RBAC
	err = r.postResources(resources, nil, true)
	if err != nil {
		return err
	}

	// Update project map
	r.resourcesLock.Lock()
	r.resources = resourcesMap
	r.resourcesLock.Unlock()

	return nil
}

// AddProject adds a new project resource to RBAC.
func (r *Server) AddProject(id int64, name string) error {
	resource := rbacResource{
		Name:       name,
		Identifier: strconv.FormatInt(id, 10),
	}

	// Update RBAC
	err := r.postResources([]rbacResource{resource}, nil, false)
	if err != nil {
		return err
	}

	// Update project map
	r.resourcesLock.Lock()
	r.resources[name] = strconv.FormatInt(id, 10)
	r.resourcesLock.Unlock()

	return nil
}

// DeleteProject adds a new project resource to RBAC.
func (r *Server) DeleteProject(id int64) error {
	// Update RBAC
	err := r.postResources(nil, []string{strconv.FormatInt(id, 10)}, false)
	if err != nil {
		return err
	}

	// Update project map
	r.resourcesLock.Lock()
	for k, v := range r.resources {
		if v == strconv.FormatInt(id, 10) {
			delete(r.resources, k)
			break
		}
	}
	r.resourcesLock.Unlock()

	return nil
}

// RenameProject renames an existing project resource in RBAC.
func (r *Server) RenameProject(id int64, name string) error {
	return r.AddProject(id, name)
}

// UserAccess returns a UserAccess struct for the user.
func (r *Server) UserAccess(username string) (*UserAccess, error) {
	r.permissionsLock.Lock()
	defer r.permissionsLock.Unlock()

	// Check whether the permissions are cached.
	_, cached := r.permissions[username]

	if !cached {
		_ = r.syncPermissions(username)
	}

	// Checked if the user exists.
	permissions, ok := r.permissions[username]
	if !ok {
		return nil, errUnknownUser
	}

	// Prepare the response.
	access := UserAccess{
		Admin:    shared.StringInSlice("admin", permissions[""]),
		Projects: map[string][]string{},
	}

	for k, v := range permissions {
		// Skip the global permissions.
		if k == "" {
			continue
		}

		// Look for project name.
		for projectName, resourceID := range r.resources {
			if k != resourceID {
				continue
			}

			access.Projects[projectName] = v
			break
		}

		// Ignore unknown projects.
	}

	return &access, nil
}

func (r *Server) flushCache() {
	r.permissionsLock.Lock()
	defer r.permissionsLock.Unlock()

	logger.Info("Flushing RBAC permissions cache")

	for k, v := range r.permissions {
		for k := range v {
			delete(v, k)
		}

		delete(r.permissions, k)
	}

	logger.Info("Flushed RBAC permissions cache")
}

func (r *Server) syncAdmin(username string) bool {
	u, err := url.Parse(r.apiURL)
	if err != nil {
		return false
	}

	values := url.Values{}
	values.Set("u", username)
	u.RawQuery = values.Encode()
	u.Path = path.Join(u.Path, "/api/service/v1/resources/lxd/permissions-for-user")

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return false
	}

	resp, err := r.client.Do(req)
	if err != nil {
		return false
	}

	defer func() { _ = resp.Body.Close() }()

	var permissions map[string][]string

	err = json.NewDecoder(resp.Body).Decode(&permissions)
	if err != nil {
		return false
	}

	return shared.StringInSlice("admin", permissions[""])
}

func (r *Server) syncPermissions(username string) error {
	u, err := url.Parse(r.apiURL)
	if err != nil {
		return err
	}

	values := url.Values{}
	values.Set("u", username)
	u.RawQuery = values.Encode()
	u.Path = path.Join(u.Path, "/api/service/v1/resources/project/permissions-for-user")

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return err
	}

	resp, err := r.client.Do(req)
	if err != nil {
		return err
	}

	defer func() { _ = resp.Body.Close() }()

	var permissions map[string][]string

	err = json.NewDecoder(resp.Body).Decode(&permissions)
	if err != nil {
		return err
	}

	if r.syncAdmin(username) {
		permissions[""] = []string{"admin"}
	}

	// No need to acquire the lock since the caller (HasPermission) already has it.
	r.permissions[username] = permissions

	return nil
}

func (r *Server) postResources(updates []rbacResource, removals []string, force bool) error {
	// Make sure that we have a baseline sync in place
	if !force && r.lastSyncID == "" {
		return r.SyncProjects()
	}

	// Generate the URL
	u, err := url.Parse(r.apiURL)
	if err != nil {
		return err
	}

	u.Path = path.Join(u.Path, "/api/service/v1/resources/project")

	// Prepare the request body
	resourcePost := rbacResourcePost{
		Updates:  updates,
		Removals: removals,
	}

	if force {
		resourcePost.LastSyncID = nil
	} else {
		resourcePost.LastSyncID = &r.lastSyncID
	}

	body, err := json.Marshal(&resourcePost)
	if err != nil {
		return err
	}

	// Perform the request
	req, err := http.NewRequest("POST", u.String(), bytes.NewReader(body))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")

	resp, err := r.client.Do(req)
	if err != nil {
		return err
	}

	defer func() { _ = resp.Body.Close() }()

	// Handle errors
	if resp.StatusCode == 409 {
		// Sync IDs don't match, force sync
		return r.SyncProjects()
	} else if resp.StatusCode != http.StatusOK {
		// Something went wrong
		return errors.New(resp.Status)
	}

	// Extract the new SyncID
	var postRespose rbacResourcePostResponse
	err = json.NewDecoder(resp.Body).Decode(&postRespose)
	if err != nil {
		return err
	}

	r.lastSyncID = postRespose.SyncID

	return nil
}
<|endoftext|>


<commit_before>package util

import (
	"fmt"
	"sort"
	"strings"

	"github.com/lxc/lxd/shared"
)

// CompareConfigs compares two config maps and returns an error if they differ.
func CompareConfigs(config1, config2 map[string]string, exclude []string) error {
	if exclude == nil {
		exclude = []string{}
	}

	delta := []string{}
	for key, value := range config1 {
		if shared.StringInSlice(key, exclude) {
			continue
		}
		if config2[key] != value {
			delta = append(delta, key)
		}
	}
	for key, value := range config2 {
		if shared.StringInSlice(key, exclude) {
			continue
		}
		if config1[key] != value {
			present := false
			for i := range delta {
				if delta[i] == key {
					present = true
				}
				break
			}
			if !present {
				delta = append(delta, key)
			}
		}
	}
	sort.Strings(delta)
	if len(delta) > 0 {
		return fmt.Errorf("different values for keys: %s", strings.Join(delta, ", "))
	}

	return nil
}

// CopyConfig creates a new map with a copy of the given config.
func CopyConfig(config map[string]string) map[string]string {
	copy := map[string]string{}
	for key, value := range config {
		copy[key] = value
	}
	return copy
}

// SplitNTrimSpace returns result of strings.SplitN() and then strings.TrimSpace() on each element.
// Accepts nilIfEmpty argument which if true, will return nil slice if s is empty (after trimming space).
func SplitNTrimSpace(s string, sep string, n int, nilIfEmpty bool) []string {
	if nilIfEmpty && strings.TrimSpace(s) == "" {
		return nil
	}

	parts := strings.SplitN(s, sep, n)

	for i, v := range parts {
		parts[i] = strings.TrimSpace(v)
	}

	return parts
}
<commit_msg>lxd/util/config: Avoid unnecessary allocations in CopyConfig<commit_after>package util

import (
	"fmt"
	"sort"
	"strings"

	"github.com/lxc/lxd/shared"
)

// CompareConfigs compares two config maps and returns an error if they differ.
func CompareConfigs(config1, config2 map[string]string, exclude []string) error {
	if exclude == nil {
		exclude = []string{}
	}

	delta := []string{}
	for key, value := range config1 {
		if shared.StringInSlice(key, exclude) {
			continue
		}
		if config2[key] != value {
			delta = append(delta, key)
		}
	}
	for key, value := range config2 {
		if shared.StringInSlice(key, exclude) {
			continue
		}
		if config1[key] != value {
			present := false
			for i := range delta {
				if delta[i] == key {
					present = true
				}
				break
			}
			if !present {
				delta = append(delta, key)
			}
		}
	}
	sort.Strings(delta)
	if len(delta) > 0 {
		return fmt.Errorf("different values for keys: %s", strings.Join(delta, ", "))
	}

	return nil
}

// CopyConfig creates a new map with a copy of the given config.
func CopyConfig(config map[string]string) map[string]string {
	copy := make(map[string]string, len(config))
	for key, value := range config {
		copy[key] = value
	}

	return copy
}

// SplitNTrimSpace returns result of strings.SplitN() and then strings.TrimSpace() on each element.
// Accepts nilIfEmpty argument which if true, will return nil slice if s is empty (after trimming space).
func SplitNTrimSpace(s string, sep string, n int, nilIfEmpty bool) []string {
	if nilIfEmpty && strings.TrimSpace(s) == "" {
		return nil
	}

	parts := strings.SplitN(s, sep, n)

	for i, v := range parts {
		parts[i] = strings.TrimSpace(v)
	}

	return parts
}
<|endoftext|>


<commit_before>package main

import (
        "encoding/json"
        "flag"
        "fmt"
        "io/ioutil"
        "net/http"
        "strings"
        "time"
)

type Stop struct {
        V            int     `json:"__v,int"`
        Id           string  `json:"_id"`
        City         string
        Name         string
        Sort_id      int
        Destinations []Destination
}

type Destination struct {
        V            int     `json:"__v,int"`
        Id           string  `json:"_id"`
        City         string
        Name         string
        Sort_id      int
        Destinations []string
}

type Time struct {
        FromId  string
        From    string
        ToId    string
        To      string
        Date    string
        //Arrival string
        Route   int
        Busstop string
        Hash    string
        Id      string  `json:"_id"`
        V       int     `json:"__v"`
}

func main() {

        fromString := flag.String("from", "UiT",
                "The bus stop you are traveling from")
        toString := flag.String("to", "Sentrum",
                "The bus stop you are traveling to")

        flag.Parse()

        busStopsURL := "http://rutebuss.no/stops"

        resp, err := http.Get(busStopsURL)
        if err != nil {
                fmt.Print("Bus stops could not be downlaoded...", err)
                return
        }

        body, err := ioutil.ReadAll(resp.Body)

        if err != nil {
                fmt.Println("Could not read body ", err)
                return
        }

        stops := make([]Stop, 100)

        err = json.Unmarshal(body, &stops)

        if err != nil {
                fmt.Print("Bus route ", *fromString, "-", *toString, "does not exist")
                return
        }

        fmt.Println("From", *fromString, "to", *toString)

        var from, to string

        for _, stop := range stops {
                if strings.ToLower(stop.Name) == strings.ToLower(*fromString) {
                        from = stop.Id
                }
                if strings.ToLower(stop.Name) == strings.ToLower(*toString) {
                        to = stop.Id
                }
        }

        if from == "" && to == "" {
                fmt.Println("Bus route ", from, "-", to, "does not exist")
                return
        }

        t := time.Now()
        timeString := t.UTC().Format(time.RFC3339)

        travelURL :=
                "http://rutebuss.no/departure?from=" + from + "&to=" + to + "&date=" + timeString

        resp, err = http.Get(travelURL)
        if err != nil {
                fmt.Print("Bus times could not be downlaoded...", err)
                return
        }

        body, err = ioutil.ReadAll(resp.Body)

        if err != nil {
                fmt.Println("Could not read body ", err)
                return
        }

        times := make([]Time, 10)

        err = json.Unmarshal(body, &times)

        if err != nil {
                fmt.Println("Bus route ", *fromString, "-", *toString, "does not exist")
                return
        }

        if len(times) == 0 {
                fmt.Println("Bus route ", *fromString, "-", *toString, "does not exist")
        }

        for _, t := range times {
                departure, err := time.Parse(time.RFC3339, t.Date)
                if err != nil {
                        fmt.Println("Parsing of date went horrible... ", err)
                }
                untilDeparture := departure.Sub(time.Now())
                untilDString := untilDeparture.String()
                untilDString = strings.Split(untilDString, ".")[0]
                untilDString = untilDString + "s"

                hour := time.Hour

                departure = departure.Add(hour)

                tm := departure.Format(time.Kitchen)
                fmt.Println("Bus", t.Route, "leaves at", tm, "in",
                        untilDString, "from", t.Busstop)
        }

}
<commit_msg>listing availible stops on --help<commit_after>package main

import (
        "encoding/json"
        "flag"
        "fmt"
        "io/ioutil"
        "net/http"
        "strings"
        "time"
        "os"
)

type Stop struct {
        V            int     `json:"__v,int"`
        Id           string  `json:"_id"`
        City         string
        Name         string
        Sort_id      int
        Destinations []Destination
}

type Destination struct {
        V            int     `json:"__v,int"`
        Id           string  `json:"_id"`
        City         string
        Name         string
        Sort_id      int
        Destinations []string
}

type Time struct {
        FromId  string
        From    string
        ToId    string
        To      string
        Date    string
        //Arrival string
        Route   int
        Busstop string
        Hash    string
        Id      string  `json:"_id"`
        V       int     `json:"__v"`
}

func main() {

        busStopsURL := "http://rutebuss.no/stops"

        resp, err := http.Get(busStopsURL)
        if err != nil {
                fmt.Print("Bus stops could not be downlaoded...", err)
                return
        }

        body, err := ioutil.ReadAll(resp.Body)

        if err != nil {
                fmt.Println("Could not read body ", err)
                return
        }

        stops := make([]Stop, 100)

        err = json.Unmarshal(body, &stops)

        if err != nil {
                fmt.Print("Error contacting rutebuss.no")
                return
        }

        availibleStops := ""
        for _, stp := range(stops) {
            availibleStops += stp.Name + "\n\t"
        }

        fromString := flag.String("from", "UiT",
                "The bus stop you are traveling from")
        toString := flag.String("to", "Sentrum",
                "The bus stop you are traveling to")

        flag.Usage = func(){
            flag.PrintDefaults()
            fmt.Fprintf(os.Stderr, "Availible stops: \n\t"+availibleStops)

        }

        flag.Parse()

        fmt.Println("From", *fromString, "to", *toString)

        var from, to string

        for _, stop := range stops {
                if strings.ToLower(stop.Name) == strings.ToLower(*fromString) {
                        from = stop.Id
                }
                if strings.ToLower(stop.Name) == strings.ToLower(*toString) {
                        to = stop.Id
                }
        }

        if from == "" && to == "" {
                fmt.Println("Bus route ", from, "-", to, "does not exist")
                return
        }

        t := time.Now()
        timeString := t.UTC().Format(time.RFC3339)

        travelURL :=
                "http://rutebuss.no/departure?from=" + from + "&to=" + to + "&date=" + timeString

        resp, err = http.Get(travelURL)
        if err != nil {
                fmt.Print("Bus times could not be downlaoded...", err)
                return
        }

        body, err = ioutil.ReadAll(resp.Body)

        if err != nil {
                fmt.Println("Could not read body ", err)
                return
        }

        times := make([]Time, 10)

        err = json.Unmarshal(body, &times)

        if err != nil {
                fmt.Println("Bus route ", *fromString, "-", *toString, "does not exist")
                return
        }

        if len(times) == 0 {
                fmt.Println("Bus route ", *fromString, "-", *toString, "does not exist")
        }

        for _, t := range times {
                departure, err := time.Parse(time.RFC3339, t.Date)
                if err != nil {
                        fmt.Println("Parsing of date went horrible... ", err)
                }
                untilDeparture := departure.Sub(time.Now())
                untilDString := untilDeparture.String()
                untilDString = strings.Split(untilDString, ".")[0]
                untilDString = untilDString + "s"

                hour := time.Hour

                departure = departure.Add(hour)

                tm := departure.Format(time.Kitchen)
                fmt.Println("Bus", t.Route, "leaves at", tm, "in",
                        untilDString, "from", t.Busstop)
        }

}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
    "encoding/json"
    "time"
)

type Stop struct {
    V int                   `json:"__v,int"`
    Id string               `json:"_id"`
    City string             `json:"city"`
    Name string             `json:"name"`
    Sort_id int             `json:"sort_id,int"`
    Destinations [] Destination    `json:"destinations"`
}

type Destination struct {
    V int                   `json:"__v,int"`
    Id string               `json:"_id"`
    City string             `json:"city"`
    Name string            `json:"name"`
    Sort_id int             `json:"sort_id, int"`
    Destinations [] string    `json:"destinations,[]string"`
}

type Time struct {
    FromId string
    From string
    ToId string
    To string
    Date string
    //Arrival string
    Route int
    Busstop string
    Hash string
    Id string `json:"_id"`
    V int `json:"__v"`
}

func main() {

    busStopsURL := "http://rutebuss.no/stops"

    resp, err := http.Get(busStopsURL)
    if err != nil {
        fmt.Print("Bus stops could not be downlaoded...", err)
    }

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {
        fmt.Println("Could not read body ", err)
    }

    stops := make([]Stop, 100)

    err = json.Unmarshal(body, &stops)

    if err != nil {
        fmt.Println(string(body))
        fmt.Println("Could not unmarshal body... ", err,  string(body[5]))
    }

    fmt.Println("From", stops[0].Name,"to", stops[1].Name)

    from := stops[0].Id
    to := stops[1].Id

    t := time.Now()
    timeString := t.UTC().Format(time.RFC3339)

    travelURL :=
        "http://rutebuss.no/departure?from="+from+"&to="+to+"&date="+timeString

    resp, err = http.Get(travelURL)
    if err != nil {
        fmt.Print("Bus times could not be downlaoded...", err)
    }

    body, err = ioutil.ReadAll(resp.Body)

    if err != nil {
        fmt.Println("Could not read body ", err)
    }

    times := make([]Time, 10)

    err = json.Unmarshal(body, &times)

    if err != nil {
        fmt.Println(string(body))
        fmt.Println("Could not unmarshal body... ", err,  string(body[5]))
    }

    for _, t := range times {
        departure, err := time.Parse(time.RFC3339, t.Date)
        if err != nil {
            fmt.Println("Parsing of date went horrible... ",err)
        }
        untilDeparture := departure.Sub(time.Now())

        fmt.Println(untilDeparture.String())
    }

}

<commit_msg>json clutter<commit_after>package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
    "encoding/json"
    "time"
)

type Stop struct {
    V int                   `json:"__v,int"`
    Id string               `json:"_id"`
    City string
    Name string
    Sort_id int
    Destinations [] Destination
}

type Destination struct {
    V int                   `json:"__v,int"`
    Id string               `json:"_id"`
    City string
    Name string
    Sort_id int
    Destinations [] string
}

type Time struct {
    FromId string
    From string
    ToId string
    To string
    Date string
    //Arrival string
    Route int
    Busstop string
    Hash string
    Id string `json:"_id"`
    V int `json:"__v"`
}

func main() {

    busStopsURL := "http://rutebuss.no/stops"

    resp, err := http.Get(busStopsURL)
    if err != nil {
        fmt.Print("Bus stops could not be downlaoded...", err)
    }

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {
        fmt.Println("Could not read body ", err)
    }

    stops := make([]Stop, 100)

    err = json.Unmarshal(body, &stops)

    if err != nil {
        fmt.Println(string(body))
        fmt.Println("Could not unmarshal body... ", err,  string(body[5]))
    }

    fmt.Println("From", stops[0].Name,"to", stops[1].Name)

    from := stops[0].Id
    to := stops[1].Id

    t := time.Now()
    timeString := t.UTC().Format(time.RFC3339)

    travelURL :=
        "http://rutebuss.no/departure?from="+from+"&to="+to+"&date="+timeString

    resp, err = http.Get(travelURL)
    if err != nil {
        fmt.Print("Bus times could not be downlaoded...", err)
    }

    body, err = ioutil.ReadAll(resp.Body)

    if err != nil {
        fmt.Println("Could not read body ", err)
    }

    times := make([]Time, 10)

    err = json.Unmarshal(body, &times)

    if err != nil {
        fmt.Println(string(body))
        fmt.Println("Could not unmarshal body... ", err,  string(body[5]))
    }

    for _, t := range times {
        departure, err := time.Parse(time.RFC3339, t.Date)
        if err != nil {
            fmt.Println("Parsing of date went horrible... ",err)
        }
        untilDeparture := departure.Sub(time.Now())

        fmt.Println(untilDeparture.String())
    }

}

<|endoftext|>


<commit_before>package main

import (
	"github.com/bitly/go-simplejson"
	"github.com/ninjasphere/go-ninja"
	"github.com/ninjasphere/go-ninja/channels"
	"github.com/ninjasphere/go-ninja/devices"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-sonos"
	"github.com/ninjasphere/go-sonos/upnp"
)

const (
	defaultInstanceID = 0
	defaultSpeed      = "1"
)

type sonosPlayer struct {
	*sonos.Sonos
	log    *logger.Logger
	player *devices.MediaPlayerDevice
}

func (sp *sonosPlayer) applyPlayPause(playing bool) error {

	sp.log.Infof("applyPlayPause called, playing: %t", playing)

	if playing {
		err := sp.Play(defaultInstanceID, defaultSpeed)

		if err != nil {
			return err
		}

		return sp.player.UpdateControlState(channels.MediaControlEventPaused)

	}

	err = sp.Pause(defaultInstanceID)

	if err != nil {
		return err
	}

	return sp.player.UpdateControlState(channels.MediaControlEventPlaying)
}

func (sp *sonosPlayer) applyStop() error {
	sp.log.Infof("applyStop called")

	err := sp.Stop(defaultInstanceID)

	if err != nil {
		return err
	}

	return sp.player.UpdateControlState(channels.MediaControlEventStopped)
}

func (sp *sonosPlayer) applyPlaylistJump(delta int) error {
	sp.log.Infof("applyPlaylistJump called, delta : %d", delta)
	if delta < 0 {
		return sp.Previous(defaultInstanceID)
	}
	return sp.Next(defaultInstanceID)
}

func (sp *sonosPlayer) applyVolume(volume float64) error {
	sp.log.Infof("applyVolume called, volume %f", volume)

	vol := uint16(volume * 100)

	err := sp.SetVolume(defaultInstanceID, upnp.Channel_Master, vol)

	if err != nil {
		return err
	}

	return sp.player.UpdateVolumeState(volume)
}

func (sp *sonosPlayer) applyMuted(muted bool) error {
	err := sp.SetMute(defaultInstanceID, upnp.Channel_Master, muted)

	if err != nil {
		return err
	}

	return sp.player.UpdateMutedState(muted)
}

func (sp *sonosPlayer) bindMethods() {

	sp.player.ApplyPlayPause = sp.applyPlayPause
	sp.player.ApplyStop = sp.applyStop
	sp.player.ApplyPlaylistJump = sp.applyPlaylistJump
	sp.player.ApplyVolume = sp.applyVolume
	sp.player.ApplyMuted = sp.applyMuted

	sp.player.EnableControlChannel([]string{
		"playing",
		"paused",
		"stopped",
		"idle",
	})

	sp.player.EnableVolumeChannel()
}

func (sp *sonosPlayer) updateState() error {

	muted, err := sp.GetMute(defaultInstanceID, upnp.Channel_Master)

	if err != nil {
		return err
	}

	nlog.Infof("UpdateMutedState %t", muted)
	if sp.player.UpdateMutedState(muted); err != nil {
		return err
	}

	vol, err := sp.GetVolume(defaultInstanceID, upnp.Channel_Master)

	if err != nil {
		return err
	}

	var volume float64

	if vol > 0 {
		volume = float64(vol) / 100
	} else {
		volume = float64(0)

	}

	nlog.Infof("UpdateVolumeState %d  %f", vol, volume)
	return sp.player.UpdateVolumeState(volume)
}

func NewPlayer(bus *ninja.DriverBus, sonosUnit *sonos.Sonos) (*sonosPlayer, error) {

	group, _ := sonosUnit.GetZoneGroupAttributes()

	id := group.CurrentZoneGroupID
	name := group.CurrentZoneGroupName

	nlog.Infof("Making media player with ID: %s Label: %s", id, name)

	sigs, _ := simplejson.NewJson([]byte(`{
			"ninja:manufacturer": "Sonos",
			"ninja:productName": "Sonos Player",
			"ninja:productType": "MediaPlayer",
			"ninja:thingType": "MediaPlayer"
	}`))

	deviceBus, err := bus.AnnounceDevice(id, "media-player", name, sigs)
	if err != nil {
		nlog.FatalError(err, "Failed to create media player device bus")
	}

	player, err := devices.CreateMediaPlayerDevice(name, deviceBus)

	if err != nil {
		nlog.FatalError(err, "Failed to create media player device")
	}

	sp := &sonosPlayer{sonosUnit, logger.GetLogger("sonosPlayer"), player}

	sp.bindMethods()

	err = sp.updateState()

	if err != nil {
		nlog.FatalError(err, "Failed to create media player device bus")
	}

	return sp, nil
}
<commit_msg>Nailed by the walrus.<commit_after>package main

import (
	"github.com/bitly/go-simplejson"
	"github.com/ninjasphere/go-ninja"
	"github.com/ninjasphere/go-ninja/channels"
	"github.com/ninjasphere/go-ninja/devices"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-sonos"
	"github.com/ninjasphere/go-sonos/upnp"
)

const (
	defaultInstanceID = 0
	defaultSpeed      = "1"
)

type sonosPlayer struct {
	*sonos.Sonos
	log    *logger.Logger
	player *devices.MediaPlayerDevice
}

func (sp *sonosPlayer) applyPlayPause(playing bool) error {

	sp.log.Infof("applyPlayPause called, playing: %t", playing)

	if playing {
		err := sp.Play(defaultInstanceID, defaultSpeed)

		if err != nil {
			return err
		}

		return sp.player.UpdateControlState(channels.MediaControlEventPaused)

	}

	err := sp.Pause(defaultInstanceID)

	if err != nil {
		return err
	}

	return sp.player.UpdateControlState(channels.MediaControlEventPlaying)
}

func (sp *sonosPlayer) applyStop() error {
	sp.log.Infof("applyStop called")

	err := sp.Stop(defaultInstanceID)

	if err != nil {
		return err
	}

	return sp.player.UpdateControlState(channels.MediaControlEventStopped)
}

func (sp *sonosPlayer) applyPlaylistJump(delta int) error {
	sp.log.Infof("applyPlaylistJump called, delta : %d", delta)
	if delta < 0 {
		return sp.Previous(defaultInstanceID)
	}
	return sp.Next(defaultInstanceID)
}

func (sp *sonosPlayer) applyVolume(volume float64) error {
	sp.log.Infof("applyVolume called, volume %f", volume)

	vol := uint16(volume * 100)

	err := sp.SetVolume(defaultInstanceID, upnp.Channel_Master, vol)

	if err != nil {
		return err
	}

	return sp.player.UpdateVolumeState(volume)
}

func (sp *sonosPlayer) applyMuted(muted bool) error {
	err := sp.SetMute(defaultInstanceID, upnp.Channel_Master, muted)

	if err != nil {
		return err
	}

	return sp.player.UpdateMutedState(muted)
}

func (sp *sonosPlayer) bindMethods() {

	sp.player.ApplyPlayPause = sp.applyPlayPause
	sp.player.ApplyStop = sp.applyStop
	sp.player.ApplyPlaylistJump = sp.applyPlaylistJump
	sp.player.ApplyVolume = sp.applyVolume
	sp.player.ApplyMuted = sp.applyMuted

	sp.player.EnableControlChannel([]string{
		"playing",
		"paused",
		"stopped",
		"idle",
	})

	sp.player.EnableVolumeChannel()
}

func (sp *sonosPlayer) updateState() error {

	muted, err := sp.GetMute(defaultInstanceID, upnp.Channel_Master)

	if err != nil {
		return err
	}

	nlog.Infof("UpdateMutedState %t", muted)
	if sp.player.UpdateMutedState(muted); err != nil {
		return err
	}

	vol, err := sp.GetVolume(defaultInstanceID, upnp.Channel_Master)

	if err != nil {
		return err
	}

	var volume float64

	if vol > 0 {
		volume = float64(vol) / 100
	} else {
		volume = float64(0)

	}

	nlog.Infof("UpdateVolumeState %d  %f", vol, volume)
	return sp.player.UpdateVolumeState(volume)
}

func NewPlayer(bus *ninja.DriverBus, sonosUnit *sonos.Sonos) (*sonosPlayer, error) {

	group, _ := sonosUnit.GetZoneGroupAttributes()

	id := group.CurrentZoneGroupID
	name := group.CurrentZoneGroupName

	nlog.Infof("Making media player with ID: %s Label: %s", id, name)

	sigs, _ := simplejson.NewJson([]byte(`{
			"ninja:manufacturer": "Sonos",
			"ninja:productName": "Sonos Player",
			"ninja:productType": "MediaPlayer",
			"ninja:thingType": "MediaPlayer"
	}`))

	deviceBus, err := bus.AnnounceDevice(id, "media-player", name, sigs)
	if err != nil {
		nlog.FatalError(err, "Failed to create media player device bus")
	}

	player, err := devices.CreateMediaPlayerDevice(name, deviceBus)

	if err != nil {
		nlog.FatalError(err, "Failed to create media player device")
	}

	sp := &sonosPlayer{sonosUnit, logger.GetLogger("sonosPlayer"), player}

	sp.bindMethods()

	err = sp.updateState()

	if err != nil {
		nlog.FatalError(err, "Failed to create media player device bus")
	}

	return sp, nil
}
<|endoftext|>


<commit_before>package workflow

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
)

// executor enables calling TerraForm from Go, across platforms, with any
// additional providers/provisioners that the currently executing binary
// exposes.
//
// The TerraForm binary is expected to be in the executing binary's folder, in
// the current working directory or in the PATH.
type executor struct {
	binaryPath string
}

// Set the binary names for different platforms
const (
	tfBinUnix    = "terraform"
	tfBinWindows = "terraform.exe"
)

// errBinaryNotFound denotes the fact that the TerraForm binary could not be
// found on disk.
var errBinaryNotFound = errors.New(
	"TerraForm not in executable's folder, cwd nor PATH",
)

// newExecutor initializes a new Executor.
func newExecutor() (*executor, error) {
	ex := new(executor)

	// Find the TerraForm binary.
	binPath, err := tfBinaryPath()
	if err != nil {
		return nil, err
	}

	ex.binaryPath = binPath
	return ex, nil
}

// Execute runs the given command and arguments against TerraForm.
//
// An error is returned if the TerraForm binary could not be found, or if the
// TerraForm call itself failed, in which case, details can be found in the
// output.
func (ex *executor) execute(clusterDir string, args ...string) error {
	// Prepare TerraForm command by setting up the command, configuration,
	// and the working directory
	if clusterDir == "" {
		return fmt.Errorf("clusterDir is unset. Quitting")
	}

	cmd := exec.Command(ex.binaryPath, args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = clusterDir

	// Start TerraForm.
	return cmd.Run()
}

// tfBinaryPath searches for a TerraForm binary on disk:
// - in the executing binary's folder,
// - in the current working directory,
// - in the PATH.
// The first to be found is the one returned.
func tfBinaryPath() (string, error) {
	// Depending on the platform, the expected binary name is different.
	binaryFileName := tfBinUnix
	if runtime.GOOS == "windows" {
		binaryFileName = tfBinWindows
	}

	// Find the current executable's real path
	execPath, err := os.Executable()
	if err != nil {
		return "", err
	}

	// Look into the executable's folder.
	if execFolderPath, err := filepath.Abs(filepath.Dir(execPath)); err == nil {
		path := filepath.Join(execFolderPath, binaryFileName)
		if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
			return path, nil
		}
	}

	// Look into cwd.
	if workingDirectory, err := os.Getwd(); err == nil {
		path := filepath.Join(workingDirectory, binaryFileName)
		if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
			return path, nil
		}
	}

	// If we still haven't found the executable, look for it
	// in the PATH.
	if path, err := exec.LookPath(binaryFileName); err == nil {
		return filepath.Abs(path)
	}

	return "", errBinaryNotFound
}
<commit_msg>pkg/executor: handle symlinked binary<commit_after>package workflow

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
)

// executor enables calling TerraForm from Go, across platforms, with any
// additional providers/provisioners that the currently executing binary
// exposes.
//
// The TerraForm binary is expected to be in the executing binary's folder, in
// the current working directory or in the PATH.
type executor struct {
	binaryPath string
}

// Set the binary names for different platforms
const (
	tfBinUnix    = "terraform"
	tfBinWindows = "terraform.exe"
)

// errBinaryNotFound denotes the fact that the TerraForm binary could not be
// found on disk.
var errBinaryNotFound = errors.New(
	"TerraForm not in executable's folder, cwd nor PATH",
)

// newExecutor initializes a new Executor.
func newExecutor() (*executor, error) {
	ex := new(executor)

	// Find the TerraForm binary.
	binPath, err := tfBinaryPath()
	if err != nil {
		return nil, err
	}

	ex.binaryPath = binPath
	return ex, nil
}

// Execute runs the given command and arguments against TerraForm.
//
// An error is returned if the TerraForm binary could not be found, or if the
// TerraForm call itself failed, in which case, details can be found in the
// output.
func (ex *executor) execute(clusterDir string, args ...string) error {
	// Prepare TerraForm command by setting up the command, configuration,
	// and the working directory
	if clusterDir == "" {
		return fmt.Errorf("clusterDir is unset. Quitting")
	}

	cmd := exec.Command(ex.binaryPath, args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = clusterDir

	// Start TerraForm.
	return cmd.Run()
}

// tfBinaryPath searches for a TerraForm binary on disk:
// - in the executing binary's folder,
// - in the current working directory,
// - in the PATH.
// The first to be found is the one returned.
func tfBinaryPath() (string, error) {
	// Depending on the platform, the expected binary name is different.
	binaryFileName := tfBinUnix
	if runtime.GOOS == "windows" {
		binaryFileName = tfBinWindows
	}

	// Find the current executable's path, gets an absolute path or error
	execPath, err := os.Executable()
	if err == nil {
		// execPath could be a symlink
		if stat, err := os.Stat(execPath); err == nil && (stat.Mode()&os.ModeSymlink) == os.ModeSymlink {
			if evalExecPath, err := filepath.EvalSymlinks(execPath); err != nil {
				execPath = evalExecPath
			}
		}

		// Look into the executable's folder.
		path := filepath.Join(filepath.Dir(execPath), binaryFileName)
		if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
			return path, nil
		}
	}

	// Look into cwd.
	if workingDirectory, err := os.Getwd(); err == nil {
		path := filepath.Join(workingDirectory, binaryFileName)
		if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
			return path, nil
		}
	}

	// If we still haven't found the executable, look for it
	// in the PATH.
	if path, err := exec.LookPath(binaryFileName); err == nil {
		return filepath.Abs(path)
	}

	return "", errBinaryNotFound
}
<|endoftext|>