36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
168 lines
4.4 KiB
Go
168 lines
4.4 KiB
Go
package proc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pranshuparmar/witr/pkg/model"
|
|
)
|
|
|
|
// Incus and LXD share the same REST API shape (Incus is a fork of LXD), so
|
|
// `incus list --format json` and `lxc list --format json` return the same
|
|
// payload. Shared helpers here parameterize on the binary so each runtime
|
|
// stays a thin registration.
|
|
|
|
type lxdLikeInstance struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
CreatedAt string `json:"created_at"`
|
|
Description string `json:"description"`
|
|
Project string `json:"project"`
|
|
Config map[string]string `json:"config"`
|
|
ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
|
|
State struct {
|
|
Status string `json:"status"`
|
|
Pid int `json:"pid"`
|
|
Network map[string]lxdLikeNetworkEntry `json:"network"`
|
|
} `json:"state"`
|
|
}
|
|
|
|
type lxdLikeNetworkEntry struct {
|
|
Addresses []struct {
|
|
Family string `json:"family"`
|
|
Address string `json:"address"`
|
|
Scope string `json:"scope"`
|
|
} `json:"addresses"`
|
|
}
|
|
|
|
func lxdLikeList(bin, runtime string) []*model.ContainerMatch {
|
|
ctx, cancel := context.WithTimeout(context.Background(), runtimeQueryTimeout)
|
|
defer cancel()
|
|
out, err := exec.CommandContext(ctx, bin, "list", "--format", "json").Output()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return parseLXDLikeList(out, runtime)
|
|
}
|
|
|
|
func lxdLikeHostPID(bin, id string) int {
|
|
if !isValidContainerID(id) {
|
|
return 0
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), runtimeQueryTimeout)
|
|
defer cancel()
|
|
out, err := exec.CommandContext(ctx, bin, "list", id, "--format", "json").Output()
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
var instances []lxdLikeInstance
|
|
if err := json.Unmarshal(out, &instances); err != nil {
|
|
return 0
|
|
}
|
|
for _, in := range instances {
|
|
if in.Name == id {
|
|
return in.State.Pid
|
|
}
|
|
}
|
|
if len(instances) > 0 {
|
|
return instances[0].State.Pid
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func lxdLikeEnrich(bin string, match *model.ContainerMatch) {
|
|
if match == nil || match.Name == "" || !isValidContainerID(match.Name) {
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), runtimeQueryTimeout)
|
|
defer cancel()
|
|
out, err := exec.CommandContext(ctx, bin, "list", match.Name, "--format", "json").Output()
|
|
if err != nil {
|
|
return
|
|
}
|
|
var instances []lxdLikeInstance
|
|
if json.Unmarshal(out, &instances) != nil {
|
|
return
|
|
}
|
|
for _, in := range instances {
|
|
if in.Name != match.Name {
|
|
continue
|
|
}
|
|
if nets := formatLXDLikeNetworks(in.State.Network); nets != "" {
|
|
match.Networks = nets
|
|
}
|
|
if mounts := formatLXDLikeMounts(in.ExpandedDevices); mounts != "" {
|
|
match.Mounts = mounts
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
func parseLXDLikeList(out []byte, runtime string) []*model.ContainerMatch {
|
|
var instances []lxdLikeInstance
|
|
if err := json.Unmarshal(out, &instances); err != nil {
|
|
return nil
|
|
}
|
|
var matches []*model.ContainerMatch
|
|
for _, in := range instances {
|
|
image := in.Config["image.description"]
|
|
if image == "" {
|
|
if os := in.Config["image.os"]; os != "" {
|
|
image = strings.TrimSpace(os + " " + in.Config["image.release"])
|
|
}
|
|
}
|
|
state := strings.ToLower(in.Status)
|
|
created, _ := time.Parse(time.RFC3339Nano, in.CreatedAt)
|
|
matches = append(matches, &model.ContainerMatch{
|
|
Runtime: runtime,
|
|
ID: in.Name,
|
|
Name: in.Name,
|
|
Image: image,
|
|
State: state,
|
|
Status: in.Status,
|
|
CreatedAt: created,
|
|
})
|
|
}
|
|
return matches
|
|
}
|
|
|
|
func formatLXDLikeNetworks(networks map[string]lxdLikeNetworkEntry) string {
|
|
var parts []string
|
|
for iface, entry := range networks {
|
|
if iface == "lo" {
|
|
continue
|
|
}
|
|
for _, addr := range entry.Addresses {
|
|
if addr.Scope == "link" || addr.Scope == "local" {
|
|
continue
|
|
}
|
|
parts = append(parts, iface+": "+addr.Address)
|
|
}
|
|
}
|
|
return strings.Join(parts, ", ")
|
|
}
|
|
|
|
func formatLXDLikeMounts(devices map[string]map[string]string) string {
|
|
var parts []string
|
|
for name, dev := range devices {
|
|
if dev["type"] != "disk" {
|
|
continue
|
|
}
|
|
source := dev["source"]
|
|
dest := dev["path"]
|
|
if source == "" || dest == "" {
|
|
continue
|
|
}
|
|
entry := source + " → " + dest
|
|
if dev["readonly"] == "true" {
|
|
entry += " (ro)"
|
|
}
|
|
parts = append(parts, name+": "+entry)
|
|
}
|
|
return strings.Join(parts, ", ")
|
|
}
|