<commit_before>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

// Package agent implements a service that runs on a serviced node. It is
// responsible for ensuring that a particular node is running the correct services
// and reporting the state and health of those services back to the master
// serviced.

package main

// This is the main entry point for the application. Here we parse command line
// flags and either start a service or execute command line functions.

//svc "github.com/zenoss/serviced/svc"
import (
	"github.com/zenoss/serviced"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/dao/elasticsearch"
	"github.com/zenoss/serviced/isvcs"
	"github.com/zenoss/serviced/shell"
	"github.com/zenoss/serviced/volume"
	_ "github.com/zenoss/serviced/volume/btrfs"
	_ "github.com/zenoss/serviced/volume/rsync"
	"github.com/zenoss/serviced/web"

	"flag"
	"fmt"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"os/signal"
	"os/user"
	"path"
	"strconv"
	"syscall"
	"time"

	"github.com/zenoss/glog"
)

// Store the command line options
var options struct {
	port             string
	listen           string
	master           bool
	agent            bool
	muxPort          int
	tls              bool
	keyPEMFile       string
	certPEMFile      string
	varPath          string // Directory to store data, eg isvcs & service volumes
	resourcePath     string
	zookeepers       ListOpts
	repstats         bool
	statshost        string
	statsperiod      int
	mcusername       string
	mcpasswd         string
	mount            ListOpts
	resourceperiod   int
	vfs              string
	esStartupTimeout int
	hostaliases      string
}

var agentIP string

// getEnvVarInt() returns the env var as an int value or the defaultValue if env var is unset
func getEnvVarInt(envVar string, defaultValue int) int {
	envVarValue := os.Getenv(envVar)
	if len(envVarValue) > 0 {
		if value, err := strconv.Atoi(envVarValue); err != nil {
			glog.Errorf("Could not convert env var %s:%s to integer, error:%s", envVar, envVarValue, err)
			return defaultValue
		} else {
			return value
		}
	}
	return defaultValue
}

// ensureMinimumInt sets the env var and command line flag to the given minimum if the value is less than the minimum
func ensureMinimumInt(envVar string, flagName string, minimum int) {
	theFlag := flag.Lookup(flagName)
	value, _ := strconv.Atoi(theFlag.Value.String())
	if value < minimum {
		glog.Infof("overriding flag %s:%s with minimum value of %v", flagName, theFlag.Value.String(), minimum)
		valueStr := strconv.Itoa(minimum)
		os.Setenv(envVar, valueStr)
		flag.Set(flagName, valueStr)
	} else {
		os.Setenv(envVar, theFlag.Value.String())
	}
}

// Setup flag options (static block)
func init() {
	var err error
	agentIP, err = serviced.GetIPAddress()
	if err != nil {
		panic(err)
	}

	flag.StringVar(&options.port, "port", agentIP+":4979", "port for remote serviced (example.com:8080)")
	flag.StringVar(&options.listen, "listen", ":4979", "port for local serviced (example.com:8080)")
	flag.BoolVar(&options.master, "master", false, "run in master mode, ie the control plane service")
	flag.BoolVar(&options.agent, "agent", false, "run in agent mode, ie a host in a resource pool")
	flag.IntVar(&options.muxPort, "muxport", 22250, "multiplexing port to use")
	flag.BoolVar(&options.tls, "tls", true, "enable TLS")

	varPathDefault := path.Join(os.TempDir(), "serviced")
	if len(os.Getenv("SERVICED_HOME")) > 0 {
		varPathDefault = path.Join(os.Getenv("SERVICED_HOME"), "var")
	} else {
		if user, err := user.Current(); err == nil {
			varPathDefault = path.Join(os.TempDir(), "serviced-"+user.Username, "var")
		}
	}
	flag.StringVar(&options.varPath, "varPath", varPathDefault, "path to store serviced data")

	flag.StringVar(&options.keyPEMFile, "keyfile", "", "path to private key file (defaults to compiled in private key)")
	flag.StringVar(&options.certPEMFile, "certfile", "", "path to public certificate file (defaults to compiled in public cert)")
	options.zookeepers = make(ListOpts, 0)
	flag.Var(&options.zookeepers, "zk", "Specify a zookeeper instance to connect to (e.g. -zk localhost:2181 )")
	flag.BoolVar(&options.repstats, "reportstats", true, "report container statistics")
	flag.StringVar(&options.statshost, "statshost", "127.0.0.1:8443", "host:port for container statistics")
	flag.IntVar(&options.statsperiod, "statsperiod", 5, "Period (minutes) for container statistics reporting")
	flag.StringVar(&options.mcusername, "mcusername", "scott", "Username for the Zenoss metric consumer")
	flag.StringVar(&options.mcpasswd, "mcpasswd", "tiger", "Password for the Zenoss metric consumer")
	options.mount = make(ListOpts, 0)
	flag.Var(&options.mount, "mount", "bind mount: container_image:host_path:container_path (e.g. -mount zenoss/zenoss5x:/home/zenoss/zenhome/zenoss/Products/:/opt/zenoss/Products/)")
	flag.StringVar(&options.vfs, "vfs", "rsync", "file system for container volumes")
	flag.StringVar(&options.hostaliases, "hostaliases", "", "list of aliases for this host, e.g., localhost:goldmine:goldmine.net")

	flag.IntVar(&options.esStartupTimeout, "esStartupTimeout", getEnvVarInt("ES_STARTUP_TIMEOUT", 600), "time to wait on elasticsearch startup before bailing")

	flag.Usage = func() {
		flag.PrintDefaults()
	}
}

func compareVersion(a, b []int) int {
	astr := ""
	for _, s := range a {
		astr += fmt.Sprintf("%12d", s)
	}
	bstr := ""
	for _, s := range b {
		bstr += fmt.Sprintf("%12d", s)
	}
	if astr > bstr {
		return -1
	}
	if astr < bstr {
		return 1
	}
	return 0
}

// Start the agent or master services on this host.
func startServer() {
	l, err := net.Listen("tcp", options.listen)
	if err != nil {
		glog.Fatalf("Could not bind to port %v. Is another instance running", err)
	}

	isvcs.Init()
	isvcs.Mgr.SetVolumesDir(options.varPath + "/isvcs")

	dockerVersion, err := serviced.GetDockerVersion()
	if err != nil {
		glog.Fatalf("Could not determine docker version: %s", err)
	}

	atLeast := []int{0, 7, 5}
	atMost := []int{0, 8, 1}
	if compareVersion(atLeast, dockerVersion.Client) < 0 || compareVersion(atMost, dockerVersion.Client) > 0 {
		glog.Fatal("serviced needs at least docker >= 0.7.5 or <= 0.8.1 but not 0.8.0")
	}
	if compareVersion([]int{0, 8, 0}, dockerVersion.Client) == 0 {
		glog.Fatal("serviced specifically does not support docker 0.8.0")

	}

	if _, ok := volume.Registered(options.vfs); !ok {
		glog.Fatalf("no driver registered for %s", options.vfs)
	}

	if options.master {
		var master dao.ControlPlane
		var err error
		master, err = elasticsearch.NewControlSvc("localhost", 9200, options.zookeepers, options.varPath, options.vfs)

		if err != nil {
			glog.Fatalf("Could not start ControlPlane service: %v", err)
		}
		// register the API
		glog.V(0).Infoln("registering ControlPlane service")
		rpc.RegisterName("LoadBalancer", master)
		rpc.RegisterName("ControlPlane", master)

		// TODO: Make bind port for web server optional?
		cpserver := web.NewServiceConfig(":8787", options.port, options.zookeepers, options.repstats, options.hostaliases)
		go cpserver.ServeUI()
		go cpserver.Serve()
	}
	if options.agent {
		mux := serviced.TCPMux{}

		mux.CertPEMFile = options.certPEMFile
		mux.KeyPEMFile = options.keyPEMFile
		mux.Enabled = true
		mux.Port = options.muxPort
		mux.UseTLS = options.tls

		agent, err := serviced.NewHostAgent(options.port, options.varPath, options.mount, options.vfs, options.zookeepers, mux)
		if err != nil {
			glog.Fatalf("Could not start ControlPlane agent: %v", err)
		}
		// register the API
		glog.V(0).Infoln("registering ControlPlaneAgent service")
		rpc.RegisterName("ControlPlaneAgent", agent)

		go func() {
			signalChan := make(chan os.Signal, 10)
			signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
			<-signalChan
			glog.V(0).Info("Shutting down due to interrupt")
			err = agent.Shutdown()
			if err != nil {
				glog.V(1).Infof("Agent shutdown with error: %v", err)
			}
			isvcs.Mgr.Stop()
			os.Exit(0)
		}()

		// TODO: Integrate this server into the rpc server, or something.
		// Currently its only use is for command execution.
		go func() {
			sio := shell.NewProcessExecutorServer(options.port)
			http.ListenAndServe(":50000", sio)
		}()
	}

	rpc.HandleHTTP()

	if options.repstats {
		statsdest := fmt.Sprintf("http://%s/api/metrics/store", options.statshost)
		sr := StatsReporter{statsdest, options.mcusername, options.mcpasswd}

		glog.V(1).Infoln("Staring container statistics reporter")
		statsduration := time.Duration(options.statsperiod) * time.Minute
		go sr.Report(statsduration)
	}

	glog.V(0).Infof("Listening on %s", l.Addr().String())
	http.Serve(l, nil) // start the server
}

// main entry point of the product
func main() {

	// parse the command line flags
	flag.Parse()
	ensureMinimumInt("ES_STARTUP_TIMEOUT", "esStartupTimeout", 30)

	// are we in server mode
	if (options.master || options.agent) && len(flag.Args()) == 0 {
		startServer()
	} else {
		// we are in command line mode
		if len(flag.Args()) == 0 {
			// no arguments were give, show help
			cli := ServicedCli{}
			cli.CmdHelp(flag.Args()...)
			flag.Usage()
		} else {
			ParseCommands(flag.Args()...)
		}
	}
	glog.Flush()
}
<commit_msg>return errors to cli<commit_after>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

// Package agent implements a service that runs on a serviced node. It is
// responsible for ensuring that a particular node is running the correct services
// and reporting the state and health of those services back to the master
// serviced.

package main

// This is the main entry point for the application. Here we parse command line
// flags and either start a service or execute command line functions.

//svc "github.com/zenoss/serviced/svc"
import (
	"github.com/zenoss/serviced"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/dao/elasticsearch"
	"github.com/zenoss/serviced/isvcs"
	"github.com/zenoss/serviced/shell"
	"github.com/zenoss/serviced/volume"
	_ "github.com/zenoss/serviced/volume/btrfs"
	_ "github.com/zenoss/serviced/volume/rsync"
	"github.com/zenoss/serviced/web"

	"flag"
	"fmt"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"os/signal"
	"os/user"
	"path"
	"strconv"
	"syscall"
	"time"

	"github.com/zenoss/glog"
)

// Store the command line options
var options struct {
	port             string
	listen           string
	master           bool
	agent            bool
	muxPort          int
	tls              bool
	keyPEMFile       string
	certPEMFile      string
	varPath          string // Directory to store data, eg isvcs & service volumes
	resourcePath     string
	zookeepers       ListOpts
	repstats         bool
	statshost        string
	statsperiod      int
	mcusername       string
	mcpasswd         string
	mount            ListOpts
	resourceperiod   int
	vfs              string
	esStartupTimeout int
	hostaliases      string
}

var agentIP string

// getEnvVarInt() returns the env var as an int value or the defaultValue if env var is unset
func getEnvVarInt(envVar string, defaultValue int) int {
	envVarValue := os.Getenv(envVar)
	if len(envVarValue) > 0 {
		if value, err := strconv.Atoi(envVarValue); err != nil {
			glog.Errorf("Could not convert env var %s:%s to integer, error:%s", envVar, envVarValue, err)
			return defaultValue
		} else {
			return value
		}
	}
	return defaultValue
}

