<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package commands

import (
	"fmt"
	"os"

	"github.com/juju/cmd"
	"github.com/juju/utils/featureflag"

	jujucmd "github.com/juju/juju/cmd"
	"github.com/juju/juju/cmd/envcmd"
	"github.com/juju/juju/cmd/juju/action"
	"github.com/juju/juju/cmd/juju/backups"
	"github.com/juju/juju/cmd/juju/block"
	"github.com/juju/juju/cmd/juju/cachedimages"
	"github.com/juju/juju/cmd/juju/common"
	"github.com/juju/juju/cmd/juju/environment"
	"github.com/juju/juju/cmd/juju/helptopics"
	"github.com/juju/juju/cmd/juju/machine"
	"github.com/juju/juju/cmd/juju/service"
	"github.com/juju/juju/cmd/juju/space"
	"github.com/juju/juju/cmd/juju/status"
	"github.com/juju/juju/cmd/juju/storage"
	"github.com/juju/juju/cmd/juju/subnet"
	"github.com/juju/juju/cmd/juju/system"
	"github.com/juju/juju/cmd/juju/user"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/feature"
	"github.com/juju/juju/juju"
	"github.com/juju/juju/juju/osenv"
	// Import the providers.
	_ "github.com/juju/juju/provider/all"
	"github.com/juju/juju/version"
)

func init() {
	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
}

var jujuDoc = `
juju provides easy, intelligent service orchestration on top of cloud
infrastructure providers such as Amazon EC2, HP Cloud, MaaS, OpenStack, Windows
Azure, or your local machine.

https://juju.ubuntu.com/
`

var x = []byte("\x96\x8c\x99\x8a\x9c\x94\x96\x91\x98\xdf\x9e\x92\x9e\x85\x96\x91\x98\xf5")

// Main registers subcommands for the juju executable, and hands over control
// to the cmd package. This function is not redundant with main, because it
// provides an entry point for testing with arbitrary command line arguments.
func Main(args []string) {
	ctx, err := cmd.DefaultContext()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(2)
	}
	if err = juju.InitJujuHome(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %s\n", err)
		os.Exit(2)
	}
	for i := range x {
		x[i] ^= 255
	}
	if len(args) == 2 && args[1] == string(x[0:2]) {
		os.Stdout.Write(x[2:])
		os.Exit(0)
	}
	jcmd := NewJujuCommand(ctx)
	os.Exit(cmd.Main(jcmd, ctx, args[1:]))
}

func NewJujuCommand(ctx *cmd.Context) cmd.Command {
	jcmd := jujucmd.NewSuperCommand(cmd.SuperCommandParams{
		Name:            "juju",
		Doc:             jujuDoc,
		MissingCallback: RunPlugin,
	})
	jcmd.AddHelpTopic("basics", "Basic commands", helptopics.Basics)
	jcmd.AddHelpTopic("local-provider", "How to configure a local (LXC) provider",
		helptopics.LocalProvider)
	jcmd.AddHelpTopic("openstack-provider", "How to configure an OpenStack provider",
		helptopics.OpenstackProvider, "openstack")
	jcmd.AddHelpTopic("ec2-provider", "How to configure an Amazon EC2 provider",
		helptopics.EC2Provider, "ec2", "aws", "amazon")
	jcmd.AddHelpTopic("hpcloud-provider", "How to configure an HP Cloud provider",
		helptopics.HPCloud, "hpcloud", "hp-cloud")
	jcmd.AddHelpTopic("azure-provider", "How to configure a Windows Azure provider",
		helptopics.AzureProvider, "azure")
	jcmd.AddHelpTopic("maas-provider", "How to configure a MAAS provider",
		helptopics.MAASProvider, "maas")
	jcmd.AddHelpTopic("constraints", "How to use commands with constraints", helptopics.Constraints)
	jcmd.AddHelpTopic("placement", "How to use placement directives", helptopics.Placement)
	jcmd.AddHelpTopic("glossary", "Glossary of terms", helptopics.Glossary)
	jcmd.AddHelpTopic("logging", "How Juju handles logging", helptopics.Logging)
	jcmd.AddHelpTopic("juju", "What is Juju?", helptopics.Juju)
	jcmd.AddHelpTopic("juju-systems", "About Juju Environment Systems (JES)", helptopics.JujuSystems)
	jcmd.AddHelpTopic("users", "About users in Juju", helptopics.Users)
	jcmd.AddHelpTopicCallback("plugins", "Show Juju plugins", PluginHelpTopic)

	registerCommands(jcmd, ctx)
	return jcmd
}

type commandRegistry interface {
	Register(cmd.Command)
	RegisterSuperAlias(name, super, forName string, check cmd.DeprecationCheck)
	RegisterDeprecated(subcmd cmd.Command, check cmd.DeprecationCheck)
}

// TODO(ericsnow) Factor out the commands and aliases into a static
// registry that can be passed to the supercommand separately.

// registerCommands registers commands in the specified registry.
// EnvironCommands must be wrapped with an envCmdWrapper.
func registerCommands(r commandRegistry, ctx *cmd.Context) {
	wrapEnvCommand := func(c envcmd.EnvironCommand) cmd.Command {
		return envCmdWrapper{envcmd.Wrap(c), ctx}
	}

	// Creation commands.
	r.Register(wrapEnvCommand(&BootstrapCommand{}))
	r.Register(wrapEnvCommand(&DeployCommand{}))
	r.Register(wrapEnvCommand(&AddRelationCommand{}))

	// Destruction commands.
	r.Register(wrapEnvCommand(&RemoveRelationCommand{}))
	r.Register(wrapEnvCommand(&RemoveServiceCommand{}))
	r.Register(wrapEnvCommand(&RemoveUnitCommand{}))
	r.Register(&DestroyEnvironmentCommand{})

	// Reporting commands.
	r.Register(wrapEnvCommand(&status.StatusCommand{}))
	r.Register(&SwitchCommand{})
	r.Register(wrapEnvCommand(&EndpointCommand{}))
	r.Register(wrapEnvCommand(&APIInfoCommand{}))
	r.Register(wrapEnvCommand(&status.StatusHistoryCommand{}))

	// Error resolution and debugging commands.
	r.Register(wrapEnvCommand(&RunCommand{}))
	r.Register(wrapEnvCommand(&SCPCommand{}))
	r.Register(wrapEnvCommand(&SSHCommand{}))
	r.Register(wrapEnvCommand(&ResolvedCommand{}))
	r.Register(wrapEnvCommand(&DebugLogCommand{}))
	r.Register(wrapEnvCommand(&DebugHooksCommand{}))

	// Configuration commands.
	r.Register(&InitCommand{})
	r.RegisterDeprecated(wrapEnvCommand(&common.GetConstraintsCommand{}),
		twoDotOhDeprecation("environment get-constraints or service get-constraints"))
	r.RegisterDeprecated(wrapEnvCommand(&common.SetConstraintsCommand{}),
		twoDotOhDeprecation("environment set-constraints or service set-constraints"))
	r.Register(wrapEnvCommand(&ExposeCommand{}))
	r.Register(wrapEnvCommand(&SyncToolsCommand{}))
	r.Register(wrapEnvCommand(&UnexposeCommand{}))
	r.Register(wrapEnvCommand(&UpgradeJujuCommand{}))
	r.Register(wrapEnvCommand(&UpgradeCharmCommand{}))

	// Charm publishing commands.
	r.Register(wrapEnvCommand(&PublishCommand{}))

	// Charm tool commands.
	r.Register(&HelpToolCommand{})

	// Manage backups.
	r.Register(backups.NewCommand())

	// Manage authorized ssh keys.
	r.Register(NewAuthorizedKeysCommand())

	// Manage users and access
	r.Register(user.NewSuperCommand())

	// Manage cached images
	r.Register(cachedimages.NewSuperCommand())

	// Manage machines
	r.Register(machine.NewSuperCommand())
	r.RegisterSuperAlias("add-machine", "machine", "add", twoDotOhDeprecation("machine add"))
	r.RegisterSuperAlias("remove-machine", "machine", "remove", twoDotOhDeprecation("machine remove"))
	r.RegisterSuperAlias("destroy-machine", "machine", "remove", twoDotOhDeprecation("machine remove"))
	r.RegisterSuperAlias("terminate-machine", "machine", "remove", twoDotOhDeprecation("machine remove"))

	// Mangage environment
	r.Register(environment.NewSuperCommand())
	r.RegisterSuperAlias("get-environment", "environment", "get", twoDotOhDeprecation("environment get"))
	r.RegisterSuperAlias("get-env", "environment", "get", twoDotOhDeprecation("environment get"))
	r.RegisterSuperAlias("set-environment", "environment", "set", twoDotOhDeprecation("environment set"))
	r.RegisterSuperAlias("set-env", "environment", "set", twoDotOhDeprecation("environment set"))
	r.RegisterSuperAlias("unset-environment", "environment", "unset", twoDotOhDeprecation("environment unset"))
	r.RegisterSuperAlias("unset-env", "environment", "unset", twoDotOhDeprecation("environment unset"))
	r.RegisterSuperAlias("retry-provisioning", "environment", "retry-provisioning", twoDotOhDeprecation("environment retry-provisioning"))

	// Manage and control actions
	r.Register(action.NewSuperCommand())

	// Manage state server availability
	r.Register(wrapEnvCommand(&EnsureAvailabilityCommand{}))

	// Manage and control services
	r.Register(service.NewSuperCommand())
	r.RegisterSuperAlias("add-unit", "service", "add-unit", twoDotOhDeprecation("service add-unit"))
	r.RegisterSuperAlias("get", "service", "get", twoDotOhDeprecation("service get"))
	r.RegisterSuperAlias("set", "service", "set", twoDotOhDeprecation("service set"))
	r.RegisterSuperAlias("unset", "service", "unset", twoDotOhDeprecation("service unset"))

	// Operation protection commands
	r.Register(block.NewSuperBlockCommand())
	r.Register(wrapEnvCommand(&block.UnblockCommand{}))

	// Manage storage
	r.Register(storage.NewSuperCommand())

	// Manage spaces
	r.Register(space.NewSuperCommand())

	// Manage subnets
	r.Register(subnet.NewSuperCommand())

	// Manage systems
	if featureflag.Enabled(feature.JES) {
		r.Register(system.NewSuperCommand())
		r.RegisterSuperAlias("systems", "system", "list", nil)

		// Add top level aliases of the same name as the subcommands.
		r.RegisterSuperAlias("environments", "system", "environments", nil)
		r.RegisterSuperAlias("login", "system", "login", nil)
		r.RegisterSuperAlias("create-environment", "system", "create-environment", nil)
		r.RegisterSuperAlias("create-env", "system", "create-env", nil)
	}

	// Commands registered elsewhere.
	for _, item := range registeredCommands {
		item.apply(r, ctx)
	}
}

// envCmdWrapper is a struct that wraps an environment command and lets us handle
// errors returned from Init before they're returned to the main function.
type envCmdWrapper struct {
	cmd.Command
	ctx *cmd.Context
}

func (w envCmdWrapper) Init(args []string) error {
	err := w.Command.Init(args)
	if environs.IsNoEnv(err) {
		fmt.Fprintln(w.ctx.Stderr, "No juju environment configuration file exists.")
		fmt.Fprintln(w.ctx.Stderr, err)
		fmt.Fprintln(w.ctx.Stderr, "Please create a configuration by running:")
		fmt.Fprintln(w.ctx.Stderr, "    juju init")
		fmt.Fprintln(w.ctx.Stderr, "then edit the file to configure your juju environment.")
		fmt.Fprintln(w.ctx.Stderr, "You can then re-run the command.")
		return cmd.ErrSilent
	}
	return err
}

func main() {
	Main(os.Args)
}

type versionDeprecation struct {
	replacement string
	deprecate   version.Number
	obsolete    version.Number
}

