github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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     juju add-machine ssh:user@10.10.0.3   (manually provisions a machine with ssh)
    52  
    53  See Also:
    54     juju help constraints
    55  `
    56  
    57  // AddMachineCommand starts a new machine and registers it in the environment.
    58  type AddMachineCommand struct {
    59  	cmd.EnvCommandBase
    60  	// If specified, use this series, else use the environment default-series
    61  	Series string
    62  	// If specified, these constraints are merged with those already in the environment.
    63  	Constraints   constraints.Value
    64  	MachineId     string
    65  	ContainerType instance.ContainerType
    66  	SSHHost       string
    67  }
    68  
    69  func (c *AddMachineCommand) Info() *cmd.Info {
    70  	return &cmd.Info{
    71  		Name:    "add-machine",
    72  		Args:    "[<container>:machine | <container> | ssh:[user@]host]",
    73  		Purpose: "start a new, empty machine and optionally a container, or add a container to a machine",
    74  		Doc:     addMachineDoc,
    75  	}
    76  }
    77  
    78  func (c *AddMachineCommand) SetFlags(f *gnuflag.FlagSet) {
    79  	c.EnvCommandBase.SetFlags(f)
    80  	f.StringVar(&c.Series, "series", "", "the charm series")
    81  	f.Var(constraints.ConstraintsValue{&c.Constraints}, "constraints", "additional machine constraints")
    82  }
    83  
    84  func (c *AddMachineCommand) Init(args []string) error {
    85  	if c.Constraints.Container != nil {
    86  		return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container)
    87  	}
    88  	containerSpec, err := cmd.ZeroOrOneArgs(args)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	if containerSpec == "" {
    93  		return nil
    94  	}
    95  	if strings.HasPrefix(containerSpec, sshHostPrefix) {
    96  		c.SSHHost = containerSpec[len(sshHostPrefix):]
    97  	} else {
    98  		// container arg can either be 'type:machine' or 'type'
    99  		if c.ContainerType, err = instance.ParseContainerType(containerSpec); err != nil {
   100  			if names.IsMachine(containerSpec) || !cmd.IsMachineOrNewContainer(containerSpec) {
   101  				return fmt.Errorf("malformed container argument %q", containerSpec)
   102  			}
   103  			sep := strings.Index(containerSpec, ":")
   104  			c.MachineId = containerSpec[sep+1:]
   105  			c.ContainerType, err = instance.ParseContainerType(containerSpec[:sep])
   106  		}
   107  	}
   108  	return err
   109  }
   110  
   111  // addMachine1dot16 runs Client.AddMachines using a direct DB connection to maintain
   112  // compatibility with an API server running 1.16 or older (when AddMachines
   113  // was not available). This fallback can be removed when we no longer maintain
   114  // 1.16 compatibility.
   115  // This was copied directly from the code in AddMachineCommand.Run in 1.16
   116  func (c *AddMachineCommand) addMachine1dot16() (string, error) {
   117  	conn, err := juju.NewConnFromName(c.EnvName)
   118  	if err != nil {
   119  		return "", err
   120  	}
   121  	defer conn.Close()
   122  
   123  	series := c.Series
   124  	if series == "" {
   125  		conf, err := conn.State.EnvironConfig()
   126  		if err != nil {
   127  			return "", err
   128  		}
   129  		series = conf.DefaultSeries()
   130  	}
   131  	template := state.MachineTemplate{
   132  		Series:      series,
   133  		Constraints: c.Constraints,
   134  		Jobs:        []state.MachineJob{state.JobHostUnits},
   135  	}
   136  	var m *state.Machine
   137  	switch {
   138  	case c.ContainerType == "":
   139  		m, err = conn.State.AddOneMachine(template)
   140  	case c.MachineId != "":
   141  		m, err = conn.State.AddMachineInsideMachine(template, c.MachineId, c.ContainerType)
   142  	default:
   143  		m, err = conn.State.AddMachineInsideNewMachine(template, template, c.ContainerType)
   144  	}
   145  	if err != nil {
   146  		return "", err
   147  	}
   148  	return m.String(), err
   149  }
   150  
   151  func (c *AddMachineCommand) Run(ctx *cmd.Context) error {
   152  	if c.SSHHost != "" {
   153  		args := manual.ProvisionMachineArgs{
   154  			Host:    c.SSHHost,
   155  			EnvName: c.EnvName,
   156  			Stdin:   ctx.Stdin,
   157  			Stdout:  ctx.Stdout,
   158  			Stderr:  ctx.Stderr,
   159  		}
   160  		_, err := manual.ProvisionMachine(args)
   161  		return err
   162  	}
   163  
   164  	client, err := juju.NewAPIClientFromName(c.EnvName)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	defer client.Close()
   169  
   170  	machineParams := params.AddMachineParams{
   171  		ParentId:      c.MachineId,
   172  		ContainerType: c.ContainerType,
   173  		Series:        c.Series,
   174  		Constraints:   c.Constraints,
   175  		Jobs:          []params.MachineJob{params.JobHostUnits},
   176  	}
   177  	results, err := client.AddMachines([]params.AddMachineParams{machineParams})
   178  	var machineId string
   179  	if params.IsCodeNotImplemented(err) {
   180  		logger.Infof("AddMachines not supported by the API server, " +
   181  			"falling back to 1.16 compatibility mode (direct DB access)")
   182  		machineId, err = c.addMachine1dot16()
   183  	} else if err != nil {
   184  		return err
   185  	} else {
   186  		// Currently, only one machine is added, but in future there may be several added in one call.
   187  		machineInfo := results[0]
   188  		var machineErr *params.Error
   189  		machineId, machineErr = machineInfo.Machine, machineInfo.Error
   190  		if machineErr != nil {
   191  			err = machineErr
   192  		}
   193  	}
   194  	if err != nil {
   195  		return err
   196  	}
   197  	if c.ContainerType == "" {
   198  		logger.Infof("created machine %v", machineId)
   199  	} else {
   200  		logger.Infof("created %q container on machine %v", c.ContainerType, machineId)
   201  	}
   202  	return nil
   203  }