<commit_before>package agent

import (
	"crypto/tls"
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	mqtt "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"github.com/juju/loggo"
)

var AlreadyConfigured = errors.New("Already configured")
var AlreadyUnConfigured = errors.New("Already unconfigured")

//
// Acts as a bridge between local and cloud brokers, this includes reconnecting
// and emitting status changes.
//
// Once configured and started this will attempt to connect to connect to local
// and cloud brokers, if something dies it will reconnect based on the configured
// reconnect backoff.
//
type Bridge struct {
	conf   *Config
	local  *mqtt.MqttClient
	remote *mqtt.MqttClient
	log    loggo.Logger

	localTopics []replaceTopic
	cloudTopics []replaceTopic

	cloudUrl string
	token    string

	timer       *time.Timer
	reconnectCh chan bool
	shutdownCh  chan bool

	Configured bool
	Connected  bool
	Counter    int64

	LastError error

	bridgeLock sync.Mutex
}

type replaceTopic struct {
	on      string
	replace string
	with    string
}

func (r *replaceTopic) updated(originalTopic string) string {
	return strings.Replace(originalTopic, r.replace, r.with, 1)
}

var localTopics = []replaceTopic{
	{on: "$location/calibration", replace: "$location", with: "$cloud/location"},
	{on: "$location/delete", replace: "$location", with: "$cloud/location"},
	{on: "$device/+/+/rssi", replace: "$device", with: "$cloud/device"},
	{on: "$node/+/module/status", replace: "$node", with: "$cloud/node"},
	{on: "$device/+/channel/+/+", replace: "$device", with: "$cloud/device"},
	{on: "$device/+/channel/+/+/event/+", replace: "$device", with: "$cloud/device"},
	{on: "$ninja/services/rpc/+/+", replace: "$ninja", with: "$cloud/ninja"},
}

var cloudTopics = []replaceTopic{
	{on: "$cloud/location/calibration/progress", replace: "$cloud/location", with: "$location"},
	{on: "$cloud/device/+/+/location", replace: "$cloud/device", with: "$device"},
	{on: "$cloud/device/+/announce", replace: "$cloud/device", with: "$device"},
	{on: "$cloud/device/+/channel/+/+/announce", replace: "$cloud/device", with: "$device"},
	{on: "$cloud/device/+/channel/+/+/reply", replace: "$cloud/device", with: "$device"},
	{on: "$cloud/ninja/services/rpc/+/+/reply", replace: "$cloud/ninja", with: "$ninja"},
}

func createBridge(conf *Config) *Bridge {
	return &Bridge{conf: conf, localTopics: localTopics, cloudTopics: cloudTopics, log: loggo.GetLogger("bridge")}
}

func (b *Bridge) start(cloudUrl string, token string) (err error) {

	if b.Configured {
		b.log.Warningf("Already configured.")
		return AlreadyConfigured
	}

	defer b.bridgeLock.Unlock()

	b.bridgeLock.Lock()

	b.log.Infof("Connecting the bridge")

	b.Configured = true

	b.cloudUrl = cloudUrl
	b.token = token

	b.reconnectCh = make(chan bool, 1)
	b.shutdownCh = make(chan bool, 1)

	if err = b.connect(); err != nil {
		b.log.Errorf("Connect failed %s", err)
		b.scheduleReconnect(err)
	}

	go b.mainBridgeLoop()

	return err
}

func (b *Bridge) stop() error {

	if !b.Configured {
		b.log.Warningf("Already unconfigured.")
		return AlreadyUnConfigured
	}

	defer b.bridgeLock.Unlock()

	b.bridgeLock.Lock()

	b.log.Infof("Disconnecting bridge")

	if b.Configured {
		// tell the worker to shutdown
		b.shutdownCh <- true

		b.Configured = false
	}

	b.resetTimer()

	b.disconnectAll()

	return nil
}

func (b *Bridge) connect() (err error) {

	if b.local, err = b.buildClient(b.conf.LocalUrl, ""); err != nil {
		b.Connected = false
		return err
	}

	if b.remote, err = b.buildClient(b.cloudUrl, b.token); err != nil {
		b.Connected = false
		return err
	}

	if err = b.subscriptions(); err != nil {
		return err
	}

	// we are now connected
	b.Connected = true

	return nil
}

func (b *Bridge) reconnect() (err error) {

	if b.local, err = b.buildClient(b.conf.LocalUrl, ""); err != nil {
		b.Connected = false
		return err
	}

	if b.remote, err = b.buildClient(b.cloudUrl, b.token); err != nil {
		b.Connected = false
		return err
	}

	if err = b.subscriptions(); err != nil {
		return err
	}

	// we are now connected
	b.Connected = true
	b.LastError = nil

	return nil
}

func (b *Bridge) subscriptions() (err error) {

	if err = b.subscribe(b.local, b.remote, b.localTopics, "local"); err != nil {
		return err
	}

	if err = b.subscribe(b.remote, b.local, b.cloudTopics, "cloud"); err != nil {
		return err
	}
	return nil

}

func (b *Bridge) disconnectAll() {
	b.log.Infof("disconnectAll")
	// we are now disconnected
	b.Connected = false
	if b.local != nil && b.local.IsConnected() {
		b.local.Disconnect(100)
	}
	if b.remote != nil && b.remote.IsConnected() {
		b.local.Disconnect(100)
	}
}

func (b *Bridge) mainBridgeLoop() {

	for {
		select {
		case <-b.reconnectCh:
			b.log.Infof("reconnecting")
			if err := b.reconnect(); err != nil {
				b.log.Errorf("Reconnect failed %s", err)
				b.scheduleReconnect(err)
			}
		case <-b.shutdownCh:
			b.log.Infof("shutting down bridge")
			return
		}

	}

}

func (b *Bridge) buildClient(server string, token string) (*mqtt.MqttClient, error) {

	b.log.Infof("building client for %s", server)

	opts := mqtt.NewClientOptions().SetBroker(server).SetTlsConfig(&tls.Config{InsecureSkipVerify: true})

	if token != "" {
		opts.SetUsername(token)
	}

	opts.SetClientId(fmt.Sprintf("%d", time.Now().Unix()))

	opts.SetTimeout(15) // set a 15 second ping time for ELB

	if !b.conf.Debug {

		// shutup
		opts.SetTraceLevel(mqtt.Off)

	}

	// pretty much log the reason and quit
	opts.SetOnConnectionLost(b.onConnectionLoss)

	client := mqtt.NewClient(opts)
	_, err := client.Start()
	return client, err
}

func (b *Bridge) subscribe(src *mqtt.MqttClient, dst *mqtt.MqttClient, topics []replaceTopic, tag string) (err error) {
	for _, topic := range topics {

		topicFilter, _ := mqtt.NewTopicFilter(topic.on, 0)
		b.log.Infof("(%s) subscribed to %s", tag, topic.on)

		if receipt, err := src.StartSubscription(b.buildHandler(topic, tag, dst), topicFilter); err != nil {
			return err
		} else {
			<-receipt
			b.log.Infof("(%s) subscribed to %+v", tag, topicFilter)
		}
	}

	return nil
}

func (b *Bridge) unsubscribe(client *mqtt.MqttClient, topics []replaceTopic, tag string) {
	topicNames := []string{}
	for _, topic := range topics {
		topicNames = append(topicNames, topic.on)
	}
	b.log.Infof("(%s) unsubscribed to %s", tag, topicNames)
	client.EndSubscription(topicNames...)
}

func (b *Bridge) buildHandler(topic replaceTopic, tag string, dst *mqtt.MqttClient) mqtt.MessageHandler {
	return func(src *mqtt.MqttClient, msg mqtt.Message) {
		if b.conf.IsDebug() {
			b.log.Infof("(%s) topic: %s updated: %s len: %d", tag, msg.Topic(), topic.updated(msg.Topic()), len(msg.Payload()))
		}
		b.Counter++
		dst.PublishMessage(topic.updated(msg.Topic()), mqtt.NewMessage(msg.Payload()))
	}
}

func (b *Bridge) scheduleReconnect(reason error) {
	b.LastError = reason
	b.disconnectAll()
	b.resetTimer()

	switch reason {
	case mqtt.ErrBadCredentials:
		b.log.Warningf("Reconnect failed trying again in 30s")

		b.timer = time.AfterFunc(30*time.Second, func() {
			b.reconnectCh <- true
		})

	default:
		b.log.Warningf("Reconnect failed trying again in 5s")
		// TODO add exponential backoff
		b.timer = time.AfterFunc(5*time.Second, func() {
			b.reconnectCh <- true
		})
	}

}

func (b *Bridge) resetTimer() {
	if b.timer != nil {
		b.timer.Stop()
	}
}

func (b *Bridge) onConnectionLoss(client *mqtt.MqttClient, reason error) {
	b.log.Errorf("Connection failed %s", reason)

	// we are now disconnected
	b.Connected = false

	b.scheduleReconnect(reason)

}

func (b *Bridge) IsConnected() bool {
	if b.remote == nil || b.local == nil {
		return false
	}
	return (b.remote.IsConnected() && b.local.IsConnected())
}
<commit_msg>Add timeseries (non-RPC) cloud service<commit_after>package agent

import (
	"crypto/tls"
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	mqtt "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"github.com/juju/loggo"
)

var AlreadyConfigured = errors.New("Already configured")
var AlreadyUnConfigured = errors.New("Already unconfigured")

//
// Acts as a bridge between local and cloud brokers, this includes reconnecting
// and emitting status changes.
//
// Once configured and started this will attempt to connect to connect to local
// and cloud brokers, if something dies it will reconnect based on the configured
// reconnect backoff.
//
type Bridge struct {
	conf   *Config
	local  *mqtt.MqttClient
	remote *mqtt.MqttClient
	log    loggo.Logger

	localTopics []replaceTopic
	cloudTopics []replaceTopic

	cloudUrl string
	token    string

	timer       *time.Timer
	reconnectCh chan bool
	shutdownCh  chan bool

	Configured bool
	Connected  bool
	Counter    int64

	LastError error

	bridgeLock sync.Mutex
}

type replaceTopic struct {
	on      string
	replace string
	with    string
}

func (r *replaceTopic) updated(originalTopic string) string {
	return strings.Replace(originalTopic, r.replace, r.with, 1)
}

var localTopics = []replaceTopic{
	{on: "$location/calibration", replace: "$location", with: "$cloud/location"},
	{on: "$location/delete", replace: "$location", with: "$cloud/location"},
	{on: "$device/+/+/rssi", replace: "$device", with: "$cloud/device"},
	{on: "$node/+/module/status", replace: "$node", with: "$cloud/node"},
	{on: "$device/+/channel/+/+", replace: "$device", with: "$cloud/device"},
	{on: "$device/+/channel/+/+/event/+", replace: "$device", with: "$cloud/device"},
	{on: "$ninja/services/rpc/+/+", replace: "$ninja", with: "$cloud/ninja"},
	{on: "$ninja/services/+", replace: "$ninja", with: "$cloud/ninja"},
}

var cloudTopics = []replaceTopic{
	{on: "$cloud/location/calibration/progress", replace: "$cloud/location", with: "$location"},
	{on: "$cloud/device/+/+/location", replace: "$cloud/device", with: "$device"},
	{on: "$cloud/device/+/announce", replace: "$cloud/device", with: "$device"},
	{on: "$cloud/device/+/channel/+/+/announce", replace: "$cloud/device", with: "$device"},
	{on: "$cloud/device/+/channel/+/+/reply", replace: "$cloud/device", with: "$device"},
	{on: "$cloud/ninja/services/rpc/+/+/reply", replace: "$cloud/ninja", with: "$ninja"},
}

func createBridge(conf *Config) *Bridge {
	return &Bridge{conf: conf, localTopics: localTopics, cloudTopics: cloudTopics, log: loggo.GetLogger("bridge")}
}

func (b *Bridge) start(cloudUrl string, token string) (err error) {

	if b.Configured {
		b.log.Warningf("Already configured.")
		return AlreadyConfigured
	}

	defer b.bridgeLock.Unlock()

	b.bridgeLock.Lock()

	b.log.Infof("Connecting the bridge")

	b.Configured = true

	b.cloudUrl = cloudUrl
	b.token = token

	b.reconnectCh = make(chan bool, 1)
	b.shutdownCh = make(chan bool, 1)

	if err = b.connect(); err != nil {
		b.log.Errorf("Connect failed %s", err)
		b.scheduleReconnect(err)
	}

	go b.mainBridgeLoop()

	return err
}

