github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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" 12 "github.com/juju/utils" 13 "github.com/juju/utils/os" 14 "github.com/juju/utils/series" 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 25 // NonceFile is written by cloud-init as the last thing it does. 26 // The file will contain the machine's nonce. The filename is 27 // relative to the Juju data-dir. 28 NonceFile = "nonce.txt" 29 ) 30 31 // UserdataConfig is the bridge between instancecfg and cloudinit 32 // It supports different levels of configuration for instances 33 type UserdataConfig interface { 34 // Configure is a convenience function that updates the cloudinit.Config 35 // with appropriate configuration. It will run ConfigureBasic() and 36 // ConfigureJuju() 37 Configure() error 38 // ConfigureBasic updates the provided cloudinit.Config with 39 // basic configuration to initialise an OS image. 40 ConfigureBasic() error 41 // ConfigureJuju updates the provided cloudinit.Config with configuration 42 // to initialise a Juju machine agent. 43 ConfigureJuju() error 44 } 45 46 // NewUserdataConfig is supposed to take in an instanceConfig as well as a 47 // cloudinit.cloudConfig and add attributes in the cloudinit structure based on 48 // the values inside instanceConfig and on the series 49 func NewUserdataConfig(icfg *instancecfg.InstanceConfig, conf cloudinit.CloudConfig) (UserdataConfig, error) { 50 // TODO(ericsnow) bug #1426217 51 // Protect icfg and conf better. 52 operatingSystem, err := series.GetOSFromSeries(icfg.Series) 53 if err != nil { 54 return nil, err 55 } 56 57 base := baseConfigure{ 58 tag: names.NewMachineTag(icfg.MachineId), 59 icfg: icfg, 60 conf: conf, 61 os: operatingSystem, 62 } 63 64 switch operatingSystem { 65 case os.Ubuntu: 66 return &unixConfigure{base}, nil 67 case os.CentOS: 68 return &unixConfigure{base}, nil 69 case os.Windows: 70 return &windowsConfigure{base}, nil 71 default: 72 return nil, errors.NotSupportedf("OS %s", icfg.Series) 73 } 74 } 75 76 type baseConfigure struct { 77 tag names.Tag 78 icfg *instancecfg.InstanceConfig 79 conf cloudinit.CloudConfig 80 os os.OSType 81 } 82 83 // addAgentInfo adds agent-required information to the agent's directory 84 // and returns the agent directory name. 85 func (c *baseConfigure) addAgentInfo(tag names.Tag) (agent.Config, error) { 86 acfg, err := c.icfg.AgentConfig(tag, c.icfg.AgentVersion().Number) 87 if err != nil { 88 return nil, errors.Trace(err) 89 } 90 acfg.SetValue(agent.AgentServiceName, c.icfg.MachineAgentServiceName) 91 cmds, err := acfg.WriteCommands(c.conf.ShellRenderer()) 92 if err != nil { 93 return nil, errors.Annotate(err, "failed to write commands") 94 } 95 c.conf.AddScripts(cmds...) 96 return acfg, nil 97 } 98 99 func (c *baseConfigure) addMachineAgentToBoot() error { 100 svc, err := c.icfg.InitService(c.conf.ShellRenderer()) 101 if err != nil { 102 return errors.Trace(err) 103 } 104 105 // Make the agent run via a symbolic link to the actual tools 106 // directory, so it can upgrade itself without needing to change 107 // the init script. 108 toolsDir := c.icfg.ToolsDir(c.conf.ShellRenderer()) 109 c.conf.AddScripts(c.toolsSymlinkCommand(toolsDir)) 110 111 name := c.tag.String() 112 cmds, err := svc.InstallCommands() 113 if err != nil { 114 return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name) 115 } 116 startCmds, err := svc.StartCommands() 117 if err != nil { 118 return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name) 119 } 120 cmds = append(cmds, startCmds...) 121 122 svcName := c.icfg.MachineAgentServiceName 123 // TODO (gsamfira): This is temporary until we find a cleaner way to fix 124 // cloudinit.LogProgressCmd to not add >&9 on Windows. 125 targetOS, err := series.GetOSFromSeries(c.icfg.Series) 126 if err != nil { 127 return err 128 } 129 if targetOS != os.Windows { 130 c.conf.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (%s)", svcName)) 131 } 132 c.conf.AddScripts(cmds...) 133 return nil 134 } 135 136 // SetUbuntuUser creates an "ubuntu" use for unix systems so the juju client 137 // can access the machine using ssh with the configuration we expect. 138 // On precise, the default cloudinit version is too old to support the users 139 // option, so instead rely on the default user being created and adding keys. 140 // It may make sense in the future to add a "juju" user instead across 141 // all distributions. 142 func SetUbuntuUser(conf cloudinit.CloudConfig, authorizedKeys string) { 143 targetSeries := conf.GetSeries() 144 if targetSeries == "precise" { 145 conf.SetSSHAuthorizedKeys(authorizedKeys) 146 } else { 147 var groups []string 148 targetOS, _ := series.GetOSFromSeries(targetSeries) 149 switch targetOS { 150 case os.Ubuntu: 151 groups = UbuntuGroups 152 case os.CentOS: 153 groups = CentOSGroups 154 } 155 conf.AddUser(&cloudinit.User{ 156 Name: "ubuntu", 157 Groups: groups, 158 Shell: "/bin/bash", 159 Sudo: []string{"ALL=(ALL) NOPASSWD:ALL"}, 160 SSHAuthorizedKeys: authorizedKeys, 161 }) 162 } 163 } 164 165 // TODO(ericsnow) toolsSymlinkCommand should just be replaced with a 166 // call to shell.Renderer.Symlink. 167 168 func (c *baseConfigure) toolsSymlinkCommand(toolsDir string) string { 169 switch c.os { 170 case os.Windows: 171 return fmt.Sprintf( 172 `cmd.exe /C mklink /D %s %v`, 173 c.conf.ShellRenderer().FromSlash(toolsDir), 174 c.icfg.AgentVersion(), 175 ) 176 default: 177 // TODO(dfc) ln -nfs, so it doesn't fail if for some reason that 178 // the target already exists. 179 return fmt.Sprintf( 180 "ln -s %v %s", 181 c.icfg.AgentVersion(), 182 shquote(toolsDir), 183 ) 184 } 185 } 186 187 func shquote(p string) string { 188 return utils.ShQuote(p) 189 }