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