github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/commands/deploy.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	"gopkg.in/juju/charm.v6-unstable"
    14  	"launchpad.net/gnuflag"
    15  
    16  	"github.com/juju/juju/api"
    17  	apiservice "github.com/juju/juju/api/service"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/cmd/envcmd"
    20  	"github.com/juju/juju/cmd/juju/block"
    21  	"github.com/juju/juju/cmd/juju/service"
    22  	"github.com/juju/juju/constraints"
    23  	"github.com/juju/juju/juju/osenv"
    24  	"github.com/juju/juju/storage"
    25  )
    26  
    27  type DeployCommand struct {
    28  	envcmd.EnvCommandBase
    29  	service.UnitCommandBase
    30  	CharmName    string
    31  	ServiceName  string
    32  	Config       cmd.FileVar
    33  	Constraints  constraints.Value
    34  	Networks     string // TODO(dimitern): Drop this in a follow-up and fix docs.
    35  	BumpRevision bool   // Remove this once the 1.16 support is dropped.
    36  	RepoPath     string // defaults to JUJU_REPOSITORY
    37  	RegisterURL  string
    38  
    39  	// TODO(axw) move this to UnitCommandBase once we support --storage
    40  	// on add-unit too.
    41  	//
    42  	// Storage is a map of storage constraints, keyed on the storage name
    43  	// defined in charm storage metadata.
    44  	Storage map[string]storage.Constraints
    45  }
    46  
    47  const deployDoc = `
    48  <charm name> can be a charm URL, or an unambiguously condensed form of it;
    49  assuming a current series of "precise", the following forms will be accepted:
    50  
    51  For cs:precise/mysql
    52    mysql
    53    precise/mysql
    54  
    55  For cs:~user/precise/mysql
    56    cs:~user/mysql
    57  
    58  The current series is determined first by the default-series environment
    59  setting, followed by the preferred series for the charm in the charm store.
    60  
    61  In these cases, a versioned charm URL will be expanded as expected (for example,
    62  mysql-33 becomes cs:precise/mysql-33).
    63  
    64  However, for local charms, when the default-series is not specified in the
    65  environment, one must specify the series. For example:
    66    local:precise/mysql
    67  
    68  <service name>, if omitted, will be derived from <charm name>.
    69  
    70  Constraints can be specified when using deploy by specifying the --constraints
    71  flag.  When used with deploy, service-specific constraints are set so that later
    72  machines provisioned with add-unit will use the same constraints (unless changed
    73  by set-constraints).
    74  
    75  Charms can be deployed to a specific machine using the --to argument.
    76  If the destination is an LXC container the default is to use lxc-clone
    77  to create the container where possible. For Ubuntu deployments, lxc-clone
    78  is supported for the trusty OS series and later. A 'template' container is
    79  created with the name
    80    juju-<series>-template
    81  where <series> is the OS series, for example 'juju-trusty-template'.
    82  
    83  You can override the use of clone by changing the provider configuration:
    84    lxc-clone: false
    85  
    86  If you have the main container directory mounted on a btrfs partition,
    87  then the clone will be using btrfs snapshots to create the containers.
    88  This means that clones use up much less disk space.  If you do not have btrfs,
    89  lxc will attempt to use aufs (an overlay type filesystem). You can
    90  explicitly ask Juju to create full containers and not overlays by specifying
    91  the following in the provider configuration:
    92    lxc-clone-aufs: false
    93  
    94  Examples:
    95     juju deploy mysql --to 23       (deploy to machine 23)
    96     juju deploy mysql --to 24/lxc/3 (deploy to lxc container 3 on host machine 24)
    97     juju deploy mysql --to lxc:25   (deploy to a new lxc container on host machine 25)
    98  
    99     juju deploy mysql -n 5 --constraints mem=8G
   100     (deploy 5 instances of mysql with at least 8 GB of RAM each)
   101  
   102  See Also:
   103     juju help constraints
   104     juju help set-constraints
   105     juju help get-constraints
   106  `
   107  
   108  func (c *DeployCommand) Info() *cmd.Info {
   109  	return &cmd.Info{
   110  		Name:    "deploy",
   111  		Args:    "<charm name> [<service name>]",
   112  		Purpose: "deploy a new service",
   113  		Doc:     deployDoc,
   114  	}
   115  }
   116  
   117  func (c *DeployCommand) SetFlags(f *gnuflag.FlagSet) {
   118  	c.UnitCommandBase.SetFlags(f)
   119  	f.IntVar(&c.NumUnits, "n", 1, "number of service units to deploy for principal charms")
   120  	f.BoolVar(&c.BumpRevision, "u", false, "increment local charm directory revision (DEPRECATED)")
   121  	f.BoolVar(&c.BumpRevision, "upgrade", false, "")
   122  	f.Var(&c.Config, "config", "path to yaml-formatted service config")
   123  	f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "set service constraints")
   124  	f.StringVar(&c.Networks, "networks", "", "deprecated and ignored: use space constraints instead.")
   125  	f.StringVar(&c.RepoPath, "repository", os.Getenv(osenv.JujuRepositoryEnvKey), "local charm repository")
   126  	f.Var(storageFlag{&c.Storage}, "storage", "charm storage constraints")
   127  }
   128  
   129  func (c *DeployCommand) Init(args []string) error {
   130  	switch len(args) {
   131  	case 2:
   132  		if !names.IsValidService(args[1]) {
   133  			return fmt.Errorf("invalid service name %q", args[1])
   134  		}
   135  		c.ServiceName = args[1]
   136  		fallthrough
   137  	case 1:
   138  		if _, err := charm.InferURL(args[0], "fake"); err != nil {
   139  			return fmt.Errorf("invalid charm name %q", args[0])
   140  		}
   141  		c.CharmName = args[0]
   142  	case 0:
   143  		return errors.New("no charm specified")
   144  	default:
   145  		return cmd.CheckEmpty(args[2:])
   146  	}
   147  	return c.UnitCommandBase.Init(args)
   148  }
   149  
   150  func (c *DeployCommand) newServiceAPIClient() (*apiservice.Client, error) {
   151  	root, err := c.NewAPIRoot()
   152  	if err != nil {
   153  		return nil, errors.Trace(err)
   154  	}
   155  	return apiservice.NewClient(root), nil
   156  }
   157  
   158  func (c *DeployCommand) Run(ctx *cmd.Context) error {
   159  	client, err := c.NewAPIClient()
   160  	if err != nil {
   161  		return err
   162  	}
   163  	defer client.Close()
   164  
   165  	conf, err := service.GetClientConfig(client)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	if err := c.CheckProvider(conf); err != nil {
   171  		return err
   172  	}
   173  
   174  	csClient, err := newCharmStoreClient()
   175  	if err != nil {
   176  		return errors.Trace(err)
   177  	}
   178  	defer csClient.jar.Save()
   179  	curl, repo, err := resolveCharmURL(c.CharmName, csClient.params, ctx.AbsPath(c.RepoPath), conf)
   180  	if err != nil {
   181  		return errors.Trace(err)
   182  	}
   183  
   184  	curl, err = addCharmViaAPI(client, ctx, curl, repo, csClient)
   185  	if err != nil {
   186  		return block.ProcessBlockedError(err, block.BlockChange)
   187  	}
   188  
   189  	if c.BumpRevision {
   190  		ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
   191  	}
   192  
   193  	charmInfo, err := client.CharmInfo(curl.String())
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	numUnits := c.NumUnits
   199  	if charmInfo.Meta.Subordinate {
   200  		if !constraints.IsEmpty(&c.Constraints) {
   201  			return errors.New("cannot use --constraints with subordinate service")
   202  		}
   203  		if numUnits == 1 && c.PlacementSpec == "" {
   204  			numUnits = 0
   205  		} else {
   206  			return errors.New("cannot use --num-units or --to with subordinate service")
   207  		}
   208  	}
   209  	serviceName := c.ServiceName
   210  	if serviceName == "" {
   211  		serviceName = charmInfo.Meta.Name
   212  	}
   213  
   214  	var configYAML []byte
   215  	if c.Config.Path != "" {
   216  		configYAML, err = c.Config.Read(ctx)
   217  		if err != nil {
   218  			return err
   219  		}
   220  	}
   221  
   222  	// If storage or placement is specified, we attempt to use a new API on the service facade.
   223  	if len(c.Storage) > 0 || len(c.Placement) > 0 {
   224  		notSupported := errors.New("cannot deploy charms with storage or placement: not supported by the API server")
   225  		serviceClient, err := c.newServiceAPIClient()
   226  		if err != nil {
   227  			return notSupported
   228  		}
   229  		defer serviceClient.Close()
   230  		for i, p := range c.Placement {
   231  			if p.Scope == "env-uuid" {
   232  				p.Scope = serviceClient.EnvironmentUUID()
   233  			}
   234  			c.Placement[i] = p
   235  		}
   236  		err = serviceClient.ServiceDeploy(
   237  			curl.String(),
   238  			serviceName,
   239  			numUnits,
   240  			string(configYAML),
   241  			c.Constraints,
   242  			c.PlacementSpec,
   243  			c.Placement,
   244  			[]string{},
   245  			c.Storage,
   246  		)
   247  		if params.IsCodeNotImplemented(err) {
   248  			return notSupported
   249  		}
   250  		return block.ProcessBlockedError(err, block.BlockChange)
   251  	}
   252  
   253  	if len(c.Networks) > 0 {
   254  		ctx.Infof("use of --networks is deprecated and is ignored. Please use spaces to manage placement within networks")
   255  	}
   256  
   257  	err = client.ServiceDeploy(
   258  		curl.String(),
   259  		serviceName,
   260  		numUnits,
   261  		string(configYAML),
   262  		c.Constraints,
   263  		c.PlacementSpec)
   264  
   265  	if err != nil {
   266  		return block.ProcessBlockedError(err, block.BlockChange)
   267  	}
   268  
   269  	state, err := c.NewAPIRoot()
   270  	if err != nil {
   271  		return err
   272  	}
   273  	err = registerMeteredCharm(c.RegisterURL, state, csClient.jar, curl.String(), serviceName, client.EnvironmentUUID())
   274  	if params.IsCodeNotImplemented(err) {
   275  		// The state server is too old to support metering.  Warn
   276  		// the user, but don't return an error.
   277  		logger.Warningf("current state server version does not support charm metering")
   278  		return nil
   279  	}
   280  
   281  	return block.ProcessBlockedError(err, block.BlockChange)
   282  }
   283  
   284  type metricCredentialsAPI interface {
   285  	SetMetricCredentials(string, []byte) error
   286  	Close() error
   287  }
   288  
   289  type metricsCredentialsAPIImpl struct {
   290  	api   *apiservice.Client
   291  	state api.Connection
   292  }
   293  
   294  // SetMetricCredentials sets the credentials on the service.
   295  func (s *metricsCredentialsAPIImpl) SetMetricCredentials(serviceName string, data []byte) error {
   296  	return s.api.SetMetricCredentials(serviceName, data)
   297  }
   298  
   299  // Close closes the api connection
   300  func (s *metricsCredentialsAPIImpl) Close() error {
   301  	err := s.api.Close()
   302  	if err != nil {
   303  		return errors.Trace(err)
   304  	}
   305  	err = s.state.Close()
   306  	if err != nil {
   307  		return errors.Trace(err)
   308  	}
   309  	return nil
   310  }
   311  
   312  var getMetricCredentialsAPI = func(state api.Connection) (metricCredentialsAPI, error) {
   313  	return &metricsCredentialsAPIImpl{api: apiservice.NewClient(state), state: state}, nil
   314  }