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  }