func (b *Bridge) stop() error {

	if !b.Configured {
		b.log.Warningf("Already unconfigured.")
		return AlreadyUnConfigured
	}

	defer b.bridgeLock.Unlock()

	b.bridgeLock.Lock()

	b.log.Infof("Disconnecting bridge")

	if b.Configured {
		// tell the worker to shutdown
		b.shutdownCh <- true

		b.Configured = false
	}

	b.resetTimer()

	b.disconnectAll()

	return nil
}

func (b *Bridge) connect() (err error) {

	if b.local, err = b.buildClient(b.conf.LocalUrl, ""); err != nil {
		b.Connected = false
		return err
	}

	if b.remote, err = b.buildClient(b.cloudUrl, b.token); err != nil {
		b.Connected = false
		return err
	}

	if err = b.subscriptions(); err != nil {
		return err
	}

	// we are now connected
	b.Connected = true

	return nil
}

func (b *Bridge) reconnect() (err error) {

	if b.local, err = b.buildClient(b.conf.LocalUrl, ""); err != nil {
		b.Connected = false
		return err
	}

	if b.remote, err = b.buildClient(b.cloudUrl, b.token); err != nil {
		b.Connected = false
		return err
	}

	if err = b.subscriptions(); err != nil {
		return err
	}

	// we are now connected
	b.Connected = true
	b.LastError = nil

	return nil
}

func (b *Bridge) subscriptions() (err error) {

	if err = b.subscribe(b.local, b.remote, b.localTopics, "local"); err != nil {
		return err
	}

	if err = b.subscribe(b.remote, b.local, b.cloudTopics, "cloud"); err != nil {
		return err
	}
	return nil

}

func (b *Bridge) disconnectAll() {
	b.log.Infof("disconnectAll")
	// we are now disconnected
	b.Connected = false
	if b.local != nil && b.local.IsConnected() {
		b.local.Disconnect(100)
	}
	if b.remote != nil && b.remote.IsConnected() {
		b.local.Disconnect(100)
	}
}

func (b *Bridge) mainBridgeLoop() {

	for {
		select {
		case <-b.reconnectCh:
			b.log.Infof("reconnecting")
			if err := b.reconnect(); err != nil {
				b.log.Errorf("Reconnect failed %s", err)
				b.scheduleReconnect(err)
			}
		case <-b.shutdownCh:
			b.log.Infof("shutting down bridge")
			return
		}

	}

}

func (b *Bridge) buildClient(server string, token string) (*mqtt.MqttClient, error) {

	b.log.Infof("building client for %s", server)

	opts := mqtt.NewClientOptions().SetBroker(server).SetTlsConfig(&tls.Config{InsecureSkipVerify: true})

	if token != "" {
		opts.SetUsername(token)
	}

	opts.SetClientId(fmt.Sprintf("%d", time.Now().Unix()))

	opts.SetTimeout(15) // set a 15 second ping time for ELB

	if !b.conf.Debug {

		// shutup
		opts.SetTraceLevel(mqtt.Off)

	}

	// pretty much log the reason and quit
	opts.SetOnConnectionLost(b.onConnectionLoss)

	client := mqtt.NewClient(opts)
	_, err := client.Start()
	return client, err
}

func (b *Bridge) subscribe(src *mqtt.MqttClient, dst *mqtt.MqttClient, topics []replaceTopic, tag string) (err error) {
	for _, topic := range topics {

		topicFilter, _ := mqtt.NewTopicFilter(topic.on, 0)
		b.log.Infof("(%s) subscribed to %s", tag, topic.on)

		if receipt, err := src.StartSubscription(b.buildHandler(topic, tag, dst), topicFilter); err != nil {
			return err
		} else {
			<-receipt
			b.log.Infof("(%s) subscribed to %+v", tag, topicFilter)
		}
	}

	return nil
}

func (b *Bridge) unsubscribe(client *mqtt.MqttClient, topics []replaceTopic, tag string) {
	topicNames := []string{}
	for _, topic := range topics {
		topicNames = append(topicNames, topic.on)
	}
	b.log.Infof("(%s) unsubscribed to %s", tag, topicNames)
	client.EndSubscription(topicNames...)
}

func (b *Bridge) buildHandler(topic replaceTopic, tag string, dst *mqtt.MqttClient) mqtt.MessageHandler {
	return func(src *mqtt.MqttClient, msg mqtt.Message) {
		if b.conf.IsDebug() {
			b.log.Infof("(%s) topic: %s updated: %s len: %d", tag, msg.Topic(), topic.updated(msg.Topic()), len(msg.Payload()))
		}
		b.Counter++
		dst.PublishMessage(topic.updated(msg.Topic()), mqtt.NewMessage(msg.Payload()))
	}
}

func (b *Bridge) scheduleReconnect(reason error) {
	b.LastError = reason
	b.disconnectAll()
	b.resetTimer()

	switch reason {
	case mqtt.ErrBadCredentials:
		b.log.Warningf("Reconnect failed trying again in 30s")

		b.timer = time.AfterFunc(30*time.Second, func() {
			b.reconnectCh <- true
		})

	default:
		b.log.Warningf("Reconnect failed trying again in 5s")
		// TODO add exponential backoff
		b.timer = time.AfterFunc(5*time.Second, func() {
			b.reconnectCh <- true
		})
	}

}

func (b *Bridge) resetTimer() {
	if b.timer != nil {
		b.timer.Stop()
	}
}

func (b *Bridge) onConnectionLoss(client *mqtt.MqttClient, reason error) {
	b.log.Errorf("Connection failed %s", reason)

	// we are now disconnected
	b.Connected = false

	b.scheduleReconnect(reason)

}

func (b *Bridge) IsConnected() bool {
	if b.remote == nil || b.local == nil {
		return false
	}
	return (b.remote.IsConnected() && b.local.IsConnected())
}
<|endoftext|>


<commit_before>package agent

import (
	"bytes"
	"fmt"
	"github.com/armon/go-metrics"
	"github.com/hashicorp/serf/serf"
	"io"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

const (
	windows = "windows"
)

// invokeEventScript will execute the given event script with the given
// event. Depending on the event, the semantics of how data are passed
// are a bit different. For all events, the SERF_EVENT environmental
// variable is the type of the event. For user events, the SERF_USER_EVENT
// environmental variable is also set, containing the name of the user
// event that was fired.
//
// In all events, data is passed in via stdin to faciliate piping. See
// the various stdin functions below for more information.
func invokeEventScript(logger *log.Logger, script string, self serf.Member, event serf.Event) error {
	defer metrics.MeasureSince([]string{"agent", "invoke", script}, time.Now())
	var output bytes.Buffer

	// Determine the shell invocation based on OS
	var shell, flag string
	if runtime.GOOS == windows {
		shell = "cmd"
		flag = "/C"
	} else {
		shell = "/bin/sh"
		flag = "-c"
	}

	cmd := exec.Command(shell, flag, script)
	cmd.Env = append(os.Environ(),
		"SERF_EVENT="+event.EventType().String(),
		"SERF_SELF_NAME="+self.Name,
		"SERF_SELF_ROLE="+self.Tags["role"],
	)
	cmd.Stderr = &output
	cmd.Stdout = &output

	// Add all the tags
	for name, val := range self.Tags {
		tag_env := fmt.Sprintf("SERF_TAG_%s=%s", strings.ToUpper(name), val)
		cmd.Env = append(cmd.Env, tag_env)
	}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	switch e := event.(type) {
	case serf.MemberEvent:
		go memberEventStdin(logger, stdin, &e)
	case serf.UserEvent:
		cmd.Env = append(cmd.Env, "SERF_USER_EVENT="+e.Name)
		cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_USER_LTIME=%d", e.LTime))
		go streamPayload(logger, stdin, e.Payload)
	case serf.Query:
		cmd.Env = append(cmd.Env, "SERF_QUERY_NAME="+e.Name)
		cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_QUERY_LTIME=%d", e.LTime))
		go streamPayload(logger, stdin, e.Payload)
	default:
		return fmt.Errorf("Unknown event type: %s", event.EventType().String())
	}

	if err := cmd.Start(); err != nil {
		return err
	}

	err = cmd.Wait()
	logger.Printf("[DEBUG] agent: Event '%s' script output: %s",
		event.EventType().String(), output.String())

	// If this is a query and we have output, respond
	if query, ok := event.(serf.Query); ok && len(output.Bytes()) > 0 {
		if err := query.Respond(output.Bytes()); err != nil {
			logger.Printf("[WARN] agent: Failed to respond to query '%s': %s",
				event.String(), err)
		}
	}

	if err != nil {
		return err
	}

	return nil
}

// eventClean cleans a value to be a parameter in an event line.
func eventClean(v string) string {
	v = strings.Replace(v, "\t", "\\t", -1)
	v = strings.Replace(v, "\n", "\\n", -1)
	return v
}

// Sends data on stdin for a member event.
//
// The format for the data is unix tool friendly, separated by whitespace
// and newlines. The structure of each line for any member event is:
// "NAME    ADDRESS    ROLE    TAGS" where the whitespace is actually tabs.
// The name and role are cleaned so that newlines and tabs are replaced
// with "\n" and "\t" respectively.
func memberEventStdin(logger *log.Logger, stdin io.WriteCloser, e *serf.MemberEvent) {
	defer stdin.Close()
	for _, member := range e.Members {
		// Format the tags as tag1=v1,tag2=v2,...
		var tagPairs []string
		for name, value := range member.Tags {
			tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", name, value))
		}
		tags := strings.Join(tagPairs, ",")

		// Send the entire line
		_, err := stdin.Write([]byte(fmt.Sprintf(
			"%s\t%s\t%s\t%s\n",
			eventClean(member.Name),
			member.Addr.String(),
			eventClean(member.Tags["role"]),
			eventClean(tags))))
		if err != nil {
			return
		}
	}
}

// Sends data on stdin for an event. The stdin simply contains the
// payload (if any).
// Most shells read implementations need a newline, force it to be there
func streamPayload(logger *log.Logger, stdin io.WriteCloser, buf []byte) {
	defer stdin.Close()

	// Append a newline to payload if missing
	payload := buf
	if len(payload) > 0 && payload[len(payload)-1] != '\n' {
		payload = append(payload, '\n')
	}

	if _, err := stdin.Write(payload); err != nil {
		logger.Printf("[ERR] Error writing payload: %s", err)
		return
	}
}
<commit_msg>agent: Do not respond to query with non-zero exit code<commit_after>package agent

import (
	"bytes"
	"fmt"
	"github.com/armon/go-metrics"
	"github.com/hashicorp/serf/serf"
	"io"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

const (
	windows = "windows"
)

// invokeEventScript will execute the given event script with the given
// event. Depending on the event, the semantics of how data are passed
// are a bit different. For all events, the SERF_EVENT environmental
// variable is the type of the event. For user events, the SERF_USER_EVENT
// environmental variable is also set, containing the name of the user
// event that was fired.
//
// In all events, data is passed in via stdin to faciliate piping. See
// the various stdin functions below for more information.
func invokeEventScript(logger *log.Logger, script string, self serf.Member, event serf.Event) error {
	defer metrics.MeasureSince([]string{"agent", "invoke", script}, time.Now())
	var output bytes.Buffer

	// Determine the shell invocation based on OS
	var shell, flag string
	if runtime.GOOS == windows {
		shell = "cmd"
		flag = "/C"
	} else {
		shell = "/bin/sh"
		flag = "-c"
	}

	cmd := exec.Command(shell, flag, script)
	cmd.Env = append(os.Environ(),
		"SERF_EVENT="+event.EventType().String(),
		"SERF_SELF_NAME="+self.Name,
		"SERF_SELF_ROLE="+self.Tags["role"],
	)
	cmd.Stderr = &output
	cmd.Stdout = &output

	// Add all the tags
	for name, val := range self.Tags {
		tag_env := fmt.Sprintf("SERF_TAG_%s=%s", strings.ToUpper(name), val)
		cmd.Env = append(cmd.Env, tag_env)
	}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	switch e := event.(type) {
	case serf.MemberEvent:
		go memberEventStdin(logger, stdin, &e)
	case serf.UserEvent:
		cmd.Env = append(cmd.Env, "SERF_USER_EVENT="+e.Name)
		cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_USER_LTIME=%d", e.LTime))
		go streamPayload(logger, stdin, e.Payload)
	case serf.Query:
		cmd.Env = append(cmd.Env, "SERF_QUERY_NAME="+e.Name)
		cmd.Env = append(cmd.Env, fmt.Sprintf("SERF_QUERY_LTIME=%d", e.LTime))
		go streamPayload(logger, stdin, e.Payload)
	default:
		return fmt.Errorf("Unknown event type: %s", event.EventType().String())
	}

	if err := cmd.Start(); err != nil {
		return err
	}

	err = cmd.Wait()
	logger.Printf("[DEBUG] agent: Event '%s' script output: %s, err: %v",
		event.EventType().String(), output.String())
	if err != nil {
		return err
	}

	// If this is a query and we have output, respond
	if query, ok := event.(serf.Query); ok && len(output.Bytes()) > 0 {
		if err := query.Respond(output.Bytes()); err != nil {
			logger.Printf("[WARN] agent: Failed to respond to query '%s': %s",
				event.String(), err)
		}
	}

	return nil
}

// eventClean cleans a value to be a parameter in an event line.
func eventClean(v string) string {
	v = strings.Replace(v, "\t", "\\t", -1)
	v = strings.Replace(v, "\n", "\\n", -1)
	return v
}

// Sends data on stdin for a member event.
//
// The format for the data is unix tool friendly, separated by whitespace
// and newlines. The structure of each line for any member event is:
// "NAME    ADDRESS    ROLE    TAGS" where the whitespace is actually tabs.
// The name and role are cleaned so that newlines and tabs are replaced
// with "\n" and "\t" respectively.
func memberEventStdin(logger *log.Logger, stdin io.WriteCloser, e *serf.MemberEvent) {
	defer stdin.Close()
	for _, member := range e.Members {
		// Format the tags as tag1=v1,tag2=v2,...
		var tagPairs []string
		for name, value := range member.Tags {
			tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", name, value))
		}
		tags := strings.Join(tagPairs, ",")

		// Send the entire line
		_, err := stdin.Write([]byte(fmt.Sprintf(
			"%s\t%s\t%s\t%s\n",
			eventClean(member.Name),
			member.Addr.String(),
			eventClean(member.Tags["role"]),
			eventClean(tags))))
		if err != nil {
			return
		}
	}
}

// Sends data on stdin for an event. The stdin simply contains the
// payload (if any).
// Most shells read implementations need a newline, force it to be there
func streamPayload(logger *log.Logger, stdin io.WriteCloser, buf []byte) {
	defer stdin.Close()

	// Append a newline to payload if missing
	payload := buf
	if len(payload) > 0 && payload[len(payload)-1] != '\n' {
		payload = append(payload, '\n')
	}

	if _, err := stdin.Write(payload); err != nil {
		logger.Printf("[ERR] Error writing payload: %s", err)
		return
	}
}
<|endoftext|>


<commit_before>package command

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/jbgo/sftbot/db"
	"github.com/jbgo/sftbot/plx"
	"github.com/jbgo/sftbot/trading"
	"io/ioutil"
	"log"
	"sort"
	"strings"
)

const PROSPECT_DB = "sftbot-prospect.db"

type ProspectCommand struct {
	Flags *flag.FlagSet

	Config string

	DBStore db.Store
}

func (c *ProspectCommand) Synopsis() string {
	return "search for markets that look tradeable"
}

func (c *ProspectCommand) Help() string {
	return formatHelpText(`
Usage: sftbot plx ticker [options]

  ` + c.Synopsis() + `

` + helpOptions(c))
}

func (c *ProspectCommand) InitFlags() *flag.FlagSet {
	c.Flags = flag.NewFlagSet("plx ticker", flag.ContinueOnError)

	c.Flags.StringVar(&c.Config, "config", "", "Trader config file (JSON)")

	return c.Flags
}

type ByVolumeDesc []plx.TickerEntry

func (a ByVolumeDesc) Len() int           { return len(a) }
func (a ByVolumeDesc) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByVolumeDesc) Less(i, j int) bool { return a[i].BaseVolume > a[j].BaseVolume }

