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