<commit_before>package clicommand

import (
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/buildkite/agent/agent"
	"github.com/buildkite/agent/cliconfig"
	"github.com/buildkite/agent/logger"
	"github.com/urfave/cli"
)

var StartDescription = `Usage:

   buildkite-agent start [arguments...]

Description:

   When a job is ready to run it will call the "bootstrap-script"
   and pass it all the environment variables required for the job to run.
   This script is responsible for checking out the code, and running the
   actual build script defined in the pipeline.

   The agent will run any jobs within a PTY (pseudo terminal) if available.

Example:

   $ buildkite-agent start --token xxx`

type AgentStartConfig struct {
	Config                       string   `cli:"config"`
	Token                        string   `cli:"token" validate:"required"`
	Name                         string   `cli:"name"`
	Priority                     string   `cli:"priority"`
	DisconnectAfterJob           bool     `cli:"disconnect-after-job"`
	DisconnectAfterJobTimeout    int      `cli:"disconnect-after-job-timeout"`
	BootstrapScript              string   `cli:"bootstrap-script" normalize:"filepath" validate:"required"`
	BuildPath                    string   `cli:"build-path" normalize:"filepath" validate:"required"`
	HooksPath                    string   `cli:"hooks-path" normalize:"filepath"`
	PluginsPath                  string   `cli:"plugins-path" normalize:"filepath"`
	Tags                         []string `cli:"tags"`
	TagsFromEC2                  bool     `cli:"tags-from-ec2"`
	TagsFromEC2Tags              bool     `cli:"tags-from-ec2-tags"`
	TagsFromGCP                  bool     `cli:"tags-from-gcp"`
	WaitForEC2TagsTimeout        string   `cli:"wait-for-ec2-tags-timeout"`
	GitCloneFlags                string   `cli:"git-clone-flags"`
	GitCleanFlags                string   `cli:"git-clean-flags"`
	NoColor                      bool     `cli:"no-color"`
	NoSSHFingerprintVerification bool     `cli:"no-automatic-ssh-fingerprint-verification"`
	NoCommandEval                bool     `cli:"no-command-eval"`
	NoPlugins                    bool     `cli:"no-plugins"`
	NoPTY                        bool     `cli:"no-pty"`
	TimestampLines               bool     `cli:"timestamp-lines"`
	Endpoint                     string   `cli:"endpoint" validate:"required"`
	Debug                        bool     `cli:"debug"`
	DebugHTTP                    bool     `cli:"debug-http"`
	Experiments                  []string `cli:"experiment"`
	/* Deprecated */
	MetaData        []string `cli:"meta-data" deprecated-and-renamed-to:"Tags"`
	MetaDataEC2     bool     `cli:"meta-data-ec2" deprecated-and-renamed-to:"TagsFromEC2"`
	MetaDataEC2Tags bool     `cli:"meta-data-ec2-tags" deprecated-and-renamed-to:"TagsFromEC2Tags"`
	MetaDataGCP     bool     `cli:"meta-data-gcp" deprecated-and-renamed-to:"TagsFromGCP"`
}

func DefaultConfigFilePaths() (paths []string) {
	// Toggle beetwen windows an *nix paths
	if runtime.GOOS == "windows" {
		paths = []string{
			"$USERPROFILE\\AppData\\Local\\BuildkiteAgent\\buildkite-agent.cfg",
		}
	} else {
		paths = []string{
			"$HOME/.buildkite-agent/buildkite-agent.cfg",
			"/usr/local/etc/buildkite-agent/buildkite-agent.cfg",
			"/etc/buildkite-agent/buildkite-agent.cfg",
		}
	}

	// Also check to see if there's a buildkite-agent.cfg in the folder
	// that the binary is running in.
	pathToBinary, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err == nil {
		pathToRelativeConfig := filepath.Join(pathToBinary, "buildkite-agent.cfg")
		paths = append([]string{pathToRelativeConfig}, paths...)
	}

	return
}

var AgentStartCommand = cli.Command{
	Name:        "start",
	Usage:       "Starts a Buildkite agent",
	Description: StartDescription,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:   "config",
			Value:  "",
			Usage:  "Path to a configuration file",
			EnvVar: "BUILDKITE_AGENT_CONFIG",
		},
		cli.StringFlag{
			Name:   "token",
			Value:  "",
			Usage:  "Your account agent token",
			EnvVar: "BUILDKITE_AGENT_TOKEN",
		},
		cli.StringFlag{
			Name:   "name",
			Value:  "",
			Usage:  "The name of the agent",
			EnvVar: "BUILDKITE_AGENT_NAME",
		},
		cli.StringFlag{
			Name:   "priority",
			Value:  "",
			Usage:  "The priority of the agent (higher priorities are assigned work first)",
			EnvVar: "BUILDKITE_AGENT_PRIORITY",
		},
		cli.BoolFlag{
			Name:   "disconnect-after-job",
			Usage:  "Disconnect the agent after running a job",
			EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_JOB",
		},
		cli.IntFlag{
			Name:   "disconnect-after-job-timeout",
			Value:  120,
			Usage:  "When --disconnect-after-job is specified, the number of seconds to wait for a job before shutting down",
			EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_JOB_TIMEOUT",
		},
		cli.StringSliceFlag{
			Name:   "tags",
			Value:  &cli.StringSlice{},
			Usage:  "A comma-separated list of tags for the agent (e.g. \"linux\" or \"mac,xcode=8\")",
			EnvVar: "BUILDKITE_AGENT_TAGS",
		},
		cli.BoolFlag{
			Name:   "tags-from-ec2",
			Usage:  "Include the host's EC2 meta-data as tags (instance-id, instance-type, and ami-id)",
			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2",
		},
		cli.BoolFlag{
			Name:   "tags-from-ec2-tags",
			Usage:  "Include the host's EC2 tags as tags",
			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_TAGS",
		},
		cli.BoolFlag{
			Name:   "tags-from-gcp",
			Usage:  "Include the host's Google Cloud meta-data as tags (instance-id, machine-type, preemptible, project-id, region, and zone)",
			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_GCP",
		},
		cli.DurationFlag{
			Name:   "wait-for-ec2-tags-timeout",
			Usage:  "The amount of time to wait for tags from EC2 before proceeding",
			EnvVar: "BUILDKITE_AGENT_WAIT_FOR_EC2_TAGS_TIMEOUT",
			Value:  time.Second * 10,
		},
		cli.StringFlag{
			Name:   "git-clone-flags",
			Value:  "-v",
			Usage:  "Flags to pass to the \"git clone\" command",
			EnvVar: "BUILDKITE_GIT_CLONE_FLAGS",
		},
		cli.StringFlag{
			Name:   "git-clean-flags",
			Value:  "-fxdq",
			Usage:  "Flags to pass to \"git clean\" command",
			EnvVar: "BUILDKITE_GIT_CLEAN_FLAGS",
		},
		cli.StringFlag{
			Name:   "bootstrap-script",
			Value:  "buildkite-agent bootstrap",
			Usage:  "Path to the bootstrap script",
			EnvVar: "BUILDKITE_BOOTSTRAP_SCRIPT_PATH",
		},
		cli.StringFlag{
			Name:   "build-path",
			Value:  "",
			Usage:  "Path to where the builds will run from",
			EnvVar: "BUILDKITE_BUILD_PATH",
		},
		cli.StringFlag{
			Name:   "hooks-path",
			Value:  "",
			Usage:  "Directory where the hook scripts are found",
			EnvVar: "BUILDKITE_HOOKS_PATH",
		},
		cli.StringFlag{
			Name:   "plugins-path",
			Value:  "",
			Usage:  "Directory where the plugins are saved to",
			EnvVar: "BUILDKITE_PLUGINS_PATH",
		},
		cli.BoolFlag{
			Name:   "timestamp-lines",
			Usage:  "Prepend timestamps on each line of output.",
			EnvVar: "BUILDKITE_TIMESTAMP_LINES",
		},
		cli.BoolFlag{
			Name:   "no-pty",
			Usage:  "Do not run jobs within a pseudo terminal",
			EnvVar: "BUILDKITE_NO_PTY",
		},
		cli.BoolFlag{
			Name:   "no-automatic-ssh-fingerprint-verification",
			Usage:  "Don't automatically verify SSH fingerprints",
			EnvVar: "BUILDKITE_NO_AUTOMATIC_SSH_FINGERPRINT_VERIFICATION",
		},
		cli.BoolFlag{
			Name:   "no-command-eval",
			Usage:  "Don't allow this agent to run arbitrary console commands",
			EnvVar: "BUILDKITE_NO_COMMAND_EVAL",
		},
		cli.BoolFlag{
			Name:   "no-plugins",
			Usage:  "Don't allow this agent to load plugins",
			EnvVar: "BUILDKITE_NO_PLUGINS",
		},
		ExperimentsFlag,
		EndpointFlag,
		NoColorFlag,
		DebugFlag,
		DebugHTTPFlag,
		/* Deprecated flags which will be removed in v4 */
		cli.StringSliceFlag{
			Name:   "meta-data",
			Value:  &cli.StringSlice{},
			Hidden: true,
			EnvVar: "BUILDKITE_AGENT_META_DATA",
		},
		cli.BoolFlag{
			Name:   "meta-data-ec2",
			Hidden: true,
			EnvVar: "BUILDKITE_AGENT_META_DATA_EC2",
		},
		cli.BoolFlag{
			Name:   "meta-data-ec2-tags",
			Hidden: true,
			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_TAGS",
		},
		cli.BoolFlag{
			Name:   "meta-data-gcp",
			Hidden: true,
			EnvVar: "BUILDKITE_AGENT_META_DATA_GCP",
		},
	},
	Action: func(c *cli.Context) {
		// The configuration will be loaded into this struct
		cfg := AgentStartConfig{}

		// Setup the config loader. You'll see that we also path paths to
		// potential config files. The loader will use the first one it finds.
		loader := cliconfig.Loader{
			CLI:                    c,
			Config:                 &cfg,
			DefaultConfigFilePaths: DefaultConfigFilePaths(),
		}

		// Load the configuration
		if err := loader.Load(); err != nil {
			logger.Fatal("%s", err)
		}

		// Setup the any global configuration options
		HandleGlobalFlags(cfg)

		// Force some settings if on Windows (these aren't supported
		// yet)
		if runtime.GOOS == "windows" {
			cfg.NoPTY = true
		}

		// Make sure the DisconnectAfterJobTimeout value is correct
		if cfg.DisconnectAfterJob && cfg.DisconnectAfterJobTimeout < 120 {
			logger.Fatal("The timeout for `disconnect-after-job` must be at least 120 seconds")
		}

		var ec2TagTimeout time.Duration
		if t := cfg.WaitForEC2TagsTimeout; t != "" {
			var err error
			ec2TagTimeout, err = time.ParseDuration(t)
			if err != nil {
				logger.Fatal("Failed to parse ec2 tag timeout: %v", err)
			}
		}

		// Setup the agent
		pool := agent.AgentPool{
			Token:                 cfg.Token,
			Name:                  cfg.Name,
			Priority:              cfg.Priority,
			Tags:                  cfg.Tags,
			TagsFromEC2:           cfg.TagsFromEC2,
			TagsFromEC2Tags:       cfg.TagsFromEC2Tags,
			TagsFromGCP:           cfg.TagsFromGCP,
			WaitForEC2TagsTimeout: ec2TagTimeout,
			Endpoint:              cfg.Endpoint,
			AgentConfiguration: &agent.AgentConfiguration{
				BootstrapScript:            cfg.BootstrapScript,
				BuildPath:                  cfg.BuildPath,
				HooksPath:                  cfg.HooksPath,
				PluginsPath:                cfg.PluginsPath,
				GitCloneFlags:              cfg.GitCloneFlags,
				GitCleanFlags:              cfg.GitCleanFlags,
				SSHFingerprintVerification: !cfg.NoSSHFingerprintVerification,
				CommandEval:                !cfg.NoCommandEval,
				PluginsEnabled:             !cfg.NoPlugins,
				RunInPty:                   !cfg.NoPTY,
				TimestampLines:             cfg.TimestampLines,
				DisconnectAfterJob:         cfg.DisconnectAfterJob,
				DisconnectAfterJobTimeout:  cfg.DisconnectAfterJobTimeout,
			},
		}

		// Store the loaded config file path on the pool so we can
		// show it when the agent starts
		if loader.File != nil {
			pool.ConfigFilePath = loader.File.Path
		}

		// Start the agent pool
		if err := pool.Start(); err != nil {
			logger.Fatal("%s", err)
		}
	},
}
<commit_msg>Generate a default for bootstrap-script when empty<commit_after>package clicommand

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/buildkite/agent/agent"
	"github.com/buildkite/agent/cliconfig"
	"github.com/buildkite/agent/logger"
	"github.com/urfave/cli"
)

