service-tools/service-generator/createcmd.go

283 lines
9.1 KiB
Go

package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/coreos/go-systemd/unit"
"github.com/rivo/tview"
"github.com/spf13/cobra"
)
type CreateOptions struct {
Type string
Description string
Exec string
ExecStartPre string
ExecStartPost string
ExecReload string
ExecStop string
ExecStopPost string
WorkingDirectory string
RootDirectory string
User string
Group string
Restart string
RestartSec string
TimeoutStartSec string
TimeoutStopSec string
After string
WantedBy string
}
var (
createOpts = CreateOptions{}
types = Strings{"simple", "forking", "oneshot", "dbus", "notify", "idle"}
restarts = Strings{"no", "always", "on-success", "on-failure", "on-abnormal", "on-abort", "on-watchdog"}
createCmd = &cobra.Command{
Use: "create <executable> <description> [after] [wanted-by]",
Short: "creates a new Unit file",
Long: `The create command creates a new systemd Unit file`,
RunE: func(cmd *cobra.Command, args []string) error {
ts, err := targets()
if err != nil {
return fmt.Errorf("Can't find systemd targets: %s", err)
}
createOpts.Type = strings.ToLower(createOpts.Type)
if !types.Contains(createOpts.Type) {
return fmt.Errorf("No such service type: %s", createOpts.Type)
}
createOpts.Restart = strings.ToLower(createOpts.Restart)
if !restarts.Contains(createOpts.Restart) {
return fmt.Errorf("No such restart type: %s", createOpts.Restart)
}
switch len(args) {
case 4:
createOpts.WantedBy = args[3]
fallthrough
case 3:
createOpts.After = args[2]
fallthrough
case 2:
createOpts.Description = args[1]
fallthrough
case 1:
createOpts.Exec = args[0]
}
if len(args) >= 2 {
if err := validate(); err != nil {
return err
}
return executeCreate()
}
app := tview.NewApplication()
form := tview.NewForm()
descriptionField := tview.NewInputField().
SetLabel("Description:").
SetText(createOpts.Description).
SetFieldWidth(40).
SetAcceptanceFunc(nil).
SetChangedFunc(func(s string) {
createOpts.Description = s
})
form.
AddDropDown("Type:", types, types.IndexOf(createOpts.Type), func(s string, i int) {
createOpts.Type = s
}).
AddInputField("Exec on start:", createOpts.Exec, 40, nil, func(s string) {
createOpts.Exec = s
descriptionField.SetText(fmt.Sprintf("%s service", filepath.Base(createOpts.Exec)))
}).
AddFormItem(descriptionField).
AddInputField("Exec on stop:", createOpts.ExecStop, 40, nil, func(s string) {
createOpts.ExecStop = s
}).
AddInputField("Exec on reload:", createOpts.ExecReload, 40, nil, func(s string) {
createOpts.ExecReload = s
}).
AddDropDown("Restarts on:", restarts, restarts.IndexOf(createOpts.Restart), func(s string, i int) {
createOpts.Restart = s
}).
AddDropDown("Start after target:", ts.Strings(), Strings(ts.Strings()).IndexOf(createOpts.After), func(s string, i int) {
createOpts.After = s
}).
AddDropDown("Wanted by target:", ts.Strings(), Strings(ts.Strings()).IndexOf(createOpts.WantedBy), func(s string, i int) {
createOpts.WantedBy = s
})
var apperr error
form.
AddButton("Create", func() {
app.Stop()
if err := validate(); err != nil {
apperr = err
} else {
executeCreate()
}
}).
AddButton("Cancel", func() {
app.Stop()
})
form.SetBorder(true).SetTitle("Create new service").SetTitleAlign(tview.AlignCenter)
if err := app.SetRoot(form, true).Run(); err != nil {
return err
}
return apperr
},
}
)
func validateExecutables(executable string, allowEmpty bool) (string, error) {
executable = strings.TrimSpace(executable)
if len(executable) == 0 {
if allowEmpty {
return executable, nil
}
return executable, fmt.Errorf("Need an executable to create a service for")
}
stat, err := os.Stat(executable)
if os.IsNotExist(err) {
return executable, fmt.Errorf("Could not find executable: %s is not a file", executable)
}
if stat.IsDir() {
return executable, fmt.Errorf("Could not find executable: %s is a directory", executable)
}
if stat.Mode()&0111 == 0 {
return executable, fmt.Errorf("%s is not executable", executable)
}
return executable, nil
}
func validate() error {
ts, err := targets()
if err != nil {
return fmt.Errorf("Can't find systemd targets: %s", err)
}
// Executable checks
createOpts.Exec, err = validateExecutables(createOpts.Exec, false)
if err != nil {
return err
}
createOpts.ExecReload, err = validateExecutables(createOpts.ExecReload, true)
if err != nil {
return err
}
createOpts.ExecStartPre, err = validateExecutables(createOpts.ExecStartPre, true)
if err != nil {
return err
}
createOpts.ExecStartPost, err = validateExecutables(createOpts.ExecStartPost, true)
if err != nil {
return err
}
createOpts.ExecStop, err = validateExecutables(createOpts.ExecStop, true)
if err != nil {
return err
}
createOpts.ExecStopPost, err = validateExecutables(createOpts.ExecStopPost, true)
if err != nil {
return err
}
// Description check
if len(createOpts.Description) == 0 {
return fmt.Errorf("Description for this service can't be empty")
}
// Target checks
if len(createOpts.After) > 0 && !ts.Contains(createOpts.After) {
return fmt.Errorf("Could not create service: no such target")
}
if len(createOpts.WantedBy) > 0 && !ts.Contains(createOpts.WantedBy) {
return fmt.Errorf("Could not create service: no such target")
}
return nil
}
func executeCreate() error {
u := []*unit.UnitOption{
&unit.UnitOption{"Unit", "Description", createOpts.Description},
&unit.UnitOption{"Unit", "After", createOpts.After},
&unit.UnitOption{"Service", "Type", createOpts.Type},
&unit.UnitOption{"Service", "WorkingDirectory", createOpts.WorkingDirectory},
&unit.UnitOption{"Service", "RootDirectory", createOpts.RootDirectory},
&unit.UnitOption{"Service", "ExecStart", createOpts.Exec},
&unit.UnitOption{"Service", "ExecStartPre", createOpts.ExecStartPre},
&unit.UnitOption{"Service", "ExecStartPost", createOpts.ExecStartPost},
&unit.UnitOption{"Service", "ExecReload", createOpts.ExecReload},
&unit.UnitOption{"Service", "ExecStop", createOpts.ExecStop},
&unit.UnitOption{"Service", "ExecStopPost", createOpts.ExecStopPost},
&unit.UnitOption{"Service", "User", createOpts.User},
&unit.UnitOption{"Service", "Group", createOpts.Group},
&unit.UnitOption{"Service", "Restart", createOpts.Restart},
&unit.UnitOption{"Service", "RestartSec", createOpts.RestartSec},
&unit.UnitOption{"Service", "TimeoutStartSec", createOpts.TimeoutStartSec},
&unit.UnitOption{"Service", "TimeoutStopSec", createOpts.TimeoutStopSec},
&unit.UnitOption{"Install", "WantedBy", createOpts.WantedBy},
}
r := unit.Serialize(stripEmptyOptions(u))
b, err := ioutil.ReadAll(r)
if err != nil {
return fmt.Errorf("Encountered error while reading output: %v", err)
}
filename := filepath.Base(createOpts.Exec) + ".service"
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("Could not create file: %s", err)
}
defer f.Close()
_, err = f.Write(b)
if err != nil {
return fmt.Errorf("Could not write to file: %s", err)
}
fmt.Printf("Generated Unit file: %s\n%s\n", filename, b)
return nil
}
func init() {
createCmd.PersistentFlags().StringVarP(&createOpts.Type, "type", "t", "simple", "Type of service (simple, forking, oneshot, dbus, notify or idle)")
createCmd.PersistentFlags().StringVar(&createOpts.ExecStartPre, "execstartpre", "", "Executable to run before the service starts")
createCmd.PersistentFlags().StringVar(&createOpts.ExecStartPost, "execstartpost", "", "Executable to run after the service started")
createCmd.PersistentFlags().StringVar(&createOpts.ExecReload, "execreload", "", "Executable to run to reload the service")
createCmd.PersistentFlags().StringVar(&createOpts.ExecStop, "execstop", "", "Executable to run to stop the service")
createCmd.PersistentFlags().StringVar(&createOpts.ExecStopPost, "execstoppost", "", "Executable to run after the service stopped")
createCmd.PersistentFlags().StringVarP(&createOpts.WorkingDirectory, "workingdir", "w", "", "Working-directory of the service")
createCmd.PersistentFlags().StringVar(&createOpts.RootDirectory, "rootdir", "", "Root-directory of the service")
createCmd.PersistentFlags().StringVarP(&createOpts.User, "user", "u", "root", "User to run service as")
createCmd.PersistentFlags().StringVarP(&createOpts.Group, "group", "g", "root", "Group to run service as")
createCmd.PersistentFlags().StringVarP(&createOpts.Restart, "restart", "r", "on-failure", "When to restart (no, always, on-success, on-failure, on-abnormal, on-abort or on-watchdog)")
createCmd.PersistentFlags().StringVarP(&createOpts.RestartSec, "restartsec", "s", "", "How many seconds between restarts")
createCmd.PersistentFlags().StringVar(&createOpts.TimeoutStartSec, "timeoutstartsec", "", "How many seconds to wait for a startup")
createCmd.PersistentFlags().StringVar(&createOpts.TimeoutStopSec, "timeoutstopsec", "", "How many seconds to wait when stoping a service")
RootCmd.AddCommand(createCmd)
}