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  `