<commit_before>package gateway

import (
	"net"
	"net/http"
	"runtime"
	"sync"
	"sync/atomic"
	"time"

	log "github.com/funkygao/log4go"
	"github.com/julienschmidt/httprouter"
)

type webServer struct {
	name       string
	maxClients int
	gw         *Gateway

	httpListener net.Listener
	httpServer   *http.Server

	httpsListener net.Listener
	httpsServer   *http.Server

	router *httprouter.Router

	waitExitFunc    waitExitFunc
	connStateFunc   connStateFunc
	onConnNewFunc   onConnNewFunc
	onConnCloseFunc onConnCloseFunc

	onStop   func()
	onceStop sync.Once

	activeConnN int32
}

func newWebServer(name string, httpAddr, httpsAddr string, maxClients int,
	gw *Gateway) *webServer {
	this := &webServer{
		name:       name,
		gw:         gw,
		maxClients: maxClients,
		router:     httprouter.New(),
	}

	if Options.EnableHttpPanicRecover {
		this.router.PanicHandler = func(w http.ResponseWriter,
			r *http.Request, err interface{}) {
			log.Error("%s[%s] %s %s: %+v", this.name, r.RemoteAddr, r.Method, r.RequestURI, err)
		}
	}

	if httpAddr != "" {
		this.httpServer = &http.Server{
			Addr:           httpAddr,
			Handler:        this.router,
			ReadTimeout:    Options.HttpReadTimeout,
			WriteTimeout:   Options.HttpWriteTimeout,
			MaxHeaderBytes: Options.HttpHeaderMaxBytes,
		}
	}

	if httpsAddr != "" {
		this.httpsServer = &http.Server{
			Addr:           httpsAddr,
			Handler:        this.router,
			ReadTimeout:    Options.HttpReadTimeout,
			WriteTimeout:   Options.HttpWriteTimeout,
			MaxHeaderBytes: Options.HttpHeaderMaxBytes,
		}
	}

	return this
}

func (this *webServer) Start() {
	if this.waitExitFunc == nil {
		this.waitExitFunc = this.waitExit
	}
	if this.connStateFunc == nil {
		this.connStateFunc = this.connStateHandler
	}

	if this.httpsServer != nil {
		this.httpsServer.ConnState = this.connStateFunc
		this.startServer(true)
	}

	if this.httpServer != nil {
		this.httpServer.ConnState = this.connStateFunc
		this.startServer(false)
	}

}

func (this *webServer) Router() *httprouter.Router {
	return this.router
}

func (this *webServer) startServer(https bool) {
	var err error
	waitListenerUp := make(chan struct{})
	go func() {
		if Options.CpuAffinity {
			runtime.LockOSThread()
		}

		var (
			retryDelay         time.Duration
			theListener        net.Listener
			waitListenerUpOnce sync.Once
			addr               string
		)
		for {
			if https {
				this.httpsListener, err = net.Listen("tcp", this.httpsServer.Addr)
				if err != nil {
					if retryDelay == 0 {
						retryDelay = 50 * time.Millisecond
					} else {
						retryDelay = 2 * retryDelay
					}
					if maxDelay := time.Second; retryDelay > maxDelay {
						retryDelay = maxDelay
					}
					log.Error("%s listener %v, retry in %v", this.name, err, retryDelay)
					time.Sleep(retryDelay)
					continue
				}

				this.httpsListener, err = setupHttpsListener(this.httpsListener,
					this.gw.certFile, this.gw.keyFile)
				if err != nil {
					panic(err)
				}

				theListener = this.httpsListener
				addr = this.httpsServer.Addr
			} else {
				this.httpListener, err = net.Listen("tcp", this.httpServer.Addr)
				theListener = this.httpListener
				addr = this.httpServer.Addr
			}

			if err != nil {
				if retryDelay == 0 {
					retryDelay = 50 * time.Millisecond
				} else {
					retryDelay = 2 * retryDelay
				}
				if maxDelay := time.Second; retryDelay > maxDelay {
					retryDelay = maxDelay
				}
				log.Error("%s listener %v, retry in %v", this.name, err, retryDelay)
				time.Sleep(retryDelay)
				continue
			}

			theListener = LimitListener(this.name, this.gw, theListener, this.maxClients)
			waitListenerUpOnce.Do(func() {
				close(waitListenerUp)
			})

			// on non-temporary err, net/http will close the listener
			if https {
				err = this.httpsServer.Serve(theListener)
			} else {
				err = this.httpServer.Serve(theListener)
			}

			select {
			case <-this.gw.shutdownCh:
				log.Trace("%s server stopped on %s", this.name, addr)
				return

			default:
				log.Error("%s server: %v", this.name, err)
			}
		}
	}()

	// FIXME if net.Listen fails, kateway will not be able to stop
	// e,g. start a kateway, then start another, the 2nd will not be able to stop

	// wait for the listener up
	<-waitListenerUp

	this.gw.wg.Add(1)
	if https {
		go this.waitExitFunc(this.httpsServer, this.httpsListener, this.gw.shutdownCh)
	} else {
		go this.waitExitFunc(this.httpServer, this.httpListener, this.gw.shutdownCh)
	}

	if https {
		log.Info("%s https server ready on %s", this.name, this.httpsServer.Addr)
	} else {
		log.Info("%s http server ready on %s", this.name, this.httpServer.Addr)
	}
}

func (this *webServer) connStateHandler(c net.Conn, cs http.ConnState) {
	switch cs {
	case http.StateNew:
		atomic.AddInt32(&this.activeConnN, 1)

		if this.onConnNewFunc != nil {
			this.onConnNewFunc(c)
		}

	case http.StateActive, http.StateIdle:
		// do nothing by default

	case http.StateClosed, http.StateHijacked:
		atomic.AddInt32(&this.activeConnN, -1)

		if this.onConnCloseFunc != nil {
			this.onConnCloseFunc(c)
		}

	}
}

func (this *webServer) waitExit(server *http.Server, listener net.Listener, exit <-chan struct{}) {
	<-exit

	// HTTP response will have "Connection: close"
	server.SetKeepAlivesEnabled(false)

	// avoid new connections
	if err := listener.Close(); err != nil {
		log.Error(err.Error())
	}

	log.Trace("%s on %s listener closed", this.name, server.Addr)

	waitStart := time.Now()
	for {
		activeConnN := atomic.LoadInt32(&this.activeConnN)
		if activeConnN == 0 {
			// good luck, all connections finished
			break
		}

		// timeout mechanism
		if time.Since(waitStart) > Options.SubTimeout {
			log.Warn("%s on %s still left %d conns, but forced to shutdown after %s",
				this.name, server.Addr, activeConnN, Options.SubTimeout)
			break
		}

		time.Sleep(time.Millisecond * 50)
	}
	log.Trace("%s on %s all connections finished", this.name, server.Addr)

	if this.onStop != nil {
		// if both http and https, without sync once, onStop will be called twice
		this.onceStop.Do(func() {
			this.onStop()
		})
	}

	this.gw.wg.Done()
}