// Deprecated implements cmd.DeprecationCheck.
// If the current version is after the deprecate version number,
// the command is deprecated and the replacement should be used.
func (v *versionDeprecation) Deprecated() (bool, string) {
	if version.Current.Number.Compare(v.deprecate) > 0 {
		return true, v.replacement
	}
	return false, ""
}

// Obsolete implements cmd.DeprecationCheck.
// If the current version is after the obsolete version number,
// the command is obsolete and shouldn't be registered.
func (v *versionDeprecation) Obsolete() bool {
	return version.Current.Number.Compare(v.obsolete) > 0
}

func twoDotOhDeprecation(replacement string) cmd.DeprecationCheck {
	return &versionDeprecation{
		replacement: replacement,
		deprecate:   version.MustParse("2.0-00"),
		obsolete:    version.MustParse("3.0-00"),
	}
}
<commit_msg>Add a TODO.<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package commands

import (
	"fmt"
	"os"

	"github.com/juju/cmd"
	"github.com/juju/utils/featureflag"

	jujucmd "github.com/juju/juju/cmd"
	"github.com/juju/juju/cmd/envcmd"
	"github.com/juju/juju/cmd/juju/action"
	"github.com/juju/juju/cmd/juju/backups"
	"github.com/juju/juju/cmd/juju/block"
	"github.com/juju/juju/cmd/juju/cachedimages"
	"github.com/juju/juju/cmd/juju/common"
	"github.com/juju/juju/cmd/juju/environment"
	"github.com/juju/juju/cmd/juju/helptopics"
	"github.com/juju/juju/cmd/juju/machine"
	"github.com/juju/juju/cmd/juju/service"
	"github.com/juju/juju/cmd/juju/space"
	"github.com/juju/juju/cmd/juju/status"
	"github.com/juju/juju/cmd/juju/storage"
	"github.com/juju/juju/cmd/juju/subnet"
	"github.com/juju/juju/cmd/juju/system"
	"github.com/juju/juju/cmd/juju/user"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/feature"
	"github.com/juju/juju/juju"
	"github.com/juju/juju/juju/osenv"
	// Import the providers.
	_ "github.com/juju/juju/provider/all"
	"github.com/juju/juju/version"
)

func init() {
	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
}

// TODO(ericsnow) Move the following to cmd/juju/main.go:
//  jujuDoc
//  Main

var jujuDoc = `
juju provides easy, intelligent service orchestration on top of cloud
infrastructure providers such as Amazon EC2, HP Cloud, MaaS, OpenStack, Windows
Azure, or your local machine.

https://juju.ubuntu.com/
`

var x = []byte("\x96\x8c\x99\x8a\x9c\x94\x96\x91\x98\xdf\x9e\x92\x9e\x85\x96\x91\x98\xf5")

// Main registers subcommands for the juju executable, and hands over control
// to the cmd package. This function is not redundant with main, because it
// provides an entry point for testing with arbitrary command line arguments.
func Main(args []string) {
	ctx, err := cmd.DefaultContext()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(2)
	}
	if err = juju.InitJujuHome(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %s\n", err)
		os.Exit(2)
	}
	for i := range x {
		x[i] ^= 255
	}
	if len(args) == 2 && args[1] == string(x[0:2]) {
		os.Stdout.Write(x[2:])
		os.Exit(0)
	}
	jcmd := NewJujuCommand(ctx)
	os.Exit(cmd.Main(jcmd, ctx, args[1:]))
}

func NewJujuCommand(ctx *cmd.Context) cmd.Command {
	jcmd := jujucmd.NewSuperCommand(cmd.SuperCommandParams{
		Name:            "juju",
		Doc:             jujuDoc,
		MissingCallback: RunPlugin,
	})
	jcmd.AddHelpTopic("basics", "Basic commands", helptopics.Basics)
	jcmd.AddHelpTopic("local-provider", "How to configure a local (LXC) provider",
		helptopics.LocalProvider)
	jcmd.AddHelpTopic("openstack-provider", "How to configure an OpenStack provider",
		helptopics.OpenstackProvider, "openstack")
	jcmd.AddHelpTopic("ec2-provider", "How to configure an Amazon EC2 provider",
		helptopics.EC2Provider, "ec2", "aws", "amazon")
	jcmd.AddHelpTopic("hpcloud-provider", "How to configure an HP Cloud provider",
		helptopics.HPCloud, "hpcloud", "hp-cloud")
	jcmd.AddHelpTopic("azure-provider", "How to configure a Windows Azure provider",
		helptopics.AzureProvider, "azure")
	jcmd.AddHelpTopic("maas-provider", "How to configure a MAAS provider",
		helptopics.MAASProvider, "maas")
	jcmd.AddHelpTopic("constraints", "How to use commands with constraints", helptopics.Constraints)
	jcmd.AddHelpTopic("placement", "How to use placement directives", helptopics.Placement)
	jcmd.AddHelpTopic("glossary", "Glossary of terms", helptopics.Glossary)
	jcmd.AddHelpTopic("logging", "How Juju handles logging", helptopics.Logging)
	jcmd.AddHelpTopic("juju", "What is Juju?", helptopics.Juju)
	jcmd.AddHelpTopic("juju-systems", "About Juju Environment Systems (JES)", helptopics.JujuSystems)
	jcmd.AddHelpTopic("users", "About users in Juju", helptopics.Users)
	jcmd.AddHelpTopicCallback("plugins", "Show Juju plugins", PluginHelpTopic)

	registerCommands(jcmd, ctx)
	return jcmd
}

type commandRegistry interface {
	Register(cmd.Command)
	RegisterSuperAlias(name, super, forName string, check cmd.DeprecationCheck)
	RegisterDeprecated(subcmd cmd.Command, check cmd.DeprecationCheck)
}

// TODO(ericsnow) Factor out the commands and aliases into a static
// registry that can be passed to the supercommand separately.

// registerCommands registers commands in the specified registry.
// EnvironCommands must be wrapped with an envCmdWrapper.
func registerCommands(r commandRegistry, ctx *cmd.Context) {
	wrapEnvCommand := func(c envcmd.EnvironCommand) cmd.Command {
		return envCmdWrapper{envcmd.Wrap(c), ctx}
	}

	// Creation commands.
	r.Register(wrapEnvCommand(&BootstrapCommand{}))
	r.Register(wrapEnvCommand(&DeployCommand{}))
	r.Register(wrapEnvCommand(&AddRelationCommand{}))

	// Destruction commands.
	r.Register(wrapEnvCommand(&RemoveRelationCommand{}))
	r.Register(wrapEnvCommand(&RemoveServiceCommand{}))
	r.Register(wrapEnvCommand(&RemoveUnitCommand{}))
	r.Register(&DestroyEnvironmentCommand{})

	// Reporting commands.
	r.Register(wrapEnvCommand(&status.StatusCommand{}))
	r.Register(&SwitchCommand{})
	r.Register(wrapEnvCommand(&EndpointCommand{}))
	r.Register(wrapEnvCommand(&APIInfoCommand{}))
	r.Register(wrapEnvCommand(&status.StatusHistoryCommand{}))

	// Error resolution and debugging commands.
	r.Register(wrapEnvCommand(&RunCommand{}))
	r.Register(wrapEnvCommand(&SCPCommand{}))
	r.Register(wrapEnvCommand(&SSHCommand{}))
	r.Register(wrapEnvCommand(&ResolvedCommand{}))
	r.Register(wrapEnvCommand(&DebugLogCommand{}))
	r.Register(wrapEnvCommand(&DebugHooksCommand{}))

	// Configuration commands.
	r.Register(&InitCommand{})
	r.RegisterDeprecated(wrapEnvCommand(&common.GetConstraintsCommand{}),
		twoDotOhDeprecation("environment get-constraints or service get-constraints"))
	r.RegisterDeprecated(wrapEnvCommand(&common.SetConstraintsCommand{}),
		twoDotOhDeprecation("environment set-constraints or service set-constraints"))
	r.Register(wrapEnvCommand(&ExposeCommand{}))
	r.Register(wrapEnvCommand(&SyncToolsCommand{}))
	r.Register(wrapEnvCommand(&UnexposeCommand{}))
	r.Register(wrapEnvCommand(&UpgradeJujuCommand{}))
	r.Register(wrapEnvCommand(&UpgradeCharmCommand{}))

	// Charm publishing commands.
	r.Register(wrapEnvCommand(&PublishCommand{}))

	// Charm tool commands.
	r.Register(&HelpToolCommand{})

	// Manage backups.
	r.Register(backups.NewCommand())

	// Manage authorized ssh keys.
	r.Register(NewAuthorizedKeysCommand())

	// Manage users and access
	r.Register(user.NewSuperCommand())

	// Manage cached images
	r.Register(cachedimages.NewSuperCommand())

	// Manage machines
	r.Register(machine.NewSuperCommand())
	r.RegisterSuperAlias("add-machine", "machine", "add", twoDotOhDeprecation("machine add"))
	r.RegisterSuperAlias("remove-machine", "machine", "remove", twoDotOhDeprecation("machine remove"))
	r.RegisterSuperAlias("destroy-machine", "machine", "remove", twoDotOhDeprecation("machine remove"))
	r.RegisterSuperAlias("terminate-machine", "machine", "remove", twoDotOhDeprecation("machine remove"))

	// Mangage environment
	r.Register(environment.NewSuperCommand())
	r.RegisterSuperAlias("get-environment", "environment", "get", twoDotOhDeprecation("environment get"))
	r.RegisterSuperAlias("get-env", "environment", "get", twoDotOhDeprecation("environment get"))
	r.RegisterSuperAlias("set-environment", "environment", "set", twoDotOhDeprecation("environment set"))
	r.RegisterSuperAlias("set-env", "environment", "set", twoDotOhDeprecation("environment set"))
	r.RegisterSuperAlias("unset-environment", "environment", "unset", twoDotOhDeprecation("environment unset"))
	r.RegisterSuperAlias("unset-env", "environment", "unset", twoDotOhDeprecation("environment unset"))
	r.RegisterSuperAlias("retry-provisioning", "environment", "retry-provisioning", twoDotOhDeprecation("environment retry-provisioning"))

	// Manage and control actions
	r.Register(action.NewSuperCommand())

	// Manage state server availability
	r.Register(wrapEnvCommand(&EnsureAvailabilityCommand{}))

	// Manage and control services
	r.Register(service.NewSuperCommand())
	r.RegisterSuperAlias("add-unit", "service", "add-unit", twoDotOhDeprecation("service add-unit"))
	r.RegisterSuperAlias("get", "service", "get", twoDotOhDeprecation("service get"))
	r.RegisterSuperAlias("set", "service", "set", twoDotOhDeprecation("service set"))
	r.RegisterSuperAlias("unset", "service", "unset", twoDotOhDeprecation("service unset"))

	// Operation protection commands
	r.Register(block.NewSuperBlockCommand())
	r.Register(wrapEnvCommand(&block.UnblockCommand{}))

	// Manage storage
	r.Register(storage.NewSuperCommand())

	// Manage spaces
	r.Register(space.NewSuperCommand())

	// Manage subnets
	r.Register(subnet.NewSuperCommand())

	// Manage systems
	if featureflag.Enabled(feature.JES) {
		r.Register(system.NewSuperCommand())
		r.RegisterSuperAlias("systems", "system", "list", nil)

		// Add top level aliases of the same name as the subcommands.
		r.RegisterSuperAlias("environments", "system", "environments", nil)
		r.RegisterSuperAlias("login", "system", "login", nil)
		r.RegisterSuperAlias("create-environment", "system", "create-environment", nil)
		r.RegisterSuperAlias("create-env", "system", "create-env", nil)
	}

	// Commands registered elsewhere.
	for _, item := range registeredCommands {
		item.apply(r, ctx)
	}
}

