launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/cloudinit/options.go (about)

     1  // Copyright 2011, 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloudinit
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"launchpad.net/juju-core/utils"
    10  	"launchpad.net/juju-core/utils/ssh"
    11  )
    12  
    13  // CloudToolsPrefsPath defines the default location of
    14  // apt_preferences(5) file for the cloud-tools pocket.
    15  const CloudToolsPrefsPath = "/etc/apt/preferences.d/50-cloud-tools"
    16  
    17  // SetAttr sets an arbitrary attribute in the cloudinit config.
    18  // If value is nil the attribute will be deleted; otherwise
    19  // the value will be marshalled according to the rules
    20  // of the goyaml Marshal function.
    21  func (cfg *Config) SetAttr(name string, value interface{}) {
    22  	cfg.set(name, value != nil, value)
    23  }
    24  
    25  // SetUser sets the user name that will be used for some other options.
    26  // The user will be assumed to already exist in the machine image.
    27  // The default user is "ubuntu".
    28  func (cfg *Config) SetUser(user string) {
    29  	cfg.set("user", user != "", user)
    30  }
    31  
    32  // SetAptUpgrade sets whether cloud-init runs "apt-get upgrade"
    33  // on first boot.
    34  func (cfg *Config) SetAptUpgrade(yes bool) {
    35  	cfg.set("apt_upgrade", yes, yes)
    36  }
    37  
    38  // AptUpgrade returns the value set by SetAptUpgrade, or
    39  // false if no call to SetAptUpgrade has been made.
    40  func (cfg *Config) AptUpgrade() bool {
    41  	update, _ := cfg.attrs["apt_upgrade"].(bool)
    42  	return update
    43  }
    44  
    45  // SetUpdate sets whether cloud-init runs "apt-get update"
    46  // on first boot.
    47  func (cfg *Config) SetAptUpdate(yes bool) {
    48  	cfg.set("apt_update", yes, yes)
    49  }
    50  
    51  // AptUpdate returns the value set by SetAptUpdate, or
    52  // false if no call to SetAptUpdate has been made.
    53  func (cfg *Config) AptUpdate() bool {
    54  	update, _ := cfg.attrs["apt_update"].(bool)
    55  	return update
    56  }
    57  
    58  // SetAptProxy sets the URL to be used as the apt
    59  // proxy.
    60  func (cfg *Config) SetAptProxy(url string) {
    61  	cfg.set("apt_proxy", url != "", url)
    62  }
    63  
    64  // SetAptMirror sets the URL to be used as the apt
    65  // mirror site. If not set, the URL is selected based
    66  // on cloud metadata in EC2 - <region>.archive.ubuntu.com
    67  func (cfg *Config) SetAptMirror(url string) {
    68  	cfg.set("apt_mirror", url != "", url)
    69  }
    70  
    71  // SetAptPreserveSourcesList sets whether /etc/apt/sources.list
    72  // is overwritten by the mirror. If true, SetAptMirror above
    73  // will have no effect.
    74  func (cfg *Config) SetAptPreserveSourcesList(yes bool) {
    75  	cfg.set("apt_mirror", yes, yes)
    76  }
    77  
    78  // AddAptSource adds an apt source. The key holds the
    79  // public key of the source, in the form expected by apt-key(8).
    80  func (cfg *Config) AddAptSource(name, key string, prefs *AptPreferences) {
    81  	src, _ := cfg.attrs["apt_sources"].([]*AptSource)
    82  	cfg.attrs["apt_sources"] = append(src,
    83  		&AptSource{
    84  			Source: name,
    85  			Key:    key,
    86  			Prefs:  prefs,
    87  		},
    88  	)
    89  	if prefs != nil {
    90  		// Create the apt preferences file.
    91  		cfg.AddFile(prefs.Path, prefs.FileContents(), 0644)
    92  	}
    93  }
    94  
    95  // AptSources returns the apt sources added with AddAptSource.
    96  func (cfg *Config) AptSources() []*AptSource {
    97  	srcs, _ := cfg.attrs["apt_sources"].([]*AptSource)
    98  	return srcs
    99  }
   100  
   101  // SetDebconfSelections provides preseeded debconf answers
   102  // for the boot process. The given answers will be used as input
   103  // to debconf-set-selections(1).
   104  func (cfg *Config) SetDebconfSelections(answers string) {
   105  	cfg.set("debconf_selections", answers != "", answers)
   106  }
   107  
   108  // AddPackage adds a package to be installed on first boot.
   109  // If any packages are specified, "apt-get update"
   110  // will be called.
   111  func (cfg *Config) AddPackage(name string) {
   112  	cfg.attrs["packages"] = append(cfg.Packages(), name)
   113  }
   114  
   115  // AddPackageFromTargetRelease adds a package to be installed using
   116  // the given release, passed to apt-get with the --target-release
   117  // argument.
   118  func (cfg *Config) AddPackageFromTargetRelease(packageName, targetRelease string) {
   119  	cfg.AddPackage(fmt.Sprintf("--target-release '%s' '%s'", targetRelease, packageName))
   120  }
   121  
   122  // Packages returns a list of packages that will be
   123  // installed on first boot.
   124  func (cfg *Config) Packages() []string {
   125  	pkgs, _ := cfg.attrs["packages"].([]string)
   126  	return pkgs
   127  }
   128  
   129  func (cfg *Config) addCmd(kind string, c *command) {
   130  	cfg.attrs[kind] = append(cfg.getCmds(kind), c)
   131  }
   132  
   133  func (cfg *Config) getCmds(kind string) []*command {
   134  	cmds, _ := cfg.attrs[kind].([]*command)
   135  	return cmds
   136  }
   137  
   138  // getCmdStrings returns a slice of interface{}, where
   139  // each interface's dynamic value is either a string
   140  // or slice of strings.
   141  func (cfg *Config) getCmdStrings(kind string) []interface{} {
   142  	cmds := cfg.getCmds(kind)
   143  	result := make([]interface{}, len(cmds))
   144  	for i, cmd := range cmds {
   145  		if cmd.args != nil {
   146  			result[i] = append([]string{}, cmd.args...)
   147  		} else {
   148  			result[i] = cmd.literal
   149  		}
   150  	}
   151  	return result
   152  }
   153  
   154  // BootCmds returns a list of commands added with
   155  // AddBootCmd*.
   156  //
   157  // Each element in the resultant slice is either a
   158  // string or []string, corresponding to how the command
   159  // was added.
   160  func (cfg *Config) BootCmds() []interface{} {
   161  	return cfg.getCmdStrings("bootcmd")
   162  }
   163  
   164  // RunCmds returns a list of commands that will be
   165  // run at first boot.
   166  //
   167  // Each element in the resultant slice is either a
   168  // string or []string, corresponding to how the command
   169  // was added.
   170  func (cfg *Config) RunCmds() []interface{} {
   171  	return cfg.getCmdStrings("runcmd")
   172  }
   173  
   174  // AddRunCmd adds a command to be executed
   175  // at first boot. The command will be run
   176  // by the shell with any metacharacters retaining
   177  // their special meaning (that is, no quoting takes place).
   178  func (cfg *Config) AddRunCmd(cmd string) {
   179  	cfg.addCmd("runcmd", &command{literal: cmd})
   180  }
   181  
   182  // AddRunCmdArgs is like AddRunCmd except that the command
   183  // will be executed with the given arguments properly quoted.
   184  func (cfg *Config) AddRunCmdArgs(args ...string) {
   185  	cfg.addCmd("runcmd", &command{args: args})
   186  }
   187  
   188  // AddBootCmd is like AddRunCmd except that the
   189  // command will run very early in the boot process,
   190  // and it will run on every boot, not just the first time.
   191  func (cfg *Config) AddBootCmd(cmd string) {
   192  	cfg.addCmd("bootcmd", &command{literal: cmd})
   193  }
   194  
   195  // AddBootCmdArgs is like AddBootCmd except that the command
   196  // will be executed with the given arguments properly quoted.
   197  func (cfg *Config) AddBootCmdArgs(args ...string) {
   198  	cfg.addCmd("bootcmd", &command{args: args})
   199  }
   200  
   201  // SetDisableEC2Metadata sets whether access to the
   202  // EC2 metadata service is disabled early in boot
   203  // via a null route ( route del -host 169.254.169.254 reject).
   204  func (cfg *Config) SetDisableEC2Metadata(yes bool) {
   205  	cfg.set("disable_ec2_metadata", yes, yes)
   206  }
   207  
   208  // SetFinalMessage sets to message that will be written
   209  // when the system has finished booting for the first time.
   210  // By default, the message is:
   211  // "cloud-init boot finished at $TIMESTAMP. Up $UPTIME seconds".
   212  func (cfg *Config) SetFinalMessage(msg string) {
   213  	cfg.set("final_message", msg != "", msg)
   214  }
   215  
   216  // SetLocale sets the locale; it defaults to en_US.UTF-8.
   217  func (cfg *Config) SetLocale(locale string) {
   218  	cfg.set("locale", locale != "", locale)
   219  }
   220  
   221  // AddMount adds a mount point. The given
   222  // arguments will be used as a line in /etc/fstab.
   223  func (cfg *Config) AddMount(args ...string) {
   224  	mounts, _ := cfg.attrs["mounts"].([][]string)
   225  	cfg.attrs["mounts"] = append(mounts, args)
   226  }
   227  
   228  // OutputKind represents a destination for command output.
   229  type OutputKind string
   230  
   231  const (
   232  	OutInit   OutputKind = "init"
   233  	OutConfig OutputKind = "config"
   234  	OutFinal  OutputKind = "final"
   235  	OutAll    OutputKind = "all"
   236  )
   237  
   238  // SetOutput specifies destination for command output.
   239  // Valid values for the kind "init", "config", "final" and "all".
   240  // Each of stdout and stderr can take one of the following forms:
   241  //   >>file
   242  //       appends to file
   243  //   >file
   244  //       overwrites file
   245  //   |command
   246  //       pipes to the given command.
   247  func (cfg *Config) SetOutput(kind OutputKind, stdout, stderr string) {
   248  	out, _ := cfg.attrs["output"].(map[string]interface{})
   249  	if out == nil {
   250  		out = make(map[string]interface{})
   251  	}
   252  	if stderr == "" {
   253  		out[string(kind)] = stdout
   254  	} else {
   255  		out[string(kind)] = []string{stdout, stderr}
   256  	}
   257  	cfg.attrs["output"] = out
   258  }
   259  
   260  // Output returns the output destination passed to SetOutput for
   261  // the specified output kind.
   262  func (cfg *Config) Output(kind OutputKind) (stdout, stderr string) {
   263  	if out, ok := cfg.attrs["output"].(map[string]interface{}); ok {
   264  		switch out := out[string(kind)].(type) {
   265  		case string:
   266  			stdout = out
   267  		case []string:
   268  			stdout, stderr = out[0], out[1]
   269  		}
   270  	}
   271  	return stdout, stderr
   272  }
   273  
   274  // AddSSHKey adds a pre-generated ssh key to the
   275  // server keyring. Keys that are added like this will be
   276  // written to /etc/ssh and new random keys will not
   277  // be generated.
   278  func (cfg *Config) AddSSHKey(keyType SSHKeyType, keyData string) {
   279  	keys, _ := cfg.attrs["ssh_keys"].(map[SSHKeyType]string)
   280  	if keys == nil {
   281  		keys = make(map[SSHKeyType]string)
   282  		cfg.attrs["ssh_keys"] = keys
   283  	}
   284  	keys[keyType] = keyData
   285  }
   286  
   287  // SetDisableRoot sets whether ssh login is disabled to the root account
   288  // via the ssh authorized key associated with the instance metadata.
   289  // It is true by default.
   290  func (cfg *Config) SetDisableRoot(disable bool) {
   291  	// note that disable_root defaults to true, so we include
   292  	// the option only if disable is false.
   293  	cfg.set("disable_root", !disable, disable)
   294  }
   295  
   296  // AddSSHAuthorizedKey adds a set of keys in
   297  // ssh authorized_keys format (see ssh(8) for details)
   298  // that will be added to ~/.ssh/authorized_keys for the
   299  // configured user (see SetUser).
   300  func (cfg *Config) AddSSHAuthorizedKeys(keyData string) {
   301  	akeys, _ := cfg.attrs["ssh_authorized_keys"].([]string)
   302  	keys := ssh.SplitAuthorisedKeys(keyData)
   303  	for _, key := range keys {
   304  		// Ensure the key has a comment prepended with "Juju:" so we
   305  		// can distinguish between Juju managed keys and those added
   306  		// externally.
   307  		jujuKey := ssh.EnsureJujuComment(key)
   308  		akeys = append(akeys, jujuKey)
   309  	}
   310  	cfg.attrs["ssh_authorized_keys"] = akeys
   311  }
   312  
   313  // AddScripts is a simple shorthand for calling AddRunCmd multiple times.
   314  func (cfg *Config) AddScripts(scripts ...string) {
   315  	for _, s := range scripts {
   316  		cfg.AddRunCmd(s)
   317  	}
   318  }
   319  
   320  // AddFile will add multiple run_cmd entries to safely set the contents of a
   321  // specific file to the requested contents.
   322  func (cfg *Config) AddFile(filename, data string, mode uint) {
   323  	// Note: recent versions of cloud-init have the "write_files"
   324  	// module, which can write arbitrary files. We currently support
   325  	// 12.04 LTS, which uses an older version of cloud-init without
   326  	// this module.
   327  	p := shquote(filename)
   328  	// Don't use the shell's echo builtin here; the interpretation
   329  	// of escape sequences differs between shells, namely bash and
   330  	// dash. Instead, we use printf (or we could use /bin/echo).
   331  	cfg.AddScripts(
   332  		fmt.Sprintf("install -D -m %o /dev/null %s", mode, p),
   333  		fmt.Sprintf(`printf '%%s\n' %s > %s`, shquote(data), p),
   334  	)
   335  }
   336  
   337  func shquote(p string) string {
   338  	return utils.ShQuote(p)
   339  }
   340  
   341  // TODO
   342  // byobu
   343  // grub_dpkg
   344  // mcollective
   345  // phone_home
   346  // puppet
   347  // resizefs
   348  // rightscale_userdata
   349  // rsyslog
   350  // scripts_per_boot
   351  // scripts_per_instance
   352  // scripts_per_once
   353  // scripts_user
   354  // set_hostname
   355  // set_passwords
   356  // ssh_import_id
   357  // timezone
   358  // update_etc_hosts
   359  // update_hostname