<commit_before>package action

import (
	"context"
	"fmt"

	"github.com/blang/semver"
	"github.com/fatih/color"
	"github.com/justwatchcom/gopass/config"
	"github.com/justwatchcom/gopass/utils/ctxutil"
	"github.com/justwatchcom/gopass/utils/out"
	"github.com/justwatchcom/gopass/utils/pwgen/xkcdgen"
	"github.com/justwatchcom/gopass/utils/termwiz"
	"github.com/pkg/errors"
	"github.com/urfave/cli"
)

// Initialized returns an error if the store is not properly
// prepared.
func (s *Action) Initialized(ctx context.Context, c *cli.Context) error {
	if s.gpg.Version(ctx).LT(semver.Version{Major: 2, Minor: 0, Patch: 0}) {
		out.Red(ctx, "Warning: Using GPG 1.x. Using GPG 2.0 or later is highly recommended")
	}
	if !s.Store.Initialized() {
		if ctxutil.IsInteractive(ctx) {
			if ok, err := s.askForBool(ctx, "It seems you are new to gopass. Do you want to run the onboarding wizard?", true); err == nil && ok {
				return s.InitOnboarding(ctx, c)
			}
		}
		return s.exitError(ctx, ExitNotInitialized, nil, "password-store is not initialized. Try '%s init'", s.Name)
	}
	return nil
}

// Init a new password store with a first gpg id
func (s *Action) Init(ctx context.Context, c *cli.Context) error {
	path := c.String("path")
	alias := c.String("store")
	nogit := c.Bool("nogit")

	ctx = out.WithPrefix(ctx, "[init] ")
	out.Cyan(ctx, "Initializing a new password store ...")

	if err := s.init(ctx, alias, path, nogit, c.Args()...); err != nil {
		return s.exitError(ctx, ExitUnknown, err, "failed to initialized store: %s", err)
	}
	return nil
}

func (s *Action) init(ctx context.Context, alias, path string, nogit bool, keys ...string) error {
	if path == "" {
		if alias != "" {
			path = config.PwStoreDir(alias)
		} else {
			path = s.Store.Path()
		}
	}

	if len(keys) < 1 {
		nk, err := s.askForPrivateKey(ctx, color.CyanString("Please select a private key for encrypting secrets:"))
		if err != nil {
			return errors.Wrapf(err, "failed to read user input")
		}
		keys = []string{nk}
	}

	if err := s.Store.Init(ctx, alias, path, keys...); err != nil {
		return errors.Wrapf(err, "failed to init store '%s' at '%s'", alias, path)
	}

	if alias != "" && path != "" {
		if err := s.Store.AddMount(ctx, alias, path); err != nil {
			return errors.Wrapf(err, "failed to add mount '%s'", alias)
		}
	}

	if !nogit {
		sk := ""
		if len(keys) == 1 {
			sk = keys[0]
		}
		if err := s.gitInit(ctx, alias, sk); err != nil {
			out.Debug(ctx, "Stacktrace: %+v\n", err)
			out.Red(ctx, "Failed to init git: %s", err)
		}
	}

	out.Green(ctx, "Password store %s initialized for:", path)
	for _, recipient := range s.Store.ListRecipients(ctx, alias) {
		r := "0x" + recipient
		if kl, err := s.gpg.FindPublicKeys(ctx, recipient); err == nil && len(kl) > 0 {
			r = kl[0].OneLine()
		}
		out.Yellow(ctx, "  "+r)
	}

	// write config
	if err := s.cfg.Save(); err != nil {
		return s.exitError(ctx, ExitConfig, err, "failed to write config: %s", err)
	}

	return nil
}

// InitOnboarding will invoke the onboarding / setup wizard
func (s *Action) InitOnboarding(ctx context.Context, c *cli.Context) error {
	remote := c.String("remote")
	team := c.String("alias")
	create := c.Bool("create")
	name := c.String("name")
	email := c.String("email")

	ctx = out.AddPrefix(ctx, "[init] ")

	// check for existing GPG keypairs (private/secret keys). We need at least
	// one useable key pair. If none exists try to create one
	if !s.initHasUseablePrivateKeys(ctx) {
		out.Yellow(ctx, "No useable GPG keys. Generating new key pair")
		ctx := out.AddPrefix(ctx, "[gpg] ")
		out.Print(ctx, "Key generation may take up to a few minutes")
		if err := s.initCreatePrivateKey(ctx, name, email); err != nil {
			return errors.Wrapf(err, "failed to create new private key")
		}
	}

	// if a git remote and a team name are given attempt unattended team setup
	if remote != "" && team != "" {
		if create {
			return s.initCreateTeam(ctx, c, team, remote)
		}
		return s.initJoinTeam(ctx, c, team, remote)
	}

	// no flags given, run interactively
	choices := []string{
		"Local store",
		"Create a Team",
		"Join an existing Team",
	}
	act, sel := termwiz.GetSelection(ctx, "Store for secret", "<↑/↓> to change the selection, <→> to select, <ESC> to quit", choices)
	switch act {
	case "show":
		switch sel {
		case 0:
			return s.initLocal(ctx, c)
		case 1:
			return s.initCreateTeam(ctx, c, "", "")
		case 2:
			return s.initJoinTeam(ctx, c, "", "")
		}
	default:
		return fmt.Errorf("user aborted")
	}
	return nil
}

func (s *Action) initCreatePrivateKey(ctx context.Context, name, email string) error {
	out.Green(ctx, "Creating key pair ...")
	if name != "" && email != "" {
		ctx := out.AddPrefix(ctx, " ")
		passphrase := xkcdgen.Random()
		if err := s.gpg.CreatePrivateKeyBatch(ctx, name, email, passphrase); err != nil {
			return errors.Wrapf(err, "failed to create new private key in batch mode")
		}
		out.Green(ctx, "-> OK")
		out.Print(ctx, color.MagentaString("Passphrase: ")+color.HiGreenString(passphrase))
	} else {
		ctx := out.WithPrefix(ctx, " ")
		if err := s.gpg.CreatePrivateKey(ctx); err != nil {
			return errors.Wrapf(err, "failed to create new private key in interactive mode")
		}
		out.Green(ctx, "-> OK")
	}

	kl, err := s.gpg.ListPrivateKeys(ctx)
	if err != nil {
		return errors.Wrapf(err, "failed to list private keys")
	}
	klu := kl.UseableKeys()
	if len(klu) > 1 {
		out.Cyan(ctx, "WARNING: More than one private key detected. Make sure to communicate the right one")
		return nil
	}
	if len(klu) < 1 {
		out.Debug(ctx, "Private Keys: %+v", kl)
		return errors.New("failed to create a useable key pair")
	}
	key := klu[0]
	fn := key.ID() + ".pub.key"
	if err := s.gpg.ExportPublicKey(ctx, key.Fingerprint, fn); err != nil {
		return errors.Wrapf(err, "failed to export public key")
	}
	out.Cyan(ctx, "Public key exported to '%s'", fn)
	out.Green(ctx, "Done")
	return nil
}

func (s *Action) initHasUseablePrivateKeys(ctx context.Context) bool {
	kl, err := s.gpg.ListPrivateKeys(ctx)
	if err != nil {
		return false
	}
	return len(kl.UseableKeys()) > 0
}

// initLocal will initalize a local store, useful for local-only setups or as
// part of team setups to create the root store
func (s *Action) initLocal(ctx context.Context, c *cli.Context) error {
	ctx = out.AddPrefix(ctx, "[local] ")

	out.Print(ctx, "Initializing your local store ...")
	if err := s.init(out.WithHidden(ctx, true), "", "", false); err != nil {
		return errors.Wrapf(err, "failed to init local store")
	}
	out.Green(ctx, " -> OK")

	out.Print(ctx, "Configuring your local store ...")
	// autosync
	if want, err := s.askForBool(ctx, "Do you want to automatically push any changes to the git remote (if any)?", true); err == nil {
		s.cfg.Root.AutoSync = want
	}

	// noconfirm
	if want, err := s.askForBool(ctx, "Do you want to always confirm recipients when encrypting?", false); err == nil {
		s.cfg.Root.NoConfirm = !want
	}

	// save config
	if err := s.cfg.Save(); err != nil {
		return errors.Wrapf(err, "failed to save config")
	}

	out.Green(ctx, " -> OK")
	return nil
}

// initCreateTeam will create a local root store and a shared team store
func (s *Action) initCreateTeam(ctx context.Context, c *cli.Context, team, remote string) error {
	var err error

	out.Print(ctx, "Creating a new team ...")
	if err := s.initLocal(ctx, c); err != nil {
		return errors.Wrapf(err, "failed to create local store")
	}

	// name of the new team
	team, err = s.askForString(ctx, "Please enter the name of your team (may contain slashes)", team)
	if err != nil {
		return errors.Wrapf(err, "failed to read user input")
	}
	ctx = out.AddPrefix(ctx, "["+team+"] ")

	out.Print(ctx, "Initializing your shared store ...")
	if err := s.init(out.WithHidden(ctx, true), team, "", false); err != nil {
		return errors.Wrapf(err, "failed to init shared store")
	}
	out.Green(ctx, " -> OK")

	out.Print(ctx, "Configuring the git remote ...")
	remote, err = s.askForString(ctx, "Please enter the git remote for your shared store", remote)
	if err != nil {
		return errors.Wrapf(err, "failed to read user input")
	}
	{
		ctx := out.WithHidden(ctx, true)
		if err := s.Store.Git(ctx, team, false, false, "remote", "add", "origin", remote); err != nil {
			return errors.Wrapf(err, "failed to add git remote")
		}
		if err := s.Store.Git(ctx, team, false, false, "push", "origin", "master"); err != nil {
			return errors.Wrapf(err, "failed to push to git remote")
		}
	}
	out.Green(ctx, " -> OK")
	out.Green(ctx, "Created Team '%s'", team)
	return nil
}

