github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/cloudconfig/userdatacfg.go (about)

     1  // Copyright 2012, 2013, 2014, 2015 Canonical Ltd.
     2  // Copyright 2014, 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package cloudconfig
     6  
     7  import (
     8  	"fmt"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names"
    12  	"github.com/juju/utils"
    13  
    14  	"github.com/juju/juju/agent"
    15  	"github.com/juju/juju/cloudconfig/cloudinit"
    16  	"github.com/juju/juju/cloudconfig/instancecfg"
    17  	"github.com/juju/juju/version"
    18  )
    19  
    20  const (
    21  	// fileSchemePrefix is the prefix for file:// URLs.
    22  	fileSchemePrefix = "file://"
    23  
    24  	// NonceFile is written by cloud-init as the last thing it does.
    25  	// The file will contain the machine's nonce. The filename is
    26  	// relative to the Juju data-dir.
    27  	NonceFile = "nonce.txt"
    28  )
    29  
    30  // UserdataConfig is the bridge between instancecfg and cloudinit
    31  // It supports different levels of configuration for instances
    32  type UserdataConfig interface {
    33  	// Configure is a convenience function that updates the cloudinit.Config
    34  	// with appropriate configuration. It will run ConfigureBasic() and
    35  	// ConfigureJuju()
    36  	Configure() error
    37  	// ConfigureBasic updates the provided cloudinit.Config with
    38  	// basic configuration to initialise an OS image.
    39  	ConfigureBasic() error
    40  	// ConfigureJuju updates the provided cloudinit.Config with configuration
    41  	// to initialise a Juju machine agent.
    42  	ConfigureJuju() error
    43  }
    44  
    45  // UserdataConfig is supposed to take in an instanceConfig as well as a
    46  // cloudinit.cloudConfig and add attributes in the cloudinit structure based on
    47  // the values inside instanceConfig and on the series
    48  func NewUserdataConfig(icfg *instancecfg.InstanceConfig, conf cloudinit.CloudConfig) (UserdataConfig, error) {
    49  	// TODO(ericsnow) bug #1426217
    50  	// Protect icfg and conf better.
    51  	operatingSystem, err := version.GetOSFromSeries(icfg.Series)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	base := baseConfigure{
    57  		tag:  names.NewMachineTag(icfg.MachineId),
    58  		icfg: icfg,
    59  		conf: conf,
    60  		os:   operatingSystem,
    61  	}
    62  
    63  	switch operatingSystem {
    64  	case version.Ubuntu:
    65  		return &unixConfigure{base}, nil
    66  	case version.CentOS:
    67  		return &unixConfigure{base}, nil
    68  	case version.Windows:
    69  		return &windowsConfigure{base}, nil
    70  	default:
    71  		return nil, errors.NotSupportedf("OS %s", icfg.Series)
    72  	}
    73  }
    74  
    75  type baseConfigure struct {
    76  	tag  names.Tag
    77  	icfg *instancecfg.InstanceConfig
    78  	conf cloudinit.CloudConfig
    79  	os   version.OSType
    80  }
    81  
    82  // addAgentInfo adds agent-required information to the agent's directory
    83  // and returns the agent directory name.
    84  func (c *baseConfigure) addAgentInfo(tag names.Tag) (agent.Config, error) {
    85  	acfg, err := c.icfg.AgentConfig(tag, c.icfg.Tools.Version.Number)
    86  	if err != nil {
    87  		return nil, errors.Trace(err)
    88  	}
    89  	acfg.SetValue(agent.AgentServiceName, c.icfg.MachineAgentServiceName)
    90  	cmds, err := acfg.WriteCommands(c.conf.ShellRenderer())
    91  	if err != nil {
    92  		return nil, errors.Annotate(err, "failed to write commands")
    93  	}
    94  	c.conf.AddScripts(cmds...)
    95  	return acfg, nil
    96  }
    97  
    98  func (c *baseConfigure) addMachineAgentToBoot() error {
    99  	svc, err := c.icfg.InitService(c.conf.ShellRenderer())
   100  	if err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  
   104  	// Make the agent run via a symbolic link to the actual tools
   105  	// directory, so it can upgrade itself without needing to change
   106  	// the init script.
   107  	toolsDir := c.icfg.ToolsDir(c.conf.ShellRenderer())
   108  	c.conf.AddScripts(c.toolsSymlinkCommand(toolsDir))
   109  
   110  	name := c.tag.String()
   111  	cmds, err := svc.InstallCommands()
   112  	if err != nil {
   113  		return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name)
   114  	}
   115  	startCmds, err := svc.StartCommands()
   116  	if err != nil {
   117  		return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name)
   118  	}
   119  	cmds = append(cmds, startCmds...)
   120  
   121  	svcName := c.icfg.MachineAgentServiceName
   122  	// TODO (gsamfira): This is temporary until we find a cleaner way to fix
   123  	// cloudinit.LogProgressCmd to not add >&9 on Windows.
   124  	targetOS, err := version.GetOSFromSeries(c.icfg.Series)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	if targetOS != version.Windows {
   129  		c.conf.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (%s)", svcName))
   130  	}
   131  	c.conf.AddScripts(cmds...)
   132  	return nil
   133  }
   134  
   135  // TODO(ericsnow) toolsSymlinkCommand should just be replaced with a
   136  // call to shell.Renderer.Symlink.
   137  
   138  func (c *baseConfigure) toolsSymlinkCommand(toolsDir string) string {
   139  	switch c.os {
   140  	case version.Windows:
   141  		return fmt.Sprintf(
   142  			`cmd.exe /C mklink /D %s %v`,
   143  			c.conf.ShellRenderer().FromSlash(toolsDir),
   144  			c.icfg.Tools.Version,
   145  		)
   146  	default:
   147  		// TODO(dfc) ln -nfs, so it doesn't fail if for some reason that
   148  		// the target already exists.
   149  		return fmt.Sprintf(
   150  			"ln -s %v %s",
   151  			c.icfg.Tools.Version,
   152  			shquote(toolsDir),
   153  		)
   154  	}
   155  }
   156  
   157  func shquote(p string) string {
   158  	return utils.ShQuote(p)
   159  }