<commit_before>package podtask

import (
	"container/ring"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	log "github.com/golang/glog"
	mesos "github.com/mesos/mesos-go/mesosproto"
	"github.com/mesosphere/kubernetes-mesos/pkg/scheduler/metrics"
)

const (
	//TODO(jdef) move this somewhere else
	PodPath = "/pods"

	// length of historical record of finished tasks
	defaultFinishedTasksSize = 1024
)

// state store for pod tasks
type Registry interface {
	// register the specified task with this registry, as long as the current error
	// condition is nil. if no errors occur then return a copy of the registered task.
	Register(*T, error) (*T, error)

	// unregister the specified task from this registry
	Unregister(*T)

	// update state for the registered task identified by task.ID, returning a copy of
	// the updated task, if any.
	Update(task *T) error

	// return the task registered for the specified task ID and its current state.
	// if there is no such task then StateUnknown is returned.
	Get(taskId string) (task *T, currentState StateType)

	// return the non-terminal task corresponding to the specified pod ID
	ForPod(podID string) (task *T, currentState StateType)

	// update the task status given the specified mesos task status update, returning a
	// copy of the updated task (if any) and its state.
	UpdateStatus(status *mesos.TaskStatus) (*T, StateType)

	// return a list of task ID's that match the given filter, or all task ID's if filter == nil.
	List(filter func(*T) bool) []*T
}

type inMemoryRegistry struct {
	rw            sync.RWMutex
	taskRegistry  map[string]*T
	tasksFinished *ring.Ring
	podToTask     map[string]string
}

func NewInMemoryRegistry() Registry {
	return &inMemoryRegistry{
		taskRegistry:  make(map[string]*T),
		tasksFinished: ring.New(defaultFinishedTasksSize),
		podToTask:     make(map[string]string),
	}
}

func (k *inMemoryRegistry) List(accepts func(t *T) bool) (tasks []*T) {
	k.rw.RLock()
	defer k.rw.RUnlock()
	for _, task := range k.taskRegistry {
		if accepts == nil || accepts(task) {
			tasks = append(tasks, task.Clone())
		}
	}
	return
}

func (k *inMemoryRegistry) ForPod(podID string) (task *T, currentState StateType) {
	k.rw.RLock()
	defer k.rw.RUnlock()
	tid, ok := k.podToTask[podID]
	if !ok {
		return nil, StateUnknown
	}
	t, state := k._get(tid)
	return t.Clone(), state
}

// registers a pod task unless the spec'd error is not nil
func (k *inMemoryRegistry) Register(task *T, err error) (*T, error) {
	if err == nil {
		k.rw.Lock()
		defer k.rw.Unlock()
		if _, found := k.podToTask[task.podKey]; found {
			return nil, fmt.Errorf("task already registered for pod key %q", task.podKey)
		}
		if _, found := k.taskRegistry[task.ID]; found {
			return nil, fmt.Errorf("task already registered for id %q", task.ID)
		}
		k.podToTask[task.podKey] = task.ID
		k.taskRegistry[task.ID] = task
	}
	return task.Clone(), err
}

// updates internal task state. updates are limited to Spec, Flags, and Offer for
// StatePending tasks, and are limited to Flag updates (additive only) for StateRunning tasks.
func (k *inMemoryRegistry) Update(task *T) error {
	if task == nil {
		return nil
	}
	k.rw.Lock()
	defer k.rw.Unlock()
	switch internal, state := k._get(task.ID); state {
	case StateUnknown:
		return fmt.Errorf("no such task: %v", task.ID)
	case StatePending:
		internal.Offer = task.Offer
		internal.Spec = task.Spec
		(&task.Spec).copyTo(&internal.Spec)
		internal.Flags = map[FlagType]struct{}{}
		fallthrough
	case StateRunning:
		for k, v := range task.Flags {
			internal.Flags[k] = v
		}
		return nil
	default:
		return fmt.Errorf("may not update task %v in state %v", task.ID, state)
	}
}

func (k *inMemoryRegistry) Unregister(task *T) {
	k.rw.Lock()
	defer k.rw.Unlock()
	delete(k.podToTask, task.podKey)
	delete(k.taskRegistry, task.ID)
}

func (k *inMemoryRegistry) Get(taskId string) (*T, StateType) {
	k.rw.RLock()
	defer k.rw.RUnlock()
	t, state := k._get(taskId)
	return t.Clone(), state
}

// assume that the caller has already locked around access to task state.
// the caller is also responsible for cloning the task object before it leaves
// the context of this registry.
func (k *inMemoryRegistry) _get(taskId string) (*T, StateType) {
	if task, found := k.taskRegistry[taskId]; found {
		return task, task.State
	}
	return nil, StateUnknown
}

func (k *inMemoryRegistry) UpdateStatus(status *mesos.TaskStatus) (*T, StateType) {
	taskId := status.GetTaskId().GetValue()

	k.rw.Lock()
	defer k.rw.Unlock()
	task, state := k._get(taskId)

	switch status.GetState() {
	case mesos.TaskState_TASK_STAGING:
		k.handleTaskStaging(task, state, status)
	case mesos.TaskState_TASK_STARTING:
		k.handleTaskStarting(task, state, status)
	case mesos.TaskState_TASK_RUNNING:
		k.handleTaskRunning(task, state, status)
	case mesos.TaskState_TASK_FINISHED:
		k.handleTaskFinished(task, state, status)
	case mesos.TaskState_TASK_FAILED:
		k.handleTaskFailed(task, state, status)
	case mesos.TaskState_TASK_KILLED:
		k.handleTaskKilled(task, state, status)
	case mesos.TaskState_TASK_LOST:
		k.handleTaskLost(task, state, status)
	default:
		log.Warning("unhandled task status update: %+v", status)
	}
	return task.Clone(), state
}

func (k *inMemoryRegistry) handleTaskStaging(task *T, state StateType, status *mesos.TaskStatus) {
	if status.GetSource() != mesos.TaskStatus_SOURCE_MASTER {
		log.Errorf("received STAGING for task %v with unexpected source: %v",
			status.GetTaskId().GetValue(), status.GetSource())
	}
}

func (k *inMemoryRegistry) handleTaskStarting(task *T, state StateType, status *mesos.TaskStatus) {
	// we expect to receive this when a launched task is finally "bound"
	// via the API server. however, there's nothing specific for us to do here.
	switch state {
	case StatePending:
		task.UpdatedTime = time.Now()
		if !task.Has(Bound) {
			task.Set(Bound)
			task.bindTime = task.UpdatedTime
			timeToBind := task.bindTime.Sub(task.launchTime)
			metrics.BindLatency.Observe(metrics.InMicroseconds(timeToBind))
		}
	default:
		log.Warningf("Ignore status TASK_STARTING because the the task is not pending")
	}
}

func (k *inMemoryRegistry) handleTaskRunning(task *T, state StateType, status *mesos.TaskStatus) {
	switch state {
	case StatePending:
		task.UpdatedTime = time.Now()
		log.Infof("Received running status for pending task: %+v", status)
		fillRunningPodInfo(task, status)
		task.State = StateRunning
	case StateRunning:
		task.UpdatedTime = time.Now()
		log.V(2).Info("Ignore status TASK_RUNNING because the the task is already running")
	case StateFinished:
		log.Warningf("Ignore status TASK_RUNNING because the the task is already finished")
	default:
		log.Warningf("Ignore status TASK_RUNNING (%+v) because the the task is discarded", status.GetTaskId())
	}
}

func ParsePodStatusResult(taskStatus *mesos.TaskStatus) (result api.PodStatusResult, err error) {
	if taskStatus.Data != nil {
		err = json.Unmarshal(taskStatus.Data, &result)
	} else {
		err = fmt.Errorf("missing TaskStatus.Data")
	}
	return
}

func fillRunningPodInfo(task *T, taskStatus *mesos.TaskStatus) {
	if taskStatus.GetReason() == mesos.TaskStatus_REASON_RECONCILIATION && taskStatus.GetSource() == mesos.TaskStatus_SOURCE_MASTER {
		// there is no data..
		return
	}
	//TODO(jdef) determine the usefullness of this information (if any)
	if result, err := ParsePodStatusResult(taskStatus); err != nil {
		log.Errorf("invalid TaskStatus.Data for task '%v': %v", task.ID, err)
	} else {
		task.podStatus = result.Status
		log.Infof("received pod status for task %v: %+v", task.ID, result.Status)
	}
}

func (k *inMemoryRegistry) handleTaskFinished(task *T, state StateType, status *mesos.TaskStatus) {
	switch state {
	case StatePending:
		panic("Pending task finished, this couldn't happen")
	case StateRunning:
		log.V(2).Infof("received finished status for running task: %+v", status)
		delete(k.podToTask, task.podKey)
		task.State = StateFinished
		task.UpdatedTime = time.Now()
		k.tasksFinished = k.recordFinishedTask(task.ID)
	case StateFinished:
		log.Warningf("Ignore status TASK_FINISHED because the the task is already finished")
	default:
		log.Warningf("Ignore status TASK_FINISHED because the the task is not running")
	}
}

// record that a task has finished.
// older record are expunged one at a time once the historical ring buffer is saturated.
// assumes caller is holding state lock.
func (k *inMemoryRegistry) recordFinishedTask(taskId string) *ring.Ring {
	slot := k.tasksFinished.Next()
	if slot.Value != nil {
		// garbage collect older finished task from the registry
		gctaskId := slot.Value.(string)
		if gctask, found := k.taskRegistry[gctaskId]; found && gctask.State == StateFinished {
			delete(k.taskRegistry, gctaskId)
		}
	}
	slot.Value = taskId
	return slot
}

func (k *inMemoryRegistry) handleTaskFailed(task *T, state StateType, status *mesos.TaskStatus) {
	switch state {
	case StatePending:
		delete(k.taskRegistry, task.ID)
		delete(k.podToTask, task.podKey)
	case StateRunning:
		delete(k.taskRegistry, task.ID)
		delete(k.podToTask, task.podKey)
	}
}

func (k *inMemoryRegistry) handleTaskKilled(task *T, state StateType, status *mesos.TaskStatus) {
	defer func() {
		msg := fmt.Sprintf("task killed: %+v, task %+v", status, task)
		if task != nil && task.Has(Deleted) {
			// we were expecting this, nothing out of the ordinary
			log.V(2).Infoln(msg)
		} else {
			log.Errorln(msg)
		}
	}()
	switch state {
	case StatePending, StateRunning:
		delete(k.taskRegistry, task.ID)
		delete(k.podToTask, task.podKey)
	}
}

func (k *inMemoryRegistry) handleTaskLost(task *T, state StateType, status *mesos.TaskStatus) {
	switch state {
	case StateRunning, StatePending:
		delete(k.taskRegistry, task.ID)
		delete(k.podToTask, task.podKey)
	}
}
<commit_msg>clean up task registry logging<commit_after>package podtask

import (
	"container/ring"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	log "github.com/golang/glog"
	mesos "github.com/mesos/mesos-go/mesosproto"
	"github.com/mesosphere/kubernetes-mesos/pkg/scheduler/metrics"
)

const (
	//TODO(jdef) move this somewhere else
	PodPath = "/pods"

	// length of historical record of finished tasks
	defaultFinishedTasksSize = 1024
)

// state store for pod tasks
type Registry interface {
	// register the specified task with this registry, as long as the current error
	// condition is nil. if no errors occur then return a copy of the registered task.
	Register(*T, error) (*T, error)

	// unregister the specified task from this registry
	Unregister(*T)

	// update state for the registered task identified by task.ID, returning a copy of
	// the updated task, if any.
	Update(task *T) error

	// return the task registered for the specified task ID and its current state.
	// if there is no such task then StateUnknown is returned.
	Get(taskId string) (task *T, currentState StateType)

	// return the non-terminal task corresponding to the specified pod ID
	ForPod(podID string) (task *T, currentState StateType)

	// update the task status given the specified mesos task status update, returning a
	// copy of the updated task (if any) and its state.
	UpdateStatus(status *mesos.TaskStatus) (*T, StateType)

	// return a list of task ID's that match the given filter, or all task ID's if filter == nil.
	List(filter func(*T) bool) []*T
}

type inMemoryRegistry struct {
	rw            sync.RWMutex
	taskRegistry  map[string]*T
	tasksFinished *ring.Ring
	podToTask     map[string]string
}

