<commit_before>package metafora

import (
	"math/rand"
	"sort"
	"sync"
	"time"
)

var (

	// balance calls are randomized and this is the upper bound of the random
	// amount
	balanceJitterMax = 10 * int64(time.Second)

	//FIXME should probably be improved, see usage in Run()
	consumerRetryDelay = 10 * time.Second
)

type runningTask struct {
	h Handler
	c chan struct{}
}

// Consumer is the core Metafora task runner.
type Consumer struct {
	// Func to create new handlers
	handler HandlerFunc

	// Map of task:Handler
	running map[string]runningTask

	// Mutex to protect access to running
	runL sync.Mutex

	// WaitGroup for running handlers
	hwg sync.WaitGroup

	bal      Balancer
	balEvery time.Duration
	coord    Coordinator
	logger   *logger
	stop     chan struct{} // closed by Shutdown to cause Run to exit

	watch chan string // channel for watcher to send tasks to main loop

	// Set by command handler, read anywhere via Consumer.frozen()
	freezeL sync.Mutex
	freeze  bool
}

// NewConsumer returns a new consumer and calls Init on the Balancer and Coordinator.
func NewConsumer(coord Coordinator, h HandlerFunc, b Balancer) (*Consumer, error) {
	c := &Consumer{
		running:  make(map[string]runningTask),
		handler:  h,
		bal:      b,
		balEvery: 15 * time.Minute, //TODO make balance wait configurable
		coord:    coord,
		logger:   stdoutLogger(),
		stop:     make(chan struct{}),
		watch:    make(chan string),
	}

	// initialize balancer with the consumer and a prefixed logger
	b.Init(&struct {
		*Consumer
		Logger
	}{Consumer: c, Logger: c.logger})

	if err := coord.Init(&coordinatorContext{Consumer: c, Logger: c.logger}); err != nil {
		return nil, err
	}
	return c, nil
}

// SetLogger assigns the logger to use as well as a level
//
// The logger parameter is an interface that requires the following
// method to be implemented (such as the the stdlib log.Logger):
//
//    Output(calldepth int, s string)
//
func (c *Consumer) SetLogger(l logOutputter, lvl LogLevel) {
	c.logger.l = l
	c.logger.lvl = lvl
}

// Run is the core run loop of Metafora. It is responsible for calling into the
// Coordinator to claim work and Balancer to rebalance work.
//
// Run blocks until Shutdown is called or an internal error occurs.
func (c *Consumer) Run() {
	c.logger.Log(LogLevelDebug, "Starting consumer")

	// chans for core goroutines to communicate with main loop
	balance := make(chan bool)
	cmdChan := make(chan Command)

	// Balance is called by the main loop when the balance channel is ticked
	go func() {
		randInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n
		for {
			select {
			case <-c.stop:
				// Shutdown has been called.
				return
			case <-time.After(c.balEvery + time.Duration(randInt(balanceJitterMax))):
				balance <- true
			}
		}
	}()

	// Watch for new tasks in a goroutine
	go c.watcher()

	// Watch for new commands in a goroutine
	go func() {
		defer close(cmdChan)
		for {
			cmd, err := c.coord.Command()
			if err != nil {
				//FIXME add more sophisticated error handling
				c.logger.Log(LogLevelError, "Coordinator returned an error during command, waiting and retrying. %v", err)
				select {
				case <-c.stop:
					return
				case <-time.After(consumerRetryDelay):
				}
				continue
			}
			if cmd == nil {
				c.logger.Log(LogLevelDebug, "Command coordinator exited")
				return
			}
			// Send command to watcher (or shutdown)
			select {
			case <-c.stop:
				return
			case cmdChan <- cmd:
			}
		}
	}()

	// Make sure Run() cleans up on exit (stops coordinator, releases tasks, etc)
	defer c.shutdown()

	// Main Loop ensures events are processed synchronously
	for {
		if c.frozen() {
			// Only recv commands while frozen
			select {
			case <-c.stop:
				// Shutdown has been called.
				return
			case cmd, ok := <-cmdChan:
				if !ok {
					c.logger.Log(LogLevelDebug, "Command channel closed. Exiting main loop.")
					return
				}
				c.logger.Log(LogLevelDebug, "Received command: %s", cmd)
				c.handleCommand(cmd)
			}
			continue
		}

		select {
		case <-c.stop:
			// Shutdown has been called.
			return
		case <-balance:
			c.balance()
		case task, ok := <-c.watch:
			if !ok {
				c.logger.Log(LogLevelDebug, "Watch channel closed. Exiting main loop.")
				return
			}
			if !c.bal.CanClaim(task) {
				c.logger.Log(LogLevelInfo, "Balancer rejected task %s", task)
				break
			}
			if !c.coord.Claim(task) {
				c.logger.Log(LogLevelInfo, "Coordinator unable to claim task %s", task)
				break
			}
			c.claimed(task)
		case cmd, ok := <-cmdChan:
			if !ok {
				c.logger.Log(LogLevelDebug, "Command channel closed. Exiting main loop.")
				return
			}
			c.handleCommand(cmd)
		}
	}
}

func (c *Consumer) watcher() {
	defer close(c.watch)
	c.logger.Log(LogLevelDebug, "Consumer watching")

	for {
		task, err := c.coord.Watch()
		if err != nil {
			//FIXME add more sophisticated error handling
			c.logger.Log(LogLevelError, "Coordinator returned an error during watch, waiting and retrying: %v", err)
			select {
			case <-c.stop:
				return
			case <-time.After(consumerRetryDelay):
			}
			continue
		}
		if task == "" {
			c.logger.Log(LogLevelDebug, "Coordinator has closed, exiting watch loop")
			return
		}
		// Send task to watcher (or shutdown)
		select {
		case <-c.stop:
			return
		case c.watch <- task:
		}
	}
}

func (c *Consumer) balance() {
	for _, task := range c.bal.Balance() {
		//TODO Release tasks asynchronously as their shutdown might be slow?
		c.release(task)
	}
}

// shutdown is the actual shutdown logic called when Run() exits.
func (c *Consumer) shutdown() {
	c.logger.Log(LogLevelDebug, "Closing Coordinator")
	c.coord.Close()

	// Build list of of currently running tasks
	tasks := c.Tasks()
	c.logger.Log(LogLevelInfo, "Sending stop signal to %d handler(s)", len(tasks))

	// Concurrently shutdown handlers as they may take a while to shutdown
	for _, id := range tasks {
		go c.release(id)
	}

	c.logger.Log(LogLevelInfo, "Waiting for handlers to exit")
	c.hwg.Wait()
}

// Shutdown stops the main Run loop, calls Stop on all handlers, and calls
// Close on the Coordinator. Running tasks will be released for other nodes to
// claim.
func (c *Consumer) Shutdown() {
	select {
	case <-c.stop:
		// already stopped
	default:
		c.logger.Log(LogLevelDebug, "Stopping Run loop")
		close(c.stop)
		c.hwg.Wait()
	}
}

// Tasks returns a sorted list of running Task IDs.
func (c *Consumer) Tasks() []string {
	c.runL.Lock()
	defer c.runL.Unlock()
	t := make([]string, len(c.running))
	i := 0
	for id, _ := range c.running {
		t[i] = id
		i++
	}
	sort.Strings(t)
	return t
}

// claimed starts a handler for a claimed task. It is the only method to
// manipulate c.running and closes the runningTask channel when a handler's Run
// method exits.
func (c *Consumer) claimed(taskID string) {
	h := c.handler()

	c.logger.Log(LogLevelDebug, "Attempting to start task "+taskID)
	// Associate handler with taskID
	// **This is the only place tasks should be added to c.running**
	c.runL.Lock()
	c.running[taskID] = runningTask{h: h, c: make(chan struct{})}
	c.runL.Unlock()

	c.hwg.Add(1)
	// Start handler in its own goroutine
	go func() {
		defer func() {
			if err := recover(); err != nil {
				c.logger.Log(LogLevelError, "Handler %s panic()'d: %v", taskID, err)
				// panics are considered fatal errors. Make sure the task isn't
				// rescheduled.
				c.coord.Done(taskID)
			}
			// **This is the only place tasks should be removed from c.running**
			c.runL.Lock()
			close(c.running[taskID].c)
			delete(c.running, taskID)
			c.runL.Unlock()
			c.hwg.Done()
		}()

		// Run the task
		c.logger.Log(LogLevelDebug, "Calling run for task %s", taskID)
		if err := h.Run(taskID); err != nil {
			if ferr, ok := err.(FatalError); ok && ferr.Fatal() {
				c.logger.Log(LogLevelError, "Handler for %s exited with fatal error: %v", taskID, err)
			} else {
				c.logger.Log(LogLevelError, "Handler for %s exited with error: %v", taskID, err)
				// error was non-fatal, release and let another node try
				c.coord.Release(taskID)
				return
			}
		}
		c.coord.Done(taskID)
	}()
}

// release stops and Coordinator.Release()s a task if it's running.
//
// release blocks until the task handler stops running.
func (c *Consumer) release(taskID string) {
	// Stop task...
	if c.stopTask(taskID) {
		// ...instruct the coordinator to release it
		c.coord.Release(taskID)
	}
}

// stopTask returns true if the task was running and stopped successfully.
func (c *Consumer) stopTask(taskID string) bool {
	c.runL.Lock()
	task, ok := c.running[taskID]
	c.runL.Unlock()

	if !ok {
		// This can happen if a task completes during Balance() and is not an error.
		c.logger.Log(LogLevelWarn, "Tried to release a non-running task: %s", taskID)
		return false
	}

	// all handler methods must be wrapped in a recover to prevent a misbehaving
	// handler from crashing the entire consumer
	func() {
		defer func() {
			if err := recover(); err != nil {
				c.logger.Log(LogLevelError, "Handler %s panic()'d on Stop: %v", taskID, err)
			}
		}()

		task.h.Stop()
	}()

	// Once the handler is stopped...
	//FIXME should there be a timeout here?
	<-task.c
	return true
}

func (c *Consumer) frozen() bool {
	c.freezeL.Lock()
	r := c.freeze
	c.freezeL.Unlock()
	return r
}

func (c *Consumer) handleCommand(cmd Command) {
	switch cmd.Name() {
	case cmdFreeze:
		if c.frozen() {
			c.logger.Log(LogLevelInfo, "Ignoring freeze command: already frozen")
			return
		}
		c.logger.Log(LogLevelInfo, "Freezing")
		c.freezeL.Lock()
		c.freeze = true
		c.freezeL.Unlock()
	case cmdUnfreeze:
		if !c.frozen() {
			c.logger.Log(LogLevelInfo, "Ignoring unfreeze command: not frozen")
			return
		}
		c.logger.Log(LogLevelInfo, "Unfreezing")
		c.freezeL.Lock()
		c.freeze = false
		c.freezeL.Unlock()
	case cmdBalance:
		c.logger.Log(LogLevelInfo, "Balancing due to command")
		c.balance()
		c.logger.Log(LogLevelDebug, "Finished balancing due to command")
	case cmdReleaseTask:
		taskI, ok := cmd.Parameters()["task"]
		task, ok2 := taskI.(string)
		if !ok || !ok2 {
			c.logger.Log(LogLevelError, "Release task command didn't contain a valid task")
			return
		}
		c.logger.Log(LogLevelInfo, "Releasing task %s due to command", task)
		c.release(task)
	case cmdStopTask:
		taskI, ok := cmd.Parameters()["task"]
		task, ok2 := taskI.(string)
		if !ok || !ok2 {
			c.logger.Log(LogLevelError, "Stop task command didn't contain a valid task")
			return
		}
		c.logger.Log(LogLevelInfo, "Stopping task %s due to command", task)
		c.stopTask(task)
	default:
		c.logger.Log(LogLevelWarn, "Discarding unknown command: %s", cmd.Name())
	}
}
<commit_msg>We should probably actually log when tasks start and stop<commit_after>package metafora

