80 lines
2.4 KiB
Go
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
|
|
}
|