2020-12-09 15:31:34 +00:00
package analyser
import (
2020-12-16 12:02:02 +00:00
"reflect"
2020-12-09 15:31:34 +00:00
"sort"
2021-02-15 14:49:20 +00:00
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
2021-03-19 18:20:57 +00:00
"github.com/r3labs/diff/v2"
2020-12-16 12:02:02 +00:00
"github.com/cloudskiff/driftctl/pkg/alerter"
2020-12-09 15:31:34 +00:00
"github.com/cloudskiff/driftctl/pkg/resource"
)
2021-02-12 21:30:36 +00:00
type UnmanagedSecurityGroupRulesAlert struct { }
func newUnmanagedSecurityGroupRulesAlert ( ) * UnmanagedSecurityGroupRulesAlert {
return & UnmanagedSecurityGroupRulesAlert { }
}
func ( u * UnmanagedSecurityGroupRulesAlert ) Message ( ) string {
2021-03-26 17:05:43 +00:00
return "You have unmanaged security group rules that could be false positives, find out more at https://docs.driftctl.com/0.7.0/limitations#terraform-resources"
2021-02-12 21:30:36 +00:00
}
func ( u * UnmanagedSecurityGroupRulesAlert ) ShouldIgnoreResource ( ) bool {
return false
}
type ComputedDiffAlert struct { }
func NewComputedDiffAlert ( ) * ComputedDiffAlert {
return & ComputedDiffAlert { }
}
func ( c * ComputedDiffAlert ) Message ( ) string {
return "You have diffs on computed fields, check the documentation for potential false positive drifts"
}
func ( c * ComputedDiffAlert ) ShouldIgnoreResource ( ) bool {
return false
}
2020-12-16 12:02:02 +00:00
type Analyzer struct {
alerter * alerter . Alerter
}
2020-12-09 15:31:34 +00:00
2020-12-22 15:47:22 +00:00
type Filter interface {
IsResourceIgnored ( res resource . Resource ) bool
IsFieldIgnored ( res resource . Resource , path [ ] string ) bool
2020-12-18 14:28:46 +00:00
}
2020-12-16 12:02:02 +00:00
func NewAnalyzer ( alerter * alerter . Alerter ) Analyzer {
return Analyzer { alerter }
2020-12-09 15:31:34 +00:00
}
2020-12-16 12:02:02 +00:00
func ( a Analyzer ) Analyze ( remoteResources , resourcesFromState [ ] resource . Resource , filter Filter ) ( Analysis , error ) {
2020-12-09 15:31:34 +00:00
analysis := Analysis { }
2021-01-04 15:19:34 +00:00
// Iterate on remote resources and filter ignored resources
filteredRemoteResource := make ( [ ] resource . Resource , 0 , len ( remoteResources ) )
for _ , remoteRes := range remoteResources {
2020-12-16 12:02:02 +00:00
if filter . IsResourceIgnored ( remoteRes ) || a . alerter . IsResourceIgnored ( remoteRes ) {
2021-01-04 15:19:34 +00:00
continue
}
filteredRemoteResource = append ( filteredRemoteResource , remoteRes )
}
2021-01-21 17:05:29 +00:00
haveComputedDiff := false
2020-12-09 15:31:34 +00:00
for _ , stateRes := range resourcesFromState {
2021-01-04 15:19:34 +00:00
i , remoteRes , found := findCorrespondingRes ( filteredRemoteResource , stateRes )
2020-12-22 15:47:22 +00:00
2020-12-16 12:02:02 +00:00
if filter . IsResourceIgnored ( stateRes ) || a . alerter . IsResourceIgnored ( stateRes ) {
2020-12-22 15:47:22 +00:00
continue
}
2020-12-09 15:31:34 +00:00
if ! found {
analysis . AddDeleted ( stateRes )
continue
}
2021-01-04 15:19:34 +00:00
// Remove managed resources, so it will remain only unmanaged ones
filteredRemoteResource = removeResourceByIndex ( i , filteredRemoteResource )
2020-12-09 15:31:34 +00:00
analysis . AddManaged ( stateRes )
delta , _ := diff . Diff ( stateRes , remoteRes )
if len ( delta ) > 0 {
sort . Slice ( delta , func ( i , j int ) bool {
return delta [ i ] . Type < delta [ j ] . Type
} )
2021-01-08 17:14:26 +00:00
changelog := make ( [ ] Change , 0 , len ( delta ) )
2020-12-22 15:47:22 +00:00
for _ , change := range delta {
if filter . IsFieldIgnored ( stateRes , change . Path ) {
continue
}
2021-01-08 17:14:26 +00:00
c := Change { Change : change }
2021-01-11 16:33:23 +00:00
c . Computed = a . isComputedField ( stateRes , c )
2021-01-21 17:05:29 +00:00
if c . Computed {
2021-01-21 17:05:29 +00:00
haveComputedDiff = true
2021-01-21 17:05:29 +00:00
}
2021-01-08 17:14:26 +00:00
changelog = append ( changelog , c )
2020-12-22 15:47:22 +00:00
}
if len ( changelog ) > 0 {
analysis . AddDifference ( Difference {
Res : stateRes ,
Changelog : changelog ,
} )
}
2021-01-21 17:05:29 +00:00
}
2021-01-29 11:43:46 +00:00
}
2021-02-15 14:49:20 +00:00
if a . hasUnmanagedSecurityGroupRules ( filteredRemoteResource ) {
2021-02-12 21:30:36 +00:00
a . alerter . SendAlert ( "" , newUnmanagedSecurityGroupRulesAlert ( ) )
2021-02-15 14:49:20 +00:00
}
2021-01-29 11:43:46 +00:00
if haveComputedDiff {
2021-02-12 21:30:36 +00:00
a . alerter . SendAlert ( "" , NewComputedDiffAlert ( ) )
2020-12-09 15:31:34 +00:00
}
2021-01-04 15:19:34 +00:00
// Add remaining unmanaged resources
analysis . AddUnmanaged ( filteredRemoteResource ... )
2021-03-19 18:20:57 +00:00
// Sort resources by Terraform Id
// The purpose is to have a predictable output
analysis . SortResources ( )
2021-01-11 16:33:23 +00:00
analysis . SetAlerts ( a . alerter . Retrieve ( ) )
2020-12-16 12:02:02 +00:00
2020-12-09 15:31:34 +00:00
return analysis , nil
}
func findCorrespondingRes ( resources [ ] resource . Resource , res resource . Resource ) ( int , resource . Resource , bool ) {
for i , r := range resources {
if resource . IsSameResource ( res , r ) {
return i , r , true
}
}
return - 1 , nil , false
}
2021-01-04 15:19:34 +00:00
func removeResourceByIndex ( i int , resources [ ] resource . Resource ) [ ] resource . Resource {
if i == len ( resources ) - 1 {
return resources [ : len ( resources ) - 1 ]
}
return append ( resources [ : i ] , resources [ i + 1 : ] ... )
}
2020-12-16 12:02:02 +00:00
2021-01-08 17:14:26 +00:00
// isComputedField returns true if the field that generated the diff of a resource
2020-12-16 12:02:02 +00:00
// has a computed tag
2021-01-08 17:14:26 +00:00
func ( a Analyzer ) isComputedField ( stateRes resource . Resource , change Change ) bool {
if field , ok := a . getField ( reflect . TypeOf ( stateRes ) , change . Path ) ; ok {
return field . Tag . Get ( "computed" ) == "true"
}
return false
}
2020-12-16 12:02:02 +00:00
// getField recursively finds the deepest field inside a resource depending on
// its path and its type
func ( a Analyzer ) getField ( t reflect . Type , path [ ] string ) ( reflect . StructField , bool ) {
switch t . Kind ( ) {
case reflect . Ptr :
return a . getField ( t . Elem ( ) , path )
case reflect . Slice :
return a . getField ( t . Elem ( ) , path [ 1 : ] )
default :
{
2021-01-14 17:17:34 +00:00
if field , ok := t . FieldByName ( path [ 0 ] ) ; ok && a . hasNestedFields ( field . Type ) && len ( path ) > 1 {
2020-12-16 12:02:02 +00:00
return a . getField ( field . Type , path [ 1 : ] )
} else {
return field , ok
}
}
}
}
// hasNestedFields will return true if the current field is either a struct
// or a slice of struct
func ( a Analyzer ) hasNestedFields ( t reflect . Type ) bool {
switch t . Kind ( ) {
case reflect . Ptr :
return a . hasNestedFields ( t . Elem ( ) )
case reflect . Slice :
return t . Elem ( ) . Kind ( ) == reflect . Struct
default :
return t . Kind ( ) == reflect . Struct
}
}
2021-02-15 14:49:20 +00:00
// hasUnmanagedSecurityGroupRules returns true if we find at least one unmanaged
// security group rule
func ( a Analyzer ) hasUnmanagedSecurityGroupRules ( unmanagedResources [ ] resource . Resource ) bool {
for _ , res := range unmanagedResources {
if res . TerraformType ( ) == resourceaws . AwsSecurityGroupRuleResourceType {
return true
}
}
return false
}