<commit_before>package ec2

import (
	"fmt"
	"launchpad.net/juju-core/cloudinit"
	"launchpad.net/juju-core/log"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/upstart"
	"path"
	"strings"
)

// machineConfig represents initialization information for a new juju machine.
// Creation of cloudinit data from this struct is largely provider-independent,
// but we'll keep it internal until we need to factor it out.
type machineConfig struct {
	// provisioner specifies whether the new machine will run a provisioning agent.
	provisioner bool

	// zookeeper specifies whether the new machine will run a zookeeper instance.
	zookeeper bool

	// instanceIdAccessor holds bash code that evaluates to the current instance id.
	instanceIdAccessor string

	// providerType identifies the provider type so the host
	// knows which kind of provider to use.
	providerType string

	// stateInfo holds the means for the new instance to communicate with the
	// juju state. Unless the new machine is running zookeeper (Zookeeper is
	// set), there must be at least one zookeeper address supplied.
	stateInfo *state.Info

	// toolsURL is the URL to be used for downloading the juju tools.
	toolsURL string

	// machineId identifies the new machine. It must be non-negative.
	machineId int

	// authorizedKeys specifies the keys that are allowed to
	// connect to the machine (see cloudinit.SSHAddAuthorizedKeys)
	// If no keys are supplied, there can be no ssh access to the node.
	// On a bootstrap machine, that is fatal. On other
	// machines it will mean that the ssh, scp and debug-hooks
	// commands cannot work.
	authorizedKeys string
}

type requiresError string

func (e requiresError) Error() string {
	return "invalid machine configuration: missing " + string(e)
}

func addScripts(c *cloudinit.Config, scripts ...string) {
	for _, s := range scripts {
		c.AddRunCmd(s)
	}
}

func newCloudInit(cfg *machineConfig) (*cloudinit.Config, error) {
	if err := verifyConfig(cfg); err != nil {
		return nil, err
	}
	c := cloudinit.New()

	c.AddSSHAuthorizedKeys(cfg.authorizedKeys)
	c.AddPackage("libzookeeper-mt2")
	if cfg.zookeeper {
		pkgs := []string{
			"default-jre-headless",
			"zookeeper",
			"zookeeperd",
		}
		for _, pkg := range pkgs {
			c.AddPackage(pkg)
		}
	}

	addScripts(c,
		"sudo mkdir -p /var/lib/juju",
		"sudo mkdir -p /var/log/juju")

	// Make a directory for the tools to live in, then fetch the
	// tools and unarchive them into it.
	addScripts(c,
		"bin="+shquote(cfg.jujuTools()),
		"mkdir -p $bin",
		fmt.Sprintf("wget -O - %s | tar xz -C $bin", shquote(cfg.toolsURL)),
	)

	addScripts(c,
		"JUJU_ZOOKEEPER="+shquote(cfg.zookeeperHostAddrs()),
		fmt.Sprintf("JUJU_MACHINE_ID=%d", cfg.machineId),
	)

	debugFlag := ""
	if log.Debug {
		debugFlag = " --debug"
	}

	// zookeeper scripts
	if cfg.zookeeper {
		addScripts(c,
			cfg.jujuTools()+"/jujud initzk"+
				" --instance-id "+cfg.instanceIdAccessor+
				" --env-type "+shquote(cfg.providerType)+
				" --zookeeper-servers localhost"+zkPortSuffix+
				debugFlag,
		)
	}

	if err := addAgentScript(c, cfg, "machine", fmt.Sprintf("--machine-id %d "+debugFlag, cfg.machineId)); err != nil {
		return nil, err
	}
	if cfg.provisioner {
		if err := addAgentScript(c, cfg, "provisioning", debugFlag); err != nil {
			return nil, err
		}
	}

	// general options
	c.SetAptUpgrade(true)
	c.SetAptUpdate(true)
	c.SetOutput(cloudinit.OutAll, "| tee -a /var/log/cloud-init-output.log", "")
	return c, nil
}

