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  }