<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"time"

	"mig.ninja/mig"
	"mig.ninja/mig/client"
	"mig.ninja/mig/modules"
)

func usage() {
	fmt.Printf(`%s - Mozilla InvestiGator command line client
usage: %s <module> <global options> <module parameters>

--- Global options ---

-c <path>	path to an alternative confiig file. If not set, use ~/.migrc

-e <duration>	time after which the action expires. 60 seconds by default.
		example: -e 300s (5 minutes)

-i <file>	load and run action from a file. supersedes other action flags.

-p <bool>       display action json that would be used and exit

-show <mode>	type of results to show. if not set, default is 'found'.
		* found: 	only print positive results
		* notfound: 	only print negative results
		* all: 		print all results

-render <mode>	defines how results should be rendered:
		* text (default):	results are printed to the console
		* map:			results are geolocated and a google map is generated

-t <target>	target to launch the action on. A target must be specified.
		examples:
		* linux agents:          -t "queueloc LIKE 'linux.%%'"
		* agents named *mysql*:  -t "name like '%%mysql%%'"
		* proxied linux agents:  -t "queueloc LIKE 'linux.%%' AND environment->>'isproxied' = 'true'"
		* agents operated by IT: -t "tags#>>'{operator}'='IT'"
		* run on local system:	 -t local
		* use a migrc macro:     -t mymacroname

-target-found    <action ID>
-target-notfound <action ID>
		targets agents that have eiher found or not found results in a previous action.
		example: -target-found 123456

-v		verbose output, includes debug information and raw queries

-V		print version

-z <bool>       compress action before sending it to agents

Progress information is sent to stderr, silence it with "2>/dev/null".
Results are sent to stdout, redirect them with "1>/path/to/file".

--- Modules documentation ---
Each module provides its own set of parameters. Module parameters must be set *after*
global options. Help is available by calling "<module> help". Available modules are:
`, os.Args[0], os.Args[0])
	for module, _ := range modules.Available {
		fmt.Printf("* %s\n", module)
	}
	fmt.Printf("To access a module documentation, use: %s <module> help\n", os.Args[0])
	os.Exit(1)
}

func continueOnFlagError() {
	return
}

func main() {
	var (
		conf                                    client.Configuration
		cli                                     client.Client
		err                                     error
		op                                      mig.Operation
		a                                       mig.Action
		migrc, show, render, target, expiration string
		afile, targetfound, targetnotfound      string
		printAndExit                            bool
		verbose, showversion                    bool
		compressAction                          bool
		modargs                                 []string
		run                                     interface{}
	)
	defer func() {
		if e := recover(); e != nil {
			fmt.Fprintf(os.Stderr, "%v\n", e)
		}
	}()
	homedir := client.FindHomedir()
	fs := flag.NewFlagSet("mig flag", flag.ContinueOnError)
	fs.Usage = continueOnFlagError
	fs.BoolVar(&printAndExit, "p", false, "display action json that would be used and exit")
	fs.StringVar(&migrc, "c", homedir+"/.migrc", "alternative configuration file")
	fs.StringVar(&show, "show", "found", "type of results to show")
	fs.StringVar(&render, "render", "text", "results rendering mode")
	fs.StringVar(&target, "t", "", "action target")
	fs.StringVar(&targetfound, "target-found", "", "targets agents that have found results in a previous action.")
	fs.StringVar(&targetnotfound, "target-notfound", "", "targets agents that haven't found results in a previous action.")
	fs.StringVar(&expiration, "e", "300s", "expiration")
	fs.StringVar(&afile, "i", "/path/to/file", "Load action from file")
	fs.BoolVar(&verbose, "v", false, "Enable verbose output")
	fs.BoolVar(&showversion, "V", false, "Show version")
	fs.BoolVar(&compressAction, "z", false, "Request compression of action parameters")

	// if first argument is missing, or is help, print help
	// otherwise, pass the remainder of the arguments to the module for parsing
	// this client is agnostic to module parameters
	if len(os.Args) < 2 || os.Args[1] == "help" || os.Args[1] == "-h" || os.Args[1] == "--help" {
		usage()
	}

	if showversion || (len(os.Args) > 1 && (os.Args[1] == "-V" || os.Args[1] == "version")) {
		fmt.Println(mig.Version)
		os.Exit(0)
	}

	// instantiate an API client
	conf, err = client.ReadConfiguration(migrc)
	if err != nil {
		panic(err)
	}
	cli, err = client.NewClient(conf, "cmd-"+mig.Version)
	if err != nil {
		panic(err)
	}
	if verbose {
		cli.EnableDebug()
	}

	// when reading the action from a file, go directly to launch
	if os.Args[1] == "-i" {
		err = fs.Parse(os.Args[1:])
		if err != nil {
			panic(err)
		}
		if afile == "/path/to/file" {
			panic("-i flag must take an action file path as argument")
		}
		a, err = mig.ActionFromFile(afile)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stderr, "[info] launching action from file, all flags are ignored\n")
		if printAndExit {
			actionstr, err := a.IndentedString()
			if err != nil {
				panic(err)
			}
			fmt.Fprintf(os.Stdout, "%v\n", actionstr)
			os.Exit(0)
		}
		goto readytolaunch
	}

	// arguments parsing works as follow:
	// * os.Args[1] must contain the name of the module to launch. we first verify
	//   that a module exist for this name and then continue parsing
	// * os.Args[2:] contains both global options and module parameters. We parse the
	//   whole []string to extract global options, and module parameters will be left
	//   unparsed in fs.Args()
	// * fs.Args() with the module parameters is passed as a string to the module parser
	//   which will return a module operation to store in the action
	op.Module = os.Args[1]
	if _, ok := modules.Available[op.Module]; !ok {
		panic("Unknown module " + op.Module)
	}

	// -- Ugly hack Warning --
	// Parse() will fail on the first flag that is not defined, but in our case module flags
	// are defined in the module packages and not in this program. Therefore, the flag parse error
	// is expected. Unfortunately, Parse() writes directly to stderr and displays the error to
	// the user, which confuses them. The right fix would be to prevent Parse() from writing to
	// stderr, since that's really the job of the calling program, but in the meantime we work around
	// it by redirecting stderr to null before calling Parse(), and put it back to normal afterward.
	// for ref, issue is at https://github.com/golang/go/blob/master/src/flag/flag.go#L793
	fs.SetOutput(os.NewFile(uintptr(87592), os.DevNull))
	err = fs.Parse(os.Args[2:])
	fs.SetOutput(nil)
	if err != nil {
		// ignore the flag not defined error, which is expected because
		// module parameters are defined in modules and not in main
		if len(err.Error()) > 30 && err.Error()[0:29] == "flag provided but not defined" {
			// requeue the parameter that failed
			modargs = append(modargs, err.Error()[31:])
		} else {
			// if it's another error, panic
			panic(err)
		}
	}
	for _, arg := range fs.Args() {
		modargs = append(modargs, arg)
	}
	run = modules.Available[op.Module].NewRun()
	if _, ok := run.(modules.HasParamsParser); !ok {
		fmt.Fprintf(os.Stderr, "[error] module '%s' does not support command line invocation\n", op.Module)
		os.Exit(2)
	}
	op.Parameters, err = run.(modules.HasParamsParser).ParamsParser(modargs)
	if err != nil || op.Parameters == nil {
		panic(err)
	}
	// If compression has been enabled, flag it in the operation.
	if compressAction {
		op.WantCompressed = true
	}
	// Make sure a target value was specified
	if target == "" {
		fmt.Fprintf(os.Stderr, "[error] No target was specified with -t after the module name\n\n"+
			"See MIG documentation on target strings and creating target macros\n"+
			"for help. If you are sure you want to target everything online, you\n"+
			"can use \"status='online'\" as the argument to -t. See the usage\n"+
			"output for the mig command for more examples.\n")
		os.Exit(2)
	}
	// If running against the local target, don't post the action to the MIG API
	// but run it locally instead.
	if target == "local" {
		msg, err := modules.MakeMessage(modules.MsgClassParameters, op.Parameters, false)
		if err != nil {
			panic(err)
		}
		out := run.(modules.Runner).Run(bytes.NewBuffer(msg))
		if len(out) == 0 {
			panic("got empty results, run failed")
		}
		if _, ok := run.(modules.HasResultsPrinter); ok {
			var modres modules.Result
			err := json.Unmarshal([]byte(out), &modres)
			if err != nil {
				panic(err)
			}
			outRes, err := run.(modules.HasResultsPrinter).PrintResults(modres, true)
			if err != nil {
				panic(err)
			}
			for _, resLine := range outRes {
				fmt.Println(resLine)
			}
		} else {
			out = fmt.Sprintf("%s\n", out)
		}
		os.Exit(0)
	}

	a.Operations = append(a.Operations, op)

	for _, arg := range os.Args[1:] {
		a.Name += arg + " "
	}

	// Determine if the specified target was a macro, and if so get the correct
	// target string
	target = cli.ResolveTargetMacro(target)
	if targetfound != "" && targetnotfound != "" {
		panic("Both -target-found and -target-foundnothing cannot be used simultaneously")
	}
	if targetfound != "" {
		targetQuery := fmt.Sprintf(`id IN (select agentid from commands, json_array_elements(commands.results) as `+
			`r where actionid=%s and r#>>'{foundanything}' = 'true')`, targetfound)
		target = targetQuery + " AND " + target
	}
	if targetnotfound != "" {
		targetQuery := fmt.Sprintf(`id NOT IN (select agentid from commands, json_array_elements(commands.results) as `+
			`r where actionid=%s and r#>>'{foundanything}' = 'false')`, targetnotfound)
		target = targetQuery + " AND " + target
	}
	a.Target = target

	if printAndExit {
		actionstr, err := a.IndentedString()
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stdout, "%v\n", actionstr)
		os.Exit(0)
	}

readytolaunch:
	// set the validity 60 second in the past to deal with clock skew
	a.ValidFrom = time.Now().Add(-60 * time.Second).UTC()
	period, err := time.ParseDuration(expiration)
	if err != nil {
		panic(err)
	}
	a.ExpireAfter = a.ValidFrom.Add(period)
	// add extra 60 seconds taken for clock skew
	a.ExpireAfter = a.ExpireAfter.Add(60 * time.Second).UTC()

	a, err = cli.CompressAction(a)
	if err != nil {
		panic(err)
	}
	asig, err := cli.SignAction(a)
	if err != nil {
		panic(err)
	}
	a = asig

	// evaluate target before launch, give a change to cancel before going out to agents
	agents, err := cli.EvaluateAgentTarget(a.Target)
	if err != nil {
		panic(err)
	}
	fmt.Fprintf(os.Stderr, "\x1b[33m%d agents will be targeted. ctrl+c to cancel. launching in \x1b[0m", len(agents))
	for i := 5; i > 0; i-- {
		time.Sleep(1 * time.Second)
		fmt.Fprintf(os.Stderr, "\x1b[33m%d\x1b[0m ", i)
	}
	fmt.Fprintf(os.Stderr, "\x1b[33mGO\n\x1b[0m")

	// launch and follow
	a, err = cli.PostAction(a)
	if err != nil {
		panic(err)
	}
	c := make(chan os.Signal, 1)
	done := make(chan bool, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		err = cli.FollowAction(a, len(agents))
		if err != nil {
			panic(err)
		}
		done <- true
	}()
	select {
	case <-c:
		fmt.Fprintf(os.Stderr, "stop following action. agents may still be running. printing available results:\n")
		goto printresults
	case <-done:
		goto printresults
	}
printresults:
	err = cli.PrintActionResults(a, show, render)
	if err != nil {
		panic(err)
	}
}
<commit_msg>[minor/bug] fix behavior of cmd target-notfound<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"time"

	"mig.ninja/mig"
	"mig.ninja/mig/client"
	"mig.ninja/mig/modules"
)

