<commit_before>package main

import (
	"flag"
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	_build "neon/build"
	_ "neon/builtin"
	_ "neon/task"
	"neon/util"
	"os"
	"path/filepath"
	"time"
)

const (
	// DefaultBuildFile is the default name for build file
	DefaultBuildFile = "build.yml"
	// DefaultConfiguration is the default location for configuration file
	DefaultConfiguration = "~/.neon/settings.yml"
)

// Configuration holds configuration properties
type Configuration struct {
	// Files maps directories to build files
	Files map[string]string
}

// Version is passed while compiling
var Version string

// Configuration is loaded configuration
var configuration = &Configuration{}

// LoadConfiguration loads configuration file
func LoadConfiguration() (*Configuration, error) {
	configuration := Configuration{}
	file := util.ExpandUserHome(DefaultConfiguration)
	if util.FileExists(file) {
		source, err := ioutil.ReadFile(file)
		if err != nil {
			return nil, err
		}
		err = yaml.Unmarshal(source, &configuration)
		if err != nil {
			return nil, err
		}
	}
	// expand user homes in files
	abs := make(map[string]string)
	for dir, build := range configuration.Files {
		abs[util.ExpandUserHome(dir)] = util.ExpandUserHome(build)
	}
	configuration.Files = abs
	return &configuration, nil
}

// ParseCommandLine parses command line and returns parsed options
func ParseCommandLine() (string, bool, bool, string, bool, bool, string, bool, bool, string, bool, string, string, bool,
	string, bool, bool, []string) {
	file := flag.String("file", DefaultBuildFile, "Build file to run")
	info := flag.Bool("info", false, "Print build information")
	version := flag.Bool("version", false, "Print neon version")
	props := flag.String("props", "", "Build properties")
	timeit := flag.Bool("time", false, "Print build duration")
	tasks := flag.Bool("tasks", false, "Print tasks list")
	task := flag.String("task", "", "Print help on given task")
	targs := flag.Bool("targets", false, "Print targets list")
	builtins := flag.Bool("builtins", false, "Print builtins list")
	builtin := flag.String("builtin", "", "Print help on given builtin")
	refs := flag.Bool("refs", false, "Print tasks and builtins reference")
	install := flag.String("install", "", "Install given plugin")
	repo := flag.String("repo", _build.DefaultRepo, "Neon plugin repository for installation")
	grey := flag.Bool("grey", false, "Print on terminal without colors")
	template := flag.String("template", "", "Run given template")
	templates := flag.Bool("templates", false, "List available templates in repository")
	parents := flag.Bool("parents", false, "List available parent build files in repository")
	flag.Parse()
	targets := flag.Args()
	return *file, *info, *version, *props, *timeit, *tasks, *task, *targs, *builtins,
		*builtin, *refs, *install, *repo, *grey, *template, *templates, *parents, targets
}

// FindBuildFile finds build file and returns its path
// - name: the name of the build file
// Return:
// - path of found build file
// - an error if something went wrong
func FindBuildFile(name string) (string, string, error) {
	absolute, err := filepath.Abs(name)
	if err != nil {
		return "", "", fmt.Errorf("getting build file path: %v", err)
	}
	file := filepath.Base(absolute)
	dir := filepath.Dir(absolute)
	for {
		path := filepath.Join(dir, file)
		if util.FileExists(path) {
			return path, dir, nil
		}
		if path, ok := configuration.Files[dir]; ok && util.FileExists(path) {
			return path, dir, nil
		}
		parent := filepath.Dir(dir)
		if parent == dir {
			return "", "", fmt.Errorf("build file not found")
		}
		dir = parent
	}
}

// Program entry point
func main() {
	var err error
	start := time.Now()
	// load configuration file
	configuration, err = LoadConfiguration()
	if err != nil {
		PrintError(fmt.Errorf("loading configuration file '%s': %v", DefaultConfiguration, err), 6)
	}
	// parse command line
	file, info, version, props, timeit, tasks, task, targs, builtins, builtin, refs, install, repo, grey, template,
		templates, parents, targets := ParseCommandLine()
	// options that do not require we load build file
	_build.Grey = grey
	if tasks {
		_build.PrintTasks()
		return
	} else if task != "" {
		_build.PrintHelpTask(task)
		return
	} else if builtins {
		_build.PrintBuiltins()
		return
	} else if builtin != "" {
		_build.PrintHelpBuiltin(builtin)
		return
	} else if refs {
		_build.PrintReference()
		return
	} else if version {
		_build.Message(Version)
		return
	} else if install != "" {
		err := _build.InstallPlugin(install, repo)
		PrintError(err, 6)
		return
	} else if templates {
		_build.PrintTemplates(repo)
		return
	} else if parents {
		_build.PrintParents(repo)
		return
	}
	// options that do require we load build file
	if template != "" {
		file, err = _build.TemplatePath(template, repo)
		PrintError(err, 1)
	}
	path, base, err := FindBuildFile(file)
	PrintError(err, 1)
	build, err := _build.NewBuild(path, base)
	PrintError(err, 2)
	err = build.SetCommandLineProperties(props)
	PrintError(err, 3)
	if targs {
		build.PrintTargets()
		return
	} else if info {
		context := _build.NewContext(build)
		err = context.Init()
		PrintError(err, 4)
		err = build.Info(context)
		PrintError(err, 4)
		return
	} else {
		os.Chdir(build.Dir)
		context := _build.NewContext(build)
		err = context.Init()
		PrintError(err, 5)
		err = build.Run(context, targets)
		duration := time.Now().Sub(start)
		if timeit || duration.Seconds() > 10 {
			_build.Message("Build duration: %s", duration.String())
		}
		PrintError(err, 5)
		_build.PrintOk()
		return
	}
}

// PrintError prints an error and exits if any
// - error: the error to check
// - code: the exit code if error is not nil
func PrintError(err error, code int) {
	if err != nil {
		_build.PrintError(err.Error())
		os.Exit(code)
	}
}
<commit_msg>Added message with running build file<commit_after>package main

import (
	"flag"
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	_build "neon/build"
	_ "neon/builtin"
	_ "neon/task"
	"neon/util"
	"os"
	"path/filepath"
	"time"
)

const (
	// DefaultBuildFile is the default name for build file
	DefaultBuildFile = "build.yml"
	// DefaultConfiguration is the default location for configuration file
	DefaultConfiguration = "~/.neon/settings.yml"
)

// Configuration holds configuration properties
type Configuration struct {
	// Files maps directories to build files
	Files map[string]string
}

// Version is passed while compiling
var Version string

// Configuration is loaded configuration
var configuration = &Configuration{}

// LoadConfiguration loads configuration file
func LoadConfiguration() (*Configuration, error) {
	configuration := Configuration{}
	file := util.ExpandUserHome(DefaultConfiguration)
	if util.FileExists(file) {
		source, err := ioutil.ReadFile(file)
		if err != nil {
			return nil, err
		}
		err = yaml.Unmarshal(source, &configuration)
		if err != nil {
			return nil, err
		}
	}
	// expand user homes in files
	abs := make(map[string]string)
	for dir, build := range configuration.Files {
		abs[util.ExpandUserHome(dir)] = util.ExpandUserHome(build)
	}
	configuration.Files = abs
	return &configuration, nil
}

// ParseCommandLine parses command line and returns parsed options
func ParseCommandLine() (string, bool, bool, string, bool, bool, string, bool, bool, string, bool, string, string, bool,
	string, bool, bool, []string) {
	file := flag.String("file", DefaultBuildFile, "Build file to run")
	info := flag.Bool("info", false, "Print build information")
	version := flag.Bool("version", false, "Print neon version")
	props := flag.String("props", "", "Build properties")
	timeit := flag.Bool("time", false, "Print build duration")
	tasks := flag.Bool("tasks", false, "Print tasks list")
	task := flag.String("task", "", "Print help on given task")
	targs := flag.Bool("targets", false, "Print targets list")
	builtins := flag.Bool("builtins", false, "Print builtins list")
	builtin := flag.String("builtin", "", "Print help on given builtin")
	refs := flag.Bool("refs", false, "Print tasks and builtins reference")
	install := flag.String("install", "", "Install given plugin")
	repo := flag.String("repo", _build.DefaultRepo, "Neon plugin repository for installation")
	grey := flag.Bool("grey", false, "Print on terminal without colors")
	template := flag.String("template", "", "Run given template")
	templates := flag.Bool("templates", false, "List available templates in repository")
	parents := flag.Bool("parents", false, "List available parent build files in repository")
	flag.Parse()
	targets := flag.Args()
	return *file, *info, *version, *props, *timeit, *tasks, *task, *targs, *builtins,
		*builtin, *refs, *install, *repo, *grey, *template, *templates, *parents, targets
}

// FindBuildFile finds build file and returns its path
// - name: the name of the build file
// Return:
// - path of found build file
// - an error if something went wrong
func FindBuildFile(name string) (string, string, error) {
	absolute, err := filepath.Abs(name)
	if err != nil {
		return "", "", fmt.Errorf("getting build file path: %v", err)
	}
	file := filepath.Base(absolute)
	dir := filepath.Dir(absolute)
	for {
		path := filepath.Join(dir, file)
		if util.FileExists(path) {
			return path, dir, nil
		}
		if path, ok := configuration.Files[dir]; ok && util.FileExists(path) {
			return path, dir, nil
		}
		parent := filepath.Dir(dir)
		if parent == dir {
			return "", "", fmt.Errorf("build file not found")
		}
		dir = parent
	}
}

// Program entry point
func main() {
	var err error
	start := time.Now()
	// load configuration file
	configuration, err = LoadConfiguration()
	if err != nil {
		PrintError(fmt.Errorf("loading configuration file '%s': %v", DefaultConfiguration, err), 6)
	}
	// parse command line
	file, info, version, props, timeit, tasks, task, targs, builtins, builtin, refs, install, repo, grey, template,
		templates, parents, targets := ParseCommandLine()
	// options that do not require we load build file
	_build.Grey = grey
	if tasks {
		_build.PrintTasks()
		return
	} else if task != "" {
		_build.PrintHelpTask(task)
		return
	} else if builtins {
		_build.PrintBuiltins()
		return
	} else if builtin != "" {
		_build.PrintHelpBuiltin(builtin)
		return
	} else if refs {
		_build.PrintReference()
		return
	} else if version {
		_build.Message(Version)
		return
	} else if install != "" {
		err := _build.InstallPlugin(install, repo)
		PrintError(err, 6)
		return
	} else if templates {
		_build.PrintTemplates(repo)
		return
	} else if parents {
		_build.PrintParents(repo)
		return
	}
	// options that do require we load build file
	if template != "" {
		file, err = _build.TemplatePath(template, repo)
		PrintError(err, 1)
	}
	path, base, err := FindBuildFile(file)
	PrintError(err, 1)
	_build.Message("Build: %s", path)
	build, err := _build.NewBuild(path, base)
	PrintError(err, 2)
	err = build.SetCommandLineProperties(props)
	PrintError(err, 3)
	if targs {
		build.PrintTargets()
		return
	} else if info {
		context := _build.NewContext(build)
		err = context.Init()
		PrintError(err, 4)
		err = build.Info(context)
		PrintError(err, 4)
		return
	} else {
		os.Chdir(build.Dir)
		context := _build.NewContext(build)
		err = context.Init()
		PrintError(err, 5)
		err = build.Run(context, targets)
		duration := time.Now().Sub(start)
		if timeit || duration.Seconds() > 10 {
			_build.Message("Build duration: %s", duration.String())
		}
		PrintError(err, 5)
		_build.PrintOk()
		return
	}
}

