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