// initJoinTeam will create a local root store and clone and existing store to
// a mount
func (s *Action) initJoinTeam(ctx context.Context, c *cli.Context, team, remote string) error {
	var err error

	out.Print(ctx, "Joining existing team ...")
	if err := s.initLocal(ctx, c); err != nil {
		return errors.Wrapf(err, "failed to create local store")
	}

	// name of the existing team
	team, err = s.askForString(ctx, "Please enter the name of your team (may contain slashes)", team)
	if err != nil {
		return err
	}
	ctx = out.AddPrefix(ctx, "["+team+"]")

	out.Print(ctx, "Configuring git remote ...")
	remote, err = s.askForString(ctx, "Please enter the git remote for your shared store", remote)
	if err != nil {
		return err
	}

	out.Print(ctx, "Cloning from the git remote ...")
	if err := s.clone(out.WithHidden(ctx, true), remote, team, ""); err != nil {
		return errors.Wrapf(err, "failed to clone repo")
	}
	out.Green(ctx, " -> OK")
	out.Green(ctx, "Joined Team '%s'", team)
	out.Yellow(ctx, "Note: You still need to request access to decrypt any secret!")
	return nil
}
<commit_msg>Ask for git remote in local store setup (#491)<commit_after>package action

import (
	"context"
	"fmt"

	"github.com/blang/semver"
	"github.com/fatih/color"
	"github.com/justwatchcom/gopass/config"
	"github.com/justwatchcom/gopass/utils/ctxutil"
	"github.com/justwatchcom/gopass/utils/out"
	"github.com/justwatchcom/gopass/utils/pwgen/xkcdgen"
	"github.com/justwatchcom/gopass/utils/termwiz"
	"github.com/pkg/errors"
	"github.com/urfave/cli"
)

// Initialized returns an error if the store is not properly
// prepared.
func (s *Action) Initialized(ctx context.Context, c *cli.Context) error {
	if s.gpg.Version(ctx).LT(semver.Version{Major: 2, Minor: 0, Patch: 0}) {
		out.Red(ctx, "Warning: Using GPG 1.x. Using GPG 2.0 or later is highly recommended")
	}
	if !s.Store.Initialized() {
		if ctxutil.IsInteractive(ctx) {
			if ok, err := s.askForBool(ctx, "It seems you are new to gopass. Do you want to run the onboarding wizard?", true); err == nil && ok {
				return s.InitOnboarding(ctx, c)
			}
		}
		return s.exitError(ctx, ExitNotInitialized, nil, "password-store is not initialized. Try '%s init'", s.Name)
	}
	return nil
}

// Init a new password store with a first gpg id
func (s *Action) Init(ctx context.Context, c *cli.Context) error {
	path := c.String("path")
	alias := c.String("store")
	nogit := c.Bool("nogit")

	ctx = out.WithPrefix(ctx, "[init] ")
	out.Cyan(ctx, "Initializing a new password store ...")

	if err := s.init(ctx, alias, path, nogit, c.Args()...); err != nil {
		return s.exitError(ctx, ExitUnknown, err, "failed to initialized store: %s", err)
	}
	return nil
}

func (s *Action) init(ctx context.Context, alias, path string, nogit bool, keys ...string) error {
	if path == "" {
		if alias != "" {
			path = config.PwStoreDir(alias)
		} else {
			path = s.Store.Path()
		}
	}

	if len(keys) < 1 {
		nk, err := s.askForPrivateKey(ctx, color.CyanString("Please select a private key for encrypting secrets:"))
		if err != nil {
			return errors.Wrapf(err, "failed to read user input")
		}
		keys = []string{nk}
	}

	if err := s.Store.Init(ctx, alias, path, keys...); err != nil {
		return errors.Wrapf(err, "failed to init store '%s' at '%s'", alias, path)
	}

	if alias != "" && path != "" {
		if err := s.Store.AddMount(ctx, alias, path); err != nil {
			return errors.Wrapf(err, "failed to add mount '%s'", alias)
		}
	}

	if !nogit {
		sk := ""
		if len(keys) == 1 {
			sk = keys[0]
		}
		if err := s.gitInit(ctx, alias, sk); err != nil {
			out.Debug(ctx, "Stacktrace: %+v\n", err)
			out.Red(ctx, "Failed to init git: %s", err)
		}
	}

	out.Green(ctx, "Password store %s initialized for:", path)
	for _, recipient := range s.Store.ListRecipients(ctx, alias) {
		r := "0x" + recipient
		if kl, err := s.gpg.FindPublicKeys(ctx, recipient); err == nil && len(kl) > 0 {
			r = kl[0].OneLine()
		}
		out.Yellow(ctx, "  "+r)
	}

	// write config
	if err := s.cfg.Save(); err != nil {
		return s.exitError(ctx, ExitConfig, err, "failed to write config: %s", err)
	}

	return nil
}

// InitOnboarding will invoke the onboarding / setup wizard
func (s *Action) InitOnboarding(ctx context.Context, c *cli.Context) error {
	remote := c.String("remote")
	team := c.String("alias")
	create := c.Bool("create")
	name := c.String("name")
	email := c.String("email")

	ctx = out.AddPrefix(ctx, "[init] ")

	// check for existing GPG keypairs (private/secret keys). We need at least
	// one useable key pair. If none exists try to create one
	if !s.initHasUseablePrivateKeys(ctx) {
		out.Yellow(ctx, "No useable GPG keys. Generating new key pair")
		ctx := out.AddPrefix(ctx, "[gpg] ")
		out.Print(ctx, "Key generation may take up to a few minutes")
		if err := s.initCreatePrivateKey(ctx, name, email); err != nil {
			return errors.Wrapf(err, "failed to create new private key")
		}
	}

	// if a git remote and a team name are given attempt unattended team setup
	if remote != "" && team != "" {
		if create {
			return s.initCreateTeam(ctx, c, team, remote)
		}
		return s.initJoinTeam(ctx, c, team, remote)
	}

	// no flags given, run interactively
	choices := []string{
		"Local store",
		"Create a Team",
		"Join an existing Team",
	}
	act, sel := termwiz.GetSelection(ctx, "Store for secret", "<↑/↓> to change the selection, <→> to select, <ESC> to quit", choices)
	switch act {
	case "show":
		switch sel {
		case 0:
			return s.initLocal(ctx, c)
		case 1:
			return s.initCreateTeam(ctx, c, "", "")
		case 2:
			return s.initJoinTeam(ctx, c, "", "")
		}
	default:
		return fmt.Errorf("user aborted")
	}
	return nil
}

func (s *Action) initCreatePrivateKey(ctx context.Context, name, email string) error {
	out.Green(ctx, "Creating key pair ...")
	if name != "" && email != "" {
		ctx := out.AddPrefix(ctx, " ")
		passphrase := xkcdgen.Random()
		if err := s.gpg.CreatePrivateKeyBatch(ctx, name, email, passphrase); err != nil {
			return errors.Wrapf(err, "failed to create new private key in batch mode")
		}
		out.Green(ctx, "-> OK")
		out.Print(ctx, color.MagentaString("Passphrase: ")+color.HiGreenString(passphrase))
	} else {
		ctx := out.WithPrefix(ctx, " ")
		if err := s.gpg.CreatePrivateKey(ctx); err != nil {
			return errors.Wrapf(err, "failed to create new private key in interactive mode")
		}
		out.Green(ctx, "-> OK")
	}

	kl, err := s.gpg.ListPrivateKeys(ctx)
	if err != nil {
		return errors.Wrapf(err, "failed to list private keys")
	}
	klu := kl.UseableKeys()
	if len(klu) > 1 {
		out.Cyan(ctx, "WARNING: More than one private key detected. Make sure to communicate the right one")
		return nil
	}
	if len(klu) < 1 {
		out.Debug(ctx, "Private Keys: %+v", kl)
		return errors.New("failed to create a useable key pair")
	}
	key := klu[0]
	fn := key.ID() + ".pub.key"
	if err := s.gpg.ExportPublicKey(ctx, key.Fingerprint, fn); err != nil {
		return errors.Wrapf(err, "failed to export public key")
	}
	out.Cyan(ctx, "Public key exported to '%s'", fn)
	out.Green(ctx, "Done")
	return nil
}

func (s *Action) initHasUseablePrivateKeys(ctx context.Context) bool {
	kl, err := s.gpg.ListPrivateKeys(ctx)
	if err != nil {
		return false
	}
	return len(kl.UseableKeys()) > 0
}

// initLocal will initalize a local store, useful for local-only setups or as
// part of team setups to create the root store
func (s *Action) initLocal(ctx context.Context, c *cli.Context) error {
	ctx = out.AddPrefix(ctx, "[local] ")

	out.Print(ctx, "Initializing your local store ...")
	if err := s.init(out.WithHidden(ctx, true), "", "", false); err != nil {
		return errors.Wrapf(err, "failed to init local store")
	}
	out.Green(ctx, " -> OK")

	out.Print(ctx, "Configuring your local store ...")

	if want, err := s.askForBool(ctx, "Do you want to add a git remote?", false); err == nil && want {
		out.Print(ctx, "Configuring the git remote ...")
		remote, err := s.askForString(ctx, "Please enter the git remote for your store", "")
		if err != nil {
			return errors.Wrapf(err, "failed to read user input")
		}
		{
			ctx := out.WithHidden(ctx, true)
			if err := s.Store.Git(ctx, "", false, false, "remote", "add", "origin", remote); err != nil {
				return errors.Wrapf(err, "failed to add git remote")
			}
			if err := s.Store.Git(ctx, "", false, false, "push", "origin", "master"); err != nil {
				return errors.Wrapf(err, "failed to push to git remote")
			}
		}
		// autosync
		if want, err := s.askForBool(ctx, "Do you want to automatically push any changes to the git remote (if any)?", true); err == nil {
			s.cfg.Root.AutoSync = want
		}
	} else {
		s.cfg.Root.AutoSync = false
	}

	// noconfirm
	if want, err := s.askForBool(ctx, "Do you want to always confirm recipients when encrypting?", false); err == nil {
		s.cfg.Root.NoConfirm = !want
	}

	// save config
	if err := s.cfg.Save(); err != nil {
		return errors.Wrapf(err, "failed to save config")
	}

	out.Green(ctx, " -> OK")
	return nil
}

// initCreateTeam will create a local root store and a shared team store
func (s *Action) initCreateTeam(ctx context.Context, c *cli.Context, team, remote string) error {
	var err error

	out.Print(ctx, "Creating a new team ...")
	if err := s.initLocal(ctx, c); err != nil {
		return errors.Wrapf(err, "failed to create local store")
	}

	// name of the new team
	team, err = s.askForString(ctx, "Please enter the name of your team (may contain slashes)", team)
	if err != nil {
		return errors.Wrapf(err, "failed to read user input")
	}
	ctx = out.AddPrefix(ctx, "["+team+"] ")

	out.Print(ctx, "Initializing your shared store ...")
	if err := s.init(out.WithHidden(ctx, true), team, "", false); err != nil {
		return errors.Wrapf(err, "failed to init shared store")
	}
	out.Green(ctx, " -> OK")

	out.Print(ctx, "Configuring the git remote ...")
	remote, err = s.askForString(ctx, "Please enter the git remote for your shared store", remote)
	if err != nil {
		return errors.Wrapf(err, "failed to read user input")
	}
	{
		ctx := out.WithHidden(ctx, true)
		if err := s.Store.Git(ctx, team, false, false, "remote", "add", "origin", remote); err != nil {
			return errors.Wrapf(err, "failed to add git remote")
		}
		if err := s.Store.Git(ctx, team, false, false, "push", "origin", "master"); err != nil {
			return errors.Wrapf(err, "failed to push to git remote")
		}
	}
	out.Green(ctx, " -> OK")
	out.Green(ctx, "Created Team '%s'", team)
	return nil
}

// initJoinTeam will create a local root store and clone and existing store to
// a mount
func (s *Action) initJoinTeam(ctx context.Context, c *cli.Context, team, remote string) error {
	var err error

	out.Print(ctx, "Joining existing team ...")
	if err := s.initLocal(ctx, c); err != nil {
		return errors.Wrapf(err, "failed to create local store")
	}

	// name of the existing team
	team, err = s.askForString(ctx, "Please enter the name of your team (may contain slashes)", team)
	if err != nil {
		return err
	}
	ctx = out.AddPrefix(ctx, "["+team+"]")

	out.Print(ctx, "Configuring git remote ...")
	remote, err = s.askForString(ctx, "Please enter the git remote for your shared store", remote)
	if err != nil {
		return err
	}

	out.Print(ctx, "Cloning from the git remote ...")
	if err := s.clone(out.WithHidden(ctx, true), remote, team, ""); err != nil {
		return errors.Wrapf(err, "failed to clone repo")
	}
	out.Green(ctx, " -> OK")
	out.Green(ctx, "Joined Team '%s'", team)
	out.Yellow(ctx, "Note: You still need to request access to decrypt any secret!")
	return nil
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"io"
	"net/http"
	"straitjacket/engine"
	"strconv"
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
		sendResponse(status, &wsErrorResponse{StatusCode: strconv.Itoa(status), Error: reason.Error()}, w)
	},
}

type wsExecution struct {
	ID                       string
	output                   chan interface{}
	stdinR, stdoutR, stderrR io.Reader
	stdinW, stdoutW, stderrW io.WriteCloser
}

type executions struct {
	image      *engine.Image
	executions map[string]*wsExecution
	messages   chan interface{}
	wg         sync.WaitGroup
}

func (exes *executions) cleanup() {
	exes.wg.Wait()
	if exes.image != nil {
		exes.image.Remove()
	}
	close(exes.messages)
}

