github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/environs/manual/init.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package manual 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "strconv" 11 "strings" 12 13 "github.com/juju/utils" 14 15 "github.com/juju/juju/instance" 16 "github.com/juju/juju/juju/arch" 17 "github.com/juju/juju/utils/ssh" 18 ) 19 20 // detectionScript is the script to run on the remote machine to 21 // detect the OS series and hardware characteristics. 22 const detectionScript = `#!/bin/bash 23 set -e 24 lsb_release -cs 25 uname -m 26 grep MemTotal /proc/meminfo 27 cat /proc/cpuinfo` 28 29 // checkProvisionedScript is the script to run on the remote machine 30 // to check if a machine has already been provisioned. 31 // 32 // This is a little convoluted to avoid returning an error in the 33 // common case of no matching files. 34 const checkProvisionedScript = "ls /etc/init/ | grep juju.*\\.conf || exit 0" 35 36 // checkProvisioned checks if any juju upstart jobs already 37 // exist on the host machine. 38 func checkProvisioned(host string) (bool, error) { 39 logger.Infof("Checking if %s is already provisioned", host) 40 cmd := ssh.Command("ubuntu@"+host, []string{"/bin/bash"}, nil) 41 var stdout, stderr bytes.Buffer 42 cmd.Stdout = &stdout 43 cmd.Stderr = &stderr 44 cmd.Stdin = strings.NewReader(checkProvisionedScript) 45 if err := cmd.Run(); err != nil { 46 if stderr.Len() != 0 { 47 err = fmt.Errorf("%v (%v)", err, strings.TrimSpace(stderr.String())) 48 } 49 return false, err 50 } 51 output := strings.TrimSpace(stdout.String()) 52 provisioned := len(output) > 0 53 if provisioned { 54 logger.Infof("%s is already provisioned [%q]", host, output) 55 } else { 56 logger.Infof("%s is not provisioned", host) 57 } 58 return provisioned, nil 59 } 60 61 // Patch for testing. 62 var DetectSeriesAndHardwareCharacteristics = detectSeriesAndHardwareCharacteristics 63 64 // detectSeriesAndHardwareCharacteristics detects the OS 65 // series and hardware characteristics of the remote machine 66 // by connecting to the machine and executing a bash script. 67 func detectSeriesAndHardwareCharacteristics(host string) (hc instance.HardwareCharacteristics, series string, err error) { 68 logger.Infof("Detecting series and characteristics on %s", host) 69 cmd := ssh.Command("ubuntu@"+host, []string{"/bin/bash"}, nil) 70 var stdout, stderr bytes.Buffer 71 cmd.Stdout = &stdout 72 cmd.Stderr = &stderr 73 cmd.Stdin = bytes.NewBufferString(detectionScript) 74 if err := cmd.Run(); err != nil { 75 if stderr.Len() != 0 { 76 err = fmt.Errorf("%v (%v)", err, strings.TrimSpace(stderr.String())) 77 } 78 return hc, "", err 79 } 80 lines := strings.Split(stdout.String(), "\n") 81 series = strings.TrimSpace(lines[0]) 82 83 arch := arch.NormaliseArch(lines[1]) 84 hc.Arch = &arch 85 86 // HardwareCharacteristics wants memory in megabytes, 87 // meminfo reports it in kilobytes. 88 memkB := strings.Fields(lines[2])[1] // "MemTotal: NNN kB" 89 hc.Mem = new(uint64) 90 *hc.Mem, err = strconv.ParseUint(memkB, 10, 0) 91 *hc.Mem /= 1024 92 93 // For each "physical id", count the number of cores. 94 // This way we only count physical cores, not additional 95 // logical cores due to hyperthreading. 96 recorded := make(map[string]bool) 97 var physicalId string 98 hc.CpuCores = new(uint64) 99 for _, line := range lines[3:] { 100 if strings.HasPrefix(line, "physical id") { 101 physicalId = strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) 102 } else if strings.HasPrefix(line, "cpu cores") { 103 var cores uint64 104 value := strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) 105 if cores, err = strconv.ParseUint(value, 10, 0); err != nil { 106 return hc, "", err 107 } 108 if !recorded[physicalId] { 109 *hc.CpuCores += cores 110 recorded[physicalId] = true 111 } 112 } 113 } 114 if *hc.CpuCores == 0 { 115 // In the case of a single-core, non-HT CPU, we'll see no 116 // "physical id" or "cpu cores" lines. 117 *hc.CpuCores = 1 118 } 119 120 // TODO(axw) calculate CpuPower. What algorithm do we use? 121 logger.Infof("series: %s, characteristics: %s", series, hc) 122 return hc, series, nil 123 } 124 125 // InitUbuntuUser adds the ubuntu user if it doesn't 126 // already exist, updates its ~/.ssh/authorized_keys, 127 // and enables passwordless sudo for it. 128 // 129 // InitUbuntuUser will initially attempt to login as 130 // the ubuntu user, and verify that passwordless sudo 131 // is enabled; only if this is false will there be an 132 // attempt with the specified login. 133 // 134 // authorizedKeys may be empty, in which case the file 135 // will be created and left empty. 136 // 137 // stdin and stdout will be used for remote sudo prompts, 138 // if the ubuntu user must be created/updated. 139 func InitUbuntuUser(host, login, authorizedKeys string, stdin io.Reader, stdout io.Writer) error { 140 logger.Infof("initialising %q, user %q", host, login) 141 142 // To avoid unnecessary prompting for the specified login, 143 // initUbuntuUser will first attempt to ssh to the machine 144 // as "ubuntu" with password authentication disabled, and 145 // ensure that it can use sudo without a password. 146 // 147 // Note that we explicitly do not allocate a PTY, so we 148 // get a failure if sudo prompts. 149 cmd := ssh.Command("ubuntu@"+host, []string{"sudo", "-n", "true"}, nil) 150 if cmd.Run() == nil { 151 logger.Infof("ubuntu user is already initialised") 152 return nil 153 } 154 155 // Failed to login as ubuntu (or passwordless sudo is not enabled). 156 // Use specified login, and execute the initUbuntuScript below. 157 if login != "" { 158 host = login + "@" + host 159 } 160 script := fmt.Sprintf(initUbuntuScript, utils.ShQuote(authorizedKeys)) 161 var options ssh.Options 162 options.AllowPasswordAuthentication() 163 options.EnablePTY() 164 cmd = ssh.Command(host, []string{"sudo", "/bin/bash -c " + utils.ShQuote(script)}, &options) 165 var stderr bytes.Buffer 166 cmd.Stdin = stdin 167 cmd.Stdout = stdout // for sudo prompt 168 cmd.Stderr = &stderr 169 if err := cmd.Run(); err != nil { 170 if stderr.Len() != 0 { 171 err = fmt.Errorf("%v (%v)", err, strings.TrimSpace(stderr.String())) 172 } 173 return err 174 } 175 return nil 176 } 177 178 const initUbuntuScript = ` 179 set -e 180 (id ubuntu &> /dev/null) || useradd -m ubuntu -s /bin/bash 181 umask 0077 182 temp=$(mktemp) 183 echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' > $temp 184 install -m 0440 $temp /etc/sudoers.d/90-juju-ubuntu 185 rm $temp 186 su ubuntu -c 'install -D -m 0600 /dev/null ~/.ssh/authorized_keys' 187 export authorized_keys=%s 188 if [ ! -z "$authorized_keys" ]; then 189 su ubuntu -c 'printf "%%s\n" "$authorized_keys" >> ~/.ssh/authorized_keys' 190 fi`