import (
	"math/rand"
	"sort"
	"sync"
	"time"
)

var (

	// balance calls are randomized and this is the upper bound of the random
	// amount
	balanceJitterMax = 10 * int64(time.Second)

	//FIXME should probably be improved, see usage in Run()
	consumerRetryDelay = 10 * time.Second
)

type runningTask struct {
	h Handler
	c chan struct{}
}

// Consumer is the core Metafora task runner.
type Consumer struct {
	// Func to create new handlers
	handler HandlerFunc

	// Map of task:Handler
	running map[string]runningTask

	// Mutex to protect access to running
	runL sync.Mutex

	// WaitGroup for running handlers
	hwg sync.WaitGroup

	bal      Balancer
	balEvery time.Duration
	coord    Coordinator
	logger   *logger
	stop     chan struct{} // closed by Shutdown to cause Run to exit

	watch chan string // channel for watcher to send tasks to main loop

	// Set by command handler, read anywhere via Consumer.frozen()
	freezeL sync.Mutex
	freeze  bool
}

// NewConsumer returns a new consumer and calls Init on the Balancer and Coordinator.
func NewConsumer(coord Coordinator, h HandlerFunc, b Balancer) (*Consumer, error) {
	c := &Consumer{
		running:  make(map[string]runningTask),
		handler:  h,
		bal:      b,
		balEvery: 15 * time.Minute, //TODO make balance wait configurable
		coord:    coord,
		logger:   stdoutLogger(),
		stop:     make(chan struct{}),
		watch:    make(chan string),
	}

	// initialize balancer with the consumer and a prefixed logger
	b.Init(&struct {
		*Consumer
		Logger
	}{Consumer: c, Logger: c.logger})

	if err := coord.Init(&coordinatorContext{Consumer: c, Logger: c.logger}); err != nil {
		return nil, err
	}
	return c, nil
}

// SetLogger assigns the logger to use as well as a level
//
// The logger parameter is an interface that requires the following
// method to be implemented (such as the the stdlib log.Logger):
//
//    Output(calldepth int, s string)
//
func (c *Consumer) SetLogger(l logOutputter, lvl LogLevel) {
	c.logger.l = l
	c.logger.lvl = lvl
}

// Run is the core run loop of Metafora. It is responsible for calling into the
// Coordinator to claim work and Balancer to rebalance work.
//
// Run blocks until Shutdown is called or an internal error occurs.
func (c *Consumer) Run() {
	c.logger.Log(LogLevelDebug, "Starting consumer")

	// chans for core goroutines to communicate with main loop
	balance := make(chan bool)
	cmdChan := make(chan Command)

	// Balance is called by the main loop when the balance channel is ticked
	go func() {
		randInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n
		for {
			select {
			case <-c.stop:
				// Shutdown has been called.
				return
			case <-time.After(c.balEvery + time.Duration(randInt(balanceJitterMax))):
				balance <- true
			}
		}
	}()

	// Watch for new tasks in a goroutine
	go c.watcher()

	// Watch for new commands in a goroutine
	go func() {
		defer close(cmdChan)
		for {
			cmd, err := c.coord.Command()
			if err != nil {
				//FIXME add more sophisticated error handling
				c.logger.Log(LogLevelError, "Coordinator returned an error during command, waiting and retrying. %v", err)
				select {
				case <-c.stop:
					return
				case <-time.After(consumerRetryDelay):
				}
				continue
			}
			if cmd == nil {
				c.logger.Log(LogLevelDebug, "Command coordinator exited")
				return
			}
			// Send command to watcher (or shutdown)
			select {
			case <-c.stop:
				return
			case cmdChan <- cmd:
			}
		}
	}()

	// Make sure Run() cleans up on exit (stops coordinator, releases tasks, etc)
	defer c.shutdown()

	// Main Loop ensures events are processed synchronously
	for {
		if c.frozen() {
			// Only recv commands while frozen
			select {
			case <-c.stop:
				// Shutdown has been called.
				return
			case cmd, ok := <-cmdChan:
				if !ok {
					c.logger.Log(LogLevelDebug, "Command channel closed. Exiting main loop.")
					return
				}
				c.logger.Log(LogLevelDebug, "Received command: %s", cmd)
				c.handleCommand(cmd)
			}
			continue
		}

		select {
		case <-c.stop:
			// Shutdown has been called.
			return
		case <-balance:
			c.balance()
		case task, ok := <-c.watch:
			if !ok {
				c.logger.Log(LogLevelDebug, "Watch channel closed. Exiting main loop.")
				return
			}
			if !c.bal.CanClaim(task) {
				c.logger.Log(LogLevelInfo, "Balancer rejected task %s", task)
				break
			}
			if !c.coord.Claim(task) {
				c.logger.Log(LogLevelInfo, "Coordinator unable to claim task %s", task)
				break
			}
			c.claimed(task)
		case cmd, ok := <-cmdChan:
			if !ok {
				c.logger.Log(LogLevelDebug, "Command channel closed. Exiting main loop.")
				return
			}
			c.handleCommand(cmd)
		}
	}
}

func (c *Consumer) watcher() {
	defer close(c.watch)
	c.logger.Log(LogLevelDebug, "Consumer watching")

	for {
		task, err := c.coord.Watch()
		if err != nil {
			//FIXME add more sophisticated error handling
			c.logger.Log(LogLevelError, "Coordinator returned an error during watch, waiting and retrying: %v", err)
			select {
			case <-c.stop:
				return
			case <-time.After(consumerRetryDelay):
			}
			continue
		}
		if task == "" {
			c.logger.Log(LogLevelDebug, "Coordinator has closed, exiting watch loop")
			return
		}
		// Send task to watcher (or shutdown)
		select {
		case <-c.stop:
			return
		case c.watch <- task:
		}
	}
}

func (c *Consumer) balance() {
	for _, task := range c.bal.Balance() {
		//TODO Release tasks asynchronously as their shutdown might be slow?
		c.release(task)
	}
}

// shutdown is the actual shutdown logic called when Run() exits.
func (c *Consumer) shutdown() {
	c.logger.Log(LogLevelDebug, "Closing Coordinator")
	c.coord.Close()

	// Build list of of currently running tasks
	tasks := c.Tasks()
	c.logger.Log(LogLevelInfo, "Sending stop signal to %d handler(s)", len(tasks))

	// Concurrently shutdown handlers as they may take a while to shutdown
	for _, id := range tasks {
		go c.release(id)
	}

	c.logger.Log(LogLevelInfo, "Waiting for handlers to exit")
	c.hwg.Wait()
}

// Shutdown stops the main Run loop, calls Stop on all handlers, and calls
// Close on the Coordinator. Running tasks will be released for other nodes to
// claim.
func (c *Consumer) Shutdown() {
	select {
	case <-c.stop:
		// already stopped
	default:
		c.logger.Log(LogLevelDebug, "Stopping Run loop")
		close(c.stop)
		c.hwg.Wait()
	}
}

// Tasks returns a sorted list of running Task IDs.
func (c *Consumer) Tasks() []string {
	c.runL.Lock()
	defer c.runL.Unlock()
	t := make([]string, len(c.running))
	i := 0
	for id, _ := range c.running {
		t[i] = id
		i++
	}
	sort.Strings(t)
	return t
}

// claimed starts a handler for a claimed task. It is the only method to
// manipulate c.running and closes the runningTask channel when a handler's Run
// method exits.
func (c *Consumer) claimed(taskID string) {
	h := c.handler()

	c.logger.Log(LogLevelDebug, "Attempting to start task "+taskID)
	// Associate handler with taskID
	// **This is the only place tasks should be added to c.running**
	c.runL.Lock()
	c.running[taskID] = runningTask{h: h, c: make(chan struct{})}
	c.runL.Unlock()

	c.hwg.Add(1)
	// Start handler in its own goroutine
	go func() {
		c.logger.Log(LogLevelInfo, "Task started: %s", taskID)
		defer c.logger.Log(LogLevelInfo, "Task exited: %s", taskID)
		defer func() {
			if err := recover(); err != nil {
				c.logger.Log(LogLevelError, "Handler %s panic()'d: %v", taskID, err)
				// panics are considered fatal errors. Make sure the task isn't
				// rescheduled.
				c.coord.Done(taskID)
			}
			// **This is the only place tasks should be removed from c.running**
			c.runL.Lock()
			close(c.running[taskID].c)
			delete(c.running, taskID)
			c.runL.Unlock()
			c.hwg.Done()
		}()

		// Run the task
		c.logger.Log(LogLevelDebug, "Calling run for task %s", taskID)
		if err := h.Run(taskID); err != nil {
			if ferr, ok := err.(FatalError); ok && ferr.Fatal() {
				c.logger.Log(LogLevelError, "Handler for %s exited with fatal error: %v", taskID, err)
			} else {
				c.logger.Log(LogLevelError, "Handler for %s exited with error: %v", taskID, err)
				// error was non-fatal, release and let another node try
				c.coord.Release(taskID)
				return
			}
		}
		c.coord.Done(taskID)
	}()
}

// release stops and Coordinator.Release()s a task if it's running.
//
// release blocks until the task handler stops running.
func (c *Consumer) release(taskID string) {
	// Stop task...
	if c.stopTask(taskID) {
		// ...instruct the coordinator to release it
		c.coord.Release(taskID)
	}
}

// stopTask returns true if the task was running and stopped successfully.
func (c *Consumer) stopTask(taskID string) bool {
	c.runL.Lock()
	task, ok := c.running[taskID]
	c.runL.Unlock()

	if !ok {
		// This can happen if a task completes during Balance() and is not an error.
		c.logger.Log(LogLevelWarn, "Tried to release a non-running task: %s", taskID)
		return false
	}

	// all handler methods must be wrapped in a recover to prevent a misbehaving
	// handler from crashing the entire consumer
	func() {
		defer func() {
			if err := recover(); err != nil {
				c.logger.Log(LogLevelError, "Handler %s panic()'d on Stop: %v", taskID, err)
			}
		}()

		task.h.Stop()
	}()

	// Once the handler is stopped...
	//FIXME should there be a timeout here?
	<-task.c
	return true
}

func (c *Consumer) frozen() bool {
	c.freezeL.Lock()
	r := c.freeze
	c.freezeL.Unlock()
	return r
}

func (c *Consumer) handleCommand(cmd Command) {
	switch cmd.Name() {
	case cmdFreeze:
		if c.frozen() {
			c.logger.Log(LogLevelInfo, "Ignoring freeze command: already frozen")
			return
		}
		c.logger.Log(LogLevelInfo, "Freezing")
		c.freezeL.Lock()
		c.freeze = true
		c.freezeL.Unlock()
	case cmdUnfreeze:
		if !c.frozen() {
			c.logger.Log(LogLevelInfo, "Ignoring unfreeze command: not frozen")
			return
		}
		c.logger.Log(LogLevelInfo, "Unfreezing")
		c.freezeL.Lock()
		c.freeze = false
		c.freezeL.Unlock()
	case cmdBalance:
		c.logger.Log(LogLevelInfo, "Balancing due to command")
		c.balance()
		c.logger.Log(LogLevelDebug, "Finished balancing due to command")
	case cmdReleaseTask:
		taskI, ok := cmd.Parameters()["task"]
		task, ok2 := taskI.(string)
		if !ok || !ok2 {
			c.logger.Log(LogLevelError, "Release task command didn't contain a valid task")
			return
		}
		c.logger.Log(LogLevelInfo, "Releasing task %s due to command", task)
		c.release(task)
	case cmdStopTask:
		taskI, ok := cmd.Parameters()["task"]
		task, ok2 := taskI.(string)
		if !ok || !ok2 {
			c.logger.Log(LogLevelError, "Stop task command didn't contain a valid task")
			return
		}
		c.logger.Log(LogLevelInfo, "Stopping task %s due to command", task)
		c.stopTask(task)
	default:
		c.logger.Log(LogLevelWarn, "Discarding unknown command: %s", cmd.Name())
	}
}
<|endoftext|>