// PrintError prints an error and exits if any
// - error: the error to check
// - code: the exit code if error is not nil
func PrintError(err error, code int) {
	if err != nil {
		_build.PrintError(err.Error())
		os.Exit(code)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"

	"github.com/gorilla/websocket"
)

// WebSocket is a wrapper around a gorilla.WebSocket for claws.
type WebSocket struct {
	conn      *websocket.Conn
	writeChan chan string
	closed    bool
}

// ReadChannel retrieves a channel from which to read messages out of.
func (w *WebSocket) ReadChannel() <-chan string {
	ch := make(chan string, 16)
	go w.readChannel(ch)
	return ch
}

// Write writes a message to the WebSocket
func (w *WebSocket) Write(msg string) {
	w.writeChan <- msg
}

func (w *WebSocket) readChannel(c chan<- string) {
	for {
		_type, msg, err := w.conn.ReadMessage()
		if err != nil {
			if !w.closed {
				state.Error(err.Error())
				w.close(c)
			}
			return
		}

		switch _type {
		case websocket.TextMessage, websocket.BinaryMessage:
			c <- string(msg)
		case websocket.CloseMessage:
			cl := "Closed WebSocket"
			if len(msg) > 0 {
				cl += " " + string(msg)
			}
			state.Debug(cl)
			w.close(c)
			return
		case websocket.PingMessage, websocket.PongMessage:
			if len(msg) > 0 {
				state.Debug("Ping/pong with " + string(msg))
			}
		}
	}
}

func (w *WebSocket) writePump() {
	for msg := range w.writeChan {
		err := w.conn.WriteMessage(websocket.TextMessage, []byte(msg))
		if err != nil {
			state.Error(err.Error())
			w.Close()
			return
		}
	}
}

// Close closes the WebSocket connection.
func (w *WebSocket) Close() error {
	if w == nil {
		return nil
	}
	if w.closed {
		return nil
	}
	w.closed = true
	if state.Conn == w {
		state.Conn = nil
	}
	close(w.writeChan)
	return w.conn.Close()
}

// close finalises the WebSocket connection.
func (w *WebSocket) close(c chan<- string) error {
	close(c)
	return w.Close()
}

// WebSocketResponseError is the error returned when there is an error in
// CreateWebSocket.
type WebSocketResponseError struct {
	Err  error
	Resp *http.Response
}

func (w WebSocketResponseError) Error() string {
	return w.Err.Error()
}

// CreateWebSocket initialises a new WebSocket connection.
func CreateWebSocket(url string) (*WebSocket, error) {
	state.Debug("Starting WebSocket connection to " + url)

	conn, resp, err := websocket.DefaultDialer.Dial(url, nil)
	if err != nil {
		return nil, WebSocketResponseError{
			Err:  err,
			Resp: resp,
		}
	}

	ws := &WebSocket{
		conn:      conn,
		writeChan: make(chan string, 128),
	}

	go ws.writePump()

	return ws, nil
}
<commit_msg>Add URL() method to WebSocket<commit_after>package main

import (
	"net/http"

	"github.com/gorilla/websocket"
)

// WebSocket is a wrapper around a gorilla.WebSocket for claws.
type WebSocket struct {
	conn      *websocket.Conn
	writeChan chan string
	closed    bool
	url       string
}

// URL returns the URL of the WebSocket.
func (w *WebSocket) URL() string {
	return w.url
}

// ReadChannel retrieves a channel from which to read messages out of.
func (w *WebSocket) ReadChannel() <-chan string {
	ch := make(chan string, 16)
	go w.readChannel(ch)
	return ch
}

// Write writes a message to the WebSocket
func (w *WebSocket) Write(msg string) {
	w.writeChan <- msg
}

func (w *WebSocket) readChannel(c chan<- string) {
	for {
		_type, msg, err := w.conn.ReadMessage()
		if err != nil {
			if !w.closed {
				state.Error(err.Error())
				w.close(c)
			}
			return
		}

		switch _type {
		case websocket.TextMessage, websocket.BinaryMessage:
			c <- string(msg)
		case websocket.CloseMessage:
			cl := "Closed WebSocket"
			if len(msg) > 0 {
				cl += " " + string(msg)
			}
			state.Debug(cl)
			w.close(c)
			return
		case websocket.PingMessage, websocket.PongMessage:
			if len(msg) > 0 {
				state.Debug("Ping/pong with " + string(msg))
			}
		}
	}
}

func (w *WebSocket) writePump() {
	for msg := range w.writeChan {
		err := w.conn.WriteMessage(websocket.TextMessage, []byte(msg))
		if err != nil {
			state.Error(err.Error())
			w.Close()
			return
		}
	}
}

// Close closes the WebSocket connection.
func (w *WebSocket) Close() error {
	if w == nil {
		return nil
	}
	if w.closed {
		return nil
	}
	w.closed = true
	if state.Conn == w {
		state.Conn = nil
	}
	close(w.writeChan)
	return w.conn.Close()
}

// close finalises the WebSocket connection.
func (w *WebSocket) close(c chan<- string) error {
	close(c)
	return w.Close()
}

// WebSocketResponseError is the error returned when there is an error in
// CreateWebSocket.
type WebSocketResponseError struct {
	Err  error
	Resp *http.Response
}

func (w WebSocketResponseError) Error() string {
	return w.Err.Error()
}

// CreateWebSocket initialises a new WebSocket connection.
func CreateWebSocket(url string) (*WebSocket, error) {
	state.Debug("Starting WebSocket connection to " + url)

	conn, resp, err := websocket.DefaultDialer.Dial(url, nil)
	if err != nil {
		return nil, WebSocketResponseError{
			Err:  err,
			Resp: resp,
		}
	}

	ws := &WebSocket{
		conn:      conn,
		writeChan: make(chan string, 128),
		url:       url,
	}

	go ws.writePump()

	return ws, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/gbrlsnchs/jwt/v3"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"github.com/julienschmidt/httprouter"
	"github.com/pkg/errors"
	"github.com/pterodactyl/wings/config"
	"github.com/pterodactyl/wings/server"
	"go.uber.org/zap"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
)

const (
	AuthenticationSuccessEvent = "auth success"
	TokenExpiringEvent         = "token expiring"
	TokenExpiredEvent          = "token expired"
	AuthenticationEvent        = "auth"
	SetStateEvent              = "set state"
	SendServerLogsEvent        = "send logs"
	SendCommandEvent           = "send command"
	ErrorEvent                 = "daemon error"
)

type WebsocketMessage struct {
	// The event to perform. Should be one of the following that are supported:
	//
	// - status : Returns the server's power state.
	// - logs : Returns the server log data at the time of the request.
	// - power : Performs a power action aganist the server based the data.
	// - command : Performs a command on a server using the data field.
	Event string `json:"event"`

	// The data to pass along, only used by power/command currently. Other requests
	// should either omit the field or pass an empty value as it is ignored.
	Args []string `json:"args,omitempty"`

	// Is set to true when the request is originating from outside of the Daemon,
	// otherwise set to false for outbound.
	inbound bool
}

type WebsocketHandler struct {
	Server     *server.Server
	Mutex      sync.Mutex
	Connection *websocket.Conn
	JWT        *WebsocketTokenPayload
}

type WebsocketTokenPayload struct {
	jwt.Payload
	UserID      json.Number `json:"user_id"`
	ServerUUID  string      `json:"server_uuid"`
	Permissions []string    `json:"permissions"`
}

const (
	PermissionConnect        = "connect"
	PermissionSendCommand    = "send-command"
	PermissionSendPower      = "send-power"
	PermissionReceiveErrors  = "receive-errors"
	PermissionReceiveInstall = "receive-install"
)

// Checks if the given token payload has a permission string.
func (wtp *WebsocketTokenPayload) HasPermission(permission string) bool {
	for _, k := range wtp.Permissions {
		if k == permission {
			return true
		}
	}

	return false
}

var alg *jwt.HMACSHA

// Validates the provided JWT against the known secret for the Daemon and returns the
// parsed data.
//
// This function DOES NOT validate that the token is valid for the connected server, nor
// does it ensure that the user providing the token is able to actually do things.
func ParseJWT(token []byte) (*WebsocketTokenPayload, error) {
	var payload WebsocketTokenPayload
	if alg == nil {
		alg = jwt.NewHS256([]byte(config.Get().AuthenticationToken))
	}

	now := time.Now()
	verifyOptions := jwt.ValidatePayload(
		&payload.Payload,
		jwt.ExpirationTimeValidator(now),
	)

	_, err := jwt.Verify(token, alg, &payload, verifyOptions)
	if err != nil {
		return nil, err
	}

	if !payload.HasPermission(PermissionConnect) {
		return nil, errors.New("not authorized to connect to this socket")
	}

	return &payload, nil
}

// Checks if the JWT is still valid.
func (wsh *WebsocketHandler) TokenValid() error {
	if wsh.JWT == nil {
		return errors.New("no jwt present")
	}

	if err := jwt.ExpirationTimeValidator(time.Now())(&wsh.JWT.Payload); err != nil {
		return err
	}

	if !wsh.JWT.HasPermission(PermissionConnect) {
		return errors.New("jwt does not have connect permission")
	}

	if wsh.Server.Uuid != wsh.JWT.ServerUUID {
		return errors.New("jwt server uuid mismatch")
	}

	return nil
}

// Handle a request for a specific server websocket. This will handle inbound requests as well
// as ensure that any console output is also passed down the wire on the socket.
func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	c, err := rt.upgrader.Upgrade(w, r, nil)
	if err != nil {
		zap.S().Errorw("error upgrading websocket", zap.Error(errors.WithStack(err)))
		http.Error(w, "failed to upgrade websocket", http.StatusInternalServerError)

		return
	}

	// Make a ticker and completion channel that is used to continuously poll the
	// JWT stored in the session to send events to the socket when it is expiring.
	ticker := time.NewTicker(time.Second * 30)
	done := make(chan bool)

	// Whenever this function is complete, end the ticker, close out the channel,
	// and then close the websocket connection.
	defer func() {
		ticker.Stop()
		done <- true
		c.Close()
	}()

	s := rt.GetServer(ps.ByName("server"))
	handler := WebsocketHandler{
		Server:     s,
		Mutex:      sync.Mutex{},
		Connection: c,
		JWT:        nil,
	}

	events := []string{
		server.StatsEvent,
		server.StatusEvent,
		server.ConsoleOutputEvent,
		server.InstallOutputEvent,
		server.DaemonMessageEvent,
	}

	eventChannel := make(chan server.Event)
	for _, event := range events {
		s.Events().Subscribe(event, eventChannel)
	}

	defer func() {
		for _, event := range events {
			s.Events().Unsubscribe(event, eventChannel)
		}

		close(eventChannel)
	}()

	// Listen for different events emitted by the server and respond to them appropriately.
	go func() {
		for {
			select {
			case d := <-eventChannel:
				handler.SendJson(&WebsocketMessage{
					Event: d.Topic,
					Args:  []string{d.Data},
				})
			}
		}
	}()

	// Sit here and check the time to expiration on the JWT every 30 seconds until
	// the token has expired. If we are within 3 minutes of the token expiring, send
	// a notice over the socket that it is expiring soon. If it has expired, send that
	// notice as well.
	go func() {
		for {
			select {
			case <-done:
				return
			case <-ticker.C:
				{
					if handler.JWT != nil {
						if handler.JWT.ExpirationTime.Unix()-time.Now().Unix() <= 0 {
							handler.SendJson(&WebsocketMessage{Event: TokenExpiredEvent})
						} else if handler.JWT.ExpirationTime.Unix()-time.Now().Unix() <= 180 {
							handler.SendJson(&WebsocketMessage{Event: TokenExpiringEvent})
						}
					}
				}
			}
		}
	}()

	for {
		j := WebsocketMessage{inbound: true}

		_, p, err := c.ReadMessage()
		if err != nil {
			if !websocket.IsCloseError(
				err,
				websocket.CloseNormalClosure,
				websocket.CloseGoingAway,
				websocket.CloseNoStatusReceived,
				websocket.CloseServiceRestart,
				websocket.CloseAbnormalClosure,
			) {
				zap.S().Errorw("error handling websocket message", zap.Error(err))
			}
			break
		}

		// Discard and JSON parse errors into the void and don't continue processing this
		// specific socket request. If we did a break here the client would get disconnected
		// from the socket, which is NOT what we want to do.
		if err := json.Unmarshal(p, &j); err != nil {
			continue
		}

		if err := handler.HandleInbound(j); err != nil {
			handler.SendErrorJson(err)
		}
	}
}