func (this *webServer) notFoundHandler(w http.ResponseWriter, r *http.Request) {
	log.Error("%s: not found %s", this.name, r.RequestURI)

	w.Header().Set("Connection", "close")
	writeErrorResponse(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}
<commit_msg>wait 1 more second before forcefully shutdown conn<commit_after>package gateway

import (
	"net"
	"net/http"
	"runtime"
	"sync"
	"sync/atomic"
	"time"

	log "github.com/funkygao/log4go"
	"github.com/julienschmidt/httprouter"
)

type webServer struct {
	name       string
	maxClients int
	gw         *Gateway

	httpListener net.Listener
	httpServer   *http.Server

	httpsListener net.Listener
	httpsServer   *http.Server

	router *httprouter.Router

	waitExitFunc    waitExitFunc
	connStateFunc   connStateFunc
	onConnNewFunc   onConnNewFunc
	onConnCloseFunc onConnCloseFunc

	onStop   func()
	onceStop sync.Once

	activeConnN int32
}

func newWebServer(name string, httpAddr, httpsAddr string, maxClients int,
	gw *Gateway) *webServer {
	this := &webServer{
		name:       name,
		gw:         gw,
		maxClients: maxClients,
		router:     httprouter.New(),
	}

	if Options.EnableHttpPanicRecover {
		this.router.PanicHandler = func(w http.ResponseWriter,
			r *http.Request, err interface{}) {
			log.Error("%s[%s] %s %s: %+v", this.name, r.RemoteAddr, r.Method, r.RequestURI, err)
		}
	}

	if httpAddr != "" {
		this.httpServer = &http.Server{
			Addr:           httpAddr,
			Handler:        this.router,
			ReadTimeout:    Options.HttpReadTimeout,
			WriteTimeout:   Options.HttpWriteTimeout,
			MaxHeaderBytes: Options.HttpHeaderMaxBytes,
		}
	}

	if httpsAddr != "" {
		this.httpsServer = &http.Server{
			Addr:           httpsAddr,
			Handler:        this.router,
			ReadTimeout:    Options.HttpReadTimeout,
			WriteTimeout:   Options.HttpWriteTimeout,
			MaxHeaderBytes: Options.HttpHeaderMaxBytes,
		}
	}

	return this
}

func (this *webServer) Start() {
	if this.waitExitFunc == nil {
		this.waitExitFunc = this.waitExit
	}
	if this.connStateFunc == nil {
		this.connStateFunc = this.connStateHandler
	}

	if this.httpsServer != nil {
		this.httpsServer.ConnState = this.connStateFunc
		this.startServer(true)
	}

	if this.httpServer != nil {
		this.httpServer.ConnState = this.connStateFunc
		this.startServer(false)
	}

}

func (this *webServer) Router() *httprouter.Router {
	return this.router
}

func (this *webServer) startServer(https bool) {
	var err error
	waitListenerUp := make(chan struct{})
	go func() {
		if Options.CpuAffinity {
			runtime.LockOSThread()
		}

		var (
			retryDelay         time.Duration
			theListener        net.Listener
			waitListenerUpOnce sync.Once
			addr               string
		)
		for {
			if https {
				this.httpsListener, err = net.Listen("tcp", this.httpsServer.Addr)
				if err != nil {
					if retryDelay == 0 {
						retryDelay = 50 * time.Millisecond
					} else {
						retryDelay = 2 * retryDelay
					}
					if maxDelay := time.Second; retryDelay > maxDelay {
						retryDelay = maxDelay
					}
					log.Error("%s listener %v, retry in %v", this.name, err, retryDelay)
					time.Sleep(retryDelay)
					continue
				}

				this.httpsListener, err = setupHttpsListener(this.httpsListener,
					this.gw.certFile, this.gw.keyFile)
				if err != nil {
					panic(err)
				}

				theListener = this.httpsListener
				addr = this.httpsServer.Addr
			} else {
				this.httpListener, err = net.Listen("tcp", this.httpServer.Addr)
				theListener = this.httpListener
				addr = this.httpServer.Addr
			}

			if err != nil {
				if retryDelay == 0 {
					retryDelay = 50 * time.Millisecond
				} else {
					retryDelay = 2 * retryDelay
				}
				if maxDelay := time.Second; retryDelay > maxDelay {
					retryDelay = maxDelay
				}
				log.Error("%s listener %v, retry in %v", this.name, err, retryDelay)
				time.Sleep(retryDelay)
				continue
			}

			theListener = LimitListener(this.name, this.gw, theListener, this.maxClients)
			waitListenerUpOnce.Do(func() {
				close(waitListenerUp)
			})

			// on non-temporary err, net/http will close the listener
			if https {
				err = this.httpsServer.Serve(theListener)
			} else {
				err = this.httpServer.Serve(theListener)
			}

			select {
			case <-this.gw.shutdownCh:
				log.Trace("%s server stopped on %s", this.name, addr)
				return

			default:
				log.Error("%s server: %v", this.name, err)
			}
		}
	}()

	// FIXME if net.Listen fails, kateway will not be able to stop
	// e,g. start a kateway, then start another, the 2nd will not be able to stop

	// wait for the listener up
	<-waitListenerUp

	this.gw.wg.Add(1)
	if https {
		go this.waitExitFunc(this.httpsServer, this.httpsListener, this.gw.shutdownCh)
	} else {
		go this.waitExitFunc(this.httpServer, this.httpListener, this.gw.shutdownCh)
	}

	if https {
		log.Info("%s https server ready on %s", this.name, this.httpsServer.Addr)
	} else {
		log.Info("%s http server ready on %s", this.name, this.httpServer.Addr)
	}
}

func (this *webServer) connStateHandler(c net.Conn, cs http.ConnState) {
	switch cs {
	case http.StateNew:
		atomic.AddInt32(&this.activeConnN, 1)

		if this.onConnNewFunc != nil {
			this.onConnNewFunc(c)
		}

	case http.StateActive, http.StateIdle:
		// do nothing by default

	case http.StateClosed, http.StateHijacked:
		atomic.AddInt32(&this.activeConnN, -1)

		if this.onConnCloseFunc != nil {
			this.onConnCloseFunc(c)
		}

	}
}

func (this *webServer) waitExit(server *http.Server, listener net.Listener, exit <-chan struct{}) {
	<-exit

	// HTTP response will have "Connection: close"
	server.SetKeepAlivesEnabled(false)

	// avoid new connections
	if err := listener.Close(); err != nil {
		log.Error(err.Error())
	}

	log.Trace("%s on %s listener closed", this.name, server.Addr)

	waitStart := time.Now()
	for {
		activeConnN := atomic.LoadInt32(&this.activeConnN)
		if activeConnN == 0 {
			// good luck, all connections finished
			break
		}

		// timeout mechanism
		if time.Since(waitStart) > Options.SubTimeout+time.Second {
			log.Warn("%s on %s still left %d conns, but forced to shutdown after %s",
				this.name, server.Addr, activeConnN, Options.SubTimeout)
			break
		}

		time.Sleep(time.Millisecond * 50)
	}
	log.Trace("%s on %s all connections finished", this.name, server.Addr)

	if this.onStop != nil {
		// if both http and https, without sync once, onStop will be called twice
		this.onceStop.Do(func() {
			this.onStop()
		})
	}

	this.gw.wg.Done()
}

func (this *webServer) notFoundHandler(w http.ResponseWriter, r *http.Request) {
	log.Error("%s: not found %s", this.name, r.RequestURI)

	w.Header().Set("Connection", "close")
	writeErrorResponse(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/litl/galaxy/registry"
	"github.com/litl/galaxy/runtime"
	"github.com/litl/galaxy/utils"
	"os"
)

var (
	stopCutoff      = flag.Int64("cutoff", 5*60, "Seconds to wait before stopping old containers")
	app             = flag.String("app", "", "App to start")
	etcdHosts       = flag.String("etcd", utils.GetEnv("GALAXY_ETCD_HOST", "http://127.0.0.1:4001"), "Comma-separated list of etcd hosts")
	env             = flag.String("env", utils.GetEnv("GALAXY_ENV", "dev"), "Environment namespace")
	pool            = flag.String("pool", utils.GetEnv("GALAXY_POOL", "web"), "Pool namespace")
	loop            = flag.Bool("loop", false, "Run continously")
	serviceConfigs  []*registry.ServiceConfig
	serviceRegistry *registry.ServiceRegistry
	serviceRuntime  *runtime.ServiceRuntime
)

func initOrDie() {

	serviceRegistry = &registry.ServiceRegistry{
		EtcdHosts: *etcdHosts,
		Env:       *env,
		Pool:      *pool,
		//FIXME: Move these closer to functions that use them
		//HostIp:       "FIXME"
		//TTL:          uint64(c.Int("ttl")),
		//Hostname:     hostname,
		//HostSSHAddr:  c.GlobalString("sshAddr"),
		//OutputBuffer: outputBuffer,
	}

	serviceRuntime = &runtime.ServiceRuntime{}

}

func startContainersIfNecessary() {
	serviceConfigs = serviceRegistry.GetServiceConfigs()

	if len(serviceConfigs) == 0 {
		fmt.Printf("No services configured for /%s/%s\n", *env, *pool)
		os.Exit(0)
	}

	for _, serviceConfig := range serviceConfigs {

		if *app != "" && serviceConfig.Name != *app {
			continue
		}

		if serviceConfig.Version == "" {
			fmt.Printf("Skipping %s. No version configured.\n", serviceConfig.Name)
			continue
		}

		container, err := serviceRuntime.StartIfNotRunning(serviceConfig)
		if err != nil {
			fmt.Printf("ERROR: Could not determine if %s is running: %s\n",
				serviceConfig.Version, err)
			os.Exit(1)
		}

		fmt.Printf("%s running as %s\n", serviceConfig.Version, container.ID)

		serviceRuntime.StopAllButLatest(serviceConfig.Version, container, *stopCutoff)
	}
}

func restartContainers(changedConfigs chan *registry.ConfigChange) {
	for {

		changedConfig := <-changedConfigs
		if changedConfig.Error != nil {
			fmt.Printf("ERROR: Error watching changes: %s\n", changedConfig.Error)
			continue
		}

		container, err := serviceRuntime.Start(changedConfig.ServiceConfig)
		if err != nil {
			fmt.Printf("ERROR: Could not start %s: %s\n",
				changedConfig.ServiceConfig.Version, err)
			continue
		}
		fmt.Printf("Restarted %s as: %s\n", changedConfig.ServiceConfig.Version, container.ID)

		serviceRuntime.StopAllButLatest(changedConfig.ServiceConfig.Version, container, *stopCutoff)

	}
}

func main() {
	flag.Parse()

	if *env == "" {
		fmt.Println("Need an env")
		flag.PrintDefaults()
		os.Exit(1)
	}

	if *pool == "" {
		fmt.Println("Need a pool")
		flag.PrintDefaults()
		os.Exit(1)
	}

	initOrDie()
	startContainersIfNecessary()

	if !*loop {
		return
	}

	restartChan := make(chan *registry.ConfigChange, 10)

	go restartContainers(restartChan)

	for {
		err := serviceRegistry.WaitForChanges(restartChan)
		if err != nil {
			fmt.Printf("ERROR: Unexpected problem waiting for changes: %s\n", err)
		}

	}

}
<commit_msg>Make commander call the new WaitForChanges<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/litl/galaxy/registry"
	"github.com/litl/galaxy/runtime"
	"github.com/litl/galaxy/utils"
	"os"
)

var (
	stopCutoff      = flag.Int64("cutoff", 5*60, "Seconds to wait before stopping old containers")
	app             = flag.String("app", "", "App to start")
	etcdHosts       = flag.String("etcd", utils.GetEnv("GALAXY_ETCD_HOST", "http://127.0.0.1:4001"), "Comma-separated list of etcd hosts")
	env             = flag.String("env", utils.GetEnv("GALAXY_ENV", "dev"), "Environment namespace")
	pool            = flag.String("pool", utils.GetEnv("GALAXY_POOL", "web"), "Pool namespace")
	loop            = flag.Bool("loop", false, "Run continously")
	serviceConfigs  []*registry.ServiceConfig
	serviceRegistry *registry.ServiceRegistry
	serviceRuntime  *runtime.ServiceRuntime
)

func initOrDie() {

	serviceRegistry = &registry.ServiceRegistry{
		EtcdHosts: *etcdHosts,
		Env:       *env,
		Pool:      *pool,
		//FIXME: Move these closer to functions that use them
		//HostIp:       "FIXME"
		//TTL:          uint64(c.Int("ttl")),
		//Hostname:     hostname,
		//HostSSHAddr:  c.GlobalString("sshAddr"),
		//OutputBuffer: outputBuffer,
	}

	serviceRuntime = &runtime.ServiceRuntime{}

}

func startContainersIfNecessary() {
	serviceConfigs = serviceRegistry.GetServiceConfigs()

	if len(serviceConfigs) == 0 {
		fmt.Printf("No services configured for /%s/%s\n", *env, *pool)
		os.Exit(0)
	}

	for _, serviceConfig := range serviceConfigs {

		if *app != "" && serviceConfig.Name != *app {
			continue
		}

		if serviceConfig.Version == "" {
			fmt.Printf("Skipping %s. No version configured.\n", serviceConfig.Name)
			continue
		}

		container, err := serviceRuntime.StartIfNotRunning(serviceConfig)
		if err != nil {
			fmt.Printf("ERROR: Could not determine if %s is running: %s\n",
				serviceConfig.Version, err)
			os.Exit(1)
		}

		fmt.Printf("%s running as %s\n", serviceConfig.Version, container.ID)

		serviceRuntime.StopAllButLatest(serviceConfig.Version, container, *stopCutoff)
	}
}

func restartContainers(changedConfigs chan *registry.ConfigChange) {
	for {

		changedConfig := <-changedConfigs
		if changedConfig.Error != nil {
			fmt.Printf("ERROR: Error watching changes: %s\n", changedConfig.Error)
			continue
		}

		container, err := serviceRuntime.Start(changedConfig.ServiceConfig)
		if err != nil {
			fmt.Printf("ERROR: Could not start %s: %s\n",
				changedConfig.ServiceConfig.Version, err)
			continue
		}
		fmt.Printf("Restarted %s as: %s\n", changedConfig.ServiceConfig.Version, container.ID)

		serviceRuntime.StopAllButLatest(changedConfig.ServiceConfig.Version, container, *stopCutoff)

	}
}

func main() {
	flag.Parse()

	if *env == "" {
		fmt.Println("Need an env")
		flag.PrintDefaults()
		os.Exit(1)
	}

	if *pool == "" {
		fmt.Println("Need a pool")
		flag.PrintDefaults()
		os.Exit(1)
	}

	initOrDie()
	startContainersIfNecessary()

	if !*loop {
		return
	}

	restartChan := make(chan *registry.ConfigChange, 10)
	cancelChan := make(chan struct{})
	// do we need to cancel ever?

	// how do we get tha last ID?
	lastID := int64(0)

	serviceRegistry.WaitForChanges(lastID, restartChan, cancelChan)
	restartContainers(restartChan)
}
<|endoftext|>


<commit_before>package http

import (
	"net/http"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
)

// Parse parses the data in a http.Request and returns a command Request object
func Parse(r *http.Request, root *cmds.Command) (cmds.Request, error) {
	path := strings.Split(r.URL.Path, "/")[3:]
	args := make([]string, 0)

	cmd, err := root.Get(path[:len(path)-1])
	if err != nil {
		// 404 if there is no command at that path
		return nil, ErrNotFound

	} else if sub := cmd.Subcommand(path[len(path)-1]); sub == nil {
		// if the last string in the path isn't a subcommand, use it as an argument
		// e.g. /objects/Qabc12345 (we are passing "Qabc12345" to the "objects" command)
		args = append(args, path[len(path)-1])
		path = path[:len(path)-1]

	} else {
		cmd = sub
	}

	opts, args2 := parseOptions(r)
	args = append(args, args2...)

	// TODO: make a way to send opts/args in request body
	//   (e.g. if form-data or form-urlencoded, then treat the same as querystring)
	// for now, to be simple, we just use the whole request body as the input stream
	// (r.Body will be nil if there is no request body, like in GET requests)
	in := r.Body

	return cmds.NewRequest(path, opts, args, in, cmd), nil
}

func parseOptions(r *http.Request) (map[string]interface{}, []string) {
	opts := make(map[string]interface{})
	var args []string

	query := r.URL.Query()
	for k, v := range query {
		if k == "arg" {
			args = v
		} else {
			opts[k] = v[0]
		}
	}

	// default to setting encoding to JSON
	_, short := opts[cmds.EncShort]
	_, long := opts[cmds.EncLong]
	if !short && !long {
		opts[cmds.EncShort] = cmds.JSON
	}

	return opts, args
}
<commit_msg>commands/http: 404 when trying to call root command<commit_after>package http

import (
	"net/http"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
)

// Parse parses the data in a http.Request and returns a command Request object
func Parse(r *http.Request, root *cmds.Command) (cmds.Request, error) {
	path := strings.Split(r.URL.Path, "/")[3:]
	args := make([]string, 0)

	cmd, err := root.Get(path[:len(path)-1])
	if err != nil {
		// 404 if there is no command at that path
		return nil, ErrNotFound

	} else if sub := cmd.Subcommand(path[len(path)-1]); sub == nil {
		if len(path) <= 1 {
			return nil, ErrNotFound
		}

		// if the last string in the path isn't a subcommand, use it as an argument
		// e.g. /objects/Qabc12345 (we are passing "Qabc12345" to the "objects" command)
		args = append(args, path[len(path)-1])
		path = path[:len(path)-1]

	} else {
		cmd = sub
	}

	opts, args2 := parseOptions(r)
	args = append(args, args2...)

	// TODO: make a way to send opts/args in request body
	//   (e.g. if form-data or form-urlencoded, then treat the same as querystring)
	// for now, to be simple, we just use the whole request body as the input stream
	// (r.Body will be nil if there is no request body, like in GET requests)
	in := r.Body

	return cmds.NewRequest(path, opts, args, in, cmd), nil
}

func parseOptions(r *http.Request) (map[string]interface{}, []string) {
	opts := make(map[string]interface{})
	var args []string

	query := r.URL.Query()
	for k, v := range query {
		if k == "arg" {
			args = v
		} else {
			opts[k] = v[0]
		}
	}

	// default to setting encoding to JSON
	_, short := opts[cmds.EncShort]
	_, long := opts[cmds.EncLong]
	if !short && !long {
		opts[cmds.EncShort] = cmds.JSON
	}

	return opts, args
}
<|endoftext|>


<commit_before><commit_msg>Added generalized coordinate specification.<commit_after><|endoftext|>


<commit_before><commit_msg>fix error handling<commit_after><|endoftext|>


<commit_before>package database

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/fatih/structs"
	"github.com/hashicorp/vault/builtin/logical/database/dbs"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
)

