<commit_before>// Package cmd is an interface for parsing the command line
package cmd

import (
	"fmt"
	"io"
	"math/rand"
	"os"
	"strings"
	"time"

	"github.com/micro/cli"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/server"

	// brokers
	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/broker/http"

	// registries
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/registry/consul"
	"github.com/micro/go-micro/registry/mdns"

	// selectors
	"github.com/micro/go-micro/selector"
	"github.com/micro/go-micro/selector/cache"

	// transports
	"github.com/micro/go-micro/transport"
	thttp "github.com/micro/go-micro/transport/http"
)

type Cmd interface {
	// The cli app within this cmd
	App() *cli.App
	// Adds options, parses flags and initialise
	// exits on error
	Init(opts ...Option) error
	// Options set within this command
	Options() Options
}

type cmd struct {
	opts Options
	app  *cli.App
}

type Option func(o *Options)

var (
	DefaultCmd = newCmd()

	DefaultFlags = []cli.Flag{
		cli.StringFlag{
			Name:   "client",
			EnvVar: "MICRO_CLIENT",
			Usage:  "Client for go-micro; rpc",
		},
		cli.StringFlag{
			Name:   "client_request_timeout",
			EnvVar: "MICRO_CLIENT_REQUEST_TIMEOUT",
			Usage:  "Sets the client request timeout. e.g 500ms, 5s, 1m. Default: 5s",
		},
		cli.IntFlag{
			Name:   "client_retries",
			EnvVar: "MICRO_CLIENT_RETRIES",
			Usage:  "Sets the client retries. Default: 1",
		},
		cli.IntFlag{
			Name:   "client_pool_size",
			EnvVar: "MICRO_CLIENT_POOL_SIZE",
			Usage:  "Sets the client connection pool size. Default: 0",
		},
		cli.StringFlag{
			Name:   "client_pool_ttl",
			EnvVar: "MICRO_CLIENT_POOL_TTL",
			Usage:  "Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m",
		},
		cli.StringFlag{
			Name:   "server_name",
			EnvVar: "MICRO_SERVER_NAME",
			Usage:  "Name of the server. go.micro.srv.example",
		},
		cli.StringFlag{
			Name:   "server_version",
			EnvVar: "MICRO_SERVER_VERSION",
			Usage:  "Version of the server. 1.1.0",
		},
		cli.StringFlag{
			Name:   "server_id",
			EnvVar: "MICRO_SERVER_ID",
			Usage:  "Id of the server. Auto-generated if not specified",
		},
		cli.StringFlag{
			Name:   "server_address",
			EnvVar: "MICRO_SERVER_ADDRESS",
			Usage:  "Bind address for the server. 127.0.0.1:8080",
		},
		cli.StringFlag{
			Name:   "server_advertise",
			EnvVar: "MICRO_SERVER_ADVERTISE",
			Usage:  "Used instead of the server_address when registering with discovery. 127.0.0.1:8080",
		},
		cli.StringSliceFlag{
			Name:   "server_metadata",
			EnvVar: "MICRO_SERVER_METADATA",
			Value:  &cli.StringSlice{},
			Usage:  "A list of key-value pairs defining metadata. version=1.0.0",
		},
		cli.StringFlag{
			Name:   "broker",
			EnvVar: "MICRO_BROKER",
			Usage:  "Broker for pub/sub. http, nats, rabbitmq",
		},
		cli.StringFlag{
			Name:   "broker_address",
			EnvVar: "MICRO_BROKER_ADDRESS",
			Usage:  "Comma-separated list of broker addresses",
		},
		cli.StringFlag{
			Name:   "registry",
			EnvVar: "MICRO_REGISTRY",
			Usage:  "Registry for discovery. consul, mdns",
		},
		cli.StringFlag{
			Name:   "registry_address",
			EnvVar: "MICRO_REGISTRY_ADDRESS",
			Usage:  "Comma-separated list of registry addresses",
		},
		cli.StringFlag{
			Name:   "selector",
			EnvVar: "MICRO_SELECTOR",
			Usage:  "Selector used to pick nodes for querying",
		},
		cli.StringFlag{
			Name:   "server",
			EnvVar: "MICRO_SERVER",
			Usage:  "Server for go-micro; rpc",
		},
		cli.StringFlag{
			Name:   "transport",
			EnvVar: "MICRO_TRANSPORT",
			Usage:  "Transport mechanism used; http",
		},
		cli.StringFlag{
			Name:   "transport_address",
			EnvVar: "MICRO_TRANSPORT_ADDRESS",
			Usage:  "Comma-separated list of transport addresses",
		},
	}

	DefaultBrokers = map[string]func(...broker.Option) broker.Broker{
		"http": http.NewBroker,
	}

	DefaultClients = map[string]func(...client.Option) client.Client{
		"rpc": client.NewClient,
	}

	DefaultRegistries = map[string]func(...registry.Option) registry.Registry{
		"consul": consul.NewRegistry,
		"mdns":   mdns.NewRegistry,
	}

	DefaultSelectors = map[string]func(...selector.Option) selector.Selector{
		"default": selector.NewSelector,
		"cache":   cache.NewSelector,
	}

	DefaultServers = map[string]func(...server.Option) server.Server{
		"rpc": server.NewServer,
	}

	DefaultTransports = map[string]func(...transport.Option) transport.Transport{
		"http": thttp.NewTransport,
	}

	// used for default selection as the fall back
	defaultClient    = "rpc"
	defaultServer    = "rpc"
	defaultBroker    = "http"
	defaultRegistry  = "consul"
	defaultSelector  = "default"
	defaultTransport = "http"
)

func init() {
	rand.Seed(time.Now().Unix())
	help := cli.HelpPrinter
	cli.HelpPrinter = func(writer io.Writer, templ string, data interface{}) {
		help(writer, templ, data)
		os.Exit(0)
	}
}

func newCmd(opts ...Option) Cmd {
	options := Options{
		Broker:    &broker.DefaultBroker,
		Client:    &client.DefaultClient,
		Registry:  &registry.DefaultRegistry,
		Server:    &server.DefaultServer,
		Selector:  &selector.DefaultSelector,
		Transport: &transport.DefaultTransport,

		Brokers:    DefaultBrokers,
		Clients:    DefaultClients,
		Registries: DefaultRegistries,
		Selectors:  DefaultSelectors,
		Servers:    DefaultServers,
		Transports: DefaultTransports,
	}

	for _, o := range opts {
		o(&options)
	}

	if len(options.Description) == 0 {
		options.Description = "a go-micro service"
	}

	cmd := new(cmd)
	cmd.opts = options
	cmd.app = cli.NewApp()
	cmd.app.Name = cmd.opts.Name
	cmd.app.Version = cmd.opts.Version
	cmd.app.Usage = cmd.opts.Description
	cmd.app.Before = cmd.Before
	cmd.app.Flags = DefaultFlags
	cmd.app.Action = func(c *cli.Context) {}

	if len(options.Version) == 0 {
		cmd.app.HideVersion = true
	}

	return cmd
}

func (c *cmd) App() *cli.App {
	return c.app
}

func (c *cmd) Options() Options {
	return c.opts
}

func (c *cmd) Before(ctx *cli.Context) error {
	// If flags are set then use them otherwise do nothing
	var serverOpts []server.Option
	var clientOpts []client.Option

	// Set the client
	if name := ctx.String("client"); len(name) > 0 {
		if cl, ok := c.opts.Clients[name]; ok {
			*c.opts.Client = cl()
		}
	}

	// Set the server
	if name := ctx.String("server"); len(name) > 0 {
		if s, ok := c.opts.Servers[name]; ok {
			*c.opts.Server = s()
		}
	}

	// Set the broker
	if name := ctx.String("broker"); len(name) > 0 || len(ctx.String("broker_address")) > 0 {
		if len(name) == 0 {
			name = defaultBroker
		}

		if b, ok := c.opts.Brokers[name]; ok {
			n := b(broker.Addrs(strings.Split(ctx.String("broker_address"), ",")...))
			*c.opts.Broker = n
		} else {
			return fmt.Errorf("Broker %s not found", name)
		}

		serverOpts = append(serverOpts, server.Broker(*c.opts.Broker))
		clientOpts = append(clientOpts, client.Broker(*c.opts.Broker))
	}

	// Set the registry
	if name := ctx.String("registry"); len(name) > 0 || len(ctx.String("registry_address")) > 0 {
		if len(name) == 0 {
			name = defaultRegistry
		}

		if r, ok := c.opts.Registries[name]; ok {
			n := r(registry.Addrs(strings.Split(ctx.String("registry_address"), ",")...))
			*c.opts.Registry = n
		} else {
			return fmt.Errorf("Registry %s not found", name)
		}

		serverOpts = append(serverOpts, server.Registry(*c.opts.Registry))
		clientOpts = append(clientOpts, client.Registry(*c.opts.Registry))

		(*c.opts.Selector).Init(selector.Registry(*c.opts.Registry))
		clientOpts = append(clientOpts, client.Selector(*c.opts.Selector))

		(*c.opts.Broker).Init(broker.Registry(*c.opts.Registry))
	}

	// Set the selector
	if name := ctx.String("selector"); len(name) > 0 {
		if s, ok := c.opts.Selectors[name]; ok {
			n := s(selector.Registry(*c.opts.Registry))
			*c.opts.Selector = n
		} else {
			return fmt.Errorf("Selector %s not found", name)
		}

		// No server option here. Should there be?
		clientOpts = append(clientOpts, client.Selector(*c.opts.Selector))
	}

	// Set the transport
	if name := ctx.String("transport"); len(name) > 0 || len(ctx.String("transport_address")) > 0 {
		if len(name) == 0 {
			name = defaultTransport
		}

		if t, ok := c.opts.Transports[name]; ok {
			n := t(transport.Addrs(strings.Split(ctx.String("transport_address"), ",")...))
			*c.opts.Transport = n
		} else {
			return fmt.Errorf("Transport %s not found", name)
		}

		serverOpts = append(serverOpts, server.Transport(*c.opts.Transport))
		clientOpts = append(clientOpts, client.Transport(*c.opts.Transport))
	}

	// Parse the server options
	metadata := make(map[string]string)
	for _, d := range ctx.StringSlice("server_metadata") {
		var key, val string
		parts := strings.Split(d, "=")
		key = parts[0]
		if len(parts) > 1 {
			val = strings.Join(parts[1:], "=")
		}
		metadata[key] = val
	}

	if len(metadata) > 0 {
		serverOpts = append(serverOpts, server.Metadata(metadata))
	}

	if len(ctx.String("server_name")) > 0 {
		serverOpts = append(serverOpts, server.Name(ctx.String("server_name")))
	}

	if len(ctx.String("server_version")) > 0 {
		serverOpts = append(serverOpts, server.Version(ctx.String("server_version")))
	}

	if len(ctx.String("server_id")) > 0 {
		serverOpts = append(serverOpts, server.Id(ctx.String("server_id")))
	}

	if len(ctx.String("server_address")) > 0 {
		serverOpts = append(serverOpts, server.Address(ctx.String("server_address")))
	}

	if len(ctx.String("server_advertise")) > 0 {
		serverOpts = append(serverOpts, server.Advertise(ctx.String("server_advertise")))
	}

	// client opts
	if r := ctx.Int("client_retries"); r > 0 {
		clientOpts = append(clientOpts, client.Retries(r))
	}

	if t := ctx.String("client_request_timeout"); len(t) > 0 {
		d, err := time.ParseDuration(t)
		if err != nil {
			return fmt.Errorf("failed to parse client_request_timeout: %v", t)
		}
		clientOpts = append(clientOpts, client.RequestTimeout(d))
	}

	if r := ctx.Int("client_pool_size"); r > 0 {
		clientOpts = append(clientOpts, client.PoolSize(r))
	}

	if t := ctx.String("client_pool_ttl"); len(t) > 0 {
		d, err := time.ParseDuration(t)
		if err != nil {
			return fmt.Errorf("failed to parse client_pool_ttl: %v", t)
		}
		clientOpts = append(clientOpts, client.PoolTTL(d))
	}

	// We have some command line opts for the server.
	// Lets set it up
	if len(serverOpts) > 0 {
		(*c.opts.Server).Init(serverOpts...)
	}

	// Use an init option?
	if len(clientOpts) > 0 {
		(*c.opts.Client).Init(clientOpts...)
	}

	return nil
}

