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