func usage() {
	fmt.Printf(`%s - Mozilla InvestiGator command line client
usage: %s <module> <global options> <module parameters>

--- Global options ---

-c <path>	path to an alternative confiig file. If not set, use ~/.migrc

-e <duration>	time after which the action expires. 60 seconds by default.
		example: -e 300s (5 minutes)

-i <file>	load and run action from a file. supersedes other action flags.

-p <bool>       display action json that would be used and exit

-show <mode>	type of results to show. if not set, default is 'found'.
		* found: 	only print positive results
		* notfound: 	only print negative results
		* all: 		print all results

-render <mode>	defines how results should be rendered:
		* text (default):	results are printed to the console
		* map:			results are geolocated and a google map is generated

-t <target>	target to launch the action on. A target must be specified.
		examples:
		* linux agents:          -t "queueloc LIKE 'linux.%%'"
		* agents named *mysql*:  -t "name like '%%mysql%%'"
		* proxied linux agents:  -t "queueloc LIKE 'linux.%%' AND environment->>'isproxied' = 'true'"
		* agents operated by IT: -t "tags#>>'{operator}'='IT'"
		* run on local system:	 -t local
		* use a migrc macro:     -t mymacroname

-target-found    <action ID>
-target-notfound <action ID>
		targets agents that have eiher found or not found results in a previous action.
		example: -target-found 123456

-v		verbose output, includes debug information and raw queries

-V		print version

-z <bool>       compress action before sending it to agents

Progress information is sent to stderr, silence it with "2>/dev/null".
Results are sent to stdout, redirect them with "1>/path/to/file".

--- Modules documentation ---
Each module provides its own set of parameters. Module parameters must be set *after*
global options. Help is available by calling "<module> help". Available modules are:
`, os.Args[0], os.Args[0])
	for module, _ := range modules.Available {
		fmt.Printf("* %s\n", module)
	}
	fmt.Printf("To access a module documentation, use: %s <module> help\n", os.Args[0])
	os.Exit(1)
}

func continueOnFlagError() {
	return
}

func main() {
	var (
		conf                                    client.Configuration
		cli                                     client.Client
		err                                     error
		op                                      mig.Operation
		a                                       mig.Action
		migrc, show, render, target, expiration string
		afile, targetfound, targetnotfound      string
		printAndExit                            bool
		verbose, showversion                    bool
		compressAction                          bool
		modargs                                 []string
		run                                     interface{}
	)
	defer func() {
		if e := recover(); e != nil {
			fmt.Fprintf(os.Stderr, "%v\n", e)
		}
	}()
	homedir := client.FindHomedir()
	fs := flag.NewFlagSet("mig flag", flag.ContinueOnError)
	fs.Usage = continueOnFlagError
	fs.BoolVar(&printAndExit, "p", false, "display action json that would be used and exit")
	fs.StringVar(&migrc, "c", homedir+"/.migrc", "alternative configuration file")
	fs.StringVar(&show, "show", "found", "type of results to show")
	fs.StringVar(&render, "render", "text", "results rendering mode")
	fs.StringVar(&target, "t", "", "action target")
	fs.StringVar(&targetfound, "target-found", "", "targets agents that have found results in a previous action.")
	fs.StringVar(&targetnotfound, "target-notfound", "", "targets agents that haven't found results in a previous action.")
	fs.StringVar(&expiration, "e", "300s", "expiration")
	fs.StringVar(&afile, "i", "/path/to/file", "Load action from file")
	fs.BoolVar(&verbose, "v", false, "Enable verbose output")
	fs.BoolVar(&showversion, "V", false, "Show version")
	fs.BoolVar(&compressAction, "z", false, "Request compression of action parameters")

	// if first argument is missing, or is help, print help
	// otherwise, pass the remainder of the arguments to the module for parsing
	// this client is agnostic to module parameters
	if len(os.Args) < 2 || os.Args[1] == "help" || os.Args[1] == "-h" || os.Args[1] == "--help" {
		usage()
	}

	if showversion || (len(os.Args) > 1 && (os.Args[1] == "-V" || os.Args[1] == "version")) {
		fmt.Println(mig.Version)
		os.Exit(0)
	}

	// instantiate an API client
	conf, err = client.ReadConfiguration(migrc)
	if err != nil {
		panic(err)
	}
	cli, err = client.NewClient(conf, "cmd-"+mig.Version)
	if err != nil {
		panic(err)
	}
	if verbose {
		cli.EnableDebug()
	}

	// when reading the action from a file, go directly to launch
	if os.Args[1] == "-i" {
		err = fs.Parse(os.Args[1:])
		if err != nil {
			panic(err)
		}
		if afile == "/path/to/file" {
			panic("-i flag must take an action file path as argument")
		}
		a, err = mig.ActionFromFile(afile)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stderr, "[info] launching action from file, all flags are ignored\n")
		if printAndExit {
			actionstr, err := a.IndentedString()
			if err != nil {
				panic(err)
			}
			fmt.Fprintf(os.Stdout, "%v\n", actionstr)
			os.Exit(0)
		}
		goto readytolaunch
	}

	// arguments parsing works as follow:
	// * os.Args[1] must contain the name of the module to launch. we first verify
	//   that a module exist for this name and then continue parsing
	// * os.Args[2:] contains both global options and module parameters. We parse the
	//   whole []string to extract global options, and module parameters will be left
	//   unparsed in fs.Args()
	// * fs.Args() with the module parameters is passed as a string to the module parser
	//   which will return a module operation to store in the action
	op.Module = os.Args[1]
	if _, ok := modules.Available[op.Module]; !ok {
		panic("Unknown module " + op.Module)
	}

	// -- Ugly hack Warning --
	// Parse() will fail on the first flag that is not defined, but in our case module flags
	// are defined in the module packages and not in this program. Therefore, the flag parse error
	// is expected. Unfortunately, Parse() writes directly to stderr and displays the error to
	// the user, which confuses them. The right fix would be to prevent Parse() from writing to
	// stderr, since that's really the job of the calling program, but in the meantime we work around
	// it by redirecting stderr to null before calling Parse(), and put it back to normal afterward.
	// for ref, issue is at https://github.com/golang/go/blob/master/src/flag/flag.go#L793
	fs.SetOutput(os.NewFile(uintptr(87592), os.DevNull))
	err = fs.Parse(os.Args[2:])
	fs.SetOutput(nil)
	if err != nil {
		// ignore the flag not defined error, which is expected because
		// module parameters are defined in modules and not in main
		if len(err.Error()) > 30 && err.Error()[0:29] == "flag provided but not defined" {
			// requeue the parameter that failed
			modargs = append(modargs, err.Error()[31:])
		} else {
			// if it's another error, panic
			panic(err)
		}
	}
	for _, arg := range fs.Args() {
		modargs = append(modargs, arg)
	}
	run = modules.Available[op.Module].NewRun()
	if _, ok := run.(modules.HasParamsParser); !ok {
		fmt.Fprintf(os.Stderr, "[error] module '%s' does not support command line invocation\n", op.Module)
		os.Exit(2)
	}
	op.Parameters, err = run.(modules.HasParamsParser).ParamsParser(modargs)
	if err != nil || op.Parameters == nil {
		panic(err)
	}
	// If compression has been enabled, flag it in the operation.
	if compressAction {
		op.WantCompressed = true
	}
	// Make sure a target value was specified
	if target == "" {
		fmt.Fprintf(os.Stderr, "[error] No target was specified with -t after the module name\n\n"+
			"See MIG documentation on target strings and creating target macros\n"+
			"for help. If you are sure you want to target everything online, you\n"+
			"can use \"status='online'\" as the argument to -t. See the usage\n"+
			"output for the mig command for more examples.\n")
		os.Exit(2)
	}
	// If running against the local target, don't post the action to the MIG API
	// but run it locally instead.
	if target == "local" {
		msg, err := modules.MakeMessage(modules.MsgClassParameters, op.Parameters, false)
		if err != nil {
			panic(err)
		}
		out := run.(modules.Runner).Run(bytes.NewBuffer(msg))
		if len(out) == 0 {
			panic("got empty results, run failed")
		}
		if _, ok := run.(modules.HasResultsPrinter); ok {
			var modres modules.Result
			err := json.Unmarshal([]byte(out), &modres)
			if err != nil {
				panic(err)
			}
			outRes, err := run.(modules.HasResultsPrinter).PrintResults(modres, true)
			if err != nil {
				panic(err)
			}
			for _, resLine := range outRes {
				fmt.Println(resLine)
			}
		} else {
			out = fmt.Sprintf("%s\n", out)
		}
		os.Exit(0)
	}

	a.Operations = append(a.Operations, op)

	for _, arg := range os.Args[1:] {
		a.Name += arg + " "
	}

	// Determine if the specified target was a macro, and if so get the correct
	// target string
	target = cli.ResolveTargetMacro(target)
	if targetfound != "" && targetnotfound != "" {
		panic("Both -target-found and -target-foundnothing cannot be used simultaneously")
	}
	if targetfound != "" {
		targetQuery := fmt.Sprintf(`id IN (select agentid from commands, json_array_elements(commands.results) as `+
			`r where actionid=%s and r#>>'{foundanything}' = 'true')`, targetfound)
		target = targetQuery + " AND " + target
	}
	if targetnotfound != "" {
		targetQuery := fmt.Sprintf(`id IN (select agentid from commands, json_array_elements(commands.results) as `+
			`r where actionid=%s and r#>>'{foundanything}' = 'false')`, targetnotfound)
		target = targetQuery + " AND " + target
	}
	a.Target = target

	if printAndExit {
		actionstr, err := a.IndentedString()
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stdout, "%v\n", actionstr)
		os.Exit(0)
	}

readytolaunch:
	// set the validity 60 second in the past to deal with clock skew
	a.ValidFrom = time.Now().Add(-60 * time.Second).UTC()
	period, err := time.ParseDuration(expiration)
	if err != nil {
		panic(err)
	}
	a.ExpireAfter = a.ValidFrom.Add(period)
	// add extra 60 seconds taken for clock skew
	a.ExpireAfter = a.ExpireAfter.Add(60 * time.Second).UTC()

	a, err = cli.CompressAction(a)
	if err != nil {
		panic(err)
	}
	asig, err := cli.SignAction(a)
	if err != nil {
		panic(err)
	}
	a = asig

	// evaluate target before launch, give a change to cancel before going out to agents
	agents, err := cli.EvaluateAgentTarget(a.Target)
	if err != nil {
		panic(err)
	}
	fmt.Fprintf(os.Stderr, "\x1b[33m%d agents will be targeted. ctrl+c to cancel. launching in \x1b[0m", len(agents))
	for i := 5; i > 0; i-- {
		time.Sleep(1 * time.Second)
		fmt.Fprintf(os.Stderr, "\x1b[33m%d\x1b[0m ", i)
	}
	fmt.Fprintf(os.Stderr, "\x1b[33mGO\n\x1b[0m")

	// launch and follow
	a, err = cli.PostAction(a)
	if err != nil {
		panic(err)
	}
	c := make(chan os.Signal, 1)
	done := make(chan bool, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		err = cli.FollowAction(a, len(agents))
		if err != nil {
			panic(err)
		}
		done <- true
	}()
	select {
	case <-c:
		fmt.Fprintf(os.Stderr, "stop following action. agents may still be running. printing available results:\n")
		goto printresults
	case <-done:
		goto printresults
	}
printresults:
	err = cli.PrintActionResults(a, show, render)
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package curator

import (
	"fmt"
	"reflect"
	"sync"
)

type ConnectionStateListener interface {
	// Called when there is a state change in the connection
	StateChanged(client CuratorFramework, newState ConnectionState)
}

// Receives notifications about errors and background events
type CuratorListener interface {
	// Called when a background task has completed or a watch has triggered
	EventReceived(client CuratorFramework, event CuratorEvent) error
}

type UnhandledErrorListener interface {
	// Called when an exception is caught in a background thread, handler, etc.
	UnhandledError(err error)
}

type connectionStateListenerCallback func(client CuratorFramework, newState ConnectionState)

type connectionStateListenerStub struct {
	callback connectionStateListenerCallback
}

func NewConnectionStateListener(callback connectionStateListenerCallback) ConnectionStateListener {
	return &connectionStateListenerStub{callback}
}

func (l *connectionStateListenerStub) StateChanged(client CuratorFramework, newState ConnectionState) {
	l.callback(client, newState)
}

type curatorListenerCallback func(client CuratorFramework, event CuratorEvent) error