func pathResetConnection(b *databaseBackend) *framework.Path {
	return &framework.Path{
		Pattern: fmt.Sprintf("reset/%s", framework.GenericNameRegex("name")),
		Fields: map[string]*framework.FieldSchema{
			"name": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Name of this DB type",
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: b.pathConnectionReset,
		},

		HelpSynopsis:    pathConfigConnectionHelpSyn,
		HelpDescription: pathConfigConnectionHelpDesc,
	}
}

func (b *databaseBackend) pathConnectionReset(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("name").(string)
	if name == "" {
		return nil, errors.New("No database name set")
	}

	// Grab the mutex lock
	b.Lock()
	defer b.Unlock()

	entry, err := req.Storage.Get(fmt.Sprintf("dbs/%s", name))
	if err != nil {
		return nil, fmt.Errorf("failed to read connection configuration")
	}
	if entry == nil {
		return nil, nil
	}

	var config dbs.DatabaseConfig
	if err := entry.DecodeJSON(&config); err != nil {
		return nil, err
	}

	db, ok := b.connections[name]
	if !ok {
		return logical.ErrorResponse("Can not change type of existing connection."), nil
	}

	db.Close()

	factory := config.GetFactory()

	db, err = factory(&config, b.System())
	if err != nil {
		return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
	}

	err = db.Initialize(config.ConnectionDetails)
	if err != nil {
		return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
	}

	b.connections[name] = db

	return nil, nil
}

func pathConfigureConnection(b *databaseBackend) *framework.Path {
	return buildConfigConnectionPath("dbs/%s", b.connectionWriteHandler(dbs.BuiltinFactory), b.connectionReadHandler())
}

func pathConfigurePluginConnection(b *databaseBackend) *framework.Path {
	return buildConfigConnectionPath("dbs/plugin/%s", b.connectionWriteHandler(dbs.PluginFactory), b.connectionReadHandler())
}

func buildConfigConnectionPath(path string, updateOp, readOp framework.OperationFunc) *framework.Path {
	return &framework.Path{
		Pattern: fmt.Sprintf(path, framework.GenericNameRegex("name")),
		Fields: map[string]*framework.FieldSchema{
			"name": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Name of this DB type",
			},

			"connection_type": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "DB type (e.g. postgres)",
			},

			"verify_connection": &framework.FieldSchema{
				Type:        framework.TypeBool,
				Default:     true,
				Description: `If set, connection_url is verified by actually connecting to the database`,
			},

			"max_open_connections": &framework.FieldSchema{
				Type: framework.TypeInt,
				Description: `Maximum number of open connections to the database;
a zero uses the default value of two and a
negative value means unlimited`,
			},

			"max_idle_connections": &framework.FieldSchema{
				Type: framework.TypeInt,
				Description: `Maximum number of idle connections to the database;
a zero uses the value of max_open_connections
and a negative value disables idle connections.
If larger than max_open_connections it will be
reduced to the same size.`,
			},

			"max_connection_lifetime": &framework.FieldSchema{
				Type:    framework.TypeString,
				Default: "0s",
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},

			"plugin_command": &framework.FieldSchema{
				Type: framework.TypeString,
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},

			"plugin_checksum": &framework.FieldSchema{
				Type: framework.TypeString,
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: updateOp,
			logical.ReadOperation:   readOp,
		},

		HelpSynopsis:    pathConfigConnectionHelpSyn,
		HelpDescription: pathConfigConnectionHelpDesc,
	}
}

// pathConnectionRead reads out the connection configuration
func (b *databaseBackend) connectionReadHandler() framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		name := data.Get("name").(string)

		entry, err := req.Storage.Get(fmt.Sprintf("dbs/%s", name))
		if err != nil {
			return nil, fmt.Errorf("failed to read connection configuration")
		}
		if entry == nil {
			return nil, nil
		}

		var config dbs.DatabaseConfig
		if err := entry.DecodeJSON(&config); err != nil {
			return nil, err
		}
		return &logical.Response{
			Data: structs.New(config).Map(),
		}, nil
	}
}

func (b *databaseBackend) connectionWriteHandler(factory dbs.Factory) framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		connType := data.Get("connection_type").(string)
		if connType == "" {
			return logical.ErrorResponse("connection_type not set"), nil
		}

		maxOpenConns := data.Get("max_open_connections").(int)
		if maxOpenConns == 0 {
			maxOpenConns = 2
		}

		maxIdleConns := data.Get("max_idle_connections").(int)
		if maxIdleConns == 0 {
			maxIdleConns = maxOpenConns
		}
		if maxIdleConns > maxOpenConns {
			maxIdleConns = maxOpenConns
		}

		maxConnLifetimeRaw := data.Get("max_connection_lifetime").(string)
		maxConnLifetime, err := time.ParseDuration(maxConnLifetimeRaw)
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf(
				"Invalid max_connection_lifetime: %s", err)), nil
		}

		config := &dbs.DatabaseConfig{
			DatabaseType:          connType,
			ConnectionDetails:     data.Raw,
			MaxOpenConnections:    maxOpenConns,
			MaxIdleConnections:    maxIdleConns,
			MaxConnectionLifetime: maxConnLifetime,
			PluginCommand:         data.Get("plugin_command").(string),
			PluginChecksum:        data.Get("plugin_checksum").(string),
		}

		name := data.Get("name").(string)
		if name == "" {
			return logical.ErrorResponse("Empty name attribute given"), nil
		}

		verifyConnection := data.Get("verify_connection").(bool)

		// Grab the mutex lock
		b.Lock()
		defer b.Unlock()

		var db dbs.DatabaseType
		if _, ok := b.connections[name]; ok {

			// Don't allow the connection type to change
			if b.connections[name].Type() != connType {
				return logical.ErrorResponse("Can not change type of existing connection."), nil
			}
		} else {
			db, err = factory(config, b.System())
			if err != nil {
				return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
			}

			err := db.Initialize(config.ConnectionDetails)
			if err != nil {
				if !strings.Contains(err.Error(), "Error Initializing Connection") {
					return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil

				}

				if verifyConnection {
					return logical.ErrorResponse(err.Error()), nil

				}
			}

			b.connections[name] = db
		}

		/*  TODO:
		// Don't check the connection_url if verification is disabled
		verifyConnection := data.Get("verify_connection").(bool)
		if verifyConnection {
			// Verify the string
			db, err := sql.Open("postgres", connURL)
			if err != nil {
				return logical.ErrorResponse(fmt.Sprintf(
					"Error validating connection info: %s", err)), nil
			}
			defer db.Close()
			if err := db.Ping(); err != nil {
				return logical.ErrorResponse(fmt.Sprintf(
					"Error validating connection info: %s", err)), nil
			}
		}
		*/

		// Store it
		entry, err := logical.StorageEntryJSON(fmt.Sprintf("dbs/%s", name), config)
		if err != nil {
			return nil, err
		}
		if err := req.Storage.Put(entry); err != nil {
			return nil, err
		}

		// Reset the DB connection
		resp := &logical.Response{}
		resp.AddWarning("Read access to this endpoint should be controlled via ACLs as it will return the connection string or URL as it is, including passwords, if any.")

		return resp, nil
	}
}

const pathConfigConnectionHelpSyn = `
Configure the connection string to talk to PostgreSQL.
`

const pathConfigConnectionHelpDesc = `
This path configures the connection string used to connect to PostgreSQL.
The value of the string can be a URL, or a PG style string in the
format of "user=foo host=bar" etc.

The URL looks like:
"postgresql://user:pass@host:port/dbname"

When configuring the connection string, the backend will verify its validity.
`
<commit_msg>Verify connections regardless of if this connections is already existing<commit_after>package database

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/fatih/structs"
	"github.com/hashicorp/vault/builtin/logical/database/dbs"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
)

func pathResetConnection(b *databaseBackend) *framework.Path {
	return &framework.Path{
		Pattern: fmt.Sprintf("reset/%s", framework.GenericNameRegex("name")),
		Fields: map[string]*framework.FieldSchema{
			"name": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Name of this DB type",
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: b.pathConnectionReset,
		},

		HelpSynopsis:    pathConfigConnectionHelpSyn,
		HelpDescription: pathConfigConnectionHelpDesc,
	}
}

func (b *databaseBackend) pathConnectionReset(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("name").(string)
	if name == "" {
		return nil, errors.New("No database name set")
	}

	// Grab the mutex lock
	b.Lock()
	defer b.Unlock()

	entry, err := req.Storage.Get(fmt.Sprintf("dbs/%s", name))
	if err != nil {
		return nil, fmt.Errorf("failed to read connection configuration")
	}
	if entry == nil {
		return nil, nil
	}

	var config dbs.DatabaseConfig
	if err := entry.DecodeJSON(&config); err != nil {
		return nil, err
	}

	db, ok := b.connections[name]
	if !ok {
		return logical.ErrorResponse("Can not change type of existing connection."), nil
	}

	db.Close()

	factory := config.GetFactory()

	db, err = factory(&config, b.System())
	if err != nil {
		return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
	}

	err = db.Initialize(config.ConnectionDetails)
	if err != nil {
		return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
	}

	b.connections[name] = db

	return nil, nil
}

func pathConfigureConnection(b *databaseBackend) *framework.Path {
	return buildConfigConnectionPath("dbs/%s", b.connectionWriteHandler(dbs.BuiltinFactory), b.connectionReadHandler())
}

func pathConfigurePluginConnection(b *databaseBackend) *framework.Path {
	return buildConfigConnectionPath("dbs/plugin/%s", b.connectionWriteHandler(dbs.PluginFactory), b.connectionReadHandler())
}

func buildConfigConnectionPath(path string, updateOp, readOp framework.OperationFunc) *framework.Path {
	return &framework.Path{
		Pattern: fmt.Sprintf(path, framework.GenericNameRegex("name")),
		Fields: map[string]*framework.FieldSchema{
			"name": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "Name of this DB type",
			},

			"connection_type": &framework.FieldSchema{
				Type:        framework.TypeString,
				Description: "DB type (e.g. postgres)",
			},

			"verify_connection": &framework.FieldSchema{
				Type:        framework.TypeBool,
				Default:     true,
				Description: `If set, connection_url is verified by actually connecting to the database`,
			},

			"max_open_connections": &framework.FieldSchema{
				Type: framework.TypeInt,
				Description: `Maximum number of open connections to the database;
a zero uses the default value of two and a
negative value means unlimited`,
			},

			"max_idle_connections": &framework.FieldSchema{
				Type: framework.TypeInt,
				Description: `Maximum number of idle connections to the database;
a zero uses the value of max_open_connections
and a negative value disables idle connections.
If larger than max_open_connections it will be
reduced to the same size.`,
			},

			"max_connection_lifetime": &framework.FieldSchema{
				Type:    framework.TypeString,
				Default: "0s",
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},

			"plugin_command": &framework.FieldSchema{
				Type: framework.TypeString,
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},

			"plugin_checksum": &framework.FieldSchema{
				Type: framework.TypeString,
				Description: `Maximum amount of time a connection may be reused;
				a zero or negative value reuses connections forever.`,
			},
		},

		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.UpdateOperation: updateOp,
			logical.ReadOperation:   readOp,
		},

		HelpSynopsis:    pathConfigConnectionHelpSyn,
		HelpDescription: pathConfigConnectionHelpDesc,
	}
}