// Perform a blocking send operation on the websocket since we want to avoid any
// concurrent writes to the connection, which would cause a runtime panic and cause
// the program to crash out.
func (wsh *WebsocketHandler) SendJson(v *WebsocketMessage) error {
	// Do not send JSON down the line if the JWT on the connection is not
	// valid!
	if err := wsh.TokenValid(); err != nil {
		return nil
	}

	// If we're sending installation output but the user does not have the required
	// permissions to see the output, don't send it down the line.
	if v.Event == server.InstallOutputEvent {
		zap.S().Debugf("%+v", v.Args)
		if wsh.JWT != nil && !wsh.JWT.HasPermission(PermissionReceiveInstall) {
			return nil
		}
	}

	return wsh.unsafeSendJson(v)
}

// Sends JSON over the websocket connection, ignoring the authentication state of the
// socket user. Do not call this directly unless you are positive a response should be
// sent back to the client!
func (wsh *WebsocketHandler) unsafeSendJson(v interface{}) error {
	wsh.Mutex.Lock()
	defer wsh.Mutex.Unlock()

	return wsh.Connection.WriteJSON(v)
}

// Sends an error back to the connected websocket instance by checking the permissions
// of the token. If the user has the "receive-errors" grant we will send back the actual
// error message, otherwise we just send back a standard error message.
func (wsh *WebsocketHandler) SendErrorJson(err error) error {
	wsh.Mutex.Lock()
	defer wsh.Mutex.Unlock()

	message := "an unexpected error was encountered while handling this request"
	if wsh.JWT != nil {
		if server.IsSuspendedError(err) || wsh.JWT.HasPermission(PermissionReceiveErrors) {
			message = err.Error()
		}
	}

	m, u := wsh.GetErrorMessage(message)

	wsm := WebsocketMessage{Event: ErrorEvent}
	wsm.Args = []string{m}

	if !server.IsSuspendedError(err) {
		zap.S().Errorw(
			"an error was encountered in the websocket process",
			zap.String("server", wsh.Server.Uuid),
			zap.String("error_identifier", u.String()),
			zap.Error(err),
		)
	}

	return wsh.Connection.WriteJSON(wsm)
}

// Converts an error message into a more readable representation and returns a UUID
// that can be cross-referenced to find the specific error that triggered.
func (wsh *WebsocketHandler) GetErrorMessage(msg string) (string, uuid.UUID) {
	u, _ := uuid.NewRandom()

	m := fmt.Sprintf("Error Event [%s]: %s", u.String(), msg)

	return m, u
}

// Handle the inbound socket request and route it to the proper server action.
func (wsh *WebsocketHandler) HandleInbound(m WebsocketMessage) error {
	if !m.inbound {
		return errors.New("cannot handle websocket message, not an inbound connection")
	}

	if m.Event != AuthenticationEvent {
		if err := wsh.TokenValid(); err != nil {
			zap.S().Debugw("jwt token is no longer valid", zap.String("message", err.Error()))

			wsh.unsafeSendJson(WebsocketMessage{
				Event: ErrorEvent,
				Args:  []string{"could not authenticate client: " + err.Error()},
			})

			return nil
		}
	}

	switch m.Event {
	case AuthenticationEvent:
		{
			token, err := ParseJWT([]byte(strings.Join(m.Args, "")))
			if err != nil {
				return err
			}

			if token.HasPermission(PermissionConnect) {
				wsh.JWT = token
			}

			// On every authentication event, send the current server status back
			// to the client. :)
			wsh.Server.Events().Publish(server.StatusEvent, wsh.Server.State)

			wsh.unsafeSendJson(WebsocketMessage{
				Event: AuthenticationSuccessEvent,
				Args:  []string{},
			})

			return nil
		}
	case SetStateEvent:
		{
			if !wsh.JWT.HasPermission(PermissionSendPower) {
				return nil
			}

			switch strings.Join(m.Args, "") {
			case "start":
				return wsh.Server.Environment.Start()
			case "stop":
				return wsh.Server.Environment.Stop()
			case "restart":
				return nil
			case "kill":
				return wsh.Server.Environment.Terminate(os.Kill)
			}

			return nil
		}
	case SendServerLogsEvent:
		{
			if running, _ := wsh.Server.Environment.IsRunning(); !running {
				return nil
			}

			logs, err := wsh.Server.Environment.Readlog(1024 * 16)
			if err != nil {
				return err
			}

			for _, line := range logs {
				wsh.SendJson(&WebsocketMessage{
					Event: server.ConsoleOutputEvent,
					Args:  []string{line},
				})
			}

			return nil
		}
	case SendCommandEvent:
		{
			if !wsh.JWT.HasPermission(PermissionSendCommand) {
				return nil
			}

			if wsh.Server.State == server.ProcessOfflineState {
				return nil
			}

			return wsh.Server.Environment.SendCommand(strings.Join(m.Args, ""))
		}
	}

	return nil
}
<commit_msg>Fixes stuck Go-routine<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/gbrlsnchs/jwt/v3"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"github.com/julienschmidt/httprouter"
	"github.com/pkg/errors"
	"github.com/pterodactyl/wings/config"
	"github.com/pterodactyl/wings/server"
	"go.uber.org/zap"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
)

const (
	AuthenticationSuccessEvent = "auth success"
	TokenExpiringEvent         = "token expiring"
	TokenExpiredEvent          = "token expired"
	AuthenticationEvent        = "auth"
	SetStateEvent              = "set state"
	SendServerLogsEvent        = "send logs"
	SendCommandEvent           = "send command"
	ErrorEvent                 = "daemon error"
)

type WebsocketMessage struct {
	// The event to perform. Should be one of the following that are supported:
	//
	// - status : Returns the server's power state.
	// - logs : Returns the server log data at the time of the request.
	// - power : Performs a power action aganist the server based the data.
	// - command : Performs a command on a server using the data field.
	Event string `json:"event"`

	// The data to pass along, only used by power/command currently. Other requests
	// should either omit the field or pass an empty value as it is ignored.
	Args []string `json:"args,omitempty"`

	// Is set to true when the request is originating from outside of the Daemon,
	// otherwise set to false for outbound.
	inbound bool
}

type WebsocketHandler struct {
	Server     *server.Server
	Mutex      sync.Mutex
	Connection *websocket.Conn
	JWT        *WebsocketTokenPayload
}

type WebsocketTokenPayload struct {
	jwt.Payload
	UserID      json.Number `json:"user_id"`
	ServerUUID  string      `json:"server_uuid"`
	Permissions []string    `json:"permissions"`
}

const (
	PermissionConnect        = "connect"
	PermissionSendCommand    = "send-command"
	PermissionSendPower      = "send-power"
	PermissionReceiveErrors  = "receive-errors"
	PermissionReceiveInstall = "receive-install"
)

// Checks if the given token payload has a permission string.
func (wtp *WebsocketTokenPayload) HasPermission(permission string) bool {
	for _, k := range wtp.Permissions {
		if k == permission {
			return true
		}
	}

	return false
}

var alg *jwt.HMACSHA

// Validates the provided JWT against the known secret for the Daemon and returns the
// parsed data.
//
// This function DOES NOT validate that the token is valid for the connected server, nor
// does it ensure that the user providing the token is able to actually do things.
func ParseJWT(token []byte) (*WebsocketTokenPayload, error) {
	var payload WebsocketTokenPayload
	if alg == nil {
		alg = jwt.NewHS256([]byte(config.Get().AuthenticationToken))
	}

	now := time.Now()
	verifyOptions := jwt.ValidatePayload(
		&payload.Payload,
		jwt.ExpirationTimeValidator(now),
	)

	_, err := jwt.Verify(token, alg, &payload, verifyOptions)
	if err != nil {
		return nil, err
	}

	if !payload.HasPermission(PermissionConnect) {
		return nil, errors.New("not authorized to connect to this socket")
	}

	return &payload, nil
}

