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

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the concurrency primitive functions.

package golisp

import (
	"fmt"
	"time"
	"unsafe"
)

type Process struct {
	Env           *SymbolTableFrame
	Code          *Data
	Wake          chan bool
	Abort         chan bool
	Restart       chan bool
	ScheduleTimer *time.Timer
}

func RegisterConcurrencyPrimitives() {
	MakePrimitiveFunction("fork", 1, ForkImpl)
	MakePrimitiveFunction("proc-sleep", 2, ProcSleepImpl)
	MakePrimitiveFunction("wake", 1, WakeImpl)
	MakePrimitiveFunction("schedule", 2, ScheduleImpl)
	MakePrimitiveFunction("reset-timeout", 1, ResetTimeoutImpl)
	MakePrimitiveFunction("abandon", 1, AbandonImpl)
}

func ForkImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)

	if !FunctionP(f) {
		err = ProcessError(fmt.Sprintf("fork expected a function, but received %v.", f), env)
		return
	}

	if FunctionValue(f).RequiredArgCount != 1 {
		err = ProcessError(fmt.Sprintf("fork expected a function with arity of 1, but it was %d.", FunctionValue(f).RequiredArgCount), env)
		return
	}

	proc := &Process{Env: env, Code: f, Wake: make(chan bool, 1), Abort: make(chan bool, 1), Restart: make(chan bool, 1)}
	procObj := ObjectWithTypeAndValue("Process", unsafe.Pointer(proc))

	go func() {
		_, err = FunctionValue(f).ApplyWithoutEval(InternalMakeList(procObj), env)
	}()

	return procObj, nil
}

func ProcSleepImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	procObj := Car(args)

	if !ObjectP(procObj) || ObjectType(procObj) != "Process" {
		err = ProcessError(fmt.Sprintf("proc-sleep expects a Process object expected but received %s.", ObjectType(procObj)), env)
		return
	}

	proc := (*Process)(ObjectValue(procObj))

	millis := Cadr(args)
	if !IntegerP(millis) {
		err = ProcessError(fmt.Sprintf("proc-sleep expected an integer as a delay, but received %v.", millis), env)
		return
	}

	woken := false
	select {
	case <-proc.Wake:
		woken = true
	case <-time.After(time.Duration(IntegerValue(millis)) * time.Millisecond):
	}

	return BooleanWithValue(woken), nil
}

func WakeImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	procObj := Car(args)

	if !ObjectP(procObj) || ObjectType(procObj) != "Process" {
		err = ProcessError(fmt.Sprintf("wake expects a Process object expected but received %s.", ObjectType(procObj)), env)
		return
	}

	proc := (*Process)(ObjectValue(procObj))
	proc.Wake <- true
	return StringWithValue("OK"), nil
}

func ScheduleImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	millis := Car(args)
	if !IntegerP(millis) {
		err = ProcessError(fmt.Sprintf("schedule expected an integer as a delay, but received %v.", millis), env)
		return
	}
	f := Cadr(args)

	if !FunctionP(f) {
		err = ProcessError(fmt.Sprintf("schedule expected a function, but received %v.", f), env)
		return
	}

	if FunctionValue(f).RequiredArgCount != 1 {
		err = ProcessError(fmt.Sprintf("schedule expected a function with arity of 1, but it was %d.", FunctionValue(f).RequiredArgCount), env)
		return
	}

	proc := &Process{
		Env:           env,
		Code:          f,
		Wake:          make(chan bool, 1),
		Abort:         make(chan bool, 1),
		Restart:       make(chan bool, 1),
		ScheduleTimer: time.NewTimer(time.Duration(IntegerValue(millis)) * time.Millisecond)}
	procObj := ObjectWithTypeAndValue("Process", unsafe.Pointer(proc))

	aborted := false
	go func() {
	Loop:
		for {
			select {
			case <-proc.Abort:
				aborted = true
				break Loop
			case <-proc.Restart:
				proc.ScheduleTimer.Reset(time.Duration(IntegerValue(millis)) * time.Millisecond)
			case <-proc.ScheduleTimer.C:
				_, err = FunctionValue(f).ApplyWithoutEval(InternalMakeList(procObj), env)
				break Loop
			}
		}
	}()

	return procObj, nil

}

func AbandonImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	procObj := Car(args)

	if !ObjectP(procObj) || ObjectType(procObj) != "Process" {
		err = ProcessError(fmt.Sprintf("adandon expects a Process object expected but received %s.", ObjectType(procObj)), env)
		return
	}

	proc := (*Process)(ObjectValue(procObj))
	proc.Abort <- true
	return StringWithValue("OK"), nil
}

func ResetTimeoutImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	procObj := Car(args)

	if !ObjectP(procObj) || ObjectType(procObj) != "Process" {
		err = ProcessError(fmt.Sprintf("restart expects a Process object expected but received %s.", ObjectType(procObj)), env)
		return
	}

	proc := (*Process)(ObjectValue(procObj))
	proc.Restart <- true
	return StringWithValue("OK"), nil
}
<commit_msg>Move over the reset-timeout fix.<commit_after>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the concurrency primitive functions.

package golisp

import (
	"fmt"
	"time"
	"unsafe"
)

type Process struct {
	Env           *SymbolTableFrame
	Code          *Data
	Wake          chan bool
	Abort         chan bool
	Restart       chan bool
	ScheduleTimer *time.Timer
}

func RegisterConcurrencyPrimitives() {
	MakePrimitiveFunction("fork", 1, ForkImpl)
	MakePrimitiveFunction("proc-sleep", 2, ProcSleepImpl)
	MakePrimitiveFunction("wake", 1, WakeImpl)
	MakePrimitiveFunction("schedule", 2, ScheduleImpl)
	MakePrimitiveFunction("reset-timeout", 1, ResetTimeoutImpl)
	MakePrimitiveFunction("abandon", 1, AbandonImpl)
}

func ForkImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)

	if !FunctionP(f) {
		err = ProcessError(fmt.Sprintf("fork expected a function, but received %v.", f), env)
		return
	}

	if FunctionValue(f).RequiredArgCount != 1 {
		err = ProcessError(fmt.Sprintf("fork expected a function with arity of 1, but it was %d.", FunctionValue(f).RequiredArgCount), env)
		return
	}

	proc := &Process{Env: env, Code: f, Wake: make(chan bool, 1), Abort: make(chan bool, 1), Restart: make(chan bool, 1)}
	procObj := ObjectWithTypeAndValue("Process", unsafe.Pointer(proc))

	go func() {
		_, err = FunctionValue(f).ApplyWithoutEval(InternalMakeList(procObj), env)
	}()

	return procObj, nil
}

func ProcSleepImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	procObj := Car(args)

	if !ObjectP(procObj) || ObjectType(procObj) != "Process" {
		err = ProcessError(fmt.Sprintf("proc-sleep expects a Process object expected but received %s.", ObjectType(procObj)), env)
		return
	}

	proc := (*Process)(ObjectValue(procObj))

	millis := Cadr(args)
	if !IntegerP(millis) {
		err = ProcessError(fmt.Sprintf("proc-sleep expected an integer as a delay, but received %v.", millis), env)
		return
	}

	woken := false
	select {
	case <-proc.Wake:
		woken = true
	case <-time.After(time.Duration(IntegerValue(millis)) * time.Millisecond):
	}

	return BooleanWithValue(woken), nil
}

func WakeImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	procObj := Car(args)

	if !ObjectP(procObj) || ObjectType(procObj) != "Process" {
		err = ProcessError(fmt.Sprintf("wake expects a Process object expected but received %s.", ObjectType(procObj)), env)
		return
	}

	proc := (*Process)(ObjectValue(procObj))
	proc.Wake <- true
	return StringWithValue("OK"), nil
}

func ScheduleImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	millis := Car(args)
	if !IntegerP(millis) {
		err = ProcessError(fmt.Sprintf("schedule expected an integer as a delay, but received %v.", millis), env)
		return
	}
	f := Cadr(args)

	if !FunctionP(f) {
		err = ProcessError(fmt.Sprintf("schedule expected a function, but received %v.", f), env)
		return
	}

	if FunctionValue(f).RequiredArgCount != 1 {
		err = ProcessError(fmt.Sprintf("schedule expected a function with arity of 1, but it was %d.", FunctionValue(f).RequiredArgCount), env)
		return
	}

	proc := &Process{
		Env:           env,
		Code:          f,
		Wake:          make(chan bool, 1),
		Abort:         make(chan bool, 1),
		Restart:       make(chan bool, 1),
		ScheduleTimer: time.NewTimer(time.Duration(IntegerValue(millis)) * time.Millisecond)}
	procObj := ObjectWithTypeAndValue("Process", unsafe.Pointer(proc))

	aborted := false
	go func() {
	Loop:
		for {
			select {
			case <-proc.Abort:
				aborted = true
				break Loop
			case <-proc.Restart:
				proc.ScheduleTimer.Reset(time.Duration(IntegerValue(millis)) * time.Millisecond)
			case <-proc.ScheduleTimer.C:
				_, err = FunctionValue(f).ApplyWithoutEval(InternalMakeList(procObj), env)
				break Loop
			}
		}
	}()

	return procObj, nil

}

func AbandonImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	procObj := Car(args)

	if !ObjectP(procObj) || ObjectType(procObj) != "Process" {
		err = ProcessError(fmt.Sprintf("adandon expects a Process object expected but received %s.", ObjectType(procObj)), env)
		return
	}

	proc := (*Process)(ObjectValue(procObj))
	proc.Abort <- true
	return StringWithValue("OK"), nil
}

func ResetTimeoutImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	procObj := Car(args)

	if !ObjectP(procObj) || ObjectType(procObj) != "Process" {
		err = ProcessError(fmt.Sprintf("restart expects a Process object expected but received %s.", ObjectType(procObj)), env)
		return
	}

	proc := (*Process)(ObjectValue(procObj))
	var str string
	select {
	case proc.Restart <- true:
		str = "OK"
	default:
		str = "task was already completed or abandoned"
	}
	return StringWithValue(str), nil
}
<|endoftext|>


<commit_before>// This utility updates the config block on crazyflie
package update

import (
	"flag"
	"log"

	"github.com/samofly/cflie"
	"github.com/samofly/cflie/boot"
)

var flags = flag.NewFlagSet("config.update", flag.ExitOnError)
var channel = flags.Int("channel", 0, "Radio channel (1..125); ch=119 used by radio bootloader; ch=10 is a factory setting")

func Main() {
	flags.Parse(flag.Args()[2:])

	log.Printf("Connecting to bootloader, please, restart Crazyflie...")
	dev, info, err := boot.Cold()
	if err != nil {
		log.Fatal(err)
	}
	defer dev.Close()

	conf, err := boot.ReadConfig(dev, info)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Config block: %+v", conf)

	if *channel != 0 {
		if *channel > cflie.MaxChannel {
			log.Fatal("Max channel: %d", cflie.MaxChannel)
		}
		if *channel <= 0 {
			log.Fatal("Channel must be positive")
		}
		conf.Channel = byte(*channel)
	}

	if err = boot.WriteConfig(dev, info, conf); err != nil {
		log.Fatal("WriteConfig: ", err)
	}
	log.Printf("Config updated, validating")

	conf2, err := boot.ReadConfig(dev, info)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Config block: %+v", conf2)

	if conf != conf2 {
		log.Fatal("Config block update failed. Want: %+v, got: %+v", conf, conf2)
	}
	log.Printf("OK")
}
<commit_msg>Implement cflie config update -speed<commit_after>// This utility updates the config block on crazyflie
package update

import (
	"flag"
	"log"

	"github.com/samofly/cflie"
	"github.com/samofly/cflie/boot"
)

var flags = flag.NewFlagSet("config.update", flag.ExitOnError)
var channel = flags.Int("channel", 0, "Radio channel (1..125); ch=119 used by radio bootloader; ch=10 is a factory setting")
var speed = flags.Int("speed", -1, "Radio speed. 0: 250 Kbit/s, 1: 1 Mbit/s, 2: 2 Mbit/s")

func Main() {
	flags.Parse(flag.Args()[2:])

	log.Printf("Connecting to bootloader, please, restart Crazyflie...")
	dev, info, err := boot.Cold()
	if err != nil {
		log.Fatal(err)
	}
	defer dev.Close()

	conf, err := boot.ReadConfig(dev, info)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Config block: %+v", conf)

	if *channel != 0 {
		if *channel > cflie.MaxChannel {
			log.Fatal("Max channel: %d", cflie.MaxChannel)
		}
		if *channel <= 0 {
			log.Fatal("Channel must be positive")
		}
		conf.Channel = byte(*channel)
	}

	if *speed > 2 {
		log.Fatal("Max speed: 2")
	}
	if *speed >= 0 {
		conf.Speed = byte(*speed)
	}

	if err = boot.WriteConfig(dev, info, conf); err != nil {
		log.Fatal("WriteConfig: ", err)
	}
	log.Printf("Config updated, validating")

	conf2, err := boot.ReadConfig(dev, info)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Config block: %+v", conf2)

	if conf != conf2 {
		log.Fatal("Config block update failed. Want: %+v, got: %+v", conf, conf2)
	}
	log.Printf("OK")
}
<|endoftext|>


