Initial commit: unitdore scaffold + syncup/edit commands
- config: load/save/add unit, Unit struct - runtime: podman + docker discovery and exists check - cmd: syncup (discover + reconcile), edit, cobra root - PLAN.md: full project plan
This commit is contained in:
73
runtime/docker.go
Normal file
73
runtime/docker.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Docker struct{}
|
||||
|
||||
func (d *Docker) Name() string { return "docker" }
|
||||
|
||||
type dockerContainer struct {
|
||||
Names string `json:"Names"`
|
||||
Image string `json:"Image"`
|
||||
Command string `json:"Command"`
|
||||
State string `json:"State"`
|
||||
}
|
||||
|
||||
func (d *Docker) ListRunning() ([]Container, error) {
|
||||
bin, err := exec.LookPath("docker")
|
||||
if err != nil {
|
||||
return nil, nil // docker not installed — not an error
|
||||
}
|
||||
|
||||
out, err := exec.Command(bin, "ps", "--format", "json").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("docker ps: %w", err)
|
||||
}
|
||||
|
||||
// Docker outputs one JSON object per line (not a JSON array)
|
||||
var containers []Container
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
var c dockerContainer
|
||||
if err := json.Unmarshal([]byte(line), &c); err != nil {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimPrefix(c.Names, "/")
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
containers = append(containers, Container{
|
||||
Name: name,
|
||||
Image: c.Image,
|
||||
Command: c.Command,
|
||||
Runtime: "docker",
|
||||
})
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (d *Docker) Exists(name string) (bool, error) {
|
||||
bin, err := exec.LookPath("docker")
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
out, err := exec.Command(bin, "ps", "-a", "--format", "{{.Names}}").Output()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("docker ps -a: %w", err)
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
if strings.TrimSpace(line) == name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
73
runtime/podman.go
Normal file
73
runtime/podman.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Podman struct{}
|
||||
|
||||
func (p *Podman) Name() string { return "podman" }
|
||||
|
||||
type podmanContainer struct {
|
||||
Names []string `json:"Names"`
|
||||
Image string `json:"Image"`
|
||||
Command string `json:"Command"`
|
||||
State string `json:"State"`
|
||||
}
|
||||
|
||||
func (p *Podman) ListRunning() ([]Container, error) {
|
||||
bin, err := exec.LookPath("podman")
|
||||
if err != nil {
|
||||
return nil, nil // podman not installed — not an error
|
||||
}
|
||||
|
||||
out, err := exec.Command(bin, "ps", "--format", "json").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("podman ps: %w", err)
|
||||
}
|
||||
|
||||
var raw []podmanContainer
|
||||
if err := json.Unmarshal(out, &raw); err != nil {
|
||||
return nil, fmt.Errorf("parsing podman output: %w", err)
|
||||
}
|
||||
|
||||
var containers []Container
|
||||
for _, c := range raw {
|
||||
name := ""
|
||||
if len(c.Names) > 0 {
|
||||
name = c.Names[0]
|
||||
}
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
containers = append(containers, Container{
|
||||
Name: name,
|
||||
Image: c.Image,
|
||||
Command: c.Command,
|
||||
Runtime: "podman",
|
||||
})
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (p *Podman) Exists(name string) (bool, error) {
|
||||
bin, err := exec.LookPath("podman")
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
out, err := exec.Command(bin, "ps", "-a", "--format", "{{.Names}}").Output()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("podman ps -a: %w", err)
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
if strings.TrimSpace(line) == name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
33
runtime/runtime.go
Normal file
33
runtime/runtime.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package runtime
|
||||
|
||||
// Container represents a discovered running container.
|
||||
type Container struct {
|
||||
Name string
|
||||
Image string
|
||||
Command string
|
||||
Runtime string // "podman" or "docker"
|
||||
}
|
||||
|
||||
// Runtime is the interface all container runtimes must implement.
|
||||
type Runtime interface {
|
||||
Name() string
|
||||
ListRunning() ([]Container, error)
|
||||
Exists(name string) (bool, error)
|
||||
}
|
||||
|
||||
// Available returns all supported runtimes (callers skip those with no binary).
|
||||
func Available() []Runtime {
|
||||
return []Runtime{&Podman{}, &Docker{}}
|
||||
}
|
||||
|
||||
// Get returns a runtime by name ("podman" or "docker").
|
||||
func Get(name string) Runtime {
|
||||
switch name {
|
||||
case "podman":
|
||||
return &Podman{}
|
||||
case "docker":
|
||||
return &Docker{}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user