// ensureMinimumInt sets the env var and command line flag to the given minimum if the value is less than the minimum
func ensureMinimumInt(envVar string, flagName string, minimum int) {
	theFlag := flag.Lookup(flagName)
	value, _ := strconv.Atoi(theFlag.Value.String())
	if value < minimum {
		glog.Infof("overriding flag %s:%s with minimum value of %v", flagName, theFlag.Value.String(), minimum)
		valueStr := strconv.Itoa(minimum)
		os.Setenv(envVar, valueStr)
		flag.Set(flagName, valueStr)
	} else {
		os.Setenv(envVar, theFlag.Value.String())
	}
}

// Setup flag options (static block)
func init() {
	var err error
	agentIP, err = serviced.GetIPAddress()
	if err != nil {
		panic(err)
	}

	flag.StringVar(&options.port, "port", agentIP+":4979", "port for remote serviced (example.com:8080)")
	flag.StringVar(&options.listen, "listen", ":4979", "port for local serviced (example.com:8080)")
	flag.BoolVar(&options.master, "master", false, "run in master mode, ie the control plane service")
	flag.BoolVar(&options.agent, "agent", false, "run in agent mode, ie a host in a resource pool")
	flag.IntVar(&options.muxPort, "muxport", 22250, "multiplexing port to use")
	flag.BoolVar(&options.tls, "tls", true, "enable TLS")

	varPathDefault := path.Join(os.TempDir(), "serviced")
	if len(os.Getenv("SERVICED_HOME")) > 0 {
		varPathDefault = path.Join(os.Getenv("SERVICED_HOME"), "var")
	} else {
		if user, err := user.Current(); err == nil {
			varPathDefault = path.Join(os.TempDir(), "serviced-"+user.Username, "var")
		}
	}
	flag.StringVar(&options.varPath, "varPath", varPathDefault, "path to store serviced data")

	flag.StringVar(&options.keyPEMFile, "keyfile", "", "path to private key file (defaults to compiled in private key)")
	flag.StringVar(&options.certPEMFile, "certfile", "", "path to public certificate file (defaults to compiled in public cert)")
	options.zookeepers = make(ListOpts, 0)
	flag.Var(&options.zookeepers, "zk", "Specify a zookeeper instance to connect to (e.g. -zk localhost:2181 )")
	flag.BoolVar(&options.repstats, "reportstats", true, "report container statistics")
	flag.StringVar(&options.statshost, "statshost", "127.0.0.1:8443", "host:port for container statistics")
	flag.IntVar(&options.statsperiod, "statsperiod", 5, "Period (minutes) for container statistics reporting")
	flag.StringVar(&options.mcusername, "mcusername", "scott", "Username for the Zenoss metric consumer")
	flag.StringVar(&options.mcpasswd, "mcpasswd", "tiger", "Password for the Zenoss metric consumer")
	options.mount = make(ListOpts, 0)
	flag.Var(&options.mount, "mount", "bind mount: container_image:host_path:container_path (e.g. -mount zenoss/zenoss5x:/home/zenoss/zenhome/zenoss/Products/:/opt/zenoss/Products/)")
	flag.StringVar(&options.vfs, "vfs", "rsync", "file system for container volumes")
	flag.StringVar(&options.hostaliases, "hostaliases", "", "list of aliases for this host, e.g., localhost:goldmine:goldmine.net")

	flag.IntVar(&options.esStartupTimeout, "esStartupTimeout", getEnvVarInt("ES_STARTUP_TIMEOUT", 600), "time to wait on elasticsearch startup before bailing")

	flag.Usage = func() {
		flag.PrintDefaults()
	}
}

func compareVersion(a, b []int) int {
	astr := ""
	for _, s := range a {
		astr += fmt.Sprintf("%12d", s)
	}
	bstr := ""
	for _, s := range b {
		bstr += fmt.Sprintf("%12d", s)
	}
	if astr > bstr {
		return -1
	}
	if astr < bstr {
		return 1
	}
	return 0
}

// Start the agent or master services on this host.
func startServer() {
	l, err := net.Listen("tcp", options.listen)
	if err != nil {
		glog.Fatalf("Could not bind to port %v. Is another instance running", err)
	}

	isvcs.Init()
	isvcs.Mgr.SetVolumesDir(options.varPath + "/isvcs")

	dockerVersion, err := serviced.GetDockerVersion()
	if err != nil {
		glog.Fatalf("Could not determine docker version: %s", err)
	}

	atLeast := []int{0, 7, 5}
	atMost := []int{0, 8, 1}
	if compareVersion(atLeast, dockerVersion.Client) < 0 || compareVersion(atMost, dockerVersion.Client) > 0 {
		glog.Fatal("serviced needs at least docker >= 0.7.5 or <= 0.8.1 but not 0.8.0")
	}
	if compareVersion([]int{0, 8, 0}, dockerVersion.Client) == 0 {
		glog.Fatal("serviced specifically does not support docker 0.8.0")

	}

	if _, ok := volume.Registered(options.vfs); !ok {
		glog.Fatalf("no driver registered for %s", options.vfs)
	}

	if options.master {
		var master dao.ControlPlane
		var err error
		master, err = elasticsearch.NewControlSvc("localhost", 9200, options.zookeepers, options.varPath, options.vfs)

		if err != nil {
			glog.Fatalf("Could not start ControlPlane service: %v", err)
		}
		// register the API
		glog.V(0).Infoln("registering ControlPlane service")
		rpc.RegisterName("LoadBalancer", master)
		rpc.RegisterName("ControlPlane", master)

		// TODO: Make bind port for web server optional?
		cpserver := web.NewServiceConfig(":8787", options.port, options.zookeepers, options.repstats, options.hostaliases)
		go cpserver.ServeUI()
		go cpserver.Serve()
	}
	if options.agent {
		mux := serviced.TCPMux{}

		mux.CertPEMFile = options.certPEMFile
		mux.KeyPEMFile = options.keyPEMFile
		mux.Enabled = true
		mux.Port = options.muxPort
		mux.UseTLS = options.tls

		agent, err := serviced.NewHostAgent(options.port, options.varPath, options.mount, options.vfs, options.zookeepers, mux)
		if err != nil {
			glog.Fatalf("Could not start ControlPlane agent: %v", err)
		}
		// register the API
		glog.V(0).Infoln("registering ControlPlaneAgent service")
		rpc.RegisterName("ControlPlaneAgent", agent)

		go func() {
			signalChan := make(chan os.Signal, 10)
			signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
			<-signalChan
			glog.V(0).Info("Shutting down due to interrupt")
			err = agent.Shutdown()
			if err != nil {
				glog.V(1).Infof("Agent shutdown with error: %v", err)
			}
			isvcs.Mgr.Stop()
			os.Exit(0)
		}()

		// TODO: Integrate this server into the rpc server, or something.
		// Currently its only use is for command execution.
		go func() {
			sio := shell.NewProcessExecutorServer(options.port)
			http.ListenAndServe(":50000", sio)
		}()
	}

	rpc.HandleHTTP()

	if options.repstats {
		statsdest := fmt.Sprintf("http://%s/api/metrics/store", options.statshost)
		sr := StatsReporter{statsdest, options.mcusername, options.mcpasswd}

		glog.V(1).Infoln("Staring container statistics reporter")
		statsduration := time.Duration(options.statsperiod) * time.Minute
		go sr.Report(statsduration)
	}

	glog.V(0).Infof("Listening on %s", l.Addr().String())
	http.Serve(l, nil) // start the server
}

// main entry point of the product
func main() {

	// parse the command line flags
	flag.Parse()
	ensureMinimumInt("ES_STARTUP_TIMEOUT", "esStartupTimeout", 30)

	// are we in server mode
	if (options.master || options.agent) && len(flag.Args()) == 0 {
		startServer()
	} else {
		// we are in command line mode
		if len(flag.Args()) == 0 {
			// no arguments were give, show help
			cli := ServicedCli{}
			cli.CmdHelp(flag.Args()...)
			flag.Usage()
		} else {
			if err := ParseCommands(flag.Args()...); err != nil {
				glog.Fatalf("%s", err)
			}
		}
	}
	glog.Flush()
}
<|endoftext|>


<commit_before>package buildinfo

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"sort"
	"strings"

	"github.com/docker/distribution/reference"
	"github.com/moby/buildkit/source"
	binfotypes "github.com/moby/buildkit/util/buildinfo/types"
	"github.com/moby/buildkit/util/urlutil"
	"github.com/pkg/errors"
)

// Decode decodes a base64 encoded build info.
func Decode(enc string) (bi binfotypes.BuildInfo, _ error) {
	dec, err := base64.StdEncoding.DecodeString(enc)
	if err != nil {
		return bi, err
	}
	err = json.Unmarshal(dec, &bi)
	return bi, err
}

// Encode encodes build info.
func Encode(ctx context.Context, buildInfo []byte, buildSources map[string]string) ([]byte, error) {
	var bi binfotypes.BuildInfo
	if buildInfo != nil {
		if err := json.Unmarshal(buildInfo, &bi); err != nil {
			return nil, err
		}
	}
	msources, err := mergeSources(ctx, buildSources, bi.Sources)
	if err != nil {
		return nil, err
	}
	return json.Marshal(binfotypes.BuildInfo{
		Frontend: bi.Frontend,
		Attrs:    filterAttrs(bi.Attrs),
		Sources:  msources,
	})
}

// mergeSources combines and fixes build sources from frontend sources.
func mergeSources(ctx context.Context, buildSources map[string]string, frontendSources []binfotypes.Source) ([]binfotypes.Source, error) {
	// Iterate and combine build sources
	mbs := map[string]binfotypes.Source{}
	for buildSource, pin := range buildSources {
		src, err := source.FromString(buildSource)
		if err != nil {
			return nil, err
		}
		switch sourceID := src.(type) {
		case *source.ImageIdentifier:
			for i, fsrc := range frontendSources {
				// use original user input from frontend sources
				if fsrc.Type == binfotypes.SourceTypeDockerImage && fsrc.Alias == sourceID.Reference.String() {
					if _, ok := mbs[fsrc.Alias]; !ok {
						parsed, err := reference.ParseNormalizedNamed(fsrc.Ref)
						if err != nil {
							return nil, errors.Wrapf(err, "failed to parse %s", fsrc.Ref)
						}
						mbs[fsrc.Alias] = binfotypes.Source{
							Type: binfotypes.SourceTypeDockerImage,
							Ref:  reference.TagNameOnly(parsed).String(),
							Pin:  pin,
						}
						frontendSources = append(frontendSources[:i], frontendSources[i+1:]...)
					}
					break
				}
			}
			if _, ok := mbs[sourceID.Reference.String()]; !ok {
				mbs[sourceID.Reference.String()] = binfotypes.Source{
					Type: binfotypes.SourceTypeDockerImage,
					Ref:  sourceID.Reference.String(),
					Pin:  pin,
				}
			}
		case *source.GitIdentifier:
			sref := sourceID.Remote
			if len(sourceID.Ref) > 0 {
				sref += "#" + sourceID.Ref
			}
			if len(sourceID.Subdir) > 0 {
				sref += ":" + sourceID.Subdir
			}
			if _, ok := mbs[sref]; !ok {
				mbs[sref] = binfotypes.Source{
					Type: binfotypes.SourceTypeGit,
					Ref:  urlutil.RedactCredentials(sref),
					Pin:  pin,
				}
			}
		case *source.HTTPIdentifier:
			if _, ok := mbs[sourceID.URL]; !ok {
				mbs[sourceID.URL] = binfotypes.Source{
					Type: binfotypes.SourceTypeHTTP,
					Ref:  urlutil.RedactCredentials(sourceID.URL),
					Pin:  pin,
				}
			}
		}
	}

	// leftover sources in frontend. Mostly duplicated ones we don't need but
	// there is an edge case if no instruction except sources one is defined
	// (e.g. FROM ...) that can be valid so take it into account.
	for _, fsrc := range frontendSources {
		if fsrc.Type != binfotypes.SourceTypeDockerImage {
			continue
		}
		if _, ok := mbs[fsrc.Alias]; !ok {
			parsed, err := reference.ParseNormalizedNamed(fsrc.Ref)
			if err != nil {
				return nil, errors.Wrapf(err, "failed to parse %s", fsrc.Ref)
			}
			mbs[fsrc.Alias] = binfotypes.Source{
				Type: binfotypes.SourceTypeDockerImage,
				Ref:  reference.TagNameOnly(parsed).String(),
				Pin:  fsrc.Pin,
			}
		}
	}

	srcs := make([]binfotypes.Source, 0, len(mbs))
	for _, bs := range mbs {
		srcs = append(srcs, bs)
	}
	sort.Slice(srcs, func(i, j int) bool {
		return srcs[i].Ref < srcs[j].Ref
	})

	return srcs, nil
}