<commit_before>package printer

import (
	"fmt"
	"io"
	"strconv"
	"strings"
)

const (
	NIL  = "nil"
	NULL = "NULL"
	IOTA = "iota"
)

//
// C implement the Printer interface for C programs
//
type CPrinter struct {
	Printer

	level    int
	sameline bool
	w        io.Writer

	iota int // incremented when 'const n = iota' or 'const n' - XXX: need to add a way to reset it
}

func (p *CPrinter) SetWriter(w io.Writer) {
	p.w = w
}

func (p *CPrinter) UpdateLevel(delta int) {
	p.level += delta
}

func (p *CPrinter) SameLine() {
	p.sameline = true
}

func (p *CPrinter) IsSameLine() bool {
	return p.sameline
}

func (p *CPrinter) GetSeparator(ftype FieldType) string {
    if ftype == METHOD || ftype == FIELD {
        return ";\n" + p.indent()
    } else {
        return ", "
    }
}

func (p *CPrinter) indent() string {
	if p.sameline {
		p.sameline = false
		return ""
	}

	return strings.Repeat("  ", p.level)
}

func (p *CPrinter) Print(values ...string) {
	fmt.Fprint(p.w, strings.Join(values, " "))
}

func (p *CPrinter) PrintLevel(term string, values ...string) {
	fmt.Fprint(p.w, p.indent(), strings.Join(values, " "), term)
}

func (p *CPrinter) PrintLevelIn(term string, values ...string) {
	p.level -= 1
	fmt.Fprint(p.w, p.indent(), strings.Join(values, " "), term)
	p.level += 1
}

func (p *CPrinter) PrintPackage(name string) {
	p.PrintLevel(NL, "//package", name)
	p.PrintLevel(NL, "#include <go.h>")
}

func (p *CPrinter) PrintImport(name, path string) {
    p.PrintLevel(NL, "//import", name, path)
}

func (p *CPrinter) PrintType(name, typedef string) {
	p.PrintLevel(SEMI, "typedef", typedef, name)
}

func (p *CPrinter) PrintValue(vtype, typedef, names, values string, ntuple, vtuple bool) {
	if vtype == "var" {
		vtype = ""
	} else if vtype == "const" && len(values) == 0 {
		values = p.FormatIdent(IOTA)
	}

	if len(typedef) == 0 {
		typedef, values = GuessType(values)
	}

	if ntuple && len(values) > 0 {
		names = fmt.Sprintf("tie(%s)", names)
	}

	p.PrintLevel(NONE, vtype, typedef, names)

	if len(values) > 0 {
		if vtuple {
			values = fmt.Sprintf("make_tuple(%s)", values)
		}

		p.Print(" =", values)
	}
	p.Print(";\n")
}

func (p *CPrinter) PrintStmt(stmt, expr string) {
	if len(stmt) > 0 {
		p.PrintLevel(SEMI, stmt, expr)
	} else {
		p.PrintLevel(SEMI, expr)
	}
}

func (p *CPrinter) PrintReturn(expr string, tuple bool) {
	if tuple {
		expr = fmt.Sprintf("make_tuple(%s)", expr)
	}

	p.PrintStmt("return", expr)
}

func (p *CPrinter) PrintFunc(receiver, name, params, results string) {
	if len(results) == 0 {
		results = "void"
	} else if IsMultiValue(results) {
		results = fmt.Sprintf("tuple<%s>", results)
	}

	if len(receiver) > 0 {
		parts := strings.SplitN(receiver, " ", 2)
		receiver = "/* " + parts[1] + " */ " + parts[0]
	}

	fmt.Fprintf(p.w, "%s %s::%s(%s) ", results, receiver, name, params)
}

func (p *CPrinter) PrintFor(init, cond, post string) {
	onlycond := len(init) == 0 && len(post) == 0

	if len(cond) == 0 {
		cond = "true"
	}

	if onlycond {
		// make it a while
		p.PrintLevel(NONE, "while (", cond)
	} else {
		p.PrintLevel(NONE, "for (")
		if len(init) > 0 {
			p.Print(init)
		}
		p.Print("; ", cond, "; ")
		if len(post) > 0 {
			p.Print(post)
		}

	}
	p.Print(") ")
}

func (p *CPrinter) PrintRange(key, value, expr string) {
	p.PrintLevel(NONE, "for", key)

	if len(value) > 0 {
		p.Print(",", value)
	}

	p.Print(" := range", expr)

}

func (p *CPrinter) PrintSwitch(init, expr string) {
	p.PrintLevel(NONE, "switch ")
	if len(init) > 0 {
		p.Print(init + "; ")
	}
	p.Print(expr)
}

func (p *CPrinter) PrintCase(expr string) {
	if len(expr) > 0 {
		p.PrintLevel(NL, "case", expr+":")
	} else {
		p.PrintLevel(NL, "default:")
	}
}

func (p *CPrinter) PrintIf(init, cond string) {
	if len(init) > 0 {
		p.PrintLevel(NONE, init+" if ")
	} else {
		p.PrintLevel(NONE, "if ")
	}
	p.Print("(", cond, ") ")
}

func (p *CPrinter) PrintElse() {
	p.Print(" else ")
}

func (p *CPrinter) PrintEmpty() {
	p.PrintLevel(SEMI, "")
}

func (p *CPrinter) PrintAssignment(lhs, op, rhs string, ltuple, rtuple bool) {
	if op == ":=" {
		// := means there are new variables to be declared (but of course I don't know the real type)
		rtype, rvalue := GuessType(rhs)
		lhs = rtype + " " + lhs
		rhs = rvalue
		op = "="
	}

	if ltuple {
		lhs = fmt.Sprintf("tie(%s)", lhs)
	}

	if rtuple {
		rhs = fmt.Sprintf("make_tuple(%s)", rhs)
	}

	p.PrintLevel(SEMI, lhs, op, rhs)
}

func (p *CPrinter) PrintSend(ch, value string) {
	p.PrintLevel(SEMI, fmt.Sprintf("Channel::Send(%s, %s)", ch, value))
}

func (p *CPrinter) FormatIdent(id string) (ret string) {
	switch id {
	case NIL:
		return NULL

	case IOTA:
		ret = strconv.Itoa(p.iota)
		p.iota += 1

	default:
		ret = id
	}

	return
}

func (p *CPrinter) FormatLiteral(lit string) string {
	if len(lit) == 0 {
		return lit
	}

	if lit[0] == '`' {
		lit = strings.Replace(lit[1:len(lit)-1], `"`, `\\"`, -1)
		lit = strings.Replace(lit, "\n", "\\n", -1)
		lit = `"` + lit + `"`
	}

	return lit
}

func (p *CPrinter) FormatUnary(op, operand string) string {
	if op == "<-" {
		return fmt.Sprintf("Channel::Receive(%s)", operand)
	}

	return fmt.Sprintf("%s%s", op, operand)
}

func (p *CPrinter) FormatBinary(lhs, op, rhs string) string {
	return fmt.Sprintf("%s %s %s", lhs, op, rhs)
}

func (p *CPrinter) FormatPair(v Pair, t FieldType) string {
	name, value := v.Name(), v.Value()

	if strings.HasPrefix(value, "[") {
		i := strings.LastIndex(value, "]")
		if i < 0 {
			// it should be an error

		} else {
			arr := value[:i+1]
			value = value[i+1:]

			if len(name) > 0 {
				name += arr
			} else {
				value += arr
			}
		}
	}

	/*
		if strings.HasPrefix(value, "*") {
			for i, c := range value {
				if c != '*' {
					name = value[:i] + name
					value = value[i:]
					break
				}
			}
		}
	*/

	if strings.HasPrefix(value, "*") {
		i := strings.LastIndex(value, "*") + 1
		value = value[i:] + value[0:i]
	}

	if t == METHOD {
		return "virtual " + fmt.Sprintf(value, name)
	} else if t == RESULT && len(name) > 0 {
		return fmt.Sprintf("%s /* %s */", value, name)
	} else if len(name) > 0 && len(value) > 0 {
		return value + " " + name
	} else {
		return value + name
	}
}

func (p *CPrinter) FormatArray(len, elt string) string {
	return fmt.Sprintf("[%s]%s", len, elt)
}

func (p *CPrinter) FormatArrayIndex(array, index string) string {
	return fmt.Sprintf("%s[%s]", array, index)
}

func (p *CPrinter) FormatSlice(slice, low, high, max string) string {
	if max == "" {
		return fmt.Sprintf("%s[%s:%s]", slice, low, high)
	} else {
		return fmt.Sprintf("%s[%s:%s:%s]", slice, low, high, max)
	}
}

func (p *CPrinter) FormatMap(key, elt string) string {
	return fmt.Sprintf("map<%s, %s>", key, elt)
}

func (p *CPrinter) FormatKeyValue(key, value string) string {
	return fmt.Sprintf("{%s, %s}", key, value)
}

func (p *CPrinter) FormatStruct(fields string) string {
	if len(fields) > 0 {
		return fmt.Sprintf("struct {\n%s%s\n%s}", p.indent(), fields, p.indent())
	} else {
		return fmt.Sprintf("struct{}")
	}
}

func (p *CPrinter) FormatInterface(methods string) string {
	if len(methods) > 0 {
		return fmt.Sprintf("struct {\n%s%s\n%s}", p.indent(), methods, p.indent())
	} else {
		return fmt.Sprintf("struct{}")
	}
}

func (p *CPrinter) FormatChan(chdir, mtype string) string {
	var chtype string

	switch chdir {
	case CHAN_BIDI:
		chtype = "Channel::Chan"
	case CHAN_SEND:
		chtype = "Channel::SendChan"
	case CHAN_RECV:
		chtype = "Channel::ReceiveChan"
	}

	return fmt.Sprintf("%s<%s>", chtype, mtype)
}

func (p *CPrinter) FormatCall(fun, args string) string {
	switch fun {
	case "fmt.Printf":
		fun = "printf"
	case "fmt.Sprintf":
		fun = "sprintf"
	case "fmt.Fprintf":
		fun = "fprintf"
	case "fmt.Println":
		fun = "fprintf"
		args = `"%s\n", ` + args
	case "os.Open":
		fun = "open"
	}

	return fmt.Sprintf("%s(%s)", fun, args)
}

func (p *CPrinter) FormatFuncType(params, results string) string {
	if len(results) == 0 {
		results = "void"
	} else if IsMultiValue(results) {
		results = fmt.Sprintf("tuple<%s>", results)
	}

	return fmt.Sprintf("%s %%s(%s)", results, params)
}

func (p *CPrinter) FormatFuncLit(ftype, body string) string {
	return fmt.Sprintf(ftype+" %s", "func", body)
}

//
// Guess type and return type and new value
//
func GuessType(value string) (string, string) {
	vtype := "void"

	if len(value) == 0 {
		return vtype, value
	}

	switch value[0] {
	case '[':
		// array or map declaration
		i := strings.Index(value, "{")
		if i >= 0 {
			vtype = value[:i]
			value = value[i:]
		}
	case '\'':
		vtype = "char"
	case '"':
		vtype = "string"

	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		vtype = "int"

	default:
		switch value {
		case "true", "false":
			vtype = "bool"

		case NIL, NULL:
			vtype = "void*"
		}
	}

	return vtype, value
}

func IsPublic(name string) bool {
	return name[0] >= 'A' && name[0] <= 'Z'
}

func IsMultiValue(expr string) bool {
	return strings.Contains(expr, ",")
}
<commit_msg>special processing of main() and added more conversions for fmt methods and print/println<commit_after>package printer

import (
	"fmt"
	"io"
	"strconv"
	"strings"
)

const (
	NIL  = "nil"
	NULL = "NULL"
	IOTA = "iota"
)

//
// C implement the Printer interface for C programs
//
type CPrinter struct {
	Printer

	level    int
	sameline bool
	w        io.Writer

	iota int // incremented when 'const n = iota' or 'const n' - XXX: need to add a way to reset it
}

func (p *CPrinter) SetWriter(w io.Writer) {
	p.w = w
}

func (p *CPrinter) UpdateLevel(delta int) {
	p.level += delta
}

func (p *CPrinter) SameLine() {
	p.sameline = true
}

func (p *CPrinter) IsSameLine() bool {
	return p.sameline
}

func (p *CPrinter) GetSeparator(ftype FieldType) string {
    if ftype == METHOD || ftype == FIELD {
        return ";\n" + p.indent()
    } else {
        return ", "
    }
}

func (p *CPrinter) indent() string {
	if p.sameline {
		p.sameline = false
		return ""
	}

	return strings.Repeat("  ", p.level)
}

func (p *CPrinter) Print(values ...string) {
	fmt.Fprint(p.w, strings.Join(values, " "))
}

func (p *CPrinter) PrintLevel(term string, values ...string) {
	fmt.Fprint(p.w, p.indent(), strings.Join(values, " "), term)
}

func (p *CPrinter) PrintLevelIn(term string, values ...string) {
	p.level -= 1
	fmt.Fprint(p.w, p.indent(), strings.Join(values, " "), term)
	p.level += 1
}

