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