forked from rosa/hakurei
internal/pkg: enter exec container
This enables much easier troubleshooting of failing cures. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -491,6 +491,104 @@ func (a *execArtifact) makeContainer(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrExecBusy is returned entering [Cache.EnterExec] while another
|
||||||
|
// goroutine has not yet returned from it.
|
||||||
|
ErrExecBusy = errors.New("scratch directories in use")
|
||||||
|
// ErrNotExec is returned for unsupported implementations of [Artifact]
|
||||||
|
// passed to [Cache.EnterExec].
|
||||||
|
ErrNotExec = errors.New("attempting to run a non-exec artifact")
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnterExec runs the container of an [Artifact] of [KindExec] or [KindExecNet]
|
||||||
|
// with its entry point, argument, and standard streams replaced with values
|
||||||
|
// supplied by the caller.
|
||||||
|
func (c *Cache) EnterExec(
|
||||||
|
ctx context.Context,
|
||||||
|
a Artifact,
|
||||||
|
retainSession bool,
|
||||||
|
stdin io.Reader,
|
||||||
|
stdout, stderr io.Writer,
|
||||||
|
path *check.Absolute,
|
||||||
|
args ...string,
|
||||||
|
) (err error) {
|
||||||
|
if !c.inExec.CompareAndSwap(false, true) {
|
||||||
|
return ErrExecBusy
|
||||||
|
}
|
||||||
|
defer c.inExec.Store(false)
|
||||||
|
|
||||||
|
var hostNet bool
|
||||||
|
var e *execArtifact
|
||||||
|
switch f := a.(type) {
|
||||||
|
case *execArtifact:
|
||||||
|
e = f
|
||||||
|
|
||||||
|
case *execNetArtifact:
|
||||||
|
e = &f.execArtifact
|
||||||
|
hostNet = true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ErrNotExec
|
||||||
|
}
|
||||||
|
|
||||||
|
deps := Collect(a.Dependencies())
|
||||||
|
if _, _, err = c.Cure(&deps); err == nil {
|
||||||
|
return errors.New("unreachable")
|
||||||
|
} else if !IsCollected(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dm := make(map[Artifact]cureRes)
|
||||||
|
for i, p := range deps {
|
||||||
|
var res cureRes
|
||||||
|
res.pathname, res.checksum, err = c.Cure(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dm[deps[i]] = res
|
||||||
|
}
|
||||||
|
|
||||||
|
scratch := c.base.Append(dirExecScratch)
|
||||||
|
temp, work := scratch.Append("temp"), scratch.Append("work")
|
||||||
|
// work created during makeContainer
|
||||||
|
if err = os.MkdirAll(temp.String(), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if chmodErr, removeErr := removeAll(scratch); chmodErr != nil || removeErr != nil {
|
||||||
|
err = errors.Join(err, chmodErr, removeErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var z *container.Container
|
||||||
|
z, err = e.makeContainer(
|
||||||
|
ctx, c.msg,
|
||||||
|
hostNet,
|
||||||
|
temp, work,
|
||||||
|
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
||||||
|
if res, ok := dm[a]; ok {
|
||||||
|
return res.pathname, res.checksum
|
||||||
|
}
|
||||||
|
panic(InvalidLookupError(c.Ident(a).Value()))
|
||||||
|
},
|
||||||
|
c.Ident,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Stdin, z.Stdout, z.Stderr = stdin, stdout, stderr
|
||||||
|
z.Path, z.Args = path, args
|
||||||
|
z.RetainSession = retainSession
|
||||||
|
|
||||||
|
if err = z.Start(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = z.Serve(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return z.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// cure is like Cure but allows optional host net namespace.
|
// cure is like Cure but allows optional host net namespace.
|
||||||
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
||||||
ctx, cancel := context.WithTimeout(f.Unwrap(), a.timeout)
|
ctx, cancel := context.WithTimeout(f.Unwrap(), a.timeout)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"unique"
|
"unique"
|
||||||
@@ -466,6 +467,11 @@ const (
|
|||||||
// pathnames allocated during [Cache.Cure].
|
// pathnames allocated during [Cache.Cure].
|
||||||
dirTemp = "temp"
|
dirTemp = "temp"
|
||||||
|
|
||||||
|
// dirExecScratch is the directory name appended to Cache.base for scratch
|
||||||
|
// space setting up the container started by [Cache.EnterExec]. Exclusivity
|
||||||
|
// via Cache.inExec.
|
||||||
|
dirExecScratch = "scratch"
|
||||||
|
|
||||||
// checksumLinknamePrefix is prepended to the encoded [Checksum] value
|
// checksumLinknamePrefix is prepended to the encoded [Checksum] value
|
||||||
// of an [Artifact] when creating a symbolic link to dirChecksum.
|
// of an [Artifact] when creating a symbolic link to dirChecksum.
|
||||||
checksumLinknamePrefix = "../" + dirChecksum + "/"
|
checksumLinknamePrefix = "../" + dirChecksum + "/"
|
||||||
@@ -481,7 +487,7 @@ type cureRes struct {
|
|||||||
// subject to the cures limit. Values pointed to by result addresses are safe
|
// subject to the cures limit. Values pointed to by result addresses are safe
|
||||||
// to access after the [sync.WaitGroup] associated with this pendingArtifactDep
|
// to access after the [sync.WaitGroup] associated with this pendingArtifactDep
|
||||||
// is done. pendingArtifactDep must not be reused or modified after it is sent
|
// is done. pendingArtifactDep must not be reused or modified after it is sent
|
||||||
// to Cache.cureDep.
|
// to cure.
|
||||||
type pendingArtifactDep struct {
|
type pendingArtifactDep struct {
|
||||||
// Dependency artifact populated during [Cache.Cure].
|
// Dependency artifact populated during [Cache.Cure].
|
||||||
a Artifact
|
a Artifact
|
||||||
@@ -553,6 +559,9 @@ type Cache struct {
|
|||||||
unlock func()
|
unlock func()
|
||||||
// Synchronises calls to Close.
|
// Synchronises calls to Close.
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
|
|
||||||
|
// Whether EnterExec has not yet returned.
|
||||||
|
inExec atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsStrict returns whether the [Cache] strictly verifies checksums.
|
// IsStrict returns whether the [Cache] strictly verifies checksums.
|
||||||
|
|||||||
Reference in New Issue
Block a user