func (c *ProspectCommand) Run(args []string) int {
	c.InitFlags()
	c.Flags.Parse(args)
	c.InitDB()

	client := plx.NewLiveClient()
	ticker, err := client.GetTicker()

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

	sort.Sort(ByVolumeDesc(ticker))

	for _, t := range ticker {
		interesting := strings.Contains(t.Market, "BTC_") && t.BaseVolume >= 400

		if !interesting {
			continue
		}

		trader, err := c.InitTrader(t.Market)

		if err != nil {
			log.Println("!!! ERROR", err)
			continue
		}

		marketData, err := trader.Prospect()

		if err != nil {
			log.Println("!!! ERROR", err)
			continue
		}

		if marketData.VolatilityIndex >= trader.VolatilityFactor {
			bid := marketData.Percentiles[trader.BuyThreshold]

			status := "WATCH"
			if marketData.CurrentPrice <= bid {
				status = "BUY"
			}

			fmt.Printf(strings.Join([]string{
				fmt.Sprintf("%-12s", t.Market),
				fmt.Sprintf("%-8s", status),
				fmt.Sprintf("price=%0.9f   ", marketData.CurrentPrice),
				fmt.Sprintf("bid=%0.9f   ", bid),
				fmt.Sprintf("volatility=%0.4f   ", marketData.VolatilityIndex),
				fmt.Sprintf("volume=%0.4f   ", t.BaseVolume),
				"\n",
			}, " "))
		}
	}

	return 0
}

func (c *ProspectCommand) InitDB() {
	dbPath := LIVE_DB

	dbStore, err := db.NewBoltStore("prospect", dbPath)
	if err != nil {
		log.Fatal(err)
	}

	c.DBStore = dbStore
}

func (c *ProspectCommand) InitTrader(marketName string) (*trading.Trader, error) {
	plxClient := plx.NewLiveClient()
	plxExchange := trading.NewPlxExchange(plxClient)

	traderConfig, err := c.LoadTraderConfig()
	if err != nil {
		return nil, err
	}

	trader, err := trading.NewTrader(marketName, plxExchange, c.DBStore, traderConfig)
	if err != nil {
		return nil, err
	}

	return trader, nil
}

func (c *ProspectCommand) LoadTraderConfig() (*trading.TraderConfig, error) {
	if len(c.Config) == 0 {
		return nil, fmt.Errorf("-config is required")
	}

	data, err := ioutil.ReadFile(c.Config)
	if err != nil {
		return nil, err
	}

	traderConfig := &trading.TraderConfig{}
	err = json.Unmarshal(data, traderConfig)

	return traderConfig, err
}
<commit_msg>less noise prospecting<commit_after>package command

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/jbgo/sftbot/db"
	"github.com/jbgo/sftbot/plx"
	"github.com/jbgo/sftbot/trading"
	"io/ioutil"
	"log"
	"sort"
	"strings"
)

const PROSPECT_DB = "sftbot-prospect.db"

type ProspectCommand struct {
	Flags *flag.FlagSet

	Config string

	DBStore db.Store
}

func (c *ProspectCommand) Synopsis() string {
	return "search for markets that look tradeable"
}

func (c *ProspectCommand) Help() string {
	return formatHelpText(`
Usage: sftbot plx ticker [options]

  ` + c.Synopsis() + `

` + helpOptions(c))
}

func (c *ProspectCommand) InitFlags() *flag.FlagSet {
	c.Flags = flag.NewFlagSet("plx ticker", flag.ContinueOnError)

	c.Flags.StringVar(&c.Config, "config", "", "Trader config file (JSON)")

	return c.Flags
}

type ByVolumeDesc []plx.TickerEntry

func (a ByVolumeDesc) Len() int           { return len(a) }
func (a ByVolumeDesc) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByVolumeDesc) Less(i, j int) bool { return a[i].BaseVolume > a[j].BaseVolume }

func (c *ProspectCommand) Run(args []string) int {
	c.InitFlags()
	c.Flags.Parse(args)
	c.InitDB()

	client := plx.NewLiveClient()
	ticker, err := client.GetTicker()

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

	sort.Sort(ByVolumeDesc(ticker))

	for _, t := range ticker {
		interesting := strings.Contains(t.Market, "BTC_") && t.BaseVolume >= 400

		if !interesting {
			continue
		}

		trader, err := c.InitTrader(t.Market)

		if err != nil {
			log.Println("!!! ERROR", err)
			continue
		}

		marketData, err := trader.Prospect()

		if err != nil {
			log.Println("!!! ERROR", err)
			continue
		}

		if marketData.VolatilityIndex >= 0.99*trader.VolatilityFactor {
			bid := marketData.Percentiles[trader.BuyThreshold]

			status := "WATCH"
			if marketData.CurrentPrice <= bid && marketData.VolatilityIndex >= trader.VolatilityFactor {
				status = "BUY"
			}

			fmt.Printf(strings.Join([]string{
				fmt.Sprintf("%-12s", t.Market),
				fmt.Sprintf("%-8s", status),
				fmt.Sprintf("price=%0.9f   ", marketData.CurrentPrice),
				fmt.Sprintf("bid=%0.9f   ", bid),
				fmt.Sprintf("volatility=%0.4f   ", marketData.VolatilityIndex),
				fmt.Sprintf("volume=%0.4f   ", t.BaseVolume),
				"\n",
			}, " "))
		}
	}

	return 0
}

func (c *ProspectCommand) InitDB() {
	dbPath := LIVE_DB

	dbStore, err := db.NewBoltStore("prospect", dbPath)
	if err != nil {
		log.Fatal(err)
	}

	c.DBStore = dbStore
}

func (c *ProspectCommand) InitTrader(marketName string) (*trading.Trader, error) {
	plxClient := plx.NewLiveClient()
	plxExchange := trading.NewPlxExchange(plxClient)

	traderConfig, err := c.LoadTraderConfig()
	if err != nil {
		return nil, err
	}

	trader, err := trading.NewTrader(marketName, plxExchange, c.DBStore, traderConfig)
	if err != nil {
		return nil, err
	}

	return trader, nil
}

func (c *ProspectCommand) LoadTraderConfig() (*trading.TraderConfig, error) {
	if len(c.Config) == 0 {
		return nil, fmt.Errorf("-config is required")
	}

	data, err := ioutil.ReadFile(c.Config)
	if err != nil {
		return nil, err
	}

	traderConfig := &trading.TraderConfig{}
	err = json.Unmarshal(data, traderConfig)

	return traderConfig, err
}
<|endoftext|>


<commit_before>package commands

import (
	"io"
	"io/ioutil"
	"os"
	"testing"

	"reflect"

	"github.com/Skarlso/go-furnace/config"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
)

type fakeCreateCFClient struct {
	cloudformationiface.CloudFormationAPI
	stackname string
	err       error
}

func (fc *fakeCreateCFClient) ValidateTemplate(input *cloudformation.ValidateTemplateInput) (*cloudformation.ValidateTemplateOutput, error) {
	return &cloudformation.ValidateTemplateOutput{}, fc.err
}

func (fc *fakeCreateCFClient) CreateStack(input *cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) {
	if fc.stackname == "NotEmptyStack" {
		return &cloudformation.CreateStackOutput{StackId: aws.String("DummyID")}, fc.err
	}
	return &cloudformation.CreateStackOutput{}, fc.err
}

func (fc *fakeCreateCFClient) WaitUntilStackCreateComplete(input *cloudformation.DescribeStacksInput) error {
	return fc.err
}

func (fc *fakeCreateCFClient) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) {
	if fc.stackname == "NotEmptyStack" {
		return NotEmptyStack, fc.err
	}
	return &cloudformation.DescribeStacksOutput{}, fc.err
}

func TestCreateProcedure(t *testing.T) {
	config.WAITFREQUENCY = 0
	client := new(CFClient)
	stackname := "NotEmptyStack"
	client.Client = &fakeCreateCFClient{err: nil, stackname: stackname}
	config := []byte("{}")
	stacks := create(stackname, config, client)
	if len(stacks) == 0 {
		t.Fatal("Stack was not returned by create.")
	}
	if *stacks[0].StackName != "TestStack" {
		t.Fatal("Not the correct stack returned. Returned was:", stacks)
	}
}

