github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/machine/remove.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package machine
     5  
     6  import (
     7  	"github.com/juju/cmd"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/gnuflag"
    10  	"gopkg.in/juju/names.v2"
    11  
    12  	"github.com/juju/juju/api"
    13  	"github.com/juju/juju/api/machinemanager"
    14  	"github.com/juju/juju/apiserver/params"
    15  	jujucmd "github.com/juju/juju/cmd"
    16  	"github.com/juju/juju/cmd/juju/block"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  )
    19  
    20  // NewRemoveCommand returns a command used to remove a specified machine.
    21  func NewRemoveCommand() cmd.Command {
    22  	return modelcmd.Wrap(&removeCommand{})
    23  }
    24  
    25  // removeCommand causes an existing machine to be destroyed.
    26  type removeCommand struct {
    27  	baseMachinesCommand
    28  	apiRoot      api.Connection
    29  	machineAPI   RemoveMachineAPI
    30  	MachineIds   []string
    31  	Force        bool
    32  	KeepInstance bool
    33  }
    34  
    35  const destroyMachineDoc = `
    36  Machines are specified by their numbers, which may be retrieved from the
    37  output of ` + "`juju status`." + `
    38  Machines responsible for the model cannot be removed.
    39  Machines running units or containers can be removed using the '--force'
    40  option; this will also remove those units and containers without giving
    41  them an opportunity to shut down cleanly.
    42  
    43  Examples:
    44  
    45  Remove machine number 5 which has no running units or containers:
    46  
    47      juju remove-machine 5
    48  
    49  Remove machine 6 and any running units or containers:
    50  
    51      juju remove-machine 6 --force
    52      
    53  Remove machine 7 from the Juju model but do not stop 
    54  the corresponding cloud instance:
    55  
    56      juju remove-machine 7 --keep-instance
    57  
    58  See also:
    59      add-machine
    60  `
    61  
    62  // Info implements Command.Info.
    63  func (c *removeCommand) Info() *cmd.Info {
    64  	return jujucmd.Info(&cmd.Info{
    65  		Name:    "remove-machine",
    66  		Args:    "<machine number> ...",
    67  		Purpose: "Removes one or more machines from a model.",
    68  		Doc:     destroyMachineDoc,
    69  	})
    70  }
    71  
    72  // SetFlags implements Command.SetFlags.
    73  func (c *removeCommand) SetFlags(f *gnuflag.FlagSet) {
    74  	c.ModelCommandBase.SetFlags(f)
    75  	f.BoolVar(&c.Force, "force", false, "Completely remove a machine and all its dependencies")
    76  	f.BoolVar(&c.KeepInstance, "keep-instance", false, "Do not stop the running cloud instance")
    77  }
    78  
    79  func (c *removeCommand) Init(args []string) error {
    80  	if len(args) == 0 {
    81  		return errors.Errorf("no machines specified")
    82  	}
    83  	for _, id := range args {
    84  		if !names.IsValidMachine(id) {
    85  			return errors.Errorf("invalid machine id %q", id)
    86  		}
    87  	}
    88  	c.MachineIds = args
    89  	return nil
    90  }
    91  
    92  type RemoveMachineAPI interface {
    93  	DestroyMachines(machines ...string) ([]params.DestroyMachineResult, error)
    94  	ForceDestroyMachines(machines ...string) ([]params.DestroyMachineResult, error)
    95  	DestroyMachinesWithParams(force, keep bool, machines ...string) ([]params.DestroyMachineResult, error)
    96  	Close() error
    97  }
    98  
    99  // TODO(axw) 2017-03-16 #1673323
   100  // Drop this in Juju 3.0.
   101  type removeMachineAdapter struct {
   102  	*api.Client
   103  }
   104  
   105  func (a removeMachineAdapter) DestroyMachines(machines ...string) ([]params.DestroyMachineResult, error) {
   106  	return a.destroyMachines(a.Client.DestroyMachines, machines)
   107  }
   108  
   109  func (a removeMachineAdapter) ForceDestroyMachines(machines ...string) ([]params.DestroyMachineResult, error) {
   110  	return a.destroyMachines(a.Client.ForceDestroyMachines, machines)
   111  }
   112  
   113  func (a removeMachineAdapter) DestroyMachinesWithParams(force, keep bool, machines ...string) ([]params.DestroyMachineResult, error) {
   114  	return a.destroyMachines(a.Client.ForceDestroyMachines, machines)
   115  }
   116  
   117  func (a removeMachineAdapter) destroyMachines(f func(...string) error, machines []string) ([]params.DestroyMachineResult, error) {
   118  	if err := f(machines...); err != nil {
   119  		return nil, err
   120  	}
   121  	results := make([]params.DestroyMachineResult, len(machines))
   122  	for i := range results {
   123  		results[i].Info = &params.DestroyMachineInfo{}
   124  	}
   125  	return results, nil
   126  }
   127  
   128  func (c *removeCommand) getAPIRoot() (api.Connection, error) {
   129  	if c.apiRoot != nil {
   130  		return c.apiRoot, nil
   131  	}
   132  	return c.NewAPIRoot()
   133  }
   134  
   135  func (c *removeCommand) getRemoveMachineAPI() (RemoveMachineAPI, error) {
   136  	root, err := c.getAPIRoot()
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	if root.BestFacadeVersion("MachineManager") < 4 && c.KeepInstance {
   141  		return nil, errors.New("this version of Juju doesn't support --keep-instance")
   142  	}
   143  	if root.BestFacadeVersion("MachineManager") >= 3 && c.machineAPI == nil {
   144  		return machinemanager.NewClient(root), nil
   145  	}
   146  	if c.machineAPI != nil {
   147  		return c.machineAPI, nil
   148  	}
   149  	return removeMachineAdapter{root.Client()}, nil
   150  }
   151  
   152  // Run implements Command.Run.
   153  func (c *removeCommand) Run(ctx *cmd.Context) error {
   154  	client, err := c.getRemoveMachineAPI()
   155  	if err != nil {
   156  		return err
   157  	}
   158  	defer client.Close()
   159  
   160  	var results []params.DestroyMachineResult
   161  	if c.KeepInstance {
   162  		results, err = client.DestroyMachinesWithParams(c.Force, c.KeepInstance, c.MachineIds...)
   163  	} else {
   164  		destroy := client.DestroyMachines
   165  		if c.Force {
   166  			destroy = client.ForceDestroyMachines
   167  		}
   168  		results, err = destroy(c.MachineIds...)
   169  	}
   170  	if err := block.ProcessBlockedError(err, block.BlockRemove); err != nil {
   171  		return err
   172  	}
   173  
   174  	anyFailed := false
   175  	for i, id := range c.MachineIds {
   176  		result := results[i]
   177  		if result.Error != nil {
   178  			anyFailed = true
   179  			ctx.Infof("removing machine %s failed: %s", id, result.Error)
   180  			continue
   181  		}
   182  		if c.KeepInstance {
   183  			ctx.Infof("removing machine %s (but retaining cloud instance)", id)
   184  		} else {
   185  			ctx.Infof("removing machine %s", id)
   186  		}
   187  		for _, entity := range result.Info.DestroyedUnits {
   188  			unitTag, err := names.ParseUnitTag(entity.Tag)
   189  			if err != nil {
   190  				logger.Warningf("%s", err)
   191  				continue
   192  			}
   193  			ctx.Infof("- will remove %s", names.ReadableString(unitTag))
   194  		}
   195  		for _, entity := range result.Info.DestroyedStorage {
   196  			storageTag, err := names.ParseStorageTag(entity.Tag)
   197  			if err != nil {
   198  				logger.Warningf("%s", err)
   199  				continue
   200  			}
   201  			ctx.Infof("- will remove %s", names.ReadableString(storageTag))
   202  		}
   203  		for _, entity := range result.Info.DetachedStorage {
   204  			storageTag, err := names.ParseStorageTag(entity.Tag)
   205  			if err != nil {
   206  				logger.Warningf("%s", err)
   207  				continue
   208  			}
   209  			ctx.Infof("- will detach %s", names.ReadableString(storageTag))
   210  		}
   211  	}
   212  
   213  	if anyFailed {
   214  		return cmd.ErrSilent
   215  	}
   216  	return nil
   217  }