// FormatOpts holds build info format options.
type FormatOpts struct {
	RemoveAttrs bool
}

// Format formats build info.
func Format(dt []byte, format FormatOpts) (_ []byte, err error) {
	if len(dt) == 0 {
		return dt, nil
	}
	var bi binfotypes.BuildInfo
	if err := json.Unmarshal(dt, &bi); err != nil {
		return nil, errors.Wrap(err, "failed to unmarshal buildinfo for formatting")
	}
	if format.RemoveAttrs {
		bi.Attrs = nil
	}
	if dt, err = json.Marshal(bi); err != nil {
		return nil, err
	}
	return dt, nil
}

var knownAttrs = []string{
	//"cmdline",
	"context",
	"filename",
	"source",

	//"add-hosts",
	//"cgroup-parent",
	//"force-network-mode",
	//"hostname",
	//"image-resolve-mode",
	//"platform",
	"shm-size",
	"target",
	"ulimit",
}

// filterAttrs filters frontent opt by picking only those that
// could effectively change the build result.
func filterAttrs(attrs map[string]*string) map[string]*string {
	filtered := make(map[string]*string)
	for k, v := range attrs {
		if v == nil {
			continue
		}
		// Control args are filtered out
		if isControlArg(k) {
			continue
		}
		// Always include args and labels
		if strings.HasPrefix(k, "build-arg:") || strings.HasPrefix(k, "label:") {
			filtered[k] = v
			continue
		}
		// Filter only for known attributes
		for _, knownAttr := range knownAttrs {
			if knownAttr == k {
				filtered[k] = v
				break
			}
		}
	}
	return filtered
}

var knownControlArgs = []string{
	"BUILDKIT_CACHE_MOUNT_NS",
	"BUILDKIT_CONTEXT_KEEP_GIT_DIR",
	"BUILDKIT_INLINE_BUILDINFO_ATTRS",
	"BUILDKIT_INLINE_CACHE",
	"BUILDKIT_MULTI_PLATFORM",
	"BUILDKIT_SANDBOX_HOSTNAME",
	"BUILDKIT_SYNTAX",
}

// isControlArg checks if a build attributes is a control arg
func isControlArg(attrKey string) bool {
	for _, k := range knownControlArgs {
		if strings.HasPrefix(attrKey, "build-arg:"+k) {
			return true
		}
	}
	return false
}

// GetMetadata returns buildinfo metadata for the specified key. If the key
// is already there, result will be merged.
func GetMetadata(metadata map[string][]byte, key string, reqFrontend string, reqAttrs map[string]string) ([]byte, error) {
	var (
		dtbi []byte
		err  error
	)
	if v, ok := metadata[key]; ok {
		var mbi binfotypes.BuildInfo
		if errm := json.Unmarshal(v, &mbi); errm != nil {
			return nil, errors.Wrapf(errm, "failed to unmarshal build info for %q", key)
		}
		if reqFrontend != "" {
			mbi.Frontend = reqFrontend
		}
		mbi.Attrs = convertMap(reduceMap(reqAttrs, mbi.Attrs))
		dtbi, err = json.Marshal(mbi)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to marshal build info for %q", key)
		}
	} else {
		dtbi, err = json.Marshal(binfotypes.BuildInfo{
			Frontend: reqFrontend,
			Attrs:    convertMap(reqAttrs),
		})
		if err != nil {
			return nil, errors.Wrapf(err, "failed to marshal build info for %q", key)
		}
	}
	return dtbi, nil
}

func reduceMap(m1 map[string]string, m2 map[string]*string) map[string]string {
	if m1 == nil && m2 == nil {
		return nil
	}
	if m1 == nil {
		m1 = map[string]string{}
	}
	for k, v := range m2 {
		if v != nil {
			m1[k] = *v
		}
	}
	return m1
}

func convertMap(m map[string]string) map[string]*string {
	res := make(map[string]*string)
	for k, v := range m {
		value := v
		res[k] = &value
	}
	return res
}
<commit_msg>buildinfo: named input contexts support<commit_after>package buildinfo

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"sort"
	"strings"

	"github.com/docker/distribution/reference"
	"github.com/moby/buildkit/source"
	binfotypes "github.com/moby/buildkit/util/buildinfo/types"
	"github.com/moby/buildkit/util/urlutil"
	"github.com/pkg/errors"
)

// Decode decodes a base64 encoded build info.
func Decode(enc string) (bi binfotypes.BuildInfo, _ error) {
	dec, err := base64.StdEncoding.DecodeString(enc)
	if err != nil {
		return bi, err
	}
	err = json.Unmarshal(dec, &bi)
	return bi, err
}

// Encode encodes build info.
func Encode(ctx context.Context, buildInfo []byte, buildSources map[string]string) ([]byte, error) {
	var bi binfotypes.BuildInfo
	if buildInfo != nil {
		if err := json.Unmarshal(buildInfo, &bi); err != nil {
			return nil, err
		}
	}
	msources, err := mergeSources(ctx, buildSources, bi.Sources)
	if err != nil {
		return nil, err
	}
	return json.Marshal(binfotypes.BuildInfo{
		Frontend: bi.Frontend,
		Attrs:    filterAttrs(bi.Attrs),
		Sources:  msources,
	})
}

// mergeSources combines and fixes build sources from frontend sources.
func mergeSources(ctx context.Context, buildSources map[string]string, frontendSources []binfotypes.Source) ([]binfotypes.Source, error) {
	// Iterate and combine build sources
	mbs := map[string]binfotypes.Source{}
	for buildSource, pin := range buildSources {
		src, err := source.FromString(buildSource)
		if err != nil {
			return nil, err
		}
		switch sourceID := src.(type) {
		case *source.ImageIdentifier:
			for i, fsrc := range frontendSources {
				// use original user input from frontend sources
				if fsrc.Type == binfotypes.SourceTypeDockerImage && fsrc.Alias == sourceID.Reference.String() {
					if _, ok := mbs[fsrc.Alias]; !ok {
						parsed, err := reference.ParseNormalizedNamed(fsrc.Ref)
						if err != nil {
							return nil, errors.Wrapf(err, "failed to parse %s", fsrc.Ref)
						}
						mbs[fsrc.Alias] = binfotypes.Source{
							Type: binfotypes.SourceTypeDockerImage,
							Ref:  reference.TagNameOnly(parsed).String(),
							Pin:  pin,
						}
						frontendSources = append(frontendSources[:i], frontendSources[i+1:]...)
					}
					break
				}
			}
			if _, ok := mbs[sourceID.Reference.String()]; !ok {
				mbs[sourceID.Reference.String()] = binfotypes.Source{
					Type: binfotypes.SourceTypeDockerImage,
					Ref:  sourceID.Reference.String(),
					Pin:  pin,
				}
			}
		case *source.GitIdentifier:
			sref := sourceID.Remote
			if len(sourceID.Ref) > 0 {
				sref += "#" + sourceID.Ref
			}
			if len(sourceID.Subdir) > 0 {
				sref += ":" + sourceID.Subdir
			}
			if _, ok := mbs[sref]; !ok {
				mbs[sref] = binfotypes.Source{
					Type: binfotypes.SourceTypeGit,
					Ref:  urlutil.RedactCredentials(sref),
					Pin:  pin,
				}
			}
		case *source.HTTPIdentifier:
			if _, ok := mbs[sourceID.URL]; !ok {
				mbs[sourceID.URL] = binfotypes.Source{
					Type: binfotypes.SourceTypeHTTP,
					Ref:  urlutil.RedactCredentials(sourceID.URL),
					Pin:  pin,
				}
			}
		}
	}

	// leftover sources in frontend. Mostly duplicated ones we don't need but
	// there is an edge case if no instruction except sources one is defined
	// (e.g. FROM ...) that can be valid so take it into account.
	for _, fsrc := range frontendSources {
		if fsrc.Type != binfotypes.SourceTypeDockerImage {
			continue
		}
		if _, ok := mbs[fsrc.Alias]; !ok {
			parsed, err := reference.ParseNormalizedNamed(fsrc.Ref)
			if err != nil {
				return nil, errors.Wrapf(err, "failed to parse %s", fsrc.Ref)
			}
			mbs[fsrc.Alias] = binfotypes.Source{
				Type: binfotypes.SourceTypeDockerImage,
				Ref:  reference.TagNameOnly(parsed).String(),
				Pin:  fsrc.Pin,
			}
		}
	}

	srcs := make([]binfotypes.Source, 0, len(mbs))
	for _, bs := range mbs {
		srcs = append(srcs, bs)
	}
	sort.Slice(srcs, func(i, j int) bool {
		return srcs[i].Ref < srcs[j].Ref
	})

	return srcs, nil
}

// FormatOpts holds build info format options.
type FormatOpts struct {
	RemoveAttrs bool
}

// Format formats build info.
func Format(dt []byte, format FormatOpts) (_ []byte, err error) {
	if len(dt) == 0 {
		return dt, nil
	}
	var bi binfotypes.BuildInfo
	if err := json.Unmarshal(dt, &bi); err != nil {
		return nil, errors.Wrap(err, "failed to unmarshal buildinfo for formatting")
	}
	if format.RemoveAttrs {
		bi.Attrs = nil
	}
	if dt, err = json.Marshal(bi); err != nil {
		return nil, err
	}
	return dt, nil
}

var knownAttrs = []string{
	//"cmdline",
	"context",
	"filename",
	"source",

	//"add-hosts",
	//"cgroup-parent",
	//"force-network-mode",
	//"hostname",
	//"image-resolve-mode",
	//"platform",
	"shm-size",
	"target",
	"ulimit",
}

// filterAttrs filters frontent opt by picking only those that
// could effectively change the build result.
func filterAttrs(attrs map[string]*string) map[string]*string {
	filtered := make(map[string]*string)
	for k, v := range attrs {
		if v == nil {
			continue
		}
		// Control args are filtered out
		if isControlArg(k) {
			continue
		}
		// Always include
		if strings.HasPrefix(k, "build-arg:") || strings.HasPrefix(k, "context:") || strings.HasPrefix(k, "label:") {
			filtered[k] = v
			continue
		}
		// Filter only for known attributes
		for _, knownAttr := range knownAttrs {
			if knownAttr == k {
				filtered[k] = v
				break
			}
		}
	}
	return filtered
}

var knownControlArgs = []string{
	"BUILDKIT_CACHE_MOUNT_NS",
	"BUILDKIT_CONTEXT_KEEP_GIT_DIR",
	"BUILDKIT_INLINE_BUILDINFO_ATTRS",
	"BUILDKIT_INLINE_CACHE",
	"BUILDKIT_MULTI_PLATFORM",
	"BUILDKIT_SANDBOX_HOSTNAME",
	"BUILDKIT_SYNTAX",
}

// isControlArg checks if a build attributes is a control arg
func isControlArg(attrKey string) bool {
	for _, k := range knownControlArgs {
		if strings.HasPrefix(attrKey, "build-arg:"+k) {
			return true
		}
	}
	return false
}