func NewInMemoryRegistry() Registry {
	return &inMemoryRegistry{
		taskRegistry:  make(map[string]*T),
		tasksFinished: ring.New(defaultFinishedTasksSize),
		podToTask:     make(map[string]string),
	}
}

func (k *inMemoryRegistry) List(accepts func(t *T) bool) (tasks []*T) {
	k.rw.RLock()
	defer k.rw.RUnlock()
	for _, task := range k.taskRegistry {
		if accepts == nil || accepts(task) {
			tasks = append(tasks, task.Clone())
		}
	}
	return
}

func (k *inMemoryRegistry) ForPod(podID string) (task *T, currentState StateType) {
	k.rw.RLock()
	defer k.rw.RUnlock()
	tid, ok := k.podToTask[podID]
	if !ok {
		return nil, StateUnknown
	}
	t, state := k._get(tid)
	return t.Clone(), state
}

// registers a pod task unless the spec'd error is not nil
func (k *inMemoryRegistry) Register(task *T, err error) (*T, error) {
	if err == nil {
		k.rw.Lock()
		defer k.rw.Unlock()
		if _, found := k.podToTask[task.podKey]; found {
			return nil, fmt.Errorf("task already registered for pod key %q", task.podKey)
		}
		if _, found := k.taskRegistry[task.ID]; found {
			return nil, fmt.Errorf("task already registered for id %q", task.ID)
		}
		k.podToTask[task.podKey] = task.ID
		k.taskRegistry[task.ID] = task
	}
	return task.Clone(), err
}

// updates internal task state. updates are limited to Spec, Flags, and Offer for
// StatePending tasks, and are limited to Flag updates (additive only) for StateRunning tasks.
func (k *inMemoryRegistry) Update(task *T) error {
	if task == nil {
		return nil
	}
	k.rw.Lock()
	defer k.rw.Unlock()
	switch internal, state := k._get(task.ID); state {
	case StateUnknown:
		return fmt.Errorf("no such task: %v", task.ID)
	case StatePending:
		internal.Offer = task.Offer
		internal.Spec = task.Spec
		(&task.Spec).copyTo(&internal.Spec)
		internal.Flags = map[FlagType]struct{}{}
		fallthrough
	case StateRunning:
		for k, v := range task.Flags {
			internal.Flags[k] = v
		}
		return nil
	default:
		return fmt.Errorf("may not update task %v in state %v", task.ID, state)
	}
}

func (k *inMemoryRegistry) Unregister(task *T) {
	k.rw.Lock()
	defer k.rw.Unlock()
	delete(k.podToTask, task.podKey)
	delete(k.taskRegistry, task.ID)
}

func (k *inMemoryRegistry) Get(taskId string) (*T, StateType) {
	k.rw.RLock()
	defer k.rw.RUnlock()
	t, state := k._get(taskId)
	return t.Clone(), state
}

// assume that the caller has already locked around access to task state.
// the caller is also responsible for cloning the task object before it leaves
// the context of this registry.
func (k *inMemoryRegistry) _get(taskId string) (*T, StateType) {
	if task, found := k.taskRegistry[taskId]; found {
		return task, task.State
	}
	return nil, StateUnknown
}

func (k *inMemoryRegistry) UpdateStatus(status *mesos.TaskStatus) (*T, StateType) {
	taskId := status.GetTaskId().GetValue()

	k.rw.Lock()
	defer k.rw.Unlock()
	task, state := k._get(taskId)

	switch status.GetState() {
	case mesos.TaskState_TASK_STAGING:
		k.handleTaskStaging(task, state, status)
	case mesos.TaskState_TASK_STARTING:
		k.handleTaskStarting(task, state, status)
	case mesos.TaskState_TASK_RUNNING:
		k.handleTaskRunning(task, state, status)
	case mesos.TaskState_TASK_FINISHED:
		k.handleTaskFinished(task, state, status)
	case mesos.TaskState_TASK_FAILED:
		k.handleTaskFailed(task, state, status)
	case mesos.TaskState_TASK_KILLED:
		k.handleTaskKilled(task, state, status)
	case mesos.TaskState_TASK_LOST:
		k.handleTaskLost(task, state, status)
	default:
		log.Warningf("unhandled status update for task: %v", taskId)
	}
	return task.Clone(), state
}

func (k *inMemoryRegistry) handleTaskStaging(task *T, state StateType, status *mesos.TaskStatus) {
	if status.GetSource() != mesos.TaskStatus_SOURCE_MASTER {
		log.Errorf("received STAGING for task %v with unexpected source: %v",
			status.GetTaskId().GetValue(), status.GetSource())
	}
}

func (k *inMemoryRegistry) handleTaskStarting(task *T, state StateType, status *mesos.TaskStatus) {
	// we expect to receive this when a launched task is finally "bound"
	// via the API server. however, there's nothing specific for us to do here.
	switch state {
	case StatePending:
		task.UpdatedTime = time.Now()
		if !task.Has(Bound) {
			task.Set(Bound)
			task.bindTime = task.UpdatedTime
			timeToBind := task.bindTime.Sub(task.launchTime)
			metrics.BindLatency.Observe(metrics.InMicroseconds(timeToBind))
		}
	default:
		taskId := status.GetTaskId().GetValue()
		log.Warningf("Ignore status TASK_STARTING because the task %v is not pending", taskId)
	}
}

func (k *inMemoryRegistry) handleTaskRunning(task *T, state StateType, status *mesos.TaskStatus) {
	taskId := status.GetTaskId().GetValue()
	switch state {
	case StatePending:
		task.UpdatedTime = time.Now()
		log.Infof("Received running status for pending task: %v", taskId)
		fillRunningPodInfo(task, status)
		task.State = StateRunning
	case StateRunning:
		task.UpdatedTime = time.Now()
		log.V(2).Infof("Ignore status TASK_RUNNING because the task %v is already running", taskId)
	case StateFinished:
		log.Warningf("Ignore status TASK_RUNNING because the task %v is already finished", taskId)
	default:
		log.Warningf("Ignore status TASK_RUNNING because the task %v is discarded", taskId)
	}
}

func ParsePodStatusResult(taskStatus *mesos.TaskStatus) (result api.PodStatusResult, err error) {
	if taskStatus.Data != nil {
		err = json.Unmarshal(taskStatus.Data, &result)
	} else {
		err = fmt.Errorf("missing TaskStatus.Data")
	}
	return
}

func fillRunningPodInfo(task *T, taskStatus *mesos.TaskStatus) {
	if taskStatus.GetReason() == mesos.TaskStatus_REASON_RECONCILIATION && taskStatus.GetSource() == mesos.TaskStatus_SOURCE_MASTER {
		// there is no data..
		return
	}
	//TODO(jdef) determine the usefullness of this information (if any)
	if result, err := ParsePodStatusResult(taskStatus); err != nil {
		log.Errorf("invalid TaskStatus.Data for task '%v': %v", task.ID, err)
	} else {
		task.podStatus = result.Status
		log.Infof("received pod status for task %v: %+v", task.ID, result.Status)
	}
}

func (k *inMemoryRegistry) handleTaskFinished(task *T, state StateType, status *mesos.TaskStatus) {
	taskId := status.GetTaskId().GetValue()
	switch state {
	case StatePending:
		panic(fmt.Sprintf("Pending task %v finished, this couldn't happen", taskId))
	case StateRunning:
		log.V(2).Infof("received finished status for running task: %v", taskId)
		delete(k.podToTask, task.podKey)
		task.State = StateFinished
		task.UpdatedTime = time.Now()
		k.tasksFinished = k.recordFinishedTask(task.ID)
	case StateFinished:
		log.Warningf("Ignore status TASK_FINISHED because the task %v is already finished", taskId)
	default:
		log.Warningf("Ignore status TASK_FINISHED because the task %v is not running", taskId)
	}
}

// record that a task has finished.
// older record are expunged one at a time once the historical ring buffer is saturated.
// assumes caller is holding state lock.
func (k *inMemoryRegistry) recordFinishedTask(taskId string) *ring.Ring {
	slot := k.tasksFinished.Next()
	if slot.Value != nil {
		// garbage collect older finished task from the registry
		gctaskId := slot.Value.(string)
		if gctask, found := k.taskRegistry[gctaskId]; found && gctask.State == StateFinished {
			delete(k.taskRegistry, gctaskId)
		}
	}
	slot.Value = taskId
	return slot
}

func (k *inMemoryRegistry) handleTaskFailed(task *T, state StateType, status *mesos.TaskStatus) {
	switch state {
	case StatePending:
		delete(k.taskRegistry, task.ID)
		delete(k.podToTask, task.podKey)
	case StateRunning:
		delete(k.taskRegistry, task.ID)
		delete(k.podToTask, task.podKey)
	}
}

func (k *inMemoryRegistry) handleTaskKilled(task *T, state StateType, status *mesos.TaskStatus) {
	defer func() {
		msg := fmt.Sprintf("task killed: %+v, task %+v", status, task)
		if task != nil && task.Has(Deleted) {
			// we were expecting this, nothing out of the ordinary
			log.V(2).Infoln(msg)
		} else {
			log.Errorln(msg)
		}
	}()
	switch state {
	case StatePending, StateRunning:
		delete(k.taskRegistry, task.ID)
		delete(k.podToTask, task.podKey)
	}
}

func (k *inMemoryRegistry) handleTaskLost(task *T, state StateType, status *mesos.TaskStatus) {
	switch state {
	case StateRunning, StatePending:
		delete(k.taskRegistry, task.ID)
		delete(k.podToTask, task.podKey)
	}
}
<|endoftext|>


<commit_before>package alerting

import (
	"errors"
	"fmt"

	"golang.org/x/sync/errgroup"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/components/imguploader"
	"github.com/grafana/grafana/pkg/components/renderer"
	"github.com/grafana/grafana/pkg/log"
	m "github.com/grafana/grafana/pkg/models"
)

type RootNotifier struct {
	log log.Logger
}

func NewRootNotifier() *RootNotifier {
	return &RootNotifier{
		log: log.New("alerting.notifier"),
	}
}

func (n *RootNotifier) GetType() string {
	return "root"
}

func (n *RootNotifier) NeedsImage() bool {
	return false
}

func (n *RootNotifier) PassesFilter(rule *Rule) bool {
	return false
}

func (n *RootNotifier) GetNotifierId() int64 {
	return 0
}

func (n *RootNotifier) GetIsDefault() bool {
	return false
}

func (n *RootNotifier) Notify(context *EvalContext) error {
	notifiers, err := n.getNotifiers(context.Rule.OrgId, context.Rule.Notifications, context)
	if err != nil {
		return err
	}

	n.log.Info("Sending notifications for", "ruleId", context.Rule.Id, "Amount to send", len(notifiers))

	if len(notifiers) == 0 {
		return nil
	}

	err = n.uploadImage(context)
	if err != nil {
		n.log.Error("Failed to upload alert panel image", "error", err)
		return err
	}

	return n.sendNotifications(context, notifiers)
}

func (n *RootNotifier) sendNotifications(context *EvalContext, notifiers []Notifier) error {
	g, _ := errgroup.WithContext(context.Ctx)

	for _, notifier := range notifiers {
		not := notifier //avoid updating scope variable in go routine
		n.log.Info("Sending notification", "type", not.GetType(), "id", not.GetNotifierId(), "isDefault", not.GetIsDefault())
		g.Go(func() error { return not.Notify(context) })
	}

	return g.Wait()
}

func (n *RootNotifier) uploadImage(context *EvalContext) (err error) {
	uploader, err := imguploader.NewImageUploader()
	if err != nil {
		return err
	}

	renderOpts := &renderer.RenderOpts{
		Width:   "800",
		Height:  "400",
		Timeout: "30",
		OrgId:   context.Rule.OrgId,
	}

	if slug, err := context.GetDashboardSlug(); err != nil {
		return err
	} else {
		renderOpts.Path = fmt.Sprintf("dashboard-solo/db/%s?&panelId=%d", slug, context.Rule.PanelId)
	}

	if imagePath, err := renderer.RenderToPng(renderOpts); err != nil {
		return err
	} else {
		context.ImageOnDiskPath = imagePath
	}

	context.ImagePublicUrl, err = uploader.Upload(context.ImageOnDiskPath)
	if err != nil {
		return err
	}

	n.log.Info("uploaded", "url", context.ImagePublicUrl)
	return nil
}