func addAgentScript(c *cloudinit.Config, cfg *machineConfig, name, args string) error {
	svc := upstart.NewService(fmt.Sprintf("jujud-%s", name))
	cmd := fmt.Sprintf(
		"%s/jujud %s --zookeeper-servers '%s' --log-file /var/log/juju/%s-agent.log %s",
		cfg.jujuTools(), name, cfg.zookeeperHostAddrs(), name, args,
	)
	conf := &upstart.Conf{
		Service: *svc,
		Desc:    fmt.Sprintf("juju %s agent", name),
		Cmd:     cmd,
	}
	cmds, err := conf.InstallCommands()
	if err != nil {
		return fmt.Errorf("cannot make cloud-init %s agent upstart script: %v", name, err)
	}
	addScripts(c, cmds...)
	return nil
}

// versionDir converts a tools URL into a name
// to use as a directory for storing the tools executables in
// by using the last element stripped of its extension.
func versionDir(toolsURL string) string {
	name := path.Base(toolsURL)
	ext := path.Ext(name)
	return name[:len(name)-len(ext)]
}

func (cfg *machineConfig) jujuTools() string {
	return "/var/lib/juju/tools/" + versionDir(cfg.toolsURL)
}

func (cfg *machineConfig) zookeeperHostAddrs() string {
	var hosts []string
	if cfg.zookeeper {
		hosts = append(hosts, "localhost"+zkPortSuffix)
	}
	if cfg.stateInfo != nil {
		hosts = append(hosts, cfg.stateInfo.Addrs...)
	}
	return strings.Join(hosts, ",")
}

// shquote quotes s so that when read by bash, no metacharacters
// within s will be interpreted as such.
func shquote(s string) string {
	// single-quote becomes single-quote, double-quote, single-quote, double-quote, single-quote
	return `'` + strings.Replace(s, `'`, `'"'"'`, -1) + `'`
}