type curatorListenerStub struct {
	callback curatorListenerCallback
}

func NewCuratorListener(callback curatorListenerCallback) CuratorListener {
	return &curatorListenerStub{callback}
}

func (l *curatorListenerStub) EventReceived(client CuratorFramework, event CuratorEvent) error {
	return l.callback(client, event)
}

type unhandledErrorListenerCallback func(err error)

type unhandledErrorListenerStub struct {
	callback unhandledErrorListenerCallback
}

func NewUnhandledErrorListener(callback unhandledErrorListenerCallback) UnhandledErrorListener {
	return &unhandledErrorListenerStub{callback}
}

func (l *unhandledErrorListenerStub) UnhandledError(err error) {
	l.callback(err)
}

// Abstracts a listenable object
type Listenable /* [T] */ interface {
	// Add the given listener.
	AddListener(listener interface{} /* T */)

	// Remove the given listener
	RemoveListener(listener interface{} /* T */)

	Len() int

	Clear()

	ForEach(fn interface{}, args ...interface{} /* T */) error
}

type ConnectionStateListenable interface {
	Listenable /* [T] */

	Add(listener ConnectionStateListener)

	Remove(listener ConnectionStateListener)
}

type CuratorListenable interface {
	Listenable /* [T] */

	Add(listener CuratorListener)

	Remove(listener CuratorListener)
}

type UnhandledErrorListenable interface {
	Listenable /* [T] */

	Add(listener UnhandledErrorListener)

	Remove(listener UnhandledErrorListener)
}

type listenerContainer struct {
	lock      sync.Mutex
	listeners map[interface{}][]reflect.Value
}

func newListenerContainer() *listenerContainer {
	return &listenerContainer{
		listeners: make(map[interface{}][]reflect.Value),
	}
}

func (c *listenerContainer) AddListener(listener interface{}) {
	c.lock.Lock()

	c.listeners[listener] = nil

	c.lock.Unlock()
}

func (c *listenerContainer) RemoveListener(listener interface{}) {
	c.lock.Lock()

	delete(c.listeners, listener)

	c.lock.Unlock()
}

func (c *listenerContainer) Len() int {
	return len(c.listeners)
}

func (c *listenerContainer) Clear() {
	c.lock.Lock()

	c.listeners = make(map[interface{}][]reflect.Value)

	c.lock.Unlock()
}

func (c *listenerContainer) ForEach(fn interface{}, args ...interface{}) error {
	v := reflect.ValueOf(fn)

	if v.Kind() != reflect.Func {
		return fmt.Errorf("`fn` should be a function, %s", fn)
	}

	var opts []reflect.Value

	for _, arg := range args {
		opts = append(opts, reflect.ValueOf(arg))
	}

	for listener, _ := range c.listeners {
		c.listeners[listener] = v.Call(append([]reflect.Value{reflect.ValueOf(listener)}, opts...))
	}

	return nil
}

type connectionStateListenerContainer struct {
	*listenerContainer
}

func newConnectionStateListenerContainer() *connectionStateListenerContainer {
	return &connectionStateListenerContainer{newListenerContainer()}
}

func (c *connectionStateListenerContainer) Add(listener ConnectionStateListener) {
	c.AddListener(listener)
}

func (c *connectionStateListenerContainer) Remove(listener ConnectionStateListener) {
	c.RemoveListener(listener)
}

type curatorListenerContainer struct {
	*listenerContainer
}

func newCuratorListenerContainer() *curatorListenerContainer {
	return &curatorListenerContainer{newListenerContainer()}
}

func (c *curatorListenerContainer) Add(listener CuratorListener) {
	c.AddListener(listener)
}

func (c *curatorListenerContainer) Remove(listener CuratorListener) {
	c.RemoveListener(listener)
}

type unhandledErrorListenerContainer struct {
	*listenerContainer
}

func newUnhandledErrorListenerContainer() *unhandledErrorListenerContainer {
	return &unhandledErrorListenerContainer{newListenerContainer()}
}

func (c *unhandledErrorListenerContainer) Add(listener UnhandledErrorListener) {
	c.AddListener(listener)
}

func (c *unhandledErrorListenerContainer) Remove(listener UnhandledErrorListener) {
	c.RemoveListener(listener)
}
<commit_msg>make listenable simple<commit_after>package curator

import (
	"sync"
)

type ConnectionStateListener interface {
	// Called when there is a state change in the connection
	StateChanged(client CuratorFramework, newState ConnectionState)
}

// Receives notifications about errors and background events
type CuratorListener interface {
	// Called when a background task has completed or a watch has triggered
	EventReceived(client CuratorFramework, event CuratorEvent) error
}

type UnhandledErrorListener interface {
	// Called when an exception is caught in a background thread, handler, etc.
	UnhandledError(err error)
}

type connectionStateListenerCallback func(client CuratorFramework, newState ConnectionState)

type connectionStateListenerStub struct {
	callback connectionStateListenerCallback
}

func NewConnectionStateListener(callback connectionStateListenerCallback) ConnectionStateListener {
	return &connectionStateListenerStub{callback}
}

func (l *connectionStateListenerStub) StateChanged(client CuratorFramework, newState ConnectionState) {
	l.callback(client, newState)
}

type curatorListenerCallback func(client CuratorFramework, event CuratorEvent) error

type curatorListenerStub struct {
	callback curatorListenerCallback
}

func NewCuratorListener(callback curatorListenerCallback) CuratorListener {
	return &curatorListenerStub{callback}
}

func (l *curatorListenerStub) EventReceived(client CuratorFramework, event CuratorEvent) error {
	return l.callback(client, event)
}

type unhandledErrorListenerCallback func(err error)

type unhandledErrorListenerStub struct {
	callback unhandledErrorListenerCallback
}

func NewUnhandledErrorListener(callback unhandledErrorListenerCallback) UnhandledErrorListener {
	return &unhandledErrorListenerStub{callback}
}

func (l *unhandledErrorListenerStub) UnhandledError(err error) {
	l.callback(err)
}

// Abstracts a listenable object
type Listenable /* [T] */ interface {
	Len() int

	Clear()

	ForEach(callback func(interface{}))
}

type ConnectionStateListenable interface {
	Listenable /* [T] */

	Add(listener ConnectionStateListener)

	Remove(listener ConnectionStateListener)
}

type CuratorListenable interface {
	Listenable /* [T] */

	Add(listener CuratorListener)

	Remove(listener CuratorListener)
}

type UnhandledErrorListenable interface {
	Listenable /* [T] */

	Add(listener UnhandledErrorListener)

	Remove(listener UnhandledErrorListener)
}

type listenerContainer struct {
	lock      sync.RWMutex
	listeners []interface{}
}

func (c *listenerContainer) AddListener(listener interface{}) {
	c.lock.Lock()

	c.listeners = append(c.listeners, listener)

	c.lock.Unlock()
}

func (c *listenerContainer) RemoveListener(listener interface{}) {
	c.lock.Lock()

	for i, l := range c.listeners {
		if l == listener {
			copy(c.listeners[i:], c.listeners[i+1:])
			c.listeners = c.listeners[:len(c.listeners)-1]
			break
		}
	}

	c.lock.Unlock()
}

func (c *listenerContainer) Len() int {
	return len(c.listeners)
}

func (c *listenerContainer) Clear() {
	c.lock.Lock()

	c.listeners = nil

	c.lock.Unlock()
}

func (c *listenerContainer) ForEach(callback func(interface{})) {
	c.lock.RLock()

	for _, listener := range c.listeners {
		callback(listener)
	}

	c.lock.RUnlock()
}

type connectionStateListenerContainer struct {
	*listenerContainer
}

func (c *connectionStateListenerContainer) Add(listener ConnectionStateListener) {
	c.AddListener(listener)
}

func (c *connectionStateListenerContainer) Remove(listener ConnectionStateListener) {
	c.RemoveListener(listener)
}

type curatorListenerContainer struct {
	*listenerContainer
}

func (c *curatorListenerContainer) Add(listener CuratorListener) {
	c.AddListener(listener)
}

func (c *curatorListenerContainer) Remove(listener CuratorListener) {
	c.RemoveListener(listener)
}

type unhandledErrorListenerContainer struct {
	*listenerContainer
}

func (c *unhandledErrorListenerContainer) Add(listener UnhandledErrorListener) {
	c.AddListener(listener)
}

func (c *unhandledErrorListenerContainer) Remove(listener UnhandledErrorListener) {
	c.RemoveListener(listener)
}
<|endoftext|>


<commit_before>package apiGatewayDeploy

import (
	"database/sql"
	"encoding/json"
	"github.com/30x/apid"
	"github.com/apigee-labs/transicator/common"
	"os"
	"time"
)

const (
	APIGEE_SYNC_EVENT = "ApigeeSync"
	DEPLOYMENT_TABLE  = "edgex.deployment"
)

func initListener(services apid.Services) {
	services.Events().Listen(APIGEE_SYNC_EVENT, &apigeeSyncHandler{})
}

type bundleConfigJson struct {
	Name         string `json:"name"`
	URI          string `json:"uri"`
	ChecksumType string `json:"checksumType"`
	Checksum     string `json:"checksum"`
}

type apigeeSyncHandler struct {
}

func (h *apigeeSyncHandler) String() string {
	return "gatewayDeploy"
}

func (h *apigeeSyncHandler) Handle(e apid.Event) {

	if changeSet, ok := e.(*common.ChangeList); ok {
		processChangeList(changeSet)
	} else if snapData, ok := e.(*common.Snapshot); ok {
		processSnapshot(snapData)
	} else {
		log.Errorf("Received invalid event. Ignoring. %v", e)
	}
}

func processSnapshot(snapshot *common.Snapshot) {

	log.Debugf("Snapshot received. Switching to DB version: %s", snapshot.SnapshotInfo)

	db, err := data.DBVersion(snapshot.SnapshotInfo)
	if err != nil {
		log.Panicf("Unable to access database: %v", err)
	}

	err = InitDB(db)
	if err != nil {
		log.Panicf("Unable to initialize database: %v", err)
	}

	tx, err := db.Begin()
	if err != nil {
		log.Panicf("Error starting transaction: %v", err)
	}

	// ensure that no new database updates are made on old database
	dbMux.Lock()
	defer dbMux.Unlock()

	defer tx.Rollback()
	for _, table := range snapshot.Tables {
		var err error
		switch table.Name {
		case DEPLOYMENT_TABLE:
			log.Debugf("Snapshot of %s with %d rows", table.Name, len(table.Rows))
			for _, row := range table.Rows {
				err = addDeployment(tx, row)
			}
		}
		if err != nil {
			log.Panicf("Error processing Snapshot: %v", err)
		}
	}

	err = tx.Commit()
	if err != nil {
		log.Panicf("Error committing Snapshot change: %v", err)
	}

	SetDB(db)

	// if no tables, this a startup event for an existing DB, start bundle downloads that didn't finish
	if len(snapshot.Tables) == 0 {
		deployments, err := getUnreadyDeployments()
		if err != nil {
			log.Panicf("unable to query database for unready deployments: %v", err)
		}
		for _, dep := range deployments {
			go downloadBundle(dep)
		}
	}

	log.Debug("Snapshot processed")
}

func processChangeList(changes *common.ChangeList) {

	tx, err := getDB().Begin()
	if err != nil {
		log.Panicf("Error processing ChangeList: %v", err)
	}
	defer tx.Rollback()

	// ensure bundle download and delete updates aren't attempted while in process
	dbMux.Lock()
	defer dbMux.Unlock()

	var bundlesToDelete []string
	for _, change := range changes.Changes {
		var err error
		switch change.Table {
		case DEPLOYMENT_TABLE:
			switch change.Operation {
			case common.Insert:
				err = addDeployment(tx, change.NewRow)
			case common.Delete:
				var id string
				change.OldRow.Get("id", &id)
				localBundleUri, err := getLocalBundleURI(tx, id)
				if err == nil {
					bundlesToDelete = append(bundlesToDelete, localBundleUri)
					err = deleteDeployment(tx, id)
				}
			default:
				log.Errorf("unexpected operation: %s", change.Operation)
			}
		}
		if err != nil {
			log.Panicf("Error processing ChangeList: %v", err)
		}
	}
	err = tx.Commit()
	if err != nil {
		log.Panicf("Error processing ChangeList: %v", err)
	}

	// clean up old bundles
	if len(bundlesToDelete) > 0 {
		log.Debugf("will delete %d old bundles", len(bundlesToDelete))
		go func() {
			// give clients a minute to avoid conflicts
			time.Sleep(bundleCleanupDelay)
			for _, b := range bundlesToDelete {
				log.Debugf("removing old bundle: %v", b)
				safeDelete(b)
			}
		}()
	}
}