// GetMetadata returns buildinfo metadata for the specified key. If the key
// is already there, result will be merged.
func GetMetadata(metadata map[string][]byte, key string, reqFrontend string, reqAttrs map[string]string) ([]byte, error) {
	var (
		dtbi []byte
		err  error
	)
	if v, ok := metadata[key]; ok {
		var mbi binfotypes.BuildInfo
		if errm := json.Unmarshal(v, &mbi); errm != nil {
			return nil, errors.Wrapf(errm, "failed to unmarshal build info for %q", key)
		}
		if reqFrontend != "" {
			mbi.Frontend = reqFrontend
		}
		mbi.Attrs = convertMap(reduceMap(reqAttrs, mbi.Attrs))
		dtbi, err = json.Marshal(mbi)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to marshal build info for %q", key)
		}
	} else {
		dtbi, err = json.Marshal(binfotypes.BuildInfo{
			Frontend: reqFrontend,
			Attrs:    convertMap(reqAttrs),
		})
		if err != nil {
			return nil, errors.Wrapf(err, "failed to marshal build info for %q", key)
		}
	}
	return dtbi, nil
}

func reduceMap(m1 map[string]string, m2 map[string]*string) map[string]string {
	if m1 == nil && m2 == nil {
		return nil
	}
	if m1 == nil {
		m1 = map[string]string{}
	}
	for k, v := range m2 {
		if v != nil {
			m1[k] = *v
		}
	}
	return m1
}

func convertMap(m map[string]string) map[string]*string {
	res := make(map[string]*string)
	for k, v := range m {
		value := v
		res[k] = &value
	}
	return res
}
<|endoftext|>


<commit_before>// Package passenger revolves around the User associated with the
// request in flight.
//
// It establishes this link via the HTTP authorization header,
// which can be used to authenticate with basic authorization or
// token based authorization.
package passenger

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/subtle"
	"encoding/binary"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"fmt"
	mathrand "math/rand"
	"net/http"
	"strings"
	"time"

	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/memcache"

	"github.com/coduno/api/model"
	"github.com/coduno/api/util/password"

	"golang.org/x/crypto/bcrypt"
	"golang.org/x/net/context"

	// Indirect import of SHA2, will be called by crypto.Hash.New()
	_ "crypto/sha256"
	_ "crypto/sha512"

	// Indirect import of SHA3, will be called by crypto.Hash.New()
	_ "golang.org/x/crypto/sha3"
)

const maxValidity = time.Hour * 24 * 365 * 2
const minValidity = time.Minute * 30
const defaultValidity = time.Hour * 24 * 14

const defaultHash = crypto.SHA3_256

const tokenLength = 8

var kinds = [2]string{model.UserKind, model.TokenKind}

var endianness = binary.LittleEndian

type key int64

var passengerKey key

// ErrNoAuthHeader signals that the request did not carry an Authorization
// header.
var ErrNoAuthHeader = errors.New("passenger: no authorization header present")

// ErrUnkAuthHeader is returned if the request contained an Authorization
// header, but it could not be parsed. The two accepted authorization
// methods are "Basic" and "Token".
var ErrUnkAuthHeader = errors.New("passenger: cannot interpret authorization header")

// ErrDigestMismatch is returned if the token provided by the user did not
// result in the same digest value after hashing as the one stored on the
// server.
var ErrDigestMismatch = errors.New("passenger: digest mismatch")

// ErrTokenNotAssociated can be returned if this package encounters invalid
// data during authentication. A Token must be child of a User.
var ErrTokenNotAssociated = errors.New("passenger: token not associated to any user")

// ErrTokenExpired is returned if the token is not valid anymore.
var ErrTokenExpired = errors.New("passenger: token expired")

// ErrTokenNotMatchingUser is returned if basic auth with token password
// was attempted, and the token could be found but does not match the user.
type ErrTokenNotMatchingUser struct {
	Parent, Actual *datastore.Key
}

func (e ErrTokenNotMatchingUser) Error() string {
	return fmt.Sprintf("passenger: parent of token %+v does not match with actual user %+v", e.Parent, e.Actual)
}

func init() {
	passengerKey = key(mathrand.Int63())
}

// Passenger holds the currently authenticated user
// together with the token (if relevant).
type Passenger struct {
	User  *datastore.Key
	Token *model.Token
}

// HasScope can be used to check whether this Passenger was
// granted access to a given scope.
// Note that a user authenticated via username and password
// (not via token) will have access to all scopes by
// default.
func (p *Passenger) HasScope(scope string) (has bool) {
	for _, grantedScope := range p.Token.Scopes {
		if scope == grantedScope {
			return true
		}
	}
	return
}

// Save will persist the passenger to Datastore and send it to Memcache.
func (p *Passenger) Save(ctx context.Context) (*datastore.Key, error) {
	now := time.Now()

	key, err := p.Token.PutWithParent(ctx, p.User)
	if err != nil {
		return nil, err
	}

	buf := new(bytes.Buffer)
	if err = gob.NewEncoder(buf).Encode(p); err != nil {
		return nil, err
	}

	item := &memcache.Item{
		Key:        key.Encode(),
		Value:      buf.Bytes(),
		Expiration: p.Token.Expiry.Sub(now) + 10*time.Second,
	}

	if err = memcache.Set(ctx, item); err != nil {
		return nil, err
	}

	return key, nil
}

func (p *Passenger) check(raw []byte) error {
	digest := crypto.Hash(p.Token.Hash).New().Sum(raw)

	if subtle.ConstantTimeCompare(digest, p.Token.Digest) != 1 {
		return ErrDigestMismatch
	}

	if p.Token.Expiry.Before(time.Now()) {
		return ErrTokenExpired
	}

	return nil
}

// IssueToken creates a new Token for the authenticated user. Callers should
// prefill the Token with whatever values they like and leave zero values
// to be set.
// The generated token will also be persisted and can be handed to the client
// with no more handling.
func (p *Passenger) IssueToken(ctx context.Context, token *model.Token) (string, error) {
	now := time.Now()

	token.Creation = now

	if token.Expiry == (time.Time{}) {
		token.Expiry = now.Add(defaultValidity)
	} else {
		if token.Expiry.Before(now.Add(minValidity)) {
			return "", fmt.Errorf("passenger: token must be valid at least %s", minValidity)
		}

		if token.Expiry.After(now.Add(maxValidity)) {
			return "", fmt.Errorf("passenger: token must be valid at most %s", maxValidity)
		}
	}

	// TODO(flowlo): This will reject all scopes for now, as we are not using them.
	// As soon as we introduce scopes, this check must be rewritten accordingly.
	if len(token.Scopes) > 0 {
		return "", fmt.Errorf("passenger: unknown scopes: %s", strings.Join(token.Scopes, ", "))
	}

	if len(token.Description) > 512 || len(token.Description) < 4 {
		return "", fmt.Errorf("passenger: description has bad len %d", len(token.Description))
	}

	var raw [tokenLength]byte

	if _, err := rand.Read(raw[:]); err != nil {
		return "", err
	}

	if token.Hash == 0 {
		token.Hash = int(defaultHash)
	}
	token.Digest = crypto.Hash(token.Hash).New().Sum(raw[:])

	// TODO(flowlo): Set token.RemoteAddr.

	clone := Passenger{
		User:  p.User,
		Token: token,
	}

	key, err := clone.Save(ctx)
	if err != nil {
		return "", err
	}

	return encodeToken(key, &raw)
}

// FromToken tries do identify a Passenger by the token he gave us.
// It will look up the Token and consequently the corresponding User.
func FromToken(ctx context.Context, Token string) (*Passenger, error) {
	key, raw, err := decodeToken(ctx, Token)
	if err != nil {
		return nil, err
	}

	var p *Passenger
	if p, err = fromCache(ctx, key); err != nil {
		if p, err = fromDatastore(ctx, key); err != nil {
			return nil, err
		}
	}

	return p, p.check(raw)
}

func fromCache(ctx context.Context, key *datastore.Key) (*Passenger, error) {
	item, err := memcache.Get(ctx, key.Encode())
	if err != nil {
		return nil, err
	}
	p := new(Passenger)
	if err := gob.NewDecoder(bytes.NewReader(item.Value)).Decode(&p); err != nil {
		return nil, err
	}
	return p, nil
}

func fromDatastore(ctx context.Context, key *datastore.Key) (*Passenger, error) {
	p := &Passenger{
		Token: &model.Token{},
	}
	if err := datastore.Get(ctx, key, p.Token); err != nil {
		return nil, err
	}
	if p.User = key.Parent(); p.User == nil {
		return nil, ErrTokenNotAssociated
	}
	return p, nil
}

// FromBasicAuth tries do identify a Passenger by the token he gave us.
// It will look up the the user by username and try to match password.
func FromBasicAuth(ctx context.Context, username, pw string) (p *Passenger, err error) {
	p = new(Passenger)
	var user model.User
	p.User, err = model.NewQueryForUser().
		Filter("Nick =", username).
		Limit(1).
		Run(ctx).
		Next(&user)

	if err != nil {
		return nil, err
	}
	err = password.Check([]byte(pw), user.HashedPassword)

	// TODO(flowlo): Depending on bcrypt is very fragile. We
	// should encapsulate that.
	if err == bcrypt.ErrMismatchedHashAndPassword {
		userKey := p.User
		p, err = FromToken(ctx, pw)
		if err != nil {
			return
		}
		if !p.User.Equal(userKey) {
			return nil, ErrTokenNotMatchingUser{Parent: p.User, Actual: userKey}
		}
	}
	return
}

// FromRequest inspects the HTTP Authorization header of the given request
// and tries to identify a passenger.
func FromRequest(ctx context.Context, r *http.Request) (*Passenger, error) {
	cookie, err := r.Cookie("token")
	if err != nil && err != http.ErrNoCookie {
		return nil, err
	}
	if err == nil {
		return FromToken(ctx, cookie.Value)
	}

	auth := ""
	if auth = r.Header.Get("Authorization"); auth == "" {
		return nil, ErrNoAuthHeader
	}

	if strings.HasPrefix(auth, "Token ") {
		return FromToken(ctx, auth[6:])
	}

	username, password, ok := "", "", false
	if username, password, ok = r.BasicAuth(); !ok {
		return nil, ErrUnkAuthHeader
	}

	return FromBasicAuth(ctx, username, password)
}

// FromContext returns the Passenger value stored in ctx if any.
func FromContext(ctx context.Context) (p *Passenger, ok bool) {
	p, ok = ctx.Value(passengerKey).(*Passenger)
	return
}

// NewContext returns a new Context that carries value p.
func NewContext(ctx context.Context, p *Passenger) context.Context {
	return context.WithValue(ctx, passengerKey, p)
}

// NewContextFromRequest wraps FromRequest and NewContext.
func NewContextFromRequest(ctx context.Context, r *http.Request) (context.Context, error) {
	p, err := FromRequest(ctx, r)
	if err != nil {
		return ctx, err
	}
	return NewContext(ctx, p), nil
}

// DecodeToken will take a token as sent by a client and translate it into a
// key to look up the full token on the server side and the raw secret.
func decodeToken(ctx context.Context, token string) (*datastore.Key, []byte, error) {
	b, err := hex.DecodeString(token)
	if err != nil {
		return nil, nil, err
	}

	if len(b) != len(kinds)*8+tokenLength {
		return nil, nil, errors.New("token length mismatch")
	}

	var intIDs [len(kinds)]int64
	for i := range intIDs {
		intID, n := binary.Varint(b[i*8 : (i+1)*8])
		if n < 8 {
			return nil, nil, errors.New("varint read mismatch")
		}
		intIDs[len(intIDs)-1-i] = intID
	}

	var key *datastore.Key
	for i := range intIDs {
		if intIDs[i] == 0 {
			continue
		}
		key = datastore.NewKey(ctx, kinds[i], "", intIDs[i], key)
		if key == nil {
			return nil, nil, errors.New("could not construct key from token")
		}
	}

	return key, b[len(kinds)*8:], nil
}