<commit_before>package kit

import (
	"crypto"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/hashicorp/go-version"
	"github.com/inconshreveable/go-update"
)

var (
	// ThemeKitVersion is the version build of the library
	ThemeKitVersion, _ = version.NewVersion("0.6.11")
	releasesURL        = "https://shopify-themekit.s3.amazonaws.com/releases/all.json"
)

// LibraryInfo will return a string array with information about the library used
// for logging.
func LibraryInfo() string {
	messageSeparator := "\n----------------------------------------------------------------\n"
	info := fmt.Sprintf("\t%s %s", "ThemeKit - Shopify Theme Utilities", ThemeKitVersion.String())
	return fmt.Sprintf("%s%s%s", messageSeparator, info, messageSeparator)
}

// PrintInfo will output the version banner for the themekit library.
func PrintInfo() {
	Print(GreenText(LibraryInfo()))
}

// IsNewUpdateAvailable will check if there is an update to the theme kit command
// and if there is one it will return true. Otherwise it will return false.
func IsNewUpdateAvailable() bool {
	list, err := fetchReleases()
	if err != nil {
		return false
	}
	return list.Get("latest").IsApplicable()
}

// InstallThemeKitVersion will take a semver string and parse it then check if that
// update is available and install it. If the string is 'latest' it will install
// the most current. If the string is latest and there is no update it will return an
// error. An error will also be returned if the requested version does not exist.
func InstallThemeKitVersion(ver string) error {
	releases, err := fetchReleases()
	if err != nil {
		return err
	}
	requestedRelease := releases.Get(ver)
	if !requestedRelease.IsValid() {
		return fmt.Errorf("version %s not found", ver)
	} else if ver == "latest" && !requestedRelease.IsApplicable() {
		return fmt.Errorf("no applicable update available")
	}
	Printf("Updating from %s to %s", YellowText(ThemeKitVersion), YellowText(requestedRelease.Version))
	err = applyUpdate(requestedRelease.ForCurrentPlatform())
	if err == nil {
		Printf(`
Successfully updated to theme kit version %v,
If you have troubles with this release please
report them to https://github.com/Shopify/themekit/issues
If your troubles are preventing you from working
you can roll back to the previous version using
the command 'theme update --version=v%s'
`, GreenText(requestedRelease.Version), YellowText(ThemeKitVersion))
	}
	return err
}

func fetchReleases() (releasesList, error) {
	var releases releasesList
	resp, err := http.Get(releasesURL)
	if err != nil {
		return releases, err
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err == nil {
		err = json.Unmarshal(data, &releases)
	}
	return releases, err
}

func applyUpdate(platformRelease platform) error {
	checksum, err := hex.DecodeString(platformRelease.Digest)
	if err != nil {
		return err
	}

	updateFile, err := http.Get(platformRelease.URL)
	if err != nil {
		return err
	}
	defer updateFile.Body.Close()

	err = update.Apply(updateFile.Body, update.Options{
		TargetPath: platformRelease.TargetPath, //used for testing
		Hash:       crypto.MD5,
		Checksum:   checksum,
	})

	if err != nil {
		if rerr := update.RollbackError(err); rerr != nil {
			return fmt.Errorf("Failed to rollback from bad update: %v", rerr)
		}
		return fmt.Errorf("Could not update and had to roll back. %v", err)
	}

	return nil
}
<commit_msg>Update version number<commit_after>package kit

import (
	"crypto"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/hashicorp/go-version"
	"github.com/inconshreveable/go-update"
)

var (
	// ThemeKitVersion is the version build of the library
	ThemeKitVersion, _ = version.NewVersion("0.6.12")
	releasesURL        = "https://shopify-themekit.s3.amazonaws.com/releases/all.json"
)

// LibraryInfo will return a string array with information about the library used
// for logging.
func LibraryInfo() string {
	messageSeparator := "\n----------------------------------------------------------------\n"
	info := fmt.Sprintf("\t%s %s", "ThemeKit - Shopify Theme Utilities", ThemeKitVersion.String())
	return fmt.Sprintf("%s%s%s", messageSeparator, info, messageSeparator)
}

// PrintInfo will output the version banner for the themekit library.
func PrintInfo() {
	Print(GreenText(LibraryInfo()))
}

// IsNewUpdateAvailable will check if there is an update to the theme kit command
// and if there is one it will return true. Otherwise it will return false.
func IsNewUpdateAvailable() bool {
	list, err := fetchReleases()
	if err != nil {
		return false
	}
	return list.Get("latest").IsApplicable()
}

// InstallThemeKitVersion will take a semver string and parse it then check if that
// update is available and install it. If the string is 'latest' it will install
// the most current. If the string is latest and there is no update it will return an
// error. An error will also be returned if the requested version does not exist.
func InstallThemeKitVersion(ver string) error {
	releases, err := fetchReleases()
	if err != nil {
		return err
	}
	requestedRelease := releases.Get(ver)
	if !requestedRelease.IsValid() {
		return fmt.Errorf("version %s not found", ver)
	} else if ver == "latest" && !requestedRelease.IsApplicable() {
		return fmt.Errorf("no applicable update available")
	}
	Printf("Updating from %s to %s", YellowText(ThemeKitVersion), YellowText(requestedRelease.Version))
	err = applyUpdate(requestedRelease.ForCurrentPlatform())
	if err == nil {
		Printf(`
Successfully updated to theme kit version %v,
If you have troubles with this release please
report them to https://github.com/Shopify/themekit/issues
If your troubles are preventing you from working
you can roll back to the previous version using
the command 'theme update --version=v%s'
`, GreenText(requestedRelease.Version), YellowText(ThemeKitVersion))
	}
	return err
}

func fetchReleases() (releasesList, error) {
	var releases releasesList
	resp, err := http.Get(releasesURL)
	if err != nil {
		return releases, err
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err == nil {
		err = json.Unmarshal(data, &releases)
	}
	return releases, err
}

func applyUpdate(platformRelease platform) error {
	checksum, err := hex.DecodeString(platformRelease.Digest)
	if err != nil {
		return err
	}

	updateFile, err := http.Get(platformRelease.URL)
	if err != nil {
		return err
	}
	defer updateFile.Body.Close()

	err = update.Apply(updateFile.Body, update.Options{
		TargetPath: platformRelease.TargetPath, //used for testing
		Hash:       crypto.MD5,
		Checksum:   checksum,
	})

	if err != nil {
		if rerr := update.RollbackError(err); rerr != nil {
			return fmt.Errorf("Failed to rollback from bad update: %v", rerr)
		}
		return fmt.Errorf("Could not update and had to roll back. %v", err)
	}

	return nil
}
<|endoftext|>


<commit_before>package clients

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iterator"
)

type GcsClient struct {
	client *storage.Client
}

func CreateGcsClient() (*GcsClient, error) {
	ctx := context.Background()
	client, err := storage.NewClient(ctx)
	if err != nil {
		return nil, err
	}
	return &GcsClient{client: client}, nil
}

func (g *GcsClient) ReadAllFiles(bucket, directory string) (map[string][]byte, error) {
	log.Printf("Reading all files in bucket %s and directory %s", bucket, directory)
	files, err := g.listFiles(bucket, directory)
	if err != nil {
		return nil, fmt.Errorf("Failed to list files in directory %s in bucket %s: %v", directory, bucket, err)
	}

	data := make(map[string][]byte)
	for _, f := range files {
		content, err := g.readFile(bucket, f)
		if err != nil {
			return nil, fmt.Errorf("Failed to read file %s in bucket %s: %v", f, bucket, err)
		}
		data[f] = content
	}
	return data, nil
}

func (g *GcsClient) listFiles(bucket, directory string) ([]string, error) {
	names := make([]string, 0)
	ctx := context.Background()
	q := &storage.Query{Prefix: directory}
	it := g.client.Bucket(bucket).Objects(ctx, q)
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		} else if err != nil {
			return nil, err
		}
		names = append(names, attrs.Name)
	}
	return names, nil
}

func (g *GcsClient) readFile(bucket, object string) ([]byte, error) {
	ctx := context.Background()
	rc, err := g.client.Bucket(bucket).Object(object).NewReader(ctx)
	if err != nil {
		return nil, err
	}
	defer rc.Close()

	data, err := ioutil.ReadAll(rc)
	if err != nil {
		return nil, err
	}
	return data, nil
}
<commit_msg>Read only one file from GCS (for testing)<commit_after>package clients

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iterator"
)

type GcsClient struct {
	client *storage.Client
}

func CreateGcsClient() (*GcsClient, error) {
	ctx := context.Background()
	client, err := storage.NewClient(ctx)
	if err != nil {
		return nil, err
	}
	return &GcsClient{client: client}, nil
}

func (g *GcsClient) ReadAllFiles(bucket, directory string) (map[string][]byte, error) {
	log.Printf("Reading all files in bucket %s and directory %s", bucket, directory)
	files, err := g.listFiles(bucket, directory)
	if err != nil {
		return nil, fmt.Errorf("Failed to list files in directory %s in bucket %s: %v", directory, bucket, err)
	}

	data := make(map[string][]byte)
	for _, f := range files {
		content, err := g.readFile(bucket, f)
		if err != nil {
			return nil, fmt.Errorf("Failed to read file %s in bucket %s: %v", f, bucket, err)
		}
		data[f] = content

		// TODO(fweikert): remove
		break
	}
	return data, nil
}

func (g *GcsClient) listFiles(bucket, directory string) ([]string, error) {
	names := make([]string, 0)
	ctx := context.Background()
	q := &storage.Query{Prefix: directory}
	it := g.client.Bucket(bucket).Objects(ctx, q)
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		} else if err != nil {
			return nil, err
		}
		names = append(names, attrs.Name)
	}
	return names, nil
}