func TestCreateReturnsEmptyStack(t *testing.T) {
	config.WAITFREQUENCY = 0
	client := new(CFClient)
	stackname := "EmptyStack"
	client.Client = &fakeCreateCFClient{err: nil, stackname: stackname}
	config := []byte("{}")
	stacks := create(stackname, config, client)
	if len(stacks) != 0 {
		t.Fatal("Stack was not empty: ", stacks)
	}
}

func TestGatheringParametersWithoutSpecifyingUserInputShouldUseDefaultValue(t *testing.T) {
	in, err := ioutil.TempFile("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer in.Close()
	validOutput := &cloudformation.ValidateTemplateOutput{
		Parameters: []*cloudformation.TemplateParameter{
			&cloudformation.TemplateParameter{
				DefaultValue: aws.String("DefaultValue"),
				Description:  aws.String("Description"),
				NoEcho:       aws.Bool(false),
				ParameterKey: aws.String("Key"),
			},
		},
	}
	params := gatherParameters(in, validOutput)
	if *params[0].ParameterKey != "Key" {
		t.Fatal("Key did not equal expected key. Was:", *params[0].ParameterKey)
	}
	if *params[0].ParameterValue != "DefaultValue" {
		t.Fatal("Value did not equal expected value. Was:", *params[0].ParameterValue)
	}
}

func TestGatheringParametersWithUserInputShouldUseInput(t *testing.T) {
	// Create a temp file
	in, err := ioutil.TempFile("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer in.Close()
	// Write the new value in that file
	_, err = io.WriteString(in, "NewValue\n")
	if err != nil {
		t.Fatal(err)
	}
	// Set the starting point for the next read to be the beginning of the file
	_, err = in.Seek(0, os.SEEK_SET)
	if err != nil {
		t.Fatal(err)
	}
	// Setup the input
	validOutput := &cloudformation.ValidateTemplateOutput{
		Parameters: []*cloudformation.TemplateParameter{
			&cloudformation.TemplateParameter{
				DefaultValue: aws.String("DefaultValue"),
				Description:  aws.String("Description"),
				NoEcho:       aws.Bool(false),
				ParameterKey: aws.String("Key"),
			},
		},
	}
	params := gatherParameters(in, validOutput)
	if *params[0].ParameterKey != "Key" {
		t.Fatal("Key did not equal expected key. Was:", *params[0].ParameterKey)
	}
	if *params[0].ParameterValue != "NewValue" {
		t.Fatal("Value did not equal expected value. Was:", *params[0].ParameterValue)
	}
}

func TestNewCreate(t *testing.T) {
	wrapper := NewCreate("furnace")
	if wrapper.Help.Arguments != "name" ||
		!reflect.DeepEqual(wrapper.Help.Examples, []string{"create", "create MyStackName"}) ||
		wrapper.Help.LongDescription != `Create a stack on which to deploy code to later on. By default FurnaceStack is used as name.` ||
		wrapper.Help.ShortDescription != "Create a stack" {
		t.Fatal("wrapper did not match with given params")
	}
}
<commit_msg>gofmt -sw<commit_after>package commands

import (
	"io"
	"io/ioutil"
	"os"
	"testing"

	"reflect"

	"github.com/Skarlso/go-furnace/config"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
)

type fakeCreateCFClient struct {
	cloudformationiface.CloudFormationAPI
	stackname string
	err       error
}

func (fc *fakeCreateCFClient) ValidateTemplate(input *cloudformation.ValidateTemplateInput) (*cloudformation.ValidateTemplateOutput, error) {
	return &cloudformation.ValidateTemplateOutput{}, fc.err
}

func (fc *fakeCreateCFClient) CreateStack(input *cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) {
	if fc.stackname == "NotEmptyStack" {
		return &cloudformation.CreateStackOutput{StackId: aws.String("DummyID")}, fc.err
	}
	return &cloudformation.CreateStackOutput{}, fc.err
}

func (fc *fakeCreateCFClient) WaitUntilStackCreateComplete(input *cloudformation.DescribeStacksInput) error {
	return fc.err
}

func (fc *fakeCreateCFClient) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) {
	if fc.stackname == "NotEmptyStack" {
		return NotEmptyStack, fc.err
	}
	return &cloudformation.DescribeStacksOutput{}, fc.err
}

func TestCreateProcedure(t *testing.T) {
	config.WAITFREQUENCY = 0
	client := new(CFClient)
	stackname := "NotEmptyStack"
	client.Client = &fakeCreateCFClient{err: nil, stackname: stackname}
	config := []byte("{}")
	stacks := create(stackname, config, client)
	if len(stacks) == 0 {
		t.Fatal("Stack was not returned by create.")
	}
	if *stacks[0].StackName != "TestStack" {
		t.Fatal("Not the correct stack returned. Returned was:", stacks)
	}
}

func TestCreateReturnsEmptyStack(t *testing.T) {
	config.WAITFREQUENCY = 0
	client := new(CFClient)
	stackname := "EmptyStack"
	client.Client = &fakeCreateCFClient{err: nil, stackname: stackname}
	config := []byte("{}")
	stacks := create(stackname, config, client)
	if len(stacks) != 0 {
		t.Fatal("Stack was not empty: ", stacks)
	}
}

func TestGatheringParametersWithoutSpecifyingUserInputShouldUseDefaultValue(t *testing.T) {
	in, err := ioutil.TempFile("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer in.Close()
	validOutput := &cloudformation.ValidateTemplateOutput{
		Parameters: []*cloudformation.TemplateParameter{
			{
				DefaultValue: aws.String("DefaultValue"),
				Description:  aws.String("Description"),
				NoEcho:       aws.Bool(false),
				ParameterKey: aws.String("Key"),
			},
		},
	}
	params := gatherParameters(in, validOutput)
	if *params[0].ParameterKey != "Key" {
		t.Fatal("Key did not equal expected key. Was:", *params[0].ParameterKey)
	}
	if *params[0].ParameterValue != "DefaultValue" {
		t.Fatal("Value did not equal expected value. Was:", *params[0].ParameterValue)
	}
}

func TestGatheringParametersWithUserInputShouldUseInput(t *testing.T) {
	// Create a temp file
	in, err := ioutil.TempFile("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer in.Close()
	// Write the new value in that file
	_, err = io.WriteString(in, "NewValue\n")
	if err != nil {
		t.Fatal(err)
	}
	// Set the starting point for the next read to be the beginning of the file
	_, err = in.Seek(0, os.SEEK_SET)
	if err != nil {
		t.Fatal(err)
	}
	// Setup the input
	validOutput := &cloudformation.ValidateTemplateOutput{
		Parameters: []*cloudformation.TemplateParameter{
			{
				DefaultValue: aws.String("DefaultValue"),
				Description:  aws.String("Description"),
				NoEcho:       aws.Bool(false),
				ParameterKey: aws.String("Key"),
			},
		},
	}
	params := gatherParameters(in, validOutput)
	if *params[0].ParameterKey != "Key" {
		t.Fatal("Key did not equal expected key. Was:", *params[0].ParameterKey)
	}
	if *params[0].ParameterValue != "NewValue" {
		t.Fatal("Value did not equal expected value. Was:", *params[0].ParameterValue)
	}
}

func TestNewCreate(t *testing.T) {
	wrapper := NewCreate("furnace")
	if wrapper.Help.Arguments != "name" ||
		!reflect.DeepEqual(wrapper.Help.Examples, []string{"create", "create MyStackName"}) ||
		wrapper.Help.LongDescription != `Create a stack on which to deploy code to later on. By default FurnaceStack is used as name.` ||
		wrapper.Help.ShortDescription != "Create a stack" {
		t.Fatal("wrapper did not match with given params")
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"testing"

	"github.com/Skarlso/go-furnace/config"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
)

var notEmptyStack = &cloudformation.DescribeStacksOutput{
	Stacks: []*cloudformation.Stack{
		&cloudformation.Stack{StackName: aws.String("TestStack")},
	},
}

type fakeCFClient struct {
	cloudformationiface.CloudFormationAPI
	stackname string
	err       error
}

func (fc *fakeCFClient) ValidateTemplate(input *cloudformation.ValidateTemplateInput) (*cloudformation.ValidateTemplateOutput, error) {
	return &cloudformation.ValidateTemplateOutput{}, fc.err
}

func (fc *fakeCFClient) CreateStack(input *cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) {
	if fc.stackname == "NotEmptyStack" {
		return &cloudformation.CreateStackOutput{StackId: aws.String("DummyID")}, fc.err
	}
	return &cloudformation.CreateStackOutput{}, fc.err
}

func (fc *fakeCFClient) WaitUntilStackCreateComplete(input *cloudformation.DescribeStacksInput) error {
	return fc.err
}

func (fc *fakeCFClient) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) {
	if fc.stackname == "NotEmptyStack" {
		return notEmptyStack, fc.err
	}
	return &cloudformation.DescribeStacksOutput{}, fc.err
}

func TestCreateProcedure(t *testing.T) {
	config.WAITFREQUENCY = 0
	client := new(CFClient)
	stackname := "NotEmptyStack"
	client.Client = &fakeCFClient{err: nil, stackname: stackname}
	config := []byte("{}")
	stacks := create(stackname, config, client)
	if len(stacks) == 0 {
		t.Fatal("Stack was not returned by create.")
	}
	if *stacks[0].StackName != "TestStack" {
		t.Fatal("Not the correct stack returned. Returned was:", stacks)
	}
}
<commit_msg>Added one more for good measures.<commit_after>package commands

import (
	"testing"

	"github.com/Skarlso/go-furnace/config"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
)

var notEmptyStack = &cloudformation.DescribeStacksOutput{
	Stacks: []*cloudformation.Stack{
		&cloudformation.Stack{StackName: aws.String("TestStack")},
	},
}

type fakeCFClient struct {
	cloudformationiface.CloudFormationAPI
	stackname string
	err       error
}

func (fc *fakeCFClient) ValidateTemplate(input *cloudformation.ValidateTemplateInput) (*cloudformation.ValidateTemplateOutput, error) {
	return &cloudformation.ValidateTemplateOutput{}, fc.err
}

func (fc *fakeCFClient) CreateStack(input *cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) {
	if fc.stackname == "NotEmptyStack" {
		return &cloudformation.CreateStackOutput{StackId: aws.String("DummyID")}, fc.err
	}
	return &cloudformation.CreateStackOutput{}, fc.err
}

func (fc *fakeCFClient) WaitUntilStackCreateComplete(input *cloudformation.DescribeStacksInput) error {
	return fc.err
}

func (fc *fakeCFClient) DescribeStacks(input *cloudformation.DescribeStacksInput) (*cloudformation.DescribeStacksOutput, error) {
	if fc.stackname == "NotEmptyStack" {
		return notEmptyStack, fc.err
	}
	return &cloudformation.DescribeStacksOutput{}, fc.err
}

func TestCreateProcedure(t *testing.T) {
	config.WAITFREQUENCY = 0
	client := new(CFClient)
	stackname := "NotEmptyStack"
	client.Client = &fakeCFClient{err: nil, stackname: stackname}
	config := []byte("{}")
	stacks := create(stackname, config, client)
	if len(stacks) == 0 {
		t.Fatal("Stack was not returned by create.")
	}
	if *stacks[0].StackName != "TestStack" {
		t.Fatal("Not the correct stack returned. Returned was:", stacks)
	}
}

func TestCreateReturnsEmptyStack(t *testing.T) {
	config.WAITFREQUENCY = 0
	client := new(CFClient)
	stackname := "EmptyStack"
	client.Client = &fakeCFClient{err: nil, stackname: stackname}
	config := []byte("{}")
	stacks := create(stackname, config, client)
	if len(stacks) != 0 {
		t.Fatal("Stack was not empty: ", stacks)
	}
}
<|endoftext|>


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

package fn

import (
	"io"
	"log"
	"os"
	"sync"
)

// Shortcut flags defining which text to prefix to each log entry generated by the Logger.
const (
	LflagsOff   = 0                                         // use to shut off log flags
	LflagsMsecs = log.Ldate | log.Ltime | log.Lmicroseconds // log.logger log flags date+time+microseconds
	LflagsBasic = log.Ldate | log.Ltime                     // log.logger log flags date+time
	LflagsCmn   = log.Ldate | log.Ltime | log.Lshortfile    // log.logger log flags date+time+shortfname
	LflagsDef   = LflagsCmn                                 // default log.logger log flags
)

// Trace message labels used during LogTraceZZZ where ZZZ is blank/Msgs/Msgp"
const (
	LbegTraceLab     = "BegTrace:"
	LendTraceLab     = "EndTrace:"
	LbegTraceMsgsLab = "BegTrMsg:"
	LendTraceMsgsLab = "EndTrMsg:"
	LbegTraceMsgpLab = "BegTrMsp:"
	LendTraceMsgpLab = "EndTrMsp:"
)

var gopathsrc string

var logOutputDef *os.File
var logOutputCur io.Writer
var logAlignFile int
var logAlignFunc int

// Log related constants
const (
	LogAlignFileDef = 24 // log alignment 'file' field minimum width
	LogAlignFileMax = 50 // log alignment 'file' field minimum width max

	LogAlignFuncDef = 0  // log alignment 'func' field minimum width
	LogAlignFuncMax = 50 // log alignment 'func' field minimum width max

	LogPrefixDef = "LogFN: " // default log.logger log prefix
)

var logt *log.Logger
var muLogt sync.Mutex // mutex protecting logt stuff and trflags state

func init() {
	gopathsrc = os.Getenv("GOPATH")
	if gopathsrc != "" {
		gopathsrc += "/src/"
	}
	logOutputDef = os.Stdout
	logOutputCur = logOutputDef
	logt = log.New(logOutputDef, LogPrefixDef, LflagsDef)
	LogSetAlignFile(LogAlignFileDef)
	LogSetAlignFunc(LogAlignFuncDef)
}

// LogSetAlignFile - return alignment [minimum width] for filename stuff
func LogSetAlignFile(minWidth int) {
	muLogt.Lock()
	defer muLogt.Unlock()
	if minWidth > LogAlignFileMax {
		minWidth = LogAlignFileMax
	} else if minWidth < 0 {
		minWidth = 0
	}
	logAlignFile = minWidth
}

// LogAlignFile - return alignment [minimum width] for filename stuff
func LogAlignFile() int {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logAlignFile
}

// LogSetAlignFunc - return alignment [minimum width] for funcname stuff
func LogSetAlignFunc(minWidth int) {
	muLogt.Lock()
	defer muLogt.Unlock()
	if minWidth > LogAlignFuncMax {
		minWidth = LogAlignFuncMax
	} else if minWidth < 0 {
		minWidth = 0
	}
	logAlignFunc = minWidth
}

// LogAlignFunc - return alignment [minimum width] for funcname stuff
func LogAlignFunc() int {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logAlignFunc
}

// LogGetOutputDef - return log output default value
func LogGetOutputDef() io.Writer {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logOutputDef
}

// lower level with no mutex
func logSetOutput(iowr io.Writer) {
	logt.SetOutput(iowr)
	logOutputCur = iowr
}

// LogSetOutput sets the output destination for the logger.
func LogSetOutput(iowr io.Writer) {
	muLogt.Lock()
	defer muLogt.Unlock()
	logSetOutput(iowr)
}

// LogGetOutput gets the output destination.
func LogGetOutput() io.Writer {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logOutputCur
}

// LogSetFlags sets the output flags for the logger.
func LogSetFlags(lflags int) {
	muLogt.Lock()
	defer muLogt.Unlock()
	logt.SetFlags(lflags)
}

// LogFlags returns the log flags for the logger.
func LogFlags() int {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logt.Flags()
}

// LogSetPrefix sets the output prefix for the logger.
func LogSetPrefix(prefix string) {
	muLogt.Lock()
	defer muLogt.Unlock()
	logt.SetPrefix(prefix)
}

// LogPrefix returns the output prefix for the logger.
func LogPrefix() string {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logt.Prefix()
}

// [log] Trace Flags options referenced during LogTrace() and LogTraceMsgs()()
const (
	Trlogignore      = 1 << iota // if set return before any processing (faster than ioutil.Discard)
	Trbegtime                    // print begin time
	Trendtime                    // print end time
	Trmicroseconds               // print microseconds on Tbegtime/Tendtime when active
	Trnodur                      // do not print duration
	Trfnbase                     // base version func name may want if log.Llongfile active
	Trfilenogps                  // when log.Llongfile active filename less gopath src portion
	Trfnobegref                  // do not print beg reference on EndTrZZZ
	Trfbegrefincfile             // include filename on beg reference on EndTrZZZ
	Trbegtimemicro   = Trbegtime | Trmicroseconds
	Trendtimemicro   = Trendtime | Trmicroseconds
	Trmicroboth      = Trbegtime | Trendtime | Trmicroseconds
	TrFlagsDef       = Trfilenogps | Trfnbase
	TrFlagsOff       = Trnodur | Trfnobegref
)

var logTraceFlags = TrFlagsDef

// LogSetTraceFlags - sets TrFlags.
func LogSetTraceFlags(t int) {
	muLogt.Lock()
	defer muLogt.Unlock()
	logTraceFlags = t
}

// LogTraceFlags - return current TrFlags value.
func LogTraceFlags() int {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logTraceFlags
}
<commit_msg>LogAlignFileDef reduce from 24 to 16<commit_after>// Copyright 2017 phcurtis fn Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fn

import (
	"io"
	"log"
	"os"
	"sync"
)

// Shortcut flags defining which text to prefix to each log entry generated by the Logger.
const (
	LflagsOff   = 0                                         // use to shut off log flags
	LflagsMsecs = log.Ldate | log.Ltime | log.Lmicroseconds // log.logger log flags date+time+microseconds
	LflagsBasic = log.Ldate | log.Ltime                     // log.logger log flags date+time
	LflagsCmn   = log.Ldate | log.Ltime | log.Lshortfile    // log.logger log flags date+time+shortfname
	LflagsDef   = LflagsCmn                                 // default log.logger log flags
)

// Trace message labels used during LogTraceZZZ where ZZZ is blank/Msgs/Msgp"
const (
	LbegTraceLab     = "BegTrace:"
	LendTraceLab     = "EndTrace:"
	LbegTraceMsgsLab = "BegTrMsg:"
	LendTraceMsgsLab = "EndTrMsg:"
	LbegTraceMsgpLab = "BegTrMsp:"
	LendTraceMsgpLab = "EndTrMsp:"
)

var gopathsrc string

var logOutputDef *os.File
var logOutputCur io.Writer
var logAlignFile int
var logAlignFunc int

// Log related constants
const (
	LogAlignFileDef = 16 // log alignment 'file' field minimum width
	LogAlignFileMax = 50 // log alignment 'file' field minimum width max

	LogAlignFuncDef = 0  // log alignment 'func' field minimum width
	LogAlignFuncMax = 50 // log alignment 'func' field minimum width max

	LogPrefixDef = "LogFN: " // default log.logger log prefix
)

var logt *log.Logger
var muLogt sync.Mutex // mutex protecting logt stuff and trflags state

func init() {
	gopathsrc = os.Getenv("GOPATH")
	if gopathsrc != "" {
		gopathsrc += "/src/"
	}
	logOutputDef = os.Stdout
	logOutputCur = logOutputDef
	logt = log.New(logOutputDef, LogPrefixDef, LflagsDef)
	LogSetAlignFile(LogAlignFileDef)
	LogSetAlignFunc(LogAlignFuncDef)
}

// LogSetAlignFile - return alignment [minimum width] for filename stuff
func LogSetAlignFile(minWidth int) {
	muLogt.Lock()
	defer muLogt.Unlock()
	if minWidth > LogAlignFileMax {
		minWidth = LogAlignFileMax
	} else if minWidth < 0 {
		minWidth = 0
	}
	logAlignFile = minWidth
}

// LogAlignFile - return alignment [minimum width] for filename stuff
func LogAlignFile() int {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logAlignFile
}

// LogSetAlignFunc - return alignment [minimum width] for funcname stuff
func LogSetAlignFunc(minWidth int) {
	muLogt.Lock()
	defer muLogt.Unlock()
	if minWidth > LogAlignFuncMax {
		minWidth = LogAlignFuncMax
	} else if minWidth < 0 {
		minWidth = 0
	}
	logAlignFunc = minWidth
}

// LogAlignFunc - return alignment [minimum width] for funcname stuff
func LogAlignFunc() int {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logAlignFunc
}

// LogGetOutputDef - return log output default value
func LogGetOutputDef() io.Writer {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logOutputDef
}

// lower level with no mutex
func logSetOutput(iowr io.Writer) {
	logt.SetOutput(iowr)
	logOutputCur = iowr
}

// LogSetOutput sets the output destination for the logger.
func LogSetOutput(iowr io.Writer) {
	muLogt.Lock()
	defer muLogt.Unlock()
	logSetOutput(iowr)
}

// LogGetOutput gets the output destination.
func LogGetOutput() io.Writer {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logOutputCur
}

// LogSetFlags sets the output flags for the logger.
func LogSetFlags(lflags int) {
	muLogt.Lock()
	defer muLogt.Unlock()
	logt.SetFlags(lflags)
}

// LogFlags returns the log flags for the logger.
func LogFlags() int {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logt.Flags()
}

// LogSetPrefix sets the output prefix for the logger.
func LogSetPrefix(prefix string) {
	muLogt.Lock()
	defer muLogt.Unlock()
	logt.SetPrefix(prefix)
}

// LogPrefix returns the output prefix for the logger.
func LogPrefix() string {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logt.Prefix()
}

// [log] Trace Flags options referenced during LogTrace() and LogTraceMsgs()()
// funcs LogSetTraceFlags sets the Trace Flags and LogTraceFlags retrieves current trace flags.
const (
	Trlogignore      = 1 << iota // if set return before any processing (faster than ioutil.Discard)
	Trbegtime                    // print begin time
	Trendtime                    // print end time
	Trmicroseconds               // print microseconds on Tbegtime/Tendtime when active
	Trnodur                      // do not print duration
	Trfnbase                     // base version func name may want if log.Llongfile active
	Trfilenogps                  // when log.Llongfile active filename less gopath src portion
	Trfnobegref                  // do not print beg reference on EndTrZZZ
	Trfbegrefincfile             // include filename on beg reference on EndTrZZZ
	Trbegtimemicro   = Trbegtime | Trmicroseconds
	Trendtimemicro   = Trendtime | Trmicroseconds
	Trmicroboth      = Trbegtime | Trendtime | Trmicroseconds
	TrFlagsDef       = Trfilenogps | Trfnbase
	TrFlagsOff       = Trnodur | Trfnobegref
)

var logTraceFlags = TrFlagsDef

// LogSetTraceFlags - sets TrFlags.
func LogSetTraceFlags(t int) {
	muLogt.Lock()
	defer muLogt.Unlock()
	logTraceFlags = t
}

// LogTraceFlags - return current TrFlags value.
func LogTraceFlags() int {
	muLogt.Lock()
	defer muLogt.Unlock()
	return logTraceFlags
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
)

const VERSION = "1.3.dev"

var (
	//
	// Flags
	//   flag description in flag.Usage
	//

	// common
	printHelpFlag    = flag.Bool("h", false, "")
	verboseFlag      = flag.Bool("v", false, "")
	printVersionFlag = flag.Bool("V", false, "")

	// server
	serverModeFlag  = flag.Bool("s", false, "")
	expireBasedFlag = flag.Bool("p", false, "")
	// flag.Duration not useful because there is not unit for days
	leaseExpiredDurationFlag  = flag.String("e", "7d", "")
	cleanupLeaseTimerFlag     = flag.String("t", "15m", "")
	missedPingsThresholdFlag  = flag.Int("m", 3, "")
	keepLeasesOverRestartFlag = flag.Bool("k", false, "")

	// client
	scriptedModeFlag          = flag.Bool("H", false, "")
	clearLeasesFlag           = flag.Bool("c", false, "")
	listNewestLeasesFirstFlag = flag.Bool("n", false, "")
	shutdownServerFlag        = flag.Bool("x", false, "")
)

var (
	verboseLog *log.Logger
)

var appDataPath = osDependAppDataPath()

func main() {
	flag.Usage = func() {
		fmt.Println("Common:")
		fmt.Println("  -h: print help")
		fmt.Println("  -v: verbose output")
		fmt.Println("  -V: print version")
		fmt.Println("Client mode:")
		fmt.Println("  -c: clear leases")
		fmt.Println("  -H: scripted mode: no headers, dates as unix time")
		fmt.Println("  -n: list newest leases first")
		fmt.Println("  -x: shutdown server")
		fmt.Println("Server mode:")
		fmt.Println("  -s: server mode")
		fmt.Println("  -p: passive mode - no active availability host check - clear leases expire based")
		fmt.Println("  -e: in passive mode: lease expire duration (valid units: 'd', 'h', 'm', 's') - default:",
			*leaseExpiredDurationFlag)
		fmt.Println("  -t: interval for checking of leases validity (valid units: 'd', 'h', 'm', 's') - default:", *cleanupLeaseTimerFlag)
		fmt.Println("  -m: in active mode: missed arpings threshold - default:", *missedPingsThresholdFlag)
		fmt.Println("  -k: keep leases over restart")
	}
	flag.Parse()

	if *printHelpFlag {
		flag.Usage()
		os.Exit(0)
	}

	//
	// action
	//
	if *serverModeFlag {
		server()
	} else {
		client()
	}
}

func osDependAppDataPath() string {
	//
	// set os depend application data path
	//
	if runtime.GOOS == "windows" {
		return os.Getenv("USERPROFILE") + "/lsleases"
	} else {
		return "/var/lib/lsleases"
	}
}
<commit_msg>version 1.3<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
)

const VERSION = "1.3"

var (
	//
	// Flags
	//   flag description in flag.Usage
	//

	// common
	printHelpFlag    = flag.Bool("h", false, "")
	verboseFlag      = flag.Bool("v", false, "")
	printVersionFlag = flag.Bool("V", false, "")

	// server
	serverModeFlag  = flag.Bool("s", false, "")
	expireBasedFlag = flag.Bool("p", false, "")
	// flag.Duration not useful because there is not unit for days
	leaseExpiredDurationFlag  = flag.String("e", "7d", "")
	cleanupLeaseTimerFlag     = flag.String("t", "15m", "")
	missedPingsThresholdFlag  = flag.Int("m", 3, "")
	keepLeasesOverRestartFlag = flag.Bool("k", false, "")

	// client
	scriptedModeFlag          = flag.Bool("H", false, "")
	clearLeasesFlag           = flag.Bool("c", false, "")
	listNewestLeasesFirstFlag = flag.Bool("n", false, "")
	shutdownServerFlag        = flag.Bool("x", false, "")
)

var (
	verboseLog *log.Logger
)

var appDataPath = osDependAppDataPath()

func main() {
	flag.Usage = func() {
		fmt.Println("Common:")
		fmt.Println("  -h: print help")
		fmt.Println("  -v: verbose output")
		fmt.Println("  -V: print version")
		fmt.Println("Client mode:")
		fmt.Println("  -c: clear leases")
		fmt.Println("  -H: scripted mode: no headers, dates as unix time")
		fmt.Println("  -n: list newest leases first")
		fmt.Println("  -x: shutdown server")
		fmt.Println("Server mode:")
		fmt.Println("  -s: server mode")
		fmt.Println("  -p: passive mode - no active availability host check - clear leases expire based")
		fmt.Println("  -e: in passive mode: lease expire duration (valid units: 'd', 'h', 'm', 's') - default:",
			*leaseExpiredDurationFlag)
		fmt.Println("  -t: interval for checking of leases validity (valid units: 'd', 'h', 'm', 's') - default:", *cleanupLeaseTimerFlag)
		fmt.Println("  -m: in active mode: missed arpings threshold - default:", *missedPingsThresholdFlag)
		fmt.Println("  -k: keep leases over restart")
	}
	flag.Parse()

	if *printHelpFlag {
		flag.Usage()
		os.Exit(0)
	}

	//
	// action
	//
	if *serverModeFlag {
		server()
	} else {
		client()
	}
}

func osDependAppDataPath() string {
	//
	// set os depend application data path
	//
	if runtime.GOOS == "windows" {
		return os.Getenv("USERPROFILE") + "/lsleases"
	} else {
		return "/var/lib/lsleases"
	}
}
<|endoftext|>


<commit_before>// +build mage

// for use with https://github.com/magefile/mage

package main

import (
	"context"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/jtolds/qod"
	"github.com/magefile/mage/mg"
)

const (
	goVersion  = "1.13.7"
	nimVersion = "1.0.6"
)

var (
	wd      string
	dateTag string
)

func init() {
	lwd, err := os.Getwd()
	qod.ANE(err)

	wd = lwd
	dateTag = time.Now().Format("01022006")
}

func shouldWork(ctx context.Context, env []string, dir string, cmdName string, args ...string) {
	loc, err := exec.LookPath(cmdName)
	qod.ANE(err)

	cmd := exec.CommandContext(ctx, loc, args...)
	cmd.Dir = dir
	cmd.Env = env

	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	log.Printf("starting process, env: %v, pwd: %s, cmd: %s, args: %v", env, dir, loc, args)
	err = cmd.Run()
	qod.ANE(err)
}

// Zig creates the latest nightly release of zig.
func Zig() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(ctx, nil, "./lang/zig", "sh", "./make_image.sh")
}

// V builds the master version of v (https://vlang.io)
func V() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(ctx, nil, "./lang/v", "docker", "build", "--no-cache", "-t", "xena/vlang:latest", "-t", "xena/vlang:"+dateTag, ".")
	shouldWork(ctx, nil, "", "docker", "push", "xena/vlang:latest")
	shouldWork(ctx, nil, "", "docker", "push", "xena/vlang:"+dateTag)
}