func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64, context *EvalContext) ([]Notifier, error) {
	query := &m.GetAlertNotificationsToSendQuery{OrgId: orgId, Ids: notificationIds}

	if err := bus.Dispatch(query); err != nil {
		return nil, err
	}

	var result []Notifier
	for _, notification := range query.Result {
		if not, err := n.createNotifierFor(notification); err != nil {
			return nil, err
		} else {
			if shouldUseNotification(not, context) {
				result = append(result, not)
			}
		}
	}

	return result, nil
}

func (n *RootNotifier) createNotifierFor(model *m.AlertNotification) (Notifier, error) {
	factory, found := notifierFactories[model.Type]
	if !found {
		return nil, errors.New("Unsupported notification type")
	}

	return factory(model)
}

func shouldUseNotification(notifier Notifier, context *EvalContext) bool {
	if !context.Firing {
		return true
	}

	if context.Error != nil {
		return true
	}

	return notifier.PassesFilter(context.Rule)
}

type NotifierFactory func(notification *m.AlertNotification) (Notifier, error)

var notifierFactories map[string]NotifierFactory = make(map[string]NotifierFactory)

func RegisterNotifier(typeName string, factory NotifierFactory) {
	notifierFactories[typeName] = factory
}
<commit_msg>chore(notification): improve log message<commit_after>package alerting

import (
	"errors"
	"fmt"

	"golang.org/x/sync/errgroup"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/components/imguploader"
	"github.com/grafana/grafana/pkg/components/renderer"
	"github.com/grafana/grafana/pkg/log"
	m "github.com/grafana/grafana/pkg/models"
)

type RootNotifier struct {
	log log.Logger
}

func NewRootNotifier() *RootNotifier {
	return &RootNotifier{
		log: log.New("alerting.notifier"),
	}
}

func (n *RootNotifier) GetType() string {
	return "root"
}

func (n *RootNotifier) NeedsImage() bool {
	return false
}

func (n *RootNotifier) PassesFilter(rule *Rule) bool {
	return false
}

func (n *RootNotifier) GetNotifierId() int64 {
	return 0
}

func (n *RootNotifier) GetIsDefault() bool {
	return false
}

func (n *RootNotifier) Notify(context *EvalContext) error {
	notifiers, err := n.getNotifiers(context.Rule.OrgId, context.Rule.Notifications, context)
	if err != nil {
		return err
	}

	n.log.Info("Sending notifications for", "ruleId", context.Rule.Id, "sent count", len(notifiers))

	if len(notifiers) == 0 {
		return nil
	}

	err = n.uploadImage(context)
	if err != nil {
		n.log.Error("Failed to upload alert panel image", "error", err)
		return err
	}

	return n.sendNotifications(context, notifiers)
}

func (n *RootNotifier) sendNotifications(context *EvalContext, notifiers []Notifier) error {
	g, _ := errgroup.WithContext(context.Ctx)

	for _, notifier := range notifiers {
		not := notifier //avoid updating scope variable in go routine
		n.log.Info("Sending notification", "type", not.GetType(), "id", not.GetNotifierId(), "isDefault", not.GetIsDefault())
		g.Go(func() error { return not.Notify(context) })
	}

	return g.Wait()
}

func (n *RootNotifier) uploadImage(context *EvalContext) (err error) {
	uploader, err := imguploader.NewImageUploader()
	if err != nil {
		return err
	}

	renderOpts := &renderer.RenderOpts{
		Width:   "800",
		Height:  "400",
		Timeout: "30",
		OrgId:   context.Rule.OrgId,
	}

	if slug, err := context.GetDashboardSlug(); err != nil {
		return err
	} else {
		renderOpts.Path = fmt.Sprintf("dashboard-solo/db/%s?&panelId=%d", slug, context.Rule.PanelId)
	}

	if imagePath, err := renderer.RenderToPng(renderOpts); err != nil {
		return err
	} else {
		context.ImageOnDiskPath = imagePath
	}

	context.ImagePublicUrl, err = uploader.Upload(context.ImageOnDiskPath)
	if err != nil {
		return err
	}

	n.log.Info("uploaded", "url", context.ImagePublicUrl)
	return nil
}

func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64, context *EvalContext) ([]Notifier, error) {
	query := &m.GetAlertNotificationsToSendQuery{OrgId: orgId, Ids: notificationIds}

	if err := bus.Dispatch(query); err != nil {
		return nil, err
	}

	var result []Notifier
	for _, notification := range query.Result {
		if not, err := n.createNotifierFor(notification); err != nil {
			return nil, err
		} else {
			if shouldUseNotification(not, context) {
				result = append(result, not)
			}
		}
	}

	return result, nil
}

func (n *RootNotifier) createNotifierFor(model *m.AlertNotification) (Notifier, error) {
	factory, found := notifierFactories[model.Type]
	if !found {
		return nil, errors.New("Unsupported notification type")
	}

	return factory(model)
}

func shouldUseNotification(notifier Notifier, context *EvalContext) bool {
	if !context.Firing {
		return true
	}

	if context.Error != nil {
		return true
	}

	return notifier.PassesFilter(context.Rule)
}

type NotifierFactory func(notification *m.AlertNotification) (Notifier, error)

var notifierFactories map[string]NotifierFactory = make(map[string]NotifierFactory)

func RegisterNotifier(typeName string, factory NotifierFactory) {
	notifierFactories[typeName] = factory
}
<|endoftext|>


<commit_before>// Description: ginweb is light weight encapsulation of gin framework
// Author: ZHU HAIHUA
// Since: 2016-02-28 14:57
package ginweb

import (
	"github.com/gin-gonic/gin"
	. "github.com/kimiazhu/ginweb/midware"
	"github.com/kimiazhu/ginweb/server"
	"github.com/kimiazhu/ginweb/conf"
	"github.com/kimiazhu/log4go"
	"sync"
	"time"
)

const VERSION = "0.0.1"

// a component is different from middleware,
// it will be initialize just before the
// application running, then you can use it in
// the entire life-cycle of the application
type component struct {
	name string
	config interface{}
	initialize func(config interface{}) (error)
	delay, interval time.Duration
}

var initCompOnce sync.Once
var components []component

func New() *gin.Engine {
	gin.SetMode(conf.Conf.SERVER.RUNMODE)
	g := gin.New()
	g.Use(Recovery())
	g.Use(AccessLog())
	return g
}

func RegisterComponent(name string, config interface{}, initialize func(config interface{}) (error))  {
	components = append(components, component{name, config, initialize, 0, 0})
}

func RegisterComponentScheduler(name string, initialize func(config interface{}) (error), config interface{}, delay, interval time.Duration) {
	components = append(components, component{name, config, initialize, delay, interval})
}

func Run(port string, engin *gin.Engine) {
	initCompOnce.Do(initialize)
	server.Start(":"+port, engin)
}

// initialize used to init all components before the app start
func initialize() {
	for _, c := range components {
		e := c.initialize(c.config)
		if e != nil {
			log4go.Error("initialize component [%s] error! %v", c.name, e)
		} else {
			log4go.Debug("initialize component [%s] success", c.name)
		}
	}
}<commit_msg>comments<commit_after>// Description: ginweb is light weight encapsulation of gin framework
// Author: ZHU HAIHUA
// Since: 2016-02-28 14:57
package ginweb

import (
	"github.com/gin-gonic/gin"
	. "github.com/kimiazhu/ginweb/midware"
	"github.com/kimiazhu/ginweb/server"
	"github.com/kimiazhu/ginweb/conf"
	"github.com/kimiazhu/log4go"
	"sync"
	"time"
)

const VERSION = "0.0.1"

// a component is different from middleware,
// it will be initialize just before the
// application running, then you can use it in
// the entire life-cycle of the application
type component struct {
	name string
	config interface{}
	initialize func(config interface{}) (error)
	delay, interval time.Duration
}

var initCompOnce sync.Once
var components []component

func New() *gin.Engine {
	gin.SetMode(conf.Conf.SERVER.RUNMODE)
	g := gin.New()
	g.Use(Recovery())
	g.Use(AccessLog())
	return g
}

func RegisterComponent(name string, config interface{}, initialize func(config interface{}) (error))  {
	components = append(components, component{name, config, initialize, 0, 0})
}

// TODO: DO NOT USE!!
func RegisterComponentScheduler(name string, initialize func(config interface{}) (error), config interface{}, delay, interval time.Duration) {
	components = append(components, component{name, config, initialize, delay, interval})
}

func Run(port string, engin *gin.Engine) {
	initCompOnce.Do(initialize)
	server.Start(":"+port, engin)
}

// initialize used to init all components before the app start
func initialize() {
	for _, c := range components {
		e := c.initialize(c.config)
		if e != nil {
			log4go.Error("initialize component [%s] error! %v", c.name, e)
		} else {
			log4go.Debug("initialize component [%s] success", c.name)
		}
	}
}<|endoftext|>


<commit_before>// This app is intented to be go-port of the defunckt's gist library in Ruby
// Currently, uploading single and multiple files are available.
// You can also create secret gists, and both anonymous and user gists.
//
// Author: Viyat Bhalodia
package gister

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

//Defines the app version
const VERSION = "v0.1.0"

//#TODO: A list of clipboard commands with copy and paste support.
//This is intended for adding the gist URLs directly to the user clipboard,
//so that manual copying is not needed.
const (
	xclip   = "xclip -o"
	xsel    = "xsel -o"
	pbcopy  = "pbpaste"
	putclip = "getclip"
)

// Defines different constants used
// GIT_IO_URL is the Github's URL shortner
// API v3 is the current version of GitHub API
const (
	GITHUB_API_URL = "https://api.github.com/"
	GIT_IO_URL     = "http://git.io"
	GHE_BASE_PATH  = "/api/v3"
)

//User agent defines a custom agent (required by GitHub)
//`token` stores the GITHUB_TOKEN from the env variables
var (
	USER_AGENT = "gist/#" + VERSION //Github requires this, else rejects API request
	token      = os.Getenv("GITHUB_TOKEN")
)

// Variables used in `Gist` struct
var (
	publicFlag  bool
	description string
	anonymous   bool
	responseObj map[string]interface{}
)

//The top-level struct for a gist file
type GistFile struct {
	Content string `json:"content"`
}

//The required structure for POST data for API purposes
type Gist struct {
	Description string              `json:"description"`
	publicFile  bool                `json:"public"`
	GistFile    map[string]GistFile `json:"files"`
}

//This function loads the GITHUB_TOKEN from a '$HOME/.gist' file
//from the user's home directory.
func loadTokenFromFile() (token string) {
	//get the tokenfile
	file := os.Getenv("$HOME") + "/.gist"
	github_token, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatal(err)
	}
	return string(github_token)
}

//Defines basic usage when program is run with the help flag
func usage() {
	fmt.Fprintf(os.Stderr, "usage: gist [-p] [-d] [-u] example.go\n")
	flag.PrintDefaults()
	os.Exit(2)
}