func (c *cmd) Init(opts ...Option) error {
	for _, o := range opts {
		o(&c.opts)
	}
	c.app.Name = c.opts.Name
	c.app.Version = c.opts.Version
	c.app.HideVersion = len(c.opts.Version) == 0
	c.app.Usage = c.opts.Description
	c.app.RunAndExitOnError()
	return nil
}

func DefaultOptions() Options {
	return DefaultCmd.Options()
}

func App() *cli.App {
	return DefaultCmd.App()
}

func Init(opts ...Option) error {
	return DefaultCmd.Init(opts...)
}

func NewCmd(opts ...Option) Cmd {
	return newCmd(opts...)
}
<commit_msg>Enable connection pooling and selector caching by default<commit_after>// Package cmd is an interface for parsing the command line
package cmd

import (
	"fmt"
	"io"
	"math/rand"
	"os"
	"strings"
	"time"

	"github.com/micro/cli"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/server"

	// brokers
	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/broker/http"

	// registries
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/registry/consul"
	"github.com/micro/go-micro/registry/mdns"

	// selectors
	"github.com/micro/go-micro/selector"
	"github.com/micro/go-micro/selector/cache"

	// transports
	"github.com/micro/go-micro/transport"
	thttp "github.com/micro/go-micro/transport/http"
)

type Cmd interface {
	// The cli app within this cmd
	App() *cli.App
	// Adds options, parses flags and initialise
	// exits on error
	Init(opts ...Option) error
	// Options set within this command
	Options() Options
}

type cmd struct {
	opts Options
	app  *cli.App
}

type Option func(o *Options)

var (
	DefaultCmd = newCmd()

	DefaultFlags = []cli.Flag{
		cli.StringFlag{
			Name:   "client",
			EnvVar: "MICRO_CLIENT",
			Usage:  "Client for go-micro; rpc",
		},
		cli.StringFlag{
			Name:   "client_request_timeout",
			EnvVar: "MICRO_CLIENT_REQUEST_TIMEOUT",
			Usage:  "Sets the client request timeout. e.g 500ms, 5s, 1m. Default: 5s",
		},
		cli.IntFlag{
			Name:   "client_retries",
			EnvVar: "MICRO_CLIENT_RETRIES",
			Usage:  "Sets the client retries. Default: 1",
		},
		cli.IntFlag{
			Name:   "client_pool_size",
			EnvVar: "MICRO_CLIENT_POOL_SIZE",
			Usage:  "Sets the client connection pool size. Default: 1",
			Value:  1,
		},
		cli.StringFlag{
			Name:   "client_pool_ttl",
			EnvVar: "MICRO_CLIENT_POOL_TTL",
			Usage:  "Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m",
		},
		cli.StringFlag{
			Name:   "server_name",
			EnvVar: "MICRO_SERVER_NAME",
			Usage:  "Name of the server. go.micro.srv.example",
		},
		cli.StringFlag{
			Name:   "server_version",
			EnvVar: "MICRO_SERVER_VERSION",
			Usage:  "Version of the server. 1.1.0",
		},
		cli.StringFlag{
			Name:   "server_id",
			EnvVar: "MICRO_SERVER_ID",
			Usage:  "Id of the server. Auto-generated if not specified",
		},
		cli.StringFlag{
			Name:   "server_address",
			EnvVar: "MICRO_SERVER_ADDRESS",
			Usage:  "Bind address for the server. 127.0.0.1:8080",
		},
		cli.StringFlag{
			Name:   "server_advertise",
			EnvVar: "MICRO_SERVER_ADVERTISE",
			Usage:  "Used instead of the server_address when registering with discovery. 127.0.0.1:8080",
		},
		cli.StringSliceFlag{
			Name:   "server_metadata",
			EnvVar: "MICRO_SERVER_METADATA",
			Value:  &cli.StringSlice{},
			Usage:  "A list of key-value pairs defining metadata. version=1.0.0",
		},
		cli.StringFlag{
			Name:   "broker",
			EnvVar: "MICRO_BROKER",
			Usage:  "Broker for pub/sub. http, nats, rabbitmq",
		},
		cli.StringFlag{
			Name:   "broker_address",
			EnvVar: "MICRO_BROKER_ADDRESS",
			Usage:  "Comma-separated list of broker addresses",
		},
		cli.StringFlag{
			Name:   "registry",
			EnvVar: "MICRO_REGISTRY",
			Usage:  "Registry for discovery. consul, mdns",
		},
		cli.StringFlag{
			Name:   "registry_address",
			EnvVar: "MICRO_REGISTRY_ADDRESS",
			Usage:  "Comma-separated list of registry addresses",
		},
		cli.StringFlag{
			Name:   "selector",
			EnvVar: "MICRO_SELECTOR",
			Usage:  "Selector used to pick nodes for querying",
			Value:  "cache",
		},
		cli.StringFlag{
			Name:   "server",
			EnvVar: "MICRO_SERVER",
			Usage:  "Server for go-micro; rpc",
		},
		cli.StringFlag{
			Name:   "transport",
			EnvVar: "MICRO_TRANSPORT",
			Usage:  "Transport mechanism used; http",
		},
		cli.StringFlag{
			Name:   "transport_address",
			EnvVar: "MICRO_TRANSPORT_ADDRESS",
			Usage:  "Comma-separated list of transport addresses",
		},
	}

	DefaultBrokers = map[string]func(...broker.Option) broker.Broker{
		"http": http.NewBroker,
	}

	DefaultClients = map[string]func(...client.Option) client.Client{
		"rpc": client.NewClient,
	}

	DefaultRegistries = map[string]func(...registry.Option) registry.Registry{
		"consul": consul.NewRegistry,
		"mdns":   mdns.NewRegistry,
	}

	DefaultSelectors = map[string]func(...selector.Option) selector.Selector{
		"default": selector.NewSelector,
		"cache":   cache.NewSelector,
	}

	DefaultServers = map[string]func(...server.Option) server.Server{
		"rpc": server.NewServer,
	}

	DefaultTransports = map[string]func(...transport.Option) transport.Transport{
		"http": thttp.NewTransport,
	}

	// used for default selection as the fall back
	defaultClient    = "rpc"
	defaultServer    = "rpc"
	defaultBroker    = "http"
	defaultRegistry  = "consul"
	defaultSelector  = "cache"
	defaultTransport = "http"
)

func init() {
	rand.Seed(time.Now().Unix())
	help := cli.HelpPrinter
	cli.HelpPrinter = func(writer io.Writer, templ string, data interface{}) {
		help(writer, templ, data)
		os.Exit(0)
	}
}

func newCmd(opts ...Option) Cmd {
	options := Options{
		Broker:    &broker.DefaultBroker,
		Client:    &client.DefaultClient,
		Registry:  &registry.DefaultRegistry,
		Server:    &server.DefaultServer,
		Selector:  &selector.DefaultSelector,
		Transport: &transport.DefaultTransport,

		Brokers:    DefaultBrokers,
		Clients:    DefaultClients,
		Registries: DefaultRegistries,
		Selectors:  DefaultSelectors,
		Servers:    DefaultServers,
		Transports: DefaultTransports,
	}

	for _, o := range opts {
		o(&options)
	}

	if len(options.Description) == 0 {
		options.Description = "a go-micro service"
	}

	cmd := new(cmd)
	cmd.opts = options
	cmd.app = cli.NewApp()
	cmd.app.Name = cmd.opts.Name
	cmd.app.Version = cmd.opts.Version
	cmd.app.Usage = cmd.opts.Description
	cmd.app.Before = cmd.Before
	cmd.app.Flags = DefaultFlags
	cmd.app.Action = func(c *cli.Context) {}

	if len(options.Version) == 0 {
		cmd.app.HideVersion = true
	}

	return cmd
}

func (c *cmd) App() *cli.App {
	return c.app
}

func (c *cmd) Options() Options {
	return c.opts
}

func (c *cmd) Before(ctx *cli.Context) error {
	// If flags are set then use them otherwise do nothing
	var serverOpts []server.Option
	var clientOpts []client.Option

	// Set the client
	if name := ctx.String("client"); len(name) > 0 {
		if cl, ok := c.opts.Clients[name]; ok {
			*c.opts.Client = cl()
		}
	}

	// Set the server
	if name := ctx.String("server"); len(name) > 0 {
		if s, ok := c.opts.Servers[name]; ok {
			*c.opts.Server = s()
		}
	}

	// Set the broker
	if name := ctx.String("broker"); len(name) > 0 || len(ctx.String("broker_address")) > 0 {
		if len(name) == 0 {
			name = defaultBroker
		}

		if b, ok := c.opts.Brokers[name]; ok {
			n := b(broker.Addrs(strings.Split(ctx.String("broker_address"), ",")...))
			*c.opts.Broker = n
		} else {
			return fmt.Errorf("Broker %s not found", name)
		}

		serverOpts = append(serverOpts, server.Broker(*c.opts.Broker))
		clientOpts = append(clientOpts, client.Broker(*c.opts.Broker))
	}

	// Set the registry
	if name := ctx.String("registry"); len(name) > 0 || len(ctx.String("registry_address")) > 0 {
		if len(name) == 0 {
			name = defaultRegistry
		}

		if r, ok := c.opts.Registries[name]; ok {
			n := r(registry.Addrs(strings.Split(ctx.String("registry_address"), ",")...))
			*c.opts.Registry = n
		} else {
			return fmt.Errorf("Registry %s not found", name)
		}

		serverOpts = append(serverOpts, server.Registry(*c.opts.Registry))
		clientOpts = append(clientOpts, client.Registry(*c.opts.Registry))

		(*c.opts.Selector).Init(selector.Registry(*c.opts.Registry))
		clientOpts = append(clientOpts, client.Selector(*c.opts.Selector))

		(*c.opts.Broker).Init(broker.Registry(*c.opts.Registry))
	}

	// Set the selector
	if name := ctx.String("selector"); len(name) > 0 {
		if s, ok := c.opts.Selectors[name]; ok {
			n := s(selector.Registry(*c.opts.Registry))
			*c.opts.Selector = n
		} else {
			return fmt.Errorf("Selector %s not found", name)
		}

		// No server option here. Should there be?
		clientOpts = append(clientOpts, client.Selector(*c.opts.Selector))
	}

	// Set the transport
	if name := ctx.String("transport"); len(name) > 0 || len(ctx.String("transport_address")) > 0 {
		if len(name) == 0 {
			name = defaultTransport
		}

		if t, ok := c.opts.Transports[name]; ok {
			n := t(transport.Addrs(strings.Split(ctx.String("transport_address"), ",")...))
			*c.opts.Transport = n
		} else {
			return fmt.Errorf("Transport %s not found", name)
		}

		serverOpts = append(serverOpts, server.Transport(*c.opts.Transport))
		clientOpts = append(clientOpts, client.Transport(*c.opts.Transport))
	}

	// Parse the server options
	metadata := make(map[string]string)
	for _, d := range ctx.StringSlice("server_metadata") {
		var key, val string
		parts := strings.Split(d, "=")
		key = parts[0]
		if len(parts) > 1 {
			val = strings.Join(parts[1:], "=")
		}
		metadata[key] = val
	}

	if len(metadata) > 0 {
		serverOpts = append(serverOpts, server.Metadata(metadata))
	}

	if len(ctx.String("server_name")) > 0 {
		serverOpts = append(serverOpts, server.Name(ctx.String("server_name")))
	}

	if len(ctx.String("server_version")) > 0 {
		serverOpts = append(serverOpts, server.Version(ctx.String("server_version")))
	}

	if len(ctx.String("server_id")) > 0 {
		serverOpts = append(serverOpts, server.Id(ctx.String("server_id")))
	}

	if len(ctx.String("server_address")) > 0 {
		serverOpts = append(serverOpts, server.Address(ctx.String("server_address")))
	}

	if len(ctx.String("server_advertise")) > 0 {
		serverOpts = append(serverOpts, server.Advertise(ctx.String("server_advertise")))
	}

	// client opts
	if r := ctx.Int("client_retries"); r > 0 {
		clientOpts = append(clientOpts, client.Retries(r))
	}

	if t := ctx.String("client_request_timeout"); len(t) > 0 {
		d, err := time.ParseDuration(t)
		if err != nil {
			return fmt.Errorf("failed to parse client_request_timeout: %v", t)
		}
		clientOpts = append(clientOpts, client.RequestTimeout(d))
	}

	if r := ctx.Int("client_pool_size"); r > 0 {
		clientOpts = append(clientOpts, client.PoolSize(r))
	}

	if t := ctx.String("client_pool_ttl"); len(t) > 0 {
		d, err := time.ParseDuration(t)
		if err != nil {
			return fmt.Errorf("failed to parse client_pool_ttl: %v", t)
		}
		clientOpts = append(clientOpts, client.PoolTTL(d))
	}

	// We have some command line opts for the server.
	// Lets set it up
	if len(serverOpts) > 0 {
		(*c.opts.Server).Init(serverOpts...)
	}

	// Use an init option?
	if len(clientOpts) > 0 {
		(*c.opts.Client).Init(clientOpts...)
	}

	return nil
}