func (p *CPrinter) PrintPackage(name string) {
	p.PrintLevel(NL, "//package", name)
	p.PrintLevel(NL, "#include <go.h>")
}

func (p *CPrinter) PrintImport(name, path string) {
    p.PrintLevel(NL, "//import", name, path)
}

func (p *CPrinter) PrintType(name, typedef string) {
	p.PrintLevel(SEMI, "typedef", typedef, name)
}

func (p *CPrinter) PrintValue(vtype, typedef, names, values string, ntuple, vtuple bool) {
	if vtype == "var" {
		vtype = ""
	} else if vtype == "const" && len(values) == 0 {
		values = p.FormatIdent(IOTA)
	}

	if len(typedef) == 0 {
		typedef, values = GuessType(values)
	}

	if ntuple && len(values) > 0 {
		names = fmt.Sprintf("tie(%s)", names)
	}

	p.PrintLevel(NONE, vtype, typedef, names)

	if len(values) > 0 {
		if vtuple {
			values = fmt.Sprintf("make_tuple(%s)", values)
		}

		p.Print(" =", values)
	}
	p.Print(";\n")
}

func (p *CPrinter) PrintStmt(stmt, expr string) {
	if len(stmt) > 0 {
		p.PrintLevel(SEMI, stmt, expr)
	} else {
		p.PrintLevel(SEMI, expr)
	}
}

func (p *CPrinter) PrintReturn(expr string, tuple bool) {
	if tuple {
		expr = fmt.Sprintf("make_tuple(%s)", expr)
	}

	p.PrintStmt("return", expr)
}

func (p *CPrinter) PrintFunc(receiver, name, params, results string) {
        if len(receiver) == 0 && len(params) == 0 && len(results) == 0 && name == "main" {
            // the "main"
            results = "int"
            params = "int argc, char **argv"
        } else {
            if len(results) == 0 {
                    results = "void"
            } else if IsMultiValue(results) {
                    results = fmt.Sprintf("tuple<%s>", results)
            }

            if len(receiver) > 0 {
                    parts := strings.SplitN(receiver, " ", 2)
                    receiver = "/* " + parts[1] + " */ " + parts[0] + "::"
            }
        }

	fmt.Fprintf(p.w, "%s %s%s(%s) ", results, receiver, name, params)
}

func (p *CPrinter) PrintFor(init, cond, post string) {
        init = strings.TrimRight(init, SEMI)
        post = strings.TrimRight(post, SEMI)

	onlycond := len(init) == 0 && len(post) == 0

	if len(cond) == 0 {
		cond = "true"
	}

	if onlycond {
		// make it a while
		p.PrintLevel(NONE, "while (", cond)
	} else {
		p.PrintLevel(NONE, "for (")
		if len(init) > 0 {
			p.Print(init)
		}
		p.Print("; " + cond + ";")
		if len(post) > 0 {
			p.Print(" " + post)
		}

	}
	p.Print(") ")
}

func (p *CPrinter) PrintRange(key, value, expr string) {
	p.PrintLevel(NONE, "for", key)

	if len(value) > 0 {
		p.Print(",", value)
	}

	p.Print(" := range", expr)

}

func (p *CPrinter) PrintSwitch(init, expr string) {
	if len(init) > 0 {
		p.PrintLevel(SEMI, init)
	}
	p.PrintLevel(NONE, "switch (", expr, ")")
}

func (p *CPrinter) PrintCase(expr string) {
	if len(expr) > 0 {
		p.PrintLevel(NL, "case", expr+":")
	} else {
		p.PrintLevel(NL, "default:")
	}
}

func (p *CPrinter) PrintIf(init, cond string) {
	if len(init) > 0 {
		p.PrintLevel(NONE, init+" if ")
	} else {
		p.PrintLevel(NONE, "if ")
	}
	p.Print("(", cond, ") ")
}

func (p *CPrinter) PrintElse() {
	p.Print(" else ")
}

func (p *CPrinter) PrintEmpty() {
	p.PrintLevel(SEMI, "")
}

func (p *CPrinter) PrintAssignment(lhs, op, rhs string, ltuple, rtuple bool) {
	if op == ":=" {
		// := means there are new variables to be declared (but of course I don't know the real type)
		rtype, rvalue := GuessType(rhs)
		lhs = rtype + " " + lhs
		rhs = rvalue
		op = "="
	}

	if ltuple {
		lhs = fmt.Sprintf("tie(%s)", lhs)
	}

	if rtuple {
		rhs = fmt.Sprintf("make_tuple(%s)", rhs)
	}

	p.PrintLevel(SEMI, lhs, op, rhs)
}

func (p *CPrinter) PrintSend(ch, value string) {
	p.PrintLevel(SEMI, fmt.Sprintf("Channel::Send(%s, %s)", ch, value))
}

func (p *CPrinter) FormatIdent(id string) (ret string) {
	switch id {
	case NIL:
		return NULL

	case IOTA:
		ret = strconv.Itoa(p.iota)
		p.iota += 1

	default:
		ret = id
	}

	return
}

func (p *CPrinter) FormatLiteral(lit string) string {
	if len(lit) == 0 {
		return lit
	}

	if lit[0] == '`' {
		lit = strings.Replace(lit[1:len(lit)-1], `"`, `\\"`, -1)
		lit = strings.Replace(lit, "\n", "\\n", -1)
		lit = `"` + lit + `"`
	}

	return lit
}

func (p *CPrinter) FormatUnary(op, operand string) string {
	if op == "<-" {
		return fmt.Sprintf("Channel::Receive(%s)", operand)
	}

	return fmt.Sprintf("%s%s", op, operand)
}

func (p *CPrinter) FormatBinary(lhs, op, rhs string) string {
	return fmt.Sprintf("%s %s %s", lhs, op, rhs)
}

func (p *CPrinter) FormatPair(v Pair, t FieldType) string {
	name, value := v.Name(), v.Value()

	if strings.HasPrefix(value, "[") {
		i := strings.LastIndex(value, "]")
		if i < 0 {
			// it should be an error

		} else {
			arr := value[:i+1]
			value = value[i+1:]

			if len(name) > 0 {
				name += arr
			} else {
				value += arr
			}
		}
	}

	/*
		if strings.HasPrefix(value, "*") {
			for i, c := range value {
				if c != '*' {
					name = value[:i] + name
					value = value[i:]
					break
				}
			}
		}
	*/

	if strings.HasPrefix(value, "*") {
		i := strings.LastIndex(value, "*") + 1
		value = value[i:] + value[0:i]
	}

	if t == METHOD {
		return "virtual " + fmt.Sprintf(value, name)
	} else if t == RESULT && len(name) > 0 {
		return fmt.Sprintf("%s /* %s */", value, name)
	} else if len(name) > 0 && len(value) > 0 {
		return value + " " + name
	} else {
		return value + name
	}
}

func (p *CPrinter) FormatArray(len, elt string) string {
	return fmt.Sprintf("[%s]%s", len, elt)
}

func (p *CPrinter) FormatArrayIndex(array, index string) string {
	return fmt.Sprintf("%s[%s]", array, index)
}

func (p *CPrinter) FormatSlice(slice, low, high, max string) string {
	if max == "" {
		return fmt.Sprintf("%s[%s:%s]", slice, low, high)
	} else {
		return fmt.Sprintf("%s[%s:%s:%s]", slice, low, high, max)
	}
}

func (p *CPrinter) FormatMap(key, elt string) string {
	return fmt.Sprintf("map<%s, %s>", key, elt)
}

func (p *CPrinter) FormatKeyValue(key, value string) string {
	return fmt.Sprintf("{%s, %s}", key, value)
}

func (p *CPrinter) FormatStruct(fields string) string {
	if len(fields) > 0 {
		return fmt.Sprintf("struct {\n%s%s\n%s}", p.indent(), fields, p.indent())
	} else {
		return fmt.Sprintf("struct{}")
	}
}

func (p *CPrinter) FormatInterface(methods string) string {
	if len(methods) > 0 {
		return fmt.Sprintf("struct {\n%s%s\n%s}", p.indent(), methods, p.indent())
	} else {
		return fmt.Sprintf("struct{}")
	}
}

func (p *CPrinter) FormatChan(chdir, mtype string) string {
	var chtype string

	switch chdir {
	case CHAN_BIDI:
		chtype = "Channel::Chan"
	case CHAN_SEND:
		chtype = "Channel::SendChan"
	case CHAN_RECV:
		chtype = "Channel::ReceiveChan"
	}

	return fmt.Sprintf("%s<%s>", chtype, mtype)
}

func (p *CPrinter) FormatCall(fun, args string) string {
	switch fun {
	case "fmt.Sprintf":
		fun = "sprintf"
	case "fmt.Fprintf":
		fun = "fprintf"
	case "fmt.Printf":
		fun = "printf"
	case "fmt.Fprintln":
		fun = "fputs"
        case "fmt.Print", "print":
                fun = "printf"
                args = fmt.Sprintf(`"%%s", %s`, args)
	case "fmt.Println", "println":
		fun = "puts"
	case "os.Open":
		fun = "open"
	}

	return fmt.Sprintf("%s(%s)", fun, args)
}

func (p *CPrinter) FormatFuncType(params, results string) string {
	if len(results) == 0 {
		results = "void"
	} else if IsMultiValue(results) {
		results = fmt.Sprintf("tuple<%s>", results)
	}

	return fmt.Sprintf("%s %%s(%s)", results, params)
}

func (p *CPrinter) FormatFuncLit(ftype, body string) string {
	return fmt.Sprintf(ftype+" %s", "func", body)
}

//
// Guess type and return type and new value
//
func GuessType(value string) (string, string) {
	vtype := "void"

	if len(value) == 0 {
		return vtype, value
	}

	switch value[0] {
	case '[':
		// array or map declaration
		i := strings.Index(value, "{")
		if i >= 0 {
			vtype = value[:i]
			value = value[i:]
		}
	case '\'':
		vtype = "char"
	case '"':
		vtype = "string"

	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		vtype = "int"

	default:
		switch value {
		case "true", "false":
			vtype = "bool"

		case NIL, NULL:
			vtype = "void*"
		}
	}

	return vtype, value
}

func IsPublic(name string) bool {
	return name[0] >= 'A' && name[0] <= 'Z'
}

