Files
audita/internal/cli/process_inputs.go

149 lines
5.4 KiB
Go
Raw Normal View History

package cli
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"path/filepath"
"time"
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
"gitea.maximumdirect.net/eric/audita/v3/internal/core/diagnostics"
coreio "gitea.maximumdirect.net/eric/audita/v3/internal/core/io"
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/preflight"
"go.maximumdirect.net/ckconfig"
commandkit "go.maximumdirect.net/commandkit"
ck "go.maximumdirect.net/configkit"
)
type processInputs struct {
Configuration *ck.Result[config.Config]
Runtime *llm.Runtime
Source *schema.SourceTranscript
RawTranscript []byte
ProtectedPaths []string
}
func loadProcessInputs(ctx context.Context, input processInput, mode commandkit.ReportingMode) (*processInputs, error) {
if mode == commandkit.ReportingModeQuiet && input.Output.Value == "" {
return nil, fmt.Errorf("quiet processing requires an output file or /dev/null")
}
selection := ckconfig.Selection{Config: input.Config, Overlay: input.Overlay, Overrides: input.Overrides}
loaded, err := loadApplication(ctx, selection, struct{}{}, nil)
if err != nil {
return nil, err
}
runtime, err := llm.NewRuntime(ctx, loaded, nil)
if err != nil {
return nil, err
}
raw, err := coreio.ReadRequiredFile(input.Transcript, "transcript")
if err != nil {
return nil, err
}
source, err := schema.ParseSourceTranscriptJSON(raw)
if err != nil {
return nil, err
}
protected := append([]string(nil), runtime.SourcePaths...)
for _, entry := range loaded.Sources() {
if entry.Kind == ck.SourceKindFile && entry.Path != "" {
protected = append(protected, entry.Path)
}
}
return &processInputs{Configuration: loaded, Runtime: runtime, Source: source, RawTranscript: raw, ProtectedPaths: protected}, nil
}
// preparedProcess groups the output resources and the facts needed for reports.
type preparedProcess struct {
Prepared *preflight.Prepared
RunID string
Started time.Time
}
func prepareProcess(ctx context.Context, input processInput, loaded *processInputs, stdout io.Writer) (*preparedProcess, error) {
cfg := &loaded.Configuration.Config
reportDir := ""
reportDirSet := false
for _, override := range input.Overrides {
if override.Path.String() == "/reporting/stdout_dir" {
reportDirSet = true
}
}
if cfg.Reporting.Enabled {
reportDir = cfg.Reporting.StdoutDir
if input.Output.Value != "" && !reportDirSet {
reportDir = filepath.Dir(input.Output.Value)
}
}
runID, err := generateRunID()
if err != nil {
return nil, err
}
started := time.Now().UTC()
snapshot := reporting.Snapshot{RunID: runID, Status: reporting.StatusInProgress, StartedAt: started, TranscriptPath: input.Transcript, OutputPath: input.Output.Value}
for index, item := range cfg.Pipeline.Stages {
snapshot.StageNames = append(snapshot.StageNames, reporting.StageLabel(index, item.Module, item.ID))
}
secrets := loaded.Runtime.ConfiguredSecrets(ctx)
initial, err := renderReportBundle(snapshot, secrets...)
if err != nil {
return nil, err
}
snapshot.Status = reporting.StatusFailed
failed, err := renderReportBundle(snapshot, secrets...)
if err != nil {
return nil, err
}
var debug *preflight.DebugInput
var warnings []error
if input.Debug.Value {
var effective bytes.Buffer
if err := loaded.Configuration.WriteEffective(&effective); err != nil {
warnings = append(warnings, err)
}
debug = &preflight.DebugInput{WorkDir: cfg.Diagnostics.WorkDir, RunID: runID, Secrets: secrets, EffectiveConfig: effective.Bytes(), Metadata: diagnostics.InvocationMetadata{Operation: "process", TranscriptPath: input.Transcript, OutputPath: input.Output.Value, Overlay: input.Overlay.Value, ConfigVersion: &cfg.Version, RunID: runID, StartedAt: started}}
}
prepared, err := preflight.Prepare(ctx, preflight.Input{InspectProfile: func(ctx context.Context, id string) error {
profile, err := loaded.Runtime.Engine.InspectProfile(ctx, id)
if err != nil {
return err
}
if profile.Backend != nil {
_, err = loaded.Runtime.Engine.InspectBackend(ctx, *profile.Backend)
}
return err
}, Config: cfg, Transcript: loaded.Source, Output: diagnostics.OutputSelection{InputTranscriptPath: input.Transcript, TranscriptPath: input.Output.Value, ReportDir: reportDir, ReportingEnabled: cfg.Reporting.Enabled, ProtectedPaths: loaded.ProtectedPaths, Stdout: stdout}, InitialReports: initial, FailedReports: failed, Debug: debug})
if err != nil {
return nil, err
}
prepared.Warnings = append(warnings, prepared.Warnings...)
return &preparedProcess{Prepared: prepared, RunID: runID, Started: started}, nil
}
func generateRunID() (string, error) {
var random [12]byte
if _, err := rand.Read(random[:]); err != nil {
return "", err
}
return "run-" + hex.EncodeToString(random[:]), nil
}
func renderReportBundle(value reporting.Snapshot, secrets ...string) (diagnostics.ReportBundle, error) {
errorsYAML, err := reporting.YAML(value.ErrorsDocument(), secrets...)
if err != nil {
return diagnostics.ReportBundle{}, err
}
recoveriesYAML, err := reporting.YAML(value.RecoveriesDocument(), secrets...)
if err != nil {
return diagnostics.ReportBundle{}, err
}
return diagnostics.ReportBundle{Errors: errorsYAML, Recoveries: recoveriesYAML, Markdown: reporting.Markdown(value, secrets...)}, nil
}