// pathConnectionRead reads out the connection configuration
func (b *databaseBackend) connectionReadHandler() framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		name := data.Get("name").(string)

		entry, err := req.Storage.Get(fmt.Sprintf("dbs/%s", name))
		if err != nil {
			return nil, fmt.Errorf("failed to read connection configuration")
		}
		if entry == nil {
			return nil, nil
		}

		var config dbs.DatabaseConfig
		if err := entry.DecodeJSON(&config); err != nil {
			return nil, err
		}
		return &logical.Response{
			Data: structs.New(config).Map(),
		}, nil
	}
}

func (b *databaseBackend) connectionWriteHandler(factory dbs.Factory) framework.OperationFunc {
	return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
		connType := data.Get("connection_type").(string)
		if connType == "" {
			return logical.ErrorResponse("connection_type not set"), nil
		}

		maxOpenConns := data.Get("max_open_connections").(int)
		if maxOpenConns == 0 {
			maxOpenConns = 2
		}

		maxIdleConns := data.Get("max_idle_connections").(int)
		if maxIdleConns == 0 {
			maxIdleConns = maxOpenConns
		}
		if maxIdleConns > maxOpenConns {
			maxIdleConns = maxOpenConns
		}

		maxConnLifetimeRaw := data.Get("max_connection_lifetime").(string)
		maxConnLifetime, err := time.ParseDuration(maxConnLifetimeRaw)
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf(
				"Invalid max_connection_lifetime: %s", err)), nil
		}

		config := &dbs.DatabaseConfig{
			DatabaseType:          connType,
			ConnectionDetails:     data.Raw,
			MaxOpenConnections:    maxOpenConns,
			MaxIdleConnections:    maxIdleConns,
			MaxConnectionLifetime: maxConnLifetime,
			PluginCommand:         data.Get("plugin_command").(string),
			PluginChecksum:        data.Get("plugin_checksum").(string),
		}

		name := data.Get("name").(string)
		if name == "" {
			return logical.ErrorResponse("Empty name attribute given"), nil
		}

		verifyConnection := data.Get("verify_connection").(bool)

		// Grab the mutex lock
		b.Lock()
		defer b.Unlock()

		db, err := factory(config, b.System())
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
		}

		err = db.Initialize(config.ConnectionDetails)
		if err != nil {
			if !strings.Contains(err.Error(), "Error Initializing Connection") {
				return logical.ErrorResponse(fmt.Sprintf("Error creating database object: %s", err)), nil
			}

			if verifyConnection {
				return logical.ErrorResponse(err.Error()), nil
			}
		}

		if _, ok := b.connections[name]; ok {
			// Don't update connection until the reset api is hit, close for
			// now.
			err = db.Close()
			if err != nil {
				return nil, err
			}

			// Don't allow the connection type to change
			if b.connections[name].Type() != connType {
				return logical.ErrorResponse("Can not change type of existing connection."), nil
			}
		} else {
			// Save the new connection
			b.connections[name] = db
		}

		// Store it
		entry, err := logical.StorageEntryJSON(fmt.Sprintf("dbs/%s", name), config)
		if err != nil {
			return nil, err
		}
		if err := req.Storage.Put(entry); err != nil {
			return nil, err
		}

		// Reset the DB connection
		resp := &logical.Response{}
		resp.AddWarning("Read access to this endpoint should be controlled via ACLs as it will return the connection string or URL as it is, including passwords, if any.")

		return resp, nil
	}
}

const pathConfigConnectionHelpSyn = `
Configure the connection string to talk to PostgreSQL.
`

const pathConfigConnectionHelpDesc = `
This path configures the connection string used to connect to PostgreSQL.
The value of the string can be a URL, or a PG style string in the
format of "user=foo host=bar" etc.

The URL looks like:
"postgresql://user:pass@host:port/dbname"

When configuring the connection string, the backend will verify its validity.
`
<|endoftext|>


<commit_before>package cli

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	fp "path"
	"runtime"
	"sort"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
	u "github.com/jbenet/go-ipfs/util"
)

// ErrInvalidSubcmd signals when the parse error is not found
var ErrInvalidSubcmd = errors.New("subcommand not found")

// Parse parses the input commandline string (cmd, flags, and args).
// returns the corresponding command Request object.
func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *cmds.Command, []string, error) {
	path, input, cmd := parsePath(input, root)
	if len(path) == 0 {
		return nil, nil, path, ErrInvalidSubcmd
	}

	opts, stringVals, err := parseOptions(input)
	if err != nil {
		return nil, cmd, path, err
	}

	optDefs, err := root.GetOptions(path)
	if err != nil {
		return nil, cmd, path, err
	}

	// check to make sure there aren't any undefined options
	for k := range opts {
		if _, found := optDefs[k]; !found {
			err = fmt.Errorf("Unrecognized option: -%s", k)
			return nil, cmd, path, err
		}
	}

	req, err := cmds.NewRequest(path, opts, nil, nil, cmd, optDefs)
	if err != nil {
		return nil, cmd, path, err
	}

	// if -r is provided, and it is associated with the package builtin
	// recursive path option, allow recursive file paths
	recursiveOpt := req.Option(cmds.RecShort)
	recursive := false
	if recursiveOpt != nil && recursiveOpt.Definition() == cmds.OptionRecursivePath {
		recursive, _, err = recursiveOpt.Bool()
		if err != nil {
			return req, nil, nil, u.ErrCast()
		}
	}

	stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive)
	if err != nil {
		return req, cmd, path, err
	}
	req.SetArguments(stringArgs)

	file := &cmds.SliceFile{"", fileArgs}
	req.SetFiles(file)

	err = cmd.CheckArguments(req)
	if err != nil {
		return req, cmd, path, err
	}

	return req, cmd, path, nil
}

// parsePath separates the command path and the opts and args from a command string
// returns command path slice, rest slice, and the corresponding *cmd.Command
func parsePath(input []string, root *cmds.Command) ([]string, []string, *cmds.Command) {
	cmd := root
	path := make([]string, 0, len(input))
	input2 := make([]string, 0, len(input))

	for i, blob := range input {
		if strings.HasPrefix(blob, "-") {
			input2 = append(input2, blob)
			continue
		}

		sub := cmd.Subcommand(blob)
		if sub == nil {
			input2 = append(input2, input[i:]...)
			break
		}
		cmd = sub
		path = append(path, blob)
	}

	return path, input2, cmd
}

// parseOptions parses the raw string values of the given options
// returns the parsed options as strings, along with the CLI args
func parseOptions(input []string) (map[string]interface{}, []string, error) {
	opts := make(map[string]interface{})
	args := []string{}

	for i := 0; i < len(input); i++ {
		blob := input[i]

		if strings.HasPrefix(blob, "-") {
			name := blob[1:]
			value := ""

			// support single and double dash
			if strings.HasPrefix(name, "-") {
				name = name[1:]
			}

			if strings.Contains(name, "=") {
				split := strings.SplitN(name, "=", 2)
				name = split[0]
				value = split[1]
			}

			if _, ok := opts[name]; ok {
				return nil, nil, fmt.Errorf("Duplicate values for option '%s'", name)
			}

			opts[name] = value

		} else {
			args = append(args, blob)
		}
	}

	return opts, args, nil
}

func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []cmds.File, error) {
	// ignore stdin on Windows
	if runtime.GOOS == "windows" {
		stdin = nil
	}

	// check if stdin is coming from terminal or is being piped in
	if stdin != nil {
		if term, err := isTerminal(stdin); err != nil {
			return nil, nil, err
		} else if term {
			stdin = nil // set to nil so we ignore it
		}
	}

	// count required argument definitions
	numRequired := 0
	for _, argDef := range argDefs {
		if argDef.Required {
			numRequired++
		}
	}

	// count number of values provided by user
	numInputs := len(inputs)
	if stdin != nil {
		numInputs += 1
	}

	// if we have more arg values provided than argument definitions,
	// and the last arg definition is not variadic (or there are no definitions), return an error
	notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic
	if notVariadic && numInputs > len(argDefs) {
		return nil, nil, fmt.Errorf("Expected %v arguments, got %v", len(argDefs), numInputs)
	}

	stringArgs := make([]string, 0, numInputs)
	fileArgs := make([]cmds.File, 0, numInputs)

	argDefIndex := 0 // the index of the current argument definition
	for i := 0; i < numInputs; i++ {
		argDef := getArgDef(argDefIndex, argDefs)

		// skip optional argument definitions if there aren't sufficient remaining inputs
		for numInputs-i <= numRequired && !argDef.Required {
			argDefIndex++
			argDef = getArgDef(argDefIndex, argDefs)
		}
		if argDef.Required {
			numRequired--
		}

		var err error
		if argDef.Type == cmds.ArgString {
			if stdin == nil {
				// add string values
				stringArgs, inputs = appendString(stringArgs, inputs)

			} else if argDef.SupportsStdin {
				// if we have a stdin, read it in and use the data as a string value
				stringArgs, stdin, err = appendStdinAsString(stringArgs, stdin)
				if err != nil {
					return nil, nil, err
				}
			}

		} else if argDef.Type == cmds.ArgFile {
			if stdin == nil {
				// treat stringArg values as file paths
				fileArgs, inputs, err = appendFile(fileArgs, inputs, argDef, recursive)
				if err != nil {
					return nil, nil, err
				}

			} else if argDef.SupportsStdin {
				// if we have a stdin, create a file from it
				fileArgs, stdin = appendStdinAsFile(fileArgs, stdin)
			}
		}

		argDefIndex++
	}

	// check to make sure we didn't miss any required arguments
	if len(argDefs) > argDefIndex {
		for _, argDef := range argDefs[argDefIndex:] {
			if argDef.Required {
				return nil, nil, fmt.Errorf("Argument '%s' is required", argDef.Name)
			}
		}
	}

	return stringArgs, fileArgs, nil
}

func getArgDef(i int, argDefs []cmds.Argument) *cmds.Argument {
	if i < len(argDefs) {
		// get the argument definition (usually just argDefs[i])
		return &argDefs[i]

	} else if len(argDefs) > 0 {
		// but if i > len(argDefs) we use the last argument definition)
		return &argDefs[len(argDefs)-1]
	}

	// only happens if there aren't any definitions
	return nil
}

func appendString(args, inputs []string) ([]string, []string) {
	return append(args, inputs[0]), inputs[1:]
}

func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, error) {
	var buf bytes.Buffer

	_, err := buf.ReadFrom(stdin)
	if err != nil {
		return nil, nil, err
	}

	return append(args, buf.String()), nil, nil
}

func appendFile(args []cmds.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]cmds.File, []string, error) {
	path := inputs[0]

	file, err := os.Open(path)
	if err != nil {
		return nil, nil, err
	}

	stat, err := file.Stat()
	if err != nil {
		return nil, nil, err
	}

	if stat.IsDir() {
		if !argDef.Recursive {
			err = fmt.Errorf("Invalid path '%s', argument '%s' does not support directories",
				path, argDef.Name)
			return nil, nil, err
		}
		if !recursive {
			err = fmt.Errorf("'%s' is a directory, use the '-%s' flag to specify directories",
				path, cmds.RecShort)
			return nil, nil, err
		}
	}

	arg, err := openPath(file, path)
	if err != nil {
		return nil, nil, err
	}

	return append(args, arg), inputs[1:], nil
}