// Vars shows the list of variables and their values.
func Vars() {
	qod.Printlnf("go version: %v", goVersion)
	qod.Printlnf("nim version: %v\n", nimVersion)

	qod.Printlnf("dateTag: %v", dateTag)
	qod.Printlnf("wd: %v", wd)
}

// All builds every image in order.
func All() {
	// Base image
	mg.Deps(Base)

	// Programming language specific images
	mg.Deps(Go, Nim, Zig, V, Elm)

	// Tools
	mg.Deps(MDBook, Pandoc)

	qod.Printlnf("all images built :)")
}

// Base builds the base image xena/alpine.
func Base() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	dir := filepath.Join(wd, "./base/alpine")

	// pull base alpine edge image for rebuilds
	shouldWork(ctx, nil, dir, "docker", "pull", "alpine:edge")

	dateSub := "xena/alpine:" + dateTag

	// build and push
	shouldWork(ctx, nil, dir, "docker", "build", "-t", "xena/alpine:latest", "-t", dateSub, ".")
	shouldWork(ctx, nil, dir, "docker", "push", "xena/alpine:latest")
	shouldWork(ctx, nil, dir, "docker", "push", dateSub)

	qod.Printlnf("built and pushed xena/alpine")
}

// Go builds the 'thick' go image xena/go.
// https://golang.org
func Go() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	mg.Deps(GoMini)

	dir := filepath.Join(wd, "./lang/go")
	dateSub := "xena/go:" + goVersion + "-" + dateTag

	shouldWork(ctx, nil, dir, "docker", "build", "-t", "xena/go:"+goVersion, "-t", dateSub, "--build-arg", "version="+goVersion, ".")
	shouldWork(ctx, nil, dir, "docker", "push", "xena/go:"+goVersion)
	shouldWork(ctx, nil, dir, "docker", "push", dateSub)

	qod.Printlnf("Built and pushed image for Go xena/go:%s %s", goVersion, dateSub)
}

