github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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" 13 "github.com/juju/packaging/config" 14 "github.com/juju/proxy" 15 "github.com/juju/utils" 16 "gopkg.in/yaml.v2" 17 ) 18 19 // ubuntuCloudConfig is the cloudconfig type specific to Ubuntu machines 20 // It simply contains a cloudConfig with the added package management-related 21 // methods for the Ubuntu version of cloudinit. 22 // It satisfies the cloudinit.CloudConfig interface 23 type ubuntuCloudConfig struct { 24 *cloudConfig 25 } 26 27 // SetPackageProxy is defined on the PackageProxyConfig interface. 28 func (cfg *ubuntuCloudConfig) SetPackageProxy(url string) { 29 cfg.SetAttr("apt_proxy", url) 30 } 31 32 // UnsetPackageProxy is defined on the PackageProxyConfig interface. 33 func (cfg *ubuntuCloudConfig) UnsetPackageProxy() { 34 cfg.UnsetAttr("apt_proxy") 35 } 36 37 // PackageProxy is defined on the PackageProxyConfig interface. 38 func (cfg *ubuntuCloudConfig) PackageProxy() string { 39 proxy, _ := cfg.attrs["apt_proxy"].(string) 40 return proxy 41 } 42 43 // SetPackageMirror is defined on the PackageMirrorConfig interface. 44 func (cfg *ubuntuCloudConfig) SetPackageMirror(url string) { 45 cfg.SetAttr("apt_mirror", url) 46 } 47 48 // UnsetPackageMirror is defined on the PackageMirrorConfig interface. 49 func (cfg *ubuntuCloudConfig) UnsetPackageMirror() { 50 cfg.UnsetAttr("apt_mirror") 51 } 52 53 // PackageMirror is defined on the PackageMirrorConfig interface. 54 func (cfg *ubuntuCloudConfig) PackageMirror() string { 55 mirror, _ := cfg.attrs["apt_mirror"].(string) 56 return mirror 57 } 58 59 // AddPackageSource is defined on the PackageSourcesConfig interface. 60 func (cfg *ubuntuCloudConfig) AddPackageSource(src packaging.PackageSource) { 61 cfg.attrs["apt_sources"] = append(cfg.PackageSources(), src) 62 } 63 64 // PackageSources is defined on the PackageSourcesConfig interface. 65 func (cfg *ubuntuCloudConfig) PackageSources() []packaging.PackageSource { 66 srcs, _ := cfg.attrs["apt_sources"].([]packaging.PackageSource) 67 return srcs 68 } 69 70 // AddPackagePreferences is defined on the PackageSourcesConfig interface. 71 func (cfg *ubuntuCloudConfig) AddPackagePreferences(prefs packaging.PackagePreferences) { 72 cfg.attrs["apt_preferences"] = append(cfg.PackagePreferences(), prefs) 73 } 74 75 // PackagePreferences is defined on the PackageSourcesConfig interface. 76 func (cfg *ubuntuCloudConfig) PackagePreferences() []packaging.PackagePreferences { 77 prefs, _ := cfg.attrs["apt_preferences"].([]packaging.PackagePreferences) 78 return prefs 79 } 80 81 func (cfg *ubuntuCloudConfig) RenderYAML() ([]byte, error) { 82 // Save the fields that we will modify 83 var oldbootcmds []string 84 oldbootcmds = copyStringSlice(cfg.BootCmds()) 85 86 // apt_preferences is not a valid field so we use a fake field in attrs 87 // and then render it differently 88 prefs := cfg.PackagePreferences() 89 for _, pref := range prefs { 90 prefFile, err := cfg.pacconfer.RenderPreferences(pref) 91 if err != nil { 92 return nil, err 93 } 94 cfg.AddBootTextFile(pref.Path, prefFile, 0644) 95 } 96 cfg.UnsetAttr("apt_preferences") 97 98 data, err := yaml.Marshal(cfg.attrs) 99 if err != nil { 100 return nil, err 101 } 102 103 // Restore the modified fields 104 cfg.SetAttr("apt_preferences", prefs) 105 if oldbootcmds != nil { 106 cfg.SetAttr("bootcmd", oldbootcmds) 107 } else { 108 cfg.UnsetAttr("bootcmd") 109 } 110 111 return append([]byte("#cloud-config\n"), data...), nil 112 } 113 114 func (cfg *ubuntuCloudConfig) RenderScript() (string, error) { 115 return renderScriptCommon(cfg) 116 } 117 118 // AddPackageCommands is defined on the AdvancedPackagingConfig interface. 119 func (cfg *ubuntuCloudConfig) AddPackageCommands( 120 packageProxySettings proxy.Settings, 121 packageMirror string, 122 addUpdateScripts bool, 123 addUpgradeScripts bool, 124 ) { 125 addPackageCommandsCommon( 126 cfg, 127 packageProxySettings, 128 packageMirror, 129 addUpdateScripts, 130 addUpgradeScripts, 131 cfg.series, 132 ) 133 } 134 135 // AddCloudArchiveCloudTools is defined on the AdvancedPackagingConfig 136 // interface. 137 func (cfg *ubuntuCloudConfig) AddCloudArchiveCloudTools() { 138 src, pref := config.GetCloudArchiveSource(cfg.series) 139 cfg.AddPackageSource(src) 140 cfg.AddPackagePreferences(pref) 141 } 142 143 // getCommandsForAddingPackages is a helper function for generating a script 144 // for adding all packages configured in this CloudConfig. 145 func (cfg *ubuntuCloudConfig) getCommandsForAddingPackages() ([]string, error) { 146 if !cfg.SystemUpdate() && len(cfg.PackageSources()) > 0 { 147 return nil, errors.New("update sources were specified, but OS updates have been disabled.") 148 } 149 150 var cmds []string 151 152 // If a mirror is specified, rewrite sources.list and rename cached index files. 153 if newMirror := cfg.PackageMirror(); newMirror != "" { 154 cmds = append(cmds, LogProgressCmd("Changing apt mirror to "+newMirror)) 155 cmds = append(cmds, "old_mirror=$("+config.ExtractAptSource+")") 156 cmds = append(cmds, "new_mirror="+newMirror) 157 cmds = append(cmds, `sed -i s,$old_mirror,$new_mirror, `+config.AptSourcesFile) 158 cmds = append(cmds, renameAptListFilesCommands("$new_mirror", "$old_mirror")...) 159 } 160 161 if len(cfg.PackageSources()) > 0 { 162 // Ensure add-apt-repository is available. 163 cmds = append(cmds, LogProgressCmd("Installing add-apt-repository")) 164 cmds = append(cmds, cfg.paccmder.InstallCmd("software-properties-common")) 165 } 166 for _, src := range cfg.PackageSources() { 167 // PPA keys are obtained by add-apt-repository, from launchpad. 168 if !strings.HasPrefix(src.URL, "ppa:") { 169 if src.Key != "" { 170 key := utils.ShQuote(src.Key) 171 cmd := fmt.Sprintf("printf '%%s\\n' %s | apt-key add -", key) 172 cmds = append(cmds, cmd) 173 } 174 } 175 cmds = append(cmds, LogProgressCmd("Adding apt repository: %s", src.URL)) 176 cmds = append(cmds, cfg.paccmder.AddRepositoryCmd(src.URL)) 177 } 178 179 for _, prefs := range cfg.PackagePreferences() { 180 prefFile, err := cfg.pacconfer.RenderPreferences(prefs) 181 if err != nil { 182 return nil, err 183 } 184 cfg.AddRunTextFile(prefs.Path, prefFile, 0644) 185 } 186 187 cmds = append(cmds, config.PackageManagerLoopFunction) 188 looper := "package_manager_loop " 189 190 if cfg.SystemUpdate() { 191 cmds = append(cmds, LogProgressCmd("Running apt-get update")) 192 cmds = append(cmds, looper+cfg.paccmder.UpdateCmd()) 193 } 194 if cfg.SystemUpgrade() { 195 cmds = append(cmds, LogProgressCmd("Running apt-get upgrade")) 196 cmds = append(cmds, looper+cfg.paccmder.UpgradeCmd()) 197 } 198 199 var pkgCmds []string 200 var pkgNames []string 201 var pkgsWithTargetRelease []string 202 pkgs := cfg.Packages() 203 for i := range pkgs { 204 pack := pkgs[i] 205 if pack == "--target-release" || len(pkgsWithTargetRelease) > 0 { 206 // We have --target-release foo/bar package. Accumulate 207 // the args until we've reached the package, before 208 // passing the 3 element slice to InstallCmd below. 209 pkgsWithTargetRelease = append(pkgsWithTargetRelease, pack) 210 if len(pkgsWithTargetRelease) < 3 { 211 // We expect exactly 3 elements, the last one being 212 // the package. 213 continue 214 } 215 } 216 pkgNames = append(pkgNames, pack) 217 installArgs := []string{pack} 218 219 if len(pkgsWithTargetRelease) == 3 { 220 // If we have a --target-release package, build the 221 // install command args from the accumulated 222 // pkgsWithTargetRelease slice and reset it. 223 installArgs = append([]string{}, pkgsWithTargetRelease...) 224 pkgsWithTargetRelease = []string{} 225 } 226 227 cmd := looper + cfg.paccmder.InstallCmd(installArgs...) 228 pkgCmds = append(pkgCmds, cmd) 229 } 230 231 if len(pkgCmds) > 0 { 232 pkgCmds = append([]string{LogProgressCmd(fmt.Sprintf("Installing %s", strings.Join(pkgNames, ", ")))}, pkgCmds...) 233 cmds = append(cmds, pkgCmds...) 234 // setting DEBIAN_FRONTEND=noninteractive prevents debconf 235 // from prompting, always taking default values instead. 236 cmds = append([]string{"export DEBIAN_FRONTEND=noninteractive"}, cmds...) 237 } 238 239 return cmds, nil 240 241 } 242 243 // renameAptListFilesCommands takes a new and old mirror string, 244 // and returns a sequence of commands that will rename the files 245 // in aptListsDirectory. 246 func renameAptListFilesCommands(newMirror, oldMirror string) []string { 247 oldPrefix := "old_prefix=" + config.AptListsDirectory + "/$(echo " + oldMirror + " | " + config.AptSourceListPrefix + ")" 248 newPrefix := "new_prefix=" + config.AptListsDirectory + "/$(echo " + newMirror + " | " + config.AptSourceListPrefix + ")" 249 renameFiles := ` 250 for old in ${old_prefix}_*; do 251 new=$(echo $old | sed s,^$old_prefix,$new_prefix,) 252 mv $old $new 253 done` 254 255 return []string{ 256 oldPrefix, 257 newPrefix, 258 // Don't do anything unless the mirror/source has changed. 259 `[ "$old_prefix" != "$new_prefix" ] &&` + renameFiles, 260 } 261 } 262 263 // addRequiredPackages is defined on the AdvancedPackagingConfig interface. 264 func (cfg *ubuntuCloudConfig) addRequiredPackages() { 265 packages := []string{ 266 "curl", 267 "cpu-checker", 268 // TODO(axw) 2014-07-02 #1277359 269 // Don't install bridge-utils in cloud-init; 270 // leave it to the networker worker. 271 "bridge-utils", 272 "cloud-utils", 273 "tmux", 274 // TODO(wpk) 2017-07-23 maybe we should do it in fanconfigurer? 275 "ubuntu-fan", 276 } 277 278 // The required packages need to come from the correct repo. 279 // For precise, that might require an explicit --target-release parameter. 280 // We cannot just pass packages below, because 281 // this will generate install commands which older 282 // versions of cloud-init (e.g. 0.6.3 in precise) will 283 // interpret incorrectly (see bug http://pad.lv/1424777). 284 for _, pack := range packages { 285 if config.SeriesRequiresCloudArchiveTools(cfg.series) && cfg.pacconfer.IsCloudArchivePackage(pack) { 286 // On precise, we need to pass a --target-release entry in 287 // pieces (as "packages") for it to work: 288 // --target-release, precise-updates/cloud-tools, 289 // package-name. All these 3 entries are needed so 290 // cloud-init 0.6.3 can generate the correct apt-get 291 // install command line for "package-name". 292 args := cfg.pacconfer.ApplyCloudArchiveTarget(pack) 293 for _, arg := range args { 294 cfg.AddPackage(arg) 295 } 296 } else { 297 cfg.AddPackage(pack) 298 } 299 } 300 } 301 302 // Updates proxy settings used when rendering the conf as a script 303 func (cfg *ubuntuCloudConfig) updateProxySettings(proxySettings proxy.Settings) { 304 // Write out the apt proxy settings 305 if (proxySettings != proxy.Settings{}) { 306 filename := config.AptProxyConfigFile 307 cfg.AddBootCmd(fmt.Sprintf( 308 `printf '%%s\n' %s > %s`, 309 utils.ShQuote(cfg.paccmder.ProxyConfigContents(proxySettings)), 310 filename)) 311 } 312 }