// envCmdWrapper is a struct that wraps an environment command and lets us handle
// errors returned from Init before they're returned to the main function.
type envCmdWrapper struct {
	cmd.Command
	ctx *cmd.Context
}

func (w envCmdWrapper) Init(args []string) error {
	err := w.Command.Init(args)
	if environs.IsNoEnv(err) {
		fmt.Fprintln(w.ctx.Stderr, "No juju environment configuration file exists.")
		fmt.Fprintln(w.ctx.Stderr, err)
		fmt.Fprintln(w.ctx.Stderr, "Please create a configuration by running:")
		fmt.Fprintln(w.ctx.Stderr, "    juju init")
		fmt.Fprintln(w.ctx.Stderr, "then edit the file to configure your juju environment.")
		fmt.Fprintln(w.ctx.Stderr, "You can then re-run the command.")
		return cmd.ErrSilent
	}
	return err
}

func main() {
	Main(os.Args)
}

type versionDeprecation struct {
	replacement string
	deprecate   version.Number
	obsolete    version.Number
}

// Deprecated implements cmd.DeprecationCheck.
// If the current version is after the deprecate version number,
// the command is deprecated and the replacement should be used.
func (v *versionDeprecation) Deprecated() (bool, string) {
	if version.Current.Number.Compare(v.deprecate) > 0 {
		return true, v.replacement
	}
	return false, ""
}

// Obsolete implements cmd.DeprecationCheck.
// If the current version is after the obsolete version number,
// the command is obsolete and shouldn't be registered.
func (v *versionDeprecation) Obsolete() bool {
	return version.Current.Number.Compare(v.obsolete) > 0
}

func twoDotOhDeprecation(replacement string) cmd.DeprecationCheck {
	return &versionDeprecation{
		replacement: replacement,
		deprecate:   version.MustParse("2.0-00"),
		obsolete:    version.MustParse("3.0-00"),
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"runtime"

	"github.com/codegangsta/cli"
	"github.com/exercism/cli/api"
	"github.com/exercism/cli/cmd"
	"github.com/exercism/cli/debug"
)

const (
	// Version is the current release of the command-line app.
	// We try to follow Semantic Versioning (http://semver.org),
	// but with the http://exercism.io app being a prototype, a
	// lot of things get out of hand.
	Version = "2.2.4"

	descDebug     = "Outputs useful debug information."
	descConfigure = "Writes config values to a JSON file."
	descFetch     = "Fetches the next unsubmitted problem in each track."
	descRestore   = "Downloads your the most recent iteration for each of your solutions on exercism.io."
	descSubmit    = "Submits a new iteration to a problem on exercism.io."
	descSkip      = "Skips a problem given a track ID and problem slug."
	descUpgrade   = "Upgrades the CLI to the latest released version."
	descTracks    = "Lists the available language tracks."
	descOpen      = "Opens exercism.io to your most recent iteration of a problem given the track ID and problem slug."
	descDownload  = "Downloads a solution given the ID of the latest iteration."
	descList      = "Lists the available problems for a language track, given its ID."
	descStatus    = "Fetches information about your progress with a given language track."

	descLongRestore  = "Restore will pull the latest revisions of exercises that have already been submitted. It will *not* overwrite existing files. If you have made changes to a file and have not submitted it, and you're trying to restore the last submitted version, first move that file out of the way, then call restore."
	descLongDownload = "The submission ID is the last part of the URL when looking at a solution on exercism.io."
)

func main() {
	api.UserAgent = fmt.Sprintf("github.com/exercism/cli v%s (%s/%s)", Version, runtime.GOOS, runtime.GOARCH)

	app := cli.NewApp()
	app.Name = "exercism"
	app.Usage = "A command line tool to interact with http://exercism.io"
	app.Version = Version
	app.Before = func(ctx *cli.Context) error {
		debug.Verbose = ctx.GlobalBool("verbose")
		debug.Println("verbose logging enabled")

		return nil
	}
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "config, c",
			Usage:  "path to config file",
			EnvVar: "EXERCISM_CONFIG_FILE,XDG_CONFIG_HOME",
		},
		cli.BoolFlag{
			Name:  "verbose",
			Usage: "turn on verbose logging",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:   "debug",
			Usage:  descDebug,
			Action: cmd.Debug,
		},
		{
			Name:  "configure",
			Usage: descConfigure,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "dir, d",
					Usage: "path to exercises directory",
				},
				cli.StringFlag{
					Name:  "host, u",
					Usage: "exercism api host",
				},
				cli.StringFlag{
					Name:  "key, k",
					Usage: "exercism.io API key (see http://exercism.io/account/key)",
				},
				cli.StringFlag{
					Name:  "api, a",
					Usage: "exercism xapi host",
				},
			},
			Action: cmd.Configure,
		},
		{
			Name:      "fetch",
			ShortName: "f",
			Usage:     descFetch,
			Action:    cmd.Fetch,
		},
		{
			Name:        "restore",
			ShortName:   "r",
			Usage:       descRestore,
			Description: descLongRestore,
			Action:      cmd.Restore,
		},
		{
			Name:   "skip",
			Usage:  descSkip,
			Action: cmd.Skip,
		},
		{
			Name:      "submit",
			ShortName: "s",
			Usage:     descSubmit,
			Action:    cmd.Submit,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "test",
					Usage: "allow submission of test files",
				},
				cli.StringFlag{
					Name:  "comment",
					Usage: "includes a comment with the submission",
				},
			},
		},
		{
			Name:      "unsubmit",
			ShortName: "u",
			Usage:     "REMOVED",
			Action: func(*cli.Context) {
				fmt.Println("For security reasons, this command is no longer in use.\nYou can delete iterations in the web interface.")
			},
		},
		{
			Name:   "upgrade",
			Usage:  descUpgrade,
			Action: cmd.Upgrade,
		},
		{
			Name:      "tracks",
			ShortName: "t",
			Usage:     descTracks,
			Action:    cmd.Tracks,
		},
		{
			Name:      "open",
			ShortName: "op",
			Usage:     descOpen,
			Action:    cmd.Open,
		},
		{
			Name:        "download",
			ShortName:   "dl",
			Usage:       descDownload,
			Description: descLongDownload,
			Action:      cmd.Download,
		},
		{
			Name:      "list",
			ShortName: "li",
			Usage:     descList,
			Action:    cmd.List,
		},
		{
			Name:      "status",
			ShortName: "st",
			Usage:     descStatus,
			Action:    cmd.Status,
		},
	}
	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Fixes typo in exercism restore description -deletes your from description since was typo<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"runtime"

	"github.com/codegangsta/cli"
	"github.com/exercism/cli/api"
	"github.com/exercism/cli/cmd"
	"github.com/exercism/cli/debug"
)

const (
	// Version is the current release of the command-line app.
	// We try to follow Semantic Versioning (http://semver.org),
	// but with the http://exercism.io app being a prototype, a
	// lot of things get out of hand.
	Version = "2.2.4"

	descDebug     = "Outputs useful debug information."
	descConfigure = "Writes config values to a JSON file."
	descFetch     = "Fetches the next unsubmitted problem in each track."
	descRestore   = "Downloads the most recent iteration for each of your solutions on exercism.io."
	descSubmit    = "Submits a new iteration to a problem on exercism.io."
	descSkip      = "Skips a problem given a track ID and problem slug."
	descUpgrade   = "Upgrades the CLI to the latest released version."
	descTracks    = "Lists the available language tracks."
	descOpen      = "Opens exercism.io to your most recent iteration of a problem given the track ID and problem slug."
	descDownload  = "Downloads a solution given the ID of the latest iteration."
	descList      = "Lists the available problems for a language track, given its ID."
	descStatus    = "Fetches information about your progress with a given language track."

	descLongRestore  = "Restore will pull the latest revisions of exercises that have already been submitted. It will *not* overwrite existing files. If you have made changes to a file and have not submitted it, and you're trying to restore the last submitted version, first move that file out of the way, then call restore."
	descLongDownload = "The submission ID is the last part of the URL when looking at a solution on exercism.io."
)

func main() {
	api.UserAgent = fmt.Sprintf("github.com/exercism/cli v%s (%s/%s)", Version, runtime.GOOS, runtime.GOARCH)

	app := cli.NewApp()
	app.Name = "exercism"
	app.Usage = "A command line tool to interact with http://exercism.io"
	app.Version = Version
	app.Before = func(ctx *cli.Context) error {
		debug.Verbose = ctx.GlobalBool("verbose")
		debug.Println("verbose logging enabled")

		return nil
	}
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "config, c",
			Usage:  "path to config file",
			EnvVar: "EXERCISM_CONFIG_FILE,XDG_CONFIG_HOME",
		},
		cli.BoolFlag{
			Name:  "verbose",
			Usage: "turn on verbose logging",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:   "debug",
			Usage:  descDebug,
			Action: cmd.Debug,
		},
		{
			Name:  "configure",
			Usage: descConfigure,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "dir, d",
					Usage: "path to exercises directory",
				},
				cli.StringFlag{
					Name:  "host, u",
					Usage: "exercism api host",
				},
				cli.StringFlag{
					Name:  "key, k",
					Usage: "exercism.io API key (see http://exercism.io/account/key)",
				},
				cli.StringFlag{
					Name:  "api, a",
					Usage: "exercism xapi host",
				},
			},
			Action: cmd.Configure,
		},
		{
			Name:      "fetch",
			ShortName: "f",
			Usage:     descFetch,
			Action:    cmd.Fetch,
		},
		{
			Name:        "restore",
			ShortName:   "r",
			Usage:       descRestore,
			Description: descLongRestore,
			Action:      cmd.Restore,
		},
		{
			Name:   "skip",
			Usage:  descSkip,
			Action: cmd.Skip,
		},
		{
			Name:      "submit",
			ShortName: "s",
			Usage:     descSubmit,
			Action:    cmd.Submit,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "test",
					Usage: "allow submission of test files",
				},
				cli.StringFlag{
					Name:  "comment",
					Usage: "includes a comment with the submission",
				},
			},
		},
		{
			Name:      "unsubmit",
			ShortName: "u",
			Usage:     "REMOVED",
			Action: func(*cli.Context) {
				fmt.Println("For security reasons, this command is no longer in use.\nYou can delete iterations in the web interface.")
			},
		},
		{
			Name:   "upgrade",
			Usage:  descUpgrade,
			Action: cmd.Upgrade,
		},
		{
			Name:      "tracks",
			ShortName: "t",
			Usage:     descTracks,
			Action:    cmd.Tracks,
		},
		{
			Name:      "open",
			ShortName: "op",
			Usage:     descOpen,
			Action:    cmd.Open,
		},
		{
			Name:        "download",
			ShortName:   "dl",
			Usage:       descDownload,
			Description: descLongDownload,
			Action:      cmd.Download,
		},
		{
			Name:      "list",
			ShortName: "li",
			Usage:     descList,
			Action:    cmd.List,
		},
		{
			Name:      "status",
			ShortName: "st",
			Usage:     descStatus,
			Action:    cmd.Status,
		},
	}
	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package containerbuddy

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"sync"
)

var (
	// Version is the version for this build, set at build time via LDFLAGS
	Version string
	// GitHash is the short-form commit hash of this build, set at build time
	GitHash string
)

// Passing around config as a context to functions would be the ideomatic way.
// But we need to support configuration reload from signals and have that reload
// effect function calls in the main goroutine. Wherever possible we should be
// accessing via `getConfig` at the "top" of a goroutine and then use the config
// as context for a function after that.
var (
	globalConfig *Config
	configLock   = new(sync.RWMutex)
)

func getConfig() *Config {
	configLock.RLock()
	defer configLock.RUnlock()
	return globalConfig
}

