github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/commands/destroyenvironment.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "bufio" 8 stderrors "errors" 9 "fmt" 10 "io" 11 "strings" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "launchpad.net/gnuflag" 16 17 "github.com/juju/juju/api" 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/cmd/envcmd" 20 "github.com/juju/juju/cmd/juju/block" 21 "github.com/juju/juju/environs" 22 "github.com/juju/juju/environs/config" 23 "github.com/juju/juju/environs/configstore" 24 "github.com/juju/juju/juju" 25 ) 26 27 var NoEnvironmentError = stderrors.New("no environment specified") 28 var DoubleEnvironmentError = stderrors.New("you cannot supply both -e and the envname as a positional argument") 29 30 // DestroyEnvironmentCommand destroys an environment. 31 type DestroyEnvironmentCommand struct { 32 envcmd.EnvCommandBase 33 cmd.CommandBase 34 envName string 35 assumeYes bool 36 force bool 37 } 38 39 func (c *DestroyEnvironmentCommand) Info() *cmd.Info { 40 return &cmd.Info{ 41 Name: "destroy-environment", 42 Args: "<environment name>", 43 Purpose: "terminate all machines and other associated resources for an environment", 44 } 45 } 46 47 func (c *DestroyEnvironmentCommand) SetFlags(f *gnuflag.FlagSet) { 48 f.BoolVar(&c.assumeYes, "y", false, "Do not ask for confirmation") 49 f.BoolVar(&c.assumeYes, "yes", false, "") 50 f.BoolVar(&c.force, "force", false, "Forcefully destroy the environment, directly through the environment provider") 51 f.StringVar(&c.envName, "e", "", "juju environment to operate in") 52 f.StringVar(&c.envName, "environment", "", "juju environment to operate in") 53 } 54 55 func (c *DestroyEnvironmentCommand) Init(args []string) error { 56 if c.envName != "" { 57 logger.Warningf("-e/--environment flag is deprecated in 1.18, " + 58 "please supply environment as a positional parameter") 59 // They supplied the -e flag 60 if len(args) == 0 { 61 // We're happy, we have enough information 62 return nil 63 } 64 // You can't supply -e ENV and ENV as a positional argument 65 return DoubleEnvironmentError 66 } 67 // No -e flag means they must supply the environment positionally 68 switch len(args) { 69 case 0: 70 return NoEnvironmentError 71 case 1: 72 c.envName = args[0] 73 return nil 74 default: 75 return cmd.CheckEmpty(args[1:]) 76 } 77 } 78 79 func (c *DestroyEnvironmentCommand) Run(ctx *cmd.Context) (result error) { 80 store, err := configstore.Default() 81 if err != nil { 82 return errors.Annotate(err, "cannot open environment info storage") 83 } 84 85 cfgInfo, err := store.ReadInfo(c.envName) 86 if err != nil { 87 return errors.Annotate(err, "cannot read environment info") 88 } 89 90 var hasBootstrapCfg bool 91 var serverEnviron environs.Environ 92 if bootstrapCfg := cfgInfo.BootstrapConfig(); bootstrapCfg != nil { 93 hasBootstrapCfg = true 94 serverEnviron, err = getServerEnv(bootstrapCfg) 95 if err != nil { 96 return errors.Trace(err) 97 } 98 } 99 100 if c.force { 101 if hasBootstrapCfg { 102 // If --force is supplied on a server environment, then don't 103 // attempt to use the API. This is necessary to destroy broken 104 // environments, where the API server is inaccessible or faulty. 105 return environs.Destroy(serverEnviron, store) 106 } else { 107 // Force only makes sense on the server environment. 108 return errors.Errorf("cannot force destroy environment without bootstrap information") 109 } 110 } 111 112 apiclient, err := juju.NewAPIClientFromName(c.envName) 113 if err != nil { 114 if errors.IsNotFound(err) { 115 logger.Warningf("environment not found, removing config file") 116 ctx.Infof("environment not found, removing config file") 117 return environs.DestroyInfo(c.envName, store) 118 } 119 return errors.Annotate(err, "cannot connect to API") 120 } 121 defer apiclient.Close() 122 info, err := apiclient.EnvironmentInfo() 123 if err != nil { 124 return errors.Annotate(err, "cannot get information for environment") 125 } 126 127 if !c.assumeYes { 128 fmt.Fprintf(ctx.Stdout, destroyEnvMsg, c.envName, info.ProviderType) 129 130 scanner := bufio.NewScanner(ctx.Stdin) 131 scanner.Scan() 132 err := scanner.Err() 133 if err != nil && err != io.EOF { 134 return errors.Annotate(err, "environment destruction aborted") 135 } 136 answer := strings.ToLower(scanner.Text()) 137 if answer != "y" && answer != "yes" { 138 return stderrors.New("environment destruction aborted") 139 } 140 } 141 142 if info.UUID == info.ServerUUID { 143 if !hasBootstrapCfg { 144 // serverEnviron will be nil as we didn't have the jenv bootstrap 145 // config to build it. But we do have a connection to the API 146 // server, so get the config from there. 147 bootstrapCfg, err := apiclient.EnvironmentGet() 148 if err != nil { 149 return errors.Annotate(err, "environment destruction failed") 150 } 151 serverEnviron, err = getServerEnv(bootstrapCfg) 152 if err != nil { 153 return errors.Annotate(err, "environment destruction failed") 154 } 155 } 156 157 if err := c.destroyEnv(apiclient); err != nil { 158 return errors.Annotate(err, "environment destruction failed") 159 } 160 if err := environs.Destroy(serverEnviron, store); err != nil { 161 return errors.Annotate(err, "environment destruction failed") 162 } 163 return environs.DestroyInfo(c.envName, store) 164 } 165 166 // If this is not the server environment, there is no bootstrap info and 167 // we do not call Destroy on the provider. Destroying the environment via 168 // the API and cleaning up the jenv file is sufficient. 169 if err := c.destroyEnv(apiclient); err != nil { 170 errors.Annotate(err, "cannot destroy environment") 171 } 172 return environs.DestroyInfo(c.envName, store) 173 } 174 175 func getServerEnv(bootstrapCfg map[string]interface{}) (environs.Environ, error) { 176 cfg, err := config.New(config.NoDefaults, bootstrapCfg) 177 if err != nil { 178 return nil, errors.Trace(err) 179 } 180 return environs.New(cfg) 181 } 182 183 func (c *DestroyEnvironmentCommand) destroyEnv(apiclient *api.Client) (result error) { 184 defer func() { 185 result = c.ensureUserFriendlyErrorLog(result) 186 }() 187 err := apiclient.DestroyEnvironment() 188 if cmdErr := processDestroyError(err); cmdErr != nil { 189 return cmdErr 190 } 191 192 return nil 193 } 194 195 // processDestroyError determines how to format error message based on its code. 196 // Note that CodeNotImplemented errors have not be propogated in previous implementation. 197 // This behaviour was preserved. 198 func processDestroyError(err error) error { 199 if err == nil || params.IsCodeNotImplemented(err) { 200 return nil 201 } 202 if params.IsCodeOperationBlocked(err) { 203 return err 204 } 205 return errors.Annotate(err, "destroying environment") 206 } 207 208 // ensureUserFriendlyErrorLog ensures that error will be logged and displayed 209 // in a user-friendly manner with readable and digestable error message. 210 func (c *DestroyEnvironmentCommand) ensureUserFriendlyErrorLog(err error) error { 211 if err == nil { 212 return nil 213 } 214 if params.IsCodeOperationBlocked(err) { 215 return block.ProcessBlockedError(err, block.BlockDestroy) 216 } 217 logger.Errorf(stdFailureMsg, c.envName) 218 return err 219 } 220 221 var destroyEnvMsg = ` 222 WARNING! this command will destroy the %q environment (type: %s) 223 This includes all machines, services, data and other resources. 224 225 Continue [y/N]? `[1:] 226 227 var stdFailureMsg = `failed to destroy environment %q 228 229 If the environment is unusable, then you may run 230 231 juju destroy-environment --force 232 233 to forcefully destroy the environment. Upon doing so, review 234 your environment provider console for any resources that need 235 to be cleaned up. Using force will also by-pass destroy-envrionment block. 236 237 `