var StartDescription = `Usage:

   buildkite-agent start [arguments...]

Description:

   When a job is ready to run it will call the "bootstrap-script"
   and pass it all the environment variables required for the job to run.
   This script is responsible for checking out the code, and running the
   actual build script defined in the pipeline.

   The agent will run any jobs within a PTY (pseudo terminal) if available.

Example:

   $ buildkite-agent start --token xxx`

type AgentStartConfig struct {
	Config                       string   `cli:"config"`
	Token                        string   `cli:"token" validate:"required"`
	Name                         string   `cli:"name"`
	Priority                     string   `cli:"priority"`
	DisconnectAfterJob           bool     `cli:"disconnect-after-job"`
	DisconnectAfterJobTimeout    int      `cli:"disconnect-after-job-timeout"`
	BootstrapScript              string   `cli:"bootstrap-script" normalize:"filepath"`
	BuildPath                    string   `cli:"build-path" normalize:"filepath" validate:"required"`
	HooksPath                    string   `cli:"hooks-path" normalize:"filepath"`
	PluginsPath                  string   `cli:"plugins-path" normalize:"filepath"`
	Tags                         []string `cli:"tags"`
	TagsFromEC2                  bool     `cli:"tags-from-ec2"`
	TagsFromEC2Tags              bool     `cli:"tags-from-ec2-tags"`
	TagsFromGCP                  bool     `cli:"tags-from-gcp"`
	WaitForEC2TagsTimeout        string   `cli:"wait-for-ec2-tags-timeout"`
	GitCloneFlags                string   `cli:"git-clone-flags"`
	GitCleanFlags                string   `cli:"git-clean-flags"`
	NoColor                      bool     `cli:"no-color"`
	NoSSHFingerprintVerification bool     `cli:"no-automatic-ssh-fingerprint-verification"`
	NoCommandEval                bool     `cli:"no-command-eval"`
	NoPlugins                    bool     `cli:"no-plugins"`
	NoPTY                        bool     `cli:"no-pty"`
	TimestampLines               bool     `cli:"timestamp-lines"`
	Endpoint                     string   `cli:"endpoint" validate:"required"`
	Debug                        bool     `cli:"debug"`
	DebugHTTP                    bool     `cli:"debug-http"`
	Experiments                  []string `cli:"experiment"`
	/* Deprecated */
	MetaData        []string `cli:"meta-data" deprecated-and-renamed-to:"Tags"`
	MetaDataEC2     bool     `cli:"meta-data-ec2" deprecated-and-renamed-to:"TagsFromEC2"`
	MetaDataEC2Tags bool     `cli:"meta-data-ec2-tags" deprecated-and-renamed-to:"TagsFromEC2Tags"`
	MetaDataGCP     bool     `cli:"meta-data-gcp" deprecated-and-renamed-to:"TagsFromGCP"`
}

func DefaultConfigFilePaths() (paths []string) {
	// Toggle beetwen windows an *nix paths
	if runtime.GOOS == "windows" {
		paths = []string{
			"$USERPROFILE\\AppData\\Local\\BuildkiteAgent\\buildkite-agent.cfg",
		}
	} else {
		paths = []string{
			"$HOME/.buildkite-agent/buildkite-agent.cfg",
			"/usr/local/etc/buildkite-agent/buildkite-agent.cfg",
			"/etc/buildkite-agent/buildkite-agent.cfg",
		}
	}

	// Also check to see if there's a buildkite-agent.cfg in the folder
	// that the binary is running in.
	pathToBinary, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err == nil {
		pathToRelativeConfig := filepath.Join(pathToBinary, "buildkite-agent.cfg")
		paths = append([]string{pathToRelativeConfig}, paths...)
	}

	return
}

var AgentStartCommand = cli.Command{
	Name:        "start",
	Usage:       "Starts a Buildkite agent",
	Description: StartDescription,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:   "config",
			Value:  "",
			Usage:  "Path to a configuration file",
			EnvVar: "BUILDKITE_AGENT_CONFIG",
		},
		cli.StringFlag{
			Name:   "token",
			Value:  "",
			Usage:  "Your account agent token",
			EnvVar: "BUILDKITE_AGENT_TOKEN",
		},
		cli.StringFlag{
			Name:   "name",
			Value:  "",
			Usage:  "The name of the agent",
			EnvVar: "BUILDKITE_AGENT_NAME",
		},
		cli.StringFlag{
			Name:   "priority",
			Value:  "",
			Usage:  "The priority of the agent (higher priorities are assigned work first)",
			EnvVar: "BUILDKITE_AGENT_PRIORITY",
		},
		cli.BoolFlag{
			Name:   "disconnect-after-job",
			Usage:  "Disconnect the agent after running a job",
			EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_JOB",
		},
		cli.IntFlag{
			Name:   "disconnect-after-job-timeout",
			Value:  120,
			Usage:  "When --disconnect-after-job is specified, the number of seconds to wait for a job before shutting down",
			EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_JOB_TIMEOUT",
		},
		cli.StringSliceFlag{
			Name:   "tags",
			Value:  &cli.StringSlice{},
			Usage:  "A comma-separated list of tags for the agent (e.g. \"linux\" or \"mac,xcode=8\")",
			EnvVar: "BUILDKITE_AGENT_TAGS",
		},
		cli.BoolFlag{
			Name:   "tags-from-ec2",
			Usage:  "Include the host's EC2 meta-data as tags (instance-id, instance-type, and ami-id)",
			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2",
		},
		cli.BoolFlag{
			Name:   "tags-from-ec2-tags",
			Usage:  "Include the host's EC2 tags as tags",
			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_TAGS",
		},
		cli.BoolFlag{
			Name:   "tags-from-gcp",
			Usage:  "Include the host's Google Cloud meta-data as tags (instance-id, machine-type, preemptible, project-id, region, and zone)",
			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_GCP",
		},
		cli.DurationFlag{
			Name:   "wait-for-ec2-tags-timeout",
			Usage:  "The amount of time to wait for tags from EC2 before proceeding",
			EnvVar: "BUILDKITE_AGENT_WAIT_FOR_EC2_TAGS_TIMEOUT",
			Value:  time.Second * 10,
		},
		cli.StringFlag{
			Name:   "git-clone-flags",
			Value:  "-v",
			Usage:  "Flags to pass to the \"git clone\" command",
			EnvVar: "BUILDKITE_GIT_CLONE_FLAGS",
		},
		cli.StringFlag{
			Name:   "git-clean-flags",
			Value:  "-fxdq",
			Usage:  "Flags to pass to \"git clean\" command",
			EnvVar: "BUILDKITE_GIT_CLEAN_FLAGS",
		},
		cli.StringFlag{
			Name:   "bootstrap-script",
			Value:  "",
			Usage:  "Path to the bootstrap script",
			EnvVar: "BUILDKITE_BOOTSTRAP_SCRIPT_PATH",
		},
		cli.StringFlag{
			Name:   "build-path",
			Value:  "",
			Usage:  "Path to where the builds will run from",
			EnvVar: "BUILDKITE_BUILD_PATH",
		},
		cli.StringFlag{
			Name:   "hooks-path",
			Value:  "",
			Usage:  "Directory where the hook scripts are found",
			EnvVar: "BUILDKITE_HOOKS_PATH",
		},
		cli.StringFlag{
			Name:   "plugins-path",
			Value:  "",
			Usage:  "Directory where the plugins are saved to",
			EnvVar: "BUILDKITE_PLUGINS_PATH",
		},
		cli.BoolFlag{
			Name:   "timestamp-lines",
			Usage:  "Prepend timestamps on each line of output.",
			EnvVar: "BUILDKITE_TIMESTAMP_LINES",
		},
		cli.BoolFlag{
			Name:   "no-pty",
			Usage:  "Do not run jobs within a pseudo terminal",
			EnvVar: "BUILDKITE_NO_PTY",
		},
		cli.BoolFlag{
			Name:   "no-automatic-ssh-fingerprint-verification",
			Usage:  "Don't automatically verify SSH fingerprints",
			EnvVar: "BUILDKITE_NO_AUTOMATIC_SSH_FINGERPRINT_VERIFICATION",
		},
		cli.BoolFlag{
			Name:   "no-command-eval",
			Usage:  "Don't allow this agent to run arbitrary console commands",
			EnvVar: "BUILDKITE_NO_COMMAND_EVAL",
		},
		cli.BoolFlag{
			Name:   "no-plugins",
			Usage:  "Don't allow this agent to load plugins",
			EnvVar: "BUILDKITE_NO_PLUGINS",
		},
		ExperimentsFlag,
		EndpointFlag,
		NoColorFlag,
		DebugFlag,
		DebugHTTPFlag,
		/* Deprecated flags which will be removed in v4 */
		cli.StringSliceFlag{
			Name:   "meta-data",
			Value:  &cli.StringSlice{},
			Hidden: true,
			EnvVar: "BUILDKITE_AGENT_META_DATA",
		},
		cli.BoolFlag{
			Name:   "meta-data-ec2",
			Hidden: true,
			EnvVar: "BUILDKITE_AGENT_META_DATA_EC2",
		},
		cli.BoolFlag{
			Name:   "meta-data-ec2-tags",
			Hidden: true,
			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_TAGS",
		},
		cli.BoolFlag{
			Name:   "meta-data-gcp",
			Hidden: true,
			EnvVar: "BUILDKITE_AGENT_META_DATA_GCP",
		},
	},
	Action: func(c *cli.Context) {
		// The configuration will be loaded into this struct
		cfg := AgentStartConfig{}

		// Setup the config loader. You'll see that we also path paths to
		// potential config files. The loader will use the first one it finds.
		loader := cliconfig.Loader{
			CLI:                    c,
			Config:                 &cfg,
			DefaultConfigFilePaths: DefaultConfigFilePaths(),
		}

		// Load the configuration
		if err := loader.Load(); err != nil {
			logger.Fatal("%s", err)
		}

		// Setup the any global configuration options
		HandleGlobalFlags(cfg)

		// Force some settings if on Windows (these aren't supported
		// yet)
		if runtime.GOOS == "windows" {
			cfg.NoPTY = true
		}

		// Set a useful default for the bootstrap script
		if cfg.BootstrapScript == "" {
			cfg.BootstrapScript = fmt.Sprintf("%q bootstrap", os.Args[0])
		}

		// Make sure the DisconnectAfterJobTimeout value is correct
		if cfg.DisconnectAfterJob && cfg.DisconnectAfterJobTimeout < 120 {
			logger.Fatal("The timeout for `disconnect-after-job` must be at least 120 seconds")
		}

		var ec2TagTimeout time.Duration
		if t := cfg.WaitForEC2TagsTimeout; t != "" {
			var err error
			ec2TagTimeout, err = time.ParseDuration(t)
			if err != nil {
				logger.Fatal("Failed to parse ec2 tag timeout: %v", err)
			}
		}

		// Setup the agent
		pool := agent.AgentPool{
			Token:                 cfg.Token,
			Name:                  cfg.Name,
			Priority:              cfg.Priority,
			Tags:                  cfg.Tags,
			TagsFromEC2:           cfg.TagsFromEC2,
			TagsFromEC2Tags:       cfg.TagsFromEC2Tags,
			TagsFromGCP:           cfg.TagsFromGCP,
			WaitForEC2TagsTimeout: ec2TagTimeout,
			Endpoint:              cfg.Endpoint,
			AgentConfiguration: &agent.AgentConfiguration{
				BootstrapScript:            cfg.BootstrapScript,
				BuildPath:                  cfg.BuildPath,
				HooksPath:                  cfg.HooksPath,
				PluginsPath:                cfg.PluginsPath,
				GitCloneFlags:              cfg.GitCloneFlags,
				GitCleanFlags:              cfg.GitCleanFlags,
				SSHFingerprintVerification: !cfg.NoSSHFingerprintVerification,
				CommandEval:                !cfg.NoCommandEval,
				PluginsEnabled:             !cfg.NoPlugins,
				RunInPty:                   !cfg.NoPTY,
				TimestampLines:             cfg.TimestampLines,
				DisconnectAfterJob:         cfg.DisconnectAfterJob,
				DisconnectAfterJobTimeout:  cfg.DisconnectAfterJobTimeout,
			},
		}

		// Store the loaded config file path on the pool so we can
		// show it when the agent starts
		if loader.File != nil {
			pool.ConfigFilePath = loader.File.Path
		}

		// Start the agent pool
		if err := pool.Start(); err != nil {
			logger.Fatal("%s", err)
		}
	},
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"net"
	"strings"
	"time"
)