// Config is the top-level Containerbuddy Configuration
type Config struct {
	Consul       string           `json:"consul,omitempty"`
	Etcd         json.RawMessage  `json:"etcd,omitempty"`
	LogConfig    *LogConfig       `json:"logging,omitempty"`
	OnStart      json.RawMessage  `json:"onStart,omitempty"`
	PreStop      json.RawMessage  `json:"preStop,omitempty"`
	PostStop     json.RawMessage  `json:"postStop,omitempty"`
	StopTimeout  int              `json:"stopTimeout"`
	Services     []*ServiceConfig `json:"services"`
	Backends     []*BackendConfig `json:"backends"`
	onStartCmd   *exec.Cmd
	preStopCmd   *exec.Cmd
	postStopCmd  *exec.Cmd
	Command      *exec.Cmd
	QuitChannels []chan bool
}

// ServiceConfig configures the service, discovery data, and health checks
type ServiceConfig struct {
	ID               string
	Name             string          `json:"name"`
	Poll             int             `json:"poll"` // time in seconds
	HealthCheckExec  json.RawMessage `json:"health"`
	Port             int             `json:"port"`
	TTL              int             `json:"ttl"`
	Interfaces       json.RawMessage `json:"interfaces"`
	Tags             []string        `json:"tags,omitempty"`
	IpAddress        string          `json:"ipaddress,omitempty"`
	discoveryService DiscoveryService
	healthCheckCmd   *exec.Cmd
}

// BackendConfig represents a command to execute when another application changes
type BackendConfig struct {
	Name             string          `json:"name"`
	Poll             int             `json:"poll"` // time in seconds
	OnChangeExec     json.RawMessage `json:"onChange"`
	Tag              string          `json:"tag,omitempty"`
	discoveryService DiscoveryService
	lastState        interface{}
	onChangeCmd      *exec.Cmd
}

// Pollable is base abstraction for backends and services that support polling
type Pollable interface {
	PollTime() int
}

// PollTime returns the backend's  poll time
func (b BackendConfig) PollTime() int {
	return b.Poll
}

// CheckForUpstreamChanges checks the service discovery endpoint for any changes
// in a dependent backend. Returns true when there has been a change.
func (b *BackendConfig) CheckForUpstreamChanges() bool {
	return b.discoveryService.CheckForUpstreamChanges(b)
}

// OnChange runs the backend's onChange command, returning the results
func (b *BackendConfig) OnChange() (int, error) {
	exitCode, err := run(b.onChangeCmd)
	// Reset command object - since it can't be reused
	b.onChangeCmd = argsToCmd(b.onChangeCmd.Args)
	return exitCode, err
}

// PollTime returns the service's poll time
func (s ServiceConfig) PollTime() int {
	return s.Poll
}

// SendHeartbeat sends a heartbeat for this service
func (s *ServiceConfig) SendHeartbeat() {
	s.discoveryService.SendHeartbeat(s)
}

// MarkForMaintenance marks this service for maintenance
func (s *ServiceConfig) MarkForMaintenance() {
	s.discoveryService.MarkForMaintenance(s)
}

// Deregister will deregister this instance of the service
func (s *ServiceConfig) Deregister() {
	s.discoveryService.Deregister(s)
}

// CheckHealth runs the service's health command, returning the results
func (s *ServiceConfig) CheckHealth() (int, error) {
	exitCode, err := run(s.healthCheckCmd)
	// Reset command object - since it can't be reused
	s.healthCheckCmd = argsToCmd(s.healthCheckCmd.Args)
	return exitCode, err
}

const (
	// Amount of time to wait before killing the application
	defaultStopTimeout int = 5
)

func parseInterfaces(raw json.RawMessage) ([]string, error) {
	if raw == nil {
		return []string{}, nil
	}
	// Parse as a string
	var jsonString string
	if err := json.Unmarshal(raw, &jsonString); err == nil {
		return []string{jsonString}, nil
	}

	var jsonArray []string
	if err := json.Unmarshal(raw, &jsonArray); err == nil {
		return jsonArray, nil
	}

	return []string{}, errors.New("interfaces must be a string or an array")
}

func parseCommandArgs(raw json.RawMessage) (*exec.Cmd, error) {
	if raw == nil {
		return nil, nil
	}
	// Parse as a string
	var stringCmd string
	if err := json.Unmarshal(raw, &stringCmd); err == nil {
		return strToCmd(stringCmd), nil
	}

	var arrayCmd []string
	if err := json.Unmarshal(raw, &arrayCmd); err == nil {
		return argsToCmd(arrayCmd), nil
	}
	return nil, errors.New("Command argument must be a string or an array")
}

func loadConfig() (*Config, error) {

	var configFlag string
	var versionFlag bool

	if !flag.Parsed() {
		flag.StringVar(&configFlag, "config", "",
			"JSON config or file:// path to JSON config file.")
		flag.BoolVar(&versionFlag, "version", false, "Show version identifier and quit.")
		flag.Parse()
	} else {
		// allows for safe configuration reload
		configFlag = flag.Lookup("config").Value.String()
	}
	if versionFlag {
		fmt.Printf("Version: %s\nGitHash: %s\n", Version, GitHash)
		os.Exit(0)
	}
	if configFlag == "" {
		configFlag = os.Getenv("CONTAINERBUDDY")
	}

	config, err := parseConfig(configFlag)
	if err != nil {
		return nil, err
	}
	return initializeConfig(config)
}

func initializeConfig(config *Config) (*Config, error) {
	var discovery DiscoveryService
	discoveryCount := 0
	onStartCmd, err := parseCommandArgs(config.OnStart)
	if err != nil {
		return nil, fmt.Errorf("Could not parse `onStart`: %s", err)
	}
	config.onStartCmd = onStartCmd

	preStopCmd, err := parseCommandArgs(config.PreStop)
	if err != nil {
		return nil, fmt.Errorf("Could not parse `preStop`: %s", err)
	}
	config.preStopCmd = preStopCmd

	postStopCmd, err := parseCommandArgs(config.PostStop)
	if err != nil {
		return nil, fmt.Errorf("Could not parse `postStop`: %s", err)
	}
	config.postStopCmd = postStopCmd

	for _, discoveryBackend := range []string{"Consul", "Etcd"} {
		switch discoveryBackend {
		case "Consul":
			if config.Consul != "" {
				discovery = NewConsulConfig(config.Consul)
				discoveryCount++
			}
		case "Etcd":
			if config.Etcd != nil {
				discovery = NewEtcdConfig(config.Etcd)
				discoveryCount++
			}
		}
	}

	if discoveryCount == 0 {
		return nil, errors.New("No discovery backend defined")
	} else if discoveryCount > 1 {
		return nil, errors.New("More than one discovery backend defined")
	}

	if config.LogConfig != nil {
		err := config.LogConfig.init()
		if err != nil {
			return nil, err
		}
	}

	if config.StopTimeout == 0 {
		config.StopTimeout = defaultStopTimeout
	}

	for _, backend := range config.Backends {
		if backend.Name == "" {
			return nil, fmt.Errorf("backend must have a `name`")
		}
		cmd, err := parseCommandArgs(backend.OnChangeExec)
		if err != nil {
			return nil, fmt.Errorf("Could not parse `onChange` in backend %s: %s",
				backend.Name, err)
		}
		if cmd == nil {
			return nil, fmt.Errorf("`onChange` is required in backend %s",
				backend.Name)
		}
		if backend.Poll < 1 {
			return nil, fmt.Errorf("`poll` must be > 0 in backend %s",
				backend.Name)
		}
		backend.onChangeCmd = cmd
		backend.discoveryService = discovery
	}

	hostname, _ := os.Hostname()
	for _, service := range config.Services {
		if service.Name == "" {
			return nil, fmt.Errorf("service must have a `name`")
		}
		service.ID = fmt.Sprintf("%s-%s", service.Name, hostname)
		service.discoveryService = discovery
		if service.Poll < 1 {
			return nil, fmt.Errorf("`poll` must be > 0 in service %s",
				service.Name)
		}
		if service.TTL < 1 {
			return nil, fmt.Errorf("`ttl` must be > 0 in service %s",
				service.Name)
		}
		if service.Port < 1 {
			return nil, fmt.Errorf("`port` must be > 0 in service %s",
				service.Name)
		}

		if cmd, err := parseCommandArgs(service.HealthCheckExec); err != nil {
			return nil, fmt.Errorf("Could not parse `health` in service %s: %s",
				service.Name, err)
		} else if cmd == nil {
			return nil, fmt.Errorf("`health` is required in service %s",
				service.Name)
		} else {
			service.healthCheckCmd = cmd
		}

		interfaces, ifaceErr := parseInterfaces(service.Interfaces)
		if ifaceErr != nil {
			return nil, ifaceErr
		}

		if service.IpAddress == "" {
			if service.IpAddress, err = GetIP(interfaces); err != nil {
				return nil, err
			}
		}
	}

	configLock.Lock()
	globalConfig = config
	configLock.Unlock()

	return config, nil
}

func parseConfig(configFlag string) (*Config, error) {
	if configFlag == "" {
		return nil, errors.New("-config flag is required")
	}

	var data []byte
	if strings.HasPrefix(configFlag, "file://") {
		var err error
		fName := strings.SplitAfter(configFlag, "file://")[1]
		if data, err = ioutil.ReadFile(fName); err != nil {
			return nil, fmt.Errorf("Could not read config file: %s", err)
		}
	} else {
		data = []byte(configFlag)
	}

	template, err := ApplyTemplate(data)
	if err != nil {
		return nil, fmt.Errorf(
			"Could not apply template to config: %s", err)
	}
	return unmarshalConfig(template)
}

func unmarshalConfig(data []byte) (*Config, error) {
	config := &Config{}
	if err := json.Unmarshal(data, &config); err != nil {
		syntax, ok := err.(*json.SyntaxError)
		if !ok {
			return nil, fmt.Errorf(
				"Could not parse configuration: %s",
				err)
		}
		return nil, newJSONParseError(data, syntax)
	}
	return config, nil
}

func newJSONParseError(js []byte, syntax *json.SyntaxError) error {
	line, col, err := highlightError(js, syntax.Offset)
	return fmt.Errorf("Parse error at line:col [%d:%d]: %s\n%s", line, col, syntax, err)
}

func highlightError(data []byte, pos int64) (int, int, string) {
	prevLine := ""
	thisLine := ""
	highlight := ""
	line := 1
	col := pos
	offset := int64(0)
	r := bytes.NewReader(data)
	scanner := bufio.NewScanner(r)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		prevLine = thisLine
		thisLine = fmt.Sprintf("%5d: %s\n", line, scanner.Text())
		readBytes := int64(len(scanner.Bytes()))
		offset += readBytes
		if offset >= pos-1 {
			highlight = fmt.Sprintf("%s^", strings.Repeat("-", int(7+col-1)))
			break
		}
		col -= readBytes + 1
		line++
	}
	return line, int(col), fmt.Sprintf("%s%s%s", prevLine, thisLine, highlight)
}

func argsToCmd(args []string) *exec.Cmd {
	if len(args) == 0 {
		return nil
	}
	if len(args) > 1 {
		return exec.Command(args[0], args[1:]...)
	}
	return exec.Command(args[0])
}

func strToCmd(command string) *exec.Cmd {
	if command != "" {
		return argsToCmd(strings.Split(strings.TrimSpace(command), " "))
	}
	return nil
}
<commit_msg>test if provided ip is valid<commit_after>package containerbuddy

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"strings"
	"sync"
)

var (
	// Version is the version for this build, set at build time via LDFLAGS
	Version string
	// GitHash is the short-form commit hash of this build, set at build time
	GitHash string
)

// Passing around config as a context to functions would be the ideomatic way.
// But we need to support configuration reload from signals and have that reload
// effect function calls in the main goroutine. Wherever possible we should be
// accessing via `getConfig` at the "top" of a goroutine and then use the config
// as context for a function after that.
var (
	globalConfig *Config
	configLock   = new(sync.RWMutex)
)

func getConfig() *Config {
	configLock.RLock()
	defer configLock.RUnlock()
	return globalConfig
}

