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  }