github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/cloudconfig/cloudinit/cloudinit_ubuntu.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 "fmt" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/packaging/v3" 13 "github.com/juju/packaging/v3/config" 14 "github.com/juju/proxy" 15 "github.com/juju/utils/v3" 16 "gopkg.in/yaml.v2" 17 18 "github.com/juju/juju/core/snap" 19 jujupackaging "github.com/juju/juju/packaging" 20 ) 21 22 // ubuntuCloudConfig is the cloudconfig type specific to Ubuntu machines 23 // It simply contains a cloudConfig with the added package management-related 24 // methods for the Ubuntu version of cloudinit. 25 // It satisfies the cloudinit.CloudConfig interface 26 type ubuntuCloudConfig struct { 27 *cloudConfig 28 } 29 30 // SetPackageProxy is defined on the PackageProxyConfig interface. 31 func (cfg *ubuntuCloudConfig) SetPackageProxy(url string) { 32 cfg.SetAttr("apt_proxy", url) 33 } 34 35 // UnsetPackageProxy is defined on the PackageProxyConfig interface. 36 func (cfg *ubuntuCloudConfig) UnsetPackageProxy() { 37 cfg.UnsetAttr("apt_proxy") 38 } 39 40 // PackageProxy is defined on the PackageProxyConfig interface. 41 func (cfg *ubuntuCloudConfig) PackageProxy() string { 42 p, _ := cfg.attrs["apt_proxy"].(string) 43 return p 44 } 45 46 // SetPackageMirror is defined on the PackageMirrorConfig interface. 47 func (cfg *ubuntuCloudConfig) SetPackageMirror(url string) { 48 cfg.SetAttr("apt_mirror", url) 49 } 50 51 // UnsetPackageMirror is defined on the PackageMirrorConfig interface. 52 func (cfg *ubuntuCloudConfig) UnsetPackageMirror() { 53 cfg.UnsetAttr("apt_mirror") 54 } 55 56 // PackageMirror is defined on the PackageMirrorConfig interface. 57 func (cfg *ubuntuCloudConfig) PackageMirror() string { 58 mirror, _ := cfg.attrs["apt_mirror"].(string) 59 return mirror 60 } 61 62 // AddPackageSource is defined on the PackageSourcesConfig interface. 63 func (cfg *ubuntuCloudConfig) AddPackageSource(src packaging.PackageSource) { 64 cfg.attrs["apt_sources"] = append(cfg.PackageSources(), src) 65 } 66 67 // PackageSources is defined on the PackageSourcesConfig interface. 68 func (cfg *ubuntuCloudConfig) PackageSources() []packaging.PackageSource { 69 srcs, _ := cfg.attrs["apt_sources"].([]packaging.PackageSource) 70 return srcs 71 } 72 73 // AddPackagePreferences is defined on the PackageSourcesConfig interface. 74 func (cfg *ubuntuCloudConfig) AddPackagePreferences(prefs packaging.PackagePreferences) { 75 cfg.attrs["apt_preferences"] = append(cfg.PackagePreferences(), prefs) 76 } 77 78 // PackagePreferences is defined on the PackageSourcesConfig interface. 79 func (cfg *ubuntuCloudConfig) PackagePreferences() []packaging.PackagePreferences { 80 prefs, _ := cfg.attrs["apt_preferences"].([]packaging.PackagePreferences) 81 return prefs 82 } 83 84 func (cfg *ubuntuCloudConfig) RenderYAML() ([]byte, error) { 85 // Save the fields that we will modify 86 var oldbootcmds []string 87 oldbootcmds = copyStringSlice(cfg.BootCmds()) 88 89 // apt_preferences is not a valid field so we use a fake field in attrs 90 // and then render it differently 91 prefs := cfg.PackagePreferences() 92 pkgConfer := cfg.getPackagingConfigurer(jujupackaging.AptPackageManager) 93 for _, pref := range prefs { 94 prefFile, err := pkgConfer.RenderPreferences(pref) 95 if err != nil { 96 return nil, err 97 } 98 cfg.AddBootTextFile(pref.Path, prefFile, 0644) 99 } 100 cfg.UnsetAttr("apt_preferences") 101 102 data, err := yaml.Marshal(cfg.attrs) 103 if err != nil { 104 return nil, err 105 } 106 107 // Restore the modified fields 108 cfg.SetAttr("apt_preferences", prefs) 109 if oldbootcmds != nil { 110 cfg.SetAttr("bootcmd", oldbootcmds) 111 } else { 112 cfg.UnsetAttr("bootcmd") 113 } 114 115 return append([]byte("#cloud-config\n"), data...), nil 116 } 117 118 func (cfg *ubuntuCloudConfig) RenderScript() (string, error) { 119 return renderScriptCommon(cfg) 120 } 121 122 // AddPackageCommands is defined on the AdvancedPackagingConfig interface. 123 func (cfg *ubuntuCloudConfig) AddPackageCommands( 124 proxyCfg PackageManagerProxyConfig, 125 addUpdateScripts bool, 126 addUpgradeScripts bool, 127 ) error { 128 return addPackageCommandsCommon( 129 cfg, 130 proxyCfg, 131 addUpdateScripts, 132 addUpgradeScripts, 133 ) 134 } 135 136 // getCommandsForAddingPackages is a helper function for generating a script 137 // for adding all packages configured in this CloudConfig. 138 func (cfg *ubuntuCloudConfig) getCommandsForAddingPackages() ([]string, error) { 139 if !cfg.SystemUpdate() && len(cfg.PackageSources()) > 0 { 140 return nil, errors.New("update sources were specified, but OS updates have been disabled.") 141 } 142 143 var cmds []string 144 pkgCmder := cfg.paccmder[jujupackaging.AptPackageManager] 145 146 // If a mirror is specified, rewrite sources.list and rename cached index files. 147 if newMirror := cfg.PackageMirror(); newMirror != "" { 148 cmds = append(cmds, LogProgressCmd(fmt.Sprintf("Changing apt mirror to %q", newMirror))) 149 cmds = append(cmds, pkgCmder.SetMirrorCommands(newMirror, newMirror)...) 150 } 151 152 if len(cfg.PackageSources()) > 0 { 153 // Ensure add-apt-repository is available. 154 cmds = append(cmds, LogProgressCmd("Installing add-apt-repository")) 155 cmds = append(cmds, pkgCmder.InstallCmd("software-properties-common")) 156 } 157 for _, src := range cfg.PackageSources() { 158 // PPA keys are obtained by add-apt-repository, from launchpad. 159 if !strings.HasPrefix(src.URL, "ppa:") { 160 if src.Key != "" { 161 key := utils.ShQuote(src.Key) 162 cmd := fmt.Sprintf("echo %s | apt-key add -", key) 163 cmds = append(cmds, cmd) 164 } 165 } 166 cmds = append(cmds, LogProgressCmd("Adding apt repository: %s", src.URL)) 167 cmds = append(cmds, pkgCmder.AddRepositoryCmd(src.URL)) 168 } 169 170 pkgConfer := cfg.getPackagingConfigurer(jujupackaging.AptPackageManager) 171 for _, prefs := range cfg.PackagePreferences() { 172 prefFile, err := pkgConfer.RenderPreferences(prefs) 173 if err != nil { 174 return nil, err 175 } 176 cfg.AddRunTextFile(prefs.Path, prefFile, 0644) 177 } 178 179 cmds = append(cmds, config.PackageManagerLoopFunction) 180 looper := "package_manager_loop " 181 182 if cfg.SystemUpdate() { 183 cmds = append(cmds, LogProgressCmd("Running apt-get update")) 184 cmds = append(cmds, looper+pkgCmder.UpdateCmd()) 185 } 186 if cfg.SystemUpgrade() { 187 cmds = append(cmds, LogProgressCmd("Running apt-get upgrade")) 188 cmds = append(cmds, looper+"apt-mark hold cloud-init") 189 cmds = append(cmds, looper+pkgCmder.UpgradeCmd()) 190 cmds = append(cmds, looper+"apt-mark unhold cloud-init") 191 } 192 193 var pkgCmds []string 194 var pkgNames []string 195 var pkgsWithTargetRelease []string 196 pkgs := cfg.Packages() 197 for i := range pkgs { 198 pack := pkgs[i] 199 if pack == "--target-release" || len(pkgsWithTargetRelease) > 0 { 200 // We have --target-release foo/bar package. Accumulate 201 // the args until we've reached the package, before 202 // passing the 3 element slice to InstallCmd below. 203 pkgsWithTargetRelease = append(pkgsWithTargetRelease, pack) 204 if len(pkgsWithTargetRelease) < 3 { 205 // We expect exactly 3 elements, the last one being 206 // the package. 207 continue 208 } 209 } 210 pkgNames = append(pkgNames, pack) 211 installArgs := []string{pack} 212 213 if len(pkgsWithTargetRelease) == 3 { 214 // If we have a --target-release package, build the 215 // install command args from the accumulated 216 // pkgsWithTargetRelease slice and reset it. 217 installArgs = append([]string{}, pkgsWithTargetRelease...) 218 pkgsWithTargetRelease = []string{} 219 } 220 221 cmd := looper + pkgCmder.InstallCmd(installArgs...) 222 pkgCmds = append(pkgCmds, cmd) 223 } 224 225 if len(pkgCmds) > 0 { 226 pkgCmds = append([]string{LogProgressCmd(fmt.Sprintf("Installing %s", strings.Join(pkgNames, ", ")))}, pkgCmds...) 227 cmds = append(cmds, pkgCmds...) 228 // setting DEBIAN_FRONTEND=noninteractive prevents debconf 229 // from prompting, always taking default values instead. 230 cmds = append([]string{"export DEBIAN_FRONTEND=noninteractive"}, cmds...) 231 } 232 233 return cmds, nil 234 235 } 236 237 // addRequiredPackages is defined on the AdvancedPackagingConfig interface. 238 func (cfg *ubuntuCloudConfig) addRequiredPackages() { 239 packages := []string{ 240 "curl", 241 "cpu-checker", 242 "tmux", 243 "ubuntu-fan", 244 } 245 for _, pack := range packages { 246 cfg.AddPackage(pack) 247 } 248 } 249 250 var waitSnapSeeded = ` 251 n=1 252 while true; do 253 254 echo "Attempt $n to wait for snapd to be seeded...\n" 255 snap wait core seed.loaded && break 256 if [ $n -eq 5 ]; then 257 echo "snapd not initialised" 258 break 259 fi 260 261 echo "Wait for snapd failed, retrying in 5s" 262 sleep 5 263 n=$((n+1)) 264 done 265 `[1:] 266 267 // Updates proxy settings used when rendering the conf as a script 268 func (cfg *ubuntuCloudConfig) updateProxySettings(proxyCfg PackageManagerProxyConfig) error { 269 // Write out the apt proxy settings 270 if aptProxy := proxyCfg.AptProxy(); (aptProxy != proxy.Settings{}) { 271 pkgCmder := cfg.paccmder[jujupackaging.AptPackageManager] 272 filename := config.AptProxyConfigFile 273 cfg.AddBootCmd(fmt.Sprintf( 274 `echo %s > %s`, 275 utils.ShQuote(pkgCmder.ProxyConfigContents(aptProxy)), 276 filename)) 277 } 278 279 once := false 280 addWaitSnapSeeded := func() { 281 if once { 282 return 283 } 284 cfg.AddRunCmd(waitSnapSeeded) 285 once = true 286 } 287 // Write out the snap http/https proxy settings 288 if snapProxy := proxyCfg.SnapProxy(); (snapProxy != proxy.Settings{}) { 289 addWaitSnapSeeded() 290 pkgCmder := cfg.paccmder[jujupackaging.SnapPackageManager] 291 for _, cmd := range pkgCmder.SetProxyCmds(snapProxy) { 292 cfg.AddRunCmd(cmd) 293 } 294 } 295 296 // Configure snap store proxy 297 if proxyURL := proxyCfg.SnapStoreProxyURL(); proxyURL != "" { 298 assertions, storeID, err := snap.LookupAssertions(proxyURL) 299 if err != nil { 300 return err 301 } 302 logger.Infof("auto-detected snap store assertions from proxy") 303 logger.Infof("auto-detected snap store ID as %q", storeID) 304 addWaitSnapSeeded() 305 cfg.genSnapStoreProxyCmds(assertions, storeID) 306 } else if proxyCfg.SnapStoreAssertions() != "" && proxyCfg.SnapStoreProxyID() != "" { 307 addWaitSnapSeeded() 308 cfg.genSnapStoreProxyCmds(proxyCfg.SnapStoreAssertions(), proxyCfg.SnapStoreProxyID()) 309 } 310 311 return nil 312 } 313 314 func (cfg *ubuntuCloudConfig) genSnapStoreProxyCmds(assertions, storeID string) { 315 cfg.AddRunTextFile("/etc/snap.assertions", assertions, 0600) 316 cfg.AddRunCmd("snap ack /etc/snap.assertions") 317 cfg.AddRunCmd("rm /etc/snap.assertions") 318 cfg.AddRunCmd("snap set core proxy.store=" + storeID) 319 }