func IsMultiValue(expr string) bool {
	return strings.Contains(expr, ",")
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Nuclio 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 dockerclient

import (
	"fmt"
	"path"
	"strings"

	"github.com/nuclio/nuclio/pkg/cmdrunner"
	"github.com/nuclio/nuclio/pkg/errors"

	"github.com/nuclio/nuclio-sdk"
	"k8s.io/apimachinery/pkg/util/json"
)

// ShellClient is a docker client that uses the shell to communicate with docker
type ShellClient struct {
	logger    nuclio.Logger
	cmdRunner cmdrunner.CmdRunner
}

// NewShellClient creates a new docker client
func NewShellClient(parentLogger nuclio.Logger, runner cmdrunner.CmdRunner) (*ShellClient, error) {
	var err error

	newClient := &ShellClient{
		logger:    parentLogger.GetChild("docker"),
		cmdRunner: runner,
	}

	// set cmdrunner
	if newClient.cmdRunner == nil {
		newClient.cmdRunner, err = cmdrunner.NewShellRunner(newClient.logger)
		if err != nil {
			return nil, errors.Wrap(err, "Failed to create command runner")
		}
	}

	// verify
	_, err = newClient.cmdRunner.Run(nil, "docker version")
	if err != nil {
		return nil, errors.Wrap(err, "No docker client found")
	}

	return newClient, nil
}

// Build will build a docker image, given build options
func (c *ShellClient) Build(buildOptions *BuildOptions) error {
	c.logger.DebugWith("Building image", "image", buildOptions.ImageName)

	// if context dir is not passed, use the dir containing the dockerfile
	if buildOptions.ContextDir == "" && buildOptions.DockerfilePath != "" {
		buildOptions.ContextDir = path.Dir(buildOptions.DockerfilePath)
	}

	// user can only specify context directory
	if buildOptions.DockerfilePath == "" && buildOptions.ContextDir != "" {
		buildOptions.DockerfilePath = path.Join(buildOptions.ContextDir, "Dockerfile")
	}

	cacheOption := ""
	if buildOptions.NoCache {
		cacheOption = "--no-cache"
	}

	_, err := c.runCommand(&cmdrunner.RunOptions{WorkingDir: &buildOptions.ContextDir},
		"docker build --force-rm -t %s -f %s %s .",
		buildOptions.ImageName,
		buildOptions.DockerfilePath,
		cacheOption)

	return err
}

// CopyObjectsFromImage copies objects (files, directories) from a given image to local storage. it does
// this through an intermediate container which is deleted afterwards
func (c *ShellClient) CopyObjectsFromImage(imageName string, objectsToCopy map[string]string, allowCopyErrors bool) error {
	runResult, err := c.runCommand(nil, "docker create %s", imageName)
	if err != nil {
		return errors.Wrapf(err, "Failed to create container from %s", imageName)
	}

	containerID := runResult.Output
	containerID = strings.TrimSpace(containerID)
	defer func() {
		c.runCommand(nil, "docker rm -f %s", containerID)
	}()

	for objectImagePath, objectLocalPath := range objectsToCopy {
		_, err = c.runCommand(nil, "docker cp %s:%s %s", containerID, objectImagePath, objectLocalPath)
		if err != nil && !allowCopyErrors {
			return errors.Wrapf(err, "Can't copy %s:%s -> %s", containerID, objectImagePath, objectLocalPath)
		}
	}

	return nil
}

// PushImage pushes a local image to a remote docker repository
func (c *ShellClient) PushImage(imageName string, registryURL string) error {
	taggedImageName := registryURL + "/" + imageName

	c.logger.InfoWith("Pushing image", "from", imageName, "to", taggedImageName)

	_, err := c.runCommand(nil, "docker tag %s %s", imageName, taggedImageName)
	if err != nil {
		return errors.Wrap(err, "Failed to tag image")
	}

	_, err = c.runCommand(nil, "docker push %s", taggedImageName)
	if err != nil {
		return errors.Wrap(err, "Failed to push image")
	}

	return nil
}

// PullImage pulls an image from a remote docker repository
func (c *ShellClient) PullImage(imageURL string) error {
	_, err := c.runCommand(nil, "docker pull %s", imageURL)
	return err
}

// RemoveImage will remove (delete) a local image
func (c *ShellClient) RemoveImage(imageName string) error {
	_, err := c.runCommand(nil, "docker rmi -f %s", imageName)
	return err
}

// RunContainer will run a container based on an image and run options
func (c *ShellClient) RunContainer(imageName string, runOptions *RunOptions) (string, error) {
	portsArgument := ""

	for localPort, dockerPort := range runOptions.Ports {
		portsArgument += fmt.Sprintf("-p %d:%d ", localPort, dockerPort)
	}

	nameArgument := ""
	if runOptions.ContainerName != "" {
		nameArgument = fmt.Sprintf("--name %s", runOptions.ContainerName)
	}

	netArgument := ""
	if runOptions.NetworkType != "" {
		nameArgument = fmt.Sprintf("--net %s", runOptions.NetworkType)
	}

	labelArgument := ""
	if runOptions.Labels != nil {
		for labelName, labelValue := range runOptions.Labels {
			labelArgument += fmt.Sprintf("--label %s='%s' ", labelName, labelValue)
		}
	}

	envArgument := ""
	if runOptions.Env != nil {
		for envName, envValue := range runOptions.Env {
			labelArgument += fmt.Sprintf("--env %s=%s ", envName, envValue)
		}
	}

	volumeArgument := ""
	if runOptions.Volumes != nil {
		for volumeHostPath, volumeContainerPath := range runOptions.Volumes {
			volumeArgument += fmt.Sprintf("--volume %s:%s ", volumeHostPath, volumeContainerPath)
		}
	}

	runResult, err := c.cmdRunner.Run(nil,
		"docker run -d %s %s %s %s %s %s %s",
		portsArgument,
		nameArgument,
		netArgument,
		labelArgument,
		envArgument,
		volumeArgument,
		imageName)

	if err != nil {
		c.logger.WarnWith("Failed to run container",
			"err", err,
			"stdout", runResult.Output,
			"stderr", runResult.Stderr)

		return "", err
	}

	stdoutLines := strings.Split(runResult.Output, "\n")
	lastStdoutLine := c.getLastNonEmptyLine(stdoutLines)

	// make sure there are no spaces in the ID
	if strings.Contains(lastStdoutLine, " ") {
		return "", fmt.Errorf("Output from docker command includes more than just ID: %s", lastStdoutLine)
	}

	return lastStdoutLine, err
}

// RemoveContainer removes a container given a container ID
func (c *ShellClient) RemoveContainer(containerID string) error {
	_, err := c.runCommand(nil, "docker rm -f %s", containerID)
	return err
}

// GetContainerLogs returns raw logs from a given container ID
// Concatenating stdout and stderr since there's no way to re-interlace them
func (c *ShellClient) GetContainerLogs(containerID string) (string, error) {
	runResult, err := c.runCommand(nil, "docker logs %s", containerID)
	return runResult.Output, err
}

// GetContainers returns a list of container IDs which match a certain criteria
func (c *ShellClient) GetContainers(options *GetContainerOptions) ([]Container, error) {
	c.logger.DebugWith("Getting containers", "options", options)

	filterArgument := ""
	for labelName, labelValue := range options.Labels {
		filterArgument += fmt.Sprintf(`--filter "label=%s=%s" `, labelName, labelValue)
	}

	runResult, err := c.runCommand(nil, "docker ps -q %s", filterArgument)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to get containers")
	}

	containerIDsAsString := runResult.Output
	if len(containerIDsAsString) == 0 {
		return []Container{}, nil
	}

	runResult, err = c.runCommand(nil,
		"docker inspect %s",
		strings.Replace(containerIDsAsString, "\n", " ", -1))
	if err != nil {
		return nil, errors.Wrap(err, "Failed to inspect containers")
	}

	containersInfoString := runResult.Output

	var containersInfo []Container

	// parse the result
	if err := json.Unmarshal([]byte(containersInfoString), &containersInfo); err != nil {
		return nil, errors.Wrap(err, "Failed to parse inspect response")
	}

	return containersInfo, nil
}

// LogIn allows docker client to access secured registries
func (c *ShellClient) LogIn(options *LogInOptions) error {
	_, err := c.runCommand(nil, `docker login -u %s -p '%s' %s`,
		options.Username,
		options.Password,
		options.URL)

	return err
}

func (c *ShellClient) runCommand(runOptions *cmdrunner.RunOptions, format string, vars ...interface{}) (cmdrunner.RunResult, error) {

	// if user
	if runOptions == nil {
		runOptions = &cmdrunner.RunOptions{}
	}

	// make sure output mode is that stdout and stderr are two different streams (don't combine)
	runOptions.CaptureOutputMode = cmdrunner.CaptureOutputModeStdout

	runResult, err := c.cmdRunner.Run(runOptions, format, vars...)

	if runResult.Stderr != "" {
		c.logger.WarnWith("Docker command outputted to stderr - this may result in errors",
			"cmd", fmt.Sprintf(format, vars),
			"stderr", runResult.Stderr)
	}

	return runResult, err
}

func (c *ShellClient) getLastNonEmptyLine(lines []string) string {

	// iterate backwards over the libes
	for idx := len(lines) - 1; idx >= 0; idx-- {
		if lines[idx] != "" {
			return lines[idx]
		}
	}

	return ""
}
<commit_msg>Added Pull Image log (#423)<commit_after>/*
Copyright 2017 The Nuclio 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 dockerclient

import (
	"fmt"
	"path"
	"strings"

	"github.com/nuclio/nuclio/pkg/cmdrunner"
	"github.com/nuclio/nuclio/pkg/errors"

	"github.com/nuclio/nuclio-sdk"
	"k8s.io/apimachinery/pkg/util/json"
)

// ShellClient is a docker client that uses the shell to communicate with docker
type ShellClient struct {
	logger    nuclio.Logger
	cmdRunner cmdrunner.CmdRunner
}

// NewShellClient creates a new docker client
func NewShellClient(parentLogger nuclio.Logger, runner cmdrunner.CmdRunner) (*ShellClient, error) {
	var err error

	newClient := &ShellClient{
		logger:    parentLogger.GetChild("docker"),
		cmdRunner: runner,
	}

	// set cmdrunner
	if newClient.cmdRunner == nil {
		newClient.cmdRunner, err = cmdrunner.NewShellRunner(newClient.logger)
		if err != nil {
			return nil, errors.Wrap(err, "Failed to create command runner")
		}
	}

	// verify
	_, err = newClient.cmdRunner.Run(nil, "docker version")
	if err != nil {
		return nil, errors.Wrap(err, "No docker client found")
	}

	return newClient, nil
}

// Build will build a docker image, given build options
func (c *ShellClient) Build(buildOptions *BuildOptions) error {
	c.logger.DebugWith("Building image", "image", buildOptions.ImageName)

	// if context dir is not passed, use the dir containing the dockerfile
	if buildOptions.ContextDir == "" && buildOptions.DockerfilePath != "" {
		buildOptions.ContextDir = path.Dir(buildOptions.DockerfilePath)
	}

	// user can only specify context directory
	if buildOptions.DockerfilePath == "" && buildOptions.ContextDir != "" {
		buildOptions.DockerfilePath = path.Join(buildOptions.ContextDir, "Dockerfile")
	}

	cacheOption := ""
	if buildOptions.NoCache {
		cacheOption = "--no-cache"
	}

	_, err := c.runCommand(&cmdrunner.RunOptions{WorkingDir: &buildOptions.ContextDir},
		"docker build --force-rm -t %s -f %s %s .",
		buildOptions.ImageName,
		buildOptions.DockerfilePath,
		cacheOption)

	return err
}

// CopyObjectsFromImage copies objects (files, directories) from a given image to local storage. it does
// this through an intermediate container which is deleted afterwards
func (c *ShellClient) CopyObjectsFromImage(imageName string, objectsToCopy map[string]string, allowCopyErrors bool) error {
	runResult, err := c.runCommand(nil, "docker create %s", imageName)
	if err != nil {
		return errors.Wrapf(err, "Failed to create container from %s", imageName)
	}

	containerID := runResult.Output
	containerID = strings.TrimSpace(containerID)
	defer func() {
		c.runCommand(nil, "docker rm -f %s", containerID)
	}()

	for objectImagePath, objectLocalPath := range objectsToCopy {
		_, err = c.runCommand(nil, "docker cp %s:%s %s", containerID, objectImagePath, objectLocalPath)
		if err != nil && !allowCopyErrors {
			return errors.Wrapf(err, "Can't copy %s:%s -> %s", containerID, objectImagePath, objectLocalPath)
		}
	}

	return nil
}

// PushImage pushes a local image to a remote docker repository
func (c *ShellClient) PushImage(imageName string, registryURL string) error {
	taggedImageName := registryURL + "/" + imageName

	c.logger.InfoWith("Pushing image", "from", imageName, "to", taggedImageName)

	_, err := c.runCommand(nil, "docker tag %s %s", imageName, taggedImageName)
	if err != nil {
		return errors.Wrap(err, "Failed to tag image")
	}

	_, err = c.runCommand(nil, "docker push %s", taggedImageName)
	if err != nil {
		return errors.Wrap(err, "Failed to push image")
	}

	return nil
}

// PullImage pulls an image from a remote docker repository
func (c *ShellClient) PullImage(imageURL string) error {
	c.logger.InfoWith("Pulling base image", "name", imageURL)

	_, err := c.runCommand(nil, "docker pull %s", imageURL)
	return err
}

// RemoveImage will remove (delete) a local image
func (c *ShellClient) RemoveImage(imageName string) error {
	_, err := c.runCommand(nil, "docker rmi -f %s", imageName)
	return err
}

// RunContainer will run a container based on an image and run options
func (c *ShellClient) RunContainer(imageName string, runOptions *RunOptions) (string, error) {
	portsArgument := ""

	for localPort, dockerPort := range runOptions.Ports {
		portsArgument += fmt.Sprintf("-p %d:%d ", localPort, dockerPort)
	}

	nameArgument := ""
	if runOptions.ContainerName != "" {
		nameArgument = fmt.Sprintf("--name %s", runOptions.ContainerName)
	}

	netArgument := ""
	if runOptions.NetworkType != "" {
		nameArgument = fmt.Sprintf("--net %s", runOptions.NetworkType)
	}

	labelArgument := ""
	if runOptions.Labels != nil {
		for labelName, labelValue := range runOptions.Labels {
			labelArgument += fmt.Sprintf("--label %s='%s' ", labelName, labelValue)
		}
	}

	envArgument := ""
	if runOptions.Env != nil {
		for envName, envValue := range runOptions.Env {
			labelArgument += fmt.Sprintf("--env %s=%s ", envName, envValue)
		}
	}

	volumeArgument := ""
	if runOptions.Volumes != nil {
		for volumeHostPath, volumeContainerPath := range runOptions.Volumes {
			volumeArgument += fmt.Sprintf("--volume %s:%s ", volumeHostPath, volumeContainerPath)
		}
	}

	runResult, err := c.cmdRunner.Run(nil,
		"docker run -d %s %s %s %s %s %s %s",
		portsArgument,
		nameArgument,
		netArgument,
		labelArgument,
		envArgument,
		volumeArgument,
		imageName)

	if err != nil {
		c.logger.WarnWith("Failed to run container",
			"err", err,
			"stdout", runResult.Output,
			"stderr", runResult.Stderr)

		return "", err
	}

	stdoutLines := strings.Split(runResult.Output, "\n")
	lastStdoutLine := c.getLastNonEmptyLine(stdoutLines)

	// make sure there are no spaces in the ID
	if strings.Contains(lastStdoutLine, " ") {
		return "", fmt.Errorf("Output from docker command includes more than just ID: %s", lastStdoutLine)
	}

	return lastStdoutLine, err
}

// RemoveContainer removes a container given a container ID
func (c *ShellClient) RemoveContainer(containerID string) error {
	_, err := c.runCommand(nil, "docker rm -f %s", containerID)
	return err
}

// GetContainerLogs returns raw logs from a given container ID
// Concatenating stdout and stderr since there's no way to re-interlace them
func (c *ShellClient) GetContainerLogs(containerID string) (string, error) {
	runResult, err := c.runCommand(nil, "docker logs %s", containerID)
	return runResult.Output, err
}

// GetContainers returns a list of container IDs which match a certain criteria
func (c *ShellClient) GetContainers(options *GetContainerOptions) ([]Container, error) {
	c.logger.DebugWith("Getting containers", "options", options)

	filterArgument := ""
	for labelName, labelValue := range options.Labels {
		filterArgument += fmt.Sprintf(`--filter "label=%s=%s" `, labelName, labelValue)
	}

	runResult, err := c.runCommand(nil, "docker ps -q %s", filterArgument)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to get containers")
	}

	containerIDsAsString := runResult.Output
	if len(containerIDsAsString) == 0 {
		return []Container{}, nil
	}

	runResult, err = c.runCommand(nil,
		"docker inspect %s",
		strings.Replace(containerIDsAsString, "\n", " ", -1))
	if err != nil {
		return nil, errors.Wrap(err, "Failed to inspect containers")
	}

	containersInfoString := runResult.Output

	var containersInfo []Container

	// parse the result
	if err := json.Unmarshal([]byte(containersInfoString), &containersInfo); err != nil {
		return nil, errors.Wrap(err, "Failed to parse inspect response")
	}

	return containersInfo, nil
}