// ExecuteWSHandler handles websocket API connections.
func (ctx *Context) ExecuteWSHandler(res http.ResponseWriter, hreq *http.Request) {
	ws, err := upgrader.Upgrade(res, hreq, nil)
	if err != nil {
		// error response is written by the Upgrader callback defined above
		return
	}
	defer ws.Close()
	// 1024 fudge value is for the other input params like language
	ws.SetReadLimit((int64)(ctx.MaxSourceSize + ctx.MaxStdinSize + 1024))
	// SetReadTimeout ?

	exes := &executions{
		executions: map[string]*wsExecution{},
		messages:   make(chan interface{}),
	}
	go exes.write(ctx.logger(hreq), ws)
	defer exes.cleanup()

	for {
		req := wsRequest{}
		err = ws.ReadJSON(&req)
		if _, ok := err.(*websocket.CloseError); ok {
			break
		}
		if err != nil {
			exes.messages <- wsError(nil, err)
			break
		}

		ctx.logger(hreq).WithFields(logrus.Fields{
			"request": req,
		}).Info("processing wsrequest")

		exes.process(ctx, &req)
	}
}

type wsProtocolError struct {
	msg string
}

func (e *wsProtocolError) Error() string {
	return e.msg
}

func wsError(id *string, err error) *wsErrorResponse {
	statusCode := "500"
	if _, ok := err.(*wsProtocolError); ok {
		statusCode = "400"
	}
	return &wsErrorResponse{
		ID:         id,
		StatusCode: statusCode,
		Error:      err.Error(),
	}
}

func (exes *executions) process(ctx *Context, req *wsRequest) {
	if req.Compile != nil {
		compileResult, err := exes.compile(ctx, req.Compile)
		if err == nil {
			exes.messages <- buildResult(&engine.RunResult{CompileStep: compileResult}, nil, nil)
		} else {
			exes.messages <- wsError(nil, err)
		}
	}
	if req.Run != nil {
		if exes.image == nil {
			exes.messages <- wsError(&req.Run.ID, &wsProtocolError{"must compile before running"})
		} else {
			execution := exes.newExecution(ctx, req.Run)
			execution.run(ctx, &exes.wg, req.Run, exes.image)
		}
	}
	if req.Write != nil {
		execution := exes.executions[req.Write.ID]
		if execution == nil {
			exes.messages <- wsError(&req.Write.ID, &wsProtocolError{"specified id does not exist"})
		} else {
			execution.stdinW.Write([]byte(req.Write.Stdin))
		}
	}
	if req.Close != nil {
		execution := exes.executions[req.Close.ID]
		if execution == nil {
			exes.messages <- wsError(&req.Write.ID, &wsProtocolError{"specified id does not exist"})
		} else {
			execution.stdinW.Close()
		}
	}
}

func (exes *executions) write(logger *logrus.Entry, ws *websocket.Conn) {
	for msg := range exes.messages {
		logger.WithFields(logrus.Fields{"response": msg}).Info("response")
		if ws.WriteJSON(msg) != nil {
			break
		}
	}
}

func (exes *executions) compile(ctx *Context, req *wsCompileRequest) (*engine.ExecutionResult, error) {
	// remove any previous image
	if exes.image != nil {
		exes.image.Remove()
	}

	if req == nil || req.Language == "" || req.Source == "" {
		return nil, fmt.Errorf("required parameters are: language, source")
	}

	if req.Timeout == 0 {
		req.Timeout = ctx.DefaultTimeout
	}

	if len(req.Source) > ctx.MaxSourceSize {
		return nil, fmt.Errorf("source code is too large, max size: %d bytes", ctx.MaxSourceSize)
	}
	language := ctx.Engine.FindLanguage(req.Language)
	if language == nil {
		return nil, fmt.Errorf("unsupported language '%s'", req.Language)
	}

	image, res, err := language.Compile(req.Timeout, req.Source)
	exes.image = image
	return res, err
}

func (exes *executions) newExecution(ctx *Context, req *wsRunRequest) *wsExecution {
	if req.Timeout == 0 {
		req.Timeout = ctx.DefaultTimeout
	}
	exe := &wsExecution{ID: req.ID, output: exes.messages}
	exe.stdinR, exe.stdinW = io.Pipe()
	exe.stdoutR, exe.stdoutW = io.Pipe()
	exe.stderrR, exe.stderrW = io.Pipe()
	exes.executions[req.ID] = exe
	return exe
}

func (exe *wsExecution) run(ctx *Context, wg *sync.WaitGroup, req *wsRunRequest, image *engine.Image) {
	wg.Add(3)
	go func() { io.Copy(&wsWriter{exe.ID, exe.output, "stdout"}, exe.stdoutR); wg.Done() }()
	go func() { io.Copy(&wsWriter{exe.ID, exe.output, "stderr"}, exe.stderrR); wg.Done() }()
	go func() {
		defer func() {
			exe.stdoutW.Close()
			exe.stderrW.Close()
			wg.Done()
		}()
		runStep, err := image.Run(&engine.RunOptions{
			Stdin:         exe.stdinR,
			Stdout:        exe.stdoutW,
			Stderr:        exe.stderrW,
			Timeout:       req.Timeout,
			MaxOutputSize: ctx.MaxOutputSize,
		})
		if err == nil {
			result := buildResult(&engine.RunResult{RunStep: runStep}, nil, nil)
			result.ID = exe.ID
			exe.output <- &result
		} else {
			exe.output <- wsError(&exe.ID, err)
		}
	}()
}

type wsWriter struct {
	ID     string
	output chan interface{}
	stream string
}

func (wsWriter *wsWriter) Write(p []byte) (int, error) {
	message := struct {
		ID     string `json:"id"`
		Stream string `json:"stream"`
		Output string `json:"output"`
	}{wsWriter.ID, wsWriter.stream, string(p)}
	wsWriter.output <- &message
	return len(p), nil
}
<commit_msg>wait for messages to be written before closing the websocket<commit_after>package handlers

import (
	"fmt"
	"io"
	"net/http"
	"straitjacket/engine"
	"strconv"
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
		sendResponse(status, &wsErrorResponse{StatusCode: strconv.Itoa(status), Error: reason.Error()}, w)
	},
}

type wsExecution struct {
	ID                       string
	output                   chan interface{}
	stdinR, stdoutR, stderrR io.Reader
	stdinW, stdoutW, stderrW io.WriteCloser
}

type executions struct {
	image      *engine.Image
	executions map[string]*wsExecution
	messages   chan interface{}
	wg         sync.WaitGroup
	writerWg   sync.WaitGroup
}

func (exes *executions) cleanup() {
	exes.wg.Wait()
	if exes.image != nil {
		exes.image.Remove()
	}
	close(exes.messages)
	exes.writerWg.Wait()
}

// ExecuteWSHandler handles websocket API connections.
func (ctx *Context) ExecuteWSHandler(res http.ResponseWriter, hreq *http.Request) {
	ws, err := upgrader.Upgrade(res, hreq, nil)
	if err != nil {
		// error response is written by the Upgrader callback defined above
		return
	}
	defer ws.Close()
	// 1024 fudge value is for the other input params like language
	ws.SetReadLimit((int64)(ctx.MaxSourceSize + ctx.MaxStdinSize + 1024))
	// SetReadTimeout ?

	exes := &executions{
		executions: map[string]*wsExecution{},
		messages:   make(chan interface{}),
	}
	exes.write(ctx.logger(hreq), ws)
	defer exes.cleanup()

	for {
		req := wsRequest{}
		err = ws.ReadJSON(&req)
		if _, ok := err.(*websocket.CloseError); ok {
			break
		}
		if err != nil {
			exes.messages <- wsError(nil, err)
			break
		}

		ctx.logger(hreq).WithFields(logrus.Fields{
			"request": req,
		}).Info("processing wsrequest")

		exes.process(ctx, &req)
	}
}

type wsProtocolError struct {
	msg string
}

func (e *wsProtocolError) Error() string {
	return e.msg
}

func wsError(id *string, err error) *wsErrorResponse {
	statusCode := "500"
	if _, ok := err.(*wsProtocolError); ok {
		statusCode = "400"
	}
	return &wsErrorResponse{
		ID:         id,
		StatusCode: statusCode,
		Error:      err.Error(),
	}
}

func (exes *executions) process(ctx *Context, req *wsRequest) {
	if req.Compile != nil {
		compileResult, err := exes.compile(ctx, req.Compile)
		if err == nil {
			exes.messages <- buildResult(&engine.RunResult{CompileStep: compileResult}, nil, nil)
		} else {
			exes.messages <- wsError(nil, err)
		}
	}
	if req.Run != nil {
		if exes.image == nil {
			exes.messages <- wsError(&req.Run.ID, &wsProtocolError{"must compile before running"})
		} else {
			execution := exes.newExecution(ctx, req.Run)
			execution.run(ctx, &exes.wg, req.Run, exes.image)
		}
	}
	if req.Write != nil {
		execution := exes.executions[req.Write.ID]
		if execution == nil {
			exes.messages <- wsError(&req.Write.ID, &wsProtocolError{"specified id does not exist"})
		} else {
			execution.stdinW.Write([]byte(req.Write.Stdin))
		}
	}
	if req.Close != nil {
		execution := exes.executions[req.Close.ID]
		if execution == nil {
			exes.messages <- wsError(&req.Write.ID, &wsProtocolError{"specified id does not exist"})
		} else {
			execution.stdinW.Close()
		}
	}
}

func (exes *executions) write(logger *logrus.Entry, ws *websocket.Conn) {
	exes.writerWg.Add(1)
	go func() {
		defer exes.writerWg.Done()
		for msg := range exes.messages {
			logger.WithFields(logrus.Fields{"response": msg}).Info("response")
			if ws.WriteJSON(msg) != nil {
				break
			}
		}
	}()
}

func (exes *executions) compile(ctx *Context, req *wsCompileRequest) (*engine.ExecutionResult, error) {
	// remove any previous image
	if exes.image != nil {
		exes.image.Remove()
	}

	if req == nil || req.Language == "" || req.Source == "" {
		return nil, fmt.Errorf("required parameters are: language, source")
	}

	if req.Timeout == 0 {
		req.Timeout = ctx.DefaultTimeout
	}

	if len(req.Source) > ctx.MaxSourceSize {
		return nil, fmt.Errorf("source code is too large, max size: %d bytes", ctx.MaxSourceSize)
	}
	language := ctx.Engine.FindLanguage(req.Language)
	if language == nil {
		return nil, fmt.Errorf("unsupported language '%s'", req.Language)
	}

	image, res, err := language.Compile(req.Timeout, req.Source)
	exes.image = image
	return res, err
}

func (exes *executions) newExecution(ctx *Context, req *wsRunRequest) *wsExecution {
	if req.Timeout == 0 {
		req.Timeout = ctx.DefaultTimeout
	}
	exe := &wsExecution{ID: req.ID, output: exes.messages}
	exe.stdinR, exe.stdinW = io.Pipe()
	exe.stdoutR, exe.stdoutW = io.Pipe()
	exe.stderrR, exe.stderrW = io.Pipe()
	exes.executions[req.ID] = exe
	return exe
}

func (exe *wsExecution) run(ctx *Context, wg *sync.WaitGroup, req *wsRunRequest, image *engine.Image) {
	wg.Add(3)
	go func() { io.Copy(&wsWriter{exe.ID, exe.output, "stdout"}, exe.stdoutR); wg.Done() }()
	go func() { io.Copy(&wsWriter{exe.ID, exe.output, "stderr"}, exe.stderrR); wg.Done() }()
	go func() {
		defer func() {
			exe.stdoutW.Close()
			exe.stderrW.Close()
			wg.Done()
		}()
		runStep, err := image.Run(&engine.RunOptions{
			Stdin:         exe.stdinR,
			Stdout:        exe.stdoutW,
			Stderr:        exe.stderrW,
			Timeout:       req.Timeout,
			MaxOutputSize: ctx.MaxOutputSize,
		})
		if err == nil {
			result := buildResult(&engine.RunResult{RunStep: runStep}, nil, nil)
			result.ID = exe.ID
			exe.output <- &result
		} else {
			exe.output <- wsError(&exe.ID, err)
		}
	}()
}

