Files
knowledge-governance/internal/qaopsorg/directory_runner.go
T

80 lines
2.4 KiB
Go

package qaopsorg
import (
"context"
"encoding/json"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"time"
)
var ErrDirectorySync = errors.New("directory synchronization failed; active snapshot was not replaced")
type ExecDirectoryRunner struct {
Python string
Script string
RuntimeRoot string
SpoolRoot string
Timeout time.Duration
}
// Only server-owned paths cross this boundary. Credentials are read by the
// existing directory client from its private file, never passed in argv/logs.
func (r ExecDirectoryRunner) Run(ctx context.Context) (Import, error) {
for _, path := range []string{r.Python, r.Script, r.RuntimeRoot, r.SpoolRoot} {
if !filepath.IsAbs(path) || filepath.Clean(path) != path {
return Import{}, ErrDirectorySync
}
}
if r.Timeout == 0 {
r.Timeout = 20 * time.Minute
}
if r.Timeout < time.Millisecond || r.Timeout > 30*time.Minute {
return Import{}, ErrDirectorySync
}
work, err := os.MkdirTemp(r.SpoolRoot, "qa-directory-")
if err != nil {
return Import{}, ErrDirectorySync
}
defer os.RemoveAll(work)
output := filepath.Join(work, "directory.json")
runCtx, cancel := context.WithTimeout(ctx, r.Timeout)
defer cancel()
cmd := exec.CommandContext(runCtx, r.Python, r.Script, "--runtime-root", r.RuntimeRoot, "--output", output, "--fetch-live-directory")
cmd.Dir = r.RuntimeRoot
cmd.Env = []string{"PYTHONUNBUFFERED=1"}
for _, key := range []string{"PATH", "HOME", "TMPDIR", "LANG", "TZ"} {
if value, ok := os.LookupEnv(key); ok {
cmd.Env = append(cmd.Env, key+"="+value)
}
}
cmd.Stdout = io.Discard
cmd.Stderr = io.Discard
cmd.WaitDelay = 3 * time.Second
if err = cmd.Run(); err != nil {
return Import{}, ErrDirectorySync
}
info, err := os.Lstat(output)
if err != nil || !info.Mode().IsRegular() || info.Mode().Perm()&0077 != 0 || info.Size() < 1 || info.Size() > 16<<20 {
return Import{}, ErrDirectorySync
}
file, err := os.Open(output)
if err != nil {
return Import{}, ErrDirectorySync
}
defer file.Close()
decoder := json.NewDecoder(io.LimitReader(file, (16<<20)+1))
decoder.DisallowUnknownFields()
var result Import
if decoder.Decode(&result) != nil || decoder.Decode(&struct{}{}) != io.EOF || !result.Complete || result.Source != "weekly_report_live" || result.SyncedAt.IsZero() || result.SyncedAt.After(time.Now().Add(time.Minute)) {
return Import{}, ErrDirectorySync
}
if _, err = Preview(result.Directory, nil); err != nil {
return Import{}, ErrDirectorySync
}
return result, nil
}