func (g *GcsClient) readFile(bucket, object string) ([]byte, error) {
	ctx := context.Background()
	rc, err := g.client.Bucket(bucket).Object(object).NewReader(ctx)
	if err != nil {
		return nil, err
	}
	defer rc.Close()

	data, err := ioutil.ReadAll(rc)
	if err != nil {
		return nil, err
	}
	return data, nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// Params currently used:
// token - an OAuth 2.0 bearer token to use when authenticating
// username - the default username to use - if not present, $USER
// endpoint - the default endpoint to use - if not present, https://uk0.bigv.io
// auth-endpoint - the default auth API endpoint to use - if not present, https://auth.bytemark.co.uk

// A Config determines the configuration of the bigv client.
// It's responsible for handling things like the credentials to use and what endpoints to talk to.
//
// Each configuration item is read from the following places, falling back to successive places:
//
// Per-command command-line flags, global command-line flags, environment variables, configuration directory, hard-coded defaults
//
//The location of the configuration directory is read from global command-line flags, or is otherwise ~/.go-bigv
//
type Config struct {
	Dir         string
	Memo        map[string]string
	Definitions map[string]string
}

// Do I really need to have the flags passed in here?
// Yes. Doing commands will be sorted out in a different place, and I don't want to touch it here.

// NewConfig sets up a new config struct. Pass in an empty string to default to ~/.go-bigv
func NewConfig(configDir string, flags *flag.FlagSet) (config *Config) {
	config = new(Config)
	config.Memo = make(map[string]string)
	config.Dir = filepath.Join(os.Getenv("HOME"), "/.go-bigv")
	if os.Getenv("BIGV_CONFIG_DIR") != "" {
		config.Dir = os.Getenv("BIGV_CONFIG_DIR")
	}

	if configDir != "" {
		err := os.MkdirAll(configDir, 0600)
		if err != nil {
			// TODO(telyn): Better error handling here

			panic(err)
		}

		stat, err := os.Stat(configDir)
		if err != nil {
			// TODO(telyn): Better error handling here
			panic(err)
		}

		if !stat.IsDir() {
			fmt.Printf("%s is not a directory", configDir)
			panic("Cannot continue")
		}
		config.Dir = configDir
	}

	if flags != nil {
		// dump all the flags into the memo
		// should be reet...reet?
		flags.Visit(func(f *flag.Flag) {
			config.Memo[f.Name] = f.Value.String()
		})
	}
	return config
}

// GetPath joins the given string onto the end of the Config.Dir path
func (config *Config) GetPath(name string) string {
	return filepath.Join(config.Dir, name)
}
func (config *Config) GetUrl(path ...string) *url.URL {
	url, err := url.Parse(config.Get("endpoint"))
	if err != nil {
		panic("Endpoint is not a valid URL")
	}
	url.Parse("/" + strings.Join([]string(path), "/"))
	return url
}

func (config *Config) LoadDefinitions() {
	stat, err := os.Stat(config.GetPath("definitions"))

	if err != nil || time.Since(stat.ModTime()) > 24*time.Hour {
		// TODO(telyn): grab it off the internet
		//		url := config.GetUrl("definitions.json")
	} else {
		_, err := ioutil.ReadFile(config.GetPath("definitions"))
		if err != nil {
			panic("Couldn't load definitions")
		}
	}

}

func (config *Config) Get(name string) string {
	// try to read the Memo
	if val, ok := config.Memo[name]; ok {
		return val
	} else {
		return config.Read(name)
	}
	return ""
}

// Set stores the given key-value pair in config's Memo. This storage does not persist once the program terminates.
func (config *Config) Set(name string, value string) {
	config.Memo[name] = value
}

// SetPersistent writes a file to the config directory for the given key-value pair.
func (config *Config) SetPersistent(name, value string) {
	config.Set(name, value)
	err := ioutil.WriteFile(config.GetPath("user"), []byte(value), 0600)
	if err != nil {
		fmt.Println("Couldn't write to config directory " + config.Dir)
		panic(err)
	}
}

func (config *Config) GetDefault(name string) string {
	// ideally most of these should just be	os.Getenv("BIGV_"+name.Upcase().Replace("-","_"))
	switch name {
	case "user":
		return FirstNotEmpty(os.Getenv("BIGV_USER"), os.Getenv("USER"))
	case "endpoint":
		return FirstNotEmpty(os.Getenv("BIGV_ENDPOINT"), "https://uk0.bigv.io")
	case "auth-endpoint":
		return FirstNotEmpty(os.Getenv("BIGV_AUTH_ENDPOINT"), "https://auth.bytemark.co.uk")
	case "account":
		return FirstNotEmpty(os.Getenv("BIGV_ACCOUNT"), os.Getenv("BIGV_USER"), os.Getenv("USER"))
	case "debug-level":
		return FirstNotEmpty(os.Getenv("BIGV_DEBUG_LEVEL"), "0")
	}
	return ""
}

func (config *Config) Read(name string) string {
	contents, err := ioutil.ReadFile(config.GetPath(name))
	if err != nil {
		if os.IsNotExist(err) {
			return config.GetDefault(name)
		}
		fmt.Printf("Couldn't read config for %s", name)
		panic(err)
	}

	return string(contents)
}
<commit_msg>Rearrange some code<commit_after>package cmd

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// Params currently used:
// token - an OAuth 2.0 bearer token to use when authenticating
// username - the default username to use - if not present, $USER
// endpoint - the default endpoint to use - if not present, https://uk0.bigv.io
// auth-endpoint - the default auth API endpoint to use - if not present, https://auth.bytemark.co.uk

// A Config determines the configuration of the bigv client.
// It's responsible for handling things like the credentials to use and what endpoints to talk to.
//
// Each configuration item is read from the following places, falling back to successive places:
//
// Per-command command-line flags, global command-line flags, environment variables, configuration directory, hard-coded defaults
//
//The location of the configuration directory is read from global command-line flags, or is otherwise ~/.go-bigv
//
type Config struct {
	Dir         string
	Memo        map[string]string
	Definitions map[string]string
}

// Do I really need to have the flags passed in here?
// Yes. Doing commands will be sorted out in a different place, and I don't want to touch it here.

// NewConfig sets up a new config struct. Pass in an empty string to default to ~/.go-bigv
func NewConfig(configDir string, flags *flag.FlagSet) (config *Config) {
	config = new(Config)
	config.Memo = make(map[string]string)
	config.Dir = filepath.Join(os.Getenv("HOME"), "/.go-bigv")
	if os.Getenv("BIGV_CONFIG_DIR") != "" {
		config.Dir = os.Getenv("BIGV_CONFIG_DIR")
	}

	if configDir != "" {
		err := os.MkdirAll(configDir, 0600)
		if err != nil {
			// TODO(telyn): Better error handling here

			panic(err)
		}

		stat, err := os.Stat(configDir)
		if err != nil {
			// TODO(telyn): Better error handling here
			panic(err)
		}

		if !stat.IsDir() {
			fmt.Printf("%s is not a directory", configDir)
			panic("Cannot continue")
		}
		config.Dir = configDir
	}

	if flags != nil {
		// dump all the flags into the memo
		// should be reet...reet?
		flags.Visit(func(f *flag.Flag) {
			config.Memo[f.Name] = f.Value.String()
		})
	}
	return config
}

// GetPath joins the given string onto the end of the Config.Dir path
func (config *Config) GetPath(name string) string {
	return filepath.Join(config.Dir, name)
}
func (config *Config) GetUrl(path ...string) *url.URL {
	url, err := url.Parse(config.Get("endpoint"))
	if err != nil {
		panic("Endpoint is not a valid URL")
	}
	url.Parse("/" + strings.Join([]string(path), "/"))
	return url
}

func (config *Config) LoadDefinitions() {
	stat, err := os.Stat(config.GetPath("definitions"))

	if err != nil || time.Since(stat.ModTime()) > 24*time.Hour {
		// TODO(telyn): grab it off the internet
		//		url := config.GetUrl("definitions.json")
	} else {
		_, err := ioutil.ReadFile(config.GetPath("definitions"))
		if err != nil {
			panic("Couldn't load definitions")
		}
	}

}

func (config *Config) Get(name string) string {
	// try to read the Memo
	if val, ok := config.Memo[name]; ok {
		return val
	} else {
		return config.Read(name)
	}
	return ""
}

func (config *Config) GetDefault(name string) string {
	// ideally most of these should just be	os.Getenv("BIGV_"+name.Upcase().Replace("-","_"))
	switch name {
	case "user":
		return FirstNotEmpty(os.Getenv("BIGV_USER"), os.Getenv("USER"))
	case "endpoint":
		return FirstNotEmpty(os.Getenv("BIGV_ENDPOINT"), "https://uk0.bigv.io")
	case "auth-endpoint":
		return FirstNotEmpty(os.Getenv("BIGV_AUTH_ENDPOINT"), "https://auth.bytemark.co.uk")
	case "account":
		return FirstNotEmpty(os.Getenv("BIGV_ACCOUNT"), os.Getenv("BIGV_USER"), os.Getenv("USER"))
	case "debug-level":
		return FirstNotEmpty(os.Getenv("BIGV_DEBUG_LEVEL"), "0")
	}
	return ""
}

func (config *Config) Read(name string) string {
	contents, err := ioutil.ReadFile(config.GetPath(name))
	if err != nil {
		if os.IsNotExist(err) {
			return config.GetDefault(name)
		}
		fmt.Printf("Couldn't read config for %s", name)
		panic(err)
	}

	return string(contents)
}

// Set stores the given key-value pair in config's Memo. This storage does not persist once the program terminates.
func (config *Config) Set(name string, value string) {
	config.Memo[name] = value
}

// SetPersistent writes a file to the config directory for the given key-value pair.
func (config *Config) SetPersistent(name, value string) {
	config.Set(name, value)
	err := ioutil.WriteFile(config.GetPath("user"), []byte(value), 0600)
	if err != nil {
		fmt.Println("Couldn't write to config directory " + config.Dir)
		panic(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"

	sys "golang.org/x/sys/unix"
)

const (
	ntty    = 6 // TODO: launch gettys automatically?
	shell   = "/bin/sh"
	runc    = "/etc/rc"
	dev     = "/dev/"
	console = "/dev/console"
	login   = "/bin/login"
)

var cons *os.File

func init() {
	runtime.GOMAXPROCS(1)
}

func main() {
	signal.Ignore(sys.SIGINT, sys.SIGTERM, sys.SIGHUP)

	f, err := os.OpenFile(console, os.O_RDWR, 0)
	if err != nil {
		panic("no console")
	}
	cons = f

	log.SetPrefix("init: ")
	log.SetOutput(cons)
	log.SetFlags(log.Ltime | log.Lmicroseconds)

	for {
		run()
	}
}

func run() {
	type stage struct {
		name string
		fn   func() error
	}
	for _, s := range []stage{
		{"single-user", single},
		{"runcom", runcom},
		{"multi-user", multiple},
		{"shutdown", shutdown},
	} {
		log.Printf("starting phase %s", s.name)
		if err := s.fn(); err != nil {
			log.Printf("stage %s: %v", s.name, err)
			return
		}
	}
}

func shutdown() error {
	log.Println("killing all processes")
	for i := 0; i < 5; i++ {
		sys.Kill(-1, sys.SIGKILL)
	}
	var err error
	for err == nil {
		_, err = sys.Wait4(-1, nil, 0, nil)
	}

	return nil
}

func single() error {
	if _, err := exec.LookPath(shell); err != nil {
		panic("no shell at " + shell)
	}
	log.Println("press ctl-d to proceed to multi-user mode")
	return (&exec.Cmd{
		Path:   shell,
		Args:   []string{"-sh", "+m"},
		Stdin:  cons,
		Stdout: cons,
		Stderr: cons,
	}).Run()
}

func runcom() error {
	return (&exec.Cmd{
		Path: shell,
		Args: []string{shell, runc},
	}).Run()
}

func multiple() error {
	// shutdown is initiated by SIGHUP
	c := make(chan os.Signal, 1)
	signal.Notify(c, sys.SIGHUP)

	quit := make(chan bool, 1)
	go reap(quit)

	// start TTY sessions
	sessionWg = sync.WaitGroup{}
	for i := 1; i < ntty; i++ {
		log.Printf("starting session on tty%d", i+1)
		sessions[i] = newSession(i)
		go sessions[i].getty()
	}

	<-c // wait for SIGHUP
	signal.Ignore(sys.SIGHUP)
	log.Println("received SIGHUP")

	// kill reap goroutine
	quit <- true

	// close all sessions
	for i := 1; i < ntty; i++ {
		go sessions[i].close()
		sessions[i] = nil
	}
	log.Println("waiting for sessions to exit")
	sessionWg.Wait()

	return nil
}

var (
	sessions  [ntty]*session
	sessionWg sync.WaitGroup
)

type session struct {
	tty  string
	quit chan bool
	proc *os.Process
}

func newSession(i int) *session {
	sessionWg.Add(1)
	return &session{
		tty:  fmt.Sprintf("tty%d", i+1),
		quit: make(chan bool, 1),
	}
}

func (s *session) String() string {
	if s == nil {
		return "nil session"
	}
	return s.tty + ".session"
}

func (s *session) getty() {
	for {
		select {
		case <-s.quit:
			log.Printf("%v: exiting", s)
			sessionWg.Done()
			return
		default:
			path := dev + s.tty
			f, err := os.OpenFile(path, os.O_RDWR, 0)
			if err != nil {
				log.Printf("%v: open %s: %v", s, path, err)
				break
			}
			f.Chown(0, 0)
			f.Chmod(0620)
			cmd := &exec.Cmd{
				Path:   login,
				Args:   []string{"login"},
				Stdin:  f,
				Stdout: f,
				Stderr: f,
				// start a new session and set the controlling TTY
				SysProcAttr: &syscall.SysProcAttr{
					Setsid:  true,
					Setctty: true,
					Ctty:    int(f.Fd()),
				},
			}
			if err := cmd.Start(); err != nil {
				log.Printf("%v: start login: %v", s, err)
				f.Close()
				// we're in trouble; return to single-user mode.
				sys.Kill(1, sys.SIGHUP)
				return
			}
			s.proc = cmd.Process
			f.Close()
			cmd.Wait()
		}
	}
}

func (s *session) close() {
	if s == nil {
		return
	}
	if s.quit != nil {
		s.quit <- true
	}
	if s.proc != nil {
		s.proc.Kill()
	}
}

func reap(quit <-chan bool) {
	log.Println("reap: starting")
	ticker := time.NewTicker(1 * time.Minute)
	for {
		select {
		case <-quit:
			ticker.Stop()
			log.Println("reap: exiting")
			return
		case <-ticker.C:
			for {
				pid, err := sys.Wait4(-1, nil, sys.WNOHANG, nil)
				if err != nil {
					log.Printf("reap: wait: %v", err)
					break
				}
				if pid == 0 {
					break
				}
			}
		}
	}
}
<commit_msg>init: write stdout and stderr to /dev/console in runcom phase<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"

	sys "golang.org/x/sys/unix"
)

const (
	ntty    = 6 // TODO: launch gettys automatically?
	shell   = "/bin/sh"
	runc    = "/etc/rc"
	dev     = "/dev/"
	console = "/dev/console"
	login   = "/bin/login"
)

var cons *os.File

func init() {
	runtime.GOMAXPROCS(1)
}

func main() {
	signal.Ignore(sys.SIGINT, sys.SIGTERM, sys.SIGHUP)

	f, err := os.OpenFile(console, os.O_RDWR, 0)
	if err != nil {
		panic("no console")
	}
	cons = f

	log.SetPrefix("init: ")
	log.SetOutput(cons)
	log.SetFlags(log.Ltime | log.Lmicroseconds)

	for {
		run()
	}
}

func run() {
	type stage struct {
		name string
		fn   func() error
	}
	for _, s := range []stage{
		{"single-user", single},
		{"runcom", runcom},
		{"multi-user", multiple},
		{"shutdown", shutdown},
	} {
		log.Printf("starting phase %s", s.name)
		if err := s.fn(); err != nil {
			log.Printf("stage %s: %v", s.name, err)
			return
		}
	}
}

func shutdown() error {
	log.Println("killing all processes")
	for i := 0; i < 5; i++ {
		sys.Kill(-1, sys.SIGKILL)
	}
	var err error
	for err == nil {
		_, err = sys.Wait4(-1, nil, 0, nil)
	}

	return nil
}

func single() error {
	if _, err := exec.LookPath(shell); err != nil {
		panic("no shell at " + shell)
	}
	log.Println("press ctl-d to proceed to multi-user mode")
	return (&exec.Cmd{
		Path:   shell,
		Args:   []string{"-sh", "+m"},
		Stdin:  cons,
		Stdout: cons,
		Stderr: cons,
	}).Run()
}

func runcom() error {
	return (&exec.Cmd{
		Path:   shell,
		Args:   []string{shell, runc},
		Stdout: cons,
		Stderr: cons,
	}).Run()
}

func multiple() error {
	// shutdown is initiated by SIGHUP
	c := make(chan os.Signal, 1)
	signal.Notify(c, sys.SIGHUP)

	quit := make(chan bool, 1)
	go reap(quit)

	// start TTY sessions
	sessionWg = sync.WaitGroup{}
	for i := 1; i < ntty; i++ {
		log.Printf("starting session on tty%d", i+1)
		sessions[i] = newSession(i)
		go sessions[i].getty()
	}

	<-c // wait for SIGHUP
	signal.Ignore(sys.SIGHUP)
	log.Println("received SIGHUP")

	// kill reap goroutine
	quit <- true

	// close all sessions
	for i := 1; i < ntty; i++ {
		go sessions[i].close()
		sessions[i] = nil
	}
	log.Println("waiting for sessions to exit")
	sessionWg.Wait()

	return nil
}

var (
	sessions  [ntty]*session
	sessionWg sync.WaitGroup
)

type session struct {
	tty  string
	quit chan bool
	proc *os.Process
}

func newSession(i int) *session {
	sessionWg.Add(1)
	return &session{
		tty:  fmt.Sprintf("tty%d", i+1),
		quit: make(chan bool, 1),
	}
}

func (s *session) String() string {
	if s == nil {
		return "nil session"
	}
	return s.tty + ".session"
}

func (s *session) getty() {
	for {
		select {
		case <-s.quit:
			log.Printf("%v: exiting", s)
			sessionWg.Done()
			return
		default:
			path := dev + s.tty
			f, err := os.OpenFile(path, os.O_RDWR, 0)
			if err != nil {
				log.Printf("%v: open %s: %v", s, path, err)
				break
			}
			f.Chown(0, 0)
			f.Chmod(0620)
			cmd := &exec.Cmd{
				Path:   login,
				Args:   []string{"login"},
				Stdin:  f,
				Stdout: f,
				Stderr: f,
				// start a new session and set the controlling TTY
				SysProcAttr: &syscall.SysProcAttr{
					Setsid:  true,
					Setctty: true,
					Ctty:    int(f.Fd()),
				},
			}
			if err := cmd.Start(); err != nil {
				log.Printf("%v: start login: %v", s, err)
				f.Close()
				// we're in trouble; return to single-user mode.
				sys.Kill(1, sys.SIGHUP)
				return
			}
			s.proc = cmd.Process
			f.Close()
			cmd.Wait()
		}
	}
}

func (s *session) close() {
	if s == nil {
		return
	}
	if s.quit != nil {
		s.quit <- true
	}
	if s.proc != nil {
		s.proc.Kill()
	}
}

func reap(quit <-chan bool) {
	log.Println("reap: starting")
	ticker := time.NewTicker(1 * time.Minute)
	for {
		select {
		case <-quit:
			ticker.Stop()
			log.Println("reap: exiting")
			return
		case <-ticker.C:
			for {
				pid, err := sys.Wait4(-1, nil, sys.WNOHANG, nil)
				if err != nil {
					log.Printf("reap: wait: %v", err)
					break
				}
				if pid == 0 {
					break
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package sorter

// InsertionSort sorts the given array according to insertion sort algoritm
func InsertionSort(arr []int) []int {
	var key int
	var j int
	var resultArr []int

	for i := 1; i < len(arr); i++ {
		key = arr[i]
		j = i - 1
		for j >= 0 && arr[j] > key {
			arr[j+1] = arr[j]
			j--
		}
		arr[j+1] = key
	}

	resultArr = arr
	return resultArr
}
<commit_msg>insertion: variable that unused is removed<commit_after>package sorter

// InsertionSort sorts the given array according to insertion sort algoritm
func InsertionSort(arr []int) []int {
	var key int
	var j int

	for i := 1; i < len(arr); i++ {
		key = arr[i]
		j = i - 1
		for j >= 0 && arr[j] > key {
			arr[j+1] = arr[j]
			j--
		}
		arr[j+1] = key
	}

	return arr
}
<|endoftext|>


<commit_before>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
// Copyright 2017 Charles University, Faculty of Arts,
//                Institute of the Czech National Corpus
//
// 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 db

import (
	"database/sql"
	"fmt"
	"log"

	"github.com/czcorpus/vert-tagextract/db/colgen"
	_ "github.com/mattn/go-sqlite3" // sqlite3 driver load
	"github.com/tomachalek/vertigo"
)

// prepareInsert creates a prepared statement for the INSERT
// operation.
func prepareInsert(database *sql.Tx, cols []string) *sql.Stmt {
	valReplac := make([]string, len(cols))
	for i := range cols {
		valReplac[i] = "?"
	}
	ans, err := database.Prepare(fmt.Sprintf("INSERT INTO item (%s) VALUES (%s)", joinArgs(cols), joinArgs(valReplac)))
	if err != nil {
		panic(err)
	}
	return ans
}

// TTExtractor handles writing parsed data
// to a sqlite3 database. Parsed values are
// received pasivelly by implementing vertigo.LineProcessor
type TTExtractor struct {
	lineCounter        int
	atomCounter        int
	tokenInAtomCounter int
	corpusID           string
	database           *sql.DB
	transaction        *sql.Tx
	insertStatement    *sql.Stmt
	stack              *structStack
	atomStruct         string
	structures         map[string][]string
	attrNames          []string
	colgenFn           colgen.AlignedColGenFn
}

// ProcToken is a part of vertigo.LineProcessor implementation.
// It is called by Vertigo parser when a token line is encountered.
func (tte *TTExtractor) ProcToken(tk *vertigo.Token) {
	tte.lineCounter++
	tte.tokenInAtomCounter++
}

// ProcStructClose is a part of vertigo.LineProcessor implementation.
// It is called by Vertigo parser when a closing structure tag is
// encountered.
func (tte *TTExtractor) ProcStructClose(st *vertigo.StructureClose) {
	tte.stack.Pop()
	tte.lineCounter++
}

// acceptAttr tests whether a structural attribute
// [structName].[attrName] is configured (see _example/*.json) to be imported
func (tte *TTExtractor) acceptAttr(structName string, attrName string) bool {
	tmp := tte.structures[structName]
	for _, v := range tmp {
		if v == attrName {
			return true
		}
	}
	return false
}

// ProcStruct is a part of vertigo.LineProcessor implementation.
// It si called by Vertigo parser when an opening structure tag
// is encountered.
func (tte *TTExtractor) ProcStruct(st *vertigo.Structure) {
	tte.stack.Push(st)
	if st.Name == tte.atomStruct {
		attrs := make(map[string]interface{})
		tte.stack.forEachAttr(func(s string, k string, v string) {
			if tte.acceptAttr(s, k) {
				attrs[fmt.Sprintf("%s_%s", s, k)] = v
			}
		})
		if tte.atomCounter == 0 {
			tte.attrNames = make([]string, len(attrs)+4)
			i := 0
			for k := range attrs {
				tte.attrNames[i] = k
				i++
			}
			tte.attrNames[i] = "wordcount"
			tte.attrNames[i+1] = "poscount"
			tte.attrNames[i+2] = "corpus_id"
			if tte.colgenFn != nil {
				tte.attrNames[i+3] = "item_id"

			} else {
				tte.attrNames = tte.attrNames[:i+3]
			}
			tte.insertStatement = prepareInsert(tte.transaction, tte.attrNames)
		}
		attrs["wordcount"] = 0
		attrs["poscount"] = tte.tokenInAtomCounter
		attrs["corpus_id"] = tte.corpusID
		if tte.colgenFn != nil {
			attrs["item_id"] = tte.colgenFn(attrs)
		}
		values := make([]interface{}, len(tte.attrNames))
		for i, n := range tte.attrNames {
			if attrs[n] != nil {
				values[i] = attrs[n]

			} else {
				values[i] = "" // liveattrs plug-in does not like NULLs
			}
		}
		_, err := tte.insertStatement.Exec(values...)
		if err != nil {
			log.Fatalf("Failed to insert data: %s", err)
		}
		tte.atomCounter++
		tte.tokenInAtomCounter = 0
	}
	tte.lineCounter++
}

// Run starts the parsing and metadata extraction
// process. The method expects a proper database
// schema to be ready (see database.go for details).
// The whole process runs within a transaction which
// makes sqlite3 inserts a few orders of magnitude
// faster.
func (tte *TTExtractor) Run(conf *vertigo.ParserConf) {
	log.Print("Starting to process the vertical file...")
	tte.database.Exec("PRAGMA synchronous = OFF")
	tte.database.Exec("PRAGMA journal_mode = MEMORY")
	var err error
	tte.transaction, err = tte.database.Begin()
	if err != nil {
		log.Fatalf("Failed to start a database transaction: %s", err)
	}
	vertigo.ParseVerticalFile(conf, tte)
	tte.transaction.Commit()
	log.Print("...DONE")
}

// NewTTExtractor is a factory function to
// instantiate proper TTExtractor.
func NewTTExtractor(database *sql.DB, corpusID string, atomStruct string, structures map[string][]string,
	colgenFn colgen.AlignedColGenFn) *TTExtractor {
	return &TTExtractor{
		database:   database,
		corpusID:   corpusID,
		atomStruct: atomStruct,
		structures: structures,
		stack:      &structStack{},
		colgenFn:   colgenFn,
	}
}
<commit_msg>Process returned error<commit_after>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
// Copyright 2017 Charles University, Faculty of Arts,
//                Institute of the Czech National Corpus
//
// 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 db

import (
	"database/sql"
	"fmt"
	"log"

	"github.com/czcorpus/vert-tagextract/db/colgen"
	_ "github.com/mattn/go-sqlite3" // sqlite3 driver load
	"github.com/tomachalek/vertigo"
)

// prepareInsert creates a prepared statement for the INSERT
// operation.
func prepareInsert(database *sql.Tx, cols []string) *sql.Stmt {
	valReplac := make([]string, len(cols))
	for i := range cols {
		valReplac[i] = "?"
	}
	ans, err := database.Prepare(fmt.Sprintf("INSERT INTO item (%s) VALUES (%s)", joinArgs(cols), joinArgs(valReplac)))
	if err != nil {
		panic(err)
	}
	return ans
}

// TTExtractor handles writing parsed data
// to a sqlite3 database. Parsed values are
// received pasivelly by implementing vertigo.LineProcessor
type TTExtractor struct {
	lineCounter        int
	atomCounter        int
	tokenInAtomCounter int
	corpusID           string
	database           *sql.DB
	transaction        *sql.Tx
	insertStatement    *sql.Stmt
	stack              *structStack
	atomStruct         string
	structures         map[string][]string
	attrNames          []string
	colgenFn           colgen.AlignedColGenFn
}

// ProcToken is a part of vertigo.LineProcessor implementation.
// It is called by Vertigo parser when a token line is encountered.
func (tte *TTExtractor) ProcToken(tk *vertigo.Token) {
	tte.lineCounter++
	tte.tokenInAtomCounter++
}

// ProcStructClose is a part of vertigo.LineProcessor implementation.
// It is called by Vertigo parser when a closing structure tag is
// encountered.
func (tte *TTExtractor) ProcStructClose(st *vertigo.StructureClose) {
	tte.stack.Pop()
	tte.lineCounter++
}

// acceptAttr tests whether a structural attribute
// [structName].[attrName] is configured (see _example/*.json) to be imported
func (tte *TTExtractor) acceptAttr(structName string, attrName string) bool {
	tmp := tte.structures[structName]
	for _, v := range tmp {
		if v == attrName {
			return true
		}
	}
	return false
}

// ProcStruct is a part of vertigo.LineProcessor implementation.
// It si called by Vertigo parser when an opening structure tag
// is encountered.
func (tte *TTExtractor) ProcStruct(st *vertigo.Structure) {
	tte.stack.Push(st)
	if st.Name == tte.atomStruct {
		attrs := make(map[string]interface{})
		tte.stack.forEachAttr(func(s string, k string, v string) {
			if tte.acceptAttr(s, k) {
				attrs[fmt.Sprintf("%s_%s", s, k)] = v
			}
		})
		if tte.atomCounter == 0 {
			tte.attrNames = make([]string, len(attrs)+4)
			i := 0
			for k := range attrs {
				tte.attrNames[i] = k
				i++
			}
			tte.attrNames[i] = "wordcount"
			tte.attrNames[i+1] = "poscount"
			tte.attrNames[i+2] = "corpus_id"
			if tte.colgenFn != nil {
				tte.attrNames[i+3] = "item_id"

			} else {
				tte.attrNames = tte.attrNames[:i+3]
			}
			tte.insertStatement = prepareInsert(tte.transaction, tte.attrNames)
		}
		attrs["wordcount"] = 0
		attrs["poscount"] = tte.tokenInAtomCounter
		attrs["corpus_id"] = tte.corpusID
		if tte.colgenFn != nil {
			attrs["item_id"] = tte.colgenFn(attrs)
		}
		values := make([]interface{}, len(tte.attrNames))
		for i, n := range tte.attrNames {
			if attrs[n] != nil {
				values[i] = attrs[n]

			} else {
				values[i] = "" // liveattrs plug-in does not like NULLs
			}
		}
		_, err := tte.insertStatement.Exec(values...)
		if err != nil {
			log.Fatalf("Failed to insert data: %s", err)
		}
		tte.atomCounter++
		tte.tokenInAtomCounter = 0
	}
	tte.lineCounter++
}

// Run starts the parsing and metadata extraction
// process. The method expects a proper database
// schema to be ready (see database.go for details).
// The whole process runs within a transaction which
// makes sqlite3 inserts a few orders of magnitude
// faster.
func (tte *TTExtractor) Run(conf *vertigo.ParserConf) {
	log.Print("Starting to process the vertical file...")
	tte.database.Exec("PRAGMA synchronous = OFF")
	tte.database.Exec("PRAGMA journal_mode = MEMORY")
	var err error
	tte.transaction, err = tte.database.Begin()
	if err != nil {
		log.Fatalf("Failed to start a database transaction: %s", err)
	}
	parserErr := vertigo.ParseVerticalFile(conf, tte)
	if parserErr != nil {
		tte.transaction.Rollback()
		log.Fatalf("Failed to parse vertical file: %s", parserErr)

	} else {
		tte.transaction.Commit()
		log.Print("...DONE")
	}
}

// NewTTExtractor is a factory function to
// instantiate proper TTExtractor.
func NewTTExtractor(database *sql.DB, corpusID string, atomStruct string, structures map[string][]string,
	colgenFn colgen.AlignedColGenFn) *TTExtractor {
	return &TTExtractor{
		database:   database,
		corpusID:   corpusID,
		atomStruct: atomStruct,
		structures: structures,
		stack:      &structStack{},
		colgenFn:   colgenFn,
	}
}
<|endoftext|>


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

package layers

// This file implements the ASF-RMCP header specified in section 3.2.2.2 of
// https://www.dmtf.org/sites/default/files/standards/documents/DSP0136.pdf

import (
	"fmt"

	"github.com/google/gopacket"
)

// RMCPClass is the class of a RMCP layer's payload, e.g. ASF or IPMI. This is a
// 4-bit unsigned int on the wire; all but 6 (ASF), 7 (IPMI) and 8 (OEM-defined)
// are currently reserved.
type RMCPClass uint8

// LayerType returns the payload layer type corresponding to a RMCP class.
func (c RMCPClass) LayerType() gopacket.LayerType {
	if lt := rmcpClassLayerTypes[uint8(c)]; lt != 0 {
		return lt
	}
	return gopacket.LayerTypePayload
}

func (c RMCPClass) String() string {
	return fmt.Sprintf("%v(%v)", uint8(c), c.LayerType())
}

const (
	// RMCPVersion1 identifies RMCP v1.0 in the Version header field. Lower
	// values are considered legacy, while higher values are reserved by the
	// specification.
	RMCPVersion1 uint8 = 0x06

	// RMCPNormal indicates a "normal" message, i.e. not an acknowledgement.
	RMCPNormal uint8 = 0

	// RMCPAck indicates a message is acknowledging a received normal message.
	RMCPAck uint8 = 1 << 7

	// RMCPClassASF identifies an RMCP message as containing an ASF-RMCP
	// payload.
	RMCPClassASF RMCPClass = 0x06

	// RMCPClassIPMI identifies an RMCP message as containing an IPMI payload.
	RMCPClassIPMI RMCPClass = 0x07

	// RMCPClassOEM identifies an RMCP message as containing an OEM-defined
	// payload.
	RMCPClassOEM RMCPClass = 0x08
)

var (
	rmcpClassLayerTypes = [16]gopacket.LayerType{
		// RMCPClassASF and RMCPClassIPMI are to implement; OEM layer type (8)
		// is deliberately not implemented, so we return LayerTypePayload
	}
)

// RMCP describes the format of an RMCP header, which forms a UDP payload. See
// section 3.2.2.2.
type RMCP struct {
	BaseLayer

	// Version identifies the version of the RMCP header.  0x06 indicates RMCP
	// v1.0; lower values are legacy, higher values are reserved.
	Version uint8

	// Sequence is the sequence number assicated with the message.  Note that
	// this rolls over to 0 after 254, not 255. Seq num 255 indicates the
	// receiver must not send an ACK.
	Sequence uint8

	// Ack indicates whether this packet is an acknowledgement. If if it is, the
	// payload will be empty.
	Ack bool

	// Class idicates the structure of the payload. There are only 2^4 valid
	// values, however there is no uint4 data type. N.B.  the Ack bit has been
	// split off into another field. The most significant 4 bits of this field
	// will always be 0.
	Class RMCPClass
}

// LayerType returns LayerTypeRMCP. It partially satisfies Layer and
// SerializableLayer.
func (r *RMCP) LayerType() gopacket.LayerType {
	return LayerTypeRMCP
}

// CanDecode returns LayerTypeRMCP. It partially satisfies DecodingLayer.
func (r *RMCP) CanDecode() gopacket.LayerClass {
	return r.LayerType()
}

// DecodeFromBytes makes the layer represent the provided bytes. It partially
// satisfies DecodingLayer.
func (r *RMCP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	if len(data) < 4 {
		df.SetTruncated()
		return fmt.Errorf("invalid RMCP header, length %v less than 4",
			len(data))
	}

	r.BaseLayer.Contents = data[:4]
	r.BaseLayer.Payload = data[4:]

	r.Version = uint8(data[0])
	// 1 byte reserved
	r.Sequence = uint8(data[2])
	r.Ack = data[3]&RMCPAck != 0
	r.Class = RMCPClass(data[3] & 0xF)
	return nil
}

// NextLayerType returns the data layer of this RMCP layer. This partially
// satisfies DecodingLayer.
func (r *RMCP) NextLayerType() gopacket.LayerType {
	return r.Class.LayerType()
}

// Payload returns the data layer. It partially satisfies ApplicationLayer.
func (r *RMCP) Payload() []byte {
	return r.BaseLayer.Payload
}

// SerializeTo writes the serialized fom of this layer into the SerializeBuffer,
// partially satisfying SerializableLayer.
func (r *RMCP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
	bytes, err := b.PrependBytes(4)
	if err != nil {
		return err
	}
	bytes[0] = r.Version
	// 1 byte reserved, 0x00
	bytes[2] = r.Sequence
	bytes[3] = bool2uint8(r.Ack)<<7 | uint8(r.Class) // thanks, BFD layer
	return nil
}

// decodeRMCP decodes the byte slice into an RMCP type, and sets the application
// layer to it.
func decodeRMCP(data []byte, p gopacket.PacketBuilder) error {
	rmcp := &RMCP{}
	err := rmcp.DecodeFromBytes(data, p)
	p.AddLayer(rmcp)
	p.SetApplicationLayer(rmcp)
	if err != nil {
		return err
	}
	return p.NextDecoder(rmcp.NextLayerType())
}
<commit_msg>Fix typos in documentation<commit_after>// Copyright 2019 The GoPacket Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file in the root of the source tree.

package layers

// This file implements the ASF-RMCP header specified in section 3.2.2.2 of
// https://www.dmtf.org/sites/default/files/standards/documents/DSP0136.pdf

import (
	"fmt"

	"github.com/google/gopacket"
)

// RMCPClass is the class of a RMCP layer's payload, e.g. ASF or IPMI. This is a
// 4-bit unsigned int on the wire; all but 6 (ASF), 7 (IPMI) and 8 (OEM-defined)
// are currently reserved.
type RMCPClass uint8

// LayerType returns the payload layer type corresponding to a RMCP class.
func (c RMCPClass) LayerType() gopacket.LayerType {
	if lt := rmcpClassLayerTypes[uint8(c)]; lt != 0 {
		return lt
	}
	return gopacket.LayerTypePayload
}

func (c RMCPClass) String() string {
	return fmt.Sprintf("%v(%v)", uint8(c), c.LayerType())
}

const (
	// RMCPVersion1 identifies RMCP v1.0 in the Version header field. Lower
	// values are considered legacy, while higher values are reserved by the
	// specification.
	RMCPVersion1 uint8 = 0x06

	// RMCPNormal indicates a "normal" message, i.e. not an acknowledgement.
	RMCPNormal uint8 = 0

	// RMCPAck indicates a message is acknowledging a received normal message.
	RMCPAck uint8 = 1 << 7

	// RMCPClassASF identifies an RMCP message as containing an ASF-RMCP
	// payload.
	RMCPClassASF RMCPClass = 0x06

	// RMCPClassIPMI identifies an RMCP message as containing an IPMI payload.
	RMCPClassIPMI RMCPClass = 0x07

	// RMCPClassOEM identifies an RMCP message as containing an OEM-defined
	// payload.
	RMCPClassOEM RMCPClass = 0x08
)

var (
	rmcpClassLayerTypes = [16]gopacket.LayerType{
		// RMCPClassASF and RMCPClassIPMI are to implement; OEM layer type (8)
		// is deliberately not implemented, so we return LayerTypePayload
	}
)

// RMCP describes the format of an RMCP header, which forms a UDP payload. See
// section 3.2.2.2.
type RMCP struct {
	BaseLayer

	// Version identifies the version of the RMCP header. 0x06 indicates RMCP
	// v1.0; lower values are legacy, higher values are reserved.
	Version uint8

	// Sequence is the sequence number assicated with the message. Note that
	// this rolls over to 0 after 254, not 255. Seq num 255 indicates the
	// receiver must not send an ACK.
	Sequence uint8

	// Ack indicates whether this packet is an acknowledgement. If it is, the
	// payload will be empty.
	Ack bool

	// Class idicates the structure of the payload. There are only 2^4 valid
	// values, however there is no uint4 data type. N.B. the Ack bit has been
	// split off into another field. The most significant 4 bits of this field
	// will always be 0.
	Class RMCPClass
}

// LayerType returns LayerTypeRMCP. It partially satisfies Layer and
// SerializableLayer.
func (r *RMCP) LayerType() gopacket.LayerType {
	return LayerTypeRMCP
}

// CanDecode returns LayerTypeRMCP. It partially satisfies DecodingLayer.
func (r *RMCP) CanDecode() gopacket.LayerClass {
	return r.LayerType()
}

// DecodeFromBytes makes the layer represent the provided bytes. It partially
// satisfies DecodingLayer.
func (r *RMCP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	if len(data) < 4 {
		df.SetTruncated()
		return fmt.Errorf("invalid RMCP header, length %v less than 4",
			len(data))
	}

	r.BaseLayer.Contents = data[:4]
	r.BaseLayer.Payload = data[4:]

	r.Version = uint8(data[0])
	// 1 byte reserved
	r.Sequence = uint8(data[2])
	r.Ack = data[3]&RMCPAck != 0
	r.Class = RMCPClass(data[3] & 0xF)
	return nil
}

// NextLayerType returns the data layer of this RMCP layer. This partially
// satisfies DecodingLayer.
func (r *RMCP) NextLayerType() gopacket.LayerType {
	return r.Class.LayerType()
}

// Payload returns the data layer. It partially satisfies ApplicationLayer.
func (r *RMCP) Payload() []byte {
	return r.BaseLayer.Payload
}

// SerializeTo writes the serialized fom of this layer into the SerializeBuffer,
// partially satisfying SerializableLayer.
func (r *RMCP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
	bytes, err := b.PrependBytes(4)
	if err != nil {
		return err
	}
	bytes[0] = r.Version
	// 1 byte reserved, 0x00
	bytes[2] = r.Sequence
	bytes[3] = bool2uint8(r.Ack)<<7 | uint8(r.Class) // thanks, BFD layer
	return nil
}

// decodeRMCP decodes the byte slice into an RMCP type, and sets the application
// layer to it.
func decodeRMCP(data []byte, p gopacket.PacketBuilder) error {
	rmcp := &RMCP{}
	err := rmcp.DecodeFromBytes(data, p)
	p.AddLayer(rmcp)
	p.SetApplicationLayer(rmcp)
	if err != nil {
		return err
	}
	return p.NextDecoder(rmcp.NextLayerType())
}
<|endoftext|>


<commit_before>package amf

import (
	"encoding/binary"
	"io"
	"math"
)

// marker: 1 byte 0x00
// format: 8 byte big endian float64
func (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (result float64, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_NUMBER_MARKER); err != nil {
		return
	}

	var bytes []byte

	if bytes, err = ReadBytes(r, 8); err != nil {
		return
	}

	u64n := binary.BigEndian.Uint64(bytes)
	result = math.Float64frombits(u64n)

	return
}

// marker: 1 byte 0x01
// format: 1 byte, 0x00 = false, 0x01 = true
func (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (result bool, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_BOOLEAN_MARKER); err != nil {
		return
	}

	var b byte
	if b, err = ReadByte(r); err != nil {
		return
	}

	if b == AMF0_BOOLEAN_FALSE {
		return false, nil
	} else if b == AMF0_BOOLEAN_TRUE {
		return true, nil
	}

	return false, Error("decode amf0: unexpected value %v for boolean", b)
}

// marker: 1 byte 0x02
// format:
// - 2 byte big endian uint16 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_STRING_MARKER); err != nil {
		return
	}

	var bytes []byte
	if bytes, err = ReadBytes(r, 2); err != nil {
		return
	}

	len := binary.BigEndian.Uint16(bytes)

	if bytes, err = ReadBytes(r, int(len)); err != nil {
		return
	}

	return string(bytes), nil
}

// marker: 1 byte 0x03
// format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Object, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_OBJECT_MARKER); err != nil {
		return nil, err
	}

	result := make(Object)
	d.refCache = append(d.refCache, result)

	for {
		key, err := d.DecodeAmf0String(r, false)
		if err != nil {
			return nil, err
		}

		if key == "" {
			if err = AssertMarker(r, true, AMF0_OBJECT_END_MARKER); err != nil {
				return nil, Error("decode amf0: expected object end marker")
			}

			break
		}

		value, err := d.DecodeAmf0(r)
		if err != nil {
			return nil, Error("decode amf0: unable to decode object value: %s", err)
		}

		result[key] = value
	}

	return result, nil

}

// marker: 1 byte 0x05
// no additional data
func (d *Decoder) DecodeAmf0Null(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_NULL_MARKER)
	return
}

// marker: 1 byte 0x06
// no additional data
func (d *Decoder) DecodeAmf0Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_UNDEFINED_MARKER)
	return
}