func (c *cmd) Init(opts ...Option) error {
	for _, o := range opts {
		o(&c.opts)
	}
	c.app.Name = c.opts.Name
	c.app.Version = c.opts.Version
	c.app.HideVersion = len(c.opts.Version) == 0
	c.app.Usage = c.opts.Description
	c.app.RunAndExitOnError()
	return nil
}

func DefaultOptions() Options {
	return DefaultCmd.Options()
}

func App() *cli.App {
	return DefaultCmd.App()
}

func Init(opts ...Option) error {
	return DefaultCmd.Init(opts...)
}

func NewCmd(opts ...Option) Cmd {
	return newCmd(opts...)
}
<|endoftext|>


<commit_before>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package rtr_brk_http

import (
	"encoding/json"
	"fmt"
	"github.com/thethingsnetwork/core"
	"github.com/thethingsnetwork/core/lorawan/semtech"
	"github.com/thethingsnetwork/core/testing/mock_components"
	"github.com/thethingsnetwork/core/utils/log"
	"github.com/thethingsnetwork/core/utils/pointer"
	. "github.com/thethingsnetwork/core/utils/testing"
	"io"
	"net/http"
	"reflect"
	"testing"
	"time"
)

// ----- The adapter can be created and listen straigthforwardly
func TestListenOptionsTest(t *testing.T) {
	adapter, router := genAdapterAndRouter(t)

	Desc(t, "Listen to adapter")
	if err := adapter.Listen(router, nil); err != nil {
		Ko(t, "No error was expected but got: %+v", err)
		return
	}
	Ok(t)
}

// ----- The adapter should forward a payload to a set of brokers
func TestForwardPayload(t *testing.T) {
	tests := []forwardPayloadTest{
		{genValidPayload(), genBrokers([]int{200}), nil},
		{genValidPayload(), genBrokers([]int{200, 200}), nil},
		{genInvalidPayload(), nil, core.ErrInvalidPayload},
	}

	for _, test := range tests {
		test.run(t)
	}
}

type forwardPayloadTest struct {
	payload semtech.Payload
	brokers map[string]int
	want    error
}

func (test forwardPayloadTest) run(t *testing.T) {
	//Describe
	Desc(t, "Forward %v to %v", test.payload, test.brokers)

	// Build
	adapter, router := genAdapterAndRouter(t)
	adapter.Listen(router, toBrokerAddrs(test.brokers))
	cmsg := listenHTTP(t, test.brokers)

	// Operate
	<-time.After(time.Millisecond * 250)
	got := adapter.Forward(router, test.payload, toBrokerAddrs(test.brokers)...)

	// Check
	<-time.After(time.Millisecond * 250)
	checkErrors(t, test.want, got)
	checkReception(t, len(test.brokers), test.payload, cmsg)
}

// ----- The adapter should broadcast a payload to a set of broker
func TestBroadcastPayload(t *testing.T) {
	tests := []broadcastPayloadTest{
		{genValidPayload(), genBrokers([]int{200, 200}), nil},
		{genValidPayload(), genBrokers([]int{200, 404}), nil},
		{genValidPayloadInvalidDevAddr(), nil, core.ErrInvalidPayload},
		{genInvalidPayload(), nil, core.ErrInvalidPayload},
	}

	for _, test := range tests {
		test.run(t)
	}
}

type broadcastPayloadTest struct {
	payload semtech.Payload
	brokers map[string]int
	want    error
}

func (test broadcastPayloadTest) run(t *testing.T) {
	// Describe
	Desc(t, "Forward %v to %v", test.payload, test.brokers)

	// Build
	adapter, router := genAdapterAndRouter(t)
	adapter.Listen(router, toBrokerAddrs(test.brokers))
	cmsg := listenHTTP(t, test.brokers)

	// Operate
	<-time.After(time.Millisecond * 250)
	got := adapter.Broadcast(router, test.payload)

	// Check
	<-time.After(time.Millisecond * 250)
	checkErrors(t, test.want, got)
	checkReception(t, len(test.brokers), test.payload, cmsg)
	checkRegistration(t, router, test.payload, test.brokers)
}

// ----- Build Utilities

// Create an instance of an Adapter with a predefined logger + a mock router
func genAdapterAndRouter(t *testing.T) (Adapter, core.Router) {
	return Adapter{
		Logger: log.TestLogger{
			Tag: "Adapter",
			T:   t,
		},
	}, mock_components.NewRouter()
}

// gen a very basic payload holding an RXPK packet and identifying a valid device address
func genValidPayload() semtech.Payload {
	return semtech.Payload{
		RXPK: []semtech.RXPK{{
			Data: pointer.String("/xRC/zcBAAABqqq7uw=="),
			Freq: pointer.Float64(866.349812),
			Rssi: pointer.Int(-35),
		},
		},
	}
}

// gen a very basic payload holding an RXPK packet but with scrap data
func genValidPayloadInvalidDevAddr() semtech.Payload {
	return semtech.Payload{
		RXPK: []semtech.RXPK{{
			Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"),
			Freq: pointer.Float64(866.349812),
			Rssi: pointer.Int(-35),
		},
		},
	}
}

// gen a payload with no RXPK nor STAT packet
func genInvalidPayload() semtech.Payload {
	return semtech.Payload{}
}

// Keep track of open TCP ports
var port int = 3000

// gen a list of brokers given a list of http response status in the form address -> status
func genBrokers(status []int) map[string]int {
	brokers := make(map[string]int)
	for _, s := range status {
		brokers[fmt.Sprintf("0.0.0.0:%d", port)] = s
		port += 1
	}
	return brokers
}

// Transform the broker map address -> status to a list a BrokerAddress
func toBrokerAddrs(addrs map[string]int) []core.BrokerAddress {
	brokers := make([]core.BrokerAddress, 0)
	for addr := range addrs {
		brokers = append(brokers, core.BrokerAddress(addr))
	}
	return brokers
}

// Create an http handler that will listen to json request on "/" and forward payload into a
// dedicated channel. A custom response status can be given in param.
func createServeMux(t *testing.T, status int, cmsg chan semtech.Payload) *http.ServeMux {
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
		defer req.Body.Close()
		res.Header().Set("Content-Type", "application/json")

		// Check the header type
		if req.Header.Get("Content-Type") != "application/json" {
			t.Log("Unexpected content-type ignore")
			res.WriteHeader(http.StatusBadRequest)
			res.Write(nil)
			return
		}

		// Check the body as well
		var payload semtech.Payload
		raw := make([]byte, 512)
		n, err := req.Body.Read(raw)
		if err != nil && err != io.EOF {
			t.Logf("Error reading request body: %v", err)
			res.WriteHeader(http.StatusBadRequest)
			res.Write(nil)
			return
		}

		if err := json.Unmarshal(raw[:n], &payload); err != nil {
			t.Logf("Error while unmarshaling: %v", err)
			res.WriteHeader(http.StatusBadRequest)
			res.Write(nil)
			return
		}

		// Send a fake response
		res.WriteHeader(status)
		res.Write(nil)
		cmsg <- payload
	})
	return serveMux
}

// ----- Operate Utilities

// Start one http server per address which will forward request to the returned channel of payloads.
func listenHTTP(t *testing.T, addrs map[string]int) chan semtech.Payload {
	cmsg := make(chan semtech.Payload)

	for addr, status := range addrs {
		go func(addr string, status int) {
			s := &http.Server{Addr: addr, Handler: createServeMux(t, status, cmsg)}
			if err := s.ListenAndServe(); err != nil {
				panic(err)
			}
		}(addr, status)
	}

	return cmsg
}

// ----- Check Utilities
func checkErrors(t *testing.T, want error, got error) bool {
	// Check for the error
	if want != nil {
		if want != got {
			Ko(t, "Expected error %v but got %v", want, got)
			return false
		}
		Ok(t)
		return true
	}
	return true
}

func checkReception(t *testing.T, nbExpected int, want semtech.Payload, cmsg chan semtech.Payload) bool {
	// Check if payload should have been sent
	if nbExpected <= 0 {
		Ok(t)
		return true
	}

	// Gather payloads and check one of them
	var payloads []semtech.Payload
	select {
	case payload := <-cmsg:
		payloads = append(payloads, payload)
		if len(payloads) == nbExpected {
			break
		}
	case <-time.After(time.Millisecond * 500):
		Ko(t, "%d payload(s) send to server(s) whereas %d was/were expected", len(payloads), nbExpected)
		return false
	}

	if !reflect.DeepEqual(want, payloads[0]) {
		Ko(t, "Expected %+v to be sent but server received: %+v", want, payloads[0])
		return false
	}

	Ok(t)
	return true
}

func checkRegistration(t *testing.T, router core.Router, payload semtech.Payload, brokers map[string]int) bool {
	if len(brokers) == 0 {
		Ok(t)
		return true
	}

	devAddr, err := payload.UniformDevAddr()
	if err != nil {
		panic(err)
	}

	mockRouter := router.(*mock_components.Router) // Need to access to registered devices of mock router

outer:
	for addr, status := range brokers {
		if status != 200 { // Not a HTTP 200 OK, broker probably does not handle that device
			continue
		}

		addrs, ok := mockRouter.Devices[*devAddr] // Get all registered brokers for that device
		if !ok {
			Ko(t, "Broker %s wasn't registered for payload %v", addr, payload)
			return false
		}

		for _, broker := range addrs {
			if string(broker) == addr {
				continue outer // We are registered, everything's fine for that broker
			}
		}

		Ko(t, "Broker %s wasn't registered for payload %v", addr, payload)
		return false
	}

	Ok(t)
	return true
}
<commit_msg>[router] Fix test issue with blocking channel<commit_after>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package rtr_brk_http

import (
	"encoding/json"
	"fmt"
	"github.com/thethingsnetwork/core"
	"github.com/thethingsnetwork/core/lorawan/semtech"
	"github.com/thethingsnetwork/core/testing/mock_components"
	"github.com/thethingsnetwork/core/utils/log"
	"github.com/thethingsnetwork/core/utils/pointer"
	. "github.com/thethingsnetwork/core/utils/testing"
	"io"
	"net/http"
	"reflect"
	"testing"
	"time"
)

