github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/system/destroy.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package system
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"strings"
    12  	"text/tabwriter"
    13  
    14  	"github.com/juju/cmd"
    15  	"github.com/juju/errors"
    16  	"launchpad.net/gnuflag"
    17  
    18  	"github.com/juju/juju/api/systemmanager"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/cmd/envcmd"
    21  	"github.com/juju/juju/cmd/juju/block"
    22  	"github.com/juju/juju/environs"
    23  	"github.com/juju/juju/environs/config"
    24  	"github.com/juju/juju/environs/configstore"
    25  	"github.com/juju/juju/juju"
    26  )
    27  
    28  // DestroyCommand destroys the specified system.
    29  type DestroyCommand struct {
    30  	DestroyCommandBase
    31  	destroyEnvs bool
    32  }
    33  
    34  var destroyDoc = `Destroys the specified system`
    35  var destroySysMsg = `
    36  WARNING! This command will destroy the %q system.
    37  This includes all machines, services, data and other resources.
    38  
    39  Continue [y/N]? `[1:]
    40  
    41  // destroySystemAPI defines the methods on the system manager API endpoint
    42  // that the destroy command calls.
    43  type destroySystemAPI interface {
    44  	Close() error
    45  	EnvironmentConfig() (map[string]interface{}, error)
    46  	DestroySystem(destroyEnvs bool, ignoreBlocks bool) error
    47  	ListBlockedEnvironments() ([]params.EnvironmentBlockInfo, error)
    48  }
    49  
    50  // destroyClientAPI defines the methods on the client API endpoint that the
    51  // destroy command might call.
    52  type destroyClientAPI interface {
    53  	Close() error
    54  	EnvironmentGet() (map[string]interface{}, error)
    55  	DestroyEnvironment() error
    56  }
    57  
    58  // Info implements Command.Info.
    59  func (c *DestroyCommand) Info() *cmd.Info {
    60  	return &cmd.Info{
    61  		Name:    "destroy",
    62  		Args:    "<system name>",
    63  		Purpose: "terminate all machines and other associated resources for a system environment",
    64  		Doc:     destroyDoc,
    65  	}
    66  }
    67  
    68  // SetFlags implements Command.SetFlags.
    69  func (c *DestroyCommand) SetFlags(f *gnuflag.FlagSet) {
    70  	f.BoolVar(&c.destroyEnvs, "destroy-all-environments", false, "destroy all hosted environments on the system")
    71  	c.DestroyCommandBase.SetFlags(f)
    72  }
    73  
    74  func (c *DestroyCommand) getSystemAPI() (destroySystemAPI, error) {
    75  	if c.api != nil {
    76  		return c.api, c.apierr
    77  	}
    78  	root, err := juju.NewAPIFromName(c.systemName)
    79  	if err != nil {
    80  		return nil, errors.Trace(err)
    81  	}
    82  
    83  	return systemmanager.NewClient(root), nil
    84  }
    85  
    86  // Run implements Command.Run
    87  func (c *DestroyCommand) Run(ctx *cmd.Context) error {
    88  	store, err := configstore.Default()
    89  	if err != nil {
    90  		return errors.Annotate(err, "cannot open system info storage")
    91  	}
    92  
    93  	cfgInfo, err := store.ReadInfo(c.systemName)
    94  	if err != nil {
    95  		return errors.Annotate(err, "cannot read system info")
    96  	}
    97  
    98  	// Verify that we're destroying a system
    99  	apiEndpoint := cfgInfo.APIEndpoint()
   100  	if apiEndpoint.ServerUUID != "" && apiEndpoint.EnvironUUID != apiEndpoint.ServerUUID {
   101  		return errors.Errorf("%q is not a system; use juju environment destroy to destroy it", c.systemName)
   102  	}
   103  
   104  	if !c.assumeYes {
   105  		if err = confirmDestruction(ctx, c.systemName); err != nil {
   106  			return err
   107  		}
   108  	}
   109  
   110  	// Attempt to connect to the API.  If we can't, fail the destroy.  Users will
   111  	// need to use the system kill command if we can't connect.
   112  	api, err := c.getSystemAPI()
   113  	if err != nil {
   114  		return c.ensureUserFriendlyErrorLog(errors.Annotate(err, "cannot connect to API"), ctx, nil)
   115  	}
   116  	defer api.Close()
   117  
   118  	// Obtain bootstrap / system environ information
   119  	systemEnviron, err := c.getSystemEnviron(cfgInfo, api)
   120  	if err != nil {
   121  		return errors.Annotate(err, "cannot obtain bootstrap information")
   122  	}
   123  
   124  	// Attempt to destroy the system.
   125  	err = api.DestroySystem(c.destroyEnvs, false)
   126  	if params.IsCodeNotImplemented(err) {
   127  		// Fall back to using the client endpoint to destroy the system,
   128  		// sending the info we were already able to collect.
   129  		return c.destroySystemViaClient(ctx, cfgInfo, systemEnviron, store)
   130  	}
   131  	if err != nil {
   132  		return c.ensureUserFriendlyErrorLog(errors.Annotate(err, "cannot destroy system"), ctx, api)
   133  	}
   134  
   135  	return environs.Destroy(systemEnviron, store)
   136  }
   137  
   138  // destroySystemViaClient attempts to destroy the system using the client
   139  // endpoint for older juju systems which do not implement systemmanager.DestroySystem
   140  func (c *DestroyCommand) destroySystemViaClient(ctx *cmd.Context, info configstore.EnvironInfo, systemEnviron environs.Environ, store configstore.Storage) error {
   141  	api, err := c.getClientAPI()
   142  	if err != nil {
   143  		return c.ensureUserFriendlyErrorLog(errors.Annotate(err, "cannot connect to API"), ctx, nil)
   144  	}
   145  	defer api.Close()
   146  
   147  	err = api.DestroyEnvironment()
   148  	if err != nil {
   149  		return c.ensureUserFriendlyErrorLog(errors.Annotate(err, "cannot destroy system"), ctx, nil)
   150  	}
   151  
   152  	return environs.Destroy(systemEnviron, store)
   153  }
   154  
   155  // ensureUserFriendlyErrorLog ensures that error will be logged and displayed
   156  // in a user-friendly manner with readable and digestable error message.
   157  func (c *DestroyCommand) ensureUserFriendlyErrorLog(destroyErr error, ctx *cmd.Context, api destroySystemAPI) error {
   158  	if destroyErr == nil {
   159  		return nil
   160  	}
   161  	if params.IsCodeOperationBlocked(destroyErr) {
   162  		logger.Errorf(`there are blocks preventing system destruction
   163  To remove all blocks in the system, please run:
   164  
   165      juju system remove-blocks
   166  
   167  `)
   168  		if api != nil {
   169  			envs, err := api.ListBlockedEnvironments()
   170  			var bytes []byte
   171  			if err == nil {
   172  				bytes, err = formatTabularBlockedEnvironments(envs)
   173  			}
   174  
   175  			if err != nil {
   176  				logger.Errorf("Unable to list blocked environments: %s", err)
   177  				return cmd.ErrSilent
   178  			}
   179  			ctx.Infof(string(bytes))
   180  		}
   181  		return cmd.ErrSilent
   182  	}
   183  	logger.Errorf(stdFailureMsg, c.systemName)
   184  	return destroyErr
   185  }
   186  
   187  var stdFailureMsg = `failed to destroy system %q
   188  
   189  If the system is unusable, then you may run
   190  
   191      juju system kill
   192  
   193  to forcibly destroy the system. Upon doing so, review
   194  your environment provider console for any resources that need
   195  to be cleaned up.
   196  `
   197  
   198  func formatTabularBlockedEnvironments(value interface{}) ([]byte, error) {
   199  	envs, ok := value.([]params.EnvironmentBlockInfo)
   200  	if !ok {
   201  		return nil, errors.Errorf("expected value of type %T, got %T", envs, value)
   202  	}
   203  
   204  	var out bytes.Buffer
   205  	const (
   206  		// To format things into columns.
   207  		minwidth = 0
   208  		tabwidth = 1
   209  		padding  = 2
   210  		padchar  = ' '
   211  		flags    = 0
   212  	)
   213  	tw := tabwriter.NewWriter(&out, minwidth, tabwidth, padding, padchar, flags)
   214  	fmt.Fprintf(tw, "NAME\tENVIRONMENT UUID\tOWNER\tBLOCKS\n")
   215  	for _, env := range envs {
   216  		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", env.Name, env.UUID, env.OwnerTag, blocksToStr(env.Blocks))
   217  	}
   218  	tw.Flush()
   219  	return out.Bytes(), nil
   220  }
   221  
   222  func blocksToStr(blocks []string) string {
   223  	result := ""
   224  	sep := ""
   225  	for _, blk := range blocks {
   226  		result = result + sep + block.OperationFromType(blk)
   227  		sep = ","
   228  	}
   229  
   230  	return result
   231  }
   232  
   233  // DestroyCommandBase provides common attributes and methods that both the system
   234  // destroy and system kill commands require.
   235  type DestroyCommandBase struct {
   236  	envcmd.SysCommandBase
   237  	systemName string
   238  	assumeYes  bool
   239  
   240  	// The following fields are for mocking out
   241  	// api behavior for testing.
   242  	api       destroySystemAPI
   243  	apierr    error
   244  	clientapi destroyClientAPI
   245  }
   246  
   247  func (c *DestroyCommandBase) getClientAPI() (destroyClientAPI, error) {
   248  	if c.clientapi != nil {
   249  		return c.clientapi, nil
   250  	}
   251  	root, err := juju.NewAPIFromName(c.systemName)
   252  	if err != nil {
   253  		return nil, errors.Trace(err)
   254  	}
   255  	return root.Client(), nil
   256  }
   257  
   258  // SetFlags implements Command.SetFlags.
   259  func (c *DestroyCommandBase) SetFlags(f *gnuflag.FlagSet) {
   260  	f.BoolVar(&c.assumeYes, "y", false, "Do not ask for confirmation")
   261  	f.BoolVar(&c.assumeYes, "yes", false, "")
   262  }
   263  
   264  // Init implements Command.Init.
   265  func (c *DestroyCommandBase) Init(args []string) error {
   266  	switch len(args) {
   267  	case 0:
   268  		return errors.New("no system specified")
   269  	case 1:
   270  		c.systemName = args[0]
   271  		return nil
   272  	default:
   273  		return cmd.CheckEmpty(args[1:])
   274  	}
   275  }
   276  
   277  // getSystemEnviron gets the bootstrap information required to destroy the environment
   278  // by first checking the config store, then querying the API if the information is not
   279  // in the store.
   280  func (c *DestroyCommandBase) getSystemEnviron(info configstore.EnvironInfo, sysAPI destroySystemAPI) (_ environs.Environ, err error) {
   281  	bootstrapCfg := info.BootstrapConfig()
   282  	if bootstrapCfg == nil {
   283  		if sysAPI == nil {
   284  			return nil, errors.New("unable to get bootstrap information from API")
   285  		}
   286  		bootstrapCfg, err = sysAPI.EnvironmentConfig()
   287  		if params.IsCodeNotImplemented(err) {
   288  			// Fallback to the client API. Better to encapsulate the logic for
   289  			// old servers than worry about connecting twice.
   290  			client, err := c.getClientAPI()
   291  			if err != nil {
   292  				return nil, errors.Trace(err)
   293  			}
   294  			defer client.Close()
   295  			bootstrapCfg, err = client.EnvironmentGet()
   296  			if err != nil {
   297  				return nil, errors.Trace(err)
   298  			}
   299  		} else if err != nil {
   300  			return nil, errors.Trace(err)
   301  		}
   302  	}
   303  
   304  	cfg, err := config.New(config.NoDefaults, bootstrapCfg)
   305  	if err != nil {
   306  		return nil, errors.Trace(err)
   307  	}
   308  	return environs.New(cfg)
   309  }
   310  
   311  func confirmDestruction(ctx *cmd.Context, systemName string) error {
   312  	// Get confirmation from the user that they want to continue
   313  	fmt.Fprintf(ctx.Stdout, destroySysMsg, systemName)
   314  
   315  	scanner := bufio.NewScanner(ctx.Stdin)
   316  	scanner.Scan()
   317  	err := scanner.Err()
   318  	if err != nil && err != io.EOF {
   319  		return errors.Annotate(err, "system destruction aborted")
   320  	}
   321  	answer := strings.ToLower(scanner.Text())
   322  	if answer != "y" && answer != "yes" {
   323  		return errors.New("system destruction aborted")
   324  	}
   325  
   326  	return nil
   327  }