From 6fd9986cbcd32f95a6864dfdab1b7b42f793885a Mon Sep 17 00:00:00 2001 From: Martin Guibert Date: Wed, 28 Jul 2021 16:26:34 +0200 Subject: [PATCH 1/2] do not fail when --from is incorrect, send alert. fail only if all from are incorrect --- pkg/cmd/scan.go | 9 +- pkg/cmd/scan/output/console.go | 13 +-- pkg/iac/supplier/IacChainSupplier.go | 85 ++++++++++++++++++ pkg/iac/supplier/IacChainSupplier_test.go | 87 ++++++++++++++++++ pkg/iac/supplier/supplier.go | 6 +- pkg/iac/supplier/supplier_test.go | 5 +- pkg/iac/terraform/state/alerts.go | 20 +++++ pkg/iac/terraform/state/enumerator/file.go | 4 + pkg/iac/terraform/state/enumerator/s3.go | 4 + .../state/enumerator/state_enumerator.go | 1 + .../terraform/state/terraform_state_reader.go | 25 ++++-- pkg/resource/chain_supplier.go | 57 ------------ pkg/resource/chain_supplier_test.go | 89 ------------------- 13 files changed, 241 insertions(+), 164 deletions(-) create mode 100644 pkg/iac/supplier/IacChainSupplier.go create mode 100644 pkg/iac/supplier/IacChainSupplier_test.go create mode 100644 pkg/iac/terraform/state/alerts.go delete mode 100644 pkg/resource/chain_supplier.go delete mode 100644 pkg/resource/chain_supplier_test.go diff --git a/pkg/cmd/scan.go b/pkg/cmd/scan.go index 456949a2..86331ef7 100644 --- a/pkg/cmd/scan.go +++ b/pkg/cmd/scan.go @@ -209,6 +209,7 @@ func scanRun(opts *pkg.ScanOptions) error { err := remote.Activate(opts.To, opts.ProviderVersion, alerter, providerLibrary, remoteLibrary, scanProgress, resourceSchemaRepository, resFactory, opts.ConfigDir) if err != nil { + WriteAlert(alerter) return err } @@ -224,8 +225,9 @@ func scanRun(opts *pkg.ScanOptions) error { scanner := remote.NewScanner(remoteLibrary, alerter, remote.ScannerOptions{Deep: opts.Deep}, driftIgnore) - iacSupplier, err := supplier.GetIACSupplier(opts.From, providerLibrary, opts.BackendOptions, iacProgress, resFactory, driftIgnore) + iacSupplier, err := supplier.GetIACSupplier(opts.From, providerLibrary, opts.BackendOptions, iacProgress, alerter, resFactory, driftIgnore) if err != nil { + WriteAlert(alerter) return err } @@ -250,6 +252,7 @@ func scanRun(opts *pkg.ScanOptions) error { analysis, err := ctl.Run() if err != nil { + WriteAlert(alerter) return err } @@ -289,6 +292,10 @@ func scanRun(opts *pkg.ScanOptions) error { return nil } +func WriteAlert(alerter *alerter.Alerter) { + _ = output.NewConsole().WriteAlerts(alerter.Retrieve()) +} + func parseFromFlag(from []string) ([]config.SupplierConfig, error) { configs := make([]config.SupplierConfig, 0, len(from)) diff --git a/pkg/cmd/scan/output/console.go b/pkg/cmd/scan/output/console.go index 2158f155..91ac3211 100644 --- a/pkg/cmd/scan/output/console.go +++ b/pkg/cmd/scan/output/console.go @@ -9,7 +9,8 @@ import ( "strings" "github.com/aws/aws-sdk-go/aws/awsutil" - "github.com/cloudskiff/driftctl/pkg/remote/alerts" + "github.com/cloudskiff/driftctl/pkg/alerter" + remotealerts "github.com/cloudskiff/driftctl/pkg/remote/alerts" "github.com/fatih/color" "github.com/mattn/go-isatty" "github.com/r3labs/diff/v2" @@ -160,12 +161,15 @@ func (c *Console) Write(analysis *analyser.Analysis) error { } c.writeSummary(analysis) + return c.WriteAlerts(analysis.Alerts()) +} +func (c *Console) WriteAlerts(alerts alerter.Alerts) error { enumerationErrorMessage := "" - for _, a := range analysis.Alerts() { - for _, alert := range a { + for _, alerts := range alerts { + for _, alert := range alerts { fmt.Println(color.YellowString(alert.Message())) - if alert, ok := alert.(*alerts.RemoteAccessDeniedAlert); ok && enumerationErrorMessage == "" { + if alert, ok := alert.(*remotealerts.RemoteAccessDeniedAlert); ok && enumerationErrorMessage == "" { enumerationErrorMessage = alert.GetProviderMessage() } } @@ -174,7 +178,6 @@ func (c *Console) Write(analysis *analyser.Analysis) error { if enumerationErrorMessage != "" { _, _ = fmt.Fprintf(os.Stderr, "\n%s\n", color.YellowString(enumerationErrorMessage)) } - return nil } diff --git a/pkg/iac/supplier/IacChainSupplier.go b/pkg/iac/supplier/IacChainSupplier.go new file mode 100644 index 00000000..8e134c41 --- /dev/null +++ b/pkg/iac/supplier/IacChainSupplier.go @@ -0,0 +1,85 @@ +package supplier + +import ( + "context" + "runtime" + + "github.com/cloudskiff/driftctl/pkg/parallel" + "github.com/cloudskiff/driftctl/pkg/resource" + "github.com/pkg/errors" +) + +type IacChainSupplier struct { + suppliers []resource.Supplier + runner *parallel.ParallelRunner +} + +func NewIacChainSupplier() *IacChainSupplier { + return &IacChainSupplier{ + suppliers: []resource.Supplier{}, + runner: parallel.NewParallelRunner(context.TODO(), int64(runtime.NumCPU())), + } +} + +func (r *IacChainSupplier) AddSupplier(supplier resource.Supplier) { + r.suppliers = append(r.suppliers, supplier) +} + +func (r *IacChainSupplier) CountSuppliers() int { + return len(r.suppliers) +} + +func (r *IacChainSupplier) Resources() ([]*resource.Resource, error) { + + if len(r.suppliers) <= 0 { + return nil, errors.New("There was an error retrieving your states check alerts for details.") + } + + for _, supplier := range r.suppliers { + sup := supplier + r.runner.Run(func() (interface{}, error) { + resources, err := sup.Resources() + return &result{err, resources}, nil + }) + } + + results := make([]*resource.Resource, 0) + nbErrors := 0 +ReadLoop: + for { + select { + case supplierResult, ok := <-r.runner.Read(): + if !ok || supplierResult == nil { + break ReadLoop + } + // Type cannot be invalid as return type is enforced + // in run function on top + result, _ := supplierResult.(*result) + + if result.err != nil { + nbErrors++ + continue + } + + results = append(results, result.res...) + case <-r.runner.DoneChan(): + break ReadLoop + } + } + + if r.runner.Err() != nil { + return nil, r.runner.Err() + } + + if nbErrors == len(r.suppliers) { + // only fail if all suppliers failed + return nil, errors.New("There was an error retrieving your states check alerts for details.") + } + + return results, nil +} + +type result struct { + err error + res []*resource.Resource +} diff --git a/pkg/iac/supplier/IacChainSupplier_test.go b/pkg/iac/supplier/IacChainSupplier_test.go new file mode 100644 index 00000000..df8255bf --- /dev/null +++ b/pkg/iac/supplier/IacChainSupplier_test.go @@ -0,0 +1,87 @@ +package supplier + +import ( + "reflect" + "testing" + + "github.com/cloudskiff/driftctl/pkg/resource" + "github.com/pkg/errors" +) + +func TestIacChainSupplier_Resources(t *testing.T) { + tests := []struct { + name string + initSuppliers func(suppliers *[]resource.Supplier) + want []*resource.Resource + wantErr bool + }{ + { + name: "All failed", + initSuppliers: func(suppliers *[]resource.Supplier) { + sup := &resource.MockSupplier{} + sup.On("Resources").Return(nil, errors.New("1")) + *suppliers = append(*suppliers, sup) + + sup = &resource.MockSupplier{} + sup.On("Resources").Return(nil, errors.New("2")) + *suppliers = append(*suppliers, sup) + + sup = &resource.MockSupplier{} + sup.On("Resources").Return(nil, errors.New("3")) + *suppliers = append(*suppliers, sup) + }, + want: nil, + wantErr: true, + }, + { + name: "Partial failed", + initSuppliers: func(suppliers *[]resource.Supplier) { + sup := &resource.MockSupplier{} + sup.On("Resources").Return(nil, errors.New("1")) + *suppliers = append(*suppliers, sup) + + sup = &resource.MockSupplier{} + sup.On("Resources").Return(nil, errors.New("2")) + *suppliers = append(*suppliers, sup) + + sup = &resource.MockSupplier{} + sup.On("Resources").Return([]*resource.Resource{ + &resource.Resource{ + Id: "ID", + Type: "TYPE", + Attrs: nil, + }, + }, nil) + *suppliers = append(*suppliers, sup) + }, + want: []*resource.Resource{ + &resource.Resource{ + Id: "ID", + Type: "TYPE", + Attrs: nil, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewIacChainSupplier() + suppliers := make([]resource.Supplier, 0) + tt.initSuppliers(&suppliers) + + for _, supplier := range suppliers { + r.AddSupplier(supplier) + } + + got, err := r.Resources() + if (err != nil) != tt.wantErr { + t.Errorf("Resources() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Resources() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/iac/supplier/supplier.go b/pkg/iac/supplier/supplier.go index 5eb5c27f..e20c7871 100644 --- a/pkg/iac/supplier/supplier.go +++ b/pkg/iac/supplier/supplier.go @@ -3,6 +3,7 @@ package supplier import ( "fmt" + "github.com/cloudskiff/driftctl/pkg/alerter" "github.com/cloudskiff/driftctl/pkg/filter" "github.com/cloudskiff/driftctl/pkg/iac/terraform/state/backend" "github.com/cloudskiff/driftctl/pkg/output" @@ -34,10 +35,11 @@ func GetIACSupplier(configs []config.SupplierConfig, library *terraform.ProviderLibrary, backendOpts *backend.Options, progress output.Progress, + alerter *alerter.Alerter, factory resource.ResourceFactory, filter filter.Filter) (resource.Supplier, error) { - chainSupplier := resource.NewChainSupplier() + chainSupplier := NewIacChainSupplier() for _, config := range configs { if !IsSupplierSupported(config.Key) { return nil, errors.Errorf("Unsupported supplier '%s'", config.Key) @@ -49,7 +51,7 @@ func GetIACSupplier(configs []config.SupplierConfig, var err error switch config.Key { case state.TerraformStateReaderSupplier: - supplier, err = state.NewReader(config, library, backendOpts, progress, deserializer, filter) + supplier, err = state.NewReader(config, library, backendOpts, progress, alerter, deserializer, filter) default: return nil, errors.Errorf("Unsupported supplier '%s'", config.Key) } diff --git a/pkg/iac/supplier/supplier_test.go b/pkg/iac/supplier/supplier_test.go index 97507746..a8d8655b 100644 --- a/pkg/iac/supplier/supplier_test.go +++ b/pkg/iac/supplier/supplier_test.go @@ -5,6 +5,7 @@ import ( "reflect" "testing" + "github.com/cloudskiff/driftctl/pkg/alerter" "github.com/cloudskiff/driftctl/pkg/filter" "github.com/cloudskiff/driftctl/pkg/iac/config" "github.com/cloudskiff/driftctl/pkg/iac/terraform/state/backend" @@ -90,10 +91,12 @@ func TestGetIACSupplier(t *testing.T) { repo := resource.InitFakeSchemaRepository("aws", "3.19.0") factory := terraform.NewTerraformResourceFactory(repo) + alerter := alerter.NewAlerter() testFilter := &filter.MockFilter{} - _, err := GetIACSupplier(tt.args.config, terraform.NewProviderLibrary(), tt.args.options, progress, factory, testFilter) + _, err := GetIACSupplier(tt.args.config, terraform.NewProviderLibrary(), tt.args.options, progress, alerter, factory, testFilter) + if tt.wantErr != nil && err.Error() != tt.wantErr.Error() { t.Errorf("GetIACSupplier() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/iac/terraform/state/alerts.go b/pkg/iac/terraform/state/alerts.go new file mode 100644 index 00000000..c879ec8a --- /dev/null +++ b/pkg/iac/terraform/state/alerts.go @@ -0,0 +1,20 @@ +package state + +import "fmt" + +type StateReadingAlert struct { + key string + err string +} + +func NewStateReadingAlert(key string, err error) *StateReadingAlert { + return &StateReadingAlert{key: key, err: err.Error()} +} + +func (s *StateReadingAlert) Message() string { + return fmt.Sprintf("Your analysis will be incomplete. There was an error reading state file '%s': %s", s.key, s.err) +} + +func (s *StateReadingAlert) ShouldIgnoreResource() bool { + return false +} diff --git a/pkg/iac/terraform/state/enumerator/file.go b/pkg/iac/terraform/state/enumerator/file.go index abc81b56..202fbc16 100644 --- a/pkg/iac/terraform/state/enumerator/file.go +++ b/pkg/iac/terraform/state/enumerator/file.go @@ -23,6 +23,10 @@ func NewFileEnumerator(config config.SupplierConfig) *FileEnumerator { } } +func (s *FileEnumerator) Path() string { + return s.config.Path +} + func (s *FileEnumerator) Enumerate() ([]string, error) { path := s.config.Path diff --git a/pkg/iac/terraform/state/enumerator/s3.go b/pkg/iac/terraform/state/enumerator/s3.go index 7a1c74aa..e0d5420a 100644 --- a/pkg/iac/terraform/state/enumerator/s3.go +++ b/pkg/iac/terraform/state/enumerator/s3.go @@ -33,6 +33,10 @@ func NewS3Enumerator(config config.SupplierConfig) *S3Enumerator { } } +func (s *S3Enumerator) Path() string { + return s.config.Path +} + func (s *S3Enumerator) Enumerate() ([]string, error) { bucketPath := strings.Split(s.config.Path, "/") if len(bucketPath) < 2 { diff --git a/pkg/iac/terraform/state/enumerator/state_enumerator.go b/pkg/iac/terraform/state/enumerator/state_enumerator.go index 16bda7c2..864a169d 100644 --- a/pkg/iac/terraform/state/enumerator/state_enumerator.go +++ b/pkg/iac/terraform/state/enumerator/state_enumerator.go @@ -7,6 +7,7 @@ import ( ) type StateEnumerator interface { + Path() string Enumerate() ([]string, error) } diff --git a/pkg/iac/terraform/state/terraform_state_reader.go b/pkg/iac/terraform/state/terraform_state_reader.go index 8e6049ee..165e1a81 100644 --- a/pkg/iac/terraform/state/terraform_state_reader.go +++ b/pkg/iac/terraform/state/terraform_state_reader.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" + "github.com/cloudskiff/driftctl/pkg/alerter" "github.com/cloudskiff/driftctl/pkg/filter" "github.com/cloudskiff/driftctl/pkg/output" - "github.com/fatih/color" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states/statefile" @@ -39,6 +39,7 @@ type TerraformStateReader struct { backendOptions *backend.Options progress output.Progress filter filter.Filter + alerter *alerter.Alerter } func (r *TerraformStateReader) initReader() error { @@ -46,8 +47,8 @@ func (r *TerraformStateReader) initReader() error { return nil } -func NewReader(config config.SupplierConfig, library *terraform.ProviderLibrary, backendOpts *backend.Options, progress output.Progress, deserializer *resource.Deserializer, filter filter.Filter) (*TerraformStateReader, error) { - reader := TerraformStateReader{library: library, config: config, deserializer: deserializer, backendOptions: backendOpts, progress: progress, filter: filter} +func NewReader(config config.SupplierConfig, library *terraform.ProviderLibrary, backendOpts *backend.Options, progress output.Progress, alerter *alerter.Alerter, deserializer *resource.Deserializer, filter filter.Filter) (*TerraformStateReader, error) { + reader := TerraformStateReader{library: library, config: config, deserializer: deserializer, backendOptions: backendOpts, progress: progress, alerter: alerter, filter: filter} err := reader.initReader() if err != nil { return nil, err @@ -229,26 +230,32 @@ func (r *TerraformStateReader) retrieveForState(path string) ([]*resource.Resour func (r *TerraformStateReader) retrieveMultiplesStates() ([]*resource.Resource, error) { keys, err := r.enumerator.Enumerate() if err != nil { + r.alerter.SendAlert("", NewStateReadingAlert(r.enumerator.Path(), err)) return nil, err } + logrus.WithFields(logrus.Fields{ "keys": keys, }).Debug("Enumerated keys") + results := make([]*resource.Resource, 0) + nbAlert := 0 for _, key := range keys { resources, err := r.retrieveForState(key) if err != nil { - if _, ok := err.(*UnsupportedVersionError); ok { - color.New(color.Bold, color.FgYellow).Printf("WARNING: %s\n", err) - continue - } - - return nil, err + r.alerter.SendAlert("", NewStateReadingAlert(key, err)) + nbAlert++ + continue } results = append(results, resources...) } + if nbAlert == len(keys) { + // all key failed, throw an error + return results, errors.Errorf("Files were found but none of them could be read as a Terraform state.") + } + return results, nil } diff --git a/pkg/resource/chain_supplier.go b/pkg/resource/chain_supplier.go deleted file mode 100644 index 01434f4b..00000000 --- a/pkg/resource/chain_supplier.go +++ /dev/null @@ -1,57 +0,0 @@ -package resource - -import ( - "context" - "runtime" - - "github.com/cloudskiff/driftctl/pkg/parallel" -) - -type ChainSupplier struct { - suppliers []Supplier - runner *parallel.ParallelRunner -} - -func NewChainSupplier() *ChainSupplier { - return &ChainSupplier{ - runner: parallel.NewParallelRunner(context.TODO(), int64(runtime.NumCPU())), - } -} - -func (r *ChainSupplier) AddSupplier(supplier Supplier) { - r.suppliers = append(r.suppliers, supplier) -} - -func (r *ChainSupplier) Resources() ([]*Resource, error) { - - for _, supplier := range r.suppliers { - sup := supplier - r.runner.Run(func() (interface{}, error) { - return sup.Resources() - }) - } - - results := make([]*Resource, 0) - -ReadLoop: - for { - select { - case supplierResult, ok := <-r.runner.Read(): - if !ok || supplierResult == nil { - break ReadLoop - } - // Type cannot be invalid as return type is enforced - // by Supplier interface - resources, _ := supplierResult.([]*Resource) - results = append(results, resources...) - case <-r.runner.DoneChan(): - break ReadLoop - } - } - - if r.runner.Err() != nil { - return nil, r.runner.Err() - } - - return results, nil -} diff --git a/pkg/resource/chain_supplier_test.go b/pkg/resource/chain_supplier_test.go deleted file mode 100644 index bf70df5d..00000000 --- a/pkg/resource/chain_supplier_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package resource_test - -import ( - "errors" - "testing" - - "github.com/cloudskiff/driftctl/pkg/resource" - "github.com/stretchr/testify/assert" -) - -func TestChainSupplier_Resources(t *testing.T) { - - assert := assert.New(t) - - fakeTestSupplier := resource.MockSupplier{} - fakeTestSupplier.On("Resources").Return( - []*resource.Resource{ - &resource.Resource{ - Id: "fake-supplier-1_fake-resource-1", - }, - &resource.Resource{ - Id: "fake-supplier-1_fake-resource-2", - }, - }, - nil, - ).Once() - - anotherFakeTestSupplier := resource.MockSupplier{} - anotherFakeTestSupplier.On("Resources").Return( - []*resource.Resource{ - &resource.Resource{ - Id: "fake-supplier-2_fake-resource-1", - }, - &resource.Resource{ - Id: "fake-supplier-2_fake-resource-2", - }, - }, - nil, - ).Once() - - chain := resource.NewChainSupplier() - chain.AddSupplier(&fakeTestSupplier) - chain.AddSupplier(&anotherFakeTestSupplier) - - res, err := chain.Resources() - - if err != nil { - t.Fatal(err) - } - - anotherFakeTestSupplier.AssertExpectations(t) - fakeTestSupplier.AssertExpectations(t) - assert.Len(res, 4) -} - -func TestChainSupplier_Resources_WithError(t *testing.T) { - - assert := assert.New(t) - - fakeTestSupplier := resource.MockSupplier{} - fakeTestSupplier. - On("Resources"). - Return([]*resource.Resource{ - &resource.Resource{ - Id: "fake-supplier-1_fake-resource-1", - }, - &resource.Resource{ - Id: "fake-supplier-1_fake-resource-2", - }, - }, - nil, - ) - - anotherFakeTestSupplier := resource.MockSupplier{} - anotherFakeTestSupplier. - On("Resources"). - Return(nil, errors.New("error from another supplier")). - Once() - - chain := resource.NewChainSupplier() - chain.AddSupplier(&fakeTestSupplier) - chain.AddSupplier(&anotherFakeTestSupplier) - - res, err := chain.Resources() - - anotherFakeTestSupplier.AssertExpectations(t) - assert.Nil(res) - assert.Equal("error from another supplier", err.Error()) -} From a8918f7df1fdefb02d674bcb90ae34de87e3976c Mon Sep 17 00:00:00 2001 From: Martin Guibert Date: Wed, 15 Sep 2021 21:39:11 +0200 Subject: [PATCH 2/2] minor fixes, remove writealert. save reading alert in batch --- pkg/cmd/scan.go | 7 ----- pkg/cmd/scan/output/console.go | 13 ++++----- pkg/iac/errors.go | 27 +++++++++++++++++++ pkg/iac/supplier/IacChainSupplier.go | 24 ++++++----------- pkg/iac/terraform/state/alerts.go | 2 +- pkg/iac/terraform/state/enumerator/file.go | 4 +-- pkg/iac/terraform/state/enumerator/s3.go | 4 +-- .../state/enumerator/state_enumerator.go | 2 +- .../terraform/state/terraform_state_reader.go | 20 ++++++++------ 9 files changed, 58 insertions(+), 45 deletions(-) create mode 100644 pkg/iac/errors.go diff --git a/pkg/cmd/scan.go b/pkg/cmd/scan.go index 86331ef7..a069927a 100644 --- a/pkg/cmd/scan.go +++ b/pkg/cmd/scan.go @@ -209,7 +209,6 @@ func scanRun(opts *pkg.ScanOptions) error { err := remote.Activate(opts.To, opts.ProviderVersion, alerter, providerLibrary, remoteLibrary, scanProgress, resourceSchemaRepository, resFactory, opts.ConfigDir) if err != nil { - WriteAlert(alerter) return err } @@ -227,7 +226,6 @@ func scanRun(opts *pkg.ScanOptions) error { iacSupplier, err := supplier.GetIACSupplier(opts.From, providerLibrary, opts.BackendOptions, iacProgress, alerter, resFactory, driftIgnore) if err != nil { - WriteAlert(alerter) return err } @@ -252,7 +250,6 @@ func scanRun(opts *pkg.ScanOptions) error { analysis, err := ctl.Run() if err != nil { - WriteAlert(alerter) return err } @@ -292,10 +289,6 @@ func scanRun(opts *pkg.ScanOptions) error { return nil } -func WriteAlert(alerter *alerter.Alerter) { - _ = output.NewConsole().WriteAlerts(alerter.Retrieve()) -} - func parseFromFlag(from []string) ([]config.SupplierConfig, error) { configs := make([]config.SupplierConfig, 0, len(from)) diff --git a/pkg/cmd/scan/output/console.go b/pkg/cmd/scan/output/console.go index 91ac3211..2158f155 100644 --- a/pkg/cmd/scan/output/console.go +++ b/pkg/cmd/scan/output/console.go @@ -9,8 +9,7 @@ import ( "strings" "github.com/aws/aws-sdk-go/aws/awsutil" - "github.com/cloudskiff/driftctl/pkg/alerter" - remotealerts "github.com/cloudskiff/driftctl/pkg/remote/alerts" + "github.com/cloudskiff/driftctl/pkg/remote/alerts" "github.com/fatih/color" "github.com/mattn/go-isatty" "github.com/r3labs/diff/v2" @@ -161,15 +160,12 @@ func (c *Console) Write(analysis *analyser.Analysis) error { } c.writeSummary(analysis) - return c.WriteAlerts(analysis.Alerts()) -} -func (c *Console) WriteAlerts(alerts alerter.Alerts) error { enumerationErrorMessage := "" - for _, alerts := range alerts { - for _, alert := range alerts { + for _, a := range analysis.Alerts() { + for _, alert := range a { fmt.Println(color.YellowString(alert.Message())) - if alert, ok := alert.(*remotealerts.RemoteAccessDeniedAlert); ok && enumerationErrorMessage == "" { + if alert, ok := alert.(*alerts.RemoteAccessDeniedAlert); ok && enumerationErrorMessage == "" { enumerationErrorMessage = alert.GetProviderMessage() } } @@ -178,6 +174,7 @@ func (c *Console) WriteAlerts(alerts alerter.Alerts) error { if enumerationErrorMessage != "" { _, _ = fmt.Fprintf(os.Stderr, "\n%s\n", color.YellowString(enumerationErrorMessage)) } + return nil } diff --git a/pkg/iac/errors.go b/pkg/iac/errors.go new file mode 100644 index 00000000..f99fc819 --- /dev/null +++ b/pkg/iac/errors.go @@ -0,0 +1,27 @@ +package iac + +import ( + "fmt" + "strings" +) + +type StateReadingError struct { + errors []error +} + +func NewStateReadingError() *StateReadingError { + return &StateReadingError{} +} + +func (s *StateReadingError) Add(err error) { + s.errors = append(s.errors, err) +} + +func (s *StateReadingError) Error() string { + var err strings.Builder + _, _ = fmt.Fprint(&err, "There were errors reading your states files : \n") + for _, e := range s.errors { + _, _ = fmt.Fprintf(&err, " - %s\n", e.Error()) + } + return err.String() +} diff --git a/pkg/iac/supplier/IacChainSupplier.go b/pkg/iac/supplier/IacChainSupplier.go index 8e134c41..23bb4f85 100644 --- a/pkg/iac/supplier/IacChainSupplier.go +++ b/pkg/iac/supplier/IacChainSupplier.go @@ -4,9 +4,9 @@ import ( "context" "runtime" + "github.com/cloudskiff/driftctl/pkg/iac" "github.com/cloudskiff/driftctl/pkg/parallel" "github.com/cloudskiff/driftctl/pkg/resource" - "github.com/pkg/errors" ) type IacChainSupplier struct { @@ -16,8 +16,7 @@ type IacChainSupplier struct { func NewIacChainSupplier() *IacChainSupplier { return &IacChainSupplier{ - suppliers: []resource.Supplier{}, - runner: parallel.NewParallelRunner(context.TODO(), int64(runtime.NumCPU())), + runner: parallel.NewParallelRunner(context.TODO(), int64(runtime.NumCPU())), } } @@ -25,16 +24,8 @@ func (r *IacChainSupplier) AddSupplier(supplier resource.Supplier) { r.suppliers = append(r.suppliers, supplier) } -func (r *IacChainSupplier) CountSuppliers() int { - return len(r.suppliers) -} - func (r *IacChainSupplier) Resources() ([]*resource.Resource, error) { - if len(r.suppliers) <= 0 { - return nil, errors.New("There was an error retrieving your states check alerts for details.") - } - for _, supplier := range r.suppliers { sup := supplier r.runner.Run(func() (interface{}, error) { @@ -44,7 +35,8 @@ func (r *IacChainSupplier) Resources() ([]*resource.Resource, error) { } results := make([]*resource.Resource, 0) - nbErrors := 0 + isSuccess := false + retrieveError := iac.NewStateReadingError() ReadLoop: for { select { @@ -57,10 +49,10 @@ ReadLoop: result, _ := supplierResult.(*result) if result.err != nil { - nbErrors++ + retrieveError.Add(result.err) continue } - + isSuccess = true results = append(results, result.res...) case <-r.runner.DoneChan(): break ReadLoop @@ -71,9 +63,9 @@ ReadLoop: return nil, r.runner.Err() } - if nbErrors == len(r.suppliers) { + if !isSuccess { // only fail if all suppliers failed - return nil, errors.New("There was an error retrieving your states check alerts for details.") + return nil, retrieveError } return results, nil diff --git a/pkg/iac/terraform/state/alerts.go b/pkg/iac/terraform/state/alerts.go index c879ec8a..c23f9135 100644 --- a/pkg/iac/terraform/state/alerts.go +++ b/pkg/iac/terraform/state/alerts.go @@ -12,7 +12,7 @@ func NewStateReadingAlert(key string, err error) *StateReadingAlert { } func (s *StateReadingAlert) Message() string { - return fmt.Sprintf("Your analysis will be incomplete. There was an error reading state file '%s': %s", s.key, s.err) + return fmt.Sprintf("Your analysis may be incomplete. There was an error reading state file '%s': %s", s.key, s.err) } func (s *StateReadingAlert) ShouldIgnoreResource() bool { diff --git a/pkg/iac/terraform/state/enumerator/file.go b/pkg/iac/terraform/state/enumerator/file.go index 202fbc16..fb3aea1e 100644 --- a/pkg/iac/terraform/state/enumerator/file.go +++ b/pkg/iac/terraform/state/enumerator/file.go @@ -23,8 +23,8 @@ func NewFileEnumerator(config config.SupplierConfig) *FileEnumerator { } } -func (s *FileEnumerator) Path() string { - return s.config.Path +func (s *FileEnumerator) Origin() string { + return s.config.String() } func (s *FileEnumerator) Enumerate() ([]string, error) { diff --git a/pkg/iac/terraform/state/enumerator/s3.go b/pkg/iac/terraform/state/enumerator/s3.go index e0d5420a..ed304f4f 100644 --- a/pkg/iac/terraform/state/enumerator/s3.go +++ b/pkg/iac/terraform/state/enumerator/s3.go @@ -33,8 +33,8 @@ func NewS3Enumerator(config config.SupplierConfig) *S3Enumerator { } } -func (s *S3Enumerator) Path() string { - return s.config.Path +func (s *S3Enumerator) Origin() string { + return s.config.String() } func (s *S3Enumerator) Enumerate() ([]string, error) { diff --git a/pkg/iac/terraform/state/enumerator/state_enumerator.go b/pkg/iac/terraform/state/enumerator/state_enumerator.go index 864a169d..f852e6bb 100644 --- a/pkg/iac/terraform/state/enumerator/state_enumerator.go +++ b/pkg/iac/terraform/state/enumerator/state_enumerator.go @@ -7,7 +7,7 @@ import ( ) type StateEnumerator interface { - Path() string + Origin() string Enumerate() ([]string, error) } diff --git a/pkg/iac/terraform/state/terraform_state_reader.go b/pkg/iac/terraform/state/terraform_state_reader.go index 165e1a81..3a3edf85 100644 --- a/pkg/iac/terraform/state/terraform_state_reader.go +++ b/pkg/iac/terraform/state/terraform_state_reader.go @@ -6,6 +6,7 @@ import ( "github.com/cloudskiff/driftctl/pkg/alerter" "github.com/cloudskiff/driftctl/pkg/filter" + "github.com/cloudskiff/driftctl/pkg/iac" "github.com/cloudskiff/driftctl/pkg/output" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/states" @@ -222,16 +223,17 @@ func (r *TerraformStateReader) retrieveForState(path string) ([]*resource.Resour r.progress.Inc() values, err := r.retrieve() if err != nil { - return nil, err + return nil, errors.Wrap(err, r.config.String()) } - return r.decode(values) + decode, err := r.decode(values) + return decode, errors.Wrap(err, r.config.String()) } func (r *TerraformStateReader) retrieveMultiplesStates() ([]*resource.Resource, error) { keys, err := r.enumerator.Enumerate() if err != nil { - r.alerter.SendAlert("", NewStateReadingAlert(r.enumerator.Path(), err)) - return nil, err + r.alerter.SendAlert("", NewStateReadingAlert(r.enumerator.Origin(), err)) + return nil, errors.Wrap(err, r.config.String()) } logrus.WithFields(logrus.Fields{ @@ -239,21 +241,23 @@ func (r *TerraformStateReader) retrieveMultiplesStates() ([]*resource.Resource, }).Debug("Enumerated keys") results := make([]*resource.Resource, 0) - nbAlert := 0 + isSuccess := false + readingError := iac.NewStateReadingError() for _, key := range keys { resources, err := r.retrieveForState(key) if err != nil { + readingError.Add(err) r.alerter.SendAlert("", NewStateReadingAlert(key, err)) - nbAlert++ continue } + isSuccess = true results = append(results, resources...) } - if nbAlert == len(keys) { + if !isSuccess { // all key failed, throw an error - return results, errors.Errorf("Files were found but none of them could be read as a Terraform state.") + return results, readingError } return results, nil