type wsWriter struct {
	ID     string
	output chan interface{}
	stream string
}

func (wsWriter *wsWriter) Write(p []byte) (int, error) {
	message := struct {
		ID     string `json:"id"`
		Stream string `json:"stream"`
		Output string `json:"output"`
	}{wsWriter.ID, wsWriter.stream, string(p)}
	wsWriter.output <- &message
	return len(p), nil
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/thatguystone/cog"
	"github.com/thatguystone/cog/check"
)

type testCfger struct {
	One int
	Two int
}

func (t *testCfger) Validate(_ *Cfg, es *cog.Errors) {
	if t.One != 1 {
		es.Add(fmt.Errorf("`One` != 1 (==%d)", t.One))
	}

	if t.Two != 2 {
		es.Add(fmt.Errorf("`Two` != 2 (==%d)", t.Two))
	}
}

type testListener struct {
	Addr string
}

func (t *testListener) Validate(c *Cfg, es *cog.Errors) {
	c.ResolveListen(&t.Addr, es)
}

func TestMain(m *testing.M) {
	check.Main(m)
}

func testEC2MetadataServer() *httptest.Server {
	mux := http.NewServeMux()
	mux.HandleFunc("/latest/meta-data/local-ipv4",
		func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte("EC2PRIV"))
		})

	return httptest.NewServer(mux)
}

func TestRegister(t *testing.T) {
	c := check.New(t)

	Register("TestRegister", func() Configer { return nil })
	c.Panic(func() {
		Register("TestRegister", nil)
	})
}

func TestNewCoverage(t *testing.T) {
	check.New(t)

	Register("TestNewCoverage", func() Configer { return nil })
	New()
}

func TestResolveListen(t *testing.T) {
	c := check.New(t)

	ec2md := testEC2MetadataServer()
	defer ec2md.Close()

	tests := []struct {
		in      string
		out     string
		err     bool
		ec2Addr string
	}{
		{
			in:  "test:80",
			out: "test:80",
		},
		{
			in:  "ec2:80",
			out: "EC2PRIV:80",
		},
		{
			in:  "ec2",
			err: true,
		},
		{
			in:      "ec2:80",
			ec2Addr: "127.0.0.1:9999999",
			err:     true,
		},
		{
			in:  "",
			err: true,
		},
	}

	for _, test := range tests {
		file := "config.json"
		c.FS.SWriteFile(file,
			fmt.Sprintf(`{"Listen": {"Addr": "%s"}}`,
				test.in))

		l := &testListener{}
		cfg := Cfg{
			Modules: Modules{
				"Listen": l,
			},
		}

		if test.ec2Addr != "" {
			cfg.ec2MetadataBase = test.ec2Addr
		} else {
			cfg.ec2MetadataBase = ec2md.URL
		}

		err := cfg.LoadAndValidate(c.FS.Path(file))
		if test.err {
			c.Error(err)
		} else {
			c.NotError(err)
			c.Equal(test.out, l.Addr)
		}
	}
}
<commit_msg>Drop unused struct in config test<commit_after>package config

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/thatguystone/cog"
	"github.com/thatguystone/cog/check"
)

type testListener struct {
	Addr string
}

func (t *testListener) Validate(c *Cfg, es *cog.Errors) {
	c.ResolveListen(&t.Addr, es)
}

func TestMain(m *testing.M) {
	check.Main(m)
}

func testEC2MetadataServer() *httptest.Server {
	mux := http.NewServeMux()
	mux.HandleFunc("/latest/meta-data/local-ipv4",
		func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte("EC2PRIV"))
		})

	return httptest.NewServer(mux)
}

func TestRegister(t *testing.T) {
	c := check.New(t)

	Register("TestRegister", func() Configer { return nil })
	c.Panic(func() {
		Register("TestRegister", nil)
	})
}

func TestNewCoverage(t *testing.T) {
	check.New(t)

	Register("TestNewCoverage", func() Configer { return nil })
	New()
}

func TestResolveListen(t *testing.T) {
	c := check.New(t)

	ec2md := testEC2MetadataServer()
	defer ec2md.Close()

	tests := []struct {
		in      string
		out     string
		err     bool
		ec2Addr string
	}{
		{
			in:  "test:80",
			out: "test:80",
		},
		{
			in:  "ec2:80",
			out: "EC2PRIV:80",
		},
		{
			in:  "ec2",
			err: true,
		},
		{
			in:      "ec2:80",
			ec2Addr: "127.0.0.1:9999999",
			err:     true,
		},
		{
			in:  "",
			err: true,
		},
	}

	for _, test := range tests {
		file := "config.json"
		c.FS.SWriteFile(file,
			fmt.Sprintf(`{"Listen": {"Addr": "%s"}}`,
				test.in))

		l := &testListener{}
		cfg := Cfg{
			Modules: Modules{
				"Listen": l,
			},
		}

		if test.ec2Addr != "" {
			cfg.ec2MetadataBase = test.ec2Addr
		} else {
			cfg.ec2MetadataBase = ec2md.URL
		}

		err := cfg.LoadAndValidate(c.FS.Path(file))
		if test.err {
			c.Error(err)
		} else {
			c.NotError(err)
			c.Equal(test.out, l.Addr)
		}
	}
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"testing"
)

func assertEqual(expect, actual interface{}, t *testing.T) {
	if !reflect.DeepEqual(expect, actual) {
		t.Errorf("Items not equal:\nexpected %q\nhave     %q\n", expect, actual)
	}
}

func configFile(content string) (*os.File, error) {
	file, err := ioutil.TempFile("", ".doget.yml")
	if err != nil {
		return nil, err
	}

	if _, err := file.Write([]byte(content)); err != nil {
		os.Remove(file.Name())
		return nil, err
	}
	if err := file.Close(); err != nil {
		os.Remove(file.Name())
		return nil, err
	}

	return file, nil
}

func Test_search_path(t *testing.T) {
	path := SearchPath()
	assertEqual(4, len(path), t)
}

func Test_empty_source(t *testing.T) {
	config := Empty()
	assertEqual("", config.Source, t)
}

func Test_default_source(t *testing.T) {
	config := Default()
	assertEqual("<default>", config.Source, t)
}

func Test_parse_only_nonexisting_files_does_return_error(t *testing.T) {
	_, err := Empty().Merge("doesNotExist", "doesNotExist2")
	assertEqual(fmt.Errorf("None of the given config files exist: [\"doesNotExist\" \"doesNotExist2\"]"), err, t)
}

func Test_parse_nonexisting_and_existing_file(t *testing.T) {
	file, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(file.Name())

	config, err := Empty().Merge("doesNotExist", file.Name())
	if nil != err {
		t.Errorf("Expected no error, got %v", err)
		return
	}

	assertEqual(file.Name(), config.Source, t)
}

func Test_can_parse_empty_file(t *testing.T) {
	file, err := configFile("")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(file.Name())

	Empty().Merge(file.Name())
}

func Test_single_file_source(t *testing.T) {
	file, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(file.Name())

	config, _ := Empty().Merge(file.Name())
	assertEqual(file.Name(), config.Source, t)
}

func Test_multiple_file_sources(t *testing.T) {
	global, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(global.Name())

	user, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(user.Name())

	config, _ := Empty().Merge(global.Name(), user.Name())
	assertEqual(global.Name()+";"+user.Name(), config.Source, t)
}

func Test_same_file_sources_multiple_times(t *testing.T) {
	global, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(global.Name())

	config, _ := Empty().Merge(global.Name(), global.Name())
	assertEqual(global.Name(), config.Source, t)
}

