Add prefix/suffix support, short ID fallback, unit docs
- config: add Prefix/Suffix fields to Config struct - systemd: ServiceName/Generate/UnitPath/Install/Uninstall/Enable/Disable/Status all accept prefix+suffix - runtime: fall back to short container ID (12 chars) when container has no name - cmd: active, status, install all thread prefix/suffix from config - systemd/generator_test.go: updated all calls + added TestGenerate_WithPrefixSuffix - docs/generated-units.md: full examples of every unit type + ordering + naming - README: updated config docs, prefix/suffix section, link to docs/
This commit is contained in:
@@ -51,17 +51,17 @@ type templateData struct {
|
||||
ExecStop string
|
||||
}
|
||||
|
||||
// ServiceName returns the systemd service name for a unit.
|
||||
func ServiceName(u config.Unit) string {
|
||||
return fmt.Sprintf("unitdore-%s.service", u.Name)
|
||||
// ServiceName returns the systemd service name for a unit, with optional prefix/suffix.
|
||||
func ServiceName(u config.Unit, prefix, suffix string) string {
|
||||
return fmt.Sprintf("unitdore-%s%s%s.service", prefix, u.Name, suffix)
|
||||
}
|
||||
|
||||
// Generate produces the .service file content for a unit.
|
||||
func Generate(u config.Unit) (string, error) {
|
||||
func Generate(u config.Unit, prefix, suffix string) (string, error) {
|
||||
execStart, execStop := buildExecCommands(u)
|
||||
|
||||
data := templateData{
|
||||
ServiceName: ServiceName(u),
|
||||
ServiceName: ServiceName(u, prefix, suffix),
|
||||
Unit: u,
|
||||
ExecStart: execStart,
|
||||
ExecStop: execStop,
|
||||
|
||||
@@ -9,10 +9,21 @@ import (
|
||||
|
||||
func TestServiceName(t *testing.T) {
|
||||
u := config.Unit{Name: "nginx"}
|
||||
got := ServiceName(u)
|
||||
want := "unitdore-nginx.service"
|
||||
if got != want {
|
||||
t.Errorf("ServiceName() = %q, want %q", got, want)
|
||||
|
||||
tests := []struct {
|
||||
prefix, suffix, want string
|
||||
}{
|
||||
{"", "", "unitdore-nginx.service"},
|
||||
{"prod-", "", "unitdore-prod-nginx.service"},
|
||||
{"", "-web", "unitdore-nginx-web.service"},
|
||||
{"prod-", "-web", "unitdore-prod-nginx-web.service"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := ServiceName(u, tt.prefix, tt.suffix)
|
||||
if got != tt.want {
|
||||
t.Errorf("ServiceName(%q, %q) = %q, want %q", tt.prefix, tt.suffix, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +35,7 @@ func TestGenerate_SystemUnit(t *testing.T) {
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
content, err := Generate(u)
|
||||
content, err := Generate(u, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error: %v", err)
|
||||
}
|
||||
@@ -59,7 +70,7 @@ func TestGenerate_UserUnit(t *testing.T) {
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
content, err := Generate(u)
|
||||
content, err := Generate(u, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error: %v", err)
|
||||
}
|
||||
@@ -77,7 +88,6 @@ func TestGenerate_UserUnit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// System unit markers must NOT appear
|
||||
if strings.Contains(content, "WantedBy=multi-user.target") {
|
||||
t.Error("Generate() user unit should not contain multi-user.target")
|
||||
}
|
||||
@@ -91,7 +101,7 @@ func TestGenerate_CustomCommand(t *testing.T) {
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
content, err := Generate(u)
|
||||
content, err := Generate(u, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error: %v", err)
|
||||
}
|
||||
@@ -108,7 +118,7 @@ func TestGenerate_DockerRuntime(t *testing.T) {
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
content, err := Generate(u)
|
||||
content, err := Generate(u, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error: %v", err)
|
||||
}
|
||||
@@ -128,7 +138,7 @@ func TestGenerate_UnknownRuntime(t *testing.T) {
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
content, err := Generate(u)
|
||||
content, err := Generate(u, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() should not error on unknown runtime: %v", err)
|
||||
}
|
||||
@@ -138,6 +148,23 @@ func TestGenerate_UnknownRuntime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_WithPrefixSuffix(t *testing.T) {
|
||||
u := config.Unit{
|
||||
Name: "nginx",
|
||||
Runtime: "podman",
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
content, err := Generate(u, "prod-", "-web")
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(content, "unitdore-prod-nginx-web.service") {
|
||||
t.Errorf("Generate() prefix+suffix not reflected in service name:\n%s", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildExecCommands(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -13,26 +13,27 @@ import (
|
||||
const systemUnitDir = "/etc/systemd/system"
|
||||
|
||||
// UnitPath returns the filesystem path where the .service file should be written.
|
||||
func UnitPath(u config.Unit) (string, error) {
|
||||
func UnitPath(u config.Unit, prefix, suffix string) (string, error) {
|
||||
svcName := ServiceName(u, prefix, suffix)
|
||||
if u.User == "" {
|
||||
return filepath.Join(systemUnitDir, ServiceName(u)), nil
|
||||
return filepath.Join(systemUnitDir, svcName), nil
|
||||
}
|
||||
// Rootless: write to the user's systemd config dir
|
||||
home, err := userHomeDir(u.User)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, ".config", "systemd", "user", ServiceName(u)), nil
|
||||
return filepath.Join(home, ".config", "systemd", "user", svcName), nil
|
||||
}
|
||||
|
||||
// Install writes the .service file for a unit and reloads systemd.
|
||||
func Install(u config.Unit) error {
|
||||
content, err := Generate(u)
|
||||
func Install(u config.Unit, prefix, suffix string) error {
|
||||
content, err := Generate(u, prefix, suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, err := UnitPath(u)
|
||||
path, err := UnitPath(u, prefix, suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,8 +50,8 @@ func Install(u config.Unit) error {
|
||||
}
|
||||
|
||||
// Uninstall removes the .service file for a unit and reloads systemd.
|
||||
func Uninstall(u config.Unit) error {
|
||||
path, err := UnitPath(u)
|
||||
func Uninstall(u config.Unit, prefix, suffix string) error {
|
||||
path, err := UnitPath(u, prefix, suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,18 +64,18 @@ func Uninstall(u config.Unit) error {
|
||||
}
|
||||
|
||||
// Enable enables and starts a unit.
|
||||
func Enable(u config.Unit) error {
|
||||
return systemctl(u.User, "enable", "--now", ServiceName(u))
|
||||
func Enable(u config.Unit, prefix, suffix string) error {
|
||||
return systemctl(u.User, "enable", "--now", ServiceName(u, prefix, suffix))
|
||||
}
|
||||
|
||||
// Disable disables and stops a unit.
|
||||
func Disable(u config.Unit) error {
|
||||
return systemctl(u.User, "disable", "--now", ServiceName(u))
|
||||
func Disable(u config.Unit, prefix, suffix string) error {
|
||||
return systemctl(u.User, "disable", "--now", ServiceName(u, prefix, suffix))
|
||||
}
|
||||
|
||||
// Status returns the ActiveState of a unit ("active", "inactive", "failed", "unknown").
|
||||
func Status(u config.Unit) string {
|
||||
args := []string{"show", "-p", "ActiveState", "--value", ServiceName(u)}
|
||||
func Status(u config.Unit, prefix, suffix string) string {
|
||||
args := []string{"show", "-p", "ActiveState", "--value", ServiceName(u, prefix, suffix)}
|
||||
var out []byte
|
||||
var err error
|
||||
|
||||
@@ -90,8 +91,8 @@ func Status(u config.Unit) string {
|
||||
}
|
||||
|
||||
// IsInstalled returns true if the unit file exists on disk.
|
||||
func IsInstalled(u config.Unit) bool {
|
||||
path, err := UnitPath(u)
|
||||
func IsInstalled(u config.Unit, prefix, suffix string) bool {
|
||||
path, err := UnitPath(u, prefix, suffix)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user