// EncodeToken translates the key and raw secret of a newly generated token to
// a form suitable for the client.
func encodeToken(key *datastore.Key, raw *[tokenLength]byte) (string, error) {
	// Buffer size will be 8 (size of an int64) times the number of keys
	// in the hirarchy plus the length of the raw token itself.
	var b [len(kinds)*8 + tokenLength]byte

	for i := range kinds {
		if n := binary.PutVarint(b[i*8:(i+1)*8], key.IntID()); n < 8 {
			return "", errors.New("short write when encoding token")
		}
		if key != nil {
			key = key.Parent()
		}
	}

	copy(b[len(kinds)*8:len(kinds)*8+tokenLength], raw[:])

	return hex.EncodeToString(b[:]), nil
}
<commit_msg>auth: Allow graceful fallback from bad cookie<commit_after>// Package passenger revolves around the User associated with the
// request in flight.
//
// It establishes this link via the HTTP authorization header,
// which can be used to authenticate with basic authorization or
// token based authorization.
package passenger

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/subtle"
	"encoding/binary"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"fmt"
	mathrand "math/rand"
	"net/http"
	"strings"
	"time"

	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/memcache"

	"github.com/coduno/api/model"
	"github.com/coduno/api/util/password"

	"golang.org/x/crypto/bcrypt"
	"golang.org/x/net/context"

	// Indirect import of SHA2, will be called by crypto.Hash.New()
	_ "crypto/sha256"
	_ "crypto/sha512"

	// Indirect import of SHA3, will be called by crypto.Hash.New()
	_ "golang.org/x/crypto/sha3"
)

const maxValidity = time.Hour * 24 * 365 * 2
const minValidity = time.Minute * 30
const defaultValidity = time.Hour * 24 * 14

const defaultHash = crypto.SHA3_256

const tokenLength = 8

var kinds = [2]string{model.UserKind, model.TokenKind}

var endianness = binary.LittleEndian

type key int64

var passengerKey key

// ErrNoAuthHeader signals that the request did not carry an Authorization
// header.
var ErrNoAuthHeader = errors.New("passenger: no authorization header present")

// ErrUnkAuthHeader is returned if the request contained an Authorization
// header, but it could not be parsed. The two accepted authorization
// methods are "Basic" and "Token".
var ErrUnkAuthHeader = errors.New("passenger: cannot interpret authorization header")

// ErrDigestMismatch is returned if the token provided by the user did not
// result in the same digest value after hashing as the one stored on the
// server.
var ErrDigestMismatch = errors.New("passenger: digest mismatch")

// ErrTokenNotAssociated can be returned if this package encounters invalid
// data during authentication. A Token must be child of a User.
var ErrTokenNotAssociated = errors.New("passenger: token not associated to any user")

// ErrTokenExpired is returned if the token is not valid anymore.
var ErrTokenExpired = errors.New("passenger: token expired")

// ErrTokenNotMatchingUser is returned if basic auth with token password
// was attempted, and the token could be found but does not match the user.
type ErrTokenNotMatchingUser struct {
	Parent, Actual *datastore.Key
}

func (e ErrTokenNotMatchingUser) Error() string {
	return fmt.Sprintf("passenger: parent of token %+v does not match with actual user %+v", e.Parent, e.Actual)
}

func init() {
	passengerKey = key(mathrand.Int63())
}

// Passenger holds the currently authenticated user
// together with the token (if relevant).
type Passenger struct {
	User  *datastore.Key
	Token *model.Token
}

// HasScope can be used to check whether this Passenger was
// granted access to a given scope.
// Note that a user authenticated via username and password
// (not via token) will have access to all scopes by
// default.
func (p *Passenger) HasScope(scope string) (has bool) {
	for _, grantedScope := range p.Token.Scopes {
		if scope == grantedScope {
			return true
		}
	}
	return
}

// Save will persist the passenger to Datastore and send it to Memcache.
func (p *Passenger) Save(ctx context.Context) (*datastore.Key, error) {
	now := time.Now()

	key, err := p.Token.PutWithParent(ctx, p.User)
	if err != nil {
		return nil, err
	}

	buf := new(bytes.Buffer)
	if err = gob.NewEncoder(buf).Encode(p); err != nil {
		return nil, err
	}

	item := &memcache.Item{
		Key:        key.Encode(),
		Value:      buf.Bytes(),
		Expiration: p.Token.Expiry.Sub(now) + 10*time.Second,
	}

	if err = memcache.Set(ctx, item); err != nil {
		return nil, err
	}

	return key, nil
}

func (p *Passenger) check(raw []byte) error {
	digest := crypto.Hash(p.Token.Hash).New().Sum(raw)

	if subtle.ConstantTimeCompare(digest, p.Token.Digest) != 1 {
		return ErrDigestMismatch
	}

	if p.Token.Expiry.Before(time.Now()) {
		return ErrTokenExpired
	}

	return nil
}

// IssueToken creates a new Token for the authenticated user. Callers should
// prefill the Token with whatever values they like and leave zero values
// to be set.
// The generated token will also be persisted and can be handed to the client
// with no more handling.
func (p *Passenger) IssueToken(ctx context.Context, token *model.Token) (string, error) {
	now := time.Now()

	token.Creation = now

	if token.Expiry == (time.Time{}) {
		token.Expiry = now.Add(defaultValidity)
	} else {
		if token.Expiry.Before(now.Add(minValidity)) {
			return "", fmt.Errorf("passenger: token must be valid at least %s", minValidity)
		}

		if token.Expiry.After(now.Add(maxValidity)) {
			return "", fmt.Errorf("passenger: token must be valid at most %s", maxValidity)
		}
	}

	// TODO(flowlo): This will reject all scopes for now, as we are not using them.
	// As soon as we introduce scopes, this check must be rewritten accordingly.
	if len(token.Scopes) > 0 {
		return "", fmt.Errorf("passenger: unknown scopes: %s", strings.Join(token.Scopes, ", "))
	}

	if len(token.Description) > 512 || len(token.Description) < 4 {
		return "", fmt.Errorf("passenger: description has bad len %d", len(token.Description))
	}

	var raw [tokenLength]byte

	if _, err := rand.Read(raw[:]); err != nil {
		return "", err
	}

	if token.Hash == 0 {
		token.Hash = int(defaultHash)
	}
	token.Digest = crypto.Hash(token.Hash).New().Sum(raw[:])

	// TODO(flowlo): Set token.RemoteAddr.

	clone := Passenger{
		User:  p.User,
		Token: token,
	}

	key, err := clone.Save(ctx)
	if err != nil {
		return "", err
	}

	return encodeToken(key, &raw)
}

// FromToken tries do identify a Passenger by the token he gave us.
// It will look up the Token and consequently the corresponding User.
func FromToken(ctx context.Context, Token string) (*Passenger, error) {
	key, raw, err := decodeToken(ctx, Token)
	if err != nil {
		return nil, err
	}

	var p *Passenger
	if p, err = fromCache(ctx, key); err != nil {
		if p, err = fromDatastore(ctx, key); err != nil {
			return nil, err
		}
	}

	return p, p.check(raw)
}

func fromCache(ctx context.Context, key *datastore.Key) (*Passenger, error) {
	item, err := memcache.Get(ctx, key.Encode())
	if err != nil {
		return nil, err
	}
	p := new(Passenger)
	if err := gob.NewDecoder(bytes.NewReader(item.Value)).Decode(&p); err != nil {
		return nil, err
	}
	return p, nil
}

func fromDatastore(ctx context.Context, key *datastore.Key) (*Passenger, error) {
	p := &Passenger{
		Token: &model.Token{},
	}
	if err := datastore.Get(ctx, key, p.Token); err != nil {
		return nil, err
	}
	if p.User = key.Parent(); p.User == nil {
		return nil, ErrTokenNotAssociated
	}
	return p, nil
}

// FromBasicAuth tries do identify a Passenger by the token he gave us.
// It will look up the the user by username and try to match password.
func FromBasicAuth(ctx context.Context, username, pw string) (p *Passenger, err error) {
	p = new(Passenger)
	var user model.User
	p.User, err = model.NewQueryForUser().
		Filter("Nick =", username).
		Limit(1).
		Run(ctx).
		Next(&user)

	if err != nil {
		return nil, err
	}
	err = password.Check([]byte(pw), user.HashedPassword)

	// TODO(flowlo): Depending on bcrypt is very fragile. We
	// should encapsulate that.
	if err == bcrypt.ErrMismatchedHashAndPassword {
		userKey := p.User
		p, err = FromToken(ctx, pw)
		if err != nil {
			return
		}
		if !p.User.Equal(userKey) {
			return nil, ErrTokenNotMatchingUser{Parent: p.User, Actual: userKey}
		}
	}
	return
}

// FromRequest inspects the HTTP Authorization header of the given request
// and tries to identify a passenger.
func FromRequest(ctx context.Context, r *http.Request) (*Passenger, error) {
	cookie, err := r.Cookie("token")
	if err != nil && err != http.ErrNoCookie {
		return nil, err
	}
	if err == nil {
		if p, err := FromToken(ctx, cookie.Value); err == nil {
			return p, err
		}
	}

	auth := ""
	if auth = r.Header.Get("Authorization"); auth == "" {
		return nil, ErrNoAuthHeader
	}

	if strings.HasPrefix(auth, "Token ") {
		return FromToken(ctx, auth[6:])
	}

	username, password, ok := "", "", false
	if username, password, ok = r.BasicAuth(); !ok {
		return nil, ErrUnkAuthHeader
	}

	return FromBasicAuth(ctx, username, password)
}

// FromContext returns the Passenger value stored in ctx if any.
func FromContext(ctx context.Context) (p *Passenger, ok bool) {
	p, ok = ctx.Value(passengerKey).(*Passenger)
	return
}

// NewContext returns a new Context that carries value p.
func NewContext(ctx context.Context, p *Passenger) context.Context {
	return context.WithValue(ctx, passengerKey, p)
}

// NewContextFromRequest wraps FromRequest and NewContext.
func NewContextFromRequest(ctx context.Context, r *http.Request) (context.Context, error) {
	p, err := FromRequest(ctx, r)
	if err != nil {
		return ctx, err
	}
	return NewContext(ctx, p), nil
}

// DecodeToken will take a token as sent by a client and translate it into a
// key to look up the full token on the server side and the raw secret.
func decodeToken(ctx context.Context, token string) (*datastore.Key, []byte, error) {
	b, err := hex.DecodeString(token)
	if err != nil {
		return nil, nil, err
	}

	if len(b) != len(kinds)*8+tokenLength {
		return nil, nil, errors.New("token length mismatch")
	}

	var intIDs [len(kinds)]int64
	for i := range intIDs {
		intID, n := binary.Varint(b[i*8 : (i+1)*8])
		if n < 8 {
			return nil, nil, errors.New("varint read mismatch")
		}
		intIDs[len(intIDs)-1-i] = intID
	}

	var key *datastore.Key
	for i := range intIDs {
		if intIDs[i] == 0 {
			continue
		}
		key = datastore.NewKey(ctx, kinds[i], "", intIDs[i], key)
		if key == nil {
			return nil, nil, errors.New("could not construct key from token")
		}
	}

	return key, b[len(kinds)*8:], nil
}

// EncodeToken translates the key and raw secret of a newly generated token to
// a form suitable for the client.
func encodeToken(key *datastore.Key, raw *[tokenLength]byte) (string, error) {
	// Buffer size will be 8 (size of an int64) times the number of keys
	// in the hirarchy plus the length of the raw token itself.
	var b [len(kinds)*8 + tokenLength]byte

	for i := range kinds {
		if n := binary.PutVarint(b[i*8:(i+1)*8], key.IntID()); n < 8 {
			return "", errors.New("short write when encoding token")
		}
		if key != nil {
			key = key.Parent()
		}
	}

	copy(b[len(kinds)*8:len(kinds)*8+tokenLength], raw[:])

	return hex.EncodeToString(b[:]), nil
}
<|endoftext|>


