feat: add `driftctl fmt` command
parent
f2f846de9e
commit
4734fbe5a8
|
@ -69,6 +69,7 @@ func NewDriftctlCmd(build build.BuildInterface) *DriftctlCmd {
|
||||||
cmd.PersistentFlags().BoolP("send-crash-report", "", false, "Enable error reporting. Crash data will be sent to us via Sentry.\nWARNING: may leak sensitive data (please read the documentation for more details)\nThis flag should be used only if an error occurs during execution")
|
cmd.PersistentFlags().BoolP("send-crash-report", "", false, "Enable error reporting. Crash data will be sent to us via Sentry.\nWARNING: may leak sensitive data (please read the documentation for more details)\nThis flag should be used only if an error occurs during execution")
|
||||||
|
|
||||||
cmd.AddCommand(NewScanCmd(&pkg.ScanOptions{}))
|
cmd.AddCommand(NewScanCmd(&pkg.ScanOptions{}))
|
||||||
|
cmd.AddCommand(NewFmtCmd(&pkg.FmtOptions{}))
|
||||||
cmd.AddCommand(NewGenDriftIgnoreCmd())
|
cmd.AddCommand(NewGenDriftIgnoreCmd())
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
cmderrors "github.com/snyk/driftctl/pkg/cmd/errors"
|
||||||
|
"github.com/snyk/driftctl/pkg/cmd/scan/output"
|
||||||
|
"github.com/snyk/driftctl/pkg/iac/config"
|
||||||
|
"github.com/snyk/driftctl/pkg/iac/supplier"
|
||||||
|
"github.com/snyk/driftctl/pkg/iac/terraform/state/backend"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseFromFlag(from []string) ([]config.SupplierConfig, error) {
|
||||||
|
|
||||||
|
configs := make([]config.SupplierConfig, 0, len(from))
|
||||||
|
|
||||||
|
for _, flag := range from {
|
||||||
|
schemePath := strings.Split(flag, "://")
|
||||||
|
if len(schemePath) != 2 || schemePath[1] == "" || schemePath[0] == "" {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"\nAccepted schemes are: %s",
|
||||||
|
strings.Join(supplier.GetSupportedSchemes(), ","),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Unable to parse from flag '%s'",
|
||||||
|
flag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := schemePath[0]
|
||||||
|
path := schemePath[1]
|
||||||
|
supplierBackend := strings.Split(scheme, "+")
|
||||||
|
if len(supplierBackend) > 2 {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(fmt.Sprintf(
|
||||||
|
"\nAccepted schemes are: %s",
|
||||||
|
strings.Join(supplier.GetSupportedSchemes(), ","),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Unable to parse from scheme '%s'",
|
||||||
|
scheme,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
supplierKey := supplierBackend[0]
|
||||||
|
if !supplier.IsSupplierSupported(supplierKey) {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"\nAccepted values are: %s",
|
||||||
|
strings.Join(supplier.GetSupportedSuppliers(), ","),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Unsupported IaC source '%s'",
|
||||||
|
supplierKey,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
backendString := ""
|
||||||
|
if len(supplierBackend) == 2 {
|
||||||
|
backendString = supplierBackend[1]
|
||||||
|
if !backend.IsSupported(backendString) {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"\nAccepted values are: %s",
|
||||||
|
strings.Join(backend.GetSupportedBackends(), ","),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Unsupported IaC backend '%s'",
|
||||||
|
backendString,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configs = append(configs, config.SupplierConfig{
|
||||||
|
Key: supplierKey,
|
||||||
|
Backend: backendString,
|
||||||
|
Path: path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOutputFlags(out []string) ([]output.OutputConfig, error) {
|
||||||
|
result := make([]output.OutputConfig, 0, len(out))
|
||||||
|
for _, v := range out {
|
||||||
|
o, err := parseOutputFlag(v)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
result = append(result, *o)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOutputFlag(out string) (*output.OutputConfig, error) {
|
||||||
|
schemeOpts := strings.Split(out, "://")
|
||||||
|
if len(schemeOpts) < 2 || schemeOpts[0] == "" {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"\nAccepted formats are: %s",
|
||||||
|
strings.Join(output.SupportedOutputsExample(), ","),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Unable to parse output flag '%s'",
|
||||||
|
out,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
o := &output.OutputConfig{
|
||||||
|
Key: schemeOpts[0],
|
||||||
|
}
|
||||||
|
if !output.IsSupported(o.Key) {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"\nValid formats are: %s",
|
||||||
|
strings.Join(output.SupportedOutputsExample(), ","),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Unsupported output '%s'",
|
||||||
|
o.Key,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := schemeOpts[1:]
|
||||||
|
|
||||||
|
switch o.Key {
|
||||||
|
case output.JSONOutputType:
|
||||||
|
if len(opts) != 1 || opts[0] == "" {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"\nMust be of kind: %s",
|
||||||
|
output.Example(output.JSONOutputType),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Invalid json output '%s'",
|
||||||
|
out,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
o.Path = opts[0]
|
||||||
|
case output.HTMLOutputType:
|
||||||
|
if len(opts) != 1 || opts[0] == "" {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"\nMust be of kind: %s",
|
||||||
|
output.Example(output.HTMLOutputType),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Invalid html output '%s'",
|
||||||
|
out,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
o.Path = opts[0]
|
||||||
|
case output.PlanOutputType:
|
||||||
|
if len(opts) != 1 || opts[0] == "" {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
cmderrors.NewUsageError(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"\nMust be of kind: %s",
|
||||||
|
output.Example(output.PlanOutputType),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"Invalid plan output '%s'",
|
||||||
|
out,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
o.Path = opts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return o, nil
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/snyk/driftctl/pkg/cmd/scan/output"
|
||||||
|
"github.com/snyk/driftctl/pkg/iac/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseFromFlag(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
from []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []config.SupplierConfig
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test complete from parsing",
|
||||||
|
args: args{
|
||||||
|
from: []string{"tfstate+s3://bucket/path/to/state.tfstate"},
|
||||||
|
},
|
||||||
|
want: []config.SupplierConfig{
|
||||||
|
{
|
||||||
|
Key: "tfstate",
|
||||||
|
Backend: "s3",
|
||||||
|
Path: "bucket/path/to/state.tfstate",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test complete from parsing with multiples flags",
|
||||||
|
args: args{
|
||||||
|
from: []string{"tfstate+s3://bucket/path/to/state.tfstate", "tfstate:///tmp/my-state.tfstate"},
|
||||||
|
},
|
||||||
|
want: []config.SupplierConfig{
|
||||||
|
{
|
||||||
|
Key: "tfstate",
|
||||||
|
Backend: "s3",
|
||||||
|
Path: "bucket/path/to/state.tfstate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "tfstate",
|
||||||
|
Backend: "",
|
||||||
|
Path: "/tmp/my-state.tfstate",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseFromFlag(tt.args.from)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseFromFlag() error = %v, err %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("parseFromFlag() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseOutputFlag(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
out []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []output.OutputConfig
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test empty output",
|
||||||
|
args: args{
|
||||||
|
out: []string{""},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{},
|
||||||
|
err: fmt.Errorf("Unable to parse output flag '': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test empty array",
|
||||||
|
args: args{
|
||||||
|
out: []string{},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test invalid",
|
||||||
|
args: args{
|
||||||
|
out: []string{"sdgjsdgjsdg"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{},
|
||||||
|
err: fmt.Errorf("Unable to parse output flag 'sdgjsdgjsdg': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test invalid",
|
||||||
|
args: args{
|
||||||
|
out: []string{"://"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{},
|
||||||
|
err: fmt.Errorf("Unable to parse output flag '://': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test unsupported",
|
||||||
|
args: args{
|
||||||
|
out: []string{"foobar://"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{},
|
||||||
|
err: fmt.Errorf("Unsupported output 'foobar': \nValid formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test empty json",
|
||||||
|
args: args{
|
||||||
|
out: []string{"json://"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{},
|
||||||
|
err: fmt.Errorf("Invalid json output 'json://': \nMust be of kind: json://PATH/TO/FILE.json"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test valid console",
|
||||||
|
args: args{
|
||||||
|
out: []string{"console://"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{
|
||||||
|
{
|
||||||
|
Key: "console",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test valid json",
|
||||||
|
args: args{
|
||||||
|
out: []string{"json:///tmp/foobar.json"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{
|
||||||
|
{
|
||||||
|
Key: "json",
|
||||||
|
Path: "/tmp/foobar.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test empty jsonplan",
|
||||||
|
args: args{
|
||||||
|
out: []string{"plan://"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{},
|
||||||
|
err: fmt.Errorf("Invalid plan output 'plan://': \nMust be of kind: plan://PATH/TO/FILE.json"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test valid jsonplan",
|
||||||
|
args: args{
|
||||||
|
out: []string{"plan:///tmp/foobar.json"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{
|
||||||
|
{
|
||||||
|
Key: "plan",
|
||||||
|
Path: "/tmp/foobar.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test multiple output values",
|
||||||
|
args: args{
|
||||||
|
out: []string{"console:///dev/stdout", "json://result.json"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{
|
||||||
|
{
|
||||||
|
Key: "console",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "json",
|
||||||
|
Path: "result.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test multiple output values with invalid value",
|
||||||
|
args: args{
|
||||||
|
out: []string{"console:///dev/stdout", "invalid://result.json"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{
|
||||||
|
{
|
||||||
|
Key: "console",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: fmt.Errorf("Unsupported output 'invalid': \nValid formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test multiple valid output values",
|
||||||
|
args: args{
|
||||||
|
out: []string{"json://result1.json", "json://result2.json", "json://result3.json"},
|
||||||
|
},
|
||||||
|
want: []output.OutputConfig{
|
||||||
|
{
|
||||||
|
Key: "json",
|
||||||
|
Path: "result1.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "json",
|
||||||
|
Path: "result2.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "json",
|
||||||
|
Path: "result3.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseOutputFlags(tt.args.out)
|
||||||
|
if err != nil && err.Error() != tt.err.Error() {
|
||||||
|
t.Fatalf("got error = '%v', expected '%v'", err, tt.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Fatalf("parseOutputFlag() got = '%v', want '%v'", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/snyk/driftctl/pkg/analyser"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/snyk/driftctl/pkg"
|
||||||
|
"github.com/snyk/driftctl/pkg/cmd/scan/output"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFmtCmd(opts *pkg.FmtOptions) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "fmt",
|
||||||
|
Long: "Take an analysis results in JSON on stdin and return it in another format",
|
||||||
|
Hidden: true,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
outputFlag, _ := cmd.Flags().GetStringSlice("output")
|
||||||
|
if len(outputFlag) > 1 {
|
||||||
|
return errors.New("Only one output format can be set")
|
||||||
|
}
|
||||||
|
out, err := parseOutputFlags(outputFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.Output = out[0]
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runFmt(opts, os.Stdin)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fl := cmd.Flags()
|
||||||
|
fl.StringSliceP(
|
||||||
|
"output",
|
||||||
|
"o",
|
||||||
|
[]string{output.Example(output.ConsoleOutputType)},
|
||||||
|
"Output format, by default it will write to the console\n"+
|
||||||
|
"Accepted formats are: "+strings.Join(output.SupportedOutputsExample(), ",")+"\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runFmt(opts *pkg.FmtOptions, reader io.Reader) error {
|
||||||
|
|
||||||
|
var analysisText []byte
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for scanner.Scan() {
|
||||||
|
analysisText = append(analysisText, scanner.Bytes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
analysis := analyser.NewAnalysis(analyser.AnalyzerOptions{})
|
||||||
|
err := json.Unmarshal(analysisText, analysis)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.GetOutput(opts.Output).Write(analysis)
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/snyk/driftctl/pkg"
|
||||||
|
"github.com/snyk/driftctl/pkg/cmd/scan/output"
|
||||||
|
"github.com/snyk/driftctl/test"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_runFmt_InvalidInput(t *testing.T) {
|
||||||
|
opts := &pkg.FmtOptions{
|
||||||
|
Output: output.OutputConfig{
|
||||||
|
Key: output.ConsoleOutputType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
input, err := os.Open("testdata/fmt/input_stdin_invalid.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer input.Close()
|
||||||
|
|
||||||
|
err = runFmt(opts, input)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Equal(t, "invalid character 'i' looking for beginning of value", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_runFmt(t *testing.T) {
|
||||||
|
opts := &pkg.FmtOptions{
|
||||||
|
Output: output.OutputConfig{
|
||||||
|
Key: output.ConsoleOutputType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
input, err := os.Open("testdata/fmt/input_stdin_valid.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer input.Close()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
err = runFmt(opts, input)
|
||||||
|
|
||||||
|
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
|
||||||
|
assert.Nil(t, w.Close())
|
||||||
|
os.Stdout = stdout // restoring the real stdout
|
||||||
|
os.Stderr = stderr
|
||||||
|
output := <-outC
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedBytes, err := os.ReadFile("testdata/fmt/expected_console.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, string(expectedBytes), string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFmtCmd_Valid(t *testing.T) {
|
||||||
|
rootCmd := &cobra.Command{Use: "root"}
|
||||||
|
scanCmd := NewFmtCmd(&pkg.FmtOptions{})
|
||||||
|
scanCmd.RunE = func(_ *cobra.Command, args []string) error { return nil }
|
||||||
|
rootCmd.AddCommand(scanCmd)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{args: []string{"fmt"}},
|
||||||
|
{args: []string{"fmt", "-o", "json://test.json"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
output, err := test.Execute(rootCmd, tt.args...)
|
||||||
|
if output != "" {
|
||||||
|
t.Errorf("Unexpected output: %v", output)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFmtCmd_Invalid(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
args []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{args: []string{"fmt", "test"}, expected: `unknown command "test" for "root fmt"`},
|
||||||
|
{args: []string{"fmt", "-o", "json://test.json", "-o", "html://test.html"}, expected: "Only one output format can be set"},
|
||||||
|
{args: []string{"fmt", "-o", "foobar://barfoo"}, expected: "Unsupported output 'foobar': \nValid formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
rootCmd := &cobra.Command{Use: "root"}
|
||||||
|
rootCmd.AddCommand(NewFmtCmd(&pkg.FmtOptions{}))
|
||||||
|
_, err := test.Execute(rootCmd, tt.args...)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Invalid arg should generate error")
|
||||||
|
}
|
||||||
|
if err.Error() != tt.expected {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", tt.expected, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
169
pkg/cmd/scan.go
169
pkg/cmd/scan.go
|
@ -26,7 +26,6 @@ import (
|
||||||
cmderrors "github.com/snyk/driftctl/pkg/cmd/errors"
|
cmderrors "github.com/snyk/driftctl/pkg/cmd/errors"
|
||||||
"github.com/snyk/driftctl/pkg/cmd/scan/output"
|
"github.com/snyk/driftctl/pkg/cmd/scan/output"
|
||||||
"github.com/snyk/driftctl/pkg/filter"
|
"github.com/snyk/driftctl/pkg/filter"
|
||||||
"github.com/snyk/driftctl/pkg/iac/config"
|
|
||||||
"github.com/snyk/driftctl/pkg/iac/supplier"
|
"github.com/snyk/driftctl/pkg/iac/supplier"
|
||||||
"github.com/snyk/driftctl/pkg/iac/terraform/state/backend"
|
"github.com/snyk/driftctl/pkg/iac/terraform/state/backend"
|
||||||
globaloutput "github.com/snyk/driftctl/pkg/output"
|
globaloutput "github.com/snyk/driftctl/pkg/output"
|
||||||
|
@ -354,174 +353,6 @@ func scanRun(opts *pkg.ScanOptions) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFromFlag(from []string) ([]config.SupplierConfig, error) {
|
|
||||||
|
|
||||||
configs := make([]config.SupplierConfig, 0, len(from))
|
|
||||||
|
|
||||||
for _, flag := range from {
|
|
||||||
schemePath := strings.Split(flag, "://")
|
|
||||||
if len(schemePath) != 2 || schemePath[1] == "" || schemePath[0] == "" {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"\nAccepted schemes are: %s",
|
|
||||||
strings.Join(supplier.GetSupportedSchemes(), ","),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Unable to parse from flag '%s'",
|
|
||||||
flag,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
scheme := schemePath[0]
|
|
||||||
path := schemePath[1]
|
|
||||||
supplierBackend := strings.Split(scheme, "+")
|
|
||||||
if len(supplierBackend) > 2 {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(fmt.Sprintf(
|
|
||||||
"\nAccepted schemes are: %s",
|
|
||||||
strings.Join(supplier.GetSupportedSchemes(), ","),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Unable to parse from scheme '%s'",
|
|
||||||
scheme,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
supplierKey := supplierBackend[0]
|
|
||||||
if !supplier.IsSupplierSupported(supplierKey) {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"\nAccepted values are: %s",
|
|
||||||
strings.Join(supplier.GetSupportedSuppliers(), ","),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Unsupported IaC source '%s'",
|
|
||||||
supplierKey,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
backendString := ""
|
|
||||||
if len(supplierBackend) == 2 {
|
|
||||||
backendString = supplierBackend[1]
|
|
||||||
if !backend.IsSupported(backendString) {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"\nAccepted values are: %s",
|
|
||||||
strings.Join(backend.GetSupportedBackends(), ","),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Unsupported IaC backend '%s'",
|
|
||||||
backendString,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configs = append(configs, config.SupplierConfig{
|
|
||||||
Key: supplierKey,
|
|
||||||
Backend: backendString,
|
|
||||||
Path: path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return configs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOutputFlags(out []string) ([]output.OutputConfig, error) {
|
|
||||||
result := make([]output.OutputConfig, 0, len(out))
|
|
||||||
for _, v := range out {
|
|
||||||
o, err := parseOutputFlag(v)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result = append(result, *o)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOutputFlag(out string) (*output.OutputConfig, error) {
|
|
||||||
schemeOpts := strings.Split(out, "://")
|
|
||||||
if len(schemeOpts) < 2 || schemeOpts[0] == "" {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"\nAccepted formats are: %s",
|
|
||||||
strings.Join(output.SupportedOutputsExample(), ","),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Unable to parse output flag '%s'",
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
o := &output.OutputConfig{
|
|
||||||
Key: schemeOpts[0],
|
|
||||||
}
|
|
||||||
if !output.IsSupported(o.Key) {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"\nValid formats are: %s",
|
|
||||||
strings.Join(output.SupportedOutputsExample(), ","),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Unsupported output '%s'",
|
|
||||||
o.Key,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := schemeOpts[1:]
|
|
||||||
|
|
||||||
switch o.Key {
|
|
||||||
case output.JSONOutputType:
|
|
||||||
if len(opts) != 1 || opts[0] == "" {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"\nMust be of kind: %s",
|
|
||||||
output.Example(output.JSONOutputType),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Invalid json output '%s'",
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
o.Path = opts[0]
|
|
||||||
case output.HTMLOutputType:
|
|
||||||
if len(opts) != 1 || opts[0] == "" {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"\nMust be of kind: %s",
|
|
||||||
output.Example(output.HTMLOutputType),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Invalid html output '%s'",
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
o.Path = opts[0]
|
|
||||||
case output.PlanOutputType:
|
|
||||||
if len(opts) != 1 || opts[0] == "" {
|
|
||||||
return nil, errors.Wrapf(
|
|
||||||
cmderrors.NewUsageError(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"\nMust be of kind: %s",
|
|
||||||
output.Example(output.PlanOutputType),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"Invalid plan output '%s'",
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
o.Path = opts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return o, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateTfProviderVersionString(version string) error {
|
func validateTfProviderVersionString(version string) error {
|
||||||
if version == "" {
|
if version == "" {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/snyk/driftctl/pkg"
|
"github.com/snyk/driftctl/pkg"
|
||||||
"github.com/snyk/driftctl/pkg/cmd/scan/output"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/snyk/driftctl/pkg/iac/config"
|
|
||||||
"github.com/snyk/driftctl/test"
|
"github.com/snyk/driftctl/test"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Test successful scan
|
// TODO: Test successful scan
|
||||||
|
@ -111,231 +105,6 @@ func TestScanCmd_Invalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_parseFromFlag(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
from []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want []config.SupplierConfig
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test complete from parsing",
|
|
||||||
args: args{
|
|
||||||
from: []string{"tfstate+s3://bucket/path/to/state.tfstate"},
|
|
||||||
},
|
|
||||||
want: []config.SupplierConfig{
|
|
||||||
{
|
|
||||||
Key: "tfstate",
|
|
||||||
Backend: "s3",
|
|
||||||
Path: "bucket/path/to/state.tfstate",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test complete from parsing with multiples flags",
|
|
||||||
args: args{
|
|
||||||
from: []string{"tfstate+s3://bucket/path/to/state.tfstate", "tfstate:///tmp/my-state.tfstate"},
|
|
||||||
},
|
|
||||||
want: []config.SupplierConfig{
|
|
||||||
{
|
|
||||||
Key: "tfstate",
|
|
||||||
Backend: "s3",
|
|
||||||
Path: "bucket/path/to/state.tfstate",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "tfstate",
|
|
||||||
Backend: "",
|
|
||||||
Path: "/tmp/my-state.tfstate",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := parseFromFlag(tt.args.from)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("parseFromFlag() error = %v, err %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("parseFromFlag() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_parseOutputFlag(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
out []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want []output.OutputConfig
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "test empty output",
|
|
||||||
args: args{
|
|
||||||
out: []string{""},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{},
|
|
||||||
err: fmt.Errorf("Unable to parse output flag '': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test empty array",
|
|
||||||
args: args{
|
|
||||||
out: []string{},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test invalid",
|
|
||||||
args: args{
|
|
||||||
out: []string{"sdgjsdgjsdg"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{},
|
|
||||||
err: fmt.Errorf("Unable to parse output flag 'sdgjsdgjsdg': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test invalid",
|
|
||||||
args: args{
|
|
||||||
out: []string{"://"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{},
|
|
||||||
err: fmt.Errorf("Unable to parse output flag '://': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test unsupported",
|
|
||||||
args: args{
|
|
||||||
out: []string{"foobar://"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{},
|
|
||||||
err: fmt.Errorf("Unsupported output 'foobar': \nValid formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test empty json",
|
|
||||||
args: args{
|
|
||||||
out: []string{"json://"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{},
|
|
||||||
err: fmt.Errorf("Invalid json output 'json://': \nMust be of kind: json://PATH/TO/FILE.json"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test valid console",
|
|
||||||
args: args{
|
|
||||||
out: []string{"console://"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{
|
|
||||||
{
|
|
||||||
Key: "console",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test valid json",
|
|
||||||
args: args{
|
|
||||||
out: []string{"json:///tmp/foobar.json"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{
|
|
||||||
{
|
|
||||||
Key: "json",
|
|
||||||
Path: "/tmp/foobar.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test empty jsonplan",
|
|
||||||
args: args{
|
|
||||||
out: []string{"plan://"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{},
|
|
||||||
err: fmt.Errorf("Invalid plan output 'plan://': \nMust be of kind: plan://PATH/TO/FILE.json"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test valid jsonplan",
|
|
||||||
args: args{
|
|
||||||
out: []string{"plan:///tmp/foobar.json"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{
|
|
||||||
{
|
|
||||||
Key: "plan",
|
|
||||||
Path: "/tmp/foobar.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test multiple output values",
|
|
||||||
args: args{
|
|
||||||
out: []string{"console:///dev/stdout", "json://result.json"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{
|
|
||||||
{
|
|
||||||
Key: "console",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "json",
|
|
||||||
Path: "result.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test multiple output values with invalid value",
|
|
||||||
args: args{
|
|
||||||
out: []string{"console:///dev/stdout", "invalid://result.json"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{
|
|
||||||
{
|
|
||||||
Key: "console",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: fmt.Errorf("Unsupported output 'invalid': \nValid formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "test multiple valid output values",
|
|
||||||
args: args{
|
|
||||||
out: []string{"json://result1.json", "json://result2.json", "json://result3.json"},
|
|
||||||
},
|
|
||||||
want: []output.OutputConfig{
|
|
||||||
{
|
|
||||||
Key: "json",
|
|
||||||
Path: "result1.json",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "json",
|
|
||||||
Path: "result2.json",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "json",
|
|
||||||
Path: "result3.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := parseOutputFlags(tt.args.out)
|
|
||||||
if err != nil && err.Error() != tt.err.Error() {
|
|
||||||
t.Fatalf("got error = '%v', expected '%v'", err, tt.err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Fatalf("parseOutputFlag() got = '%v', want '%v'", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Options(t *testing.T) {
|
func Test_Options(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
Found missing resources:
|
||||||
|
- testuser1 (aws_iam_user)
|
||||||
|
- testrole1 (aws_iam_role)
|
||||||
|
Found resources not covered by IaC:
|
||||||
|
aws_iam_access_key:
|
||||||
|
- AKIAXYUOJZ3H5YCXF34G
|
||||||
|
- AKIAXYUOJZ3HV2LTLXD2
|
||||||
|
- AKIAXYUOJZ3HUSPPQQ4L
|
||||||
|
aws_iam_role:
|
||||||
|
- OrganizationAccountAccessRole
|
||||||
|
- driftctl_assume\_role
|
||||||
|
aws_iam_role_policy:
|
||||||
|
- OrganizationAccountAccessRole:AdministratorAccess
|
||||||
|
- driftctl_assume_role:driftctl_policy.10
|
||||||
|
aws_iam_user:
|
||||||
|
- driftctl
|
||||||
|
- sundowndev
|
||||||
|
- test_user
|
||||||
|
aws_iam_user_policy:
|
||||||
|
- driftctl:driftctlrole
|
||||||
|
Found changed resources:
|
||||||
|
- test-20210416154114486700000001 (aws_s3_bucket):
|
||||||
|
~ BucketPrefix: "test-" => <nil>
|
||||||
|
+ Tags.tag2: <nil> => "value"
|
||||||
|
~ Tags.test: "test" => "test1"
|
||||||
|
Found 14 resource(s)
|
||||||
|
- 7% coverage
|
||||||
|
- 1 resource(s) managed by Terraform
|
||||||
|
- 11 resource(s) not managed by Terraform
|
||||||
|
- 2 resource(s) found in a Terraform state but missing on the cloud provider
|
|
@ -0,0 +1 @@
|
||||||
|
invalid
|
|
@ -0,0 +1,112 @@
|
||||||
|
{
|
||||||
|
"summary": {
|
||||||
|
"total_resources": 12,
|
||||||
|
"total_changed": 1,
|
||||||
|
"total_unmanaged": 11,
|
||||||
|
"total_missing": 0,
|
||||||
|
"total_managed": 1
|
||||||
|
},
|
||||||
|
"managed": [
|
||||||
|
{
|
||||||
|
"id": "test-20210416154114486700000001",
|
||||||
|
"type": "aws_s3_bucket"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unmanaged": [
|
||||||
|
{
|
||||||
|
"id": "driftctl",
|
||||||
|
"type": "aws_iam_user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sundowndev",
|
||||||
|
"type": "aws_iam_user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test_user",
|
||||||
|
"type": "aws_iam_user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "OrganizationAccountAccessRole:AdministratorAccess",
|
||||||
|
"type": "aws_iam_role_policy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "driftctl_assume_role:driftctl_policy.10",
|
||||||
|
"type": "aws_iam_role_policy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "OrganizationAccountAccessRole",
|
||||||
|
"type": "aws_iam_role"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "driftctl_assume\\_role",
|
||||||
|
"type": "aws_iam_role"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "driftctl:driftctlrole",
|
||||||
|
"type": "aws_iam_user_policy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AKIAXYUOJZ3H5YCXF34G",
|
||||||
|
"type": "aws_iam_access_key"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AKIAXYUOJZ3HV2LTLXD2",
|
||||||
|
"type": "aws_iam_access_key"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AKIAXYUOJZ3HUSPPQQ4L",
|
||||||
|
"type": "aws_iam_access_key"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"missing": [
|
||||||
|
{
|
||||||
|
"id": "testuser1",
|
||||||
|
"type": "aws_iam_user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "testrole1",
|
||||||
|
"type": "aws_iam_role"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"differences": [
|
||||||
|
{
|
||||||
|
"res": {
|
||||||
|
"id": "test-20210416154114486700000001",
|
||||||
|
"type": "aws_s3_bucket"
|
||||||
|
},
|
||||||
|
"changelog": [
|
||||||
|
{
|
||||||
|
"type": "update",
|
||||||
|
"path": [
|
||||||
|
"BucketPrefix"
|
||||||
|
],
|
||||||
|
"from": "test-",
|
||||||
|
"to": null,
|
||||||
|
"computed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "create",
|
||||||
|
"path": [
|
||||||
|
"Tags",
|
||||||
|
"tag2"
|
||||||
|
],
|
||||||
|
"from": null,
|
||||||
|
"to": "value",
|
||||||
|
"computed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "update",
|
||||||
|
"path": [
|
||||||
|
"Tags",
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"from": "test",
|
||||||
|
"to": "test1",
|
||||||
|
"computed": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"coverage": 8,
|
||||||
|
"alerts": null
|
||||||
|
}
|
|
@ -19,6 +19,10 @@ import (
|
||||||
"github.com/snyk/driftctl/pkg/resource"
|
"github.com/snyk/driftctl/pkg/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FmtOptions struct {
|
||||||
|
Output output.OutputConfig
|
||||||
|
}
|
||||||
|
|
||||||
type ScanOptions struct {
|
type ScanOptions struct {
|
||||||
Coverage bool
|
Coverage bool
|
||||||
Detect bool
|
Detect bool
|
||||||
|
|
Loading…
Reference in New Issue