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