// ----- The adapter can be created and listen straigthforwardly
func TestListenOptionsTest(t *testing.T) {
	adapter, router := genAdapterAndRouter(t)

	Desc(t, "Listen to adapter")
	if err := adapter.Listen(router, nil); err != nil {
		Ko(t, "No error was expected but got: %+v", err)
		return
	}
	Ok(t)
}

// ----- The adapter should forward a payload to a set of brokers
func TestForwardPayload(t *testing.T) {
	tests := []forwardPayloadTest{
		{genValidPayload(), genBrokers([]int{200}), nil},
		{genValidPayload(), genBrokers([]int{200, 200}), nil},
		{genInvalidPayload(), nil, core.ErrInvalidPayload},
	}

	for _, test := range tests {
		test.run(t)
	}
}

type forwardPayloadTest struct {
	payload semtech.Payload
	brokers map[string]int
	want    error
}

func (test forwardPayloadTest) run(t *testing.T) {
	//Describe
	Desc(t, "Forward %v to %v", test.payload, test.brokers)

	// Build
	adapter, router := genAdapterAndRouter(t)
	adapter.Listen(router, toBrokerAddrs(test.brokers))
	cmsg := listenHTTP(t, test.brokers)

	// Operate
	<-time.After(time.Millisecond * 250)
	got := adapter.Forward(router, test.payload, toBrokerAddrs(test.brokers)...)

	// Check
	<-time.After(time.Millisecond * 100)
	checkErrors(t, test.want, got)
	checkReception(t, len(test.brokers), test.payload, cmsg)
}

// ----- The adapter should broadcast a payload to a set of broker
func TestBroadcastPayload(t *testing.T) {
	tests := []broadcastPayloadTest{
		{genValidPayload(), genBrokers([]int{200, 200}), nil},
		{genValidPayload(), genBrokers([]int{200, 404}), nil},
		{genValidPayloadInvalidDevAddr(), nil, core.ErrInvalidPayload},
		{genInvalidPayload(), nil, core.ErrInvalidPayload},
	}

	for _, test := range tests {
		test.run(t)
	}
}

type broadcastPayloadTest struct {
	payload semtech.Payload
	brokers map[string]int
	want    error
}

func (test broadcastPayloadTest) run(t *testing.T) {
	// Describe
	Desc(t, "Broadcast %v to %v", test.payload, test.brokers)

	// Build
	adapter, router := genAdapterAndRouter(t)
	adapter.Listen(router, toBrokerAddrs(test.brokers))
	cmsg := listenHTTP(t, test.brokers)

	// Operate
	<-time.After(time.Millisecond * 250)
	got := adapter.Broadcast(router, test.payload)

	// Check
	<-time.After(time.Millisecond * 100)
	checkErrors(t, test.want, got)
	checkReception(t, len(test.brokers), test.payload, cmsg)
	checkRegistration(t, router, test.payload, test.brokers)
}

// ----- Build Utilities

// Create an instance of an Adapter with a predefined logger + a mock router
func genAdapterAndRouter(t *testing.T) (Adapter, core.Router) {
	return Adapter{
		Logger: log.TestLogger{
			Tag: "Adapter",
			T:   t,
		},
	}, mock_components.NewRouter()
}

// gen a very basic payload holding an RXPK packet and identifying a valid device address
func genValidPayload() semtech.Payload {
	return semtech.Payload{
		RXPK: []semtech.RXPK{{
			Data: pointer.String("/xRC/zcBAAABqqq7uw=="),
			Freq: pointer.Float64(866.349812),
			Rssi: pointer.Int(-35),
		},
		},
	}
}

// gen a very basic payload holding an RXPK packet but with scrap data
func genValidPayloadInvalidDevAddr() semtech.Payload {
	return semtech.Payload{
		RXPK: []semtech.RXPK{{
			Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"),
			Freq: pointer.Float64(866.349812),
			Rssi: pointer.Int(-35),
		},
		},
	}
}

// gen a payload with no RXPK nor STAT packet
func genInvalidPayload() semtech.Payload {
	return semtech.Payload{}
}

// Keep track of open TCP ports
var port int = 3000

// gen a list of brokers given a list of http response status in the form address -> status
func genBrokers(status []int) map[string]int {
	brokers := make(map[string]int)
	for _, s := range status {
		brokers[fmt.Sprintf("0.0.0.0:%d", port)] = s
		port += 1
	}
	return brokers
}

// Transform the broker map address -> status to a list a BrokerAddress
func toBrokerAddrs(addrs map[string]int) []core.BrokerAddress {
	brokers := make([]core.BrokerAddress, 0)
	for addr := range addrs {
		brokers = append(brokers, core.BrokerAddress(addr))
	}
	return brokers
}

// Create an http handler that will listen to json request on "/" and forward payload into a
// dedicated channel. A custom response status can be given in param.
func createServeMux(t *testing.T, status int, cmsg chan semtech.Payload) *http.ServeMux {
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
		defer req.Body.Close()
		res.Header().Set("Content-Type", "application/json")

		// Check the header type
		if req.Header.Get("Content-Type") != "application/json" {
			t.Log("Unexpected content-type ignore")
			res.WriteHeader(http.StatusBadRequest)
			res.Write(nil)
			return
		}

		// Check the body as well
		var payload semtech.Payload
		raw := make([]byte, 512)
		n, err := req.Body.Read(raw)
		if err != nil && err != io.EOF {
			t.Logf("Error reading request body: %v", err)
			res.WriteHeader(http.StatusBadRequest)
			res.Write(nil)
			return
		}

		if err := json.Unmarshal(raw[:n], &payload); err != nil {
			t.Logf("Error while unmarshaling: %v", err)
			res.WriteHeader(http.StatusBadRequest)
			res.Write(nil)
			return
		}

		// Send a fake response
		res.WriteHeader(status)
		res.Write(nil)
		cmsg <- payload
	})
	return serveMux
}

// ----- Operate Utilities

// Start one http server per address which will forward request to the returned channel of payloads.
func listenHTTP(t *testing.T, addrs map[string]int) chan semtech.Payload {
	cmsg := make(chan semtech.Payload, len(addrs))

	for addr, status := range addrs {
		go func(addr string, status int) {
			s := &http.Server{Addr: addr, Handler: createServeMux(t, status, cmsg)}
			if err := s.ListenAndServe(); err != nil {
				panic(err)
			}
		}(addr, status)
	}

	return cmsg
}

// ----- Check Utilities
func checkErrors(t *testing.T, want error, got error) bool {
	// Check for the error
	if want != nil {
		if want != got {
			Ko(t, "Expected error %v but got %v", want, got)
			return false
		}
		Ok(t)
		return true
	}
	return true
}

func checkReception(t *testing.T, nbExpected int, want semtech.Payload, cmsg chan semtech.Payload) bool {
	// Check if payload should have been sent
	if nbExpected <= 0 {
		Ok(t)
		return true
	}

	// Gather payloads and check one of them
	var payloads []semtech.Payload
	select {
	case payload := <-cmsg:
		payloads = append(payloads, payload)
		if len(payloads) == nbExpected {
			break
		}
	case <-time.After(time.Millisecond * 500):
		Ko(t, "%d payload(s) send to server(s) whereas %d was/were expected", len(payloads), nbExpected)
		return false
	}

	if !reflect.DeepEqual(want, payloads[0]) {
		Ko(t, "Expected %+v to be sent but server received: %+v", want, payloads[0])
		return false
	}

	Ok(t)
	return true
}

func checkRegistration(t *testing.T, router core.Router, payload semtech.Payload, brokers map[string]int) bool {
	if len(brokers) == 0 {
		Ok(t)
		return true
	}

	devAddr, err := payload.UniformDevAddr()
	if err != nil {
		panic(err)
	}

	mockRouter := router.(*mock_components.Router) // Need to access to registered devices of mock router

outer:
	for addr, status := range brokers {
		if status != 200 { // Not a HTTP 200 OK, broker probably does not handle that device
			continue
		}

		addrs, ok := mockRouter.Devices[*devAddr] // Get all registered brokers for that device
		if !ok {
			Ko(t, "Broker %s wasn't registered for payload %v", addr, payload)
			return false
		}

		for _, broker := range addrs {
			if string(broker) == addr {
				continue outer // We are registered, everything's fine for that broker
			}
		}

		Ko(t, "Broker %s wasn't registered for payload %v", addr, payload)
		return false
	}

	Ok(t)
	return true
}
<|endoftext|>


<commit_before>package premailer

import (
	"fmt"
	"log"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/PuerkitoBio/goquery"
	"github.com/vanng822/css"
	"golang.org/x/net/html"
)

// Premailer is the inteface of Premailer
type Premailer interface {
	// Transform process and inlining css
	// It start to collect the rules in the document style tags
	// Calculate specificity and sort the rules based on that
	// It then collects the affected elements
	// And applies the rules on those
	// The leftover rules will put back into a style element
	Transform() (string, error)
}

var unmergableSelector = regexp.MustCompile("(?i)\\:{1,2}(visited|active|hover|focus|link|root|in-range|invalid|valid|after|before|selection|target|first\\-(line|letter))|^\\@")
var notSupportedSelector = regexp.MustCompile("(?i)\\:(checked|disabled|enabled|lang)")

type premailer struct {
	doc       *goquery.Document
	elIdAttr  string
	elements  map[string]*elementRules
	rules     []*styleRule
	leftover  []*css.CSSRule
	allRules  [][]*css.CSSRule
	elementId int
	processed bool
	options   *Options
}

// NewPremailer return a new instance of Premailer
// It take a Document as argument and it shouldn't be nil
func NewPremailer(doc *goquery.Document, options *Options) Premailer {
	pr := premailer{}
	pr.doc = doc
	pr.rules = make([]*styleRule, 0)
	pr.allRules = make([][]*css.CSSRule, 0)
	pr.leftover = make([]*css.CSSRule, 0)
	pr.elements = make(map[string]*elementRules)
	pr.elIdAttr = "pr-el-id"
	if options == nil {
		options = NewOptions()
	}
	pr.options = options
	return &pr
}

func (pr *premailer) sortRules() {
	ruleIndex := 0
	for ruleSetIndex, rules := range pr.allRules {
		if rules == nil {
			continue
		}
		for _, rule := range rules {
			if rule.Type != css.STYLE_RULE {
				pr.leftover = append(pr.leftover, rule)
				continue
			}
			normalStyles := make([]*css.CSSStyleDeclaration, 0)
			importantStyles := make([]*css.CSSStyleDeclaration, 0)

			for _, s := range rule.Style.Styles {
				if s.Important == 1 {
					importantStyles = append(importantStyles, s)
				} else {
					normalStyles = append(normalStyles, s)
				}
			}

			selectors := strings.Split(rule.Style.SelectorText, ",")
			for _, selector := range selectors {
				if unmergableSelector.MatchString(selector) || notSupportedSelector.MatchString(selector) {
					// cause longer css
					pr.leftover = append(pr.leftover, copyRule(selector, rule))
					continue
				}
				if strings.Contains(selector, "*") {
					// keep this?
					pr.leftover = append(pr.leftover, copyRule(selector, rule))
					continue
				}
				if len(normalStyles) > 0 {
					pr.rules = append(pr.rules, &styleRule{makeSpecificity(0, ruleSetIndex, ruleIndex, selector), selector, normalStyles})
					ruleIndex += 1
				}
				if len(importantStyles) > 0 {
					pr.rules = append(pr.rules, &styleRule{makeSpecificity(1, ruleSetIndex, ruleIndex, selector), selector, importantStyles})
					ruleIndex += 1
				}
			}
		}
	}
	sort.Sort(bySpecificity(pr.rules))
}

