From 3befaead8cba7de8d06bed0e7e53c9ef2bce86c3 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Sun, 18 Oct 2026 09:09:42 -0400 Subject: [PATCH] fix(adopt): a jail with no network is adopted with none --- pkg/engine/appjail/adopt.go | 30 +++++++++++++++++++----------- pkg/engine/appjail/adopt_test.go | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/pkg/engine/appjail/adopt.go b/pkg/engine/appjail/adopt.go index e122447..4b3fff9 100644 --- a/pkg/engine/appjail/adopt.go +++ b/pkg/engine/appjail/adopt.go @@ -149,18 +149,10 @@ func inspectJail(ctx context.Context, name string) jailInfo { out, _ := exec.CommandContext(ctx, args[0], args[1:]...).Output() return strings.TrimSpace(string(out)) } - // last line only: `jail get` prints a header with these flags, `list` doesn't - lastLine := func(s string) string { - lines := strings.Split(strings.TrimSpace(s), "\n") - return lines[len(lines)-1] - } info := jailInfo{Name: name, Env: map[string]string{}} - if f := strings.Fields(lastLine(run("appjail", "jail", "get", "-HIpt", "--", name, "networks", "network_ip4"))); len(f) >= 2 && f[0] != "-" { - info.Network = strings.Split(f[0], ",")[0] - if f[1] != "-" { - info.Address = strings.Split(f[1], ",")[0] - } - } + // No -H here: on `jail get` it ADDS the header, where on the list + // commands below it takes it away. + info.Network, info.Address = jailNetwork(run("appjail", "jail", "get", "-Ipt", "--", name, "networks", "network_ip4")) for _, line := range strings.Split(run("appjail", "expose", "list", "-HIpt", name, "hport", "jport", "protocol"), "\n") { if f := strings.Fields(line); len(f) == 3 { info.Exposes = append(info.Exposes, exposeEntry{f[0], f[1], f[2]}) @@ -196,6 +188,22 @@ func inspectJail(ctx context.Context, name string) jailInfo { return info } +// jailNetwork reads `appjail jail get -Ipt networks network_ip4`: +// the jail's first network and its address there. A jail with no network +// prints nothing. Read with -H, that was the header alone, and adopt wrote +// a virtualnet on network "NETWORKS" at address "NETWORK_IP4". +func jailNetwork(out string) (network, address string) { + f := strings.Fields(out) + if len(f) < 2 || f[0] == "-" { + return "", "" + } + network = strings.Split(f[0], ",")[0] + if f[1] != "-" { + address = strings.Split(f[1], ",")[0] + } + return network, address +} + // plumbingRe matches the jail parameters appjail itself adds for the // virtual network, NAT and exposes; director re-adds them from the spec, // so they must not be baked into template.conf. diff --git a/pkg/engine/appjail/adopt_test.go b/pkg/engine/appjail/adopt_test.go index 6647e25..6c9f6aa 100644 --- a/pkg/engine/appjail/adopt_test.go +++ b/pkg/engine/appjail/adopt_test.go @@ -149,3 +149,20 @@ func TestConvertJailQuotesLiteralEnv(t *testing.T) { t.Errorf("reference was quoted:\n%s", spec.Compose) } } + +// What `jail get -Ipt networks network_ip4` prints (appjail 5.7.0 on +// netlab). The header is what -H used to add: read as data, it became a +// virtualnet on "NETWORKS" at "NETWORK_IP4". +func TestJailNetwork(t *testing.T) { + for _, c := range []struct{ out, network, address string }{ + {"ajnet 10.0.0.3", "ajnet", "10.0.0.3"}, + {"ajnet,other 10.0.0.3,10.1.0.2", "ajnet", "10.0.0.3"}, + {"ajnet -", "ajnet", ""}, + {"", "", ""}, + {"- -", "", ""}, + } { + if n, a := jailNetwork(c.out); n != c.network || a != c.address { + t.Errorf("%q: got %q %q, want %q %q", c.out, n, a, c.network, c.address) + } + } +}