github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/cloudconfig/userdatacfg.go (about)

     1  // Copyright 2012, 2013, 2014, 2015 Canonical Ltd.
     2  // Copyright 2014, 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package cloudconfig
     6  
     7  import (
     8  	"fmt"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/proxy"
    13  	"github.com/juju/utils/v3"
    14  
    15  	"github.com/juju/juju/agent"
    16  	"github.com/juju/juju/cloudconfig/cloudinit"
    17  	"github.com/juju/juju/cloudconfig/instancecfg"
    18  	"github.com/juju/juju/core/os/ostype"
    19  )
    20  
    21  const (
    22  	// fileSchemePrefix is the prefix for file:// URLs.
    23  	fileSchemePrefix  = "file://"
    24  	httpSchemePrefix  = "http://"
    25  	httpsSchemePrefix = "https://"
    26  
    27  	// NonceFile is written by cloud-init as the last thing it does.
    28  	// The file will contain the machine's nonce. The filename is
    29  	// relative to the Juju data-dir.
    30  	NonceFile = "nonce.txt"
    31  )
    32  
    33  // UserdataConfig is the bridge between instancecfg and cloudinit
    34  // It supports different levels of configuration for instances
    35  type UserdataConfig interface {
    36  	// Configure is a convenience function that updates the cloudinit.Config
    37  	// with appropriate configuration. It will run ConfigureBasic() and
    38  	// ConfigureJuju()
    39  	Configure() error
    40  
    41  	// ConfigureBasic updates the provided cloudinit.Config with
    42  	// basic configuration to initialise an OS image.
    43  	ConfigureBasic() error
    44  
    45  	// ConfigureJuju updates the provided cloudinit.Config with configuration
    46  	// to initialise a Juju machine agent.
    47  	ConfigureJuju() error
    48  
    49  	// ConfigureCustomOverrides updates the provided cloudinit.Config with
    50  	// user provided cloudinit data.  Data provided will overwrite current
    51  	// values with three exceptions: preruncmd was handled in ConfigureBasic()
    52  	// and packages and postruncmd were handled in ConfigureJuju().
    53  	ConfigureCustomOverrides() error
    54  }
    55  
    56  // NewUserdataConfig is supposed to take in an instanceConfig as well as a
    57  // cloudinit.cloudConfig and add attributes in the cloudinit structure based on
    58  // the values inside instanceConfig and on the series
    59  func NewUserdataConfig(icfg *instancecfg.InstanceConfig, conf cloudinit.CloudConfig) (UserdataConfig, error) {
    60  	// TODO(ericsnow) bug #1426217
    61  	// Protect icfg and conf better.
    62  	operatingSystem := ostype.OSTypeForName(icfg.Base.OS)
    63  	base := baseConfigure{
    64  		tag:  names.NewMachineTag(icfg.MachineId),
    65  		icfg: icfg,
    66  		conf: conf,
    67  		os:   operatingSystem,
    68  	}
    69  
    70  	switch operatingSystem {
    71  	case ostype.Ubuntu:
    72  		return &unixConfigure{base}, nil
    73  	case ostype.CentOS:
    74  		return &unixConfigure{base}, nil
    75  	default:
    76  		return nil, errors.NotSupportedf("OS %s", icfg.Base.OS)
    77  	}
    78  }
    79  
    80  type baseConfigure struct {
    81  	tag  names.Tag
    82  	icfg *instancecfg.InstanceConfig
    83  	conf cloudinit.CloudConfig
    84  	os   ostype.OSType
    85  }
    86  
    87  // addAgentInfo adds agent-required information to the agent's directory
    88  // and returns the agent directory name.
    89  func (c *baseConfigure) addAgentInfo(tag names.Tag) (agent.Config, error) {
    90  	acfg, err := c.icfg.AgentConfig(tag, c.icfg.AgentVersion().Number)
    91  	if err != nil {
    92  		return nil, errors.Trace(err)
    93  	}
    94  	acfg.SetValue(agent.AgentServiceName, c.icfg.MachineAgentServiceName)
    95  	cmds, err := acfg.WriteCommands(c.conf.ShellRenderer())
    96  	if err != nil {
    97  		return nil, errors.Annotate(err, "failed to write commands")
    98  	}
    99  	c.conf.AddScripts(cmds...)
   100  	return acfg, nil
   101  }
   102  
   103  func (c *baseConfigure) addMachineAgentToBoot() error {
   104  	svc, err := c.icfg.InitService(c.conf.ShellRenderer())
   105  	if err != nil {
   106  		return errors.Trace(err)
   107  	}
   108  
   109  	// Make the agent run via a symbolic link to the actual tools
   110  	// directory, so it can upgrade itself without needing to change
   111  	// the init script.
   112  	toolsDir := c.icfg.ToolsDir(c.conf.ShellRenderer())
   113  	c.conf.AddScripts(c.toolsSymlinkCommand(toolsDir))
   114  
   115  	name := c.tag.String()
   116  	cmds, err := svc.InstallCommands()
   117  	if err != nil {
   118  		return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name)
   119  	}
   120  	startCmds, err := svc.StartCommands()
   121  	if err != nil {
   122  		return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name)
   123  	}
   124  	cmds = append(cmds, startCmds...)
   125  
   126  	svcName := c.icfg.MachineAgentServiceName
   127  	c.conf.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (service %s)", svcName))
   128  	c.conf.AddScripts(cmds...)
   129  	return nil
   130  }
   131  
   132  // SetUbuntuUser creates an "ubuntu" use for unix systems so the juju client
   133  // can access the machine using ssh with the configuration we expect.
   134  // It may make sense in the future to add a "juju" user instead across
   135  // all distributions.
   136  func SetUbuntuUser(conf cloudinit.CloudConfig, authorizedKeys string) {
   137  	targetOS := ostype.OSTypeForName(conf.GetOS())
   138  	var groups []string
   139  	switch targetOS {
   140  	case ostype.Ubuntu:
   141  		groups = UbuntuGroups
   142  	case ostype.CentOS:
   143  		groups = CentOSGroups
   144  	}
   145  	conf.AddUser(&cloudinit.User{
   146  		Name:              "ubuntu",
   147  		Groups:            groups,
   148  		Shell:             "/bin/bash",
   149  		Sudo:              "ALL=(ALL) NOPASSWD:ALL",
   150  		SSHAuthorizedKeys: authorizedKeys,
   151  	})
   152  
   153  }
   154  
   155  // TODO(ericsnow) toolsSymlinkCommand should just be replaced with a
   156  // call to shell.Renderer.Symlink.
   157  
   158  func (c *baseConfigure) toolsSymlinkCommand(toolsDir string) string {
   159  	return fmt.Sprintf(
   160  		"ln -s %v %s",
   161  		c.icfg.AgentVersion(),
   162  		shquote(toolsDir),
   163  	)
   164  }
   165  
   166  func shquote(p string) string {
   167  	return utils.ShQuote(p)
   168  }
   169  
   170  // packageManagerProxySettings implements cloudinit.PackageManagerProxyConfig.
   171  type packageManagerProxySettings struct {
   172  	aptProxy            proxy.Settings
   173  	aptMirror           string
   174  	snapProxy           proxy.Settings
   175  	snapStoreAssertions string
   176  	snapStoreProxyID    string
   177  	snapStoreProxyURL   string
   178  }
   179  
   180  // AptProxy implements cloudinit.PackageManagerProxyConfig.
   181  func (p packageManagerProxySettings) AptProxy() proxy.Settings { return p.aptProxy }
   182  
   183  // AptMirror implements cloudinit.PackageManagerConfig.
   184  func (p packageManagerProxySettings) AptMirror() string { return p.aptMirror }
   185  
   186  // SnapProxy implements cloudinit.PackageManagerProxyConfig.
   187  func (p packageManagerProxySettings) SnapProxy() proxy.Settings { return p.snapProxy }
   188  
   189  // SnapStoreAssertions implements cloudinit.PackageManagerProxyConfig.
   190  func (p packageManagerProxySettings) SnapStoreAssertions() string { return p.snapStoreAssertions }
   191  
   192  // SnapStoreProxyID implements cloudinit.PackageManagerProxyConfig.
   193  func (p packageManagerProxySettings) SnapStoreProxyID() string { return p.snapStoreProxyID }
   194  
   195  // SnapStoreProxyURL implements cloudinit.PackageManagerProxyConfig.
   196  func (p packageManagerProxySettings) SnapStoreProxyURL() string { return p.snapStoreProxyURL }