func (pr *premailer) collectRules() {
	var wg sync.WaitGroup
	pr.doc.Find("style:not([data-premailer='ignore'])").Each(func(_ int, s *goquery.Selection) {
		if media, exist := s.Attr("media"); exist && media != "all" {
			return
		}
		wg.Add(1)
		pr.allRules = append(pr.allRules, nil)
		go func(ruleSetIndex int) {
			defer func() {
				wg.Done()
				if r := recover(); r != nil {
					pr.allRules[ruleSetIndex] = nil
					log.Println("Got error when passing css")
					log.Println(r)
				}
			}()
			ss := css.Parse(s.Text())
			pr.allRules[ruleSetIndex] = ss.GetCSSRuleList()
			s.ReplaceWithHtml("")
		}(len(pr.allRules) - 1)
	})
	wg.Wait()

}

func (pr *premailer) collectElements() {
	for _, rule := range pr.rules {
		pr.doc.Find(rule.selector).Each(func(_ int, s *goquery.Selection) {
			if id, exist := s.Attr(pr.elIdAttr); exist {
				pr.elements[id].rules = append(pr.elements[id].rules, rule)
			} else {
				id := strconv.Itoa(pr.elementId)
				s.SetAttr(pr.elIdAttr, id)
				rules := make([]*styleRule, 0)
				rules = append(rules, rule)
				pr.elements[id] = &elementRules{element: s, rules: rules, cssToAttributes: pr.options.CssToAttributes}
				pr.elementId += 1
			}
		})

	}
}

func (pr *premailer) applyInline() {
	for _, element := range pr.elements {
		element.inline()
		element.element.RemoveAttr(pr.elIdAttr)
		if pr.options.RemoveClasses {
			element.element.RemoveAttr("class")
		}
	}
}

func (pr *premailer) addLeftover() {
	if len(pr.leftover) > 0 {
		headNode := pr.doc.Find("head")

		styleNode := &html.Node{}
		styleNode.Type = html.ElementNode
		styleNode.Data = "style"
		styleNode.Attr = []html.Attribute{{Key: "type", Val: "text/css"}}
		cssNode := &html.Node{}
		cssData := make([]string, 0, len(pr.leftover))
		for _, rule := range pr.leftover {
			if rule.Type == css.MEDIA_RULE {
				mcssData := make([]string, 0, len(rule.Rules))
				for _, mrule := range rule.Rules {
					mcssData = append(mcssData, makeRuleImportant(mrule))
				}
				cssData = append(cssData, fmt.Sprintf("%s %s{\n%s\n}\n",
					rule.Type.Text(),
					rule.Style.SelectorText,
					strings.Join(mcssData, "\n")))
			} else {
				cssData = append(cssData, makeRuleImportant(rule))
			}
		}
		cssNode.Data = strings.Join(cssData, "")
		cssNode.Type = html.TextNode
		styleNode.AppendChild(cssNode)
		headNode.AppendNodes(styleNode)
	}
}

// Transform process and inlining css
// It start to collect the rules in the document style tags
// Calculate specificity and sort the rules based on that
// It then collects the affected elements
// And applies the rules on those
// The leftover rules will put back into a style element
func (pr *premailer) Transform() (string, error) {
	if !pr.processed {
		pr.collectRules()
		pr.sortRules()
		pr.collectElements()
		pr.applyInline()
		pr.addLeftover()
		pr.processed = true
	}
	return pr.doc.Html()
}
<commit_msg>css.Parse does no longer panic<commit_after>package premailer

import (
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/PuerkitoBio/goquery"
	"github.com/vanng822/css"
	"golang.org/x/net/html"
)

// Premailer is the inteface of Premailer
type Premailer interface {
	// Transform process and inlining css
	// It start to collect the rules in the document style tags
	// Calculate specificity and sort the rules based on that
	// It then collects the affected elements
	// And applies the rules on those
	// The leftover rules will put back into a style element
	Transform() (string, error)
}

var unmergableSelector = regexp.MustCompile("(?i)\\:{1,2}(visited|active|hover|focus|link|root|in-range|invalid|valid|after|before|selection|target|first\\-(line|letter))|^\\@")
var notSupportedSelector = regexp.MustCompile("(?i)\\:(checked|disabled|enabled|lang)")

type premailer struct {
	doc       *goquery.Document
	elIdAttr  string
	elements  map[string]*elementRules
	rules     []*styleRule
	leftover  []*css.CSSRule
	allRules  [][]*css.CSSRule
	elementId int
	processed bool
	options   *Options
}

// NewPremailer return a new instance of Premailer
// It take a Document as argument and it shouldn't be nil
func NewPremailer(doc *goquery.Document, options *Options) Premailer {
	pr := premailer{}
	pr.doc = doc
	pr.rules = make([]*styleRule, 0)
	pr.allRules = make([][]*css.CSSRule, 0)
	pr.leftover = make([]*css.CSSRule, 0)
	pr.elements = make(map[string]*elementRules)
	pr.elIdAttr = "pr-el-id"
	if options == nil {
		options = NewOptions()
	}
	pr.options = options
	return &pr
}

func (pr *premailer) sortRules() {
	ruleIndex := 0
	for ruleSetIndex, rules := range pr.allRules {
		if rules == nil {
			continue
		}
		for _, rule := range rules {
			if rule.Type != css.STYLE_RULE {
				pr.leftover = append(pr.leftover, rule)
				continue
			}
			normalStyles := make([]*css.CSSStyleDeclaration, 0)
			importantStyles := make([]*css.CSSStyleDeclaration, 0)

			for _, s := range rule.Style.Styles {
				if s.Important == 1 {
					importantStyles = append(importantStyles, s)
				} else {
					normalStyles = append(normalStyles, s)
				}
			}

			selectors := strings.Split(rule.Style.SelectorText, ",")
			for _, selector := range selectors {
				if unmergableSelector.MatchString(selector) || notSupportedSelector.MatchString(selector) {
					// cause longer css
					pr.leftover = append(pr.leftover, copyRule(selector, rule))
					continue
				}
				if strings.Contains(selector, "*") {
					// keep this?
					pr.leftover = append(pr.leftover, copyRule(selector, rule))
					continue
				}
				if len(normalStyles) > 0 {
					pr.rules = append(pr.rules, &styleRule{makeSpecificity(0, ruleSetIndex, ruleIndex, selector), selector, normalStyles})
					ruleIndex += 1
				}
				if len(importantStyles) > 0 {
					pr.rules = append(pr.rules, &styleRule{makeSpecificity(1, ruleSetIndex, ruleIndex, selector), selector, importantStyles})
					ruleIndex += 1
				}
			}
		}
	}
	sort.Sort(bySpecificity(pr.rules))
}

func (pr *premailer) collectRules() {
	var wg sync.WaitGroup
	pr.doc.Find("style:not([data-premailer='ignore'])").Each(func(_ int, s *goquery.Selection) {
		if media, exist := s.Attr("media"); exist && media != "all" {
			return
		}
		wg.Add(1)
		pr.allRules = append(pr.allRules, nil)
		go func(ruleSetIndex int) {
			defer wg.Done()
			ss := css.Parse(s.Text())
			pr.allRules[ruleSetIndex] = ss.GetCSSRuleList()
			s.ReplaceWithHtml("")
		}(len(pr.allRules) - 1)
	})
	wg.Wait()

}

func (pr *premailer) collectElements() {
	for _, rule := range pr.rules {
		pr.doc.Find(rule.selector).Each(func(_ int, s *goquery.Selection) {
			if id, exist := s.Attr(pr.elIdAttr); exist {
				pr.elements[id].rules = append(pr.elements[id].rules, rule)
			} else {
				id := strconv.Itoa(pr.elementId)
				s.SetAttr(pr.elIdAttr, id)
				rules := make([]*styleRule, 0)
				rules = append(rules, rule)
				pr.elements[id] = &elementRules{element: s, rules: rules, cssToAttributes: pr.options.CssToAttributes}
				pr.elementId += 1
			}
		})

	}
}

func (pr *premailer) applyInline() {
	for _, element := range pr.elements {
		element.inline()
		element.element.RemoveAttr(pr.elIdAttr)
		if pr.options.RemoveClasses {
			element.element.RemoveAttr("class")
		}
	}
}

func (pr *premailer) addLeftover() {
	if len(pr.leftover) > 0 {
		headNode := pr.doc.Find("head")

		styleNode := &html.Node{}
		styleNode.Type = html.ElementNode
		styleNode.Data = "style"
		styleNode.Attr = []html.Attribute{{Key: "type", Val: "text/css"}}
		cssNode := &html.Node{}
		cssData := make([]string, 0, len(pr.leftover))
		for _, rule := range pr.leftover {
			if rule.Type == css.MEDIA_RULE {
				mcssData := make([]string, 0, len(rule.Rules))
				for _, mrule := range rule.Rules {
					mcssData = append(mcssData, makeRuleImportant(mrule))
				}
				cssData = append(cssData, fmt.Sprintf("%s %s{\n%s\n}\n",
					rule.Type.Text(),
					rule.Style.SelectorText,
					strings.Join(mcssData, "\n")))
			} else {
				cssData = append(cssData, makeRuleImportant(rule))
			}
		}
		cssNode.Data = strings.Join(cssData, "")
		cssNode.Type = html.TextNode
		styleNode.AppendChild(cssNode)
		headNode.AppendNodes(styleNode)
	}
}

// Transform process and inlining css
// It start to collect the rules in the document style tags
// Calculate specificity and sort the rules based on that
// It then collects the affected elements
// And applies the rules on those
// The leftover rules will put back into a style element
func (pr *premailer) Transform() (string, error) {
	if !pr.processed {
		pr.collectRules()
		pr.sortRules()
		pr.collectElements()
		pr.applyInline()
		pr.addLeftover()
		pr.processed = true
	}
	return pr.doc.Html()
}
<|endoftext|>


<commit_before>package sign

import (
	"crypto/rsa"
	"fmt"
	"net/http"
	"strings"
	"time"
)

const (
	// CookiePolicyName name of the policy cookie
	CookiePolicyName = "CloudFront-Policy"
	// CookieSignatureName name of the signature cookie
	CookieSignatureName = "CloudFront-Signature"
	// CookieKeyIDName name of the signing Key ID cookie
	CookieKeyIDName = "CloudFront-Key-Pair-Id"
)

// A CookieOptions optional additional options that can be applied to the signed
// cookies.
type CookieOptions struct {
	Path   string
	Domain string
	Secure bool
}

// apply will integration the options provided into the base cookie options
// a new copy will be returned. The base CookieOption will not be modified.
func (o CookieOptions) apply(opts ...func(*CookieOptions)) CookieOptions {
	if len(opts) == 0 {
		return o
	}

	for _, opt := range opts {
		opt(&o)
	}

	return o
}

// A CookieSigner provides signing utilities to sign Cookies for Amazon CloudFront
// resources. Using a private key and Credential Key Pair key ID the CookieSigner
// only needs to be created once per Credential Key Pair key ID and private key.
//
// More information about signed Cookies and their structure can be found at:
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html
//
// To sign a Cookie, create a CookieSigner with your private key and credential
// pair key ID. Once you have a CookieSigner instance you can call Sign or
// SignWithPolicy to sign the URLs.
//
// The signer is safe to use concurrently, but the optional cookies options
// are not safe to modify concurrently.
type CookieSigner struct {
	keyID   string
	privKey *rsa.PrivateKey

	Opts CookieOptions
}

// NewCookieSigner constructs and returns a new CookieSigner to be used to for
// signing Amazon CloudFront URL resources with.
func NewCookieSigner(keyID string, privKey *rsa.PrivateKey, opts ...func(*CookieOptions)) *CookieSigner {
	signer := &CookieSigner{
		keyID:   keyID,
		privKey: privKey,
		Opts:    CookieOptions{}.apply(opts...),
	}

	return signer
}