func verifyConfig(cfg *machineConfig) error {
	if cfg.machineId < 0 {
		return fmt.Errorf("invalid machine configuration: negative machine id")
	}
	if cfg.providerType == "" {
		return requiresError("provider type")
	}
	if cfg.toolsURL == "" {
		return requiresError("tools URL")
	}
	if cfg.zookeeper {<commit_msg>environs/ec2: run agents through symbolic link<commit_after>package ec2

import (
	"fmt"
	"launchpad.net/juju-core/cloudinit"
	"launchpad.net/juju-core/log"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/upstart"
	"path"
	"strings"
)

// machineConfig represents initialization information for a new juju machine.
// Creation of cloudinit data from this struct is largely provider-independent,
// but we'll keep it internal until we need to factor it out.
type machineConfig struct {
	// provisioner specifies whether the new machine will run a provisioning agent.
	provisioner bool

	// zookeeper specifies whether the new machine will run a zookeeper instance.
	zookeeper bool

	// instanceIdAccessor holds bash code that evaluates to the current instance id.
	instanceIdAccessor string

	// providerType identifies the provider type so the host
	// knows which kind of provider to use.
	providerType string

	// stateInfo holds the means for the new instance to communicate with the
	// juju state. Unless the new machine is running zookeeper (Zookeeper is
	// set), there must be at least one zookeeper address supplied.
	stateInfo *state.Info

	// toolsURL is the URL to be used for downloading the juju tools.
	toolsURL string

	// machineId identifies the new machine. It must be non-negative.
	machineId int

	// authorizedKeys specifies the keys that are allowed to
	// connect to the machine (see cloudinit.SSHAddAuthorizedKeys)
	// If no keys are supplied, there can be no ssh access to the node.
	// On a bootstrap machine, that is fatal. On other
	// machines it will mean that the ssh, scp and debug-hooks
	// commands cannot work.
	authorizedKeys string
}

type requiresError string

func (e requiresError) Error() string {
	return "invalid machine configuration: missing " + string(e)
}

func addScripts(c *cloudinit.Config, scripts ...string) {
	for _, s := range scripts {
		c.AddRunCmd(s)
	}
}

func newCloudInit(cfg *machineConfig) (*cloudinit.Config, error) {
	if err := verifyConfig(cfg); err != nil {
		return nil, err
	}
	c := cloudinit.New()

	c.AddSSHAuthorizedKeys(cfg.authorizedKeys)
	c.AddPackage("libzookeeper-mt2")
	if cfg.zookeeper {
		c.AddPackage("default-jre-headless")
		c.AddPackage("zookeeper")
		c.AddPackage("zookeeperd")
	}

	addScripts(c,
		"sudo mkdir -p /var/lib/juju",
		"sudo mkdir -p /var/log/juju")

	// Make a directory for the tools to live in, then fetch the
	// tools and unarchive them into it.
	addScripts(c,
		"bin="+shquote(cfg.jujuTools()),
		"mkdir -p $bin",
		fmt.Sprintf("wget -O - %s | tar xz -C $bin", shquote(cfg.toolsURL)),
	)

	addScripts(c,
		"JUJU_ZOOKEEPER="+shquote(cfg.zookeeperHostAddrs()),
		fmt.Sprintf("JUJU_MACHINE_ID=%d", cfg.machineId),
	)

	debugFlag := ""
	if log.Debug {
		debugFlag = " --debug"
	}

	// zookeeper scripts
	if cfg.zookeeper {
		addScripts(c,
			cfg.jujuTools()+"/jujud initzk"+
				" --instance-id "+cfg.instanceIdAccessor+
				" --env-type "+shquote(cfg.providerType)+
				" --zookeeper-servers localhost"+zkPortSuffix+
				debugFlag,
		)
	}

	if err := addAgentScript(c, cfg, "machine", fmt.Sprintf("--machine-id %d "+debugFlag, cfg.machineId)); err != nil {
		return nil, err
	}
	if cfg.provisioner {
		if err := addAgentScript(c, cfg, "provisioning", debugFlag); err != nil {
			return nil, err
		}
	}

	// general options
	c.SetAptUpgrade(true)
	c.SetAptUpdate(true)
	c.SetOutput(cloudinit.OutAll, "| tee -a /var/log/cloud-init-output.log", "")
	return c, nil
}

func addAgentScript(c *cloudinit.Config, cfg *machineConfig, name, args string) error {
	// Make the agent run via a symbolic link to the actual tools
	// directory, so it can upgrade itself without needing to change
	// the upstart script.
	addScripts(c, fmt.Sprintf("ln -s $bin %s/%s", toolsDir, name))
	svc := upstart.NewService(fmt.Sprintf("jujud-%s", name))
	cmd := fmt.Sprintf(
		"%s/%s/jujud %s --zookeeper-servers '%s' --log-file /var/log/juju/%s-agent.log %s",
		toolsDir, name, name, cfg.zookeeperHostAddrs(), name, args,
	)
	conf := &upstart.Conf{
		Service: *svc,
		Desc:    fmt.Sprintf("juju %s agent", name),
		Cmd:     cmd,
	}
	cmds, err := conf.InstallCommands()
	if err != nil {
		return fmt.Errorf("cannot make cloud-init %s agent upstart script: %v", name, err)
	}
	addScripts(c, cmds...)
	return nil
}

// versionDir converts a tools URL into a name
// to use as a directory for storing the tools executables in
// by using the last element stripped of its extension.
func versionDir(toolsURL string) string {
	name := path.Base(toolsURL)
	ext := path.Ext(name)
	return name[:len(name)-len(ext)]
}

const toolsDir = "/var/lib/juju/tools"

func (cfg *machineConfig) jujuTools() string {
	return toolsDir + "/" + versionDir(cfg.toolsURL)
}

func (cfg *machineConfig) zookeeperHostAddrs() string {
	var hosts []string
	if cfg.zookeeper {
		hosts = append(hosts, "localhost"+zkPortSuffix)
	}
	if cfg.stateInfo != nil {
		hosts = append(hosts, cfg.stateInfo.Addrs...)
	}
	return strings.Join(hosts, ",")
}

// shquote quotes s so that when read by bash, no metacharacters
// within s will be interpreted as such.
func shquote(s string) string {
	// single-quote becomes single-quote, double-quote, single-quote, double-quote, single-quote
	return `'` + strings.Replace(s, `'`, `'"'"'`, -1) + `'`
}

func verifyConfig(cfg *machineConfig) error {
	if cfg.machineId < 0 {
		return fmt.Errorf("invalid machine configuration: negative machine id")
	}
	if cfg.providerType == "" {
		return requiresError("provider type")
	}
	if cfg.toolsURL == "" {
		return requiresError("tools URL")
	}
	if cfg.zookeeper {
{'fraction_non_alphanumeric': 0.12694205812465728, 'fraction_numerical': 0.0008225187351489673, 'mean_word_length': 3.7848710100568432, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5253944', 'n_tokens_mistral': 3883, 'n_tokens_neox': 3658, 'n_words': 1134}