package app import ( "fmt" "io" "strings" "gitea.maximumdirect.net/eric/distributor/internal/config" "gitea.maximumdirect.net/eric/distributor/internal/publish" "gitea.maximumdirect.net/eric/distributor/internal/storage" ) func WriteRunReport(w io.Writer, format OutputFormat, report RunReport) error { if IsJSONOutput(format) { return WriteJSONEnvelope(w, "run", len(report.OutputErrors) == 0, report.Warnings, report, report.OutputErrors) } return writeRunReportText(w, report) } func writeRunReportText(w io.Writer, report RunReport) error { if w == nil { return nil } if err := writeWarnings(w, report.PreambleWarnings); err != nil { return err } if _, err := fmt.Fprintf(w, "Configured pipelines: %d\n", len(report.Pipelines)); err != nil { return err } for _, pipeline := range report.Pipelines { if err := writeWarnings(w, pipeline.Warnings); err != nil { return err } if _, err := fmt.Fprintf(w, "- pipeline=%s source=%s bundles=%d destinations=%s\n", pipeline.ID, pipeline.SourceBackend, pipeline.BundleCount, destinationIDSummary(pipeline.Destinations)); err != nil { return err } for _, event := range pipeline.events { if event.warning != nil { if err := writeWarnings(w, []OutputWarning{*event.warning}); err != nil { return err } continue } if event.actionIndex < 0 || event.actionIndex >= len(report.Actions) { continue } writeRunActionLine(w, report.Actions[event.actionIndex]) } } _, err := fmt.Fprintln(w, report.Summary.Line()) return err } func writeRunActionLine(w io.Writer, action RunActionRecord) { if action.Action == "error" { destinationID := action.DestinationID if destinationID == "" { destinationID = "unknown" } fmt.Fprintf(w, " - bundle=%s destination=%s backend=%s%s action=error reason=%q\n", action.BundlePath, destinationID, action.Backend, pathMappingRecordSummary(action), action.Reason) return } fmt.Fprintf(w, " - bundle=%s destination=%s backend=%s%s action=%s%s outputs=%s reason=%q\n", action.BundlePath, action.DestinationID, action.Backend, pathMappingRecordSummary(action), action.Action, workflowRecordSummary(action), outputRecordSummary(action.Outputs), action.Reason) } func pathMappingRecordSummary(action RunActionRecord) string { if action.PathMapping != config.PathMappingFixed { return "" } return fmt.Sprintf(" path_mapping=fixed target=%s", action.DestinationPath) } func workflowRecordSummary(action RunActionRecord) string { if action.Workflow == "" { return "" } return fmt.Sprintf(" workflow=%s", action.Workflow) } func outputRecordSummary(outputs []RunOutputRecord) string { if len(outputs) == 0 { return "none" } paths := make([]string, 0, len(outputs)) for _, output := range outputs { paths = append(paths, output.Path) } return strings.Join(paths, ",") } func destinationIDSummary(ids []string) string { if len(ids) == 0 { return "none" } return strings.Join(ids, ",") } type RunReport struct { DryRun bool `json:"dry_run"` Pipelines []RunPipelineSummary `json:"pipelines"` Actions []RunActionRecord `json:"actions"` Summary RunSummaryCounters `json:"summary"` Warnings []OutputWarning `json:"-"` OutputErrors []OutputError `json:"-"` PreambleWarnings []OutputWarning `json:"-"` } func (r *RunReport) addWarning(warning OutputWarning) { r.Warnings = append(r.Warnings, warning) } func (r *RunReport) addWarnings(warnings []OutputWarning) { r.Warnings = append(r.Warnings, warnings...) } type RunPipelineSummary struct { ID string `json:"id"` SourceBackend string `json:"source_backend"` BundleCount int `json:"bundle_count"` Destinations []string `json:"destinations"` Warnings []OutputWarning `json:"-"` events []runPipelineEvent } type runPipelineEvent struct { warning *OutputWarning actionIndex int } func warningEvent(warning OutputWarning) runPipelineEvent { return runPipelineEvent{warning: &warning, actionIndex: -1} } func actionEvent(actionIndex int) runPipelineEvent { return runPipelineEvent{actionIndex: actionIndex} } type RunActionRecord struct { PipelineID string `json:"pipeline_id,omitempty"` DestinationID string `json:"destination_id"` Backend string `json:"backend"` BundleID string `json:"bundle_id,omitempty"` BundlePath string `json:"bundle_path"` DestinationPath string `json:"destination_path"` PathMapping string `json:"path_mapping,omitempty"` Workflow string `json:"workflow,omitempty"` Action string `json:"action"` PrimaryURL string `json:"primary_url,omitempty"` Reason string `json:"reason,omitempty"` Outputs []RunOutputRecord `json:"outputs"` } type RunOutputRecord struct { Path string `json:"path"` Kind string `json:"kind"` SourcePath string `json:"source_path,omitempty"` Transform string `json:"transform,omitempty"` URL string `json:"url,omitempty"` SHA256 string `json:"sha256"` Size int64 `json:"size"` } func runActionFromPlan(backend string, plan publish.Plan, planErr error) RunActionRecord { if planErr != nil { destinationID := plan.DestinationID if destinationID == "" { destinationID = "unknown" } action := "error" outputs := []RunOutputRecord{} switch plan.Action { case publish.ActionFailUnmanaged, publish.ActionFailConflict: action = string(plan.Action) outputs = runOutputsFromPlan(plan.Outputs) } return RunActionRecord{ PipelineID: plan.PipelineID, DestinationID: destinationID, Backend: backend, BundleID: plan.BundleID, BundlePath: storage.DisplayPath(plan.BundlePath), DestinationPath: storage.DisplayPath(plan.DestinationBundlePath), PathMapping: plan.PathMapping, Workflow: plan.Workflow, Action: action, PrimaryURL: plan.PrimaryURL, Reason: planErr.Error(), Outputs: outputs, } } return RunActionRecord{ PipelineID: plan.PipelineID, DestinationID: plan.DestinationID, Backend: backend, BundleID: plan.BundleID, BundlePath: storage.DisplayPath(plan.BundlePath), DestinationPath: storage.DisplayPath(plan.DestinationBundlePath), PathMapping: plan.PathMapping, Workflow: plan.Workflow, Action: string(plan.Action), PrimaryURL: plan.PrimaryURL, Reason: plan.Reason, Outputs: runOutputsFromPlan(plan.Outputs), } } func errorAction(pipelineID, destinationID, backend, bundlePath string, err error) RunActionRecord { return RunActionRecord{ PipelineID: pipelineID, DestinationID: destinationID, Backend: backend, BundlePath: storage.DisplayPath(bundlePath), DestinationPath: storage.DisplayPath(bundlePath), Action: "error", Reason: err.Error(), Outputs: []RunOutputRecord{}, } } func runOutputsFromPlan(outputs []publish.Output) []RunOutputRecord { results := make([]RunOutputRecord, 0, len(outputs)) for _, output := range outputs { stateOutput := output.StateOutputFile() results = append(results, RunOutputRecord{ Path: stateOutput.Path, Kind: stateOutput.Kind, SourcePath: stateOutput.SourcePath, Transform: stateOutput.Transform, URL: stateOutput.URL, SHA256: stateOutput.SHA256, Size: stateOutput.Size, }) } return results }