github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/os" 12 "github.com/juju/os/series" 13 "github.com/juju/utils" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/agent" 17 "github.com/juju/juju/cloudconfig/cloudinit" 18 "github.com/juju/juju/cloudconfig/instancecfg" 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, err := series.GetOSFromSeries(icfg.Series) 63 if err != nil { 64 return nil, err 65 } 66 67 base := baseConfigure{ 68 tag: names.NewMachineTag(icfg.MachineId), 69 icfg: icfg, 70 conf: conf, 71 os: operatingSystem, 72 } 73 74 switch operatingSystem { 75 case os.Ubuntu: 76 return &unixConfigure{base}, nil 77 case os.CentOS: 78 return &unixConfigure{base}, nil 79 case os.OpenSUSE: 80 return &unixConfigure{base}, nil 81 case os.Windows: 82 return &windowsConfigure{base}, nil 83 default: 84 return nil, errors.NotSupportedf("OS %s", icfg.Series) 85 } 86 } 87 88 type baseConfigure struct { 89 tag names.Tag 90 icfg *instancecfg.InstanceConfig 91 conf cloudinit.CloudConfig 92 os os.OSType 93 } 94 95 // addAgentInfo adds agent-required information to the agent's directory 96 // and returns the agent directory name. 97 func (c *baseConfigure) addAgentInfo(tag names.Tag) (agent.Config, error) { 98 acfg, err := c.icfg.AgentConfig(tag, c.icfg.AgentVersion().Number) 99 if err != nil { 100 return nil, errors.Trace(err) 101 } 102 acfg.SetValue(agent.AgentServiceName, c.icfg.MachineAgentServiceName) 103 cmds, err := acfg.WriteCommands(c.conf.ShellRenderer()) 104 if err != nil { 105 return nil, errors.Annotate(err, "failed to write commands") 106 } 107 c.conf.AddScripts(cmds...) 108 return acfg, nil 109 } 110 111 func (c *baseConfigure) addMachineAgentToBoot() error { 112 svc, err := c.icfg.InitService(c.conf.ShellRenderer()) 113 if err != nil { 114 return errors.Trace(err) 115 } 116 117 // Make the agent run via a symbolic link to the actual tools 118 // directory, so it can upgrade itself without needing to change 119 // the init script. 120 toolsDir := c.icfg.ToolsDir(c.conf.ShellRenderer()) 121 c.conf.AddScripts(c.toolsSymlinkCommand(toolsDir)) 122 123 name := c.tag.String() 124 cmds, err := svc.InstallCommands() 125 if err != nil { 126 return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name) 127 } 128 startCmds, err := svc.StartCommands() 129 if err != nil { 130 return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name) 131 } 132 cmds = append(cmds, startCmds...) 133 134 svcName := c.icfg.MachineAgentServiceName 135 // TODO (gsamfira): This is temporary until we find a cleaner way to fix 136 // cloudinit.LogProgressCmd to not add >&9 on Windows. 137 targetOS, err := series.GetOSFromSeries(c.icfg.Series) 138 if err != nil { 139 return err 140 } 141 if targetOS != os.Windows { 142 c.conf.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (service %s)", svcName)) 143 } 144 c.conf.AddScripts(cmds...) 145 return nil 146 } 147 148 // SetUbuntuUser creates an "ubuntu" use for unix systems so the juju client 149 // can access the machine using ssh with the configuration we expect. 150 // On precise, the default cloudinit version is too old to support the users 151 // option, so instead rely on the default user being created and adding keys. 152 // It may make sense in the future to add a "juju" user instead across 153 // all distributions. 154 func SetUbuntuUser(conf cloudinit.CloudConfig, authorizedKeys string) { 155 targetSeries := conf.GetSeries() 156 if targetSeries == "precise" { 157 conf.SetSSHAuthorizedKeys(authorizedKeys) 158 } else { 159 var groups []string 160 targetOS, _ := series.GetOSFromSeries(targetSeries) 161 switch targetOS { 162 case os.Ubuntu: 163 groups = UbuntuGroups 164 case os.CentOS: 165 groups = CentOSGroups 166 case os.OpenSUSE: 167 groups = OpenSUSEGroups 168 } 169 conf.AddUser(&cloudinit.User{ 170 Name: "ubuntu", 171 Groups: groups, 172 Shell: "/bin/bash", 173 Sudo: []string{"ALL=(ALL) NOPASSWD:ALL"}, 174 SSHAuthorizedKeys: authorizedKeys, 175 }) 176 } 177 } 178 179 // TODO(ericsnow) toolsSymlinkCommand should just be replaced with a 180 // call to shell.Renderer.Symlink. 181 182 func (c *baseConfigure) toolsSymlinkCommand(toolsDir string) string { 183 switch c.os { 184 case os.Windows: 185 return fmt.Sprintf( 186 `cmd.exe /C mklink /D %s %v`, 187 c.conf.ShellRenderer().FromSlash(toolsDir), 188 c.icfg.AgentVersion(), 189 ) 190 default: 191 // TODO(dfc) ln -nfs, so it doesn't fail if for some reason that 192 // the target already exists. 193 return fmt.Sprintf( 194 "ln -s %v %s", 195 c.icfg.AgentVersion(), 196 shquote(toolsDir), 197 ) 198 } 199 } 200 201 func shquote(p string) string { 202 return utils.ShQuote(p) 203 }