type User struct {
	nick       string
	user       string
	ident      string
	dead       bool
	nickset    bool
	waiting    bool
	connection net.Conn
	id         int
	realname   string
	userset    bool
	registered bool
	ip         string
	host       string
	epoch      time.Time
	lastrcv    time.Time
	nextcheck  time.Time
	chanlist   map[string]*Channel
}

func (user *User) PingChecker() {
	for {
		if user.dead {
			break
		}
		if time.Now().After(user.nextcheck) {
			if user.waiting {
				since := time.Since(user.lastrcv).Seconds()
				user.Quit(fmt.Sprintf("Ping Timeout: %.0f seconds", since))
				break
			} else {
				user.SendLine(fmt.Sprintf("PING :%s", sname))
				user.waiting = true
				user.nextcheck.Add(ping_time * time.Second)
				log.Printf("Sent user %s ping", user.nick)
			}
		}
		time.Sleep(ping_check_time * time.Second)
	}
}

func (user *User) QuitCommandHandler(args []string) {
	var reason string
	if len(args) > 1 {
		args[1] = StripLeading(args[1], ":")
		var buffer bytes.Buffer
		for i := 1; i < len(args); i++ {
			buffer.WriteString(args[i])
			buffer.WriteString(" ")
		}
		reason = strings.TrimSpace(buffer.String())
	} else {
		reason = "Leaving"
	}
	user.Quit(reason)
	log.Printf("User %s Quit (%s)", user.nick, reason)
}

func (user *User) Quit(reason string) {
	targets := []*User{user}
	for _, k := range user.chanlist {
		targets = append(targets, k.GetUserList()...)
		delete(k.userlist, user.id)
		delete(user.chanlist, k.name)
		k.ShouldIDie()
	}
	SendToMany(fmt.Sprintf(":%s QUIT :%s", user.GetHostMask(), reason), targets)
	user.SendLinef("ERROR :Closing Link: %s (%s)", user.host, reason)
	user.dead = true
	if user.connection != nil {
		user.connection.Close()
	}
	delete(userlist, user.id)
}

func (user *User) FireNumeric(numeric int, args ...interface{}) {
	msg := strcat(fmt.Sprintf(":%s %.3d %s ", sname, numeric, user.nick), fmt.Sprintf(NUM[numeric], args...))
	user.SendLine(msg)
}

func NewUser(conn net.Conn) *User {
	userip := GetIpFromConn(conn)
	log.Println("New connection from", userip)
	counter = counter + 1
	user := &User{id: counter, connection: conn, ip: userip, nick: "*"}
	user.chanlist = make(map[string]*Channel)
	user.host = user.ip
	user.epoch = time.Now()
	user.lastrcv = time.Now()
	user.nextcheck = time.Now().Add(ping_time * time.Second)
	userlist[user.id] = user
	if resolvehosts {
		go user.UserHostLookup()
	}
	go user.PingChecker()
	return user
}

func (user *User) SendLine(msg string) {
	msg = fmt.Sprintf("%s\n", msg)
	if user.dead {
		return
	}
	_, err := user.connection.Write([]byte(msg))
	if err != nil {
		user.dead = true
		user.Quit("Error")
		log.Printf("Error sending message to %s, disconnecting\n", user.nick)
	}
	//log.Printf("Send to %s: %s", user.nick, msg)
}

func (user *User) SendLinef(msg string, args ...interface{}) {
	user.SendLine(fmt.Sprintf(msg, args...))
}

func (user *User) HandleRequests() {
	b := bufio.NewReader(user.connection)
	for {
		if user.dead {
			break
		}
		line, err := b.ReadString('\n')
		if err != nil {
			log.Println("Error reading:", err.Error())
			user.dead = true
			user.Quit("Error")
		}
		if line == "" {
			user.dead = true
			user.Quit("Error")
			break
		}
		line = strings.TrimSpace(line)
		//log.Println("Receive from", fmt.Sprintf("%s:", user.nick), line)
		ProcessLine(user, line)
	}
}
func (user *User) NickHandler(args []string) {
	oldnick := user.nick
	if len(args) < 2 {
		user.FireNumeric(ERR_NONICKNAMEGIVEN)
		return
	}
	if NickHasBadChars(args[1]) {
		user.FireNumeric(ERR_ERRONEOUSNICKNAME, args[1])
		return
	}
	if GetUserByNick(args[1]) != nil {
		user.FireNumeric(ERR_NICKNAMEINUSE, args[1])
		return
	}
	if !user.nickset {
		user.nickset = true
	} else if user.registered {
		targets := []*User{}
		targets = append(targets, user)
		for _, k := range user.chanlist {
			targets = append(targets, k.GetUserList()...)
		}
		SendToMany(fmt.Sprintf(":%s NICK %s", user.GetHostMask(), args[1]), targets)
	}
	user.nick = args[1]
	log.Printf("User %s changed nick to %s", oldnick, user.nick)
	if !user.registered && user.userset {
		user.UserRegistrationFinished()
	}
}

func (user *User) UserHandler(args []string) {
	if len(args) < 5 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "USER")
		return
	}
	user.ident = args[1]
	args[4] = StripLeading(args[4], ":")
	var buffer bytes.Buffer
	for i := 4; i < len(args); i++ {
		buffer.WriteString(args[i])
		buffer.WriteString(" ")
	}
	user.realname = strings.TrimSpace(buffer.String())
	user.userset = true
	if !user.registered && user.nickset {
		user.UserRegistrationFinished()
	}
}

func (user *User) UserRegistrationFinished() {
	user.registered = true
	log.Printf("User %d finished registration\n", user.id)
	user.FireNumeric(RPL_WELCOME, user.nick, user.ident, user.host)
	user.FireNumeric(RPL_YOURHOST, sname, software, softwarev)
	user.FireNumeric(RPL_CREATED, epoch)
	//TODO fire RPL_MYINFO when we actually have enough stuff to do it
	user.FireNumeric(RPL_ISUPPORT, isupport)
	user.FireLusers()
}

func (user *User) UserHostLookup() {
	user.SendLinef(":%s NOTICE %s :*** Looking up your hostname...", sname, user.nick)
	adds, err := net.LookupAddr(user.ip)
	if err != nil {
		user.SendLinef("%s NOTICE %s :*** Unable to resolve your hostname", sname, user.nick)
		return
	}
	addstring := adds[0]
	adds, err = net.LookupHost(addstring)
	if err != nil {
		user.SendLinef("%s NOTICE %s :*** Unable to resolve your hostname", sname, user.nick)
		return
	}
	for _, k := range adds {
		if user.ip == k {
			user.host = addstring
			user.SendLinef(":%s NOTICE %s :*** Found your hostname", sname, user.nick)
			return
		}
	}
	user.SendLinef(":%s NOTICE %s :*** Your forward and reverse DNS do not match, ignoring hostname", sname, user.nick)
}

func (user *User) CommandNotFound(args []string) {
	log.Printf("User %s attempted unknown command %s", user.nick, args[0])
	user.FireNumeric(ERR_UNKNOWNCOMMAND, args[0])
}

func (user *User) GetHostMask() string {
	return fmt.Sprintf("%s!%s@%s", user.nick, user.ident, user.host)
}

func (user *User) JoinHandler(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "JOIN")
		return
	}
	if !ValidChanName(args[1]) {
		user.FireNumeric(ERR_NOSUCHCHANNEL, args[1])
		return
	}
	channel := GetChannelByName(args[1])
	if channel == nil {
		channel = NewChannel(args[1])
	}
	channel.JoinUser(user)
	user.chanlist[channel.name] = channel
	log.Printf("User %s joined %s", user.nick, channel.name)
}

func (user *User) FireLusers() {
	user.FireNumeric(RPL_LUSERCLIENT, len(userlist), 0, 1) //0 services and 1 servers for now
	user.FireNumeric(RPL_LUSEROP, 0)                       //also 0 for now
	user.FireNumeric(RPL_LUSERUNKNOWN, 0)                  //also 0...
	user.FireNumeric(RPL_LUSERCHANNELS, len(chanlist))
	user.FireNumeric(RPL_LUSERME, len(userlist), 1)
}

func (user *User) PartHandler(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "PART")
		return
	}
	channel := GetChannelByName(args[1])
	if channel != nil {
		list := channel.GetUserList()
		delete(channel.userlist, user.id)
		delete(user.chanlist, channel.name)
		SendToMany2f(list, ":%s PART %s :%s", user.GetHostMask(), channel.name, "Leaving")
		log.Printf("User %s PART %s: %s", user.nick, channel.name, "Leaving")
		channel.ShouldIDie()
	} //else?
}

func (user *User) PrivmsgHandler(args []string) {
	if len(args) < 3 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "PRIVMSG")
		return
	}
	if ValidChanName(args[1]) { //TODO part of this should be sent to the channel "object"
		//presumably a channel
		j := GetChannelByName(args[1])
		if j != nil {
			if j.HasMode("n") && !user.IsIn(j) {
				user.FireNumeric(ERR_CANNOTSENDTOCHAN, j.name)
				return
			}
			//channel exists, send the message
			msg := FormatMessageArgs(args)
			list := j.GetUserList()
			for _, l := range list {
				if l != user {
					l.SendLinef(":%s PRIVMSG %s :%s", user.GetHostMask(), j.name, msg)
				}
			}
			log.Printf("User %s CHANMSG %s: %s", user.nick, j.name, msg)
			return
		} else {
			//channel didnt exist but get channel by name makes one anyways, lets kill it...
			user.FireNumeric(ERR_NOSUCHCHANNEL, args[1])
			return
		}
	} else {
		//maybe its a user
		target := GetUserByNick(args[1])
		if target != nil {
			msg := FormatMessageArgs(args)
			target.SendLinef(":%s PRIVMSG %s :%s", user.GetHostMask(), target.nick, msg)
			log.Printf("User %s PRIVMSG %s: %s", user.nick, target.nick, msg)
		}
	}
}