// marker: 1 byte 0x07
// format: 2 byte big endian uint16
func (d *Decoder) DecodeAmf0Reference(r io.Reader, decodeMarker bool) (interface{}, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_REFERENCE_MARKER); err != nil {
		return nil, err
	}

	var err error
	var bytes []byte
	if bytes, err = ReadBytes(r, 2); err != nil {
		return nil, err
	}

	ref := binary.BigEndian.Uint16(bytes)

	if int(ref) > len(d.refCache) {
		return nil, Error("decode amf0: bad reference %d (current length %d)", ref, len(d.refCache))
	}

	result := d.refCache[ref]

	return result, nil
}

// marker: 1 byte 0x08
// format:
// - 4 byte big endian uint32 with length of associative array
// - normal object format:
//   - loop encoded string followed by encoded value
//   - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (EcmaArray, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_ECMA_ARRAY_MARKER); err != nil {
		return nil, err
	}

	bytes, err := ReadBytes(r, 4)
	if err != nil {
		return nil, err
	}

	l := binary.BigEndian.Uint32(bytes)

	obj, err := d.DecodeAmf0Object(r, false)
	if err != nil {
		return nil, Error("decode amf0: unable to decode ecma array object: %s", err)
	}

	result := EcmaArray(obj)

	if int(l) != len(result) {
		return nil, Error("decode amf0: ecma array has unexpected length %d (expected %d)", len(result), l)
	}

	return result, nil
}

