launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/cmd/juju/addmachine.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"launchpad.net/gnuflag"
    11  
    12  	"launchpad.net/juju-core/cmd"
    13  	"launchpad.net/juju-core/constraints"
    14  	"launchpad.net/juju-core/environs/manual"
    15  	"launchpad.net/juju-core/instance"
    16  	"launchpad.net/juju-core/juju"
    17  	"launchpad.net/juju-core/names"
    18  	"launchpad.net/juju-core/state"
    19  	"launchpad.net/juju-core/state/api/params"
    20  )
    21  
    22  // sshHostPrefix is the prefix for a machine to be "manually provisioned".
    23  const sshHostPrefix = "ssh:"
    24  
    25  var addMachineDoc = `
    26  
    27  If no container is specified, a new machine will be
    28  provisioned.  If a container is specified, a new machine will be provisioned
    29  with that container.
    30  
    31  To add a container to an existing machine, use the <container>:<machinenumber>
    32  format.
    33  
    34  When adding a new machine, you may specify constraints for the machine to be
    35  provisioned.  Constraints cannot be combined with deploying a container to an
    36  existing machine.
    37  
    38  Currently, the only supported container type is lxc.
    39  
    40  Machines are created in a clean state and ready to have units deployed.
    41  
    42  This command also supports manual provisioning of existing machines via SSH. The
    43  target machine must be able to communicate with the API server, and be able to
    44  access the environment storage.
    45  
    46  Examples:
    47     juju add-machine                      (starts a new machine)
    48     juju add-machine lxc                  (starts a new machine with an lxc container)
    49     juju add-machine lxc:4                (starts a new lxc container on machine 4)
    50     juju add-machine --constraints mem=8G (starts a machine with at least 8GB RAM)
    51  
    52  See Also:
    53     juju help constraints
    54  `
    55  
    56  // AddMachineCommand starts a new machine and registers it in the environment.
    57  type AddMachineCommand struct {
    58  	cmd.EnvCommandBase
    59  	// If specified, use this series, else use the environment default-series
    60  	Series string
    61  	// If specified, these constraints are merged with those already in the environment.
    62  	Constraints   constraints.Value
    63  	MachineId     string
    64  	ContainerType instance.ContainerType
    65  	SSHHost       string
    66  }
    67  
    68  func (c *AddMachineCommand) Info() *cmd.Info {
    69  	return &cmd.Info{
    70  		Name:    "add-machine",
    71  		Args:    "[<container>:machine | <container> | ssh:[user@]host]",
    72  		Purpose: "start a new, empty machine and optionally a container, or add a container to a machine",
    73  		Doc:     addMachineDoc,
    74  	}
    75  }
    76  
    77  func (c *AddMachineCommand) SetFlags(f *gnuflag.FlagSet) {
    78  	c.EnvCommandBase.SetFlags(f)
    79  	f.StringVar(&c.Series, "series", "", "the charm series")
    80  	f.Var(constraints.ConstraintsValue{&c.Constraints}, "constraints", "additional machine constraints")
    81  }
    82  
    83  func (c *AddMachineCommand) Init(args []string) error {
    84  	if c.Constraints.Container != nil {
    85  		return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container)
    86  	}
    87  	containerSpec, err := cmd.ZeroOrOneArgs(args)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	if containerSpec == "" {
    92  		return nil
    93  	}
    94  	if strings.HasPrefix(containerSpec, sshHostPrefix) {
    95  		c.SSHHost = containerSpec[len(sshHostPrefix):]
    96  	} else {
    97  		// container arg can either be 'type:machine' or 'type'
    98  		if c.ContainerType, err = instance.ParseContainerType(containerSpec); err != nil {
    99  			if names.IsMachine(containerSpec) || !cmd.IsMachineOrNewContainer(containerSpec) {
   100  				return fmt.Errorf("malformed container argument %q", containerSpec)
   101  			}
   102  			sep := strings.Index(containerSpec, ":")
   103  			c.MachineId = containerSpec[sep+1:]
   104  			c.ContainerType, err = instance.ParseContainerType(containerSpec[:sep])
   105  		}
   106  	}
   107  	return err
   108  }
   109  
   110  // addMachine1dot16 runs Client.AddMachines using a direct DB connection to maintain
   111  // compatibility with an API server running 1.16 or older (when AddMachines
   112  // was not available). This fallback can be removed when we no longer maintain
   113  // 1.16 compatibility.
   114  // This was copied directly from the code in AddMachineCommand.Run in 1.16
   115  func (c *AddMachineCommand) addMachine1dot16() (string, error) {
   116  	conn, err := juju.NewConnFromName(c.EnvName)
   117  	if err != nil {
   118  		return "", err
   119  	}
   120  	defer conn.Close()
   121  
   122  	series := c.Series
   123  	if series == "" {
   124  		conf, err := conn.State.EnvironConfig()
   125  		if err != nil {
   126  			return "", err
   127  		}
   128  		series = conf.DefaultSeries()
   129  	}
   130  	template := state.MachineTemplate{
   131  		Series:      series,
   132  		Constraints: c.Constraints,
   133  		Jobs:        []state.MachineJob{state.JobHostUnits},
   134  	}
   135  	var m *state.Machine
   136  	switch {
   137  	case c.ContainerType == "":
   138  		m, err = conn.State.AddOneMachine(template)
   139  	case c.MachineId != "":
   140  		m, err = conn.State.AddMachineInsideMachine(template, c.MachineId, c.ContainerType)
   141  	default:
   142  		m, err = conn.State.AddMachineInsideNewMachine(template, template, c.ContainerType)
   143  	}
   144  	if err != nil {
   145  		return "", err
   146  	}
   147  	return m.String(), err
   148  }
   149  
   150  func (c *AddMachineCommand) Run(ctx *cmd.Context) error {
   151  	if c.SSHHost != "" {
   152  		args := manual.ProvisionMachineArgs{
   153  			Host:    c.SSHHost,
   154  			EnvName: c.EnvName,
   155  			Stdin:   ctx.Stdin,
   156  			Stdout:  ctx.Stdout,
   157  			Stderr:  ctx.Stderr,
   158  		}
   159  		_, err := manual.ProvisionMachine(args)
   160  		return err
   161  	}
   162  
   163  	client, err := juju.NewAPIClientFromName(c.EnvName)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	defer client.Close()
   168  
   169  	machineParams := params.AddMachineParams{
   170  		ParentId:      c.MachineId,
   171  		ContainerType: c.ContainerType,
   172  		Series:        c.Series,
   173  		Constraints:   c.Constraints,
   174  		Jobs:          []params.MachineJob{params.JobHostUnits},
   175  	}
   176  	results, err := client.AddMachines([]params.AddMachineParams{machineParams})
   177  	var machineId string
   178  	if params.IsCodeNotImplemented(err) {
   179  		logger.Infof("AddMachines not supported by the API server, " +
   180  			"falling back to 1.16 compatibility mode (direct DB access)")
   181  		machineId, err = c.addMachine1dot16()
   182  	} else if err != nil {
   183  		return err
   184  	} else {
   185  		// Currently, only one machine is added, but in future there may be several added in one call.
   186  		machineInfo := results[0]
   187  		var machineErr *params.Error
   188  		machineId, machineErr = machineInfo.Machine, machineInfo.Error
   189  		if machineErr != nil {
   190  			err = machineErr
   191  		}
   192  	}
   193  	if err != nil {
   194  		return err
   195  	}
   196  	if c.ContainerType == "" {
   197  		logger.Infof("created machine %v", machineId)
   198  	} else {
   199  		logger.Infof("created %q container on machine %v", c.ContainerType, machineId)
   200  	}
   201  	return nil
   202  }