Use printer to not break output isolation

added pinter interface to print info
json output use void printer is the path output it stdout
main
Martin Guibert 2021-03-03 17:20:25 +01:00
parent 8f584d67af
commit f31a8a8395
15 changed files with 303 additions and 28 deletions

View File

@ -5,6 +5,43 @@ end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace=true
indent_style = space
indent_size = 4
[.editorconfig]
ij_editorconfig_align_group_field_declarations = false
ij_editorconfig_space_after_colon = false
ij_editorconfig_space_after_comma = true
ij_editorconfig_space_before_colon = false
ij_editorconfig_space_before_comma = false
ij_editorconfig_spaces_around_assignment_operators = true
[{*.go,*.go2}]
ij_continuation_indent_size = 4
ij_go_add_leading_space_to_comments = true
ij_go_add_parentheses_for_single_import = false
ij_go_call_parameters_new_line_after_left_paren = true
ij_go_call_parameters_right_paren_on_new_line = true
ij_go_call_parameters_wrap = off
ij_go_fill_paragraph_width = 80
ij_go_group_current_project_imports = true
ij_go_group_stdlib_imports = true
ij_go_import_sorting = gofmt
ij_go_keep_indents_on_empty_lines = false
ij_go_move_all_imports_in_one_declaration = true
ij_go_move_all_stdlib_imports_in_one_group = true
ij_go_remove_redundant_import_aliases = true
ij_go_use_back_quotes_for_imports = false
ij_go_wrap_comp_lit = off
ij_go_wrap_comp_lit_newline_after_lbrace = true
ij_go_wrap_comp_lit_newline_before_rbrace = true
ij_go_wrap_func_params = off
ij_go_wrap_func_params_newline_after_lparen = true
ij_go_wrap_func_params_newline_before_rparen = true
ij_go_wrap_func_result = off
ij_go_wrap_func_result_newline_after_lparen = true
ij_go_wrap_func_result_newline_before_rparen = true
[*.json]
insert_final_newline = false

View File

@ -65,7 +65,7 @@ deps:
.PHONY: install-tools
install-tools:
$(GOGET) -u gotest.tools/gotestsum
$(GOGET) gotest.tools/gotestsum
$(GOGET) github.com/vektra/mockery/.../

50
goland_watchers.xml Normal file
View File

@ -0,0 +1,50 @@
<TaskOptions>
<TaskOptions>
<option name="arguments" value="fmt $FilePath$" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="go" />
<option name="immediateSync" value="false" />
<option name="name" value="go fmt" />
<option name="output" value="$FilePath$" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$GoExecPath$" />
<option name="runOnExternalChanges" value="false" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="true" />
<option name="workingDir" value="$ProjectFileDir$" />
<envs>
<env name="GOROOT" value="$GOROOT$" />
<env name="GOPATH" value="$GOPATH$" />
<env name="PATH" value="$GoBinDirs$" />
</envs>
</TaskOptions>
<TaskOptions>
<option name="arguments" value="run --disable=typecheck $FileDir$" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="go" />
<option name="immediateSync" value="false" />
<option name="name" value="golangci-lint" />
<option name="output" value="" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="golangci-lint" />
<option name="runOnExternalChanges" value="false" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="true" />
<option name="workingDir" value="$ProjectFileDir$" />
<envs>
<env name="GOROOT" value="$GOROOT$" />
<env name="GOPATH" value="$GOPATH$" />
<env name="PATH" value="$GoBinDirs$" />
</envs>
</TaskOptions>
</TaskOptions>

View File

@ -64,15 +64,15 @@ func run() int {
if cmd.IsReportingEnabled(&driftctlCmd.Command) {
sentry.CaptureException(err)
}
fmt.Fprintln(os.Stderr, color.RedString("%s", err))
_, _ = fmt.Fprintln(os.Stderr, color.RedString("%s", err))
return 1
}
if checkVersion {
newVersion := <-latestVersionChan
if newVersion != "" {
fmt.Println("\n\nYour version of driftctl is outdated, please upgrade !")
fmt.Printf("Current: %s; Latest: %s\n", version.Current(), newVersion)
_, _ = fmt.Fprintln(os.Stderr, "\n\nYour version of driftctl is outdated, please upgrade !")
_, _ = fmt.Fprintf(os.Stderr, "Current: %s; Latest: %s\n", version.Current(), newVersion)
}
}

View File