func appendStdinAsFile(args []cmds.File, stdin *os.File) ([]cmds.File, *os.File) {
	arg := &cmds.ReaderFile{"", stdin}
	return append(args, arg), nil
}

// recursively get file or directory contents as a cmds.File
func openPath(file *os.File, path string) (cmds.File, error) {
	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	// for non-directories, return a ReaderFile
	if !stat.IsDir() {
		return &cmds.ReaderFile{path, file}, nil
	}

	// for directories, recursively iterate though children then return as a SliceFile
	contents, err := file.Readdir(0)
	if err != nil {
		return nil, err
	}

	// make sure contents are sorted so -- repeatably -- we get the same inputs.
	sort.Sort(sortFIByName(contents))

	files := make([]cmds.File, 0, len(contents))
	for _, child := range contents {
		childPath := fp.Join(path, child.Name())
		childFile, err := os.Open(childPath)
		if err != nil {
			return nil, err
		}

		f, err := openPath(childFile, childPath)
		if err != nil {
			return nil, err
		}

		files = append(files, f)
	}

	return &cmds.SliceFile{path, files}, nil
}

// isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`),
// and false otherwise (e.g. nothing is being piped in, so stdin is
// coming from the terminal)
func isTerminal(stdin *os.File) (bool, error) {
	stat, err := stdin.Stat()
	if err != nil {
		return false, err
	}

	// if stdin is a CharDevice, return true
	return ((stat.Mode() & os.ModeCharDevice) != 0), nil
}

type sortFIByName []os.FileInfo

func (es sortFIByName) Len() int           { return len(es) }
func (es sortFIByName) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }
func (es sortFIByName) Less(i, j int) bool { return es[i].Name() < es[j].Name() }
<commit_msg>commands/cli: When parsing stdin as string argument, split lines into separate tokens<commit_after>package cli

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	fp "path"
	"runtime"
	"sort"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
	u "github.com/jbenet/go-ipfs/util"
)

// ErrInvalidSubcmd signals when the parse error is not found
var ErrInvalidSubcmd = errors.New("subcommand not found")

// Parse parses the input commandline string (cmd, flags, and args).
// returns the corresponding command Request object.
func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *cmds.Command, []string, error) {
	path, input, cmd := parsePath(input, root)
	if len(path) == 0 {
		return nil, nil, path, ErrInvalidSubcmd
	}

	opts, stringVals, err := parseOptions(input)
	if err != nil {
		return nil, cmd, path, err
	}

	optDefs, err := root.GetOptions(path)
	if err != nil {
		return nil, cmd, path, err
	}

	// check to make sure there aren't any undefined options
	for k := range opts {
		if _, found := optDefs[k]; !found {
			err = fmt.Errorf("Unrecognized option: -%s", k)
			return nil, cmd, path, err
		}
	}

	req, err := cmds.NewRequest(path, opts, nil, nil, cmd, optDefs)
	if err != nil {
		return nil, cmd, path, err
	}

	// if -r is provided, and it is associated with the package builtin
	// recursive path option, allow recursive file paths
	recursiveOpt := req.Option(cmds.RecShort)
	recursive := false
	if recursiveOpt != nil && recursiveOpt.Definition() == cmds.OptionRecursivePath {
		recursive, _, err = recursiveOpt.Bool()
		if err != nil {
			return req, nil, nil, u.ErrCast()
		}
	}

	stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive)
	if err != nil {
		return req, cmd, path, err
	}
	req.SetArguments(stringArgs)

	file := &cmds.SliceFile{"", fileArgs}
	req.SetFiles(file)

	err = cmd.CheckArguments(req)
	if err != nil {
		return req, cmd, path, err
	}

	return req, cmd, path, nil
}

// parsePath separates the command path and the opts and args from a command string
// returns command path slice, rest slice, and the corresponding *cmd.Command
func parsePath(input []string, root *cmds.Command) ([]string, []string, *cmds.Command) {
	cmd := root
	path := make([]string, 0, len(input))
	input2 := make([]string, 0, len(input))

	for i, blob := range input {
		if strings.HasPrefix(blob, "-") {
			input2 = append(input2, blob)
			continue
		}

		sub := cmd.Subcommand(blob)
		if sub == nil {
			input2 = append(input2, input[i:]...)
			break
		}
		cmd = sub
		path = append(path, blob)
	}

	return path, input2, cmd
}

// parseOptions parses the raw string values of the given options
// returns the parsed options as strings, along with the CLI args
func parseOptions(input []string) (map[string]interface{}, []string, error) {
	opts := make(map[string]interface{})
	args := []string{}

	for i := 0; i < len(input); i++ {
		blob := input[i]

		if strings.HasPrefix(blob, "-") {
			name := blob[1:]
			value := ""

			// support single and double dash
			if strings.HasPrefix(name, "-") {
				name = name[1:]
			}

			if strings.Contains(name, "=") {
				split := strings.SplitN(name, "=", 2)
				name = split[0]
				value = split[1]
			}

			if _, ok := opts[name]; ok {
				return nil, nil, fmt.Errorf("Duplicate values for option '%s'", name)
			}

			opts[name] = value

		} else {
			args = append(args, blob)
		}
	}

	return opts, args, nil
}

func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []cmds.File, error) {
	// ignore stdin on Windows
	if runtime.GOOS == "windows" {
		stdin = nil
	}

	// check if stdin is coming from terminal or is being piped in
	if stdin != nil {
		if term, err := isTerminal(stdin); err != nil {
			return nil, nil, err
		} else if term {
			stdin = nil // set to nil so we ignore it
		}
	}

	// count required argument definitions
	numRequired := 0
	for _, argDef := range argDefs {
		if argDef.Required {
			numRequired++
		}
	}

	// count number of values provided by user
	numInputs := len(inputs)
	if stdin != nil {
		numInputs += 1
	}

	// if we have more arg values provided than argument definitions,
	// and the last arg definition is not variadic (or there are no definitions), return an error
	notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic
	if notVariadic && numInputs > len(argDefs) {
		return nil, nil, fmt.Errorf("Expected %v arguments, got %v", len(argDefs), numInputs)
	}

	stringArgs := make([]string, 0, numInputs)
	fileArgs := make([]cmds.File, 0, numInputs)

	argDefIndex := 0 // the index of the current argument definition
	for i := 0; i < numInputs; i++ {
		argDef := getArgDef(argDefIndex, argDefs)

		// skip optional argument definitions if there aren't sufficient remaining inputs
		for numInputs-i <= numRequired && !argDef.Required {
			argDefIndex++
			argDef = getArgDef(argDefIndex, argDefs)
		}
		if argDef.Required {
			numRequired--
		}

		var err error
		if argDef.Type == cmds.ArgString {
			if stdin == nil {
				// add string values
				stringArgs, inputs = appendString(stringArgs, inputs)

			} else if argDef.SupportsStdin {
				// if we have a stdin, read it in and use the data as a string value
				stringArgs, stdin, err = appendStdinAsString(stringArgs, stdin)
				if err != nil {
					return nil, nil, err
				}
			}

		} else if argDef.Type == cmds.ArgFile {
			if stdin == nil {
				// treat stringArg values as file paths
				fileArgs, inputs, err = appendFile(fileArgs, inputs, argDef, recursive)
				if err != nil {
					return nil, nil, err
				}

			} else if argDef.SupportsStdin {
				// if we have a stdin, create a file from it
				fileArgs, stdin = appendStdinAsFile(fileArgs, stdin)
			}
		}

		argDefIndex++
	}

	// check to make sure we didn't miss any required arguments
	if len(argDefs) > argDefIndex {
		for _, argDef := range argDefs[argDefIndex:] {
			if argDef.Required {
				return nil, nil, fmt.Errorf("Argument '%s' is required", argDef.Name)
			}
		}
	}

	return stringArgs, fileArgs, nil
}

func getArgDef(i int, argDefs []cmds.Argument) *cmds.Argument {
	if i < len(argDefs) {
		// get the argument definition (usually just argDefs[i])
		return &argDefs[i]

	} else if len(argDefs) > 0 {
		// but if i > len(argDefs) we use the last argument definition)
		return &argDefs[len(argDefs)-1]
	}

	// only happens if there aren't any definitions
	return nil
}

func appendString(args, inputs []string) ([]string, []string) {
	return append(args, inputs[0]), inputs[1:]
}

func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, error) {
	var buf bytes.Buffer

	_, err := buf.ReadFrom(stdin)
	if err != nil {
		return nil, nil, err
	}

	input := strings.TrimSpace(buf.String())
	return append(args, strings.Split(input, "\n")...), nil, nil
}

func appendFile(args []cmds.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]cmds.File, []string, error) {
	path := inputs[0]

	file, err := os.Open(path)
	if err != nil {
		return nil, nil, err
	}

	stat, err := file.Stat()
	if err != nil {
		return nil, nil, err
	}

	if stat.IsDir() {
		if !argDef.Recursive {
			err = fmt.Errorf("Invalid path '%s', argument '%s' does not support directories",
				path, argDef.Name)
			return nil, nil, err
		}
		if !recursive {
			err = fmt.Errorf("'%s' is a directory, use the '-%s' flag to specify directories",
				path, cmds.RecShort)
			return nil, nil, err
		}
	}

	arg, err := openPath(file, path)
	if err != nil {
		return nil, nil, err
	}

	return append(args, arg), inputs[1:], nil
}

func appendStdinAsFile(args []cmds.File, stdin *os.File) ([]cmds.File, *os.File) {
	arg := &cmds.ReaderFile{"", stdin}
	return append(args, arg), nil
}

// recursively get file or directory contents as a cmds.File
func openPath(file *os.File, path string) (cmds.File, error) {
	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	// for non-directories, return a ReaderFile
	if !stat.IsDir() {
		return &cmds.ReaderFile{path, file}, nil
	}

	// for directories, recursively iterate though children then return as a SliceFile
	contents, err := file.Readdir(0)
	if err != nil {
		return nil, err
	}

	// make sure contents are sorted so -- repeatably -- we get the same inputs.
	sort.Sort(sortFIByName(contents))

	files := make([]cmds.File, 0, len(contents))
	for _, child := range contents {
		childPath := fp.Join(path, child.Name())
		childFile, err := os.Open(childPath)
		if err != nil {
			return nil, err
		}

		f, err := openPath(childFile, childPath)
		if err != nil {
			return nil, err
		}

		files = append(files, f)
	}

	return &cmds.SliceFile{path, files}, nil
}

// isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`),
// and false otherwise (e.g. nothing is being piped in, so stdin is
// coming from the terminal)
func isTerminal(stdin *os.File) (bool, error) {
	stat, err := stdin.Stat()
	if err != nil {
		return false, err
	}

	// if stdin is a CharDevice, return true
	return ((stat.Mode() & os.ModeCharDevice) != 0), nil
}

type sortFIByName []os.FileInfo

func (es sortFIByName) Len() int           { return len(es) }
func (es sortFIByName) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }
func (es sortFIByName) Less(i, j int) bool { return es[i].Name() < es[j].Name() }
<|endoftext|>


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

package index

import (
	"github.com/andreaskoch/allmark2/common/logger"
	"github.com/andreaskoch/allmark2/common/route"
	"github.com/andreaskoch/allmark2/common/tree/itemtree"
	"github.com/andreaskoch/allmark2/model"
)

func New(logger logger.Logger, repositoryName string) *Index {
	return &Index{
		logger:         logger,
		repositoryName: repositoryName,

		itemList: make([]*model.Item, 0),
		routeMap: make(map[string]*model.Item),
		itemTree: itemtree.New(),
	}
}

type Index struct {
	logger         logger.Logger
	repositoryName string

	// indizes
	itemList []*model.Item
	routeMap map[string]*model.Item // route -> item,
	itemTree *itemtree.ItemTree
}

func (index *Index) IsMatch(route route.Route) (item *model.Item, isMatch bool) {

	// check for a direct match
	if item, isMatch = index.routeMap[route.Value()]; isMatch {
		return item, isMatch
	}

	// no match
	return nil, false
}

