github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"github.com/juju/utils/os"
    14  	"github.com/juju/utils/series"
    15  
    16  	"github.com/juju/juju/agent"
    17  	"github.com/juju/juju/cloudconfig/cloudinit"
    18  	"github.com/juju/juju/cloudconfig/instancecfg"
    19  )
    20  
    21  const (
    22  	// fileSchemePrefix is the prefix for file:// URLs.
    23  	fileSchemePrefix = "file://"
    24  
    25  	// NonceFile is written by cloud-init as the last thing it does.
    26  	// The file will contain the machine's nonce. The filename is
    27  	// relative to the Juju data-dir.
    28  	NonceFile = "nonce.txt"
    29  )
    30  
    31  // UserdataConfig is the bridge between instancecfg and cloudinit
    32  // It supports different levels of configuration for instances
    33  type UserdataConfig interface {
    34  	// Configure is a convenience function that updates the cloudinit.Config
    35  	// with appropriate configuration. It will run ConfigureBasic() and
    36  	// ConfigureJuju()
    37  	Configure() error
    38  	// ConfigureBasic updates the provided cloudinit.Config with
    39  	// basic configuration to initialise an OS image.
    40  	ConfigureBasic() error
    41  	// ConfigureJuju updates the provided cloudinit.Config with configuration
    42  	// to initialise a Juju machine agent.
    43  	ConfigureJuju() error
    44  }
    45  
    46  // NewUserdataConfig is supposed to take in an instanceConfig as well as a
    47  // cloudinit.cloudConfig and add attributes in the cloudinit structure based on
    48  // the values inside instanceConfig and on the series
    49  func NewUserdataConfig(icfg *instancecfg.InstanceConfig, conf cloudinit.CloudConfig) (UserdataConfig, error) {
    50  	// TODO(ericsnow) bug #1426217
    51  	// Protect icfg and conf better.
    52  	operatingSystem, err := series.GetOSFromSeries(icfg.Series)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	base := baseConfigure{
    58  		tag:  names.NewMachineTag(icfg.MachineId),
    59  		icfg: icfg,
    60  		conf: conf,
    61  		os:   operatingSystem,
    62  	}
    63  
    64  	switch operatingSystem {
    65  	case os.Ubuntu:
    66  		return &unixConfigure{base}, nil
    67  	case os.CentOS:
    68  		return &unixConfigure{base}, nil
    69  	case os.Windows:
    70  		return &windowsConfigure{base}, nil
    71  	default:
    72  		return nil, errors.NotSupportedf("OS %s", icfg.Series)
    73  	}
    74  }
    75  
    76  type baseConfigure struct {
    77  	tag  names.Tag
    78  	icfg *instancecfg.InstanceConfig
    79  	conf cloudinit.CloudConfig
    80  	os   os.OSType
    81  }
    82  
    83  // addAgentInfo adds agent-required information to the agent's directory
    84  // and returns the agent directory name.
    85  func (c *baseConfigure) addAgentInfo(tag names.Tag) (agent.Config, error) {
    86  	acfg, err := c.icfg.AgentConfig(tag, c.icfg.AgentVersion().Number)
    87  	if err != nil {
    88  		return nil, errors.Trace(err)
    89  	}
    90  	acfg.SetValue(agent.AgentServiceName, c.icfg.MachineAgentServiceName)
    91  	cmds, err := acfg.WriteCommands(c.conf.ShellRenderer())
    92  	if err != nil {
    93  		return nil, errors.Annotate(err, "failed to write commands")
    94  	}
    95  	c.conf.AddScripts(cmds...)
    96  	return acfg, nil
    97  }
    98  
    99  func (c *baseConfigure) addMachineAgentToBoot() error {
   100  	svc, err := c.icfg.InitService(c.conf.ShellRenderer())
   101  	if err != nil {
   102  		return errors.Trace(err)
   103  	}
   104  
   105  	// Make the agent run via a symbolic link to the actual tools
   106  	// directory, so it can upgrade itself without needing to change
   107  	// the init script.
   108  	toolsDir := c.icfg.ToolsDir(c.conf.ShellRenderer())
   109  	c.conf.AddScripts(c.toolsSymlinkCommand(toolsDir))
   110  
   111  	name := c.tag.String()
   112  	cmds, err := svc.InstallCommands()
   113  	if err != nil {
   114  		return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name)
   115  	}
   116  	startCmds, err := svc.StartCommands()
   117  	if err != nil {
   118  		return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name)
   119  	}
   120  	cmds = append(cmds, startCmds...)
   121  
   122  	svcName := c.icfg.MachineAgentServiceName
   123  	// TODO (gsamfira): This is temporary until we find a cleaner way to fix
   124  	// cloudinit.LogProgressCmd to not add >&9 on Windows.
   125  	targetOS, err := series.GetOSFromSeries(c.icfg.Series)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	if targetOS != os.Windows {
   130  		c.conf.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (%s)", svcName))
   131  	}
   132  	c.conf.AddScripts(cmds...)
   133  	return nil
   134  }
   135  
   136  // SetUbuntuUser creates an "ubuntu" use for unix systems so the juju client
   137  // can access the machine using ssh with the configuration we expect.
   138  // On precise, the default cloudinit version is too old to support the users
   139  // option, so instead rely on the default user being created and adding keys.
   140  // It may make sense in the future to add a "juju" user instead across
   141  // all distributions.
   142  func SetUbuntuUser(conf cloudinit.CloudConfig, authorizedKeys string) {
   143  	targetSeries := conf.GetSeries()
   144  	if targetSeries == "precise" {
   145  		conf.SetSSHAuthorizedKeys(authorizedKeys)
   146  	} else {
   147  		var groups []string
   148  		targetOS, _ := series.GetOSFromSeries(targetSeries)
   149  		switch targetOS {
   150  		case os.Ubuntu:
   151  			groups = UbuntuGroups
   152  		case os.CentOS:
   153  			groups = CentOSGroups
   154  		}
   155  		conf.AddUser(&cloudinit.User{
   156  			Name:              "ubuntu",
   157  			Groups:            groups,
   158  			Shell:             "/bin/bash",
   159  			Sudo:              []string{"ALL=(ALL) NOPASSWD:ALL"},
   160  			SSHAuthorizedKeys: authorizedKeys,
   161  		})
   162  	}
   163  }
   164  
   165  // TODO(ericsnow) toolsSymlinkCommand should just be replaced with a
   166  // call to shell.Renderer.Symlink.
   167  
   168  func (c *baseConfigure) toolsSymlinkCommand(toolsDir string) string {
   169  	switch c.os {
   170  	case os.Windows:
   171  		return fmt.Sprintf(
   172  			`cmd.exe /C mklink /D %s %v`,
   173  			c.conf.ShellRenderer().FromSlash(toolsDir),
   174  			c.icfg.AgentVersion(),
   175  		)
   176  	default:
   177  		// TODO(dfc) ln -nfs, so it doesn't fail if for some reason that
   178  		// the target already exists.
   179  		return fmt.Sprintf(
   180  			"ln -s %v %s",
   181  			c.icfg.AgentVersion(),
   182  			shquote(toolsDir),
   183  		)
   184  	}
   185  }
   186  
   187  func shquote(p string) string {
   188  	return utils.ShQuote(p)
   189  }