// marker: 1 byte 0x0a
// format:
// - 4 byte big endian uint32 to determine length of associative array
// - n (length) encoded values
func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) (StrictArray, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_STRICT_ARRAY_MARKER); err != nil {
		return nil, err
	}

	var bytes []byte
	var err error
	if bytes, err = ReadBytes(r, 4); err != nil {
		return nil, err
	}

	l := binary.BigEndian.Uint32(bytes)

	result := make(StrictArray, l)
	d.refCache = append(d.refCache, result)

	for i := uint32(0); i < l; i++ {
		value, err := d.DecodeAmf0(r)
		if err != nil {
			return nil, Error("decode amf0: unable to decode strict array object: %s", err)
		}

		result[i] = value
	}

	return result, nil
}

// marker: 1 byte 0x0b
// format:
// - normal number format:
//   - 8 byte big endian float64
// - 2 byte unused
func (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result float64, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_DATE_MARKER); err != nil {
		return
	}

	if result, err = d.DecodeAmf0Number(r, false); err != nil {
		return float64(0), Error("decode amf0: unable to decode float in date: %s", err)
	}

	if _, err = ReadBytes(r, 2); err != nil {
		return float64(0), Error("decode amf0: unable to read 2 trail bytes in date: %s", err)
	}

	return
}

