feat: add iac progress bar

main
sundowndev 2021-05-03 18:41:52 +02:00
parent 87ad272856
commit 252674cb56
9 changed files with 101 additions and 29 deletions

View File

@ -147,11 +147,12 @@ func scanRun(opts *pkg.ScanOptions) error {
providerLibrary := terraform.NewProviderLibrary()
supplierLibrary := resource.NewSupplierLibrary()
progress := globaloutput.NewProgress()
iacProgress := globaloutput.NewProgress("Scanning states", "Scanned states", true)
scanProgress := globaloutput.NewProgress("Scanning resources", "Scanned resources", true)
resourceSchemaRepository := resource.NewSchemaRepository()
err := remote.Activate(opts.To, alerter, providerLibrary, supplierLibrary, progress, resourceSchemaRepository)
err := remote.Activate(opts.To, alerter, providerLibrary, supplierLibrary, scanProgress, resourceSchemaRepository)
if err != nil {
return err
}
@ -165,14 +166,14 @@ func scanRun(opts *pkg.ScanOptions) error {
scanner := pkg.NewScanner(supplierLibrary.Suppliers(), alerter, resourceSchemaRepository)
iacSupplier, err := supplier.GetIACSupplier(opts.From, providerLibrary, opts.BackendOptions, resourceSchemaRepository)
iacSupplier, err := supplier.GetIACSupplier(opts.From, providerLibrary, opts.BackendOptions, iacProgress, resourceSchemaRepository)
if err != nil {
return err
}
resFactory := terraform.NewTerraformResourceFactory(providerLibrary, resourceSchemaRepository)
ctl := pkg.NewDriftCTL(scanner, iacSupplier, alerter, resFactory, opts, resourceSchemaRepository)
ctl := pkg.NewDriftCTL(scanner, iacSupplier, alerter, resFactory, opts, scanProgress, iacProgress, resourceSchemaRepository)
go func() {
<-c
@ -180,10 +181,7 @@ func scanRun(opts *pkg.ScanOptions) error {
ctl.Stop()
}()
progress.Start()
analysis, err := ctl.Run()
progress.Stop()
if err != nil {
return err
}

View File

@ -3,6 +3,7 @@ package pkg
import (
"fmt"
globaloutput "github.com/cloudskiff/driftctl/pkg/output"
"github.com/jmespath/go-jmespath"
"github.com/sirupsen/logrus"
@ -36,10 +37,12 @@ type DriftCTL struct {
filter *jmespath.JMESPath
resourceFactory resource.ResourceFactory
strictMode bool
scanProgress globaloutput.Progress
iacProgress globaloutput.Progress
resourceSchemaRepository resource.SchemaRepositoryInterface
}
func NewDriftCTL(remoteSupplier resource.Supplier, iacSupplier resource.Supplier, alerter *alerter.Alerter, resFactory resource.ResourceFactory, opts *ScanOptions, resourceSchemaRepository resource.SchemaRepositoryInterface) *DriftCTL {
func NewDriftCTL(remoteSupplier resource.Supplier, iacSupplier resource.Supplier, alerter *alerter.Alerter, resFactory resource.ResourceFactory, opts *ScanOptions, scanProgress globaloutput.Progress, iacProgress globaloutput.Progress, resourceSchemaRepository resource.SchemaRepositoryInterface) *DriftCTL {
return &DriftCTL{
remoteSupplier,
iacSupplier,
@ -48,6 +51,8 @@ func NewDriftCTL(remoteSupplier resource.Supplier, iacSupplier resource.Supplier
opts.Filter,
resFactory,
opts.StrictMode,
scanProgress,
iacProgress,
resourceSchemaRepository,
}
}
@ -133,12 +138,16 @@ func (d DriftCTL) Stop() {
func (d DriftCTL) scan() (remoteResources []resource.Resource, resourcesFromState []resource.Resource, err error) {
logrus.Info("Start reading IaC")
d.iacProgress.Start()
resourcesFromState, err = d.iacSupplier.Resources()
if err != nil {
return nil, nil, err
}
d.iacProgress.Stop()
logrus.Info("Start scanning cloud provider")
d.scanProgress.Start()
defer d.scanProgress.Stop()
remoteResources, err = d.remoteSupplier.Resources()
if err != nil {
return nil, nil, err

View File

@ -13,6 +13,7 @@ import (
"github.com/cloudskiff/driftctl/pkg/alerter"
"github.com/cloudskiff/driftctl/pkg/analyser"
"github.com/cloudskiff/driftctl/pkg/filter"
"github.com/cloudskiff/driftctl/pkg/output"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/cloudskiff/driftctl/pkg/resource/github"
@ -72,11 +73,20 @@ func runTest(t *testing.T, cases TestCases) {
c.mocks(resourceFactory)
}
driftctl := pkg.NewDriftCTL(remoteSupplier, stateSupplier, testAlerter, resourceFactory, c.options, repo)
scanProgress := &output.MockProgress{}
scanProgress.On("Start").Return().Once()
scanProgress.On("Stop").Return().Once()
iacProgress := &output.MockProgress{}
iacProgress.On("Start").Return().Once()
iacProgress.On("Stop").Return().Once()
driftctl := pkg.NewDriftCTL(remoteSupplier, stateSupplier, testAlerter, resourceFactory, c.options, scanProgress, iacProgress, repo)
analysis, err := driftctl.Run()
c.assert(test.NewScanResult(t, analysis), err)
scanProgress.AssertExpectations(t)
})
}
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/cloudskiff/driftctl/pkg/iac/terraform/state/backend"
"github.com/cloudskiff/driftctl/pkg/output"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -28,7 +29,7 @@ func IsSupplierSupported(supplierKey string) bool {
return false
}
func GetIACSupplier(configs []config.SupplierConfig, library *terraform.ProviderLibrary, backendOpts *backend.Options, resourceSchemaRepository resource.SchemaRepositoryInterface) (resource.Supplier, error) {
func GetIACSupplier(configs []config.SupplierConfig, library *terraform.ProviderLibrary, backendOpts *backend.Options, progress output.Progress, resourceSchemaRepository resource.SchemaRepositoryInterface) (resource.Supplier, error) {
chainSupplier := resource.NewChainSupplier()
for _, config := range configs {
if !IsSupplierSupported(config.Key) {
@ -39,7 +40,7 @@ func GetIACSupplier(configs []config.SupplierConfig, library *terraform.Provider
var err error
switch config.Key {
case state.TerraformStateReaderSupplier:
supplier, err = state.NewReader(config, library, backendOpts, resourceSchemaRepository)
supplier, err = state.NewReader(config, library, backendOpts, progress, resourceSchemaRepository)
default:
return nil, errors.Errorf("Unsupported supplier '%s'", config.Key)
}

View File

@ -7,6 +7,7 @@ import (
"github.com/cloudskiff/driftctl/pkg/iac/config"
"github.com/cloudskiff/driftctl/pkg/iac/terraform/state/backend"
"github.com/cloudskiff/driftctl/pkg/output"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test/resource"
)
@ -83,8 +84,12 @@ func TestGetIACSupplier(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
progress := &output.MockProgress{}
progress.On("Start").Return().Times(1)
repo := resource.InitFakeSchemaRepository("aws", "3.19.0")
_, err := GetIACSupplier(tt.args.config, terraform.NewProviderLibrary(), tt.args.options, repo)
_, err := GetIACSupplier(tt.args.config, terraform.NewProviderLibrary(), tt.args.options, progress, repo)
if tt.wantErr != nil && err.Error() != tt.wantErr.Error() {
t.Errorf("GetIACSupplier() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@ -3,6 +3,7 @@ package state
import (
"fmt"
"github.com/cloudskiff/driftctl/pkg/output"
"github.com/fatih/color"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
@ -30,6 +31,7 @@ type TerraformStateReader struct {
enumerator enumerator.StateEnumerator
deserializers []deserializer.CTYDeserializer
backendOptions *backend.Options
progress output.Progress
resourceSchemaRepository resource.SchemaRepositoryInterface
}
@ -38,8 +40,8 @@ func (r *TerraformStateReader) initReader() error {
return nil
}
func NewReader(config config.SupplierConfig, library *terraform.ProviderLibrary, backendOpts *backend.Options, resourceSchemaRepository resource.SchemaRepositoryInterface) (*TerraformStateReader, error) {
reader := TerraformStateReader{library: library, config: config, deserializers: iac.Deserializers(), backendOptions: backendOpts, resourceSchemaRepository: resourceSchemaRepository}
func NewReader(config config.SupplierConfig, library *terraform.ProviderLibrary, backendOpts *backend.Options, progress output.Progress, resourceSchemaRepository resource.SchemaRepositoryInterface) (*TerraformStateReader, error) {
reader := TerraformStateReader{library: library, config: config, deserializers: iac.Deserializers(), backendOptions: backendOpts, progress: progress, resourceSchemaRepository: resourceSchemaRepository}
err := reader.initReader()
if err != nil {
return nil, err
@ -227,6 +229,7 @@ func (r *TerraformStateReader) retrieveForState(path string) ([]resource.Resourc
"path": r.config.Path,
"backend": r.config.Backend,
}).Debug("Reading resources from state")
r.progress.Inc()
values, err := r.retrieve()
if err != nil {
return nil, err

View File

@ -99,14 +99,16 @@ func TestTerraformStateReader_AWS_Resources(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
progress := &output.MockProgress{}
progress.On("Inc").Return().Times(1)
progress.On("Stop").Return().Times(1)
shouldUpdate := tt.dirName == *goldenfile.Update
var realProvider *aws.AWSTerraformProvider
if shouldUpdate {
var err error
progress := &output.MockProgress{}
progress.On("Inc").Return()
realProvider, err = aws.NewAWSTerraformProvider(progress)
if err != nil {
t.Fatal(err)
@ -130,6 +132,7 @@ func TestTerraformStateReader_AWS_Resources(t *testing.T) {
},
library: library,
deserializers: iac.Deserializers(),
progress: progress,
resourceSchemaRepository: repo,
}
@ -180,14 +183,16 @@ func TestTerraformStateReader_Github_Resources(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
progress := &output.MockProgress{}
progress.On("Inc").Return().Times(1)
progress.On("Stop").Return().Times(1)
shouldUpdate := tt.dirName == *goldenfile.Update
var realProvider *github.GithubTerraformProvider
if shouldUpdate {
var err error
progress := &output.MockProgress{}
progress.On("Inc").Return()
realProvider, err = github.NewGithubTerraformProvider(progress)
if err != nil {
t.Fatal(err)
@ -211,6 +216,7 @@ func TestTerraformStateReader_Github_Resources(t *testing.T) {
},
library: library,
deserializers: iac.Deserializers(),
progress: progress,
resourceSchemaRepository: repo,
}

View File

@ -1,6 +1,7 @@
package output
import (
"fmt"
"time"
"go.uber.org/atomic"
@ -22,17 +23,31 @@ type Progress interface {
Val() uint64
}
type progress struct {
endChan chan struct{}
started *atomic.Bool
count *atomic.Uint64
type ProgressOptions struct {
LoadingText string
FinishedText string
ShowCount bool
}
func NewProgress() *progress {
type progress struct {
endChan chan struct{}
started *atomic.Bool
count *atomic.Uint64
loadingText string
finishedText string
showCount bool
highestLineLength int
}
func NewProgress(loadingText, finishedText string, showCount bool) *progress {
return &progress{
nil,
atomic.NewBool(false),
atomic.NewUint64(0),
loadingText,
finishedText,
showCount,
0,
}
}
@ -47,7 +62,11 @@ func (p *progress) Start() {
func (p *progress) Stop() {
if p.started.Swap(false) {
Printf("Scanned resources: (%d)\n", p.count.Load())
if p.showCount {
p.printf("%s (%d)\n", p.finishedText, p.count.Load())
} else {
p.printf("%s\r", p.finishedText)
}
close(p.endChan)
}
}
@ -67,7 +86,7 @@ func (p *progress) Val() uint64 {
func (p *progress) render() {
i := -1
Printf("Scanning resources:\r")
p.printf("%s\r", p.loadingText)
for {
select {
case <-p.endChan:
@ -77,7 +96,11 @@ func (p *progress) render() {
if i >= len(spinner) {
i = 0
}
Printf("Scanning resources: %s (%d)\r", spinner[i], p.count.Load())
if p.showCount {
p.printf("%s %s (%d)\r", p.loadingText, spinner[i], p.count.Load())
} else {
p.printf("%s %s\r", p.loadingText, spinner[i])
}
}
}
}
@ -101,3 +124,20 @@ Loop:
}
logrus.Debug("Progress did not receive any tic. Stopping...")
}
func (p *progress) flush() {
for i := 0; i < p.highestLineLength; i++ {
Printf(" ")
}
Printf("\r")
}
func (p *progress) printf(format string, args ...interface{}) {
txt := fmt.Sprintf(format, args...)
length := len(txt)
if length > p.highestLineLength {
p.highestLineLength = length
}
p.flush()
Printf(txt)
}

View File

@ -8,7 +8,7 @@ import (
)
func TestProgressTimeoutDoesNotInc(t *testing.T) {
progress := NewProgress()
progress := NewProgress("loading", "loaded", false)
progress.Start()
progress.Inc()
progress.Stop() // should not hang
@ -21,7 +21,7 @@ func TestProgressTimeoutDoesNotInc(t *testing.T) {
}
func TestProgressTimeoutDoesNotHang(t *testing.T) {
progress := NewProgress()
progress := NewProgress("loading", "loaded", false)
progress.Start()
time.Sleep(progressTimeout)
for progress.started.Load() == true {
@ -32,7 +32,7 @@ func TestProgressTimeoutDoesNotHang(t *testing.T) {
}
func TestProgress(t *testing.T) {
progress := NewProgress()
progress := NewProgress("loading", "loaded", false)
progress.Start()
progress.Inc()
progress.Inc()