launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/cmd/juju/deploy.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  
    11  	"launchpad.net/gnuflag"
    12  	"launchpad.net/juju-core/charm"
    13  	"launchpad.net/juju-core/cmd"
    14  	"launchpad.net/juju-core/constraints"
    15  	"launchpad.net/juju-core/environs/config"
    16  	"launchpad.net/juju-core/juju"
    17  	"launchpad.net/juju-core/juju/osenv"
    18  	"launchpad.net/juju-core/names"
    19  	"launchpad.net/juju-core/state/api"
    20  	"launchpad.net/juju-core/state/api/params"
    21  )
    22  
    23  type DeployCommand struct {
    24  	cmd.EnvCommandBase
    25  	UnitCommandBase
    26  	CharmName    string
    27  	ServiceName  string
    28  	Config       cmd.FileVar
    29  	Constraints  constraints.Value
    30  	BumpRevision bool   // Remove this once the 1.16 support is dropped.
    31  	RepoPath     string // defaults to JUJU_REPOSITORY
    32  }
    33  
    34  const deployDoc = `
    35  <charm name> can be a charm URL, or an unambiguously condensed form of it;
    36  assuming a current default series of "precise", the following forms will be
    37  accepted.
    38  
    39  For cs:precise/mysql
    40    mysql
    41    precise/mysql
    42  
    43  For cs:~user/precise/mysql
    44    cs:~user/mysql
    45  
    46  For local:precise/mysql
    47    local:mysql
    48  
    49  In all cases, a versioned charm URL will be expanded as expected (for example,
    50  mysql-33 becomes cs:precise/mysql-33).
    51  
    52  <service name>, if omitted, will be derived from <charm name>.
    53  
    54  Constraints can be specified when using deploy by specifying the --constraints
    55  flag.  When used with deploy, service-specific constraints are set so that later
    56  machines provisioned with add-unit will use the same constraints (unless changed
    57  by set-constraints).
    58  
    59  Charms can be deployed to a specific machine using the --to argument.
    60  
    61  Examples:
    62     juju deploy mysql --to 23       (Deploy to machine 23)
    63     juju deploy mysql --to 24/lxc/3 (Deploy to lxc container 3 on host machine 24)
    64     juju deploy mysql --to lxc:25   (Deploy to a new lxc container on host machine 25)
    65     
    66     juju deploy mysql -n 5 --constraints mem=8G (deploy 5 instances of mysql with at least 8 GB of RAM each)
    67  
    68  See Also:
    69     juju help constraints
    70     juju help set-constraints
    71     juju help get-constraints
    72  `
    73  
    74  func (c *DeployCommand) Info() *cmd.Info {
    75  	return &cmd.Info{
    76  		Name:    "deploy",
    77  		Args:    "<charm name> [<service name>]",
    78  		Purpose: "deploy a new service",
    79  		Doc:     deployDoc,
    80  	}
    81  }
    82  
    83  func (c *DeployCommand) SetFlags(f *gnuflag.FlagSet) {
    84  	c.EnvCommandBase.SetFlags(f)
    85  	c.UnitCommandBase.SetFlags(f)
    86  	f.IntVar(&c.NumUnits, "n", 1, "number of service units to deploy for principal charms")
    87  	f.BoolVar(&c.BumpRevision, "u", false, "increment local charm directory revision (DEPRECATED)")
    88  	f.BoolVar(&c.BumpRevision, "upgrade", false, "")
    89  	f.Var(&c.Config, "config", "path to yaml-formatted service config")
    90  	f.Var(constraints.ConstraintsValue{&c.Constraints}, "constraints", "set service constraints")
    91  	f.StringVar(&c.RepoPath, "repository", os.Getenv(osenv.JujuRepositoryEnvKey), "local charm repository")
    92  }
    93  
    94  func (c *DeployCommand) Init(args []string) error {
    95  	switch len(args) {
    96  	case 2:
    97  		if !names.IsService(args[1]) {
    98  			return fmt.Errorf("invalid service name %q", args[1])
    99  		}
   100  		c.ServiceName = args[1]
   101  		fallthrough
   102  	case 1:
   103  		if _, err := charm.InferURL(args[0], "fake"); err != nil {
   104  			return fmt.Errorf("invalid charm name %q", args[0])
   105  		}
   106  		c.CharmName = args[0]
   107  	case 0:
   108  		return errors.New("no charm specified")
   109  	default:
   110  		return cmd.CheckEmpty(args[2:])
   111  	}
   112  	return c.UnitCommandBase.Init(args)
   113  }
   114  
   115  func (c *DeployCommand) Run(ctx *cmd.Context) error {
   116  	client, err := juju.NewAPIClientFromName(c.EnvName)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	defer client.Close()
   121  
   122  	attrs, err := client.EnvironmentGet()
   123  	if params.IsCodeNotImplemented(err) {
   124  		logger.Infof("EnvironmentGet not supported by the API server, " +
   125  			"falling back to 1.16 compatibility mode (direct DB access)")
   126  		return c.run1dot16(ctx)
   127  	}
   128  	if err != nil {
   129  		return err
   130  	}
   131  	conf, err := config.New(config.NoDefaults, attrs)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	curl, err := charm.InferURL(c.CharmName, conf.DefaultSeries())
   136  	if err != nil {
   137  		return err
   138  	}
   139  	repo, err := charm.InferRepository(curl, ctx.AbsPath(c.RepoPath))
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	repo = config.AuthorizeCharmRepo(repo, conf)
   145  
   146  	curl, err = addCharmViaAPI(client, ctx, curl, repo)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	if c.BumpRevision {
   152  		ctx.Stdout.Write([]byte("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.\n"))
   153  	}
   154  
   155  	charmInfo, err := client.CharmInfo(curl.String())
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	numUnits := c.NumUnits
   161  	if charmInfo.Meta.Subordinate {
   162  		if !constraints.IsEmpty(&c.Constraints) {
   163  			return errors.New("cannot use --constraints with subordinate service")
   164  		}
   165  		if numUnits == 1 && c.ToMachineSpec == "" {
   166  			numUnits = 0
   167  		} else {
   168  			return errors.New("cannot use --num-units or --to with subordinate service")
   169  		}
   170  	}
   171  	serviceName := c.ServiceName
   172  	if serviceName == "" {
   173  		serviceName = charmInfo.Meta.Name
   174  	}
   175  
   176  	var configYAML []byte
   177  	if c.Config.Path != "" {
   178  		configYAML, err = c.Config.Read(ctx)
   179  		if err != nil {
   180  			return err
   181  		}
   182  	}
   183  	return client.ServiceDeploy(
   184  		curl.String(),
   185  		serviceName,
   186  		numUnits,
   187  		string(configYAML),
   188  		c.Constraints,
   189  		c.ToMachineSpec,
   190  	)
   191  }
   192  
   193  // run1dot16 implements the deploy command in 1.16 compatibility mode,
   194  // with direct state access. Remove this when support for 1.16 is
   195  // dropped.
   196  func (c *DeployCommand) run1dot16(ctx *cmd.Context) error {
   197  	conn, err := juju.NewConnFromName(c.EnvName)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	defer conn.Close()
   202  	conf, err := conn.State.EnvironConfig()
   203  	if err != nil {
   204  		return err
   205  	}
   206  	curl, err := charm.InferURL(c.CharmName, conf.DefaultSeries())
   207  	if err != nil {
   208  		return err
   209  	}
   210  	repo, err := charm.InferRepository(curl, ctx.AbsPath(c.RepoPath))
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	repo = config.AuthorizeCharmRepo(repo, conf)
   216  
   217  	// TODO(fwereade) it's annoying to roundtrip the bytes through the client
   218  	// here, but it's the original behaviour and not convenient to change.
   219  	// PutCharm will always be required in some form for local charms; and we
   220  	// will need an EnsureStoreCharm method somewhere that gets the state.Charm
   221  	// for use in the following checks.
   222  	ch, err := conn.PutCharm(curl, repo, c.BumpRevision)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	numUnits := c.NumUnits
   227  	if ch.Meta().Subordinate {
   228  		if !constraints.IsEmpty(&c.Constraints) {
   229  			return errors.New("cannot use --constraints with subordinate service")
   230  		}
   231  		if numUnits == 1 && c.ToMachineSpec == "" {
   232  			numUnits = 0
   233  		} else {
   234  			return errors.New("cannot use --num-units or --to with subordinate service")
   235  		}
   236  	}
   237  
   238  	serviceName := c.ServiceName
   239  	if serviceName == "" {
   240  		serviceName = ch.Meta().Name
   241  	}
   242  	var settings charm.Settings
   243  	if c.Config.Path != "" {
   244  		configYAML, err := c.Config.Read(ctx)
   245  		if err != nil {
   246  			return err
   247  		}
   248  		settings, err = ch.Config().ParseSettingsYAML(configYAML, serviceName)
   249  		if err != nil {
   250  			return err
   251  		}
   252  	}
   253  	_, err = juju.DeployService(conn.State,
   254  		juju.DeployServiceParams{
   255  			ServiceName:    serviceName,
   256  			Charm:          ch,
   257  			NumUnits:       numUnits,
   258  			ConfigSettings: settings,
   259  			Constraints:    c.Constraints,
   260  			ToMachineSpec:  c.ToMachineSpec,
   261  		})
   262  	return err
   263  }
   264  
   265  // addCharmViaAPI calls the appropriate client API calls to add the
   266  // given charm URL to state. Also displays the charm URL of the added
   267  // charm on stdout.
   268  func addCharmViaAPI(client *api.Client, ctx *cmd.Context, curl *charm.URL, repo charm.Repository) (*charm.URL, error) {
   269  	if curl.Revision < 0 {
   270  		latest, err := charm.Latest(repo, curl)
   271  		if err != nil {
   272  			return nil, err
   273  		}
   274  		curl = curl.WithRevision(latest)
   275  	}
   276  	switch curl.Schema {
   277  	case "local":
   278  		ch, err := repo.Get(curl)
   279  		if err != nil {
   280  			return nil, err
   281  		}
   282  		stateCurl, err := client.AddLocalCharm(curl, ch)
   283  		if err != nil {
   284  			return nil, err
   285  		}
   286  		curl = stateCurl
   287  	case "cs":
   288  		err := client.AddCharm(curl)
   289  		if err != nil {
   290  			return nil, err
   291  		}
   292  	default:
   293  		return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema)
   294  	}
   295  	report := fmt.Sprintf("Added charm %q to the environment.\n", curl)
   296  	ctx.Stdout.Write([]byte(report))
   297  	return curl, nil
   298  }