diff --git a/cmd/juju/application/bundle.go b/cmd/juju/application/bundle.go index 343f0a5..b4f2328 100644 --- a/cmd/juju/application/bundle.go +++ b/cmd/juju/application/bundle.go @@ -453,6 +453,20 @@ h.log.Infof("avoid creating other machines to host %s units", msg) return nil } + // Check if machine mapping was provided for services + for _, application := range services { + for _, toMach := range h.data.Applications[application].To { + if targetMach, ok := machinesMap[toMach]; ok { + if ! machinesProvided[targetMach] { + h.results[id] = targetMach + machinesProvided[targetMach] = true + msg = application + h.log.Infof("Assigned existing (pre-created) machine: %s to host: %s units", targetMach, msg) + return nil + } + } + } + } cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. diff --git a/cmd/juju/application/deploy.go b/cmd/juju/application/deploy.go index 5c4889b..7ef423b 100644 --- a/cmd/juju/application/deploy.go +++ b/cmd/juju/application/deploy.go @@ -39,6 +39,10 @@ var planURL = "https://api.jujucharms.com/omnibus/v2" +// machinesMap stores map from logical machines to existing machines +var machinesMap map[string]string +var machinesProvided map[string]bool + type CharmAdder interface { AddLocalCharm(*charm.URL, charm.Charm) (*charm.URL, error) AddCharm(*charm.URL, params.Channel) error @@ -255,6 +259,7 @@ ConstraintsStr string Constraints constraints.Value BindToSpaces string + MachinesMapStr string // TODO(axw) move this to UnitCommandBase once we support --storage // on add-unit too. @@ -417,7 +422,7 @@ // charmOnlyFlags and bundleOnlyFlags are used to validate flags based on // whether we are deploying a charm or a bundle. charmOnlyFlags = []string{"bind", "config", "constraints", "force", "n", "num-units", "series", "to", "resource"} - bundleOnlyFlags = []string{} + bundleOnlyFlags = []string{"machines"} modelCommandBaseFlags = []string{"B", "no-browser-login"} ) @@ -435,6 +440,7 @@ f.Var(storageFlag{&c.Storage, &c.BundleStorage}, "storage", "Charm storage constraints") f.Var(stringMap{&c.Resources}, "resource", "Resource to be uploaded to the controller") f.StringVar(&c.BindToSpaces, "bind", "", "Configure application endpoint bindings to spaces") + f.StringVar(&c.MachinesMapStr, "machines", "", "Map logical machines to existing machines (pre-created)") for _, step := range c.Steps { step.SetFlags(f) @@ -462,6 +468,9 @@ } if err := c.parseBind(); err != nil { + return err + } + if err := c.parseMachines(); err != nil { return err } return c.UnitCommandBase.Init(args) @@ -649,6 +658,48 @@ return nil } +const parseMachinesErrorPrefix = "--machines must be in the form '= ...'. " + +// parseMachines parses the --machines option. Valid forms are: +// e.g. "dbserver=0 webserver=1" +func (c *DeployCommand) parseMachines() error { + mapping := make(map[string]string) + if c.MachinesMapStr == "" { + return nil + } + + for _, s := range strings.Split(c.MachinesMapStr, " ") { + s = strings.TrimSpace(s) + if s == "" { + continue + } + + v := strings.Split(s, "=") + var lmachine, tmachine string + switch len(v) { + case 1: + return errors.New(parseMachinesErrorPrefix) + case 2: + if v[0] == "" { + return errors.New(parseMachinesErrorPrefix + "Found = without target machine name.") + } + lmachine = v[0] + tmachine = v[1] + default: + return errors.New(parseMachinesErrorPrefix + "Found multiple = in binding. Did you forget to space-separate the binding list?") + } + + //TODO: add checking if target machine exists - perhaps isValidMachine + //if !isValidMachine(machine) { + // return errors.New(parseMachinesErrorPrefix + "Target machine name invalid.") + //} + mapping[lmachine] = tmachine + } + machinesMap = mapping + machinesProvided = make(map[string]bool) + return nil +} + func (c *DeployCommand) Run(ctx *cmd.Context) error { var err error c.Constraints, err = common.ParseConstraints(ctx, c.ConstraintsStr)