// Sign returns the cookies needed to allow user agents to make arbetrary
// requests to cloudfront for the resource(s) defined by the policy.
//
// Sign will create a CloudFront policy with only a resource and condition of
// DateLessThan equal to the expires time provided.
//
// The returned slice cookies should all be added to the Client's cookies or
// server's response.
//
// Example:
//    s := sign.NewCookieSigner(keyID, privKey)
//
//    // Get Signed cookies for a resource that will expire in 1 hour
//    cookies, err := s.Sign("*", time.Now().Add(1 * time.Hour))
//    if err != nil {
//        fmt.Println("failed to create signed cookies", err)
//        return
//    }
//
//    // Or get Signed cookies for a resource that will expire in 1 hour
//    // and set path and domain of cookies
//    cookies, err := s.Sign("*", time.Now().Add(1 * time.Hour), func(o *sign.CookieOptions) {
//        o.Path = "/"
//        o.Domain = ".example.com"
//    })
//    if err != nil {
//        fmt.Println("failed to create signed cookies", err)
//        return
//    }
//
//    // Server Response via http.ResponseWriter
//    for _, c := range cookies {
//        http.SetCookie(w, c)
//    }
//
//    // Client request via the cookie jar
//    if client.CookieJar != nil {
//        for _, c := range cookies {
//           client.Cookie(w, c)
//        }
//    }
func (s CookieSigner) Sign(u string, expires time.Time, opts ...func(*CookieOptions)) ([]*http.Cookie, error) {
	scheme, err := cookieURLScheme(u)
	if err != nil {
		return nil, err
	}

	resource, err := CreateResource(scheme, u)
	if err != nil {
		return nil, err
	}

	p := NewCannedPolicy(resource, expires)
	return createCookies(p, s.keyID, s.privKey, s.Opts.apply(opts...))
}

// Returns and validates the URL's scheme.
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html#private-content-custom-policy-statement-cookies
func cookieURLScheme(u string) (string, error) {
	parts := strings.SplitN(u, "://", 2)
	if len(parts) != 2 {
		return "", fmt.Errorf("invalid cookie URL, missing scheme")
	}

	scheme := strings.ToLower(parts[0])
	if scheme != "http" && scheme != "https" && scheme != "http*" {
		return "", fmt.Errorf("invalid cookie URL scheme. Expect http, https, or http*. Go, %s", scheme)
	}

	return scheme, nil
}

// SignWithPolicy returns the cookies needed to allow user agents to make
// arbetrairy requets to cloudfront for the resource(s) defined by the policy.
//
// The returned slice cookies should all be added to the Client's cookies or
// server's response.
//
// Example:
//    s := sign.NewCookieSigner(keyID, privKey)
//
//    policy := &sign.Policy{
//        Statements: []sign.Statement{
//            {
//                // Read the provided documentation on how to set this
//                // correctly, you'll probably want to use wildcards.
//                Resource: rawCloudFrontURL,
//                Condition: sign.Condition{
//                    // Optional IP source address range
//                    IPAddress: &sign.IPAddress{SourceIP: "192.0.2.0/24"},
//                    // Optional date URL is not valid until
//                    DateGreaterThan: &sign.AWSEpochTime{time.Now().Add(30 * time.Minute)},
//                    // Required date the URL will expire after
//                    DateLessThan: &sign.AWSEpochTime{time.Now().Add(1 * time.Hour)},
//                },
//            },
//        },
//    }
//
//    // Get Signed cookies for a resource that will expire in 1 hour
//    cookies, err := s.SignWithPolicy(policy)
//    if err != nil {
//        fmt.Println("failed to create signed cookies", err)
//        return
//    }
//
//    // Or get Signed cookies for a resource that will expire in 1 hour
//    // and set path and domain of cookies
//    cookies, err := s.Sign(policy, func(o *sign.CookieOptions) {
//        o.Path = "/"
//        o.Domain = ".example.com"
//    })
//    if err != nil {
//        fmt.Println("failed to create signed cookies", err)
//        return
//    }
//
//    // Server Response via http.ResponseWriter
//    for _, c := range cookies {
//        http.SetCookie(w, c)
//    }
//
//    // Client request via the cookie jar
//    if client.CookieJar != nil {
//        for _, c := range cookies {
//           client.Cookie(w, c)
//        }
//    }
func (s CookieSigner) SignWithPolicy(p *Policy, opts ...func(*CookieOptions)) ([]*http.Cookie, error) {
	return createCookies(p, s.keyID, s.privKey, s.Opts.apply(opts...))
}

