package plan import ( "context" "encoding/json" "fmt" ) // Advisory is one security advisory from arch-audit. type Advisory struct { ID string `json:"id"` Severity string `json:"severity"` Status string `json:"status"` Kind string `json:"kind"` Fixed string `json:"fixed"` URL string `json:"url"` Packages []string `json:"packages"` Issues []string `json:"issues"` } // HasFix reports whether an update would resolve the advisory. func (a Advisory) HasFix() bool { return a.Fixed != "" } // DiffLine is one row of a diff. Kind is ctx, add, del or gap. type DiffLine struct { Kind string `json:"kind"` Old *int `json:"old"` New *int `json:"new"` Text string `json:"text"` } // Diff is a structured diff plus its counts. type Diff struct { Lines []DiffLine `json:"lines"` Added int `json:"added"` Removed int `json:"removed"` Truncated bool `json:"truncated"` } // Finding is one audit result. type Finding struct { Rule string `json:"rule"` Severity string `json:"severity"` Title string `json:"title"` Evidence string `json:"evidence"` Where string `json:"where"` } // Glyph mirrors the severity marker the rest of the tool uses. func (f Finding) Glyph() string { switch f.Severity { case "BLOCK": return "!!" case "LOOK": return "!" default: return "ยท" } } // Role maps a severity onto a palette role name. func (f Finding) Role() string { switch f.Severity { case "BLOCK": return "alert" case "LOOK": return "warn" default: return "muted" } } // Meta is what the AUR knows about a package. type Meta struct { Maintainer string `json:"maintainer"` Orphaned bool `json:"orphaned"` OutOfDate *int64 `json:"out_of_date"` Votes int `json:"votes"` Popularity float64 `json:"popularity"` FirstSubmitted int64 `json:"first_submitted"` LastModified int64 `json:"last_modified"` URL string `json:"url"` PackageBase string `json:"package_base"` Depends []string `json:"depends"` MakeDepends []string `json:"make_depends"` } // Detail is the lazy drill-down payload for one package. type Detail struct { Package string `json:"package"` Kind string `json:"kind"` Installed string `json:"installed"` Available string `json:"available"` BaselineCommit string `json:"baseline_commit"` BaselineHow string `json:"baseline_how"` BaselineNote string `json:"baseline_note"` CurrentCommit string `json:"current_commit"` CurrentCommitSummary string `json:"current_commit_summary"` CurrentCommitWhen string `json:"current_commit_when"` Diff Diff `json:"diff"` Findings []Finding `json:"findings"` Verdict string `json:"verdict"` Meta Meta `json:"meta"` Advisories []Advisory `json:"advisories"` Pkgbuild []string `json:"pkgbuild"` Ignored bool `json:"ignored"` //: the available version is older than the installed one Downgrade bool `json:"downgrade"` } // BaselineLabel says what the diff was compared against, in the user's terms. func (d *Detail) BaselineLabel() string { switch d.BaselineHow { case "recorded": return "vs last built (" + short(d.BaselineCommit) + ")" case "version": return fmt.Sprintf("vs revision matching installed %s (%s)", d.Installed, short(d.BaselineCommit)) default: return "no baseline" } } func short(sha string) string { if len(sha) > 8 { return sha[:8] } return sha } // LoadDetail fetches the drill-down for one package. func (b Backend) LoadDetail(ctx context.Context, pkg string) (*Detail, error) { args := append([]string{"show", pkg, "--json"}, b.Args...) out, err := b.Run(ctx, args...) if err != nil { return nil, err } var d Detail if err := json.Unmarshal(out, &d); err != nil { return nil, fmt.Errorf("could not parse detail for %s: %w", pkg, err) } return &d, nil } // AdvisoryDetail is the security tracker's own record for one advisory. type AdvisoryDetail struct { ID string `json:"id"` Available bool `json:"available"` Reason string `json:"reason"` Packages []string `json:"packages"` Status string `json:"status"` Severity string `json:"severity"` Kind string `json:"kind"` Affected string `json:"affected"` Fixed string `json:"fixed"` Ticket string `json:"ticket"` Issues []string `json:"issues"` Advisories []string `json:"advisories"` References []string `json:"references"` Notes string `json:"notes"` URL string `json:"url"` } // SecurityReport is a fresh arch-audit run. type SecurityReport struct { Available bool `json:"available"` Reason string `json:"reason"` Total int `json:"total"` Fixable int `json:"fixable"` Unfixable int `json:"unfixable"` Advisories []Advisory `json:"advisories"` } // LoadAdvisory fetches one advisory from the tracker. func (b Backend) LoadAdvisory(ctx context.Context, id string) (*AdvisoryDetail, error) { out, err := b.Run(ctx, "advisory", id, "--json") if err != nil { return nil, err } var detail AdvisoryDetail if err := json.Unmarshal(out, &detail); err != nil { return nil, fmt.Errorf("could not parse the advisory for %s: %w", id, err) } return &detail, nil } // RunSecurity runs arch-audit again, now. func (b Backend) RunSecurity(ctx context.Context) (*SecurityReport, error) { out, err := b.Run(ctx, "security", "--json") if err != nil { return nil, err } var report SecurityReport if err := json.Unmarshal(out, &report); err != nil { return nil, fmt.Errorf("could not parse the security run: %w", err) } return &report, nil }