// Config is the top-level Containerbuddy Configuration
type Config struct {
	Consul       string           `json:"consul,omitempty"`
	Etcd         json.RawMessage  `json:"etcd,omitempty"`
	LogConfig    *LogConfig       `json:"logging,omitempty"`
	OnStart      json.RawMessage  `json:"onStart,omitempty"`
	PreStop      json.RawMessage  `json:"preStop,omitempty"`
	PostStop     json.RawMessage  `json:"postStop,omitempty"`
	StopTimeout  int              `json:"stopTimeout"`
	Services     []*ServiceConfig `json:"services"`
	Backends     []*BackendConfig `json:"backends"`
	onStartCmd   *exec.Cmd
	preStopCmd   *exec.Cmd
	postStopCmd  *exec.Cmd
	Command      *exec.Cmd
	QuitChannels []chan bool
}

// ServiceConfig configures the service, discovery data, and health checks
type ServiceConfig struct {
	ID               string
	Name             string          `json:"name"`
	Poll             int             `json:"poll"` // time in seconds
	HealthCheckExec  json.RawMessage `json:"health"`
	Port             int             `json:"port"`
	TTL              int             `json:"ttl"`
	Interfaces       json.RawMessage `json:"interfaces"`
	Tags             []string        `json:"tags,omitempty"`
	IpAddress        string          `json:"ipaddress,omitempty"`
	discoveryService DiscoveryService
	healthCheckCmd   *exec.Cmd
}

// BackendConfig represents a command to execute when another application changes
type BackendConfig struct {
	Name             string          `json:"name"`
	Poll             int             `json:"poll"` // time in seconds
	OnChangeExec     json.RawMessage `json:"onChange"`
	Tag              string          `json:"tag,omitempty"`
	discoveryService DiscoveryService
	lastState        interface{}
	onChangeCmd      *exec.Cmd
}

// Pollable is base abstraction for backends and services that support polling
type Pollable interface {
	PollTime() int
}

// PollTime returns the backend's  poll time
func (b BackendConfig) PollTime() int {
	return b.Poll
}

// CheckForUpstreamChanges checks the service discovery endpoint for any changes
// in a dependent backend. Returns true when there has been a change.
func (b *BackendConfig) CheckForUpstreamChanges() bool {
	return b.discoveryService.CheckForUpstreamChanges(b)
}

// OnChange runs the backend's onChange command, returning the results
func (b *BackendConfig) OnChange() (int, error) {
	exitCode, err := run(b.onChangeCmd)
	// Reset command object - since it can't be reused
	b.onChangeCmd = argsToCmd(b.onChangeCmd.Args)
	return exitCode, err
}

// PollTime returns the service's poll time
func (s ServiceConfig) PollTime() int {
	return s.Poll
}

// SendHeartbeat sends a heartbeat for this service
func (s *ServiceConfig) SendHeartbeat() {
	s.discoveryService.SendHeartbeat(s)
}

// MarkForMaintenance marks this service for maintenance
func (s *ServiceConfig) MarkForMaintenance() {
	s.discoveryService.MarkForMaintenance(s)
}

// Deregister will deregister this instance of the service
func (s *ServiceConfig) Deregister() {
	s.discoveryService.Deregister(s)
}

// CheckHealth runs the service's health command, returning the results
func (s *ServiceConfig) CheckHealth() (int, error) {
	exitCode, err := run(s.healthCheckCmd)
	// Reset command object - since it can't be reused
	s.healthCheckCmd = argsToCmd(s.healthCheckCmd.Args)
	return exitCode, err
}

const (
	// Amount of time to wait before killing the application
	defaultStopTimeout int = 5
)

func parseInterfaces(raw json.RawMessage) ([]string, error) {
	if raw == nil {
		return []string{}, nil
	}
	// Parse as a string
	var jsonString string
	if err := json.Unmarshal(raw, &jsonString); err == nil {
		return []string{jsonString}, nil
	}

	var jsonArray []string
	if err := json.Unmarshal(raw, &jsonArray); err == nil {
		return jsonArray, nil
	}

	return []string{}, errors.New("interfaces must be a string or an array")
}

func parseCommandArgs(raw json.RawMessage) (*exec.Cmd, error) {
	if raw == nil {
		return nil, nil
	}
	// Parse as a string
	var stringCmd string
	if err := json.Unmarshal(raw, &stringCmd); err == nil {
		return strToCmd(stringCmd), nil
	}

	var arrayCmd []string
	if err := json.Unmarshal(raw, &arrayCmd); err == nil {
		return argsToCmd(arrayCmd), nil
	}
	return nil, errors.New("Command argument must be a string or an array")
}

func loadConfig() (*Config, error) {

	var configFlag string
	var versionFlag bool

	if !flag.Parsed() {
		flag.StringVar(&configFlag, "config", "",
			"JSON config or file:// path to JSON config file.")
		flag.BoolVar(&versionFlag, "version", false, "Show version identifier and quit.")
		flag.Parse()
	} else {
		// allows for safe configuration reload
		configFlag = flag.Lookup("config").Value.String()
	}
	if versionFlag {
		fmt.Printf("Version: %s\nGitHash: %s\n", Version, GitHash)
		os.Exit(0)
	}
	if configFlag == "" {
		configFlag = os.Getenv("CONTAINERBUDDY")
	}

	config, err := parseConfig(configFlag)
	if err != nil {
		return nil, err
	}
	return initializeConfig(config)
}

func initializeConfig(config *Config) (*Config, error) {
	var discovery DiscoveryService
	discoveryCount := 0
	onStartCmd, err := parseCommandArgs(config.OnStart)
	if err != nil {
		return nil, fmt.Errorf("Could not parse `onStart`: %s", err)
	}
	config.onStartCmd = onStartCmd

	preStopCmd, err := parseCommandArgs(config.PreStop)
	if err != nil {
		return nil, fmt.Errorf("Could not parse `preStop`: %s", err)
	}
	config.preStopCmd = preStopCmd

	postStopCmd, err := parseCommandArgs(config.PostStop)
	if err != nil {
		return nil, fmt.Errorf("Could not parse `postStop`: %s", err)
	}
	config.postStopCmd = postStopCmd

	for _, discoveryBackend := range []string{"Consul", "Etcd"} {
		switch discoveryBackend {
		case "Consul":
			if config.Consul != "" {
				discovery = NewConsulConfig(config.Consul)
				discoveryCount++
			}
		case "Etcd":
			if config.Etcd != nil {
				discovery = NewEtcdConfig(config.Etcd)
				discoveryCount++
			}
		}
	}

	if discoveryCount == 0 {
		return nil, errors.New("No discovery backend defined")
	} else if discoveryCount > 1 {
		return nil, errors.New("More than one discovery backend defined")
	}

	if config.LogConfig != nil {
		err := config.LogConfig.init()
		if err != nil {
			return nil, err
		}
	}

	if config.StopTimeout == 0 {
		config.StopTimeout = defaultStopTimeout
	}

	for _, backend := range config.Backends {
		if backend.Name == "" {
			return nil, fmt.Errorf("backend must have a `name`")
		}
		cmd, err := parseCommandArgs(backend.OnChangeExec)
		if err != nil {
			return nil, fmt.Errorf("Could not parse `onChange` in backend %s: %s",
				backend.Name, err)
		}
		if cmd == nil {
			return nil, fmt.Errorf("`onChange` is required in backend %s",
				backend.Name)
		}
		if backend.Poll < 1 {
			return nil, fmt.Errorf("`poll` must be > 0 in backend %s",
				backend.Name)
		}
		backend.onChangeCmd = cmd
		backend.discoveryService = discovery
	}

	hostname, _ := os.Hostname()
	for _, service := range config.Services {
		if service.Name == "" {
			return nil, fmt.Errorf("service must have a `name`")
		}
		service.ID = fmt.Sprintf("%s-%s", service.Name, hostname)
		service.discoveryService = discovery
		if service.Poll < 1 {
			return nil, fmt.Errorf("`poll` must be > 0 in service %s",
				service.Name)
		}
		if service.TTL < 1 {
			return nil, fmt.Errorf("`ttl` must be > 0 in service %s",
				service.Name)
		}
		if service.Port < 1 {
			return nil, fmt.Errorf("`port` must be > 0 in service %s",
				service.Name)
		}

		if cmd, err := parseCommandArgs(service.HealthCheckExec); err != nil {
			return nil, fmt.Errorf("Could not parse `health` in service %s: %s",
				service.Name, err)
		} else if cmd == nil {
			return nil, fmt.Errorf("`health` is required in service %s",
				service.Name)
		} else {
			service.healthCheckCmd = cmd
		}

		interfaces, ifaceErr := parseInterfaces(service.Interfaces)
		if ifaceErr != nil {
			return nil, ifaceErr
		}

		if service.IpAddress == "" {
			if service.IpAddress, err = GetIP(interfaces); err != nil {
				return nil, err
			}
		} else {
			if err := net.ParseIP(service.IpAddress); err == nil {
				return nil, fmt.Errorf("Could not parse `ipaddress` in service %s",
					service.Name)
			}
		}
	}

	configLock.Lock()
	globalConfig = config
	configLock.Unlock()

	return config, nil
}

func parseConfig(configFlag string) (*Config, error) {
	if configFlag == "" {
		return nil, errors.New("-config flag is required")
	}

	var data []byte
	if strings.HasPrefix(configFlag, "file://") {
		var err error
		fName := strings.SplitAfter(configFlag, "file://")[1]
		if data, err = ioutil.ReadFile(fName); err != nil {
			return nil, fmt.Errorf("Could not read config file: %s", err)
		}
	} else {
		data = []byte(configFlag)
	}

	template, err := ApplyTemplate(data)
	if err != nil {
		return nil, fmt.Errorf(
			"Could not apply template to config: %s", err)
	}
	return unmarshalConfig(template)
}

func unmarshalConfig(data []byte) (*Config, error) {
	config := &Config{}
	if err := json.Unmarshal(data, &config); err != nil {
		syntax, ok := err.(*json.SyntaxError)
		if !ok {
			return nil, fmt.Errorf(
				"Could not parse configuration: %s",
				err)
		}
		return nil, newJSONParseError(data, syntax)
	}
	return config, nil
}

func newJSONParseError(js []byte, syntax *json.SyntaxError) error {
	line, col, err := highlightError(js, syntax.Offset)
	return fmt.Errorf("Parse error at line:col [%d:%d]: %s\n%s", line, col, syntax, err)
}

func highlightError(data []byte, pos int64) (int, int, string) {
	prevLine := ""
	thisLine := ""
	highlight := ""
	line := 1
	col := pos
	offset := int64(0)
	r := bytes.NewReader(data)
	scanner := bufio.NewScanner(r)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		prevLine = thisLine
		thisLine = fmt.Sprintf("%5d: %s\n", line, scanner.Text())
		readBytes := int64(len(scanner.Bytes()))
		offset += readBytes
		if offset >= pos-1 {
			highlight = fmt.Sprintf("%s^", strings.Repeat("-", int(7+col-1)))
			break
		}
		col -= readBytes + 1
		line++
	}
	return line, int(col), fmt.Sprintf("%s%s%s", prevLine, thisLine, highlight)
}

func argsToCmd(args []string) *exec.Cmd {
	if len(args) == 0 {
		return nil
	}
	if len(args) > 1 {
		return exec.Command(args[0], args[1:]...)
	}
	return exec.Command(args[0])
}