// Checks if the JWT is still valid.
func (wsh *WebsocketHandler) TokenValid() error {
	if wsh.JWT == nil {
		return errors.New("no jwt present")
	}

	if err := jwt.ExpirationTimeValidator(time.Now())(&wsh.JWT.Payload); err != nil {
		return err
	}

	if !wsh.JWT.HasPermission(PermissionConnect) {
		return errors.New("jwt does not have connect permission")
	}

	if wsh.Server.Uuid != wsh.JWT.ServerUUID {
		return errors.New("jwt server uuid mismatch")
	}

	return nil
}

// Handle a request for a specific server websocket. This will handle inbound requests as well
// as ensure that any console output is also passed down the wire on the socket.
func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	c, err := rt.upgrader.Upgrade(w, r, nil)
	if err != nil {
		zap.S().Errorw("error upgrading websocket", zap.Error(errors.WithStack(err)))
		http.Error(w, "failed to upgrade websocket", http.StatusInternalServerError)

		return
	}

	// Make a ticker and completion channel that is used to continuously poll the
	// JWT stored in the session to send events to the socket when it is expiring.
	ticker := time.NewTicker(time.Second * 30)
	done := make(chan bool)

	// Whenever this function is complete, end the ticker, close out the channel,
	// and then close the websocket connection.
	defer func() {
		ticker.Stop()
		done <- true
		c.Close()
	}()

	s := rt.GetServer(ps.ByName("server"))
	handler := WebsocketHandler{
		Server:     s,
		Mutex:      sync.Mutex{},
		Connection: c,
		JWT:        nil,
	}

	events := []string{
		server.StatsEvent,
		server.StatusEvent,
		server.ConsoleOutputEvent,
		server.InstallOutputEvent,
		server.DaemonMessageEvent,
	}

	eventChannel := make(chan server.Event)
	for _, event := range events {
		s.Events().Subscribe(event, eventChannel)
	}

	defer func() {
		for _, event := range events {
			s.Events().Unsubscribe(event, eventChannel)
		}

		close(eventChannel)
	}()

	// Listen for different events emitted by the server and respond to them appropriately.
	go func() {
		for d := range eventChannel {
			handler.SendJson(&WebsocketMessage{
				Event: d.Topic,
				Args:  []string{d.Data},
			})
		}
	}()
	// Sit here and check the time to expiration on the JWT every 30 seconds until
	// the token has expired. If we are within 3 minutes of the token expiring, send
	// a notice over the socket that it is expiring soon. If it has expired, send that
	// notice as well.
	go func() {
		for {
			select {
			case <-done:
				return
			case <-ticker.C:
				{
					if handler.JWT != nil {
						if handler.JWT.ExpirationTime.Unix()-time.Now().Unix() <= 0 {
							handler.SendJson(&WebsocketMessage{Event: TokenExpiredEvent})
						} else if handler.JWT.ExpirationTime.Unix()-time.Now().Unix() <= 180 {
							handler.SendJson(&WebsocketMessage{Event: TokenExpiringEvent})
						}
					}
				}
			}
		}
	}()

	for {
		j := WebsocketMessage{inbound: true}

		_, p, err := c.ReadMessage()
		if err != nil {
			if !websocket.IsCloseError(
				err,
				websocket.CloseNormalClosure,
				websocket.CloseGoingAway,
				websocket.CloseNoStatusReceived,
				websocket.CloseServiceRestart,
				websocket.CloseAbnormalClosure,
			) {
				zap.S().Errorw("error handling websocket message", zap.Error(err))
			}
			break
		}

		// Discard and JSON parse errors into the void and don't continue processing this
		// specific socket request. If we did a break here the client would get disconnected
		// from the socket, which is NOT what we want to do.
		if err := json.Unmarshal(p, &j); err != nil {
			continue
		}

		if err := handler.HandleInbound(j); err != nil {
			handler.SendErrorJson(err)
		}
	}
}

// Perform a blocking send operation on the websocket since we want to avoid any
// concurrent writes to the connection, which would cause a runtime panic and cause
// the program to crash out.
func (wsh *WebsocketHandler) SendJson(v *WebsocketMessage) error {
	// Do not send JSON down the line if the JWT on the connection is not
	// valid!
	if err := wsh.TokenValid(); err != nil {
		return nil
	}

	// If we're sending installation output but the user does not have the required
	// permissions to see the output, don't send it down the line.
	if v.Event == server.InstallOutputEvent {
		zap.S().Debugf("%+v", v.Args)
		if wsh.JWT != nil && !wsh.JWT.HasPermission(PermissionReceiveInstall) {
			return nil
		}
	}

	return wsh.unsafeSendJson(v)
}

// Sends JSON over the websocket connection, ignoring the authentication state of the
// socket user. Do not call this directly unless you are positive a response should be
// sent back to the client!
func (wsh *WebsocketHandler) unsafeSendJson(v interface{}) error {
	wsh.Mutex.Lock()
	defer wsh.Mutex.Unlock()

	return wsh.Connection.WriteJSON(v)
}

// Sends an error back to the connected websocket instance by checking the permissions
// of the token. If the user has the "receive-errors" grant we will send back the actual
// error message, otherwise we just send back a standard error message.
func (wsh *WebsocketHandler) SendErrorJson(err error) error {
	wsh.Mutex.Lock()
	defer wsh.Mutex.Unlock()

	message := "an unexpected error was encountered while handling this request"
	if wsh.JWT != nil {
		if server.IsSuspendedError(err) || wsh.JWT.HasPermission(PermissionReceiveErrors) {
			message = err.Error()
		}
	}

	m, u := wsh.GetErrorMessage(message)

	wsm := WebsocketMessage{Event: ErrorEvent}
	wsm.Args = []string{m}

	if !server.IsSuspendedError(err) {
		zap.S().Errorw(
			"an error was encountered in the websocket process",
			zap.String("server", wsh.Server.Uuid),
			zap.String("error_identifier", u.String()),
			zap.Error(err),
		)
	}

	return wsh.Connection.WriteJSON(wsm)
}

// Converts an error message into a more readable representation and returns a UUID
// that can be cross-referenced to find the specific error that triggered.
func (wsh *WebsocketHandler) GetErrorMessage(msg string) (string, uuid.UUID) {
	u, _ := uuid.NewRandom()

	m := fmt.Sprintf("Error Event [%s]: %s", u.String(), msg)

	return m, u
}

// Handle the inbound socket request and route it to the proper server action.
func (wsh *WebsocketHandler) HandleInbound(m WebsocketMessage) error {
	if !m.inbound {
		return errors.New("cannot handle websocket message, not an inbound connection")
	}

	if m.Event != AuthenticationEvent {
		if err := wsh.TokenValid(); err != nil {
			zap.S().Debugw("jwt token is no longer valid", zap.String("message", err.Error()))

			wsh.unsafeSendJson(WebsocketMessage{
				Event: ErrorEvent,
				Args:  []string{"could not authenticate client: " + err.Error()},
			})

			return nil
		}
	}

	switch m.Event {
	case AuthenticationEvent:
		{
			token, err := ParseJWT([]byte(strings.Join(m.Args, "")))
			if err != nil {
				return err
			}

			if token.HasPermission(PermissionConnect) {
				wsh.JWT = token
			}

			// On every authentication event, send the current server status back
			// to the client. :)
			wsh.Server.Events().Publish(server.StatusEvent, wsh.Server.State)

			wsh.unsafeSendJson(WebsocketMessage{
				Event: AuthenticationSuccessEvent,
				Args:  []string{},
			})

			return nil
		}
	case SetStateEvent:
		{
			if !wsh.JWT.HasPermission(PermissionSendPower) {
				return nil
			}

			switch strings.Join(m.Args, "") {
			case "start":
				return wsh.Server.Environment.Start()
			case "stop":
				return wsh.Server.Environment.Stop()
			case "restart":
				return nil
			case "kill":
				return wsh.Server.Environment.Terminate(os.Kill)
			}

			return nil
		}
	case SendServerLogsEvent:
		{
			if running, _ := wsh.Server.Environment.IsRunning(); !running {
				return nil
			}

			logs, err := wsh.Server.Environment.Readlog(1024 * 16)
			if err != nil {
				return err
			}

			for _, line := range logs {
				wsh.SendJson(&WebsocketMessage{
					Event: server.ConsoleOutputEvent,
					Args:  []string{line},
				})
			}

			return nil
		}
	case SendCommandEvent:
		{
			if !wsh.JWT.HasPermission(PermissionSendCommand) {
				return nil
			}

			if wsh.Server.State == server.ProcessOfflineState {
				return nil
			}

			return wsh.Server.Environment.SendCommand(strings.Join(m.Args, ""))
		}
	}

	return nil
}
<|endoftext|>


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

package dwarfgen

import (
	"sort"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/internal/dwarf"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// See golang.org/issue/20390.
func xposBefore(p, q src.XPos) bool {
	return base.Ctxt.PosTable.Pos(p).Before(base.Ctxt.PosTable.Pos(q))
}

func findScope(marks []ir.Mark, pos src.XPos) ir.ScopeID {
	i := sort.Search(len(marks), func(i int) bool {
		return xposBefore(pos, marks[i].Pos)
	})
	if i == 0 {
		return 0
	}
	return marks[i-1].Scope
}

func assembleScopes(fnsym *obj.LSym, fn *ir.Func, dwarfVars []*dwarf.Var, varScopes []ir.ScopeID) []dwarf.Scope {
	// Initialize the DWARF scope tree based on lexical scopes.
	dwarfScopes := make([]dwarf.Scope, 1+len(fn.Parents))
	for i, parent := range fn.Parents {
		dwarfScopes[i+1].Parent = int32(parent)
	}

	scopeVariables(dwarfVars, varScopes, dwarfScopes)
	if fnsym.Func().Text != nil {
		scopePCs(fnsym, fn.Marks, dwarfScopes)
	}
	return compactScopes(dwarfScopes)
}

// scopeVariables assigns DWARF variable records to their scopes.
func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ir.ScopeID, dwarfScopes []dwarf.Scope) {
	sort.Stable(varsByScopeAndOffset{dwarfVars, varScopes})

	i0 := 0
	for i := range dwarfVars {
		if varScopes[i] == varScopes[i0] {
			continue
		}
		dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:i]
		i0 = i
	}
	if i0 < len(dwarfVars) {
		dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:]
	}
}

// scopePCs assigns PC ranges to their scopes.
func scopePCs(fnsym *obj.LSym, marks []ir.Mark, dwarfScopes []dwarf.Scope) {
	// If there aren't any child scopes (in particular, when scope
	// tracking is disabled), we can skip a whole lot of work.
	if len(marks) == 0 {
		return
	}
	p0 := fnsym.Func().Text
	scope := findScope(marks, p0.Pos)
	for p := p0; p != nil; p = p.Link {
		if p.Pos == p0.Pos {
			continue
		}
		dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: p.Pc})
		p0 = p
		scope = findScope(marks, p0.Pos)
	}
	if p0.Pc < fnsym.Size {
		dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: fnsym.Size})
	}
}

