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 }