//The main function parses the CLI args. It also checks the files, and
//loads them into an array.
//Then the files are separated into GistFile structs and collectively
//the files are saved in `files` field in the Gist struct.
//A request is then made to the GitHub api - it depends on whether it is
//anonymous gist or not.
//The response recieved is parsed and the Gist URL is printed to STDOUT.
func main() {
	flag.BoolVar(&publicFlag, "p", true, "Set to false for private gist.")
	flag.BoolVar(&anonymous, "u", true, "Set false if gist should be not anonymous")
	flag.StringVar(&description, "d", "This is a gist", "Description for gist.")
	flag.Usage = usage
	flag.Parse()

	files_list := flag.Args()
	if len(files_list) == 0 {
		log.Fatal("Error: No files specified.")
	}

	//fmt.Println(files_list)
	//fmt.Println(token)

	files := map[string]GistFile{}

	for _, filename := range files_list {
		fmt.Println("Checking file:", filename)
		content, err := ioutil.ReadFile(filename)
		if err != nil {
			log.Fatal("File Error: ", err)
		}
		files[filename] = GistFile{string(content)}
	}

	if description == "" {
		description = strings.Join(files_list, ", ")
	}

	//create a gist from the files array
	gist := Gist{
		description,
		publicFlag,
		files,
	}

	pfile, err := json.Marshal(gist)
	if err != nil {
		log.Fatal("Cannot marshal json: ", err)
	}

	//Check if JSON marshalling succeeds
	fmt.Println("OK")

	b := bytes.NewBuffer(pfile)
	fmt.Println("Uploading...")

	//Separate uploading methods depending on whether the gist is anonymous or not
	if anonymous == true {
		response, err := http.Post("https://api.github.com/gists", "application/json", b)
		if err != nil {
			log.Fatal("HTTP error: ", err)
		}

		err = json.NewDecoder(response.Body).Decode(&responseObj)
		if err != nil {
			log.Fatal("Response JSON error: ", err)
		}

		fmt.Println("--- Gist URL ---")
		fmt.Println(responseObj["html_url"])
	} else {
		req, err := http.NewRequest("POST", "https://api.github.com/gists", b)
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Accept", "application/json")
		req.SetBasicAuth(token, "x-oauth-basic")

		client := http.Client{}
		response, err := client.Do(req)
		if err != nil {
			log.Fatal("HTTP error: ", err)
		}
		err = json.NewDecoder(response.Body).Decode(&responseObj)
		if err != nil {
			log.Fatal("Response JSON error: ", err)
		}

		fmt.Println("--- Gist URL ---")
		fmt.Println(responseObj["html_url"])
	}
}
<commit_msg>still learning the go packaging rules - has to package main to be used as standalone<commit_after>// This app is intented to be go-port of the defunckt's gist library in Ruby
// Currently, uploading single and multiple files are available.
// You can also create secret gists, and both anonymous and user gists.
//
// Author: Viyat Bhalodia
package main

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

//Defines the app version
const VERSION = "v0.1.0"

//#TODO: A list of clipboard commands with copy and paste support.
//This is intended for adding the gist URLs directly to the user clipboard,
//so that manual copying is not needed.
const (
	xclip   = "xclip -o"
	xsel    = "xsel -o"
	pbcopy  = "pbpaste"
	putclip = "getclip"
)

// Defines different constants used
// GIT_IO_URL is the Github's URL shortner
// API v3 is the current version of GitHub API
const (
	GITHUB_API_URL = "https://api.github.com/"
	GIT_IO_URL     = "http://git.io"
	GHE_BASE_PATH  = "/api/v3"
)

//User agent defines a custom agent (required by GitHub)
//`token` stores the GITHUB_TOKEN from the env variables
var (
	USER_AGENT = "gist/#" + VERSION //Github requires this, else rejects API request
	token      = os.Getenv("GITHUB_TOKEN")
)

// Variables used in `Gist` struct
var (
	publicFlag  bool
	description string
	anonymous   bool
	responseObj map[string]interface{}
)

//The top-level struct for a gist file
type GistFile struct {
	Content string `json:"content"`
}

//The required structure for POST data for API purposes
type Gist struct {
	Description string              `json:"description"`
	publicFile  bool                `json:"public"`
	GistFile    map[string]GistFile `json:"files"`
}

//This function loads the GITHUB_TOKEN from a '$HOME/.gist' file
//from the user's home directory.
func loadTokenFromFile() (token string) {
	//get the tokenfile
	file := os.Getenv("$HOME") + "/.gist"
	github_token, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatal(err)
	}
	return string(github_token)
}

//Defines basic usage when program is run with the help flag
func usage() {
	fmt.Fprintf(os.Stderr, "usage: gist [-p] [-d] [-u] example.go\n")
	flag.PrintDefaults()
	os.Exit(2)
}