func dataDeploymentFromRow(row common.Row) (d DataDeployment, err error) {

	row.Get("id", &d.ID)
	row.Get("bundle_config_id", &d.BundleConfigID)
	row.Get("apid_cluster_id", &d.ApidClusterID)
	row.Get("data_scope_id", &d.DataScopeID)
	row.Get("bundle_config_json", &d.BundleConfigJSON)
	row.Get("config_json", &d.ConfigJSON)
	row.Get("created", &d.Created)
	row.Get("created_by", &d.CreatedBy)
	row.Get("updated", &d.Updated)
	row.Get("updated_by", &d.UpdatedBy)

	var bc bundleConfigJson
	json.Unmarshal([]byte(d.BundleConfigJSON), &bc)
	if err != nil {
		log.Errorf("JSON decoding Manifest failed: %v", err)
		return
	}

	d.BundleName = bc.Name
	d.BundleURI = bc.URI
	d.BundleChecksumType = bc.ChecksumType
	d.BundleChecksum = bc.Checksum

	return
}

func addDeployment(tx *sql.Tx, row common.Row) (err error) {

	var d DataDeployment
	d, err = dataDeploymentFromRow(row)
	if err != nil {
		return
	}

	err = InsertDeployment(tx, d)
	if err != nil {
		return
	}

	// todo: limit # concurrent downloads?
	go downloadBundle(d)
	return
}

func safeDelete(file string) {
	if e := os.Remove(file); e != nil && !os.IsNotExist(e) {
		log.Warnf("unable to delete file %s: %v", file, e)
	}
}
<commit_msg>Launch existing bundle download in separate thread<commit_after>package apiGatewayDeploy

import (
	"database/sql"
	"encoding/json"
	"github.com/30x/apid"
	"github.com/apigee-labs/transicator/common"
	"os"
	"time"
)

const (
	APIGEE_SYNC_EVENT = "ApigeeSync"
	DEPLOYMENT_TABLE  = "edgex.deployment"
)

func initListener(services apid.Services) {
	services.Events().Listen(APIGEE_SYNC_EVENT, &apigeeSyncHandler{})
}

type bundleConfigJson struct {
	Name         string `json:"name"`
	URI          string `json:"uri"`
	ChecksumType string `json:"checksumType"`
	Checksum     string `json:"checksum"`
}

type apigeeSyncHandler struct {
}

func (h *apigeeSyncHandler) String() string {
	return "gatewayDeploy"
}

func (h *apigeeSyncHandler) Handle(e apid.Event) {

	if changeSet, ok := e.(*common.ChangeList); ok {
		processChangeList(changeSet)
	} else if snapData, ok := e.(*common.Snapshot); ok {
		processSnapshot(snapData)
	} else {
		log.Errorf("Received invalid event. Ignoring. %v", e)
	}
}

func processSnapshot(snapshot *common.Snapshot) {

	log.Debugf("Snapshot received. Switching to DB version: %s", snapshot.SnapshotInfo)

	db, err := data.DBVersion(snapshot.SnapshotInfo)
	if err != nil {
		log.Panicf("Unable to access database: %v", err)
	}

	err = InitDB(db)
	if err != nil {
		log.Panicf("Unable to initialize database: %v", err)
	}

	tx, err := db.Begin()
	if err != nil {
		log.Panicf("Error starting transaction: %v", err)
	}

	// ensure that no new database updates are made on old database
	dbMux.Lock()
	defer dbMux.Unlock()

	defer tx.Rollback()
	for _, table := range snapshot.Tables {
		var err error
		switch table.Name {
		case DEPLOYMENT_TABLE:
			log.Debugf("Snapshot of %s with %d rows", table.Name, len(table.Rows))
			for _, row := range table.Rows {
				err = addDeployment(tx, row)
			}
		}
		if err != nil {
			log.Panicf("Error processing Snapshot: %v", err)
		}
	}

	err = tx.Commit()
	if err != nil {
		log.Panicf("Error committing Snapshot change: %v", err)
	}

	SetDB(db)

	// if no tables, this a startup event for an existing DB, start bundle downloads that didn't finish
	if len(snapshot.Tables) == 0 {
		go func() {
			deployments, err := getUnreadyDeployments()
			if err != nil {
				log.Panicf("unable to query database for unready deployments: %v", err)
			}
			for _, dep := range deployments {
				go downloadBundle(dep)
			}
		}()
	}

	log.Debug("Snapshot processed")
}

func processChangeList(changes *common.ChangeList) {

	tx, err := getDB().Begin()
	if err != nil {
		log.Panicf("Error processing ChangeList: %v", err)
	}
	defer tx.Rollback()

	// ensure bundle download and delete updates aren't attempted while in process
	dbMux.Lock()
	defer dbMux.Unlock()

	var bundlesToDelete []string
	for _, change := range changes.Changes {
		var err error
		switch change.Table {
		case DEPLOYMENT_TABLE:
			switch change.Operation {
			case common.Insert:
				err = addDeployment(tx, change.NewRow)
			case common.Delete:
				var id string
				change.OldRow.Get("id", &id)
				localBundleUri, err := getLocalBundleURI(tx, id)
				if err == nil {
					bundlesToDelete = append(bundlesToDelete, localBundleUri)
					err = deleteDeployment(tx, id)
				}
			default:
				log.Errorf("unexpected operation: %s", change.Operation)
			}
		}
		if err != nil {
			log.Panicf("Error processing ChangeList: %v", err)
		}
	}
	err = tx.Commit()
	if err != nil {
		log.Panicf("Error processing ChangeList: %v", err)
	}

	// clean up old bundles
	if len(bundlesToDelete) > 0 {
		log.Debugf("will delete %d old bundles", len(bundlesToDelete))
		go func() {
			// give clients a minute to avoid conflicts
			time.Sleep(bundleCleanupDelay)
			for _, b := range bundlesToDelete {
				log.Debugf("removing old bundle: %v", b)
				safeDelete(b)
			}
		}()
	}
}

func dataDeploymentFromRow(row common.Row) (d DataDeployment, err error) {

	row.Get("id", &d.ID)
	row.Get("bundle_config_id", &d.BundleConfigID)
	row.Get("apid_cluster_id", &d.ApidClusterID)
	row.Get("data_scope_id", &d.DataScopeID)
	row.Get("bundle_config_json", &d.BundleConfigJSON)
	row.Get("config_json", &d.ConfigJSON)
	row.Get("created", &d.Created)
	row.Get("created_by", &d.CreatedBy)
	row.Get("updated", &d.Updated)
	row.Get("updated_by", &d.UpdatedBy)

	var bc bundleConfigJson
	json.Unmarshal([]byte(d.BundleConfigJSON), &bc)
	if err != nil {
		log.Errorf("JSON decoding Manifest failed: %v", err)
		return
	}

	d.BundleName = bc.Name
	d.BundleURI = bc.URI
	d.BundleChecksumType = bc.ChecksumType
	d.BundleChecksum = bc.Checksum

	return
}

func addDeployment(tx *sql.Tx, row common.Row) (err error) {

	var d DataDeployment
	d, err = dataDeploymentFromRow(row)
	if err != nil {
		return
	}

	err = InsertDeployment(tx, d)
	if err != nil {
		return
	}

	// todo: limit # concurrent downloads?
	go downloadBundle(d)
	return
}

func safeDelete(file string) {
	if e := os.Remove(file); e != nil && !os.IsNotExist(e) {
		log.Warnf("unable to delete file %s: %v", file, e)
	}
}
<|endoftext|>


<commit_before>package gohm_test

import (
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/karrick/gohm"
)

func TestLogAllWithoutError(t *testing.T) {
	logOutput := new(bytes.Buffer)

	response := "something interesting"

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(response))
	}), gohm.Config{LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), fmt.Sprintf(" %d ", http.StatusOK); !strings.Contains(actual, expected) {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogAllWithError(t *testing.T) {
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusConflict)
	}), gohm.Config{LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), fmt.Sprintf(" %d ", http.StatusConflict); !strings.Contains(actual, expected) {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

// then set log errors, and try both error and not error

func TestLogErrorsWhenWriteHeaderErrorStatus(t *testing.T) {
	logBitmask := gohm.LogStatusErrors
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), fmt.Sprintf(" %d ", http.StatusForbidden); !strings.Contains(actual, expected) {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogErrorsWithError(t *testing.T) {
	logBitmask := gohm.LogStatusErrors
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), fmt.Sprintf(" %d ", http.StatusForbidden); !strings.Contains(actual, expected) {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogErrorsWithoutError(t *testing.T) {
	logBitmask := gohm.LogStatusErrors
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// no error
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), ""; actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

const apacheTimeFormat = "02/Jan/2006:15:04:05 -0700"

func TestLogWithFormatStatusEscapedCharacters(t *testing.T) {
	format := "\\{client-ip\\}"
	logBitmask := uint32(gohm.LogStatus1xx | gohm.LogStatus2xx | gohm.LogStatus3xx | gohm.LogStatus4xx | gohm.LogStatus5xx)
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput, LogFormat: format})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), "{client-ip}\n"; actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogWithFormatStatic(t *testing.T) {
	format := "{client} {client-ip} {client-port} - \"{method} {uri} {proto}\" {status} {bytes}"
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogFormat: format, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	// hardcoded test request remote address
	client := req.RemoteAddr
	clientIP := client
	clientPort := client
	if colon := strings.LastIndex(client, ":"); colon != -1 {
		clientIP = client[:colon]
		clientPort = client[colon+1:]
	}

	expected := fmt.Sprintf("%s %s %s - \"GET /some/url HTTP/1.1\" %d 26\n", client, clientIP, clientPort, http.StatusForbidden)
	if actual := logOutput.String(); actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogWithFormatIgnoresInvalidTokens(t *testing.T) {
	format := "This is an {invalid-token} with a {status} after it"
	logBitmask := uint32(gohm.LogStatus1xx | gohm.LogStatus2xx | gohm.LogStatus3xx | gohm.LogStatus4xx | gohm.LogStatus5xx)
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogFormat: format, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	expected := fmt.Sprintf("This is an {invalid-token} with a %d after it\n", http.StatusForbidden)
	if actual := logOutput.String(); actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func timeFromEpochString(t *testing.T, value string) time.Time {
	epoch, err := strconv.Atoi(value)
	if err != nil {
		t.Fatal(err)
	}
	return time.Unix(int64(epoch), 0)
}

func TestLogWithFormatDynamic(t *testing.T) {
	format := "{begin-epoch} {end-epoch} {begin} {begin-iso8601} {end} {end-iso8601} {duration}"
	logBitmask := uint32(gohm.LogStatus1xx | gohm.LogStatus2xx | gohm.LogStatus3xx | gohm.LogStatus4xx | gohm.LogStatus5xx)
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogFormat: format, LogWriter: logOutput})

	beforeTime := time.Now() //.Round(time.Second)

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	afterTime := time.Now() //.Round(time.Second)

	// first, grab the begin-epoch, and compute the other begin values
	actual := logOutput.String()

	indexFirstSpace := strings.IndexByte(actual, ' ')
	beginString := actual[:indexFirstSpace]

	beginTime := timeFromEpochString(t, beginString)
	if beginTime.Before(beforeTime.Truncate(time.Second)) {
		t.Errorf("Begin: %v; Before: %v", beginTime, beforeTime)
	}
	if beginTime.After(afterTime) {
		t.Errorf("Begin: %v; After: %v", beginTime, afterTime)
	}

	// first, grab the end-epoch, and compute the other end values
	indexSecondSpace := indexFirstSpace + strings.IndexByte(actual[indexFirstSpace+1:], ' ')
	endString := actual[indexFirstSpace+1 : indexSecondSpace+1]
	endTime := timeFromEpochString(t, endString)
	if endTime.Before(beforeTime.Truncate(time.Second)) {
		t.Errorf("End: %v; Before: %v", endTime, beforeTime)
	}
	if endTime.After(afterTime) {
		t.Errorf("End: %v; After: %v", endTime, afterTime)
	}

	if actual, expected := actual[len(actual)-1:], "\n"; actual != expected {
		t.Errorf("Actual: %#v; #Expected: %#v", actual, expected)
	}

	indexFinalSpace := strings.LastIndexByte(actual, ' ')
	durationString := actual[indexFinalSpace+1 : len(actual)-1]

	// to check duration, let's just ensure we can parse it as a float, and it's less than the span duration
	durationFloat, err := strconv.ParseFloat(durationString, 64)
	if err != nil {
		t.Errorf("Actual: %#v; Expected: %#v", err, nil)
	}
	durationMilliseconds := afterTime.Sub(beforeTime).Nanoseconds() / 1000
	if int64(durationFloat*1000000) > durationMilliseconds {
		t.Errorf("durationFloat: %v; durationMilliseconds: %v", durationFloat, durationMilliseconds)
	}

	expected := fmt.Sprintf("%s %s %s %s %s %s %s\n", beginString, endString,
		beginTime.UTC().Format(apacheTimeFormat),
		beginTime.UTC().Format(time.RFC3339),
		endTime.UTC().Format(apacheTimeFormat),
		endTime.UTC().Format(time.RFC3339),
		durationString)

	if actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func BenchmarkWithLogsElided(b *testing.B) {
	logBitmask := uint32(gohm.LogStatus4xx | gohm.LogStatus5xx) // only errors
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// this does not error, so nothing ought to be logged
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput})

	for i := 0; i < b.N; i++ {
		req := httptest.NewRequest("GET", "/some/url", nil)
		rr := httptest.NewRecorder()
		handler.ServeHTTP(rr, req)
		logOutput.Reset()
	}
}