func compactScopes(dwarfScopes []dwarf.Scope) []dwarf.Scope {
	// Reverse pass to propagate PC ranges to parent scopes.
	for i := len(dwarfScopes) - 1; i > 0; i-- {
		s := &dwarfScopes[i]
		dwarfScopes[s.Parent].UnifyRanges(s)
	}

	return dwarfScopes
}

type varsByScopeAndOffset struct {
	vars   []*dwarf.Var
	scopes []ir.ScopeID
}

func (v varsByScopeAndOffset) Len() int {
	return len(v.vars)
}

func (v varsByScopeAndOffset) Less(i, j int) bool {
	if v.scopes[i] != v.scopes[j] {
		return v.scopes[i] < v.scopes[j]
	}
	return v.vars[i].StackOffset < v.vars[j].StackOffset
}

func (v varsByScopeAndOffset) Swap(i, j int) {
	v.vars[i], v.vars[j] = v.vars[j], v.vars[i]
	v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i]
}
<commit_msg>cmd/compile: preserve argument order in debug_info<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dwarfgen

import (
	"sort"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/internal/dwarf"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// See golang.org/issue/20390.
func xposBefore(p, q src.XPos) bool {
	return base.Ctxt.PosTable.Pos(p).Before(base.Ctxt.PosTable.Pos(q))
}

func findScope(marks []ir.Mark, pos src.XPos) ir.ScopeID {
	i := sort.Search(len(marks), func(i int) bool {
		return xposBefore(pos, marks[i].Pos)
	})
	if i == 0 {
		return 0
	}
	return marks[i-1].Scope
}

func assembleScopes(fnsym *obj.LSym, fn *ir.Func, dwarfVars []*dwarf.Var, varScopes []ir.ScopeID) []dwarf.Scope {
	// Initialize the DWARF scope tree based on lexical scopes.
	dwarfScopes := make([]dwarf.Scope, 1+len(fn.Parents))
	for i, parent := range fn.Parents {
		dwarfScopes[i+1].Parent = int32(parent)
	}

	scopeVariables(dwarfVars, varScopes, dwarfScopes, fnsym.ABI() != obj.ABI0)
	if fnsym.Func().Text != nil {
		scopePCs(fnsym, fn.Marks, dwarfScopes)
	}
	return compactScopes(dwarfScopes)
}

// scopeVariables assigns DWARF variable records to their scopes.
func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ir.ScopeID, dwarfScopes []dwarf.Scope, regabi bool) {
	if regabi {
		sort.Stable(varsByScope{dwarfVars, varScopes})
	} else {
		sort.Stable(varsByScopeAndOffset{dwarfVars, varScopes})
	}

	i0 := 0
	for i := range dwarfVars {
		if varScopes[i] == varScopes[i0] {
			continue
		}
		dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:i]
		i0 = i
	}
	if i0 < len(dwarfVars) {
		dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:]
	}
}

// scopePCs assigns PC ranges to their scopes.
func scopePCs(fnsym *obj.LSym, marks []ir.Mark, dwarfScopes []dwarf.Scope) {
	// If there aren't any child scopes (in particular, when scope
	// tracking is disabled), we can skip a whole lot of work.
	if len(marks) == 0 {
		return
	}
	p0 := fnsym.Func().Text
	scope := findScope(marks, p0.Pos)
	for p := p0; p != nil; p = p.Link {
		if p.Pos == p0.Pos {
			continue
		}
		dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: p.Pc})
		p0 = p
		scope = findScope(marks, p0.Pos)
	}
	if p0.Pc < fnsym.Size {
		dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: fnsym.Size})
	}
}

func compactScopes(dwarfScopes []dwarf.Scope) []dwarf.Scope {
	// Reverse pass to propagate PC ranges to parent scopes.
	for i := len(dwarfScopes) - 1; i > 0; i-- {
		s := &dwarfScopes[i]
		dwarfScopes[s.Parent].UnifyRanges(s)
	}

	return dwarfScopes
}

type varsByScopeAndOffset struct {
	vars   []*dwarf.Var
	scopes []ir.ScopeID
}

func (v varsByScopeAndOffset) Len() int {
	return len(v.vars)
}

func (v varsByScopeAndOffset) Less(i, j int) bool {
	if v.scopes[i] != v.scopes[j] {
		return v.scopes[i] < v.scopes[j]
	}
	return v.vars[i].StackOffset < v.vars[j].StackOffset
}

func (v varsByScopeAndOffset) Swap(i, j int) {
	v.vars[i], v.vars[j] = v.vars[j], v.vars[i]
	v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i]
}

type varsByScope struct {
	vars   []*dwarf.Var
	scopes []ir.ScopeID
}

func (v varsByScope) Len() int {
	return len(v.vars)
}

func (v varsByScope) Less(i, j int) bool {
	return v.scopes[i] < v.scopes[j]
}

func (v varsByScope) Swap(i, j int) {
	v.vars[i], v.vars[j] = v.vars[j], v.vars[i]
	v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i]
}
<|endoftext|>


<commit_before>package Golf

import (
	"testing"
)

func TestStringRendering(t *testing.T) {
	cases := []struct {
		content string
		args    map[string]interface{}
		output  string
	}{
		{
			"This is a sample template without args.",
			map[string]interface{}{},
			"This is a sample template without args.",
		},
		{
			"Article: {{ .title }}",
			map[string]interface{}{
				"title": "Hello World",
			},
			"Article: Hello World",
		},
		{
			"Article: {{ .title }}, Count: {{ .count }}",
			map[string]interface{}{
				"title": "Hello World",
				"count": 5,
			},
			"Article: Hello World, Count: 5",
		},
	}

	for _, c := range cases {
		view := NewView()
		result, error := view.RenderFromString("", c.content, c.args)
		if error != nil {
			panic(error)
		}
		if result != c.output {
			t.Errorf("Rendered content %q != %q", result, c.output)
		}
	}
}
<commit_msg>[test] Add test case `TestSetTemplateLoader`<commit_after>package Golf

import (
	"testing"
)

func TestStringRendering(t *testing.T) {
	cases := []struct {
		content string
		args    map[string]interface{}
		output  string
	}{
		{
			"This is a sample template without args.",
			map[string]interface{}{},
			"This is a sample template without args.",
		},
		{
			"Article: {{ .title }}",
			map[string]interface{}{
				"title": "Hello World",
			},
			"Article: Hello World",
		},
		{
			"Article: {{ .title }}, Count: {{ .count }}",
			map[string]interface{}{
				"title": "Hello World",
				"count": 5,
			},
			"Article: Hello World, Count: 5",
		},
	}

	for _, c := range cases {
		view := NewView()
		result, err := view.RenderFromString("", c.content, c.args)
		if err != nil {
			t.Errorf("Can not render from string")
		}
		if result != c.output {
			t.Errorf("Rendered content %q != %q", result, c.output)
		}
	}
}

