Merge pull request #239 from cloudskiff/better_error_handling

Better error handling
main
Elie 2021-02-10 15:05:47 +01:00 committed by GitHub
commit cb5b62b3df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 227 additions and 89 deletions

View File

@ -11,8 +11,9 @@ import (
cmderrors "github.com/cloudskiff/driftctl/pkg/cmd/errors"
"github.com/cloudskiff/driftctl/pkg/config"
"github.com/cloudskiff/driftctl/pkg/version"
"github.com/cloudskiff/driftctl/sentry"
"github.com/fatih/color"
"github.com/getsentry/sentry-go"
gosentry "github.com/getsentry/sentry-go"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
)
@ -47,7 +48,7 @@ func run() int {
if cmd.IsReportingEnabled(&driftctlCmd.Command) {
err := recover()
if err != nil {
sentry.CurrentHub().Recover(err)
gosentry.CurrentHub().Recover(err)
flushSentry()
logrus.Fatalf("Captured panic: %s", err)
os.Exit(2)
@ -80,6 +81,6 @@ func run() int {
func flushSentry() {
fmt.Print("Sending error report ...")
sentry.Flush(60 * time.Second)
gosentry.Flush(60 * time.Second)
fmt.Printf(" done, thank you %s\n", color.RedString("❤️"))
}

View File

@ -1,13 +1,11 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/cloudskiff/driftctl/build"
"github.com/cloudskiff/driftctl/pkg/version"
"github.com/getsentry/sentry-go"
"github.com/cloudskiff/driftctl/sentry"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -99,12 +97,7 @@ func IsReportingEnabled(cmd *cobra.Command) bool {
func handleReporting(cmd *cobra.Command) error {
if IsReportingEnabled(cmd) {
logrus.Debug("Enabled error reporting")
return sentry.Init(sentry.ClientOptions{
Dsn: "https://9f2b735e20bc452387f7fa093f786173@o495597.ingest.sentry.io/5568568",
Release: fmt.Sprintf("driftctl@%s", version.Current()),
AttachStacktrace: true,
})
return sentry.Initialize()
}
return nil
}

View File

@ -99,7 +99,7 @@ func TestDriftctlCmd_Scan(t *testing.T) {
env: map[string]string{
"DCTL_FROM": "test",
},
err: fmt.Errorf("Unable to parse from flag: test\nAccepted schemes are: tfstate://,tfstate+s3://"),
err: fmt.Errorf("Unable to parse from flag 'test': \nAccepted schemes are: tfstate://,tfstate+s3://"),
},
{
env: map[string]string{
@ -111,7 +111,7 @@ func TestDriftctlCmd_Scan(t *testing.T) {
env: map[string]string{
"DCTL_OUTPUT": "test",
},
err: fmt.Errorf("Unable to parse output flag: test\nAccepted formats are: console://,json://PATH/TO/FILE.json"),
err: fmt.Errorf("Unable to parse output flag 'test': \nAccepted formats are: console://,json://PATH/TO/FILE.json"),
},
{
env: map[string]string{

13
pkg/cmd/errors/errors.go Normal file
View File

@ -0,0 +1,13 @@
package errors
type UsageError struct {
msg string
}
func NewUsageError(msg string) UsageError {
return UsageError{msg}
}
func (u UsageError) Error() string {
return u.msg
}

View File

@ -1,7 +1,6 @@
package cmd
import (
"errors"
"fmt"
"os"
"os/signal"
@ -20,6 +19,7 @@ import (
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/jmespath/go-jmespath"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -53,7 +53,7 @@ func NewScanCmd() *cobra.Command {
to, _ := cmd.Flags().GetString("to")
if !remote.IsSupported(to) {
return fmt.Errorf(
return errors.Errorf(
"unsupported cloud provider '%s'\nValid values are: %s",
to,
strings.Join(remote.GetSupportedRemotes(), ","),
@ -71,7 +71,7 @@ func NewScanCmd() *cobra.Command {
if filterFlag != "" {
expr, err := filter.BuildExpression(filterFlag)
if err != nil {
return fmt.Errorf("unable to parse filter expression: %s", err.Error())
return errors.Wrap(err, "unable to parse filter expression")
}
opts.Filter = expr
}
@ -155,10 +155,10 @@ func scanRun(opts *ScanOptions) error {
ctl.Stop()
}()
analysis := ctl.Run()
analysis, err := ctl.Run()
if analysis == nil {
return errors.New("unable to run driftctl")
if err != nil {
return err
}
err = output.GetOutput(opts.Output).Write(analysis)
@ -180,10 +180,15 @@ func parseFromFlag(from []string) ([]config.SupplierConfig, error) {
for _, flag := range from {
schemePath := strings.Split(flag, "://")
if len(schemePath) != 2 || schemePath[1] == "" || schemePath[0] == "" {
return nil, fmt.Errorf(
"Unable to parse from flag: %s\nAccepted schemes are: %s",
return nil, errors.Wrapf(
cmderrors.NewUsageError(
fmt.Sprintf(
"\nAccepted schemes are: %s",
strings.Join(supplier.GetSupportedSchemes(), ","),
),
),
"Unable to parse from flag '%s'",
flag,
strings.Join(supplier.GetSupportedSchemes(), ","),
)
}
@ -191,19 +196,28 @@ func parseFromFlag(from []string) ([]config.SupplierConfig, error) {
path := schemePath[1]
supplierBackend := strings.Split(scheme, "+")
if len(supplierBackend) > 2 {
return nil, fmt.Errorf(
"Unable to parse from scheme: %s\nAccepted schemes are: %s",
return nil, errors.Wrapf(
cmderrors.NewUsageError(fmt.Sprintf(
"\nAccepted schemes are: %s",
strings.Join(supplier.GetSupportedSchemes(), ","),
),
),
"Unable to parse from scheme '%s'",
scheme,
strings.Join(supplier.GetSupportedSchemes(), ","),
)
}
supplierKey := supplierBackend[0]
if !supplier.IsSupplierSupported(supplierKey) {
return nil, fmt.Errorf(
"Unsupported IaC source: %s\nAccepted values are: %s",
return nil, errors.Wrapf(
cmderrors.NewUsageError(
fmt.Sprintf(
"\nAccepted values are: %s",
strings.Join(supplier.GetSupportedSuppliers(), ","),
),
),
"Unsupported IaC source '%s'",
supplierKey,
strings.Join(supplier.GetSupportedSuppliers(), ","),
)
}
@ -211,10 +225,15 @@ func parseFromFlag(from []string) ([]config.SupplierConfig, error) {
if len(supplierBackend) == 2 {
backendString = supplierBackend[1]
if !backend.IsSupported(backendString) {
return nil, fmt.Errorf(
"Unsupported IaC backend: %s\nAccepted values are: %s",
return nil, errors.Wrapf(
cmderrors.NewUsageError(
fmt.Sprintf(
"\nAccepted values are: %s",
strings.Join(backend.GetSupportedBackends(), ","),
),
),
"Unsupported IaC backend '%s'",
backendString,
strings.Join(backend.GetSupportedBackends(), ","),
)
}
}
@ -232,19 +251,29 @@ func parseFromFlag(from []string) ([]config.SupplierConfig, error) {
func parseOutputFlag(out string) (*output.OutputConfig, error) {
schemeOpts := strings.Split(out, "://")
if len(schemeOpts) < 2 || schemeOpts[0] == "" {
return nil, fmt.Errorf(
"Unable to parse output flag: %s\nAccepted formats are: %s",
return nil, errors.Wrapf(
cmderrors.NewUsageError(
fmt.Sprintf(
"\nAccepted formats are: %s",
strings.Join(output.SupportedOutputsExample(), ","),
),
),
"Unable to parse output flag '%s'",
out,
strings.Join(output.SupportedOutputsExample(), ","),
)
}
o := schemeOpts[0]
if !output.IsSupported(o) {
return nil, fmt.Errorf(
"Unsupported output '%s'\nValid formats are: %s",
return nil, errors.Wrapf(
cmderrors.NewUsageError(
fmt.Sprintf(
"\nValid formats are: %s",
strings.Join(output.SupportedOutputsExample(), ","),
),
),
"Unsupported output '%s'",
o,
strings.Join(output.SupportedOutputsExample(), ","),
)
}
@ -254,10 +283,15 @@ func parseOutputFlag(out string) (*output.OutputConfig, error) {
switch o {
case output.JSONOutputType:
if len(opts) != 1 || opts[0] == "" {
return nil, fmt.Errorf(
"Invalid json output '%s'\nMust be of kind: %s",
return nil, errors.Wrapf(
cmderrors.NewUsageError(
fmt.Sprintf(
"\nMust be of kind: %s",
output.Example(output.JSONOutputType),
),
),
"Invalid json output '%s'",
out,
output.Example(output.JSONOutputType),
)
}
options["path"] = opts[0]

View File

@ -67,14 +67,14 @@ func TestScanCmd_Invalid(t *testing.T) {
{args: []string{"scan", "-f"}, expected: `flag needs an argument: 'f' in -f`},
{args: []string{"scan", "--from"}, expected: `flag needs an argument: --from`},
{args: []string{"scan", "--from"}, expected: `flag needs an argument: --from`},
{args: []string{"scan", "--from", "tosdgjhgsdhgkjs"}, expected: "Unable to parse from flag: tosdgjhgsdhgkjs\nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "://"}, expected: "Unable to parse from flag: ://\nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "://test"}, expected: "Unable to parse from flag: ://test\nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "tosdgjhgsdhgkjs://"}, expected: "Unable to parse from flag: tosdgjhgsdhgkjs://\nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "terraform+foo+bar://test"}, expected: "Unable to parse from scheme: terraform+foo+bar\nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "unsupported://test"}, expected: "Unsupported IaC source: unsupported\nAccepted values are: tfstate"},
{args: []string{"scan", "--from", "tfstate+foobar://test"}, expected: "Unsupported IaC backend: foobar\nAccepted values are: s3"},
{args: []string{"scan", "--from", "tfstate:///tmp/test", "--from", "tfstate+toto://test"}, expected: "Unsupported IaC backend: toto\nAccepted values are: s3"},
{args: []string{"scan", "--from", "tosdgjhgsdhgkjs"}, expected: "Unable to parse from flag 'tosdgjhgsdhgkjs': \nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "://"}, expected: "Unable to parse from flag '://': \nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "://test"}, expected: "Unable to parse from flag '://test': \nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "tosdgjhgsdhgkjs://"}, expected: "Unable to parse from flag 'tosdgjhgsdhgkjs://': \nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "terraform+foo+bar://test"}, expected: "Unable to parse from scheme 'terraform+foo+bar': \nAccepted schemes are: tfstate://,tfstate+s3://"},
{args: []string{"scan", "--from", "unsupported://test"}, expected: "Unsupported IaC source 'unsupported': \nAccepted values are: tfstate"},
{args: []string{"scan", "--from", "tfstate+foobar://test"}, expected: "Unsupported IaC backend 'foobar': \nAccepted values are: s3"},
{args: []string{"scan", "--from", "tfstate:///tmp/test", "--from", "tfstate+toto://test"}, expected: "Unsupported IaC backend 'toto': \nAccepted values are: s3"},
{args: []string{"scan", "--filter", "Type='test'"}, expected: "unable to parse filter expression: SyntaxError: Expected tRbracket, received: tUnknown"},
}
@ -165,7 +165,7 @@ func Test_parseOutputFlag(t *testing.T) {
out: "",
},
want: nil,
err: fmt.Errorf("Unable to parse output flag: \nAccepted formats are: console://,json://PATH/TO/FILE.json"),
err: fmt.Errorf("Unable to parse output flag '': \nAccepted formats are: console://,json://PATH/TO/FILE.json"),
},
{
name: "test invalid",
@ -173,7 +173,7 @@ func Test_parseOutputFlag(t *testing.T) {
out: "sdgjsdgjsdg",
},
want: nil,
err: fmt.Errorf("Unable to parse output flag: sdgjsdgjsdg\nAccepted formats are: console://,json://PATH/TO/FILE.json"),
err: fmt.Errorf("Unable to parse output flag 'sdgjsdgjsdg': \nAccepted formats are: console://,json://PATH/TO/FILE.json"),
},
{
name: "test invalid",
@ -181,7 +181,7 @@ func Test_parseOutputFlag(t *testing.T) {
out: "://",
},
want: nil,
err: fmt.Errorf("Unable to parse output flag: ://\nAccepted formats are: console://,json://PATH/TO/FILE.json"),
err: fmt.Errorf("Unable to parse output flag '://': \nAccepted formats are: console://,json://PATH/TO/FILE.json"),
},
{
name: "test unsupported",
@ -189,7 +189,7 @@ func Test_parseOutputFlag(t *testing.T) {
out: "foobar://",
},
want: nil,
err: fmt.Errorf("Unsupported output 'foobar'\nValid formats are: console://,json://PATH/TO/FILE.json"),
err: fmt.Errorf("Unsupported output 'foobar': \nValid formats are: console://,json://PATH/TO/FILE.json"),
},
{
name: "test empty json",
@ -197,7 +197,7 @@ func Test_parseOutputFlag(t *testing.T) {
out: "json://",
},
want: nil,
err: fmt.Errorf("Invalid json output 'json://'\nMust be of kind: json://PATH/TO/FILE.json"),
err: fmt.Errorf("Invalid json output 'json://': \nMust be of kind: json://PATH/TO/FILE.json"),
},
{
name: "test valid console",

View File

@ -9,6 +9,7 @@ import (
"github.com/cloudskiff/driftctl/pkg/middlewares"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/jmespath/go-jmespath"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -24,11 +25,10 @@ func NewDriftCTL(remoteSupplier resource.Supplier, iacSupplier resource.Supplier
return &DriftCTL{remoteSupplier, iacSupplier, alerter, analyser.NewAnalyzer(alerter), filter}
}
func (d DriftCTL) Run() *analyser.Analysis {
func (d DriftCTL) Run() (*analyser.Analysis, error) {
remoteResources, resourcesFromState, err := d.scan()
if err != nil {
logrus.Errorf("Unable to scan resources: %+v", err)
return nil
return nil, errors.Wrap(err, "Unable to scan resources")
}
middleware := middlewares.NewChain(
@ -56,21 +56,18 @@ func (d DriftCTL) Run() *analyser.Analysis {
logrus.Debug("Ready to run middlewares")
err = middleware.Execute(&remoteResources, &resourcesFromState)
if err != nil {
logrus.Errorf("Unable to run middlewares: %+v", err)
return nil
return nil, errors.Wrap(err, "Unable to run middlewares")
}
if d.filter != nil {
engine := filter.NewFilterEngine(d.filter)
remoteResources, err = engine.Run(remoteResources)
if err != nil {
logrus.Error(err)
return nil
return nil, errors.Wrap(err, "Unable to filter remote resources")
}
resourcesFromState, err = engine.Run(resourcesFromState)
if err != nil {
logrus.Error(err)
return nil
return nil, errors.Wrap(err, "Unable to filter state resources")
}
}
@ -80,11 +77,10 @@ func (d DriftCTL) Run() *analyser.Analysis {
analysis, err := d.analyzer.Analyze(remoteResources, resourcesFromState, driftIgnore)
if err != nil {
logrus.Errorf("Unable to analyse resources: %+v", err)
return nil
return nil, errors.Wrap(err, "Unable to perform resources analysis")
}
return &analysis
return &analysis, nil
}
func (d DriftCTL) Stop() {

View File

@ -5,6 +5,7 @@ import (
"github.com/cloudskiff/driftctl/pkg/iac/terraform/state/backend"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/cloudskiff/driftctl/pkg/iac/config"
@ -31,7 +32,7 @@ func GetIACSupplier(configs []config.SupplierConfig, library *terraform.Provider
chainSupplier := resource.NewChainSupplier()
for _, config := range configs {
if !IsSupplierSupported(config.Key) {
return nil, fmt.Errorf("Unsupported supplier '%s'", config.Key)
return nil, errors.Errorf("Unsupported supplier '%s'", config.Key)
}
var supplier resource.Supplier
@ -40,7 +41,7 @@ func GetIACSupplier(configs []config.SupplierConfig, library *terraform.Provider
case state.TerraformStateReaderSupplier:
supplier, err = state.NewReader(config, library)
default:
return nil, fmt.Errorf("Unsupported supplier '%s'", config.Key)
return nil, errors.Errorf("Unsupported supplier '%s'", config.Key)
}
if err != nil {

View File

@ -1,10 +1,10 @@
package backend
import (
"fmt"
"io"
"github.com/cloudskiff/driftctl/pkg/iac/config"
"github.com/pkg/errors"
)
var supportedBackends = []string{
@ -29,7 +29,7 @@ func GetBackend(config config.SupplierConfig) (Backend, error) {
backend := config.Backend
if !IsSupported(backend) {
return nil, fmt.Errorf("Unsupported backend '%s'", backend)
return nil, errors.Errorf("Unsupported backend '%s'", backend)
}
switch backend {
@ -38,7 +38,7 @@ func GetBackend(config config.SupplierConfig) (Backend, error) {
case backendS3:
return NewS3Reader(config.Path)
default:
return nil, fmt.Errorf("Unsupported backend '%s'", backend)
return nil, errors.Errorf("Unsupported backend '%s'", backend)
}
}

View File

@ -1,11 +1,11 @@
package backend
import (
"fmt"
"io"
"strings"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/pkg/errors"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3iface"
@ -24,7 +24,7 @@ func NewS3Reader(path string) (*S3Backend, error) {
backend := S3Backend{}
bucketPath := strings.Split(path, "/")
if len(bucketPath) < 2 {
return nil, fmt.Errorf("Unable to parse S3 path: %s. Must be BUCKET_NAME/PATH/TO/OBJECT", path)
return nil, errors.Errorf("Unable to parse S3 path: %s. Must be BUCKET_NAME/PATH/TO/OBJECT", path)
}
bucket := bucketPath[0]
key := strings.Join(bucketPath[1:], "/")
@ -46,7 +46,12 @@ func (s *S3Backend) Read(p []byte) (n int, err error) {
if err != nil {
requestFailure, ok := err.(s3.RequestFailure)
if ok {
return 0, fmt.Errorf("Error reading state '%s' from s3 bucket '%s': %s", *s.input.Key, *s.input.Bucket, requestFailure.Message())
return 0, errors.Errorf(
"Error reading state '%s' from s3 bucket '%s': %s",
*s.input.Key,
*s.input.Bucket,
requestFailure.Message(),
)
}
return 0, err
}
@ -59,5 +64,5 @@ func (s *S3Backend) Close() error {
if s.reader != nil {
return s.reader.Close()
}
return fmt.Errorf("Unable to close reader as nothing was opened")
return errors.New("Unable to close reader as nothing was opened")
}

View File

@ -2,10 +2,10 @@ package parallel
import (
"context"
"fmt"
"sync"
"github.com/getsentry/sentry-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"go.uber.org/atomic"
@ -91,7 +91,7 @@ func (p *ParallelRunner) Run(runnable func() (interface{}, error)) {
defer func() {
if r := recover(); r != nil {
sentry.CurrentHub().Recover(r)
p.Stop(fmt.Errorf("A runner routine paniced: %s", r))
p.Stop(errors.Errorf("A runner routine paniced: %s", r))
}
}()
res, err := runnable()

View File

@ -1,12 +1,11 @@
package remote
import (
"fmt"
"github.com/cloudskiff/driftctl/pkg/alerter"
"github.com/cloudskiff/driftctl/pkg/remote/aws"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/pkg/errors"
)
var supportedRemotes = []string{
@ -27,7 +26,7 @@ func Activate(remote string, alerter *alerter.Alerter, providerLibrary *terrafor
case aws.RemoteAWSTerraform:
return aws.Init(alerter, providerLibrary, supplierLibrary)
default:
return fmt.Errorf("unsupported remote '%s'", remote)
return errors.Errorf("unsupported remote '%s'", remote)
}
}

View File

@ -5,6 +5,7 @@ import (
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/pkg/errors"
)
func (r *AwsRoute) String() string {
@ -28,7 +29,7 @@ func CalculateRouteID(tableId, CidrBlock, Ipv6CidrBlock *string) (string, error)
return fmt.Sprintf("r-%s%d", *tableId, hashcode.String(*Ipv6CidrBlock)), nil
}
return "", fmt.Errorf("invalid route detected for table %s", *tableId)
return "", errors.Errorf("invalid route detected for table %s", *tableId)
}
func (r *AwsRoute) NormalizeForState() (resource.Resource, error) {

View File

@ -2,9 +2,9 @@ package pkg
import (
"context"
"fmt"
"github.com/cloudskiff/driftctl/pkg/remote"
"github.com/pkg/errors"
"github.com/cloudskiff/driftctl/pkg/parallel"
"github.com/sirupsen/logrus"
@ -85,5 +85,5 @@ loop:
func (s *Scanner) Stop() {
logrus.Debug("Stopping scanner")
s.runner.Stop(fmt.Errorf("interrupted"))
s.runner.Stop(errors.New("interrupted"))
}

View File

@ -2,13 +2,12 @@ package terraform
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/hashicorp/go-getter"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -46,17 +45,21 @@ func (p *ProviderDownloader) Download(url, path string) error {
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unsuccessful request to %s: %s", url, resp.Status)
return errors.Errorf("unsuccessful request to %s: %s", url, resp.Status)
}
f, err := ioutil.TempFile("", "terraform-provider")
if err != nil {
return fmt.Errorf("failed to open temporary file to download from %s", url)
return errors.Errorf("failed to open temporary file to download from %s", url)
}
defer f.Close()
defer os.Remove(f.Name())
n, err := getter.Copy(p.context, f, resp.Body)
if err == nil && n < resp.ContentLength {
err = fmt.Errorf("incorrect response size: expected %d bytes, but got %d bytes", resp.ContentLength, n)
err = errors.Errorf(
"incorrect response size: expected %d bytes, but got %d bytes",
resp.ContentLength,
n,
)
}
if err != nil {
return err

View File

@ -7,6 +7,7 @@ import (
"runtime"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -56,7 +57,10 @@ func (p *ProviderInstaller) Install() (string, error) {
}
if info != nil && info.IsDir() {
return "", fmt.Errorf("found directory instead of provider binary in %s", providerPath)
return "", errors.Errorf(
"found directory instead of provider binary in %s",
providerPath,
)
}
if info != nil {

49
sentry/sentry.go Normal file
View File

@ -0,0 +1,49 @@
package sentry
import (
"fmt"
"reflect"
cmderrors "github.com/cloudskiff/driftctl/pkg/cmd/errors"
"github.com/cloudskiff/driftctl/pkg/version"
gosentry "github.com/getsentry/sentry-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var excludedErrorTypes = []error{
cmderrors.UsageError{},
}
func Initialize() error {
logrus.Debug("Enabled error reporting")
return gosentry.Init(gosentry.ClientOptions{
Dsn: "https://9f2b735e20bc452387f7fa093f786173@o495597.ingest.sentry.io/5568568",
Release: fmt.Sprintf("driftctl@%s", version.Current()),
AttachStacktrace: true,
})
}
func shouldCaptureException(err error) bool {
errType, causeType := reflect.TypeOf(err), reflect.TypeOf(errors.Cause(err))
for _, exludedError := range excludedErrorTypes {
switch reflect.TypeOf(exludedError) {
case errType:
return false
case causeType:
return false
default:
}
}
logrus.WithFields(logrus.Fields{
"error_type": errType,
"cause_type": causeType,
}).Debug("Sentry captured error")
return true
}
func CaptureException(err error) {
if shouldCaptureException(err) {
gosentry.CaptureException(err)
}
}

39
sentry/sentry_test.go Normal file
View File

@ -0,0 +1,39 @@
package sentry
import (
"testing"
cmderrors "github.com/cloudskiff/driftctl/pkg/cmd/errors"
"github.com/pkg/errors"
)
func Test_shouldCaptureException(t *testing.T) {
tests := []struct {
name string
err error
want bool
}{
{
name: "should not capture errors.UsageError",
err: cmderrors.UsageError{},
want: false,
},
{
name: "should not capture wrapped errors.UsageError",
err: errors.Wrap(cmderrors.UsageError{}, "test"),
want: false,
},
{
name: "should capture errors.withStack",
err: errors.New("test"),
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := shouldCaptureException(tt.err); got != tt.want {
t.Errorf("shouldCaptureException() = %v, want %v", got, tt.want)
}
})
}
}