// LogIn allows docker client to access secured registries
func (c *ShellClient) LogIn(options *LogInOptions) error {
	_, err := c.runCommand(nil, `docker login -u %s -p '%s' %s`,
		options.Username,
		options.Password,
		options.URL)

	return err
}

func (c *ShellClient) runCommand(runOptions *cmdrunner.RunOptions, format string, vars ...interface{}) (cmdrunner.RunResult, error) {

	// if user
	if runOptions == nil {
		runOptions = &cmdrunner.RunOptions{}
	}

	// make sure output mode is that stdout and stderr are two different streams (don't combine)
	runOptions.CaptureOutputMode = cmdrunner.CaptureOutputModeStdout

	runResult, err := c.cmdRunner.Run(runOptions, format, vars...)

	if runResult.Stderr != "" {
		c.logger.WarnWith("Docker command outputted to stderr - this may result in errors",
			"cmd", fmt.Sprintf(format, vars),
			"stderr", runResult.Stderr)
	}

	return runResult, err
}

func (c *ShellClient) getLastNonEmptyLine(lines []string) string {

	// iterate backwards over the libes
	for idx := len(lines) - 1; idx >= 0; idx-- {
		if lines[idx] != "" {
			return lines[idx]
		}
	}

	return ""
}
<|endoftext|>


<commit_before>package dsdk

import (
	"context"
	"fmt"
	_path "path"
	"strconv"

	greq "github.com/levigross/grequests"
)

type AppInstance struct {
	AccessControlMode       string                 `json:"access_control_mode,omitempty" mapstructure:"access_control_mode"`
	AdminState              string                 `json:"admin_state,omitempty" mapstructure:"admin_state"`
	AppTemplate             *AppTemplate           `json:"app_template,omitempty" mapstructure:"app_template"`
	Causes                  []string               `json:"causes,omitempty" mapstructure:"causes"`
	CloneSrc                *AppInstance           `json:"clone_src,omitempty" mapstructure:"clone_src"`
	CreateMode              string                 `json:"create_mode,omitempty" mapstructure:"create_mode"`
	DeploymentState         string                 `json:"deployment_state,omitempty" mapstructure:"deployment_state"`
	Descr                   string                 `json:"descr,omitempty" mapstructure:"descr"`
	Health                  string                 `json:"health,omitempty" mapstructure:"health"`
	Id                      string                 `json:"id,omitempty" mapstructure:"id"`
	Name                    string                 `json:"name,omitempty" mapstructure:"name"`
	OpState                 string                 `json:"op_state,omitempty" mapstructure:"op_state"`
	Path                    string                 `json:"path,omitempty" mapstructure:"path"`
	RemoteRestorePercentage int                    `json:"remote_restore_percentage,omitempty" mapstructure:"remote_restore_percentage"`
	RemoteRestoreProgress   string                 `json:"remote_restore_progress,omitempty" mapstructure:"remote_restore_progress"`
	RepairPriority          string                 `json:"repair_priority,omitempty" mapstructure:"repair_priority"`
	RestorePoint            string                 `json:"restore_point,omitempty" mapstructure:"restore_point"`
	RestoreProgress         string                 `json:"restore_progress,omitempty" mapstructure:"restore_progress"`
	SnapshotPolicies        []*SnapshotPolicy      `json:"snapshot_policies,omitempty" mapstructure:"snapshot_policies"`
	Snapshots               []*Snapshot            `json:"snapshots,omitempty" mapstructure:"snapshots"`
	StorageInstances        []*StorageInstance     `json:"storage_instances,omitempty" mapstructure:"storage_instances"`
	StoragePool             []*StoragePool         `json:"storage_pool,omitempty" mapstructure:"storage_pool"`
	TemplateOverride        map[string]interface{} `json:"template_override,omitempty" mapstructure:"template_override"`
	Uuid                    string                 `json:"uuid,omitempty" mapstructure:"uuid"`
	StorageInstancesEp      *StorageInstances      `json:"-"`
}

func RegisterAppInstanceEndpoints(a *AppInstance) {
	a.StorageInstancesEp = newStorageInstances(a.Path)
	for _, si := range a.StorageInstances {
		RegisterStorageInstanceEndpoints(si)
	}
}

type AppInstances struct {
	Path string
}

type AppInstancesCreateRequest struct {
	Ctxt             context.Context        `json:"-"`
	AppTemplate      *AppTemplate           `json:"app_template,omitempty" mapstructure:"app_template"`
	CloneSnapshotSrc *Snapshot              `json:"clone_snapshot_src,omitempty" mapstructure:"clone_snapshot_src"`
	CloneVolumeSrc   *Volume                `json:"clone_volume_src,omitempty" mapstructure:"clone_volume_src"`
	CloneSrc         *AppInstance           `json:"clone_src,omitempty" mapstructure:"clone_src"`
	CreateMode       string                 `json:"create_mode,omitempty" mapstructure:"create_mode"`
	Descr            string                 `json:"descr,omitempty" mapstructure:"descr"`
	Name             string                 `json:"name,omitempty" mapstructure:"name"`
	RepairPriority   string                 `json:"repair_priority,omitempty" mapstructure:"repair_priority"`
	SnapshotPolicies []*SnapshotPolicy      `json:"snapshot_policies,omitempty" mapstructure:"snapshot_policies"`
	StorageInstances []*StorageInstance     `json:"storage_instances,omitempty" mapstructure:"storage_instances"`
	StoragePool      []*StoragePool         `json:"storage_pool,omitempty" mapstructure:"storage_pool"`
	TemplateOverride map[string]interface{} `json:"template_override,omitempty" mapstructure:"template_override"`
}

func newAppInstances(path string) *AppInstances {
	return &AppInstances{
		Path: _path.Join(path, "app_instances"),
	}
}

func (e *AppInstances) Create(ro *AppInstancesCreateRequest) (*AppInstance, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro}
	rs, apierr, err := GetConn(ro.Ctxt).Post(ro.Ctxt, e.Path, gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := &AppInstance{}
	if err = FillStruct(rs.Data, resp); err != nil {
		return nil, nil, err
	}
	RegisterAppInstanceEndpoints(resp)
	return resp, nil, nil
}

type AppInstancesListRequest struct {
	Ctxt   context.Context `json:"-"`
	Params ListParams      `json:"params,omitempty"`
}

