github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/cloudconfig/cloudinit/cloudinit.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Copyright 2015 Cloudbase Solutions SRL 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package cloudinit 6 7 import ( 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/packaging/v3/commands" 12 "github.com/juju/packaging/v3/config" 13 "github.com/juju/utils/v3/shell" 14 "github.com/juju/utils/v3/ssh" 15 16 jujupackaging "github.com/juju/juju/packaging" 17 ) 18 19 // cloudConfig represents a set of cloud-init configuration options. 20 type cloudConfig struct { 21 // osName is the os for which this cloudConfig is made for. 22 osName string 23 24 // paccmder is a map containing PackageCommander instances for all 25 // package managers supported by a particular target. 26 paccmder map[jujupackaging.PackageManagerName]commands.PackageCommander 27 28 // pacconfer is a map containing PackagingConfigurer instances for all 29 // package managers supported by a particular target. 30 pacconfer map[jujupackaging.PackageManagerName]config.PackagingConfigurer 31 32 // renderer is the shell Renderer for this cloudConfig. 33 renderer shell.Renderer 34 35 // fileTransporter allows the cloud config to optionally emit files that it 36 // wants to be planted on the target machine. 37 fileTransporter FileTransporter 38 39 // attrs is the map of options set on this cloudConfig. 40 // main attributes used in the options map and their corresponding types: 41 // 42 // user string 43 // package_update bool 44 // package_upgrade bool 45 // packages []string 46 // runcmd []string 47 // bootcmd []string 48 // disable_ec2_metadata bool 49 // final_message string 50 // locale string 51 // mounts [][]string 52 // output map[OutputKind]string 53 // shh_keys map[SSHKeyType]string 54 // ssh_authorized_keys []string 55 // disable_root bool 56 // 57 // used only for Ubuntu but implemented as runcmds on CentOS: 58 // apt_proxy string 59 // apt_mirror string/bool 60 // apt_sources []*packaging.Source 61 // 62 // instead, the following corresponding options are used temporarily, 63 // but are translated to runcmds and removed right before rendering: 64 // package_proxy 65 // package_mirror 66 // package_sources 67 // package_preferences 68 // 69 // old TODO's: 70 // byobu 71 // grub_dpkg 72 // mcollective 73 // phone_home 74 // puppet 75 // resizefs 76 // rightscale_userdata 77 // scripts_per_boot 78 // scripts_per_instance 79 // scripts_per_once 80 // scripts_user 81 // set_hostname 82 // set_passwords 83 // ssh_import_id 84 // timezone 85 // update_etc_hosts 86 // update_hostname 87 attrs map[string]interface{} 88 89 // omitNetplanHWAddrMatch if true, causes Netplan to be rendered without 90 // a stanza that matches by MAC address in order to apply configuration to 91 // a device. 92 // This will be recruited for LXD, where we have observed 22.04 containers 93 // being assigned a different MAC to the one configured. 94 // For these cases we fall back to the default match by ID (name). 95 // MAC address matching is still required by KVM where devices are assigned 96 // different names by the kernel to those we configured. 97 omitNetplanHWAddrMatch bool 98 } 99 100 // getPackagingConfigurer is defined on the AdvancedPackagingConfig interface. 101 func (cfg *cloudConfig) getPackagingConfigurer(mgrName jujupackaging.PackageManagerName) config.PackagingConfigurer { 102 if cfg.pacconfer == nil { 103 return nil 104 } 105 return cfg.pacconfer[mgrName] 106 } 107 108 // GetOS is defined on the CloudConfig interface. 109 func (cfg *cloudConfig) GetOS() string { 110 return cfg.osName 111 } 112 113 // SetAttr is defined on the CloudConfig interface. 114 func (cfg *cloudConfig) SetAttr(name string, value interface{}) { 115 cfg.attrs[name] = value 116 } 117 118 // UnsetAttr is defined on the CloudConfig interface. 119 func (cfg *cloudConfig) UnsetAttr(name string) { 120 delete(cfg.attrs, name) 121 } 122 123 func (cfg *cloudConfig) SetFileTransporter(ft FileTransporter) { 124 cfg.fileTransporter = ft 125 } 126 127 func annotateKeys(rawKeys string) []string { 128 cfgKeys := []string{} 129 keys := ssh.SplitAuthorisedKeys(rawKeys) 130 for _, key := range keys { 131 // ensure our keys have "Juju:" prepended to differentiate 132 // Juju-managed keys and externally added ones 133 jujuKey := ssh.EnsureJujuComment(key) 134 cfgKeys = append(cfgKeys, jujuKey) 135 } 136 return cfgKeys 137 } 138 139 // AddUser is defined on the UsersConfig interface. 140 func (cfg *cloudConfig) AddUser(user *User) { 141 users, _ := cfg.attrs["users"].([]map[string]interface{}) 142 newUser := map[string]interface{}{ 143 "name": user.Name, 144 "lock_passwd": true, 145 } 146 if user.Groups != nil { 147 newUser["groups"] = user.Groups 148 } 149 if user.Shell != "" { 150 newUser["shell"] = user.Shell 151 } 152 if user.SSHAuthorizedKeys != "" { 153 newUser["ssh_authorized_keys"] = annotateKeys(user.SSHAuthorizedKeys) 154 } 155 if user.Sudo != "" { 156 newUser["sudo"] = user.Sudo 157 } 158 cfg.SetAttr("users", append(users, newUser)) 159 } 160 161 // UnsetUsers is defined on the UsersConfig interface. 162 func (cfg *cloudConfig) UnsetUsers() { 163 cfg.UnsetAttr("users") 164 } 165 166 // SetSystemUpdate is defined on the SystemUpdateConfig interface. 167 func (cfg *cloudConfig) SetSystemUpdate(yes bool) { 168 cfg.SetAttr("package_update", yes) 169 } 170 171 // UnsetSystemUpdate is defined on the SystemUpdateConfig interface. 172 func (cfg *cloudConfig) UnsetSystemUpdate() { 173 cfg.UnsetAttr("package_update") 174 } 175 176 // SystemUpdate is defined on the SystemUpdateConfig interface. 177 func (cfg *cloudConfig) SystemUpdate() bool { 178 update, _ := cfg.attrs["package_update"].(bool) 179 return update 180 } 181 182 // SetSystemUpgrade is defined on the SystemUpgradeConfig interface. 183 func (cfg *cloudConfig) SetSystemUpgrade(yes bool) { 184 cfg.SetAttr("package_upgrade", yes) 185 } 186 187 // UnsetSystemUpgrade is defined on the SystemUpgradeConfig interface. 188 func (cfg *cloudConfig) UnsetSystemUpgrade() { 189 cfg.UnsetAttr("package_upgrade") 190 } 191 192 // SystemUpgrade is defined on the SystemUpgradeConfig interface. 193 func (cfg *cloudConfig) SystemUpgrade() bool { 194 upgrade, _ := cfg.attrs["package_upgrade"].(bool) 195 return upgrade 196 } 197 198 // AddPackage is defined on the PackagingConfig interface. 199 func (cfg *cloudConfig) AddPackage(pack string) { 200 cfg.attrs["packages"] = append(cfg.Packages(), pack) 201 } 202 203 // RemovePackage is defined on the PackagingConfig interface. 204 func (cfg *cloudConfig) RemovePackage(pack string) { 205 cfg.attrs["packages"] = removeStringFromSlice(cfg.Packages(), pack) 206 } 207 208 // Packages is defined on the PackagingConfig interface. 209 func (cfg *cloudConfig) Packages() []string { 210 packs, _ := cfg.attrs["packages"].([]string) 211 return packs 212 } 213 214 // AddRunCmd is defined on the RunCmdsConfig interface. 215 func (cfg *cloudConfig) AddRunCmd(args ...string) { 216 cfg.attrs["runcmd"] = append(cfg.RunCmds(), strings.Join(args, " ")) 217 } 218 219 // AddScripts is defined on the RunCmdsConfig interface. 220 func (cfg *cloudConfig) AddScripts(script ...string) { 221 for _, line := range script { 222 cfg.AddRunCmd(line) 223 } 224 } 225 226 // PrependRunCmd is defined on the RunCmdsConfig interface. 227 func (cfg *cloudConfig) PrependRunCmd(args ...string) { 228 cfg.attrs["runcmd"] = append([]string{strings.Join(args, " ")}, cfg.RunCmds()...) 229 } 230 231 // RemoveRunCmd is defined on the RunCmdsConfig interface. 232 func (cfg *cloudConfig) RemoveRunCmd(cmd string) { 233 cfg.attrs["runcmd"] = removeStringFromSlice(cfg.RunCmds(), cmd) 234 } 235 236 // RunCmds is defined on the RunCmdsConfig interface. 237 func (cfg *cloudConfig) RunCmds() []string { 238 cmds, _ := cfg.attrs["runcmd"].([]string) 239 return cmds 240 } 241 242 // AddBootCmd is defined on the BootCmdsConfig interface. 243 func (cfg *cloudConfig) AddBootCmd(args ...string) { 244 cfg.attrs["bootcmd"] = append(cfg.BootCmds(), strings.Join(args, " ")) 245 } 246 247 // RemoveBootCmd is defined on the BootCmdsConfig interface. 248 func (cfg *cloudConfig) RemoveBootCmd(cmd string) { 249 cfg.attrs["bootcmd"] = removeStringFromSlice(cfg.BootCmds(), cmd) 250 } 251 252 // BootCmds is defined on the BootCmdsConfig interface. 253 func (cfg *cloudConfig) BootCmds() []string { 254 cmds, _ := cfg.attrs["bootcmd"].([]string) 255 return cmds 256 } 257 258 // SetDisableEC2Metadata is defined on the EC2MetadataConfig interface. 259 func (cfg *cloudConfig) SetDisableEC2Metadata(set bool) { 260 cfg.SetAttr("disable_ec2_metadata", set) 261 } 262 263 // UnsetDisableEC2Metadata is defined on the EC2MetadataConfig interface. 264 func (cfg *cloudConfig) UnsetDisableEC2Metadata() { 265 cfg.UnsetAttr("disable_ec2_metadata") 266 } 267 268 // DisableEC2Metadata is defined on the EC2MetadataConfig interface. 269 func (cfg *cloudConfig) DisableEC2Metadata() bool { 270 disEC2, _ := cfg.attrs["disable_ec2_metadata"].(bool) 271 return disEC2 272 } 273 274 // SetFinalMessage is defined on the FinalMessageConfig interface. 275 func (cfg *cloudConfig) SetFinalMessage(message string) { 276 cfg.SetAttr("final_message", message) 277 } 278 279 // UnsetFinalMessage is defined on the FinalMessageConfig interface. 280 func (cfg *cloudConfig) UnsetFinalMessage() { 281 cfg.UnsetAttr("final_message") 282 } 283 284 // FinalMessage is defined on the FinalMessageConfig interface. 285 func (cfg *cloudConfig) FinalMessage() string { 286 message, _ := cfg.attrs["final_message"].(string) 287 return message 288 } 289 290 // SetLocale is defined on the LocaleConfig interface. 291 func (cfg *cloudConfig) SetLocale(locale string) { 292 cfg.SetAttr("locale", locale) 293 } 294 295 // UnsetLocale is defined on the LocaleConfig interface. 296 func (cfg *cloudConfig) UnsetLocale() { 297 cfg.UnsetAttr("locale") 298 } 299 300 // Locale is defined on the LocaleConfig interface. 301 func (cfg *cloudConfig) Locale() string { 302 locale, _ := cfg.attrs["locale"].(string) 303 return locale 304 } 305 306 // AddMount adds takes arguments for installing a mount point in /etc/fstab 307 // The options are of the order and format specific to fstab entries: 308 // <device> <mountpoint> <filesystem> <options> <backup setting> <fsck priority> 309 func (cfg *cloudConfig) AddMount(mount ...string) { 310 mounts, _ := cfg.attrs["mounts"].([][]string) 311 cfg.SetAttr("mounts", append(mounts, mount)) 312 } 313 314 // SetOutput is defined on the OutputConfig interface. 315 func (cfg *cloudConfig) SetOutput(kind OutputKind, stdout, stderr string) { 316 out, _ := cfg.attrs["output"].(map[string]interface{}) 317 if out == nil { 318 out = make(map[string]interface{}) 319 } 320 321 if stderr == "" { 322 out[string(kind)] = stdout 323 } else { 324 out[string(kind)] = []string{stdout, stderr} 325 } 326 327 cfg.SetAttr("output", out) 328 } 329 330 // Output is defined on the OutputConfig interface. 331 func (cfg *cloudConfig) Output(kind OutputKind) (stdout, stderr string) { 332 if out, ok := cfg.attrs["output"].(map[string]interface{}); ok { 333 switch out := out[string(kind)].(type) { 334 case string: 335 stdout = out 336 case []string: 337 stdout, stderr = out[0], out[1] 338 } 339 } 340 341 return stdout, stderr 342 } 343 344 // SetSSHAuthorizedKeys is defined on the SSHAuthorizedKeysConfig interface. 345 func (cfg *cloudConfig) SetSSHAuthorizedKeys(rawKeys string) { 346 keys := annotateKeys(rawKeys) 347 if len(keys) != 0 { 348 cfg.SetAttr("ssh_authorized_keys", keys) 349 } else { 350 cfg.UnsetAttr("ssh_authorized_keys") 351 } 352 } 353 354 // SetSSHKeys is defined on the SSHKeysConfig interface. 355 func (cfg *cloudConfig) SetSSHKeys(keys SSHKeys) error { 356 if len(keys) == 0 { 357 cfg.UnsetAttr("ssh_keys") 358 return nil 359 } 360 attr := make(map[string]interface{}) 361 for _, key := range keys { 362 privateKeyName, publicKeyName, err := NamesForSSHKeyAlgorithm(key.PublicKeyAlgorithm) 363 if err != nil { 364 return errors.Trace(err) 365 } 366 attr[string(privateKeyName)] = key.Private 367 attr[string(publicKeyName)] = key.Public 368 } 369 cfg.SetAttr("ssh_keys", attr) 370 return nil 371 } 372 373 // SetDisableRoot is defined on the RootUserConfig interface. 374 func (cfg *cloudConfig) SetDisableRoot(disable bool) { 375 cfg.SetAttr("disable_root", disable) 376 } 377 378 // UnsetDisableRoot is defined on the RootUserConfig interface. 379 func (cfg *cloudConfig) UnsetDisableRoot() { 380 cfg.UnsetAttr("disable_root") 381 } 382 383 // DisableRoot is defined on the RootUserConfig interface. 384 func (cfg *cloudConfig) DisableRoot() bool { 385 disable, _ := cfg.attrs["disable_root"].(bool) 386 return disable 387 } 388 389 // ManageEtcHosts enables or disables management of /etc/hosts. 390 func (cfg *cloudConfig) ManageEtcHosts(manage bool) { 391 if manage { 392 cfg.SetAttr("manage_etc_hosts", true) 393 } else { 394 cfg.UnsetAttr("manage_etc_hosts") 395 } 396 } 397 398 // AddRunTextFile is defined on the WrittenFilesConfig interface. 399 func (cfg *cloudConfig) AddRunTextFile(filename, contents string, perm uint) { 400 cfg.AddScripts(addFileCmds(filename, []byte(contents), perm, false)...) 401 } 402 403 // AddBootTextFile is defined on the WrittenFilesConfig interface. 404 func (cfg *cloudConfig) AddBootTextFile(filename, contents string, perm uint) { 405 for _, cmd := range addFileCmds(filename, []byte(contents), perm, false) { 406 cfg.AddBootCmd(cmd) 407 } 408 } 409 410 // AddRunBinaryFile is defined on the WrittenFilesConfig interface. 411 func (cfg *cloudConfig) AddRunBinaryFile(filename string, data []byte, mode uint) { 412 if cfg.fileTransporter != nil { 413 source := cfg.fileTransporter.SendBytes(filename, data) 414 cfg.AddScripts(addFileCopyCmds(source, filename, mode)...) 415 return 416 } 417 cfg.AddScripts(addFileCmds(filename, data, mode, true)...) 418 } 419 420 // ShellRenderer is defined on the RenderConfig interface. 421 func (cfg *cloudConfig) ShellRenderer() shell.Renderer { 422 return cfg.renderer 423 }