func (*confSuite) TestValidateMissingDesc(c *gc.C) { conf := common.Conf{ ExecStart: "/path/to/some-command a b c", } err := conf.Validate(renderer) c.Check(err, gc.ErrorMatches, ".*missing Desc.*") }
func (*confSuite) TestValidateMissingExecStart(c *gc.C) { conf := common.Conf{ Desc: "some service", } err := conf.Validate(renderer) c.Check(err, gc.ErrorMatches, ".*missing ExecStart.*") }
func (*confSuite) TestValidateRelativeExecStart(c *gc.C) { conf := common.Conf{ Desc: "some service", ExecStart: "some-command a b c", } err := conf.Validate(renderer) c.Check(err, gc.ErrorMatches, `.*relative path in ExecStart \(.*`) }
func (*confSuite) TestValidateDoubleQuotedExecutable(c *gc.C) { conf := common.Conf{ Desc: "some service", ExecStart: `"/path/to/some-command" a b c`, } err := conf.Validate(renderer) c.Check(err, jc.ErrorIsNil) }
func (*confSuite) TestValidatePartiallyQuotedExecutable(c *gc.C) { conf := common.Conf{ Desc: "some service", ExecStart: "'/path/to/some-command a b c'", } err := conf.Validate(renderer) c.Check(err, gc.ErrorMatches, `.*relative path in ExecStart \(.*`) }
func (*confSuite) TestIsZero(c *gc.C) { conf := common.Conf{ Desc: "some service", ExecStart: "/path/to/some-command a b c", } isZero := conf.IsZero() c.Check(isZero, jc.IsFalse) }
func (*confSuite) TestValidateOkay(c *gc.C) { conf := common.Conf{ Desc: "some service", ExecStart: "/path/to/some-command a b c", } err := conf.Validate(renderer) c.Check(err, jc.ErrorIsNil) }
func (s *Service) setConf(conf common.Conf) error { if conf.IsZero() { s.Service.Conf = conf return nil } normalConf, data := s.normalize(conf) if err := s.validate(normalConf); err != nil { return errors.Trace(err) } s.Script = data s.Service.Conf = normalConf return nil }
// normalize adjusts the conf to more standardized content and // returns a new Conf with that updated content. It also returns the // content of any script file that should accompany the conf. func normalize(name string, conf common.Conf, scriptPath string, renderer confRenderer) (common.Conf, []byte) { var data []byte var cmds []string if conf.Logfile != "" { filename := conf.Logfile cmds = append(cmds, "# Set up logging.") cmds = append(cmds, renderer.Touch(filename, nil)...) // TODO(ericsnow) We should drop the assumption that the logfile // is syslog. user, group := syslogUserGroup() cmds = append(cmds, renderer.Chown(filename, user, group)...) cmds = append(cmds, renderer.Chmod(filename, 0600)...) cmds = append(cmds, renderer.RedirectOutput(filename)...) cmds = append(cmds, renderer.RedirectFD("out", "err")...) cmds = append(cmds, "", "# Run the script.", ) // We leave conf.Logfile alone (it will be ignored during validation). } cmds = append(cmds, conf.ExecStart) if conf.ExtraScript != "" { cmds = append([]string{conf.ExtraScript}, cmds...) conf.ExtraScript = "" } if !isSimpleCommand(strings.Join(cmds, "\n")) { data = renderer.RenderScript(cmds) conf.ExecStart = scriptPath } if len(conf.Env) == 0 { conf.Env = nil } if len(conf.Limit) == 0 { conf.Limit = nil } if conf.Transient { // TODO(ericsnow) Handle Transient via systemd-run command? conf.ExecStopPost = commands{}.disable(name) } return conf, data }
// AgentConf returns the data that defines an init service config // for the identified agent. func AgentConf(info AgentInfo, renderer shell.Renderer) common.Conf { conf := common.Conf{ Desc: fmt.Sprintf("juju agent for %s", info.name), ExecStart: info.cmd(renderer), Logfile: info.logFile(renderer), Env: osenv.FeatureFlags(), Timeout: agentServiceTimeout, ServiceBinary: info.jujud(renderer), ServiceArgs: info.execArgs(renderer), } switch info.Kind { case AgentKindMachine: conf.Limit = map[string]int{ "nofile": maxAgentFiles, } case AgentKindUnit: conf.Desc = "juju unit agent for " + info.ID } return conf }
func validate(name string, conf common.Conf, renderer shell.Renderer) error { if name == "" { return errors.NotValidf("missing service name") } if err := conf.Validate(renderer); err != nil { return errors.Trace(err) } if conf.ExtraScript != "" { return errors.NotValidf("unexpected ExtraScript") } // We ignore Desc and Logfile. for k := range conf.Limit { if _, ok := limitMap[k]; !ok { return errors.NotValidf("conf.Limit key %q", k) } } return nil }
func shutdownInitCommands(initSystem, series string) ([]string, error) { shutdownCmd := "/sbin/shutdown -h now" name := "juju-template-restart" desc := "juju shutdown job" execStart := shutdownCmd conf := common.Conf{ Desc: desc, Transient: true, AfterStopped: "cloud-final", ExecStart: execStart, } // systemd uses targets for synchronization of services if initSystem == service.InitSystemSystemd { conf.AfterStopped = "cloud-config.target" } svc, err := service.NewService(name, conf, series) if err != nil { return nil, errors.Trace(err) } cmds, err := svc.InstallCommands() if err != nil { return nil, errors.Trace(err) } startCommands, err := svc.StartCommands() if err != nil { return nil, errors.Trace(err) } cmds = append(cmds, startCommands...) return cmds, nil }
func shutdownInitCommands(initSystem, series string) ([]string, error) { // These files are removed just before the template shuts down. cleanupOnShutdown := []string{ // We remove any dhclient lease files so there's no chance a // clone to reuse a lease from the template it was cloned // from. "/var/lib/dhcp/dhclient*", // Both of these sets of files below are recreated on boot and // if we leave them in the template's rootfs boot logs coming // from cloned containers will be appended. It's better to // keep clean logs for diagnosing issues / debugging. "/var/log/cloud-init*.log", } // Using EOC below as the template shutdown script is itself // passed through cat > ... < EOF. replaceNetConfCmd := fmt.Sprintf( "/bin/cat > /etc/network/interfaces << EOC%sEOC\n ", defaultEtcNetworkInterfaces, ) paths := strings.Join(cleanupOnShutdown, " ") removeCmd := fmt.Sprintf("/bin/rm -fr %s\n ", paths) shutdownCmd := "/sbin/shutdown -h now" name := "juju-template-restart" desc := "juju shutdown job" execStart := shutdownCmd if environs.AddressAllocationEnabled() { // Only do the cleanup and replacement of /e/n/i when address // allocation feature flag is enabled. execStart = replaceNetConfCmd + removeCmd + shutdownCmd } conf := common.Conf{ Desc: desc, Transient: true, AfterStopped: "cloud-final", ExecStart: execStart, } // systemd uses targets for synchronization of services if initSystem == service.InitSystemSystemd { conf.AfterStopped = "cloud-config.target" } svc, err := service.NewService(name, conf, series) if err != nil { return nil, errors.Trace(err) } cmds, err := svc.InstallCommands() if err != nil { return nil, errors.Trace(err) } startCommands, err := svc.StartCommands() if err != nil { return nil, errors.Trace(err) } cmds = append(cmds, startCommands...) return cmds, nil }
func (*confSuite) TestIsZeroTrue(c *gc.C) { var conf common.Conf isZero := conf.IsZero() c.Check(isZero, jc.IsTrue) }
func deserializeOptions(opts []*unit.UnitOption, renderer shell.Renderer) (common.Conf, error) { var conf common.Conf for _, uo := range opts { switch uo.Section { case "Unit": switch uo.Name { case "Description": conf.Desc = uo.Value case "After": // Do nothing until we support it in common.Conf. default: return conf, errors.NotSupportedf("Unit directive %q", uo.Name) } case "Service": switch { case uo.Name == "ExecStart": conf.ExecStart = uo.Value case uo.Name == "Environment": if conf.Env == nil { conf.Env = make(map[string]string) } var value = uo.Value if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) { value = value[1 : len(value)-1] } parts := strings.SplitN(value, "=", 2) if len(parts) != 2 { return conf, errors.NotValidf("service environment value %q", uo.Value) } conf.Env[parts[0]] = parts[1] case strings.HasPrefix(uo.Name, "Limit"): if conf.Limit == nil { conf.Limit = make(map[string]int) } for k, v := range limitMap { if v == uo.Name { n, err := strconv.Atoi(uo.Value) if err != nil { return conf, errors.Trace(err) } conf.Limit[k] = n break } } case uo.Name == "TimeoutSec": timeout, err := strconv.Atoi(uo.Value) if err != nil { return conf, errors.Trace(err) } conf.Timeout = timeout case uo.Name == "Type": // Do nothing until we support it in common.Conf. case uo.Name == "RemainAfterExit": // Do nothing until we support it in common.Conf. case uo.Name == "Restart": // Do nothing until we support it in common.Conf. default: return conf, errors.NotSupportedf("Service directive %q", uo.Name) } case "Install": switch uo.Name { case "WantedBy": if uo.Value != "multi-user.target" { return conf, errors.NotValidf("unit target %q", uo.Value) } default: return conf, errors.NotSupportedf("Install directive %q", uo.Name) } default: return conf, errors.NotSupportedf("section %q", uo.Name) } } err := validate("<>", conf, renderer) return conf, errors.Trace(err) }
func NewService(name string, conf common.Conf) *Service { if conf.InitDir == "" { conf.InitDir = InitDir } return &Service{Name: name, Conf: conf} }