func (user *User) TopicHandler(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "TOPIC")
		return
	}
	k := GetChannelByName(args[1])
	if k == nil {
		user.FireNumeric(ERR_NOSUCHCHANNEL, args[1])
		return
	}
	if len(args) < 3 {
		k.FireTopic(user)
		return
	}
	if k.GetUserPriv(user) < 100 && k.HasMode("t") {
		return //doesn't have privs to change channel
		// TODO fire the correct numeric
	}
	msg := FormatMessageArgs(args)
	k.SetTopic(msg, user.GetHostMask())
}

func (user *User) ModeHandler(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "MODE")
		return
	}
	if ChanUserNone(args[1]) == 1 {
		channel := GetChannelByName(args[1])
		if len(args) < 3 {
			//just digging around...
			channel.FireModes(user)
			log.Printf("User %s requested modes for %s", user.nick, channel.name)
		}
	}
}

func (user *User) IsIn(channel *Channel) bool {
	for _, k := range user.chanlist {
		if k == channel {
			return true
		}
	}
	return false
}

func (user *User) PingCmd(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "PING")
		return
	}
	args[1] = StripLeading(args[1], ":")
	user.SendLinef(":%s PONG %s :%s", sname, sname, args[1])
}
<commit_msg>strip trailing . from hostname<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"net"
	"strings"
	"time"
)

type User struct {
	nick       string
	user       string
	ident      string
	dead       bool
	nickset    bool
	waiting    bool
	connection net.Conn
	id         int
	realname   string
	userset    bool
	registered bool
	ip         string
	host       string
	epoch      time.Time
	lastrcv    time.Time
	nextcheck  time.Time
	chanlist   map[string]*Channel
}

func (user *User) PingChecker() {
	for {
		if user.dead {
			break
		}
		if time.Now().After(user.nextcheck) {
			if user.waiting {
				since := time.Since(user.lastrcv).Seconds()
				user.Quit(fmt.Sprintf("Ping Timeout: %.0f seconds", since))
				break
			} else {
				user.SendLine(fmt.Sprintf("PING :%s", sname))
				user.waiting = true
				user.nextcheck.Add(ping_time * time.Second)
				log.Printf("Sent user %s ping", user.nick)
			}
		}
		time.Sleep(ping_check_time * time.Second)
	}
}

func (user *User) QuitCommandHandler(args []string) {
	var reason string
	if len(args) > 1 {
		args[1] = StripLeading(args[1], ":")
		var buffer bytes.Buffer
		for i := 1; i < len(args); i++ {
			buffer.WriteString(args[i])
			buffer.WriteString(" ")
		}
		reason = strings.TrimSpace(buffer.String())
	} else {
		reason = "Leaving"
	}
	user.Quit(reason)
	log.Printf("User %s Quit (%s)", user.nick, reason)
}

func (user *User) Quit(reason string) {
	targets := []*User{user}
	for _, k := range user.chanlist {
		targets = append(targets, k.GetUserList()...)
		delete(k.userlist, user.id)
		delete(user.chanlist, k.name)
		k.ShouldIDie()
	}
	SendToMany(fmt.Sprintf(":%s QUIT :%s", user.GetHostMask(), reason), targets)
	user.SendLinef("ERROR :Closing Link: %s (%s)", user.host, reason)
	user.dead = true
	if user.connection != nil {
		user.connection.Close()
	}
	delete(userlist, user.id)
}

func (user *User) FireNumeric(numeric int, args ...interface{}) {
	msg := strcat(fmt.Sprintf(":%s %.3d %s ", sname, numeric, user.nick), fmt.Sprintf(NUM[numeric], args...))
	user.SendLine(msg)
}

func NewUser(conn net.Conn) *User {
	userip := GetIpFromConn(conn)
	log.Println("New connection from", userip)
	counter = counter + 1
	user := &User{id: counter, connection: conn, ip: userip, nick: "*"}
	user.chanlist = make(map[string]*Channel)
	user.host = user.ip
	user.epoch = time.Now()
	user.lastrcv = time.Now()
	user.nextcheck = time.Now().Add(ping_time * time.Second)
	userlist[user.id] = user
	if resolvehosts {
		go user.UserHostLookup()
	}
	go user.PingChecker()
	return user
}

func (user *User) SendLine(msg string) {
	msg = fmt.Sprintf("%s\n", msg)
	if user.dead {
		return
	}
	_, err := user.connection.Write([]byte(msg))
	if err != nil {
		user.dead = true
		user.Quit("Error")
		log.Printf("Error sending message to %s, disconnecting\n", user.nick)
	}
	//log.Printf("Send to %s: %s", user.nick, msg)
}

func (user *User) SendLinef(msg string, args ...interface{}) {
	user.SendLine(fmt.Sprintf(msg, args...))
}

func (user *User) HandleRequests() {
	b := bufio.NewReader(user.connection)
	for {
		if user.dead {
			break
		}
		line, err := b.ReadString('\n')
		if err != nil {
			log.Println("Error reading:", err.Error())
			user.dead = true
			user.Quit("Error")
		}
		if line == "" {
			user.dead = true
			user.Quit("Error")
			break
		}
		line = strings.TrimSpace(line)
		//log.Println("Receive from", fmt.Sprintf("%s:", user.nick), line)
		ProcessLine(user, line)
	}
}
func (user *User) NickHandler(args []string) {
	oldnick := user.nick
	if len(args) < 2 {
		user.FireNumeric(ERR_NONICKNAMEGIVEN)
		return
	}
	if NickHasBadChars(args[1]) {
		user.FireNumeric(ERR_ERRONEOUSNICKNAME, args[1])
		return
	}
	if GetUserByNick(args[1]) != nil {
		user.FireNumeric(ERR_NICKNAMEINUSE, args[1])
		return
	}
	if !user.nickset {
		user.nickset = true
	} else if user.registered {
		targets := []*User{}
		targets = append(targets, user)
		for _, k := range user.chanlist {
			targets = append(targets, k.GetUserList()...)
		}
		SendToMany(fmt.Sprintf(":%s NICK %s", user.GetHostMask(), args[1]), targets)
	}
	user.nick = args[1]
	log.Printf("User %s changed nick to %s", oldnick, user.nick)
	if !user.registered && user.userset {
		user.UserRegistrationFinished()
	}
}

func (user *User) UserHandler(args []string) {
	if len(args) < 5 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "USER")
		return
	}
	user.ident = args[1]
	args[4] = StripLeading(args[4], ":")
	var buffer bytes.Buffer
	for i := 4; i < len(args); i++ {
		buffer.WriteString(args[i])
		buffer.WriteString(" ")
	}
	user.realname = strings.TrimSpace(buffer.String())
	user.userset = true
	if !user.registered && user.nickset {
		user.UserRegistrationFinished()
	}
}

func (user *User) UserRegistrationFinished() {
	user.registered = true
	log.Printf("User %d finished registration\n", user.id)
	user.FireNumeric(RPL_WELCOME, user.nick, user.ident, user.host)
	user.FireNumeric(RPL_YOURHOST, sname, software, softwarev)
	user.FireNumeric(RPL_CREATED, epoch)
	//TODO fire RPL_MYINFO when we actually have enough stuff to do it
	user.FireNumeric(RPL_ISUPPORT, isupport)
	user.FireLusers()
}

func (user *User) UserHostLookup() {
	user.SendLinef(":%s NOTICE %s :*** Looking up your hostname...", sname, user.nick)
	adds, err := net.LookupAddr(user.ip)
	if err != nil {
		user.SendLinef("%s NOTICE %s :*** Unable to resolve your hostname", sname, user.nick)
		return
	}
	addstring := adds[0]
	adds, err = net.LookupHost(addstring)
	if err != nil {
		user.SendLinef("%s NOTICE %s :*** Unable to resolve your hostname", sname, user.nick)
		return
	}
	for _, k := range adds {
		if user.ip == k {
			addstring = strings.TrimSuffix(addstring, ".")
			user.host = addstring
			user.SendLinef(":%s NOTICE %s :*** Found your hostname", sname, user.nick)
			return
		}
	}
	user.SendLinef(":%s NOTICE %s :*** Your forward and reverse DNS do not match, ignoring hostname", sname, user.nick)
}

func (user *User) CommandNotFound(args []string) {
	log.Printf("User %s attempted unknown command %s", user.nick, args[0])
	user.FireNumeric(ERR_UNKNOWNCOMMAND, args[0])
}

func (user *User) GetHostMask() string {
	return fmt.Sprintf("%s!%s@%s", user.nick, user.ident, user.host)
}

func (user *User) JoinHandler(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "JOIN")
		return
	}
	if !ValidChanName(args[1]) {
		user.FireNumeric(ERR_NOSUCHCHANNEL, args[1])
		return
	}
	channel := GetChannelByName(args[1])
	if channel == nil {
		channel = NewChannel(args[1])
	}
	channel.JoinUser(user)
	user.chanlist[channel.name] = channel
	log.Printf("User %s joined %s", user.nick, channel.name)
}

func (user *User) FireLusers() {
	user.FireNumeric(RPL_LUSERCLIENT, len(userlist), 0, 1) //0 services and 1 servers for now
	user.FireNumeric(RPL_LUSEROP, 0)                       //also 0 for now
	user.FireNumeric(RPL_LUSERUNKNOWN, 0)                  //also 0...
	user.FireNumeric(RPL_LUSERCHANNELS, len(chanlist))
	user.FireNumeric(RPL_LUSERME, len(userlist), 1)
}

func (user *User) PartHandler(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "PART")
		return
	}
	channel := GetChannelByName(args[1])
	if channel != nil {
		list := channel.GetUserList()
		delete(channel.userlist, user.id)
		delete(user.chanlist, channel.name)
		SendToMany2f(list, ":%s PART %s :%s", user.GetHostMask(), channel.name, "Leaving")
		log.Printf("User %s PART %s: %s", user.nick, channel.name, "Leaving")
		channel.ShouldIDie()
	} //else?
}

func (user *User) PrivmsgHandler(args []string) {
	if len(args) < 3 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "PRIVMSG")
		return
	}
	if ValidChanName(args[1]) { //TODO part of this should be sent to the channel "object"
		//presumably a channel
		j := GetChannelByName(args[1])
		if j != nil {
			if j.HasMode("n") && !user.IsIn(j) {
				user.FireNumeric(ERR_CANNOTSENDTOCHAN, j.name)
				return
			}
			//channel exists, send the message
			msg := FormatMessageArgs(args)
			list := j.GetUserList()
			for _, l := range list {
				if l != user {
					l.SendLinef(":%s PRIVMSG %s :%s", user.GetHostMask(), j.name, msg)
				}
			}
			log.Printf("User %s CHANMSG %s: %s", user.nick, j.name, msg)
			return
		} else {
			//channel didnt exist but get channel by name makes one anyways, lets kill it...
			user.FireNumeric(ERR_NOSUCHCHANNEL, args[1])
			return
		}
	} else {
		//maybe its a user
		target := GetUserByNick(args[1])
		if target != nil {
			msg := FormatMessageArgs(args)
			target.SendLinef(":%s PRIVMSG %s :%s", user.GetHostMask(), target.nick, msg)
			log.Printf("User %s PRIVMSG %s: %s", user.nick, target.nick, msg)
		}
	}
}