func (e *AppInstances) List(ro *AppInstancesListRequest) ([]*AppInstance, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{
		JSON:   ro,
		Params: ro.Params.ToMap()}
	rs, apierr, err := GetConn(ro.Ctxt).GetList(ro.Ctxt, e.Path, gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := []*AppInstance{}
	for _, data := range rs.Data {
		elem := &AppInstance{}
		adata := data.(map[string]interface{})
		if err = FillStruct(adata, elem); err != nil {
			return nil, nil, err
		}
		RegisterAppInstanceEndpoints(elem)
		resp = append(resp, elem)
	}
	return resp, nil, nil
}

type AppInstancesGetRequest struct {
	Ctxt context.Context `json:"-"`
	Id   string          `json:"-"`
}

func (e *AppInstances) Get(ro *AppInstancesGetRequest) (*AppInstance, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro}
	rs, apierr, err := GetConn(ro.Ctxt).Get(ro.Ctxt, _path.Join(e.Path, ro.Id), gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := &AppInstance{}
	if err = FillStruct(rs.Data, resp); err != nil {
		return nil, nil, err
	}
	RegisterAppInstanceEndpoints(resp)
	return resp, nil, nil
}

type AppInstanceSetRequest struct {
	Ctxt               context.Context    `json:"-"`
	AdminState         string             `json:"admin_state,omitempty" mapstructure:"admin_state"`
	Descr              string             `json:"descr,omitempty" mapstructure:"descr"`
	Force              bool               `json:"force,omitempty" mapstructure:"force"`
	Name               string             `json:"name,omitempty" mapstructure:"name"`
	Provisioned        string             `json:"provisioned,omitempty" mapstructure:"provisioned"`
	RemoteProvider     string             `json:"remote_provider,omitempty" mapstructure:"remote_provider"`
	RemoteRestorePoint string             `json:"remote_restore_point,omitempty" mapstructure:"remote_restore_point"`
	RepairPriority     string             `json:"repair_priority,omitempty" mapstructure:"repair_priority"`
	RestorePoint       string             `json:"restore_point,omitempty" mapstructure:"restore_point"`
	SnapshotPolicies   []*SnapshotPolicy  `json:"snapshot_policies,omitempty" mapstructure:"snapshot_policies"`
	StorageInstances   []*StorageInstance `json:"storage_instances,omitempty" mapstructure:"storage_instances"`
	StoragePool        []*StoragePool     `json:"storage_pool,omitempty" mapstructure:"storage_pool"`
}

func (e *AppInstance) Set(ro *AppInstanceSetRequest) (*AppInstance, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro}
	rs, apierr, err := GetConn(ro.Ctxt).Put(ro.Ctxt, e.Path, gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := &AppInstance{}
	if err = FillStruct(rs.Data, resp); err != nil {
		return nil, nil, err
	}
	RegisterAppInstanceEndpoints(resp)
	return resp, nil, nil
}

type AppInstanceDeleteRequest struct {
	Ctxt  context.Context `json:"-"`
	Force bool            `json:"force,omitempty" mapstructure:"force"`
}

func (e *AppInstance) Delete(ro *AppInstanceDeleteRequest) (*AppInstance, *ApiErrorResponse, error) {
	rs, apierr, err := GetConn(ro.Ctxt).Delete(ro.Ctxt, e.Path, nil)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := &AppInstance{}
	if err = FillStruct(rs.Data, resp); err != nil {
		return nil, nil, err
	}
	RegisterAppInstanceEndpoints(resp)
	return resp, nil, nil
}

type AppInstanceMetadata map[string]string

type AppInstanceMetadataGetRequest struct {
	Ctxt context.Context `json:"-"`
}

func (e *AppInstance) GetMetadata(ro *AppInstanceMetadataGetRequest) (*AppInstanceMetadata, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro}
	rs, apierr, err := GetConn(ro.Ctxt).Get(ro.Ctxt, _path.Join(e.Path, "metadata"), gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	return stringifyResults(rs), nil, nil
}

type AppInstanceMetadataSetRequest struct {
	Ctxt     context.Context `json:"-"`
	Metadata map[string]string
}

func (e *AppInstance) SetMetadata(ro *AppInstanceMetadataSetRequest) (*AppInstanceMetadata, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro.Metadata}
	rs, apierr, err := GetConn(ro.Ctxt).Put(ro.Ctxt, _path.Join(e.Path, "metadata"), gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	return stringifyResults(rs), nil, nil
}

func stringifyResults(rs *ApiOuter) *AppInstanceMetadata {
	resp := &AppInstanceMetadata{}
	for k, v := range rs.Data {
		var nv string
		switch t := v.(type) {
		case string:
			nv = v.(string)
		case bool:
			nv = strconv.FormatBool(v.(bool))
		case int:
			nv = strconv.FormatInt(int64(v.(int)), 10)
		default:
			panic(fmt.Sprintf("Don't know this, what do?: %s", t))
		}

		(*resp)[k] = nv
	}
	return resp
}
<commit_msg>Fixing AppInstance AppTemplate structure to be AppInstance specific structure<commit_after>package dsdk

import (
	"context"
	"fmt"
	_path "path"
	"strconv"

	greq "github.com/levigross/grequests"
)

type AppInstance struct {
	AccessControlMode       string                  `json:"access_control_mode,omitempty" mapstructure:"access_control_mode"`
	AdminState              string                  `json:"admin_state,omitempty" mapstructure:"admin_state"`
	AppTemplate             *AppInstanceAppTemplate `json:"app_template,omitempty" mapstructure:"app_template"`
	Causes                  []string                `json:"causes,omitempty" mapstructure:"causes"`
	CloneSrc                *AppInstance            `json:"clone_src,omitempty" mapstructure:"clone_src"`
	CreateMode              string                  `json:"create_mode,omitempty" mapstructure:"create_mode"`
	DeploymentState         string                  `json:"deployment_state,omitempty" mapstructure:"deployment_state"`
	Descr                   string                  `json:"descr,omitempty" mapstructure:"descr"`
	Health                  string                  `json:"health,omitempty" mapstructure:"health"`
	Id                      string                  `json:"id,omitempty" mapstructure:"id"`
	Name                    string                  `json:"name,omitempty" mapstructure:"name"`
	OpState                 string                  `json:"op_state,omitempty" mapstructure:"op_state"`
	Path                    string                  `json:"path,omitempty" mapstructure:"path"`
	RemoteRestorePercentage int                     `json:"remote_restore_percentage,omitempty" mapstructure:"remote_restore_percentage"`
	RemoteRestoreProgress   string                  `json:"remote_restore_progress,omitempty" mapstructure:"remote_restore_progress"`
	RepairPriority          string                  `json:"repair_priority,omitempty" mapstructure:"repair_priority"`
	RestorePoint            string                  `json:"restore_point,omitempty" mapstructure:"restore_point"`
	RestoreProgress         string                  `json:"restore_progress,omitempty" mapstructure:"restore_progress"`
	SnapshotPolicies        []*SnapshotPolicy       `json:"snapshot_policies,omitempty" mapstructure:"snapshot_policies"`
	Snapshots               []*Snapshot             `json:"snapshots,omitempty" mapstructure:"snapshots"`
	StorageInstances        []*StorageInstance      `json:"storage_instances,omitempty" mapstructure:"storage_instances"`
	StoragePool             []*StoragePool          `json:"storage_pool,omitempty" mapstructure:"storage_pool"`
	TemplateOverride        map[string]interface{}  `json:"template_override,omitempty" mapstructure:"template_override"`
	Uuid                    string                  `json:"uuid,omitempty" mapstructure:"uuid"`
	StorageInstancesEp      *StorageInstances       `json:"-"`
}

func RegisterAppInstanceEndpoints(a *AppInstance) {
	a.StorageInstancesEp = newStorageInstances(a.Path)
	for _, si := range a.StorageInstances {
		RegisterStorageInstanceEndpoints(si)
	}
}

type AppInstances struct {
	Path string
}

type AppInstancesCreateRequest struct {
	Ctxt             context.Context         `json:"-"`
	AppTemplate      *AppInstanceAppTemplate `json:"app_template,omitempty" mapstructure:"app_template"`
	CloneSnapshotSrc *Snapshot               `json:"clone_snapshot_src,omitempty" mapstructure:"clone_snapshot_src"`
	CloneVolumeSrc   *Volume                 `json:"clone_volume_src,omitempty" mapstructure:"clone_volume_src"`
	CloneSrc         *AppInstance            `json:"clone_src,omitempty" mapstructure:"clone_src"`
	CreateMode       string                  `json:"create_mode,omitempty" mapstructure:"create_mode"`
	Descr            string                  `json:"descr,omitempty" mapstructure:"descr"`
	Name             string                  `json:"name,omitempty" mapstructure:"name"`
	RepairPriority   string                  `json:"repair_priority,omitempty" mapstructure:"repair_priority"`
	SnapshotPolicies []*SnapshotPolicy       `json:"snapshot_policies,omitempty" mapstructure:"snapshot_policies"`
	StorageInstances []*StorageInstance      `json:"storage_instances,omitempty" mapstructure:"storage_instances"`
	StoragePool      []*StoragePool          `json:"storage_pool,omitempty" mapstructure:"storage_pool"`
	TemplateOverride map[string]interface{}  `json:"template_override,omitempty" mapstructure:"template_override"`
}

func newAppInstances(path string) *AppInstances {
	return &AppInstances{
		Path: _path.Join(path, "app_instances"),
	}
}

func (e *AppInstances) Create(ro *AppInstancesCreateRequest) (*AppInstance, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro}
	rs, apierr, err := GetConn(ro.Ctxt).Post(ro.Ctxt, e.Path, gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := &AppInstance{}
	if err = FillStruct(rs.Data, resp); err != nil {
		return nil, nil, err
	}
	RegisterAppInstanceEndpoints(resp)
	return resp, nil, nil
}

type AppInstancesListRequest struct {
	Ctxt   context.Context `json:"-"`
	Params ListParams      `json:"params,omitempty"`
}

func (e *AppInstances) List(ro *AppInstancesListRequest) ([]*AppInstance, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{
		JSON:   ro,
		Params: ro.Params.ToMap()}
	rs, apierr, err := GetConn(ro.Ctxt).GetList(ro.Ctxt, e.Path, gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := []*AppInstance{}
	for _, data := range rs.Data {
		elem := &AppInstance{}
		adata := data.(map[string]interface{})
		if err = FillStruct(adata, elem); err != nil {
			return nil, nil, err
		}
		RegisterAppInstanceEndpoints(elem)
		resp = append(resp, elem)
	}
	return resp, nil, nil
}

type AppInstancesGetRequest struct {
	Ctxt context.Context `json:"-"`
	Id   string          `json:"-"`
}

func (e *AppInstances) Get(ro *AppInstancesGetRequest) (*AppInstance, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro}
	rs, apierr, err := GetConn(ro.Ctxt).Get(ro.Ctxt, _path.Join(e.Path, ro.Id), gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := &AppInstance{}
	if err = FillStruct(rs.Data, resp); err != nil {
		return nil, nil, err
	}
	RegisterAppInstanceEndpoints(resp)
	return resp, nil, nil
}

type AppInstanceSetRequest struct {
	Ctxt               context.Context    `json:"-"`
	AdminState         string             `json:"admin_state,omitempty" mapstructure:"admin_state"`
	Descr              string             `json:"descr,omitempty" mapstructure:"descr"`
	Force              bool               `json:"force,omitempty" mapstructure:"force"`
	Name               string             `json:"name,omitempty" mapstructure:"name"`
	Provisioned        string             `json:"provisioned,omitempty" mapstructure:"provisioned"`
	RemoteProvider     string             `json:"remote_provider,omitempty" mapstructure:"remote_provider"`
	RemoteRestorePoint string             `json:"remote_restore_point,omitempty" mapstructure:"remote_restore_point"`
	RepairPriority     string             `json:"repair_priority,omitempty" mapstructure:"repair_priority"`
	RestorePoint       string             `json:"restore_point,omitempty" mapstructure:"restore_point"`
	SnapshotPolicies   []*SnapshotPolicy  `json:"snapshot_policies,omitempty" mapstructure:"snapshot_policies"`
	StorageInstances   []*StorageInstance `json:"storage_instances,omitempty" mapstructure:"storage_instances"`
	StoragePool        []*StoragePool     `json:"storage_pool,omitempty" mapstructure:"storage_pool"`
}

func (e *AppInstance) Set(ro *AppInstanceSetRequest) (*AppInstance, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro}
	rs, apierr, err := GetConn(ro.Ctxt).Put(ro.Ctxt, e.Path, gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := &AppInstance{}
	if err = FillStruct(rs.Data, resp); err != nil {
		return nil, nil, err
	}
	RegisterAppInstanceEndpoints(resp)
	return resp, nil, nil
}

type AppInstanceDeleteRequest struct {
	Ctxt  context.Context `json:"-"`
	Force bool            `json:"force,omitempty" mapstructure:"force"`
}

func (e *AppInstance) Delete(ro *AppInstanceDeleteRequest) (*AppInstance, *ApiErrorResponse, error) {
	rs, apierr, err := GetConn(ro.Ctxt).Delete(ro.Ctxt, e.Path, nil)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	resp := &AppInstance{}
	if err = FillStruct(rs.Data, resp); err != nil {
		return nil, nil, err
	}
	RegisterAppInstanceEndpoints(resp)
	return resp, nil, nil
}

type AppInstanceAppTemplate struct {
	Path           string `json:"path,omitempty" mapstructure:"path"`
	ResolvedPath   string `json:"resolved_path,omitempty" mapstructure:"resolved_path"`
	ResolvedTenant string `json:"resolved_tenant,omitempty" mapstructure:"resolved_tenant"`
}

type AppInstanceMetadata map[string]string

type AppInstanceMetadataGetRequest struct {
	Ctxt context.Context `json:"-"`
}

func (e *AppInstance) GetMetadata(ro *AppInstanceMetadataGetRequest) (*AppInstanceMetadata, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro}
	rs, apierr, err := GetConn(ro.Ctxt).Get(ro.Ctxt, _path.Join(e.Path, "metadata"), gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	return stringifyResults(rs), nil, nil
}

type AppInstanceMetadataSetRequest struct {
	Ctxt     context.Context `json:"-"`
	Metadata map[string]string
}

func (e *AppInstance) SetMetadata(ro *AppInstanceMetadataSetRequest) (*AppInstanceMetadata, *ApiErrorResponse, error) {
	gro := &greq.RequestOptions{JSON: ro.Metadata}
	rs, apierr, err := GetConn(ro.Ctxt).Put(ro.Ctxt, _path.Join(e.Path, "metadata"), gro)
	if apierr != nil {
		return nil, apierr, err
	}
	if err != nil {
		return nil, nil, err
	}
	return stringifyResults(rs), nil, nil
}

func stringifyResults(rs *ApiOuter) *AppInstanceMetadata {
	resp := &AppInstanceMetadata{}
	for k, v := range rs.Data {
		var nv string
		switch t := v.(type) {
		case string:
			nv = v.(string)
		case bool:
			nv = strconv.FormatBool(v.(bool))
		case int:
			nv = strconv.FormatInt(int64(v.(int)), 10)
		default:
			panic(fmt.Sprintf("Don't know this, what do?: %s", t))
		}

		(*resp)[k] = nv
	}
	return resp
}
<|endoftext|>


<commit_before>package offers

import (
	"errors"
	"testing"
	"time"

	mesos "github.com/mesos/mesos-go/mesosproto"
	util "github.com/mesos/mesos-go/mesosutil"
	"github.com/mesosphere/kubernetes-mesos/pkg/proc"
)

func TestExpiredOffer(t *testing.T) {
	t.Parallel()

	ttl := 2 * time.Second
	o := Expired("test", "testhost", ttl)

	if o.Id() != "test" {
		t.Error("expiredOffer does not return its Id")
	}
	if o.Host() != "testhost" {
		t.Error("expiredOffer does not return its hostname")
	}
	if o.HasExpired() != true {
		t.Error("expiredOffer is not expired")
	}
	if o.Details() != nil {
		t.Error("expiredOffer does not return nil Details")
	}
	if o.Acquire() != false {
		t.Error("expiredOffer must not be able to be acquired")
	}
	if delay := o.GetDelay(); !(0 < delay && delay <= ttl) {
		t.Error("expiredOffer does not return a valid deadline")
	}
} // TestExpiredOffer

func TestTimedOffer(t *testing.T) {
	t.Parallel()

	ttl := 2 * time.Second
	now := time.Now()
	o := &liveOffer{nil, now.Add(ttl), 0}

	if o.HasExpired() {
		t.Errorf("offer ttl was %v and should not have expired yet", ttl)
	}
	if !o.Acquire() {
		t.Fatal("1st acquisition of offer failed")
	}
	o.Release()
	if !o.Acquire() {
		t.Fatal("2nd acquisition of offer failed")
	}
	if o.Acquire() {
		t.Fatal("3rd acquisition of offer passed but prior claim was not released")
	}
	o.Release()
	if !o.Acquire() {
		t.Fatal("4th acquisition of offer failed")
	}
	o.Release()
	time.Sleep(ttl)
	if !o.HasExpired() {
		t.Fatal("offer not expired after ttl passed")
	}
	if !o.Acquire() {
		t.Fatal("5th acquisition of offer failed; should not be tied to expiration")
	}
	if o.Acquire() {
		t.Fatal("6th acquisition of offer succeeded; should already be acquired")
	}
} // TestTimedOffer

func TestOfferStorage(t *testing.T) {
	ttl := time.Second / 4
	declinedNum := 0
	config := RegistryConfig{
		DeclineOffer: func(offerId string) <-chan error {
			declinedNum = declinedNum + 1
			return proc.ErrorChan(nil)
		},
		Compat: func(o *mesos.Offer) bool {
			return o.Hostname == nil || *o.Hostname != "incompatiblehost"
		},
		TTL:           ttl,
		LingerTTL:     2 * ttl,
	}
	storage := CreateRegistry(config)

	done := make(chan struct{})
	storage.Init(done)

	// Add offer
	id := util.NewOfferID("foo")
	o := &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})

	// Added offer should be in the storage
	if obj, ok := storage.Get(id.GetValue()); obj == nil || !ok {
		t.Error("offer not added")
	}
	if obj, _ := storage.Get(id.GetValue()); obj.Details() != o {
		t.Error("added offer differs from returned offer")
	}

	// Not-added offer is not in storage
	if obj, ok := storage.Get("bar"); obj != nil || ok {
		t.Error("offer bar should not exist in storage")
	}

	// Deleted offer lingers in storage, is acquired and declined
	offer, _ := storage.Get(id.GetValue())
	declinedNumBefore := declinedNum
	storage.Delete(id.GetValue(), "deleted for test")
	if obj, _ := storage.Get(id.GetValue()); obj == nil {
		t.Error("deleted offer is not lingering")
	}
	if obj, _ := storage.Get(id.GetValue()); !obj.HasExpired() {
		t.Error("deleted offer is no expired")
	}
	if ok := offer.Acquire(); ok {
		t.Error("deleted offer can be acquired")
	}
	if declinedNum <= declinedNumBefore {
		t.Error("deleted offer was not declined")
	}

	// Acquired offer is only declined after 2*ttl
	id = util.NewOfferID("foo2")
	o = &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})
	offer, _ = storage.Get(id.GetValue())
	declinedNumBefore = declinedNum
	offer.Acquire()
	storage.Delete(id.GetValue(), "deleted for test")
	if declinedNum > declinedNumBefore {
		t.Error("acquired offer is declined")
	}

	offer.Release()
	time.Sleep(3 * ttl)
	if declinedNum <= declinedNumBefore {
		t.Error("released offer is not declined after 2*ttl")
	}

	// Added offer should be expired after ttl, but lingering
	id = util.NewOfferID("foo3")
	o = &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})

	time.Sleep(2 * ttl)
	obj, ok := storage.Get(id.GetValue())
	if obj == nil || !ok {
		t.Error("offer not lingering after ttl")
	}
	if !obj.HasExpired() {
		t.Error("offer is not expired after ttl")
	}

	// Should be deleted when waiting longer than LingerTTL
	time.Sleep(2 * ttl)
	if obj, ok := storage.Get(id.GetValue()); obj != nil || ok {
		t.Error("offer not deleted after LingerTTL")
	}

	// Incompatible offer is declined
	id = util.NewOfferID("foo4")
	incompatibleHostname := "incompatiblehost"
	o = &mesos.Offer{Id: id, Hostname:&incompatibleHostname}
	declinedNumBefore = declinedNum
	storage.Add([]*mesos.Offer{o})
	if obj, ok := storage.Get(id.GetValue()); obj != nil || ok {
		t.Error("incompatible offer not rejected")
	}
	if declinedNum <= declinedNumBefore {
		t.Error("incompatible offer is not declined")
	}

	// Invalidated offer are not declined, but expired
	id = util.NewOfferID("foo5")
	o = &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})
	offer, _ = storage.Get(id.GetValue())
	declinedNumBefore = declinedNum
	storage.Invalidate(id.GetValue())
	if obj, _ := storage.Get(id.GetValue()); !obj.HasExpired() {
		t.Error("invalidated offer is not expired")
	}
	if declinedNum > declinedNumBefore {
		t.Error("invalidated offer is declined")
	}
	if ok := offer.Acquire(); ok {
		t.Error("invalidated offer can be acquired")
	}

	// Invalidate "" will invalidate all offers
	id = util.NewOfferID("foo6")
	o = &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})
	id2 := util.NewOfferID("foo7")
	o2 := &mesos.Offer{Id: id2}
	storage.Add([]*mesos.Offer{o2})
	storage.Invalidate("")
	if obj, _ := storage.Get(id.GetValue()); !obj.HasExpired() {
		t.Error("invalidated offer is not expired")
	}
	if obj2, _ := storage.Get(id2.GetValue()); !obj2.HasExpired() {
		t.Error("invalidated offer is not expired")
	}

	// InvalidateForSlave invalides all offers for that slave, but only those
	id = util.NewOfferID("foo8")
	slaveId := util.NewSlaveID("test-slave")
	o = &mesos.Offer{Id: id, SlaveId:slaveId}
	storage.Add([]*mesos.Offer{o})
	id2 = util.NewOfferID("foo9")
	o2 = &mesos.Offer{Id: id2}
	storage.Add([]*mesos.Offer{o2})
	storage.InvalidateForSlave(slaveId.GetValue())
	if obj, _ := storage.Get(id.GetValue()); !obj.HasExpired() {
		t.Error("invalidated offer for test-slave is not expired")
	}
	if obj2, _ := storage.Get(id2.GetValue()); obj2.HasExpired() {
		t.Error("invalidated offer another slave is expired")
	}

	close(done)
} // TestOfferStorage

