internal/pkg: enter exec container
All checks were successful
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m38s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m22s
All checks were successful
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m38s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 5m21s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m22s
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
|
||||
}
|
||||
|
||||
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.
|
||||
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
||||
ctx, cancel := context.WithTimeout(f.Unwrap(), a.timeout)
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unique"
|
||||
@@ -466,6 +467,11 @@ const (
|
||||
// pathnames allocated during [Cache.Cure].
|
||||
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
|
||||
// of an [Artifact] when creating a symbolic link to dirChecksum.
|
||||
checksumLinknamePrefix = "../" + dirChecksum + "/"
|
||||
@@ -481,7 +487,7 @@ type cureRes struct {
|
||||
// subject to the cures limit. Values pointed to by result addresses are safe
|
||||
// to access after the [sync.WaitGroup] associated with this pendingArtifactDep
|
||||
// is done. pendingArtifactDep must not be reused or modified after it is sent
|
||||
// to Cache.cureDep.
|
||||
// to cure.
|
||||
type pendingArtifactDep struct {
|
||||
// Dependency artifact populated during [Cache.Cure].
|
||||
a Artifact
|
||||
@@ -553,6 +559,9 @@ type Cache struct {
|
||||
unlock func()
|
||||
// Synchronises calls to Close.
|
||||
closeOnce sync.Once
|
||||
|
||||
// Whether EnterExec has not yet returned.
|
||||
inExec atomic.Bool
|
||||
}
|
||||
|
||||
// IsStrict returns whether the [Cache] strictly verifies checksums.
|
||||
|
||||
Reference in New Issue
Block a user