func strToCmd(command string) *exec.Cmd {
	if command != "" {
		return argsToCmd(strings.Split(strings.TrimSpace(command), " "))
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Arne Roomann-Kurrik
//
// 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 twodee

import (
	"fmt"
	"github.com/kurrik/Go-SDL/mixer"
	"github.com/kurrik/Go-SDL/sdl"
)

type SoundEffect struct {
	chunk *mixer.Chunk
}

func NewSoundEffect(path string) (s *SoundEffect, err error) {
	s = &SoundEffect{
		chunk: mixer.LoadWAV(path),
	}
	if s.chunk == nil {
		err = fmt.Errorf("Could not load sound effect: %v", sdl.GetError())
	}
	return
}

func (s *SoundEffect) Delete() {
	s.chunk.Free()
}

func (s *SoundEffect) Play(times int) {
	s.chunk.PlayChannel(-1, times-1)
}

func (s *SoundEffect) PlayChannel(channel int, times int) {
	s.chunk.PlayChannel(channel, times-1)
}
<commit_msg>Added ability to set sound effect volume<commit_after>// Copyright 2014 Arne Roomann-Kurrik
//
// 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 twodee

import (
	"fmt"
	"github.com/kurrik/Go-SDL/mixer"
	"github.com/kurrik/Go-SDL/sdl"
)

type SoundEffect struct {
	chunk *mixer.Chunk
}

func NewSoundEffect(path string) (s *SoundEffect, err error) {
	s = &SoundEffect{
		chunk: mixer.LoadWAV(path),
	}
	if s.chunk == nil {
		err = fmt.Errorf("Could not load sound effect: %v", sdl.GetError())
	}
	return
}

func (s *SoundEffect) Delete() {
	s.chunk.Free()
}

func (s *SoundEffect) Play(times int) {
	s.chunk.PlayChannel(-1, times-1)
}

func (s *SoundEffect) PlayChannel(channel int, times int) {
	s.chunk.PlayChannel(channel, times-1)
}

func (s *SoundEffect) SetVolume(volume int) {
	s.chunk.Volume(volume)
}
<|endoftext|>


<commit_before>package alicloud

import (
	"bytes"
	"fmt"
	"log"
	"regexp"
	"sort"

	"github.com/denverdino/aliyungo/ecs"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
	"time"
)

func dataSourceAlicloudImage() *schema.Resource {
	return &schema.Resource{
		Read: dataSourceAlicloudImageRead,

		Schema: map[string]*schema.Schema{
			//"filter": dataSourceFiltersSchema(),
			"name_regex": {
				Type:         schema.TypeString,
				Optional:     true,
				ForceNew:     true,
				ValidateFunc: validateNameRegex,
			},
			"most_recent": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
				ForceNew: true,
			},
			"owners": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},
			// Computed values.
			"architecture": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"creation_time": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"description": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"image_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"image_location": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"image_owner_alias": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"os_type": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"os_name": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"name": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"platform": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"is_self_shared": {
				Type:     schema.TypeBool,
				Computed: true,
			},
			"status": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"state": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"size": {
				Type:     schema.TypeString,
				Computed: true,
			},
			// Complex computed values
			"disk_device_mappings": {
				Type:     schema.TypeSet,
				Computed: true,
				Set:      imageDiskDeviceMappingHash,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"device": {
							Type:     schema.TypeString,
							Computed: true,
						},
						"size": {
							Type:     schema.TypeString,
							Computed: true,
						},
						"snapshot_id": {
							Type:     schema.TypeString,
							Computed: true,
						},
					},
				},
			},
			"product_code": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"tags": tagsSchema(),
		},
	}
}

// dataSourceAlicloudImageDescriptionRead performs the Alicloud Image lookup.
func dataSourceAlicloudImageRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AliyunClient).ecsconn

	//filters, filtersOk := d.GetOk("filter")
	nameRegex, nameRegexOk := d.GetOk("name_regex")
	owners, ownersOk := d.GetOk("owners")

	//if executableUsersOk == false && filtersOk == false && nameRegexOk == false && ownersOk == false {
	//	return fmt.Errorf("One of executable_users, filters, name_regex, or owners must be assigned")
	//}
	if nameRegexOk == false && ownersOk == false {
		return fmt.Errorf("One of name_regex, or owners must be assigned")
	}

	params := &ecs.DescribeImagesArgs{
		RegionId: getRegion(d, meta),
	}

	//if filtersOk {
	//	params.Filters = buildAwsDataSourceFilters(filters.(*schema.Set))
	//}
	if ownersOk {
		params.ImageOwnerAlias = ecs.ImageOwnerAlias(owners.(string))
	}

	resp, _, err := conn.DescribeImages(params)
	if err != nil {
		return err
	}

	var filteredImages []ecs.ImageType
	if nameRegexOk {
		r := regexp.MustCompile(nameRegex.(string))
		for _, image := range resp {
			// Check for a very rare case where the response would include no
			// image name. No name means nothing to attempt a match against,
			// therefore we are skipping such image.
			if image.ImageName == "" {
				log.Printf("[WARN] Unable to find Image name to match against "+
					"for image ID %q, nothing to do.",
					image.ImageId)
				continue
			}
			if r.MatchString(image.ImageName) {
				filteredImages = append(filteredImages, image)
			}
		}
	} else {
		filteredImages = resp[:]
	}

	var image ecs.ImageType
	if len(filteredImages) < 1 {
		return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.")
	}

	if len(filteredImages) > 1 {
		recent := d.Get("most_recent").(bool)
		log.Printf("[DEBUG] alicloud_image - multiple results found and `most_recent` is set to: %t", recent)
		if recent {
			image = mostRecentImage(filteredImages)
		} else {
			return fmt.Errorf("Your query returned more than one result. Please try a more " +
				"specific search criteria, or set `most_recent` attribute to true.")
		}
	} else {
		// Query returned single result.
		image = filteredImages[0]
	}
	d.Set("image_location", getRegion(d, meta))

	log.Printf("[DEBUG] alicloud_image - Single Image found: %s", image.ImageId)
	return imageDescriptionAttributes(d, image)
}

type imageSort []ecs.ImageType

func (a imageSort) Len() int      { return len(a) }
func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a imageSort) Less(i, j int) bool {
	itime, _ := time.Parse(time.RFC3339, a[i].CreationTime.String())
	jtime, _ := time.Parse(time.RFC3339, a[j].CreationTime.String())
	return itime.Unix() < jtime.Unix()
}

// Returns the most recent Image out of a slice of images.
func mostRecentImage(images []ecs.ImageType) ecs.ImageType {
	sortedImages := images
	sort.Sort(imageSort(sortedImages))
	return sortedImages[len(sortedImages)-1]
}

// populate the numerous fields that the image description returns.
func imageDescriptionAttributes(d *schema.ResourceData, image ecs.ImageType) error {
	// Simple attributes first
	d.SetId(image.ImageId)
	d.Set("architecture", image.Architecture)
	d.Set("creation_time", image.CreationTime)
	d.Set("description", image.Description)
	d.Set("image_id", image.ImageId)
	d.Set("image_owner_alias", image.ImageOwnerAlias)
	d.Set("os_name", image.OSName)
	d.Set("name", image.ImageName)
	//if image.Platform != nil {
	//	d.Set("platform", image.Platform)
	//}
	//d.Set("is_self_shared", image.Public)
	d.Set("status", image.Status)
	d.Set("state", image.Status)
	d.Set("size", image.Size)
	d.Set("product_code", image.ProductCode)
	//d.Set("tags", tagsToMap(image.tags))
	// Complex types get their own functions
	if err := d.Set("disk_device_mappings", imageDiskDeviceMappings(image.DiskDeviceMappings.DiskDeviceMapping)); err != nil {
		return err
	}

	return nil
}

// Returns a set of disk device mappings.
func imageDiskDeviceMappings(m []ecs.DiskDeviceMapping) *schema.Set {
	s := &schema.Set{
		F: imageDiskDeviceMappingHash,
	}
	for _, v := range m {
		mapping := map[string]interface{}{
			"device":      v.Device,
			"size":        v.Size,
			"snapshot_id": v.SnapshotId,
		}

		log.Printf("[DEBUG] alicloud_image - adding disk device mapping: %v", mapping)
		s.Add(mapping)
	}

	return s
}

// Generates a hash for the set hash function used by the disk_device_mappings
// attribute.
func imageDiskDeviceMappingHash(v interface{}) int {
	var buf bytes.Buffer
	// All keys added in alphabetical order.
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["device"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["size"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))

	return hashcode.String(buf.String())
}
<commit_msg>remove data_source_alicloud_image.go<commit_after><|endoftext|>


<commit_before>// cli_func
package websql

import (
	"encoding/json"
	"errors"
	"math"
	"strings"

	"github.com/elgs/gostrgen"
	"github.com/satori/go.uuid"
)

func (this *WebSQL) processCliCommand(message []byte) (string, error) {
	cliCommand := &Command{}
	json.Unmarshal(message, cliCommand)
	if this.service.Secret != cliCommand.Secret {
		return "", errors.New("Failed to validate secret.")
	}
	switch cliCommand.Type {
	case "CLI_DN_LIST":
		return this.masterData.ListDataNodes(cliCommand.Data), nil
	case "CLI_DN_ADD":
		dataNode := &DataNode{}
		err := json.Unmarshal([]byte(cliCommand.Data), dataNode)
		if err != nil {
			return "", err
		}
		id := strings.Replace(uuid.NewV4().String(), "-", "", -1)
		dataNode.Id = id
		err = this.masterData.AddDataNode(dataNode)
		if err != nil {
			return "", err
		}
	case "CLI_DN_UPDATE":
		dataNode := &DataNode{}
		err := json.Unmarshal([]byte(cliCommand.Data), dataNode)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateDataNode(dataNode)
		if err != nil {
			return "", err
		}
	case "CLI_DN_REMOVE":
		err := this.masterData.RemoveDataNode(cliCommand.Data)
		if err != nil {
			return "", err
		}
	case "CLI_APP_LIST":
		return this.masterData.ListApps(cliCommand.Data), nil
	case "CLI_APP_ADD":
		app := &App{}
		err := json.Unmarshal([]byte(cliCommand.Data), app)
		if err != nil {
			return "", err
		}
		id := strings.Replace(uuid.NewV4().String(), "-", "", -1)
		app.Id = id
		name := app.Name
		namePrefix := name[:int(math.Min(float64(len(name)), 8))]
		dbName, err := gostrgen.RandGen(16-len(namePrefix), gostrgen.LowerDigit, "", "")
		if err != nil {
			return "", err
		}
		app.DbName = dbName
		err = this.masterData.AddApp(app)
		if err != nil {
			return "", err
		}
	case "CLI_APP_UPDATE":
		app := &App{}
		err := json.Unmarshal([]byte(cliCommand.Data), app)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateApp(app)
		if err != nil {
			return "", err
		}
	case "CLI_APP_REMOVE":
		err := this.masterData.RemoveApp(cliCommand.Data)
		if err != nil {
			return "", err
		}
	case "CLI_QUERY_ADD":
		query := &Query{}
		err := json.Unmarshal([]byte(cliCommand.Data), query)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddQuery(query)
		if err != nil {
			return "", err
		}
	case "CLI_QUERY_UPDATE":
		query := &Query{}
		err := json.Unmarshal([]byte(cliCommand.Data), query)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateQuery(query)
		if err != nil {
			return "", err
		}
	case "CLI_QUERY_RELOAD_ALL":
		err := this.masterData.ReloadAllQueries(cliCommand.Data)
		if err != nil {
			return "", err
		}
	case "CLI_QUERY_REMOVE":
		query := &Query{}
		err := json.Unmarshal([]byte(cliCommand.Data), query)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveQuery(query.Id, query.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_ADD":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_UPDATE":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_REMOVE":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveJob(job.Id, job.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_START":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.StartJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_RESTART":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.RestartJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_STOP":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.StopJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_TOKEN_ADD":
		token := &Token{}
		err := json.Unmarshal([]byte(cliCommand.Data), token)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddToken(token)
		if err != nil {
			return "", err
		}
	case "CLI_TOKEN_UPDATE":
		token := &Token{}
		err := json.Unmarshal([]byte(cliCommand.Data), token)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateToken(token)
		if err != nil {
			return "", err
		}
	case "CLI_TOKEN_REMOVE":
		token := &Token{}
		err := json.Unmarshal([]byte(cliCommand.Data), token)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveToken(token.Id, token.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_LI_ADD":
		li := &LocalInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), li)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddLI(li)
		if err != nil {
			return "", err
		}
	case "CLI_LI_UPDATE":
		li := &LocalInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), li)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateLI(li)
		if err != nil {
			return "", err
		}
	case "CLI_LI_REMOVE":
		li := &LocalInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), li)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveLI(li.Id, li.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_RI_ADD":
		ri := &RemoteInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), ri)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddRI(ri)
		if err != nil {
			return "", err
		}
	case "CLI_RI_UPDATE":
		ri := &RemoteInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), ri)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateRI(ri)
		if err != nil {
			return "", err
		}
	case "CLI_RI_REMOVE":
		ri := &RemoteInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), ri)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveRI(ri.Id, ri.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_SHOW_MASTER":
		masterDataBytes, err := json.Marshal(this.masterData)
		if err != nil {
			return "", err
		}
		return string(masterDataBytes), nil
	case "CLI_SHOW_API_NODES":
		apiNodesBytes, err := json.Marshal(this.apiNodes)
		if err != nil {
			return "", err
		}
		return string(apiNodesBytes), nil
	case "CLI_PROPAGATE":
		err := this.masterData.Propagate()
		if err != nil {
			return "", err
		}
		return "", nil
	}
	return "", nil
}
<commit_msg>Update.<commit_after>// cli_func
package websql