func TestSetTemplateLoader(t *testing.T) {
	view := NewView()
	view.SetTemplateLoader("test", "/test/path/")
	if view.templateLoader["test"] == nil {
		t.Errorf("Could not set template loader for view")
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package vm

// mtail programs may be updated while emtail is running, and they will be
// reloaded without having to restart the mtail process. Programs can be
// created and deleted as well, and some configuration systems do an atomic
// rename of the program when it is installed, so mtail is also aware of file
// moves.

import (
	"errors"
	"expvar"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sync"

	"github.com/golang/glog"
	"github.com/spf13/afero"

	"github.com/google/mtail/metrics"
	"github.com/google/mtail/watcher"
)

var (
	// ProgLoads counts the number of program load events.
	ProgLoads = expvar.NewMap("prog_loads_total")
	// ProgLoadErrors counts the number of program load errors.
	ProgLoadErrors = expvar.NewMap("prog_load_errors")
)

var (
	// LineCount counts the number of lines read by the virtual machine engine from the input channel.
	LineCount = expvar.NewInt("line_count")
)

const (
	fileExt = ".mtail"
)

// LoadProgs loads all programs in a directory and starts watching the
// directory for filesystem changes.  The total number of program errors is
// returned.
func (l *Loader) LoadProgs(programPath string) error {
	l.w.Add(programPath)

	s, err := os.Stat(programPath)
	if err != nil {
		return fmt.Errorf("failed to stat: %s", err)
	}
	switch {
	case s.IsDir():
		fis, err := ioutil.ReadDir(programPath)
		if err != nil {
			return fmt.Errorf("Failed to list programs in %q: %s", programPath, err)
		}

		for _, fi := range fis {
			if fi.IsDir() {
				continue
			}
			l.LoadProg(path.Join(programPath, fi.Name()))
		}
		return nil
	default:
		return l.LoadProg(programPath)
	}
}

// LoadProg loads or reloads a program from the path specified.  The name of
// the program is the basename of the file.
func (l *Loader) LoadProg(programPath string) error {
	name := filepath.Base(programPath)
	if filepath.Ext(name) != fileExt {
		glog.Infof("Skipping %s due to file extension.", programPath)
		return nil
	}
	f, err := l.fs.Open(programPath)
	if err != nil {
		ProgLoadErrors.Add(name, 1)
		return fmt.Errorf("Failed to read program %q: %s", programPath, err)
	}
	defer f.Close()
	return l.CompileAndRun(name, f)
}

// CompileAndRun compiles a program read from the input, starting execution if
// it succeeds.  If an existing virtual machine of the same name already
// exists, the previous virtual machine is terminated and the new loaded over
// it.  If the new program fails to compile, any existing virtual machine with
// the same name remains running.
func (l *Loader) CompileAndRun(name string, input io.Reader) error {
	o := &Options{CompileOnly: l.compileOnly, SyslogUseCurrentYear: l.syslogUseCurrentYear}
	v, errs := Compile(name, input, l.ms, o)
	if errs != nil {
		ProgLoadErrors.Add(name, 1)
		return fmt.Errorf("compile failed for %s:\n%s", name, errs)
	}
	if l.dumpBytecode {
		v.DumpByteCode(name)
	}
	ProgLoads.Add(name, 1)
	glog.Infof("Loaded program %s", name)

	l.handleMu.Lock()
	defer l.handleMu.Unlock()
	// Stop any previous VM.
	if handle, ok := l.handles[name]; ok {
		close(handle.lines)
		<-handle.done
	}
	l.handles[name] = &vmHandle{make(chan string), make(chan struct{})}
	go v.Run(l.handles[name].lines, l.handles[name].done)
	return nil
}

// Loader handles the lifecycle of programs and virtual machines, by watching
// the configured program source directory, compiling changes to programs, and
// managing the running virtual machines that receive input from the lines
// channel.
type Loader struct {
	w  watcher.Watcher // watches for program changes
	fs afero.Fs        // filesystem interface
	ms *metrics.Store  // pointer to store to pass to compiler

	handles  map[string]*vmHandle // map of program names to virtual machines
	handleMu sync.RWMutex         // guards accesses to handles

	watcherDone chan struct{} // Synchronise shutdown of the watcher and lines handlers.
	VMsDone     chan struct{} // Notify mtail when all running VMs are shutdown.

	compileOnly          bool // Only compile programs and report errors, do not load VMs.
	dumpBytecode         bool // Instructs the loader to dump to stdout the compiled program after compilation.
	syslogUseCurrentYear bool // Instructs the VM to overwrite zero years with the current year in a strptime instruction.
}

// LoaderOptions contains the required and optional parameters for creating a
// new Loader.
type LoaderOptions struct {
	Store *metrics.Store
	Lines <-chan string
	W     watcher.Watcher // Not required, will use watcher.LogWatcher if zero.
	FS    afero.Fs        // Not required, will use afero.OsFs if zero.

	CompileOnly          bool
	DumpBytecode         bool
	SyslogUseCurrentYear bool
}

// NewLoader creates a new program loader.  It takes a filesystem watcher
// and a filesystem interface as arguments.  If fs is nil, it will use the
// default filesystem interface.
func NewLoader(o LoaderOptions) (*Loader, error) {
	if o.Store == nil || o.Lines == nil {
		return nil, errors.New("loader needs a store and lines")
	}
	fs := o.FS
	if fs == nil {
		fs = &afero.OsFs{}
	}
	w := o.W
	if w == nil {
		var err error
		w, err = watcher.NewLogWatcher()
		if err != nil {
			return nil, fmt.Errorf("Couldn't create a watcher for loader: %s", err)
		}
	}
	l := &Loader{
		w:                    w,
		ms:                   o.Store,
		fs:                   fs,
		handles:              make(map[string]*vmHandle),
		watcherDone:          make(chan struct{}),
		VMsDone:              make(chan struct{}),
		compileOnly:          o.CompileOnly,
		dumpBytecode:         o.DumpBytecode,
		syslogUseCurrentYear: o.SyslogUseCurrentYear}

	go l.processEvents()
	go l.processLines(o.Lines)
	return l, nil
}

type vmHandle struct {
	lines chan string
	done  chan struct{}
}

// processEvents manages program lifecycle triggered by events from the
// filesystem watcher.
func (l *Loader) processEvents() {
	defer close(l.watcherDone)
	for event := range l.w.Events() {
		switch event := event.(type) {
		case watcher.DeleteEvent:
			glog.Infof("delete prog %s", event.Pathname)
			l.UnloadProgram(event.Pathname)
		case watcher.UpdateEvent:
			glog.Infof("update prog %s", event.Pathname)
			l.LoadProg(event.Pathname)
		case watcher.CreateEvent:
			l.w.Add(event.Pathname)
		default:
			glog.V(1).Infof("Unexected event type %+#v", event)
		}
	}
}

// processLines provides fanout of the input log lines to each virtual machine
// running.  Upon close of the incoming lines channel, it also communicates
// shutdown to the target VMs via channel close.
func (l *Loader) processLines(lines <-chan string) {
	for line := range lines {
		LineCount.Add(1)
		l.handleMu.RLock()
		for prog := range l.handles {
			l.handles[prog].lines <- line
		}
		l.handleMu.RUnlock()
	}
	glog.Info("Shutting down loader.")
	l.w.Close()
	<-l.watcherDone
	l.handleMu.Lock()
	defer l.handleMu.Unlock()
	for prog := range l.handles {
		close(l.handles[prog].lines)
		<-l.handles[prog].done
		delete(l.handles, prog)
	}
	close(l.VMsDone)
}

// UnloadProgram removes the named program from the watcher to prevent future
// updates, and terminates any currently running VM goroutine.
func (l *Loader) UnloadProgram(pathname string) {
	if err := l.w.Remove(pathname); err != nil {
		glog.Infof("Remove watch on %s failed: %s", pathname, err)
	}
	name := filepath.Base(pathname)
	l.handleMu.Lock()
	defer l.handleMu.Unlock()
	if handle, ok := l.handles[name]; ok {
		close(handle.lines)
		<-handle.done
		delete(l.handles, name)
	}
}
<commit_msg>Catch nil program from Compile.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package vm

// mtail programs may be updated while emtail is running, and they will be
// reloaded without having to restart the mtail process. Programs can be
// created and deleted as well, and some configuration systems do an atomic
// rename of the program when it is installed, so mtail is also aware of file
// moves.

import (
	"errors"
	"expvar"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sync"

	"github.com/golang/glog"
	"github.com/spf13/afero"

	"github.com/google/mtail/metrics"
	"github.com/google/mtail/watcher"
)

var (
	// ProgLoads counts the number of program load events.
	ProgLoads = expvar.NewMap("prog_loads_total")
	// ProgLoadErrors counts the number of program load errors.
	ProgLoadErrors = expvar.NewMap("prog_load_errors")
)

var (
	// LineCount counts the number of lines read by the virtual machine engine from the input channel.
	LineCount = expvar.NewInt("line_count")
)

const (
	fileExt = ".mtail"
)

// LoadProgs loads all programs in a directory and starts watching the
// directory for filesystem changes.  The total number of program errors is
// returned.
func (l *Loader) LoadProgs(programPath string) error {
	l.w.Add(programPath)

	s, err := os.Stat(programPath)
	if err != nil {
		return fmt.Errorf("failed to stat: %s", err)
	}
	switch {
	case s.IsDir():
		fis, err := ioutil.ReadDir(programPath)
		if err != nil {
			return fmt.Errorf("Failed to list programs in %q: %s", programPath, err)
		}

		for _, fi := range fis {
			if fi.IsDir() {
				continue
			}
			l.LoadProg(path.Join(programPath, fi.Name()))
		}
		return nil
	default:
		return l.LoadProg(programPath)
	}
}

// LoadProg loads or reloads a program from the path specified.  The name of
// the program is the basename of the file.
func (l *Loader) LoadProg(programPath string) error {
	name := filepath.Base(programPath)
	if filepath.Ext(name) != fileExt {
		glog.Infof("Skipping %s due to file extension.", programPath)
		return nil
	}
	f, err := l.fs.Open(programPath)
	if err != nil {
		ProgLoadErrors.Add(name, 1)
		return fmt.Errorf("Failed to read program %q: %s", programPath, err)
	}
	defer f.Close()
	return l.CompileAndRun(name, f)
}

// CompileAndRun compiles a program read from the input, starting execution if
// it succeeds.  If an existing virtual machine of the same name already
// exists, the previous virtual machine is terminated and the new loaded over
// it.  If the new program fails to compile, any existing virtual machine with
// the same name remains running.
func (l *Loader) CompileAndRun(name string, input io.Reader) error {
	o := &Options{CompileOnly: l.compileOnly, SyslogUseCurrentYear: l.syslogUseCurrentYear}
	v, errs := Compile(name, input, l.ms, o)
	if errs != nil {
		ProgLoadErrors.Add(name, 1)
		return fmt.Errorf("compile failed for %s:\n%s", name, errs)
	}
	if v == nil {
		glog.Warning("No program returned, but no errors.")
		return nil
	}
	if l.dumpBytecode {
		v.DumpByteCode(name)
	}
	ProgLoads.Add(name, 1)
	glog.Infof("Loaded program %s", name)

	l.handleMu.Lock()
	defer l.handleMu.Unlock()
	// Stop any previous VM.
	if handle, ok := l.handles[name]; ok {
		close(handle.lines)
		<-handle.done
	}
	l.handles[name] = &vmHandle{make(chan string), make(chan struct{})}
	go v.Run(l.handles[name].lines, l.handles[name].done)
	return nil
}

// Loader handles the lifecycle of programs and virtual machines, by watching
// the configured program source directory, compiling changes to programs, and
// managing the running virtual machines that receive input from the lines
// channel.
type Loader struct {
	w  watcher.Watcher // watches for program changes
	fs afero.Fs        // filesystem interface
	ms *metrics.Store  // pointer to store to pass to compiler

	handles  map[string]*vmHandle // map of program names to virtual machines
	handleMu sync.RWMutex         // guards accesses to handles

	watcherDone chan struct{} // Synchronise shutdown of the watcher and lines handlers.
	VMsDone     chan struct{} // Notify mtail when all running VMs are shutdown.

	compileOnly          bool // Only compile programs and report errors, do not load VMs.
	dumpBytecode         bool // Instructs the loader to dump to stdout the compiled program after compilation.
	syslogUseCurrentYear bool // Instructs the VM to overwrite zero years with the current year in a strptime instruction.
}

// LoaderOptions contains the required and optional parameters for creating a
// new Loader.
type LoaderOptions struct {
	Store *metrics.Store
	Lines <-chan string
	W     watcher.Watcher // Not required, will use watcher.LogWatcher if zero.
	FS    afero.Fs        // Not required, will use afero.OsFs if zero.

	CompileOnly          bool
	DumpBytecode         bool
	SyslogUseCurrentYear bool
}

// NewLoader creates a new program loader.  It takes a filesystem watcher
// and a filesystem interface as arguments.  If fs is nil, it will use the
// default filesystem interface.
func NewLoader(o LoaderOptions) (*Loader, error) {
	if o.Store == nil || o.Lines == nil {
		return nil, errors.New("loader needs a store and lines")
	}
	fs := o.FS
	if fs == nil {
		fs = &afero.OsFs{}
	}
	w := o.W
	if w == nil {
		var err error
		w, err = watcher.NewLogWatcher()
		if err != nil {
			return nil, fmt.Errorf("Couldn't create a watcher for loader: %s", err)
		}
	}
	l := &Loader{
		w:                    w,
		ms:                   o.Store,
		fs:                   fs,
		handles:              make(map[string]*vmHandle),
		watcherDone:          make(chan struct{}),
		VMsDone:              make(chan struct{}),
		compileOnly:          o.CompileOnly,
		dumpBytecode:         o.DumpBytecode,
		syslogUseCurrentYear: o.SyslogUseCurrentYear}

	go l.processEvents()
	go l.processLines(o.Lines)
	return l, nil
}

type vmHandle struct {
	lines chan string
	done  chan struct{}
}

// processEvents manages program lifecycle triggered by events from the
// filesystem watcher.
func (l *Loader) processEvents() {
	defer close(l.watcherDone)
	for event := range l.w.Events() {
		switch event := event.(type) {
		case watcher.DeleteEvent:
			glog.Infof("delete prog %s", event.Pathname)
			l.UnloadProgram(event.Pathname)
		case watcher.UpdateEvent:
			glog.Infof("update prog %s", event.Pathname)
			l.LoadProg(event.Pathname)
		case watcher.CreateEvent:
			l.w.Add(event.Pathname)
		default:
			glog.V(1).Infof("Unexected event type %+#v", event)
		}
	}
}

// processLines provides fanout of the input log lines to each virtual machine
// running.  Upon close of the incoming lines channel, it also communicates
// shutdown to the target VMs via channel close.
func (l *Loader) processLines(lines <-chan string) {
	for line := range lines {
		LineCount.Add(1)
		l.handleMu.RLock()
		for prog := range l.handles {
			l.handles[prog].lines <- line
		}
		l.handleMu.RUnlock()
	}
	glog.Info("Shutting down loader.")
	l.w.Close()
	<-l.watcherDone
	l.handleMu.Lock()
	defer l.handleMu.Unlock()
	for prog := range l.handles {
		close(l.handles[prog].lines)
		<-l.handles[prog].done
		delete(l.handles, prog)
	}
	close(l.VMsDone)
}

// UnloadProgram removes the named program from the watcher to prevent future
// updates, and terminates any currently running VM goroutine.
func (l *Loader) UnloadProgram(pathname string) {
	if err := l.w.Remove(pathname); err != nil {
		glog.Infof("Remove watch on %s failed: %s", pathname, err)
	}
	name := filepath.Base(pathname)
	l.handleMu.Lock()
	defer l.handleMu.Unlock()
	if handle, ok := l.handles[name]; ok {
		close(handle.lines)
		<-handle.done
		delete(l.handles, name)
	}
}
<|endoftext|>


<commit_before><commit_msg>Add the "PACH_JOB_ID" env var to the user code call.<commit_after><|endoftext|>


<commit_before>package json

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/influxdata/telegraf"
)

func TestSerializeMetricFloat(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": float64(91.5),
	}
	m, err := telegraf.NewMetric("cpu", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)
	expS := []string{fmt.Sprintf("{\"fields\":{\"usage_idle\":91.5},\"name\":\"cpu\",\"tags\":{\"cpu\":\"cpu0\"},\"timestamp\":%d}", now.Unix())}
	assert.Equal(t, expS, mS)
}

