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 }