func BenchmarkWithLogs(b *testing.B) {
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusForbidden) // error class forces log entry
	}), gohm.Config{LogWriter: logOutput})

	for i := 0; i < b.N; i++ {
		req := httptest.NewRequest("GET", "/some/url", nil)
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, req)
		logOutput.Reset()
	}
}
<commit_msg>simplify some log tests<commit_after>package gohm_test

import (
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/karrick/gohm"
)

func TestLogAllWithoutError(t *testing.T) {
	logOutput := new(bytes.Buffer)

	response := "something interesting"

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(response))
	}), gohm.Config{LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), fmt.Sprintf(" %d ", http.StatusOK); !strings.Contains(actual, expected) {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogAllWithError(t *testing.T) {
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusConflict)
	}), gohm.Config{LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), fmt.Sprintf(" %d ", http.StatusConflict); !strings.Contains(actual, expected) {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

// then set log errors, and try both error and not error

func TestLogErrorsWhenWriteHeaderErrorStatus(t *testing.T) {
	logBitmask := gohm.LogStatusErrors
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), fmt.Sprintf(" %d ", http.StatusForbidden); !strings.Contains(actual, expected) {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogErrorsWithError(t *testing.T) {
	logBitmask := gohm.LogStatusErrors
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), fmt.Sprintf(" %d ", http.StatusForbidden); !strings.Contains(actual, expected) {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogErrorsWithoutError(t *testing.T) {
	logBitmask := gohm.LogStatusErrors
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// no error
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), ""; actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

const apacheTimeFormat = "02/Jan/2006:15:04:05 -0700"

func TestLogWithFormatStatusEscapedCharacters(t *testing.T) {
	format := "\\{client-ip\\}"
	logBitmask := gohm.LogStatusAll
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput, LogFormat: format})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	if actual, expected := logOutput.String(), "{client-ip}\n"; actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogWithFormatStatic(t *testing.T) {
	format := "{client} {client-ip} {client-port} - \"{method} {uri} {proto}\" {status} {bytes}"
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogFormat: format, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	// hardcoded test request remote address
	client := req.RemoteAddr
	clientIP := client
	clientPort := client
	if colon := strings.LastIndex(client, ":"); colon != -1 {
		clientIP = client[:colon]
		clientPort = client[colon+1:]
	}

	expected := fmt.Sprintf("%s %s %s - \"GET /some/url HTTP/1.1\" %d 26\n", client, clientIP, clientPort, http.StatusForbidden)
	if actual := logOutput.String(); actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func TestLogWithFormatIgnoresInvalidTokens(t *testing.T) {
	format := "This is an {invalid-token} with a {status} after it"
	logBitmask := gohm.LogStatusAll
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogFormat: format, LogWriter: logOutput})

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	expected := fmt.Sprintf("This is an {invalid-token} with a %d after it\n", http.StatusForbidden)
	if actual := logOutput.String(); actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func timeFromEpochString(t *testing.T, value string) time.Time {
	epoch, err := strconv.Atoi(value)
	if err != nil {
		t.Fatal(err)
	}
	return time.Unix(int64(epoch), 0)
}

func TestLogWithFormatDynamic(t *testing.T) {
	format := "{begin-epoch} {end-epoch} {begin} {begin-iso8601} {end} {end-iso8601} {duration}"
	logBitmask := gohm.LogStatusAll
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gohm.Error(w, "some error", http.StatusForbidden)
	}), gohm.Config{LogBitmask: &logBitmask, LogFormat: format, LogWriter: logOutput})

	beforeTime := time.Now()

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/some/url", nil)

	handler.ServeHTTP(rr, req)

	afterTime := time.Now()

	// first, grab the begin-epoch, and compute the other begin values
	actual := logOutput.String()

	indexFirstSpace := strings.IndexByte(actual, ' ')
	beginString := actual[:indexFirstSpace]

	beginTime := timeFromEpochString(t, beginString)
	if beginTime.Before(beforeTime.Truncate(time.Second)) {
		t.Errorf("Begin: %v; Before: %v", beginTime, beforeTime)
	}
	if beginTime.After(afterTime) {
		t.Errorf("Begin: %v; After: %v", beginTime, afterTime)
	}

	// first, grab the end-epoch, and compute the other end values
	indexSecondSpace := indexFirstSpace + strings.IndexByte(actual[indexFirstSpace+1:], ' ')
	endString := actual[indexFirstSpace+1 : indexSecondSpace+1]
	endTime := timeFromEpochString(t, endString)
	if endTime.Before(beforeTime.Truncate(time.Second)) {
		t.Errorf("End: %v; Before: %v", endTime, beforeTime)
	}
	if endTime.After(afterTime) {
		t.Errorf("End: %v; After: %v", endTime, afterTime)
	}

	if actual, expected := actual[len(actual)-1:], "\n"; actual != expected {
		t.Errorf("Actual: %#v; #Expected: %#v", actual, expected)
	}

	indexFinalSpace := strings.LastIndexByte(actual, ' ')
	durationString := actual[indexFinalSpace+1 : len(actual)-1]

	// to check duration, let's just ensure we can parse it as a float, and it's less than the span duration
	durationFloat, err := strconv.ParseFloat(durationString, 64)
	if err != nil {
		t.Errorf("Actual: %#v; Expected: %#v", err, nil)
	}
	durationMilliseconds := afterTime.Sub(beforeTime).Nanoseconds() / 1000
	if int64(durationFloat*1000000) > durationMilliseconds {
		t.Errorf("durationFloat: %v; durationMilliseconds: %v", durationFloat, durationMilliseconds)
	}

	expected := fmt.Sprintf("%s %s %s %s %s %s %s\n", beginString, endString,
		beginTime.UTC().Format(apacheTimeFormat),
		beginTime.UTC().Format(time.RFC3339),
		endTime.UTC().Format(apacheTimeFormat),
		endTime.UTC().Format(time.RFC3339),
		durationString)

	if actual != expected {
		t.Fatalf("Actual: %#v; Expected: %#v", actual, expected)
	}
}

func BenchmarkWithLogsElided(b *testing.B) {
	logBitmask := uint32(gohm.LogStatus4xx | gohm.LogStatus5xx) // only errors
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// this does not error, so nothing ought to be logged
	}), gohm.Config{LogBitmask: &logBitmask, LogWriter: logOutput})

	for i := 0; i < b.N; i++ {
		req := httptest.NewRequest("GET", "/some/url", nil)
		rr := httptest.NewRecorder()
		handler.ServeHTTP(rr, req)
		logOutput.Reset()
	}
}

func BenchmarkWithLogs(b *testing.B) {
	logOutput := new(bytes.Buffer)

	handler := gohm.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusForbidden) // error class forces log entry
	}), gohm.Config{LogWriter: logOutput})

	for i := 0; i < b.N; i++ {
		req := httptest.NewRequest("GET", "/some/url", nil)
		w := httptest.NewRecorder()
		handler.ServeHTTP(w, req)
		logOutput.Reset()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
	"time"
)

const VERSION = "1.3.dev"

var (
	//
	// Flags
	//   flag description in flag.Usage
	//

	// common
	printHelpFlag    = flag.Bool("h", false, "")
	verboseFlag      = flag.Bool("v", false, "")
	printVersionFlag = flag.Bool("V", false, "")

	// server
	serverModeFlag  = flag.Bool("s", false, "")
	expireBasedFlag = flag.Bool("p", false, "")
	// flag.Duration not useful because there is not unit for days
	leaseExpiredDurationFlag  = flag.String("e", "7d", "")
	cleanupLeaseTimerFlag     = flag.String("t", "30m", "")
	missedPingsThresholdFlag  = flag.Int("m", 3, "")
	keepLeasesOverRestartFlag = flag.Bool("k", false, "")

	// client
	scriptedModeFlag          = flag.Bool("H", false, "")
	clearLeasesFlag           = flag.Bool("c", false, "")
	listNewestLeasesFirstFlag = flag.Bool("n", false, "")
	shutdownServerFlag        = flag.Bool("x", false, "")
)

var (
	verboseLog           *log.Logger
	leaseExpiredDuration time.Duration
	cleanupLeaseTimer    time.Duration
)

var appDataPath = osDependAppDataPath()

func main() {
	flag.Usage = func() {
		fmt.Println("Common:")
		fmt.Println("  -h: print help")
		fmt.Println("  -v: verbose output")
		fmt.Println("  -V: print version")
		fmt.Println("Client mode:")
		fmt.Println("  -c: clear leases")
		fmt.Println("  -H: scripted mode: no headers, dates as unix time")
		fmt.Println("  -n: list newest leases first")
		fmt.Println("  -x: shutdown server")
		fmt.Println("Server mode:")
		fmt.Println("  -s: server mode")
		fmt.Println("  -p: passive mode - no active availability host check - clear leases expire based")
		fmt.Println("  -e: in passive mode: lease expire duration (valid units: 'd', 'h', 'm', 's') - default:",
			*leaseExpiredDurationFlag)
		fmt.Println("  -t: interval for checking of leases validity (valid units: 'd', 'h', 'm', 's') - default:", *cleanupLeaseTimerFlag)
		fmt.Println("  -m: in active mode: missed arpings threshold - default:", *missedPingsThresholdFlag)
		fmt.Println("  -k: keep leases over restart")
	}
	flag.Parse()

	if *printHelpFlag {
		flag.Usage()
		os.Exit(0)
	}

	var err error
	leaseExpiredDuration, err = parseDuration(*leaseExpiredDurationFlag)
	exitOnError(err)

	cleanupLeaseTimer, err = parseDuration(*cleanupLeaseTimerFlag)
	exitOnError(err)

	//
	// action
	//
	if *serverModeFlag {
		server()
	} else {
		client()
	}
}

func osDependAppDataPath() string {
	//
	// set os depend application data path
	//
	if runtime.GOOS == "windows" {
		return os.Getenv("USERPROFILE") + "/lsleases"
	} else {
		return "/var/lib/lsleases"
	}
}
<commit_msg>decrease cleanup timer to 15minutes<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
	"time"
)

const VERSION = "1.3.dev"