//The main function parses the CLI args. It also checks the files, and
//loads them into an array.
//Then the files are separated into GistFile structs and collectively
//the files are saved in `files` field in the Gist struct.
//A request is then made to the GitHub api - it depends on whether it is
//anonymous gist or not.
//The response recieved is parsed and the Gist URL is printed to STDOUT.
func main() {
	flag.BoolVar(&publicFlag, "p", true, "Set to false for private gist.")
	flag.BoolVar(&anonymous, "u", true, "Set false if gist should be not anonymous")
	flag.StringVar(&description, "d", "This is a gist", "Description for gist.")
	flag.Usage = usage
	flag.Parse()

	files_list := flag.Args()
	if len(files_list) == 0 {
		log.Fatal("Error: No files specified.")
	}

	//fmt.Println(files_list)
	//fmt.Println(token)

	files := map[string]GistFile{}

	for _, filename := range files_list {
		fmt.Println("Checking file:", filename)
		content, err := ioutil.ReadFile(filename)
		if err != nil {
			log.Fatal("File Error: ", err)
		}
		files[filename] = GistFile{string(content)}
	}

	if description == "" {
		description = strings.Join(files_list, ", ")
	}

	//create a gist from the files array
	gist := Gist{
		description,
		publicFlag,
		files,
	}

	pfile, err := json.Marshal(gist)
	if err != nil {
		log.Fatal("Cannot marshal json: ", err)
	}

	//Check if JSON marshalling succeeds
	fmt.Println("OK")

	b := bytes.NewBuffer(pfile)
	fmt.Println("Uploading...")

	//Separate uploading methods depending on whether the gist is anonymous or not
	if anonymous == true {
		response, err := http.Post("https://api.github.com/gists", "application/json", b)
		if err != nil {
			log.Fatal("HTTP error: ", err)
		}

		err = json.NewDecoder(response.Body).Decode(&responseObj)
		if err != nil {
			log.Fatal("Response JSON error: ", err)
		}

		fmt.Println("--- Gist URL ---")
		fmt.Println(responseObj["html_url"])
	} else {
		req, err := http.NewRequest("POST", "https://api.github.com/gists", b)
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Accept", "application/json")
		req.SetBasicAuth(token, "x-oauth-basic")

		client := http.Client{}
		response, err := client.Do(req)
		if err != nil {
			log.Fatal("HTTP error: ", err)
		}
		err = json.NewDecoder(response.Body).Decode(&responseObj)
		if err != nil {
			log.Fatal("Response JSON error: ", err)
		}

		fmt.Println("--- Gist URL ---")
		fmt.Println(responseObj["html_url"])
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/bouk/monkey"
	"github.com/evandroflores/claimr/model"
	"github.com/shomali11/slacker"
	"github.com/stretchr/testify/assert"
)

func TestTryToClaimContainerNotFound(t *testing.T) {
	containerName := "container-not-found"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(Messages["container-not-found-on-channel"], containerName, channelName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	claim(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestTryToClaimAContainerInUseByAnotherUser(t *testing.T) {
	containerName := "container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"
	anotherUser := "anotherUser"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName, InUseBy: anotherUser}
	err := container.Add()
	defer container.Delete()

	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(Messages["container-in-use"], containerName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	claim(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestTryToClaimAContainerInUseByYou(t *testing.T) {
	containerName := "container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName, InUseBy: userName}
	err := container.Add()
	defer container.Delete()

	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(Messages["container-in-use-by-you"], containerName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	claim(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestClaimErrorWhenUpdate(t *testing.T) {

	guard := monkey.PatchInstanceMethod(reflect.TypeOf(model.Container{}), "Update",
		func(container model.Container) error {
			return fmt.Errorf("simulated error")
		})

	containerName := "my-container-claim"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	defer container.Delete()
	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, Messages["fail-to-update"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName, "reason": ""})

	claim(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
	guard.Unpatch()
}

func TestClaiming(t *testing.T) {
	containerName := "my-container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	assert.NoError(t, err)
	defer container.Delete()

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(Messages["container-claimed"], containerName, userName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	claim(mockRequest, mockResponse)

	containerFromDB, err2 := model.GetContainer(teamName, channelName, containerName)
	assert.NoError(t, err2)
	assert.Equal(t, userName, containerFromDB.InUseBy)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestClaimingWithOneWordReason(t *testing.T) {
	containerName := "my-container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"
	reason := "tests"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	assert.NoError(t, err)
	defer container.Delete()

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(Messages["container-claimed"], containerName, userName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName, "reason": reason})
	patchGetEventText := monkey.Patch(GetEventText,
		func(request *slacker.Request) string {
			return reason
		})

	claim(mockRequest, mockResponse)

	containerFromDB, err2 := model.GetContainer(teamName, channelName, containerName)
	assert.NoError(t, err2)
	assert.Equal(t, userName, containerFromDB.InUseBy)
	assert.Equal(t, reason, containerFromDB.InUseForReason)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
	patchGetEventText.Unpatch()
}

func TestClaimingWithMultiwordsReason(t *testing.T) {
	containerName := "my-container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"
	reason := "testing my container"
	firstWord := strings.Split(reason, " ")[0]

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	assert.NoError(t, err)
	defer container.Delete()

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(Messages["container-claimed"], containerName, userName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName, "reason": firstWord})

	patchGetEventText := monkey.Patch(GetEventText,
		func(request *slacker.Request) string {
			return reason
		})

	claim(mockRequest, mockResponse)

	containerFromDB, err2 := model.GetContainer(teamName, channelName, containerName)
	assert.NoError(t, err2)
	assert.Equal(t, userName, containerFromDB.InUseBy)
	assert.Equal(t, reason, containerFromDB.InUseForReason)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
	patchGetEventText.Unpatch()
}
<commit_msg>moving cmd/claim<commit_after>package cmd

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/bouk/monkey"
	"github.com/evandroflores/claimr/messages"
	"github.com/evandroflores/claimr/model"
	"github.com/shomali11/slacker"
	"github.com/stretchr/testify/assert"
)

func TestTryToClaimContainerNotFound(t *testing.T) {
	containerName := "container-not-found"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(messages.Messages["container-not-found-on-channel"], containerName, channelName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	claim(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestTryToClaimAContainerInUseByAnotherUser(t *testing.T) {
	containerName := "container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"
	anotherUser := "anotherUser"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName, InUseBy: anotherUser}
	err := container.Add()
	defer container.Delete()

	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(messages.Messages["container-in-use"], containerName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	claim(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestTryToClaimAContainerInUseByYou(t *testing.T) {
	containerName := "container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName, InUseBy: userName}
	err := container.Add()
	defer container.Delete()

	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(messages.Messages["container-in-use-by-you"], containerName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	claim(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestClaimErrorWhenUpdate(t *testing.T) {

	guard := monkey.PatchInstanceMethod(reflect.TypeOf(model.Container{}), "Update",
		func(container model.Container) error {
			return fmt.Errorf("simulated error")
		})

	containerName := "my-container-claim"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	defer container.Delete()
	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, messages.Messages["fail-to-update"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName, "reason": ""})

	claim(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
	guard.Unpatch()
}

func TestClaiming(t *testing.T) {
	containerName := "my-container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	assert.NoError(t, err)
	defer container.Delete()

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(messages.Messages["container-claimed"], containerName, userName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	claim(mockRequest, mockResponse)

	containerFromDB, err2 := model.GetContainer(teamName, channelName, containerName)
	assert.NoError(t, err2)
	assert.Equal(t, userName, containerFromDB.InUseBy)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestClaimingWithOneWordReason(t *testing.T) {
	containerName := "my-container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"
	reason := "tests"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	assert.NoError(t, err)
	defer container.Delete()

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(messages.Messages["container-claimed"], containerName, userName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName, "reason": reason})
	patchGetEventText := monkey.Patch(GetEventText,
		func(request *slacker.Request) string {
			return reason
		})

	claim(mockRequest, mockResponse)

	containerFromDB, err2 := model.GetContainer(teamName, channelName, containerName)
	assert.NoError(t, err2)
	assert.Equal(t, userName, containerFromDB.InUseBy)
	assert.Equal(t, reason, containerFromDB.InUseForReason)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
	patchGetEventText.Unpatch()
}

func TestClaimingWithMultiwordsReason(t *testing.T) {
	containerName := "my-container"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"
	reason := "testing my container"
	firstWord := strings.Split(reason, " ")[0]

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	assert.NoError(t, err)
	defer container.Delete()

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf(messages.Messages["container-claimed"], containerName, userName))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName, "reason": firstWord})

	patchGetEventText := monkey.Patch(GetEventText,
		func(request *slacker.Request) string {
			return reason
		})

	claim(mockRequest, mockResponse)

	containerFromDB, err2 := model.GetContainer(teamName, channelName, containerName)
	assert.NoError(t, err2)
	assert.Equal(t, userName, containerFromDB.InUseBy)
	assert.Equal(t, reason, containerFromDB.InUseForReason)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
	patchGetEventText.Unpatch()
}
<|endoftext|>


<commit_before>// Copyright © 2016 Prometheus Team
//
// 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 cmd

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
	kingpin "gopkg.in/alecthomas/kingpin.v2"

	"github.com/prometheus/promu/util/sh"
)

var (
	dockerBuilderImageName = "quay.io/prometheus/golang-builder"

	defaultMainPlatforms = []string{
		"linux/amd64", "linux/386", "darwin/amd64", "darwin/386", "windows/amd64", "windows/386",
		"freebsd/amd64", "freebsd/386", "openbsd/amd64", "openbsd/386", "netbsd/amd64", "netbsd/386",
		"dragonfly/amd64",
	}
	defaultARMPlatforms = []string{
		"freebsd/armv6", "freebsd/armv7", "freebsd/arm64",
		"linux/armv5", "linux/armv6", "linux/armv7", "linux/arm64",
		"netbsd/armv6", "netbsd/armv7", "netbsd/arm64",
		"openbsd/armv7", "openbsd/arm64",
	}
	defaultPowerPCPlatforms = []string{
		"aix/ppc64", "linux/ppc64", "linux/ppc64le",
	}
	defaultMIPSPlatforms = []string{
		"linux/mips", "linux/mipsle",
		"linux/mips64", "linux/mips64le",
	}
	defaultS390Platforms = []string{
		"linux/s390x",
	}
	armPlatformsAliases = map[string][]string{
		"linux/arm":   {"linux/armv5", "linux/armv6", "linux/armv7"},
		"freebsd/arm": {"freebsd/armv6", "freebsd/armv7"},
		"openbsd/arm": {"openbsd/armv7"},
		"netbsd/arm":  {"netbsd/armv6", "netbsd/armv7"},
	}
)

var (
	crossbuildcmd        = app.Command("crossbuild", "Crossbuild a Go project using Golang builder Docker images")
	crossBuildCgoFlagSet bool
	crossBuildCgoFlag    = crossbuildcmd.Flag("cgo", "Enable CGO using several docker images with different crossbuild toolchains.").
				PreAction(func(c *kingpin.ParseContext) error {
			crossBuildCgoFlagSet = true
			return nil
		}).Default("false").Bool()
	goFlagSet bool
	goFlag    = crossbuildcmd.Flag("go", "Golang builder version to use (e.g. 1.11)").
			PreAction(func(c *kingpin.ParseContext) error {
			goFlagSet = true
			return nil
		}).String()
	platformsFlagSet bool
	platformsFlag    = crossbuildcmd.Flag("platforms", "Space separated list of platforms to build").Short('p').
				PreAction(func(c *kingpin.ParseContext) error {
			platformsFlagSet = true
			return nil
		}).Strings()
	// kingpin doesn't currently support using the crossbuild command and the
	// crossbuild tarball subcommand at the same time, so we treat the
	// tarball subcommand as an optional arg
	tarballsSubcommand = crossbuildcmd.Arg("tarballs", "Optionally pass the string \"tarballs\" from cross-built binaries").String()
)

func runCrossbuild() {
	//Check required configuration
	if len(strings.TrimSpace(config.Repository.Path)) == 0 {
		log.Fatalf("missing required '%s' configuration", "repository.path")
	}
	if *tarballsSubcommand == "tarballs" {
		runCrossbuildTarballs()
		return
	}

	if crossBuildCgoFlagSet {
		config.Go.CGo = *crossBuildCgoFlag
	}
	if goFlagSet {
		config.Go.Version = *goFlag
	}
	if platformsFlagSet {
		config.Crossbuild.Platforms = *platformsFlag
	}

	var (
		mainPlatforms    []string
		armPlatforms     []string
		powerPCPlatforms []string
		mipsPlatforms    []string
		s390xPlatforms   []string
		unknownPlatforms []string

		cgo       = config.Go.CGo
		goVersion = config.Go.Version
		repoPath  = config.Repository.Path
		platforms = config.Crossbuild.Platforms

		dockerBaseBuilderImage    = fmt.Sprintf("%s:%s-base", dockerBuilderImageName, goVersion)
		dockerMainBuilderImage    = fmt.Sprintf("%s:%s-main", dockerBuilderImageName, goVersion)
		dockerARMBuilderImage     = fmt.Sprintf("%s:%s-arm", dockerBuilderImageName, goVersion)
		dockerPowerPCBuilderImage = fmt.Sprintf("%s:%s-powerpc", dockerBuilderImageName, goVersion)
		dockerMIPSBuilderImage    = fmt.Sprintf("%s:%s-mips", dockerBuilderImageName, goVersion)
		dockerS390XBuilderImage   = fmt.Sprintf("%s:%s-s390x", dockerBuilderImageName, goVersion)
	)

	for _, platform := range platforms {
		switch {
		case stringInSlice(platform, defaultMainPlatforms):
			mainPlatforms = append(mainPlatforms, platform)
		case stringInSlice(platform, defaultARMPlatforms):
			armPlatforms = append(armPlatforms, platform)
		case stringInSlice(platform, defaultPowerPCPlatforms):
			powerPCPlatforms = append(powerPCPlatforms, platform)
		case stringInSlice(platform, defaultMIPSPlatforms):
			mipsPlatforms = append(mipsPlatforms, platform)
		case stringInSlice(platform, defaultS390Platforms):
			s390xPlatforms = append(s390xPlatforms, platform)
		case stringInMapKeys(platform, armPlatformsAliases):
			armPlatforms = append(armPlatforms, armPlatformsAliases[platform]...)
		default:
			unknownPlatforms = append(unknownPlatforms, platform)
		}
	}

	if len(unknownPlatforms) > 0 {
		warn(errors.Errorf("unknown/unhandled platforms: %s", unknownPlatforms))
	}

	if !cgo {
		// In non-CGO, use the base image without any crossbuild toolchain
		var allPlatforms []string
		allPlatforms = append(allPlatforms, mainPlatforms[:]...)
		allPlatforms = append(allPlatforms, armPlatforms[:]...)
		allPlatforms = append(allPlatforms, powerPCPlatforms[:]...)
		allPlatforms = append(allPlatforms, mipsPlatforms[:]...)
		allPlatforms = append(allPlatforms, s390xPlatforms[:]...)

		pg := &platformGroup{"base", dockerBaseBuilderImage, allPlatforms}
		if err := pg.Build(repoPath); err != nil {
			fatal(errors.Wrapf(err, "The %s builder docker image exited unexpectedly", pg.Name))
		}
	} else {
		for _, pg := range []platformGroup{
			{"main", dockerMainBuilderImage, mainPlatforms},
			{"ARM", dockerARMBuilderImage, armPlatforms},
			{"PowerPC", dockerPowerPCBuilderImage, powerPCPlatforms},
			{"MIPS", dockerMIPSBuilderImage, mipsPlatforms},
			{"s390x", dockerS390XBuilderImage, s390xPlatforms},
		} {
			if err := pg.Build(repoPath); err != nil {
				fatal(errors.Wrapf(err, "The %s builder docker image exited unexpectedly", pg.Name))
			}
		}
	}
}

type platformGroup struct {
	Name        string
	DockerImage string
	Platforms   []string
}

func (pg platformGroup) Build(repoPath string) error {
	platformsParam := strings.Join(pg.Platforms[:], " ")
	if len(platformsParam) == 0 {
		return nil
	}

	fmt.Printf("> running the %s builder docker image\n", pg.Name)

	cwd, err := os.Getwd()
	if err != nil {
		return errors.Wrapf(err, "couldn't get current working directory")
	}

	ctrName := "promu-crossbuild-" + pg.Name + strconv.FormatInt(time.Now().Unix(), 10)
	err = sh.RunCommand("docker", "create", "-t",
		"--name", ctrName,
		pg.DockerImage,
		"-i", repoPath,
		"-p", platformsParam)
	if err != nil {
		return err
	}

	err = sh.RunCommand("docker", "cp",
		cwd+"/.",
		ctrName+":/app/")
	if err != nil {
		return err
	}

	err = sh.RunCommand("docker", "start", "-a", ctrName)
	if err != nil {
		return err
	}

	err = sh.RunCommand("docker", "cp", "-a",
		ctrName+":/app/.build/.",
		cwd+"/.build")
	if err != nil {
		return err
	}
	return sh.RunCommand("docker", "rm", "-f", ctrName)
}
<commit_msg>Remove default build of darwin/386 (#187)<commit_after>// Copyright © 2016 Prometheus Team
//
// 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 cmd

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
	kingpin "gopkg.in/alecthomas/kingpin.v2"

	"github.com/prometheus/promu/util/sh"
)

var (
	dockerBuilderImageName = "quay.io/prometheus/golang-builder"

	defaultMainPlatforms = []string{
		"darwin/amd64",
		"dragonfly/amd64",
		"freebsd/amd64", "freebsd/386",
		"linux/amd64", "linux/386",
		"netbsd/amd64", "netbsd/386",
		"openbsd/amd64", "openbsd/386",
		"windows/amd64", "windows/386",
	}
	defaultARMPlatforms = []string{
		"freebsd/armv6", "freebsd/armv7", "freebsd/arm64",
		"linux/armv5", "linux/armv6", "linux/armv7", "linux/arm64",
		"netbsd/armv6", "netbsd/armv7", "netbsd/arm64",
		"openbsd/armv7", "openbsd/arm64",
	}
	defaultPowerPCPlatforms = []string{
		"aix/ppc64", "linux/ppc64", "linux/ppc64le",
	}
	defaultMIPSPlatforms = []string{
		"linux/mips", "linux/mipsle",
		"linux/mips64", "linux/mips64le",
	}
	defaultS390Platforms = []string{
		"linux/s390x",
	}
	armPlatformsAliases = map[string][]string{
		"linux/arm":   {"linux/armv5", "linux/armv6", "linux/armv7"},
		"freebsd/arm": {"freebsd/armv6", "freebsd/armv7"},
		"openbsd/arm": {"openbsd/armv7"},
		"netbsd/arm":  {"netbsd/armv6", "netbsd/armv7"},
	}
)

var (
	crossbuildcmd        = app.Command("crossbuild", "Crossbuild a Go project using Golang builder Docker images")
	crossBuildCgoFlagSet bool
	crossBuildCgoFlag    = crossbuildcmd.Flag("cgo", "Enable CGO using several docker images with different crossbuild toolchains.").
				PreAction(func(c *kingpin.ParseContext) error {
			crossBuildCgoFlagSet = true
			return nil
		}).Default("false").Bool()
	goFlagSet bool
	goFlag    = crossbuildcmd.Flag("go", "Golang builder version to use (e.g. 1.11)").
			PreAction(func(c *kingpin.ParseContext) error {
			goFlagSet = true
			return nil
		}).String()
	platformsFlagSet bool
	platformsFlag    = crossbuildcmd.Flag("platforms", "Space separated list of platforms to build").Short('p').
				PreAction(func(c *kingpin.ParseContext) error {
			platformsFlagSet = true
			return nil
		}).Strings()
	// kingpin doesn't currently support using the crossbuild command and the
	// crossbuild tarball subcommand at the same time, so we treat the
	// tarball subcommand as an optional arg
	tarballsSubcommand = crossbuildcmd.Arg("tarballs", "Optionally pass the string \"tarballs\" from cross-built binaries").String()
)

func runCrossbuild() {
	//Check required configuration
	if len(strings.TrimSpace(config.Repository.Path)) == 0 {
		log.Fatalf("missing required '%s' configuration", "repository.path")
	}
	if *tarballsSubcommand == "tarballs" {
		runCrossbuildTarballs()
		return
	}

	if crossBuildCgoFlagSet {
		config.Go.CGo = *crossBuildCgoFlag
	}
	if goFlagSet {
		config.Go.Version = *goFlag
	}
	if platformsFlagSet {
		config.Crossbuild.Platforms = *platformsFlag
	}

	var (
		mainPlatforms    []string
		armPlatforms     []string
		powerPCPlatforms []string
		mipsPlatforms    []string
		s390xPlatforms   []string
		unknownPlatforms []string

		cgo       = config.Go.CGo
		goVersion = config.Go.Version
		repoPath  = config.Repository.Path
		platforms = config.Crossbuild.Platforms

		dockerBaseBuilderImage    = fmt.Sprintf("%s:%s-base", dockerBuilderImageName, goVersion)
		dockerMainBuilderImage    = fmt.Sprintf("%s:%s-main", dockerBuilderImageName, goVersion)
		dockerARMBuilderImage     = fmt.Sprintf("%s:%s-arm", dockerBuilderImageName, goVersion)
		dockerPowerPCBuilderImage = fmt.Sprintf("%s:%s-powerpc", dockerBuilderImageName, goVersion)
		dockerMIPSBuilderImage    = fmt.Sprintf("%s:%s-mips", dockerBuilderImageName, goVersion)
		dockerS390XBuilderImage   = fmt.Sprintf("%s:%s-s390x", dockerBuilderImageName, goVersion)
	)

	for _, platform := range platforms {
		switch {
		case stringInSlice(platform, defaultMainPlatforms):
			mainPlatforms = append(mainPlatforms, platform)
		case stringInSlice(platform, defaultARMPlatforms):
			armPlatforms = append(armPlatforms, platform)
		case stringInSlice(platform, defaultPowerPCPlatforms):
			powerPCPlatforms = append(powerPCPlatforms, platform)
		case stringInSlice(platform, defaultMIPSPlatforms):
			mipsPlatforms = append(mipsPlatforms, platform)
		case stringInSlice(platform, defaultS390Platforms):
			s390xPlatforms = append(s390xPlatforms, platform)
		case stringInMapKeys(platform, armPlatformsAliases):
			armPlatforms = append(armPlatforms, armPlatformsAliases[platform]...)
		default:
			unknownPlatforms = append(unknownPlatforms, platform)
		}
	}

	if len(unknownPlatforms) > 0 {
		warn(errors.Errorf("unknown/unhandled platforms: %s", unknownPlatforms))
	}

	if !cgo {
		// In non-CGO, use the base image without any crossbuild toolchain
		var allPlatforms []string
		allPlatforms = append(allPlatforms, mainPlatforms[:]...)
		allPlatforms = append(allPlatforms, armPlatforms[:]...)
		allPlatforms = append(allPlatforms, powerPCPlatforms[:]...)
		allPlatforms = append(allPlatforms, mipsPlatforms[:]...)
		allPlatforms = append(allPlatforms, s390xPlatforms[:]...)

		pg := &platformGroup{"base", dockerBaseBuilderImage, allPlatforms}
		if err := pg.Build(repoPath); err != nil {
			fatal(errors.Wrapf(err, "The %s builder docker image exited unexpectedly", pg.Name))
		}
	} else {
		for _, pg := range []platformGroup{
			{"main", dockerMainBuilderImage, mainPlatforms},
			{"ARM", dockerARMBuilderImage, armPlatforms},
			{"PowerPC", dockerPowerPCBuilderImage, powerPCPlatforms},
			{"MIPS", dockerMIPSBuilderImage, mipsPlatforms},
			{"s390x", dockerS390XBuilderImage, s390xPlatforms},
		} {
			if err := pg.Build(repoPath); err != nil {
				fatal(errors.Wrapf(err, "The %s builder docker image exited unexpectedly", pg.Name))
			}
		}
	}
}

type platformGroup struct {
	Name        string
	DockerImage string
	Platforms   []string
}

func (pg platformGroup) Build(repoPath string) error {
	platformsParam := strings.Join(pg.Platforms[:], " ")
	if len(platformsParam) == 0 {
		return nil
	}

	fmt.Printf("> running the %s builder docker image\n", pg.Name)

	cwd, err := os.Getwd()
	if err != nil {
		return errors.Wrapf(err, "couldn't get current working directory")
	}

	ctrName := "promu-crossbuild-" + pg.Name + strconv.FormatInt(time.Now().Unix(), 10)
	err = sh.RunCommand("docker", "create", "-t",
		"--name", ctrName,
		pg.DockerImage,
		"-i", repoPath,
		"-p", platformsParam)
	if err != nil {
		return err
	}

	err = sh.RunCommand("docker", "cp",
		cwd+"/.",
		ctrName+":/app/")
	if err != nil {
		return err
	}

	err = sh.RunCommand("docker", "start", "-a", ctrName)
	if err != nil {
		return err
	}

	err = sh.RunCommand("docker", "cp", "-a",
		ctrName+":/app/.build/.",
		cwd+"/.build")
	if err != nil {
		return err
	}
	return sh.RunCommand("docker", "rm", "-f", ctrName)
}
<|endoftext|>


<commit_before>// Copyright © 2016 Prometheus Team
//
// 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 cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/mcuadros/go-version"
	"github.com/progrium/go-shell"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	dockerBuilderImageName = "quay.io/prometheus/golang-builder"

	defaultGoVersion     = "1.5.4"
	defaultMainPlatforms = []string{
		"linux/amd64", "linux/386", "darwin/amd64", "darwin/386", "windows/amd64", "windows/386",
		"freebsd/amd64", "freebsd/386", "openbsd/amd64", "openbsd/386", "netbsd/amd64", "netbsd/386",
		"dragonfly/amd64",
	}
	defaultARMPlatforms = []string{
		"linux/arm", "linux/arm64", "freebsd/arm", "openbsd/arm", "netbsd/arm",
	}
	defaultPowerPCPlatforms = []string{
		"linux/ppc64", "linux/ppc64le",
	}
	defaultMIPSPlatforms = []string{
		"linux/mips64", "linux/mips64le",
	}
)

// crossbuildCmd represents the crossbuild command
var crossbuildCmd = &cobra.Command{
	Use:   "crossbuild",
	Short: "Crossbuild a Go project using Golang builder Docker images",
	Long:  `Crossbuild a Go project using Golang builder Docker images`,
	Run: func(cmd *cobra.Command, args []string) {
		runCrossbuild()
	},
	PreRun: func(cmd *cobra.Command, args []string) {
		if err := hasRequiredConfigurations("repository.path"); err != nil {
			fatal(err)
		}
	},
}

// init prepares cobra flags
func init() {
	Promu.AddCommand(crossbuildCmd)

	crossbuildCmd.Flags().String("go", "", "Golang builder version to use")
	crossbuildCmd.Flags().StringP("platforms", "p", "", "Platforms to build")

	viper.BindPFlag("go", crossbuildCmd.Flags().Lookup("go"))
	viper.BindPFlag("crossbuild.platforms", crossbuildCmd.Flags().Lookup("platforms"))

	viper.SetDefault("go", defaultGoVersion)
	// Current bug in viper: SeDefault doesn't work with nested key
	// platforms := defaultMainPlatforms
	// platforms = append(platforms, defaultARMPlatforms...)
	// platforms = append(platforms, defaultPowerPCPlatforms...)
	// platforms = append(platforms, defaultMIPSPlatforms...)
	// viper.SetDefault("crossbuild.platforms", platforms)
}

func runCrossbuild() {
	defer shell.ErrExit()
	shell.Tee = os.Stdout

	if viper.GetBool("verbose") {
		shell.Trace = true
	}

	var (
		mainPlatforms    []string
		armPlatforms     []string
		powerPCPlatforms []string
		mipsPlatforms    []string
		unknownPlatforms []string

		goVersion = viper.GetString("go")
		repoPath  = viper.GetString("repository.path")
		platforms = viper.GetStringSlice("crossbuild.platforms")

		dockerMainBuilderImage    = fmt.Sprintf("%s:%s-main", dockerBuilderImageName, goVersion)
		dockerARMBuilderImage     = fmt.Sprintf("%s:%s-arm", dockerBuilderImageName, goVersion)
		dockerPowerPCBuilderImage = fmt.Sprintf("%s:%s-powerpc", dockerBuilderImageName, goVersion)
		dockerMIPSBuilderImage    = fmt.Sprintf("%s:%s-mips", dockerBuilderImageName, goVersion)
	)

	for _, platform := range platforms {
		switch {
		case stringInSlice(platform, defaultMainPlatforms):
			mainPlatforms = append(mainPlatforms, platform)
		case stringInSlice(platform, defaultARMPlatforms):
			armPlatforms = append(armPlatforms, platform)
		case stringInSlice(platform, defaultPowerPCPlatforms):
			powerPCPlatforms = append(powerPCPlatforms, platform)
		case stringInSlice(platform, defaultMIPSPlatforms):
			mipsPlatforms = append(mipsPlatforms, platform)
		default:
			unknownPlatforms = append(unknownPlatforms, platform)
		}
	}

	if len(unknownPlatforms) > 0 {
		warn(fmt.Errorf("unknown/unhandled platforms: %s", unknownPlatforms))
	}

	if mainPlatformsParam := strings.Join(mainPlatforms[:], " "); mainPlatformsParam != "" {
		fmt.Println("> running the main builder docker image")
		if err := docker("run --rm -t -v $PWD:/app", dockerMainBuilderImage, "-i", repoPath, "-p", q(mainPlatformsParam)); err != nil {
			fatalMsg("The main builder docker image exited unexpectedly", err)
		}
	}

	if armPlatformsParam := strings.Join(armPlatforms[:], " "); armPlatformsParam != "" {
		fmt.Println("> running the ARM builder docker image")
		if err := docker("run --rm -t -v $PWD:/app", dockerARMBuilderImage, "-i", repoPath, "-p", q(armPlatformsParam)); err != nil {
			fatalMsg("The ARM builder docker image exited unexpectedly", err)
		}
	}

	if powerPCPlatformsParam := strings.Join(powerPCPlatforms[:], " "); powerPCPlatformsParam != "" {
		fmt.Println("> running the PowerPC builder docker image")
		if err := docker("run --rm -t -v $PWD:/app", dockerPowerPCBuilderImage, "-i", repoPath, "-p", q(powerPCPlatformsParam)); err != nil {
			fatalMsg("The PowerPC builder docker image exited unexpectedly", err)
		}
	}

	if mipsPlatformsParam := strings.Join(mipsPlatforms[:], " "); mipsPlatformsParam != "" {
		if version.Compare(goVersion, "1.6", ">=") {
			fmt.Println("> running the MIPS builder docker image")
			if err := docker("run --rm -t -v $PWD:/app", dockerMIPSBuilderImage, "-i", repoPath, "-p", q(mipsPlatformsParam)); err != nil {
				fatalMsg("The MIPS builder docker image exited unexpectedly", err)
			}
		} else {
			warn(fmt.Errorf("MIPS architectures are only available with Go 1.6+"))
		}
	}
}
<commit_msg>crossbuild: bump default go version to 1.6.2<commit_after>// Copyright © 2016 Prometheus Team
//
// 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 cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/mcuadros/go-version"
	"github.com/progrium/go-shell"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	dockerBuilderImageName = "quay.io/prometheus/golang-builder"

	defaultGoVersion     = "1.6.2"
	defaultMainPlatforms = []string{
		"linux/amd64", "linux/386", "darwin/amd64", "darwin/386", "windows/amd64", "windows/386",
		"freebsd/amd64", "freebsd/386", "openbsd/amd64", "openbsd/386", "netbsd/amd64", "netbsd/386",
		"dragonfly/amd64",
	}
	defaultARMPlatforms = []string{
		"linux/arm", "linux/arm64", "freebsd/arm", "openbsd/arm", "netbsd/arm",
	}
	defaultPowerPCPlatforms = []string{
		"linux/ppc64", "linux/ppc64le",
	}
	defaultMIPSPlatforms = []string{
		"linux/mips64", "linux/mips64le",
	}
)

// crossbuildCmd represents the crossbuild command
var crossbuildCmd = &cobra.Command{
	Use:   "crossbuild",
	Short: "Crossbuild a Go project using Golang builder Docker images",
	Long:  `Crossbuild a Go project using Golang builder Docker images`,
	Run: func(cmd *cobra.Command, args []string) {
		runCrossbuild()
	},
	PreRun: func(cmd *cobra.Command, args []string) {
		if err := hasRequiredConfigurations("repository.path"); err != nil {
			fatal(err)
		}
	},
}

// init prepares cobra flags
func init() {
	Promu.AddCommand(crossbuildCmd)

	crossbuildCmd.Flags().String("go", "", "Golang builder version to use")
	crossbuildCmd.Flags().StringP("platforms", "p", "", "Platforms to build")

	viper.BindPFlag("go", crossbuildCmd.Flags().Lookup("go"))
	viper.BindPFlag("crossbuild.platforms", crossbuildCmd.Flags().Lookup("platforms"))

	viper.SetDefault("go", defaultGoVersion)
	// Current bug in viper: SeDefault doesn't work with nested key
	// platforms := defaultMainPlatforms
	// platforms = append(platforms, defaultARMPlatforms...)
	// platforms = append(platforms, defaultPowerPCPlatforms...)
	// platforms = append(platforms, defaultMIPSPlatforms...)
	// viper.SetDefault("crossbuild.platforms", platforms)
}

func runCrossbuild() {
	defer shell.ErrExit()
	shell.Tee = os.Stdout

	if viper.GetBool("verbose") {
		shell.Trace = true
	}

	var (
		mainPlatforms    []string
		armPlatforms     []string
		powerPCPlatforms []string
		mipsPlatforms    []string
		unknownPlatforms []string

		goVersion = viper.GetString("go")
		repoPath  = viper.GetString("repository.path")
		platforms = viper.GetStringSlice("crossbuild.platforms")

		dockerMainBuilderImage    = fmt.Sprintf("%s:%s-main", dockerBuilderImageName, goVersion)
		dockerARMBuilderImage     = fmt.Sprintf("%s:%s-arm", dockerBuilderImageName, goVersion)
		dockerPowerPCBuilderImage = fmt.Sprintf("%s:%s-powerpc", dockerBuilderImageName, goVersion)
		dockerMIPSBuilderImage    = fmt.Sprintf("%s:%s-mips", dockerBuilderImageName, goVersion)
	)

	for _, platform := range platforms {
		switch {
		case stringInSlice(platform, defaultMainPlatforms):
			mainPlatforms = append(mainPlatforms, platform)
		case stringInSlice(platform, defaultARMPlatforms):
			armPlatforms = append(armPlatforms, platform)
		case stringInSlice(platform, defaultPowerPCPlatforms):
			powerPCPlatforms = append(powerPCPlatforms, platform)
		case stringInSlice(platform, defaultMIPSPlatforms):
			mipsPlatforms = append(mipsPlatforms, platform)
		default:
			unknownPlatforms = append(unknownPlatforms, platform)
		}
	}

	if len(unknownPlatforms) > 0 {
		warn(fmt.Errorf("unknown/unhandled platforms: %s", unknownPlatforms))
	}

	if mainPlatformsParam := strings.Join(mainPlatforms[:], " "); mainPlatformsParam != "" {
		fmt.Println("> running the main builder docker image")
		if err := docker("run --rm -t -v $PWD:/app", dockerMainBuilderImage, "-i", repoPath, "-p", q(mainPlatformsParam)); err != nil {
			fatalMsg("The main builder docker image exited unexpectedly", err)
		}
	}

	if armPlatformsParam := strings.Join(armPlatforms[:], " "); armPlatformsParam != "" {
		fmt.Println("> running the ARM builder docker image")
		if err := docker("run --rm -t -v $PWD:/app", dockerARMBuilderImage, "-i", repoPath, "-p", q(armPlatformsParam)); err != nil {
			fatalMsg("The ARM builder docker image exited unexpectedly", err)
		}
	}

	if powerPCPlatformsParam := strings.Join(powerPCPlatforms[:], " "); powerPCPlatformsParam != "" {
		fmt.Println("> running the PowerPC builder docker image")
		if err := docker("run --rm -t -v $PWD:/app", dockerPowerPCBuilderImage, "-i", repoPath, "-p", q(powerPCPlatformsParam)); err != nil {
			fatalMsg("The PowerPC builder docker image exited unexpectedly", err)
		}
	}

	if mipsPlatformsParam := strings.Join(mipsPlatforms[:], " "); mipsPlatformsParam != "" {
		if version.Compare(goVersion, "1.6", ">=") {
			fmt.Println("> running the MIPS builder docker image")
			if err := docker("run --rm -t -v $PWD:/app", dockerMIPSBuilderImage, "-i", repoPath, "-p", q(mipsPlatformsParam)); err != nil {
				fatalMsg("The MIPS builder docker image exited unexpectedly", err)
			}
		} else {
			warn(fmt.Errorf("MIPS architectures are only available with Go 1.6+"))
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 The Go 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 main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/build/buildlet"
	"golang.org/x/build/dashboard"
	"golang.org/x/build/internal/envutil"
	"golang.org/x/build/internal/gomote/protos"
	"golang.org/x/sync/errgroup"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func legacyRun(args []string) error {
	if activeGroup != nil {
		return fmt.Errorf("command does not support groups")
	}

	fs := flag.NewFlagSet("run", flag.ContinueOnError)
	fs.Usage = func() {
		fmt.Fprintln(os.Stderr, "run usage: gomote run [run-opts] <instance> <cmd> [args...]")
		fs.PrintDefaults()
		os.Exit(1)
	}
	var sys bool
	fs.BoolVar(&sys, "system", false, "run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'")
	var debug bool
	fs.BoolVar(&debug, "debug", false, "write debug info about the command's execution before it begins")
	var env stringSlice
	fs.Var(&env, "e", "Environment variable KEY=value. The -e flag may be repeated multiple times to add multiple things to the environment.")
	var firewall bool
	fs.BoolVar(&firewall, "firewall", false, "Enable outbound firewall on machine. This is on by default on many builders (where supported) but disabled by default on gomote for ease of debugging. Once any command has been run with the -firewall flag on, it's on for the lifetime of that gomote instance.")
	var path string
	fs.StringVar(&path, "path", "", "Comma-separated list of ExecOpts.Path elements. The special string 'EMPTY' means to run without any $PATH. The empty string (default) does not modify the $PATH. Otherwise, the following expansions apply: the string '$PATH' expands to the current PATH element(s), the substring '$WORKDIR' expands to the buildlet's temp workdir.")

	var dir string
	fs.StringVar(&dir, "dir", "", "Directory to run from. Defaults to the directory of the command, or the work directory if -system is true.")
	var builderEnv string
	fs.StringVar(&builderEnv, "builderenv", "", "Optional alternate builder to act like. Must share the same underlying buildlet host type, or it's an error. For instance, linux-amd64-race or linux-386-387 are compatible with linux-amd64, but openbsd-amd64 and openbsd-386 are different hosts.")

	fs.Parse(args)
	if fs.NArg() < 2 {
		fs.Usage()
	}
	name, cmd := fs.Arg(0), fs.Arg(1)

	var conf *dashboard.BuildConfig

	bc, conf, err := clientAndConf(name)
	if err != nil {
		return err
	}

	if builderEnv != "" {
		altConf, ok := dashboard.Builders[builderEnv]
		if !ok {
			return fmt.Errorf("unknown --builderenv=%q builder value", builderEnv)
		}
		if altConf.HostType != conf.HostType {
			return fmt.Errorf("--builderEnv=%q has host type %q, which is not compatible with the named buildlet's host type %q",
				builderEnv, altConf.HostType, conf.HostType)
		}
		conf = altConf
	}

	var pathOpt []string
	if path == "EMPTY" {
		pathOpt = []string{} // non-nil
	} else if path != "" {
		pathOpt = strings.Split(path, ",")
	}
	env = append(env, "GO_DISABLE_OUTBOUND_NETWORK="+fmt.Sprint(firewall))

	remoteErr, execErr := bc.Exec(context.Background(), cmd, buildlet.ExecOpts{
		Dir:         dir,
		SystemLevel: sys || strings.HasPrefix(cmd, "/"),
		Output:      os.Stdout,
		Args:        fs.Args()[2:],
		ExtraEnv:    envutil.Dedup(conf.GOOS(), append(conf.Env(), []string(env)...)),
		Debug:       debug,
		Path:        pathOpt,
	})
	if execErr != nil {
		return fmt.Errorf("Error trying to execute %s: %v", cmd, execErr)
	}
	return remoteErr
}

// stringSlice implements flag.Value, specifically for storing environment
// variable key=value pairs.
type stringSlice []string

func (*stringSlice) String() string { return "" } // default value

func (ss *stringSlice) Set(v string) error {
	if v != "" {
		if !strings.Contains(v, "=") {
			return fmt.Errorf("-e argument %q doesn't contains an '=' sign.", v)
		}
		*ss = append(*ss, v)
	}
	return nil
}

func run(args []string) error {
	fs := flag.NewFlagSet("run", flag.ContinueOnError)
	fs.Usage = func() {
		fmt.Fprintln(os.Stderr, "run usage: gomote run [run-opts] <instance> <cmd> [args...]")
		fs.PrintDefaults()
		os.Exit(1)
	}
	var sys bool
	fs.BoolVar(&sys, "system", false, "run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'")
	var debug bool
	fs.BoolVar(&debug, "debug", false, "write debug info about the command's execution before it begins")
	var env stringSlice
	fs.Var(&env, "e", "Environment variable KEY=value. The -e flag may be repeated multiple times to add multiple things to the environment.")
	var firewall bool
	fs.BoolVar(&firewall, "firewall", false, "Enable outbound firewall on machine. This is on by default on many builders (where supported) but disabled by default on gomote for ease of debugging. Once any command has been run with the -firewall flag on, it's on for the lifetime of that gomote instance.")
	var path string
	fs.StringVar(&path, "path", "", "Comma-separated list of ExecOpts.Path elements. The special string 'EMPTY' means to run without any $PATH. The empty string (default) does not modify the $PATH. Otherwise, the following expansions apply: the string '$PATH' expands to the current PATH element(s), the substring '$WORKDIR' expands to the buildlet's temp workdir.")

	var dir string
	fs.StringVar(&dir, "dir", "", "Directory to run from. Defaults to the directory of the command, or the work directory if -system is true.")
	var builderEnv string
	fs.StringVar(&builderEnv, "builderenv", "", "Optional alternate builder to act like. Must share the same underlying buildlet host type, or it's an error. For instance, linux-amd64-race or linux-386-387 are compatible with linux-amd64, but openbsd-amd64 and openbsd-386 are different hosts.")

	fs.Parse(args)
	if fs.NArg() == 0 {
		fs.Usage()
	}
	// First check if the instance name refers to a live instance.
	ctx := context.Background()
	client := gomoteServerClient(ctx)
	_, err := client.InstanceAlive(ctx, &protos.InstanceAliveRequest{
		GomoteId: fs.Arg(0),
	})
	var cmd string
	var cmdArgs []string
	var runSet []string
	if err != nil {
		// When there's no active group, this must be an instance name.
		// Given that we got an error, we should surface that.
		if activeGroup == nil {
			return fmt.Errorf("instance %q: %s", fs.Arg(0), statusFromError(err))
		}
		// When there is an active group, this just means that we're going
		// to use the group instead and assume the rest is a command.
		for _, inst := range activeGroup.Instances {
			runSet = append(runSet, inst)
		}
		cmd = fs.Arg(0)
		cmdArgs = fs.Args()[1:]
	} else {
		runSet = append(runSet, fs.Arg(0))
		if fs.NArg() == 1 {
			fmt.Fprintln(os.Stderr, "missing command")
			fs.Usage()
		}
		cmd = fs.Arg(1)
		cmdArgs = fs.Args()[2:]
	}
	var pathOpt []string
	if path == "EMPTY" {
		pathOpt = []string{} // non-nil
	} else if path != "" {
		pathOpt = strings.Split(path, ",")
	}
	env = append(env, "GO_DISABLE_OUTBOUND_NETWORK="+fmt.Sprint(firewall))

	detailedProgress := len(runSet) == 1

	// Create temporary directory for output.
	// This is useful even if we don't have multiple gomotes running, since
	// it's easy to accidentally lose the output.
	tmpOutDir, err := os.MkdirTemp("", "gomote")
	if err != nil {
		return err
	}

	eg, ctx := errgroup.WithContext(context.Background())
	for _, inst := range runSet {
		inst := inst
		if !detailedProgress {
			fmt.Fprintf(os.Stderr, "# Running command on %q...\n", inst)
		}
		eg.Go(func() error {
			outf, err := os.Create(filepath.Join(tmpOutDir, fmt.Sprintf("%s.stdout", inst)))
			if err != nil {
				return err
			}
			defer func() {
				outf.Close()
				fmt.Fprintf(os.Stderr, "# Wrote results from %q to %q.\n", inst, outf.Name())
			}()
			fmt.Fprintf(os.Stderr, "# Streaming results from %q to %q...\n", inst, outf.Name())
			var outWriter io.Writer
			if detailedProgress {
				outWriter = io.MultiWriter(os.Stdout, outf)
			} else {
				outWriter = outf
			}

			client := gomoteServerClient(ctx)
			stream, err := client.ExecuteCommand(ctx, &protos.ExecuteCommandRequest{
				AppendEnvironment: []string(env),
				Args:              cmdArgs,
				Command:           cmd,
				Debug:             debug,
				Directory:         dir,
				GomoteId:          inst,
				Path:              pathOpt,
				SystemLevel:       sys || strings.HasPrefix(cmd, "/"),
				ImitateHostType:   builderEnv,
			})
			if err != nil {
				return fmt.Errorf("unable to execute %s: %s", cmd, statusFromError(err))
			}
			for {
				update, err := stream.Recv()
				if err == io.EOF {
					return nil
				}
				if err != nil {
					// execution error
					if status.Code(err) == codes.Aborted {
						return fmt.Errorf("Error trying to execute %s: %v", cmd, statusFromError(err))
					}
					// remote error
					return fmt.Errorf("unable to execute %s: %s", cmd, statusFromError(err))
				}
				fmt.Fprintf(outWriter, string(update.GetOutput()))
			}
		})
	}
	return eg.Wait()
}
<commit_msg>cmd/gomote: factor out run into doRun<commit_after>// Copyright 2015 The Go 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 main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/build/buildlet"
	"golang.org/x/build/dashboard"
	"golang.org/x/build/internal/envutil"
	"golang.org/x/build/internal/gomote/protos"
	"golang.org/x/sync/errgroup"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func legacyRun(args []string) error {
	if activeGroup != nil {
		return fmt.Errorf("command does not support groups")
	}

	fs := flag.NewFlagSet("run", flag.ContinueOnError)
	fs.Usage = func() {
		fmt.Fprintln(os.Stderr, "run usage: gomote run [run-opts] <instance> <cmd> [args...]")
		fs.PrintDefaults()
		os.Exit(1)
	}
	var sys bool
	fs.BoolVar(&sys, "system", false, "run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'")
	var debug bool
	fs.BoolVar(&debug, "debug", false, "write debug info about the command's execution before it begins")
	var env stringSlice
	fs.Var(&env, "e", "Environment variable KEY=value. The -e flag may be repeated multiple times to add multiple things to the environment.")
	var firewall bool
	fs.BoolVar(&firewall, "firewall", false, "Enable outbound firewall on machine. This is on by default on many builders (where supported) but disabled by default on gomote for ease of debugging. Once any command has been run with the -firewall flag on, it's on for the lifetime of that gomote instance.")
	var path string
	fs.StringVar(&path, "path", "", "Comma-separated list of ExecOpts.Path elements. The special string 'EMPTY' means to run without any $PATH. The empty string (default) does not modify the $PATH. Otherwise, the following expansions apply: the string '$PATH' expands to the current PATH element(s), the substring '$WORKDIR' expands to the buildlet's temp workdir.")

	var dir string
	fs.StringVar(&dir, "dir", "", "Directory to run from. Defaults to the directory of the command, or the work directory if -system is true.")
	var builderEnv string
	fs.StringVar(&builderEnv, "builderenv", "", "Optional alternate builder to act like. Must share the same underlying buildlet host type, or it's an error. For instance, linux-amd64-race or linux-386-387 are compatible with linux-amd64, but openbsd-amd64 and openbsd-386 are different hosts.")

	fs.Parse(args)
	if fs.NArg() < 2 {
		fs.Usage()
	}
	name, cmd := fs.Arg(0), fs.Arg(1)

	var conf *dashboard.BuildConfig

	bc, conf, err := clientAndConf(name)
	if err != nil {
		return err
	}

	if builderEnv != "" {
		altConf, ok := dashboard.Builders[builderEnv]
		if !ok {
			return fmt.Errorf("unknown --builderenv=%q builder value", builderEnv)
		}
		if altConf.HostType != conf.HostType {
			return fmt.Errorf("--builderEnv=%q has host type %q, which is not compatible with the named buildlet's host type %q",
				builderEnv, altConf.HostType, conf.HostType)
		}
		conf = altConf
	}

	var pathOpt []string
	if path == "EMPTY" {
		pathOpt = []string{} // non-nil
	} else if path != "" {
		pathOpt = strings.Split(path, ",")
	}
	env = append(env, "GO_DISABLE_OUTBOUND_NETWORK="+fmt.Sprint(firewall))

	remoteErr, execErr := bc.Exec(context.Background(), cmd, buildlet.ExecOpts{
		Dir:         dir,
		SystemLevel: sys || strings.HasPrefix(cmd, "/"),
		Output:      os.Stdout,
		Args:        fs.Args()[2:],
		ExtraEnv:    envutil.Dedup(conf.GOOS(), append(conf.Env(), []string(env)...)),
		Debug:       debug,
		Path:        pathOpt,
	})
	if execErr != nil {
		return fmt.Errorf("Error trying to execute %s: %v", cmd, execErr)
	}
	return remoteErr
}

// stringSlice implements flag.Value, specifically for storing environment
// variable key=value pairs.
type stringSlice []string

func (*stringSlice) String() string { return "" } // default value

func (ss *stringSlice) Set(v string) error {
	if v != "" {
		if !strings.Contains(v, "=") {
			return fmt.Errorf("-e argument %q doesn't contains an '=' sign.", v)
		}
		*ss = append(*ss, v)
	}
	return nil
}

func run(args []string) error {
	fs := flag.NewFlagSet("run", flag.ContinueOnError)
	fs.Usage = func() {
		fmt.Fprintln(os.Stderr, "run usage: gomote run [run-opts] <instance> <cmd> [args...]")
		fs.PrintDefaults()
		os.Exit(1)
	}
	var sys bool
	fs.BoolVar(&sys, "system", false, "run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'")
	var debug bool
	fs.BoolVar(&debug, "debug", false, "write debug info about the command's execution before it begins")
	var env stringSlice
	fs.Var(&env, "e", "Environment variable KEY=value. The -e flag may be repeated multiple times to add multiple things to the environment.")
	var firewall bool
	fs.BoolVar(&firewall, "firewall", false, "Enable outbound firewall on machine. This is on by default on many builders (where supported) but disabled by default on gomote for ease of debugging. Once any command has been run with the -firewall flag on, it's on for the lifetime of that gomote instance.")
	var path string
	fs.StringVar(&path, "path", "", "Comma-separated list of ExecOpts.Path elements. The special string 'EMPTY' means to run without any $PATH. The empty string (default) does not modify the $PATH. Otherwise, the following expansions apply: the string '$PATH' expands to the current PATH element(s), the substring '$WORKDIR' expands to the buildlet's temp workdir.")

	var dir string
	fs.StringVar(&dir, "dir", "", "Directory to run from. Defaults to the directory of the command, or the work directory if -system is true.")
	var builderEnv string
	fs.StringVar(&builderEnv, "builderenv", "", "Optional alternate builder to act like. Must share the same underlying buildlet host type, or it's an error. For instance, linux-amd64-race or linux-386-387 are compatible with linux-amd64, but openbsd-amd64 and openbsd-386 are different hosts.")

	fs.Parse(args)
	if fs.NArg() == 0 {
		fs.Usage()
	}
	// First check if the instance name refers to a live instance.
	ctx := context.Background()
	client := gomoteServerClient(ctx)
	_, err := client.InstanceAlive(ctx, &protos.InstanceAliveRequest{
		GomoteId: fs.Arg(0),
	})
	var cmd string
	var cmdArgs []string
	var runSet []string
	if err != nil {
		// When there's no active group, this must be an instance name.
		// Given that we got an error, we should surface that.
		if activeGroup == nil {
			return fmt.Errorf("instance %q: %s", fs.Arg(0), statusFromError(err))
		}
		// When there is an active group, this just means that we're going
		// to use the group instead and assume the rest is a command.
		for _, inst := range activeGroup.Instances {
			runSet = append(runSet, inst)
		}
		cmd = fs.Arg(0)
		cmdArgs = fs.Args()[1:]
	} else {
		runSet = append(runSet, fs.Arg(0))
		if fs.NArg() == 1 {
			fmt.Fprintln(os.Stderr, "missing command")
			fs.Usage()
		}
		cmd = fs.Arg(1)
		cmdArgs = fs.Args()[2:]
	}

	detailedProgress := len(runSet) == 1
	var pathOpt []string
	if path == "EMPTY" {
		pathOpt = []string{} // non-nil
	} else if path != "" {
		pathOpt = strings.Split(path, ",")
	}

	// Create temporary directory for output.
	// This is useful even if we don't have multiple gomotes running, since
	// it's easy to accidentally lose the output.
	tmpOutDir, err := os.MkdirTemp("", "gomote")
	if err != nil {
		return err
	}

	eg, ctx := errgroup.WithContext(context.Background())
	for _, inst := range runSet {
		inst := inst
		if !detailedProgress {
			fmt.Fprintf(os.Stderr, "# Running command on %q...\n", inst)
		}
		eg.Go(func() error {
			return doRun(
				ctx,
				inst,
				tmpOutDir,
				cmd,
				cmdArgs,
				detailedProgress,
				runDir(dir),
				runBuilderEnv(builderEnv),
				runEnv(env),
				runPath(pathOpt),
				runSystem(sys),
				runDebug(debug),
				runFirewall(firewall),
			)
		})
	}
	return eg.Wait()
}

func doRun(ctx context.Context, inst, tmpOutDir, cmd string, cmdArgs []string, detailedProgress bool, opts ...runOpt) error {
	req := &protos.ExecuteCommandRequest{
		AppendEnvironment: []string{},
		Args:              cmdArgs,
		Command:           cmd,
		Path:              []string{},
		GomoteId:          inst,
	}
	for _, opt := range opts {
		opt(req)
	}
	if !req.SystemLevel {
		req.SystemLevel = strings.HasPrefix(cmd, "/")
	}

	outf, err := os.Create(filepath.Join(tmpOutDir, fmt.Sprintf("%s.stdout", inst)))
	if err != nil {
		return err
	}
	defer func() {
		outf.Close()
		fmt.Fprintf(os.Stderr, "# Wrote results from %q to %q.\n", inst, outf.Name())
	}()
	fmt.Fprintf(os.Stderr, "# Streaming results from %q to %q...\n", inst, outf.Name())
	var outWriter io.Writer
	if detailedProgress {
		outWriter = io.MultiWriter(os.Stdout, outf)
	} else {
		outWriter = outf
	}

	client := gomoteServerClient(ctx)
	stream, err := client.ExecuteCommand(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to execute %s: %s", cmd, statusFromError(err))
	}
	for {
		update, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			// execution error
			if status.Code(err) == codes.Aborted {
				return fmt.Errorf("Error trying to execute %s: %v", cmd, statusFromError(err))
			}
			// remote error
			return fmt.Errorf("unable to execute %s: %s", cmd, statusFromError(err))
		}
		fmt.Fprintf(outWriter, string(update.GetOutput()))
	}
}

type runOpt func(*protos.ExecuteCommandRequest)

func runBuilderEnv(builderEnv string) runOpt {
	return func(r *protos.ExecuteCommandRequest) {
		r.ImitateHostType = builderEnv
	}
}

func runDir(dir string) runOpt {
	return func(r *protos.ExecuteCommandRequest) {
		r.Directory = dir
	}
}

func runEnv(env []string) runOpt {
	return func(r *protos.ExecuteCommandRequest) {
		r.AppendEnvironment = append(r.AppendEnvironment, env...)
	}
}

func runPath(path []string) runOpt {
	return func(r *protos.ExecuteCommandRequest) {
		r.Path = append(r.Path, path...)
	}
}

func runDebug(debug bool) runOpt {
	return func(r *protos.ExecuteCommandRequest) {
		r.Debug = debug
	}
}

func runSystem(sys bool) runOpt {
	return func(r *protos.ExecuteCommandRequest) {
		r.SystemLevel = sys
	}
}

func runFirewall(firewall bool) runOpt {
	return func(r *protos.ExecuteCommandRequest) {
		r.AppendEnvironment = append(r.AppendEnvironment, "GO_DISABLE_OUTBOUND_NETWORK="+fmt.Sprint(firewall))
	}
}
<|endoftext|>