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  }