func TestWalk(t *testing.T) {
	t.Parallel()
	config := RegistryConfig{
		DeclineOffer: func(offerId string) <-chan error {
			return proc.ErrorChan(nil)
		},
		TTL:           0 * time.Second,
		LingerTTL:     0 * time.Second,
		ListenerDelay: 0 * time.Second,
	}
	storage := CreateRegistry(config)
	acceptedOfferId := ""
	walked := 0
	walker1 := func(p Perishable) (bool, error) {
		walked++
		if p.Acquire() {
			acceptedOfferId = p.Details().Id.GetValue()
			return true, nil
		}
		return false, nil
	}
	// sanity check
	err := storage.Walk(walker1)
	if err != nil {
		t.Fatalf("received impossible error %v", err)
	}
	if walked != 0 {
		t.Fatal("walked empty storage")
	}
	if acceptedOfferId != "" {
		t.Fatal("somehow found an offer when registry was empty")
	}
	impl, ok := storage.(*offerStorage)
	if !ok {
		t.Fatal("unexpected offer storage impl")
	}
	// single offer
	ttl := 2 * time.Second
	now := time.Now()
	o := &liveOffer{&mesos.Offer{Id: util.NewOfferID("foo")}, now.Add(ttl), 0}

	impl.offers.Add(o)
	err = storage.Walk(walker1)
	if err != nil {
		t.Fatalf("received impossible error %v", err)
	}
	if walked != 1 {
		t.Fatalf("walk count %d", walked)
	}
	if acceptedOfferId != "foo" {
		t.Fatalf("found offer %v", acceptedOfferId)
	}

	acceptedOfferId = ""
	err = storage.Walk(walker1)
	if err != nil {
		t.Fatalf("received impossible error %v", err)
	}
	if walked != 2 {
		t.Fatalf("walk count %d", walked)
	}
	if acceptedOfferId != "" {
		t.Fatalf("found offer %v", acceptedOfferId)
	}

	walker2 := func(p Perishable) (bool, error) {
		walked++
		return true, nil
	}
	err = storage.Walk(walker2)
	if err != nil {
		t.Fatalf("received impossible error %v", err)
	}
	if walked != 3 {
		t.Fatalf("walk count %d", walked)
	}
	if acceptedOfferId != "" {
		t.Fatalf("found offer %v", acceptedOfferId)
	}

	walker3 := func(p Perishable) (bool, error) {
		walked++
		return true, errors.New("baz")
	}
	err = storage.Walk(walker3)
	if err == nil {
		t.Fatal("expected error")
	}
	if walked != 4 {
		t.Fatalf("walk count %d", walked)
	}
}
<commit_msg>add OfferStorage listener tests<commit_after>package offers

import (
	"errors"
	"testing"
	"time"

	mesos "github.com/mesos/mesos-go/mesosproto"
	util "github.com/mesos/mesos-go/mesosutil"
	"github.com/mesosphere/kubernetes-mesos/pkg/proc"
)

func TestExpiredOffer(t *testing.T) {
	t.Parallel()

	ttl := 2 * time.Second
	o := Expired("test", "testhost", ttl)

	if o.Id() != "test" {
		t.Error("expiredOffer does not return its Id")
	}
	if o.Host() != "testhost" {
		t.Error("expiredOffer does not return its hostname")
	}
	if o.HasExpired() != true {
		t.Error("expiredOffer is not expired")
	}
	if o.Details() != nil {
		t.Error("expiredOffer does not return nil Details")
	}
	if o.Acquire() != false {
		t.Error("expiredOffer must not be able to be acquired")
	}
	if delay := o.GetDelay(); !(0 < delay && delay <= ttl) {
		t.Error("expiredOffer does not return a valid deadline")
	}
} // TestExpiredOffer

func TestTimedOffer(t *testing.T) {
	t.Parallel()

	ttl := 2 * time.Second
	now := time.Now()
	o := &liveOffer{nil, now.Add(ttl), 0}

	if o.HasExpired() {
		t.Errorf("offer ttl was %v and should not have expired yet", ttl)
	}
	if !o.Acquire() {
		t.Fatal("1st acquisition of offer failed")
	}
	o.Release()
	if !o.Acquire() {
		t.Fatal("2nd acquisition of offer failed")
	}
	if o.Acquire() {
		t.Fatal("3rd acquisition of offer passed but prior claim was not released")
	}
	o.Release()
	if !o.Acquire() {
		t.Fatal("4th acquisition of offer failed")
	}
	o.Release()
	time.Sleep(ttl)
	if !o.HasExpired() {
		t.Fatal("offer not expired after ttl passed")
	}
	if !o.Acquire() {
		t.Fatal("5th acquisition of offer failed; should not be tied to expiration")
	}
	if o.Acquire() {
		t.Fatal("6th acquisition of offer succeeded; should already be acquired")
	}
} // TestTimedOffer

func TestOfferStorage(t *testing.T) {
	ttl := time.Second / 4
	declinedNum := 0
	config := RegistryConfig{
		DeclineOffer: func(offerId string) <-chan error {
			declinedNum = declinedNum + 1
			return proc.ErrorChan(nil)
		},
		Compat: func(o *mesos.Offer) bool {
			return o.Hostname == nil || *o.Hostname != "incompatiblehost"
		},
		TTL:           ttl,
		LingerTTL:     2 * ttl,
	}
	storage := CreateRegistry(config)

	done := make(chan struct{})
	storage.Init(done)

	// Add offer
	id := util.NewOfferID("foo")
	o := &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})

	// Added offer should be in the storage
	if obj, ok := storage.Get(id.GetValue()); obj == nil || !ok {
		t.Error("offer not added")
	}
	if obj, _ := storage.Get(id.GetValue()); obj.Details() != o {
		t.Error("added offer differs from returned offer")
	}

	// Not-added offer is not in storage
	if obj, ok := storage.Get("bar"); obj != nil || ok {
		t.Error("offer bar should not exist in storage")
	}

	// Deleted offer lingers in storage, is acquired and declined
	offer, _ := storage.Get(id.GetValue())
	declinedNumBefore := declinedNum
	storage.Delete(id.GetValue(), "deleted for test")
	if obj, _ := storage.Get(id.GetValue()); obj == nil {
		t.Error("deleted offer is not lingering")
	}
	if obj, _ := storage.Get(id.GetValue()); !obj.HasExpired() {
		t.Error("deleted offer is no expired")
	}
	if ok := offer.Acquire(); ok {
		t.Error("deleted offer can be acquired")
	}
	if declinedNum <= declinedNumBefore {
		t.Error("deleted offer was not declined")
	}

	// Acquired offer is only declined after 2*ttl
	id = util.NewOfferID("foo2")
	o = &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})
	offer, _ = storage.Get(id.GetValue())
	declinedNumBefore = declinedNum
	offer.Acquire()
	storage.Delete(id.GetValue(), "deleted for test")
	if declinedNum > declinedNumBefore {
		t.Error("acquired offer is declined")
	}

	offer.Release()
	time.Sleep(3 * ttl)
	if declinedNum <= declinedNumBefore {
		t.Error("released offer is not declined after 2*ttl")
	}

	// Added offer should be expired after ttl, but lingering
	id = util.NewOfferID("foo3")
	o = &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})

	time.Sleep(2 * ttl)
	obj, ok := storage.Get(id.GetValue())
	if obj == nil || !ok {
		t.Error("offer not lingering after ttl")
	}
	if !obj.HasExpired() {
		t.Error("offer is not expired after ttl")
	}

	// Should be deleted when waiting longer than LingerTTL
	time.Sleep(2 * ttl)
	if obj, ok := storage.Get(id.GetValue()); obj != nil || ok {
		t.Error("offer not deleted after LingerTTL")
	}

	// Incompatible offer is declined
	id = util.NewOfferID("foo4")
	incompatibleHostname := "incompatiblehost"
	o = &mesos.Offer{Id: id, Hostname:&incompatibleHostname}
	declinedNumBefore = declinedNum
	storage.Add([]*mesos.Offer{o})
	if obj, ok := storage.Get(id.GetValue()); obj != nil || ok {
		t.Error("incompatible offer not rejected")
	}
	if declinedNum <= declinedNumBefore {
		t.Error("incompatible offer is not declined")
	}

	// Invalidated offer are not declined, but expired
	id = util.NewOfferID("foo5")
	o = &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})
	offer, _ = storage.Get(id.GetValue())
	declinedNumBefore = declinedNum
	storage.Invalidate(id.GetValue())
	if obj, _ := storage.Get(id.GetValue()); !obj.HasExpired() {
		t.Error("invalidated offer is not expired")
	}
	if declinedNum > declinedNumBefore {
		t.Error("invalidated offer is declined")
	}
	if ok := offer.Acquire(); ok {
		t.Error("invalidated offer can be acquired")
	}

	// Invalidate "" will invalidate all offers
	id = util.NewOfferID("foo6")
	o = &mesos.Offer{Id: id}
	storage.Add([]*mesos.Offer{o})
	id2 := util.NewOfferID("foo7")
	o2 := &mesos.Offer{Id: id2}
	storage.Add([]*mesos.Offer{o2})
	storage.Invalidate("")
	if obj, _ := storage.Get(id.GetValue()); !obj.HasExpired() {
		t.Error("invalidated offer is not expired")
	}
	if obj2, _ := storage.Get(id2.GetValue()); !obj2.HasExpired() {
		t.Error("invalidated offer is not expired")
	}

	// InvalidateForSlave invalides all offers for that slave, but only those
	id = util.NewOfferID("foo8")
	slaveId := util.NewSlaveID("test-slave")
	o = &mesos.Offer{Id: id, SlaveId:slaveId}
	storage.Add([]*mesos.Offer{o})
	id2 = util.NewOfferID("foo9")
	o2 = &mesos.Offer{Id: id2}
	storage.Add([]*mesos.Offer{o2})
	storage.InvalidateForSlave(slaveId.GetValue())
	if obj, _ := storage.Get(id.GetValue()); !obj.HasExpired() {
		t.Error("invalidated offer for test-slave is not expired")
	}
	if obj2, _ := storage.Get(id2.GetValue()); obj2.HasExpired() {
		t.Error("invalidated offer another slave is expired")
	}

	close(done)
} // TestOfferStorage

