github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/application/consume.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package application 5 6 import ( 7 "github.com/juju/cmd" 8 "github.com/juju/errors" 9 "gopkg.in/juju/names.v2" 10 11 "github.com/juju/juju/api/application" 12 "github.com/juju/juju/api/applicationoffers" 13 "github.com/juju/juju/apiserver/params" 14 jujucmd "github.com/juju/juju/cmd" 15 "github.com/juju/juju/cmd/modelcmd" 16 "github.com/juju/juju/core/crossmodel" 17 ) 18 19 var usageConsumeSummary = ` 20 Add a remote offer to the model.`[1:] 21 22 var usageConsumeDetails = ` 23 Adds a remote offer to the model. Relations can be created later using "juju relate". 24 25 The remote offer is identified by providing a path to the offer: 26 [<model owner>/]<model name>.<application name> 27 for an application in another model in this controller (if owner isn't specified it's assumed to be the logged-in user) 28 29 Examples: 30 $ juju consume othermodel.mysql 31 $ juju consume owner/othermodel.mysql 32 $ juju consume anothercontroller:owner/othermodel.mysql 33 34 See also: 35 add-relation 36 offer`[1:] 37 38 // NewConsumeCommand returns a command to add remote offers to 39 // the model. 40 func NewConsumeCommand() cmd.Command { 41 return modelcmd.Wrap(&consumeCommand{}) 42 } 43 44 // consumeCommand adds remote offers to the model without 45 // relating them to other applications. 46 type consumeCommand struct { 47 modelcmd.ModelCommandBase 48 sourceAPI applicationConsumeDetailsAPI 49 targetAPI applicationConsumeAPI 50 remoteApplication string 51 applicationAlias string 52 } 53 54 // Info implements cmd.Command. 55 func (c *consumeCommand) Info() *cmd.Info { 56 return jujucmd.Info(&cmd.Info{ 57 Name: "consume", 58 Args: "<remote offer path> [<local application name>]", 59 Purpose: usageConsumeSummary, 60 Doc: usageConsumeDetails, 61 }) 62 } 63 64 // Init implements cmd.Command. 65 func (c *consumeCommand) Init(args []string) error { 66 if len(args) == 0 { 67 return errors.New("no remote offer specified") 68 } 69 c.remoteApplication = args[0] 70 if len(args) > 1 { 71 if !names.IsValidApplication(args[1]) { 72 return errors.Errorf("invalid application name %q", args[1]) 73 } 74 c.applicationAlias = args[1] 75 return cmd.CheckEmpty(args[2:]) 76 } 77 return nil 78 } 79 80 func (c *consumeCommand) getTargetAPI() (applicationConsumeAPI, error) { 81 if c.targetAPI != nil { 82 return c.targetAPI, nil 83 } 84 root, err := c.NewAPIRoot() 85 if err != nil { 86 return nil, errors.Trace(err) 87 } 88 return application.NewClient(root), nil 89 } 90 91 func (c *consumeCommand) getSourceAPI(url *crossmodel.OfferURL) (applicationConsumeDetailsAPI, error) { 92 if c.sourceAPI != nil { 93 return c.sourceAPI, nil 94 } 95 96 if url.Source == "" { 97 var err error 98 controllerName, err := c.ControllerName() 99 if err != nil { 100 return nil, errors.Trace(err) 101 } 102 url.Source = controllerName 103 } 104 root, err := c.CommandBase.NewAPIRoot(c.ClientStore(), url.Source, "") 105 if err != nil { 106 return nil, errors.Trace(err) 107 } 108 return applicationoffers.NewClient(root), nil 109 } 110 111 // Run adds the requested remote offer to the model. Implements 112 // cmd.Command. 113 func (c *consumeCommand) Run(ctx *cmd.Context) error { 114 accountDetails, err := c.CurrentAccountDetails() 115 if err != nil { 116 return errors.Trace(err) 117 } 118 url, err := crossmodel.ParseOfferURL(c.remoteApplication) 119 if err != nil { 120 return errors.Trace(err) 121 } 122 if url.HasEndpoint() { 123 return errors.Errorf("remote offer %q shouldn't include endpoint", c.remoteApplication) 124 } 125 if url.User == "" { 126 url.User = accountDetails.User 127 c.remoteApplication = url.Path() 128 } 129 sourceClient, err := c.getSourceAPI(url) 130 if err != nil { 131 return errors.Trace(err) 132 } 133 defer sourceClient.Close() 134 135 consumeDetails, err := sourceClient.GetConsumeDetails(url.AsLocal().String()) 136 if err != nil { 137 return errors.Trace(err) 138 } 139 // Parse the offer details URL and add the source controller so 140 // things like status can show the original source of the offer. 141 offerURL, err := crossmodel.ParseOfferURL(consumeDetails.Offer.OfferURL) 142 if err != nil { 143 return errors.Trace(err) 144 } 145 offerURL.Source = url.Source 146 consumeDetails.Offer.OfferURL = offerURL.String() 147 148 targetClient, err := c.getTargetAPI() 149 if err != nil { 150 return errors.Trace(err) 151 } 152 defer targetClient.Close() 153 154 arg := crossmodel.ConsumeApplicationArgs{ 155 Offer: *consumeDetails.Offer, 156 ApplicationAlias: c.applicationAlias, 157 Macaroon: consumeDetails.Macaroon, 158 } 159 if consumeDetails.ControllerInfo != nil { 160 controllerTag, err := names.ParseControllerTag(consumeDetails.ControllerInfo.ControllerTag) 161 if err != nil { 162 return errors.Trace(err) 163 } 164 arg.ControllerInfo = &crossmodel.ControllerInfo{ 165 ControllerTag: controllerTag, 166 Alias: consumeDetails.ControllerInfo.Alias, 167 Addrs: consumeDetails.ControllerInfo.Addrs, 168 CACert: consumeDetails.ControllerInfo.CACert, 169 } 170 } 171 localName, err := targetClient.Consume(arg) 172 if err != nil { 173 return errors.Trace(err) 174 } 175 ctx.Infof("Added %s as %s", c.remoteApplication, localName) 176 return nil 177 } 178 179 type applicationConsumeAPI interface { 180 Close() error 181 Consume(crossmodel.ConsumeApplicationArgs) (string, error) 182 } 183 184 type applicationConsumeDetailsAPI interface { 185 Close() error 186 GetConsumeDetails(string) (params.ConsumeOfferDetails, error) 187 }