mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Add Sandbox Inspection and Debugging Commands (#261)
* feat: add sandbox DX commands * fix: Linter errors * fix: Sandbox deny log parsing * fix: Sandbox docs * refactor: Maintain SSOT across pkg dependencies * fix: Linter errors
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||
"github.com/safedep/pmg/usefulerror"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const ExitCodeProbeFailure = 2
|
||||
|
||||
var sandboxErrorExit = func(_ *cobra.Command, err error) error {
|
||||
type exitCoder interface{ ExitCode() int }
|
||||
if ec, ok := err.(exitCoder); ok {
|
||||
ui.ErrorExitWithCode(err, ec.ExitCode())
|
||||
return nil
|
||||
}
|
||||
|
||||
ui.ErrorExit(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var validDrivers = map[pmgsandbox.DriverName]struct{}{
|
||||
pmgsandbox.DriverSeatbelt: {},
|
||||
pmgsandbox.DriverBubblewrap: {},
|
||||
pmgsandbox.DriverLandlock: {},
|
||||
}
|
||||
|
||||
func validateDriver(name string) error {
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
if _, ok := validDrivers[pmgsandbox.DriverName(name)]; !ok {
|
||||
return invalidArgumentError(
|
||||
fmt.Sprintf("unknown driver %q", name),
|
||||
"Use one of: seatbelt, bubblewrap, landlock",
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func invalidArgumentError(message, help string) error {
|
||||
return usefulerror.Useful().
|
||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
||||
WithHumanError(message).
|
||||
WithHelp(help).
|
||||
Wrap(errors.New(message))
|
||||
}
|
||||
|
||||
func notFoundError(message, help string) error {
|
||||
return usefulerror.Useful().
|
||||
WithCode(usefulerror.ErrCodeNotFound).
|
||||
WithHumanError(message).
|
||||
WithHelp(help).
|
||||
Wrap(errors.New(message))
|
||||
}
|
||||
|
||||
func writeJSONIndent(out io.Writer, v any) error {
|
||||
enc := json.NewEncoder(out)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(v)
|
||||
}
|
||||
|
||||
// renderTable prints rows with ANSI-aware column alignment. The first row is
|
||||
// treated as a header. After each data row, optional continuation lines may be
|
||||
// emitted via the after callback (passed the data row index; -1 for header).
|
||||
func renderTable(out io.Writer, rows [][]string, after func(rowIdx int) error) error {
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
cols := len(rows[0])
|
||||
widths := make([]int, cols)
|
||||
for _, row := range rows {
|
||||
for i, cell := range row {
|
||||
if w := visibleWidth(cell); w > widths[i] {
|
||||
widths[i] = w
|
||||
}
|
||||
}
|
||||
}
|
||||
for rIdx, row := range rows {
|
||||
for i, cell := range row {
|
||||
if i == cols-1 {
|
||||
if _, err := fmt.Fprint(out, cell); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
pad := widths[i] - visibleWidth(cell)
|
||||
if _, err := fmt.Fprint(out, cell, strings.Repeat(" ", pad+2)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := fmt.Fprintln(out); err != nil {
|
||||
return err
|
||||
}
|
||||
if after != nil {
|
||||
dataIdx := rIdx - 1
|
||||
if err := after(dataIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// firstColumnIndent returns blanks the width of the first column plus the
|
||||
// two-space padding renderTable uses, for continuation-line alignment.
|
||||
func firstColumnIndent(rows [][]string) string {
|
||||
if len(rows) == 0 {
|
||||
return ""
|
||||
}
|
||||
w := 0
|
||||
for _, row := range rows {
|
||||
if v := visibleWidth(row[0]); v > w {
|
||||
w = v
|
||||
}
|
||||
}
|
||||
return strings.Repeat(" ", w+2)
|
||||
}
|
||||
|
||||
var ansiEscapeRe = regexp.MustCompile(`\x1b\[[0-9;]*[A-Za-z]`)
|
||||
|
||||
// visibleWidth returns the printable width of s with ANSI escape sequences
|
||||
// stripped — text/tabwriter counts escape bytes as visible chars, misaligning
|
||||
// colored cells.
|
||||
func visibleWidth(s string) int {
|
||||
return len(ansiEscapeRe.ReplaceAllString(s, ""))
|
||||
}
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
if n <= 3 {
|
||||
return s[:n]
|
||||
}
|
||||
return s[:n-3] + "..."
|
||||
}
|
||||
|
||||
func truncateLeft(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
if n <= 3 {
|
||||
return s[len(s)-n:]
|
||||
}
|
||||
return "..." + s[len(s)-(n-3):]
|
||||
}
|
||||
Reference in New Issue
Block a user