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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package crossmodel
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/gnuflag"
    13  
    14  	"github.com/juju/juju/api/applicationoffers"
    15  	jujucmd "github.com/juju/juju/cmd"
    16  	"github.com/juju/juju/cmd/juju/block"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  	"github.com/juju/juju/core/crossmodel"
    19  	"github.com/juju/juju/jujuclient"
    20  )
    21  
    22  // NewRemoveOfferCommand returns a command used to remove a specified offer.
    23  func NewRemoveOfferCommand() cmd.Command {
    24  	removeCmd := &removeCommand{}
    25  	removeCmd.newAPIFunc = func(controllerName string) (RemoveAPI, error) {
    26  		return removeCmd.NewApplicationOffersAPI(controllerName)
    27  	}
    28  	return modelcmd.WrapController(removeCmd)
    29  }
    30  
    31  type removeCommand struct {
    32  	modelcmd.ControllerCommandBase
    33  	newAPIFunc  func(string) (RemoveAPI, error)
    34  	offers      []string
    35  	offerSource string
    36  
    37  	assumeYes bool
    38  	force     bool
    39  }
    40  
    41  const destroyOfferDoc = `
    42  Remove one or more application offers.
    43  
    44  If the --force option is specified, any existing relations to the
    45  offer will also be removed.
    46  
    47  Offers to remove are normally specified by their URL.
    48  It's also possible to specify just the offer name, in which case
    49  the offer is considered to reside in the current model.
    50  
    51  Examples:
    52  
    53      juju remove-offer prod.model/hosted-mysql
    54      juju remove-offer prod.model/hosted-mysql --force
    55      juju remove-offer hosted-mysql
    56  
    57  See also:
    58      find-offers
    59      offer
    60  `
    61  
    62  // Info implements Command.Info.
    63  func (c *removeCommand) Info() *cmd.Info {
    64  	return jujucmd.Info(&cmd.Info{
    65  		Name:    "remove-offer",
    66  		Args:    "<offer-url> ...",
    67  		Purpose: "Removes one or more offers specified by their URL.",
    68  		Doc:     destroyOfferDoc,
    69  	})
    70  }
    71  
    72  // SetFlags implements Command.SetFlags.
    73  func (c *removeCommand) SetFlags(f *gnuflag.FlagSet) {
    74  	c.ControllerCommandBase.SetFlags(f)
    75  	f.BoolVar(&c.force, "force", false, "remove the offer as well as any relations to the offer")
    76  	f.BoolVar(&c.assumeYes, "y", false, "Do not prompt for confirmation")
    77  	f.BoolVar(&c.assumeYes, "yes", false, "")
    78  }
    79  
    80  // Init implements Command.Init.
    81  func (c *removeCommand) Init(args []string) error {
    82  	if len(args) == 0 {
    83  		return errors.Errorf("no offers specified")
    84  	}
    85  	c.offers = args
    86  	return nil
    87  }
    88  
    89  // RemoveAPI defines the API methods that the remove offer command uses.
    90  type RemoveAPI interface {
    91  	Close() error
    92  	DestroyOffers(force bool, offerURLs ...string) error
    93  	BestAPIVersion() int
    94  }
    95  
    96  // NewApplicationOffersAPI returns an application offers api.
    97  func (c *removeCommand) NewApplicationOffersAPI(controllerName string) (*applicationoffers.Client, error) {
    98  	root, err := c.CommandBase.NewAPIRoot(c.ClientStore(), controllerName, "")
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	return applicationoffers.NewClient(root), nil
   103  }
   104  
   105  var removeOfferMsg = `
   106  WARNING! This command will remove offers: %v
   107  This includes all relations to those offers.
   108  
   109  Continue [y/N]? `[1:]
   110  
   111  // Run implements Command.Run.
   112  func (c *removeCommand) Run(ctx *cmd.Context) error {
   113  	// Allow for the offers to be specified by name rather than a full URL.
   114  	// In that case, we need to assume the offer resides in the current model.
   115  	controllerName, err := c.ControllerName()
   116  	if err != nil {
   117  		return errors.Trace(err)
   118  	}
   119  	store := c.ClientStore()
   120  	currentModel, err := store.CurrentModel(controllerName)
   121  	if err != nil {
   122  		return errors.Trace(err)
   123  	}
   124  	for i, urlStr := range c.offers {
   125  		url, err := crossmodel.ParseOfferURL(urlStr)
   126  		if err != nil {
   127  			url, err = makeURLFromCurrentModel(urlStr, c.offerSource, currentModel)
   128  			if err != nil {
   129  				return errors.Trace(err)
   130  			}
   131  			c.offers[i] = url.String()
   132  		}
   133  		if c.offerSource == "" {
   134  			c.offerSource = url.Source
   135  		}
   136  		if c.offerSource != url.Source {
   137  			return errors.New("all offer URLs must use the same controller")
   138  		}
   139  	}
   140  
   141  	if c.offerSource == "" {
   142  		c.offerSource = controllerName
   143  	}
   144  
   145  	if !c.assumeYes && c.force {
   146  		fmt.Fprintf(ctx.Stdout, removeOfferMsg, strings.Join(c.offers, ", "))
   147  
   148  		if err := jujucmd.UserConfirmYes(ctx); err != nil {
   149  			return errors.Annotate(err, "offer removal")
   150  		}
   151  	}
   152  
   153  	api, err := c.newAPIFunc(c.offerSource)
   154  	if err != nil {
   155  		return errors.Trace(err)
   156  	}
   157  	defer api.Close()
   158  
   159  	if c.force && api.BestAPIVersion() < 2 {
   160  		return errors.NotSupportedf("on this juju controller, remove-offer --force")
   161  	}
   162  
   163  	err = api.DestroyOffers(c.force, c.offers...)
   164  	return block.ProcessBlockedError(err, block.BlockRemove)
   165  }
   166  
   167  func makeURLFromCurrentModel(urlStr, offerSource, currentModel string) (*crossmodel.OfferURL, error) {
   168  	// We may have just been given an offer name.
   169  	// Try again with the current model as the host model.
   170  	modelName := currentModel
   171  	userName := ""
   172  	if jujuclient.IsQualifiedModelName(currentModel) {
   173  		baseName, userTag, err := jujuclient.SplitModelName(currentModel)
   174  		if err != nil {
   175  			return nil, errors.Trace(err)
   176  		}
   177  		modelName = baseName
   178  		userName = userTag.Name()
   179  	}
   180  	derivedUrl := crossmodel.MakeURL(userName, modelName, urlStr, offerSource)
   181  	return crossmodel.ParseOfferURL(derivedUrl)
   182  }