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