// Prepares the cookies to be attached to the header. An (optional) options
// struct is provided in case people don't want to manually edit their cookies.
func createCookies(p *Policy, keyID string, privKey *rsa.PrivateKey, opt CookieOptions) ([]*http.Cookie, error) {
	b64Sig, b64Policy, err := p.Sign(privKey)
	if err != nil {
		return nil, err
	}

	// Creates proper cookies
	cPolicy := &http.Cookie{
		Name:     CookiePolicyName,
		Value:    string(b64Policy),
		HttpOnly: true,
	}
	cSignature := &http.Cookie{
		Name:     CookieSignatureName,
		Value:    string(b64Sig),
		HttpOnly: true,
	}
	cKey := &http.Cookie{
		Name:     CookieKeyIDName,
		Value:    keyID,
		HttpOnly: true,
	}

	cookies := []*http.Cookie{cPolicy, cSignature, cKey}

	// Applie the cookie options
	for _, c := range cookies {
		c.Path = opt.Path
		c.Domain = opt.Domain
		c.Secure = opt.Secure
	}

	return cookies, nil
}
<commit_msg>service/cloudfront/sign: Fix Documentation Typo (#3402)<commit_after>package sign

import (
	"crypto/rsa"
	"fmt"
	"net/http"
	"strings"
	"time"
)

const (
	// CookiePolicyName name of the policy cookie
	CookiePolicyName = "CloudFront-Policy"
	// CookieSignatureName name of the signature cookie
	CookieSignatureName = "CloudFront-Signature"
	// CookieKeyIDName name of the signing Key ID cookie
	CookieKeyIDName = "CloudFront-Key-Pair-Id"
)

// A CookieOptions optional additional options that can be applied to the signed
// cookies.
type CookieOptions struct {
	Path   string
	Domain string
	Secure bool
}

// apply will integration the options provided into the base cookie options
// a new copy will be returned. The base CookieOption will not be modified.
func (o CookieOptions) apply(opts ...func(*CookieOptions)) CookieOptions {
	if len(opts) == 0 {
		return o
	}

	for _, opt := range opts {
		opt(&o)
	}

	return o
}

// A CookieSigner provides signing utilities to sign Cookies for Amazon CloudFront
// resources. Using a private key and Credential Key Pair key ID the CookieSigner
// only needs to be created once per Credential Key Pair key ID and private key.
//
// More information about signed Cookies and their structure can be found at:
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html
//
// To sign a Cookie, create a CookieSigner with your private key and credential
// pair key ID. Once you have a CookieSigner instance you can call Sign or
// SignWithPolicy to sign the URLs.
//
// The signer is safe to use concurrently, but the optional cookies options
// are not safe to modify concurrently.
type CookieSigner struct {
	keyID   string
	privKey *rsa.PrivateKey

	Opts CookieOptions
}

// NewCookieSigner constructs and returns a new CookieSigner to be used to for
// signing Amazon CloudFront URL resources with.
func NewCookieSigner(keyID string, privKey *rsa.PrivateKey, opts ...func(*CookieOptions)) *CookieSigner {
	signer := &CookieSigner{
		keyID:   keyID,
		privKey: privKey,
		Opts:    CookieOptions{}.apply(opts...),
	}

	return signer
}

// Sign returns the cookies needed to allow user agents to make arbetrary
// requests to cloudfront for the resource(s) defined by the policy.
//
// Sign will create a CloudFront policy with only a resource and condition of
// DateLessThan equal to the expires time provided.
//
// The returned slice cookies should all be added to the Client's cookies or
// server's response.
//
// Example:
//    s := sign.NewCookieSigner(keyID, privKey)
//
//    // Get Signed cookies for a resource that will expire in 1 hour
//    cookies, err := s.Sign("*", time.Now().Add(1 * time.Hour))
//    if err != nil {
//        fmt.Println("failed to create signed cookies", err)
//        return
//    }
//
//    // Or get Signed cookies for a resource that will expire in 1 hour
//    // and set path and domain of cookies
//    cookies, err := s.Sign("*", time.Now().Add(1 * time.Hour), func(o *sign.CookieOptions) {
//        o.Path = "/"
//        o.Domain = ".example.com"
//    })
//    if err != nil {
//        fmt.Println("failed to create signed cookies", err)
//        return
//    }
//
//    // Server Response via http.ResponseWriter
//    for _, c := range cookies {
//        http.SetCookie(w, c)
//    }
//
//    // Client request via the cookie jar
//    if client.CookieJar != nil {
//        for _, c := range cookies {
//           client.Cookie(w, c)
//        }
//    }
func (s CookieSigner) Sign(u string, expires time.Time, opts ...func(*CookieOptions)) ([]*http.Cookie, error) {
	scheme, err := cookieURLScheme(u)
	if err != nil {
		return nil, err
	}

	resource, err := CreateResource(scheme, u)
	if err != nil {
		return nil, err
	}

	p := NewCannedPolicy(resource, expires)
	return createCookies(p, s.keyID, s.privKey, s.Opts.apply(opts...))
}

// Returns and validates the URL's scheme.
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html#private-content-custom-policy-statement-cookies
func cookieURLScheme(u string) (string, error) {
	parts := strings.SplitN(u, "://", 2)
	if len(parts) != 2 {
		return "", fmt.Errorf("invalid cookie URL, missing scheme")
	}

	scheme := strings.ToLower(parts[0])
	if scheme != "http" && scheme != "https" && scheme != "http*" {
		return "", fmt.Errorf("invalid cookie URL scheme. Expect http, https, or http*. Go, %s", scheme)
	}

	return scheme, nil
}

// SignWithPolicy returns the cookies needed to allow user agents to make
// arbetrairy requets to cloudfront for the resource(s) defined by the policy.
//
// The returned slice cookies should all be added to the Client's cookies or
// server's response.
//
// Example:
//    s := sign.NewCookieSigner(keyID, privKey)
//
//    policy := &sign.Policy{
//        Statements: []sign.Statement{
//            {
//                // Read the provided documentation on how to set this
//                // correctly, you'll probably want to use wildcards.
//                Resource: rawCloudFrontURL,
//                Condition: sign.Condition{
//                    // Optional IP source address range
//                    IPAddress: &sign.IPAddress{SourceIP: "192.0.2.0/24"},
//                    // Optional date URL is not valid until
//                    DateGreaterThan: &sign.AWSEpochTime{time.Now().Add(30 * time.Minute)},
//                    // Required date the URL will expire after
//                    DateLessThan: &sign.AWSEpochTime{time.Now().Add(1 * time.Hour)},
//                },
//            },
//        },
//    }
//
//    // Get Signed cookies for a resource that will expire in 1 hour
//    cookies, err := s.SignWithPolicy(policy)
//    if err != nil {
//        fmt.Println("failed to create signed cookies", err)
//        return
//    }
//
//    // Or get Signed cookies for a resource that will expire in 1 hour
//    // and set path and domain of cookies
//    cookies, err := s.SignWithPolicy(policy, func(o *sign.CookieOptions) {
//        o.Path = "/"
//        o.Domain = ".example.com"
//    })
//    if err != nil {
//        fmt.Println("failed to create signed cookies", err)
//        return
//    }
//
//    // Server Response via http.ResponseWriter
//    for _, c := range cookies {
//        http.SetCookie(w, c)
//    }
//
//    // Client request via the cookie jar
//    if client.CookieJar != nil {
//        for _, c := range cookies {
//           client.Cookie(w, c)
//        }
//    }
func (s CookieSigner) SignWithPolicy(p *Policy, opts ...func(*CookieOptions)) ([]*http.Cookie, error) {
	return createCookies(p, s.keyID, s.privKey, s.Opts.apply(opts...))
}

// Prepares the cookies to be attached to the header. An (optional) options
// struct is provided in case people don't want to manually edit their cookies.
func createCookies(p *Policy, keyID string, privKey *rsa.PrivateKey, opt CookieOptions) ([]*http.Cookie, error) {
	b64Sig, b64Policy, err := p.Sign(privKey)
	if err != nil {
		return nil, err
	}

	// Creates proper cookies
	cPolicy := &http.Cookie{
		Name:     CookiePolicyName,
		Value:    string(b64Policy),
		HttpOnly: true,
	}
	cSignature := &http.Cookie{
		Name:     CookieSignatureName,
		Value:    string(b64Sig),
		HttpOnly: true,
	}
	cKey := &http.Cookie{
		Name:     CookieKeyIDName,
		Value:    keyID,
		HttpOnly: true,
	}

	cookies := []*http.Cookie{cPolicy, cSignature, cKey}

	// Applie the cookie options
	for _, c := range cookies {
		c.Path = opt.Path
		c.Domain = opt.Domain
		c.Secure = opt.Secure
	}

	return cookies, nil
}
<|endoftext|>


<commit_before>package process

import (
	"fmt"
	"io"
	"os"
	"sync"
	"syscall"
	"time"
)

type ProcessState int
type CtrlState int
type Event int

const (
	ProcessStopped ProcessState = iota
	ProcessStarting
	ProcessRunning
	ProcessStopping
)

const (
	StartEvent Event = iota
	StopEvent
)

const (
	COMMAND_START int = iota
	COMMAND_STOP
	COMMAND_RESTART
)

type processCommand struct {
	Command int
	Reply   chan error
}

func (p *ProcessState) String() string {
	switch *p {
	case ProcessStopped:
		return "stopped"
	case ProcessStarting:
		return "starting"
	case ProcessRunning:
		return "running"
	case ProcessStopping:
		return "stopping"
	}
	return "unknown"
}

func (e *Event) String() string {
	switch *e {
	case StartEvent:
		return "start"
	case StopEvent:
		return "stop"
	}
	return "unknown"
}

// Process represents a process running under command of Nord agent
type Process struct {
	// Process Name
	Name string `json:"name"`

	// Enabled indicates whether to run this process. A value of false will ensure
	// it is always stopped
	Enabled bool `json:"enabled"`

	// RunAtLoad key is used to control whether your process is launched once
	// at the time the config is loaded. The default is true.
	RunAtLoad bool

	// Process Identifier
	pid int `json:"pid,int"`

	// Last status code from this process exiting
	LastExitStatus int `json:"last_exit_status,int"`

	// Launch timeout
	Timeout time.Duration `json:"timeout"`

	// Command is the executable and arguments to run
	Command []string `json:"command"`

	// Environment is a hash of environment vars to set for this process
	Environment map[string]string `json:"environment"`

	// The time this process started
	StartedAt time.Time `json:"started_at"`

	// Signal to send to the process to gracefully exit
	KillSignal os.Signal `json:"kill_signal"`

	// Timeout to wait for process to exit gracefully before killing
	KillTimeout time.Duration `json:"kill_timeout"`

	// Throttle relaunching
	Throttle time.Duration `json:"throttle"`

	// Restart process it exits
	KeepAlive bool `json:"keep_alive"`

	// User and Group to switch to after exec
	UserName  string `json:"user"`
	GroupName string `json:"group"`

	// WorkingDirectory is the directory to chdir to after forking
	WorkingDirectory string `json:"working_directory"`

	// Outlets are used to send the process output to other services
	outlets []*io.Writer

	// Internal state of the process
	state ProcessState

	proc       *os.Process
	outputChan chan []byte
	done       chan int
	Events     chan Event
	manage     chan *processCommand
	waitChan   chan bool

	runner ProcessRunner

	stateMu sync.Mutex
	pidMu   sync.Mutex

	sync.Mutex
}

// ProcessRunner is an interface for running processes, used mainly for switching
// between a live runner and a test runner
type ProcessRunner interface {
	Exec(*Process, chan []byte, chan int) (*os.Process, error)
}

// NewProcess constructs a new Process instance which can be accepted by
// the management queue
func NewProcess(name string, command ...string) *Process {
	return &Process{
		Name:        name,
		Command:     command,
		state:       ProcessStopped,
		Environment: make(map[string]string),
		KillTimeout: time.Second * 10,
		KillSignal:  os.Signal(syscall.SIGQUIT),
		KeepAlive:   true,
		Throttle:    time.Second * 10,

		outputChan: make(chan []byte),
		done:       make(chan int),
		manage:     make(chan *processCommand),
		Events:     make(chan Event),
		waitChan:   make(chan bool),
	}
}

// NewProcessFromConfig creates a process from a ProcessConf
func NewProcessFromConfig(conf *ProcessConfig) *Process {
	var programArgs []string

	if conf.Program != "" {
		programArgs = append(programArgs, conf.Program)
	}
	if len(conf.ProgramArguments) > 0 {
		programArgs = append(programArgs, conf.ProgramArguments...)
	}

	return &Process{
		Name:             conf.Name,
		Enabled:          !conf.Disabled,
		Command:          programArgs,
		Environment:      conf.EnvironmentVariables,
		KillSignal:       conf.KillSignal,
		KillTimeout:      conf.KillTimeout,
		Throttle:         conf.ThrottleInterval,
		KeepAlive:        conf.KeepAlive,
		RunAtLoad:        conf.RunAtLoad,
		WorkingDirectory: conf.WorkingDirectory,
		UserName:         conf.UserName,
		GroupName:        conf.GroupName,
	}
}

func (p *Process) OutputChan() chan []byte {
	return p.outputChan
}

// Status returns the process state
func (p *Process) Status() string {
	p.stateMu.Lock()
	defer p.stateMu.Unlock()
	return p.state.String()
}

func (p *Process) setStatus(state ProcessState) {
	p.stateMu.Lock()
	defer p.stateMu.Unlock()
	p.state = state
}

func (p *Process) Wait() {
	<-p.waitChan
}

// SetRunner sets the process runner (useful for testing with a mock runner)
// FIXME(ivanvanderbyl): This probably doesn't need to be exported
func (p *Process) SetRunner(r ProcessRunner) {
	p.runner = r
}

// Start the process
func (p *Process) Start() error {
	replyChan := make(chan error)
	c := &processCommand{COMMAND_START, replyChan}
	p.manage <- c
	return <-c.Reply
}

func (p *Process) Stop() error {
	replyChan := make(chan error)
	c := &processCommand{COMMAND_STOP, replyChan}
	p.manage <- c
	return <-c.Reply
}

func (p *Process) Restart() error {
	replyChan := make(chan error)
	c := &processCommand{COMMAND_RESTART, replyChan}
	p.manage <- c
	return <-c.Reply
}

func (p *Process) exec() error {
	p.Lock()
	defer p.Unlock()

	if p.runner == nil {
		p.runner = &DefaultRunner{}
	}

	p.setStatus(ProcessStarting)
	p.StartedAt = time.Now()

	proc, err := p.runner.Exec(p, p.outputChan, p.done)
	if err != nil {
		return err
	}

	p.proc = proc

	p.setStatus(ProcessRunning)
	return nil
}

func (p *Process) setPid(pid int) {
	p.pidMu.Lock()
	defer p.pidMu.Unlock()
	p.pid = pid
}

func (p *Process) PID() int {
	p.pidMu.Lock()
	defer p.pidMu.Unlock()
	return p.pid
}

func (p *Process) terminate() error {
	// didExit := make(chan bool)

	err := p.proc.Signal(syscall.SIGQUIT)
	if err != nil {
		return err
	}

	if p.PID() == 0 {
	}
	return nil

	// return p.proc.Kill()
}

func (p *Process) formattedEnv() []string {
	var env []string

	for k, v := range p.Environment {
		env = append(env, fmt.Sprintf("%s=%s", k, v))
	}

	return env
}

func (p *Process) finish(status int) {
	p.stateMu.Lock()
	defer p.stateMu.Unlock()

	p.StartedAt = time.Time{}
	p.LastExitStatus = status
	p.state = ProcessStopped
}

func (p *Process) Run() {
	go p.runloop()
}

func (p *Process) runloop() {
	go func(p *Process) {
		// var willExit bool = false

		for {
			select {
			case status := <-p.done:
				fmt.Println("Processs Exited")
				p.finish(status)

				select {
				case p.waitChan <- true:
				default:
				}

				select {
				case p.Events <- StopEvent:
				default:
				}

				// 				if p.KeepAlive && !willExit {
				// 					<-time.After(p.Throttle)
				// 					p.Start()
				// 				}

				// willExit = false

			case command := <-p.manage:
				switch command.Command {
				case COMMAND_START:
					err := p.exec()
					if err != nil {
						fmt.Println("Failed to start process:", err.Error())
					} else {
						// p.Events <- StartEvent
						select {
						case p.Events <- StartEvent:
						default:
							fmt.Println("Start Event Ignored")
						}
					}
					command.Reply <- err

					fmt.Println("Started")

				case COMMAND_STOP:
					fmt.Println("Received stop command", p.proc.Pid)
					if p.proc != nil {
						p.terminate()
					} else {
						fmt.Println("proc is nil")
					}
					command.Reply <- nil

					// err := p.terminate()
					// if err != nil {
					// 	fmt.Println("Error terminating process", err.Error())
					// }

				case COMMAND_RESTART:
					if p.PID() != 0 {
						p.Stop()
						<-time.After(p.Throttle)
					}
					p.Start()
					command.Reply <- nil
				}

				// 			case command := <-p.commands:
				// 				switch command.Command {
				// 				case COMMAND_START:
				// 					command.Reply <- p.exec()

				// 				case COMMAND_STOP:
				// 					// User initiated stop, do not relaunch
				// 					willExit = true
				// 					command.Reply <- p.terminate()
				// 				}
			}
		}
	}(p)
}
<commit_msg>Configured more process from config args<commit_after>package process

import (
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
	"syscall"
	"time"
)

type ProcessState int
type CtrlState int
type Event int

const (
	ProcessStopped ProcessState = iota
	ProcessStarting
	ProcessRunning
	ProcessStopping
)

const (
	StartEvent Event = iota
	StopEvent
)

const (
	COMMAND_START int = iota
	COMMAND_STOP
	COMMAND_RESTART
)

type processCommand struct {
	Command int
	Reply   chan error
}

func (p *ProcessState) String() string {
	switch *p {
	case ProcessStopped:
		return "stopped"
	case ProcessStarting:
		return "starting"
	case ProcessRunning:
		return "running"
	case ProcessStopping:
		return "stopping"
	}
	return "unknown"
}

func (e *Event) String() string {
	switch *e {
	case StartEvent:
		return "start"
	case StopEvent:
		return "stop"
	}
	return "unknown"
}

// Process represents a process running under command of Nord agent
type Process struct {
	// Process Name
	Name string `json:"name"`

	// Enabled indicates whether to run this process. A value of false will ensure
	// it is always stopped
	Enabled bool `json:"enabled"`

	// RunAtLoad key is used to control whether your process is launched once
	// at the time the config is loaded. The default is true.
	RunAtLoad bool

	// Process Identifier
	pid int `json:"pid,int"`

	// Last status code from this process exiting
	LastExitStatus int `json:"last_exit_status,int"`

	// Launch timeout
	Timeout time.Duration `json:"timeout"`

	// Command is the executable and arguments to run
	Command []string `json:"command"`

	// Environment is a hash of environment vars to set for this process
	Environment map[string]string `json:"environment"`

	// The time this process started
	StartedAt time.Time `json:"started_at"`

	// Signal to send to the process to gracefully exit
	KillSignal os.Signal `json:"kill_signal"`

	// Timeout to wait for process to exit gracefully before killing
	KillTimeout time.Duration `json:"kill_timeout"`

	// Throttle relaunching
	Throttle time.Duration `json:"throttle"`

	// Restart process it exits
	KeepAlive bool `json:"keep_alive"`

	// User and Group to switch to after exec
	UserName  string `json:"user"`
	GroupName string `json:"group"`

	// WorkingDirectory is the directory to chdir to after forking
	WorkingDirectory string `json:"working_directory"`

	// Outlets are used to send the process output to other services
	outlets []*io.Writer

	// Internal state of the process
	state ProcessState

	proc       *os.Process
	outputChan chan []byte
	done       chan int
	Events     chan Event
	manage     chan *processCommand
	waitChan   chan bool

	runner ProcessRunner

	stateMu sync.Mutex
	pidMu   sync.Mutex

	sync.Mutex
}

// ProcessRunner is an interface for running processes, used mainly for switching
// between a live runner and a test runner
type ProcessRunner interface {
	Exec(*Process, chan []byte, chan int) (*os.Process, error)
}

// NewProcess constructs a new Process instance which can be accepted by
// the management queue
func NewProcess(name string, command ...string) *Process {
	return &Process{
		Name:        name,
		Command:     command,
		state:       ProcessStopped,
		Environment: make(map[string]string),
		KillTimeout: time.Second * 10,
		KillSignal:  os.Signal(syscall.SIGQUIT),
		KeepAlive:   true,
		Throttle:    time.Second * 10,

		outputChan: make(chan []byte),
		done:       make(chan int),
		manage:     make(chan *processCommand),
		Events:     make(chan Event),
		waitChan:   make(chan bool),
	}
}

// NewProcessFromConfig creates a process from a ProcessConf
func NewProcessFromConfig(conf *ProcessConfig) *Process {
	var programArgs []string

	if conf.Program != "" {
		programArgs = append(programArgs, conf.Program)
	}
	if len(conf.ProgramArguments) > 0 {
		programArgs = append(programArgs, conf.ProgramArguments...)
	}

	var killSignal os.Signal
	switch strings.Trim(strings.ToUpper(conf.KillSignal), " ") {
	case "SIGQUIT", "QUIT":
		killSignal = syscall.SIGQUIT
	case "SIGTERM", "TERM":
		killSignal = syscall.SIGTERM
	case "SIGKILL", "KILL":
		killSignal = syscall.SIGKILL
	default:
		killSignal = syscall.SIGKILL
	}

	killTimeout, err := time.ParseDuration(conf.KillTimeout)
	if err != nil {
		killTimeout = time.Second * 10
	}

	throttleInterval, err := time.ParseDuration(conf.ThrottleInterval)
	if err != nil {
		throttleInterval = time.Second * 15
	}

	return &Process{
		Name:             conf.Name,
		Enabled:          !conf.Disabled,
		Command:          programArgs,
		Environment:      conf.EnvironmentVariables,
		KillSignal:       killSignal,
		KillTimeout:      killTimeout,
		Throttle:         throttleInterval,
		KeepAlive:        conf.KeepAlive,
		RunAtLoad:        conf.RunAtLoad,
		WorkingDirectory: conf.WorkingDirectory,
		UserName:         conf.UserName,
		GroupName:        conf.GroupName,
	}
}

func (p *Process) OutputChan() chan []byte {
	return p.outputChan
}

// Status returns the process state
func (p *Process) Status() string {
	p.stateMu.Lock()
	defer p.stateMu.Unlock()
	return p.state.String()
}

func (p *Process) IsRunning() bool {
	p.stateMu.Lock()
	defer p.stateMu.Unlock()
	return p.state == ProcessRunning
}

func (p *Process) IsStopped() bool {
	p.stateMu.Lock()
	defer p.stateMu.Unlock()
	return p.state == ProcessStopped
}

func (p *Process) setStatus(state ProcessState) {
	p.stateMu.Lock()
	defer p.stateMu.Unlock()
	p.state = state
}

func (p *Process) Wait() {
	<-p.waitChan
}

// SetRunner sets the process runner (useful for testing with a mock runner)
// FIXME(ivanvanderbyl): This probably doesn't need to be exported
func (p *Process) SetRunner(r ProcessRunner) {
	p.runner = r
}

// Start the process
func (p *Process) Start() error {
	replyChan := make(chan error)
	c := &processCommand{COMMAND_START, replyChan}
	p.manage <- c
	return <-c.Reply
}

func (p *Process) Stop() error {
	replyChan := make(chan error)
	c := &processCommand{COMMAND_STOP, replyChan}
	p.manage <- c
	return <-c.Reply
}

func (p *Process) Restart() error {
	replyChan := make(chan error)
	c := &processCommand{COMMAND_RESTART, replyChan}
	p.manage <- c
	return <-c.Reply
}

func (p *Process) exec() error {
	p.Lock()
	defer p.Unlock()

	if p.runner == nil {
		p.runner = &DefaultRunner{}
	}

	p.setStatus(ProcessStarting)
	p.StartedAt = time.Now()

	proc, err := p.runner.Exec(p, p.outputChan, p.done)
	if err != nil {
		return err
	}

	p.proc = proc

	p.setStatus(ProcessRunning)
	return nil
}

func (p *Process) setPid(pid int) {
	p.pidMu.Lock()
	defer p.pidMu.Unlock()
	p.pid = pid
}

func (p *Process) PID() int {
	p.pidMu.Lock()
	defer p.pidMu.Unlock()
	return p.pid
}

func (p *Process) terminate() error {
	// didExit := make(chan bool)

	err := p.proc.Signal(syscall.SIGQUIT)
	if err != nil {
		return err
	}

	if p.PID() == 0 {
	}
	return nil

	// return p.proc.Kill()
}

func (p *Process) formattedEnv() []string {
	var env []string

	for k, v := range p.Environment {
		env = append(env, fmt.Sprintf("%s=%s", k, v))
	}

	return env
}

func (p *Process) finish(status int) {
	p.stateMu.Lock()
	defer p.stateMu.Unlock()

	p.StartedAt = time.Time{}
	p.LastExitStatus = status
	p.state = ProcessStopped
}

func (p *Process) Run() {
	go p.runloop()
}

func (p *Process) runloop() {
	go func(p *Process) {
		// var willExit bool = false

		for {
			select {
			case status := <-p.done:
				fmt.Println("Processs Exited")
				p.finish(status)

				select {
				case p.waitChan <- true:
				default:
				}

				select {
				case p.Events <- StopEvent:
				default:
				}

				// 				if p.KeepAlive && !willExit {
				// 					<-time.After(p.Throttle)
				// 					p.Start()
				// 				}

				// willExit = false

			case command := <-p.manage:
				switch command.Command {
				case COMMAND_START:
					err := p.exec()
					if err != nil {
						fmt.Println("Failed to start process:", err.Error())
					} else {
						// p.Events <- StartEvent
						select {
						case p.Events <- StartEvent:
						default:
							fmt.Println("Start Event Ignored")
						}
					}
					command.Reply <- err

					fmt.Println("Started")

				case COMMAND_STOP:
					fmt.Println("Received stop command", p.proc.Pid)
					if p.proc != nil {
						p.terminate()
					} else {
						fmt.Println("proc is nil")
					}
					command.Reply <- nil

					// err := p.terminate()
					// if err != nil {
					// 	fmt.Println("Error terminating process", err.Error())
					// }

				case COMMAND_RESTART:
					if p.PID() != 0 {
						p.Stop()
						<-time.After(p.Throttle)
					}
					p.Start()
					command.Reply <- nil
				}

				// 			case command := <-p.commands:
				// 				switch command.Command {
				// 				case COMMAND_START:
				// 					command.Reply <- p.exec()

				// 				case COMMAND_STOP:
				// 					// User initiated stop, do not relaunch
				// 					willExit = true
				// 					command.Reply <- p.terminate()
				// 				}
			}
		}
	}(p)
}
<|endoftext|>


<commit_before>package plugin

import (
	"os"
	"syscall"
)

const STILL_ACTIVE = 259

// _pidAlive tests whether a process is alive or not
func _pidAlive(pid int) bool {
	const da = syscall.STANDARD_RIGHTS_READ |
		syscall.PROCESS_QUERY_INFORMATION |
		syscall.SYNCHRONIZE
	h, e := syscall.OpenProcess(da, false, uint32(pid))
	if e != nil {
		return false
	}

	var ec uint32
	e = syscall.GetExitCodeProcess(h, &ec)
	if e != nil {
		return false
	}

	return ec == STILL_ACTIVE
}
<commit_msg>update windows style<commit_after>package plugin

import (
	"os"
	"syscall"
)

const (
	// Weird name but matches the MSDN docs
	exit_STILL_ACTIVE = 259

	processDesiredAccess = syscall.STANDARD_RIGHTS_READ |
		syscall.PROCESS_QUERY_INFORMATION |
		syscall.SYNCHRONIZE
)

// _pidAlive tests whether a process is alive or not
func _pidAlive(pid int) bool {
	h, err := syscall.OpenProcess(processDesiredAccess, false, uint32(pid))
	if err != nil {
		return false
	}

	var ec uint32
	e = syscall.GetExitCodeProcess(h, &ec)
	if e != nil {
		return false
	}

	return ec == exit_STILL_ACTIVE
}
<|endoftext|>


<commit_before>package processors

import (
	"regexp"

	gostatic "github.com/piranha/gostatic/lib"
)

// YamlProcessor is empty struct
type YamlProcessor struct {
}

// NewYamlProcessor is constructor for YamlProcessor
func NewYamlProcessor() *YamlProcessor {
	return &YamlProcessor{}
}

// Process is function for runnig ProcessYaml
func (p *YamlProcessor) Process(page *gostatic.Page, args []string) error {
	return ProcessYaml(page, args)
}

// Description is function for write description
func (p *YamlProcessor) Description() string {
	return "read config from content using yaml format"
}

// Mode is work mode for this processor
func (p *YamlProcessor) Mode() int {
	return gostatic.Pre
}

// ProcessYaml is function for proccess yaml header
func ProcessYaml(page *gostatic.Page, args []string) error {
	r, err := regexp.Compile(`(?sm)^---\r?\n(.*)^---\r?\n(.*)`)
	if err != nil {
		panic(err)
	}
	parts := r.FindStringSubmatch(page.Content())

	if len(parts) != 3 {
		// no configuration, well then...
		page.PageHeader = *gostatic.NewPageHeader()
		return nil
	}

	page.PageHeader = *gostatic.ParseYamlHeader(parts[1])
	page.SetContent(parts[2])
	return nil
}
<commit_msg>those comments are useless and had mistakes in them<commit_after>package processors

import (
	"regexp"

	gostatic "github.com/piranha/gostatic/lib"
)

type YamlProcessor struct {
}

func NewYamlProcessor() *YamlProcessor {
	return &YamlProcessor{}
}

func (p *YamlProcessor) Process(page *gostatic.Page, args []string) error {
	return ProcessYaml(page, args)
}

func (p *YamlProcessor) Description() string {
	return "read config from content using yaml format"
}

func (p *YamlProcessor) Mode() int {
	return gostatic.Pre
}

func ProcessYaml(page *gostatic.Page, args []string) error {
	r, err := regexp.Compile(`(?sm)^---\r?\n(.*)^---\r?\n(.*)`)
	if err != nil {
		panic(err)
	}
	parts := r.FindStringSubmatch(page.Content())

	if len(parts) != 3 {
		// no configuration, well then...
		page.PageHeader = *gostatic.NewPageHeader()
		return nil
	}

	page.PageHeader = *gostatic.ParseYamlHeader(parts[1])
	page.SetContent(parts[2])
	return nil
}
<|endoftext|>