var (
	//
	// Flags
	//   flag description in flag.Usage
	//

	// common
	printHelpFlag    = flag.Bool("h", false, "")
	verboseFlag      = flag.Bool("v", false, "")
	printVersionFlag = flag.Bool("V", false, "")

	// server
	serverModeFlag  = flag.Bool("s", false, "")
	expireBasedFlag = flag.Bool("p", false, "")
	// flag.Duration not useful because there is not unit for days
	leaseExpiredDurationFlag  = flag.String("e", "7d", "")
	cleanupLeaseTimerFlag     = flag.String("t", "15m", "")
	missedPingsThresholdFlag  = flag.Int("m", 3, "")
	keepLeasesOverRestartFlag = flag.Bool("k", false, "")

	// client
	scriptedModeFlag          = flag.Bool("H", false, "")
	clearLeasesFlag           = flag.Bool("c", false, "")
	listNewestLeasesFirstFlag = flag.Bool("n", false, "")
	shutdownServerFlag        = flag.Bool("x", false, "")
)

var (
	verboseLog           *log.Logger
	leaseExpiredDuration time.Duration
	cleanupLeaseTimer    time.Duration
)

var appDataPath = osDependAppDataPath()

func main() {
	flag.Usage = func() {
		fmt.Println("Common:")
		fmt.Println("  -h: print help")
		fmt.Println("  -v: verbose output")
		fmt.Println("  -V: print version")
		fmt.Println("Client mode:")
		fmt.Println("  -c: clear leases")
		fmt.Println("  -H: scripted mode: no headers, dates as unix time")
		fmt.Println("  -n: list newest leases first")
		fmt.Println("  -x: shutdown server")
		fmt.Println("Server mode:")
		fmt.Println("  -s: server mode")
		fmt.Println("  -p: passive mode - no active availability host check - clear leases expire based")
		fmt.Println("  -e: in passive mode: lease expire duration (valid units: 'd', 'h', 'm', 's') - default:",
			*leaseExpiredDurationFlag)
		fmt.Println("  -t: interval for checking of leases validity (valid units: 'd', 'h', 'm', 's') - default:", *cleanupLeaseTimerFlag)
		fmt.Println("  -m: in active mode: missed arpings threshold - default:", *missedPingsThresholdFlag)
		fmt.Println("  -k: keep leases over restart")
	}
	flag.Parse()

	if *printHelpFlag {
		flag.Usage()
		os.Exit(0)
	}

	var err error
	leaseExpiredDuration, err = parseDuration(*leaseExpiredDurationFlag)
	exitOnError(err)

	cleanupLeaseTimer, err = parseDuration(*cleanupLeaseTimerFlag)
	exitOnError(err)

	//
	// action
	//
	if *serverModeFlag {
		server()
	} else {
		client()
	}
}

func osDependAppDataPath() string {
	//
	// set os depend application data path
	//
	if runtime.GOOS == "windows" {
		return os.Getenv("USERPROFILE") + "/lsleases"
	} else {
		return "/var/lib/lsleases"
	}
}
<|endoftext|>


<commit_before>package lua

import (
	"fmt"
	"reflect"
	"syscall"
	"unsafe"
)

var lua_pushnil = luaDLL.NewProc("lua_pushnil")

func (this Lua) PushNil() {
	lua_pushnil.Call(this.State())
}

var lua_pushboolean = luaDLL.NewProc("lua_pushboolean")

func (this Lua) PushBool(value bool) {
	if value {
		lua_pushboolean.Call(this.State(), 1)
	} else {
		lua_pushboolean.Call(this.State(), 0)
	}
}

var lua_pushinteger = luaDLL.NewProc("lua_pushinteger")

func (this Lua) PushInteger(value Integer) {
	params := make([]uintptr, 0, 4)
	params = append(params, this.State())
	params = value.Expand(params)
	lua_pushinteger.Call(params...)
}

var lua_pushlstring = luaDLL.NewProc("lua_pushlstring")

func (this Lua) PushBytes(data []byte) {
	if data != nil && len(data) >= 1 {
		lua_pushlstring.Call(this.State(),
			uintptr(unsafe.Pointer(&data[0])),
			uintptr(len(data)))
	} else {
		zerobyte := []byte{'\000'}
		lua_pushlstring.Call(this.State(),
			uintptr(unsafe.Pointer(&zerobyte[0])),
			0)
	}
}

var lua_pushstring = luaDLL.NewProc("lua_pushstring")

func (this Lua) PushString(str string) {
	// BytePtrFromString can not use the string which contains NUL
	array := make([]byte, len(str)+1)
	copy(array, str)
	lua_pushlstring.Call(this.State(),
		uintptr(unsafe.Pointer(&array[0])),
		uintptr(len(str)))
}

var lua_pushlightuserdata = luaDLL.NewProc("lua_pushlightuserdata")

func (this Lua) PushLightUserData(p unsafe.Pointer) {
	lua_pushlightuserdata.Call(this.State(), uintptr(p))
}

var lua_pushvalue = luaDLL.NewProc("lua_pushvalue")

func (this Lua) PushValue(index int) {
	lua_pushvalue.Call(this.State(), uintptr(index))
}

var lua_pushcclosure = luaDLL.NewProc("lua_pushcclosure")

func (this Lua) PushGoClosure(fn func(Lua) int, n uintptr) {
	lua_pushcclosure.Call(this.State(), syscall.NewCallbackCDecl(fn), n)
}

func (this Lua) PushGoFunction(fn func(Lua) int) {
	this.PushGoClosure(fn, 0)
}

func UpValueIndex(i int) int {
	return LUA_REGISTRYINDEX - i
}

type TGoFunction func(Lua) int

func (this TGoFunction) Push(L Lua) int {
	L.PushGoFunction(this)
	return 1
}

func (this Lua) PushCFunction(fn uintptr) {
	lua_pushcclosure.Call(this.State(), fn, 0)
}

type Object interface {
	Push(Lua) int
}

func (this Lua) Push(values ...interface{}) int {
	for _, value := range values {
		if value == nil {
			this.PushNil()
			continue
		}
		switch t := value.(type) {
		case bool:
			this.PushBool(t)
		case int:
			this.PushInteger(Integer(t))
		case int64:
			this.PushInteger(Integer(t))
		case string:
			this.PushString(t)
		case func(L Lua) int:
			this.PushGoFunction(t)
		case []byte:
			this.PushBytes(t)
		case error:
			this.PushString(t.Error())
		case TTable:
			this.NewTable()
			for key, val := range t.Dict {
				this.PushString(key)
				this.Push(val)
				this.SetTable(-3)
			}
			for key, val := range t.Array {
				this.Push(key)
				this.Push(val)
				this.SetTable(-3)
			}
		case unsafe.Pointer:
			this.PushLightUserData(t)
		case Object:
			t.Push(this)
		default:
			if !this.PushReflect(value) {
				panic(fmt.Sprintf(
					"lua.Lua.Push(%T): value is not supported type", t))
			}
		}
	}
	return len(values)
}

func (this Lua) PushReflect(value interface{}) bool {
	if value == nil {
		this.PushNil()
	}
	return this.pushReflect(reflect.ValueOf(value))
}

func (this Lua) pushReflect(value reflect.Value) bool {
	switch value.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		this.PushInteger(Integer(value.Int()))
	case reflect.Uint, reflect.Uint16, reflect.Uint32,
		reflect.Uint64, reflect.Uintptr:
		this.PushInteger(Integer(value.Uint()))
	case reflect.Bool:
		this.PushBool(value.Bool())
	case reflect.String:
		this.PushString(value.String())
	case reflect.Interface:
		this.Push(value.Interface())
	case reflect.Slice, reflect.Array:
		this.NewTable()
		for i, end := 0, value.Len(); i < end; i++ {
			val := value.Index(i)
			this.PushInteger(Integer(i + 1))
			this.pushReflect(val)
			this.SetTable(-3)
		}
	case reflect.Map:
		this.NewTable()
		for _, key := range value.MapKeys() {
			this.pushReflect(key)
			val := value.MapIndex(key)
			this.pushReflect(val)
			this.SetTable(-3)
		}
	default:
		return false
	}
	return true
}
<commit_msg>lua.Lua.PushReflect push []byte with lua_pushlstring() not as table<commit_after>package lua

import (
	"fmt"
	"reflect"
	"syscall"
	"unsafe"
)

var lua_pushnil = luaDLL.NewProc("lua_pushnil")

func (this Lua) PushNil() {
	lua_pushnil.Call(this.State())
}

var lua_pushboolean = luaDLL.NewProc("lua_pushboolean")

func (this Lua) PushBool(value bool) {
	if value {
		lua_pushboolean.Call(this.State(), 1)
	} else {
		lua_pushboolean.Call(this.State(), 0)
	}
}

var lua_pushinteger = luaDLL.NewProc("lua_pushinteger")

func (this Lua) PushInteger(value Integer) {
	params := make([]uintptr, 0, 4)
	params = append(params, this.State())
	params = value.Expand(params)
	lua_pushinteger.Call(params...)
}

var lua_pushlstring = luaDLL.NewProc("lua_pushlstring")

func (this Lua) PushBytes(data []byte) {
	if data != nil && len(data) >= 1 {
		lua_pushlstring.Call(this.State(),
			uintptr(unsafe.Pointer(&data[0])),
			uintptr(len(data)))
	} else {
		zerobyte := []byte{'\000'}
		lua_pushlstring.Call(this.State(),
			uintptr(unsafe.Pointer(&zerobyte[0])),
			0)
	}
}

var lua_pushstring = luaDLL.NewProc("lua_pushstring")

func (this Lua) PushString(str string) {
	// BytePtrFromString can not use the string which contains NUL
	array := make([]byte, len(str)+1)
	copy(array, str)
	lua_pushlstring.Call(this.State(),
		uintptr(unsafe.Pointer(&array[0])),
		uintptr(len(str)))
}

var lua_pushlightuserdata = luaDLL.NewProc("lua_pushlightuserdata")

func (this Lua) PushLightUserData(p unsafe.Pointer) {
	lua_pushlightuserdata.Call(this.State(), uintptr(p))
}

var lua_pushvalue = luaDLL.NewProc("lua_pushvalue")

func (this Lua) PushValue(index int) {
	lua_pushvalue.Call(this.State(), uintptr(index))
}

var lua_pushcclosure = luaDLL.NewProc("lua_pushcclosure")

func (this Lua) PushGoClosure(fn func(Lua) int, n uintptr) {
	lua_pushcclosure.Call(this.State(), syscall.NewCallbackCDecl(fn), n)
}

func (this Lua) PushGoFunction(fn func(Lua) int) {
	this.PushGoClosure(fn, 0)
}

func UpValueIndex(i int) int {
	return LUA_REGISTRYINDEX - i
}

type TGoFunction func(Lua) int

func (this TGoFunction) Push(L Lua) int {
	L.PushGoFunction(this)
	return 1
}

func (this Lua) PushCFunction(fn uintptr) {
	lua_pushcclosure.Call(this.State(), fn, 0)
}

type Object interface {
	Push(Lua) int
}

func (this Lua) Push(values ...interface{}) int {
	for _, value := range values {
		if value == nil {
			this.PushNil()
			continue
		}
		switch t := value.(type) {
		case bool:
			this.PushBool(t)
		case int:
			this.PushInteger(Integer(t))
		case int64:
			this.PushInteger(Integer(t))
		case string:
			this.PushString(t)
		case func(L Lua) int:
			this.PushGoFunction(t)
		case []byte:
			this.PushBytes(t)
		case error:
			this.PushString(t.Error())
		case TTable:
			this.NewTable()
			for key, val := range t.Dict {
				this.PushString(key)
				this.Push(val)
				this.SetTable(-3)
			}
			for key, val := range t.Array {
				this.Push(key)
				this.Push(val)
				this.SetTable(-3)
			}
		case unsafe.Pointer:
			this.PushLightUserData(t)
		case Object:
			t.Push(this)
		default:
			if !this.PushReflect(value) {
				panic(fmt.Sprintf(
					"lua.Lua.Push(%T): value is not supported type", t))
			}
		}
	}
	return len(values)
}