func TestSerializeMetricInt(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": int64(90),
	}
	m, err := telegraf.NewMetric("cpu", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)

	expS := []string{fmt.Sprintf("{\"fields\":{\"usage_idle\":90},\"name\":\"cpu\",\"tags\":{\"cpu\":\"cpu0\"},\"timestamp\":%d}", now.Unix())}
	assert.Equal(t, expS, mS)
}

func TestSerializeMetricString(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": "foobar",
	}
	m, err := telegraf.NewMetric("cpu", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)

	expS := []string{fmt.Sprintf("{\"fields\":{\"usage_idle\":\"foobar\"},\"name\":\"cpu\",\"tags\":{\"cpu\":\"cpu0\"},\"timestamp\":%d}", now.Unix())}
	assert.Equal(t, expS, mS)
}

func TestSerializeMultiFields(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle":  int64(90),
		"usage_total": 8559615,
	}
	m, err := telegraf.NewMetric("cpu", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)

	expS := []string{fmt.Sprintf("{\"fields\":{\"usage_idle\":90,\"usage_total\":8559615},\"name\":\"cpu\",\"tags\":{\"cpu\":\"cpu0\"},\"timestamp\":%d}", now.Unix())}
	assert.Equal(t, expS, mS)
}
<commit_msg>JSON serializer: include unit test with escapes<commit_after>package json

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/influxdata/telegraf"
)

func TestSerializeMetricFloat(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": float64(91.5),
	}
	m, err := telegraf.NewMetric("cpu", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)
	expS := []string{fmt.Sprintf("{\"fields\":{\"usage_idle\":91.5},\"name\":\"cpu\",\"tags\":{\"cpu\":\"cpu0\"},\"timestamp\":%d}", now.Unix())}
	assert.Equal(t, expS, mS)
}

func TestSerializeMetricInt(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": int64(90),
	}
	m, err := telegraf.NewMetric("cpu", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)

	expS := []string{fmt.Sprintf("{\"fields\":{\"usage_idle\":90},\"name\":\"cpu\",\"tags\":{\"cpu\":\"cpu0\"},\"timestamp\":%d}", now.Unix())}
	assert.Equal(t, expS, mS)
}

func TestSerializeMetricString(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": "foobar",
	}
	m, err := telegraf.NewMetric("cpu", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)

	expS := []string{fmt.Sprintf("{\"fields\":{\"usage_idle\":\"foobar\"},\"name\":\"cpu\",\"tags\":{\"cpu\":\"cpu0\"},\"timestamp\":%d}", now.Unix())}
	assert.Equal(t, expS, mS)
}

func TestSerializeMultiFields(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle":  int64(90),
		"usage_total": 8559615,
	}
	m, err := telegraf.NewMetric("cpu", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)

	expS := []string{fmt.Sprintf("{\"fields\":{\"usage_idle\":90,\"usage_total\":8559615},\"name\":\"cpu\",\"tags\":{\"cpu\":\"cpu0\"},\"timestamp\":%d}", now.Unix())}
	assert.Equal(t, expS, mS)
}

func TestSerializeMetricWithEscapes(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu tag": "cpu0",
	}
	fields := map[string]interface{}{
		"U,age=Idle": int64(90),
	}
	m, err := telegraf.NewMetric("My CPU", tags, fields, now)
	assert.NoError(t, err)

	s := JsonSerializer{}
	mS, err := s.Serialize(m)
	assert.NoError(t, err)

	expS := []string{fmt.Sprintf(`{"fields":{"U,age=Idle":90},"name":"My CPU","tags":{"cpu tag":"cpu0"},"timestamp":%d}`, now.Unix())}
	assert.Equal(t, expS, mS)
}
<|endoftext|>


<commit_before>package gitignore

import (
	"os"
	"strconv"

	"github.com/go-git/go-billy/v5"
	"github.com/go-git/go-billy/v5/memfs"
	. "gopkg.in/check.v1"
)

type MatcherSuite struct {
	GFS  billy.Filesystem // git repository root
	IEFS billy.Filesystem // git repository root using info/exclude instead
	RFS  billy.Filesystem // root that contains user home
	MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig
	MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry
	MIFS billy.Filesystem // root that contains user home, but missing .gitignore

	SFS billy.Filesystem // root that contains /etc/gitconfig
}

var _ = Suite(&MatcherSuite{})

