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 = ¶ms.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 }