aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--internal/pkg/dir_test.go52
-rw-r--r--internal/pkg/exec.go51
-rw-r--r--internal/pkg/exec_test.go45
-rw-r--r--internal/pkg/pkg.go3
-rw-r--r--internal/pkg/testdata/main.go36
5 files changed, 179 insertions, 8 deletions
diff --git a/internal/pkg/dir_test.go b/internal/pkg/dir_test.go
index 8e4e1986..3038526f 100644
--- a/internal/pkg/dir_test.go
+++ b/internal/pkg/dir_test.go
@@ -274,32 +274,82 @@ func TestFlatten(t *testing.T) {
}, pkg.MustDecode("GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
{"sample exec container", fstest.MapFS{
- ".": {Mode: fs.ModeDir | 0700},
+ ".": {Mode: fs.ModeDir | 0700},
+
"checksum": {Mode: fs.ModeDir | 0700},
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0x0}},
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
+
"identifier": {Mode: fs.ModeDir | 0700},
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
"identifier/nfeISfLeFDr1k-g3hpE1oZ440kTqDdfF8TDpoLdbTPqaMMIl95oiqcvqjRkMjubA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
"identifier/oHuqV7p0v1Vd8IdAzjyYM8sfCS0P2LR5tfv5cb6Gbf2ZWUm8Ec-7hYPJ_qr183m7": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}, []pkg.FlatEntry{
{Mode: fs.ModeDir | 0700, Path: "."},
+
{Mode: fs.ModeDir | 0700, Path: "checksum"},
{Mode: fs.ModeDir | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0x0}},
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
+
{Mode: fs.ModeDir | 0700, Path: "identifier"},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/nfeISfLeFDr1k-g3hpE1oZ440kTqDdfF8TDpoLdbTPqaMMIl95oiqcvqjRkMjubA", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/oHuqV7p0v1Vd8IdAzjyYM8sfCS0P2LR5tfv5cb6Gbf2ZWUm8Ec-7hYPJ_qr183m7", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
}, pkg.MustDecode("7PoPpWLjFPXIymbuIYLZAzOpCYr-2PN4CZ11jFdO-mDlnZNgFO3JyOtK8HW8Jxvm")},
+
+ {"testtool net", fstest.MapFS{
+ ".": {Mode: fs.ModeDir | 0500},
+
+ "check": {Mode: 0400, Data: []byte("net")},
+ }, []pkg.FlatEntry{
+ {Mode: fs.ModeDir | 0500, Path: "."},
+
+ {Mode: 0400, Path: "check", Data: []byte("net")},
+ }, pkg.MustDecode("a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
+
+ {"sample exec net container", fstest.MapFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+ "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
+ "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W": {Mode: fs.ModeDir | 0500},
+ "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check": {Mode: 0400, Data: []byte("net")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
+ "identifier/I3T53NtN6HPAyyodHtq2B0clcsoS1nPdvCEb-Zc5K-hoqFGL2od1mftHhwG7gX1S": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
+ "identifier/nfeISfLeFDr1k-g3hpE1oZ440kTqDdfF8TDpoLdbTPqaMMIl95oiqcvqjRkMjubA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ "temp": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }, []pkg.FlatEntry{
+ {Mode: fs.ModeDir | 0700, Path: "."},
+
+ {Mode: fs.ModeDir | 0700, Path: "checksum"},
+ {Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
+ {Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
+ {Mode: fs.ModeDir | 0500, Path: "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W"},
+ {Mode: 0400, Path: "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check", Data: []byte("net")},
+
+ {Mode: fs.ModeDir | 0700, Path: "identifier"},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/I3T53NtN6HPAyyodHtq2B0clcsoS1nPdvCEb-Zc5K-hoqFGL2od1mftHhwG7gX1S", Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/nfeISfLeFDr1k-g3hpE1oZ440kTqDdfF8TDpoLdbTPqaMMIl95oiqcvqjRkMjubA", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ {Mode: fs.ModeDir | 0700, Path: "temp"},
+ {Mode: fs.ModeDir | 0700, Path: "work"},
+ }, pkg.MustDecode("bBQVFIt0FnOulljgpLnGtuzHSFgwiCMjc4pmc4rHRqXKQ60Q5aBVYp5f6aH9VdZi")},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go
index 539bd935..930624fc 100644
--- a/internal/pkg/exec.go
+++ b/internal/pkg/exec.go
@@ -6,6 +6,7 @@ import (
"errors"
"os"
"runtime"
+ "slices"
"hakurei.app/container"
"hakurei.app/container/check"
@@ -29,6 +30,9 @@ func MustPath(pathname string, a Artifact) ExecContainerPath {
// An execArtifact is an [Artifact] that produces output by running a program
// part of another [Artifact] in a [container] to produce its output.
+//
+// Methods of execArtifact does not modify any struct field or underlying arrays
+// referred to by slices.
type execArtifact struct {
// Caller-supplied context.
ctx context.Context
@@ -52,6 +56,33 @@ type execArtifact struct {
args []string
}
+// execNetArtifact is like execArtifact but implements [KnownChecksum] and has
+// its resulting container keep the host net namespace.
+type execNetArtifact struct {
+ checksum Checksum
+
+ execArtifact
+}
+
+var _ KnownChecksum = new(execNetArtifact)
+
+// Checksum returns the caller-supplied checksum.
+func (a *execNetArtifact) Checksum() Checksum { return a.checksum }
+
+// Kind returns the hardcoded [Kind] constant.
+func (a *execNetArtifact) Kind() Kind { return KindExecNet }
+
+// Params is [Checksum] concatenated with [KindExec] params.
+func (a *execNetArtifact) Params() []byte {
+ return slices.Concat(a.checksum[:], a.execArtifact.Params())
+}
+
+// Cure cures the [Artifact] by curing all its dependencies then running the
+// container described by the caller. The container retains host networking.
+func (a *execNetArtifact) Cure(c *CureContext) error {
+ return a.cure(c, true)
+}
+
// NewExec returns a new [Artifact] bounded by ctx, it cures all [Artifact]
// in paths at the specified maximum concurrent cures limit. Specified paths are
// bind mounted read-only in the specified order in the resulting container.
@@ -60,12 +91,16 @@ type execArtifact struct {
// The working and temporary directories are both created and mounted writable
// on /work and /tmp respectively.
//
+// If checksum is non-nil, the resulting [Artifact] implements [KnownChecksum]
+// and its container runs in the host net namespace.
+//
// A cures value of 0 or lower is equivalent to the value returned by
// [runtime.NumCPU].
func NewExec(
ctx context.Context,
msg message.Msg,
cures int,
+ checksum *Checksum,
dir *check.Absolute,
env []string,
@@ -74,7 +109,11 @@ func NewExec(
paths ...ExecContainerPath,
) Artifact {
- return &execArtifact{ctx, paths, msg, cures, dir, env, path, args}
+ a := execArtifact{ctx, paths, msg, cures, dir, env, path, args}
+ if checksum == nil {
+ return &a
+ }
+ return &execNetArtifact{*checksum, a}
}
// Kind returns the hardcoded [Kind] constant.
@@ -126,6 +165,12 @@ func (a *execArtifact) Dependencies() []Artifact {
// Cure cures the [Artifact] by curing all its dependencies then running the
// container described by the caller.
func (a *execArtifact) Cure(c *CureContext) (err error) {
+ return a.cure(c, false)
+}
+
+// cure is like Cure but allows optional host net namespace. This is used for
+// the [KnownChecksum] variant where networking is allowed.
+func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
cures := a.cures
if cures < 1 {
cures = runtime.NumCPU()
@@ -205,7 +250,11 @@ func (a *execArtifact) Cure(c *CureContext) (err error) {
z.ForwardCancel = true
z.SeccompPresets |= std.PresetStrict
z.ParentPerm = 0700
+ z.HostNet = hostNet
z.Hostname = "cure"
+ if z.HostNet {
+ z.Hostname = "cure-net"
+ }
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
if a.msg.IsVerbose() {
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
diff --git a/internal/pkg/exec_test.go b/internal/pkg/exec_test.go
index 0ac06010..1b2f84a8 100644
--- a/internal/pkg/exec_test.go
+++ b/internal/pkg/exec_test.go
@@ -39,6 +39,7 @@ func TestExec(t *testing.T) {
t.Context(),
msg,
0,
+ nil,
check.MustAbs("/work"),
[]string{"HAKUREI_TEST=1"},
check.MustAbs("/opt/bin/testtool"),
@@ -66,6 +67,7 @@ func TestExec(t *testing.T) {
t.Context(),
msg,
0,
+ nil,
check.MustAbs("/work"),
[]string{"HAKUREI_TEST=1"},
check.MustAbs("/opt/bin/testtool"),
@@ -84,6 +86,7 @@ func TestExec(t *testing.T) {
t.Context(),
msg,
0,
+ nil,
check.MustAbs("/work"),
[]string{"HAKUREI_TEST=1"},
check.MustAbs("/opt/bin/testtool"),
@@ -99,6 +102,7 @@ func TestExec(t *testing.T) {
t.Context(),
msg,
0,
+ nil,
check.MustAbs("/work"),
nil,
check.MustAbs("/opt/bin/testtool"),
@@ -110,6 +114,47 @@ func TestExec(t *testing.T) {
testtoolDestroy(t, base, c)
}, pkg.MustDecode("7PoPpWLjFPXIymbuIYLZAzOpCYr-2PN4CZ11jFdO-mDlnZNgFO3JyOtK8HW8Jxvm")},
+
+ {"net", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ c.SetStrict(true)
+ testtool, testtoolDestroy := newTesttool()
+
+ msg := message.New(log.New(os.Stderr, "container: ", 0))
+ msg.SwapVerbose(testing.Verbose())
+
+ wantChecksum := pkg.MustDecode(
+ "a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W",
+ )
+ cureMany(t, c, []cureStep{
+ {"container", pkg.NewExec(
+ t.Context(),
+ msg,
+ 0,
+ &wantChecksum,
+ check.MustAbs("/work"),
+ []string{"HAKUREI_TEST=1"},
+ check.MustAbs("/opt/bin/testtool"),
+ []string{"testtool", "net"},
+
+ pkg.MustPath("/file", newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xfe, 0},
+ nil,
+ nil, nil,
+ )),
+ pkg.MustPath("/.hakurei", stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(c *pkg.CureContext) error {
+ return os.MkdirAll(c.GetWorkDir().String(), 0700)
+ },
+ }),
+ pkg.MustPath("/opt", testtool),
+ ), ignorePathname, wantChecksum, nil},
+ })
+
+ testtoolDestroy(t, base, c)
+ }, pkg.MustDecode("bBQVFIt0FnOulljgpLnGtuzHSFgwiCMjc4pmc4rHRqXKQ60Q5aBVYp5f6aH9VdZi")},
})
}
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go
index 00d6cf76..423aea12 100644
--- a/internal/pkg/pkg.go
+++ b/internal/pkg/pkg.go
@@ -185,6 +185,9 @@ const (
KindTar
// KindExec is the kind of [Artifact] returned by [NewExec].
KindExec
+ // KindExecNet is the kind of [Artifact] returned by [NewExec] but with a
+ // non-nil checksum.
+ KindExecNet
)
// Ident returns a deterministic identifier for the supplied params and
diff --git a/internal/pkg/testdata/main.go b/internal/pkg/testdata/main.go
index d63acdbd..2a0144f3 100644
--- a/internal/pkg/testdata/main.go
+++ b/internal/pkg/testdata/main.go
@@ -16,7 +16,13 @@ func main() {
log.SetFlags(0)
log.SetPrefix("testtool: ")
- if wantArgs := []string{"testtool"}; !slices.Equal(os.Args, wantArgs) {
+ var hostNet bool
+ wantArgs := []string{"testtool"}
+ if len(os.Args) == 2 {
+ hostNet = true
+ wantArgs = []string{"testtool", "net"}
+ }
+ if !slices.Equal(os.Args, wantArgs) {
log.Fatalf("Args: %q, want %q", os.Args, wantArgs)
}
if wantEnv := []string{
@@ -31,6 +37,22 @@ func main() {
log.Fatalf("Executable: %q, want %q", got, wantExec)
}
+ wantHostname := "cure"
+ if hostNet {
+ wantHostname += "-net"
+ }
+
+ if hostname, err := os.Hostname(); err != nil {
+ log.Fatalf("Hostname: error = %v", err)
+ } else if hostname != wantHostname {
+ log.Fatalf("Hostname: %q, want %q", hostname, wantHostname)
+ }
+
+ ident := "oHuqV7p0v1Vd8IdAzjyYM8sfCS0P2LR5tfv5cb6Gbf2ZWUm8Ec-7hYPJ_qr183m7"
+ if hostNet {
+ ident = "I3T53NtN6HPAyyodHtq2B0clcsoS1nPdvCEb-Zc5K-hoqFGL2od1mftHhwG7gX1S"
+ }
+
var m *vfs.MountInfo
if f, err := os.Open(fhs.Proc + "self/mountinfo"); err != nil {
log.Fatalf("Open: error = %v", err)
@@ -64,14 +86,12 @@ func main() {
next() // testtool artifact
next()
- if path.Base(m.Root) !=
- "oHuqV7p0v1Vd8IdAzjyYM8sfCS0P2LR5tfv5cb6Gbf2ZWUm8Ec-7hYPJ_qr183m7" || m.Target != "/work" {
+ if path.Base(m.Root) != ident || m.Target != "/work" {
log.Fatal("unexpected work mount entry")
}
next()
- if path.Base(m.Root) !=
- "oHuqV7p0v1Vd8IdAzjyYM8sfCS0P2LR5tfv5cb6Gbf2ZWUm8Ec-7hYPJ_qr183m7" || m.Target != "/tmp" {
+ if path.Base(m.Root) != ident || m.Target != "/tmp" {
log.Fatal("unexpected temp mount entry")
}
@@ -97,7 +117,11 @@ func main() {
os.Exit(1)
}
- if err := os.WriteFile("check", []byte{0}, 0400); err != nil {
+ checkData := []byte{0}
+ if hostNet {
+ checkData = []byte("net")
+ }
+ if err := os.WriteFile("check", checkData, 0400); err != nil {
log.Fatal(err)
}
}