Phase 5-7: README, Makefile, unit tests
- README.md: full usage docs - Makefile: build/install/uninstall/test/lint/clean targets - config/config_test.go: 7 tests (load, save, find, add, roundtrip, invalid yaml) - runtime/runtime_test.go: 6 tests (get, available, ListRunning graceful degradation) - systemd/generator_test.go: 7 tests (ServiceName, Generate system/user/custom/docker/unknown, buildExecCommands) - fix: docker/podman ListRunning returns nil (not error) when daemon unavailable - fix: invalid YAML test uses tab-indent which is genuinely unparseable
This commit is contained in:
45
Makefile
Normal file
45
Makefile
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
BINARY := unitdore
|
||||||
|
INSTALL_DIR := /usr/local/bin
|
||||||
|
CONFIG_DIR := /etc/unitdore
|
||||||
|
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||||
|
LDFLAGS := -ldflags "-X main.version=$(VERSION)"
|
||||||
|
|
||||||
|
.PHONY: all build install uninstall test lint clean
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
## build: compile the binary
|
||||||
|
build:
|
||||||
|
go build $(LDFLAGS) -o $(BINARY) .
|
||||||
|
|
||||||
|
## install: install binary and create config dir
|
||||||
|
install: build
|
||||||
|
install -Dm755 $(BINARY) $(INSTALL_DIR)/$(BINARY)
|
||||||
|
mkdir -p $(CONFIG_DIR)
|
||||||
|
@echo "Installed to $(INSTALL_DIR)/$(BINARY)"
|
||||||
|
@echo "Config dir: $(CONFIG_DIR)"
|
||||||
|
|
||||||
|
## uninstall: remove binary
|
||||||
|
uninstall:
|
||||||
|
rm -f $(INSTALL_DIR)/$(BINARY)
|
||||||
|
@echo "Removed $(INSTALL_DIR)/$(BINARY)"
|
||||||
|
|
||||||
|
## test: run all unit tests
|
||||||
|
test:
|
||||||
|
go test ./... -v
|
||||||
|
|
||||||
|
## test-short: run tests without verbose output
|
||||||
|
test-short:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
## lint: run go vet
|
||||||
|
lint:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
## clean: remove built binary
|
||||||
|
clean:
|
||||||
|
rm -f $(BINARY)
|
||||||
|
|
||||||
|
## help: show this help
|
||||||
|
help:
|
||||||
|
@grep -E '^## ' Makefile | sed 's/## / /'
|
||||||
131
README.md
Normal file
131
README.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# unitdore
|
||||||
|
|
||||||
|
> *A door you open and close for container units.*
|
||||||
|
|
||||||
|
Unitdore bridges your container runtime (Podman, Docker) and systemd. It discovers running containers, stores them in a config file, and generates + manages systemd `.service` units for each one.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o unitdore .
|
||||||
|
sudo cp unitdore /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Discover running containers
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo unitdore syncup
|
||||||
|
```
|
||||||
|
|
||||||
|
Queries Podman and Docker for running containers, adds new ones to the config, and reconciles any that have disappeared (marking them disabled).
|
||||||
|
|
||||||
|
### Edit the config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo unitdore edit
|
||||||
|
```
|
||||||
|
|
||||||
|
Opens `/etc/unitdore/units.yaml` in `$EDITOR` (falls back to `vi`).
|
||||||
|
|
||||||
|
### Install systemd unit files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo unitdore install
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates `.service` files for all enabled units and writes them to systemd. Use `--dry-run` to preview without writing.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo unitdore install --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enable and start units
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo unitdore active
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs `systemctl enable --now` for all installed, enabled units in startup order.
|
||||||
|
|
||||||
|
### Check status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo unitdore status
|
||||||
|
```
|
||||||
|
|
||||||
|
Prints a summary table:
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME RUNTIME USER ENABLED INSTALLED STATE REASON
|
||||||
|
nginx podman root yes yes active
|
||||||
|
myapp docker hein yes yes active
|
||||||
|
oldthing podman hein no no — container not found
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config file
|
||||||
|
|
||||||
|
**Location:** `/etc/unitdore/units.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
units:
|
||||||
|
- name: nginx
|
||||||
|
runtime: podman # podman | docker
|
||||||
|
user: "" # empty = root/system unit
|
||||||
|
command: "" # optional: override ExecStart entirely
|
||||||
|
order: 1 # startup order (lower = earlier)
|
||||||
|
delay: 0s # delay after previous order group
|
||||||
|
enabled: true
|
||||||
|
disabled_reason: "" # auto-set by syncup reconciliation
|
||||||
|
|
||||||
|
- name: myapp
|
||||||
|
runtime: docker
|
||||||
|
user: hein # rootless: generates user unit for this user
|
||||||
|
order: 2
|
||||||
|
delay: 5s
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generated unit file (example)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/systemd/system/unitdore-nginx.service
|
||||||
|
# Generated by unitdore — do not edit manually
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Unitdore: nginx (podman)
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/podman start -a nginx
|
||||||
|
ExecStop=/usr/bin/podman stop nginx
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
For user units (rootless containers), files go to `~/.config/systemd/user/` and target `default.target`.
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
```
|
||||||
|
--config string path to units config file (default "/etc/unitdore/units.yaml")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Go 1.21+
|
||||||
|
- systemd
|
||||||
|
- Podman and/or Docker (only what you have installed is used)
|
||||||
|
- Root for system units; the relevant user for rootless units
|
||||||
146
config/config_test.go
Normal file
146
config/config_test.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadOrEmpty_NoFile(t *testing.T) {
|
||||||
|
cfg, err := LoadOrEmpty("/tmp/unitdore-nonexistent-test.yaml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error for missing file, got: %v", err)
|
||||||
|
}
|
||||||
|
if len(cfg.Units) != 0 {
|
||||||
|
t.Errorf("expected empty units, got %d", len(cfg.Units))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveAndLoad(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "units.yaml")
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Units: []Unit{
|
||||||
|
{Name: "nginx", Runtime: "podman", Order: 1, Enabled: true},
|
||||||
|
{Name: "myapp", Runtime: "docker", User: "hein", Order: 2, Enabled: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.Save(path); err != nil {
|
||||||
|
t.Fatalf("Save failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded, err := Load(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(loaded.Units) != 2 {
|
||||||
|
t.Errorf("expected 2 units, got %d", len(loaded.Units))
|
||||||
|
}
|
||||||
|
if loaded.Units[0].Name != "nginx" {
|
||||||
|
t.Errorf("expected nginx, got %s", loaded.Units[0].Name)
|
||||||
|
}
|
||||||
|
if loaded.Units[1].User != "hein" {
|
||||||
|
t.Errorf("expected user hein, got %s", loaded.Units[1].User)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindUnit(t *testing.T) {
|
||||||
|
cfg := &Config{
|
||||||
|
Units: []Unit{
|
||||||
|
{Name: "nginx", Runtime: "podman"},
|
||||||
|
{Name: "myapp", Runtime: "docker"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
u := cfg.FindUnit("nginx")
|
||||||
|
if u == nil {
|
||||||
|
t.Fatal("expected to find nginx, got nil")
|
||||||
|
}
|
||||||
|
if u.Runtime != "podman" {
|
||||||
|
t.Errorf("expected podman, got %s", u.Runtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
missing := cfg.FindUnit("nothere")
|
||||||
|
if missing != nil {
|
||||||
|
t.Errorf("expected nil for missing unit, got %+v", missing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddUnit(t *testing.T) {
|
||||||
|
cfg := &Config{}
|
||||||
|
|
||||||
|
added := cfg.AddUnit(Unit{Name: "nginx", Runtime: "podman", Enabled: true})
|
||||||
|
if !added {
|
||||||
|
t.Error("expected AddUnit to return true for new unit")
|
||||||
|
}
|
||||||
|
if len(cfg.Units) != 1 {
|
||||||
|
t.Errorf("expected 1 unit, got %d", len(cfg.Units))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding a duplicate should return false and not grow the list
|
||||||
|
added = cfg.AddUnit(Unit{Name: "nginx", Runtime: "docker"})
|
||||||
|
if added {
|
||||||
|
t.Error("expected AddUnit to return false for duplicate")
|
||||||
|
}
|
||||||
|
if len(cfg.Units) != 1 {
|
||||||
|
t.Errorf("expected still 1 unit, got %d", len(cfg.Units))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoad_InvalidYAML(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "bad.yaml")
|
||||||
|
// Use a tab character in a mapping key — this is genuinely invalid YAML
|
||||||
|
os.WriteFile(path, []byte("units:\n - name: ok\n\t broken: tab-indent"), 0644)
|
||||||
|
|
||||||
|
_, err := Load(path)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error for invalid YAML, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSave_CreatesParentDir(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "subdir", "nested", "units.yaml")
|
||||||
|
|
||||||
|
cfg := &Config{Units: []Unit{{Name: "test", Runtime: "podman", Enabled: true}}}
|
||||||
|
if err := cfg.Save(path); err != nil {
|
||||||
|
t.Fatalf("Save failed to create parent dirs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
t.Errorf("expected file to exist: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabledReason_Roundtrip(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "units.yaml")
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Units: []Unit{
|
||||||
|
{
|
||||||
|
Name: "ghost",
|
||||||
|
Runtime: "podman",
|
||||||
|
Enabled: false,
|
||||||
|
DisabledReason: "container not found",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.Save(path); err != nil {
|
||||||
|
t.Fatalf("Save failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded, err := Load(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if loaded.Units[0].DisabledReason != "container not found" {
|
||||||
|
t.Errorf("expected disabled_reason to survive roundtrip, got: %q", loaded.Units[0].DisabledReason)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,8 @@ func (d *Docker) ListRunning() ([]Container, error) {
|
|||||||
|
|
||||||
out, err := exec.Command(bin, "ps", "--format", "json").Output()
|
out, err := exec.Command(bin, "ps", "--format", "json").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("docker ps: %w", err)
|
// Docker installed but daemon not running — treat as no containers
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Docker outputs one JSON object per line (not a JSON array)
|
// Docker outputs one JSON object per line (not a JSON array)
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ func (p *Podman) ListRunning() ([]Container, error) {
|
|||||||
|
|
||||||
out, err := exec.Command(bin, "ps", "--format", "json").Output()
|
out, err := exec.Command(bin, "ps", "--format", "json").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("podman ps: %w", err)
|
// Podman installed but not usable — treat as no containers
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var raw []podmanContainer
|
var raw []podmanContainer
|
||||||
|
|||||||
71
runtime/runtime_test.go
Normal file
71
runtime/runtime_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGet_Podman(t *testing.T) {
|
||||||
|
rt := Get("podman")
|
||||||
|
if rt == nil {
|
||||||
|
t.Fatal("Get(podman) returned nil")
|
||||||
|
}
|
||||||
|
if rt.Name() != "podman" {
|
||||||
|
t.Errorf("Name() = %q, want %q", rt.Name(), "podman")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_Docker(t *testing.T) {
|
||||||
|
rt := Get("docker")
|
||||||
|
if rt == nil {
|
||||||
|
t.Fatal("Get(docker) returned nil")
|
||||||
|
}
|
||||||
|
if rt.Name() != "docker" {
|
||||||
|
t.Errorf("Name() = %q, want %q", rt.Name(), "docker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_Unknown(t *testing.T) {
|
||||||
|
rt := Get("containerd")
|
||||||
|
if rt != nil {
|
||||||
|
t.Errorf("Get(containerd) should return nil for unknown runtime, got %+v", rt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvailable_ReturnsRuntimes(t *testing.T) {
|
||||||
|
runtimes := Available()
|
||||||
|
if len(runtimes) == 0 {
|
||||||
|
t.Error("Available() returned empty list")
|
||||||
|
}
|
||||||
|
names := map[string]bool{}
|
||||||
|
for _, rt := range runtimes {
|
||||||
|
names[rt.Name()] = true
|
||||||
|
}
|
||||||
|
if !names["podman"] {
|
||||||
|
t.Error("Available() should include podman")
|
||||||
|
}
|
||||||
|
if !names["docker"] {
|
||||||
|
t.Error("Available() should include docker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRunning and Exists are integration-level — they call external binaries.
|
||||||
|
// We test that they don't panic when the binary is absent (returns nil/false, no error).
|
||||||
|
func TestPodman_ListRunning_NoBinary(t *testing.T) {
|
||||||
|
// This test is meaningful if podman isn't installed; if it is, we just check no error occurs
|
||||||
|
p := &Podman{}
|
||||||
|
containers, err := p.ListRunning()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ListRunning() should not error when podman is absent: %v", err)
|
||||||
|
}
|
||||||
|
// containers may be nil or populated — both are fine
|
||||||
|
_ = containers
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocker_ListRunning_NoBinary(t *testing.T) {
|
||||||
|
d := &Docker{}
|
||||||
|
containers, err := d.ListRunning()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ListRunning() should not error when docker is absent: %v", err)
|
||||||
|
}
|
||||||
|
_ = containers
|
||||||
|
}
|
||||||
179
systemd/generator_test.go
Normal file
179
systemd/generator_test.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package systemd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/warkanum/unitdore/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate_SystemUnit(t *testing.T) {
|
||||||
|
u := config.Unit{
|
||||||
|
Name: "nginx",
|
||||||
|
Runtime: "podman",
|
||||||
|
Order: 1,
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := Generate(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Generate() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checks := []string{
|
||||||
|
"[Unit]",
|
||||||
|
"[Service]",
|
||||||
|
"[Install]",
|
||||||
|
"unitdore-nginx.service",
|
||||||
|
"Description=Unitdore: nginx (podman)",
|
||||||
|
"After=network.target",
|
||||||
|
"WantedBy=multi-user.target",
|
||||||
|
"ExecStart=/usr/bin/podman start -a nginx",
|
||||||
|
"ExecStop=/usr/bin/podman stop nginx",
|
||||||
|
"Restart=on-failure",
|
||||||
|
"Generated by unitdore",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, check := range checks {
|
||||||
|
if !strings.Contains(content, check) {
|
||||||
|
t.Errorf("Generate() missing %q in output:\n%s", check, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate_UserUnit(t *testing.T) {
|
||||||
|
u := config.Unit{
|
||||||
|
Name: "myapp",
|
||||||
|
Runtime: "docker",
|
||||||
|
User: "hein",
|
||||||
|
Order: 2,
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := Generate(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Generate() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checks := []string{
|
||||||
|
"After=default.target",
|
||||||
|
"WantedBy=default.target",
|
||||||
|
"ExecStart=/usr/bin/docker start myapp",
|
||||||
|
"ExecStop=/usr/bin/docker stop myapp",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, check := range checks {
|
||||||
|
if !strings.Contains(content, check) {
|
||||||
|
t.Errorf("Generate() user unit missing %q in output:\n%s", check, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate_CustomCommand(t *testing.T) {
|
||||||
|
u := config.Unit{
|
||||||
|
Name: "custom",
|
||||||
|
Runtime: "podman",
|
||||||
|
Command: "/usr/local/bin/mystart.sh",
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := Generate(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Generate() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(content, "ExecStart=/usr/local/bin/mystart.sh") {
|
||||||
|
t.Errorf("Generate() should use custom command, got:\n%s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate_DockerRuntime(t *testing.T) {
|
||||||
|
u := config.Unit{
|
||||||
|
Name: "redis",
|
||||||
|
Runtime: "docker",
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := Generate(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Generate() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(content, "ExecStart=/usr/bin/docker start redis") {
|
||||||
|
t.Errorf("Generate() wrong ExecStart for docker:\n%s", content)
|
||||||
|
}
|
||||||
|
if !strings.Contains(content, "ExecStop=/usr/bin/docker stop redis") {
|
||||||
|
t.Errorf("Generate() wrong ExecStop for docker:\n%s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate_UnknownRuntime(t *testing.T) {
|
||||||
|
u := config.Unit{
|
||||||
|
Name: "weird",
|
||||||
|
Runtime: "containerd",
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := Generate(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Generate() should not error on unknown runtime: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(content, "/usr/bin/containerd start weird") {
|
||||||
|
t.Errorf("Generate() should fall back to /usr/bin/<runtime> for unknown runtimes:\n%s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildExecCommands(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
unit config.Unit
|
||||||
|
wantStart string
|
||||||
|
wantStop string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "podman",
|
||||||
|
unit: config.Unit{Name: "app", Runtime: "podman"},
|
||||||
|
wantStart: "/usr/bin/podman start -a app",
|
||||||
|
wantStop: "/usr/bin/podman stop app",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docker",
|
||||||
|
unit: config.Unit{Name: "app", Runtime: "docker"},
|
||||||
|
wantStart: "/usr/bin/docker start app",
|
||||||
|
wantStop: "/usr/bin/docker stop app",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom command",
|
||||||
|
unit: config.Unit{Name: "app", Runtime: "podman", Command: "/bin/custom"},
|
||||||
|
wantStart: "/bin/custom",
|
||||||
|
wantStop: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
start, stop := buildExecCommands(tt.unit)
|
||||||
|
if start != tt.wantStart {
|
||||||
|
t.Errorf("start = %q, want %q", start, tt.wantStart)
|
||||||
|
}
|
||||||
|
if stop != tt.wantStop {
|
||||||
|
t.Errorf("stop = %q, want %q", stop, tt.wantStop)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user