driftctl/pkg/analyser/analyzer_test.go

566 lines
12 KiB
Go
Raw Normal View History

package analyser
import (
"encoding/json"
"io/ioutil"
"github.com/stretchr/testify/mock"
"github.com/cloudskiff/driftctl/mocks"
testresource "github.com/cloudskiff/driftctl/test/resource"
"github.com/stretchr/testify/assert"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/r3labs/diff/v2"
"testing"
)
func TestAnalyze(t *testing.T) {
cases := []struct {
name string
iac []resource.Resource
ignoredRes []resource.Resource
cloud []resource.Resource
ignoredDrift []struct {
res resource.Resource
path []string
}
expected Analysis
hasDrifted bool
}{
{
name: "TestNilValues", // Cover division by zero case
iac: nil,
cloud: nil,
expected: Analysis{},
},
{
name: "TestNothingToCompare", // Cover division by zero case
iac: []resource.Resource{},
cloud: []resource.Resource{},
expected: Analysis{},
},
{
name: "TestIgnoreFromCoverageIacNotInCloud",
iac: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
},
},
cloud: []resource.Resource{},
expected: Analysis{
summary: Summary{
TotalResources: 1,
TotalDeleted: 1,
},
deleted: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
},
},
},
hasDrifted: true,
},
{
name: "TestResourceIgnoredDeleted",
iac: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
},
},
ignoredRes: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
},
},
cloud: []resource.Resource{},
expected: Analysis{
summary: Summary{
TotalResources: 0,
TotalDeleted: 0,
},
},
hasDrifted: false,
},
{
name: "Test100PercentCoverage with ignore",
iac: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
},
&testresource.FakeResource{
Id: "foobar2",
},
},
ignoredRes: []resource.Resource{
&testresource.FakeResource{
Id: "foobar2",
},
},
cloud: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
},
&testresource.FakeResource{
Id: "foobar2",
},
},
expected: Analysis{
managed: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
},
},
summary: Summary{
TotalManaged: 1,
TotalResources: 1,
},
},
},
{
name: "Test100PercentCoverage",
iac: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
},
},
cloud: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
},
},
expected: Analysis{
managed: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
},
},
summary: Summary{
TotalManaged: 1,
TotalResources: 1,
},
},
},
{
name: "TestUnmanagedResource",
iac: []resource.Resource{},
cloud: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
},
},
expected: Analysis{
summary: Summary{
TotalResources: 1,
TotalUnmanaged: 1,
},
unmanaged: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
},
},
},
hasDrifted: true,
},
{
name: "TestDiff",
iac: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
},
cloud: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
FooBar: "barfoo",
BarFoo: "foobar",
},
},
expected: Analysis{
managed: []resource.Resource{
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
},
summary: Summary{
TotalResources: 1,
TotalDrifted: 1,
TotalManaged: 1,
},
differences: []Difference{
{
2020-12-22 11:17:04 +00:00
Res: &testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
Changelog: diff.Changelog{
diff.Change{
Type: "update",
From: "foobar",
To: "barfoo",
Path: []string{
"FooBar",
},
},
diff.Change{
Type: "update",
From: "barfoo",
To: "foobar",
Path: []string{
"BarFoo",
},
},
},
},
},
},
hasDrifted: true,
},
{
name: "TestDiff with partial ignore",
iac: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
},
cloud: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
FooBar: "barfoo",
BarFoo: "foobar",
},
},
ignoredDrift: []struct {
res resource.Resource
path []string
}{
{
res: &testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
path: []string{"FooBar"},
},
},
expected: Analysis{
managed: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
},
summary: Summary{
TotalResources: 1,
TotalDrifted: 1,
TotalManaged: 1,
},
differences: []Difference{
{
Res: &testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
Changelog: diff.Changelog{
diff.Change{
Type: "update",
From: "barfoo",
To: "foobar",
Path: []string{
"BarFoo",
},
},
},
},
},
},
hasDrifted: true,
},
{
name: "TestDiff with full ignore",
iac: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
},
cloud: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
FooBar: "barfoo",
BarFoo: "foobar",
},
},
ignoredDrift: []struct {
res resource.Resource
path []string
}{
{
res: &testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
path: []string{"FooBar"},
},
{
res: &testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
path: []string{"BarFoo"},
},
},
expected: Analysis{
managed: []resource.Resource{
&testresource.FakeResource{
Id: "foobar",
FooBar: "foobar",
BarFoo: "barfoo",
},
},
summary: Summary{
TotalResources: 1,
TotalDrifted: 0,
TotalManaged: 1,
},
},
hasDrifted: false,
},
}
analyzer := NewAnalyzer()
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
filter := &mocks.Filter{}
for _, ignored := range c.ignoredRes {
filter.On("IsResourceIgnored", ignored).Return(true)
}
filter.On("IsResourceIgnored", mock.Anything).Return(false)
for _, s := range c.ignoredDrift {
filter.On("IsFieldIgnored", s.res, s.path).Return(true)
}
filter.On("IsFieldIgnored", mock.Anything, mock.Anything).Return(false)
result, err := analyzer.Analyze(c.cloud, c.iac, filter)
if err != nil {
t.Error(err)
return
}
if result.IsSync() == c.hasDrifted {
t.Errorf("Drifted state does not match, got %t expected %t", result.IsSync(), !c.hasDrifted)
}
managedChanges, err := diff.Diff(result.Managed(), c.expected.Managed())
if err != nil {
t.Fatalf("Unable to compare %+v", err)
}
if len(managedChanges) > 0 {
for _, change := range managedChanges {
t.Errorf("%+v", change)
}
}
unmanagedChanges, err := diff.Diff(result.Unmanaged(), c.expected.Unmanaged())
if err != nil {
t.Fatalf("Unable to compare %+v", err)
}
if len(unmanagedChanges) > 0 {
for _, change := range unmanagedChanges {
t.Errorf("%+v", change)
}
}
deletedChanges, err := diff.Diff(result.Deleted(), c.expected.Deleted())
if err != nil {
t.Fatalf("Unable to compare %+v", err)
}
if len(deletedChanges) > 0 {
for _, change := range deletedChanges {
t.Errorf("%+v", change)
}
}
diffChanges, err := diff.Diff(result.Differences(), c.expected.Differences())
if err != nil {
t.Fatalf("Unable to compare %+v", err)
}
if len(diffChanges) > 0 {
for _, change := range diffChanges {
t.Errorf("%+v", change)
}
}
summaryChanges, err := diff.Diff(result.Summary(), c.expected.Summary())
if err != nil {
t.Fatalf("Unable to compare %+v", err)
}
if len(summaryChanges) > 0 {
for _, change := range summaryChanges {
t.Errorf("%+v", change)
}
}
})
}
}
func TestAnalysis_MarshalJSON(t *testing.T) {
goldenFile := "./testdata/output.json"
analysis := Analysis{}
analysis.AddManaged(
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "AKIA5QYBVVD25KFXJHYJ",
Type: "aws_iam_access_key",
2020-12-22 11:17:04 +00:00
}, &testresource.FakeResource{
Id: "driftctl2",
Type: "aws_managed_resource",
},
)
analysis.AddUnmanaged(
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "driftctl",
Type: "aws_s3_bucket_policy",
2020-12-22 11:17:04 +00:00
}, &testresource.FakeResource{
Id: "driftctl",
Type: "aws_s3_bucket_notification",
},
)
analysis.AddDeleted(
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "test-driftctl2",
Type: "aws_iam_user",
FooBar: "test",
},
2020-12-22 11:17:04 +00:00
&testresource.FakeResource{
Id: "AKIA5QYBVVD2Y6PBAAPY",
Type: "aws_iam_access_key",
},
)
analysis.AddDifference(Difference{
2020-12-22 11:17:04 +00:00
Res: &testresource.FakeResource{
Id: "AKIA5QYBVVD25KFXJHYJ",
Type: "aws_iam_access_key",
},
Changelog: []diff.Change{
{
Type: "update",
Path: []string{"Status"},
From: "Active",
To: "Inactive",
},
},
})
got, err := json.MarshalIndent(analysis, "", "\t")
if err != nil {
t.Fatal(err)
}
if *goldenfile.Update == "TestAnalysis_MarshalJSON" {
if err := ioutil.WriteFile(goldenFile, got, 0600); err != nil {
t.Fatal(err)
}
}
expected, err := ioutil.ReadFile(goldenFile)
if err != nil {
t.Fatal(err)
}
assert.Nil(t, err)
assert.Equal(t, string(expected), string(got))
}
func TestAnalysis_UnmarshalJSON(t *testing.T) {
expected := Analysis{
summary: Summary{
TotalResources: 6,
TotalDrifted: 1,
TotalUnmanaged: 2,
TotalDeleted: 2,
TotalManaged: 2,
},
managed: []resource.Resource{
resource.SerializedResource{
Id: "AKIA5QYBVVD25KFXJHYJ",
Type: "aws_iam_access_key",
},
resource.SerializedResource{
Id: "test-managed",
Type: "aws_iam_user",
},
},
unmanaged: []resource.Resource{
resource.SerializedResource{
Id: "driftctl",
Type: "aws_s3_bucket_policy",
},
resource.SerializedResource{
Id: "driftctl",
Type: "aws_s3_bucket_notification",
},
},
deleted: []resource.Resource{
resource.SerializedResource{
Id: "test-driftctl2",
Type: "aws_iam_user",
},
resource.SerializedResource{
Id: "AKIA5QYBVVD2Y6PBAAPY",
Type: "aws_iam_access_key",
},
},
differences: []Difference{
{
Res: resource.SerializedResource{
Id: "AKIA5QYBVVD25KFXJHYJ",
Type: "aws_iam_access_key",
},
Changelog: []diff.Change{
{
Type: "update",
Path: []string{"Status"},
From: "Active",
To: "Inactive",
},
},
},
},
}
got := Analysis{}
input, err := ioutil.ReadFile("./testdata/input.json")
if err != nil {
t.Fatal(err)
}
err = json.Unmarshal(input, &got)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, expected, got)
assert.Equal(t, 33, got.Coverage())
assert.Equal(t, 2, got.Summary().TotalUnmanaged)
assert.Equal(t, 2, got.Summary().TotalManaged)
assert.Equal(t, 2, got.Summary().TotalDeleted)
assert.Equal(t, 6, got.Summary().TotalResources)
assert.Equal(t, 1, got.Summary().TotalDrifted)
}