import (
	"encoding/json"
	"errors"
	"math"
	"strings"

	"github.com/elgs/gostrgen"
	"github.com/satori/go.uuid"
)

func (this *WebSQL) processCliCommand(message []byte) (string, error) {
	cliCommand := &Command{}
	json.Unmarshal(message, cliCommand)
	if this.service.Secret != cliCommand.Secret {
		return "", errors.New("Failed to validate secret.")
	}
	switch cliCommand.Type {
	case "CLI_DN_LIST":
		return this.masterData.ListDataNodes(cliCommand.Data), nil
	case "CLI_DN_ADD":
		dataNode := &DataNode{}
		err := json.Unmarshal([]byte(cliCommand.Data), dataNode)
		if err != nil {
			return "", err
		}
		id := strings.Replace(uuid.NewV4().String(), "-", "", -1)
		dataNode.Id = id
		err = this.masterData.AddDataNode(dataNode)
		if err != nil {
			return "", err
		}
	case "CLI_DN_UPDATE":
		dataNode := &DataNode{}
		err := json.Unmarshal([]byte(cliCommand.Data), dataNode)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateDataNode(dataNode)
		if err != nil {
			return "", err
		}
	case "CLI_DN_REMOVE":
		err := this.masterData.RemoveDataNode(cliCommand.Data)
		if err != nil {
			return "", err
		}
	case "CLI_APP_LIST":
		return this.masterData.ListApps(cliCommand.Data), nil
	case "CLI_APP_ADD":
		app := &App{}
		err := json.Unmarshal([]byte(cliCommand.Data), app)
		if err != nil {
			return "", err
		}
		id := strings.Replace(uuid.NewV4().String(), "-", "", -1)
		app.Id = id
		name := app.Name
		namePrefix := name[:int(math.Min(float64(len(name)), 8))]
		dbName, err := gostrgen.RandGen(16-len(namePrefix), gostrgen.LowerDigit, "", "")
		if err != nil {
			return "", err
		}
		app.DbName = namePrefix + dbName
		err = this.masterData.AddApp(app)
		if err != nil {
			return "", err
		}
	case "CLI_APP_UPDATE":
		app := &App{}
		err := json.Unmarshal([]byte(cliCommand.Data), app)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateApp(app)
		if err != nil {
			return "", err
		}
	case "CLI_APP_REMOVE":
		err := this.masterData.RemoveApp(cliCommand.Data)
		if err != nil {
			return "", err
		}
	case "CLI_QUERY_ADD":
		query := &Query{}
		err := json.Unmarshal([]byte(cliCommand.Data), query)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddQuery(query)
		if err != nil {
			return "", err
		}
	case "CLI_QUERY_UPDATE":
		query := &Query{}
		err := json.Unmarshal([]byte(cliCommand.Data), query)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateQuery(query)
		if err != nil {
			return "", err
		}
	case "CLI_QUERY_RELOAD_ALL":
		err := this.masterData.ReloadAllQueries(cliCommand.Data)
		if err != nil {
			return "", err
		}
	case "CLI_QUERY_REMOVE":
		query := &Query{}
		err := json.Unmarshal([]byte(cliCommand.Data), query)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveQuery(query.Id, query.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_ADD":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_UPDATE":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_REMOVE":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveJob(job.Id, job.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_START":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.StartJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_RESTART":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.RestartJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_JOB_STOP":
		job := &Job{}
		err := json.Unmarshal([]byte(cliCommand.Data), job)
		if err != nil {
			return "", err
		}
		err = this.masterData.StopJob(job)
		if err != nil {
			return "", err
		}
	case "CLI_TOKEN_ADD":
		token := &Token{}
		err := json.Unmarshal([]byte(cliCommand.Data), token)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddToken(token)
		if err != nil {
			return "", err
		}
	case "CLI_TOKEN_UPDATE":
		token := &Token{}
		err := json.Unmarshal([]byte(cliCommand.Data), token)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateToken(token)
		if err != nil {
			return "", err
		}
	case "CLI_TOKEN_REMOVE":
		token := &Token{}
		err := json.Unmarshal([]byte(cliCommand.Data), token)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveToken(token.Id, token.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_LI_ADD":
		li := &LocalInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), li)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddLI(li)
		if err != nil {
			return "", err
		}
	case "CLI_LI_UPDATE":
		li := &LocalInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), li)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateLI(li)
		if err != nil {
			return "", err
		}
	case "CLI_LI_REMOVE":
		li := &LocalInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), li)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveLI(li.Id, li.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_RI_ADD":
		ri := &RemoteInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), ri)
		if err != nil {
			return "", err
		}
		err = this.masterData.AddRI(ri)
		if err != nil {
			return "", err
		}
	case "CLI_RI_UPDATE":
		ri := &RemoteInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), ri)
		if err != nil {
			return "", err
		}
		err = this.masterData.UpdateRI(ri)
		if err != nil {
			return "", err
		}
	case "CLI_RI_REMOVE":
		ri := &RemoteInterceptor{}
		err := json.Unmarshal([]byte(cliCommand.Data), ri)
		if err != nil {
			return "", err
		}
		err = this.masterData.RemoveRI(ri.Id, ri.AppId)
		if err != nil {
			return "", err
		}
	case "CLI_SHOW_MASTER":
		masterDataBytes, err := json.Marshal(this.masterData)
		if err != nil {
			return "", err
		}
		return string(masterDataBytes), nil
	case "CLI_SHOW_API_NODES":
		apiNodesBytes, err := json.Marshal(this.apiNodes)
		if err != nil {
			return "", err
		}
		return string(apiNodesBytes), nil
	case "CLI_PROPAGATE":
		err := this.masterData.Propagate()
		if err != nil {
			return "", err
		}
		return "", nil
	}
	return "", nil
}
<|endoftext|>


<commit_before>package cli_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/phylake/go-cli"
)

//
// testCmd implement cli.Command
//

type testCmd struct {
	commandName string
	shortHelp   string
	longHelp    string
	execute     bool
	subCommands []cli.Command

	executeCalled bool
	executeArgs   []string
	executeStdin  *os.File
}

func (c *testCmd) Name() string {
	return c.commandName
}

func (c *testCmd) ShortHelp() string {
	return c.shortHelp
}

func (c *testCmd) LongHelp() string {
	return c.longHelp
}

func (c *testCmd) Execute(args []string, stdin *os.File) bool {
	c.executeCalled = true
	c.executeArgs = args
	c.executeStdin = stdin
	return c.execute
}

func (c *testCmd) SubCommands() []cli.Command {
	return c.subCommands
}

func newDriver(args []string) (*cli.Driver, *bytes.Buffer) {
	var stdout bytes.Buffer

	stdin, err := ioutil.TempFile("", "go-cli")
	Expect(err).To(BeNil())

	// program name is ARGV[0]
	args = append([]string{"go-cli"}, args...)

	d := cli.NewWithEnv(args, stdin, &stdout)
	return d, &stdout
}

//
// BEGIN tests
//

var _ = Describe("CLI", func() {

	Context("formatting", func() {

		It("trims newlines out of ShortHelp()", func() {

			d, stdout := newDriver(nil)

			d.RegisterRoot(&testCmd{
				longHelp: "program description",
				subCommands: []cli.Command{
					&testCmd{
						commandName: "foo",
						shortHelp:   "short\n help",
					},
				},
			})

			d.ParseInput()

			// be careful of whitespace in this string
			expected := `program description

Commands:
    foo - short help
`
			Expect(stdout.String()).To(Equal(expected))
		})

		It("pads the longest command name", func() {

			d, stdout := newDriver(nil)

			d.RegisterRoot(&testCmd{
				longHelp: "program description",
				subCommands: []cli.Command{
					&testCmd{
						commandName: "foo",
						shortHelp:   "short help",
					},

					&testCmd{
						commandName: "longerFoo",
						shortHelp:   "short help",
					},
				},
			})

			d.ParseInput()

			// be careful of whitespace in this string
			expected := `program description

Commands:
    foo       - short help
    longerFoo - short help
`
			Expect(stdout.String()).To(Equal(expected))
		})
	})

	It("executes a non-root command", func() {

		d, stdout := newDriver([]string{"foo"})

		cmd := &testCmd{
			commandName: "foo",
		}
		d.RegisterRoot(&testCmd{
			subCommands: []cli.Command{cmd},
		})

		d.ParseInput()

		fmt.Fprintln(GinkgoWriter, stdout.String())
		Expect(cmd.executeCalled).To(BeTrue())
	})

	It("passes remaining args to Execute", func() {

		d, _ := newDriver([]string{"foo", "arg1", "arg2"})

		cmd := &testCmd{
			commandName: "foo",
		}
		d.RegisterRoot(&testCmd{
			subCommands: []cli.Command{cmd},
		})

		d.ParseInput()

		Expect(cmd.executeCalled).To(BeTrue())
		Expect(cmd.executeArgs).To(Equal([]string{"arg1", "arg2"}))
	})
})
<commit_msg>moved more relevant tests up<commit_after>package cli_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/phylake/go-cli"
)

//
// testCmd implement cli.Command
//

type testCmd struct {
	commandName string
	shortHelp   string
	longHelp    string
	execute     bool
	subCommands []cli.Command

	executeCalled bool
	executeArgs   []string
	executeStdin  *os.File
}

func (c *testCmd) Name() string {
	return c.commandName
}

func (c *testCmd) ShortHelp() string {
	return c.shortHelp
}

func (c *testCmd) LongHelp() string {
	return c.longHelp
}

func (c *testCmd) Execute(args []string, stdin *os.File) bool {
	c.executeCalled = true
	c.executeArgs = args
	c.executeStdin = stdin
	return c.execute
}

func (c *testCmd) SubCommands() []cli.Command {
	return c.subCommands
}

func newDriver(args []string) (*cli.Driver, *bytes.Buffer) {
	var stdout bytes.Buffer

	stdin, err := ioutil.TempFile("", "go-cli")
	Expect(err).To(BeNil())

	// program name is ARGV[0]
	args = append([]string{"go-cli"}, args...)

	d := cli.NewWithEnv(args, stdin, &stdout)
	return d, &stdout
}

//
// BEGIN tests
//

