github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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/v3"
    13  	"github.com/juju/packaging/v3/config"
    14  	"github.com/juju/proxy"
    15  	"github.com/juju/utils/v3"
    16  	"gopkg.in/yaml.v2"
    17  
    18  	"github.com/juju/juju/core/snap"
    19  	jujupackaging "github.com/juju/juju/packaging"
    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  	p, _ := cfg.attrs["apt_proxy"].(string)
    43  	return p
    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  	pkgConfer := cfg.getPackagingConfigurer(jujupackaging.AptPackageManager)
    93  	for _, pref := range prefs {
    94  		prefFile, err := pkgConfer.RenderPreferences(pref)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  		cfg.AddBootTextFile(pref.Path, prefFile, 0644)
    99  	}
   100  	cfg.UnsetAttr("apt_preferences")
   101  
   102  	data, err := yaml.Marshal(cfg.attrs)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	// Restore the modified fields
   108  	cfg.SetAttr("apt_preferences", prefs)
   109  	if oldbootcmds != nil {
   110  		cfg.SetAttr("bootcmd", oldbootcmds)
   111  	} else {
   112  		cfg.UnsetAttr("bootcmd")
   113  	}
   114  
   115  	return append([]byte("#cloud-config\n"), data...), nil
   116  }
   117  
   118  func (cfg *ubuntuCloudConfig) RenderScript() (string, error) {
   119  	return renderScriptCommon(cfg)
   120  }
   121  
   122  // AddPackageCommands is defined on the AdvancedPackagingConfig interface.
   123  func (cfg *ubuntuCloudConfig) AddPackageCommands(
   124  	proxyCfg PackageManagerProxyConfig,
   125  	addUpdateScripts bool,
   126  	addUpgradeScripts bool,
   127  ) error {
   128  	return addPackageCommandsCommon(
   129  		cfg,
   130  		proxyCfg,
   131  		addUpdateScripts,
   132  		addUpgradeScripts,
   133  	)
   134  }
   135  
   136  // getCommandsForAddingPackages is a helper function for generating a script
   137  // for adding all packages configured in this CloudConfig.
   138  func (cfg *ubuntuCloudConfig) getCommandsForAddingPackages() ([]string, error) {
   139  	if !cfg.SystemUpdate() && len(cfg.PackageSources()) > 0 {
   140  		return nil, errors.New("update sources were specified, but OS updates have been disabled.")
   141  	}
   142  
   143  	var cmds []string
   144  	pkgCmder := cfg.paccmder[jujupackaging.AptPackageManager]
   145  
   146  	// If a mirror is specified, rewrite sources.list and rename cached index files.
   147  	if newMirror := cfg.PackageMirror(); newMirror != "" {
   148  		cmds = append(cmds, LogProgressCmd(fmt.Sprintf("Changing apt mirror to %q", newMirror)))
   149  		cmds = append(cmds, pkgCmder.SetMirrorCommands(newMirror, newMirror)...)
   150  	}
   151  
   152  	if len(cfg.PackageSources()) > 0 {
   153  		// Ensure add-apt-repository is available.
   154  		cmds = append(cmds, LogProgressCmd("Installing add-apt-repository"))
   155  		cmds = append(cmds, pkgCmder.InstallCmd("software-properties-common"))
   156  	}
   157  	for _, src := range cfg.PackageSources() {
   158  		// PPA keys are obtained by add-apt-repository, from launchpad.
   159  		if !strings.HasPrefix(src.URL, "ppa:") {
   160  			if src.Key != "" {
   161  				key := utils.ShQuote(src.Key)
   162  				cmd := fmt.Sprintf("echo %s | apt-key add -", key)
   163  				cmds = append(cmds, cmd)
   164  			}
   165  		}
   166  		cmds = append(cmds, LogProgressCmd("Adding apt repository: %s", src.URL))
   167  		cmds = append(cmds, pkgCmder.AddRepositoryCmd(src.URL))
   168  	}
   169  
   170  	pkgConfer := cfg.getPackagingConfigurer(jujupackaging.AptPackageManager)
   171  	for _, prefs := range cfg.PackagePreferences() {
   172  		prefFile, err := pkgConfer.RenderPreferences(prefs)
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  		cfg.AddRunTextFile(prefs.Path, prefFile, 0644)
   177  	}
   178  
   179  	cmds = append(cmds, config.PackageManagerLoopFunction)
   180  	looper := "package_manager_loop "
   181  
   182  	if cfg.SystemUpdate() {
   183  		cmds = append(cmds, LogProgressCmd("Running apt-get update"))
   184  		cmds = append(cmds, looper+pkgCmder.UpdateCmd())
   185  	}
   186  	if cfg.SystemUpgrade() {
   187  		cmds = append(cmds, LogProgressCmd("Running apt-get upgrade"))
   188  		cmds = append(cmds, looper+"apt-mark hold cloud-init")
   189  		cmds = append(cmds, looper+pkgCmder.UpgradeCmd())
   190  		cmds = append(cmds, looper+"apt-mark unhold cloud-init")
   191  	}
   192  
   193  	var pkgCmds []string
   194  	var pkgNames []string
   195  	var pkgsWithTargetRelease []string
   196  	pkgs := cfg.Packages()
   197  	for i := range pkgs {
   198  		pack := pkgs[i]
   199  		if pack == "--target-release" || len(pkgsWithTargetRelease) > 0 {
   200  			// We have --target-release foo/bar package. Accumulate
   201  			// the args until we've reached the package, before
   202  			// passing the 3 element slice to InstallCmd below.
   203  			pkgsWithTargetRelease = append(pkgsWithTargetRelease, pack)
   204  			if len(pkgsWithTargetRelease) < 3 {
   205  				// We expect exactly 3 elements, the last one being
   206  				// the package.
   207  				continue
   208  			}
   209  		}
   210  		pkgNames = append(pkgNames, pack)
   211  		installArgs := []string{pack}
   212  
   213  		if len(pkgsWithTargetRelease) == 3 {
   214  			// If we have a --target-release package, build the
   215  			// install command args from the accumulated
   216  			// pkgsWithTargetRelease slice and reset it.
   217  			installArgs = append([]string{}, pkgsWithTargetRelease...)
   218  			pkgsWithTargetRelease = []string{}
   219  		}
   220  
   221  		cmd := looper + pkgCmder.InstallCmd(installArgs...)
   222  		pkgCmds = append(pkgCmds, cmd)
   223  	}
   224  
   225  	if len(pkgCmds) > 0 {
   226  		pkgCmds = append([]string{LogProgressCmd(fmt.Sprintf("Installing %s", strings.Join(pkgNames, ", ")))}, pkgCmds...)
   227  		cmds = append(cmds, pkgCmds...)
   228  		// setting DEBIAN_FRONTEND=noninteractive prevents debconf
   229  		// from prompting, always taking default values instead.
   230  		cmds = append([]string{"export DEBIAN_FRONTEND=noninteractive"}, cmds...)
   231  	}
   232  
   233  	return cmds, nil
   234  
   235  }
   236  
   237  // addRequiredPackages is defined on the AdvancedPackagingConfig interface.
   238  func (cfg *ubuntuCloudConfig) addRequiredPackages() {
   239  	packages := []string{
   240  		"curl",
   241  		"cpu-checker",
   242  		"tmux",
   243  		"ubuntu-fan",
   244  	}
   245  	for _, pack := range packages {
   246  		cfg.AddPackage(pack)
   247  	}
   248  }
   249  
   250  var waitSnapSeeded = `
   251  n=1
   252  while true; do
   253  
   254  echo "Attempt $n to wait for snapd to be seeded...\n"
   255  snap wait core seed.loaded && break
   256  if [ $n -eq 5 ]; then
   257    echo "snapd not initialised"
   258    break
   259  fi
   260  
   261  echo "Wait for snapd failed, retrying in 5s"
   262  sleep 5
   263  n=$((n+1))
   264  done
   265  `[1:]
   266  
   267  // Updates proxy settings used when rendering the conf as a script
   268  func (cfg *ubuntuCloudConfig) updateProxySettings(proxyCfg PackageManagerProxyConfig) error {
   269  	// Write out the apt proxy settings
   270  	if aptProxy := proxyCfg.AptProxy(); (aptProxy != proxy.Settings{}) {
   271  		pkgCmder := cfg.paccmder[jujupackaging.AptPackageManager]
   272  		filename := config.AptProxyConfigFile
   273  		cfg.AddBootCmd(fmt.Sprintf(
   274  			`echo %s > %s`,
   275  			utils.ShQuote(pkgCmder.ProxyConfigContents(aptProxy)),
   276  			filename))
   277  	}
   278  
   279  	once := false
   280  	addWaitSnapSeeded := func() {
   281  		if once {
   282  			return
   283  		}
   284  		cfg.AddRunCmd(waitSnapSeeded)
   285  		once = true
   286  	}
   287  	// Write out the snap http/https proxy settings
   288  	if snapProxy := proxyCfg.SnapProxy(); (snapProxy != proxy.Settings{}) {
   289  		addWaitSnapSeeded()
   290  		pkgCmder := cfg.paccmder[jujupackaging.SnapPackageManager]
   291  		for _, cmd := range pkgCmder.SetProxyCmds(snapProxy) {
   292  			cfg.AddRunCmd(cmd)
   293  		}
   294  	}
   295  
   296  	// Configure snap store proxy
   297  	if proxyURL := proxyCfg.SnapStoreProxyURL(); proxyURL != "" {
   298  		assertions, storeID, err := snap.LookupAssertions(proxyURL)
   299  		if err != nil {
   300  			return err
   301  		}
   302  		logger.Infof("auto-detected snap store assertions from proxy")
   303  		logger.Infof("auto-detected snap store ID as %q", storeID)
   304  		addWaitSnapSeeded()
   305  		cfg.genSnapStoreProxyCmds(assertions, storeID)
   306  	} else if proxyCfg.SnapStoreAssertions() != "" && proxyCfg.SnapStoreProxyID() != "" {
   307  		addWaitSnapSeeded()
   308  		cfg.genSnapStoreProxyCmds(proxyCfg.SnapStoreAssertions(), proxyCfg.SnapStoreProxyID())
   309  	}
   310  
   311  	return nil
   312  }
   313  
   314  func (cfg *ubuntuCloudConfig) genSnapStoreProxyCmds(assertions, storeID string) {
   315  	cfg.AddRunTextFile("/etc/snap.assertions", assertions, 0600)
   316  	cfg.AddRunCmd("snap ack /etc/snap.assertions")
   317  	cfg.AddRunCmd("rm /etc/snap.assertions")
   318  	cfg.AddRunCmd("snap set core proxy.store=" + storeID)
   319  }