@ -7,6 +7,11 @@ import (
"strings"
"syscall"
"github.com/jmespath/go-jmespath"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/cloudskiff/driftctl/pkg"
"github.com/cloudskiff/driftctl/pkg/alerter"
cmderrors "github.com/cloudskiff/driftctl/pkg/cmd/errors"
@ -18,10 +23,6 @@ import (
"github.com/cloudskiff/driftctl/pkg/remote"
"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"
)
type ScanOptions struct {
@ -122,6 +123,8 @@ func NewScanCmd() *cobra.Command {
}
func scanRun(opts *ScanOptions) error {
selectedOutput := output.GetOutput(opts.Output)
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@ -161,7 +164,7 @@ func scanRun(opts *ScanOptions) error {
return err
}
err = output.GetOutput(opts.Output).Write(analysis)
err = selectedOutput.Write(analysis)
if err != nil {
return err
}

View File

@ -2,18 +2,18 @@ package output
import (
"fmt"
"os"
"reflect"
"strings"
"github.com/cloudskiff/driftctl/pkg/remote"
"github.com/cloudskiff/driftctl/pkg/analyser"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/fatih/color"
"github.com/nsf/jsondiff"
"github.com/r3labs/diff/v2"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/cloudskiff/driftctl/pkg/analyser"
"github.com/cloudskiff/driftctl/pkg/remote"
"github.com/cloudskiff/driftctl/pkg/resource"
)
const ConsoleOutputType = "console"
@ -106,7 +106,7 @@ func (c *Console) Write(analysis *analyser.Analysis) error {
}
if enumerationErrorMessage != "" {
fmt.Printf("\n%s\n", color.YellowString(enumerationErrorMessage))
_, _ = fmt.Fprintf(os.Stderr, "\n%s\n", color.YellowString(enumerationErrorMessage))
}
return nil

View File

@ -72,9 +72,11 @@ func TestConsole_Write(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
c := NewConsole()
old := os.Stdout // keep backup of the real stdout
stdout := os.Stdout // keep backup of the real stdout
stderr := os.Stderr // keep backup of the real stderr
r, w, _ := os.Pipe()
os.Stdout = w
os.Stderr = w
if err := c.Write(tt.args.analysis); (err != nil) != tt.wantErr {
t.Errorf("Write() error = %v, wantErr %v", err, tt.wantErr)
@ -90,7 +92,8 @@ func TestConsole_Write(t *testing.T) {
// back to normal state
w.Close()
os.Stdout = old // restoring the real stdout
os.Stdout = stdout // restoring the real stdout
os.Stderr = stderr
out := <-outC
expectedFilePath := path.Join("./testdata", tt.goldenfile)

View File

@ -19,11 +19,15 @@ func NewJSON(path string) *JSON {
}
func (c *JSON) Write(analysis *analyser.Analysis) error {
file, err := os.OpenFile(c.path, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return err
file := os.Stdout
if !isStdOut(c.path) {
f, err := os.OpenFile(c.path, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return err
}
defer f.Close()
file = f
}
defer file.Close()
json, err := json.MarshalIndent(analysis, "", "\t")
if err != nil {

View File

@ -1,15 +1,17 @@
package output
import (
"bytes"
"io"
"io/ioutil"
"os"
"path"
"testing"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/stretchr/testify/assert"
"github.com/cloudskiff/driftctl/pkg/analyser"
"github.com/cloudskiff/driftctl/test/goldenfile"
)
func TestJSON_Write(t *testing.T) {
@ -84,3 +86,74 @@ func TestJSON_Write(t *testing.T) {
})
}
}
func TestJSON_Write_stdout(t *testing.T) {
type args struct {
analysis *analyser.Analysis
}
tests := []struct {
name string
path string
goldenfile string
args args
wantErr bool
}{
{
name: "test json output stdout",
goldenfile: "output.json",
path: "stdout",
args: args{
analysis: fakeAnalysis(),
},
wantErr: false,
},
{
name: "test json output /dev/stdout",
goldenfile: "output.json",
path: "/dev/stdout",
args: args{
analysis: fakeAnalysis(),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stdout := os.Stdout // keep backup of the real stdout
r, w, _ := os.Pipe()
os.Stdout = w
c := NewJSON(tt.path)
if err := c.Write(tt.args.analysis); (err != nil) != tt.wantErr {
t.Errorf("Write() error = %v, wantErr %v", err, tt.wantErr)
}
outC := make(chan []byte)
// copy the output in a separate goroutine so printing can't block indefinitely
go func() {
var buf bytes.Buffer
_, _ = io.Copy(&buf, r)
outC <- buf.Bytes()
}()
// back to normal state
w.Close()
os.Stdout = stdout // restoring the real stdout
result := <-outC
expectedFilePath := path.Join("./testdata/", tt.goldenfile)
if *goldenfile.Update == tt.goldenfile {
if err := ioutil.WriteFile(expectedFilePath, result, 0600); err != nil {
t.Fatal(err)
}
}
expected, err := ioutil.ReadFile(expectedFilePath)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, string(expected), string(result))
})
}
}

View File

@ -4,6 +4,7 @@ import (
"sort"
"github.com/cloudskiff/driftctl/pkg/analyser"
"github.com/cloudskiff/driftctl/pkg/output"
)
type Output interface {
@ -47,6 +48,8 @@ func IsSupported(key string) bool {
}
func GetOutput(config OutputConfig) Output {
output.ChangePrinter(GetPrinter(config))
switch config.Key {
case JSONOutputType:
return NewJSON(config.Options["path"])
@ -56,3 +59,21 @@ func GetOutput(config OutputConfig) Output {
return NewConsole()
}
}
func GetPrinter(config OutputConfig) output.Printer {
switch config.Key {
case JSONOutputType:
if isStdOut(config.Options["path"]) {
return &output.VoidPrinter{}
}
fallthrough
case ConsoleOutputType:
fallthrough
default:
return output.NewConsolePrinter()
}
}
func isStdOut(path string) bool {
return path == "/dev/stdout" || path == "stdout"
}

View File

@ -2,9 +2,12 @@ package output
import (
"fmt"
"reflect"
"testing"
"github.com/cloudskiff/driftctl/pkg/alerter"
"github.com/cloudskiff/driftctl/pkg/analyser"
"github.com/cloudskiff/driftctl/pkg/output"
"github.com/cloudskiff/driftctl/pkg/remote"
"github.com/cloudskiff/driftctl/pkg/remote/aws"
"github.com/cloudskiff/driftctl/pkg/remote/github"
@ -261,3 +264,49 @@ func fakeAnalysisWithGithubEnumerationError() *analyser.Analysis {
})
return &a
}
func TestGetPrinter(t *testing.T) {
tests := []struct {
name string
path string
key string
want output.Printer
}{
{
name: "json file output",
path: "/path/to/file",
key: JSONOutputType,
want: output.NewConsolePrinter(),
},
{
name: "json stdout output",
path: "stdout",
key: JSONOutputType,
want: &output.VoidPrinter{},
},
{
name: "json /dev/stdout output",
path: "/dev/stdout",
key: JSONOutputType,
want: &output.VoidPrinter{},
},
{
name: "console stdout output",
path: "stdout",
key: ConsoleOutputType,
want: output.NewConsolePrinter(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetPrinter(OutputConfig{
Key: tt.key,
Options: map[string]string{
"path": tt.path,
},
}); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetPrinter() = %v, want %v", got, tt.want)
}
})
}
}

31
pkg/output/printer.go Normal file
View File

@ -0,0 +1,31 @@
package output
import "fmt"
var globalPrinter Printer = &VoidPrinter{}
func ChangePrinter(printer Printer) {
globalPrinter = printer
}
func Printf(format string, args ...interface{}) {
globalPrinter.Printf(format, args...)
}
type Printer interface {
Printf(format string, args ...interface{})
}
type ConsolePrinter struct{}
func NewConsolePrinter() *ConsolePrinter {
return &ConsolePrinter{}
}
func (c *ConsolePrinter) Printf(format string, args ...interface{}) {
fmt.Printf(format, args...)
}
type VoidPrinter struct{}
func (v *VoidPrinter) Printf(format string, args ...interface{}) {}

View File

@ -2,6 +2,7 @@ package aws
import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/cloudskiff/driftctl/pkg/remote/terraform"
tf "github.com/cloudskiff/driftctl/pkg/terraform"
)

View File

@ -2,13 +2,14 @@ package terraform
import (
"context"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/cloudskiff/driftctl/pkg/output"
"github.com/cloudskiff/driftctl/pkg/parallel"
tf "github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/eapache/go-resiliency/retrier"
@ -125,11 +126,11 @@ func (p *TerraformProvider) configure(alias string) error {
"alias": alias,
}).Debug("New gRPC client started")
fmt.Printf("Terraform provider initialized (name=%s", p.Config.Name)
output.Printf("Terraform provider initialized (name=%s", p.Config.Name)
if alias != "" {
fmt.Printf(", alias=%s", alias)
output.Printf(", alias=%s", alias)
}
fmt.Print(")\n")
output.Printf(")\n")
return nil
}

View File

@ -9,6 +9,8 @@ import (
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/cloudskiff/driftctl/pkg/output"
)
type HomeDirInterface interface {
@ -45,7 +47,7 @@ func (p *ProviderInstaller) Install() (string, error) {
logrus.WithFields(logrus.Fields{
"path": providerPath,
}).Debug("provider not found, downloading ...")
fmt.Printf("Downloading terraform provider: %s\n", p.config.Key)
output.Printf("Downloading terraform provider: %s\n", p.config.Key)
err := p.downloader.Download(
p.config.GetDownloadUrl(),
providerDir,