func (user *User) TopicHandler(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "TOPIC")
		return
	}
	k := GetChannelByName(args[1])
	if k == nil {
		user.FireNumeric(ERR_NOSUCHCHANNEL, args[1])
		return
	}
	if len(args) < 3 {
		k.FireTopic(user)
		return
	}
	if k.GetUserPriv(user) < 100 && k.HasMode("t") {
		return //doesn't have privs to change channel
		// TODO fire the correct numeric
	}
	msg := FormatMessageArgs(args)
	k.SetTopic(msg, user.GetHostMask())
}

func (user *User) ModeHandler(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "MODE")
		return
	}
	if ChanUserNone(args[1]) == 1 {
		channel := GetChannelByName(args[1])
		if len(args) < 3 {
			//just digging around...
			channel.FireModes(user)
			log.Printf("User %s requested modes for %s", user.nick, channel.name)
		}
	}
}

func (user *User) IsIn(channel *Channel) bool {
	for _, k := range user.chanlist {
		if k == channel {
			return true
		}
	}
	return false
}

func (user *User) PingCmd(args []string) {
	if len(args) < 2 {
		user.FireNumeric(ERR_NEEDMOREPARAMS, "PING")
		return
	}
	args[1] = StripLeading(args[1], ":")
	user.SendLinef(":%s PONG %s :%s", sname, sname, args[1])
}
<|endoftext|>


<commit_before>package msgpack

import (
	"math"
	"reflect"
	"sort"

	"github.com/vmihailenco/msgpack/v5/msgpcode"
)

func encodeMapValue(e *Encoder, v reflect.Value) error {
	if v.IsNil() {
		return e.EncodeNil()
	}

	if err := e.EncodeMapLen(v.Len()); err != nil {
		return err
	}

	for _, key := range v.MapKeys() {
		if err := e.EncodeValue(key); err != nil {
			return err
		}
		if err := e.EncodeValue(v.MapIndex(key)); err != nil {
			return err
		}
	}

	return nil
}

func encodeMapStringStringValue(e *Encoder, v reflect.Value) error {
	if v.IsNil() {
		return e.EncodeNil()
	}

	if err := e.EncodeMapLen(v.Len()); err != nil {
		return err
	}

	m := v.Convert(mapStringStringType).Interface().(map[string]string)
	if e.flags&sortedMapsFlag != 0 {
		return e.encodeSortedMapStringString(m)
	}

	for mk, mv := range m {
		if err := e.EncodeString(mk); err != nil {
			return err
		}
		if err := e.EncodeString(mv); err != nil {
			return err
		}
	}

	return nil
}

func encodeMapStringInterfaceValue(e *Encoder, v reflect.Value) error {
	if v.IsNil() {
		return e.EncodeNil()
	}
	m := v.Convert(mapStringInterfaceType).Interface().(map[string]interface{})
	if e.flags&sortedMapsFlag != 0 {
		return e.EncodeMapSorted(m)
	}
	return e.EncodeMap(m)
}

func (e *Encoder) EncodeMap(m map[string]interface{}) error {
	if m == nil {
		return e.EncodeNil()
	}
	if err := e.EncodeMapLen(len(m)); err != nil {
		return err
	}
	for mk, mv := range m {
		if err := e.EncodeString(mk); err != nil {
			return err
		}
		if err := e.Encode(mv); err != nil {
			return err
		}
	}
	return nil
}

func (e *Encoder) EncodeMapSorted(m map[string]interface{}) error {
	keys := make([]string, 0, len(m))

	for k := range m {
		keys = append(keys, k)
	}

	sort.Strings(keys)

	for _, k := range keys {
		if err := e.EncodeString(k); err != nil {
			return err
		}
		if err := e.Encode(m[k]); err != nil {
			return err
		}
	}

	return nil
}

func (e *Encoder) encodeSortedMapStringString(m map[string]string) error {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		err := e.EncodeString(k)
		if err != nil {
			return err
		}
		if err = e.EncodeString(m[k]); err != nil {
			return err
		}
	}

	return nil
}

func (e *Encoder) EncodeMapLen(l int) error {
	if l < 16 {
		return e.writeCode(msgpcode.FixedMapLow | byte(l))
	}
	if l <= math.MaxUint16 {
		return e.write2(msgpcode.Map16, uint16(l))
	}
	return e.write4(msgpcode.Map32, uint32(l))
}

func encodeStructValue(e *Encoder, strct reflect.Value) error {
	structFields := structs.Fields(strct.Type(), e.structTag)
	if e.flags&structAsArrayFlag != 0 || structFields.AsArray {
		return encodeStructValueAsArray(e, strct, structFields.List)
	}
	fields := structFields.OmitEmpty(strct)

	if err := e.EncodeMapLen(len(fields)); err != nil {
		return err
	}

	for _, f := range fields {
		if err := e.EncodeString(f.name); err != nil {
			return err
		}
		if err := f.EncodeValue(e, strct); err != nil {
			return err
		}
	}

	return nil
}