// marker: 1 byte 0x0c
// format:
// - 4 byte big endian uint32 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_LONG_STRING_MARKER); err != nil {
		return
	}

	var bytes []byte
	if bytes, err = ReadBytes(r, 4); err != nil {
		return
	}

	len := binary.BigEndian.Uint32(bytes)

	if bytes, err = ReadBytes(r, int(len)); err != nil {
		return
	}

	return string(bytes), nil
}

// marker: 1 byte 0x0d
// no additional data
func (d *Decoder) DecodeAmf0Unsupported(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_UNSUPPORTED_MARKER)
	return
}

// marker: 1 byte 0x0f
// format:
// - normal long string format
//   - 4 byte big endian uint32 header to determine size
//   - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0XmlDocument(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_XML_DOCUMENT_MARKER); err != nil {
		return
	}

	return d.DecodeAmf0LongString(r, false)
}

// marker: 1 byte 0x10
// format:
// - normal string format:
//   - 2 byte big endian uint16 header to determine size
//   - n (size) byte utf8 string
// - normal object format:
//   - loop encoded string followed by encoded value
//   - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) (*TypedObject, error) {
	result := new(TypedObject)

	err := AssertMarker(r, decodeMarker, AMF0_TYPED_OBJECT_MARKER)
	if err != nil {
		return result, err
	}

	d.refCache = append(d.refCache, result)

	result.Type, err = d.DecodeAmf0String(r, false)
	if err != nil {
		return result, Error("decode amf0: typed object unable to determine type: %s", err)
	}

	result.Object, err = d.DecodeAmf0Object(r, false)
	if err != nil {
		return result, Error("decode amf0: typed object unable to determine object: %s", err)
	}

	return result, nil
}
<commit_msg>Use strongly typed binary reads instead of hackey type conversions, improve error messages for potentially nested results.<commit_after>package amf

import (
	"encoding/binary"
	"io"
)

// marker: 1 byte 0x00
// format: 8 byte big endian float64
func (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (result float64, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_NUMBER_MARKER); err != nil {
		return
	}

	err = binary.Read(r, binary.BigEndian, &result)
	if err != nil {
		return float64(0), Error("amf0 decode: unable to read number: %s", err)
	}

	return
}

