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

     1  // Copyright 2013, 2015 Canonical Ltd.
     2  // Copyright 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package containerinit
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"path/filepath"
    12  	"strings"
    13  	"text/template"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/utils/proxy"
    18  
    19  	"github.com/juju/juju/cloudconfig"
    20  	"github.com/juju/juju/cloudconfig/cloudinit"
    21  	"github.com/juju/juju/cloudconfig/instancecfg"
    22  	"github.com/juju/juju/container"
    23  	"github.com/juju/juju/environs"
    24  	"github.com/juju/juju/service"
    25  	"github.com/juju/juju/service/common"
    26  )
    27  
    28  var (
    29  	logger = loggo.GetLogger("juju.cloudconfig.containerinit")
    30  )
    31  
    32  // WriteUserData generates the cloud-init user-data using the
    33  // specified machine and network config for a container, and writes
    34  // the serialized form out to a cloud-init file in the directory
    35  // specified.
    36  func WriteUserData(
    37  	instanceConfig *instancecfg.InstanceConfig,
    38  	networkConfig *container.NetworkConfig,
    39  	directory string,
    40  ) (string, error) {
    41  	userData, err := cloudInitUserData(instanceConfig, networkConfig)
    42  	if err != nil {
    43  		logger.Errorf("failed to create user data: %v", err)
    44  		return "", err
    45  	}
    46  	return WriteCloudInitFile(directory, userData)
    47  }
    48  
    49  // WriteCloudInitFile writes the data out to a cloud-init file in the
    50  // directory specified, and returns the filename.
    51  func WriteCloudInitFile(directory string, userData []byte) (string, error) {
    52  	userDataFilename := filepath.Join(directory, "cloud-init")
    53  	if err := ioutil.WriteFile(userDataFilename, userData, 0644); err != nil {
    54  		logger.Errorf("failed to write user data: %v", err)
    55  		return "", err
    56  	}
    57  	return userDataFilename, nil
    58  }
    59  
    60  // networkConfigTemplate defines how to render /etc/network/interfaces
    61  // file for a container with one or more NICs.
    62  const networkConfigTemplate = `
    63  # loopback interface
    64  auto lo
    65  iface lo inet loopback{{define "static"}}
    66  {{.InterfaceName | printf "# interface %q"}}{{if not .NoAutoStart}}
    67  auto {{.InterfaceName}}{{end}}
    68  iface {{.InterfaceName}} inet manual{{if gt (len .DNSServers) 0}}
    69      dns-nameservers{{range $dns := .DNSServers}} {{$dns.Value}}{{end}}{{end}}{{if gt (len .DNSSearch) 0}}
    70      dns-search {{.DNSSearch}}{{end}}
    71      pre-up ip address add {{.Address.Value}}/32 dev {{.InterfaceName}} &> /dev/null || true
    72      up ip route replace {{.GatewayAddress.Value}} dev {{.InterfaceName}}
    73      up ip route replace default via {{.GatewayAddress.Value}}
    74      down ip route del default via {{.GatewayAddress.Value}} &> /dev/null || true
    75      down ip route del {{.GatewayAddress.Value}} dev {{.InterfaceName}} &> /dev/null || true
    76      post-down ip address del {{.Address.Value}}/32 dev {{.InterfaceName}} &> /dev/null || true
    77  {{end}}{{define "dhcp"}}
    78  {{.InterfaceName | printf "# interface %q"}}{{if not .NoAutoStart}}
    79  auto {{.InterfaceName}}{{end}}
    80  iface {{.InterfaceName}} inet dhcp
    81  {{end}}{{range $nic := . }}{{if eq $nic.ConfigType "static"}}
    82  {{template "static" $nic}}{{else}}{{template "dhcp" $nic}}{{end}}{{end}}`
    83  
    84  var networkInterfacesFile = "/etc/network/interfaces"
    85  
    86  // GenerateNetworkConfig renders a network config for one or more
    87  // network interfaces, using the given non-nil networkConfig
    88  // containing a non-empty Interfaces field.
    89  func GenerateNetworkConfig(networkConfig *container.NetworkConfig) (string, error) {
    90  	if networkConfig == nil || len(networkConfig.Interfaces) == 0 {
    91  		// Don't generate networking config.
    92  		logger.Tracef("no network config to generate")
    93  		return "", nil
    94  	}
    95  
    96  	// Render the config first.
    97  	tmpl, err := template.New("config").Parse(networkConfigTemplate)
    98  	if err != nil {
    99  		return "", errors.Annotate(err, "cannot parse network config template")
   100  	}
   101  
   102  	var buf bytes.Buffer
   103  	if err = tmpl.Execute(&buf, networkConfig.Interfaces); err != nil {
   104  		return "", errors.Annotate(err, "cannot render network config")
   105  	}
   106  
   107  	return buf.String(), nil
   108  }
   109  
   110  // newCloudInitConfigWithNetworks creates a cloud-init config which
   111  // might include per-interface networking config if both networkConfig
   112  // is not nil and its Interfaces field is not empty.
   113  func newCloudInitConfigWithNetworks(series string, networkConfig *container.NetworkConfig) (cloudinit.CloudConfig, error) {
   114  	cloudConfig, err := cloudinit.New(series)
   115  	if err != nil {
   116  		return nil, errors.Trace(err)
   117  	}
   118  	config, err := GenerateNetworkConfig(networkConfig)
   119  	if err != nil || len(config) == 0 {
   120  		return cloudConfig, errors.Trace(err)
   121  	}
   122  
   123  	// Now add it to cloud-init as a file created early in the boot process.
   124  	cloudConfig.AddBootTextFile(networkInterfacesFile, config, 0644)
   125  	return cloudConfig, nil
   126  }
   127  
   128  func cloudInitUserData(
   129  	instanceConfig *instancecfg.InstanceConfig,
   130  	networkConfig *container.NetworkConfig,
   131  ) ([]byte, error) {
   132  	cloudConfig, err := newCloudInitConfigWithNetworks(instanceConfig.Series, networkConfig)
   133  	udata, err := cloudconfig.NewUserdataConfig(instanceConfig, cloudConfig)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	err = udata.Configure()
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	// Run ifconfig to get the addresses of the internal container at least
   142  	// logged in the host.
   143  	cloudConfig.AddRunCmd("ifconfig")
   144  
   145  	if instanceConfig.MachineContainerHostname != "" {
   146  		cloudConfig.SetAttr("hostname", instanceConfig.MachineContainerHostname)
   147  	}
   148  
   149  	data, err := cloudConfig.RenderYAML()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	return data, nil
   154  }
   155  
   156  // templateUserData returns a minimal user data necessary for the template.
   157  // This should have the authorized keys, base packages, the cloud archive if
   158  // necessary,  initial apt proxy config, and it should do the apt-get
   159  // update/upgrade initially.
   160  func TemplateUserData(
   161  	series string,
   162  	authorizedKeys string,
   163  	aptProxy proxy.Settings,
   164  	aptMirror string,
   165  	enablePackageUpdates bool,
   166  	enableOSUpgrades bool,
   167  	networkConfig *container.NetworkConfig,
   168  ) ([]byte, error) {
   169  	var config cloudinit.CloudConfig
   170  	var err error
   171  	if networkConfig != nil {
   172  		config, err = newCloudInitConfigWithNetworks(series, networkConfig)
   173  		if err != nil {
   174  			return nil, errors.Trace(err)
   175  		}
   176  	} else {
   177  		config, err = cloudinit.New(series)
   178  		if err != nil {
   179  			return nil, errors.Trace(err)
   180  		}
   181  	}
   182  	config.AddScripts(
   183  		"set -xe", // ensure we run all the scripts or abort.
   184  	)
   185  	// For LTS series which need support for the cloud-tools archive,
   186  	// we need to enable apt-get update regardless of the environ
   187  	// setting, otherwise provisioning will fail.
   188  	if series == "precise" && !enablePackageUpdates {
   189  		logger.Warningf("series %q requires cloud-tools archive: enabling updates", series)
   190  		enablePackageUpdates = true
   191  	}
   192  
   193  	config.AddSSHAuthorizedKeys(authorizedKeys)
   194  	if enablePackageUpdates && config.RequiresCloudArchiveCloudTools() {
   195  		config.AddCloudArchiveCloudTools()
   196  	}
   197  	config.AddPackageCommands(aptProxy, aptMirror, enablePackageUpdates, enableOSUpgrades)
   198  
   199  	initSystem, err := service.VersionInitSystem(series)
   200  	if err != nil {
   201  		return nil, errors.Trace(err)
   202  	}
   203  	cmds, err := shutdownInitCommands(initSystem, series)
   204  	if err != nil {
   205  		return nil, errors.Trace(err)
   206  	}
   207  	config.AddScripts(strings.Join(cmds, "\n"))
   208  
   209  	data, err := config.RenderYAML()
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	return data, nil
   214  }
   215  
   216  // defaultEtcNetworkInterfaces is the contents of
   217  // /etc/network/interfaces file which is left on the template LXC
   218  // container on shutdown. This is needed to allow cloned containers to
   219  // start in case no network config is provided during cloud-init, e.g.
   220  // when AUFS is used or with the local provider (see bug #1431888).
   221  const defaultEtcNetworkInterfaces = `
   222  # loopback interface
   223  auto lo
   224  iface lo inet loopback
   225  
   226  # primary interface
   227  auto eth0
   228  iface eth0 inet dhcp
   229  `
   230  
   231  func shutdownInitCommands(initSystem, series string) ([]string, error) {
   232  	// These files are removed just before the template shuts down.
   233  	cleanupOnShutdown := []string{
   234  		// We remove any dhclient lease files so there's no chance a
   235  		// clone to reuse a lease from the template it was cloned
   236  		// from.
   237  		"/var/lib/dhcp/dhclient*",
   238  		// Both of these sets of files below are recreated on boot and
   239  		// if we leave them in the template's rootfs boot logs coming
   240  		// from cloned containers will be appended. It's better to
   241  		// keep clean logs for diagnosing issues / debugging.
   242  		"/var/log/cloud-init*.log",
   243  	}
   244  
   245  	// Using EOC below as the template shutdown script is itself
   246  	// passed through cat > ... < EOF.
   247  	replaceNetConfCmd := fmt.Sprintf(
   248  		"/bin/cat > /etc/network/interfaces << EOC%sEOC\n  ",
   249  		defaultEtcNetworkInterfaces,
   250  	)
   251  	paths := strings.Join(cleanupOnShutdown, " ")
   252  	removeCmd := fmt.Sprintf("/bin/rm -fr %s\n  ", paths)
   253  	shutdownCmd := "/sbin/shutdown -h now"
   254  	name := "juju-template-restart"
   255  	desc := "juju shutdown job"
   256  
   257  	execStart := shutdownCmd
   258  	if environs.AddressAllocationEnabled() {
   259  		// Only do the cleanup and replacement of /e/n/i when address
   260  		// allocation feature flag is enabled.
   261  		execStart = replaceNetConfCmd + removeCmd + shutdownCmd
   262  	}
   263  
   264  	conf := common.Conf{
   265  		Desc:         desc,
   266  		Transient:    true,
   267  		AfterStopped: "cloud-final",
   268  		ExecStart:    execStart,
   269  	}
   270  	// systemd uses targets for synchronization of services
   271  	if initSystem == service.InitSystemSystemd {
   272  		conf.AfterStopped = "cloud-config.target"
   273  	}
   274  
   275  	svc, err := service.NewService(name, conf, series)
   276  	if err != nil {
   277  		return nil, errors.Trace(err)
   278  	}
   279  
   280  	cmds, err := svc.InstallCommands()
   281  	if err != nil {
   282  		return nil, errors.Trace(err)
   283  	}
   284  
   285  	startCommands, err := svc.StartCommands()
   286  	if err != nil {
   287  		return nil, errors.Trace(err)
   288  	}
   289  	cmds = append(cmds, startCommands...)
   290  
   291  	return cmds, nil
   292  }