func (s *MatcherSuite) SetUpTest(c *C) {
	// setup generic git repository root
	fs := memfs.New()
	f, err := fs.Create(".gitignore")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("vendor/g*/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("ignore.crlf\r\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	err = fs.MkdirAll("vendor", os.ModePerm)
	c.Assert(err, IsNil)
	f, err = fs.Create("vendor/.gitignore")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("!github.com/\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	err = fs.MkdirAll("another", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("ignore.crlf", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("vendor/github.com", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
	c.Assert(err, IsNil)

	s.GFS = fs

	// setup generic git repository root using info/exclude instead
	fs = memfs.New()
	err = fs.MkdirAll(".git/info", os.ModePerm)
	c.Assert(err, IsNil)
	f, err = fs.Create(".git/info/exclude")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("vendor/g*/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("ignore.crlf\r\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	err = fs.MkdirAll("vendor", os.ModePerm)
	c.Assert(err, IsNil)
	f, err = fs.Create("vendor/.gitignore")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("!github.com/\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	err = fs.MkdirAll("another", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("ignore.crlf", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("vendor/github.com", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
	c.Assert(err, IsNil)

	s.IEFS = fs

	// setup root that contains user home
	home, err := os.UserHomeDir()
	c.Assert(err, IsNil)

	fs = memfs.New()
	err = fs.MkdirAll(home, os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, gitconfigFile))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("[core]\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("	excludesfile = " + strconv.Quote(fs.Join(home, ".gitignore_global")) + "\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, ".gitignore_global"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("# IntelliJ\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte(".idea/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("*.iml\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.RFS = fs

	// root that contains user home, but missing ~/.gitconfig
	fs = memfs.New()
	err = fs.MkdirAll(home, os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, ".gitignore_global"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("# IntelliJ\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte(".idea/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("*.iml\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.MCFS = fs

	// setup root that contains user home, but missing excludesfile entry
	fs = memfs.New()
	err = fs.MkdirAll(home, os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, gitconfigFile))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("[core]\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, ".gitignore_global"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("# IntelliJ\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte(".idea/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("*.iml\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.MEFS = fs

	// setup root that contains user home, but missing .gitnignore
	fs = memfs.New()
	err = fs.MkdirAll(home, os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, gitconfigFile))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("[core]\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("	excludesfile = " + strconv.Quote(fs.Join(home, ".gitignore_global")) + "\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.MIFS = fs

	// setup root that contains user home
	fs = memfs.New()
	err = fs.MkdirAll("etc", os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(systemFile)
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("[core]\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("	excludesfile = /etc/gitignore_global\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	f, err = fs.Create("/etc/gitignore_global")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("# IntelliJ\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte(".idea/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("*.iml\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.SFS = fs
}

func (s *MatcherSuite) TestDir_ReadPatterns(c *C) {
	ps, err := ReadPatterns(s.GFS, nil)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 3)

	m := NewMatcher(ps)
	c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true)
	c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true)
	c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false)

	ps, err = ReadPatterns(s.IEFS, nil)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 3)

	m = NewMatcher(ps)
	c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true)
	c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true)
	c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false)
}

func (s *MatcherSuite) TestDir_LoadGlobalPatterns(c *C) {
	ps, err := LoadGlobalPatterns(s.RFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 2)

	m := NewMatcher(ps)
	c.Assert(m.Match([]string{"go-git.v4.iml"}, true), Equals, true)
	c.Assert(m.Match([]string{".idea"}, true), Equals, true)
}

func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingGitconfig(c *C) {
	ps, err := LoadGlobalPatterns(s.MCFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 0)
}

func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingExcludesfile(c *C) {
	ps, err := LoadGlobalPatterns(s.MEFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 0)
}

func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingGitignore(c *C) {
	ps, err := LoadGlobalPatterns(s.MIFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 0)
}

func (s *MatcherSuite) TestDir_LoadSystemPatterns(c *C) {
	ps, err := LoadSystemPatterns(s.SFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 2)

	m := NewMatcher(ps)
	c.Assert(m.Match([]string{"go-git.v4.iml"}, true), Equals, true)
	c.Assert(m.Match([]string{".idea"}, true), Equals, true)
}
<commit_msg>better tests<commit_after>package gitignore

import (
	"os"
	"strconv"

	"github.com/go-git/go-billy/v5"
	"github.com/go-git/go-billy/v5/memfs"
	. "gopkg.in/check.v1"
)

type MatcherSuite struct {
	GFS  billy.Filesystem // git repository root
	RFS  billy.Filesystem // root that contains user home
	MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig
	MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry
	MIFS billy.Filesystem // root that contains user home, but missing .gitignore

	SFS billy.Filesystem // root that contains /etc/gitconfig
}

var _ = Suite(&MatcherSuite{})

func (s *MatcherSuite) SetUpTest(c *C) {
	// setup generic git repository root
	fs := memfs.New()

	err := fs.MkdirAll(".git/info", os.ModePerm)
	c.Assert(err, IsNil)
	f, err := fs.Create(".git/info/exclude")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("exclude.crlf\r\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	f, err = fs.Create(".gitignore")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("vendor/g*/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("ignore.crlf\r\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	err = fs.MkdirAll("vendor", os.ModePerm)
	c.Assert(err, IsNil)
	f, err = fs.Create("vendor/.gitignore")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("!github.com/\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	err = fs.MkdirAll("another", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("exclude.crlf", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("ignore.crlf", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("vendor/github.com", os.ModePerm)
	c.Assert(err, IsNil)
	err = fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
	c.Assert(err, IsNil)

	s.GFS = fs

	// setup root that contains user home
	home, err := os.UserHomeDir()
	c.Assert(err, IsNil)

	fs = memfs.New()
	err = fs.MkdirAll(home, os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, gitconfigFile))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("[core]\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("	excludesfile = " + strconv.Quote(fs.Join(home, ".gitignore_global")) + "\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, ".gitignore_global"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("# IntelliJ\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte(".idea/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("*.iml\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.RFS = fs

	// root that contains user home, but missing ~/.gitconfig
	fs = memfs.New()
	err = fs.MkdirAll(home, os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, ".gitignore_global"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("# IntelliJ\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte(".idea/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("*.iml\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.MCFS = fs

	// setup root that contains user home, but missing excludesfile entry
	fs = memfs.New()
	err = fs.MkdirAll(home, os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, gitconfigFile))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("[core]\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, ".gitignore_global"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("# IntelliJ\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte(".idea/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("*.iml\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.MEFS = fs

	// setup root that contains user home, but missing .gitnignore
	fs = memfs.New()
	err = fs.MkdirAll(home, os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(fs.Join(home, gitconfigFile))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("[core]\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("	excludesfile = " + strconv.Quote(fs.Join(home, ".gitignore_global")) + "\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.MIFS = fs

	// setup root that contains user home
	fs = memfs.New()
	err = fs.MkdirAll("etc", os.ModePerm)
	c.Assert(err, IsNil)

	f, err = fs.Create(systemFile)
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("[core]\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("	excludesfile = /etc/gitignore_global\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	f, err = fs.Create("/etc/gitignore_global")
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("# IntelliJ\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte(".idea/\n"))
	c.Assert(err, IsNil)
	_, err = f.Write([]byte("*.iml\n"))
	c.Assert(err, IsNil)
	err = f.Close()
	c.Assert(err, IsNil)

	s.SFS = fs
}

func (s *MatcherSuite) TestDir_ReadPatterns(c *C) {
	ps, err := ReadPatterns(s.GFS, nil)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 4)

	m := NewMatcher(ps)
	c.Assert(m.Match([]string{"exclude.crlf"}, true), Equals, true)
	c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true)
	c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true)
	c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false)
}

func (s *MatcherSuite) TestDir_LoadGlobalPatterns(c *C) {
	ps, err := LoadGlobalPatterns(s.RFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 2)

	m := NewMatcher(ps)
	c.Assert(m.Match([]string{"go-git.v4.iml"}, true), Equals, true)
	c.Assert(m.Match([]string{".idea"}, true), Equals, true)
}

func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingGitconfig(c *C) {
	ps, err := LoadGlobalPatterns(s.MCFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 0)
}

func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingExcludesfile(c *C) {
	ps, err := LoadGlobalPatterns(s.MEFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 0)
}

func (s *MatcherSuite) TestDir_LoadGlobalPatternsMissingGitignore(c *C) {
	ps, err := LoadGlobalPatterns(s.MIFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 0)
}

func (s *MatcherSuite) TestDir_LoadSystemPatterns(c *C) {
	ps, err := LoadSystemPatterns(s.SFS)
	c.Assert(err, IsNil)
	c.Assert(ps, HasLen, 2)

	m := NewMatcher(ps)
	c.Assert(m.Match([]string{"go-git.v4.iml"}, true), Equals, true)
	c.Assert(m.Match([]string{".idea"}, true), Equals, true)
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
)

type VoicePipe struct {
	RootDir   string
	Directive *Directive
	Stdout    io.Writer
	Stderr    io.Writer
}

func NewVoicePipe(path string, stdout, stderr io.Writer) (*VoicePipe, error) {
	d, err := NewDirective(path)
	if err != nil {
		return nil, err
	}
	return &VoicePipe{
		RootDir:   path,
		Directive: d,
		Stdout:    stdout,
		Stderr:    stderr,
	}, nil
}

func (vp *VoicePipe) Resources() ([]os.FileInfo, error) {
	rs := make([]os.FileInfo, 0)
	fis, err := ioutil.ReadDir(vp.RootDir)
	if err != nil {
		return rs, err
	}
	for _, fi := range fis {
		n := fi.Name()
		if n != ".voicepipe" && n != "voicepipe.yml" && n != "Dockerfile" {
			rs = append(rs, fi)
		}
	}
	return rs, nil
}

func (vp *VoicePipe) SetupWorkDirFor(id ImageDirective, rs []os.FileInfo) error {
	dir := path.Join(vp.RootDir, ".voicepipe", id.Tag)
	if err := os.RemoveAll(dir); err != nil {
		return err
	}
	if err := os.MkdirAll(dir, 0775); err != nil {
		return err
	}
	for _, fi := range rs {
		if fi.Name() != "Dockerfile" {
			src := path.Join(vp.RootDir, fi.Name())
			tgt := path.Join(dir, fi.Name())
			if err := os.Link(src, tgt); err != nil {
				return err
			}
			continue
		}
		buf, err := ioutil.ReadFile(path.Join(vp.RootDir, fi.Name()))
		if err != nil {
			return err
		}
		df, err := Unmarshal(buf)
		if err != nil {
			return err
		}
		// TODO: copying structures costs a lot
		for k, v := range id.Parameters {
			df = ReplaceEnv(*df, k, v)
		}
		if err := ioutil.WriteFile(fi.Name(), df.Marshal(), 775); err != nil {
			return err
		}
	}
	return nil
}

func (vp *VoicePipe) SetupWorkingDir() error {
	rs, err := vp.Resources()
	if err != nil {
		return err
	}
	for _, id := range vp.Directive.ImageDirectives {
		if err := vp.SetupWorkDirFor(*id, rs); err != nil {
			return err
		}
	}
	return nil
}

func (vp *VoicePipe) BuildImage(id ImageDirective) error {
	dir := path.Join(vp.RootDir, ".voicepipe", id.Tag)
	tag := vp.Directive.Repository + ":" + id.Tag
	cmd := exec.Command("docker", "build", "--rm", "-t", tag, dir)
	cmd.Stdout = vp.Stdout
	cmd.Stderr = vp.Stderr
	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func (vp *VoicePipe) BuildImages() error {
	for _, id := range vp.Directive.ImageDirectives {
		if err := vp.BuildImage(*id); err != nil {
			return err
		}
	}
	return nil
}

func (vp *VoicePipe) Run() error {
	err := vp.SetupWorkingDir()
	if err != nil {
		return err
	}
	err = vp.BuildImages()
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>Rename functions<commit_after>package main

import (
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
)

type VoicePipe struct {
	RootDir   string
	Directive *Directive
	Stdout    io.Writer
	Stderr    io.Writer
}

func NewVoicePipe(path string, stdout, stderr io.Writer) (*VoicePipe, error) {
	d, err := NewDirective(path)
	if err != nil {
		return nil, err
	}
	return &VoicePipe{
		RootDir:   path,
		Directive: d,
		Stdout:    stdout,
		Stderr:    stderr,
	}, nil
}

func (vp *VoicePipe) Resources() ([]os.FileInfo, error) {
	rs := make([]os.FileInfo, 0)
	fis, err := ioutil.ReadDir(vp.RootDir)
	if err != nil {
		return rs, err
	}
	for _, fi := range fis {
		n := fi.Name()
		if n != ".voicepipe" && n != "voicepipe.yml" && n != "Dockerfile" {
			rs = append(rs, fi)
		}
	}
	return rs, nil
}

func (vp *VoicePipe) Setup(id ImageDirective, rs []os.FileInfo) error {
	dir := path.Join(vp.RootDir, ".voicepipe", id.Tag)
	if err := os.RemoveAll(dir); err != nil {
		return err
	}
	if err := os.MkdirAll(dir, 0775); err != nil {
		return err
	}
	for _, fi := range rs {
		if fi.Name() != "Dockerfile" {
			src := path.Join(vp.RootDir, fi.Name())
			tgt := path.Join(dir, fi.Name())
			if err := os.Link(src, tgt); err != nil {
				return err
			}
			continue
		}
		buf, err := ioutil.ReadFile(path.Join(vp.RootDir, fi.Name()))
		if err != nil {
			return err
		}
		df, err := Unmarshal(buf)
		if err != nil {
			return err
		}
		// TODO: copying structures costs a lot
		for k, v := range id.Parameters {
			df = ReplaceEnv(*df, k, v)
		}
		if err := ioutil.WriteFile(fi.Name(), df.Marshal(), 775); err != nil {
			return err
		}
	}
	return nil
}

func (vp *VoicePipe) SetupAll() error {
	rs, err := vp.Resources()
	if err != nil {
		return err
	}
	for _, id := range vp.Directive.ImageDirectives {
		if err := vp.Setup(*id, rs); err != nil {
			return err
		}
	}
	return nil
}

func (vp *VoicePipe) Build(id ImageDirective) error {
	dir := path.Join(vp.RootDir, ".voicepipe", id.Tag)
	tag := vp.Directive.Repository + ":" + id.Tag
	cmd := exec.Command("docker", "build", "--rm", "-t", tag, dir)
	cmd.Stdout = vp.Stdout
	cmd.Stderr = vp.Stderr
	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func (vp *VoicePipe) BuildAll() error {
	for _, id := range vp.Directive.ImageDirectives {
		if err := vp.BuildImage(*id); err != nil {
			return err
		}
	}
	return nil
}

func (vp *VoicePipe) Run() error {
	err := vp.SetupAll()
	if err != nil {
		return err
	}
	err = vp.BuildAll()
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>