2020-12-09 15:31:34 +00:00
|
|
|
package analyser
|
|
|
|
|
|
|
|
import (
|
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-04-13 08:25:18 +00:00
|
|
|
return "You have unmanaged security group rules that could be false positives, find out more at https://docs.driftctl.com/limitations"
|
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 {
|
2021-06-22 12:52:47 +00:00
|
|
|
return "You have diffs on computed fields, check the documentation for potential false positive drifts: https://docs.driftctl.com/limitations"
|
2021-02-12 21:30:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ComputedDiffAlert) ShouldIgnoreResource() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-06-11 15:10:06 +00:00
|
|
|
type AnalyzerOptions struct {
|
|
|
|
Deep bool
|
|
|
|
}
|
|
|
|
|
2020-12-16 12:02:02 +00:00
|
|
|
type Analyzer struct {
|
2021-05-21 14:09:45 +00:00
|
|
|
alerter *alerter.Alerter
|
2021-06-11 15:10:06 +00:00
|
|
|
options AnalyzerOptions
|
2020-12-16 12:02:02 +00:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2021-06-11 15:10:06 +00:00
|
|
|
func NewAnalyzer(alerter *alerter.Alerter, options AnalyzerOptions) Analyzer {
|
|
|
|
return Analyzer{alerter, options}
|
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)
|
|
|
|
|
2021-06-11 15:10:06 +00:00
|
|
|
// Stop there if we are not in deep mode, we do not want to compute diffs
|
|
|
|
if !a.options.Deep {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-03-26 08:44:55 +00:00
|
|
|
var delta diff.Changelog
|
2021-05-21 14:09:45 +00:00
|
|
|
delta, _ = diff.Diff(stateRes.Attributes(), remoteRes.Attributes())
|
2021-03-26 17:30:17 +00:00
|
|
|
|
|
|
|
if len(delta) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
changelog := make([]Change, 0, len(delta))
|
|
|
|
for _, change := range delta {
|
|
|
|
if filter.IsFieldIgnored(stateRes, change.Path) {
|
|
|
|
continue
|
2020-12-22 15:47:22 +00:00
|
|
|
}
|
2021-03-26 17:30:17 +00:00
|
|
|
c := Change{Change: change}
|
2021-05-21 14:09:45 +00:00
|
|
|
resSchema := stateRes.Schema()
|
|
|
|
if resSchema != nil {
|
|
|
|
c.Computed = resSchema.IsComputedField(c.Path)
|
|
|
|
c.JsonString = resSchema.IsJsonStringField(c.Path)
|
2021-03-26 08:44:55 +00:00
|
|
|
}
|
2021-03-26 17:30:17 +00:00
|
|
|
if c.Computed {
|
|
|
|
haveComputedDiff = true
|
2020-12-22 15:47:22 +00:00
|
|
|
}
|
2021-03-26 17:30:17 +00:00
|
|
|
changelog = append(changelog, c)
|
|
|
|
}
|
|
|
|
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-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
|
|
|
|
}
|