func (this Lua) PushReflect(value interface{}) bool {
	if value == nil {
		this.PushNil()
	}
	return this.pushReflect(reflect.ValueOf(value))
}

func (this Lua) pushReflect(value reflect.Value) bool {
	switch value.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		this.PushInteger(Integer(value.Int()))
	case reflect.Uint, reflect.Uint16, reflect.Uint32,
		reflect.Uint64, reflect.Uintptr:
		this.PushInteger(Integer(value.Uint()))
	case reflect.Bool:
		this.PushBool(value.Bool())
	case reflect.String:
		this.PushString(value.String())
	case reflect.Interface:
		this.Push(value.Interface())
	case reflect.Slice, reflect.Array:
		elem := value.Type().Elem()
		if elem.Kind() == reflect.Uint8 {
			buffer := make([]byte, 0, value.Len())
			for i, end := 0, value.Len(); i < end; i++ {
				buffer = append(buffer, byte(value.Index(i).Uint()))
			}
			this.PushBytes(buffer)
		} else {
			this.NewTable()
			for i, end := 0, value.Len(); i < end; i++ {
				val := value.Index(i)
				this.PushInteger(Integer(i + 1))
				this.pushReflect(val)
				this.SetTable(-3)
			}
		}
	case reflect.Map:
		this.NewTable()
		for _, key := range value.MapKeys() {
			this.pushReflect(key)
			val := value.MapIndex(key)
			this.pushReflect(val)
			this.SetTable(-3)
		}
	default:
		return false
	}
	return true
}
<|endoftext|>


<commit_before>package models

type Ping struct {
	FileId    string `json:"fileId"`
	AccountId int64  `json:"accountId,string"`
}
<commit_msg>Socialapi: implemented a basic model for Ping<commit_after>// Package provides the basic data structures for the collaboration worker
package models

import "time"

// Ping holds the ping data that comes from a collaboration session
type Ping struct {
	// FileId holds the collaboration file id
	FileId string `json:"fileId"`

	// AccountId holds the host's id the only one that can send this request
	AccountId int64 `json:"accountId,string"`

	// CreatedAt holds the ping time
	CreatedAt time.Time `json:"createdAt"`
}

// NewPing creates an empty ping
func NewPing() *Ping {
	return &Ping{}
}

// GetId returns the id of the ping, it is here just to satisfy Bongo.Modellable
// interface
func (a Ping) GetId() int64 {
	return 0
}

// BongoName returns the unique name for the bongo operations
func (a Ping) BongoName() string {
	return "collaboration.ping"
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/user"
	"path"
	"path/filepath"
	"strings"
	"syscall"

	"gopkg.in/macaroon-bakery.v2/httpbakery/form"

	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/logging"
	"github.com/lxc/lxd/shared/version"

	schemaform "gopkg.in/juju/environschema.v1/form"
)

var configPath string
var execName string

func main() {
	execName = os.Args[0]

	if err := run(); err != nil {
		msg := fmt.Sprintf(i18n.G("error: %v"), err)

		lxdErr := getLocalErr(err)
		switch lxdErr {
		case syscall.ENOENT:
			msg = i18n.G("LXD socket not found; is LXD installed and running?")
		case syscall.ECONNREFUSED:
			msg = i18n.G("Connection refused; is LXD running?")
		case syscall.EACCES:
			msg = i18n.G("Permission denied, are you in the lxd group?")
		}

		fmt.Fprintln(os.Stderr, fmt.Sprintf("%s", msg))
		os.Exit(1)
	}
}

func run() error {
	verbose := gnuflag.Bool("verbose", false, i18n.G("Enable verbose mode"))
	debug := gnuflag.Bool("debug", false, i18n.G("Enable debug mode"))
	forceLocal := gnuflag.Bool("force-local", false, i18n.G("Force using the local unix socket"))
	noAlias := gnuflag.Bool("no-alias", false, i18n.G("Ignore aliases when determining what command to run"))

	var configDir string
	if os.Getenv("LXD_CONF") != "" {
		configDir = os.Getenv("LXD_CONF")
	} else if os.Getenv("HOME") != "" {
		configDir = path.Join(os.Getenv("HOME"), ".config", "lxc")
	} else {
		user, err := user.Current()
		if err != nil {
			return err
		}

		configDir = path.Join(user.HomeDir, ".config", "lxc")
	}
	configPath = os.ExpandEnv(path.Join(configDir, "config.yml"))

	if len(os.Args) >= 3 && os.Args[1] == "config" && os.Args[2] == "profile" {
		fmt.Fprintf(os.Stderr, i18n.G("`lxc config profile` is deprecated, please use `lxc profile`")+"\n")
		os.Args = append(os.Args[:1], os.Args[2:]...)
	}

	if len(os.Args) >= 2 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
		os.Args[1] = "help"
	}

	if len(os.Args) >= 2 && (os.Args[1] == "--all") {
		os.Args = []string{os.Args[0], "help", "--all"}
	}

	if shared.StringInSlice("--version", os.Args) {
		os.Args = []string{os.Args[0], "version"}
	}

	if len(os.Args) < 2 {
		commands["help"].run(nil, nil)
		os.Exit(1)
	}

	var conf *config.Config
	var err error

	if *forceLocal {
		conf = config.NewConfig("", true)
	} else if shared.PathExists(configPath) {
		conf, err = config.LoadConfig(configPath)
		if err != nil {
			return err
		}
	} else {
		conf = config.NewConfig(filepath.Dir(configPath), true)
	}

	// Add interactor for external authentication
	conf.SetAuthInteractor(form.Interactor{Filler: schemaform.IOFiller{}})

	// Save cookies on exit
	defer conf.SaveCookies()

	// Set the user agent
	conf.UserAgent = version.UserAgent

	// This is quite impolite, but it seems gnuflag needs us to shift our
	// own exename out of the arguments before parsing them. However, this
	// is useful for execIfAlias, which wants to know exactly the command
	// line we received, and in some cases is called before this shift, and
	// in others after. So, let's save the original args.
	origArgs := os.Args
	name := os.Args[1]

	/* at this point we haven't parsed the args, so we have to look for
	 * --no-alias by hand.
	 */
	if !shared.StringInSlice("--no-alias", origArgs) {
		execIfAliases(conf, origArgs)
	}
	cmd, ok := commands[name]
	if !ok {
		commands["help"].run(nil, nil)
		fmt.Fprintf(os.Stderr, "\n"+i18n.G("error: unknown command: %s")+"\n", name)
		os.Exit(1)
	}
	cmd.flags()
	gnuflag.Usage = func() {
		fmt.Print(cmd.usage())
		fmt.Printf("\n\n%s\n", i18n.G("Options:"))

		gnuflag.SetOut(os.Stdout)
		gnuflag.PrintDefaults()
		os.Exit(0)
	}

	os.Args = os.Args[1:]
	gnuflag.Parse(true)

	logger.Log, err = logging.GetLogger("", "", *verbose, *debug, nil)
	if err != nil {
		return err
	}

	// If the user is running a command that may attempt to connect to the local daemon
	// and this is the first time the client has been run by the user, then check to see
	// if LXD has been properly configured.  Don't display the message if the var path
	// does not exist (LXD not installed), as the user may be targeting a remote daemon.
	if os.Args[0] != "help" && os.Args[0] != "version" && shared.PathExists(shared.VarPath("")) && !shared.PathExists(conf.ConfigDir) {
		// Create the config dir so that we don't get in here again for this user.
		err = os.MkdirAll(conf.ConfigDir, 0750)
		if err != nil {
			return err
		}

		// And save the initial configuration
		err = conf.SaveConfig(configPath)
		if err != nil {
			return err
		}

		fmt.Fprintf(os.Stderr, i18n.G("If this is your first time using LXD, you should also run: lxd init")+"\n")
		fmt.Fprintf(os.Stderr, i18n.G("To start your first container, try: lxc launch ubuntu:16.04")+"\n\n")
	}

	err = cmd.run(conf, gnuflag.Args())
	if err == errArgs || err == errUsage {
		out := os.Stdout
		if err == errArgs {
			/* If we got an error about invalid arguments, let's try to
			 * expand this as an alias
			 */
			if !*noAlias {
				execIfAliases(conf, origArgs)
			}

			out = os.Stderr
		}
		gnuflag.SetOut(out)

		if err == errArgs {
			fmt.Fprintf(out, i18n.G("error: %v"), err)
			fmt.Fprintf(out, "\n\n")
		}
		fmt.Fprint(out, cmd.usage())
		fmt.Fprintf(out, "\n\n%s\n", i18n.G("Options:"))

		gnuflag.PrintDefaults()

		if err == errArgs {
			os.Exit(1)
		}
		os.Exit(0)
	}

	return err
}

type command interface {
	usage() string
	flags()
	showByDefault() bool
	run(conf *config.Config, args []string) error
}

var commands = map[string]command{
	"config":    &configCmd{},
	"console":   &consoleCmd{},
	"copy":      &copyCmd{},
	"delete":    &deleteCmd{},
	"exec":      &execCmd{},
	"file":      &fileCmd{},
	"finger":    &fingerCmd{},
	"query":     &queryCmd{},
	"help":      &helpCmd{},
	"image":     &imageCmd{},
	"info":      &infoCmd{},
	"init":      &initCmd{},
	"launch":    &launchCmd{},
	"list":      &listCmd{},
	"manpage":   &manpageCmd{},
	"monitor":   &monitorCmd{},
	"rename":    &renameCmd{},
	"move":      &moveCmd{},
	"network":   &networkCmd{},
	"operation": &operationCmd{},
	"pause": &actionCmd{
		action:      shared.Freeze,
		description: i18n.G("Pause containers."),
		name:        "pause",
	},
	"profile": &profileCmd{},
	"publish": &publishCmd{},
	"remote":  &remoteCmd{},
	"restart": &actionCmd{
		action:      shared.Restart,
		description: i18n.G("Restart containers."),
		hasTimeout:  true,
		visible:     true,
		name:        "restart",
		timeout:     -1,
	},
	"restore":  &restoreCmd{},
	"snapshot": &snapshotCmd{},
	"start": &actionCmd{
		action:      shared.Start,
		description: i18n.G("Start containers."),
		visible:     true,
		name:        "start",
	},
	"stop": &actionCmd{
		action:      shared.Stop,
		description: i18n.G("Stop containers."),
		hasTimeout:  true,
		visible:     true,
		name:        "stop",
		timeout:     -1,
	},
	"storage": &storageCmd{},
	"version": &versionCmd{},
}

// defaultAliases contains LXC's built-in command line aliases.  The built-in
// aliases are checked only if no user-defined alias was found.
var defaultAliases = map[string]string{
	"shell": "exec @ARGS@ -- login -f root",

	"cp": "copy",
	"ls": "list",
	"mv": "move",
	"rm": "delete",

	"image cp": "image copy",
	"image ls": "image list",
	"image rm": "image delete",

	"image alias ls": "image alias list",
	"image alias rm": "image alias delete",

	"remote ls": "remote list",
	"remote mv": "remote rename",
	"remote rm": "remote remove",

	"config device ls": "config device list",
	"config device rm": "config device remove",
}

var errArgs = fmt.Errorf(i18n.G("wrong number of subcommand arguments"))
var errUsage = fmt.Errorf("show usage")

func findAlias(aliases map[string]string, origArgs []string) ([]string, []string, bool) {
	foundAlias := false
	aliasKey := []string{}
	aliasValue := []string{}

	for k, v := range aliases {
		foundAlias = true
		for i, key := range strings.Split(k, " ") {
			if len(origArgs) <= i+1 || origArgs[i+1] != key {
				foundAlias = false
				break
			}
		}

		if foundAlias {
			aliasKey = strings.Split(k, " ")
			aliasValue = strings.Split(v, " ")
			break
		}
	}

	return aliasKey, aliasValue, foundAlias
}

