<commit_before>/*
Copyright 2017 WALLIX

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 commands

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/spf13/cobra"
	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/config"
	"github.com/wallix/awless/console"
	"github.com/wallix/awless/graph"
	"github.com/wallix/awless/logger"
	"github.com/wallix/awless/sync"
)

var (
	listAllSiblingsFlag          bool
	showPropertiesValuesOnlyFlag []string
)

func init() {
	RootCmd.AddCommand(showCmd)
	showCmd.Flags().BoolVar(&listAllSiblingsFlag, "siblings", false, "List all the resource's siblings")
	showCmd.Flags().StringSliceVar(&showPropertiesValuesOnlyFlag, "values-for", []string{}, "Output values only for given properties keys")
}

var showCmd = &cobra.Command{
	Use:   "show REFERENCE",
	Short: "Show a resource and its interrelations given a REFERENCE: id or name",
	Example: `  awless show i-8d43b21b            # show an instance via its ref
  awless show AIDAJ3Z24GOKHTZO4OIX6 # show a user via its ref
  awless show jsmith                # show a user via its ref,
  awless show @jsmith               # forcing search by name`,
	PersistentPreRun:  applyHooks(initLoggerHook, initAwlessEnvHook, initCloudServicesHook, initSyncerHook, firstInstallDoneHook),
	PersistentPostRun: applyHooks(verifyNewVersionHook, onVersionUpgrade),

	RunE: func(cmd *cobra.Command, args []string) error {
		if len(args) < 1 {
			return errors.New("REFERENCE required. See examples.")
		}

		ref := args[0]
		notFound := fmt.Sprintf("resource with reference %s not found", deprefix(ref))

		var resource *graph.Resource
		var gph *graph.Graph

		resource, gph = findResourceInLocalGraphs(ref)

		if resource == nil && localGlobalFlag {
			logger.Info(notFound)
			return nil
		} else if resource == nil {
			runFullSync()

			if resource, gph = findResourceInLocalGraphs(ref); resource == nil {
				logger.Info(notFound)
				return nil
			}
		}

		if !localGlobalFlag && config.GetAutosync() {
			srv, err := cloud.GetServiceForType(resource.Type())
			exitOn(err)
			logger.Verbosef("syncing service for %s type", resource.Type())
			if _, err = sync.DefaultSyncer.Sync(srv); err != nil {
				logger.Verbose(err)
			}
			resource, gph = findResourceInLocalGraphs(ref)
		}

		if resource != nil {
			if len(showPropertiesValuesOnlyFlag) > 0 {
				showResourceValuesOnlyFor(resource, showPropertiesValuesOnlyFlag)
				return nil
			}
			showResource(resource, gph)
		}

		return nil
	},
}

func showResourceValuesOnlyFor(resource *graph.Resource, propKeys []string) {
	var normalized []string
	for _, p := range propKeys {
		normalized = append(normalized, strings.ToLower(strings.Replace(p, " ", "", -1)))
	}

	valuesForKeys := map[string]string{}
	isIncluded := func(s string) (bool, string) {
		for _, n := range normalized {
			if n == strings.ToLower(s) {
				return true, n
			}
		}
		return false, ""
	}
	for k, v := range resource.Properties {
		if ok, p := isIncluded(k); ok {
			valuesForKeys[p] = fmt.Sprint(v)
		}
	}

	var values []string
	for _, n := range normalized {
		if v, ok := valuesForKeys[n]; ok {
			values = append(values, v)
		}
	}

	fmt.Println(strings.Join(values, ","))
}

func showResource(resource *graph.Resource, gph *graph.Graph) {
	displayer, err := console.BuildOptions(
		console.WithHeaders(console.DefaultsColumnDefinitions[resource.Type()]),
		console.WithFormat(listingFormat),
		console.WithMaxWidth(console.GetTerminalWidth()),
	).SetSource(resource).Build()
	exitOn(err)

	exitOn(displayer.Print(os.Stdout))

	var parents []*graph.Resource
	err = gph.Accept(&graph.ParentsVisitor{From: resource, Each: graph.VisitorCollectFunc(&parents)})
	exitOn(err)

	var parentsW bytes.Buffer
	var count int
	for i := len(parents) - 1; i >= 0; i-- {
		if count == 0 {
			fmt.Fprintf(&parentsW, "%s\n", parents[i])
		} else {
			fmt.Fprintf(&parentsW, "%s↳ %s\n", strings.Repeat("\t", count), parents[i])
		}
		count++
	}

	var childrenW bytes.Buffer
	var hasChildren bool
	printWithTabs := func(r *graph.Resource, distance int) error {
		var tabs bytes.Buffer
		tabs.WriteString(strings.Repeat("\t", count))
		for i := 0; i < distance; i++ {
			tabs.WriteByte('\t')
		}

		display := r.String()
		if r.Same(resource) {
			display = renderGreenFn(resource.String())
		} else {
			hasChildren = true
		}
		fmt.Fprintf(&childrenW, "%s↳ %s\n", tabs.String(), display)
		return nil
	}
	err = gph.Accept(&graph.ChildrenVisitor{From: resource, Each: printWithTabs, IncludeFrom: true})
	exitOn(err)

	if len(parents) > 0 || hasChildren {
		fmt.Println(renderCyanBoldFn("\n# Relations:"))
		fmt.Printf(parentsW.String())
		fmt.Printf(childrenW.String())
	}

	appliedOn, err := gph.ListResourcesAppliedOn(resource)
	exitOn(err)
	printResourceList(renderCyanBoldFn("Applied on"), appliedOn)

	dependingOn, err := gph.ListResourcesDependingOn(resource)
	exitOn(err)
	printResourceList(renderCyanBoldFn("Depending on"), dependingOn)

	var siblings []*graph.Resource
	err = gph.Accept(&graph.SiblingsVisitor{From: resource, Each: graph.VisitorCollectFunc(&siblings)})
	exitOn(err)
	printResourceList(renderCyanBoldFn("Siblings"), siblings, "display all with flag --siblings")
}

func runFullSync() {
	if !config.GetAutosync() {
		logger.Info("autosync disabled")
		return
	}

	logger.Info("cannot resolve resource - running full sync")

	var services []cloud.Service
	for _, srv := range cloud.ServiceRegistry {
		services = append(services, srv)
	}

	if _, err := sync.DefaultSyncer.Sync(services...); err != nil {
		logger.Verbose(err)
	}
}

func findResourceInLocalGraphs(ref string) (*graph.Resource, *graph.Graph) {
	g, resources := resolveResourceFromRef(ref)
	switch len(resources) {
	case 0:
		return nil, nil
	case 1:
		return resources[0], g
	default:
		logger.Infof("%d resources found with name '%s'. Show a specific resource with:", len(resources), deprefix(ref))
		for _, res := range resources {
			var buf bytes.Buffer
			buf.WriteString(fmt.Sprintf("\t`awless show %s` to show the %s", res.Id(), res.Type()))
			if state, ok := res.Properties["State"].(string); ok {
				buf.WriteString(fmt.Sprintf(" (state: '%s')", state))
			}
			logger.Infof(buf.String())
		}

		os.Exit(0)
	}

	return nil, nil
}

func resolveResourceFromRef(ref string) (*graph.Graph, []*graph.Resource) {
	g, err := sync.LoadAllLocalGraphs()
	exitOn(err)

	name := deprefix(ref)
	byName := &graph.ByProperty{Key: "Name", Value: name}

	if strings.HasPrefix(ref, "@") {
		logger.Verbosef("prefixed with @: forcing research by name '%s'", name)
		rs, err := g.ResolveResources(byName)
		exitOn(err)
		return g, rs
	} else {
		rs, err := g.ResolveResources(&graph.ById{Id: name})
		exitOn(err)

		if len(rs) > 0 {
			return g, rs
		} else {
			rs, err := g.ResolveResources(
				byName,
				&graph.ByProperty{Key: "Arn", Value: name},
			)
			exitOn(err)

			return g, rs
		}
	}
}

func deprefix(s string) string {
	return strings.TrimPrefix(s, "@")
}

func printResourceList(title string, list []*graph.Resource, shortenListMsg ...string) {
	sort.Sort(byTypeAndString{list})
	all := graph.Resources(list).Map(func(r *graph.Resource) string { return r.String() })
	count := len(all)
	max := 3
	if count > 0 {
		if !listAllSiblingsFlag && len(shortenListMsg) > 0 && count > max {
			fmt.Printf("\n%s: %s, ... (%s)\n", title, strings.Join(all[0:max], ", "), shortenListMsg[0])
		} else {
			fmt.Printf("\n%s: %s\n", title, strings.Join(all, ", "))
		}
	}
}

type byTypeAndString struct {
	res []*graph.Resource
}

func (b byTypeAndString) Len() int { return len(b.res) }
func (b byTypeAndString) Swap(i, j int) {
	b.res[i], b.res[j] = b.res[j], b.res[i]
}
func (b byTypeAndString) Less(i, j int) bool {
	if b.res[i].Type() != b.res[j].Type() {
		return b.res[i].Type() < b.res[j].Type()
	}
	return b.res[i].String() <= b.res[j].String()
}
<commit_msg>Show: making logging more explicit<commit_after>/*
Copyright 2017 WALLIX

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 commands

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/spf13/cobra"
	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/config"
	"github.com/wallix/awless/console"
	"github.com/wallix/awless/graph"
	"github.com/wallix/awless/logger"
	"github.com/wallix/awless/sync"
)

var (
	listAllSiblingsFlag          bool
	showPropertiesValuesOnlyFlag []string
)

func init() {
	RootCmd.AddCommand(showCmd)
	showCmd.Flags().BoolVar(&listAllSiblingsFlag, "siblings", false, "List all the resource's siblings")
	showCmd.Flags().StringSliceVar(&showPropertiesValuesOnlyFlag, "values-for", []string{}, "Output values only for given properties keys")
}

var showCmd = &cobra.Command{
	Use:   "show REFERENCE",
	Short: "Show a resource and its interrelations given a REFERENCE: id or name",
	Example: `  awless show i-8d43b21b            # show an instance via its ref
  awless show AIDAJ3Z24GOKHTZO4OIX6 # show a user via its ref
  awless show jsmith                # show a user via its ref,
  awless show @jsmith               # forcing search by name`,
	PersistentPreRun:  applyHooks(initLoggerHook, initAwlessEnvHook, initCloudServicesHook, initSyncerHook, firstInstallDoneHook),
	PersistentPostRun: applyHooks(verifyNewVersionHook, onVersionUpgrade),

	RunE: func(cmd *cobra.Command, args []string) error {
		if len(args) < 1 {
			return errors.New("REFERENCE required. See examples.")
		}

		ref := args[0]
		notFound := fmt.Sprintf("resource with reference '%s' not found", deprefix(ref))

		var resource *graph.Resource
		var gph *graph.Graph

		resource, gph = findResourceInLocalGraphs(ref)

		if resource == nil && localGlobalFlag {
			logger.Info(notFound)
			return nil
		} else if resource == nil {
			runFullSync()

			if resource, gph = findResourceInLocalGraphs(ref); resource == nil {
				logger.Info(notFound)
				return nil
			}
		}

		if !localGlobalFlag && config.GetAutosync() {
			srv, err := cloud.GetServiceForType(resource.Type())
			exitOn(err)
			logger.Verbosef("syncing service for %s type", resource.Type())
			if _, err = sync.DefaultSyncer.Sync(srv); err != nil {
				logger.Verbose(err)
			}
			resource, gph = findResourceInLocalGraphs(ref)
		}

		if resource != nil {
			if len(showPropertiesValuesOnlyFlag) > 0 {
				showResourceValuesOnlyFor(resource, showPropertiesValuesOnlyFlag)
				return nil
			}
			showResource(resource, gph)
		}

		return nil
	},
}

func showResourceValuesOnlyFor(resource *graph.Resource, propKeys []string) {
	var normalized []string
	for _, p := range propKeys {
		normalized = append(normalized, strings.ToLower(strings.Replace(p, " ", "", -1)))
	}

	valuesForKeys := map[string]string{}
	isIncluded := func(s string) (bool, string) {
		for _, n := range normalized {
			if n == strings.ToLower(s) {
				return true, n
			}
		}
		return false, ""
	}
	for k, v := range resource.Properties {
		if ok, p := isIncluded(k); ok {
			valuesForKeys[p] = fmt.Sprint(v)
		}
	}

	var values []string
	for _, n := range normalized {
		if v, ok := valuesForKeys[n]; ok {
			values = append(values, v)
		}
	}

	fmt.Println(strings.Join(values, ","))
}

func showResource(resource *graph.Resource, gph *graph.Graph) {
	displayer, err := console.BuildOptions(
		console.WithHeaders(console.DefaultsColumnDefinitions[resource.Type()]),
		console.WithFormat(listingFormat),
		console.WithMaxWidth(console.GetTerminalWidth()),
	).SetSource(resource).Build()
	exitOn(err)

	exitOn(displayer.Print(os.Stdout))

	var parents []*graph.Resource
	err = gph.Accept(&graph.ParentsVisitor{From: resource, Each: graph.VisitorCollectFunc(&parents)})
	exitOn(err)

	var parentsW bytes.Buffer
	var count int
	for i := len(parents) - 1; i >= 0; i-- {
		if count == 0 {
			fmt.Fprintf(&parentsW, "%s\n", parents[i])
		} else {
			fmt.Fprintf(&parentsW, "%s↳ %s\n", strings.Repeat("\t", count), parents[i])
		}
		count++
	}

	var childrenW bytes.Buffer
	var hasChildren bool
	printWithTabs := func(r *graph.Resource, distance int) error {
		var tabs bytes.Buffer
		tabs.WriteString(strings.Repeat("\t", count))
		for i := 0; i < distance; i++ {
			tabs.WriteByte('\t')
		}

		display := r.String()
		if r.Same(resource) {
			display = renderGreenFn(resource.String())
		} else {
			hasChildren = true
		}
		fmt.Fprintf(&childrenW, "%s↳ %s\n", tabs.String(), display)
		return nil
	}
	err = gph.Accept(&graph.ChildrenVisitor{From: resource, Each: printWithTabs, IncludeFrom: true})
	exitOn(err)

	if len(parents) > 0 || hasChildren {
		fmt.Println(renderCyanBoldFn("\n# Relations:"))
		fmt.Printf(parentsW.String())
		fmt.Printf(childrenW.String())
	}

	appliedOn, err := gph.ListResourcesAppliedOn(resource)
	exitOn(err)
	printResourceList(renderCyanBoldFn("Applied on"), appliedOn)

	dependingOn, err := gph.ListResourcesDependingOn(resource)
	exitOn(err)
	printResourceList(renderCyanBoldFn("Depending on"), dependingOn)

	var siblings []*graph.Resource
	err = gph.Accept(&graph.SiblingsVisitor{From: resource, Each: graph.VisitorCollectFunc(&siblings)})
	exitOn(err)
	printResourceList(renderCyanBoldFn("Siblings"), siblings, "display all with flag --siblings")
}

func runFullSync() {
	if !config.GetAutosync() {
		logger.Info("autosync disabled")
		return
	}

	logger.Infof("cannot find resource in existing data synced locally")
	logger.Infof("running sync for current region '%s'", config.GetAWSRegion())

	var services []cloud.Service
	for _, srv := range cloud.ServiceRegistry {
		services = append(services, srv)
	}

	if _, err := sync.DefaultSyncer.Sync(services...); err != nil {
		logger.Verbose(err)
	}
}

func findResourceInLocalGraphs(ref string) (*graph.Resource, *graph.Graph) {
	g, resources := resolveResourceFromRef(ref)
	switch len(resources) {
	case 0:
		return nil, nil
	case 1:
		return resources[0], g
	default:
		logger.Infof("%d resources found with name '%s'. Show a specific resource with:", len(resources), deprefix(ref))
		for _, res := range resources {
			var buf bytes.Buffer
			buf.WriteString(fmt.Sprintf("\t`awless show %s` to show the %s", res.Id(), res.Type()))
			if state, ok := res.Properties["State"].(string); ok {
				buf.WriteString(fmt.Sprintf(" (state: '%s')", state))
			}
			logger.Infof(buf.String())
		}

		os.Exit(0)
	}

	return nil, nil
}

func resolveResourceFromRef(ref string) (*graph.Graph, []*graph.Resource) {
	g, err := sync.LoadAllLocalGraphs()
	exitOn(err)

	name := deprefix(ref)
	byName := &graph.ByProperty{Key: "Name", Value: name}

	if strings.HasPrefix(ref, "@") {
		logger.Verbosef("prefixed with @: forcing research by name '%s'", name)
		rs, err := g.ResolveResources(byName)
		exitOn(err)
		return g, rs
	} else {
		rs, err := g.ResolveResources(&graph.ById{Id: name})
		exitOn(err)

		if len(rs) > 0 {
			return g, rs
		} else {
			rs, err := g.ResolveResources(
				byName,
				&graph.ByProperty{Key: "Arn", Value: name},
			)
			exitOn(err)

			return g, rs
		}
	}
}

func deprefix(s string) string {
	return strings.TrimPrefix(s, "@")
}

func printResourceList(title string, list []*graph.Resource, shortenListMsg ...string) {
	sort.Sort(byTypeAndString{list})
	all := graph.Resources(list).Map(func(r *graph.Resource) string { return r.String() })
	count := len(all)
	max := 3
	if count > 0 {
		if !listAllSiblingsFlag && len(shortenListMsg) > 0 && count > max {
			fmt.Printf("\n%s: %s, ... (%s)\n", title, strings.Join(all[0:max], ", "), shortenListMsg[0])
		} else {
			fmt.Printf("\n%s: %s\n", title, strings.Join(all, ", "))
		}
	}
}

type byTypeAndString struct {
	res []*graph.Resource
}

func (b byTypeAndString) Len() int { return len(b.res) }
func (b byTypeAndString) Swap(i, j int) {
	b.res[i], b.res[j] = b.res[j], b.res[i]
}
func (b byTypeAndString) Less(i, j int) bool {
	if b.res[i].Type() != b.res[j].Type() {
		return b.res[i].Type() < b.res[j].Type()
	}
	return b.res[i].String() <= b.res[j].String()
}
<|endoftext|>


<commit_before>package driver

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/config"
        "github.com/hashicorp/nomad/client/getter"
	"github.com/hashicorp/nomad/nomad/structs"
)

var (
	reQemuVersion = regexp.MustCompile(`version (\d[\.\d+]+)`)
)

// QemuDriver is a driver for running images via Qemu
// We attempt to chose sane defaults for now, with more configuration available
// planned in the future
type QemuDriver struct {
	DriverContext
}

// qemuHandle is returned from Start/Open as a handle to the PID
type qemuHandle struct {
	proc   *os.Process
	vmID   string
	waitCh chan error
	doneCh chan struct{}
}

// qemuPID is a struct to map the pid running the process to the vm image on
// disk
type qemuPID struct {
	Pid  int
	VmID string
}

// NewQemuDriver is used to create a new exec driver
func NewQemuDriver(ctx *DriverContext) Driver {
	return &QemuDriver{*ctx}
}

func (d *QemuDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
	bin := "qemu-system-x86_64"
	if runtime.GOOS == "windows" {
		// On windows, the "qemu-system-x86_64" command does not respond to the
		// version flag.
		bin = "qemu-img"
	}
	outBytes, err := exec.Command(bin, "--version").Output()
	if err != nil {
		return false, nil
	}
	out := strings.TrimSpace(string(outBytes))

	matches := reQemuVersion.FindStringSubmatch(out)
	if len(matches) != 2 {
		return false, fmt.Errorf("Unable to parse Qemu version string: %#v", matches)
	}

	node.Attributes["driver.qemu"] = "1"
	node.Attributes["driver.qemu.version"] = matches[1]

	return true, nil
}

// Run an existing Qemu image. Start() will pull down an existing, valid Qemu
// image and save it to the Drivers Allocation Dir
func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
	// Get the image source
        source, ok := task.Config["artifact_source"]
	if !ok || source == "" {
		return nil, fmt.Errorf("Missing source image Qemu driver")
	}

	// Qemu defaults to 128M of RAM for a given VM. Instead, we force users to
	// supply a memory size in the tasks resources
	if task.Resources == nil || task.Resources.MemoryMB == 0 {
		return nil, fmt.Errorf("Missing required Task Resource: Memory")
	}

	// Get the tasks local directory.
	taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName]
	if !ok {
		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
	}

        // Proceed to download an artifact to be executed.
        vmPath, err := getter.GetArtifact(
                filepath.Join(taskDir, allocdir.TaskLocal),
                task.Config["artifact_source"],
                task.Config["checksum"],
                d.logger,
        )
        if err != nil {
                d.logger.Printf("\n---\nDEBUG\n---\n error here: %s\n---\n", err)
                return nil, err
	}

        vmID := filepath.Base(vmPath)

	// Parse configuration arguments
	// Create the base arguments
	accelerator := "tcg"
	if acc, ok := task.Config["accelerator"]; ok {
		accelerator = acc
	}
	// TODO: Check a lower bounds, e.g. the default 128 of Qemu
	mem := fmt.Sprintf("%dM", task.Resources.MemoryMB)

	args := []string{
		"qemu-system-x86_64",
		"-machine", "type=pc,accel=" + accelerator,
		"-name", vmID,
		"-m", mem,
		"-drive", "file=" + vmPath,
		"-nodefconfig",
		"-nodefaults",
		"-nographic",
	}

	// Check the Resources required Networks to add port mappings. If no resources
	// are required, we assume the VM is a purely compute job and does not require
	// the outside world to be able to reach it. VMs ran without port mappings can
	// still reach out to the world, but without port mappings it is effectively
	// firewalled
	if len(task.Resources.Networks) > 0 {
		// TODO: Consolidate these into map of host/guest port when we have HCL
		// Note: Host port must be open and available
		// Get and split guest ports. The guest_ports configuration must match up with
		// the Reserved ports in the Task Resources
		// Users can supply guest_hosts as a list of posts to map on the guest vm.
		// These map 1:1 with the requested Reserved Ports from the hostmachine.
		ports := strings.Split(task.Config["guest_ports"], ",")
		if len(ports) == 0 {
			return nil, fmt.Errorf("[ERR] driver.qemu: Error parsing required Guest Ports")
		}

		// TODO: support more than a single, default Network
		if len(ports) != len(task.Resources.Networks[0].ReservedPorts) {
			return nil, fmt.Errorf("[ERR] driver.qemu: Error matching Guest Ports with Reserved ports")
		}

		// Loop through the reserved ports and construct the hostfwd string, to map
		// reserved ports to the ports listenting in the VM
		// Ex:
		//    hostfwd=tcp::22000-:22,hostfwd=tcp::80-:8080
		reservedPorts := task.Resources.Networks[0].ReservedPorts
		var forwarding string
		for i, p := range ports {
			forwarding = fmt.Sprintf("%s,hostfwd=tcp::%s-:%s", forwarding, strconv.Itoa(reservedPorts[i]), p)
		}

		if "" == forwarding {
			return nil, fmt.Errorf("[ERR] driver.qemu:  Error constructing port forwarding")
		}

		args = append(args,
			"-netdev",
			fmt.Sprintf("user,id=user.0%s", forwarding),
			"-device", "virtio-net,netdev=user.0",
		)
	}

	// If using KVM, add optimization args
	if accelerator == "kvm" {
		args = append(args,
			"-enable-kvm",
			"-cpu", "host",
			// Do we have cores information available to the Driver?
			// "-smp", fmt.Sprintf("%d", cores),
		)
	}

	// Start Qemu
	var outBuf, errBuf bytes.Buffer
	cmd := exec.Command(args[0], args[1:]...)
	cmd.Stdout = &outBuf
	cmd.Stderr = &errBuf

	d.logger.Printf("[DEBUG] Starting QemuVM command: %q", strings.Join(args, " "))
	if err := cmd.Start(); err != nil {
		return nil, fmt.Errorf(
			"Error running QEMU: %s\n\nOutput: %s\n\nError: %s",
			err, outBuf.String(), errBuf.String())
	}

	d.logger.Printf("[INFO] Started new QemuVM: %s", vmID)

	// Create and Return Handle
	h := &qemuHandle{
		proc:   cmd.Process,
		vmID:   vmPath,
		doneCh: make(chan struct{}),
		waitCh: make(chan error, 1),
	}

	go h.run()
	return h, nil
}

func (d *QemuDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
	// Parse the handle
	pidBytes := []byte(strings.TrimPrefix(handleID, "QEMU:"))
	qpid := &qemuPID{}
	if err := json.Unmarshal(pidBytes, qpid); err != nil {
		return nil, fmt.Errorf("failed to parse Qemu handle '%s': %v", handleID, err)
	}

	// Find the process
	proc, err := os.FindProcess(qpid.Pid)
	if proc == nil || err != nil {
		return nil, fmt.Errorf("failed to find Qemu PID %d: %v", qpid.Pid, err)
	}

	// Return a driver handle
	h := &qemuHandle{
		proc:   proc,
		vmID:   qpid.VmID,
		doneCh: make(chan struct{}),
		waitCh: make(chan error, 1),
	}

	go h.run()
	return h, nil
}

func (h *qemuHandle) ID() string {
	// Return a handle to the PID
	pid := &qemuPID{
		Pid:  h.proc.Pid,
		VmID: h.vmID,
	}
	data, err := json.Marshal(pid)
	if err != nil {
		log.Printf("[ERR] failed to marshal Qemu PID to JSON: %s", err)
	}
	return fmt.Sprintf("QEMU:%s", string(data))
}

func (h *qemuHandle) WaitCh() chan error {
	return h.waitCh
}

func (h *qemuHandle) Update(task *structs.Task) error {
	// Update is not possible
	return nil
}

// Kill is used to terminate the task. We send an Interrupt
// and then provide a 5 second grace period before doing a Kill.
//
// TODO: allow a 'shutdown_command' that can be executed over a ssh connection
// to the VM
func (h *qemuHandle) Kill() error {
	h.proc.Signal(os.Interrupt)
	select {
	case <-h.doneCh:
		return nil
	case <-time.After(5 * time.Second):
		return h.proc.Kill()
	}
}

func (h *qemuHandle) run() {
	ps, err := h.proc.Wait()
	close(h.doneCh)
	if err != nil {
		h.waitCh <- err
	} else if !ps.Success() {
		h.waitCh <- fmt.Errorf("task exited with error")
	}
	close(h.waitCh)
}
<commit_msg>remove debug line<commit_after>package driver

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/config"
        "github.com/hashicorp/nomad/client/getter"
	"github.com/hashicorp/nomad/nomad/structs"
)

var (
	reQemuVersion = regexp.MustCompile(`version (\d[\.\d+]+)`)
)

// QemuDriver is a driver for running images via Qemu
// We attempt to chose sane defaults for now, with more configuration available
// planned in the future
type QemuDriver struct {
	DriverContext
}

// qemuHandle is returned from Start/Open as a handle to the PID
type qemuHandle struct {
	proc   *os.Process
	vmID   string
	waitCh chan error
	doneCh chan struct{}
}

// qemuPID is a struct to map the pid running the process to the vm image on
// disk
type qemuPID struct {
	Pid  int
	VmID string
}

// NewQemuDriver is used to create a new exec driver
func NewQemuDriver(ctx *DriverContext) Driver {
	return &QemuDriver{*ctx}
}

func (d *QemuDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
	bin := "qemu-system-x86_64"
	if runtime.GOOS == "windows" {
		// On windows, the "qemu-system-x86_64" command does not respond to the
		// version flag.
		bin = "qemu-img"
	}
	outBytes, err := exec.Command(bin, "--version").Output()
	if err != nil {
		return false, nil
	}
	out := strings.TrimSpace(string(outBytes))

	matches := reQemuVersion.FindStringSubmatch(out)
	if len(matches) != 2 {
		return false, fmt.Errorf("Unable to parse Qemu version string: %#v", matches)
	}

	node.Attributes["driver.qemu"] = "1"
	node.Attributes["driver.qemu.version"] = matches[1]

	return true, nil
}

// Run an existing Qemu image. Start() will pull down an existing, valid Qemu
// image and save it to the Drivers Allocation Dir
func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
	// Get the image source
        source, ok := task.Config["artifact_source"]
	if !ok || source == "" {
		return nil, fmt.Errorf("Missing source image Qemu driver")
	}

	// Qemu defaults to 128M of RAM for a given VM. Instead, we force users to
	// supply a memory size in the tasks resources
	if task.Resources == nil || task.Resources.MemoryMB == 0 {
		return nil, fmt.Errorf("Missing required Task Resource: Memory")
	}

	// Get the tasks local directory.
	taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName]
	if !ok {
		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
	}

        // Proceed to download an artifact to be executed.
        vmPath, err := getter.GetArtifact(
                filepath.Join(taskDir, allocdir.TaskLocal),
                task.Config["artifact_source"],
                task.Config["checksum"],
                d.logger,
        )
        if err != nil {
                return nil, err
        }

        vmID := filepath.Base(vmPath)

	// Parse configuration arguments
	// Create the base arguments
	accelerator := "tcg"
	if acc, ok := task.Config["accelerator"]; ok {
		accelerator = acc
	}
	// TODO: Check a lower bounds, e.g. the default 128 of Qemu
	mem := fmt.Sprintf("%dM", task.Resources.MemoryMB)

	args := []string{
		"qemu-system-x86_64",
		"-machine", "type=pc,accel=" + accelerator,
		"-name", vmID,
		"-m", mem,
		"-drive", "file=" + vmPath,
		"-nodefconfig",
		"-nodefaults",
		"-nographic",
	}

	// Check the Resources required Networks to add port mappings. If no resources
	// are required, we assume the VM is a purely compute job and does not require
	// the outside world to be able to reach it. VMs ran without port mappings can
	// still reach out to the world, but without port mappings it is effectively
	// firewalled
	if len(task.Resources.Networks) > 0 {
		// TODO: Consolidate these into map of host/guest port when we have HCL
		// Note: Host port must be open and available
		// Get and split guest ports. The guest_ports configuration must match up with
		// the Reserved ports in the Task Resources
		// Users can supply guest_hosts as a list of posts to map on the guest vm.
		// These map 1:1 with the requested Reserved Ports from the hostmachine.
		ports := strings.Split(task.Config["guest_ports"], ",")
		if len(ports) == 0 {
			return nil, fmt.Errorf("[ERR] driver.qemu: Error parsing required Guest Ports")
		}

		// TODO: support more than a single, default Network
		if len(ports) != len(task.Resources.Networks[0].ReservedPorts) {
			return nil, fmt.Errorf("[ERR] driver.qemu: Error matching Guest Ports with Reserved ports")
		}

		// Loop through the reserved ports and construct the hostfwd string, to map
		// reserved ports to the ports listenting in the VM
		// Ex:
		//    hostfwd=tcp::22000-:22,hostfwd=tcp::80-:8080
		reservedPorts := task.Resources.Networks[0].ReservedPorts
		var forwarding string
		for i, p := range ports {
			forwarding = fmt.Sprintf("%s,hostfwd=tcp::%s-:%s", forwarding, strconv.Itoa(reservedPorts[i]), p)
		}

		if "" == forwarding {
			return nil, fmt.Errorf("[ERR] driver.qemu:  Error constructing port forwarding")
		}

		args = append(args,
			"-netdev",
			fmt.Sprintf("user,id=user.0%s", forwarding),
			"-device", "virtio-net,netdev=user.0",
		)
	}

	// If using KVM, add optimization args
	if accelerator == "kvm" {
		args = append(args,
			"-enable-kvm",
			"-cpu", "host",
			// Do we have cores information available to the Driver?
			// "-smp", fmt.Sprintf("%d", cores),
		)
	}

	// Start Qemu
	var outBuf, errBuf bytes.Buffer
	cmd := exec.Command(args[0], args[1:]...)
	cmd.Stdout = &outBuf
	cmd.Stderr = &errBuf

	d.logger.Printf("[DEBUG] Starting QemuVM command: %q", strings.Join(args, " "))
	if err := cmd.Start(); err != nil {
		return nil, fmt.Errorf(
			"Error running QEMU: %s\n\nOutput: %s\n\nError: %s",
			err, outBuf.String(), errBuf.String())
	}

	d.logger.Printf("[INFO] Started new QemuVM: %s", vmID)

	// Create and Return Handle
	h := &qemuHandle{
		proc:   cmd.Process,
		vmID:   vmPath,
		doneCh: make(chan struct{}),
		waitCh: make(chan error, 1),
	}

	go h.run()
	return h, nil
}

func (d *QemuDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
	// Parse the handle
	pidBytes := []byte(strings.TrimPrefix(handleID, "QEMU:"))
	qpid := &qemuPID{}
	if err := json.Unmarshal(pidBytes, qpid); err != nil {
		return nil, fmt.Errorf("failed to parse Qemu handle '%s': %v", handleID, err)
	}

	// Find the process
	proc, err := os.FindProcess(qpid.Pid)
	if proc == nil || err != nil {
		return nil, fmt.Errorf("failed to find Qemu PID %d: %v", qpid.Pid, err)
	}

	// Return a driver handle
	h := &qemuHandle{
		proc:   proc,
		vmID:   qpid.VmID,
		doneCh: make(chan struct{}),
		waitCh: make(chan error, 1),
	}

	go h.run()
	return h, nil
}

func (h *qemuHandle) ID() string {
	// Return a handle to the PID
	pid := &qemuPID{
		Pid:  h.proc.Pid,
		VmID: h.vmID,
	}
	data, err := json.Marshal(pid)
	if err != nil {
		log.Printf("[ERR] failed to marshal Qemu PID to JSON: %s", err)
	}
	return fmt.Sprintf("QEMU:%s", string(data))
}

func (h *qemuHandle) WaitCh() chan error {
	return h.waitCh
}

func (h *qemuHandle) Update(task *structs.Task) error {
	// Update is not possible
	return nil
}

// Kill is used to terminate the task. We send an Interrupt
// and then provide a 5 second grace period before doing a Kill.
//
// TODO: allow a 'shutdown_command' that can be executed over a ssh connection
// to the VM
func (h *qemuHandle) Kill() error {
	h.proc.Signal(os.Interrupt)
	select {
	case <-h.doneCh:
		return nil
	case <-time.After(5 * time.Second):
		return h.proc.Kill()
	}
}

func (h *qemuHandle) run() {
	ps, err := h.proc.Wait()
	close(h.doneCh)
	if err != nil {
		h.waitCh <- err
	} else if !ps.Success() {
		h.waitCh <- fmt.Errorf("task exited with error")
	}
	close(h.waitCh)
}
<|endoftext|>


<commit_before>package ams

import (
	"context"
	"encoding/xml"
	"fmt"
	"path"

	"github.com/pkg/errors"
)

const (
	jobsEndpoint   = "Jobs"
	jobInputAsset  = "JobInputAsset(0)"
	jobOutputAsset = "JobOutputAsset(0)"
)

const (
	JobQueued = iota
	JobScheduled
	JobProcessing
	JobFinished
	JobError
	JobCanceled
	JobCanceling
)

type MetaData struct {
	URI string `json:"uri"`
}

type MediaAsset struct {
	MetaData MetaData `json:"__metadata"`
}

func NewMediaAsset(uri string) MediaAsset {
	return MediaAsset{
		MetaData: MetaData{
			URI: uri,
		},
	}
}

type Job struct {
	ID              string  `json:"Id"`
	Name            string  `json:"Name"`
	StartTime       string  `json:"StartTime"`
	EndTime         string  `json:"EndTime"`
	LastModified    string  `json:"LastModified"`
	Priority        int     `json:"Priority"`
	RunningDuration float64 `json:"RunningDuration"`
	State           int     `json:"State"`
}

func (c *Client) addJob(ctx context.Context, assetID, mediaProcessorID, configuration string, taskBody TaskBody) (*Job, error) {
	jobName := fmt.Sprintf("Job - Asset %s", assetID)
	assetURI := c.buildAssetURI(assetID)
	taskName := fmt.Sprintf("Task - Asset %s", assetID)
	taskBodyXML, err := xml.Marshal(taskBody)
	if err != nil {
		return nil, errors.Wrap(err, "taskBody xml marshal failed")
	}

	params := map[string]interface{}{
		"Name": jobName,
		"InputMediaAssets": []MediaAsset{
			NewMediaAsset(assetURI),
		},
		"Tasks": []Task{
			{
				Name:             taskName,
				Configuration:    configuration,
				MediaProcessorID: mediaProcessorID,
				TaskBody:         string(taskBodyXML),
			},
		},
	}
	var out struct {
		Data Job `json:"d"`
	}
	if err := c.post(ctx, jobsEndpoint, params, &out, withOData(true)); err != nil {
		return nil, err
	}
	return &out.Data, nil
}

func (c *Client) AddEncodeJob(ctx context.Context, assetID, mediaProcessorID, outputAssetName string) (*Job, error) {
	configuration := "Adaptive Streaming"

	c.logger.Printf("[INFO] post encode asset[#%s] job ...", assetID)

	taskBody := TaskBody{
		InputAsset: AssetTag{Asset: jobInputAsset},
		OutputAsset: AssetTag{
			Asset: jobOutputAsset,
			Name:  outputAssetName,
		},
	}
	job, err := c.addJob(ctx, assetID, mediaProcessorID, configuration, taskBody)
	if err != nil {
		return nil, err
	}
	c.logger.Printf("[INFO] completed")
	return job, nil
}

func (c *Client) AddThumbnailJob(ctx context.Context, assetID, mediaProcessorID string) (*Job, error) {
	configuration := `{
  "Version": 1.0,
  "Codecs": [
    {
      "PngLayers": [
        {
          "Type": "PngLayer",
          "Width": "100%",
          "Height": "100%"
        }
      ],
      "Start": "{Best}",
      "Type": "PngImage"
    }
  ],
  "Outputs": [
    {
      "FileName": "{Basename}_{Index}{Extension}",
      "Format": {
        "Type": "PngFormat"
      }
    }
  ]
}`
	c.logger.Printf("[INFO] post thumbnail create [#%s] job ...", assetID)

	taskBody := newTaskBody()
	job, err := c.addJob(ctx, assetID, mediaProcessorID, configuration, *taskBody)
	if err != nil {
		return nil, err
	}
	c.logger.Printf("[INFO] completed")
	return job, nil
}

func (c *Client) GetOutputMediaAssets(ctx context.Context, jobID string) ([]Asset, error) {
	c.logger.Printf("[INFO] get job[#%s]'s output media assets ...", jobID)

	endpoint := path.Join(toJobResource(jobID), "OutputMediaAssets")
	var out struct {
		Assets []Asset `json:"value"`
	}
	if err := c.get(ctx, endpoint, &out); err != nil {
		return nil, err
	}

	c.logger.Printf("[INFO] completed")
	return out.Assets, nil
}

func (c *Client) GetJob(ctx context.Context, jobID string) (*Job, error) {
	c.logger.Printf("[INFO] get job #%s ...", jobID)

	endpoint := toJobResource(jobID)
	var out Job
	if err := c.get(ctx, endpoint, &out); err != nil {
		return nil, err
	}

	c.logger.Printf("[INFO] completed")
	return &out, nil
}

func toJobResource(jobID string) string {
	return toResource(jobsEndpoint, jobID)
}
<commit_msg>refactor: change addJob signature<commit_after>package ams

import (
	"context"
	"encoding/xml"
	"fmt"
	"path"

	"github.com/pkg/errors"
)

const (
	jobsEndpoint   = "Jobs"
	jobInputAsset  = "JobInputAsset(0)"
	jobOutputAsset = "JobOutputAsset(0)"
)

const (
	JobQueued = iota
	JobScheduled
	JobProcessing
	JobFinished
	JobError
	JobCanceled
	JobCanceling
)

type MetaData struct {
	URI string `json:"uri"`
}

type MediaAsset struct {
	MetaData MetaData `json:"__metadata"`
}

func NewMediaAsset(uri string) MediaAsset {
	return MediaAsset{
		MetaData: MetaData{
			URI: uri,
		},
	}
}

type Job struct {
	ID              string  `json:"Id"`
	Name            string  `json:"Name"`
	StartTime       string  `json:"StartTime"`
	EndTime         string  `json:"EndTime"`
	LastModified    string  `json:"LastModified"`
	Priority        int     `json:"Priority"`
	RunningDuration float64 `json:"RunningDuration"`
	State           int     `json:"State"`
}

func (c *Client) addJob(ctx context.Context, assetID, mediaProcessorID, configuration string, taskBody *TaskBody) (*Job, error) {
	jobName := fmt.Sprintf("Job - Asset %s", assetID)
	assetURI := c.buildAssetURI(assetID)
	taskName := fmt.Sprintf("Task - Asset %s", assetID)
	taskBodyXML, err := xml.Marshal(taskBody)
	if err != nil {
		return nil, errors.Wrap(err, "taskBody xml marshal failed")
	}

	params := map[string]interface{}{
		"Name": jobName,
		"InputMediaAssets": []MediaAsset{
			NewMediaAsset(assetURI),
		},
		"Tasks": []Task{
			{
				Name:             taskName,
				Configuration:    configuration,
				MediaProcessorID: mediaProcessorID,
				TaskBody:         string(taskBodyXML),
			},
		},
	}
	var out struct {
		Data Job `json:"d"`
	}
	if err := c.post(ctx, jobsEndpoint, params, &out, withOData(true)); err != nil {
		return nil, err
	}
	return &out.Data, nil
}

func (c *Client) AddEncodeJob(ctx context.Context, assetID, mediaProcessorID, outputAssetName string) (*Job, error) {
	configuration := "Adaptive Streaming"

	c.logger.Printf("[INFO] post encode asset[#%s] job ...", assetID)

	taskBody := newTaskBody()
	taskBody.OutputAsset.Name = outputAssetName
	job, err := c.addJob(ctx, assetID, mediaProcessorID, configuration, taskBody)
	if err != nil {
		return nil, err
	}
	c.logger.Printf("[INFO] completed")
	return job, nil
}

func (c *Client) AddThumbnailJob(ctx context.Context, assetID, mediaProcessorID string) (*Job, error) {
	configuration := `{
  "Version": 1.0,
  "Codecs": [
    {
      "PngLayers": [
        {
          "Type": "PngLayer",
          "Width": "100%",
          "Height": "100%"
        }
      ],
      "Start": "{Best}",
      "Type": "PngImage"
    }
  ],
  "Outputs": [
    {
      "FileName": "{Basename}_{Index}{Extension}",
      "Format": {
        "Type": "PngFormat"
      }
    }
  ]
}`
	c.logger.Printf("[INFO] post thumbnail create [#%s] job ...", assetID)

	taskBody := newTaskBody()
	job, err := c.addJob(ctx, assetID, mediaProcessorID, configuration, taskBody)
	if err != nil {
		return nil, err
	}
	c.logger.Printf("[INFO] completed")
	return job, nil
}

func (c *Client) GetOutputMediaAssets(ctx context.Context, jobID string) ([]Asset, error) {
	c.logger.Printf("[INFO] get job[#%s]'s output media assets ...", jobID)

	endpoint := path.Join(toJobResource(jobID), "OutputMediaAssets")
	var out struct {
		Assets []Asset `json:"value"`
	}
	if err := c.get(ctx, endpoint, &out); err != nil {
		return nil, err
	}

	c.logger.Printf("[INFO] completed")
	return out.Assets, nil
}

func (c *Client) GetJob(ctx context.Context, jobID string) (*Job, error) {
	c.logger.Printf("[INFO] get job #%s ...", jobID)

	endpoint := toJobResource(jobID)
	var out Job
	if err := c.get(ctx, endpoint, &out); err != nil {
		return nil, err
	}

	c.logger.Printf("[INFO] completed")
	return &out, nil
}

func toJobResource(jobID string) string {
	return toResource(jobsEndpoint, jobID)
}
<|endoftext|>


<commit_before>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package client

import (
	"bytes"
	"errors"
	"fmt"
	"net/url"

	"github.com/bogem/nehm/ui"
	"github.com/valyala/fasthttp"
)

const (
	apiURL   = "https://api.soundcloud.com"
	clientID = "11a37feb6ccc034d5975f3f803928a32"
)

var (
	ErrForbidden = errors.New("403 - Forbidden")
	ErrNotFound  = errors.New("404 - Not Found")

	uriBuffer = new(bytes.Buffer)
)

func addClientID(params *url.Values) {
	params.Set("client_id", clientID)
}

func resolve(params url.Values) ([]byte, error) {
	uri := formResolveURI(params)
	return get(uri)
}

func formResolveURI(params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/resolve?%v", apiURL, params.Encode())
	return uriBuffer.String()
}

func search(params url.Values) ([]byte, error) {
	uri := formSearchURI(params)
	return get(uri)
}

func formSearchURI(params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/tracks?%v", apiURL, params.Encode())
	return uriBuffer.String()
}

func getFavorites(uid string, params url.Values) ([]byte, error) {
	uri := formFavoritesURI(uid, params)
	return get(uri)
}

func formFavoritesURI(uid string, params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/users/%v/favorites?%v", apiURL, uid, params.Encode())
	return uriBuffer.String()
}

func get(uri string) ([]byte, error) {
	statusCode, body, err := makeGetRequest(uri)
	if err != nil {
		return nil, err
	}
	if err := handleStatusCode(statusCode); err != nil {
		return nil, err
	}
	return body, nil
}

var bodyBuffer []byte

func makeGetRequest(uri string) (int, []byte, error) {
	return fasthttp.Get(bodyBuffer, uri)
}

func handleStatusCode(statusCode int) error {
	switch {
	case statusCode == 403:
		return ErrForbidden
	case statusCode == 404:
		return ErrNotFound
	case statusCode >= 300 && statusCode < 500:
		return fmt.Errorf("invalid response from SoundCloud: %v", statusCode)
	case statusCode >= 500:
		ui.Term("There is a problem by SoundCloud. Please wait a while", nil)
	}
	return nil
}
<commit_msg>Remove bodyBuffer from http client<commit_after>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package client

import (
	"bytes"
	"errors"
	"fmt"
	"net/url"

	"github.com/bogem/nehm/ui"
	"github.com/valyala/fasthttp"
)

const (
	apiURL   = "https://api.soundcloud.com"
	clientID = "11a37feb6ccc034d5975f3f803928a32"
)

var (
	ErrForbidden = errors.New("403 - Forbidden")
	ErrNotFound  = errors.New("404 - Not Found")

	uriBuffer = new(bytes.Buffer)
)

func addClientID(params *url.Values) {
	params.Set("client_id", clientID)
}

func resolve(params url.Values) ([]byte, error) {
	uri := formResolveURI(params)
	return get(uri)
}

func formResolveURI(params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/resolve?%v", apiURL, params.Encode())
	return uriBuffer.String()
}

func search(params url.Values) ([]byte, error) {
	uri := formSearchURI(params)
	return get(uri)
}

func formSearchURI(params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/tracks?%v", apiURL, params.Encode())
	return uriBuffer.String()
}

func getFavorites(uid string, params url.Values) ([]byte, error) {
	uri := formFavoritesURI(uid, params)
	return get(uri)
}

func formFavoritesURI(uid string, params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/users/%v/favorites?%v", apiURL, uid, params.Encode())
	return uriBuffer.String()
}

func get(uri string) ([]byte, error) {
	statusCode, body, err := fasthttp.Get(nil, uri)
	if err != nil {
		return nil, err
	}
	if err := handleStatusCode(statusCode); err != nil {
		return nil, err
	}
	return body, nil
}

func handleStatusCode(statusCode int) error {
	switch {
	case statusCode == 403:
		return ErrForbidden
	case statusCode == 404:
		return ErrNotFound
	case statusCode >= 300 && statusCode < 500:
		return fmt.Errorf("invalid response from SoundCloud: %v", statusCode)
	case statusCode >= 500:
		ui.Term("There is a problem by SoundCloud. Please wait a while", nil)
	}
	return nil
}
<|endoftext|>


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

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"testing"

	"github.com/u-root/u-root/pkg/cpio"
	"github.com/u-root/u-root/pkg/testutil"
	itest "github.com/u-root/u-root/pkg/uroot/initramfs/test"
)

var twocmds = []string{
	"github.com/u-root/u-root/cmds/ls",
	"github.com/u-root/u-root/cmds/init",
}

var srcmds = []string{
	"github.com/u-root/u-root/cmds/ls",
	"github.com/u-root/u-root/cmds/init",
	"github.com/u-root/u-root/cmds/installcommand",
}

type buildSourceValidator struct {
	gopath string
	goroot string
	env    []string
}

func (b buildSourceValidator) Validate(a *cpio.Archive) error {
	dir, err := ioutil.TempDir("", "u-root-source-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	// Unpack into dir.
	err = cpio.ForEachRecord(a.Reader(), func(r cpio.Record) error {
		return cpio.CreateFileInRoot(r, dir, false)
	})
	if err != nil {
		return err
	}

	goroot := filepath.Join(dir, b.goroot)
	gopath := filepath.Join(dir, b.gopath)
	// go build ./src/...
	c := exec.Command(filepath.Join(goroot, "bin/go"), "build", filepath.Join(gopath, "src/..."))
	c.Env = append(b.env,
		fmt.Sprintf("GOPATH=%s", gopath),
		fmt.Sprintf("GOROOT=%s", goroot))
	out, err := c.CombinedOutput()
	if err != nil {
		return fmt.Errorf("could not build go source %v; output\n%s", err, out)
	}
	return nil
}

func TestUrootCmdline(t *testing.T) {
	samplef, err := ioutil.TempFile("", "u-root-sample-")
	if err != nil {
		t.Fatal(err)
	}
	samplef.Close()
	defer os.RemoveAll(samplef.Name())

	for _, tt := range []struct {
		name       string
		env        []string
		args       []string
		err        error
		validators []itest.ArchiveValidator
	}{
		{
			name: "include one extra file",
			args: []string{"-nocmd", "-files=/bin/bash"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
			},
		},
		{
			name: "include multiple extra files",
			args: []string{"-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
				itest.HasFile{"bin/ls"},
				itest.HasFile{samplef.Name()},
			},
		},
		{
			name: "include one extra file with rename",
			args: []string{"-nocmd", "-files=/bin/bash:bin/bush"},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bush"},
			},
		},
		{
			name: "hosted source mode",
			args: append([]string{"-build=source", "-base=/dev/null", "-defaultsh=", "-initcmd="}, srcmds...),
		},
		{
			name: "hosted bb mode",
			args: append([]string{"-build=bb", "-base=/dev/null", "-defaultsh=", "-initcmd="}, twocmds...),
		},
		{
			name: "AMD64 bb build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=bb"},
		},
		{
			name: "AMD64 source build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=source"},
			validators: []itest.ArchiveValidator{
				buildSourceValidator{
					goroot: "/go",
					gopath: ".",
					env:    []string{"GOARCH=amd64"},
				},
			},
		},
		{
			name: "ARM7 bb build",
			env:  []string{"GOARCH=arm", "GOARM=7"},
			args: []string{"-build=bb"},
		},
		{
			name: "ARM64 bb build",
			env:  []string{"GOARCH=arm64"},
			args: []string{"-build=bb"},
		},
		{
			name: "Power 64bit bb build",
			env:  []string{"GOARCH=ppc64le"},
			args: []string{"-build=bb"},
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			delFiles := true
			f, sum1 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f.Name())
				}
			}()

			a, err := itest.ReadArchive(f.Name())
			if err != nil {
				t.Fatal(err)
			}

			for _, v := range tt.validators {
				if err := v.Validate(a); err != nil {
					t.Errorf("validator failed: %v / archive:\n%s", err, a)
				}
			}

			f2, sum2 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f2.Name())
				}
			}()
			if !bytes.Equal(sum1, sum2) {
				delFiles = false
				t.Errorf("not reproducible, hashes don't match")
				t.Errorf("env: %v args: %v", tt.env, tt.args)
				t.Errorf("file1: %v file2: %v", f.Name(), f2.Name())
			}
		})
	}
}

func buildIt(t *testing.T, args, env []string, want error) (*os.File, []byte) {
	f, err := ioutil.TempFile("", "u-root-")
	if err != nil {
		t.Fatal(err)
	}

	arg := append([]string{"-o", f.Name()}, args...)
	c := testutil.Command(t, arg...)
	t.Logf("Commandline: %v", arg)
	c.Env = append(c.Env, env...)
	if out, err := c.CombinedOutput(); err != want {
		t.Fatalf("Error: %v\nOutput:\n%s", err, out)
	} else if err != nil {
		h1 := sha256.New()
		if _, err := io.Copy(h1, f); err != nil {
			t.Fatal()
		}
		return f, h1.Sum(nil)
	}
	return f, nil
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<commit_msg>uroot-test: add GOCACHE temp dir<commit_after>// Copyright 2015-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"testing"

	"github.com/u-root/u-root/pkg/cpio"
	"github.com/u-root/u-root/pkg/testutil"
	itest "github.com/u-root/u-root/pkg/uroot/initramfs/test"
)

var twocmds = []string{
	"github.com/u-root/u-root/cmds/ls",
	"github.com/u-root/u-root/cmds/init",
}

var srcmds = []string{
	"github.com/u-root/u-root/cmds/ls",
	"github.com/u-root/u-root/cmds/init",
	"github.com/u-root/u-root/cmds/installcommand",
}

type buildSourceValidator struct {
	gopath string
	goroot string
	env    []string
}

func (b buildSourceValidator) Validate(a *cpio.Archive) error {
	dir, err := ioutil.TempDir("", "u-root-source-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	if err := os.Mkdir(filepath.Join(dir, "tmp"), 0755); err != nil {
		return err
	}

	// Unpack into dir.
	err = cpio.ForEachRecord(a.Reader(), func(r cpio.Record) error {
		return cpio.CreateFileInRoot(r, dir, false)
	})
	if err != nil {
		return err
	}

	goroot := filepath.Join(dir, b.goroot)
	gopath := filepath.Join(dir, b.gopath)
	// go build ./src/...
	c := exec.Command(filepath.Join(goroot, "bin/go"), "build", filepath.Join(gopath, "src/..."))
	c.Env = append(b.env,
		fmt.Sprintf("GOPATH=%s", gopath),
		fmt.Sprintf("GOCACHE=%s", filepath.Join(dir, "tmp")),
		fmt.Sprintf("GOROOT=%s", goroot))
	out, err := c.CombinedOutput()
	if err != nil {
		return fmt.Errorf("could not build go source %v; output\n%s", err, out)
	}
	return nil
}

func TestUrootCmdline(t *testing.T) {
	samplef, err := ioutil.TempFile("", "u-root-sample-")
	if err != nil {
		t.Fatal(err)
	}
	samplef.Close()
	defer os.RemoveAll(samplef.Name())

	for _, tt := range []struct {
		name       string
		env        []string
		args       []string
		err        error
		validators []itest.ArchiveValidator
	}{
		{
			name: "include one extra file",
			args: []string{"-nocmd", "-files=/bin/bash"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
			},
		},
		{
			name: "include multiple extra files",
			args: []string{"-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
				itest.HasFile{"bin/ls"},
				itest.HasFile{samplef.Name()},
			},
		},
		{
			name: "include one extra file with rename",
			args: []string{"-nocmd", "-files=/bin/bash:bin/bush"},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bush"},
			},
		},
		{
			name: "hosted source mode",
			args: append([]string{"-build=source", "-base=/dev/null", "-defaultsh=", "-initcmd="}, srcmds...),
		},
		{
			name: "hosted bb mode",
			args: append([]string{"-build=bb", "-base=/dev/null", "-defaultsh=", "-initcmd="}, twocmds...),
		},
		{
			name: "AMD64 bb build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=bb"},
		},
		{
			name: "AMD64 source build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=source"},
			validators: []itest.ArchiveValidator{
				buildSourceValidator{
					goroot: "/go",
					gopath: ".",
					env:    []string{"GOARCH=amd64"},
				},
			},
		},
		{
			name: "ARM7 bb build",
			env:  []string{"GOARCH=arm", "GOARM=7"},
			args: []string{"-build=bb"},
		},
		{
			name: "ARM64 bb build",
			env:  []string{"GOARCH=arm64"},
			args: []string{"-build=bb"},
		},
		{
			name: "Power 64bit bb build",
			env:  []string{"GOARCH=ppc64le"},
			args: []string{"-build=bb"},
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			delFiles := true
			f, sum1 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f.Name())
				}
			}()

			a, err := itest.ReadArchive(f.Name())
			if err != nil {
				t.Fatal(err)
			}

			for _, v := range tt.validators {
				if err := v.Validate(a); err != nil {
					t.Errorf("validator failed: %v / archive:\n%s", err, a)
				}
			}

			f2, sum2 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f2.Name())
				}
			}()
			if !bytes.Equal(sum1, sum2) {
				delFiles = false
				t.Errorf("not reproducible, hashes don't match")
				t.Errorf("env: %v args: %v", tt.env, tt.args)
				t.Errorf("file1: %v file2: %v", f.Name(), f2.Name())
			}
		})
	}
}

func buildIt(t *testing.T, args, env []string, want error) (*os.File, []byte) {
	f, err := ioutil.TempFile("", "u-root-")
	if err != nil {
		t.Fatal(err)
	}

	arg := append([]string{"-o", f.Name()}, args...)
	c := testutil.Command(t, arg...)
	t.Logf("Commandline: %v", arg)
	c.Env = append(c.Env, env...)
	if out, err := c.CombinedOutput(); err != want {
		t.Fatalf("Error: %v\nOutput:\n%s", err, out)
	} else if err != nil {
		h1 := sha256.New()
		if _, err := io.Copy(h1, f); err != nil {
			t.Fatal()
		}
		return f, h1.Sum(nil)
	}
	return f, nil
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<|endoftext|>


<commit_before>/*-
Copyright (c) 2016, Jörg Pernfuß <code.jpe@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Package auth implements the common key exchange and
// authentication bits between SOMA service and client.
package auth

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"hash"
	"math/big"
	"net"
	"net/http"
	"time"

	"golang.org/x/crypto/nacl/box"

	"github.com/dchest/blake2b"
	"github.com/satori/go.uuid"
)

type Kex struct {
	Public               string    `json:"public_key"`
	Request              uuid.UUID `json:"request,omitempty"`
	InitializationVector string    `json:"initialization_vector"`
	// unexported private fields
	private  string    `json:"-"`
	peer     string    `json:"-"`
	sourceIP net.IP    `json:"-"`
	count    uint      `json:"-"`
	time     time.Time `json:"-"`
}

// NewKex returns a Kex with a set random
// InitializationVector and new generated random keypair
func NewKex() *Kex {
	var (
		err error
		k   Kex
	)

	k = Kex{}

	if err = k.GenerateNewVector(); err != nil {
		return nil
	}

	if err = k.GenerateNewKeypair(); err != nil {
		return nil
	}
	return &k
}

// GenerateNewKeypair generate a new public,private Keypair
func (k *Kex) GenerateNewKeypair() error {
	var (
		err                   error
		bRandom, bSecret      []byte
		hBlake                hash.Hash
		publicKey, privateKey *[32]byte
	)

	// generate keypair, starting with 1024bit random as
	// noted in DJB's docs
	bRandom = make([]byte, 128)
	if _, err = rand.Read(bRandom); err != nil {
		return err
	}

	// hash 1k down to 256bit
	hBlake = blake2b.New256()
	hBlake.Write(bRandom)
	bSecret = hBlake.Sum(nil)
	// generate keys
	if publicKey, privateKey, err = box.GenerateKey(
		bytes.NewReader(bSecret),
	); err != nil {
		return err
	}

	// set keys
	k.Public = hex.EncodeToString(publicKey[:])
	k.private = hex.EncodeToString(privateKey[:])
	return nil
}

// GenerateNewRequestID generate a new UUIDv4 for this Kex
func (k *Kex) GenerateNewRequestID() {
	k.Request = uuid.NewV4()
}

// GenerateNewVector generates a new random Initialization
// Vector
func (k *Kex) GenerateNewVector() error {
	var (
		b   []byte
		err error
	)

	// generate the IV from 192bit of randomness
	b = make([]byte, 24)
	if _, err = rand.Read(b); err != nil {
		return err
	}

	k.InitializationVector = hex.EncodeToString(b)
	return nil
}

// IsExpired returns true if the Kex-Exchange is more than
// KexExpirySeconds seconds old
func (k *Kex) IsExpired() bool {
	return time.Now().UTC().After(k.time.UTC().Add(
		time.Duration(KexExpirySeconds) * time.Second))
}

// IsSameSource returns true if the paramter IP address is the
// same as the one recorded in the Kex
func (k *Kex) IsSameSource(ip net.IP) bool {
	return k.sourceIP.Equal(ip)
}

// IsSameSourceString returns true if the paramter IP address is
// same as the one recorded in the Kex
func (k *Kex) IsSameSourceString(addr string) bool {
	return k.IsSameSource(net.ParseIP(extractAddress(addr)))
}

// NextNonce returns the next nonce to use. Nonces are built by
// interpreting the IV as a positive integer number and adding the
// count of requested nonces; thus implementing a simple non-repeating
// counter. The IV itself is never used as a nonce.
// Returns nil on error
func (k *Kex) NextNonce() *[24]byte {
	var (
		ib    []byte
		iv    *big.Int
		nonce *[24]byte
		err   error
	)

	k.count += 1
	if ib, err = hex.DecodeString(k.InitializationVector); err != nil {
		// hex decode failed
		return nil
	}

	// initialize
	iv = big.NewInt(0)
	// convert k.IV to Int by setting ib bytes as value
	iv.SetBytes(ib)
	// ensure the resulting number is positive
	iv.Abs(iv)
	// add the current counter value on top
	iv.Add(iv, big.NewInt(int64(k.count)))
	// check the resulting number still fits *[24]byte
	if len(iv.Bytes()) != 24 {
		return nil
	}

	// build nonce
	nonce = &[24]byte{}
	copy(nonce[:], iv.Bytes()[0:24])
	return nonce
}

// PeerKey returns the public key of the kex peer, or nil if it has
// not been set yet.
func (k *Kex) PeerKey() *[32]byte {
	var (
		pk   []byte
		err  error
		peer *[32]byte
	)

	// k.peer has not been set yet
	if k.peer == "" {
		return nil
	}

	// k.peer was set incorrect
	if pk, err = hex.DecodeString(k.peer); err != nil {
		return nil
	}

	// how?!
	if len(pk) != 32 {
		return nil
	}

	// return public key bytes
	peer = &[32]byte{}
	copy(peer[:], pk[0:32])
	return peer
}

// PrivateKey returns our private key for this kex, or nil if it has
// not been set yet.
func (k *Kex) PrivateKey() *[32]byte {
	var (
		pk      []byte
		err     error
		private *[32]byte
	)

	// k.private has not been set yet
	if k.private == "" {
		return nil
	}

	// k.private was set incorrect
	if pk, err = hex.DecodeString(k.private); err != nil {
		return nil
	}

	// again: how?
	if len(pk) != 32 {
		return nil
	}

	// return private key bytes
	private = &[32]byte{}
	copy(private[:], pk[0:32])
	return private
}

// PublicKey returns our public key for this key exchange, or nil if
// it has not been set yet.
func (k *Kex) PublicKey() *[32]byte {
	var (
		pk     []byte
		err    error
		public *[32]byte
	)

	// k.Public has not been set yet
	if k.Public == "" {
		return nil
	}

	// k.Public was set incorrect
	if pk, err = hex.DecodeString(k.Public); err != nil {
		return nil
	}

	// you know it.
	if len(pk) != 32 {
		return nil
	}

	// return public key bytes
	public = &[32]byte{}
	copy(public[:], pk[0:32])
	return public
}

// SetPeerKey sets the kex peer public key
func (k *Kex) SetPeerKey(pk *[32]byte) {
	k.peer = hex.EncodeToString(pk[:])
}

// SetRequestUUID sets the UUID of this Kex from a string
func (k *Kex) SetRequestUUID(s string) error {
	var err error

	if k.Request, err = uuid.FromString(s); err != nil {
		return err
	}

	return nil
}

// SetTimeUTC records the current time within the Kex
func (k *Kex) SetTimeUTC() {
	k.time = time.Now().UTC()
}

// SetIPAddress records the client's IP address
func (k *Kex) SetIPAddress(r *http.Request) {
	k.SetIPAddressString(r.RemoteAddr)
}

// SetIPAddressString records the client's IP address
func (k *Kex) SetIPAddressString(addr string) {
	k.sourceIP = net.ParseIP(extractAddress(addr))
}

// DecodeAndDecrypt takes a base64 encoded message and decrypts it
// using the exchanged keys.
func (k *Kex) DecodeAndDecrypt(encoded, plaintext *[]byte) error {
	var (
		nonce            *[24]byte
		peerKey, privKey *[32]byte
		decoded          []byte
		dlen             int
		err              error
		ok               bool
	)

	// check is kex is still valid
	if k.IsExpired() {
		return ErrCrypt
	}

	// calculate the next nonce
	if nonce = k.NextNonce(); nonce == nil {
		return ErrCrypt
	}

	if peerKey = k.PeerKey(); peerKey == nil {
		return ErrCrypt
	}

	if privKey = k.PrivateKey(); privKey == nil {
		return ErrCrypt
	}

	// decode input
	if dlen, err = base64.StdEncoding.Decode(decoded, *encoded); err != nil {
		return ErrCrypt
	}

	// decrypt ciphertext
	*plaintext, ok = box.Open(nil, decoded[:dlen], nonce, peerKey, privKey)
	if !ok {
		return ErrCrypt
	}

	return nil
}

// EncryptAndEncode takes a plaintext messages and encrypts it
// using the exchanged keys. The ciphertext is then encoded as base64.
func (k *Kex) EncryptAndEncode(plaintext, encoded *[]byte) error {
	var (
		nonce            *[24]byte
		peerKey, privKey *[32]byte
		ciphertext       []byte
	)

	// check is kex is still valid
	if k.IsExpired() {
		return ErrCrypt
	}

	if nonce = k.NextNonce(); nonce == nil {
		return ErrCrypt
	}

	if peerKey = k.PeerKey(); peerKey == nil {
		return ErrCrypt
	}

	if privKey = k.PrivateKey(); privKey == nil {
		return ErrCrypt
	}

	// encrypt input
	ciphertext = box.Seal(nil, *plaintext, nonce, peerKey, privKey)
	if len(ciphertext) == 0 {
		return ErrCrypt
	}

	// encode ciphertext
	*encoded = make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext)))
	base64.StdEncoding.Encode(*encoded, ciphertext)

	return nil
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Prepare slices for decoding<commit_after>/*-
Copyright (c) 2016, Jörg Pernfuß <code.jpe@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Package auth implements the common key exchange and
// authentication bits between SOMA service and client.
package auth

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"hash"
	"math/big"
	"net"
	"net/http"
	"time"

	"golang.org/x/crypto/nacl/box"

	"github.com/dchest/blake2b"
	"github.com/satori/go.uuid"
)

type Kex struct {
	Public               string    `json:"public_key"`
	Request              uuid.UUID `json:"request,omitempty"`
	InitializationVector string    `json:"initialization_vector"`
	// unexported private fields
	private  string    `json:"-"`
	peer     string    `json:"-"`
	sourceIP net.IP    `json:"-"`
	count    uint      `json:"-"`
	time     time.Time `json:"-"`
}

// NewKex returns a Kex with a set random
// InitializationVector and new generated random keypair
func NewKex() *Kex {
	var (
		err error
		k   Kex
	)

	k = Kex{}

	if err = k.GenerateNewVector(); err != nil {
		return nil
	}

	if err = k.GenerateNewKeypair(); err != nil {
		return nil
	}
	return &k
}

// GenerateNewKeypair generate a new public,private Keypair
func (k *Kex) GenerateNewKeypair() error {
	var (
		err                   error
		bRandom, bSecret      []byte
		hBlake                hash.Hash
		publicKey, privateKey *[32]byte
	)

	// generate keypair, starting with 1024bit random as
	// noted in DJB's docs
	bRandom = make([]byte, 128)
	if _, err = rand.Read(bRandom); err != nil {
		return err
	}

	// hash 1k down to 256bit
	hBlake = blake2b.New256()
	hBlake.Write(bRandom)
	bSecret = hBlake.Sum(nil)
	// generate keys
	if publicKey, privateKey, err = box.GenerateKey(
		bytes.NewReader(bSecret),
	); err != nil {
		return err
	}

	// set keys
	k.Public = hex.EncodeToString(publicKey[:])
	k.private = hex.EncodeToString(privateKey[:])
	return nil
}

// GenerateNewRequestID generate a new UUIDv4 for this Kex
func (k *Kex) GenerateNewRequestID() {
	k.Request = uuid.NewV4()
}

// GenerateNewVector generates a new random Initialization
// Vector
func (k *Kex) GenerateNewVector() error {
	var (
		b   []byte
		err error
	)

	// generate the IV from 192bit of randomness
	b = make([]byte, 24)
	if _, err = rand.Read(b); err != nil {
		return err
	}

	k.InitializationVector = hex.EncodeToString(b)
	return nil
}

// IsExpired returns true if the Kex-Exchange is more than
// KexExpirySeconds seconds old
func (k *Kex) IsExpired() bool {
	return time.Now().UTC().After(k.time.UTC().Add(
		time.Duration(KexExpirySeconds) * time.Second))
}

// IsSameSource returns true if the paramter IP address is the
// same as the one recorded in the Kex
func (k *Kex) IsSameSource(ip net.IP) bool {
	return k.sourceIP.Equal(ip)
}

// IsSameSourceString returns true if the paramter IP address is
// same as the one recorded in the Kex
func (k *Kex) IsSameSourceString(addr string) bool {
	return k.IsSameSource(net.ParseIP(extractAddress(addr)))
}

// NextNonce returns the next nonce to use. Nonces are built by
// interpreting the IV as a positive integer number and adding the
// count of requested nonces; thus implementing a simple non-repeating
// counter. The IV itself is never used as a nonce.
// Returns nil on error
func (k *Kex) NextNonce() *[24]byte {
	var (
		ib    []byte
		iv    *big.Int
		nonce *[24]byte
		err   error
	)

	k.count += 1
	if ib, err = hex.DecodeString(k.InitializationVector); err != nil {
		// hex decode failed
		return nil
	}

	// initialize
	iv = big.NewInt(0)
	// convert k.IV to Int by setting ib bytes as value
	iv.SetBytes(ib)
	// ensure the resulting number is positive
	iv.Abs(iv)
	// add the current counter value on top
	iv.Add(iv, big.NewInt(int64(k.count)))
	// check the resulting number still fits *[24]byte
	if len(iv.Bytes()) != 24 {
		return nil
	}

	// build nonce
	nonce = &[24]byte{}
	copy(nonce[:], iv.Bytes()[0:24])
	return nonce
}

// PeerKey returns the public key of the kex peer, or nil if it has
// not been set yet.
func (k *Kex) PeerKey() *[32]byte {
	var (
		pk   []byte
		err  error
		peer *[32]byte
	)

	// k.peer has not been set yet
	if k.peer == "" {
		return nil
	}

	// k.peer was set incorrect
	if pk, err = hex.DecodeString(k.peer); err != nil {
		return nil
	}

	// how?!
	if len(pk) != 32 {
		return nil
	}

	// return public key bytes
	peer = &[32]byte{}
	copy(peer[:], pk[0:32])
	return peer
}

// PrivateKey returns our private key for this kex, or nil if it has
// not been set yet.
func (k *Kex) PrivateKey() *[32]byte {
	var (
		pk      []byte
		err     error
		private *[32]byte
	)

	// k.private has not been set yet
	if k.private == "" {
		return nil
	}

	// k.private was set incorrect
	if pk, err = hex.DecodeString(k.private); err != nil {
		return nil
	}

	// again: how?
	if len(pk) != 32 {
		return nil
	}

	// return private key bytes
	private = &[32]byte{}
	copy(private[:], pk[0:32])
	return private
}

// PublicKey returns our public key for this key exchange, or nil if
// it has not been set yet.
func (k *Kex) PublicKey() *[32]byte {
	var (
		pk     []byte
		err    error
		public *[32]byte
	)

	// k.Public has not been set yet
	if k.Public == "" {
		return nil
	}

	// k.Public was set incorrect
	if pk, err = hex.DecodeString(k.Public); err != nil {
		return nil
	}

	// you know it.
	if len(pk) != 32 {
		return nil
	}

	// return public key bytes
	public = &[32]byte{}
	copy(public[:], pk[0:32])
	return public
}

// SetPeerKey sets the kex peer public key
func (k *Kex) SetPeerKey(pk *[32]byte) {
	k.peer = hex.EncodeToString(pk[:])
}

// SetRequestUUID sets the UUID of this Kex from a string
func (k *Kex) SetRequestUUID(s string) error {
	var err error

	if k.Request, err = uuid.FromString(s); err != nil {
		return err
	}

	return nil
}

// SetTimeUTC records the current time within the Kex
func (k *Kex) SetTimeUTC() {
	k.time = time.Now().UTC()
}

// SetIPAddress records the client's IP address
func (k *Kex) SetIPAddress(r *http.Request) {
	k.SetIPAddressString(r.RemoteAddr)
}

// SetIPAddressString records the client's IP address
func (k *Kex) SetIPAddressString(addr string) {
	k.sourceIP = net.ParseIP(extractAddress(addr))
}

// DecodeAndDecrypt takes a base64 encoded message and decrypts it
// using the exchanged keys.
func (k *Kex) DecodeAndDecrypt(encoded, plaintext *[]byte) error {
	var (
		nonce            *[24]byte
		peerKey, privKey *[32]byte
		decoded          []byte
		dlen             int
		err              error
		ok               bool
	)

	// check is kex is still valid
	if k.IsExpired() {
		return ErrCrypt
	}

	// calculate the next nonce
	if nonce = k.NextNonce(); nonce == nil {
		return ErrCrypt
	}

	if peerKey = k.PeerKey(); peerKey == nil {
		return ErrCrypt
	}

	if privKey = k.PrivateKey(); privKey == nil {
		return ErrCrypt
	}

	// decode input
	decoded = make([]byte, base64.StdEncoding.DecodedLen(len(*encoded)))
	if dlen, err = base64.StdEncoding.Decode(decoded, *encoded); err != nil {
		return ErrCrypt
	}

	// decrypt ciphertext
	*plaintext = make([]byte, dlen)
	*plaintext, ok = box.Open(nil, decoded[:dlen], nonce, peerKey, privKey)
	if !ok {
		return ErrCrypt
	}

	return nil
}

// EncryptAndEncode takes a plaintext messages and encrypts it
// using the exchanged keys. The ciphertext is then encoded as base64.
func (k *Kex) EncryptAndEncode(plaintext, encoded *[]byte) error {
	var (
		nonce            *[24]byte
		peerKey, privKey *[32]byte
		ciphertext       []byte
	)

	// check is kex is still valid
	if k.IsExpired() {
		return ErrCrypt
	}

	if nonce = k.NextNonce(); nonce == nil {
		return ErrCrypt
	}

	if peerKey = k.PeerKey(); peerKey == nil {
		return ErrCrypt
	}

	if privKey = k.PrivateKey(); privKey == nil {
		return ErrCrypt
	}

	// encrypt input
	ciphertext = box.Seal(nil, *plaintext, nonce, peerKey, privKey)
	if len(ciphertext) == 0 {
		return ErrCrypt
	}

	// encode ciphertext
	*encoded = make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext)))
	base64.StdEncoding.Encode(*encoded, ciphertext)

	return nil
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>package main

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

	"periph.io/x/periph/conn/gpio"
	"periph.io/x/periph/conn/gpio/gpioreg"
	"periph.io/x/periph/host"
	"github.com/rs/cors"
)

var (
	mode     = 2
	demoMode = false
	isPushoverEnabled = false

	// LEDs
	ledRedPin    = 4
	ledYellowPin = 17
	ledGreenPin  = 27
	ledToColor   = map[int]string{}
	ledMap = map[int]gpio.PinIO{}
	ledMode 		 = 0

	// Buttons
	buttonPin 	= 22
	buttons 		= map[int]gpio.PinIO{}

	// GPIOs
	gpios = map[int]Gpio{}
	demoNum = 26

	// Pushover
	pushoverUser = ""
	pushoverToken = ""
	pushoverApi = "https://api.pushover.net:443/1/messages.json"
)

func getLEDString(color string) string {
	return "Toggle " + strings.ToUpper(color)
}

func setGpio(id int, name string, value int) {
	gpios[id] = Gpio{id, name, value}
}

func toggleLED(id int, pin gpio.PinIO, color string) {
	//fmt.Println(getLEDString(color))
	if !demoMode {
		//fmt.Println("Val to write", !pin.Read())
		pin.Out(!pin.Read())
		value := 0
		if pin.Read() == gpio.High {
			value = 1
		}
		setGpio(id, pin.Name(), value)
	}
}

func initButtons() {
	buttons[0] = gpioreg.ByName(strconv.Itoa(buttonPin))
	if err := buttons[0].In(gpio.PullUp, gpio.BothEdges); err != nil {
  	log.Fatal(err)
  }
	for buttonIndex := 0; buttonIndex < len(buttons); buttonIndex++ {
		fmt.Printf("%s: %s\n", buttons[buttonIndex], buttons[buttonIndex].Function())
	}
}

func initLEDs() {
	if mode == 2 {
		ledMap[0] = gpioreg.ByName(strconv.Itoa(ledRedPin))
		ledMap[1] = gpioreg.ByName(strconv.Itoa(ledYellowPin))
		ledMap[2] = gpioreg.ByName(strconv.Itoa(ledGreenPin))
		for i := 0; i < len(ledMap); i++ {
			fmt.Println("Resetting ", ledMap[i])
			if err := ledMap[i].Out(gpio.Low); err != nil {
        log.Fatal(err)
      }
		}
	}
	initLEDcolors()
}

func initLEDcolors() {
	ledToColor[0] = "red"
	ledToColor[1] = "yellow"
	ledToColor[2] = "green"
}

func initGPIO() error {
	_,err := host.Init()
	return err
}

func doLedToggling(i int, isSleepEnabled bool) {
	if !demoMode {
	  if mode == 2{
			toggleLED(i%3, ledMap[i%3], ledToColor[i%3])
		}
	} else {
		fmt.Println(getLEDString(ledToColor[i%3]))
		setGpio(i%demoNum, "GPIO" + strconv.Itoa(i%demoNum), i % 2)
	}

	if isSleepEnabled {
		time.Sleep(time.Second)
	}
}

type Gpio struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Value int    `json:"value"`
}

func getGpios(w http.ResponseWriter, r *http.Request) {
	gpiosToJson := make([]Gpio, 0, len(gpios))
	fmt.Printf("Transforming %d GPIOs\n", len(gpios))
	for  _, value := range gpios {
  	gpiosToJson = append(gpiosToJson, value)
	}
	fmt.Printf("Transformed into %d gpios\n", len(gpiosToJson))
	json, err := json.Marshal(gpiosToJson)

	if err != nil {
		panic(err)
	}
	w.Write(json)
}

type PushoverMessage struct {
	Token string `json:"token"`
	User string `json:"user"`
	Message string `json:"message"`
}

func toggleLedMode() {
	if ledMode == 0 {
		ledMode = 1
	} else {
		ledMode = 0
	}
}

func ledModeHandler(w http.ResponseWriter, r *http.Request) {
	toggleLedMode()

	if isPushoverEnabled {
		message := PushoverMessage{pushoverToken, pushoverUser, "LED mode toggled"}
		json, err := json.Marshal(message)
		var jsonStr = []byte(json)

		// Build the request
		client := &http.Client{}
		req, err := http.NewRequest("POST", pushoverApi, bytes.NewBuffer(jsonStr))
		if err != nil {
			fmt.Println("Request ERROR:", err)
			return
		}

	  req.Header.Add("Content-Type", "application/json")
		resp, err := client.Do(req)
		if err != nil {
			fmt.Println("Response ERROR:", err)
			return
		}
		defer resp.Body.Close()
		fmt.Println("Response: ", *resp)
	}
}

func initWebServer() {
	fmt.Println("Initializing Webserver")
	mux := http.NewServeMux()
	mux.HandleFunc("/v1/gpios", getGpios)
	mux.HandleFunc("/v1/ledMode", ledModeHandler)
	handler := cors.Default().Handler(mux)
	http.ListenAndServe(":8080", handler)
}

func listenForButtonPress(button gpio.PinIO) {
	buttonState := gpio.High
	for {
		fmt.Println("Check button ", button)
		button.WaitForEdge(-1)
		buttonState = button.Read()
		fmt.Printf("-> %s\n", buttonState)
		if buttonState == gpio.High {
			toggleLedMode()
		}
	}
}

func listenForButtonsPress() {
	fmt.Println("Listening for button presses")
	go listenForButtonPress(buttons[0])
}

func main() {
	fmt.Println("Parsing parameters")
	num := flag.Int("num", 0, "number of blinks")
	buttonEnabled := flag.Bool("button", false, "button mode")
	api := flag.Bool("api", true, "API enabled")
	demo := flag.Bool("demo", false, "Demo mode enabled")
	pushoverFromCli := flag.Bool("pushover", false, "Pushover notifications enabled")

	flag.Parse()

	demoMode = *demo
	isPushoverEnabled = *pushoverFromCli
	pushoverUser = os.Getenv("PUSHOVER_USER")
	pushoverToken = os.Getenv("PUSHOVER_TOKEN")

	if isPushoverEnabled && (pushoverUser == "" || pushoverToken == "") {
		fmt.Println("Pushover env variables are undefined, disabling pushover notifs")
		isPushoverEnabled = false
	}

	if demoMode {
		fmt.Println("Running in demo mode, no physical hw interaction")
	}

	fmt.Println("Number of blinks:", *num)

	if *api {
		go initWebServer()
	}

	if !demoMode {
		var err = initGPIO()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		initLEDs()
		initButtons()
		if *buttonEnabled {
			listenForButtonsPress()
		}
	} else {
		initLEDcolors()
	}

	if *num == 0 {
		var counter = 0
		for {
			if ledMode == 0 {
				doLedToggling(counter, true)
			} else if ledMode == 1 {
				doLedToggling(counter, false)
				doLedToggling(counter + 1, false)
				doLedToggling(counter + 2, true)
			}
			counter++
		}
	} else {
		for i := 0; i < *num; i++ {
			doLedToggling(i, true)
		}
	}
}
<commit_msg>Extracted send pushover message to method<commit_after>package main

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

	"periph.io/x/periph/conn/gpio"
	"periph.io/x/periph/conn/gpio/gpioreg"
	"periph.io/x/periph/host"
	"github.com/rs/cors"
)

var (
	mode     = 2
	demoMode = false
	isPushoverEnabled = false

	// LEDs
	ledRedPin    = 4
	ledYellowPin = 17
	ledGreenPin  = 27
	ledToColor   = map[int]string{}
	ledMap = map[int]gpio.PinIO{}
	ledMode 		 = 0

	// Buttons
	buttonPin 	= 22
	buttons 		= map[int]gpio.PinIO{}

	// GPIOs
	gpios = map[int]Gpio{}
	demoNum = 26

	// Pushover
	pushoverUser = ""
	pushoverToken = ""
	pushoverApi = "https://api.pushover.net:443/1/messages.json"
)

func getLEDString(color string) string {
	return "Toggle " + strings.ToUpper(color)
}

func setGpio(id int, name string, value int) {
	gpios[id] = Gpio{id, name, value}
}

func toggleLED(id int, pin gpio.PinIO, color string) {
	//fmt.Println(getLEDString(color))
	if !demoMode {
		//fmt.Println("Val to write", !pin.Read())
		pin.Out(!pin.Read())
		value := 0
		if pin.Read() == gpio.High {
			value = 1
		}
		setGpio(id, pin.Name(), value)
	}
}

func initButtons() {
	buttons[0] = gpioreg.ByName(strconv.Itoa(buttonPin))
	if err := buttons[0].In(gpio.PullUp, gpio.BothEdges); err != nil {
  	log.Fatal(err)
  }
	for buttonIndex := 0; buttonIndex < len(buttons); buttonIndex++ {
		fmt.Printf("%s: %s\n", buttons[buttonIndex], buttons[buttonIndex].Function())
	}
}

func initLEDs() {
	if mode == 2 {
		ledMap[0] = gpioreg.ByName(strconv.Itoa(ledRedPin))
		ledMap[1] = gpioreg.ByName(strconv.Itoa(ledYellowPin))
		ledMap[2] = gpioreg.ByName(strconv.Itoa(ledGreenPin))
		for i := 0; i < len(ledMap); i++ {
			fmt.Println("Resetting ", ledMap[i])
			if err := ledMap[i].Out(gpio.Low); err != nil {
        log.Fatal(err)
      }
		}
	}
	initLEDcolors()
}

func initLEDcolors() {
	ledToColor[0] = "red"
	ledToColor[1] = "yellow"
	ledToColor[2] = "green"
}

func initGPIO() error {
	_,err := host.Init()
	return err
}

func doLedToggling(i int, isSleepEnabled bool) {
	if !demoMode {
	  if mode == 2{
			toggleLED(i%3, ledMap[i%3], ledToColor[i%3])
		}
	} else {
		fmt.Println(getLEDString(ledToColor[i%3]))
		setGpio(i%demoNum, "GPIO" + strconv.Itoa(i%demoNum), i % 2)
	}

	if isSleepEnabled {
		time.Sleep(time.Second)
	}
}

type Gpio struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Value int    `json:"value"`
}

func getGpios(w http.ResponseWriter, r *http.Request) {
	gpiosToJson := make([]Gpio, 0, len(gpios))
	fmt.Printf("Transforming %d GPIOs\n", len(gpios))
	for  _, value := range gpios {
  	gpiosToJson = append(gpiosToJson, value)
	}
	fmt.Printf("Transformed into %d gpios\n", len(gpiosToJson))
	json, err := json.Marshal(gpiosToJson)

	if err != nil {
		panic(err)
	}
	w.Write(json)
}

type PushoverMessage struct {
	Token string `json:"token"`
	User string `json:"user"`
	Message string `json:"message"`
}

func toggleLedMode() {
	if ledMode == 0 {
		ledMode = 1
	} else {
		ledMode = 0
	}
}

func sendPushoverMessage(message string) {
	if isPushoverEnabled {
		message := PushoverMessage{pushoverToken, pushoverUser, message}
		json, err := json.Marshal(message)
		var jsonStr = []byte(json)

		// Build the request
		client := &http.Client{}
		req, err := http.NewRequest("POST", pushoverApi, bytes.NewBuffer(jsonStr))
		if err != nil {
			fmt.Println("Request ERROR:", err)
			return
		}

	  req.Header.Add("Content-Type", "application/json")
		resp, err := client.Do(req)
		if err != nil {
			fmt.Println("Response ERROR:", err)
			return
		}
		defer resp.Body.Close()
		fmt.Println("Response: ", *resp)
	}
}

func ledModeHandler(w http.ResponseWriter, r *http.Request) {
	toggleLedMode()
	sendPushoverMessage("LED mode toggled from API")
}

func initWebServer() {
	fmt.Println("Initializing Webserver")
	mux := http.NewServeMux()
	mux.HandleFunc("/v1/gpios", getGpios)
	mux.HandleFunc("/v1/ledMode", ledModeHandler)
	handler := cors.Default().Handler(mux)
	http.ListenAndServe(":8080", handler)
}

func listenForButtonPress(button gpio.PinIO) {
	buttonState := gpio.High
	for {
		fmt.Println("Check button ", button)
		button.WaitForEdge(-1)
		buttonState = button.Read()
		fmt.Printf("-> %s\n", buttonState)
		if buttonState == gpio.High {
			toggleLedMode()
			sendPushoverMessage("LED mode toggled using button")
		}
	}
}

func listenForButtonsPress() {
	fmt.Println("Listening for button presses")
	go listenForButtonPress(buttons[0])
}

func main() {
	fmt.Println("Parsing parameters")
	num := flag.Int("num", 0, "number of blinks")
	buttonEnabled := flag.Bool("button", false, "button mode")
	api := flag.Bool("api", true, "API enabled")
	demo := flag.Bool("demo", false, "Demo mode enabled")
	pushoverFromCli := flag.Bool("pushover", false, "Pushover notifications enabled")

	flag.Parse()

	demoMode = *demo
	isPushoverEnabled = *pushoverFromCli
	pushoverUser = os.Getenv("PUSHOVER_USER")
	pushoverToken = os.Getenv("PUSHOVER_TOKEN")

	if isPushoverEnabled && (pushoverUser == "" || pushoverToken == "") {
		fmt.Println("Pushover env variables are undefined, disabling pushover notifs")
		isPushoverEnabled = false
	}

	if demoMode {
		fmt.Println("Running in demo mode, no physical hw interaction")
	}

	fmt.Println("Number of blinks:", *num)

	if *api {
		go initWebServer()
	}

	if !demoMode {
		var err = initGPIO()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		initLEDs()
		initButtons()
		if *buttonEnabled {
			listenForButtonsPress()
		}
	} else {
		initLEDcolors()
	}

	if *num == 0 {
		var counter = 0
		for {
			if ledMode == 0 {
				doLedToggling(counter, true)
			} else if ledMode == 1 {
				doLedToggling(counter, false)
				doLedToggling(counter + 1, false)
				doLedToggling(counter + 2, true)
			}
			counter++
		}
	} else {
		for i := 0; i < *num; i++ {
			doLedToggling(i, true)
		}
	}
}
<|endoftext|>