func (index *Index) IsFileMatch(route route.Route) (*model.File, bool) {

	var parent *model.Item
	parentRoute := &route
	for parentRoute != nil && parentRoute.Level() >= 0 {

		parent, _ = index.IsMatch(*parentRoute)
		if parent == nil || parent.IsVirtual() {

			// next level
			parentRoute = parentRoute.Parent()
			continue

		}

		// found a non-virtual parent
		break

	}

	// abort if there is no non-virtual parent
	if parent == nil {
		index.logger.Warn("No file found for route %q", route)
		return nil, false
	}

	// check if the parent has a file with the supplied route
	if file := parent.GetFile(route); file != nil {
		return file, true
	}

	// file not found
	return nil, false
}

func (index *Index) GetParent(childRoute *route.Route) *model.Item {

	if childRoute == nil {
		return nil
	}

	// abort if the supplied route is already a root
	if childRoute.Level() == 0 {
		return nil
	}

	// get the parent route
	parentRoute := childRoute.Parent()
	if parentRoute == nil {
		return nil
	}

	item, isMatch := index.IsMatch(*parentRoute)
	if !isMatch {
		return nil
	}

	return item
}

func (index *Index) Root() *model.Item {
	return index.itemTree.Root()
}

// Get all childs that match the given expression
func (index *Index) GetAllChilds(route *route.Route, expression func(item *model.Item) bool) []*model.Item {

	childs := make([]*model.Item, 0)

	// get all direct childs of the supplied route
	directChilds := index.GetDirectChilds(route)

	for _, child := range directChilds {

		// evaluate expression
		if !expression(child) {
			continue
		}

		// append child
		childs = append(childs, child)

		// recurse
		childs = append(childs, index.GetAllChilds(child.Route(), expression)...)

	}

	return childs
}

func (index *Index) GetDirectChilds(route *route.Route) []*model.Item {
	// get all mathching childs
	childs := index.itemTree.GetChildItems(route)

	// sort the items by ascending by route
	model.SortItemBy(sortItemsByRoute).Sort(childs)

	return childs
}

// Get a list of all item in this index.
func (index *Index) Items() []*model.Item {
	items := index.itemList
	return items
}

func (index *Index) GetItemByRoute(route *route.Route) (bool, *model.Item) {
	routeValue := route.Value()
	if item, exists := index.routeMap[routeValue]; exists {
		return true, item
	}

	return false, nil
}

func (index *Index) Add(item *model.Item) {

	// abort if item is invalid
	if item == nil {
		index.logger.Warn("Cannot add an invalid item to the index.")
		return
	}

	index.logger.Debug("Adding item %q to index", item)

	// the the item to the indizes
	index.insertItemToItemList(item)
	index.insertItemToRouteMap(item)
	index.insertItemToTree(item)
}

func (index *Index) insertItemToItemList(item *model.Item) {
	index.itemList = append(index.itemList, item)
}

func (index *Index) insertItemToRouteMap(item *model.Item) {
	itemRoute := item.Route().Value()
	index.routeMap[itemRoute] = item
}

func (index *Index) insertItemToTree(item *model.Item) {
	index.itemTree.InsertItem(item)
}

// sort the items by name
func sortItemsByRoute(item1, item2 *model.Item) bool {

	// ascending by route
	return item1.Route().Value() > item2.Route().Value()

}
<commit_msg>Automatically make root items a repository<commit_after>// Copyright 2013 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package index

import (
	"github.com/andreaskoch/allmark2/common/logger"
	"github.com/andreaskoch/allmark2/common/route"
	"github.com/andreaskoch/allmark2/common/tree/itemtree"
	"github.com/andreaskoch/allmark2/model"
)

func New(logger logger.Logger, repositoryName string) *Index {
	return &Index{
		logger:         logger,
		repositoryName: repositoryName,

		itemList: make([]*model.Item, 0),
		routeMap: make(map[string]*model.Item),
		itemTree: itemtree.New(),
	}
}

type Index struct {
	logger         logger.Logger
	repositoryName string

	// indizes
	itemList []*model.Item
	routeMap map[string]*model.Item // route -> item,
	itemTree *itemtree.ItemTree
}

func (index *Index) IsMatch(route route.Route) (item *model.Item, isMatch bool) {

	// check for a direct match
	if item, isMatch = index.routeMap[route.Value()]; isMatch {
		return item, isMatch
	}

	// no match
	return nil, false
}

func (index *Index) IsFileMatch(route route.Route) (*model.File, bool) {

	var parent *model.Item
	parentRoute := &route
	for parentRoute != nil && parentRoute.Level() >= 0 {

		parent, _ = index.IsMatch(*parentRoute)
		if parent == nil || parent.IsVirtual() {

			// next level
			parentRoute = parentRoute.Parent()
			continue

		}

		// found a non-virtual parent
		break

	}

	// abort if there is no non-virtual parent
	if parent == nil {
		index.logger.Warn("No file found for route %q", route)
		return nil, false
	}

	// check if the parent has a file with the supplied route
	if file := parent.GetFile(route); file != nil {
		return file, true
	}

	// file not found
	return nil, false
}

func (index *Index) GetParent(childRoute *route.Route) *model.Item {

	if childRoute == nil {
		return nil
	}

	// abort if the supplied route is already a root
	if childRoute.Level() == 0 {
		return nil
	}

	// get the parent route
	parentRoute := childRoute.Parent()
	if parentRoute == nil {
		return nil
	}

	item, isMatch := index.IsMatch(*parentRoute)
	if !isMatch {
		return nil
	}

	return item
}

func (index *Index) Root() *model.Item {
	return index.itemTree.Root()
}

// Get all childs that match the given expression
func (index *Index) GetAllChilds(route *route.Route, expression func(item *model.Item) bool) []*model.Item {

	childs := make([]*model.Item, 0)

	// get all direct childs of the supplied route
	directChilds := index.GetDirectChilds(route)

	for _, child := range directChilds {

		// evaluate expression
		if !expression(child) {
			continue
		}

		// append child
		childs = append(childs, child)

		// recurse
		childs = append(childs, index.GetAllChilds(child.Route(), expression)...)

	}

	return childs
}

func (index *Index) GetDirectChilds(route *route.Route) []*model.Item {
	// get all mathching childs
	childs := index.itemTree.GetChildItems(route)

	// sort the items by ascending by route
	model.SortItemBy(sortItemsByRoute).Sort(childs)

	return childs
}

// Get a list of all item in this index.
func (index *Index) Items() []*model.Item {
	items := index.itemList
	return items
}

func (index *Index) GetItemByRoute(route *route.Route) (bool, *model.Item) {
	routeValue := route.Value()
	if item, exists := index.routeMap[routeValue]; exists {
		return true, item
	}

	return false, nil
}

func (index *Index) Add(item *model.Item) {

	// abort if item is invalid
	if item == nil {
		index.logger.Warn("Cannot add an invalid item to the index.")
		return
	}

	// make root items a repository
	if item.Route().Level() == 0 {
		item.Type = model.TypeRepository
	}

	index.logger.Debug("Adding item %q to index", item)

	// the the item to the indizes
	index.insertItemToItemList(item)
	index.insertItemToRouteMap(item)
	index.insertItemToTree(item)
}

func (index *Index) insertItemToItemList(item *model.Item) {
	index.itemList = append(index.itemList, item)
}

func (index *Index) insertItemToRouteMap(item *model.Item) {
	itemRoute := item.Route().Value()
	index.routeMap[itemRoute] = item
}

func (index *Index) insertItemToTree(item *model.Item) {
	index.itemTree.InsertItem(item)
}

// sort the items by name
func sortItemsByRoute(item1, item2 *model.Item) bool {

	// ascending by route
	return item1.Route().Value() > item2.Route().Value()

}
<|endoftext|>


<commit_before><commit_msg>Create demo.go<commit_after><|endoftext|>


<commit_before>package cmd

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"sync"
	"time"

	"github.com/go-errors/errors"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmaserver"
	"github.com/sietseringers/cobra"
	"github.com/sirupsen/logrus"
	prefixed "github.com/x-cray/logrus-prefixed-formatter"
)

var (
	httpServer *http.Server
	irmaServer *irmaserver.Server
	defaulturl string

	logger = logrus.New()
)

// sessionCmd represents the session command
var sessionCmd = &cobra.Command{
	Use:   "session",
	Short: "Perform an IRMA disclosure, issuance or signature session",
	Long: `Perform an IRMA disclosure, issuance or signature session on the command line

Using either the builtin IRMA server library, or an external IRMA server (specify its URL
with --server), an IRMA session is started; the QR is printed in the terminal; and the session
result is printed when the session completes or fails.

A session request can either be constructed using the --disclose, --issue, and --sign together
with --message flags, or it can be specified as JSON to the --request flag.`,
	Example: `irma session --disclose irma-demo.MijnOverheid.root.BSN
irma session --sign irma-demo.MijnOverheid.root.BSN --message message
irma session --issue irma-demo.MijnOverheid.ageLower=yes,yes,yes,no --disclose irma-demo.MijnOverheid.root.BSN
irma session --request '{"type":"disclosing","content":[{"label":"BSN","attributes":["irma-demo.MijnOverheid.root.BSN"]}]}'
irma session --server http://localhost:8088 --authmethod token --key mytoken --disclose irma-demo.MijnOverheid.root.BSN`,
	Run: func(cmd *cobra.Command, args []string) {
		request, irmaconfig, err := configureSession(cmd)
		if err != nil {
			die("", err)
		}

		// Make sure we always run with latest configuration
		flags := cmd.Flags()
		disableUpdate, _ := flags.GetBool("disable-schemes-update")
		if !disableUpdate {
			if err = irmaconfig.UpdateSchemes(); err != nil {
				die("failed updating schemes", err)
			}
		}

		var result *server.SessionResult
		url, _ := cmd.Flags().GetString("url")
		serverurl, _ := cmd.Flags().GetString("server")
		noqr, _ := cmd.Flags().GetBool("noqr")
		binding, _ := cmd.Flags().GetBool("binding")

		if url != defaulturl && serverurl != "" {
			die("Failed to read configuration", errors.New("--url can't be combined with --server"))
		}

		if serverurl == "" {
			port, _ := flags.GetInt("port")
			privatekeysPath, _ := flags.GetString("privkeys")
			verbosity, _ := cmd.Flags().GetCount("verbose")
			result, err = libraryRequest(request, irmaconfig, url, port, privatekeysPath, noqr, verbosity, binding)
		} else {
			authmethod, _ := flags.GetString("authmethod")
			key, _ := flags.GetString("key")
			name, _ := flags.GetString("name")
			result, err = serverRequest(request, serverurl, authmethod, key, name, noqr, binding)
		}
		if err != nil {
			die("Session failed", err)
		}

		printSessionResult(result)

		// Done!
		if httpServer != nil {
			_ = httpServer.Close()
		}
	},
}

func libraryRequest(
	request irma.RequestorRequest,
	irmaconfig *irma.Configuration,
	url string,
	port int,
	privatekeysPath string,
	noqr bool,
	verbosity int,
	binding bool,
) (*server.SessionResult, error) {
	if err := configureSessionServer(url, port, privatekeysPath, irmaconfig, verbosity); err != nil {
		return nil, err
	}
	startServer(port)

	// Start the session
	resultchan := make(chan *server.SessionResult)
	qr, backendToken, _, err := irmaServer.StartSession(request, func(r *server.SessionResult) {
		resultchan <- r
	})
	if err != nil {
		return nil, errors.WrapPrefix(err, "IRMA session failed", 0)
	}

	// Currently, the default session options are the same in all conditions,
	// so do only fetch them when a change is requested
	var sessionOptions *server.SessionOptions
	if binding {
		optionsRequest := irma.NewOptionsRequest()
		optionsRequest.BindingMethod = irma.BindingMethodPin
		if sessionOptions, err = irmaServer.SetFrontendOptions(backendToken, &optionsRequest); err != nil {
			return nil, errors.WrapPrefix(err, "IRMA enable binding failed", 0)
		}
	}

	// Print QR code
	if err := printQr(qr, noqr); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
	}

	if binding {
		// Listen for session status
		statuschan := make(chan server.Status)
		go func() {
			var status server.Status
			for {
				newStatus := irmaServer.GetSessionResult(backendToken).Status
				if newStatus != status {
					status = newStatus
					statuschan <- status
					if status.Finished() {
						return
					}
				}
				time.Sleep(500 * time.Millisecond)
			}
		}()

		_, err = handleBinding(sessionOptions, statuschan, func() error {
			return irmaServer.BindingCompleted(backendToken)
		})
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to handle binding", 0)
		}
	}

	// Wait for session to finish and then return session result
	return <-resultchan, nil
}