func expandAlias(conf *config.Config, origArgs []string) ([]string, bool) {
	aliasKey, aliasValue, foundAlias := findAlias(conf.Aliases, origArgs)
	if !foundAlias {
		aliasKey, aliasValue, foundAlias = findAlias(defaultAliases, origArgs)
		if !foundAlias {
			return []string{}, false
		}
	}

	newArgs := []string{origArgs[0]}
	hasReplacedArgsVar := false

	for i, aliasArg := range aliasValue {
		if aliasArg == "@ARGS@" && len(origArgs) > i {
			newArgs = append(newArgs, origArgs[i+1:]...)
			hasReplacedArgsVar = true
		} else {
			newArgs = append(newArgs, aliasArg)
		}
	}

	if !hasReplacedArgsVar {
		/* add the rest of the arguments */
		newArgs = append(newArgs, origArgs[len(aliasKey)+1:]...)
	}

	/* don't re-do aliases the next time; this allows us to have recursive
	 * aliases, e.g. `lxc list` to `lxc list -c n`
	 */
	newArgs = append(newArgs[:2], append([]string{"--no-alias"}, newArgs[2:]...)...)

	return newArgs, true
}

func execIfAliases(conf *config.Config, origArgs []string) {
	newArgs, expanded := expandAlias(conf, origArgs)
	if !expanded {
		return
	}

	path, err := exec.LookPath(origArgs[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), err)
		os.Exit(5)
	}
	ret := syscall.Exec(path, newArgs, syscall.Environ())
	fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), ret)
	os.Exit(5)
}
<commit_msg>lxc/shell: Switch to using su -l<commit_after>package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/user"
	"path"
	"path/filepath"
	"strings"
	"syscall"

	"gopkg.in/macaroon-bakery.v2/httpbakery/form"

	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/logging"
	"github.com/lxc/lxd/shared/version"

	schemaform "gopkg.in/juju/environschema.v1/form"
)

var configPath string
var execName string

func main() {
	execName = os.Args[0]

	if err := run(); err != nil {
		msg := fmt.Sprintf(i18n.G("error: %v"), err)

		lxdErr := getLocalErr(err)
		switch lxdErr {
		case syscall.ENOENT:
			msg = i18n.G("LXD socket not found; is LXD installed and running?")
		case syscall.ECONNREFUSED:
			msg = i18n.G("Connection refused; is LXD running?")
		case syscall.EACCES:
			msg = i18n.G("Permission denied, are you in the lxd group?")
		}

		fmt.Fprintln(os.Stderr, fmt.Sprintf("%s", msg))
		os.Exit(1)
	}
}

func run() error {
	verbose := gnuflag.Bool("verbose", false, i18n.G("Enable verbose mode"))
	debug := gnuflag.Bool("debug", false, i18n.G("Enable debug mode"))
	forceLocal := gnuflag.Bool("force-local", false, i18n.G("Force using the local unix socket"))
	noAlias := gnuflag.Bool("no-alias", false, i18n.G("Ignore aliases when determining what command to run"))

	var configDir string
	if os.Getenv("LXD_CONF") != "" {
		configDir = os.Getenv("LXD_CONF")
	} else if os.Getenv("HOME") != "" {
		configDir = path.Join(os.Getenv("HOME"), ".config", "lxc")
	} else {
		user, err := user.Current()
		if err != nil {
			return err
		}

		configDir = path.Join(user.HomeDir, ".config", "lxc")
	}
	configPath = os.ExpandEnv(path.Join(configDir, "config.yml"))

	if len(os.Args) >= 3 && os.Args[1] == "config" && os.Args[2] == "profile" {
		fmt.Fprintf(os.Stderr, i18n.G("`lxc config profile` is deprecated, please use `lxc profile`")+"\n")
		os.Args = append(os.Args[:1], os.Args[2:]...)
	}

	if len(os.Args) >= 2 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
		os.Args[1] = "help"
	}

	if len(os.Args) >= 2 && (os.Args[1] == "--all") {
		os.Args = []string{os.Args[0], "help", "--all"}
	}

	if shared.StringInSlice("--version", os.Args) {
		os.Args = []string{os.Args[0], "version"}
	}

	if len(os.Args) < 2 {
		commands["help"].run(nil, nil)
		os.Exit(1)
	}

	var conf *config.Config
	var err error

	if *forceLocal {
		conf = config.NewConfig("", true)
	} else if shared.PathExists(configPath) {
		conf, err = config.LoadConfig(configPath)
		if err != nil {
			return err
		}
	} else {
		conf = config.NewConfig(filepath.Dir(configPath), true)
	}

	// Add interactor for external authentication
	conf.SetAuthInteractor(form.Interactor{Filler: schemaform.IOFiller{}})

	// Save cookies on exit
	defer conf.SaveCookies()

	// Set the user agent
	conf.UserAgent = version.UserAgent

	// This is quite impolite, but it seems gnuflag needs us to shift our
	// own exename out of the arguments before parsing them. However, this
	// is useful for execIfAlias, which wants to know exactly the command
	// line we received, and in some cases is called before this shift, and
	// in others after. So, let's save the original args.
	origArgs := os.Args
	name := os.Args[1]

	/* at this point we haven't parsed the args, so we have to look for
	 * --no-alias by hand.
	 */
	if !shared.StringInSlice("--no-alias", origArgs) {
		execIfAliases(conf, origArgs)
	}
	cmd, ok := commands[name]
	if !ok {
		commands["help"].run(nil, nil)
		fmt.Fprintf(os.Stderr, "\n"+i18n.G("error: unknown command: %s")+"\n", name)
		os.Exit(1)
	}
	cmd.flags()
	gnuflag.Usage = func() {
		fmt.Print(cmd.usage())
		fmt.Printf("\n\n%s\n", i18n.G("Options:"))

		gnuflag.SetOut(os.Stdout)
		gnuflag.PrintDefaults()
		os.Exit(0)
	}

	os.Args = os.Args[1:]
	gnuflag.Parse(true)

	logger.Log, err = logging.GetLogger("", "", *verbose, *debug, nil)
	if err != nil {
		return err
	}

	// If the user is running a command that may attempt to connect to the local daemon
	// and this is the first time the client has been run by the user, then check to see
	// if LXD has been properly configured.  Don't display the message if the var path
	// does not exist (LXD not installed), as the user may be targeting a remote daemon.
	if os.Args[0] != "help" && os.Args[0] != "version" && shared.PathExists(shared.VarPath("")) && !shared.PathExists(conf.ConfigDir) {
		// Create the config dir so that we don't get in here again for this user.
		err = os.MkdirAll(conf.ConfigDir, 0750)
		if err != nil {
			return err
		}

		// And save the initial configuration
		err = conf.SaveConfig(configPath)
		if err != nil {
			return err
		}

		fmt.Fprintf(os.Stderr, i18n.G("If this is your first time using LXD, you should also run: lxd init")+"\n")
		fmt.Fprintf(os.Stderr, i18n.G("To start your first container, try: lxc launch ubuntu:16.04")+"\n\n")
	}

	err = cmd.run(conf, gnuflag.Args())
	if err == errArgs || err == errUsage {
		out := os.Stdout
		if err == errArgs {
			/* If we got an error about invalid arguments, let's try to
			 * expand this as an alias
			 */
			if !*noAlias {
				execIfAliases(conf, origArgs)
			}

			out = os.Stderr
		}
		gnuflag.SetOut(out)

		if err == errArgs {
			fmt.Fprintf(out, i18n.G("error: %v"), err)
			fmt.Fprintf(out, "\n\n")
		}
		fmt.Fprint(out, cmd.usage())
		fmt.Fprintf(out, "\n\n%s\n", i18n.G("Options:"))

		gnuflag.PrintDefaults()

		if err == errArgs {
			os.Exit(1)
		}
		os.Exit(0)
	}

	return err
}

type command interface {
	usage() string
	flags()
	showByDefault() bool
	run(conf *config.Config, args []string) error
}

var commands = map[string]command{
	"config":    &configCmd{},
	"console":   &consoleCmd{},
	"copy":      &copyCmd{},
	"delete":    &deleteCmd{},
	"exec":      &execCmd{},
	"file":      &fileCmd{},
	"finger":    &fingerCmd{},
	"query":     &queryCmd{},
	"help":      &helpCmd{},
	"image":     &imageCmd{},
	"info":      &infoCmd{},
	"init":      &initCmd{},
	"launch":    &launchCmd{},
	"list":      &listCmd{},
	"manpage":   &manpageCmd{},
	"monitor":   &monitorCmd{},
	"rename":    &renameCmd{},
	"move":      &moveCmd{},
	"network":   &networkCmd{},
	"operation": &operationCmd{},
	"pause": &actionCmd{
		action:      shared.Freeze,
		description: i18n.G("Pause containers."),
		name:        "pause",
	},
	"profile": &profileCmd{},
	"publish": &publishCmd{},
	"remote":  &remoteCmd{},
	"restart": &actionCmd{
		action:      shared.Restart,
		description: i18n.G("Restart containers."),
		hasTimeout:  true,
		visible:     true,
		name:        "restart",
		timeout:     -1,
	},
	"restore":  &restoreCmd{},
	"snapshot": &snapshotCmd{},
	"start": &actionCmd{
		action:      shared.Start,
		description: i18n.G("Start containers."),
		visible:     true,
		name:        "start",
	},
	"stop": &actionCmd{
		action:      shared.Stop,
		description: i18n.G("Stop containers."),
		hasTimeout:  true,
		visible:     true,
		name:        "stop",
		timeout:     -1,
	},
	"storage": &storageCmd{},
	"version": &versionCmd{},
}

// defaultAliases contains LXC's built-in command line aliases.  The built-in
// aliases are checked only if no user-defined alias was found.
var defaultAliases = map[string]string{
	"shell": "exec @ARGS@ -- su -l",

	"cp": "copy",
	"ls": "list",
	"mv": "move",
	"rm": "delete",

	"image cp": "image copy",
	"image ls": "image list",
	"image rm": "image delete",

	"image alias ls": "image alias list",
	"image alias rm": "image alias delete",

	"remote ls": "remote list",
	"remote mv": "remote rename",
	"remote rm": "remote remove",

	"config device ls": "config device list",
	"config device rm": "config device remove",
}

var errArgs = fmt.Errorf(i18n.G("wrong number of subcommand arguments"))
var errUsage = fmt.Errorf("show usage")

func findAlias(aliases map[string]string, origArgs []string) ([]string, []string, bool) {
	foundAlias := false
	aliasKey := []string{}
	aliasValue := []string{}

	for k, v := range aliases {
		foundAlias = true
		for i, key := range strings.Split(k, " ") {
			if len(origArgs) <= i+1 || origArgs[i+1] != key {
				foundAlias = false
				break
			}
		}

		if foundAlias {
			aliasKey = strings.Split(k, " ")
			aliasValue = strings.Split(v, " ")
			break
		}
	}

	return aliasKey, aliasValue, foundAlias
}

func expandAlias(conf *config.Config, origArgs []string) ([]string, bool) {
	aliasKey, aliasValue, foundAlias := findAlias(conf.Aliases, origArgs)
	if !foundAlias {
		aliasKey, aliasValue, foundAlias = findAlias(defaultAliases, origArgs)
		if !foundAlias {
			return []string{}, false
		}
	}

	newArgs := []string{origArgs[0]}
	hasReplacedArgsVar := false

	for i, aliasArg := range aliasValue {
		if aliasArg == "@ARGS@" && len(origArgs) > i {
			newArgs = append(newArgs, origArgs[i+1:]...)
			hasReplacedArgsVar = true
		} else {
			newArgs = append(newArgs, aliasArg)
		}
	}

	if !hasReplacedArgsVar {
		/* add the rest of the arguments */
		newArgs = append(newArgs, origArgs[len(aliasKey)+1:]...)
	}

	/* don't re-do aliases the next time; this allows us to have recursive
	 * aliases, e.g. `lxc list` to `lxc list -c n`
	 */
	newArgs = append(newArgs[:2], append([]string{"--no-alias"}, newArgs[2:]...)...)

	return newArgs, true
}

func execIfAliases(conf *config.Config, origArgs []string) {
	newArgs, expanded := expandAlias(conf, origArgs)
	if !expanded {
		return
	}

	path, err := exec.LookPath(origArgs[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), err)
		os.Exit(5)
	}
	ret := syscall.Exec(path, newArgs, syscall.Environ())
	fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), ret)
	os.Exit(5)
}
<|endoftext|>