mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
refactor : Refactor error handling to use dry/usefulerror (#283)
* 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>
This commit is contained in:
co-authored by
Abhisek Datta
parent
e5fe0df82e
commit
20e01d5cae
+22
-21
@@ -4,7 +4,8 @@ import (
|
|||||||
"github.com/safedep/dry/cloud"
|
"github.com/safedep/dry/cloud"
|
||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,70 +30,70 @@ func runLogin(cmd *cobra.Command, args []string) error {
|
|||||||
if loginFromEnv {
|
if loginFromEnv {
|
||||||
resolver, err := cloud.NewEnvCredentialResolver()
|
resolver, err := cloud.NewEnvCredentialResolver()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to create environment credential resolver"))
|
WithHumanError("Failed to create environment credential resolver"))
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, err := resolver.Resolve()
|
creds, err := resolver.Resolve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
WithCode(errcodes.InvalidArgument).
|
||||||
WithHumanError("Failed to resolve credentials from environment").
|
WithHumanError("Failed to resolve credentials from environment").
|
||||||
WithHelp("Set SAFEDEP_API_KEY and SAFEDEP_TENANT_ID environment variables"))
|
WithHelp("Set SAFEDEP_API_KEY and SAFEDEP_TENANT_ID environment variables"))
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey, err = creds.GetAPIKey()
|
apiKey, err = creds.GetAPIKey()
|
||||||
if err != nil || apiKey == "" {
|
if err != nil || apiKey == "" {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
WithCode(errcodes.InvalidArgument).
|
||||||
WithHumanError("SAFEDEP_API_KEY environment variable is not set"))
|
WithHumanError("SAFEDEP_API_KEY environment variable is not set"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tenantID, err = creds.GetTenantDomain()
|
tenantID, err = creds.GetTenantDomain()
|
||||||
if err != nil || tenantID == "" {
|
if err != nil || tenantID == "" {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
WithCode(errcodes.InvalidArgument).
|
||||||
WithHumanError("SAFEDEP_TENANT_ID environment variable is not set"))
|
WithHumanError("SAFEDEP_TENANT_ID environment variable is not set"))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
tenantID, err = ui.PromptInput("Tenant ID: ")
|
tenantID, err = ui.PromptInput("Tenant ID: ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to read Tenant ID"))
|
WithHumanError("Failed to read Tenant ID"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if tenantID == "" {
|
if tenantID == "" {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
WithCode(errcodes.InvalidArgument).
|
||||||
WithHumanError("Tenant ID cannot be empty"))
|
WithHumanError("Tenant ID cannot be empty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey, err = ui.PromptSecret("API Key: ")
|
apiKey, err = ui.PromptSecret("API Key: ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to read API Key"))
|
WithHumanError("Failed to read API Key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
WithCode(errcodes.InvalidArgument).
|
||||||
WithHumanError("API Key cannot be empty"))
|
WithHumanError("API Key cannot be empty"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := cloud.NewKeychainCredentialStore()
|
store, err := cloud.NewKeychainCredentialStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to initialize credential store").
|
WithHumanError("Failed to initialize credential store").
|
||||||
WithHelp("Your system may not support secure credential storage"))
|
WithHelp("Your system may not support secure credential storage"))
|
||||||
}
|
}
|
||||||
@@ -103,9 +104,9 @@ func runLogin(cmd *cobra.Command, args []string) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if err := store.SaveAPIKeyCredential(apiKey, tenantID); err != nil {
|
if err := store.SaveAPIKeyCredential(apiKey, tenantID); err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to save credentials").
|
WithHumanError("Failed to save credentials").
|
||||||
WithHelp("Your system may not support secure credential storage"))
|
WithHelp("Your system may not support secure credential storage"))
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -4,7 +4,8 @@ import (
|
|||||||
"github.com/safedep/dry/cloud"
|
"github.com/safedep/dry/cloud"
|
||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,9 +20,9 @@ func newLogoutCommand() *cobra.Command {
|
|||||||
func runLogout(cmd *cobra.Command, args []string) error {
|
func runLogout(cmd *cobra.Command, args []string) error {
|
||||||
store, err := cloud.NewKeychainCredentialStore()
|
store, err := cloud.NewKeychainCredentialStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to initialize credential store").
|
WithHumanError("Failed to initialize credential store").
|
||||||
WithHelp("Your system may not support secure credential storage"))
|
WithHelp("Your system may not support secure credential storage"))
|
||||||
}
|
}
|
||||||
@@ -32,9 +33,9 @@ func runLogout(cmd *cobra.Command, args []string) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if err := store.Clear(); err != nil {
|
if err := store.Clear(); err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to clear credentials"))
|
WithHumanError("Failed to clear credentials"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-11
@@ -9,7 +9,8 @@ import (
|
|||||||
"github.com/safedep/pmg/internal/analytics"
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/audit"
|
"github.com/safedep/pmg/internal/audit"
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,8 +43,8 @@ func runSync(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.Config.Cloud.Enabled {
|
if !cfg.Config.Cloud.Enabled {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Cloud sync is not enabled").
|
WithHumanError("Cloud sync is not enabled").
|
||||||
WithHelp("Set 'cloud.enabled: true' in PMG config to enable cloud sync"))
|
WithHelp("Set 'cloud.enabled: true' in PMG config to enable cloud sync"))
|
||||||
}
|
}
|
||||||
@@ -54,15 +55,15 @@ func runSync(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
locked, err := lock.TryLockContext(lockCtx, 250*time.Millisecond)
|
locked, err := lock.TryLockContext(lockCtx, 250*time.Millisecond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to acquire cloud sync lock").
|
WithHumanError("Failed to acquire cloud sync lock").
|
||||||
WithHelp("Another sync may be in progress; try again shortly"))
|
WithHelp("Another sync may be in progress; try again shortly"))
|
||||||
}
|
}
|
||||||
if !locked {
|
if !locked {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Another cloud sync is already in progress").
|
WithHumanError("Another cloud sync is already in progress").
|
||||||
WithHelp("Wait for the in-progress sync to finish, then try again"))
|
WithHelp("Wait for the in-progress sync to finish, then try again"))
|
||||||
}
|
}
|
||||||
@@ -77,9 +78,9 @@ func runSync(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
bundle, err := audit.NewSyncClientBundle(cfg)
|
bundle, err := audit.NewSyncClientBundle(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError("Failed to initialize cloud sync client").
|
WithHumanError("Failed to initialize cloud sync client").
|
||||||
WithHelp("Run 'pmg cloud login' to store credentials, or set SAFEDEP_API_KEY and SAFEDEP_TENANT_ID environment variables"))
|
WithHelp("Run 'pmg cloud login' to store credentials, or set SAFEDEP_API_KEY and SAFEDEP_TENANT_ID environment variables"))
|
||||||
}
|
}
|
||||||
@@ -92,9 +93,9 @@ func runSync(cmd *cobra.Command, args []string) error {
|
|||||||
synced, err := bundle.Sync(ctx)
|
synced, err := bundle.Sync(ctx)
|
||||||
recordLastSyncAttempt(cfg)
|
recordLastSyncAttempt(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.ErrorExit(usefulerror.Useful().
|
ui.ErrorExit(usefulerror.NewUsefulError().
|
||||||
Wrap(err).
|
Wrap(err).
|
||||||
WithCode(usefulerror.ErrCodeNetwork).
|
WithCode(errcodes.Network).
|
||||||
WithHumanError("Failed to sync events to SafeDep Cloud").
|
WithHumanError("Failed to sync events to SafeDep Cloud").
|
||||||
WithHelp("Check your network connectivity and ensure SafeDep Cloud is reachable").
|
WithHelp("Check your network connectivity and ensure SafeDep Cloud is reachable").
|
||||||
WithAdditionalHelp("Override the cloud endpoint with SAFEDEP_CLOUD_DATA_ADDR if needed"))
|
WithAdditionalHelp("Override the cloud endpoint with SAFEDEP_CLOUD_DATA_ADDR if needed"))
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import (
|
|||||||
|
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/sandbox/platform"
|
"github.com/safedep/pmg/sandbox/platform"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// stubProbe is a minimal probe used by tests.
|
// stubProbe is a minimal probe used by tests.
|
||||||
@@ -169,7 +170,7 @@ func TestRunDoctor_UnknownDriver(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "unknown driver")
|
assert.Contains(t, err.Error(), "unknown driver")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDoctorCommandRejectsUnexpectedArgsWithUsage(t *testing.T) {
|
func TestDoctorCommandRejectsUnexpectedArgsWithUsage(t *testing.T) {
|
||||||
|
|||||||
+15
-14
@@ -11,7 +11,8 @@ import (
|
|||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ func (e *explainFailError) ExitCode() int { return ExitCodeExplainFail }
|
|||||||
|
|
||||||
func newExplainFailError(code, msg, help string) *explainFailError {
|
func newExplainFailError(code, msg, help string) *explainFailError {
|
||||||
return &explainFailError{
|
return &explainFailError{
|
||||||
UsefulError: usefulerror.Useful().
|
UsefulError: usefulerror.NewUsefulError().
|
||||||
WithCode(code).
|
WithCode(code).
|
||||||
WithHumanError(msg).
|
WithHumanError(msg).
|
||||||
WithHelp(help).
|
WithHelp(help).
|
||||||
@@ -80,7 +81,7 @@ func runExplain(out io.Writer, in io.Reader, args []string, opts *explainOptions
|
|||||||
|
|
||||||
if len(args) == 1 && !stdinMode {
|
if len(args) == 1 && !stdinMode {
|
||||||
return newExplainFailError(
|
return newExplainFailError(
|
||||||
usefulerror.ErrCodeInvalidArgument,
|
errcodes.InvalidArgument,
|
||||||
fmt.Sprintf("unexpected argument %q (use --last or pipe JSON with `-`)", args[0]),
|
fmt.Sprintf("unexpected argument %q (use --last or pipe JSON with `-`)", args[0]),
|
||||||
explainUsageHelp(),
|
explainUsageHelp(),
|
||||||
)
|
)
|
||||||
@@ -88,7 +89,7 @@ func runExplain(out io.Writer, in io.Reader, args []string, opts *explainOptions
|
|||||||
|
|
||||||
if opts.last && stdinMode {
|
if opts.last && stdinMode {
|
||||||
return newExplainFailError(
|
return newExplainFailError(
|
||||||
usefulerror.ErrCodeInvalidArgument,
|
errcodes.InvalidArgument,
|
||||||
"--last and `-` are mutually exclusive",
|
"--last and `-` are mutually exclusive",
|
||||||
explainUsageHelp(),
|
explainUsageHelp(),
|
||||||
)
|
)
|
||||||
@@ -96,7 +97,7 @@ func runExplain(out io.Writer, in io.Reader, args []string, opts *explainOptions
|
|||||||
|
|
||||||
if !opts.last && !stdinMode {
|
if !opts.last && !stdinMode {
|
||||||
return newExplainFailError(
|
return newExplainFailError(
|
||||||
usefulerror.ErrCodeInvalidArgument,
|
errcodes.InvalidArgument,
|
||||||
"no input: pass --last to read the most recent cached violation, or pipe a violation record JSON on stdin with `-`",
|
"no input: pass --last to read the most recent cached violation, or pipe a violation record JSON on stdin with `-`",
|
||||||
explainUsageHelp(),
|
explainUsageHelp(),
|
||||||
)
|
)
|
||||||
@@ -128,14 +129,14 @@ func readLatestFromCache(factory cacheFactory) (*pmgsandbox.ViolationCacheRecord
|
|||||||
entry, err := cache.Latest()
|
entry, err := cache.Latest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newExplainFailError(
|
return nil, newExplainFailError(
|
||||||
usefulerror.ErrCodeUnknown,
|
errcodes.Unknown,
|
||||||
fmt.Sprintf("read cache: %v", err),
|
fmt.Sprintf("read cache: %v", err),
|
||||||
"Check the sandbox violation cache directory and retry.",
|
"Check the sandbox violation cache directory and retry.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
return nil, newExplainFailError(
|
return nil, newExplainFailError(
|
||||||
usefulerror.ErrCodeNotFound,
|
errcodes.NotFound,
|
||||||
"no violations cached yet — run a sandboxed command first",
|
"no violations cached yet — run a sandboxed command first",
|
||||||
"Run a sandboxed package manager command first, then retry `pmg sandbox explain --last`.",
|
"Run a sandboxed package manager command first, then retry `pmg sandbox explain --last`.",
|
||||||
)
|
)
|
||||||
@@ -151,7 +152,7 @@ func readRecordFromStdin(in io.Reader) (*pmgsandbox.ViolationCacheRecord, error)
|
|||||||
data, err := io.ReadAll(in)
|
data, err := io.ReadAll(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newExplainFailError(
|
return nil, newExplainFailError(
|
||||||
usefulerror.ErrCodeInvalidArgument,
|
errcodes.InvalidArgument,
|
||||||
fmt.Sprintf("read stdin: %v", err),
|
fmt.Sprintf("read stdin: %v", err),
|
||||||
explainUsageHelp(),
|
explainUsageHelp(),
|
||||||
)
|
)
|
||||||
@@ -159,7 +160,7 @@ func readRecordFromStdin(in io.Reader) (*pmgsandbox.ViolationCacheRecord, error)
|
|||||||
|
|
||||||
if len(strings.TrimSpace(string(data))) == 0 {
|
if len(strings.TrimSpace(string(data))) == 0 {
|
||||||
return nil, newExplainFailError(
|
return nil, newExplainFailError(
|
||||||
usefulerror.ErrCodeInvalidArgument,
|
errcodes.InvalidArgument,
|
||||||
"stdin is empty: pipe a ViolationCacheRecord JSON document",
|
"stdin is empty: pipe a ViolationCacheRecord JSON document",
|
||||||
explainUsageHelp(),
|
explainUsageHelp(),
|
||||||
)
|
)
|
||||||
@@ -168,7 +169,7 @@ func readRecordFromStdin(in io.Reader) (*pmgsandbox.ViolationCacheRecord, error)
|
|||||||
var rec pmgsandbox.ViolationCacheRecord
|
var rec pmgsandbox.ViolationCacheRecord
|
||||||
if err := json.Unmarshal(data, &rec); err != nil {
|
if err := json.Unmarshal(data, &rec); err != nil {
|
||||||
return nil, newExplainFailError(
|
return nil, newExplainFailError(
|
||||||
usefulerror.ErrCodeInvalidArgument,
|
errcodes.InvalidArgument,
|
||||||
fmt.Sprintf("parse stdin JSON: %v", err),
|
fmt.Sprintf("parse stdin JSON: %v", err),
|
||||||
"Pipe a valid ViolationCacheRecord JSON document to `pmg sandbox explain -`.",
|
"Pipe a valid ViolationCacheRecord JSON document to `pmg sandbox explain -`.",
|
||||||
)
|
)
|
||||||
@@ -183,16 +184,16 @@ func readRecordFromStdin(in io.Reader) (*pmgsandbox.ViolationCacheRecord, error)
|
|||||||
|
|
||||||
func validateViolationCacheRecord(rec *pmgsandbox.ViolationCacheRecord, source string) error {
|
func validateViolationCacheRecord(rec *pmgsandbox.ViolationCacheRecord, source string) error {
|
||||||
if rec == nil {
|
if rec == nil {
|
||||||
return newExplainFailError(usefulerror.ErrCodeInvalidArgument, fmt.Sprintf("%s is empty", source), explainUsageHelp())
|
return newExplainFailError(errcodes.InvalidArgument, fmt.Sprintf("%s is empty", source), explainUsageHelp())
|
||||||
}
|
}
|
||||||
if rec.SchemaVersion == 0 {
|
if rec.SchemaVersion == 0 {
|
||||||
return newExplainFailError(usefulerror.ErrCodeInvalidArgument, fmt.Sprintf("%s is missing schema_version", source), explainUsageHelp())
|
return newExplainFailError(errcodes.InvalidArgument, fmt.Sprintf("%s is missing schema_version", source), explainUsageHelp())
|
||||||
}
|
}
|
||||||
if rec.SchemaVersion != pmgsandbox.ViolationCacheSchemaVersion {
|
if rec.SchemaVersion != pmgsandbox.ViolationCacheSchemaVersion {
|
||||||
return newExplainFailError(usefulerror.ErrCodeInvalidArgument, fmt.Sprintf("unknown schema_version %d (expected %d)", rec.SchemaVersion, pmgsandbox.ViolationCacheSchemaVersion), explainUsageHelp())
|
return newExplainFailError(errcodes.InvalidArgument, fmt.Sprintf("unknown schema_version %d (expected %d)", rec.SchemaVersion, pmgsandbox.ViolationCacheSchemaVersion), explainUsageHelp())
|
||||||
}
|
}
|
||||||
if rec.Report == nil {
|
if rec.Report == nil {
|
||||||
return newExplainFailError(usefulerror.ErrCodeInvalidArgument, fmt.Sprintf("%s is missing report", source), explainUsageHelp())
|
return newExplainFailError(errcodes.InvalidArgument, fmt.Sprintf("%s is missing report", source), explainUsageHelp())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sampleReport() *pmgsandbox.ViolationReport {
|
func sampleReport() *pmgsandbox.ViolationReport {
|
||||||
@@ -67,7 +68,7 @@ func TestExplainLastEmptyCache(t *testing.T) {
|
|||||||
assert.Equal(t, ExitCodeExplainFail, fe.ExitCode())
|
assert.Equal(t, ExitCodeExplainFail, fe.ExitCode())
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeNotFound, usefulErr.Code())
|
assert.Equal(t, errcodes.NotFound, usefulErr.Code())
|
||||||
assert.Empty(t, stdout)
|
assert.Empty(t, stdout)
|
||||||
assert.Contains(t, err.Error(), "no violations cached")
|
assert.Contains(t, err.Error(), "no violations cached")
|
||||||
}
|
}
|
||||||
@@ -221,7 +222,7 @@ func TestExplainNoMode(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
assert.Contains(t, err.Error(), "no input")
|
assert.Contains(t, err.Error(), "no input")
|
||||||
assert.Contains(t, err.Error(), "--last")
|
assert.Contains(t, err.Error(), "--last")
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-12
@@ -11,7 +11,8 @@ import (
|
|||||||
|
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,16 +49,16 @@ func validateDriver(name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func invalidArgumentError(message, help string) error {
|
func invalidArgumentError(message, help string) error {
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
WithCode(errcodes.InvalidArgument).
|
||||||
WithHumanError(message).
|
WithHumanError(message).
|
||||||
WithHelp(help).
|
WithHelp(help).
|
||||||
Wrap(errors.New(message))
|
Wrap(errors.New(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
func notFoundError(message, help string) error {
|
func notFoundError(message, help string) error {
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeNotFound).
|
WithCode(errcodes.NotFound).
|
||||||
WithHumanError(message).
|
WithHumanError(message).
|
||||||
WithHelp(help).
|
WithHelp(help).
|
||||||
Wrap(errors.New(message))
|
Wrap(errors.New(message))
|
||||||
@@ -72,7 +73,7 @@ func wrapUseful(err error, code, help string) error {
|
|||||||
if _, ok := usefulerror.AsUsefulError(err); ok {
|
if _, ok := usefulerror.AsUsefulError(err); ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(code).
|
WithCode(code).
|
||||||
WithHumanError(err.Error()).
|
WithHumanError(err.Error()).
|
||||||
WithHelp(help).
|
WithHelp(help).
|
||||||
@@ -88,27 +89,27 @@ func profileLoadError(err error) error {
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, pmgsandbox.ErrProfileNotFound):
|
case errors.Is(err, pmgsandbox.ErrProfileNotFound):
|
||||||
return wrapUseful(err, usefulerror.ErrCodeNotFound,
|
return wrapUseful(err, errcodes.NotFound,
|
||||||
"Use `pmg sandbox profile list` to see available profiles, or pass an existing profile YAML path.")
|
"Use `pmg sandbox profile list` to see available profiles, or pass an existing profile YAML path.")
|
||||||
case errors.Is(err, pmgsandbox.ErrProfileInvalid):
|
case errors.Is(err, pmgsandbox.ErrProfileInvalid):
|
||||||
return wrapUseful(err, usefulerror.ErrCodeInvalidArgument,
|
return wrapUseful(err, errcodes.InvalidArgument,
|
||||||
"Check the profile YAML for syntax/schema issues and verify any 'inherits:' parent name.")
|
"Check the profile YAML for syntax/schema issues and verify any 'inherits:' parent name.")
|
||||||
}
|
}
|
||||||
return wrapUseful(err, usefulerror.ErrCodeUnknown,
|
return wrapUseful(err, errcodes.Unknown,
|
||||||
"Failed to load the sandbox profile. Run with --verbose for the underlying cause.")
|
"Failed to load the sandbox profile. Run with --verbose for the underlying cause.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func registryInitError(err error) error {
|
func registryInitError(err error) error {
|
||||||
return wrapUseful(err, ioErrorCode(err, usefulerror.ErrCodeUnknown),
|
return wrapUseful(err, ioErrorCode(err, errcodes.Unknown),
|
||||||
"Failed to initialise the sandbox profile registry. Run with --verbose for details.")
|
"Failed to initialise the sandbox profile registry. Run with --verbose for details.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ioErrorCode(err error, fallback string) string {
|
func ioErrorCode(err error, fallback string) string {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, fs.ErrPermission):
|
case errors.Is(err, fs.ErrPermission):
|
||||||
return usefulerror.ErrCodePermissionDenied
|
return errcodes.PermissionDenied
|
||||||
case errors.Is(err, fs.ErrNotExist):
|
case errors.Is(err, fs.ErrNotExist):
|
||||||
return usefulerror.ErrCodeNotFound
|
return errcodes.NotFound
|
||||||
}
|
}
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/pmezard/go-difflib/difflib"
|
"github.com/pmezard/go-difflib/difflib"
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/sandbox/platform"
|
"github.com/safedep/pmg/sandbox/platform"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@@ -83,7 +83,7 @@ func runProfileDiff(out io.Writer, errOut io.Writer, nameA, nameB string, opts *
|
|||||||
|
|
||||||
if bytes.Equal(dataA, dataB) {
|
if bytes.Equal(dataA, dataB) {
|
||||||
if _, err := fmt.Fprintln(errOut, "profiles are identical"); err != nil {
|
if _, err := fmt.Fprintln(errOut, "profiles are identical"); err != nil {
|
||||||
return &diffOpError{err: wrapUseful(err, usefulerror.ErrCodeUnknown,
|
return &diffOpError{err: wrapUseful(err, errcodes.Unknown,
|
||||||
"Failed to write diff output. Check that stderr is writable.")}
|
"Failed to write diff output. Check that stderr is writable.")}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -99,18 +99,18 @@ func runProfileDiff(out io.Writer, errOut io.Writer, nameA, nameB string, opts *
|
|||||||
text, err := difflib.GetUnifiedDiffString(diff)
|
text, err := difflib.GetUnifiedDiffString(diff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &diffOpError{err: wrapUseful(fmt.Errorf("failed to render diff: %w", err),
|
return &diffOpError{err: wrapUseful(fmt.Errorf("failed to render diff: %w", err),
|
||||||
usefulerror.ErrCodeUnknown,
|
errcodes.Unknown,
|
||||||
"Could not render the unified diff. Re-run with --verbose for the underlying cause.")}
|
"Could not render the unified diff. Re-run with --verbose for the underlying cause.")}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.WriteString(out, text); err != nil {
|
if _, err := io.WriteString(out, text); err != nil {
|
||||||
return &diffOpError{err: wrapUseful(err, usefulerror.ErrCodeUnknown,
|
return &diffOpError{err: wrapUseful(err, errcodes.Unknown,
|
||||||
"Failed to write diff output. Check that stdout is writable.")}
|
"Failed to write diff output. Check that stdout is writable.")}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(text, "\n") {
|
if !strings.HasSuffix(text, "\n") {
|
||||||
if _, err := fmt.Fprintln(out); err != nil {
|
if _, err := fmt.Fprintln(out); err != nil {
|
||||||
return &diffOpError{err: wrapUseful(err, usefulerror.ErrCodeUnknown,
|
return &diffOpError{err: wrapUseful(err, errcodes.Unknown,
|
||||||
"Failed to write diff output. Check that stdout is writable.")}
|
"Failed to write diff output. Check that stdout is writable.")}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ func materialize(registry pmgsandbox.ProfileRegistry, name string, opts *profile
|
|||||||
if opts.driver != "" {
|
if opts.driver != "" {
|
||||||
rendered, err := platform.Render(pmgsandbox.DriverName(opts.driver), policy)
|
rendered, err := platform.Render(pmgsandbox.DriverName(opts.driver), policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapUseful(err, usefulerror.ErrCodeInvalidArgument,
|
return nil, wrapUseful(err, errcodes.InvalidArgument,
|
||||||
"Could not render the policy for the requested driver. Verify the driver is supported on this host.")
|
"Could not render the policy for the requested driver. Verify the driver is supported on this host.")
|
||||||
}
|
}
|
||||||
return rendered, nil
|
return rendered, nil
|
||||||
@@ -142,7 +142,7 @@ func materialize(registry pmgsandbox.ProfileRegistry, name string, opts *profile
|
|||||||
data, err := yaml.Marshal(policy)
|
data, err := yaml.Marshal(policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapUseful(fmt.Errorf("failed to marshal resolved policy %s: %w", name, err),
|
return nil, wrapUseful(fmt.Errorf("failed to marshal resolved policy %s: %w", name, err),
|
||||||
usefulerror.ErrCodeUnknown,
|
errcodes.Unknown,
|
||||||
"Failed to marshal the resolved policy to YAML. Re-run with --verbose for the underlying cause.")
|
"Failed to marshal the resolved policy to YAML. Re-run with --verbose for the underlying cause.")
|
||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -99,7 +100,7 @@ func TestProfileDiffUnknownProfile(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "no-such-profile-xyz")
|
assert.Contains(t, err.Error(), "no-such-profile-xyz")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok, "diff error should expose a UsefulError through Unwrap")
|
require.True(t, ok, "diff error should expose a UsefulError through Unwrap")
|
||||||
assert.Equal(t, usefulerror.ErrCodeNotFound, usefulErr.Code())
|
assert.Equal(t, errcodes.NotFound, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProfileDiffUnknownDriver(t *testing.T) {
|
func TestProfileDiffUnknownDriver(t *testing.T) {
|
||||||
@@ -113,7 +114,7 @@ func TestProfileDiffUnknownDriver(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "unknown driver")
|
assert.Contains(t, err.Error(), "unknown driver")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok, "diff error should expose a UsefulError through Unwrap")
|
require.True(t, ok, "diff error should expose a UsefulError through Unwrap")
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProfileDiffMissingProfileShowsUsage(t *testing.T) {
|
func TestProfileDiffMissingProfileShowsUsage(t *testing.T) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,13 +95,13 @@ func runProfileInit(out io.Writer, name string, opts *profileInitOptions, factor
|
|||||||
)
|
)
|
||||||
} else if !os.IsNotExist(err) {
|
} else if !os.IsNotExist(err) {
|
||||||
return wrapUseful(fmt.Errorf("failed to stat %s: %w", target, err),
|
return wrapUseful(fmt.Errorf("failed to stat %s: %w", target, err),
|
||||||
ioErrorCode(err, usefulerror.ErrCodeUnknown),
|
ioErrorCode(err, errcodes.Unknown),
|
||||||
"Could not stat the target profile path. Check the user profile directory permissions.")
|
"Could not stat the target profile path. Check the user profile directory permissions.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(userDir, 0o755); err != nil {
|
if err := os.MkdirAll(userDir, 0o755); err != nil {
|
||||||
return wrapUseful(fmt.Errorf("failed to create user profile directory %s: %w", userDir, err),
|
return wrapUseful(fmt.Errorf("failed to create user profile directory %s: %w", userDir, err),
|
||||||
ioErrorCode(err, usefulerror.ErrCodePermissionDenied),
|
ioErrorCode(err, errcodes.PermissionDenied),
|
||||||
"Could not create the user profile directory. Check filesystem permissions for "+userDir+".")
|
"Could not create the user profile directory. Check filesystem permissions for "+userDir+".")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ func runProfileInit(out io.Writer, name string, opts *profileInitOptions, factor
|
|||||||
|
|
||||||
if err := os.WriteFile(target, []byte(content), 0o644); err != nil {
|
if err := os.WriteFile(target, []byte(content), 0o644); err != nil {
|
||||||
return wrapUseful(fmt.Errorf("failed to write %s: %w", target, err),
|
return wrapUseful(fmt.Errorf("failed to write %s: %w", target, err),
|
||||||
ioErrorCode(err, usefulerror.ErrCodePermissionDenied),
|
ioErrorCode(err, errcodes.PermissionDenied),
|
||||||
"Could not write the scaffolded profile. Check filesystem permissions for "+target+".")
|
"Could not write the scaffolded profile. Check filesystem permissions for "+target+".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -97,7 +98,7 @@ func TestProfileInit_RefuseOverwrite(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), target)
|
assert.Contains(t, err.Error(), target)
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
|
|
||||||
data, err := os.ReadFile(target)
|
data, err := os.ReadFile(target)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -153,7 +154,7 @@ func TestProfileInit_InvalidName(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "invalid profile name")
|
assert.Contains(t, err.Error(), "invalid profile name")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +167,7 @@ func TestProfileInit_UnknownBuiltin(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "unknown built-in profile")
|
assert.Contains(t, err.Error(), "unknown built-in profile")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeNotFound, usefulErr.Code())
|
assert.Equal(t, errcodes.NotFound, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProfileInit_StdoutIsExactlyThePath(t *testing.T) {
|
func TestProfileInit_StdoutIsExactlyThePath(t *testing.T) {
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeUserProfileLint(t *testing.T, dir, name, body string) string {
|
func writeUserProfileLint(t *testing.T, dir, name, body string) string {
|
||||||
@@ -178,7 +179,7 @@ func TestProfileLint_UnknownProfile(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "not found")
|
assert.Contains(t, err.Error(), "not found")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeNotFound, usefulErr.Code())
|
assert.Equal(t, errcodes.NotFound, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProfileLintMissingTargetShowsUsage(t *testing.T) {
|
func TestProfileLintMissingTargetShowsUsage(t *testing.T) {
|
||||||
@@ -211,7 +212,7 @@ func TestProfileLint_InvalidYAMLReturnsInvalidArgument(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProfileLint_LiteralPath(t *testing.T) {
|
func TestProfileLint_LiteralPath(t *testing.T) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ func runProfileList(out io.Writer, opts *profileListOptions, factory registryFac
|
|||||||
|
|
||||||
summaries, err := registry.ListProfiles()
|
summaries, err := registry.ListProfiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapUseful(err, ioErrorCode(err, usefulerror.ErrCodeUnknown),
|
return wrapUseful(err, ioErrorCode(err, errcodes.Unknown),
|
||||||
"Failed to enumerate sandbox profiles. Check the user profile directory permissions.")
|
"Failed to enumerate sandbox profiles. Check the user profile directory permissions.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestRegistry(t *testing.T, userDir string) registryFactory {
|
func newTestRegistry(t *testing.T, userDir string) registryFactory {
|
||||||
@@ -119,7 +120,7 @@ func TestProfileListRegistryFailureReturnsUseful(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeUnknown, usefulErr.Code())
|
assert.Equal(t, errcodes.Unknown, usefulErr.Code())
|
||||||
assert.Contains(t, err.Error(), "boom")
|
assert.Contains(t, err.Error(), "boom")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +139,7 @@ func TestProfileListRegistryPermissionErrorReturnsPermissionDenied(t *testing.T)
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodePermissionDenied, usefulErr.Code())
|
assert.Equal(t, errcodes.PermissionDenied, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProfileListRejectsUnexpectedArgs(t *testing.T) {
|
func TestProfileListRejectsUnexpectedArgs(t *testing.T) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/sandbox/platform"
|
"github.com/safedep/pmg/sandbox/platform"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@@ -108,7 +108,7 @@ func runProfileShowResolved(out io.Writer, name string, opts *profileShowOptions
|
|||||||
data, err := yaml.Marshal(policy)
|
data, err := yaml.Marshal(policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapUseful(fmt.Errorf("failed to marshal resolved policy: %w", err),
|
return wrapUseful(fmt.Errorf("failed to marshal resolved policy: %w", err),
|
||||||
usefulerror.ErrCodeUnknown,
|
errcodes.Unknown,
|
||||||
"Failed to marshal the resolved policy to YAML. Re-run with --verbose for the underlying cause.")
|
"Failed to marshal the resolved policy to YAML. Re-run with --verbose for the underlying cause.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ func runProfileShowDriver(out io.Writer, name string, opts *profileShowOptions,
|
|||||||
|
|
||||||
rendered, err := platform.Render(pmgsandbox.DriverName(opts.driver), policy)
|
rendered, err := platform.Render(pmgsandbox.DriverName(opts.driver), policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapUseful(err, usefulerror.ErrCodeInvalidArgument,
|
return wrapUseful(err, errcodes.InvalidArgument,
|
||||||
"Could not render the policy for the requested driver. Verify the driver is supported on this host.")
|
"Could not render the policy for the requested driver. Verify the driver is supported on this host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -71,7 +72,7 @@ func TestProfileShowUnknownDriver(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "unknown driver")
|
assert.Contains(t, err.Error(), "unknown driver")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProfileShowUnknownProfileReturnsNotFound(t *testing.T) {
|
func TestProfileShowUnknownProfileReturnsNotFound(t *testing.T) {
|
||||||
@@ -88,7 +89,7 @@ func TestProfileShowUnknownProfileReturnsNotFound(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "not found")
|
assert.Contains(t, err.Error(), "not found")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeNotFound, usefulErr.Code())
|
assert.Equal(t, errcodes.NotFound, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProfileShowMissingNameShowsUsage(t *testing.T) {
|
func TestProfileShowMissingNameShowsUsage(t *testing.T) {
|
||||||
@@ -150,5 +151,5 @@ func TestProfileShowDriverNonNativeErrors(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "not available")
|
assert.Contains(t, err.Error(), "not available")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok, "non-native driver error should be reported as a UsefulError")
|
require.True(t, ok, "non-native driver error should be reported as a UsefulError")
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
|
|
||||||
"github.com/safedep/pmg/internal/ui"
|
"github.com/safedep/pmg/internal/ui"
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ func (e *violationsListFailError) ExitCode() int { return ExitCodeViolationsList
|
|||||||
|
|
||||||
func newViolationsListFailError(code, msg, help string) *violationsListFailError {
|
func newViolationsListFailError(code, msg, help string) *violationsListFailError {
|
||||||
return &violationsListFailError{
|
return &violationsListFailError{
|
||||||
UsefulError: usefulerror.Useful().
|
UsefulError: usefulerror.NewUsefulError().
|
||||||
WithCode(code).
|
WithCode(code).
|
||||||
WithHumanError(msg).
|
WithHumanError(msg).
|
||||||
WithHelp(help).
|
WithHelp(help).
|
||||||
@@ -61,7 +62,7 @@ func newViolationsListCommand(factory cacheFactory) *cobra.Command {
|
|||||||
func runViolationsList(out, errOut io.Writer, opts *violationsListOptions, factory cacheFactory) error {
|
func runViolationsList(out, errOut io.Writer, opts *violationsListOptions, factory cacheFactory) error {
|
||||||
if opts.limit < 0 {
|
if opts.limit < 0 {
|
||||||
return newViolationsListFailError(
|
return newViolationsListFailError(
|
||||||
usefulerror.ErrCodeInvalidArgument,
|
errcodes.InvalidArgument,
|
||||||
fmt.Sprintf("invalid --limit %d (must be >= 0)", opts.limit),
|
fmt.Sprintf("invalid --limit %d (must be >= 0)", opts.limit),
|
||||||
"Pass --limit 0 to show all entries, or a positive limit.",
|
"Pass --limit 0 to show all entries, or a positive limit.",
|
||||||
)
|
)
|
||||||
@@ -71,7 +72,7 @@ func runViolationsList(out, errOut io.Writer, opts *violationsListOptions, facto
|
|||||||
entries, err := cache.List()
|
entries, err := cache.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newViolationsListFailError(
|
return newViolationsListFailError(
|
||||||
usefulerror.ErrCodeUnknown,
|
errcodes.Unknown,
|
||||||
fmt.Sprintf("read cache: %v", err),
|
fmt.Sprintf("read cache: %v", err),
|
||||||
"Check the sandbox violation cache directory and retry.",
|
"Check the sandbox violation cache directory and retry.",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
pmgsandbox "github.com/safedep/pmg/sandbox"
|
pmgsandbox "github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -108,7 +109,7 @@ func TestViolationsListRejectsNegativeLimit(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "invalid --limit")
|
assert.Contains(t, err.Error(), "invalid --limit")
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodeInvalidArgument, usefulErr.Code())
|
assert.Equal(t, errcodes.InvalidArgument, usefulErr.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViolationsList_LimitZeroReturnsAll(t *testing.T) {
|
func TestViolationsList_LimitZeroReturnsAll(t *testing.T) {
|
||||||
|
|||||||
+4
-3
@@ -13,7 +13,8 @@ import (
|
|||||||
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
"github.com/safedep/dry/utils"
|
"github.com/safedep/dry/utils"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -676,8 +677,8 @@ func NewManagedConfigError() error {
|
|||||||
// managedError builds the standard "globally managed" CLI error with a useful
|
// managedError builds the standard "globally managed" CLI error with a useful
|
||||||
// code and actionable help.
|
// code and actionable help.
|
||||||
func managedError(message string) error {
|
func managedError(message string) error {
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodePermissionDenied).
|
WithCode(errcodes.PermissionDenied).
|
||||||
WithHumanError(message).
|
WithHumanError(message).
|
||||||
WithHelp("This machine's PMG configuration is centrally managed. Contact your administrator to change it.").
|
WithHelp("This machine's PMG configuration is centrally managed. Contact your administrator to change it.").
|
||||||
Wrap(errors.New(message))
|
Wrap(errors.New(message))
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package errcodes
|
||||||
|
|
||||||
|
const (
|
||||||
|
InvalidArgument = "InvalidArgument"
|
||||||
|
PermissionDenied = "PermissionDenied"
|
||||||
|
NotFound = "NotFound"
|
||||||
|
Timeout = "Timeout"
|
||||||
|
Canceled = "Canceled"
|
||||||
|
UnexpectedEOF = "UnexpectedEOF"
|
||||||
|
Unknown = "Unknown"
|
||||||
|
Lifecycle = "Lifecycle"
|
||||||
|
Network = "Network"
|
||||||
|
SandboxViolation = "SandboxViolation"
|
||||||
|
PackageManagerExecutionFailed = "PackageManagerExecutionFailed"
|
||||||
|
BubblewrapNotFound = "bubblewrap_not_found"
|
||||||
|
)
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/safedep/pmg/packagemanager"
|
"github.com/safedep/pmg/packagemanager"
|
||||||
"github.com/safedep/pmg/sandbox"
|
"github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/sandbox/executor"
|
"github.com/safedep/pmg/sandbox/executor"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExecutionMode int
|
type ExecutionMode int
|
||||||
@@ -139,7 +139,7 @@ func runPTY(
|
|||||||
beforeWait func(*PTYRuntime) error,
|
beforeWait func(*PTYRuntime) error,
|
||||||
) error {
|
) error {
|
||||||
if !result.ShouldRun() {
|
if !result.ShouldRun() {
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
Wrap(fmt.Errorf("sandbox not supported for PTY sessions")).
|
Wrap(fmt.Errorf("sandbox not supported for PTY sessions")).
|
||||||
WithHumanError("Sandbox executed command cannot be used with PTY session. Please use non-interactive TTY mode instead.")
|
WithHumanError("Sandbox executed command cannot be used with PTY session. Please use non-interactive TTY mode instead.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrorExit prints a minimal, clean error message and exits with a non-zero status code.
|
// ErrorExit prints a minimal, clean error message and exits with a non-zero status code.
|
||||||
@@ -23,7 +23,7 @@ func ErrorExitWithCode(err error, code int) {
|
|||||||
|
|
||||||
// Use help as hint, but for unknown errors show bug report link
|
// Use help as hint, but for unknown errors show bug report link
|
||||||
hint := usefulErr.Help()
|
hint := usefulErr.Help()
|
||||||
if usefulErr.Code() == usefulerror.ErrCodeUnknown {
|
if usefulErr.Code() == errcodes.Unknown {
|
||||||
hint = "Report this issue: https://github.com/safedep/pmg/issues/new?labels=bug"
|
hint = "Report this issue: https://github.com/safedep/pmg/issues/new?labels=bug"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// errorMatcher defines how to detect and convert a specific error type
|
// errorMatcher defines how to detect and convert a specific error type
|
||||||
@@ -35,8 +36,8 @@ var errorMatchers = []errorMatcher{
|
|||||||
humanError = fmt.Sprintf("File or directory not found: %s", path)
|
humanError = fmt.Sprintf("File or directory not found: %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeNotFound).
|
WithCode(errcodes.NotFound).
|
||||||
WithHumanError(humanError).
|
WithHumanError(humanError).
|
||||||
WithHelp("Check if the path exists").
|
WithHelp("Check if the path exists").
|
||||||
WithAdditionalHelp("Use 'ls' to check directory contents").
|
WithAdditionalHelp("Use 'ls' to check directory contents").
|
||||||
@@ -54,8 +55,8 @@ var errorMatchers = []errorMatcher{
|
|||||||
if path != "" {
|
if path != "" {
|
||||||
humanError = fmt.Sprintf("Permission denied: %s", path)
|
humanError = fmt.Sprintf("Permission denied: %s", path)
|
||||||
}
|
}
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodePermissionDenied).
|
WithCode(errcodes.PermissionDenied).
|
||||||
WithHumanError(humanError).
|
WithHumanError(humanError).
|
||||||
WithHelp("Check permissions or use sudo").
|
WithHelp("Check permissions or use sudo").
|
||||||
WithAdditionalHelp("Use 'ls -la' to check permissions").
|
WithAdditionalHelp("Use 'ls -la' to check permissions").
|
||||||
@@ -72,8 +73,8 @@ var errorMatchers = []errorMatcher{
|
|||||||
var exitErr *exec.ExitError
|
var exitErr *exec.ExitError
|
||||||
errors.As(err, &exitErr)
|
errors.As(err, &exitErr)
|
||||||
exitCode := exitErr.ExitCode()
|
exitCode := exitErr.ExitCode()
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeLifecycle).
|
WithCode(errcodes.Lifecycle).
|
||||||
WithHumanError(fmt.Sprintf("Command failed with exit code %d", exitCode)).
|
WithHumanError(fmt.Sprintf("Command failed with exit code %d", exitCode)).
|
||||||
WithHelp("Check command output above").
|
WithHelp("Check command output above").
|
||||||
Wrap(err)
|
Wrap(err)
|
||||||
@@ -85,8 +86,8 @@ var errorMatchers = []errorMatcher{
|
|||||||
return errors.Is(err, context.DeadlineExceeded)
|
return errors.Is(err, context.DeadlineExceeded)
|
||||||
},
|
},
|
||||||
convert: func(err error) usefulerror.UsefulError {
|
convert: func(err error) usefulerror.UsefulError {
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeTimeout).
|
WithCode(errcodes.Timeout).
|
||||||
WithHumanError("Operation timed out").
|
WithHumanError("Operation timed out").
|
||||||
WithHelp("Try again or check your network").
|
WithHelp("Try again or check your network").
|
||||||
WithAdditionalHelp("Consider increasing timeout or retry later").
|
WithAdditionalHelp("Consider increasing timeout or retry later").
|
||||||
@@ -99,8 +100,8 @@ var errorMatchers = []errorMatcher{
|
|||||||
return errors.Is(err, context.Canceled)
|
return errors.Is(err, context.Canceled)
|
||||||
},
|
},
|
||||||
convert: func(err error) usefulerror.UsefulError {
|
convert: func(err error) usefulerror.UsefulError {
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeCanceled).
|
WithCode(errcodes.Canceled).
|
||||||
WithHumanError("Operation was canceled").
|
WithHumanError("Operation was canceled").
|
||||||
Wrap(err)
|
Wrap(err)
|
||||||
},
|
},
|
||||||
@@ -121,15 +122,15 @@ var errorMatchers = []errorMatcher{
|
|||||||
convert: func(err error) usefulerror.UsefulError {
|
convert: func(err error) usefulerror.UsefulError {
|
||||||
var netErr net.Error
|
var netErr net.Error
|
||||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeTimeout).
|
WithCode(errcodes.Timeout).
|
||||||
WithHumanError("Network request timed out").
|
WithHumanError("Network request timed out").
|
||||||
WithHelp("Check your internet connection").
|
WithHelp("Check your internet connection").
|
||||||
WithAdditionalHelp("Consider increasing timeout or retry later").
|
WithAdditionalHelp("Consider increasing timeout or retry later").
|
||||||
Wrap(err)
|
Wrap(err)
|
||||||
}
|
}
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeNetwork).
|
WithCode(errcodes.Network).
|
||||||
WithHumanError("Network error occurred").
|
WithHumanError("Network error occurred").
|
||||||
WithHelp("Check your internet connection").
|
WithHelp("Check your internet connection").
|
||||||
WithAdditionalHelp("The package registry may be temporarily unavailable").
|
WithAdditionalHelp("The package registry may be temporarily unavailable").
|
||||||
@@ -142,8 +143,8 @@ var errorMatchers = []errorMatcher{
|
|||||||
return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)
|
return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)
|
||||||
},
|
},
|
||||||
convert: func(err error) usefulerror.UsefulError {
|
convert: func(err error) usefulerror.UsefulError {
|
||||||
return usefulerror.Useful().
|
return usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeUnexpectedEOF).
|
WithCode(errcodes.UnexpectedEOF).
|
||||||
WithHumanError("Unexpected end of data").
|
WithHumanError("Unexpected end of data").
|
||||||
WithHelp("Retry the download").
|
WithHelp("Retry the download").
|
||||||
WithAdditionalHelp("This may indicate network instability").
|
WithAdditionalHelp("This may indicate network instability").
|
||||||
@@ -152,6 +153,17 @@ var errorMatchers = []errorMatcher{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
usefulerror.RegisterErrorConverter("pmg-ui-converter", func(err error) (usefulerror.UsefulError, bool) {
|
||||||
|
for _, matcher := range errorMatchers {
|
||||||
|
if matcher.match(err) {
|
||||||
|
return matcher.convert(err), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// convertToUsefulError attempts to convert a regular error to a UsefulError
|
// convertToUsefulError attempts to convert a regular error to a UsefulError
|
||||||
// by analyzing the error chain for known error types.
|
// by analyzing the error chain for known error types.
|
||||||
// Returns the original error wrapped in a generic UsefulError if no specific match is found.
|
// Returns the original error wrapped in a generic UsefulError if no specific match is found.
|
||||||
@@ -164,14 +176,8 @@ func convertToUsefulError(err error) usefulerror.UsefulError {
|
|||||||
return ue
|
return ue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, matcher := range errorMatchers {
|
return usefulerror.NewUsefulError().
|
||||||
if matcher.match(err) {
|
WithCode(errcodes.Unknown).
|
||||||
return matcher.convert(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return usefulerror.Useful().
|
|
||||||
WithCode(usefulerror.ErrCodeUnknown).
|
|
||||||
WithHumanError(extractRootCause(err)).
|
WithHumanError(extractRootCause(err)).
|
||||||
WithHelp("An unexpected error occurred.").
|
WithHelp("An unexpected error occurred.").
|
||||||
Wrap(err)
|
Wrap(err)
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_convertToUsefulError(t *testing.T) {
|
func Test_ErrorConverters(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
inputError error
|
inputError error
|
||||||
@@ -24,60 +25,58 @@ func Test_convertToUsefulError(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "AlreadyUseful",
|
name: "AlreadyUseful",
|
||||||
inputError: usefulerror.Useful().
|
inputError: usefulerror.NewUsefulError().
|
||||||
WithCode("CUSTOM").
|
WithCode("CUSTOM").
|
||||||
WithHumanError("Already useful").
|
WithHumanError("Already useful").
|
||||||
Msg("test"),
|
WithMsg("test"),
|
||||||
wantCode: "CUSTOM",
|
wantCode: "CUSTOM",
|
||||||
wantHumanError: "Already useful",
|
wantHumanError: "Already useful",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "FileNotExist",
|
name: "FileNotExist",
|
||||||
inputError: &fs.PathError{Op: "open", Path: "/nonexistent/file.txt", Err: os.ErrNotExist},
|
inputError: &fs.PathError{Op: "open", Path: "/nonexistent/file.txt", Err: os.ErrNotExist},
|
||||||
wantCode: usefulerror.ErrCodeNotFound,
|
wantCode: errcodes.NotFound,
|
||||||
wantContains: "/nonexistent/file.txt",
|
wantContains: "/nonexistent/file.txt",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PermissionDenied",
|
name: "PermissionDenied",
|
||||||
inputError: &fs.PathError{Op: "open", Path: "/root/secret", Err: os.ErrPermission},
|
inputError: &fs.PathError{Op: "open", Path: "/root/secret", Err: os.ErrPermission},
|
||||||
wantCode: usefulerror.ErrCodePermissionDenied,
|
wantCode: errcodes.PermissionDenied,
|
||||||
wantContains: "/root/secret",
|
wantContains: "/root/secret",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ContextTimeout",
|
name: "ContextTimeout",
|
||||||
inputError: context.DeadlineExceeded,
|
inputError: context.DeadlineExceeded,
|
||||||
wantCode: usefulerror.ErrCodeTimeout,
|
wantCode: errcodes.Timeout,
|
||||||
wantContains: "timed out",
|
wantContains: "timed out",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ContextCanceled",
|
name: "ContextCanceled",
|
||||||
inputError: context.Canceled,
|
inputError: context.Canceled,
|
||||||
wantCode: usefulerror.ErrCodeCanceled,
|
wantCode: errcodes.Canceled,
|
||||||
wantContains: "canceled",
|
wantContains: "canceled",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UnexpectedEOF",
|
name: "UnexpectedEOF",
|
||||||
inputError: io.ErrUnexpectedEOF,
|
inputError: io.ErrUnexpectedEOF,
|
||||||
wantCode: usefulerror.ErrCodeUnexpectedEOF,
|
wantCode: errcodes.UnexpectedEOF,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "WrappedError",
|
name: "WrappedError",
|
||||||
inputError: fmt.Errorf("failed to read config: %w", os.ErrNotExist),
|
inputError: fmt.Errorf("failed to read config: %w", os.ErrNotExist),
|
||||||
wantCode: usefulerror.ErrCodeNotFound,
|
wantCode: errcodes.NotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UnknownError",
|
name: "UnknownError",
|
||||||
inputError: errors.New("some unknown error"),
|
inputError: errors.New("some unknown error"),
|
||||||
wantCode: usefulerror.ErrCodeUnknown,
|
wantNil: true,
|
||||||
wantHumanError: "some unknown error",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UnknownWrappedError",
|
name: "UnknownWrappedError",
|
||||||
inputError: fmt.Errorf("more context: %w",
|
inputError: fmt.Errorf("more context: %w",
|
||||||
fmt.Errorf("outer context: %w",
|
fmt.Errorf("outer context: %w",
|
||||||
errors.New("root cause error"))),
|
errors.New("root cause error"))),
|
||||||
wantCode: usefulerror.ErrCodeUnknown,
|
wantNil: true,
|
||||||
wantHumanError: "root cause error",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Nil",
|
name: "Nil",
|
||||||
@@ -87,19 +86,20 @@ func Test_convertToUsefulError(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "NetworkErrorMessage",
|
name: "NetworkErrorMessage",
|
||||||
inputError: errors.New("connection refused"),
|
inputError: errors.New("connection refused"),
|
||||||
wantCode: usefulerror.ErrCodeNetwork,
|
wantCode: errcodes.Network,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := convertToUsefulError(tt.inputError)
|
result, ok := usefulerror.AsUsefulError(tt.inputError)
|
||||||
|
|
||||||
if tt.wantNil {
|
if tt.wantNil {
|
||||||
assert.Nil(t, result)
|
assert.False(t, ok)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
assert.NotNil(t, result)
|
assert.NotNil(t, result)
|
||||||
assert.Equal(t, tt.wantCode, result.Code())
|
assert.Equal(t, tt.wantCode, result.Code())
|
||||||
|
|
||||||
|
|||||||
+18
-17
@@ -1,7 +1,8 @@
|
|||||||
package packagemanager
|
package packagemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -12,44 +13,44 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPackageNotFound = usefulerror.Useful().
|
ErrPackageNotFound = usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeNotFound).
|
WithCode(errcodes.NotFound).
|
||||||
WithHumanError("The requested package could not be found.").
|
WithHumanError("The requested package could not be found.").
|
||||||
WithHelp("Please check the package name and try again.")
|
WithHelp("Please check the package name and try again.")
|
||||||
|
|
||||||
ErrFailedToFetchPackage = usefulerror.Useful().
|
ErrFailedToFetchPackage = usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeNetwork).
|
WithCode(errcodes.Network).
|
||||||
WithHumanError("Failed to retrieve the requested package.").
|
WithHumanError("Failed to retrieve the requested package.").
|
||||||
WithHelp("Check your network connection and try again.").
|
WithHelp("Check your network connection and try again.").
|
||||||
Msg("failed to fetch package")
|
WithMsg("failed to fetch package")
|
||||||
|
|
||||||
ErrFailedToResolveVersion = usefulerror.Useful().
|
ErrFailedToResolveVersion = usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeNetwork).
|
WithCode(errcodes.Network).
|
||||||
WithHumanError("Failed to resolve the requested package version.").
|
WithHumanError("Failed to resolve the requested package version.").
|
||||||
WithHelp("Check your network connection and try again.").
|
WithHelp("Check your network connection and try again.").
|
||||||
Msg("failed to resolve package version")
|
WithMsg("failed to resolve package version")
|
||||||
|
|
||||||
ErrFailedToResolveDependencies = usefulerror.Useful().
|
ErrFailedToResolveDependencies = usefulerror.NewUsefulError().
|
||||||
WithCode(errDependencyResolutionFailed).
|
WithCode(errDependencyResolutionFailed).
|
||||||
WithHumanError("Failed to resolve dependencies.").
|
WithHumanError("Failed to resolve dependencies.").
|
||||||
WithHelp("Check your network connection and try again.").
|
WithHelp("Check your network connection and try again.").
|
||||||
Msg("failed to resolve dependencies")
|
WithMsg("failed to resolve dependencies")
|
||||||
|
|
||||||
ErrFailedToParsePackage = usefulerror.Useful().
|
ErrFailedToParsePackage = usefulerror.NewUsefulError().
|
||||||
WithCode(errPackageParseFailed).
|
WithCode(errPackageParseFailed).
|
||||||
WithHumanError("The package data could not be processed.").
|
WithHumanError("The package data could not be processed.").
|
||||||
WithHelp("The package may be corrupted or in an unsupported format.").
|
WithHelp("The package may be corrupted or in an unsupported format.").
|
||||||
Msg("failed to parse package")
|
WithMsg("failed to parse package")
|
||||||
|
|
||||||
ErrAuthorNotFound = usefulerror.Useful().
|
ErrAuthorNotFound = usefulerror.NewUsefulError().
|
||||||
WithCode(errPackageAuthorNotFound).
|
WithCode(errPackageAuthorNotFound).
|
||||||
WithHumanError("The package author information could not be found.").
|
WithHumanError("The package author information could not be found.").
|
||||||
WithHelp("This may be due to incomplete package metadata or network issues.").
|
WithHelp("This may be due to incomplete package metadata or network issues.").
|
||||||
Msg("author not found")
|
WithMsg("author not found")
|
||||||
|
|
||||||
ErrGitHubRateLimitExceeded = usefulerror.Useful().
|
ErrGitHubRateLimitExceeded = usefulerror.NewUsefulError().
|
||||||
WithCode(errGitHubRateLimitExceeded).
|
WithCode(errGitHubRateLimitExceeded).
|
||||||
WithHumanError("GitHub API rate limit has been exceeded.").
|
WithHumanError("GitHub API rate limit has been exceeded.").
|
||||||
WithHelp("Wait for the rate limit to reset or configure authentication to increase your rate limit.").
|
WithHelp("Wait for the rate limit to reset or configure authentication to increase your rate limit.").
|
||||||
Msg("github api rate limit exceeded")
|
WithMsg("github api rate limit exceeded")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import (
|
|||||||
"github.com/safedep/pmg/internal/audit"
|
"github.com/safedep/pmg/internal/audit"
|
||||||
"github.com/safedep/pmg/sandbox"
|
"github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/sandbox/platform"
|
"github.com/safedep/pmg/sandbox/platform"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type applySandboxConfig struct {
|
type applySandboxConfig struct {
|
||||||
@@ -58,8 +59,8 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...app
|
|||||||
|
|
||||||
policy, err = registry.GetProfile(cfg.SandboxProfileOverride)
|
policy, err = registry.GetProfile(cfg.SandboxProfileOverride)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, usefulerror.Useful().
|
return nil, usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
WithCode(errcodes.InvalidArgument).
|
||||||
WithHumanError(fmt.Sprintf("Failed to load sandbox profile override: %s", cfg.SandboxProfileOverride)).
|
WithHumanError(fmt.Sprintf("Failed to load sandbox profile override: %s", cfg.SandboxProfileOverride)).
|
||||||
WithHelp("Please verify the sandbox profile path and try again.").
|
WithHelp("Please verify the sandbox profile path and try again.").
|
||||||
WithAdditionalHelp("See more at: https://github.com/safedep/pmg/blob/main/docs/sandbox.md").
|
WithAdditionalHelp("See more at: https://github.com/safedep/pmg/blob/main/docs/sandbox.md").
|
||||||
@@ -74,8 +75,8 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...app
|
|||||||
// disable for the package manager in the config.
|
// disable for the package manager in the config.
|
||||||
policyRef, exists := cfg.Config.Sandbox.Policies[pmName]
|
policyRef, exists := cfg.Config.Sandbox.Policies[pmName]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, usefulerror.Useful().
|
return nil, usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeNotFound).
|
WithCode(errcodes.NotFound).
|
||||||
WithHumanError(fmt.Sprintf("No sandbox policy configured for %s", pmName)).
|
WithHumanError(fmt.Sprintf("No sandbox policy configured for %s", pmName)).
|
||||||
WithHelp("Please configure a sandbox policy for this package manager in the config file.").
|
WithHelp("Please configure a sandbox policy for this package manager in the config file.").
|
||||||
WithAdditionalHelp("See https://github.com/safedep/pmg/blob/main/docs/sandbox.md for more information.").
|
WithAdditionalHelp("See https://github.com/safedep/pmg/blob/main/docs/sandbox.md for more information.").
|
||||||
@@ -137,8 +138,8 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...app
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !sb.IsAvailable() {
|
if !sb.IsAvailable() {
|
||||||
return nil, usefulerror.Useful().
|
return nil, usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodeInvalidArgument).
|
WithCode(errcodes.InvalidArgument).
|
||||||
WithHumanError(fmt.Sprintf("Sandbox %s is required but not available", sb.Name())).
|
WithHumanError(fmt.Sprintf("Sandbox %s is required but not available", sb.Name())).
|
||||||
WithHelp("Please install the sandbox provider and try again.").
|
WithHelp("Please install the sandbox provider and try again.").
|
||||||
WithAdditionalHelp("See more at: https://github.com/safedep/pmg/blob/main/docs/sandbox.md").
|
WithAdditionalHelp("See more at: https://github.com/safedep/pmg/blob/main/docs/sandbox.md").
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import (
|
|||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
"github.com/safedep/pmg/sandbox"
|
"github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WrapCommandExecutionError converts a package manager execution error into a
|
// WrapCommandExecutionError converts a package manager execution error into a
|
||||||
@@ -28,8 +29,8 @@ func WrapCommandExecutionError(err error, result *sandbox.ExecutionResult, exitC
|
|||||||
}
|
}
|
||||||
|
|
||||||
help := "Check the package manager command and its arguments"
|
help := "Check the package manager command and its arguments"
|
||||||
builder := usefulerror.Useful().
|
builder := usefulerror.NewUsefulError().
|
||||||
WithCode(usefulerror.ErrCodePackageManagerExecutionFailed).
|
WithCode(errcodes.PackageManagerExecutionFailed).
|
||||||
WithHumanError(humanError).
|
WithHumanError(humanError).
|
||||||
WithHelp(help)
|
WithHelp(help)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/safedep/pmg/sandbox"
|
"github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -61,7 +62,7 @@ func TestWrapCommandExecutionErrorDoesNotAttributeFailureToSandbox(t *testing.T)
|
|||||||
|
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodePackageManagerExecutionFailed, usefulErr.Code())
|
assert.Equal(t, errcodes.PackageManagerExecutionFailed, usefulErr.Code())
|
||||||
assert.Equal(t, "Package manager command exited with code: 1", usefulErr.HumanError())
|
assert.Equal(t, "Package manager command exited with code: 1", usefulErr.HumanError())
|
||||||
assert.NotContains(t, usefulErr.Help(), "./.env")
|
assert.NotContains(t, usefulErr.Help(), "./.env")
|
||||||
assert.Contains(t, usefulErr.AdditionalHelp(), "pmg sandbox violations list")
|
assert.Contains(t, usefulErr.AdditionalHelp(), "pmg sandbox violations list")
|
||||||
@@ -76,7 +77,7 @@ func TestWrapCommandExecutionErrorOmitsBreadcrumbWhenNoViolations(t *testing.T)
|
|||||||
|
|
||||||
usefulErr, ok := usefulerror.AsUsefulError(err)
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, usefulerror.ErrCodePackageManagerExecutionFailed, usefulErr.Code())
|
assert.Equal(t, errcodes.PackageManagerExecutionFailed, usefulErr.Code())
|
||||||
assert.NotContains(t, usefulErr.AdditionalHelp(), "pmg sandbox violations list")
|
assert.NotContains(t, usefulErr.AdditionalHelp(), "pmg sandbox violations list")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/safedep/dry/log"
|
"github.com/safedep/dry/log"
|
||||||
|
"github.com/safedep/dry/usefulerror"
|
||||||
|
"github.com/safedep/pmg/errcodes"
|
||||||
"github.com/safedep/pmg/sandbox"
|
"github.com/safedep/pmg/sandbox"
|
||||||
"github.com/safedep/pmg/usefulerror"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// bubblewrapSandbox implements the Sandbox interface using Bubblewrap (bwrap) on Linux.
|
// bubblewrapSandbox implements the Sandbox interface using Bubblewrap (bwrap) on Linux.
|
||||||
@@ -45,8 +46,8 @@ func newBubblewrapSandbox() (*bubblewrapSandbox, error) {
|
|||||||
func (b *bubblewrapSandbox) Execute(ctx context.Context, cmd *exec.Cmd, policy *sandbox.SandboxPolicy) (*sandbox.ExecutionResult, error) {
|
func (b *bubblewrapSandbox) Execute(ctx context.Context, cmd *exec.Cmd, policy *sandbox.SandboxPolicy) (*sandbox.ExecutionResult, error) {
|
||||||
bwrapPath, err := exec.LookPath("bwrap")
|
bwrapPath, err := exec.LookPath("bwrap")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, usefulerror.Useful().
|
return nil, usefulerror.NewUsefulError().
|
||||||
WithCode("bubblewrap_not_found").
|
WithCode(errcodes.BubblewrapNotFound).
|
||||||
WithHumanError("Bubblewrap binary not found").
|
WithHumanError("Bubblewrap binary not found").
|
||||||
WithHelp("See more at: https://github.com/safedep/pmg/blob/main/docs/sandbox.md").
|
WithHelp("See more at: https://github.com/safedep/pmg/blob/main/docs/sandbox.md").
|
||||||
Wrap(fmt.Errorf("bubblewrap binary not found: %w", err))
|
Wrap(fmt.Errorf("bubblewrap binary not found: %w", err))
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package usefulerror
|
|
||||||
|
|
||||||
// Standard error codes that can be re-used across the project.
|
|
||||||
// We will use a human friendly format for the error codes and not align with posix error codes.
|
|
||||||
// Keep this minimal. Reuse first before adding new ones.
|
|
||||||
const (
|
|
||||||
ErrCodeInvalidArgument = "InvalidArgument"
|
|
||||||
ErrCodePermissionDenied = "PermissionDenied"
|
|
||||||
ErrCodeNotFound = "NotFound"
|
|
||||||
ErrCodeTimeout = "Timeout"
|
|
||||||
ErrCodeCanceled = "Canceled"
|
|
||||||
ErrCodeUnexpectedEOF = "UnexpectedEOF"
|
|
||||||
ErrCodeUnknown = "Unknown"
|
|
||||||
ErrCodeLifecycle = "Lifecycle"
|
|
||||||
ErrCodeNetwork = "Network"
|
|
||||||
ErrCodeSandboxViolation = "SandboxViolation"
|
|
||||||
ErrCodePackageManagerExecutionFailed = "PackageManagerExecutionFailed"
|
|
||||||
)
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package usefulerror
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UsefulError is an interface that can be implemented for custom error types
|
|
||||||
// that are actually useful for the user. Think of this as a way out of showing
|
|
||||||
// weird internal errors to the user, which actually don't help them
|
|
||||||
type UsefulError interface {
|
|
||||||
// Error returns a string that is useful for the user.
|
|
||||||
// Maintains compatibility with the standard error interface.
|
|
||||||
Error() string
|
|
||||||
|
|
||||||
// HumanError returns a string that is more human-readable.
|
|
||||||
HumanError() string
|
|
||||||
|
|
||||||
// Help returns a string that provides help or guidance specific to the
|
|
||||||
// business logic of the error.
|
|
||||||
Help() string
|
|
||||||
|
|
||||||
// AdditionalHelp returns a string that provides additional help or guidance
|
|
||||||
// This is useful for providing specific tooling related instructions such
|
|
||||||
// as common line flags to use to fix the error.
|
|
||||||
AdditionalHelp() string
|
|
||||||
|
|
||||||
// Code returns a string that can be used to identify the error types
|
|
||||||
// Meant for programmatic use, such as logging or categorization
|
|
||||||
Code() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type usefulErrorBuilder struct {
|
|
||||||
originalError error
|
|
||||||
humanError string
|
|
||||||
help string
|
|
||||||
additionalHelp string
|
|
||||||
code string
|
|
||||||
msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UsefulError = (*usefulErrorBuilder)(nil)
|
|
||||||
|
|
||||||
func Useful() *usefulErrorBuilder {
|
|
||||||
return &usefulErrorBuilder{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *usefulErrorBuilder) Wrap(originalError error) *usefulErrorBuilder {
|
|
||||||
b.originalError = originalError
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHumanError sets a string that is more human-readable.
|
|
||||||
func (b *usefulErrorBuilder) WithHumanError(humanError string) *usefulErrorBuilder {
|
|
||||||
b.humanError = humanError
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHelp sets a string that provides additional help or guidance.
|
|
||||||
func (b *usefulErrorBuilder) WithHelp(help string) *usefulErrorBuilder {
|
|
||||||
b.help = help
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithCode sets a code that can be used to identify the error types.
|
|
||||||
func (b *usefulErrorBuilder) WithCode(code string) *usefulErrorBuilder {
|
|
||||||
b.code = code
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMsg sets a message that is useful for the user, but not necessarily human-readable.
|
|
||||||
func (b *usefulErrorBuilder) Msg(msg string) *usefulErrorBuilder {
|
|
||||||
b.msg = msg
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAdditionalHelp sets a string that provides additional help or guidance.
|
|
||||||
func (b *usefulErrorBuilder) WithAdditionalHelp(additionalHelp string) *usefulErrorBuilder {
|
|
||||||
b.additionalHelp = additionalHelp
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the standard error interface. It returns the original error's message if present;
|
|
||||||
// otherwise, it returns a constructed message based on the code and msg fields, or "unknown error" if none are set.
|
|
||||||
func (b *usefulErrorBuilder) Error() string {
|
|
||||||
if b.originalError != nil {
|
|
||||||
return b.originalError.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.msg == "" {
|
|
||||||
return "unknown error"
|
|
||||||
}
|
|
||||||
|
|
||||||
msgParts := []string{}
|
|
||||||
if b.code != "" {
|
|
||||||
msgParts = append(msgParts, b.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.msg != "" {
|
|
||||||
msgParts = append(msgParts, b.msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(msgParts, ": ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// HumanError returns a string that is more human-readable.
|
|
||||||
func (b *usefulErrorBuilder) HumanError() string {
|
|
||||||
if b.humanError == "" {
|
|
||||||
return "An error occurred, but no human-readable message is available."
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.humanError
|
|
||||||
}
|
|
||||||
|
|
||||||
// Help returns a string that provides additional help or guidance.
|
|
||||||
func (b *usefulErrorBuilder) Help() string {
|
|
||||||
if b.help == "" {
|
|
||||||
return "No additional help is available for this error."
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.help
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code returns a string that can be used to identify the error types.
|
|
||||||
func (b *usefulErrorBuilder) Code() string {
|
|
||||||
if b.code == "" {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.code
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdditionalHelp returns a string that provides additional help or guidance.
|
|
||||||
func (b *usefulErrorBuilder) AdditionalHelp() string {
|
|
||||||
if b.additionalHelp == "" {
|
|
||||||
return "No additional help is available for this error."
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.additionalHelp
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsUsefulError attempts to convert a given error into a UsefulError.
|
|
||||||
func AsUsefulError(err error) (UsefulError, bool) {
|
|
||||||
if err == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
var usefulErr *usefulErrorBuilder
|
|
||||||
if errors.As(err, &usefulErr) {
|
|
||||||
return usefulErr, true
|
|
||||||
}
|
|
||||||
|
|
||||||
if usefulErr, ok := err.(UsefulError); ok {
|
|
||||||
return usefulErr, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
package usefulerror
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_Error(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
builder func() *usefulErrorBuilder
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "with original error",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful().Wrap(errors.New("original error"))
|
|
||||||
},
|
|
||||||
expected: "original error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with msg only",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful().Msg("test message")
|
|
||||||
},
|
|
||||||
expected: "test message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with code and msg",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful().WithCode("TEST001").Msg("test message")
|
|
||||||
},
|
|
||||||
expected: "TEST001: test message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with code only",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful().WithCode("TEST001")
|
|
||||||
},
|
|
||||||
expected: "unknown error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty builder",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful()
|
|
||||||
},
|
|
||||||
expected: "unknown error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := tt.builder()
|
|
||||||
assert.Equal(t, tt.expected, err.Error())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_HumanError(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
builder func() *usefulErrorBuilder
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "with human error set",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful().WithHumanError("Something went wrong")
|
|
||||||
},
|
|
||||||
expected: "Something went wrong",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty human error",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful()
|
|
||||||
},
|
|
||||||
expected: "An error occurred, but no human-readable message is available.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := tt.builder()
|
|
||||||
assert.Equal(t, tt.expected, err.HumanError())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_Help(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
builder func() *usefulErrorBuilder
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "with help set",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful().WithHelp("Try running with --verbose flag")
|
|
||||||
},
|
|
||||||
expected: "Try running with --verbose flag",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty help",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful()
|
|
||||||
},
|
|
||||||
expected: "No additional help is available for this error.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := tt.builder()
|
|
||||||
assert.Equal(t, tt.expected, err.Help())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_AdditionalHelp(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
builder func() *usefulErrorBuilder
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "with additional help set",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful().WithAdditionalHelp("Use --force to override")
|
|
||||||
},
|
|
||||||
expected: "Use --force to override",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty additional help",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful()
|
|
||||||
},
|
|
||||||
expected: "No additional help is available for this error.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := tt.builder()
|
|
||||||
assert.Equal(t, tt.expected, err.AdditionalHelp())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_Code(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
builder func() *usefulErrorBuilder
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "with code set",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful().WithCode("ERR001")
|
|
||||||
},
|
|
||||||
expected: "ERR001",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty code",
|
|
||||||
builder: func() *usefulErrorBuilder {
|
|
||||||
return Useful()
|
|
||||||
},
|
|
||||||
expected: "unknown",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := tt.builder()
|
|
||||||
assert.Equal(t, tt.expected, err.Code())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_ChainedMethods(t *testing.T) {
|
|
||||||
err := Useful().
|
|
||||||
WithCode("TEST001").
|
|
||||||
Msg("test message").
|
|
||||||
WithHumanError("User friendly error").
|
|
||||||
WithHelp("Try this fix").
|
|
||||||
WithAdditionalHelp("Or try this")
|
|
||||||
|
|
||||||
assert.Equal(t, "TEST001: test message", err.Error())
|
|
||||||
assert.Equal(t, "User friendly error", err.HumanError())
|
|
||||||
assert.Equal(t, "Try this fix", err.Help())
|
|
||||||
assert.Equal(t, "Or try this", err.AdditionalHelp())
|
|
||||||
assert.Equal(t, "TEST001", err.Code())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAsUsefulError(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input error
|
|
||||||
expectOk bool
|
|
||||||
expectError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nil error",
|
|
||||||
input: nil,
|
|
||||||
expectOk: false,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "useful error builder",
|
|
||||||
input: Useful().Msg("test"),
|
|
||||||
expectOk: true,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "regular error",
|
|
||||||
input: errors.New("regular error"),
|
|
||||||
expectOk: false,
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result, ok := AsUsefulError(tt.input)
|
|
||||||
assert.Equal(t, tt.expectOk, ok)
|
|
||||||
if tt.expectOk {
|
|
||||||
assert.NotNil(t, result)
|
|
||||||
assert.Implements(t, (*UsefulError)(nil), result)
|
|
||||||
} else {
|
|
||||||
assert.Nil(t, result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_ImplementsUsefulError(t *testing.T) {
|
|
||||||
var _ UsefulError = (*usefulErrorBuilder)(nil)
|
|
||||||
|
|
||||||
builder := Useful()
|
|
||||||
assert.Implements(t, (*UsefulError)(nil), builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_Wrap(t *testing.T) {
|
|
||||||
originalErr := errors.New("original error")
|
|
||||||
wrappedErr := Useful().Wrap(originalErr)
|
|
||||||
|
|
||||||
assert.Equal(t, "original error", wrappedErr.Error())
|
|
||||||
assert.Equal(t, "An error occurred, but no human-readable message is available.", wrappedErr.HumanError())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsefulErrorBuilder_ComplexScenario(t *testing.T) {
|
|
||||||
originalErr := errors.New("file not found")
|
|
||||||
|
|
||||||
err := Useful().
|
|
||||||
Wrap(originalErr).
|
|
||||||
WithCode("FILE001").
|
|
||||||
WithHumanError("The configuration file could not be found").
|
|
||||||
WithHelp("Make sure the config file exists in the current directory").
|
|
||||||
WithAdditionalHelp("Use --config flag to specify a different location")
|
|
||||||
|
|
||||||
assert.Equal(t, "file not found", err.Error())
|
|
||||||
assert.Equal(t, "The configuration file could not be found", err.HumanError())
|
|
||||||
assert.Equal(t, "Make sure the config file exists in the current directory", err.Help())
|
|
||||||
assert.Equal(t, "Use --config flag to specify a different location", err.AdditionalHelp())
|
|
||||||
assert.Equal(t, "FILE001", err.Code())
|
|
||||||
|
|
||||||
usefulErr, ok := AsUsefulError(err)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, err, usefulErr)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user