mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* update the go.sum Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> * migrate most of the files to dry errors Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> * update the rest of the files Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> * fixs the review comments Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> * chores Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> --------- Signed-off-by: DivyanshuVortex <divyanshuchandra9027@gmail.com> Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
189 lines
5.0 KiB
Go
189 lines
5.0 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/safedep/pmg/internal/ui"
|
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
|
"github.com/safedep/dry/usefulerror"
|
|
"github.com/safedep/pmg/errcodes"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const ExitCodeViolationsListFail = 2
|
|
|
|
type violationsListOptions struct {
|
|
limit int
|
|
jsonOut bool
|
|
}
|
|
|
|
type violationsListFailError struct {
|
|
usefulerror.UsefulError
|
|
}
|
|
|
|
func (e *violationsListFailError) ExitCode() int { return ExitCodeViolationsListFail }
|
|
|
|
func newViolationsListFailError(code, msg, help string) *violationsListFailError {
|
|
return &violationsListFailError{
|
|
UsefulError: usefulerror.NewUsefulError().
|
|
WithCode(code).
|
|
WithHumanError(msg).
|
|
WithHelp(help).
|
|
Wrap(errors.New(msg)),
|
|
}
|
|
}
|
|
|
|
func newViolationsListCommand(factory cacheFactory) *cobra.Command {
|
|
opts := &violationsListOptions{}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List cached sandbox violations as one-line summaries",
|
|
Example: " pmg sandbox violations list --limit 20",
|
|
SilenceErrors: false,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
err := runViolationsList(cmd.OutOrStdout(), cmd.ErrOrStderr(), opts, factory)
|
|
if err != nil {
|
|
return sandboxErrorExit(cmd, err)
|
|
}
|
|
return err
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVar(&opts.limit, "limit", 10, "Maximum number of entries to show (0 means all)")
|
|
cmd.Flags().BoolVar(&opts.jsonOut, "json", false, "Emit entries as JSON")
|
|
return cmd
|
|
}
|
|
|
|
func runViolationsList(out, errOut io.Writer, opts *violationsListOptions, factory cacheFactory) error {
|
|
if opts.limit < 0 {
|
|
return newViolationsListFailError(
|
|
errcodes.InvalidArgument,
|
|
fmt.Sprintf("invalid --limit %d (must be >= 0)", opts.limit),
|
|
"Pass --limit 0 to show all entries, or a positive limit.",
|
|
)
|
|
}
|
|
|
|
cache := factory()
|
|
entries, err := cache.List()
|
|
if err != nil {
|
|
return newViolationsListFailError(
|
|
errcodes.Unknown,
|
|
fmt.Sprintf("read cache: %v", err),
|
|
"Check the sandbox violation cache directory and retry.",
|
|
)
|
|
}
|
|
|
|
if opts.limit > 0 && len(entries) > opts.limit {
|
|
entries = entries[:opts.limit]
|
|
}
|
|
|
|
if opts.jsonOut {
|
|
return writeViolationsListJSON(out, entries)
|
|
}
|
|
|
|
if len(entries) == 0 {
|
|
_, err := fmt.Fprintln(errOut, "no violations cached")
|
|
return err
|
|
}
|
|
|
|
return renderViolationsListTable(out, entries)
|
|
}
|
|
|
|
func renderViolationsListTable(out io.Writer, entries []pmgsandbox.ViolationCacheEntry) error {
|
|
rows := make([][]string, 0, len(entries)+1)
|
|
rows = append(rows, []string{
|
|
ui.Colors.Bold("RECORDED"),
|
|
ui.Colors.Bold("SANDBOX"),
|
|
ui.Colors.Bold("PROFILE"),
|
|
ui.Colors.Bold("KIND"),
|
|
ui.Colors.Bold("TARGET"),
|
|
})
|
|
|
|
dash := ui.Colors.Dim("—")
|
|
for _, e := range entries {
|
|
recorded := ""
|
|
if !e.Record.RecordedAt.IsZero() {
|
|
recorded = e.Record.RecordedAt.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
sandboxName := ""
|
|
profile := ""
|
|
if e.Record.Report != nil {
|
|
sandboxName = string(e.Record.Report.SandboxName)
|
|
profile = e.Record.Report.PolicyName
|
|
}
|
|
|
|
kind := dash
|
|
target := dash
|
|
if e.Record.Report != nil {
|
|
primary := pmgsandbox.BuildExplanation(e.Record.Report).Primary
|
|
if primary != nil {
|
|
if primary.Kind != "" {
|
|
kind = string(primary.Kind)
|
|
}
|
|
if primary.Target != "" {
|
|
target = truncate(primary.Target, 60)
|
|
}
|
|
}
|
|
}
|
|
|
|
rows = append(rows, []string{recorded, sandboxName, profile, kind, target})
|
|
}
|
|
|
|
return renderTable(out, rows, nil)
|
|
}
|
|
|
|
type violationsListJSONPrimary struct {
|
|
Kind string `json:"kind"`
|
|
Target string `json:"target,omitempty"`
|
|
RuleLabel string `json:"rule_label,omitempty"`
|
|
}
|
|
|
|
type violationsListJSONEntry struct {
|
|
Path string `json:"path"`
|
|
RecordedAt string `json:"recorded_at,omitempty"`
|
|
SandboxName string `json:"sandbox_name,omitempty"`
|
|
PolicyName string `json:"policy_name,omitempty"`
|
|
Primary *violationsListJSONPrimary `json:"primary,omitempty"`
|
|
ViolationCount int `json:"violation_count"`
|
|
}
|
|
|
|
type violationsListJSONOutput struct {
|
|
Entries []violationsListJSONEntry `json:"entries"`
|
|
}
|
|
|
|
func writeViolationsListJSON(out io.Writer, entries []pmgsandbox.ViolationCacheEntry) error {
|
|
payload := violationsListJSONOutput{Entries: make([]violationsListJSONEntry, 0, len(entries))}
|
|
|
|
for _, e := range entries {
|
|
item := violationsListJSONEntry{Path: e.Path}
|
|
if !e.Record.RecordedAt.IsZero() {
|
|
item.RecordedAt = e.Record.RecordedAt.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
if e.Record.Report != nil {
|
|
item.SandboxName = string(e.Record.Report.SandboxName)
|
|
item.PolicyName = e.Record.Report.PolicyName
|
|
item.ViolationCount = len(e.Record.Report.Violations)
|
|
|
|
primary := pmgsandbox.BuildExplanation(e.Record.Report).Primary
|
|
if primary != nil {
|
|
item.Primary = &violationsListJSONPrimary{
|
|
Kind: string(primary.Kind),
|
|
Target: primary.Target,
|
|
RuleLabel: primary.RuleLabel,
|
|
}
|
|
}
|
|
}
|
|
|
|
payload.Entries = append(payload.Entries, item)
|
|
}
|
|
|
|
return writeJSONIndent(out, payload)
|
|
}
|