func serverRequest(
	request irma.RequestorRequest,
	serverurl, authmethod, key, name string,
	noqr bool,
	binding bool,
) (*server.SessionResult, error) {
	logger.Debug("Server URL: ", serverurl)

	// Start session at server
	qr, frontendToken, transport, err := postRequest(serverurl, request, name, authmethod, key)
	if err != nil {
		return nil, err
	}

	// Enable binding if necessary
	var frontendTransport *irma.HTTPTransport
	sessionOptions := &server.SessionOptions{}
	if binding {
		frontendTransport = irma.NewHTTPTransport(qr.URL, false)
		frontendTransport.SetHeader(irma.AuthorizationHeader, string(frontendToken))
		optionsRequest := irma.NewOptionsRequest()
		optionsRequest.BindingMethod = irma.BindingMethodPin
		err = frontendTransport.Post("frontend/options", sessionOptions, optionsRequest)
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to enable binding", 0)
		}
	}

	// Print session QR
	logger.Debug("QR: ", prettyprint(qr))
	if err := printQr(qr, noqr); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
	}

	statuschan := make(chan server.Status)
	errorchan := make(chan error)
	var wg sync.WaitGroup

	go server.WaitStatus(transport, server.StatusInitialized, statuschan, errorchan)
	go func() {
		err := <-errorchan
		if err != nil {
			_ = server.LogFatal(err)
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()

		var status server.Status
		if binding {
			status, err = handleBinding(sessionOptions, statuschan, func() error {
				err = frontendTransport.Post("frontend/bindingcompleted", nil, nil)
				if err != nil {
					return errors.WrapPrefix(err, "Failed to complete binding", 0)
				}
				return nil
			})
			if err != nil {
				err = errors.WrapPrefix(err, "Failed to handle binding", 0)
				return
			}
		} else {
			// Wait until client connects if binding is disabled
			status := <-statuschan
			if status != server.StatusConnected {
				err = errors.Errorf("Unexpected status: %s", status)
				return
			}
		}

		// Wait until client finishes
		status = <-statuschan
		if status != server.StatusCancelled && status != server.StatusDone {
			err = errors.Errorf("Unexpected status: %s", status)
			return
		}
	}()

	wg.Wait()
	if err != nil {
		return nil, err
	}

	// Retrieve session result
	result := &server.SessionResult{}
	if err := transport.Get("result", result); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to get session result", 0)
	}
	return result, nil
}

func postRequest(serverurl string, request irma.RequestorRequest, name, authmethod, key string) (
	*irma.Qr, irma.FrontendToken, *irma.HTTPTransport, error) {
	var (
		err       error
		pkg       = &server.SessionPackage{}
		transport = irma.NewHTTPTransport(serverurl, false)
	)

	switch authmethod {
	case "none":
		err = transport.Post("session", pkg, request)
	case "token":
		transport.SetHeader("Authorization", key)
		err = transport.Post("session", pkg, request)
	case "hmac", "rsa":
		var jwtstr string
		jwtstr, err = signRequest(request, name, authmethod, key)
		if err != nil {
			return nil, "", nil, err
		}
		logger.Debug("Session request JWT: ", jwtstr)
		err = transport.Post("session", pkg, jwtstr)
	default:
		return nil, "", nil, errors.New("Invalid authentication method (must be none, token, hmac or rsa)")
	}

	if err != nil {
		return nil, "", transport, err
	}

	backendToken := pkg.Token
	transport.Server += fmt.Sprintf("session/%s/", backendToken)

	return pkg.SessionPtr, pkg.FrontendToken, transport, err
}

func handleBinding(options *server.SessionOptions, statusChan chan server.Status, completeBinding func() error) (
	server.Status, error) {
	errorChan := make(chan error)
	status := server.StatusInitialized
	bindingStarted := false
	for {
		select {
		case status = <-statusChan:
			if status == server.StatusInitialized {
				continue
			} else if status == server.StatusBinding {
				bindingStarted = true
				go requestBindingPermission(options, completeBinding, errorChan)
				continue
			} else if status == server.StatusConnected && !bindingStarted {
				fmt.Println("Binding is not supported by the connected device.")
			}
			return status, nil
		case err := <-errorChan:
			return status, err
		}
	}
}

func requestBindingPermission(options *server.SessionOptions, completeBinding func() error, errorChan chan error) {
	if options.BindingMethod == irma.BindingMethodPin {
		fmt.Println("\nBinding code:", options.BindingCode)
		fmt.Println("Press Enter to confirm your device is connected; otherwise press Ctrl-C.")
		_, err := bufio.NewReader(os.Stdin).ReadString('\n')
		if err != nil {
			errorChan <- err
			return
		}
		if err = completeBinding(); err != nil {
			errorChan <- err
			return
		}
		fmt.Println("Binding completed.")
		errorChan <- nil
		return
	}
	errorChan <- errors.Errorf("Binding method %s is not supported", options.BindingMethod)
}

// Configuration functions

func configureSessionServer(url string, port int, privatekeysPath string, irmaconfig *irma.Configuration, verbosity int) error {
	// Replace "port" in url with actual port
	replace := "$1:" + strconv.Itoa(port)
	url = string(regexp.MustCompile("(https?://[^/]*):port").ReplaceAll([]byte(url), []byte(replace)))

	config := &server.Configuration{
		IrmaConfiguration:    irmaconfig,
		Logger:               logger,
		URL:                  url,
		DisableSchemesUpdate: true,
		Verbose:              verbosity,
	}
	if privatekeysPath != "" {
		config.IssuerPrivateKeysPath = privatekeysPath
	}

	var err error
	irmaServer, err = irmaserver.New(config)
	return err
}

func configureSession(cmd *cobra.Command) (irma.RequestorRequest, *irma.Configuration, error) {
	verbosity, _ := cmd.Flags().GetCount("verbose")
	logger.Level = server.Verbosity(verbosity)
	irma.SetLogger(logger)

	return configureRequest(cmd)
}

func init() {
	RootCmd.AddCommand(sessionCmd)

	logger.Formatter = &prefixed.TextFormatter{FullTimestamp: true}

	var err error
	defaulturl, err = server.LocalIP()
	if err != nil {
		logger.Warn("Could not determine local IP address: ", err.Error())
	} else {
		defaulturl = "http://" + defaulturl + ":port"
	}

	flags := sessionCmd.Flags()
	flags.SortFlags = false
	flags.String("server", "", "External IRMA server to post request to (leave blank to use builtin library)")
	flags.StringP("url", "u", defaulturl, "external URL to which IRMA app connects (when not using --server), \":port\" being replaced by --port value")
	flags.IntP("port", "p", 48680, "port to listen at (when not using --server)")
	flags.Bool("noqr", false, "Print JSON instead of draw QR")
	flags.Bool("binding", false, "Enable explicit binding between server and IRMA app")
	flags.StringP("request", "r", "", "JSON session request")
	flags.StringP("privkeys", "k", "", "path to private keys")
	flags.Bool("disable-schemes-update", false, "disable scheme updates")

	addRequestFlags(flags)

	flags.CountP("verbose", "v", "verbose (repeatable)")
}
<commit_msg>Improved request binding permission instruction<commit_after>package cmd

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"sync"
	"time"

	"github.com/go-errors/errors"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmaserver"
	"github.com/sietseringers/cobra"
	"github.com/sirupsen/logrus"
	prefixed "github.com/x-cray/logrus-prefixed-formatter"
)

var (
	httpServer *http.Server
	irmaServer *irmaserver.Server
	defaulturl string

	logger = logrus.New()
)

// sessionCmd represents the session command
var sessionCmd = &cobra.Command{
	Use:   "session",
	Short: "Perform an IRMA disclosure, issuance or signature session",
	Long: `Perform an IRMA disclosure, issuance or signature session on the command line

Using either the builtin IRMA server library, or an external IRMA server (specify its URL
with --server), an IRMA session is started; the QR is printed in the terminal; and the session
result is printed when the session completes or fails.

A session request can either be constructed using the --disclose, --issue, and --sign together
with --message flags, or it can be specified as JSON to the --request flag.`,
	Example: `irma session --disclose irma-demo.MijnOverheid.root.BSN
irma session --sign irma-demo.MijnOverheid.root.BSN --message message
irma session --issue irma-demo.MijnOverheid.ageLower=yes,yes,yes,no --disclose irma-demo.MijnOverheid.root.BSN
irma session --request '{"type":"disclosing","content":[{"label":"BSN","attributes":["irma-demo.MijnOverheid.root.BSN"]}]}'
irma session --server http://localhost:8088 --authmethod token --key mytoken --disclose irma-demo.MijnOverheid.root.BSN`,
	Run: func(cmd *cobra.Command, args []string) {
		request, irmaconfig, err := configureSession(cmd)
		if err != nil {
			die("", err)
		}

		// Make sure we always run with latest configuration
		flags := cmd.Flags()
		disableUpdate, _ := flags.GetBool("disable-schemes-update")
		if !disableUpdate {
			if err = irmaconfig.UpdateSchemes(); err != nil {
				die("failed updating schemes", err)
			}
		}

		var result *server.SessionResult
		url, _ := cmd.Flags().GetString("url")
		serverurl, _ := cmd.Flags().GetString("server")
		noqr, _ := cmd.Flags().GetBool("noqr")
		binding, _ := cmd.Flags().GetBool("binding")

		if url != defaulturl && serverurl != "" {
			die("Failed to read configuration", errors.New("--url can't be combined with --server"))
		}

		if serverurl == "" {
			port, _ := flags.GetInt("port")
			privatekeysPath, _ := flags.GetString("privkeys")
			verbosity, _ := cmd.Flags().GetCount("verbose")
			result, err = libraryRequest(request, irmaconfig, url, port, privatekeysPath, noqr, verbosity, binding)
		} else {
			authmethod, _ := flags.GetString("authmethod")
			key, _ := flags.GetString("key")
			name, _ := flags.GetString("name")
			result, err = serverRequest(request, serverurl, authmethod, key, name, noqr, binding)
		}
		if err != nil {
			die("Session failed", err)
		}

		printSessionResult(result)

		// Done!
		if httpServer != nil {
			_ = httpServer.Close()
		}
	},
}

func libraryRequest(
	request irma.RequestorRequest,
	irmaconfig *irma.Configuration,
	url string,
	port int,
	privatekeysPath string,
	noqr bool,
	verbosity int,
	binding bool,
) (*server.SessionResult, error) {
	if err := configureSessionServer(url, port, privatekeysPath, irmaconfig, verbosity); err != nil {
		return nil, err
	}
	startServer(port)

	// Start the session
	resultchan := make(chan *server.SessionResult)
	qr, backendToken, _, err := irmaServer.StartSession(request, func(r *server.SessionResult) {
		resultchan <- r
	})
	if err != nil {
		return nil, errors.WrapPrefix(err, "IRMA session failed", 0)
	}

	// Currently, the default session options are the same in all conditions,
	// so do only fetch them when a change is requested
	var sessionOptions *server.SessionOptions
	if binding {
		optionsRequest := irma.NewOptionsRequest()
		optionsRequest.BindingMethod = irma.BindingMethodPin
		if sessionOptions, err = irmaServer.SetFrontendOptions(backendToken, &optionsRequest); err != nil {
			return nil, errors.WrapPrefix(err, "IRMA enable binding failed", 0)
		}
	}

	// Print QR code
	if err := printQr(qr, noqr); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
	}

	if binding {
		// Listen for session status
		statuschan := make(chan server.Status)
		go func() {
			var status server.Status
			for {
				newStatus := irmaServer.GetSessionResult(backendToken).Status
				if newStatus != status {
					status = newStatus
					statuschan <- status
					if status.Finished() {
						return
					}
				}
				time.Sleep(500 * time.Millisecond)
			}
		}()

		_, err = handleBinding(sessionOptions, statuschan, func() error {
			return irmaServer.BindingCompleted(backendToken)
		})
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to handle binding", 0)
		}
	}

	// Wait for session to finish and then return session result
	return <-resultchan, nil
}