<commit_before>package validator

import (
	"testing"

	"github.com/giantswarm/inago/controller"
)

// TestValidateRequest tests the ValidateRequest function.
func TestValidateRequest(t *testing.T) {
	var tests = []struct {
		request controller.Request
		valid   bool
		err     error
	}{
		{
			request: controller.Request{
				Group: "empty",
			},
			valid: false,
			err:   noUnitsInGroupError,
		},
		{
			request: controller.Request{
				Group: "single",
				Units: []controller.Unit{
					controller.Unit{
						Name: "single-unit.service",
					},
				},
			},
			valid: true,
			err:   nil,
		},
		{
			request: controller.Request{
				Group: "single",
				Units: []controller.Unit{
					controller.Unit{
						Name: "single-unit.service",
					},
					controller.Unit{
						Name: "single-unit2.timer",
					},
				},
			},
			valid: true,
			err:   nil,
		},
		{
			request: controller.Request{
				Group: "scalable",
				Units: []controller.Unit{
					controller.Unit{
						Name: "scalable-unit@.service",
					},
				},
			},
			valid: true,
			err:   nil,
		},
		{
			request: controller.Request{
				Group: "scalable",
				Units: []controller.Unit{
					controller.Unit{
						Name: "scalable-unit@.service",
					},
					controller.Unit{
						Name: "scalable-unit2@.timer",
					},
				},
			},
			valid: true,
			err:   nil,
		},
		{
			request: controller.Request{
				Group: "mix",
				Units: []controller.Unit{
					controller.Unit{
						Name: "mix-unit1.service",
					},
					controller.Unit{
						Name: "mix-unit2@.service",
					},
				},
			},
			valid: false,
			err:   mixedSliceInstanceError,
		},
		{
			request: controller.Request{
				Group: "single",
				Units: []controller.Unit{
					controller.Unit{
						Name: "bad-prefix.service",
					},
				},
			},
			valid: false,
			err:   badUnitPrefixError,
		},
		{
			request: controller.Request{
				Group: "bad@groupname@",
				Units: []controller.Unit{
					controller.Unit{
						Name: "bad@groupname@.service",
					},
				},
			},
			valid: false,
			err:   atInGroupNameError,
		},
		{
			request: controller.Request{
				Group: "group",
				Units: []controller.Unit{
					controller.Unit{
						Name: "group-un@it@.service",
					},
				},
			},
			valid: false,
			err:   multipleAtInUnitNameError,
		},
		{
			request: controller.Request{
				Group: "group",
				Units: []controller.Unit{
					controller.Unit{
						Name: "group-unit1@.service",
					},
					controller.Unit{
						Name: "group-unit@.service",
					},
					controller.Unit{
						Name: "group-unit2@.service",
					},
					controller.Unit{
						Name: "group-unit@.service",
					},
				},
			},
			valid: false,
			err:   unitsSameNameError,
		},
	}

	for index, test := range tests {
		valid, err := ValidateRequest(test.request)
		if test.valid != valid {
			t.Errorf("%v: Request validity should be: '%v', was '%v'", index, test.valid, valid)
		}
		if test.valid && err != nil {
			t.Errorf("%v: Request should be valid, but returned err: '%v'", index, err)
		}
		if !test.valid && test.err != err {
			t.Errorf("%v: Returned err '%v' instead of err '%v'", index, err, test.err)
		}
	}
}

// TestValidateMultipleRequest tests the ValidateMultipleRequest function.
func TestValidateMultipleRequest(t *testing.T) {
	var tests = []struct {
		requests []controller.Request
		valid    bool
		err      error
	}{
		{
			requests: []controller.Request{
				controller.Request{
					Group: "a",
				},
				controller.Request{
					Group: "b",
				},
			},
			valid: true,
			err:   nil,
		},
		{
			requests: []controller.Request{
				controller.Request{
					Group: "bat",
				},
				controller.Request{
					Group: "batman",
				},
			},
			valid: false,
			err:   groupsArePrefixError,
		},
		{
			requests: []controller.Request{
				controller.Request{
					Group: "batwoman",
				},
				controller.Request{
					Group: "batman",
				},
			},
			valid: true,
			err:   nil,
		},
		{
			requests: []controller.Request{
				controller.Request{
					Group: "joker",
				},
				controller.Request{
					Group: "joker",
				},
			},
			valid: false,
			err:   groupsSameNameError,
		},
	}

	for index, test := range tests {
		valid, err := ValidateMultipleRequest(test.requests)
		if test.valid != valid {
			t.Errorf("%v: Requests validity should be: '%v', was '%v'", index, test.valid, valid)
		}
		if test.valid && err != nil {
			t.Errorf("%v: Requests should be valid, but returned err: '%v'", index, err)
		}
		if !test.valid && test.err != err {
			t.Errorf("%v: Returned err '%v' instead of err '%v'", index, err, test.err)
		}
	}
}
<commit_msg>Add comments to validation tests<commit_after>package validator

import (
	"testing"

	"github.com/giantswarm/inago/controller"
)

// TestValidateRequest tests the ValidateRequest function.
func TestValidateRequest(t *testing.T) {
	var tests = []struct {
		request controller.Request
		valid   bool
		err     error
	}{
		// Test a group with no units in it is not valid.
		{
			request: controller.Request{
				Group: "empty",
			},
			valid: false,
			err:   noUnitsInGroupError,
		},
		// Test a group with one well-named unit is valid.
		{
			request: controller.Request{
				Group: "single",
				Units: []controller.Unit{
					controller.Unit{
						Name: "single-unit.service",
					},
				},
			},
			valid: true,
			err:   nil,
		},
		// Test a group with two well-named units is valid.
		{
			request: controller.Request{
				Group: "single",
				Units: []controller.Unit{
					controller.Unit{
						Name: "single-unit.service",
					},
					controller.Unit{
						Name: "single-unit2.timer",
					},
				},
			},
			valid: true,
			err:   nil,
		},
		// Test a group with a scalable unit is valid.
		{
			request: controller.Request{
				Group: "scalable",
				Units: []controller.Unit{
					controller.Unit{
						Name: "scalable-unit@.service",
					},
				},
			},
			valid: true,
			err:   nil,
		},
		// Test a group with two scalable units is valid.
		{
			request: controller.Request{
				Group: "scalable",
				Units: []controller.Unit{
					controller.Unit{
						Name: "scalable-unit@.service",
					},
					controller.Unit{
						Name: "scalable-unit2@.timer",
					},
				},
			},
			valid: true,
			err:   nil,
		},
		// Test that a group mixing scalable and unscalable units is not valid.
		{
			request: controller.Request{
				Group: "mix",
				Units: []controller.Unit{
					controller.Unit{
						Name: "mix-unit1.service",
					},
					controller.Unit{
						Name: "mix-unit2@.service",
					},
				},
			},
			valid: false,
			err:   mixedSliceInstanceError,
		},
		// Test that units must be prefixed with their group name.
		{
			request: controller.Request{
				Group: "single",
				Units: []controller.Unit{
					controller.Unit{
						Name: "bad-prefix.service",
					},
				},
			},
			valid: false,
			err:   badUnitPrefixError,
		},
		// Test that group names cannot contain @ symbols.
		{
			request: controller.Request{
				Group: "bad@groupname@",
				Units: []controller.Unit{
					controller.Unit{
						Name: "bad@groupname@.service",
					},
				},
			},
			valid: false,
			err:   atInGroupNameError,
		},
		// Test that unit names cannot contain multiple @ symbols.
		{
			request: controller.Request{
				Group: "group",
				Units: []controller.Unit{
					controller.Unit{
						Name: "group-un@it@.service",
					},
				},
			},
			valid: false,
			err:   multipleAtInUnitNameError,
		},
		// Test that a group cannot have multiple units with the same name.
		{
			request: controller.Request{
				Group: "group",
				Units: []controller.Unit{
					controller.Unit{
						Name: "group-unit1@.service",
					},
					controller.Unit{
						Name: "group-unit@.service",
					},
					controller.Unit{
						Name: "group-unit2@.service",
					},
					controller.Unit{
						Name: "group-unit@.service",
					},
				},
			},
			valid: false,
			err:   unitsSameNameError,
		},
	}

	for index, test := range tests {
		valid, err := ValidateRequest(test.request)
		if test.valid != valid {
			t.Errorf("%v: Request validity should be: '%v', was '%v'", index, test.valid, valid)
		}
		if test.valid && err != nil {
			t.Errorf("%v: Request should be valid, but returned err: '%v'", index, err)
		}
		if !test.valid && test.err != err {
			t.Errorf("%v: Returned err '%v' instead of err '%v'", index, err, test.err)
		}
	}
}

// TestValidateMultipleRequest tests the ValidateMultipleRequest function.
func TestValidateMultipleRequest(t *testing.T) {
	var tests = []struct {
		requests []controller.Request
		valid    bool
		err      error
	}{
		// Test that two differently named groups are valid.
		{
			requests: []controller.Request{
				controller.Request{
					Group: "a",
				},
				controller.Request{
					Group: "b",
				},
			},
			valid: true,
			err:   nil,
		},
		// Test that groups which are prefixes of another are invalid.
		{
			requests: []controller.Request{
				controller.Request{
					Group: "bat",
				},
				controller.Request{
					Group: "batman",
				},
			},
			valid: false,
			err:   groupsArePrefixError,
		},
		// Test that the group prefix rule applies to the entire group name.
		{
			requests: []controller.Request{
				controller.Request{
					Group: "batwoman",
				},
				controller.Request{
					Group: "batman",
				},
			},
			valid: true,
			err:   nil,
		},
		// Test that group names must be unique.
		{
			requests: []controller.Request{
				controller.Request{
					Group: "joker",
				},
				controller.Request{
					Group: "joker",
				},
			},
			valid: false,
			err:   groupsSameNameError,
		},
	}

	for index, test := range tests {
		valid, err := ValidateMultipleRequest(test.requests)
		if test.valid != valid {
			t.Errorf("%v: Requests validity should be: '%v', was '%v'", index, test.valid, valid)
		}
		if test.valid && err != nil {
			t.Errorf("%v: Requests should be valid, but returned err: '%v'", index, err)
		}
		if !test.valid && test.err != err {
			t.Errorf("%v: Returned err '%v' instead of err '%v'", index, err, test.err)
		}
	}
}
<|endoftext|>


<commit_before>package cmdline

import "testing"

func TestParseCommandFail0(t *testing.T) {
    args := []string{"java"}
    _, err := ParseCommand(args)
    if err == nil {
        t.Error("err == nil")
    } else {
        //t.Error(err)
    }
}

func TestParseCommandFail1(t *testing.T) {
    args := []string{"java", "-cp"}
    _, err := ParseCommand(args)
    if err == nil {
        t.Error("err == nil")
    } else {
        //t.Error(err)
    }
}

func TestParseCommandFail2(t *testing.T) {
    args := []string{"java", "-cp", "a/b/c"}
    _, err := ParseCommand(args)
    if err == nil {
        t.Error("err == nil")
    } else {
        //t.Error(err)
    }
}

func TestParseCommandOK0(t *testing.T) {
    args := []string{"java", "-cp", "a/b/c", "MyClass"}
    _, err := ParseCommand(args)
    if err != nil {
        t.Error(err)
    } else {
        //t.Error(err)
    }
}

func TestParseCommandOK1(t *testing.T) {
    args := []string{"java", "-cp", "a/b/c", "MyClass", "a", "b", "c"}
    _, err := ParseCommand(args)
    if err != nil {
        t.Error(err)
    } else {
        //t.Error(err)
    }
}
<commit_msg>unit test<commit_after>package cmdline

import "testing"

func TestParseCommandFail0(t *testing.T) {
    args := []string{"java"}
    _, err := ParseCommand(args)
    if err == nil {
        t.Error("err == nil")
    } else {
        //t.Error(err)
    }
}