func encodeStructValueAsArray(e *Encoder, strct reflect.Value, fields []*field) error {
	if err := e.EncodeArrayLen(len(fields)); err != nil {
		return err
	}
	for _, f := range fields {
		if err := f.EncodeValue(e, strct); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Fix EncodeMapSorted<commit_after>package msgpack

import (
	"math"
	"reflect"
	"sort"

	"github.com/vmihailenco/msgpack/v5/msgpcode"
)

func encodeMapValue(e *Encoder, v reflect.Value) error {
	if v.IsNil() {
		return e.EncodeNil()
	}

	if err := e.EncodeMapLen(v.Len()); err != nil {
		return err
	}

	for _, key := range v.MapKeys() {
		if err := e.EncodeValue(key); err != nil {
			return err
		}
		if err := e.EncodeValue(v.MapIndex(key)); err != nil {
			return err
		}
	}

	return nil
}

func encodeMapStringStringValue(e *Encoder, v reflect.Value) error {
	if v.IsNil() {
		return e.EncodeNil()
	}

	if err := e.EncodeMapLen(v.Len()); err != nil {
		return err
	}

	m := v.Convert(mapStringStringType).Interface().(map[string]string)
	if e.flags&sortedMapsFlag != 0 {
		return e.encodeSortedMapStringString(m)
	}

	for mk, mv := range m {
		if err := e.EncodeString(mk); err != nil {
			return err
		}
		if err := e.EncodeString(mv); err != nil {
			return err
		}
	}

	return nil
}

func encodeMapStringInterfaceValue(e *Encoder, v reflect.Value) error {
	if v.IsNil() {
		return e.EncodeNil()
	}
	m := v.Convert(mapStringInterfaceType).Interface().(map[string]interface{})
	if e.flags&sortedMapsFlag != 0 {
		return e.EncodeMapSorted(m)
	}
	return e.EncodeMap(m)
}

func (e *Encoder) EncodeMap(m map[string]interface{}) error {
	if m == nil {
		return e.EncodeNil()
	}
	if err := e.EncodeMapLen(len(m)); err != nil {
		return err
	}
	for mk, mv := range m {
		if err := e.EncodeString(mk); err != nil {
			return err
		}
		if err := e.Encode(mv); err != nil {
			return err
		}
	}
	return nil
}

func (e *Encoder) EncodeMapSorted(m map[string]interface{}) error {
	if m == nil {
		return e.EncodeNil()
	}
	if err := e.EncodeMapLen(len(m)); err != nil {
		return err
	}

	keys := make([]string, 0, len(m))

	for k := range m {
		keys = append(keys, k)
	}

	sort.Strings(keys)

	for _, k := range keys {
		if err := e.EncodeString(k); err != nil {
			return err
		}
		if err := e.Encode(m[k]); err != nil {
			return err
		}
	}

	return nil
}

func (e *Encoder) encodeSortedMapStringString(m map[string]string) error {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		err := e.EncodeString(k)
		if err != nil {
			return err
		}
		if err = e.EncodeString(m[k]); err != nil {
			return err
		}
	}

	return nil
}

func (e *Encoder) EncodeMapLen(l int) error {
	if l < 16 {
		return e.writeCode(msgpcode.FixedMapLow | byte(l))
	}
	if l <= math.MaxUint16 {
		return e.write2(msgpcode.Map16, uint16(l))
	}
	return e.write4(msgpcode.Map32, uint32(l))
}

func encodeStructValue(e *Encoder, strct reflect.Value) error {
	structFields := structs.Fields(strct.Type(), e.structTag)
	if e.flags&structAsArrayFlag != 0 || structFields.AsArray {
		return encodeStructValueAsArray(e, strct, structFields.List)
	}
	fields := structFields.OmitEmpty(strct)

	if err := e.EncodeMapLen(len(fields)); err != nil {
		return err
	}

	for _, f := range fields {
		if err := e.EncodeString(f.name); err != nil {
			return err
		}
		if err := f.EncodeValue(e, strct); err != nil {
			return err
		}
	}

	return nil
}

func encodeStructValueAsArray(e *Encoder, strct reflect.Value, fields []*field) error {
	if err := e.EncodeArrayLen(len(fields)); err != nil {
		return err
	}
	for _, f := range fields {
		if err := f.EncodeValue(e, strct); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// Package libresolve implements a recursive and stub resolver for RAINS.
package libresolve

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/britram/borat"
	log "github.com/inconshreveable/log15"
	"github.com/netsec-ethz/rains/rainslib"
)

type ResolutionMode int

var (
	defaultTimeout  = 10 * time.Second
	defaultFailFast = true
)

const (
	ResolutionModeRecursive ResolutionMode = iota
	ResolutionModeForwarding
)

// Resolver provides methods to resolve names in RAINS.
type Resolver struct {
	RootNameservers []string
	Forwarders      []string
	Mode            ResolutionMode
	InsecureTLS     bool
	DialTimeout     time.Duration
	FailFast        bool
}

func New(rootNS, forwarders []string, mode ResolutionMode, insecureTLS bool) *Resolver {
	return &Resolver{
		RootNameservers: rootNS,
		Forwarders:      forwarders,
		Mode:            mode,
		InsecureTLS:     insecureTLS,
		DialTimeout:     defaultTimeout,
		FailFast:        defaultFailFast,
	}
}

func (r *Resolver) Lookup(name, context string) (*rainslib.RainsMessage, error) {
	switch r.Mode {
	case ResolutionModeRecursive:
		return r.recursiveResolve(name, context)
	case ResolutionModeForwarding:
		q := r.nameToQuery(name, context, time.Now().Add(15*time.Second).UnixNano(), []rainslib.QueryOption{})
		return r.forwardQuery(q)
	default:
		return nil, fmt.Errorf("Unsupported resolution mode: %v", r.Mode)
	}
}

func (r *Resolver) nameToQuery(name, context string, expTime int64, opts []rainslib.QueryOption) rainslib.RainsMessage {
	types := []rainslib.ObjectType{rainslib.OTIP4Addr, rainslib.OTIP6Addr, rainslib.OTDelegation, rainslib.OTServiceInfo, rainslib.OTRedirection}
	return rainslib.NewQueryMessage(name, context, expTime, types, opts, rainslib.GenerateToken())
}

func listen(conn net.Conn, tok rainslib.Token, done chan<- *rainslib.RainsMessage, ec chan<- error) {
	reader := borat.NewCBORReader(conn)
	var msg rainslib.RainsMessage
	if err := reader.Unmarshal(&msg); err != nil {
		ec <- fmt.Errorf("failed to unmarshal response: %v", err)
		return
	}
	if msg.Token != tok {
		ec <- fmt.Errorf("token response mismatch: got %v, want %v", msg.Token, tok)
		return
	}
	done <- &msg
}

func (r *Resolver) forwardQuery(q rainslib.RainsMessage) (*rainslib.RainsMessage, error) {
	if len(r.Forwarders) == 0 {
		return nil, errors.New("forwarders must be specified to use this mode")
	}
	errs := make([]error, 0)
	for i, forwarder := range r.Forwarders {
		log.Info(fmt.Sprintf("Connecting to forwarding resolver #%d: %v", i, forwarder))
		d := &net.Dialer{
			Timeout: r.DialTimeout,
		}
		conn, err := tls.DialWithDialer(d, "tcp", forwarder, &tls.Config{InsecureSkipVerify: r.InsecureTLS})
		if err != nil {
			log.Warn(fmt.Sprintf("Connection to fowarding resolver %d failed: %v", i, err))
			errs = append(errs, err)
			continue
		}
		defer conn.Close()
		writer := borat.NewCBORWriter(conn)
		if err := writer.Marshal(q); err != nil {
			errs = append(errs, fmt.Errorf("failed to marshal message to server: %v", err))
			continue
		}
		done := make(chan *rainslib.RainsMessage)
		ec := make(chan error)
		go listen(conn, q.Token, done, ec)
		select {
		case msg := <-done:
			return msg, nil
		case err := <-ec:
			return nil, err
		}
	}
	return nil, fmt.Errorf("could not connect to any specified resolver, errors: %v", errs)
}

func mergeSubjectZone(subject, zone string) string {
	if zone == "." {
		return fmt.Sprintf("%s.", subject)
	}
	if subject == "" {
		return zone
	}
	return fmt.Sprintf("%s.%s", subject, zone)
}

func NameToLabels(name string) ([]string, error) {
	if !strings.HasSuffix(name, ".") {
		return nil, fmt.Errorf("domain name must end with root qualifier '.', got %s", name)
	}
	parts := strings.Split(name, ".")
	// Last element is empty because of root dot, so discard it.
	return parts[:len(parts)-1], nil
}

// recursiveResolve starts at the root and follows delegations until it receives an answer.
func (r *Resolver) recursiveResolve(name, context string) (*rainslib.RainsMessage, error) {
	latestResolver := r.RootNameservers[0] // TODO: try multiple root nameservers.
	var resp *rainslib.RainsMessage
	for {
		log.Info(fmt.Sprintf("connecting to resolver at address: %s to resolve %q", latestResolver, name))
		d := &net.Dialer{
			Timeout: r.DialTimeout,
		}
		conn, err := tls.DialWithDialer(d, "tcp", latestResolver, &tls.Config{InsecureSkipVerify: r.InsecureTLS})
		if err != nil {
			return nil, fmt.Errorf("failed to connect to resolver: %v", err)
		}
		defer conn.Close()
		writer := borat.NewCBORWriter(conn)
		q := r.nameToQuery(name, context, time.Now().Add(15*time.Second).Unix(), []rainslib.QueryOption{})
		if err := writer.Marshal(&q); err != nil {
			return nil, fmt.Errorf("failed to marshal query to server: %v", err)
		}
		done := make(chan *rainslib.RainsMessage)
		ec := make(chan error)
		go listen(conn, q.Token, done, ec)
		select {
		case msg := <-done:
			resp = msg
		case err := <-ec:
			return nil, err
		}
		if len(resp.Content) == 0 {
			return nil, errors.New("got empty response")
		}
		// The response can either be a redirection chain or a response.
		redirectMap := make(map[string]string)
		srvMap := make(map[string]rainslib.ServiceInfo)
		concreteMap := make(map[string]string)
		for _, section := range resp.Content {
			switch section.(type) {
			case *rainslib.ZoneSection:
				// If we were given a whole zone it's because we asked for it or it's non-existance proof.
				return resp, nil
			case *rainslib.AssertionSection:
				as := section.(*rainslib.AssertionSection)
				sz := mergeSubjectZone(as.SubjectName, as.SubjectZone)
				if sz == name {
					return resp, nil
				}
				for _, obj := range as.Content {
					switch obj.Type {
					case rainslib.OTRedirection:
						redirectMap[sz] = obj.Value.(string)
					case rainslib.OTServiceInfo:
						si := obj.Value.(rainslib.ServiceInfo)
						srvMap[sz] = si
					case rainslib.OTIP4Addr:
						concreteMap[sz] = obj.Value.(string)
					case rainslib.OTIP6Addr:
						concreteMap[sz] = fmt.Sprintf("[%s]", obj.Value.(string))
					}
				}
			case *rainslib.ShardSection:
				return resp, nil
			default:
				return nil, fmt.Errorf("got unknown type: %T", section)
			}
		}
		// If we are here, there is some recursion required or there is no answer.
		// Firstly we check if there is some redirection for a suffix we are interested in.
		redirTarget := ""
		for key, value := range redirectMap {
			if strings.HasSuffix(name, key) {
				redirTarget = value
			}
		}
		if redirTarget == "" {
			return nil, fmt.Errorf("failed to find result or redirection, response was: %v", resp)
		}
		// Follow redir until we encounter a srv.
		seen := make(map[string]bool)
		for {
			if _, ok := seen[redirTarget]; ok {
				return nil, fmt.Errorf("redirect loop detected, target %q, response: %v", redirTarget, resp)
			}
			seen[redirTarget] = true
			if next, ok := redirectMap[redirTarget]; ok {
				redirTarget = next
			} else {
				break
			}
		}
		// There should now be a mapping between the next redirTarget and a serviceinfo object.
		if srvInfo, ok := srvMap[redirTarget]; ok {
			// srvInfo should contain a name
			if concreteTarget, ok := concreteMap[srvInfo.Name]; ok {
				latestResolver = fmt.Sprintf("%s:%d", concreteTarget, srvInfo.Port)
			} else {
				return nil, fmt.Errorf("serviceInfo target could not be found in response, target: %q, resp: %v", srvInfo.Name, resp)
			}
		} else {
			return nil, fmt.Errorf("recieved incomplete response, missing serviceInfo for target FQDN %q, resp: %v", redirTarget, resp)
		}
	}
}
<commit_msg>add comment for listen method<commit_after>// Package libresolve implements a recursive and stub resolver for RAINS.
package libresolve

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/britram/borat"
	log "github.com/inconshreveable/log15"
	"github.com/netsec-ethz/rains/rainslib"
)

type ResolutionMode int

var (
	defaultTimeout  = 10 * time.Second
	defaultFailFast = true
)

const (
	ResolutionModeRecursive ResolutionMode = iota
	ResolutionModeForwarding
)

// Resolver provides methods to resolve names in RAINS.
type Resolver struct {
	RootNameservers []string
	Forwarders      []string
	Mode            ResolutionMode
	InsecureTLS     bool
	DialTimeout     time.Duration
	FailFast        bool
}

func New(rootNS, forwarders []string, mode ResolutionMode, insecureTLS bool) *Resolver {
	return &Resolver{
		RootNameservers: rootNS,
		Forwarders:      forwarders,
		Mode:            mode,
		InsecureTLS:     insecureTLS,
		DialTimeout:     defaultTimeout,
		FailFast:        defaultFailFast,
	}
}

func (r *Resolver) Lookup(name, context string) (*rainslib.RainsMessage, error) {
	switch r.Mode {
	case ResolutionModeRecursive:
		return r.recursiveResolve(name, context)
	case ResolutionModeForwarding:
		q := r.nameToQuery(name, context, time.Now().Add(15*time.Second).UnixNano(), []rainslib.QueryOption{})
		return r.forwardQuery(q)
	default:
		return nil, fmt.Errorf("Unsupported resolution mode: %v", r.Mode)
	}
}

func (r *Resolver) nameToQuery(name, context string, expTime int64, opts []rainslib.QueryOption) rainslib.RainsMessage {
	types := []rainslib.ObjectType{rainslib.OTIP4Addr, rainslib.OTIP6Addr, rainslib.OTDelegation, rainslib.OTServiceInfo, rainslib.OTRedirection}
	return rainslib.NewQueryMessage(name, context, expTime, types, opts, rainslib.GenerateToken())
}

// listen waits for one message and passes it back on the provided channel, or an error on the error channel.
func listen(conn net.Conn, tok rainslib.Token, done chan<- *rainslib.RainsMessage, ec chan<- error) {
	reader := borat.NewCBORReader(conn)
	var msg rainslib.RainsMessage
	if err := reader.Unmarshal(&msg); err != nil {
		ec <- fmt.Errorf("failed to unmarshal response: %v", err)
		return
	}
	if msg.Token != tok {
		ec <- fmt.Errorf("token response mismatch: got %v, want %v", msg.Token, tok)
		return
	}
	done <- &msg
}

func (r *Resolver) forwardQuery(q rainslib.RainsMessage) (*rainslib.RainsMessage, error) {
	if len(r.Forwarders) == 0 {
		return nil, errors.New("forwarders must be specified to use this mode")
	}
	errs := make([]error, 0)
	for i, forwarder := range r.Forwarders {
		log.Info(fmt.Sprintf("Connecting to forwarding resolver #%d: %v", i, forwarder))
		d := &net.Dialer{
			Timeout: r.DialTimeout,
		}
		conn, err := tls.DialWithDialer(d, "tcp", forwarder, &tls.Config{InsecureSkipVerify: r.InsecureTLS})
		if err != nil {
			log.Warn(fmt.Sprintf("Connection to fowarding resolver %d failed: %v", i, err))
			errs = append(errs, err)
			continue
		}
		defer conn.Close()
		writer := borat.NewCBORWriter(conn)
		if err := writer.Marshal(q); err != nil {
			errs = append(errs, fmt.Errorf("failed to marshal message to server: %v", err))
			continue
		}
		done := make(chan *rainslib.RainsMessage)
		ec := make(chan error)
		go listen(conn, q.Token, done, ec)
		select {
		case msg := <-done:
			return msg, nil
		case err := <-ec:
			return nil, err
		}
	}
	return nil, fmt.Errorf("could not connect to any specified resolver, errors: %v", errs)
}

func mergeSubjectZone(subject, zone string) string {
	if zone == "." {
		return fmt.Sprintf("%s.", subject)
	}
	if subject == "" {
		return zone
	}
	return fmt.Sprintf("%s.%s", subject, zone)
}

func NameToLabels(name string) ([]string, error) {
	if !strings.HasSuffix(name, ".") {
		return nil, fmt.Errorf("domain name must end with root qualifier '.', got %s", name)
	}
	parts := strings.Split(name, ".")
	// Last element is empty because of root dot, so discard it.
	return parts[:len(parts)-1], nil
}

// recursiveResolve starts at the root and follows delegations until it receives an answer.
func (r *Resolver) recursiveResolve(name, context string) (*rainslib.RainsMessage, error) {
	latestResolver := r.RootNameservers[0] // TODO: try multiple root nameservers.
	var resp *rainslib.RainsMessage
	for {
		log.Info(fmt.Sprintf("connecting to resolver at address: %s to resolve %q", latestResolver, name))
		d := &net.Dialer{
			Timeout: r.DialTimeout,
		}
		conn, err := tls.DialWithDialer(d, "tcp", latestResolver, &tls.Config{InsecureSkipVerify: r.InsecureTLS})
		if err != nil {
			return nil, fmt.Errorf("failed to connect to resolver: %v", err)
		}
		defer conn.Close()
		writer := borat.NewCBORWriter(conn)
		q := r.nameToQuery(name, context, time.Now().Add(15*time.Second).Unix(), []rainslib.QueryOption{})
		if err := writer.Marshal(&q); err != nil {
			return nil, fmt.Errorf("failed to marshal query to server: %v", err)
		}
		done := make(chan *rainslib.RainsMessage)
		ec := make(chan error)
		go listen(conn, q.Token, done, ec)
		select {
		case msg := <-done:
			resp = msg
		case err := <-ec:
			return nil, err
		}
		if len(resp.Content) == 0 {
			return nil, errors.New("got empty response")
		}
		// The response can either be a redirection chain or a response.
		redirectMap := make(map[string]string)
		srvMap := make(map[string]rainslib.ServiceInfo)
		concreteMap := make(map[string]string)
		for _, section := range resp.Content {
			switch section.(type) {
			case *rainslib.ZoneSection:
				// If we were given a whole zone it's because we asked for it or it's non-existance proof.
				return resp, nil
			case *rainslib.AssertionSection:
				as := section.(*rainslib.AssertionSection)
				sz := mergeSubjectZone(as.SubjectName, as.SubjectZone)
				if sz == name {
					return resp, nil
				}
				for _, obj := range as.Content {
					switch obj.Type {
					case rainslib.OTRedirection:
						redirectMap[sz] = obj.Value.(string)
					case rainslib.OTServiceInfo:
						si := obj.Value.(rainslib.ServiceInfo)
						srvMap[sz] = si
					case rainslib.OTIP4Addr:
						concreteMap[sz] = obj.Value.(string)
					case rainslib.OTIP6Addr:
						concreteMap[sz] = fmt.Sprintf("[%s]", obj.Value.(string))
					}
				}
			case *rainslib.ShardSection:
				return resp, nil
			default:
				return nil, fmt.Errorf("got unknown type: %T", section)
			}
		}
		// If we are here, there is some recursion required or there is no answer.
		// Firstly we check if there is some redirection for a suffix we are interested in.
		redirTarget := ""
		for key, value := range redirectMap {
			if strings.HasSuffix(name, key) {
				redirTarget = value
			}
		}
		if redirTarget == "" {
			return nil, fmt.Errorf("failed to find result or redirection, response was: %v", resp)
		}
		// Follow redir until we encounter a srv.
		seen := make(map[string]bool)
		for {
			if _, ok := seen[redirTarget]; ok {
				return nil, fmt.Errorf("redirect loop detected, target %q, response: %v", redirTarget, resp)
			}
			seen[redirTarget] = true
			if next, ok := redirectMap[redirTarget]; ok {
				redirTarget = next
			} else {
				break
			}
		}
		// There should now be a mapping between the next redirTarget and a serviceinfo object.
		if srvInfo, ok := srvMap[redirTarget]; ok {
			// srvInfo should contain a name
			if concreteTarget, ok := concreteMap[srvInfo.Name]; ok {
				latestResolver = fmt.Sprintf("%s:%d", concreteTarget, srvInfo.Port)
			} else {
				return nil, fmt.Errorf("serviceInfo target could not be found in response, target: %q, resp: %v", srvInfo.Name, resp)
			}
		} else {
			return nil, fmt.Errorf("recieved incomplete response, missing serviceInfo for target FQDN %q, resp: %v", redirTarget, resp)
		}
	}
}
<|endoftext|>


<commit_before>package eval

//go:generate stringer -type=Type

import (
	"bytes"
	"errors"
	"fmt"
	"math/big"
	"reflect"
	"strconv"

	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/strutil"
)

// Value is the runtime representation of an elvish value.
type Value interface {
	Type() Type
	Reprer
}

type Reprer interface {
	Repr() string
}

// Booler represents a Value with a custom semantics of truthness. If a Value
// does not satisfy this interface, it is automatically true.
type Booler interface {
	Bool() bool
}

// Stringer represents a Value with a custom string representation. If a Value
// does not satisfy this interface, its Repr method is used.
type Stringer interface {
	String() string
}

// Indexer represents a Value that may be indexing.
type Indexer interface {
	Index(idx Value) Value
}

type IndexVarer interface {
	IndexVar(idx Value) Variable
}

// Caller represents a Value that may be called.
type Caller interface {
	Call(ec *evalCtx, args []Value)
}

// Type is the type of a value.
type Type int

const (
	TInvalid Type = iota
	TString
	TError
	TBool
	TList
	TMap
	TFn
	TRat
)

// Error definitions.
var (
	needIntIndex    = errors.New("need integer index")
	indexOutOfRange = errors.New("index out of range")
	errOnlyStrOrRat = errors.New("only str or rat may be converted to rat")
)

// String is just a string.
type String string

func (s String) Type() Type {
	return TString
}

func (s String) Repr() string {
	return parse.Quote(string(s))
}

func (s String) String() string {
	return string(s)
}

func (s String) Index(idx Value) Value {
	i := intIndex(idx)
	r, err := strutil.NthRune(string(s), i)
	maybeThrow(err)
	return String(string(r))
}

func intIndex(idx Value) int {
	i, err := strconv.Atoi(ToString(idx))
	if err != nil {
		err := err.(*strconv.NumError)
		if err.Err == strconv.ErrRange {
			throw(indexOutOfRange)
		} else {
			throw(needIntIndex)
		}
	}
	return i
}

// Bool represents truthness.
type Bool bool

func (b Bool) Type() Type {
	return TBool
}

func (b Bool) Repr() string {
	if b {
		return "$true"
	}
	return "$false"
}

func (b Bool) String() string {
	if b {
		return "true"
	}
	return "false"
}

func (b Bool) Bool() bool {
	return bool(b)
}

// Error represents runtime errors in elvish constructs.
type Error struct {
	inner error
}

func (e Error) Type() Type {
	return TError
}

func (e Error) Repr() string {
	if e.inner == nil {
		return "$ok"
	}
	if r, ok := e.inner.(Reprer); ok {
		return r.Repr()
	}
	return "?(error " + parse.Quote(e.inner.Error()) + ")"
}

func (e Error) String() string {
	if e.inner == nil {
		return "ok"
	}
	return e.inner.Error()
}

func (e Error) Bool() bool {
	return e.inner == nil
}

// Common Error values.
var (
	OK             = Error{nil}
	GenericFailure = Error{errors.New("generic failure")}
)

func NewFailure(text string) Error {
	return Error{errors.New(text)}
}

// multiError is multiple errors packed into one. It is used for reporting
// errors of pipelines, in which multiple forms may error.
type multiError struct {
	errors []Error
}

func (me multiError) Repr() string {
	b := new(bytes.Buffer)
	b.WriteString("?(multi-error")
	for _, e := range me.errors {
		b.WriteString(" ")
		b.WriteString(e.Repr())
	}
	b.WriteString(")")
	return b.String()
}

func (me multiError) Error() string {
	b := new(bytes.Buffer)
	b.WriteString("(")
	for i, e := range me.errors {
		if i > 0 {
			b.WriteString(" | ")
		}
		b.WriteString(e.inner.Error())
	}
	b.WriteString(")")
	return b.String()
}

func newMultiError(es ...Error) Error {
	return Error{multiError{es}}
}

// Flow is a special type of Error used for control flows.
type flow uint

const (
	Return flow = iota
	Break
	Continue
)

var flowNames = [...]string{
	"return", "break", "continue",
}

func (f flow) Repr() string {
	return "?(" + f.Error() + ")"
}

func (f flow) Error() string {
	if f >= flow(len(flowNames)) {
		return fmt.Sprintf("!(BAD FLOW: %v)", f)
	}
	return flowNames[f]
}

func allok(es []Error) bool {
	for _, e := range es {
		if e.inner != nil {
			return false
		}
	}
	return true
}

// List is a list of Value's.
type List struct {
	inner *[]Value
}

func NewList(vs ...Value) List {
	return List{&vs}
}

func (l List) Type() Type {
	return TList
}

func (l List) appendStrings(ss []string) {
	for _, s := range ss {
		*l.inner = append(*l.inner, String(s))
	}
}

func (l List) Repr() string {
	buf := new(bytes.Buffer)
	buf.WriteRune('[')
	for i, v := range *l.inner {
		if i > 0 {
			buf.WriteByte(' ')
		}
		buf.WriteString(v.Repr())
	}
	buf.WriteRune(']')
	return buf.String()
}

func (l List) Index(idx Value) Value {
	i := intIndex(idx)

	if i < 0 {
		i += len(*l.inner)
	}
	if i < 0 || i >= len(*l.inner) {
		throw(indexOutOfRange)
	}
	return (*l.inner)[i]
}

func (l List) IndexVar(idx Value) Variable {
	return listElem{l, intIndex(idx)}
}

// Map is a map from string to Value.
type Map struct {
	inner *map[Value]Value
}

func NewMap() Map {
	return Map{&map[Value]Value{}}
}

func (m Map) Type() Type {
	return TMap
}

func (m Map) Repr() string {
	buf := new(bytes.Buffer)
	buf.WriteRune('[')
	for k, v := range *m.inner {
		if buf.Len() > 1 {
			buf.WriteByte(' ')
		}
		buf.WriteByte('&')
		buf.WriteString(k.Repr())
		buf.WriteByte(' ')
		buf.WriteString(v.Repr())
	}
	buf.WriteRune(']')
	return buf.String()
}

func (m Map) Index(idx Value) Value {
	v, ok := (*m.inner)[idx]
	if !ok {
		throw(errors.New("no such key: " + idx.Repr()))
	}
	return v
}

func (m Map) IndexVar(idx Value) Variable {
	return mapElem{m, idx}
}

// Closure is a closure.
type Closure struct {
	ArgNames []string
	Op       op
	Captured map[string]Variable
}

func (c *Closure) Type() Type {
	return TFn
}

func newClosure(a []string, op op, e map[string]Variable) *Closure {
	return &Closure{a, op, e}
}

func (c *Closure) Repr() string {
	return fmt.Sprintf("<Closure%v>", *c)
}

// BuiltinFn is a builtin function.
type BuiltinFn struct {
	Name string
	Impl func(*evalCtx, []Value)
}

func (b *BuiltinFn) Type() Type {
	return TFn
}

func (b *BuiltinFn) Repr() string {
	return "$" + FnPrefix + b.Name
}

// ExternalCmd is an external command.
type ExternalCmd struct {
	Name string
}

func (e ExternalCmd) Type() Type {
	return TFn
}

func (e ExternalCmd) Repr() string {
	return "<external " + e.Name + " >"
}

// Rat is a rational number.
type Rat struct {
	b *big.Rat
}

func (r Rat) Type() Type {
	return TRat
}

func (r Rat) Repr() string {
	return "(rat " + r.String() + ")"
}

func (r Rat) String() string {
	if r.b.IsInt() {
		return r.b.Num().String()
	}
	return r.b.String()
}

func evalIndex(ec *evalCtx, l, r Value, lp, rp int) Value {
	left, ok := l.(Indexer)
	if !ok {
		ec.errorf(lp, "%s value cannot be indexing", l.Type())
	}

	right, ok := r.(String)
	if !ok {
		ec.errorf(rp, "%s invalid cannot be used as index", r.Type())
	}

	return left.Index(right)
}

// FromJSONInterface converts a interface{} that results from json.Unmarshal to
// a Value.
func FromJSONInterface(v interface{}) Value {
	if v == nil {
		// TODO Use a more appropriate type
		return String("")
	}
	switch v.(type) {
	case bool:
		return Bool(v.(bool))
	case float64, string:
		// TODO Use a numeric type for float64
		return String(fmt.Sprint(v))
	case []interface{}:
		a := v.([]interface{})
		vs := make([]Value, len(a))
		for i, v := range a {
			vs[i] = FromJSONInterface(v)
		}
		return List{&vs}
	case map[string]interface{}:
		m := v.(map[string]interface{})
		m_ := make(map[Value]Value)
		for k, v := range m {
			m_[String(k)] = FromJSONInterface(v)
		}
		return Map{&m_}
	default:
		throw(fmt.Errorf("unexpected json type: %T", v))
		return nil // not reached
	}
}

// DeepEq compares two Value's deeply.
func DeepEq(a, b Value) bool {
	return reflect.DeepEqual(a, b)
}

// ToString converts a Value to String. When the Value type implements
// String(), it is used. Otherwise Repr() is used.
func ToString(v Value) string {
	if s, ok := v.(Stringer); ok {
		return s.String()
	}
	return v.Repr()
}

// ToBool converts a Value to bool. When the Value type implements Bool(), it
// is used. Otherwise it is considered true.
func ToBool(v Value) bool {
	if b, ok := v.(Booler); ok {
		return b.Bool()
	}
	return true
}

func allTrue(vs []Value) bool {
	for _, v := range vs {
		if !ToBool(v) {
			return false
		}
	}
	return true
}

// ToRat converts a Value to rat. A str can be converted to a rat if it can be
// parsed. A rat is returned as-is. Other types of values cannot be converted.
func ToRat(v Value) (Rat, error) {
	switch v := v.(type) {
	case Rat:
		return v, nil
	case String:
		r := big.Rat{}
		_, err := fmt.Sscanln(string(v), &r)
		if err != nil {
			return Rat{}, fmt.Errorf("%s cannot be parsed as rat", v.Repr())
		}
		return Rat{&r}, nil
	default:
		return Rat{}, errOnlyStrOrRat
	}
}
<commit_msg>Fix Repr of empty Map.<commit_after>package eval

//go:generate stringer -type=Type

import (
	"bytes"
	"errors"
	"fmt"
	"math/big"
	"reflect"
	"strconv"

	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/strutil"
)

// Value is the runtime representation of an elvish value.
type Value interface {
	Type() Type
	Reprer
}

type Reprer interface {
	Repr() string
}

// Booler represents a Value with a custom semantics of truthness. If a Value
// does not satisfy this interface, it is automatically true.
type Booler interface {
	Bool() bool
}

// Stringer represents a Value with a custom string representation. If a Value
// does not satisfy this interface, its Repr method is used.
type Stringer interface {
	String() string
}

// Indexer represents a Value that may be indexing.
type Indexer interface {
	Index(idx Value) Value
}

type IndexVarer interface {
	IndexVar(idx Value) Variable
}

// Caller represents a Value that may be called.
type Caller interface {
	Call(ec *evalCtx, args []Value)
}

// Type is the type of a value.
type Type int

const (
	TInvalid Type = iota
	TString
	TError
	TBool
	TList
	TMap
	TFn
	TRat
)

// Error definitions.
var (
	needIntIndex    = errors.New("need integer index")
	indexOutOfRange = errors.New("index out of range")
	errOnlyStrOrRat = errors.New("only str or rat may be converted to rat")
)

// String is just a string.
type String string

func (s String) Type() Type {
	return TString
}

func (s String) Repr() string {
	return parse.Quote(string(s))
}

func (s String) String() string {
	return string(s)
}

func (s String) Index(idx Value) Value {
	i := intIndex(idx)
	r, err := strutil.NthRune(string(s), i)
	maybeThrow(err)
	return String(string(r))
}

func intIndex(idx Value) int {
	i, err := strconv.Atoi(ToString(idx))
	if err != nil {
		err := err.(*strconv.NumError)
		if err.Err == strconv.ErrRange {
			throw(indexOutOfRange)
		} else {
			throw(needIntIndex)
		}
	}
	return i
}

// Bool represents truthness.
type Bool bool

func (b Bool) Type() Type {
	return TBool
}

func (b Bool) Repr() string {
	if b {
		return "$true"
	}
	return "$false"
}

func (b Bool) String() string {
	if b {
		return "true"
	}
	return "false"
}

func (b Bool) Bool() bool {
	return bool(b)
}

// Error represents runtime errors in elvish constructs.
type Error struct {
	inner error
}

func (e Error) Type() Type {
	return TError
}

func (e Error) Repr() string {
	if e.inner == nil {
		return "$ok"
	}
	if r, ok := e.inner.(Reprer); ok {
		return r.Repr()
	}
	return "?(error " + parse.Quote(e.inner.Error()) + ")"
}

func (e Error) String() string {
	if e.inner == nil {
		return "ok"
	}
	return e.inner.Error()
}

func (e Error) Bool() bool {
	return e.inner == nil
}

// Common Error values.
var (
	OK             = Error{nil}
	GenericFailure = Error{errors.New("generic failure")}
)

func NewFailure(text string) Error {
	return Error{errors.New(text)}
}

// multiError is multiple errors packed into one. It is used for reporting
// errors of pipelines, in which multiple forms may error.
type multiError struct {
	errors []Error
}

func (me multiError) Repr() string {
	b := new(bytes.Buffer)
	b.WriteString("?(multi-error")
	for _, e := range me.errors {
		b.WriteString(" ")
		b.WriteString(e.Repr())
	}
	b.WriteString(")")
	return b.String()
}

func (me multiError) Error() string {
	b := new(bytes.Buffer)
	b.WriteString("(")
	for i, e := range me.errors {
		if i > 0 {
			b.WriteString(" | ")
		}
		b.WriteString(e.inner.Error())
	}
	b.WriteString(")")
	return b.String()
}

func newMultiError(es ...Error) Error {
	return Error{multiError{es}}
}

// Flow is a special type of Error used for control flows.
type flow uint

const (
	Return flow = iota
	Break
	Continue
)

var flowNames = [...]string{
	"return", "break", "continue",
}

func (f flow) Repr() string {
	return "?(" + f.Error() + ")"
}

func (f flow) Error() string {
	if f >= flow(len(flowNames)) {
		return fmt.Sprintf("!(BAD FLOW: %v)", f)
	}
	return flowNames[f]
}

func allok(es []Error) bool {
	for _, e := range es {
		if e.inner != nil {
			return false
		}
	}
	return true
}

// List is a list of Value's.
type List struct {
	inner *[]Value
}

func NewList(vs ...Value) List {
	return List{&vs}
}

func (l List) Type() Type {
	return TList
}

func (l List) appendStrings(ss []string) {
	for _, s := range ss {
		*l.inner = append(*l.inner, String(s))
	}
}

func (l List) Repr() string {
	buf := new(bytes.Buffer)
	buf.WriteRune('[')
	for i, v := range *l.inner {
		if i > 0 {
			buf.WriteByte(' ')
		}
		buf.WriteString(v.Repr())
	}
	buf.WriteRune(']')
	return buf.String()
}

func (l List) Index(idx Value) Value {
	i := intIndex(idx)

	if i < 0 {
		i += len(*l.inner)
	}
	if i < 0 || i >= len(*l.inner) {
		throw(indexOutOfRange)
	}
	return (*l.inner)[i]
}

func (l List) IndexVar(idx Value) Variable {
	return listElem{l, intIndex(idx)}
}

// Map is a map from string to Value.
type Map struct {
	inner *map[Value]Value
}

func NewMap() Map {
	return Map{&map[Value]Value{}}
}

func (m Map) Type() Type {
	return TMap
}

func (m Map) Repr() string {
	buf := new(bytes.Buffer)
	buf.WriteRune('[')
	for k, v := range *m.inner {
		if buf.Len() > 1 {
			buf.WriteByte(' ')
		}
		buf.WriteByte('&')
		buf.WriteString(k.Repr())
		buf.WriteByte(' ')
		buf.WriteString(v.Repr())
	}
	if buf.Len() == 1 {
		buf.WriteByte('&')
	}
	buf.WriteRune(']')
	return buf.String()
}

func (m Map) Index(idx Value) Value {
	v, ok := (*m.inner)[idx]
	if !ok {
		throw(errors.New("no such key: " + idx.Repr()))
	}
	return v
}

func (m Map) IndexVar(idx Value) Variable {
	return mapElem{m, idx}
}

// Closure is a closure.
type Closure struct {
	ArgNames []string
	Op       op
	Captured map[string]Variable
}

func (c *Closure) Type() Type {
	return TFn
}

func newClosure(a []string, op op, e map[string]Variable) *Closure {
	return &Closure{a, op, e}
}

func (c *Closure) Repr() string {
	return fmt.Sprintf("<Closure%v>", *c)
}

// BuiltinFn is a builtin function.
type BuiltinFn struct {
	Name string
	Impl func(*evalCtx, []Value)
}

func (b *BuiltinFn) Type() Type {
	return TFn
}

func (b *BuiltinFn) Repr() string {
	return "$" + FnPrefix + b.Name
}

// ExternalCmd is an external command.
type ExternalCmd struct {
	Name string
}

func (e ExternalCmd) Type() Type {
	return TFn
}

func (e ExternalCmd) Repr() string {
	return "<external " + e.Name + " >"
}

// Rat is a rational number.
type Rat struct {
	b *big.Rat
}

func (r Rat) Type() Type {
	return TRat
}

func (r Rat) Repr() string {
	return "(rat " + r.String() + ")"
}

func (r Rat) String() string {
	if r.b.IsInt() {
		return r.b.Num().String()
	}
	return r.b.String()
}

func evalIndex(ec *evalCtx, l, r Value, lp, rp int) Value {
	left, ok := l.(Indexer)
	if !ok {
		ec.errorf(lp, "%s value cannot be indexing", l.Type())
	}

	right, ok := r.(String)
	if !ok {
		ec.errorf(rp, "%s invalid cannot be used as index", r.Type())
	}

	return left.Index(right)
}

// FromJSONInterface converts a interface{} that results from json.Unmarshal to
// a Value.
func FromJSONInterface(v interface{}) Value {
	if v == nil {
		// TODO Use a more appropriate type
		return String("")
	}
	switch v.(type) {
	case bool:
		return Bool(v.(bool))
	case float64, string:
		// TODO Use a numeric type for float64
		return String(fmt.Sprint(v))
	case []interface{}:
		a := v.([]interface{})
		vs := make([]Value, len(a))
		for i, v := range a {
			vs[i] = FromJSONInterface(v)
		}
		return List{&vs}
	case map[string]interface{}:
		m := v.(map[string]interface{})
		m_ := make(map[Value]Value)
		for k, v := range m {
			m_[String(k)] = FromJSONInterface(v)
		}
		return Map{&m_}
	default:
		throw(fmt.Errorf("unexpected json type: %T", v))
		return nil // not reached
	}
}

// DeepEq compares two Value's deeply.
func DeepEq(a, b Value) bool {
	return reflect.DeepEqual(a, b)
}

// ToString converts a Value to String. When the Value type implements
// String(), it is used. Otherwise Repr() is used.
func ToString(v Value) string {
	if s, ok := v.(Stringer); ok {
		return s.String()
	}
	return v.Repr()
}

// ToBool converts a Value to bool. When the Value type implements Bool(), it
// is used. Otherwise it is considered true.
func ToBool(v Value) bool {
	if b, ok := v.(Booler); ok {
		return b.Bool()
	}
	return true
}

func allTrue(vs []Value) bool {
	for _, v := range vs {
		if !ToBool(v) {
			return false
		}
	}
	return true
}

// ToRat converts a Value to rat. A str can be converted to a rat if it can be
// parsed. A rat is returned as-is. Other types of values cannot be converted.
func ToRat(v Value) (Rat, error) {
	switch v := v.(type) {
	case Rat:
		return v, nil
	case String:
		r := big.Rat{}
		_, err := fmt.Sscanln(string(v), &r)
		if err != nil {
			return Rat{}, fmt.Errorf("%s cannot be parsed as rat", v.Repr())
		}
		return Rat{&r}, nil
	default:
		return Rat{}, errOnlyStrOrRat
	}
}
<|endoftext|>