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