// GoMini builds the 'mini' version of the compiler using golang.org/x/build/version.
// https://golang.org
func GoMini() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	dir := filepath.Join(wd, "./lang/go-mini")
	dateSub := "xena/go-mini:" + goVersion + "-" + dateTag

	// build and push
	shouldWork(ctx, nil, dir, "docker", "build", "-t", "xena/go-mini:"+goVersion, "-t", dateSub, "--build-arg", "version="+goVersion, ".")
	shouldWork(ctx, nil, dir, "docker", "push", "xena/go-mini:"+goVersion)
	shouldWork(ctx, nil, dir, "docker", "push", dateSub)

	qod.Printlnf("built image xena/go-mini:%s %s", goVersion, dateSub)
}

// Nim builds the image for xena/nim
// https://nim-lang.org
func Nim() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	dir := filepath.Join(wd, "./lang/nim")
	dateSub := "xena/nim:" + nimVersion + "-" + dateTag

	// build and push
	shouldWork(ctx, nil, dir, "docker", "build", "-t", "xena/nim:"+nimVersion, "-t", dateSub, "--build-arg", "nim_version="+nimVersion, ".")
	shouldWork(ctx, nil, dir, "docker", "push", "xena/nim:"+nimVersion)
	shouldWork(ctx, nil, dir, "docker", "push", dateSub)

	qod.Printlnf("build image xena/nim:%s", nimVersion)
}

// Pandoc builds pandoc
func Pandoc() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(
		ctx,
		nil,
		"./tools/pandoc",
		"docker",
		"build",
		"-t",
		"xena/pandoc:latest",
		".",
	)
	shouldWork(ctx, nil, "", "docker", "push", "xena/pandoc:latest")
}

// Elm builds an image for https://elm-lang.org
func Elm() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(
		ctx,
		nil,
		"./lang/elm",
		"docker",
		"build",
		"-t",
		"xena/elm:0.19.1",
		".",
	)
	shouldWork(ctx, nil, "", "docker", "push", "xena/elm:0.19.1")
}

// MDBook builds mdbook
func MDBook() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(
		ctx,
		nil,
		"./tools/mdbook",
		"docker",
		"build",
		"-t",
		"xena/mdbook:latest",
		".",
	)
	shouldWork(ctx, nil, "", "docker", "push", "xena/mdbook:latest")
}
<commit_msg>bump go<commit_after>// +build mage

// for use with https://github.com/magefile/mage

package main

import (
	"context"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/jtolds/qod"
	"github.com/magefile/mage/mg"
)

const (
	goVersion  = "1.14"
	nimVersion = "1.0.6"
)

var (
	wd      string
	dateTag string
)

func init() {
	lwd, err := os.Getwd()
	qod.ANE(err)

	wd = lwd
	dateTag = time.Now().Format("01022006")
}

func shouldWork(ctx context.Context, env []string, dir string, cmdName string, args ...string) {
	loc, err := exec.LookPath(cmdName)
	qod.ANE(err)

	cmd := exec.CommandContext(ctx, loc, args...)
	cmd.Dir = dir
	cmd.Env = env

	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	log.Printf("starting process, env: %v, pwd: %s, cmd: %s, args: %v", env, dir, loc, args)
	err = cmd.Run()
	qod.ANE(err)
}

// Zig creates the latest nightly release of zig.
func Zig() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(ctx, nil, "./lang/zig", "sh", "./make_image.sh")
}

// V builds the master version of v (https://vlang.io)
func V() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(ctx, nil, "./lang/v", "docker", "build", "--no-cache", "-t", "xena/vlang:latest", "-t", "xena/vlang:"+dateTag, ".")
	shouldWork(ctx, nil, "", "docker", "push", "xena/vlang:latest")
	shouldWork(ctx, nil, "", "docker", "push", "xena/vlang:"+dateTag)
}

// Vars shows the list of variables and their values.
func Vars() {
	qod.Printlnf("go version: %v", goVersion)
	qod.Printlnf("nim version: %v\n", nimVersion)

	qod.Printlnf("dateTag: %v", dateTag)
	qod.Printlnf("wd: %v", wd)
}

// All builds every image in order.
func All() {
	// Base image
	mg.Deps(Base)

	// Programming language specific images
	mg.Deps(Go, Nim, Zig, V, Elm)

	// Tools
	mg.Deps(MDBook, Pandoc)

	qod.Printlnf("all images built :)")
}

