func (c *MigrateCommand) Init(args []string) error { if len(args) == 0 { return errors.New("missing operation") } c.operation, args = args[0], args[1:] switch c.operation { case "export": if len(args) == 0 { return errors.New("missing model uuid") } c.modelUUID, args = args[0], args[1:] case "import": if len(args) == 0 { return errors.New("missing yaml filename") } c.filename, args = args[0], args[1:] default: return errors.Errorf("unknown operation %q", c.operation) } if !names.IsValidMachine(c.machineId) { return errors.Errorf("%q is not a valid machine id", c.machineId) } c.machineTag = names.NewMachineTag(c.machineId) return cmd.CheckEmpty(args) }
func (s *machineSuite) TestMachineIdFormats(c *gc.C) { for i, test := range machineIdTests { c.Logf("test %d: %q", i, test.pattern) c.Assert(names.IsValidMachine(test.pattern), gc.Equals, test.valid) c.Assert(names.IsContainerMachine(test.pattern), gc.Equals, test.container) } }
// Init is called by the cmd system to initialize the structure for // running. func (a *machineAgentCmd) Init(args []string) error { if !names.IsValidMachine(a.machineId) { return fmt.Errorf("--machine-id option must be set, and expects a non-negative integer") } if err := a.agentInitializer.CheckArgs(args); err != nil { return err } // Due to changes in the logging, and needing to care about old // models that have been upgraded, we need to explicitly remove the // file writer if one has been added, otherwise we will get duplicate // lines of all logging in the log file. loggo.RemoveWriter("logfile") if a.logToStdErr { return nil } err := a.currentConfig.ReadConfig(names.NewMachineTag(a.machineId).String()) if err != nil { return errors.Annotate(err, "cannot read agent configuration") } // the context's stderr is set as the loggo writer in github.com/juju/cmd/logging.go a.ctx.Stderr = &lumberjack.Logger{ Filename: agent.LogFilename(a.currentConfig.CurrentConfig()), MaxSize: 300, // megabytes MaxBackups: 2, } return nil }
// AddUnits starts n units of the given service and allocates machines // to them as necessary. func AddUnits(st *state.State, svc *state.Service, n int, machineIdSpec string) ([]*state.Unit, error) { if machineIdSpec != "" && n != 1 { return nil, errors.Errorf("cannot add multiple units of service %q to a single machine", svc.Name()) } var placement []*instance.Placement if machineIdSpec != "" { mid := machineIdSpec scope := instance.MachineScope var containerType instance.ContainerType specParts := strings.SplitN(machineIdSpec, ":", 2) if len(specParts) > 1 { firstPart := specParts[0] var err error if containerType, err = instance.ParseContainerType(firstPart); err == nil { mid = specParts[1] scope = string(containerType) } } if !names.IsValidMachine(mid) { return nil, fmt.Errorf("invalid force machine id %q", mid) } placement = []*instance.Placement{ { Scope: scope, Directive: mid, }, } } return AddUnitsWithPlacement(st, svc, n, placement) }
// PrivateAddress implements the server side of Client.PrivateAddress. func (c *Client) PrivateAddress(p params.PrivateAddress) (results params.PrivateAddressResults, err error) { switch { case names.IsValidMachine(p.Target): machine, err := c.api.stateAccessor.Machine(p.Target) if err != nil { return results, err } addr, err := machine.PrivateAddress() if err != nil { return results, errors.Annotatef(err, "error fetching address for machine %q", machine) } return params.PrivateAddressResults{PrivateAddress: addr.Value}, nil case names.IsValidUnit(p.Target): unit, err := c.api.stateAccessor.Unit(p.Target) if err != nil { return results, err } addr, err := unit.PrivateAddress() if err != nil { return results, errors.Annotatef(err, "error fetching address for unit %q", unit) } return params.PrivateAddressResults{PrivateAddress: addr.Value}, nil } return results, fmt.Errorf("unknown unit or machine %q", p.Target) }
func (c *SSHCommon) hostFromTarget(target string) (string, error) { // If the target is neither a machine nor a unit, // assume it's a hostname and try it directly. if !names.IsValidMachine(target) && !names.IsValidUnit(target) { return target, nil } // A target may not initially have an address (e.g. the // address updater hasn't yet run), so we must do this in // a loop. if _, err := c.ensureAPIClient(); err != nil { return "", err } var err error for a := sshHostFromTargetAttemptStrategy.Start(); a.Next(); { var addr string if c.proxy { addr, err = c.apiClient.PrivateAddress(target) } else { addr, err = c.apiClient.PublicAddress(target) } if err == nil { return addr, nil } } return "", err }
// PrivateAddress implements the server side of Client.PrivateAddress. func (c *Client) PrivateAddress(p params.PrivateAddress) (results params.PrivateAddressResults, err error) { switch { case names.IsValidMachine(p.Target): machine, err := c.api.state.Machine(p.Target) if err != nil { return results, err } addr := network.SelectInternalAddress(machine.Addresses(), false) if addr == "" { return results, fmt.Errorf("machine %q has no internal address", machine) } return params.PrivateAddressResults{PrivateAddress: addr}, nil case names.IsValidUnit(p.Target): unit, err := c.api.state.Unit(p.Target) if err != nil { return results, err } addr, ok := unit.PrivateAddress() if !ok { return results, fmt.Errorf("unit %q has no internal address", unit) } return params.PrivateAddressResults{PrivateAddress: addr}, nil } return results, fmt.Errorf("unknown unit or machine %q", p.Target) }
// addServiceUnits adds a given number of units to a service. func addServiceUnits(state *state.State, args params.AddServiceUnits) ([]*state.Unit, error) { service, err := state.Service(args.ServiceName) if err != nil { return nil, err } if args.NumUnits < 1 { return nil, fmt.Errorf("must add at least one unit") } // New API uses placement directives. if len(args.Placement) > 0 { return jjj.AddUnitsWithPlacement(state, service, args.NumUnits, args.Placement) } // Otherwise we use the older machine spec. if args.NumUnits > 1 && args.ToMachineSpec != "" { return nil, fmt.Errorf("cannot use NumUnits with ToMachineSpec") } if args.ToMachineSpec != "" && names.IsValidMachine(args.ToMachineSpec) { _, err = state.Machine(args.ToMachineSpec) if err != nil { return nil, errors.Annotatef(err, `cannot add units for service "%v" to machine %v`, args.ServiceName, args.ToMachineSpec) } } return jjj.AddUnits(state, service, args.NumUnits, args.ToMachineSpec) }
// makePlacement makes a placement directive for the given machineIdSpec. func makePlacement(machineIdSpec string) ([]*instance.Placement, error) { if machineIdSpec == "" { return nil, nil } mid := machineIdSpec scope := instance.MachineScope var containerType instance.ContainerType specParts := strings.SplitN(machineIdSpec, ":", 2) if len(specParts) > 1 { firstPart := specParts[0] var err error if containerType, err = instance.ParseContainerType(firstPart); err == nil { mid = specParts[1] scope = string(containerType) } } if !names.IsValidMachine(mid) { return nil, errors.Errorf("invalid force machine id %q", mid) } return []*instance.Placement{ { Scope: scope, Directive: mid, }, }, nil }
// ParseFilesystemAttachmentId parses a string as a filesystem attachment ID, // returning the machine and filesystem components. func ParseFilesystemAttachmentId(id string) (names.MachineTag, names.FilesystemTag, error) { fields := strings.SplitN(id, ":", 2) if len(fields) != 2 || !names.IsValidMachine(fields[0]) || !names.IsValidFilesystem(fields[1]) { return names.MachineTag{}, names.FilesystemTag{}, errors.Errorf("invalid filesystem attachment ID %q", id) } machineTag := names.NewMachineTag(fields[0]) filesystemTag := names.NewFilesystemTag(fields[1]) return machineTag, filesystemTag, nil }
// ParseVolumeAttachmentId parses a string as a volume attachment ID, // returning the machine and volume components. func ParseVolumeAttachmentId(id string) (names.MachineTag, names.VolumeTag, error) { fields := strings.SplitN(id, ":", 2) if len(fields) != 2 || !names.IsValidMachine(fields[0]) || !names.IsValidVolume(fields[1]) { return names.MachineTag{}, names.VolumeTag{}, errors.Errorf("invalid volume attachment ID %q", id) } machineTag := names.NewMachineTag(fields[0]) volumeTag := names.NewVolumeTag(fields[1]) return machineTag, volumeTag, nil }
func targetToTag(target string) (names.Tag, error) { switch { case names.IsValidMachine(target): return names.NewMachineTag(target), nil case names.IsValidUnit(target): return names.NewUnitTag(target), nil default: return nil, errors.NotValidf("target %q", target) } }
func (c *removeCommand) Init(args []string) error { if len(args) == 0 { return fmt.Errorf("no machines specified") } for _, id := range args { if !names.IsValidMachine(id) { return fmt.Errorf("invalid machine id %q", id) } } c.MachineIds = args return nil }
// Init initializes the command for running. func (a *MachineAgent) Init(args []string) error { if !names.IsValidMachine(a.MachineId) { return fmt.Errorf("--machine-id option must be set, and expects a non-negative integer") } if err := a.AgentConf.CheckArgs(args); err != nil { return err } a.runner = newRunner(isFatal, moreImportant) a.workersStarted = make(chan struct{}) a.upgradeWorkerContext = NewUpgradeWorkerContext() return nil }
func (c *FixitCommand) Init(args []string) error { if len(args) == 0 { return errors.New("missing machine-id") } var machineId string machineId, args = args[0], args[1:] if !names.IsValidMachine(machineId) { return errors.Errorf("%q is not a valid machine id", machineId) } c.machineTag = names.NewMachineTag(machineId) return cmd.CheckEmpty(args) }
func (c *RetryProvisioningCommand) Init(args []string) error { if len(args) == 0 { return fmt.Errorf("no machine specified") } c.Machines = make([]names.MachineTag, len(args)) for i, arg := range args { if !names.IsValidMachine(arg) { return fmt.Errorf("invalid machine %q", arg) } c.Machines[i] = names.NewMachineTag(arg) } return nil }
// ParsePlacement attempts to parse the specified string and create a // corresponding Placement structure. // // If the placement directive is non-empty and missing a scope, // ErrPlacementScopeMissing will be returned as well as a Placement // with an empty Scope field. func ParsePlacement(directive string) (*Placement, error) { if directive == "" { return nil, nil } if colon := strings.IndexRune(directive, ':'); colon != -1 { scope, directive := directive[:colon], directive[colon+1:] if scope == "" { return nil, ErrPlacementScopeMissing } // Sanity check: machine/container scopes require a machine ID as the value. if (scope == MachineScope || isContainerType(scope)) && !names.IsValidMachine(directive) { return nil, fmt.Errorf("invalid value %q for %q scope: expected machine-id", directive, scope) } return &Placement{Scope: scope, Directive: directive}, nil } if names.IsValidMachine(directive) { return &Placement{Scope: MachineScope, Directive: directive}, nil } if isContainerType(directive) { return &Placement{Scope: directive}, nil } return nil, ErrPlacementScopeMissing }
func matchMachineId(m *state.Machine, patterns []string) (bool, bool, error) { var anyValid bool for _, p := range patterns { if !names.IsValidMachine(p) { continue } anyValid = true if m.Id() == p || strings.HasPrefix(m.Id(), p+"/") { // Pattern matches the machine, or container's // host machine. return true, true, nil } } return false, anyValid, nil }
// NewAPI returns a new API client for the Singular facade. It exposes methods // for claiming and observing administration responsibility for the apiCaller's // model, on behalf of the supplied controller machine. func NewAPI(apiCaller base.APICaller, controllerTag names.MachineTag) (*API, error) { controllerId := controllerTag.Id() if !names.IsValidMachine(controllerId) { return nil, errors.NotValidf("controller tag") } modelTag, err := apiCaller.ModelTag() if err != nil { return nil, errors.Trace(err) } facadeCaller := base.NewFacadeCaller(apiCaller, "Singular") return &API{ modelTag: modelTag, controllerTag: controllerTag, facadeCaller: facadeCaller, }, nil }
func (c *retryProvisioningCommand) Init(args []string) error { if len(args) == 0 { return errors.Errorf("no machine specified") } c.Machines = make([]names.MachineTag, len(args)) for i, arg := range args { if !names.IsValidMachine(arg) { return errors.Errorf("invalid machine %q", arg) } if names.IsContainerMachine(arg) { return errors.Errorf("invalid machine %q retry-provisioning does not support containers", arg) } c.Machines[i] = names.NewMachineTag(arg) } return nil }
func (c *RunCommand) Init(args []string) error { if len(args) == 0 { return fmt.Errorf("no commands specified") } c.commands, args = args[0], args[1:] if c.all { if len(c.machines) != 0 { return fmt.Errorf("You cannot specify --all and individual machines") } if len(c.services) != 0 { return fmt.Errorf("You cannot specify --all and individual services") } if len(c.units) != 0 { return fmt.Errorf("You cannot specify --all and individual units") } } else { if len(c.machines) == 0 && len(c.services) == 0 && len(c.units) == 0 { return fmt.Errorf("You must specify a target, either through --all, --machine, --service or --unit") } } var nameErrors []string for _, machineId := range c.machines { if !names.IsValidMachine(machineId) { nameErrors = append(nameErrors, fmt.Sprintf(" %q is not a valid machine id", machineId)) } } for _, service := range c.services { if !names.IsValidService(service) { nameErrors = append(nameErrors, fmt.Sprintf(" %q is not a valid service name", service)) } } for _, unit := range c.units { if !names.IsValidUnit(unit) { nameErrors = append(nameErrors, fmt.Sprintf(" %q is not a valid unit name", unit)) } } if len(nameErrors) > 0 { return fmt.Errorf("The following run targets are not valid:\n%s", strings.Join(nameErrors, "\n")) } return cmd.CheckEmpty(args) }
// addServiceUnits adds a given number of units to a service. func addServiceUnits(state *state.State, args params.AddServiceUnits) ([]*state.Unit, error) { service, err := state.Service(args.ServiceName) if err != nil { return nil, err } if args.NumUnits < 1 { return nil, fmt.Errorf("must add at least one unit") } if args.NumUnits > 1 && args.ToMachineSpec != "" { return nil, fmt.Errorf("cannot use NumUnits with ToMachineSpec") } if args.ToMachineSpec != "" && names.IsValidMachine(args.ToMachineSpec) { _, err = state.Machine(args.ToMachineSpec) if err != nil { return nil, errors.Annotatef(err, `cannot add units for service "%v" to machine %v`, args.ServiceName, args.ToMachineSpec) } } return jjj.AddUnits(state, service, args.NumUnits, args.ToMachineSpec) }
// Run the commands specified on the machines identified through the // list of machines, units and services. func (a *ActionAPI) Run(run params.RunParams) (results params.ActionResults, err error) { if err := a.check.ChangeAllowed(); err != nil { return results, errors.Trace(err) } units, err := getAllUnitNames(a.state, run.Units, run.Services) if err != nil { return results, errors.Trace(err) } machines := make([]names.Tag, len(run.Machines)) for i, machineId := range run.Machines { if !names.IsValidMachine(machineId) { return results, errors.Errorf("invalid machine id %q", machineId) } machines[i] = names.NewMachineTag(machineId) } actionParams := a.createActionsParams(append(units, machines...), run.Commands, run.Timeout) return queueActions(a, actionParams) }
// Init implements cmd.Command. func (c *dumpLogsCommand) Init(args []string) error { err := c.agentConfig.CheckArgs(args) if err != nil { return errors.Trace(err) } if c.machineId == "" { machineId, err := c.findMachineId(c.agentConfig.DataDir()) if err != nil { return errors.Trace(err) } c.machineId = machineId } else if !names.IsValidMachine(c.machineId) { return errors.New("--machine-id option expects a non-negative integer") } err = c.agentConfig.ReadConfig(names.NewMachineTag(c.machineId).String()) if err != nil { return errors.Trace(err) } return nil }
func (cfg *InstanceConfig) VerifyConfig() (err error) { defer errors.DeferredAnnotatef(&err, "invalid machine configuration") if !names.IsValidMachine(cfg.MachineId) { return errors.New("invalid machine id") } if cfg.DataDir == "" { return errors.New("missing var directory") } if cfg.LogDir == "" { return errors.New("missing log directory") } if cfg.MetricsSpoolDir == "" { return errors.New("missing metrics spool directory") } if len(cfg.Jobs) == 0 { return errors.New("missing machine jobs") } if cfg.CloudInitOutputLog == "" { return errors.New("missing cloud-init output log path") } if cfg.tools == nil { // SetTools() has never been called successfully. return errors.New("missing tools") } // We don't need to check cfg.toolsURLs since SetTools() does. if cfg.MongoInfo == nil { return errors.New("missing state info") } if len(cfg.MongoInfo.CACert) == 0 { return errors.New("missing CA certificate") } if cfg.APIInfo == nil { return errors.New("missing API info") } if cfg.APIInfo.ModelTag.Id() == "" { return errors.New("missing model tag") } if len(cfg.APIInfo.CACert) == 0 { return errors.New("missing API CA certificate") } if cfg.MachineAgentServiceName == "" { return errors.New("missing machine agent service name") } if cfg.Bootstrap { if cfg.Config == nil { return errors.New("missing model configuration") } if cfg.MongoInfo.Tag != nil { return errors.New("entity tag must be nil when starting a controller") } if cfg.APIInfo.Tag != nil { return errors.New("entity tag must be nil when starting a controller") } if cfg.StateServingInfo == nil { return errors.New("missing state serving info") } if len(cfg.StateServingInfo.Cert) == 0 { return errors.New("missing controller certificate") } if len(cfg.StateServingInfo.PrivateKey) == 0 { return errors.New("missing controller private key") } if len(cfg.StateServingInfo.CAPrivateKey) == 0 { return errors.New("missing ca cert private key") } if cfg.StateServingInfo.StatePort == 0 { return errors.New("missing state port") } if cfg.StateServingInfo.APIPort == 0 { return errors.New("missing API port") } if cfg.InstanceId == "" { return errors.New("missing instance-id") } if len(cfg.HostedModelConfig) == 0 { return errors.New("missing hosted model config") } } else { if len(cfg.MongoInfo.Addrs) == 0 { return errors.New("missing state hosts") } if cfg.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) { return errors.New("entity tag must match started machine") } if len(cfg.APIInfo.Addrs) == 0 { return errors.New("missing API hosts") } if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) { return errors.New("entity tag must match started machine") } if cfg.StateServingInfo != nil { return errors.New("state serving info unexpectedly present") } if len(cfg.HostedModelConfig) != 0 { return errors.New("hosted model config unexpectedly present") } } if cfg.MachineNonce == "" { return errors.New("missing machine nonce") } return nil }
// DeployService fetches the charm from the charm store and deploys it. // The logic has been factored out into a common function which is called by // both the legacy API on the client facade, as well as the new service facade. func DeployService(st *state.State, owner string, args params.ServiceDeploy) error { curl, err := charm.ParseURL(args.CharmUrl) if err != nil { return errors.Trace(err) } if curl.Revision < 0 { return errors.Errorf("charm url must include revision") } if args.ToMachineSpec != "" && names.IsValidMachine(args.ToMachineSpec) { _, err = st.Machine(args.ToMachineSpec) if err != nil { return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ServiceName, args.ToMachineSpec) } } // Try to find the charm URL in state first. ch, err := st.Charm(curl) if errors.IsNotFound(err) { // Clients written to expect 1.16 compatibility require this next block. if curl.Schema != "cs" { return errors.Errorf(`charm url has unsupported schema %q`, curl.Schema) } if err = AddCharmWithAuthorization(st, params.AddCharmWithAuthorization{ URL: args.CharmUrl, }); err == nil { ch, err = st.Charm(curl) } } if err != nil { return errors.Trace(err) } var settings charm.Settings if len(args.ConfigYAML) > 0 { settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) } else if len(args.Config) > 0 { // Parse config in a compatible way (see function comment). settings, err = parseSettingsCompatible(ch, args.Config) } if err != nil { return errors.Trace(err) } // Convert network tags to names for any given networks. requestedNetworks, err := networkTagsToNames(args.Networks) if err != nil { return errors.Trace(err) } _, err = jjj.DeployService(st, jjj.DeployServiceParams{ ServiceName: args.ServiceName, // TODO(dfc) ServiceOwner should be a tag ServiceOwner: owner, Charm: ch, NumUnits: args.NumUnits, ConfigSettings: settings, Constraints: args.Constraints, ToMachineSpec: args.ToMachineSpec, Networks: requestedNetworks, Storage: args.Storage, }) return err }
// AddUnits starts n units of the given service and allocates machines // to them as necessary. func AddUnits(st *state.State, svc *state.Service, n int, machineIdSpec string) ([]*state.Unit, error) { units := make([]*state.Unit, n) // Hard code for now till we implement a different approach. policy := state.AssignCleanEmpty // All units should have the same networks as the service. networks, err := svc.Networks() if err != nil { return nil, fmt.Errorf("cannot get service %q networks: %v", svc.Name(), err) } // TODO what do we do if we fail half-way through this process? for i := 0; i < n; i++ { unit, err := svc.AddUnit() if err != nil { return nil, fmt.Errorf("cannot add unit %d/%d to service %q: %v", i+1, n, svc.Name(), err) } if machineIdSpec != "" { if n != 1 { return nil, fmt.Errorf("cannot add multiple units of service %q to a single machine", svc.Name()) } // machineIdSpec may be an existing machine or container, eg 3/lxc/2 // or a new container on a machine, eg lxc:3 mid := machineIdSpec var containerType instance.ContainerType specParts := strings.SplitN(machineIdSpec, ":", 2) if len(specParts) > 1 { firstPart := specParts[0] var err error if containerType, err = instance.ParseContainerType(firstPart); err == nil { mid = specParts[1] } else { mid = machineIdSpec } } if !names.IsValidMachine(mid) { return nil, fmt.Errorf("invalid force machine id %q", mid) } var unitCons *constraints.Value unitCons, err = unit.Constraints() if err != nil { return nil, err } var err error var m *state.Machine // If a container is to be used, create it. if containerType != "" { // Create the new machine marked as dirty so that // nothing else will grab it before we assign the unit to it. template := state.MachineTemplate{ Series: unit.Series(), Jobs: []state.MachineJob{state.JobHostUnits}, Dirty: true, Constraints: *unitCons, RequestedNetworks: networks, } m, err = st.AddMachineInsideMachine(template, mid, containerType) } else { m, err = st.Machine(mid) } if err != nil { return nil, fmt.Errorf("cannot assign unit %q to machine: %v", unit.Name(), err) } err = unit.AssignToMachine(m) if err != nil { return nil, err } } else if err := st.AssignUnit(unit, policy); err != nil { return nil, err } units[i] = unit } return units, nil }
func (cfg *InstanceConfig) VerifyConfig() (err error) { defer errors.DeferredAnnotatef(&err, "invalid machine configuration") if !names.IsValidMachine(cfg.MachineId) { return errors.New("invalid machine id") } if cfg.DataDir == "" { return errors.New("missing var directory") } if cfg.LogDir == "" { return errors.New("missing log directory") } if len(cfg.Jobs) == 0 { return errors.New("missing machine jobs") } if cfg.CloudInitOutputLog == "" { return errors.New("missing cloud-init output log path") } if cfg.Tools == nil { return errors.New("missing tools") } if cfg.Tools.URL == "" { return errors.New("missing tools URL") } if cfg.MongoInfo == nil { return errors.New("missing state info") } if len(cfg.MongoInfo.CACert) == 0 { return errors.New("missing CA certificate") } if cfg.APIInfo == nil { return errors.New("missing API info") } if cfg.APIInfo.EnvironTag.Id() == "" { return errors.New("missing environment tag") } if len(cfg.APIInfo.CACert) == 0 { return errors.New("missing API CA certificate") } if cfg.MachineAgentServiceName == "" { return errors.New("missing machine agent service name") } if cfg.Bootstrap { if cfg.Config == nil { return errors.New("missing environment configuration") } if cfg.MongoInfo.Tag != nil { return errors.New("entity tag must be nil when starting a state server") } if cfg.APIInfo.Tag != nil { return errors.New("entity tag must be nil when starting a state server") } if cfg.StateServingInfo == nil { return errors.New("missing state serving info") } if len(cfg.StateServingInfo.Cert) == 0 { return errors.New("missing state server certificate") } if len(cfg.StateServingInfo.PrivateKey) == 0 { return errors.New("missing state server private key") } if len(cfg.StateServingInfo.CAPrivateKey) == 0 { return errors.New("missing ca cert private key") } if cfg.StateServingInfo.StatePort == 0 { return errors.New("missing state port") } if cfg.StateServingInfo.APIPort == 0 { return errors.New("missing API port") } if cfg.InstanceId == "" { return errors.New("missing instance-id") } } else { if len(cfg.MongoInfo.Addrs) == 0 { return errors.New("missing state hosts") } if cfg.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) { return errors.New("entity tag must match started machine") } if len(cfg.APIInfo.Addrs) == 0 { return errors.New("missing API hosts") } if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) { return errors.New("entity tag must match started machine") } if cfg.StateServingInfo != nil { return errors.New("state serving info unexpectedly present") } } if cfg.MachineNonce == "" { return errors.New("missing machine nonce") } return nil }
// ServiceDeploy fetches the charm from the charm store and deploys it. // AddCharm or AddLocalCharm should be called to add the charm // before calling ServiceDeploy, although for backward compatibility // this is not necessary until 1.16 support is removed. func (c *Client) ServiceDeploy(args params.ServiceDeploy) error { curl, err := charm.ParseURL(args.CharmUrl) if err != nil { return err } if curl.Revision < 0 { return fmt.Errorf("charm url must include revision") } if args.ToMachineSpec != "" && names.IsValidMachine(args.ToMachineSpec) { _, err = c.api.state.Machine(args.ToMachineSpec) if err != nil { return fmt.Errorf(`cannot deploy "%v" to machine %v: %v`, args.ServiceName, args.ToMachineSpec, err) } } // Try to find the charm URL in state first. ch, err := c.api.state.Charm(curl) if errors.IsNotFound(err) { // Remove this whole if block when 1.16 compatibility is dropped. if curl.Schema != "cs" { return fmt.Errorf(`charm url has unsupported schema %q`, curl.Schema) } err = c.AddCharm(params.CharmURL{args.CharmUrl}) if err != nil { return err } ch, err = c.api.state.Charm(curl) if err != nil { return err } } else if err != nil { return err } var settings charm.Settings if len(args.ConfigYAML) > 0 { settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) } else if len(args.Config) > 0 { // Parse config in a compatile way (see function comment). settings, err = parseSettingsCompatible(ch, args.Config) } if err != nil { return err } // Convert network tags to names for any given networks. requestedNetworks, err := networkTagsToNames(args.Networks) if err != nil { return err } _, err = juju.DeployService(c.api.state, juju.DeployServiceParams{ ServiceName: args.ServiceName, // TODO(dfc) ServiceOwner should be a tag ServiceOwner: c.api.auth.GetAuthTag().String(), Charm: ch, NumUnits: args.NumUnits, ConfigSettings: settings, Constraints: args.Constraints, ToMachineSpec: args.ToMachineSpec, Networks: requestedNetworks, }) return err }