func serverRequest(
	request irma.RequestorRequest,
	serverurl, authmethod, key, name string,
	noqr bool,
	binding bool,
) (*server.SessionResult, error) {
	logger.Debug("Server URL: ", serverurl)

	// Start session at server
	qr, frontendToken, transport, err := postRequest(serverurl, request, name, authmethod, key)
	if err != nil {
		return nil, err
	}

	// Enable binding if necessary
	var frontendTransport *irma.HTTPTransport
	sessionOptions := &server.SessionOptions{}
	if binding {
		frontendTransport = irma.NewHTTPTransport(qr.URL, false)
		frontendTransport.SetHeader(irma.AuthorizationHeader, string(frontendToken))
		optionsRequest := irma.NewOptionsRequest()
		optionsRequest.BindingMethod = irma.BindingMethodPin
		err = frontendTransport.Post("frontend/options", sessionOptions, optionsRequest)
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to enable binding", 0)
		}
	}

	// Print session QR
	logger.Debug("QR: ", prettyprint(qr))
	if err := printQr(qr, noqr); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
	}

	statuschan := make(chan server.Status)
	errorchan := make(chan error)
	var wg sync.WaitGroup

	go server.WaitStatus(transport, server.StatusInitialized, statuschan, errorchan)
	go func() {
		err := <-errorchan
		if err != nil {
			_ = server.LogFatal(err)
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()

		var status server.Status
		if binding {
			status, err = handleBinding(sessionOptions, statuschan, func() error {
				err = frontendTransport.Post("frontend/bindingcompleted", nil, nil)
				if err != nil {
					return errors.WrapPrefix(err, "Failed to complete binding", 0)
				}
				return nil
			})
			if err != nil {
				err = errors.WrapPrefix(err, "Failed to handle binding", 0)
				return
			}
		} else {
			// Wait until client connects if binding is disabled
			status := <-statuschan
			if status != server.StatusConnected {
				err = errors.Errorf("Unexpected status: %s", status)
				return
			}
		}

		// Wait until client finishes
		status = <-statuschan
		if status != server.StatusCancelled && status != server.StatusDone {
			err = errors.Errorf("Unexpected status: %s", status)
			return
		}
	}()

	wg.Wait()
	if err != nil {
		return nil, err
	}

	// Retrieve session result
	result := &server.SessionResult{}
	if err := transport.Get("result", result); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to get session result", 0)
	}
	return result, nil
}

func postRequest(serverurl string, request irma.RequestorRequest, name, authmethod, key string) (
	*irma.Qr, irma.FrontendToken, *irma.HTTPTransport, error) {
	var (
		err       error
		pkg       = &server.SessionPackage{}
		transport = irma.NewHTTPTransport(serverurl, false)
	)

	switch authmethod {
	case "none":
		err = transport.Post("session", pkg, request)
	case "token":
		transport.SetHeader("Authorization", key)
		err = transport.Post("session", pkg, request)
	case "hmac", "rsa":
		var jwtstr string
		jwtstr, err = signRequest(request, name, authmethod, key)
		if err != nil {
			return nil, "", nil, err
		}
		logger.Debug("Session request JWT: ", jwtstr)
		err = transport.Post("session", pkg, jwtstr)
	default:
		return nil, "", nil, errors.New("Invalid authentication method (must be none, token, hmac or rsa)")
	}

	if err != nil {
		return nil, "", transport, err
	}

	backendToken := pkg.Token
	transport.Server += fmt.Sprintf("session/%s/", backendToken)

	return pkg.SessionPtr, pkg.FrontendToken, transport, err
}

func handleBinding(options *server.SessionOptions, statusChan chan server.Status, completeBinding func() error) (
	server.Status, error) {
	errorChan := make(chan error)
	status := server.StatusInitialized
	bindingStarted := false
	for {
		select {
		case status = <-statusChan:
			if status == server.StatusInitialized {
				continue
			} else if status == server.StatusBinding {
				bindingStarted = true
				go requestBindingPermission(options, completeBinding, errorChan)
				continue
			} else if status == server.StatusConnected && !bindingStarted {
				fmt.Println("Binding is not supported by the connected device.")
			}
			return status, nil
		case err := <-errorChan:
			return status, err
		}
	}
}

func requestBindingPermission(options *server.SessionOptions, completeBinding func() error, errorChan chan error) {
	if options.BindingMethod == irma.BindingMethodPin {
		fmt.Println("\nBinding code:", options.BindingCode)
		fmt.Println("Press Enter to confirm your device shows the same binding code; otherwise press Ctrl-C.")
		_, err := bufio.NewReader(os.Stdin).ReadString('\n')
		if err != nil {
			errorChan <- err
			return
		}
		if err = completeBinding(); err != nil {
			errorChan <- err
			return
		}
		fmt.Println("Binding completed.")
		errorChan <- nil
		return
	}
	errorChan <- errors.Errorf("Binding method %s is not supported", options.BindingMethod)
}

// Configuration functions

func configureSessionServer(url string, port int, privatekeysPath string, irmaconfig *irma.Configuration, verbosity int) error {
	// Replace "port" in url with actual port
	replace := "$1:" + strconv.Itoa(port)
	url = string(regexp.MustCompile("(https?://[^/]*):port").ReplaceAll([]byte(url), []byte(replace)))

	config := &server.Configuration{
		IrmaConfiguration:    irmaconfig,
		Logger:               logger,
		URL:                  url,
		DisableSchemesUpdate: true,
		Verbose:              verbosity,
	}
	if privatekeysPath != "" {
		config.IssuerPrivateKeysPath = privatekeysPath
	}

	var err error
	irmaServer, err = irmaserver.New(config)
	return err
}

func configureSession(cmd *cobra.Command) (irma.RequestorRequest, *irma.Configuration, error) {
	verbosity, _ := cmd.Flags().GetCount("verbose")
	logger.Level = server.Verbosity(verbosity)
	irma.SetLogger(logger)

	return configureRequest(cmd)
}

func init() {
	RootCmd.AddCommand(sessionCmd)

	logger.Formatter = &prefixed.TextFormatter{FullTimestamp: true}

	var err error
	defaulturl, err = server.LocalIP()
	if err != nil {
		logger.Warn("Could not determine local IP address: ", err.Error())
	} else {
		defaulturl = "http://" + defaulturl + ":port"
	}

	flags := sessionCmd.Flags()
	flags.SortFlags = false
	flags.String("server", "", "External IRMA server to post request to (leave blank to use builtin library)")
	flags.StringP("url", "u", defaulturl, "external URL to which IRMA app connects (when not using --server), \":port\" being replaced by --port value")
	flags.IntP("port", "p", 48680, "port to listen at (when not using --server)")
	flags.Bool("noqr", false, "Print JSON instead of draw QR")
	flags.Bool("binding", false, "Enable explicit binding between server and IRMA app")
	flags.StringP("request", "r", "", "JSON session request")
	flags.StringP("privkeys", "k", "", "path to private keys")
	flags.Bool("disable-schemes-update", false, "disable scheme updates")

	addRequestFlags(flags)

	flags.CountP("verbose", "v", "verbose (repeatable)")
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package commands

import (
	"reflect"
	"strings"
	"testing"

	"k8s.io/kubectl/pkg/kustomize/constants"
	"k8s.io/kubectl/pkg/kustomize/types"
	"k8s.io/kubectl/pkg/kustomize/util/fs"
)

func TestWriteAndRead(t *testing.T) {
	kustomization := &types.Kustomization{
		NamePrefix: "prefix",
	}

	fsys := fs.MakeFakeFS()
	fsys.Create(constants.KustomizationFileName)
	mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
	if err != nil {
		t.Fatalf("Unexpected Error: %v", err)
	}

	if err := mf.write(kustomization); err != nil {
		t.Fatalf("Couldn't write kustomization file: %v\n", err)
	}

	content, err := mf.read()
	if err != nil {
		t.Fatalf("Couldn't read kustomization file: %v\n", err)
	}
	if !reflect.DeepEqual(kustomization, content) {
		t.Fatal("Read kustomization is different from written kustomization")
	}
}

func TestEmptyFile(t *testing.T) {
	fsys := fs.MakeFakeFS()
	_, err := newKustomizationFile("", fsys)
	if err == nil {
		t.Fatalf("Creat kustomizationFile from empty filename should fail")
	}
}

func TestNewNotExist(t *testing.T) {
	badSuffix := "foo.bar"
	fakeFS := fs.MakeFakeFS()
	fakeFS.Mkdir(".", 0644)
	fakeFS.Create(badSuffix)
	_, err := newKustomizationFile(constants.KustomizationFileName, fakeFS)
	if err == nil {
		t.Fatalf("expect an error")
	}
	contained := "Missing kustomization file"
	if !strings.Contains(err.Error(), contained) {
		t.Fatalf("expect an error contains %q, but got %v", contained, err)
	}
	_, err = newKustomizationFile(constants.KustomizationFileName, fakeFS)
	if err == nil {
		t.Fatalf("expect an error")
	}
	if !strings.Contains(err.Error(), contained) {
		t.Fatalf("expect an error contains %q, but got %v", contained, err)
	}
	_, err = newKustomizationFile(badSuffix, fakeFS)
	if err == nil {
		t.Fatalf("expect an error")
	}
	contained = "should have .yaml suffix"
	if !strings.Contains(err.Error(), contained) {
		t.Fatalf("expect an error contains %q, but got %v", contained, err)
	}
}
<commit_msg>Typo fix in the returned info.<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package commands

import (
	"reflect"
	"strings"
	"testing"

	"k8s.io/kubectl/pkg/kustomize/constants"
	"k8s.io/kubectl/pkg/kustomize/types"
	"k8s.io/kubectl/pkg/kustomize/util/fs"
)

func TestWriteAndRead(t *testing.T) {
	kustomization := &types.Kustomization{
		NamePrefix: "prefix",
	}

	fsys := fs.MakeFakeFS()
	fsys.Create(constants.KustomizationFileName)
	mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
	if err != nil {
		t.Fatalf("Unexpected Error: %v", err)
	}

	if err := mf.write(kustomization); err != nil {
		t.Fatalf("Couldn't write kustomization file: %v\n", err)
	}

	content, err := mf.read()
	if err != nil {
		t.Fatalf("Couldn't read kustomization file: %v\n", err)
	}
	if !reflect.DeepEqual(kustomization, content) {
		t.Fatal("Read kustomization is different from written kustomization")
	}
}

func TestEmptyFile(t *testing.T) {
	fsys := fs.MakeFakeFS()
	_, err := newKustomizationFile("", fsys)
	if err == nil {
		t.Fatalf("Create kustomizationFile from empty filename should fail")
	}
}

func TestNewNotExist(t *testing.T) {
	badSuffix := "foo.bar"
	fakeFS := fs.MakeFakeFS()
	fakeFS.Mkdir(".", 0644)
	fakeFS.Create(badSuffix)
	_, err := newKustomizationFile(constants.KustomizationFileName, fakeFS)
	if err == nil {
		t.Fatalf("expect an error")
	}
	contained := "Missing kustomization file"
	if !strings.Contains(err.Error(), contained) {
		t.Fatalf("expect an error contains %q, but got %v", contained, err)
	}
	_, err = newKustomizationFile(constants.KustomizationFileName, fakeFS)
	if err == nil {
		t.Fatalf("expect an error")
	}
	if !strings.Contains(err.Error(), contained) {
		t.Fatalf("expect an error contains %q, but got %v", contained, err)
	}
	_, err = newKustomizationFile(badSuffix, fakeFS)
	if err == nil {
		t.Fatalf("expect an error")
	}
	contained = "should have .yaml suffix"
	if !strings.Contains(err.Error(), contained) {
		t.Fatalf("expect an error contains %q, but got %v", contained, err)
	}
}
<|endoftext|>