func TestListen(t *testing.T) {
	ttl := time.Second / 4
	config := RegistryConfig{
		DeclineOffer: func(offerId string) <-chan error {
			return proc.ErrorChan(nil)
		},
		Compat: func(o *mesos.Offer) bool {
			return true
		},
		TTL:           ttl,
		ListenerDelay: ttl / 2,
	}
	storage := CreateRegistry(config)

	done := make(chan struct {})
	storage.Init(done)

	// Create two listeners with a hostname filter
	hostname1 := "hostname1"
	hostname2 := "hostname2"
	listener1 := storage.Listen("listener1", func(offer *mesos.Offer) bool {
		return offer.GetHostname() == hostname1
	})
	listener2 := storage.Listen("listener2", func(offer *mesos.Offer) bool {
		return offer.GetHostname() == hostname2
	})

	// Add hostname1 offer
	id := util.NewOfferID("foo")
	o := &mesos.Offer{Id: id, Hostname:&hostname1}
	storage.Add([]*mesos.Offer{o})

	// listener1 is notified by closing channel
	select {
	case _, more := <-listener1:
		if more {
			t.Error("listener1 is not closed")
		}
	}

	// listener2 is not notified within ttl
	select {
		case <-listener2:
			t.Error("listener2 is notified")
		case <-time.After(ttl):
	}

	close(done)
} // TestListen

func TestWalk(t *testing.T) {
	t.Parallel()
	config := RegistryConfig{
		DeclineOffer: func(offerId string) <-chan error {
			return proc.ErrorChan(nil)
		},
		TTL:           0 * time.Second,
		LingerTTL:     0 * time.Second,
		ListenerDelay: 0 * time.Second,
	}
	storage := CreateRegistry(config)
	acceptedOfferId := ""
	walked := 0
	walker1 := func(p Perishable) (bool, error) {
		walked++
		if p.Acquire() {
			acceptedOfferId = p.Details().Id.GetValue()
			return true, nil
		}
		return false, nil
	}
	// sanity check
	err := storage.Walk(walker1)
	if err != nil {
		t.Fatalf("received impossible error %v", err)
	}
	if walked != 0 {
		t.Fatal("walked empty storage")
	}
	if acceptedOfferId != "" {
		t.Fatal("somehow found an offer when registry was empty")
	}
	impl, ok := storage.(*offerStorage)
	if !ok {
		t.Fatal("unexpected offer storage impl")
	}
	// single offer
	ttl := 2 * time.Second
	now := time.Now()
	o := &liveOffer{&mesos.Offer{Id: util.NewOfferID("foo")}, now.Add(ttl), 0}

	impl.offers.Add(o)
	err = storage.Walk(walker1)
	if err != nil {
		t.Fatalf("received impossible error %v", err)
	}
	if walked != 1 {
		t.Fatalf("walk count %d", walked)
	}
	if acceptedOfferId != "foo" {
		t.Fatalf("found offer %v", acceptedOfferId)
	}

	acceptedOfferId = ""
	err = storage.Walk(walker1)
	if err != nil {
		t.Fatalf("received impossible error %v", err)
	}
	if walked != 2 {
		t.Fatalf("walk count %d", walked)
	}
	if acceptedOfferId != "" {
		t.Fatalf("found offer %v", acceptedOfferId)
	}

	walker2 := func(p Perishable) (bool, error) {
		walked++
		return true, nil
	}
	err = storage.Walk(walker2)
	if err != nil {
		t.Fatalf("received impossible error %v", err)
	}
	if walked != 3 {
		t.Fatalf("walk count %d", walked)
	}
	if acceptedOfferId != "" {
		t.Fatalf("found offer %v", acceptedOfferId)
	}

	walker3 := func(p Perishable) (bool, error) {
		walked++
		return true, errors.New("baz")
	}
	err = storage.Walk(walker3)
	if err == nil {
		t.Fatal("expected error")
	}
	if walked != 4 {
		t.Fatalf("walk count %d", walked)
	}
}
<|endoftext|>


<commit_before>package proxy

import (
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/hellofresh/janus/pkg/router"
)

// NewSingleHostReverseProxy returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
// NewSingleHostReverseProxy does not rewrite the Host header.
// To rewrite Host headers, use ReverseProxy directly with a custom
// Director policy.
func NewSingleHostReverseProxy(proxy *Proxy, transport http.RoundTripper) *httputil.ReverseProxy {
	target, _ := url.Parse(proxy.TargetURL)
	targetQuery := target.RawQuery

	director := func(req *http.Request) {
		req.URL.Scheme = target.Scheme
		req.URL.Host = target.Host
		path := target.Path

		if proxy.AppendListenPath {
			log.Debug("Appending listen path to the target url")
			path = singleJoiningSlash(target.Path, req.URL.Path)
		}

		if proxy.StripListenPath {
			path = singleJoiningSlash(target.Path, req.URL.Path)
			matcher := router.NewListenPathMatcher()
			listenPath := matcher.Extract(proxy.ListenPath)

			log.Debugf("Stripping listen path: %s", listenPath)
			path = strings.Replace(path, listenPath, "", 1)
			if !strings.HasSuffix(target.Path, "/") && strings.HasSuffix(path, "/") {
				path = path[:len(path)-1]
			}
		}

		log.Debugf("Upstream Path is: %s", path)
		req.URL.Path = path

		// This is very important to avoid problems with ssl verification for the HOST header
		if !proxy.PreserveHostHeader {
			log.Debug("Preserving the host header")
			req.Host = target.Host
		}

		if targetQuery == "" || req.URL.RawQuery == "" {
			req.URL.RawQuery = targetQuery + req.URL.RawQuery
		} else {
			req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
		}
	}

	return &httputil.ReverseProxy{Director: director, Transport: transport}
}

func cleanSlashes(a string) string {
	endSlash := strings.HasSuffix(a, "//")
	startSlash := strings.HasPrefix(a, "//")

	if startSlash {
		a = "/" + strings.TrimPrefix(a, "//")
	}

	if endSlash {
		a = strings.TrimSuffix(a, "//") + "/"
	}

	return a
}

func singleJoiningSlash(a, b string) string {
	a = cleanSlashes(a)
	b = cleanSlashes(b)

	aslash := strings.HasSuffix(a, "/")
	bslash := strings.HasPrefix(b, "/")

	switch {
	case aslash && bslash:
		log.Debug(a + b)
		return a + b[1:]
	case !aslash && !bslash:
		if len(b) > 0 {
			log.Debug(a + b)
			return a + "/" + b
		}

		log.Debug(a + b)
		return a
	}

	log.Debug(a + b)
	return a + b
}
<commit_msg>Added options for reverse proxy<commit_after>package proxy

import (
	"crypto/tls"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/hellofresh/janus/pkg/router"
)

const (
	// DefaultIdleConnsPerHost the default value set for http.Transport.MaxIdleConnsPerHost.
	DefaultIdleConnsPerHost = 64

	// DefaultCloseIdleConnsPeriod the default period at which the idle connections are forcibly
	// closed.
	DefaultCloseIdleConnsPeriod = 20 * time.Second
)

// Params initialization options.
type Params struct {
	Transport Transport

	// When set, the proxy will skip the TLS verification on outgoing requests.
	InsecureSkipVerify bool

	// Same as net/http.Transport.MaxIdleConnsPerHost, but the default
	// is 64. This value supports scenarios with relatively few remote
	// hosts. When the routing table contains different hosts in the
	// range of hundreds, it is recommended to set this options to a
	// lower value.
	IdleConnectionsPerHost int

	// Defines the time period of how often the idle connections are
	// forcibly closed. The default is 12 seconds. When set to less than
	// 0, the proxy doesn't force closing the idle connections.
	CloseIdleConnsPeriod time.Duration

	// The Flush interval for copying upgraded connections
	FlushInterval time.Duration
}

// Proxy instances implement Skipper proxying functionality. For
// initializing, see the WithParams the constructor and Params.
type Proxy struct {
	roundTripper  http.RoundTripper
	quit          chan struct{}
	flushInterval time.Duration
}

// WithParams returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
// NewSingleHostReverseProxy does not rewrite the Host header.
// To rewrite Host headers, use ReverseProxy directly with a custom
// Director policy.
func WithParams(o Params) *Proxy {
	if o.IdleConnectionsPerHost <= 0 {
		o.IdleConnectionsPerHost = DefaultIdleConnsPerHost
	}

	if o.CloseIdleConnsPeriod == 0 {
		o.CloseIdleConnsPeriod = DefaultCloseIdleConnsPeriod
	}

	tr := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		MaxIdleConnsPerHost:   o.IdleConnectionsPerHost,
	}

	quit := make(chan struct{})
	if o.CloseIdleConnsPeriod > 0 {
		go func() {
			for {
				select {
				case <-time.After(o.CloseIdleConnsPeriod):
					tr.CloseIdleConnections()
				case <-quit:
					return
				}
			}
		}()
	}

	if o.InsecureSkipVerify {
		tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	}

	return &Proxy{
		roundTripper:  o.Transport.GetRoundTripper(tr),
		quit:          quit,
		flushInterval: o.FlushInterval,
	}
}

func (p *Proxy) Reverse(proxyDefinition *Definition) *httputil.ReverseProxy {
	target, _ := url.Parse(proxyDefinition.TargetURL)
	targetQuery := target.RawQuery

	director := func(req *http.Request) {
		req.URL.Scheme = target.Scheme
		req.URL.Host = target.Host
		path := target.Path

		if proxyDefinition.AppendListenPath {
			log.Debug("Appending listen path to the target url")
			path = singleJoiningSlash(target.Path, req.URL.Path)
		}

		if proxyDefinition.StripListenPath {
			path = singleJoiningSlash(target.Path, req.URL.Path)
			matcher := router.NewListenPathMatcher()
			listenPath := matcher.Extract(proxyDefinition.ListenPath)

			log.Debugf("Stripping listen path: %s", listenPath)
			path = strings.Replace(path, listenPath, "", 1)
			if !strings.HasSuffix(target.Path, "/") && strings.HasSuffix(path, "/") {
				path = path[:len(path) - 1]
			}
		}

		log.Debugf("Upstream Path is: %s", path)
		req.URL.Path = path

		// This is very important to avoid problems with ssl verification for the HOST header
		if !proxyDefinition.PreserveHostHeader {
			log.Debug("Preserving the host header")
			req.Host = target.Host
		}

		if targetQuery == "" || req.URL.RawQuery == "" {
			req.URL.RawQuery = targetQuery + req.URL.RawQuery
		} else {
			req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
		}
	}

	reverseProxy := &httputil.ReverseProxy{Director: director, Transport: p.roundTripper}
	reverseProxy.FlushInterval = p.flushInterval

	return reverseProxy
}

// Close causes the proxy to stop closing idle
// connections and, currently, has no other effect.
// It's primary purpose is to support testing.
func (p *Proxy) Close() error {
	close(p.quit)
	return nil
}

func cleanSlashes(a string) string {
	endSlash := strings.HasSuffix(a, "//")
	startSlash := strings.HasPrefix(a, "//")

	if startSlash {
		a = "/" + strings.TrimPrefix(a, "//")
	}

	if endSlash {
		a = strings.TrimSuffix(a, "//") + "/"
	}

	return a
}

func singleJoiningSlash(a, b string) string {
	a = cleanSlashes(a)
	b = cleanSlashes(b)

	aslash := strings.HasSuffix(a, "/")
	bslash := strings.HasPrefix(b, "/")

	switch {
	case aslash && bslash:
		log.Debug(a + b)
		return a + b[1:]
	case !aslash && !bslash:
		if len(b) > 0 {
			log.Debug(a + b)
			return a + "/" + b
		}

		log.Debug(a + b)
		return a
	}

	log.Debug(a + b)
	return a + b
}
<|endoftext|>