diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-03-26 15:00:25 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-03-26 15:05:04 +0900 |
| commit | e661260607573009d0f7b0cbcbb8ee561fc08f3f (patch) | |
| tree | 8a4f1393ed289acc023add65ba42eaf7b701b4f6 /internal/pkg/exec.go | |
| parent | 044490e0a521036ce10fed0b6813a8ff912237b5 (diff) | |
internal/pkg: enter exec container
This enables much easier troubleshooting of failing cures.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pkg/exec.go')
| -rw-r--r-- | internal/pkg/exec.go | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go index 244b353b..f1385529 100644 --- a/internal/pkg/exec.go +++ b/internal/pkg/exec.go @@ -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) |
