1452 lines
30 KiB
Go
1452 lines
30 KiB
Go
package analyser
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"testing"
|
|
"time"
|
|
|
|
alerter2 "github.com/snyk/driftctl/enumeration/alerter"
|
|
aws2 "github.com/snyk/driftctl/pkg/resource/aws"
|
|
|
|
"github.com/snyk/driftctl/pkg/filter"
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
testresource "github.com/snyk/driftctl/test/resource"
|
|
|
|
"github.com/snyk/driftctl/test/goldenfile"
|
|
|
|
"github.com/snyk/driftctl/enumeration/resource"
|
|
"github.com/snyk/driftctl/enumeration/resource/aws"
|
|
|
|
"github.com/r3labs/diff/v2"
|
|
)
|
|
|
|
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
|
|
}
|
|
alerts alerter2.Alerts
|
|
expected Analysis
|
|
hasDrifted bool
|
|
options *AnalyzerOptions
|
|
}{
|
|
{
|
|
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{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{},
|
|
expected: Analysis{
|
|
summary: Summary{
|
|
TotalResources: 1,
|
|
TotalDeleted: 1,
|
|
},
|
|
deleted: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
},
|
|
{
|
|
name: "TestResourceIgnoredDeleted",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
ignoredRes: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{},
|
|
expected: Analysis{
|
|
summary: Summary{
|
|
TotalResources: 0,
|
|
TotalDeleted: 0,
|
|
},
|
|
},
|
|
hasDrifted: false,
|
|
},
|
|
{
|
|
name: "Test100PercentCoverage with ignore",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
{
|
|
Id: "foobar2",
|
|
},
|
|
},
|
|
ignoredRes: []*resource.Resource{
|
|
{
|
|
Id: "foobar2",
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
{
|
|
Id: "foobar2",
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalManaged: 1,
|
|
TotalResources: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test100PercentCoverage",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalManaged: 1,
|
|
TotalResources: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "TestUnmanagedResource",
|
|
iac: []*resource.Resource{},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
summary: Summary{
|
|
TotalResources: 1,
|
|
TotalUnmanaged: 1,
|
|
},
|
|
unmanaged: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
},
|
|
{
|
|
name: "TestDiff",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "foobar",
|
|
"arn": "barfoo",
|
|
"ebs_block_device": []map[string]interface{}{
|
|
{
|
|
"volume_type": "bar",
|
|
"volume_size": 0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "barfoo",
|
|
"arn": "foobar",
|
|
"ebs_block_device": []map[string]interface{}{
|
|
{
|
|
"volume_type": "baz",
|
|
"volume_size": 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "foobar",
|
|
"arn": "barfoo",
|
|
"ebs_block_device": []map[string]interface{}{
|
|
{
|
|
"volume_type": "bar",
|
|
"volume_size": 0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 1,
|
|
TotalDrifted: 1,
|
|
TotalManaged: 1,
|
|
},
|
|
differences: []Difference{
|
|
{
|
|
Res: &resource.Resource{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "foobar",
|
|
"arn": "barfoo",
|
|
"ebs_block_device": []map[string]interface{}{
|
|
{
|
|
"volume_type": "bar",
|
|
"volume_size": 0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Changelog: Changelog{
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "foobar",
|
|
To: "barfoo",
|
|
Path: []string{
|
|
"architecture",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "barfoo",
|
|
To: "foobar",
|
|
Path: []string{
|
|
"arn",
|
|
},
|
|
},
|
|
Computed: true,
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: 0,
|
|
To: 1,
|
|
Path: []string{
|
|
"ebs_block_device",
|
|
"0",
|
|
"volume_size",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "bar",
|
|
To: "baz",
|
|
Path: []string{
|
|
"ebs_block_device",
|
|
"0",
|
|
"volume_type",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
alerts: alerter2.Alerts{
|
|
"": {
|
|
NewComputedDiffAlert(),
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
},
|
|
{
|
|
name: "TestDiff with partial ignore",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "foobar",
|
|
"arn": "barfoo",
|
|
},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "barfoo",
|
|
"arn": "foobar",
|
|
},
|
|
},
|
|
},
|
|
ignoredDrift: []struct {
|
|
res *resource.Resource
|
|
path []string
|
|
}{
|
|
{
|
|
res: &resource.Resource{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "foobar",
|
|
"arn": "barfoo",
|
|
},
|
|
},
|
|
path: []string{"architecture"},
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "foobar",
|
|
"arn": "barfoo",
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 1,
|
|
TotalDrifted: 1,
|
|
TotalManaged: 1,
|
|
},
|
|
differences: []Difference{
|
|
{
|
|
Res: &resource.Resource{
|
|
Id: "foobar",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"architecture": "foobar",
|
|
"arn": "barfoo",
|
|
},
|
|
},
|
|
Changelog: Changelog{
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "barfoo",
|
|
To: "foobar",
|
|
Path: []string{
|
|
"arn",
|
|
},
|
|
},
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
alerts: alerter2.Alerts{
|
|
"": {
|
|
NewComputedDiffAlert(),
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
},
|
|
{
|
|
name: "TestDiff with full ignore",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
},
|
|
},
|
|
},
|
|
ignoredRes: []*resource.Resource{
|
|
{
|
|
Id: "should_be_ignored",
|
|
Attrs: &resource.Attributes{},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "barfoo",
|
|
"barfoo": "foobar",
|
|
},
|
|
},
|
|
{
|
|
Id: "should_be_ignored",
|
|
Attrs: &resource.Attributes{},
|
|
},
|
|
},
|
|
ignoredDrift: []struct {
|
|
res *resource.Resource
|
|
path []string
|
|
}{
|
|
{
|
|
res: &resource.Resource{
|
|
Id: "foobar",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
},
|
|
},
|
|
path: []string{"foobar"},
|
|
},
|
|
{
|
|
res: &resource.Resource{
|
|
Id: "foobar",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
},
|
|
},
|
|
path: []string{"barfoo"},
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 1,
|
|
TotalDrifted: 0,
|
|
TotalManaged: 1,
|
|
},
|
|
},
|
|
hasDrifted: false,
|
|
},
|
|
{
|
|
name: "TestDiffWithAlertFiltering",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Type: "fakeres",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
"struct": map[string]interface{}{
|
|
"baz": "baz",
|
|
"bar": "bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "barfoo",
|
|
Type: "fakeres",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
"struct": map[string]interface{}{
|
|
"baz": "baz",
|
|
"bar": "bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "foobaz",
|
|
Type: "other",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
"struct": map[string]interface{}{
|
|
"baz": "baz",
|
|
"bar": "bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "resource",
|
|
Type: "other",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
"struct": map[string]interface{}{
|
|
"baz": "baz",
|
|
"bar": "bar",
|
|
},
|
|
"structslice": []map[string]interface{}{
|
|
{
|
|
"string": "one",
|
|
"array": []string{"foo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foobar",
|
|
Type: "fakeres",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "barfoo",
|
|
"barfoo": "foobar",
|
|
"struct": map[string]interface{}{
|
|
"baz": "bar",
|
|
"bar": "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "barfoo",
|
|
Type: "fakeres",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "barfoo",
|
|
"barfoo": "foobar",
|
|
"struct": map[string]interface{}{
|
|
"baz": "bar",
|
|
"bar": "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "foobaz",
|
|
Type: "other",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "barfoo",
|
|
"barfoo": "foobar",
|
|
"struct": map[string]interface{}{
|
|
"baz": "bar",
|
|
"bar": "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "resource",
|
|
Type: "other",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "barfoo",
|
|
"barfoo": "foobar",
|
|
"struct": map[string]interface{}{
|
|
"baz": "bar",
|
|
"bar": "baz",
|
|
},
|
|
"structslice": []map[string]interface{}{
|
|
{
|
|
"string": "two",
|
|
"array": []string{"oof"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
alerts: alerter2.Alerts{
|
|
"fakeres": {
|
|
&alerter2.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
|
|
},
|
|
"other.foobaz": {
|
|
&alerter2.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
|
|
},
|
|
"other.resource": {
|
|
&alerter2.FakeAlert{Msg: "Should not be ignored"},
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "resource",
|
|
Type: "other",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
"struct": map[string]interface{}{
|
|
"baz": "baz",
|
|
"bar": "bar",
|
|
},
|
|
"structslice": []map[string]interface{}{
|
|
{
|
|
"string": "one",
|
|
"array": []string{"foo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 1,
|
|
TotalDrifted: 1,
|
|
TotalManaged: 1,
|
|
},
|
|
differences: []Difference{
|
|
{
|
|
Res: &resource.Resource{
|
|
Id: "resource",
|
|
Type: "other",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "foobar",
|
|
"barfoo": "barfoo",
|
|
"struct": map[string]interface{}{
|
|
"baz": "baz",
|
|
"bar": "bar",
|
|
},
|
|
"structslice": []map[string]interface{}{
|
|
{
|
|
"string": "one",
|
|
"array": []string{"foo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Changelog: Changelog{
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "barfoo",
|
|
To: "foobar",
|
|
Path: []string{
|
|
"barfoo",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "foobar",
|
|
To: "barfoo",
|
|
Path: []string{
|
|
"foobar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "bar",
|
|
To: "baz",
|
|
Path: []string{
|
|
"struct",
|
|
"bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "baz",
|
|
To: "bar",
|
|
Path: []string{
|
|
"struct",
|
|
"baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "foo",
|
|
To: "oof",
|
|
Path: []string{
|
|
"structslice",
|
|
"0",
|
|
"array",
|
|
"0",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "one",
|
|
To: "two",
|
|
Path: []string{
|
|
"structslice",
|
|
"0",
|
|
"string",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
alerts: alerter2.Alerts{
|
|
"fakeres": {
|
|
&alerter2.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
|
|
},
|
|
"other.foobaz": {
|
|
&alerter2.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
|
|
},
|
|
"other.resource": {
|
|
&alerter2.FakeAlert{Msg: "Should not be ignored"},
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
},
|
|
{
|
|
name: "TestDiff with computed field send 1 alert",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "ID",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "ID",
|
|
"arn": "ARN",
|
|
},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "ID",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "IDCHANGED",
|
|
"arn": "ARNCHANGED",
|
|
},
|
|
},
|
|
},
|
|
alerts: alerter2.Alerts{},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "ID",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "ID",
|
|
"arn": "ARN",
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 1,
|
|
TotalDrifted: 1,
|
|
TotalManaged: 1,
|
|
},
|
|
differences: []Difference{
|
|
{
|
|
Res: &resource.Resource{
|
|
Id: "ID",
|
|
Type: aws.AwsAmiResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "ID",
|
|
"arn": "ARN",
|
|
},
|
|
},
|
|
Changelog: Changelog{
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "ARN",
|
|
To: "ARNCHANGED",
|
|
Path: []string{
|
|
"arn",
|
|
},
|
|
},
|
|
Computed: true,
|
|
},
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "ID",
|
|
To: "IDCHANGED",
|
|
Path: []string{
|
|
"id",
|
|
},
|
|
},
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
alerts: alerter2.Alerts{
|
|
"": {
|
|
NewComputedDiffAlert(),
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
},
|
|
{
|
|
name: "Test alert on unmanaged security group rules",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "managed security group",
|
|
Type: aws.AwsSecurityGroupResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "managed security group",
|
|
},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "managed security group",
|
|
Type: aws.AwsSecurityGroupResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "managed security group",
|
|
},
|
|
},
|
|
{
|
|
Id: "unmanaged rule",
|
|
Type: aws.AwsSecurityGroupRuleResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "unmanaged rule",
|
|
},
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "managed security group",
|
|
Type: aws.AwsSecurityGroupResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "managed security group",
|
|
},
|
|
},
|
|
},
|
|
unmanaged: []*resource.Resource{
|
|
{
|
|
Id: "unmanaged rule",
|
|
Type: aws.AwsSecurityGroupRuleResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"id": "unmanaged rule",
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 2,
|
|
TotalManaged: 1,
|
|
TotalUnmanaged: 1,
|
|
},
|
|
alerts: alerter2.Alerts{
|
|
"": {
|
|
newUnmanagedSecurityGroupRulesAlert(),
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
},
|
|
{
|
|
name: "Test sorted unmanaged & deleted resources",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "deleted resource 22",
|
|
Type: "aws_s3_bucket",
|
|
},
|
|
{
|
|
Id: "deleted resource 20",
|
|
Type: "aws_ebs_volume",
|
|
},
|
|
{
|
|
Id: "deleted resource 20",
|
|
Type: "aws_s3_bucket",
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "unmanaged resource 12",
|
|
Type: "aws_s3_bucket",
|
|
},
|
|
{
|
|
Id: "unmanaged resource 10",
|
|
Type: "aws_s3_bucket",
|
|
},
|
|
{
|
|
Id: "unmanaged resource 11",
|
|
Type: "aws_ebs_volume",
|
|
},
|
|
},
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{},
|
|
unmanaged: []*resource.Resource{
|
|
{
|
|
Id: "unmanaged resource 11",
|
|
Type: "aws_ebs_volume",
|
|
},
|
|
{
|
|
Id: "unmanaged resource 10",
|
|
Type: "aws_s3_bucket",
|
|
},
|
|
{
|
|
Id: "unmanaged resource 12",
|
|
Type: "aws_s3_bucket",
|
|
},
|
|
},
|
|
deleted: []*resource.Resource{
|
|
{
|
|
Id: "deleted resource 20",
|
|
Type: "aws_ebs_volume",
|
|
},
|
|
{
|
|
Id: "deleted resource 20",
|
|
Type: "aws_s3_bucket",
|
|
},
|
|
{
|
|
Id: "deleted resource 22",
|
|
Type: "aws_s3_bucket",
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 6,
|
|
TotalManaged: 0,
|
|
TotalUnmanaged: 3,
|
|
TotalDeleted: 3,
|
|
},
|
|
alerts: alerter2.Alerts{},
|
|
},
|
|
hasDrifted: true,
|
|
},
|
|
{
|
|
name: "Test Discriminant function",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsAppAutoscalingTargetResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"scalable_dimension": "test2",
|
|
},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsAppAutoscalingTargetResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"scalable_dimension": "test1",
|
|
},
|
|
},
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsAppAutoscalingTargetResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"scalable_dimension": "test2",
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsAppAutoscalingTargetResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"scalable_dimension": "test2",
|
|
},
|
|
},
|
|
},
|
|
unmanaged: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsAppAutoscalingTargetResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"scalable_dimension": "test1",
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 2,
|
|
TotalManaged: 1,
|
|
TotalUnmanaged: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test --only-managed flag",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test2",
|
|
},
|
|
},
|
|
{
|
|
Id: "baz",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test3",
|
|
},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test1",
|
|
},
|
|
},
|
|
{
|
|
Id: "bar",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test2",
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test2",
|
|
},
|
|
},
|
|
},
|
|
deleted: []*resource.Resource{
|
|
{
|
|
Id: "baz",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test3",
|
|
},
|
|
},
|
|
},
|
|
differences: []Difference{
|
|
{
|
|
Res: &resource.Resource{
|
|
Id: "foo",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test2",
|
|
},
|
|
},
|
|
Changelog: Changelog{
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
From: "test2",
|
|
To: "test1",
|
|
Path: []string{
|
|
"instance_type",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 2,
|
|
TotalManaged: 1,
|
|
TotalUnmanaged: 0,
|
|
TotalDeleted: 1,
|
|
TotalDrifted: 1,
|
|
},
|
|
},
|
|
options: &AnalyzerOptions{
|
|
OnlyManaged: true,
|
|
Deep: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Test --only-unmanaged flag",
|
|
iac: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test2",
|
|
},
|
|
},
|
|
{
|
|
Id: "baz",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test3",
|
|
},
|
|
},
|
|
},
|
|
cloud: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test1",
|
|
},
|
|
},
|
|
{
|
|
Id: "bar",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test2",
|
|
},
|
|
},
|
|
},
|
|
hasDrifted: true,
|
|
expected: Analysis{
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "foo",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test2",
|
|
},
|
|
},
|
|
},
|
|
unmanaged: []*resource.Resource{
|
|
{
|
|
Id: "bar",
|
|
Type: aws.AwsInstanceResourceType,
|
|
Attrs: &resource.Attributes{
|
|
"instance_type": "test2",
|
|
},
|
|
},
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 2,
|
|
TotalManaged: 1,
|
|
TotalUnmanaged: 1,
|
|
TotalDeleted: 0,
|
|
TotalDrifted: 0,
|
|
},
|
|
},
|
|
options: &AnalyzerOptions{
|
|
OnlyUnmanaged: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
differ, err := diff.NewDiffer(diff.SliceOrdering(true))
|
|
if err != nil {
|
|
t.Fatalf("Error creating new differ: %e", err)
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
testFilter := &filter.MockFilter{}
|
|
for _, ignored := range c.ignoredRes {
|
|
testFilter.On("IsResourceIgnored", ignored).Return(true)
|
|
}
|
|
testFilter.On("IsResourceIgnored", mock.Anything).Return(false)
|
|
|
|
for _, s := range c.ignoredDrift {
|
|
testFilter.On("IsFieldIgnored", s.res, s.path).Return(true)
|
|
}
|
|
testFilter.On("IsFieldIgnored", mock.Anything, mock.Anything).Return(false)
|
|
|
|
al := alerter2.NewAlerter()
|
|
if c.alerts != nil {
|
|
al.SetAlerts(c.alerts)
|
|
}
|
|
|
|
repo := testresource.InitFakeSchemaRepository("aws", "3.19.0")
|
|
aws.InitResourcesMetadata(repo)
|
|
aws2.InitResourcesMetadata(repo)
|
|
|
|
options := AnalyzerOptions{Deep: true}
|
|
if c.options != nil {
|
|
options = *c.options
|
|
}
|
|
|
|
analyzer := NewAnalyzer(al, options, testFilter)
|
|
|
|
for _, res := range c.cloud {
|
|
addSchemaToRes(res, repo)
|
|
}
|
|
|
|
for _, res := range c.iac {
|
|
addSchemaToRes(res, repo)
|
|
}
|
|
|
|
for _, res := range c.ignoredRes {
|
|
addSchemaToRes(res, repo)
|
|
}
|
|
|
|
for _, drift := range c.ignoredDrift {
|
|
addSchemaToRes(drift.res, repo)
|
|
}
|
|
|
|
result, err := analyzer.Analyze(c.cloud, c.iac)
|
|
|
|
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 := differ.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 := differ.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 := differ.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 := differ.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 := differ.Diff(c.expected.Summary(), result.Summary())
|
|
if err != nil {
|
|
t.Fatalf("Unable to compare %+v", err)
|
|
}
|
|
if len(summaryChanges) > 0 {
|
|
for _, change := range summaryChanges {
|
|
t.Errorf("%+v", change)
|
|
}
|
|
}
|
|
|
|
alertsChanges, err := differ.Diff(result.Alerts(), c.expected.Alerts())
|
|
if err != nil {
|
|
t.Fatalf("Unable to compare %+v", err)
|
|
}
|
|
if len(alertsChanges) > 0 {
|
|
for _, change := range alertsChanges {
|
|
t.Errorf("%+v", change)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func addSchemaToRes(res *resource.Resource, repo resource.SchemaRepositoryInterface) {
|
|
schema, _ := repo.GetSchema(res.ResourceType())
|
|
res.Sch = schema
|
|
}
|
|
|
|
func TestAnalysis_MarshalJSON(t *testing.T) {
|
|
goldenFile := "./testdata/output.json"
|
|
analysis := Analysis{
|
|
Duration: 241 * time.Second,
|
|
Date: time.Date(2022, 4, 8, 10, 35, 0, 0, time.UTC),
|
|
}
|
|
analysis.SetIaCSourceCount(1)
|
|
analysis.AddManaged(
|
|
&resource.Resource{
|
|
Id: "AKIA5QYBVVD25KFXJHYJ",
|
|
Type: "aws_iam_access_key",
|
|
}, &resource.Resource{
|
|
Id: "driftctl2",
|
|
Type: "aws_managed_resource",
|
|
},
|
|
)
|
|
analysis.AddUnmanaged(
|
|
&resource.Resource{
|
|
Id: "driftctl",
|
|
Type: "aws_s3_bucket_policy",
|
|
}, &resource.Resource{
|
|
Id: "driftctl",
|
|
Type: "aws_s3_bucket_notification",
|
|
},
|
|
)
|
|
analysis.AddDeleted(
|
|
&resource.Resource{
|
|
Id: "test-driftctl2",
|
|
Type: "aws_iam_user",
|
|
Attrs: &resource.Attributes{
|
|
"foobar": "test",
|
|
},
|
|
},
|
|
&resource.Resource{
|
|
Id: "AKIA5QYBVVD2Y6PBAAPY",
|
|
Type: "aws_iam_access_key",
|
|
},
|
|
)
|
|
analysis.AddDifference(Difference{
|
|
Res: &resource.Resource{
|
|
Id: "AKIA5QYBVVD25KFXJHYJ",
|
|
Type: "aws_iam_access_key",
|
|
Source: &resource.TerraformStateSource{
|
|
State: "tfstate://terraform.tfstate",
|
|
Module: "module",
|
|
Name: "my_name",
|
|
},
|
|
},
|
|
Changelog: []Change{
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
Path: []string{"status"},
|
|
From: "Active",
|
|
To: "Inactive",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
analysis.SetAlerts(alerter2.Alerts{
|
|
"aws_iam_access_key": {
|
|
&alerter2.FakeAlert{Msg: "This is an alert"},
|
|
},
|
|
})
|
|
analysis.ProviderName = "AWS"
|
|
analysis.ProviderVersion = "2.18.5"
|
|
|
|
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{
|
|
options: AnalyzerOptions{
|
|
Deep: true,
|
|
},
|
|
summary: Summary{
|
|
TotalResources: 6,
|
|
TotalDrifted: 1,
|
|
TotalUnmanaged: 2,
|
|
TotalDeleted: 2,
|
|
TotalManaged: 2,
|
|
TotalIaCSourceCount: 3,
|
|
},
|
|
managed: []*resource.Resource{
|
|
{
|
|
Id: "AKIA5QYBVVD25KFXJHYJ",
|
|
Type: "aws_iam_access_key",
|
|
},
|
|
{
|
|
Id: "test-managed",
|
|
Type: "aws_iam_user",
|
|
},
|
|
},
|
|
unmanaged: []*resource.Resource{
|
|
{
|
|
Id: "driftctl",
|
|
Type: "aws_s3_bucket_policy",
|
|
},
|
|
{
|
|
Id: "driftctl",
|
|
Type: "aws_s3_bucket_notification",
|
|
},
|
|
},
|
|
deleted: []*resource.Resource{
|
|
{
|
|
Id: "test-driftctl2",
|
|
Type: "aws_iam_user",
|
|
},
|
|
{
|
|
Id: "AKIA5QYBVVD2Y6PBAAPY",
|
|
Type: "aws_iam_access_key",
|
|
},
|
|
},
|
|
differences: []Difference{
|
|
{
|
|
Res: &resource.Resource{
|
|
Id: "AKIA5QYBVVD25KFXJHYJ",
|
|
Type: "aws_iam_access_key",
|
|
},
|
|
Changelog: []Change{
|
|
{
|
|
Change: diff.Change{
|
|
Type: "update",
|
|
Path: []string{"status"},
|
|
From: "Active",
|
|
To: "Inactive",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
alerts: alerter2.Alerts{
|
|
"aws_iam_access_key": {
|
|
&alerter2.SerializedAlert{
|
|
Msg: "This is an alert",
|
|
},
|
|
},
|
|
},
|
|
ProviderName: "AWS",
|
|
ProviderVersion: "2.18.5",
|
|
Date: time.Date(2022, 4, 8, 10, 35, 0, 0, time.UTC),
|
|
}
|
|
|
|
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.True(t, got.Options().Deep)
|
|
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)
|
|
assert.Equal(t, uint(3), got.Summary().TotalIaCSourceCount)
|
|
assert.Len(t, got.alerts, 1)
|
|
assert.Equal(t, got.alerts["aws_iam_access_key"][0].Message(), "This is an alert")
|
|
}
|