// Base builds the base image xena/alpine.
func Base() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	dir := filepath.Join(wd, "./base/alpine")

	// pull base alpine edge image for rebuilds
	shouldWork(ctx, nil, dir, "docker", "pull", "alpine:edge")

	dateSub := "xena/alpine:" + dateTag

	// build and push
	shouldWork(ctx, nil, dir, "docker", "build", "-t", "xena/alpine:latest", "-t", dateSub, ".")
	shouldWork(ctx, nil, dir, "docker", "push", "xena/alpine:latest")
	shouldWork(ctx, nil, dir, "docker", "push", dateSub)

	qod.Printlnf("built and pushed xena/alpine")
}

// Go builds the 'thick' go image xena/go.
// https://golang.org
func Go() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	mg.Deps(GoMini)

	dir := filepath.Join(wd, "./lang/go")
	dateSub := "xena/go:" + goVersion + "-" + dateTag

	shouldWork(ctx, nil, dir, "docker", "build", "-t", "xena/go:"+goVersion, "-t", dateSub, "--build-arg", "version="+goVersion, ".")
	shouldWork(ctx, nil, dir, "docker", "push", "xena/go:"+goVersion)
	shouldWork(ctx, nil, dir, "docker", "push", dateSub)

	qod.Printlnf("Built and pushed image for Go xena/go:%s %s", goVersion, dateSub)
}

// GoMini builds the 'mini' version of the compiler using golang.org/x/build/version.
// https://golang.org
func GoMini() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	dir := filepath.Join(wd, "./lang/go-mini")
	dateSub := "xena/go-mini:" + goVersion + "-" + dateTag

	// build and push
	shouldWork(ctx, nil, dir, "docker", "build", "-t", "xena/go-mini:"+goVersion, "-t", dateSub, "--build-arg", "version="+goVersion, ".")
	shouldWork(ctx, nil, dir, "docker", "push", "xena/go-mini:"+goVersion)
	shouldWork(ctx, nil, dir, "docker", "push", dateSub)

	qod.Printlnf("built image xena/go-mini:%s %s", goVersion, dateSub)
}

// Nim builds the image for xena/nim
// https://nim-lang.org
func Nim() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	dir := filepath.Join(wd, "./lang/nim")
	dateSub := "xena/nim:" + nimVersion + "-" + dateTag

	// build and push
	shouldWork(ctx, nil, dir, "docker", "build", "-t", "xena/nim:"+nimVersion, "-t", dateSub, "--build-arg", "nim_version="+nimVersion, ".")
	shouldWork(ctx, nil, dir, "docker", "push", "xena/nim:"+nimVersion)
	shouldWork(ctx, nil, dir, "docker", "push", dateSub)

	qod.Printlnf("build image xena/nim:%s", nimVersion)
}

// Pandoc builds pandoc
func Pandoc() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(
		ctx,
		nil,
		"./tools/pandoc",
		"docker",
		"build",
		"-t",
		"xena/pandoc:latest",
		".",
	)
	shouldWork(ctx, nil, "", "docker", "push", "xena/pandoc:latest")
}

// Elm builds an image for https://elm-lang.org
func Elm() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(
		ctx,
		nil,
		"./lang/elm",
		"docker",
		"build",
		"-t",
		"xena/elm:0.19.1",
		".",
	)
	shouldWork(ctx, nil, "", "docker", "push", "xena/elm:0.19.1")
}

// MDBook builds mdbook
func MDBook() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	shouldWork(
		ctx,
		nil,
		"./tools/mdbook",
		"docker",
		"build",
		"-t",
		"xena/mdbook:latest",
		".",
	)
	shouldWork(ctx, nil, "", "docker", "push", "xena/mdbook:latest")
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"log"

	"github.com/concourse/fly/rc"
)

type SaveTargetCommand struct {
	API      string   `long:"api" required:"true"            description:"Api url to target"`
	Username string   `long:"username"                       description:"Username for the api"`
	Password string   `long:"password"                       description:"Password for the api"`
	Cert     PathFlag `long:"cert"                           description:"Directory to your cert"`
	Name     string   `short:"n" long:"name" required:"true" description:"Name for target"`
	Insecure bool     `long:"skip-ssl"                       description:"Skip SSL verification"`
}

var saveTargetCommand SaveTargetCommand

func init() {
	_, err := Parser.AddCommand(
		"save-target",
		"Save a fly target to the .flyrc",
		"",
		&saveTargetCommand,
	)
	if err != nil {
		panic(err)
	}
}

// TODO: Remove these two structs
type targetProps struct {
	API      string `yaml:"api"`
	Username string
	Password string
	Cert     string
}

type TargetDetailsYAML struct {
	Targets map[string]targetProps
}

func (command *SaveTargetCommand) Execute(args []string) error {
	targetName := command.Name
	if targetName == "" {
		log.Fatalln("name not provided for target")
		return nil
	}

	err := rc.CreateOrUpdateTargets(
		targetName,
		rc.NewTarget(
			command.API,
			command.Username,
			command.Password,
			string(command.Cert),
			command.Insecure,
		),
	)
	if err != nil {
		log.Fatalln(err)
		return nil
	}

	fmt.Printf("successfully saved target %s\n", targetName)
	return nil
}
<commit_msg>Removing dead code.<commit_after>package commands

import (
	"fmt"
	"log"

	"github.com/concourse/fly/rc"
)

type SaveTargetCommand struct {
	API      string   `long:"api" required:"true"            description:"Api url to target"`
	Username string   `long:"username"                       description:"Username for the api"`
	Password string   `long:"password"                       description:"Password for the api"`
	Cert     PathFlag `long:"cert"                           description:"Directory to your cert"`
	Name     string   `short:"n" long:"name" required:"true" description:"Name for target"`
	Insecure bool     `long:"skip-ssl"                       description:"Skip SSL verification"`
}

var saveTargetCommand SaveTargetCommand

func init() {
	_, err := Parser.AddCommand(
		"save-target",
		"Save a fly target to the .flyrc",
		"",
		&saveTargetCommand,
	)
	if err != nil {
		panic(err)
	}
}

func (command *SaveTargetCommand) Execute(args []string) error {
	targetName := command.Name
	if targetName == "" {
		log.Fatalln("name not provided for target")
		return nil
	}

	err := rc.CreateOrUpdateTargets(
		targetName,
		rc.NewTarget(
			command.API,
			command.Username,
			command.Password,
			string(command.Cert),
			command.Insecure,
		),
	)
	if err != nil {
		log.Fatalln(err)
		return nil
	}

	fmt.Printf("successfully saved target %s\n", targetName)
	return nil
}
<|endoftext|>


<commit_before>package mail

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"mime"
	"net"
	"net/mail"
	"net/smtp"
	"path/filepath"
	"strings"
	"time"
)

type SmtpClient struct {
	Host     string
	Port     string
	User     string
	Password string
	From     string
	TLS      bool
}

type Attachment struct {
	Filename string
	Data     []byte
	Inline   bool
}

type Message struct {
	smtpClient      SmtpClient
	To              string
	Cc              []string
	Bcc             []string
	ReplyTo         string
	Subject         string
	Body            string
	BodyContentType string
	Attachments     map[string]*Attachment
}

func NewMessage(smtpClient *SmtpClient, to string, subject string, body string) *Message {

	return &Message{
		smtpClient:      *smtpClient,
		Subject:         subject,
		To:              to,
		Body:            body,
		BodyContentType: "text/html",
		Attachments:     make(map[string]*Attachment),
	}
}

func (m *Message) Attach(file string, inline bool) error {

	data, err := ioutil.ReadFile(file)
	if err != nil {
		return err
	}

	_, filename := filepath.Split(file)

	m.Attachments[filename] = &Attachment{
		Filename: filename,
		Data:     data,
		Inline:   inline,
	}

	return nil
}

func (m *Message) SendMail() error {

	buf := bytes.NewBuffer(nil)
	buf.WriteString("From: " + m.smtpClient.From + "\r\n")
	buf.WriteString("To: " + m.To + "\r\n")
	buf.WriteString("MIME-Version: 1.0\r\n")
	buf.WriteString("Date: " + time.Now().Format(time.RFC822) + "\r\n")

	if len(m.Cc) > 0 {
		buf.WriteString("Cc: " + strings.Join(m.Cc, ",") + "\r\n")
	}

	buf.WriteString("Subject: " + m.Subject + "\r\n")

	if len(m.ReplyTo) > 0 {
		buf.WriteString("Reply-To: " + m.ReplyTo + "\r\n")
	}

	/* Генерация тела сообщения */
	const boundary = "f46d043c813270fc6b04c2d223da"

	if len(m.Attachments) > 0 {
		buf.WriteString("Content-Type: multipart/mixed; boundary=" + boundary + "\r\n\r\n")
		buf.WriteString("--" + boundary + "\r\n")
		buf.WriteString("Content-Transfer-Encoding: 8bit\r\n")
		buf.WriteString("Content-Type: " + m.BodyContentType + "; charset=utf-8\r\n\r\n")
		buf.WriteString(m.Body)
		buf.WriteString("\r\n")
	} else {
		buf.WriteString("Content-Type: " + m.BodyContentType + "; charset=utf-8\r\n\r\n")
		buf.WriteString(m.Body)
		buf.WriteString("\r\n")
	}

	if len(m.Attachments) > 0 {
		for _, attachment := range m.Attachments {
			buf.WriteString("\r\n\r\n--" + boundary + "\r\n")

			if attachment.Inline {
				buf.WriteString("Content-Type: message/rfc822\r\n")
				buf.WriteString("Content-Disposition: inline; filename=\"" + mime.QEncoding.Encode("utf-8", attachment.Filename) + "\"\r\n\r\n")

				buf.Write(attachment.Data)
			} else {
				buf.WriteString("Content-Disposition: attachment; filename=\"" + mime.QEncoding.Encode("utf-8", attachment.Filename) + "\"\r\n")
				buf.WriteString("Content-Transfer-Encoding: base64\r\n")
				buf.WriteString("Content-Type: application/octet-stream; name=\"" + mime.QEncoding.Encode("utf-8", attachment.Filename) + "\"\r\n\r\n")

				b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data)))
				base64.StdEncoding.Encode(b, attachment.Data)

				// write base64 content in lines of up to 76 chars
				for i, l := 0, len(b); i < l; i++ {
					buf.WriteByte(b[i])
					if (i+1)%76 == 0 {
						buf.WriteString("\r\n")
					}
				}
			}

			buf.WriteString("\r\n--" + boundary)
		}

		buf.WriteString("--")
	}

	/*=======================================*/
	servername := fmt.Sprintf("%s:%s", m.smtpClient.Host, m.smtpClient.Port)
	host, _, _ := net.SplitHostPort(servername)
	auth := smtp.PlainAuth("", m.smtpClient.User, m.smtpClient.Password, host)

	if !m.smtpClient.TLS {
		err := smtp.SendMail(servername, auth, m.smtpClient.From, []string{m.To}, buf.Bytes())
		if err != nil {
			return err
		}

		return nil
	}

	/* Актуально для TLS */

	tlsconfig := &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         host,
	}

	dl := new(net.Dialer)
	dl.Timeout = 10 * time.Second
	conn, err := tls.DialWithDialer(dl, "tcp", servername, tlsconfig)

	//conn, err := tls.Dial("tcp", servername, tlsconfig)
	if err != nil {
		return err
	}

	c, err := smtp.NewClient(conn, host)
	defer func() {
		c.Quit()
	}()
	if err != nil {
		return err
	}

	// Auth
	if err = c.Auth(auth); err != nil {
		return err
	}

	// To && From
	emailFrom, err := mail.ParseAddress(m.smtpClient.From)
	if err != nil {
		return err
	}

	if err = c.Mail(emailFrom.Address); err != nil {
		return err
	}

	if err = c.Rcpt(m.To); err != nil {
		return err
	}

	// Data
	w, err := c.Data()
	if err != nil {
		return err
	}

	_, err = w.Write(buf.Bytes())
	if err != nil {
		return err
	}

	err = w.Close()
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Update<commit_after>package mail

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"mime"
	"net"
	"net/mail"
	"net/smtp"
	"path/filepath"
	"strings"
	"time"
	"gopkg.in/gomail.v2"
	"strconv"
)

type SmtpClient struct {
	Host     string
	Port     string
	User     string
	Password string
	From     string
	TLS      bool
}

type Attachment struct {
	Filename string
	Data     []byte
	Inline   bool
}

type Message struct {
	smtpClient      SmtpClient
	To              string
	Cc              []string
	Bcc             []string
	ReplyTo         string
	Subject         string
	Body            string
	BodyContentType string
	Attachments     map[string]*Attachment
}