func TestParseCommandFail1(t *testing.T) {
    args := []string{"java", "-cp"}
    _, err := ParseCommand(args)
    if err == nil {
        t.Error("err == nil")
    } else {
        //t.Error(err)
    }
}

func TestParseCommandFail2(t *testing.T) {
    args := []string{"java", "-cp", "a/b/c"}
    _, err := ParseCommand(args)
    if err == nil {
        t.Error("err == nil")
    } else {
        //t.Error(err)
    }
}

func TestParseCommandOK0(t *testing.T) {
    args := []string{"java", "-cp", "a/b/c", "MyClass"}
    cmd, err := ParseCommand(args)
    if err != nil {
        t.Error(err)
    }
    if len(cmd.options) != 1 ||
        cmd.options[0].name != "-cp" ||
        cmd.options[0].value != "a/b/c" ||
        cmd.class != "MyClass" ||
        len(cmd.args) != 0 {
        t.Error(cmd)
    }
}

func TestParseCommandOK1(t *testing.T) {
    args := []string{"java", "-cp", "a/b/c", "MyClass", "a", "b", "c"}
    cmd, err := ParseCommand(args)
    if err != nil {
        t.Error(err)
    }
    if len(cmd.args) != 3 {
        t.Error(cmd)
    }
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package buildlog

import (
	"testing"
)

func TestGroupLines(t *testing.T) {
	lorem := []string{
		"Lorem ipsum dolor sit amet",
		"consectetur adipiscing elit",
		"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
		"Ut enim ad minim veniam",
		"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat",
		"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur",
		"Excepteur sint occaecat cupidatat non proident",
		"sunt in culpa qui officia deserunt mollit anim id est laborum",
	}
	tests := []struct {
		name   string
		lines  []string
		groups []LineGroup
	}{
		{
			name:   "Test empty log",
			lines:  []string{},
			groups: []LineGroup{},
		},
		{
			name:  "Test error highlighting",
			lines: []string{"This is an ErRoR message"},
			groups: []LineGroup{
				{
					Start: 0,
					End:   1,
					Skip:  false,
				},
			},
		},
		{
			name:  "Test skip all",
			lines: lorem,
			groups: []LineGroup{
				{
					Start: 0,
					End:   8,
					Skip:  true,
				},
			},
		},
		{
			name: "Test skip none",
			lines: []string{
				"a", "b", "c", "d", "e",
				"Failed to immanentize the eschaton.",
				"a", "b", "c", "d", "e",
			},
			groups: []LineGroup{
				{
					Start: 0,
					End:   11,
					Skip:  false,
				},
			},
		},
		{
			name: "Test skip threshold",
			lines: []string{
				"a", "b", "c", "d", // skip threshold unmet
				"a", "b", "c", "d", "e", "Failed to immanentize the eschaton.", "a", "b", "c", "d", "e",
				"a", "b", "c", "d", "e", // skip threshold met
			},
			groups: []LineGroup{
				{
					Start: 0,
					End:   4,
					Skip:  false,
				},
				{
					Start: 4,
					End:   15,
					Skip:  false,
				},
				{
					Start: 15,
					End:   20,
					Skip:  true,
				},
			},
		},
		{
			name: "Test nearby errors",
			lines: []string{
				"a", "b", "c",
				"don't panic",
				"a", "b", "c",
				"don't panic",
				"a", "b", "c",
			},
			groups: []LineGroup{
				{
					Start: 0,
					End:   11,
					Skip:  false,
				},
			},
		},
		{
			name: "Test separated errors",
			lines: []string{
				"a", "b", "c",
				"don't panic",
				"a", "b", "c", "d", "e",
				"a", "b", "c",
				"a", "b", "c", "d", "e",
				"don't panic",
				"a", "b", "c",
			},
			groups: []LineGroup{
				{
					Start: 0,
					End:   9,
					Skip:  false,
				},
				{
					Start: 9,
					End:   12,
					Skip:  false,
				},
				{
					Start: 12,
					End:   21,
					Skip:  false,
				},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got := groupLines(highlightLines(test.lines, 0))
			if len(got) != len(test.groups) {
				t.Fatalf("Expected %d groups, got %d", len(test.groups), len(got))
			}
			for j, exp := range test.groups {
				if got[j].Start != exp.Start || got[j].End != exp.End {
					t.Fatalf("Group %d expected lines [%d, %d), got [%d, %d)", j, exp.Start, exp.End, got[j].Start, got[j].End)
				}
				if got[j].Skip != exp.Skip {
					t.Errorf("Lines [%d, %d) expected Skip = %t", exp.Start, exp.End, exp.Skip)
				}
			}
		})
	}
}
<commit_msg>Unit test the byte calculations.<commit_after>/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package buildlog

import (
	"testing"
)

func TestGroupLines(t *testing.T) {
	lorem := []string{
		"Lorem ipsum dolor sit amet",
		"consectetur adipiscing elit",
		"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
		"Ut enim ad minim veniam",
		"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat",
		"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur",
		"Excepteur sint occaecat cupidatat non proident",
		"sunt in culpa qui officia deserunt mollit anim id est laborum",
	}
	tests := []struct {
		name   string
		lines  []string
		groups []LineGroup
	}{
		{
			name:   "Test empty log",
			lines:  []string{},
			groups: []LineGroup{},
		},
		{
			name:  "Test error highlighting",
			lines: []string{"This is an ErRoR message"},
			groups: []LineGroup{
				{
					Start: 0,
					End:   1,
					Skip:  false,
					ByteOffset: 0,
					ByteLength: 24,
				},
			},
		},
		{
			name:  "Test skip all",
			lines: lorem,
			groups: []LineGroup{
				{
					Start: 0,
					End:   8,
					Skip:  true,
					ByteOffset: 0,
					ByteLength: 437,
				},
			},
		},
		{
			name: "Test skip none",
			lines: []string{
				"a", "b", "c", "d", "e",
				"Failed to immanentize the eschaton.",
				"a", "b", "c", "d", "e",
			},
			groups: []LineGroup{
				{
					Start: 0,
					End:   11,
					Skip:  false,
					ByteOffset: 0,
					ByteLength: 55,
				},
			},
		},
		{
			name: "Test skip threshold",
			lines: []string{
				"a", "b", "c", "d", // skip threshold unmet
				"a", "b", "c", "d", "e", "Failed to immanentize the eschaton.", "a", "b", "c", "d", "e",
				"a", "b", "c", "d", "e", // skip threshold met
			},
			groups: []LineGroup{
				{
					Start: 0,
					End:   4,
					Skip:  false,
					ByteOffset: 0,
					ByteLength: 7,
				},
				{
					Start: 4,
					End:   15,
					Skip:  false,
					ByteOffset: 8,
					ByteLength: 55,
				},
				{
					Start: 15,
					End:   20,
					Skip:  true,
					ByteOffset: 64,
					ByteLength: 9,
				},
			},
		},
		{
			name: "Test nearby errors",
			lines: []string{
				"a", "b", "c",
				"don't panic",
				"a", "b", "c",
				"don't panic",
				"a", "b", "c",
			},
			groups: []LineGroup{
				{
					Start: 0,
					End:   11,
					Skip:  false,
					ByteOffset: 0,
					ByteLength: 41,
				},
			},
		},
		{
			name: "Test separated errors",
			lines: []string{
				"a", "b", "c",
				"don't panic",
				"a", "b", "c", "d", "e",
				"a", "b", "c",
				"a", "b", "c", "d", "e",
				"don't panic",
				"a", "b", "c",
			},
			groups: []LineGroup{
				{
					Start: 0,
					End:   9,
					Skip:  false,
					ByteOffset: 0,
					ByteLength: 27,
				},
				{
					Start: 9,
					End:   12,
					Skip:  false,
					ByteOffset: 28,
					ByteLength: 5,
				},
				{
					Start: 12,
					End:   21,
					Skip:  false,
					ByteOffset: 34,
					ByteLength: 27,
				},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got := groupLines(highlightLines(test.lines, 0))
			if len(got) != len(test.groups) {
				t.Fatalf("Expected %d groups, got %d", len(test.groups), len(got))
			}
			for j, exp := range test.groups {
				if got[j].Start != exp.Start || got[j].End != exp.End {
					t.Fatalf("Group %d expected lines [%d, %d), got [%d, %d)", j, exp.Start, exp.End, got[j].Start, got[j].End)
				}
				if got[j].Skip != exp.Skip {
					t.Errorf("Lines [%d, %d) expected Skip = %t", exp.Start, exp.End, exp.Skip)
				}
				if got[j].ByteOffset != exp.ByteOffset {
					t.Errorf("Group %d expected ByteOffset %d, got %d.", j, exp.ByteOffset, got[j].ByteOffset)
				}
				if got[j].ByteLength != exp.ByteLength {
					t.Errorf("Group %d expected ByteLength %d, got %d.", j, exp.ByteLength, got[j].ByteLength)
				}
			}
		})
	}
}
<|endoftext|>


<commit_before>/**
 * (C) Copyright 2013, Deft Labs
 *
 * 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 deftlabsutil

import (
	"time"
	"syscall"
)

// Get the current time in millis since epoch. Source from stackoverflow:
// http://stackoverflow.com/questions/6161839/go-time-milliseconds
func CurrentTimeInMillis() int64 {
	tv := new(syscall.Timeval)
	syscall.Gettimeofday(tv)
	return (int64(tv.Sec)*1e3 + int64(tv.Usec)/1e3)
}

// Returns the current time in seconds since epoch (i.e., a unix timestamp). Source from stackoverflow:
// http://stackoverflow.com/questions/9539108/obtaining-a-unix-timestamp-in-go-language-current-time-in-seconds-since-epoch
func CurrentTimeInSeconds() int32 {
	return int32(time.Now().Unix())
}

// NowTimeUnixStr returns the date in unix date string format e.g., Wed Dec 11 19:03:18 EST 2013
func NowTimeUnixStr() string {
    return time.Now().Format(time.UnixDate)
}

<commit_msg>added a method to convert time to millis since epoch<commit_after>/**
 * (C) Copyright 2013, Deft Labs
 *
 * 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 deftlabsutil

import (
	"time"
	"syscall"
)

// Convert a time struct to milliseconds since epoch.
func TimeToMillis(tv *time.Time) int64 {
	return tv.UnixNano() / 1e6
}

// Get the current time in millis since epoch. Source from stackoverflow:
// http://stackoverflow.com/questions/6161839/go-time-milliseconds
func CurrentTimeInMillis() int64 {
	tv := new(syscall.Timeval)
	syscall.Gettimeofday(tv)
	return (int64(tv.Sec)*1e3 + int64(tv.Usec)/1e3)
}

// Returns the current time in seconds since epoch (i.e., a unix timestamp). Source from stackoverflow:
// http://stackoverflow.com/questions/9539108/obtaining-a-unix-timestamp-in-go-language-current-time-in-seconds-since-epoch
func CurrentTimeInSeconds() int32 {
	return int32(time.Now().Unix())
}

// NowTimeUnixStr returns the date in unix date string format e.g., Wed Dec 11 19:03:18 EST 2013
func NowTimeUnixStr() string {
    return time.Now().Format(time.UnixDate)
}

<|endoftext|>


<commit_before>package main

import (
  "fmt"
  "time"
)

const N int = 10000000

func pushDown(h *[N]int, pos, n int) {
  for 2*pos + 1 < n {
    j := 2 * pos + 1
    if j + 1 < n && h[j+1] > h[j] {
      j++
    }
    if h[pos] >= h[j] {
      break
    }

    h[pos], h[j] = h[j], h[pos]
    pos = j
  }
}

func main() {
  start := time.Now()

  var h [N]int
  for i := 0; i < N; i++ {
    h[i] = i
  }

  for i := N/2; i >= 0; i-- {
    pushDown(&h, i, N)
  }

  n := N
  for n > 1 {
    n--
    h[0], h[n] = h[n], h[0]
    pushDown(&h, 0, n)
  }

  for i := 0; i < N; i++ {
    if h[i] != i {
      panic("h[i] != i")
    }
  }

  end := time.Since(start)
  fmt.Println("Done in", end.Nanoseconds() / 1000000.0)
}
<commit_msg>heap.go performance improvement<commit_after>package main

import (
  "fmt"
  "time"
)

const N = 10000000

func pushDown(h []int, pos, n int) {
  for 2*pos + 1 < n {
    j := 2 * pos + 1
    if j + 1 < n && h[j+1] > h[j] {
      j++
    }
    if h[pos] >= h[j] {
      break
    }

    h[pos], h[j] = h[j], h[pos]
    pos = j
  }
}

func main() {
  start := time.Now()

  h := make([]int, N)
  for i := 0; i < N; i++ {
    h[i] = i
  }

  for i := N/2; i >= 0; i-- {
    pushDown(h, i, N)
  }

  n := N
  for n > 1 {
    n--
    h[0], h[n] = h[n], h[0]
    pushDown(h, 0, n)
  }

  for i := 0; i < N; i++ {
    if h[i] != i {
      panic("h[i] != i")
    }
  }

  end := time.Since(start)
  fmt.Println("Done in", end.Nanoseconds() / 1000000.0)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/codegangsta/cli"
	"os"
)

func main() {
	app := cli.NewApp()
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug,d",
			Usage: "Show more debugging",
		},
		cli.IntFlag{
			Name:  "port,p",
			Usage: "Which TCP port to use [default: 25]",
			Value: 25,
		},
		cli.IntFlag{
			Name:  "wait,w",
			Usage: "Time to wait between PINGs [default: 1000] (ms)",
			Value: 1000,
		},
		cli.IntFlag{
			Name:  "count,c",
			Usage: "Number on messages [default: 3]",
			Value: 3,
		},
		cli.IntFlag{
			Name:  "parallel, P",
			Usage: "Number of parallel workers [default 1]",
			Value: 1,
		},
		cli.IntFlag{
			Name:  "size,s",
			Usage: "Message size in kilobytes [default: 10] (KiB)",
			Value: 10,
		},
		cli.StringFlag{
			Name:  "file,f",
			Usage: "Send message file (RFC 822)",
			Value: "",
		},
		cli.StringFlag{
			Name:  "helo, H",
			Usage: "HELO domain [default: localhost.localdomain]",
			Value: "localhost.localdomain",
		},
		cli.StringFlag{
			Name:  "sender, S",
			Usage: "sender; Sender address [default: empty]",
			Value: "",
		},
		cli.StringFlag{
			Name:  "rate,r",
			Usage: "rate; Show message rate per second",
			Value: "",
		},
		cli.StringFlag{
			Name:  "quiet,q",
			Usage: "quiet; Show less output",
			Value: "",
		},
		cli.BoolFlag{
			Name:  "J",
			Usage: "Run in jailed mode (forbid --file)",
		},
	}
	app.Name = "gosping"
	app.Version = "0.0.1"
	app.Usage = "Get some SMTP statistics"
	app.ArgsUsage = "x@y.z [@server]"
	app.Run(os.Args)
}
<commit_msg>finished implementing args parsing with codegangsta/cli<commit_after>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"os"
)

var (
	appName         = "gosping"
	appHelpTemplate = `
NAME:
	{{.Name}} - {{.Usage}}

USAGE:
	{{.Name}} [options] x@y.z [@server]
	Where: x@y.z  is the address that will receive e-mail
	and server is the address to connect to (optional)

VERSION:
	{{.Version}}

OPTIONS:
	{{range .Flags}}{{.}}
	{{end}}

If no @server is specified, {{.Name}} will try to find
the recipient domain's MX record, falling back on A/AAAA records.
`
)

func main() {
	cli.AppHelpTemplate = appHelpTemplate
	app := cli.NewApp()
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug,d",
			Usage: "Show more debugging",
		},
		cli.IntFlag{
			Name:  "port,p",
			Usage: "Which TCP port to use [default: 25]",
			Value: 25,
		},
		cli.IntFlag{
			Name:  "wait,w",
			Usage: "Time to wait between PINGs [default: 1000] (ms)",
			Value: 1000,
		},
		cli.IntFlag{
			Name:  "count,c",
			Usage: "Number on messages [default: 3]",
			Value: 3,
		},
		cli.IntFlag{
			Name:  "parallel, P",
			Usage: "Number of parallel workers [default 1]",
			Value: 1,
		},
		cli.IntFlag{
			Name:  "size,s",
			Usage: "Message size in kilobytes [default: 10] (KiB)",
			Value: 10,
		},
		cli.StringFlag{
			Name:  "file,f",
			Usage: "Send message file (RFC 822)",
			Value: "",
		},
		cli.StringFlag{
			Name:  "helo, H",
			Usage: "HELO domain [default: localhost.localdomain]",
			Value: "localhost.localdomain",
		},
		cli.StringFlag{
			Name:  "sender, S",
			Usage: "sender; Sender address [default: empty]",
			Value: "",
		},
		cli.BoolFlag{
			Name:  "rate,r",
			Usage: "rate; Show message rate per second",
		},
		cli.BoolTFlag{
			Name:  "quiet,q",
			Usage: "quiet; Show less output",
		},
		cli.BoolFlag{
			Name:  "J",
			Usage: "Run in jailed mode (forbid --file)",
		},
	}
	app.Name = "gosping"
	app.Version = "0.0.1"
	app.Usage = "Get some SMTP statistics"
	app.Action = run
	app.Run(os.Args)
}

func run(c *cli.Context) {
	fmt.Printf("Debug is set to %t\n", c.Bool("debug"))
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"sync/atomic"
	"syscall"
	"unsafe"
)

type UrlMap map[string]string

// we use COW to handle reloading the map on SIGHUP; this avoids having to
// use an expensive mutex in the HTTP handler
var urlmap unsafe.Pointer
var defaultUrl string
var sigChan = make(chan os.Signal, 1)

// loads the URL mappings from urls.txt
func loadMap() {
	file, err := os.Open("urls.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	urls := make(UrlMap)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "#") || len(line) < 1 {
			continue
		}
		parts := strings.Split(line, " ")
		if len(parts) == 2 {
			key := strings.ToLower(strings.TrimSpace(parts[0]))
			if _, ok := urls[key]; ok {
				log.Printf("duplicate key %s!", key)
			}
			urls[key] = strings.TrimSpace(parts[1])
		} else {
			log.Printf("skipping malformed line:\n%s", line)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Println(err)
	}
	log.Printf("loaded %d mappings", len(urls))
	atomic.StorePointer(&urlmap, (unsafe.Pointer)(&urls))
}

// HTTP handler. If the requested URL is in our map,
// we redirect to the target. Otherwise we redirect to
// the default URL if specified.
func handler(w http.ResponseWriter, r *http.Request) {
	urls := *(*UrlMap)(atomic.LoadPointer(&urlmap))
	dest, ok := urls[strings.ToLower(r.URL.Path)]
	if !ok {
		log.Printf("no match for %s", r.URL.Path)
		if defaultUrl != "" {
			http.Redirect(w, r, defaultUrl, 302)
			return
		} else {
			http.Error(w, "not found", 404)
			return
		}
	}
	log.Printf("%s -> %s", r.URL.Path, dest)
	http.Redirect(w, r, dest, 302)
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	loadMap()
	signal.Notify(sigChan, syscall.SIGHUP)
	go func() {
		for _ = range sigChan {
			log.Println("got SIGHUP; reloading urls.txt")
			loadMap()
		}
	}()

	var pPort = flag.Int("port", 80, "listening port")
	var pUrl = flag.String("defaultUrl", "http://google.com", "default URL")
	flag.Parse()

	defaultUrl = *pUrl
	http.HandleFunc("/", handler)
	log.Printf("pid %d; listening on port %d", os.Getpid(), *pPort)
	err := http.ListenAndServe(fmt.Sprintf(":%d", *pPort), nil)
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>switched to glog for logging<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/golang/glog"
	#"github.com/mailgun/manners"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"sync/atomic"
	"syscall"
	"time"
	"unsafe"
)

type UrlMap map[string]string

// we use COW to handle reloading the map on SIGHUP; this avoids having to
// use an expensive mutex in the HTTP handler
var urlmap unsafe.Pointer
var defaultUrl string
var sigChan = make(chan os.Signal, 1)

// loads the URL mappings from urls.txt
func loadMap() {
	file, err := os.Open("urls.txt")
	if err != nil {
		glog.Fatalln(err)
	}
	defer file.Close()
	urls := make(UrlMap)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "#") || len(line) < 1 {
			continue
		}
		parts := strings.Split(line, " ")
		if len(parts) == 2 {
			key := strings.ToLower(strings.TrimSpace(parts[0]))
			if _, ok := urls[key]; ok {
				glog.Warningf("duplicate key %s!", key)
			}
			urls[key] = strings.TrimSpace(parts[1])
		} else {
			glog.Warningf("skipping malformed line:\n%s", line)
		}
	}
	if err := scanner.Err(); err != nil {
		glog.Errorln(err)
	}
	glog.Infof("loaded %d mappings", len(urls))
	atomic.StorePointer(&urlmap, (unsafe.Pointer)(&urls))
}

// HTTP handler. If the requested URL is in our map,
// we redirect to the target. Otherwise we redirect to
// the default URL if specified.
func handler(w http.ResponseWriter, r *http.Request) {
	urls := *(*UrlMap)(atomic.LoadPointer(&urlmap))
	dest, ok := urls[strings.ToLower(r.URL.Path)]
	if !ok {
		glog.Warningf("no match for %s", r.URL.Path)
		if defaultUrl != "" {
			http.Redirect(w, r, defaultUrl, 302)
			return
		} else {
			http.Error(w, "not found", 404)
			return
		}
	}
	glog.Infof("%s %s %s %s", time.Now(), r.RemoteAddr, r.URL.Path, dest)
	http.Redirect(w, r, dest, 302)
}

func onexit() {
	fmt.Println("got here")
}

func main() {
	defer onexit()
	runtime.GOMAXPROCS(runtime.NumCPU())
	loadMap()
	signal.Notify(sigChan, syscall.SIGHUP)
	go func() {
		for _ = range sigChan {
			glog.Infoln("got SIGHUP; reloading urls.txt")
			loadMap()
		}
	}()

	var pPort = flag.Int("port", 80, "listening port")
	var pUrl = flag.String("defaultUrl", "http://google.com", "default URL")
	flag.Parse()

	defaultUrl = *pUrl
	http.HandleFunc("/", handler)
	glog.Infof("pid %d; listening on port %d", os.Getpid(), *pPort)
	err := http.ListenAndServe(fmt.Sprintf(":%d", *pPort), nil)
	if err != nil {
		glog.Fatalln(err)
	}
}
<|endoftext|>


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

package gps

func (c cmd) Args() []string {
	return c.Cmd.Args
}

func (c cmd) SetDir(dir string) {
	c.Cmd.Dir = dir
}

func (c cmd) SetEnv(env []string) {
	c.Cmd.Env = env
}
<commit_msg>gps: unset some GIT_* variables for git operations<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gps

import (
	"os"
)

func (c cmd) Args() []string {
	return c.Cmd.Args
}

func (c cmd) SetDir(dir string) {
	c.Cmd.Dir = dir
}

func (c cmd) SetEnv(env []string) {
	c.Cmd.Env = env
}

func init() {
	// For our git repositories, we very much assume a "regular" topology.
	// Therefore, no value for the following variables can be relevant to
	// us. Unsetting globally properly propagates to libraries like
	// github.com/Masterminds/vcs, which cannot make the same assumption in
	// general.
	parasiteGitVars := []string{"GIT_DIR", "GIT_INDEX_FILE", "GIT_OBJECT_DIRECTORY", "GIT_WORK_TREE"}
	for _, e := range parasiteGitVars {
		os.Unsetenv(e)
	}
}
<|endoftext|>