// marker: 1 byte 0x01
// format: 1 byte, 0x00 = false, 0x01 = true
func (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (result bool, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_BOOLEAN_MARKER); err != nil {
		return
	}

	var b byte
	if b, err = ReadByte(r); err != nil {
		return
	}

	if b == AMF0_BOOLEAN_FALSE {
		return false, nil
	} else if b == AMF0_BOOLEAN_TRUE {
		return true, nil
	}

	return false, Error("decode amf0: unexpected value %v for boolean", b)
}

// marker: 1 byte 0x02
// format:
// - 2 byte big endian uint16 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_STRING_MARKER); err != nil {
		return
	}

	var length uint16
	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return "", Error("decode amf0: unable to decode string length: %s", err)
	}

	var bytes = make([]byte, length)
	if bytes, err = ReadBytes(r, int(length)); err != nil {
		return "", Error("decode amf0: unable to decode string value: %s", err)
	}

	return string(bytes), nil
}

// marker: 1 byte 0x03
// format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Object, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_OBJECT_MARKER); err != nil {
		return nil, err
	}

	result := make(Object)
	d.refCache = append(d.refCache, result)

	for {
		key, err := d.DecodeAmf0String(r, false)
		if err != nil {
			return nil, err
		}

		if key == "" {
			if err = AssertMarker(r, true, AMF0_OBJECT_END_MARKER); err != nil {
				return nil, Error("decode amf0: expected object end marker: %s", err)
			}

			break
		}

		value, err := d.DecodeAmf0(r)
		if err != nil {
			return nil, Error("decode amf0: unable to decode object value: %s", err)
		}

		result[key] = value
	}

	return result, nil

}

// marker: 1 byte 0x05
// no additional data
func (d *Decoder) DecodeAmf0Null(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_NULL_MARKER)
	return
}

// marker: 1 byte 0x06
// no additional data
func (d *Decoder) DecodeAmf0Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_UNDEFINED_MARKER)
	return
}

// marker: 1 byte 0x07
// format: 2 byte big endian uint16
func (d *Decoder) DecodeAmf0Reference(r io.Reader, decodeMarker bool) (interface{}, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_REFERENCE_MARKER); err != nil {
		return nil, err
	}

	var err error
	var ref uint16

	err = binary.Read(r, binary.BigEndian, &ref)
	if err != nil {
		return nil, Error("decode amf0: unable to decode reference id: %s", err)
	}

	if int(ref) > len(d.refCache) {
		return nil, Error("decode amf0: bad reference %d (current length %d)", ref, len(d.refCache))
	}

	result := d.refCache[ref]

	return result, nil
}

// marker: 1 byte 0x08
// format:
// - 4 byte big endian uint32 with length of associative array
// - normal object format:
//   - loop encoded string followed by encoded value
//   - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (EcmaArray, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_ECMA_ARRAY_MARKER); err != nil {
		return nil, err
	}

	var length uint32
	err := binary.Read(r, binary.BigEndian, &length)

	obj, err := d.DecodeAmf0Object(r, false)
	if err != nil {
		return nil, Error("decode amf0: unable to decode ecma array object: %s", err)
	}

	result := EcmaArray(obj)

	if int(length) != len(result) {
		return nil, Error("decode amf0: ecma array has unexpected length %d (expected %d)", len(result), length)
	}

	return result, nil
}

// marker: 1 byte 0x0a
// format:
// - 4 byte big endian uint32 to determine length of associative array
// - n (length) encoded values
func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) (StrictArray, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_STRICT_ARRAY_MARKER); err != nil {
		return nil, err
	}

	var err error
	var length uint32

	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return nil, Error("decode amf0: unable to decode strict array length: %s", err)
	}

	result := make(StrictArray, length)
	d.refCache = append(d.refCache, result)

	for i := uint32(0); i < length; i++ {
		value, err := d.DecodeAmf0(r)
		if err != nil {
			return nil, Error("decode amf0: unable to decode strict array object: %s", err)
		}

		result[i] = value
	}

	return result, nil
}

// marker: 1 byte 0x0b
// format:
// - normal number format:
//   - 8 byte big endian float64
// - 2 byte unused
func (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result float64, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_DATE_MARKER); err != nil {
		return
	}

	if result, err = d.DecodeAmf0Number(r, false); err != nil {
		return float64(0), Error("decode amf0: unable to decode float in date: %s", err)
	}

	if _, err = ReadBytes(r, 2); err != nil {
		return float64(0), Error("decode amf0: unable to read 2 trail bytes in date: %s", err)
	}

	return
}

// marker: 1 byte 0x0c
// format:
// - 4 byte big endian uint32 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_LONG_STRING_MARKER); err != nil {
		return
	}

	var length uint32
	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return "", Error("decode amf0: unable to decode long string length: %s", err)
	}

	var bytes = make([]byte, length)
	if bytes, err = ReadBytes(r, int(length)); err != nil {
		return "", Error("decode amf0: unable to decode long string value: %s", err)
	}

	return string(bytes), nil
}

// marker: 1 byte 0x0d
// no additional data
func (d *Decoder) DecodeAmf0Unsupported(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_UNSUPPORTED_MARKER)
	return
}

// marker: 1 byte 0x0f
// format:
// - normal long string format
//   - 4 byte big endian uint32 header to determine size
//   - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0XmlDocument(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_XML_DOCUMENT_MARKER); err != nil {
		return
	}

	return d.DecodeAmf0LongString(r, false)
}

// marker: 1 byte 0x10
// format:
// - normal string format:
//   - 2 byte big endian uint16 header to determine size
//   - n (size) byte utf8 string
// - normal object format:
//   - loop encoded string followed by encoded value
//   - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) (*TypedObject, error) {
	result := new(TypedObject)

	err := AssertMarker(r, decodeMarker, AMF0_TYPED_OBJECT_MARKER)
	if err != nil {
		return result, err
	}

	d.refCache = append(d.refCache, result)

	result.Type, err = d.DecodeAmf0String(r, false)
	if err != nil {
		return result, Error("decode amf0: typed object unable to determine type: %s", err)
	}

	result.Object, err = d.DecodeAmf0Object(r, false)
	if err != nil {
		return result, Error("decode amf0: typed object unable to determine object: %s", err)
	}

	return result, nil
}
<|endoftext|>


<commit_before>package leeroy

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strconv"

	"github.com/fabioxgn/go-bot"
)

var (
	repo    string = "docker/docker"
	baseurl string = "https://leeroy.dockerproject.com/"
)

type PullRequest struct {
	Number  int    `json:"number"`
	Repo    string `json:"repo"`
	Context string `json:"context"`
}

func sendRequest(pr PullRequest, url string) (err error) {
	data, err := json.Marshal(pr)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.SetBasicAuth(os.Getenv("LEEROY_USERNAME"), os.Getenv("LEEROY_PASS"))

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()

	if resp.StatusCode != 204 {
		return fmt.Errorf("Requesting %s for PR %d for %s returned status code: %d", url, pr.Number, repo, resp.StatusCode)
	}

	return nil
}

func rebuild(command *bot.Cmd) (msg string, err error) {
	if len(command.Args) < 1 {
		return "Not enough args. try: !rebuild 9437 docker/docker", nil
	}
	if len(command.Args) >= 2 {
		repo = command.Args[1]
	}

	// convert the string to an int
	num, err := strconv.Atoi(command.Args[0])
	if err != nil {
		return "", fmt.Errorf("converting PR num to int failed: %v", err)
	}

	pr := PullRequest{
		Number: num,
		Repo:   repo,
	}

	if err := sendRequest(pr, baseurl+"build/retry"); err != nil {
		return "", err
	}

	return fmt.Sprintf("Rebuilding PR %d at https://github.com/%s/pull/%d", pr.Number, repo, pr.Number), nil
}

func customBuild(command *bot.Cmd, context string) (msg string, err error) {
	if len(command.Args) < 1 {
		return fmt.Sprintf("Not enough args. try: !%s 9437 docker/docker", context), nil
	}
	if len(command.Args) >= 2 {
		repo = command.Args[1]
	}

	// convert the string to an int
	num, err := strconv.Atoi(command.Args[0])
	if err != nil {
		return "", fmt.Errorf("converting PR num to int failed: %v", err)
	}

	pr := PullRequest{
		Number:  num,
		Repo:    repo,
		Context: context,
	}

	if err := sendRequest(pr, baseurl+"build/custom"); err != nil {
		return "", err
	}

	return fmt.Sprintf("Building PR %d on %s at https://github.com/%s/pull/%d", pr.Number, context, repo, pr.Number), nil
}

func lxcBuild(command *bot.Cmd) (msg string, err error) {
	return customBuild(command, "lxc")
}

func windowsBuild(command *bot.Cmd) (msg string, err error) {
	return customBuild(command, "windows")
}

func init() {
	bot.RegisterCommand(
		"rebuild",
		"Rebuilds a PR number on Jenkins.",
		"9437",
		rebuild)

	bot.RegisterCommand(
		"lxc",
		"Build a PR on an lxc box with the lxc driver",
		"9345",
		lxcBuild,
	)

	bot.RegisterCommand(
		"windows",
		"Build a PR on a windows box",
		"9345",
		windowsBuild,
	)
}
<commit_msg>update for multiple repos<commit_after>package leeroy

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/fabioxgn/go-bot"
)

var (
	RepoPrefix string = "docker/"
	BaseUrl    string = "https://leeroy.dockerproject.com/"
)

type PullRequest struct {
	Number  int    `json:"number"`
	Repo    string `json:"repo"`
	Context string `json:"context"`
}

func parsePullRequest(arg string) (pr PullRequest, err error) {
	// parse for the repo
	// split on #
	nameArgs := strings.SplitN(arg, "#", 2)
	if len(nameArgs) <= 1 {
		return pr, fmt.Errorf("%s did not include #", arg)
	}

	pr.Repo = nameArgs[0]

	// parse the second arguement for a /
	// for if its a custom build
	buildArgs := strings.SplitN(nameArgs[1], "/", 2)
	if len(buildArgs) == 2 {
		pr.Context = buildArgs[1]
	}

	// parse as int
	pr.Number, err = strconv.Atoi(buildArgs[0])
	if err != nil {
		return pr, err
	}

	return pr, nil
}

func sendRequest(pr PullRequest, url string) (err error) {
	data, err := json.Marshal(pr)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.SetBasicAuth(os.Getenv("LEEROY_USERNAME"), os.Getenv("LEEROY_PASS"))

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()

	if resp.StatusCode != 204 {
		return fmt.Errorf("Requesting %s for PR %d for %s returned status code: %d. Make sure the repo allows builds.", url, pr.Number, pr.Repo, resp.StatusCode)
	}
	return nil
}

func rebuild(command *bot.Cmd) (msg string, err error) {
	tryString := "Try !rebuild libcontainer#234."
	if len(command.Args) < 1 {
		return "", fmt.Errorf("Not enough args. %s", tryString)
	}

	pr, err := parsePullRequest(command.Args[0])
	if err != nil {
		return "", fmt.Errorf("Error parsing pull request: %v. %s", err, tryString)
	}

	endpoint := "build/retry"
	if pr.Context != "" {
		endpoint = "build/custom"
	}

	if err := sendRequest(pr, BaseUrl+endpoint); err != nil {
		return "", err
	}

	if pr.Context != "" {
		return fmt.Sprintf("Building PR %d on %s at https://github.com/%s/pull/%d", pr.Number, pr.Context, pr.Repo, pr.Number), nil
	}

	return fmt.Sprintf("Rebuilding PR %d at https://github.com/%s/pull/%d", pr.Number, pr.Repo, pr.Number), nil
}

func init() {
	bot.RegisterCommand(
		"rebuild",
		"Rebuilds a PR number on Jenkins.",
		"libcontainer#9437",
		rebuild)
}
<|endoftext|>