func Test_single_file_repositories(t *testing.T) {
	file, err := configFile(`
repositories:
  github.com:
    url: https://github.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(file.Name())

	config, err := Empty().Merge(file.Name())
	if err != nil {
		t.Errorf("Cannot parse config file: %s", err.Error())
		return
	}

	assertEqual("https://github.com/...", config.Repositories["github.com"]["url"], t)
}

func Test_adding_a_repository(t *testing.T) {
	global, err := configFile(`
repositories:
  github.com:
    url: https://github.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(global.Name())

	user, err := configFile(`
repositories:
  example.com:
    url: https://example.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(user.Name())

	config, _ := Empty().Merge(global.Name(), user.Name())
	assertEqual("https://github.com/...", config.Repositories["github.com"]["url"], t)
	assertEqual("https://example.com/...", config.Repositories["example.com"]["url"], t)
}

func Test_overwriting_a_repository(t *testing.T) {
	global, err := configFile(`
repositories:
  github.com:
    url: https://github.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(global.Name())

	user, err := configFile(`
repositories:
  github.com:
    url: https://github.example.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(user.Name())

	config, _ := Empty().Merge(global.Name(), user.Name())
	assertEqual("https://github.example.com/...", config.Repositories["github.com"]["url"], t)
}
<commit_msg>Test MustMerge() and Merge()<commit_after>package config

import (
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"testing"
)

func assertEqual(expect, actual interface{}, t *testing.T) {
	if !reflect.DeepEqual(expect, actual) {
		t.Errorf("Items not equal:\nexpected %q\nhave     %q\n", expect, actual)
	}
}

func configFile(content string) (*os.File, error) {
	file, err := ioutil.TempFile("", ".doget.yml")
	if err != nil {
		return nil, err
	}

	if _, err := file.Write([]byte(content)); err != nil {
		os.Remove(file.Name())
		return nil, err
	}
	if err := file.Close(); err != nil {
		os.Remove(file.Name())
		return nil, err
	}

	return file, nil
}

func Test_search_path(t *testing.T) {
	path := SearchPath()
	assertEqual(4, len(path), t)
}

func Test_empty_source(t *testing.T) {
	config := Empty()
	assertEqual("", config.Source, t)
}

func Test_default_source(t *testing.T) {
	config := Default()
	assertEqual("<default>", config.Source, t)
}

func Test_merge_only_nonexisting_files_works(t *testing.T) {
	_, err := Empty().Merge("doesNotExist", "doesNotExist2")
	assertEqual(nil, err, t)
}

func Test_must_merge_only_nonexisting_files_yields_error(t *testing.T) {
	_, err := Empty().MustMerge("doesNotExist", "doesNotExist2")
	assertEqual(fmt.Errorf("None of the given config files exist: [\"doesNotExist\" \"doesNotExist2\"]"), err, t)
}

func Test_parse_nonexisting_and_existing_file(t *testing.T) {
	file, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(file.Name())

	config, err := Empty().Merge("doesNotExist", file.Name())
	if nil != err {
		t.Errorf("Expected no error, got %v", err)
		return
	}

	assertEqual(file.Name(), config.Source, t)
}

func Test_can_parse_empty_file(t *testing.T) {
	file, err := configFile("")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(file.Name())

	Empty().Merge(file.Name())
}

func Test_single_file_source(t *testing.T) {
	file, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(file.Name())

	config, _ := Empty().Merge(file.Name())
	assertEqual(file.Name(), config.Source, t)
}

func Test_multiple_file_sources(t *testing.T) {
	global, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(global.Name())

	user, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(user.Name())

	config, _ := Empty().Merge(global.Name(), user.Name())
	assertEqual(global.Name()+";"+user.Name(), config.Source, t)
}

func Test_same_file_sources_multiple_times(t *testing.T) {
	global, err := configFile("repositories:")
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(global.Name())

	config, _ := Empty().Merge(global.Name(), global.Name())
	assertEqual(global.Name(), config.Source, t)
}

func Test_single_file_repositories(t *testing.T) {
	file, err := configFile(`
repositories:
  github.com:
    url: https://github.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(file.Name())

	config, err := Empty().Merge(file.Name())
	if err != nil {
		t.Errorf("Cannot parse config file: %s", err.Error())
		return
	}

	assertEqual("https://github.com/...", config.Repositories["github.com"]["url"], t)
}

func Test_adding_a_repository(t *testing.T) {
	global, err := configFile(`
repositories:
  github.com:
    url: https://github.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(global.Name())

	user, err := configFile(`
repositories:
  example.com:
    url: https://example.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(user.Name())

	config, _ := Empty().Merge(global.Name(), user.Name())
	assertEqual("https://github.com/...", config.Repositories["github.com"]["url"], t)
	assertEqual("https://example.com/...", config.Repositories["example.com"]["url"], t)
}

func Test_overwriting_a_repository(t *testing.T) {
	global, err := configFile(`
repositories:
  github.com:
    url: https://github.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(global.Name())

	user, err := configFile(`
repositories:
  github.com:
    url: https://github.example.com/...
`)
	if err != nil {
		t.Errorf("Cannot create config file: %s", err.Error())
		return
	}
	defer os.Remove(user.Name())

	config, _ := Empty().Merge(global.Name(), user.Name())
	assertEqual("https://github.example.com/...", config.Repositories["github.com"]["url"], t)
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package featuretests

import (
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/component/all"
	jujutesting "github.com/juju/juju/juju/testing"
)

func initProcessesSuites() {
	if err := all.RegisterForServer(); err != nil {
		panic(err)
	}

	gc.Suite(&processesHookContextSuite{})
	gc.Suite(&processesWorkerSuite{})
	gc.Suite(&processesCmdJujuSuite{})
}

type processesBaseSuite struct {
	jujutesting.RepoSuite
}

type processesHookContextSuite struct {
	processesBaseSuite
}

func (s *processesHookContextSuite) TestRegister(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestLaunch(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestInfo(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestUnregister(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestDestroy(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type processesWorkerSuite struct {
	processesBaseSuite
}

func (s *processesWorkerSuite) TestSetStatus(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesWorkerSuite) TestCleanUp(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type processesCmdJujuSuite struct {
	processesBaseSuite
}

func (s *processesCmdJujuSuite) TestStatus(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}
<commit_msg>Add a testing helper.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package featuretests

import (
	"github.com/juju/names"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/component/all"
	jujutesting "github.com/juju/juju/juju/testing"
	"github.com/juju/juju/state"
)

func initProcessesSuites() {
	if err := all.RegisterForServer(); err != nil {
		panic(err)
	}

	gc.Suite(&processesHookContextSuite{})
	gc.Suite(&processesWorkerSuite{})
	gc.Suite(&processesCmdJujuSuite{})
}

type processesBaseSuite struct {
	jujutesting.JujuConnSuite
}

func (s *processesBaseSuite) addUnit(c *gc.C, charmName, serviceName string) (names.CharmTag, *state.Unit) {
	ch := s.AddTestingCharm(c, charmName)

	svc := s.AddTestingService(c, serviceName, ch)
	unit, err := svc.AddUnit()
	c.Assert(err, jc.ErrorIsNil)
	err = s.State.AssignUnit(unit, state.AssignCleanEmpty)
	c.Assert(err, jc.ErrorIsNil)

	charmTag := ch.Tag().(names.CharmTag)
	return charmTag, unit
}

type processesHookContextSuite struct {
	processesBaseSuite
}

// TODO(ericsnow) Add a test specifically for each supported plugin
// (e.g. docker)?

func (s *processesHookContextSuite) TestRegister(c *gc.C) {
	//s.addUnit(c, "proc-hooks", "a-service")
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestLaunch(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestInfo(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestUnregister(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestDestroy(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type processesWorkerSuite struct {
	processesBaseSuite
}

func (s *processesWorkerSuite) TestSetStatus(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesWorkerSuite) TestCleanUp(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type processesCmdJujuSuite struct {
	processesBaseSuite
}

func (s *processesCmdJujuSuite) TestStatus(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}
<|endoftext|>


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

import (
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +k8s:deepcopy-gen=true
// JobStatus represents the current observed state of the training Job.
type JobStatus struct {
	// Conditions is an array of current observed job conditions.
	Conditions []JobCondition `json:"conditions"`

	// ReplicaStatuses is map of ReplicaType and ReplicaStatus,
	// specifies the status of each replica.
	ReplicaStatuses map[ReplicaType]*ReplicaStatus `json:"replicaStatuses"`

	// Represents time when the job was acknowledged by the job controller.
	// It is not guaranteed to be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	StartTime *metav1.Time `json:"startTime,omitempty"`

	// Represents time when the job was completed. It is not guaranteed to
	// be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	CompletionTime *metav1.Time `json:"completionTime,omitempty"`

	// Represents last time when the job was reconciled. It is not guaranteed to
	// be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	LastReconcileTime *metav1.Time `json:"lastReconcileTime,omitempty"`
}

// ReplicaType represents the type of the replica. Each operator needs to define its
// own set of ReplicaTypes.
type ReplicaType string

// ReplicaStatus represents the current observed state of the replica.
type ReplicaStatus struct {
	// The number of actively running pods.
	Active int32 `json:"active,omitempty"`

	// The number of pods which reached phase Succeeded.
	Succeeded int32 `json:"succeeded,omitempty"`

	// The number of pods which reached phase Failed.
	Failed int32 `json:"failed,omitempty"`
}

// +k8s:deepcopy-gen=true
// ReplicaSpec is a description of the replica
type ReplicaSpec struct {
	// Replicas is the desired number of replicas of the given template.
	// If unspecified, defaults to 1.
	Replicas *int32 `json:"replicas,omitempty"`

	// Template is the object that describes the pod that
	// will be created for this replica. RestartPolicy in PodTemplateSpec
	// will be overide by RestartPolicy in ReplicaSpec
	Template v1.PodTemplateSpec `json:"template,omitempty"`

	// Restart policy for all replicas within the job.
	// One of Always, OnFailure, Never and ExitCode.
	// Default to Never.
	RestartPolicy RestartPolicy `json:"restartPolicy,omitempty"`
}

// +k8s:deepcopy-gen=true
// JobCondition describes the state of the job at a certain point.
type JobCondition struct {
	// Type of job condition.
	Type JobConditionType `json:"type"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
	// The last time this condition was updated.
	LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}

// JobConditionType defines all kinds of types of JobStatus.
type JobConditionType string

const (
	// JobCreated means the job has been accepted by the system,
	// but one or more of the pods/services has not been started.
	// This includes time before pods being scheduled and launched.
	JobCreated JobConditionType = "Created"

	// JobRunning means all sub-resources (e.g. services/pods) of this job
	// have been successfully scheduled and launched.
	// The training is running without error.
	JobRunning JobConditionType = "Running"

	// JobRestarting means one or more sub-resources (e.g. services/pods) of this job
	// reached phase failed but maybe restarted according to it's restart policy
	// which specified by user in v1.PodTemplateSpec.
	// The training is freezing/pending.
	JobRestarting JobConditionType = "Restarting"

	// JobSucceeded means all sub-resources (e.g. services/pods) of this job
	// reached phase have terminated in success.
	// The training is complete without error.
	JobSucceeded JobConditionType = "Succeeded"

	// JobFailed means one or more sub-resources (e.g. services/pods) of this job
	// reached phase failed with no restarting.
	// The training has failed its execution.
	JobFailed JobConditionType = "Failed"
)

// CleanPodPolicy describes how to deal with pods when the job is finished.
type CleanPodPolicy string

const (
	CleanPodPolicyUndefined CleanPodPolicy = ""
	CleanPodPolicyAll       CleanPodPolicy = "All"
	CleanPodPolicyRunning   CleanPodPolicy = "Running"
	CleanPodPolicyNone      CleanPodPolicy = "None"
)

// RestartPolicy describes how the replicas should be restarted.
// Only one of the following restart policies may be specified.
// If none of the following policies is specified, the default one
// is RestartPolicyAlways.
type RestartPolicy string

const (
	RestartPolicyAlways    RestartPolicy = "Always"
	RestartPolicyOnFailure RestartPolicy = "OnFailure"
	RestartPolicyNever     RestartPolicy = "Never"

	// `ExitCode` policy means that user should add exit code by themselves,
	// The job operator will check these exit codes to
	// determine the behavior when an error occurs:
	// - 1-127: permanent error, do not restart.
	// - 128-255: retryable error, will restart the pod.
	RestartPolicyExitCode RestartPolicy = "ExitCode"
)

// +k8s:deepcopy-gen=true
// RunPolicy encapsulates various runtime policies of the distributed training
// job, for example how to clean up resources and how long the job can stay
// active.
type RunPolicy struct {
	// CleanPodPolicy defines the policy to kill pods after the job completes.
	// Default to Running.
	CleanPodPolicy *CleanPodPolicy `json:"cleanPodPolicy,omitempty"`

	// TTLSecondsAfterFinished is the TTL to clean up jobs.
	// It may take extra ReconcilePeriod seconds for the cleanup, since
	// reconcile gets called periodically.
	// Default to infinite.
	TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty"`

	// Specifies the duration in seconds relative to the startTime that the job may be active
	// before the system tries to terminate it; value must be positive integer.
	// +optional
	ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`

	// Optional number of retries before marking this job failed.
	// +optional
	BackoffLimit *int32 `json:"backoffLimit,omitempty"`

        // TODO - Implement Scheduling policy.
	// See https://github.com/kubeflow/tf-operator/issues/916#issuecomment-458729706
        // SchedulingPolicy *SchedulingPolicy `json:"schedulingPolicy,omitempty"`
}
<commit_msg>Added schedulingPolicy api. (#13)<commit_after>// Copyright 2018 The Kubeflow Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1

import (
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +k8s:deepcopy-gen=true
// JobStatus represents the current observed state of the training Job.
type JobStatus struct {
	// Conditions is an array of current observed job conditions.
	Conditions []JobCondition `json:"conditions"`

	// ReplicaStatuses is map of ReplicaType and ReplicaStatus,
	// specifies the status of each replica.
	ReplicaStatuses map[ReplicaType]*ReplicaStatus `json:"replicaStatuses"`

	// Represents time when the job was acknowledged by the job controller.
	// It is not guaranteed to be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	StartTime *metav1.Time `json:"startTime,omitempty"`

	// Represents time when the job was completed. It is not guaranteed to
	// be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	CompletionTime *metav1.Time `json:"completionTime,omitempty"`

	// Represents last time when the job was reconciled. It is not guaranteed to
	// be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	LastReconcileTime *metav1.Time `json:"lastReconcileTime,omitempty"`
}

// ReplicaType represents the type of the replica. Each operator needs to define its
// own set of ReplicaTypes.
type ReplicaType string

// ReplicaStatus represents the current observed state of the replica.
type ReplicaStatus struct {
	// The number of actively running pods.
	Active int32 `json:"active,omitempty"`

	// The number of pods which reached phase Succeeded.
	Succeeded int32 `json:"succeeded,omitempty"`

	// The number of pods which reached phase Failed.
	Failed int32 `json:"failed,omitempty"`
}

// +k8s:deepcopy-gen=true
// ReplicaSpec is a description of the replica
type ReplicaSpec struct {
	// Replicas is the desired number of replicas of the given template.
	// If unspecified, defaults to 1.
	Replicas *int32 `json:"replicas,omitempty"`

	// Template is the object that describes the pod that
	// will be created for this replica. RestartPolicy in PodTemplateSpec
	// will be overide by RestartPolicy in ReplicaSpec
	Template v1.PodTemplateSpec `json:"template,omitempty"`

	// Restart policy for all replicas within the job.
	// One of Always, OnFailure, Never and ExitCode.
	// Default to Never.
	RestartPolicy RestartPolicy `json:"restartPolicy,omitempty"`
}

// +k8s:deepcopy-gen=true
// JobCondition describes the state of the job at a certain point.
type JobCondition struct {
	// Type of job condition.
	Type JobConditionType `json:"type"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
	// The last time this condition was updated.
	LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}

// JobConditionType defines all kinds of types of JobStatus.
type JobConditionType string

const (
	// JobCreated means the job has been accepted by the system,
	// but one or more of the pods/services has not been started.
	// This includes time before pods being scheduled and launched.
	JobCreated JobConditionType = "Created"

	// JobRunning means all sub-resources (e.g. services/pods) of this job
	// have been successfully scheduled and launched.
	// The training is running without error.
	JobRunning JobConditionType = "Running"

	// JobRestarting means one or more sub-resources (e.g. services/pods) of this job
	// reached phase failed but maybe restarted according to it's restart policy
	// which specified by user in v1.PodTemplateSpec.
	// The training is freezing/pending.
	JobRestarting JobConditionType = "Restarting"

	// JobSucceeded means all sub-resources (e.g. services/pods) of this job
	// reached phase have terminated in success.
	// The training is complete without error.
	JobSucceeded JobConditionType = "Succeeded"

	// JobFailed means one or more sub-resources (e.g. services/pods) of this job
	// reached phase failed with no restarting.
	// The training has failed its execution.
	JobFailed JobConditionType = "Failed"
)

// CleanPodPolicy describes how to deal with pods when the job is finished.
type CleanPodPolicy string

const (
	CleanPodPolicyUndefined CleanPodPolicy = ""
	CleanPodPolicyAll       CleanPodPolicy = "All"
	CleanPodPolicyRunning   CleanPodPolicy = "Running"
	CleanPodPolicyNone      CleanPodPolicy = "None"
)

// RestartPolicy describes how the replicas should be restarted.
// Only one of the following restart policies may be specified.
// If none of the following policies is specified, the default one
// is RestartPolicyAlways.
type RestartPolicy string

const (
	RestartPolicyAlways    RestartPolicy = "Always"
	RestartPolicyOnFailure RestartPolicy = "OnFailure"
	RestartPolicyNever     RestartPolicy = "Never"

	// `ExitCode` policy means that user should add exit code by themselves,
	// The job operator will check these exit codes to
	// determine the behavior when an error occurs:
	// - 1-127: permanent error, do not restart.
	// - 128-255: retryable error, will restart the pod.
	RestartPolicyExitCode RestartPolicy = "ExitCode"
)

// +k8s:deepcopy-gen=true
// RunPolicy encapsulates various runtime policies of the distributed training
// job, for example how to clean up resources and how long the job can stay
// active.
type RunPolicy struct {
	// CleanPodPolicy defines the policy to kill pods after the job completes.
	// Default to Running.
	CleanPodPolicy *CleanPodPolicy `json:"cleanPodPolicy,omitempty"`

	// TTLSecondsAfterFinished is the TTL to clean up jobs.
	// It may take extra ReconcilePeriod seconds for the cleanup, since
	// reconcile gets called periodically.
	// Default to infinite.
	TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty"`

	// Specifies the duration in seconds relative to the startTime that the job may be active
	// before the system tries to terminate it; value must be positive integer.
	// +optional
	ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`

	// Optional number of retries before marking this job failed.
	// +optional
	BackoffLimit *int32 `json:"backoffLimit,omitempty"`

	// SchedulingPolicy defines the policy related to scheduling, e.g. gang-scheduling
	// +optional
	SchedulingPolicy *SchedulingPolicy `json:"schedulingPolicy,omitempty"`
}

// SchedulingPolicy encapsulates various scheduling policies of the distributed training
// job, for example `minAvailable` for gang-scheduling.
type SchedulingPolicy struct {
	MinAvailable *int32 `json:"minAvailable,omitempty"`
}
<|endoftext|>


<commit_before>/*
Package router implements wrappers around
github.com/julienschmidt/httprouter.Router to enable the use of
golang.org/x/net/context.Context in endpoint handlers.

A simple example looks like this:

	package main

	import (
		"fmt"
		"log"
		"net/http"

		"github.com/teepark/contextual"
		"github.com/teepark/contextual/router"
		"golang.org/x/net/context"
	)

	func index(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Welcome!")
	}

	func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		params := router.Params(ctx)
		fmt.Fprintf(w, "Hello, %s!\n", params.ByName("name"))
	}

	func main() {
		router := router.New(nil, nil) // take defaults
		router.GET("/", contextual.HandlerFunc(index))
		router.GET("/hello/:name", contextual.HandlerFunc(hello))

		log.Fatal(http.ListenAndServe(":8080", router))
	}
*/
package router

import (
	"net/http"

	"github.com/julienschmidt/httprouter"
	"github.com/teepark/contextual"
	"github.com/teepark/contextual/middleware"
	"golang.org/x/net/context"
)

// ParamKey is the key in a context.Context under which the httprouter.Params
// will be stored.
const ParamKey = "params"

// Router wraps an httprouter.Router to accept contextual.Handlers. For
// each request it adds the httprouter.Params to the context as
// Value(router.ParamKey), then passes it to the appropriate (optionally
// middleware-wrapped) handler.
type Router struct {
	router *httprouter.Router
	mw     middleware.Middleware
}

// New creates a new Router around a given httprouter.Router.
// All arguments may be nil, in which case the Router would wrap a
// Router created with httprouter.New() and there would be no
// middleware applied.
func New(router *httprouter.Router, mw middleware.Middleware) *Router {
	if router == nil {
		router = httprouter.New()
	}

	return &Router{
		router: router,
		mw:     mw,
	}
}

// ServeHTTP implements http.Handler
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	router.router.ServeHTTP(w, r)
}

// ServeFiles serves files from a given filesystem root. The path must
// end with "/*filepath". See httprouter's documentation for details.
func (router *Router) ServeFiles(path string, root http.FileSystem) {
	router.router.ServeFiles(path, root)
}

// Handle adds a method/path handler with a context.Context argument
func (router *Router) Handle(method, path string, handle contextual.Handler) {
	router.router.Handle(method, path, handlerShim(router, handle))
}

// Handle adds an http.Handler for a method/path
func (router *Router) Handler(method, path string, handler http.Handler) {
	router.router.Handler(method, path, handler)
}

// handlerFunc adds an http.HandlerFunc for a method/path
func (router *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
	router.router.HandlerFunc(method, path, handler)
}

// GET is a shortcut for Handle("GET", ...)
func (router *Router) GET(path string, handle contextual.Handler) {
	router.Handle("GET", path, handle)
}

// HEAD is a shortcut for Handle("HEAD", ...)
func (router *Router) HEAD(path string, handle contextual.Handler) {
	router.Handle("HEAD", path, handle)
}

// POST is a shortcut for Handle("POST", ...)
func (router *Router) POST(path string, handle contextual.Handler) {
	router.Handle("POST", path, handle)
}

// PUT is a shortcut for Handle("PUT", ...)
func (router *Router) PUT(path string, handle contextual.Handler) {
	router.Handle("PUT", path, handle)
}

// DELETE is a shortcut for Handle("DELETE", ...)
func (router *Router) DELETE(path string, handle contextual.Handler) {
	router.Handle("DELETE", path, handle)
}

// OPTIONS is a shortcut for Handle("OPTIONS", ...)
func (router *Router) OPTIONS(path string, handle contextual.Handler) {
	router.Handle("OPTIONS", path, handle)
}

// PATCH is a shortcut for Handle("PATCH", ...)
func (router *Router) PATCH(path string, handle contextual.Handler) {
	router.Handle("PATCH", path, handle)
}

func handlerShim(router *Router, handle contextual.Handler) httprouter.Handle {
	if router.mw != nil {
		handle = router.mw(handle)
	}

	return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		ctx := context.WithValue(context.Background(), ParamKey, p)
		handle.Serve(ctx, w, r)
	})
}

// Params retrieves the httprouter Params from a context
func Params(c context.Context) httprouter.Params {
	p := c.Value(ParamKey)
	if p == nil {
		return nil
	}
	return p.(httprouter.Params)
}
<commit_msg>fix docs<commit_after>/*
Package router implements wrappers around
github.com/julienschmidt/httprouter.Router to enable the use of
golang.org/x/net/context.Context in endpoint handlers.

A simple example looks like this:

	package main

	import (
		"fmt"
		"log"
		"net/http"

		"github.com/teepark/contextual"
		"github.com/teepark/contextual/router"
		"golang.org/x/net/context"
	)

	func index(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Welcome!")
	}

	func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		params := router.Params(ctx)
		fmt.Fprintf(w, "Hello, %s!\n", params.ByName("name"))
	}

	func main() {
		router := router.New(nil, nil) // take defaults
		router.GET("/", contextual.HandlerFunc(index))
		router.GET("/hello/:name", contextual.HandlerFunc(hello))

		log.Fatal(http.ListenAndServe(":8080", router))
	}
*/
package router

import (
	"net/http"

	"github.com/julienschmidt/httprouter"
	"github.com/teepark/contextual"
	"github.com/teepark/contextual/middleware"
	"golang.org/x/net/context"
)

// ParamKey is the key in a context.Context under which the httprouter.Params
// will be stored.
const ParamKey = "params"

// Router wraps an httprouter.Router to accept contextual.Handlers. For
// each request it adds the httprouter.Params to the context as
// Value(router.ParamKey), then passes it to the appropriate (optionally
// middleware-wrapped) handler.
type Router struct {
	router *httprouter.Router
	mw     middleware.Middleware
}

// New creates a new Router around a given httprouter.Router.
// All arguments may be nil, in which case the Router would wrap a
// Router created with httprouter.New() and there would be no
// middleware applied.
func New(router *httprouter.Router, mw middleware.Middleware) *Router {
	if router == nil {
		router = httprouter.New()
	}

	return &Router{
		router: router,
		mw:     mw,
	}
}

// ServeHTTP implements http.Handler
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	router.router.ServeHTTP(w, r)
}

// ServeFiles serves files from a given filesystem root. The path must
// end with "/*filepath". See httprouter's documentation for details.
func (router *Router) ServeFiles(path string, root http.FileSystem) {
	router.router.ServeFiles(path, root)
}

// Handle adds a method/path handler with a context.Context argument
func (router *Router) Handle(method, path string, handle contextual.Handler) {
	router.router.Handle(method, path, handlerShim(router, handle))
}

// Handler adds an http.Handler for a method/path
func (router *Router) Handler(method, path string, handler http.Handler) {
	router.router.Handler(method, path, handler)
}

// HandlerFunc adds an http.HandlerFunc for a method/path
func (router *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
	router.router.HandlerFunc(method, path, handler)
}

// GET is a shortcut for Handle("GET", ...)
func (router *Router) GET(path string, handle contextual.Handler) {
	router.Handle("GET", path, handle)
}

// HEAD is a shortcut for Handle("HEAD", ...)
func (router *Router) HEAD(path string, handle contextual.Handler) {
	router.Handle("HEAD", path, handle)
}

// POST is a shortcut for Handle("POST", ...)
func (router *Router) POST(path string, handle contextual.Handler) {
	router.Handle("POST", path, handle)
}

// PUT is a shortcut for Handle("PUT", ...)
func (router *Router) PUT(path string, handle contextual.Handler) {
	router.Handle("PUT", path, handle)
}

// DELETE is a shortcut for Handle("DELETE", ...)
func (router *Router) DELETE(path string, handle contextual.Handler) {
	router.Handle("DELETE", path, handle)
}

// OPTIONS is a shortcut for Handle("OPTIONS", ...)
func (router *Router) OPTIONS(path string, handle contextual.Handler) {
	router.Handle("OPTIONS", path, handle)
}

// PATCH is a shortcut for Handle("PATCH", ...)
func (router *Router) PATCH(path string, handle contextual.Handler) {
	router.Handle("PATCH", path, handle)
}

func handlerShim(router *Router, handle contextual.Handler) httprouter.Handle {
	if router.mw != nil {
		handle = router.mw(handle)
	}

	return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		ctx := context.WithValue(context.Background(), ParamKey, p)
		handle.Serve(ctx, w, r)
	})
}

// Params retrieves the httprouter Params from a context
func Params(c context.Context) httprouter.Params {
	p := c.Value(ParamKey)
	if p == nil {
		return nil
	}
	return p.(httprouter.Params)
}
<|endoftext|>


<commit_before>// Example usage of papaBot.
package main

import (
	"flag"
	"fmt"
	"github.com/pawelszydlo/papa-bot"
	"github.com/pawelszydlo/papa-bot/extensions"
	"time"
)

var (
	configFile = flag.String("c", "config.ini", "Path to TOML configuration file for the bot.")
	textsFile  = flag.String("t", "texts.ini", "Path to TOML configuration file with the bot texts.")
)

func init() {
	flag.Parse()
}

// Create your own extension simply by embedding the Extension struct.
type MyExtension struct {
	extensions.Extension
	startTime time.Time
}

// Override any methods you need. For more help please take a look at bot's built in extensions.

// Will be run on bot's init or when extension is registered after bot's init.
func (ext *MyExtension) Init(bot *papaBot.Bot) error {
	ext.startTime = time.Now()
	return nil
}

// Will be run every 5 minutes. Daily will be set to true once per day.
func (ext *MyExtension) Tick(bot *papaBot.Bot, daily bool) {
	bot.SendMassNotice(
		fmt.Sprintf("I have been running for %.0f minutes now.", time.Since(ext.startTime).Minutes()))
}

// Will be run whenever an URL is found in the message.
// func (ext *MyExtension) ProcessURL(bot *papaBot.Bot, info *papaBot.UrlInfo, channel, sender, msg string) {}

// Will be run on every public message the bot receives.
// func (ext *MyExtension) ProcessMessage(bot *papaBot.Bot, channel, nick, msg string) {}

// Entry point
func main() {
	bot := papaBot.New(*configFile, *textsFile)
	extensions.RegisterBuiltinExtensions(bot)
	bot.MustRegisterExtension(new(MyExtension))
	bot.Run()
}
<commit_msg>Reformat example.<commit_after>// Example usage of papaBot.
package main

import (
	"flag"
	"fmt"
	"github.com/pawelszydlo/papa-bot"
	"github.com/pawelszydlo/papa-bot/extensions"
	"time"
)

var (
	configFile = flag.String("c", "config.ini", "Path to TOML configuration file for the bot.")
	textsFile  = flag.String("t", "texts.ini", "Path to TOML configuration file with the bot texts.")
)

func init() {
	flag.Parse()
}

// Create your own extension simply by embedding the Extension struct.
type MyExtension struct {
	extensions.Extension
	startTime time.Time
}

// Override any methods you need. For more help please take a look at bot's built in extensions.

// Will be run on bot's init or when extension is registered after bot's init.
func (ext *MyExtension) Init(bot *papaBot.Bot) error {
	ext.startTime = time.Now()
	return nil
}

// Will be run every 5 minutes. Daily will be set to true once per day.
func (ext *MyExtension) Tick(bot *papaBot.Bot, daily bool) {
	bot.SendMassNotice(
		fmt.Sprintf("I have been running for %.0f minutes now.", time.Since(ext.startTime).Minutes()))
}

// Will be run whenever an URL is found in the message.
// func (ext *MyExtension) ProcessURL(bot *papaBot.Bot, info *papaBot.UrlInfo, channel, sender, msg string) {}

// Will be run on every public message the bot receives.
// func (ext *MyExtension) ProcessMessage(bot *papaBot.Bot, channel, nick, msg string) {}

// Entry point
func main() {
	// This will create bot's structures. Feel free to modify what you need afterwards.
	bot := papaBot.New(*configFile, *textsFile)

	// As an example, change the name.
	bot.Config.Name = "David"

	// Add all built-in extensions.
	extensions.RegisterBuiltinExtensions(bot)

	// Add your own custom extension.
	bot.MustRegisterExtension(new(MyExtension))

	// This will init the bot's mechanisms and run the bot's main loop.
	bot.Run()
}
<|endoftext|>


<commit_before>package agent

import (
	"fmt"
	"net"
	"net/url"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/drone/drone/build"
	"github.com/drone/drone/model"
	"github.com/drone/drone/queue"
	"github.com/drone/drone/version"
	"github.com/drone/drone/yaml"
	"github.com/drone/drone/yaml/expander"
	"github.com/drone/drone/yaml/transform"
)

type Logger interface {
	Write(*build.Line)
}

type Agent struct {
	Update    UpdateFunc
	Logger    LoggerFunc
	Engine    build.Engine
	Timeout   time.Duration
	Platform  string
	Namespace string
	Disable   []string
	Escalate  []string
	Netrc     []string
	Local     string
	Pull      bool
}

func (a *Agent) Poll() error {

	// logrus.Infof("Starting build %s/%s#%d.%d",
	// 	payload.Repo.Owner, payload.Repo.Name, payload.Build.Number, payload.Job.Number)
	//
	//
	// 	logrus.Infof("Finished build %s/%s#%d.%d",
	// 		payload.Repo.Owner, payload.Repo.Name, payload.Build.Number, payload.Job.Number)

	return nil
}

func (a *Agent) Run(payload *queue.Work, cancel <-chan bool) error {

	payload.Job.Status = model.StatusRunning
	payload.Job.Started = time.Now().Unix()

	spec, err := a.prep(payload)
	if err != nil {
		payload.Job.Error = err.Error()
		payload.Job.ExitCode = 255
		payload.Job.Finished = payload.Job.Started
		payload.Job.Status = model.StatusError
		a.Update(payload)
		return err
	}
	a.Update(payload)
	err = a.exec(spec, payload, cancel)

	if err != nil {
		payload.Job.ExitCode = 255
		payload.Job.Error = err.Error()
	}
	if exitErr, ok := err.(*build.ExitError); ok {
		payload.Job.ExitCode = exitErr.Code
		payload.Job.Error = "" // exit errors are already written to the log
	}

	payload.Job.Finished = time.Now().Unix()

	switch payload.Job.ExitCode {
	case 128, 130, 137:
		payload.Job.Status = model.StatusKilled
	case 0:
		payload.Job.Status = model.StatusSuccess
	default:
		payload.Job.Status = model.StatusFailure
	}

	a.Update(payload)

	return err
}

func (a *Agent) prep(w *queue.Work) (*yaml.Config, error) {

	envs := toEnv(w)
	w.Yaml = expander.ExpandString(w.Yaml, envs)

	// inject the netrc file into the clone plugin if the repository is
	// private and requires authentication.
	var secrets []*model.Secret
	if w.Verified {
		secrets = append(secrets, w.Secrets...)
	}

	if w.Repo.IsPrivate {
		secrets = append(secrets, &model.Secret{
			Name:   "DRONE_NETRC_USERNAME",
			Value:  w.Netrc.Login,
			Images: []string{"*"},
			Events: []string{"*"},
		})
		secrets = append(secrets, &model.Secret{
			Name:   "DRONE_NETRC_PASSWORD",
			Value:  w.Netrc.Password,
			Images: []string{"*"},
			Events: []string{"*"},
		})
		secrets = append(secrets, &model.Secret{
			Name:   "DRONE_NETRC_MACHINE",
			Value:  w.Netrc.Machine,
			Images: []string{"*"},
			Events: []string{"*"},
		})
	}

	conf, err := yaml.ParseString(w.Yaml)
	if err != nil {
		return nil, err
	}

	src := "src"
	if url, _ := url.Parse(w.Repo.Link); url != nil {
		host, _, err := net.SplitHostPort(url.Host)
		if err == nil {
			url.Host = host
		}
		src = filepath.Join(src, url.Host, url.Path)
	}

	transform.Clone(conf, w.Repo.Kind)
	transform.Environ(conf, envs)
	transform.DefaultFilter(conf)
	if w.BuildLast != nil {
		transform.ChangeFilter(conf, w.BuildLast.Status)
	}

	transform.ImageSecrets(conf, secrets, w.Build.Event)
	transform.Identifier(conf)
	transform.WorkspaceTransform(conf, "/drone", src)

	if err := transform.Check(conf, w.Repo.IsTrusted); err != nil {
		return nil, err
	}

	transform.CommandTransform(conf)
	transform.ImagePull(conf, a.Pull)
	transform.ImageTag(conf)
	transform.ImageName(conf)
	transform.ImageNamespace(conf, a.Namespace)
	transform.ImageEscalate(conf, a.Escalate)
	transform.PluginParams(conf)

	if a.Local != "" {
		transform.PluginDisable(conf, a.Disable)
		transform.ImageVolume(conf, []string{a.Local + ":" + conf.Workspace.Path})
	}

	transform.Pod(conf)

	return conf, nil
}

func (a *Agent) exec(spec *yaml.Config, payload *queue.Work, cancel <-chan bool) error {

	conf := build.Config{
		Engine: a.Engine,
		Buffer: 500,
	}

	pipeline := conf.Pipeline(spec)
	defer pipeline.Teardown()

	// setup the build environment
	if err := pipeline.Setup(); err != nil {
		return err
	}

	timeout := time.After(time.Duration(payload.Repo.Timeout) * time.Minute)

	for {
		select {
		case <-pipeline.Done():
			return pipeline.Err()
		case <-cancel:
			pipeline.Stop()
			return fmt.Errorf("termination request received, build cancelled")
		case <-timeout:
			pipeline.Stop()
			return fmt.Errorf("maximum time limit exceeded, build cancelled")
		case <-time.After(a.Timeout):
			pipeline.Stop()
			return fmt.Errorf("terminal inactive for %v, build cancelled", a.Timeout)
		case <-pipeline.Next():

			// TODO(bradrydzewski) this entire block of code should probably get
			// encapsulated in the pipeline.
			status := model.StatusSuccess
			if pipeline.Err() != nil {
				status = model.StatusFailure
			}
			// updates the build status passed into each container. I realize this is
			// a bit out of place and will work to resolve.
			pipeline.Head().Environment["DRONE_BUILD_STATUS"] = status

			if !pipeline.Head().Constraints.Match(
				a.Platform,
				payload.Build.Deploy,
				payload.Build.Event,
				payload.Build.Branch,
				status, payload.Job.Environment) { // TODO: fix this whole section

				pipeline.Skip()
			} else {
				pipeline.Exec()
			}
		case line := <-pipeline.Pipe():
			a.Logger(line)
		}
	}
}

func toEnv(w *queue.Work) map[string]string {
	envs := map[string]string{
		"CI":                         "drone",
		"DRONE":                      "true",
		"DRONE_ARCH":                 "linux/amd64",
		"DRONE_REPO":                 w.Repo.FullName,
		"DRONE_REPO_SCM":             w.Repo.Kind,
		"DRONE_REPO_OWNER":           w.Repo.Owner,
		"DRONE_REPO_NAME":            w.Repo.Name,
		"DRONE_REPO_LINK":            w.Repo.Link,
		"DRONE_REPO_AVATAR":          w.Repo.Avatar,
		"DRONE_REPO_BRANCH":          w.Repo.Branch,
		"DRONE_REPO_PRIVATE":         fmt.Sprintf("%v", w.Repo.IsPrivate),
		"DRONE_REPO_TRUSTED":         fmt.Sprintf("%v", w.Repo.IsTrusted),
		"DRONE_REMOTE_URL":           w.Repo.Clone,
		"DRONE_COMMIT_SHA":           w.Build.Commit,
		"DRONE_COMMIT_REF":           w.Build.Ref,
		"DRONE_COMMIT_BRANCH":        w.Build.Branch,
		"DRONE_COMMIT_LINK":          w.Build.Link,
		"DRONE_COMMIT_MESSAGE":       w.Build.Message,
		"DRONE_COMMIT_AUTHOR":        w.Build.Author,
		"DRONE_COMMIT_AUTHOR_EMAIL":  w.Build.Email,
		"DRONE_COMMIT_AUTHOR_AVATAR": w.Build.Avatar,
		"DRONE_BUILD_NUMBER":         fmt.Sprintf("%d", w.Build.Number),
		"DRONE_BUILD_EVENT":          w.Build.Event,
		"DRONE_BUILD_STATUS":         w.Build.Status,
		"DRONE_BUILD_LINK":           fmt.Sprintf("%s/%s/%d", w.System.Link, w.Repo.FullName, w.Build.Number),
		"DRONE_BUILD_CREATED":        fmt.Sprintf("%d", w.Build.Created),
		"DRONE_BUILD_STARTED":        fmt.Sprintf("%d", w.Build.Started),
		"DRONE_BUILD_FINISHED":       fmt.Sprintf("%d", w.Build.Finished),
		"DRONE_YAML_VERIFIED":        fmt.Sprintf("%v", w.Verified),
		"DRONE_YAML_SIGNED":          fmt.Sprintf("%v", w.Signed),
		"DRONE_BRANCH":               w.Build.Branch,
		"DRONE_COMMIT":               w.Build.Commit,
		"DRONE_VERSION":              version.Version,
	}

	if w.Build.Event == model.EventTag {
		envs["DRONE_TAG"] = strings.TrimPrefix(w.Build.Ref, "refs/tags/")
	}
	if w.Build.Event == model.EventPull {
		envs["DRONE_PULL_REQUEST"] = pullRegexp.FindString(w.Build.Ref)
	}
	if w.Build.Event == model.EventDeploy {
		envs["DRONE_DEPLOY_TO"] = w.Build.Deploy
	}

	if w.BuildLast != nil {
		envs["DRONE_PREV_BUILD_STATUS"] = w.BuildLast.Status
		envs["DRONE_PREV_BUILD_NUMBER"] = fmt.Sprintf("%v", w.BuildLast.Number)
		envs["DRONE_PREV_COMMIT_SHA"] = w.BuildLast.Commit
	}

	// inject matrix values as environment variables
	for key, val := range w.Job.Environment {
		envs[key] = val
	}
	return envs
}

var pullRegexp = regexp.MustCompile("\\d+")
<commit_msg>display error when commands for docker plugin<commit_after>package agent

import (
	"fmt"
	"net"
	"net/url"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/drone/drone/build"
	"github.com/drone/drone/model"
	"github.com/drone/drone/queue"
	"github.com/drone/drone/version"
	"github.com/drone/drone/yaml"
	"github.com/drone/drone/yaml/expander"
	"github.com/drone/drone/yaml/transform"
)

type Logger interface {
	Write(*build.Line)
}

type Agent struct {
	Update    UpdateFunc
	Logger    LoggerFunc
	Engine    build.Engine
	Timeout   time.Duration
	Platform  string
	Namespace string
	Disable   []string
	Escalate  []string
	Netrc     []string
	Local     string
	Pull      bool
}

func (a *Agent) Poll() error {

	// logrus.Infof("Starting build %s/%s#%d.%d",
	// 	payload.Repo.Owner, payload.Repo.Name, payload.Build.Number, payload.Job.Number)
	//
	//
	// 	logrus.Infof("Finished build %s/%s#%d.%d",
	// 		payload.Repo.Owner, payload.Repo.Name, payload.Build.Number, payload.Job.Number)

	return nil
}

func (a *Agent) Run(payload *queue.Work, cancel <-chan bool) error {

	payload.Job.Status = model.StatusRunning
	payload.Job.Started = time.Now().Unix()

	spec, err := a.prep(payload)
	if err != nil {
		payload.Job.Error = err.Error()
		payload.Job.ExitCode = 255
		payload.Job.Finished = payload.Job.Started
		payload.Job.Status = model.StatusError
		a.Update(payload)
		return err
	}
	a.Update(payload)
	err = a.exec(spec, payload, cancel)

	if err != nil {
		payload.Job.ExitCode = 255
		payload.Job.Error = err.Error()
	}
	if exitErr, ok := err.(*build.ExitError); ok {
		payload.Job.ExitCode = exitErr.Code
		payload.Job.Error = "" // exit errors are already written to the log
	}

	payload.Job.Finished = time.Now().Unix()

	switch payload.Job.ExitCode {
	case 128, 130, 137:
		payload.Job.Status = model.StatusKilled
	case 0:
		payload.Job.Status = model.StatusSuccess
	default:
		payload.Job.Status = model.StatusFailure
	}

	a.Update(payload)

	return err
}

func (a *Agent) prep(w *queue.Work) (*yaml.Config, error) {

	envs := toEnv(w)
	w.Yaml = expander.ExpandString(w.Yaml, envs)

	// inject the netrc file into the clone plugin if the repository is
	// private and requires authentication.
	var secrets []*model.Secret
	if w.Verified {
		secrets = append(secrets, w.Secrets...)
	}

	if w.Repo.IsPrivate {
		secrets = append(secrets, &model.Secret{
			Name:   "DRONE_NETRC_USERNAME",
			Value:  w.Netrc.Login,
			Images: []string{"*"},
			Events: []string{"*"},
		})
		secrets = append(secrets, &model.Secret{
			Name:   "DRONE_NETRC_PASSWORD",
			Value:  w.Netrc.Password,
			Images: []string{"*"},
			Events: []string{"*"},
		})
		secrets = append(secrets, &model.Secret{
			Name:   "DRONE_NETRC_MACHINE",
			Value:  w.Netrc.Machine,
			Images: []string{"*"},
			Events: []string{"*"},
		})
	}

	conf, err := yaml.ParseString(w.Yaml)
	if err != nil {
		return nil, err
	}

	src := "src"
	if url, _ := url.Parse(w.Repo.Link); url != nil {
		host, _, err := net.SplitHostPort(url.Host)
		if err == nil {
			url.Host = host
		}
		src = filepath.Join(src, url.Host, url.Path)
	}

	transform.Clone(conf, w.Repo.Kind)
	transform.Environ(conf, envs)
	transform.DefaultFilter(conf)
	if w.BuildLast != nil {
		transform.ChangeFilter(conf, w.BuildLast.Status)
	}

	transform.ImageSecrets(conf, secrets, w.Build.Event)
	transform.Identifier(conf)
	transform.WorkspaceTransform(conf, "/drone", src)

	if err := transform.Check(conf, w.Repo.IsTrusted); err != nil {
		return nil, err
	}

	transform.CommandTransform(conf)
	transform.ImagePull(conf, a.Pull)
	transform.ImageTag(conf)
	transform.ImageName(conf)
	transform.ImageNamespace(conf, a.Namespace)
	if err := transform.ImageEscalate(conf, a.Escalate); err != nil {
		return nil, err
	}
	transform.PluginParams(conf)

	if a.Local != "" {
		transform.PluginDisable(conf, a.Disable)
		transform.ImageVolume(conf, []string{a.Local + ":" + conf.Workspace.Path})
	}

	transform.Pod(conf)

	return conf, nil
}

func (a *Agent) exec(spec *yaml.Config, payload *queue.Work, cancel <-chan bool) error {

	conf := build.Config{
		Engine: a.Engine,
		Buffer: 500,
	}

	pipeline := conf.Pipeline(spec)
	defer pipeline.Teardown()

	// setup the build environment
	if err := pipeline.Setup(); err != nil {
		return err
	}

	timeout := time.After(time.Duration(payload.Repo.Timeout) * time.Minute)

	for {
		select {
		case <-pipeline.Done():
			return pipeline.Err()
		case <-cancel:
			pipeline.Stop()
			return fmt.Errorf("termination request received, build cancelled")
		case <-timeout:
			pipeline.Stop()
			return fmt.Errorf("maximum time limit exceeded, build cancelled")
		case <-time.After(a.Timeout):
			pipeline.Stop()
			return fmt.Errorf("terminal inactive for %v, build cancelled", a.Timeout)
		case <-pipeline.Next():

			// TODO(bradrydzewski) this entire block of code should probably get
			// encapsulated in the pipeline.
			status := model.StatusSuccess
			if pipeline.Err() != nil {
				status = model.StatusFailure
			}
			// updates the build status passed into each container. I realize this is
			// a bit out of place and will work to resolve.
			pipeline.Head().Environment["DRONE_BUILD_STATUS"] = status

			if !pipeline.Head().Constraints.Match(
				a.Platform,
				payload.Build.Deploy,
				payload.Build.Event,
				payload.Build.Branch,
				status, payload.Job.Environment) { // TODO: fix this whole section

				pipeline.Skip()
			} else {
				pipeline.Exec()
			}
		case line := <-pipeline.Pipe():
			a.Logger(line)
		}
	}
}

func toEnv(w *queue.Work) map[string]string {
	envs := map[string]string{
		"CI":                         "drone",
		"DRONE":                      "true",
		"DRONE_ARCH":                 "linux/amd64",
		"DRONE_REPO":                 w.Repo.FullName,
		"DRONE_REPO_SCM":             w.Repo.Kind,
		"DRONE_REPO_OWNER":           w.Repo.Owner,
		"DRONE_REPO_NAME":            w.Repo.Name,
		"DRONE_REPO_LINK":            w.Repo.Link,
		"DRONE_REPO_AVATAR":          w.Repo.Avatar,
		"DRONE_REPO_BRANCH":          w.Repo.Branch,
		"DRONE_REPO_PRIVATE":         fmt.Sprintf("%v", w.Repo.IsPrivate),
		"DRONE_REPO_TRUSTED":         fmt.Sprintf("%v", w.Repo.IsTrusted),
		"DRONE_REMOTE_URL":           w.Repo.Clone,
		"DRONE_COMMIT_SHA":           w.Build.Commit,
		"DRONE_COMMIT_REF":           w.Build.Ref,
		"DRONE_COMMIT_BRANCH":        w.Build.Branch,
		"DRONE_COMMIT_LINK":          w.Build.Link,
		"DRONE_COMMIT_MESSAGE":       w.Build.Message,
		"DRONE_COMMIT_AUTHOR":        w.Build.Author,
		"DRONE_COMMIT_AUTHOR_EMAIL":  w.Build.Email,
		"DRONE_COMMIT_AUTHOR_AVATAR": w.Build.Avatar,
		"DRONE_BUILD_NUMBER":         fmt.Sprintf("%d", w.Build.Number),
		"DRONE_BUILD_EVENT":          w.Build.Event,
		"DRONE_BUILD_STATUS":         w.Build.Status,
		"DRONE_BUILD_LINK":           fmt.Sprintf("%s/%s/%d", w.System.Link, w.Repo.FullName, w.Build.Number),
		"DRONE_BUILD_CREATED":        fmt.Sprintf("%d", w.Build.Created),
		"DRONE_BUILD_STARTED":        fmt.Sprintf("%d", w.Build.Started),
		"DRONE_BUILD_FINISHED":       fmt.Sprintf("%d", w.Build.Finished),
		"DRONE_YAML_VERIFIED":        fmt.Sprintf("%v", w.Verified),
		"DRONE_YAML_SIGNED":          fmt.Sprintf("%v", w.Signed),
		"DRONE_BRANCH":               w.Build.Branch,
		"DRONE_COMMIT":               w.Build.Commit,
		"DRONE_VERSION":              version.Version,
	}

	if w.Build.Event == model.EventTag {
		envs["DRONE_TAG"] = strings.TrimPrefix(w.Build.Ref, "refs/tags/")
	}
	if w.Build.Event == model.EventPull {
		envs["DRONE_PULL_REQUEST"] = pullRegexp.FindString(w.Build.Ref)
	}
	if w.Build.Event == model.EventDeploy {
		envs["DRONE_DEPLOY_TO"] = w.Build.Deploy
	}

	if w.BuildLast != nil {
		envs["DRONE_PREV_BUILD_STATUS"] = w.BuildLast.Status
		envs["DRONE_PREV_BUILD_NUMBER"] = fmt.Sprintf("%v", w.BuildLast.Number)
		envs["DRONE_PREV_COMMIT_SHA"] = w.BuildLast.Commit
	}

	// inject matrix values as environment variables
	for key, val := range w.Job.Environment {
		envs[key] = val
	}
	return envs
}

var pullRegexp = regexp.MustCompile("\\d+")
<|endoftext|>