var _ = Describe("CLI", func() {

	It("executes a non-root command", func() {

		d, stdout := newDriver([]string{"foo"})

		cmd := &testCmd{
			commandName: "foo",
		}
		d.RegisterRoot(&testCmd{
			subCommands: []cli.Command{cmd},
		})

		d.ParseInput()

		fmt.Fprintln(GinkgoWriter, stdout.String())
		Expect(cmd.executeCalled).To(BeTrue())
	})

	It("passes remaining args to Execute", func() {

		d, _ := newDriver([]string{"foo", "arg1", "arg2"})

		cmd := &testCmd{
			commandName: "foo",
		}
		d.RegisterRoot(&testCmd{
			subCommands: []cli.Command{cmd},
		})

		d.ParseInput()

		Expect(cmd.executeCalled).To(BeTrue())
		Expect(cmd.executeArgs).To(Equal([]string{"arg1", "arg2"}))
	})

	Context("formatting", func() {

		It("trims newlines out of ShortHelp()", func() {

			d, stdout := newDriver(nil)

			d.RegisterRoot(&testCmd{
				longHelp: "program description",
				subCommands: []cli.Command{
					&testCmd{
						commandName: "foo",
						shortHelp:   "short\n help",
					},
				},
			})

			d.ParseInput()

			// be careful of whitespace in this string
			expected := `program description

Commands:
    foo - short help
`
			Expect(stdout.String()).To(Equal(expected))
		})

		It("pads the longest command name", func() {

			d, stdout := newDriver(nil)

			d.RegisterRoot(&testCmd{
				longHelp: "program description",
				subCommands: []cli.Command{
					&testCmd{
						commandName: "foo",
						shortHelp:   "short help",
					},

					&testCmd{
						commandName: "longerFoo",
						shortHelp:   "short help",
					},
				},
			})

			d.ParseInput()

			// be careful of whitespace in this string
			expected := `program description

Commands:
    foo       - short help
    longerFoo - short help
`
			Expect(stdout.String()).To(Equal(expected))
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2016 Netflix, Inc.
//
// 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 main

import (
	"flag"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime/debug"
	"sync"

	"github.com/netflix/rend/handlers"
	"github.com/netflix/rend/handlers/inmem"
	"github.com/netflix/rend/handlers/memcached"
	"github.com/netflix/rend/metrics"
	"github.com/netflix/rend/orcas"
	"github.com/netflix/rend/server"
)

func init() {
	// Set GOGC default explicitly
	if _, set := os.LookupEnv("GOGC"); !set {
		debug.SetGCPercent(100)
	}

	// Setting up signal handlers
	sigs := make(chan os.Signal)
	signal.Notify(sigs, os.Interrupt)

	go func() {
		<-sigs
		panic("Keyboard Interrupt")
	}()

	// http debug and metrics endpoint
	go http.ListenAndServe("localhost:11299", nil)

	// metrics output prefix
	metrics.SetPrefix("rend_")
}

// Flags
var (
	chunked bool
	l1sock  string
	l1inmem bool

	l2enabled bool
	l2sock    string

	locked      bool
	concurrency int
	multiReader bool

	port            int
	batchPort       int
	useDomainSocket bool
	sockPath        string
)

func init() {
	flag.BoolVar(&chunked, "chunked", false, "If --chunked is specified, the chunked handler is used for L1")
	flag.BoolVar(&l1inmem, "l1-inmem", false, "Use the debug in-memory in-process L1 cache")
	flag.StringVar(&l1sock, "l1-sock", "invalid.sock", "Specifies the unix socket to connect to L1")

	flag.BoolVar(&l2enabled, "l2-enabled", false, "Specifies if l2 is enabled")
	flag.StringVar(&l2sock, "l2-sock", "invalid.sock", "Specifies the unix socket to connect to L2. Only used if --l2-enabled is true.")

	flag.BoolVar(&locked, "locked", false, "Add locking to overall operations (above L1/L2 layers)")
	flag.IntVar(&concurrency, "concurrency", 8, "Concurrency level. 2^(concurrency) parallel operations permitted, assuming no collisions. Large values (>16) are likely useless and will eat up RAM. Default of 8 means 256 operations (on different keys) can happen in parallel.")
	flag.BoolVar(&multiReader, "multi-reader", true, "Allow (or disallow) multiple readers on the same key. If chunking is used, this will always be false and setting it to true will be ignored.")

	flag.IntVar(&port, "p", 11211, "External port to listen on")
	flag.IntVar(&batchPort, "bp", 11212, "External port to listen on for batch systems")
	flag.BoolVar(&useDomainSocket, "use-domain-socket", false, "Listen on a domain socket instead of a TCP port. --port will be ignored.")
	flag.StringVar(&sockPath, "sock-path", "/tmp/invalid.sock", "The socket path to listen on. Only valid in conjunction with --use-domain-socket.")

	flag.Parse()

	if concurrency >= 64 {
		panic("Concurrency cannot be more than 2^64")
	}
}

// And away we go
func main() {
	var l server.ListenArgs

	if useDomainSocket {
		l = server.ListenArgs{
			Type: server.ListenUnix,
			Path: sockPath,
		}
	} else {
		l = server.ListenArgs{
			Type: server.ListenTCP,
			Port: port,
		}
	}

	var o orcas.OrcaConst
	var h2 handlers.HandlerConst
	var h1 handlers.HandlerConst

	if l1inmem {
		h1 = inmem.New
	} else if chunked {
		h1 = memcached.Chunked(l1sock)
	} else {
		h1 = memcached.Regular(l1sock)
	}

	if l2enabled {
		o = orcas.L1L2
		h2 = memcached.Regular(l2sock)
	} else {
		o = orcas.L1Only
		h2 = handlers.NilHandler
	}

	// Add the locking wrapper if requested. The locking wrapper can either allow mutltiple readers
	// or not, with the same difference in semantics between a sync.Mutex and a sync.RWMutex. If
	// chunking is enabled, we want to ensure that stricter locking is enabled, since concurrent
	// sets into L1 with chunking can collide and cause data corruption.
	var lockset uint32
	if locked {
		if chunked || !multiReader {
			o, lockset = orcas.Locked(o, false, uint8(concurrency))
		} else {
			o, lockset = orcas.Locked(o, true, uint8(concurrency))
		}
	}

	go server.ListenAndServe(l, server.Default, o, h1, h2)

	if l2enabled {
		// If L2 is enabled, start the batch L1 / L2 orchestrator
		l = server.ListenArgs{
			Type: server.ListenTCP,
			Port: batchPort,
		}

		o := orcas.L1L2Batch

		if locked {
			o = orcas.LockedWithExisting(o, lockset)
		}

		go server.ListenAndServe(l, server.Default, o, h1, h2)
	}

	// Block forever
	wg := sync.WaitGroup{}
	wg.Add(1)
	wg.Wait()
}
<commit_msg>Adding switch to use batched handler for L1<commit_after>// Copyright 2016 Netflix, Inc.
//
// 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 main

import (
	"flag"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime/debug"
	"sync"

	"github.com/netflix/rend/handlers"
	"github.com/netflix/rend/handlers/inmem"
	"github.com/netflix/rend/handlers/memcached"
	"github.com/netflix/rend/metrics"
	"github.com/netflix/rend/orcas"
	"github.com/netflix/rend/server"
)

func init() {
	// Set GOGC default explicitly
	if _, set := os.LookupEnv("GOGC"); !set {
		debug.SetGCPercent(100)
	}

	// Setting up signal handlers
	sigs := make(chan os.Signal)
	signal.Notify(sigs, os.Interrupt)

	go func() {
		<-sigs
		panic("Keyboard Interrupt")
	}()

	// http debug and metrics endpoint
	go http.ListenAndServe("localhost:11299", nil)

	// metrics output prefix
	metrics.SetPrefix("rend_")
}

// Flags
var (
	chunked   bool
	l1sock    string
	l1inmem   bool
	l1batched bool

	l2enabled bool
	l2sock    string

	locked      bool
	concurrency int
	multiReader bool

	port            int
	batchPort       int
	useDomainSocket bool
	sockPath        string
)

func init() {
	flag.BoolVar(&chunked, "chunked", false, "If --chunked is specified, the chunked handler is used for L1")
	flag.BoolVar(&l1inmem, "l1-inmem", false, "Use the debug in-memory in-process L1 cache")
	flag.StringVar(&l1sock, "l1-sock", "invalid.sock", "Specifies the unix socket to connect to L1")
	flag.BoolVar(&l1batched, "l1-batched", false, "Uses the batching handler for L1")

	flag.BoolVar(&l2enabled, "l2-enabled", false, "Specifies if l2 is enabled")
	flag.StringVar(&l2sock, "l2-sock", "invalid.sock", "Specifies the unix socket to connect to L2. Only used if --l2-enabled is true.")

	flag.BoolVar(&locked, "locked", false, "Add locking to overall operations (above L1/L2 layers)")
	flag.IntVar(&concurrency, "concurrency", 8, "Concurrency level. 2^(concurrency) parallel operations permitted, assuming no collisions. Large values (>16) are likely useless and will eat up RAM. Default of 8 means 256 operations (on different keys) can happen in parallel.")
	flag.BoolVar(&multiReader, "multi-reader", true, "Allow (or disallow) multiple readers on the same key. If chunking is used, this will always be false and setting it to true will be ignored.")

	flag.IntVar(&port, "p", 11211, "External port to listen on")
	flag.IntVar(&batchPort, "bp", 11212, "External port to listen on for batch systems")
	flag.BoolVar(&useDomainSocket, "use-domain-socket", false, "Listen on a domain socket instead of a TCP port. --port will be ignored.")
	flag.StringVar(&sockPath, "sock-path", "/tmp/invalid.sock", "The socket path to listen on. Only valid in conjunction with --use-domain-socket.")

	flag.Parse()

	if concurrency >= 64 {
		panic("Concurrency cannot be more than 2^64")
	}
}

// And away we go
func main() {
	var l server.ListenArgs

	if useDomainSocket {
		l = server.ListenArgs{
			Type: server.ListenUnix,
			Path: sockPath,
		}
	} else {
		l = server.ListenArgs{
			Type: server.ListenTCP,
			Port: port,
		}
	}

	var o orcas.OrcaConst
	var h2 handlers.HandlerConst
	var h1 handlers.HandlerConst

	// Choose the proper L1 handler
	if l1inmem {
		h1 = inmem.New
	} else if chunked {
		h1 = memcached.Chunked(l1sock)
	} else if l1batched {
		h1 = memcached.Batched(l1sock)
	} else {
		h1 = memcached.Regular(l1sock)
	}

	if l2enabled {
		o = orcas.L1L2
		h2 = memcached.Regular(l2sock)
	} else {
		o = orcas.L1Only
		h2 = handlers.NilHandler
	}

	// Add the locking wrapper if requested. The locking wrapper can either allow mutltiple readers
	// or not, with the same difference in semantics between a sync.Mutex and a sync.RWMutex. If
	// chunking is enabled, we want to ensure that stricter locking is enabled, since concurrent
	// sets into L1 with chunking can collide and cause data corruption.
	var lockset uint32
	if locked {
		if chunked || !multiReader {
			o, lockset = orcas.Locked(o, false, uint8(concurrency))
		} else {
			o, lockset = orcas.Locked(o, true, uint8(concurrency))
		}
	}

	go server.ListenAndServe(l, server.Default, o, h1, h2)

	if l2enabled {
		// If L2 is enabled, start the batch L1 / L2 orchestrator
		l = server.ListenArgs{
			Type: server.ListenTCP,
			Port: batchPort,
		}

		o := orcas.L1L2Batch

		if locked {
			o = orcas.LockedWithExisting(o, lockset)
		}

		go server.ListenAndServe(l, server.Default, o, h1, h2)
	}

	// Block forever
	wg := sync.WaitGroup{}
	wg.Add(1)
	wg.Wait()
}
<|endoftext|>