func NewMessage(smtpClient *SmtpClient, to string, subject string, body string) *Message {

	return &Message{
		smtpClient:      *smtpClient,
		Subject:         subject,
		To:              to,
		Body:            body,
		BodyContentType: "text/html",
		Attachments:     make(map[string]*Attachment),
	}
}

func (m *Message) Attach(file string, inline bool) error {

	data, err := ioutil.ReadFile(file)
	if err != nil {
		return err
	}

	_, filename := filepath.Split(file)

	m.Attachments[filename] = &Attachment{
		Filename: filename,
		Data:     data,
		Inline:   inline,
	}

	return nil
}

func (m *Message) SendMail() error {

	buf := bytes.NewBuffer(nil)
	buf.WriteString("From: " + m.smtpClient.From + "\r\n")
	buf.WriteString("To: " + m.To + "\r\n")
	buf.WriteString("MIME-Version: 1.0\r\n")
	buf.WriteString("Date: " + time.Now().Format(time.RFC822) + "\r\n")

	if len(m.Cc) > 0 {
		buf.WriteString("Cc: " + strings.Join(m.Cc, ",") + "\r\n")
	}

	buf.WriteString("Subject: " + m.Subject + "\r\n")

	if len(m.ReplyTo) > 0 {
		buf.WriteString("Reply-To: " + m.ReplyTo + "\r\n")
	}

	/* Генерация тела сообщения */
	const boundary = "f46d043c813270fc6b04c2d223da"

	if len(m.Attachments) > 0 {
		buf.WriteString("Content-Type: multipart/mixed; boundary=" + boundary + "\r\n\r\n")
		buf.WriteString("--" + boundary + "\r\n")
		buf.WriteString("Content-Transfer-Encoding: 8bit\r\n")
		buf.WriteString("Content-Type: " + m.BodyContentType + "; charset=utf-8\r\n\r\n")
		buf.WriteString(m.Body)
		buf.WriteString("\r\n")
	} else {
		buf.WriteString("Content-Type: " + m.BodyContentType + "; charset=utf-8\r\n\r\n")
		buf.WriteString(m.Body)
		buf.WriteString("\r\n")
	}

	if len(m.Attachments) > 0 {
		for _, attachment := range m.Attachments {
			buf.WriteString("\r\n\r\n--" + boundary + "\r\n")

			if attachment.Inline {
				buf.WriteString("Content-Type: message/rfc822\r\n")
				buf.WriteString("Content-Disposition: inline; filename=\"" + mime.QEncoding.Encode("utf-8", attachment.Filename) + "\"\r\n\r\n")

				buf.Write(attachment.Data)
			} else {
				buf.WriteString("Content-Disposition: attachment; filename=\"" + mime.QEncoding.Encode("utf-8", attachment.Filename) + "\"\r\n")
				buf.WriteString("Content-Transfer-Encoding: base64\r\n")
				buf.WriteString("Content-Type: application/octet-stream; name=\"" + mime.QEncoding.Encode("utf-8", attachment.Filename) + "\"\r\n\r\n")

				b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data)))
				base64.StdEncoding.Encode(b, attachment.Data)

				// write base64 content in lines of up to 76 chars
				for i, l := 0, len(b); i < l; i++ {
					buf.WriteByte(b[i])
					if (i+1)%76 == 0 {
						buf.WriteString("\r\n")
					}
				}
			}

			buf.WriteString("\r\n--" + boundary)
		}

		buf.WriteString("--")
	}

	/*=======================================*/
	servername := fmt.Sprintf("%s:%s", m.smtpClient.Host, m.smtpClient.Port)
	host, _, _ := net.SplitHostPort(servername)
	auth := smtp.PlainAuth("", m.smtpClient.User, m.smtpClient.Password, host)

	if !m.smtpClient.TLS {

		mail := gomail.NewMessage()
		mail.SetHeader("From", m.smtpClient.From)
		mail.SetHeader("To", m.To)
		mail.SetHeader("Subject", m.Subject)
		mail.SetBody(m.BodyContentType, m.Body)

		port, _ := strconv.Atoi(m.smtpClient.Port)
		d := gomail.NewDialer(m.smtpClient.Host, port, m.smtpClient.User, m.smtpClient.Password)
		d.TLSConfig = &tls.Config{InsecureSkipVerify: true}

		err := d.DialAndSend(mail)
		return err
	}

	/* Актуально для TLS */

	tlsconfig := &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         host,
	}

	dl := new(net.Dialer)
	dl.Timeout = 10 * time.Second
	conn, err := tls.DialWithDialer(dl, "tcp", servername, tlsconfig)

	//conn, err := tls.Dial("tcp", servername, tlsconfig)
	if err != nil {
		return err
	}

	c, err := smtp.NewClient(conn, host)
	defer func() {
		c.Quit()
	}()
	if err != nil {
		return err
	}

	// Auth
	if err = c.Auth(auth); err != nil {
		return err
	}

	// To && From
	emailFrom, err := mail.ParseAddress(m.smtpClient.From)
	if err != nil {
		return err
	}

	if err = c.Mail(emailFrom.Address); err != nil {
		return err
	}

	if err = c.Rcpt(m.To); err != nil {
		return err
	}

	// Data
	w, err := c.Data()
	if err != nil {
		return err
	}

	_, err = w.Write(buf.Bytes())
	if err != nil {
		return err
	}

	err = w.Close()
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/gosexy/gettext"
	"github.com/lxc/lxd"
	"github.com/lxc/lxd/internal/gnuflag"
)

type initCmd struct{}

func (c *initCmd) showByDefault() bool {
	return false
}

func (c *initCmd) usage() string {
	return gettext.Gettext(
		"lxc init <image> [<name>] [--ephemeral|-e] [--profile|-p <profile>...]\n" +
			"\n" +
			"Initializes a container using the specified image and name.\n" +
			"\n" +
			"Not specifying -p will result in the default profile.\n" +
			"Specifying \"-p\" with no argument will result in no profile.\n" +
			"\n" +
			"Example:\n" +
			"lxc init ubuntu u1\n")
}

type profileList []string

func (f *profileList) String() string {
	return fmt.Sprint(*f)
}

func (f *profileList) Set(value string) error {
	if value == "" {
		requested_empty_profiles = true
		return nil
	}
	if f == nil {
		*f = make(profileList, 1)
	} else {
		*f = append(*f, value)
	}
	return nil
}

var profArgs profileList
var requested_empty_profiles bool = false
var ephem bool = false

func is_ephem(s string) bool {
	switch s {
	case "-e":
		return true
	case "--ephemeral":
		return true
	}
	return false
}

func is_profile(s string) bool {
	switch s {
	case "-p":
		return true
	case "--profile":
		return true
	}
	return false
}

func massage_args() {
	l := len(os.Args)
	if l < 2 {
		return
	}
	if is_profile(os.Args[l-1]) {
		requested_empty_profiles = true
		os.Args = os.Args[0 : l-1]
		return
	}
	if l < 3 {
		return
	}
	/* catch "lxc init ubuntu -p -e */
	if is_ephem(os.Args[l-1]) && is_profile(os.Args[l-2]) {
		requested_empty_profiles = true
		newargs := os.Args[0 : l-2]
		newargs = append(newargs, os.Args[l-1])
		return
	}
}

func (c *initCmd) flags() {
	massage_args()
	gnuflag.Var(&profArgs, "profile", "Profile to apply to the new container")
	gnuflag.Var(&profArgs, "p", "Profile to apply to the new container")
	gnuflag.BoolVar(&ephem, "ephemeral", false, gettext.Gettext("Ephemeral container"))
	gnuflag.BoolVar(&ephem, "e", false, gettext.Gettext("Ephemeral container"))
}

func (c *initCmd) run(config *lxd.Config, args []string) error {
	if len(args) > 2 || len(args) < 1 {
		return errArgs
	}

	iremote, image := config.ParseRemoteAndContainer(args[0])

	var name string
	var remote string
	if len(args) == 2 {
		remote, name = config.ParseRemoteAndContainer(args[1])
	} else {
		name = ""
		remote = ""
	}

	d, err := lxd.NewClient(config, remote)
	if err != nil {
		return err
	}

	// TODO: implement the syntax for supporting other image types/remotes

	/*
	 * requested_empty_profiles means user requested empty
	 * !requested_empty_profiles but len(profArgs) == 0 means use profile default
	 */
	profiles := []string{}
	for _, p := range profArgs {
		profiles = append(profiles, p)
	}
	if !requested_empty_profiles && len(profiles) == 0 {
		_, err = d.Init(name, iremote, image, nil, ephem)
	} else {
		_, err = d.Init(name, iremote, image, &profiles, ephem)
	}

	return err
}
<commit_msg>Update Init callsites for not waiting<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/gosexy/gettext"
	"github.com/lxc/lxd"
	"github.com/lxc/lxd/internal/gnuflag"
)

type initCmd struct{}

func (c *initCmd) showByDefault() bool {
	return false
}

func (c *initCmd) usage() string {
	return gettext.Gettext(
		"lxc init <image> [<name>] [--ephemeral|-e] [--profile|-p <profile>...]\n" +
			"\n" +
			"Initializes a container using the specified image and name.\n" +
			"\n" +
			"Not specifying -p will result in the default profile.\n" +
			"Specifying \"-p\" with no argument will result in no profile.\n" +
			"\n" +
			"Example:\n" +
			"lxc init ubuntu u1\n")
}

type profileList []string

func (f *profileList) String() string {
	return fmt.Sprint(*f)
}

func (f *profileList) Set(value string) error {
	if value == "" {
		requested_empty_profiles = true
		return nil
	}
	if f == nil {
		*f = make(profileList, 1)
	} else {
		*f = append(*f, value)
	}
	return nil
}

var profArgs profileList
var requested_empty_profiles bool = false
var ephem bool = false

func is_ephem(s string) bool {
	switch s {
	case "-e":
		return true
	case "--ephemeral":
		return true
	}
	return false
}

func is_profile(s string) bool {
	switch s {
	case "-p":
		return true
	case "--profile":
		return true
	}
	return false
}

func massage_args() {
	l := len(os.Args)
	if l < 2 {
		return
	}
	if is_profile(os.Args[l-1]) {
		requested_empty_profiles = true
		os.Args = os.Args[0 : l-1]
		return
	}
	if l < 3 {
		return
	}
	/* catch "lxc init ubuntu -p -e */
	if is_ephem(os.Args[l-1]) && is_profile(os.Args[l-2]) {
		requested_empty_profiles = true
		newargs := os.Args[0 : l-2]
		newargs = append(newargs, os.Args[l-1])
		return
	}
}

func (c *initCmd) flags() {
	massage_args()
	gnuflag.Var(&profArgs, "profile", "Profile to apply to the new container")
	gnuflag.Var(&profArgs, "p", "Profile to apply to the new container")
	gnuflag.BoolVar(&ephem, "ephemeral", false, gettext.Gettext("Ephemeral container"))
	gnuflag.BoolVar(&ephem, "e", false, gettext.Gettext("Ephemeral container"))
}

func (c *initCmd) run(config *lxd.Config, args []string) error {
	if len(args) > 2 || len(args) < 1 {
		return errArgs
	}

	iremote, image := config.ParseRemoteAndContainer(args[0])

	var name string
	var remote string
	if len(args) == 2 {
		remote, name = config.ParseRemoteAndContainer(args[1])
	} else {
		name = ""
		remote = ""
	}

	d, err := lxd.NewClient(config, remote)
	if err != nil {
		return err
	}

	// TODO: implement the syntax for supporting other image types/remotes

	/*
	 * requested_empty_profiles means user requested empty
	 * !requested_empty_profiles but len(profArgs) == 0 means use profile default
	 */
	profiles := []string{}
	for _, p := range profArgs {
		profiles = append(profiles, p)
	}

	var resp *lxd.Response

	if !requested_empty_profiles && len(profiles) == 0 {
		resp, err = d.Init(name, iremote, image, nil, ephem)
	} else {
		resp, err = d.Init(name, iremote, image, &profiles, ephem)
	}

	if err != nil {
		return err
	}

	return d.WaitForSuccess(resp.Operation)
}
<|endoftext|>