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