Merge branch 'main' into go1.16

main
Elie 2021-02-18 15:04:16 +01:00 committed by GitHub
commit 0c06dfa26a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 1042311 additions and 166 deletions

View File

@ -12,6 +12,24 @@ When a crash occurs in driftctl, we do not send any crash reports.
For debugging purposes, you can add `--error-reporting` when running driftctl and crash data will be sent to us via [Sentry](https://sentry.io) For debugging purposes, you can add `--error-reporting` when running driftctl and crash data will be sent to us via [Sentry](https://sentry.io)
Details of reported data can be found [here](./cmd/flags/error-reporting.md) Details of reported data can be found [here](./cmd/flags/error-reporting.md)
#### Log level
By default driftctl logger only displays warning and error messages. You can set `LOG_LEVEL` environment variable to change the default level.
Valid values are : trace,debug,info,warn,error,fatal,panic.
**Note:** In trace level, terraform provider logs will be shown.
Example
```shell
$ LOG_LEVEL=debug driftctl scan
DEBU[0000] New provider library created
DEBU[0000] Found existing provider path=/home/driftctl/.driftctl/plugins/linux_amd64/terraform-provider-aws_v3.19.0_x5
DEBU[0000] Starting gRPC client alias=us-east-1
DEBU[0001] New gRPC client started alias=us-east-1
...
```
### Usage ### Usage
- Commands - Commands

View File

@ -63,6 +63,9 @@ As AWS documentation recommends, the below policy is granting only the permissio
"Effect": "Allow", "Effect": "Allow",
"Resource": "*", "Resource": "*",
"Action": [ "Action": [
"cloudfront:GetDistribution",
"cloudfront:ListDistributions",
"cloudfront:ListTagsForResource",
"ec2:DescribeAddresses", "ec2:DescribeAddresses",
"ec2:DescribeImages", "ec2:DescribeImages",
"ec2:DescribeInstanceAttribute", "ec2:DescribeInstanceAttribute",
@ -107,6 +110,8 @@ As AWS documentation recommends, the below policy is granting only the permissio
"route53:ListHostedZones", "route53:ListHostedZones",
"route53:ListResourceRecordSets", "route53:ListResourceRecordSets",
"route53:ListTagsForResource", "route53:ListTagsForResource",
"route53:ListHealthChecks",
"route53:GetHealthCheck",
"s3:GetAccelerateConfiguration", "s3:GetAccelerateConfiguration",
"s3:GetAnalyticsConfiguration", "s3:GetAnalyticsConfiguration",
"s3:GetBucketAcl", "s3:GetBucketAcl",
@ -207,7 +212,7 @@ As AWS documentation recommends, the below policy is granting only the permissio
- [x] aws_route53_record - [x] aws_route53_record
- [x] aws_route53_zone - [x] aws_route53_zone
- [ ] aws_route53_delegation_set - [ ] aws_route53_delegation_set
- [ ] aws_route53_health_check - [x] aws_route53_health_check
- [ ] aws_route53_query_log - [ ] aws_route53_query_log
- [ ] aws_route53_vpc_association_authorization - [ ] aws_route53_vpc_association_authorization
- [ ] aws_route53_zone_association - [ ] aws_route53_zone_association
@ -260,6 +265,7 @@ As AWS documentation recommends, the below policy is granting only the permissio
- [x] aws_sqs_queue_policy - [x] aws_sqs_queue_policy
## SNS ## SNS
- [x] aws_sns_topic - [x] aws_sns_topic
- [x] aws_sns_topic_policy - [x] aws_sns_topic_policy
- [x] aws_sns_topic_subscription - [x] aws_sns_topic_subscription
@ -267,6 +273,11 @@ As AWS documentation recommends, the below policy is granting only the permissio
- [ ] aws_sns_sms_preferences - [ ] aws_sns_sms_preferences
## DynamoDB ## DynamoDB
- [x] aws_dynamodb_table - [x] aws_dynamodb_table
- [ ] aws_dynamodb_global_table - [ ] aws_dynamodb_global_table
- [ ] aws_dynamodb_table_item - [ ] aws_dynamodb_table_item
## Cloudfront
- [x] aws_cloudfront_distribution

4865
mocks/CloudfrontClient.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
package mocks
import (
cloudfront "github.com/aws/aws-sdk-go/service/cloudfront"
mock "github.com/stretchr/testify/mock"
)
// CloudfrontRepository is an autogenerated mock type for the CloudfrontRepository type
type CloudfrontRepository struct {
mock.Mock
}
// ListAllDistributions provides a mock function with given fields:
func (_m *CloudfrontRepository) ListAllDistributions() ([]*cloudfront.DistributionSummary, error) {
ret := _m.Called()
var r0 []*cloudfront.DistributionSummary
if rf, ok := ret.Get(0).(func() []*cloudfront.DistributionSummary); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*cloudfront.DistributionSummary)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}

4639
mocks/Route53Client.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
route53 "github.com/aws/aws-sdk-go/service/route53"
)
// Route53Repository is an autogenerated mock type for the Route53Repository type
type Route53Repository struct {
mock.Mock
}
// ListAllHealthChecks provides a mock function with given fields:
func (_m *Route53Repository) ListAllHealthChecks() ([]*route53.HealthCheck, error) {
ret := _m.Called()
var r0 []*route53.HealthCheck
if rf, ok := ret.Get(0).(func() []*route53.HealthCheck); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*route53.HealthCheck)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -1,8 +1,53 @@
package alerter package alerter
import "encoding/json"
type Alerts map[string][]Alert type Alerts map[string][]Alert
type Alert struct { type Alert interface {
Message string `json:"message"` Message() string
ShouldIgnoreResource bool `json:"-"` ShouldIgnoreResource() bool
}
type FakeAlert struct {
Msg string
IgnoreResource bool
}
func (f *FakeAlert) Message() string {
return f.Msg
}
func (f *FakeAlert) ShouldIgnoreResource() bool {
return f.IgnoreResource
}
type SerializableAlert struct {
Alert
}
type SerializedAlert struct {
Msg string `json:"message"`
}
func (u *SerializedAlert) Message() string {
return u.Msg
}
func (u *SerializedAlert) ShouldIgnoreResource() bool {
return false
}
func (s *SerializableAlert) UnmarshalJSON(bytes []byte) error {
var res SerializedAlert
if err := json.Unmarshal(bytes, &res); err != nil {
return err
}
s.Alert = &res
return nil
}
func (s *SerializableAlert) MarshalJSON() ([]byte, error) {
return json.Marshal(SerializedAlert{Msg: s.Message()})
} }

View File

@ -67,7 +67,7 @@ func (a *Alerter) IsResourceIgnored(res resource.Resource) bool {
func (a *Alerter) shouldBeIgnored(alert []Alert) bool { func (a *Alerter) shouldBeIgnored(alert []Alert) bool {
for _, a := range alert { for _, a := range alert {
if a.ShouldIgnoreResource { if a.ShouldIgnoreResource() {
return true return true
} }
} }

View File

@ -23,18 +23,12 @@ func TestAlerter_Alert(t *testing.T) {
name: "TestWithSingleAlert", name: "TestWithSingleAlert",
alerts: Alerts{ alerts: Alerts{
"fakeres.foobar": []Alert{ "fakeres.foobar": []Alert{
{ &FakeAlert{"This is an alert", false},
Message: "This is an alert",
ShouldIgnoreResource: false,
},
}, },
}, },
expected: Alerts{ expected: Alerts{
"fakeres.foobar": []Alert{ "fakeres.foobar": []Alert{
{ &FakeAlert{"This is an alert", false},
Message: "This is an alert",
ShouldIgnoreResource: false,
},
}, },
}, },
}, },
@ -42,38 +36,20 @@ func TestAlerter_Alert(t *testing.T) {
name: "TestWithMultipleAlerts", name: "TestWithMultipleAlerts",
alerts: Alerts{ alerts: Alerts{
"fakeres.foobar": []Alert{ "fakeres.foobar": []Alert{
{ &FakeAlert{"This is an alert", false},
Message: "This is an alert", &FakeAlert{"This is a second alert", true},
ShouldIgnoreResource: false,
},
{
Message: "This is a second alert",
ShouldIgnoreResource: true,
},
}, },
"fakeres.barfoo": []Alert{ "fakeres.barfoo": []Alert{
{ &FakeAlert{"This is a third alert", true},
Message: "This is a third alert",
ShouldIgnoreResource: true,
},
}, },
}, },
expected: Alerts{ expected: Alerts{
"fakeres.foobar": []Alert{ "fakeres.foobar": []Alert{
{ &FakeAlert{"This is an alert", false},
Message: "This is an alert", &FakeAlert{"This is a second alert", true},
ShouldIgnoreResource: false,
},
{
Message: "This is a second alert",
ShouldIgnoreResource: true,
},
}, },
"fakeres.barfoo": []Alert{ "fakeres.barfoo": []Alert{
{ &FakeAlert{"This is a third alert", true},
Message: "This is a third alert",
ShouldIgnoreResource: true,
},
}, },
}, },
}, },
@ -116,24 +92,16 @@ func TestAlerter_IgnoreResources(t *testing.T) {
name: "TestShouldNotBeIgnoredWithAlerts", name: "TestShouldNotBeIgnoredWithAlerts",
alerts: Alerts{ alerts: Alerts{
"fakeres": { "fakeres": {
{ &FakeAlert{"Should not be ignored", false},
Message: "Should not be ignored",
},
}, },
"fakeres.foobar": { "fakeres.foobar": {
{ &FakeAlert{"Should not be ignored", false},
Message: "Should not be ignored",
},
}, },
"fakeres.barfoo": { "fakeres.barfoo": {
{ &FakeAlert{"Should not be ignored", false},
Message: "Should not be ignored",
},
}, },
"other.resource": { "other.resource": {
{ &FakeAlert{"Should not be ignored", false},
Message: "Should not be ignored",
},
}, },
}, },
resource: &resource2.FakeResource{ resource: &resource2.FakeResource{
@ -146,21 +114,13 @@ func TestAlerter_IgnoreResources(t *testing.T) {
name: "TestShouldBeIgnoredWithAlertsOnWildcard", name: "TestShouldBeIgnoredWithAlertsOnWildcard",
alerts: Alerts{ alerts: Alerts{
"fakeres": { "fakeres": {
{ &FakeAlert{"Should be ignored", true},
Message: "Should be ignored",
ShouldIgnoreResource: true,
},
}, },
"other.foobaz": { "other.foobaz": {
{ &FakeAlert{"Should be ignored", true},
Message: "Should be ignored",
ShouldIgnoreResource: true,
},
}, },
"other.resource": { "other.resource": {
{ &FakeAlert{"Should not be ignored", false},
Message: "Should not be ignored",
},
}, },
}, },
resource: &resource2.FakeResource{ resource: &resource2.FakeResource{
@ -173,21 +133,13 @@ func TestAlerter_IgnoreResources(t *testing.T) {
name: "TestShouldBeIgnoredWithAlertsOnResource", name: "TestShouldBeIgnoredWithAlertsOnResource",
alerts: Alerts{ alerts: Alerts{
"fakeres": { "fakeres": {
{ &FakeAlert{"Should be ignored", true},
Message: "Should be ignored",
ShouldIgnoreResource: true,
},
}, },
"other.foobaz": { "other.foobaz": {
{ &FakeAlert{"Should be ignored", true},
Message: "Should be ignored",
ShouldIgnoreResource: true,
},
}, },
"other.resource": { "other.resource": {
{ &FakeAlert{"Should not be ignored", false},
Message: "Should not be ignored",
},
}, },
}, },
resource: &resource2.FakeResource{ resource: &resource2.FakeResource{

View File

@ -43,13 +43,13 @@ type serializableDifference struct {
} }
type serializableAnalysis struct { type serializableAnalysis struct {
Summary Summary `json:"summary"` Summary Summary `json:"summary"`
Managed []resource.SerializableResource `json:"managed"` Managed []resource.SerializableResource `json:"managed"`
Unmanaged []resource.SerializableResource `json:"unmanaged"` Unmanaged []resource.SerializableResource `json:"unmanaged"`
Deleted []resource.SerializableResource `json:"deleted"` Deleted []resource.SerializableResource `json:"deleted"`
Differences []serializableDifference `json:"differences"` Differences []serializableDifference `json:"differences"`
Coverage int `json:"coverage"` Coverage int `json:"coverage"`
Alerts alerter.Alerts `json:"alerts"` Alerts map[string][]alerter.SerializableAlert `json:"alerts"`
} }
func (a Analysis) MarshalJSON() ([]byte, error) { func (a Analysis) MarshalJSON() ([]byte, error) {
@ -69,9 +69,16 @@ func (a Analysis) MarshalJSON() ([]byte, error) {
Changelog: di.Changelog, Changelog: di.Changelog,
}) })
} }
if len(a.alerts) > 0 {
bla.Alerts = make(map[string][]alerter.SerializableAlert)
for k, v := range a.alerts {
for _, al := range v {
bla.Alerts[k] = append(bla.Alerts[k], alerter.SerializableAlert{Alert: al})
}
}
}
bla.Summary = a.summary bla.Summary = a.summary
bla.Coverage = a.Coverage() bla.Coverage = a.Coverage()
bla.Alerts = a.alerts
return json.Marshal(bla) return json.Marshal(bla)
} }
@ -108,7 +115,16 @@ func (a *Analysis) UnmarshalJSON(bytes []byte) error {
Changelog: di.Changelog, Changelog: di.Changelog,
}) })
} }
a.SetAlerts(bla.Alerts) if len(bla.Alerts) > 0 {
a.alerts = make(alerter.Alerts)
for k, v := range bla.Alerts {
for _, al := range v {
a.alerts[k] = append(a.alerts[k], &alerter.SerializedAlert{
Msg: al.Message(),
})
}
}
}
return nil return nil
} }

View File

@ -11,6 +11,34 @@ import (
"github.com/r3labs/diff/v2" "github.com/r3labs/diff/v2"
) )
type UnmanagedSecurityGroupRulesAlert struct{}
func newUnmanagedSecurityGroupRulesAlert() *UnmanagedSecurityGroupRulesAlert {
return &UnmanagedSecurityGroupRulesAlert{}
}
func (u *UnmanagedSecurityGroupRulesAlert) Message() string {
return "You have unmanaged security group rules that could be false positives, find out more at https://github.com/cloudskiff/driftctl/blob/main/doc/LIMITATIONS.md#terraform-resources"
}
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
}
type Analyzer struct { type Analyzer struct {
alerter *alerter.Alerter alerter *alerter.Alerter
} }
@ -80,17 +108,11 @@ func (a Analyzer) Analyze(remoteResources, resourcesFromState []resource.Resourc
} }
if a.hasUnmanagedSecurityGroupRules(filteredRemoteResource) { if a.hasUnmanagedSecurityGroupRules(filteredRemoteResource) {
a.alerter.SendAlert("", a.alerter.SendAlert("", newUnmanagedSecurityGroupRulesAlert())
alerter.Alert{
Message: "You have unmanaged security group rules that could be false positives, find out more at https://github.com/cloudskiff/driftctl/blob/main/doc/LIMITATIONS.md#terraform-resources",
})
} }
if haveComputedDiff { if haveComputedDiff {
a.alerter.SendAlert("", a.alerter.SendAlert("", NewComputedDiffAlert())
alerter.Alert{
Message: "You have diffs on computed fields, check the documentation for potential false positive drifts",
})
} }
// Add remaining unmanaged resources // Add remaining unmanaged resources

View File

@ -272,9 +272,7 @@ func TestAnalyze(t *testing.T) {
}, },
alerts: alerter.Alerts{ alerts: alerter.Alerts{
"": { "": {
{ NewComputedDiffAlert(),
Message: "You have diffs on computed fields, check the documentation for potential false positive drifts",
},
}, },
}, },
}, },
@ -351,9 +349,7 @@ func TestAnalyze(t *testing.T) {
}, },
alerts: alerter.Alerts{ alerts: alerter.Alerts{
"": { "": {
{ NewComputedDiffAlert(),
Message: "You have diffs on computed fields, check the documentation for potential false positive drifts",
},
}, },
}, },
}, },
@ -520,21 +516,13 @@ func TestAnalyze(t *testing.T) {
}, },
alerts: alerter.Alerts{ alerts: alerter.Alerts{
"fakeres": { "fakeres": {
{ &alerter.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
Message: "Should be ignored",
ShouldIgnoreResource: true,
},
}, },
"other.foobaz": { "other.foobaz": {
{ &alerter.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
Message: "Should be ignored",
ShouldIgnoreResource: true,
},
}, },
"other.resource": { "other.resource": {
{ &alerter.FakeAlert{Msg: "Should not be ignored"},
Message: "Should not be ignored",
},
}, },
}, },
expected: Analysis{ expected: Analysis{
@ -656,26 +644,16 @@ func TestAnalyze(t *testing.T) {
}, },
alerts: alerter.Alerts{ alerts: alerter.Alerts{
"fakeres": { "fakeres": {
{ &alerter.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
Message: "Should be ignored",
ShouldIgnoreResource: true,
},
}, },
"other.foobaz": { "other.foobaz": {
{ &alerter.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
Message: "Should be ignored",
ShouldIgnoreResource: true,
},
}, },
"other.resource": { "other.resource": {
{ &alerter.FakeAlert{Msg: "Should not be ignored"},
Message: "Should not be ignored",
},
}, },
"": { "": {
{ NewComputedDiffAlert(),
Message: "You have diffs on computed fields, check the documentation for potential false positive drifts",
},
}, },
}, },
}, },
@ -875,9 +853,7 @@ func TestAnalyze(t *testing.T) {
}, },
alerts: alerter.Alerts{ alerts: alerter.Alerts{
"": { "": {
{ NewComputedDiffAlert(),
Message: "You have diffs on computed fields, check the documentation for potential false positive drifts",
},
}, },
}, },
}, },
@ -916,9 +892,7 @@ func TestAnalyze(t *testing.T) {
}, },
alerts: alerter.Alerts{ alerts: alerter.Alerts{
"": { "": {
{ newUnmanagedSecurityGroupRulesAlert(),
Message: "You have unmanaged security group rules that could be false positives, find out more at https://github.com/cloudskiff/driftctl/blob/main/doc/LIMITATIONS.md#terraform-resources",
},
}, },
}, },
}, },
@ -1069,9 +1043,7 @@ func TestAnalysis_MarshalJSON(t *testing.T) {
}) })
analysis.SetAlerts(alerter.Alerts{ analysis.SetAlerts(alerter.Alerts{
"aws_iam_access_key": { "aws_iam_access_key": {
{ &alerter.FakeAlert{Msg: "This is an alert"},
Message: "This is an alert",
},
}, },
}) })
@ -1151,8 +1123,8 @@ func TestAnalysis_UnmarshalJSON(t *testing.T) {
}, },
alerts: alerter.Alerts{ alerts: alerter.Alerts{
"aws_iam_access_key": { "aws_iam_access_key": {
{ &alerter.SerializedAlert{
Message: "This is an alert", Msg: "This is an alert",
}, },
}, },
}, },
@ -1174,4 +1146,6 @@ func TestAnalysis_UnmarshalJSON(t *testing.T) {
assert.Equal(t, 2, got.Summary().TotalDeleted) assert.Equal(t, 2, got.Summary().TotalDeleted)
assert.Equal(t, 6, got.Summary().TotalResources) assert.Equal(t, 6, got.Summary().TotalResources)
assert.Equal(t, 1, got.Summary().TotalDrifted) assert.Equal(t, 1, got.Summary().TotalDrifted)
assert.Len(t, got.alerts, 1)
assert.Equal(t, got.alerts["aws_iam_access_key"][0].Message(), "This is an alert")
} }

View File

@ -5,6 +5,8 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/cloudskiff/driftctl/pkg/remote"
"github.com/cloudskiff/driftctl/pkg/analyser" "github.com/cloudskiff/driftctl/pkg/analyser"
"github.com/cloudskiff/driftctl/pkg/resource" "github.com/cloudskiff/driftctl/pkg/resource"
"github.com/fatih/color" "github.com/fatih/color"
@ -93,12 +95,21 @@ func (c *Console) Write(analysis *analyser.Analysis) error {
c.writeSummary(analysis) c.writeSummary(analysis)
policy := false
for _, alerts := range analysis.Alerts() { for _, alerts := range analysis.Alerts() {
for _, alert := range alerts { for _, alert := range alerts {
fmt.Printf("%s\n", color.YellowString(alert.Message)) fmt.Printf("%s\n", color.YellowString(alert.Message()))
if _, ok := alert.(*remote.EnumerationAccessDeniedAlert); ok {
policy = true
}
} }
} }
if policy {
fmt.Println(color.YellowString("\nThe latest minimal read-only policy for driftctl is always available here, please update yours: https://github.com/cloudskiff/driftctl/blob/main/doc/cmd/scan/supported_resources/aws.md"))
}
return nil return nil
} }

View File

@ -55,6 +55,12 @@ func TestConsole_Write(t *testing.T) {
args: args{analysis: fakeAnalysisWithComputedFields()}, args: args{analysis: fakeAnalysisWithComputedFields()},
wantErr: false, wantErr: false,
}, },
{
name: "test console output with enumeration alerts",
goldenfile: "output_access_denied_alert.txt",
args: args{analysis: fakeAnalysisWithEnumerationError()},
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -38,6 +38,14 @@ func TestJSON_Write(t *testing.T) {
}, },
wantErr: false, wantErr: false,
}, },
{
name: "test json output with enumeration alerts",
goldenfile: "output_access_denied_alert.json",
args: args{
analysis: fakeAnalysisWithEnumerationError(),
},
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -5,6 +5,7 @@ import (
"github.com/cloudskiff/driftctl/pkg/alerter" "github.com/cloudskiff/driftctl/pkg/alerter"
"github.com/cloudskiff/driftctl/pkg/analyser" "github.com/cloudskiff/driftctl/pkg/analyser"
"github.com/cloudskiff/driftctl/pkg/remote"
testresource "github.com/cloudskiff/driftctl/test/resource" testresource "github.com/cloudskiff/driftctl/test/resource"
"github.com/r3labs/diff/v2" "github.com/r3labs/diff/v2"
) )
@ -230,9 +231,19 @@ func fakeAnalysisWithComputedFields() *analyser.Analysis {
}}) }})
a.SetAlerts(alerter.Alerts{ a.SetAlerts(alerter.Alerts{
"": []alerter.Alert{ "": []alerter.Alert{
{ analyser.NewComputedDiffAlert(),
Message: "You have diffs on computed fields, check the documentation for potential false positive drifts", },
}, })
return &a
}
func fakeAnalysisWithEnumerationError() *analyser.Analysis {
a := analyser.Analysis{}
a.SetAlerts(alerter.Alerts{
"": []alerter.Alert{
remote.NewEnumerationAccessDeniedAlert("aws_vpc", "aws_vpc"),
remote.NewEnumerationAccessDeniedAlert("aws_sqs", "aws_sqs"),
remote.NewEnumerationAccessDeniedAlert("aws_sns", "aws_sns"),
}, },
}) })
return &a return &a

View File

@ -0,0 +1,27 @@
{
"summary": {
"total_resources": 0,
"total_drifted": 0,
"total_unmanaged": 0,
"total_deleted": 0,
"total_managed": 0
},
"managed": null,
"unmanaged": null,
"deleted": null,
"differences": null,
"coverage": 0,
"alerts": {
"": [
{
"message": "Ignoring aws_vpc from drift calculation: Listing aws_vpc is forbidden."
},
{
"message": "Ignoring aws_sqs from drift calculation: Listing aws_sqs is forbidden."
},
{
"message": "Ignoring aws_sns from drift calculation: Listing aws_sns is forbidden."
}
]
}
}

View File

@ -0,0 +1,8 @@
Found 0 resource(s)
- 0% coverage
Congrats! Your infrastructure is fully in sync.
Ignoring aws_vpc from drift calculation: Listing aws_vpc is forbidden.
Ignoring aws_sqs from drift calculation: Listing aws_sqs is forbidden.
Ignoring aws_sns from drift calculation: Listing aws_sns is forbidden.
The latest minimal read-only policy for driftctl is always available here, please update yours: https://github.com/cloudskiff/driftctl/blob/main/doc/cmd/scan/supported_resources/aws.md

View File

@ -54,6 +54,8 @@ func Deserializers() []deserializer.CTYDeserializer {
awsdeserializer.NewSNSTopicPolicyDeserializer(), awsdeserializer.NewSNSTopicPolicyDeserializer(),
awsdeserializer.NewSNSTopicSubscriptionDeserializer(), awsdeserializer.NewSNSTopicSubscriptionDeserializer(),
awsdeserializer.NewDynamoDBTableDeserializer(), awsdeserializer.NewDynamoDBTableDeserializer(),
awsdeserializer.NewRoute53HealthCheckDeserializer(),
awsdeserializer.NewCloudfrontDistributionDeserializer(),
ghdeserializer.NewGithubRepositoryDeserializer(), ghdeserializer.NewGithubRepositoryDeserializer(),
} }

View File

@ -81,6 +81,8 @@ func TestTerraformStateReader_AWS_Resources(t *testing.T) {
{name: "SNS Topic Policy", dirName: "sns_topic_policy", wantErr: false}, {name: "SNS Topic Policy", dirName: "sns_topic_policy", wantErr: false},
{name: "SNS Topic Subscription", dirName: "sns_topic_subscription", wantErr: false}, {name: "SNS Topic Subscription", dirName: "sns_topic_subscription", wantErr: false},
{name: "DynamoDB table", dirName: "dynamodb_table", wantErr: false}, {name: "DynamoDB table", dirName: "dynamodb_table", wantErr: false},
{name: "Route53 Health Check", dirName: "route53_health_check", wantErr: false},
{name: "Cloudfront distribution", dirName: "cloudfront_distribution", wantErr: false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -0,0 +1,98 @@
[
{
"Aliases": null,
"Arn": "arn:aws:cloudfront::047081014315:distribution/E1M9CNS0XSHI19",
"CallerReference": "terraform-20210216101734792900000001",
"Comment": null,
"DefaultRootObject": "",
"DomainName": "d1g0dw0i1wvlgd.cloudfront.net",
"Enabled": false,
"Etag": "E2CKBANLXUPWGQ",
"HostedZoneId": "Z2FDTNDATAQYW2",
"HttpVersion": "http2",
"Id": "E1M9CNS0XSHI19",
"InProgressValidationBatches": 0,
"IsIpv6Enabled": false,
"LastModifiedTime": "2021-02-16 10:17:35.404 +0000 UTC",
"PriceClass": "PriceClass_All",
"RetainOnDelete": false,
"Status": "Deployed",
"Tags": {},
"TrustedSigners": [
{
"Enabled": false,
"Items": []
}
],
"WaitForDeployment": true,
"WebAclId": "",
"CustomErrorResponse": [],
"DefaultCacheBehavior": [
{
"AllowedMethods": [
"GET",
"HEAD"
],
"CachedMethods": [
"GET",
"HEAD"
],
"Compress": false,
"DefaultTtl": 86400,
"FieldLevelEncryptionId": "",
"MaxTtl": 31536000,
"MinTtl": 0,
"SmoothStreaming": false,
"TargetOriginId": "S3-foo-cloudfront",
"TrustedSigners": [],
"ViewerProtocolPolicy": "allow-all",
"ForwardedValues": [
{
"Headers": null,
"QueryString": false,
"QueryStringCacheKeys": [],
"Cookies": [
{
"Forward": "none",
"WhitelistedNames": null
}
]
}
],
"LambdaFunctionAssociation": []
}
],
"LoggingConfig": [],
"OrderedCacheBehavior": [],
"Origin": [
{
"DomainName": "foo-cloudfront.s3.eu-west-3.amazonaws.com",
"OriginId": "S3-foo-cloudfront",
"OriginPath": "",
"CustomHeader": [],
"CustomOriginConfig": [],
"S3OriginConfig": []
}
],
"OriginGroup": null,
"Restrictions": [
{
"GeoRestriction": [
{
"Locations": null,
"RestrictionType": "none"
}
]
}
],
"ViewerCertificate": [
{
"AcmCertificateArn": "",
"CloudfrontDefaultCertificate": true,
"IamCertificateId": "",
"MinimumProtocolVersion": "TLSv1",
"SslSupportMethod": ""
}
]
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
{
"version": 4,
"terraform_version": "0.14.5",
"serial": 596,
"lineage": "cc4be827-a907-1623-961b-0fc1ce33973e",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_cloudfront_distribution",
"name": "foo_distribution",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"aliases": null,
"arn": "arn:aws:cloudfront::047081014315:distribution/E1M9CNS0XSHI19",
"caller_reference": "terraform-20210216101734792900000001",
"comment": null,
"custom_error_response": [],
"default_cache_behavior": [
{
"allowed_methods": [
"GET",
"HEAD"
],
"cached_methods": [
"GET",
"HEAD"
],
"compress": false,
"default_ttl": 86400,
"field_level_encryption_id": "",
"forwarded_values": [
{
"cookies": [
{
"forward": "none",
"whitelisted_names": null
}
],
"headers": null,
"query_string": false,
"query_string_cache_keys": []
}
],
"lambda_function_association": [],
"max_ttl": 31536000,
"min_ttl": 0,
"smooth_streaming": false,
"target_origin_id": "S3-foo-cloudfront",
"trusted_signers": [],
"viewer_protocol_policy": "allow-all"
}
],
"default_root_object": "",
"domain_name": "d1g0dw0i1wvlgd.cloudfront.net",
"enabled": false,
"etag": "E2CKBANLXUPWGQ",
"hosted_zone_id": "Z2FDTNDATAQYW2",
"http_version": "http2",
"id": "E1M9CNS0XSHI19",
"in_progress_validation_batches": 0,
"is_ipv6_enabled": false,
"last_modified_time": "2021-02-16 10:17:35.404 +0000 UTC",
"logging_config": [],
"ordered_cache_behavior": [],
"origin": [
{
"custom_header": [],
"custom_origin_config": [],
"domain_name": "foo-cloudfront.s3.eu-west-3.amazonaws.com",
"origin_id": "S3-foo-cloudfront",
"origin_path": "",
"s3_origin_config": []
}
],
"origin_group": [],
"price_class": "PriceClass_All",
"restrictions": [
{
"geo_restriction": [
{
"locations": null,
"restriction_type": "none"
}
]
}
],
"retain_on_delete": false,
"status": "Deployed",
"tags": {},
"trusted_signers": [
{
"enabled": false,
"items": []
}
],
"viewer_certificate": [
{
"acm_certificate_arn": "",
"cloudfront_default_certificate": true,
"iam_certificate_id": "",
"minimum_protocol_version": "TLSv1",
"ssl_support_method": ""
}
],
"wait_for_deployment": true,
"web_acl_id": ""
},
"sensitive_attributes": [],
"private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==",
"dependencies": [
"aws_s3_bucket.foo_cloudfront"
]
}
]
}
]
}

View File

@ -0,0 +1,50 @@
[
{
"ChildHealthThreshold": 0,
"ChildHealthchecks": [],
"CloudwatchAlarmName": null,
"CloudwatchAlarmRegion": null,
"Disabled": false,
"EnableSni": false,
"FailureThreshold": 5,
"Fqdn": "moadib.net",
"Id": "70994c67-b616-4caa-89bc-557153b65845",
"InsufficientDataHealthStatus": "",
"InvertHealthcheck": false,
"IpAddress": "",
"MeasureLatency": false,
"Port": 80,
"ReferenceName": null,
"Regions": [],
"RequestInterval": 30,
"ResourcePath": "/",
"SearchString": "",
"Tags": {
"Name": "tf-test-health-check"
},
"Type": "HTTP"
},
{
"ChildHealthThreshold": 0,
"ChildHealthchecks": [],
"CloudwatchAlarmName": null,
"CloudwatchAlarmRegion": null,
"Disabled": false,
"EnableSni": true,
"FailureThreshold": 5,
"Fqdn": "moadib.net",
"Id": "45b0a22b-c9db-4271-96b3-23ac49debbf3",
"InsufficientDataHealthStatus": "",
"InvertHealthcheck": false,
"IpAddress": "",
"MeasureLatency": false,
"Port": 443,
"ReferenceName": null,
"Regions": [],
"RequestInterval": 30,
"ResourcePath": "/",
"SearchString": "MoAdiB",
"Tags": null,
"Type": "HTTPS_STR_MATCH"
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
{
"version": 4,
"terraform_version": "0.14.5",
"serial": 72,
"lineage": "30081725-54a2-ce02-6ff5-45c4d961c652",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_route53_health_check",
"name": "http",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"child_health_threshold": 0,
"child_healthchecks": null,
"cloudwatch_alarm_name": null,
"cloudwatch_alarm_region": null,
"disabled": false,
"enable_sni": false,
"failure_threshold": 5,
"fqdn": "moadib.net",
"id": "70994c67-b616-4caa-89bc-557153b65845",
"insufficient_data_health_status": "",
"invert_healthcheck": false,
"ip_address": "",
"measure_latency": false,
"port": 80,
"reference_name": null,
"regions": null,
"request_interval": 30,
"resource_path": "/",
"search_string": "",
"tags": {
"Name": "tf-test-health-check"
},
"type": "HTTP"
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
},
{
"mode": "managed",
"type": "aws_route53_health_check",
"name": "https",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"child_health_threshold": 0,
"child_healthchecks": null,
"cloudwatch_alarm_name": null,
"cloudwatch_alarm_region": null,
"disabled": false,
"enable_sni": true,
"failure_threshold": 5,
"fqdn": "moadib.net",
"id": "45b0a22b-c9db-4271-96b3-23ac49debbf3",
"insufficient_data_health_status": "",
"invert_healthcheck": false,
"ip_address": "",
"measure_latency": false,
"port": 443,
"reference_name": null,
"regions": null,
"request_interval": 30,
"resource_path": "/",
"search_string": "MoAdiB",
"tags": null,
"type": "HTTPS_STR_MATCH"
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
}
]
}

View File

@ -10,6 +10,23 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type invalidRouteAlert struct {
message string
}
func newInvalidRouteAlert(awsRouteTableResourceType, tableId string) *invalidRouteAlert {
message := fmt.Sprintf("Skipped invalid route found in state for %s.%s", awsRouteTableResourceType, tableId)
return &invalidRouteAlert{message}
}
func (i *invalidRouteAlert) Message() string {
return i.message
}
func (i *invalidRouteAlert) ShouldIgnoreResource() bool {
return false
}
// Explodes routes found in aws_default_route_table.route and aws_route_table.route to dedicated resources // Explodes routes found in aws_default_route_table.route and aws_route_table.route to dedicated resources
type AwsRouteTableExpander struct { type AwsRouteTableExpander struct {
alerter alerter.AlerterInterface alerter alerter.AlerterInterface
@ -60,9 +77,7 @@ func (m *AwsRouteTableExpander) handleTable(table *aws.AwsRouteTable, results *[
for _, route := range *table.Route { for _, route := range *table.Route {
routeId, err := aws.CalculateRouteID(&table.Id, route.CidrBlock, route.Ipv6CidrBlock) routeId, err := aws.CalculateRouteID(&table.Id, route.CidrBlock, route.Ipv6CidrBlock)
if err != nil { if err != nil {
m.alerter.SendAlert(aws.AwsRouteTableResourceType, alerter.Alert{ m.alerter.SendAlert(aws.AwsRouteTableResourceType, newInvalidRouteAlert(aws.AwsRouteTableResourceType, table.Id))
Message: fmt.Sprintf("Skipped invalid route found in state for %s.%s", aws.AwsRouteTableResourceType, table.Id),
})
continue continue
} }
newRouteFromTable := &aws.AwsRoute{ newRouteFromTable := &aws.AwsRoute{
@ -107,9 +122,7 @@ func (m *AwsRouteTableExpander) handleDefaultTable(table *aws.AwsDefaultRouteTab
for _, route := range *table.Route { for _, route := range *table.Route {
routeId, err := aws.CalculateRouteID(&table.Id, route.CidrBlock, route.Ipv6CidrBlock) routeId, err := aws.CalculateRouteID(&table.Id, route.CidrBlock, route.Ipv6CidrBlock)
if err != nil { if err != nil {
m.alerter.SendAlert(aws.AwsDefaultRouteTableResourceType, alerter.Alert{ m.alerter.SendAlert(aws.AwsDefaultRouteTableResourceType, newInvalidRouteAlert(aws.AwsDefaultRouteTableResourceType, table.Id))
Message: fmt.Sprintf("Skipped invalid route found in state for %s.%s", aws.AwsDefaultRouteTableResourceType, table.Id),
})
continue continue
} }
newRouteFromTable := &aws.AwsRoute{ newRouteFromTable := &aws.AwsRoute{

View File

@ -7,7 +7,6 @@ import (
awssdk "github.com/aws/aws-sdk-go/aws" awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/cloudskiff/driftctl/mocks" "github.com/cloudskiff/driftctl/mocks"
"github.com/cloudskiff/driftctl/pkg/alerter"
"github.com/cloudskiff/driftctl/pkg/resource" "github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws" "github.com/cloudskiff/driftctl/pkg/resource/aws"
resource2 "github.com/cloudskiff/driftctl/test/resource" resource2 "github.com/cloudskiff/driftctl/test/resource"
@ -215,12 +214,12 @@ func TestAwsRouteTableExpander_Execute(t *testing.T) {
func TestAwsRouteTableExpander_ExecuteWithInvalidRoutes(t *testing.T) { func TestAwsRouteTableExpander_ExecuteWithInvalidRoutes(t *testing.T) {
mockedAlerter := &mocks.AlerterInterface{} mockedAlerter := &mocks.AlerterInterface{}
mockedAlerter.On("SendAlert", aws.AwsRouteTableResourceType, alerter.Alert{ mockedAlerter.On("SendAlert", aws.AwsRouteTableResourceType, newInvalidRouteAlert(
Message: "Skipped invalid route found in state for aws_route_table.table_from_state", "aws_route_table", "table_from_state",
}) ))
mockedAlerter.On("SendAlert", aws.AwsDefaultRouteTableResourceType, alerter.Alert{ mockedAlerter.On("SendAlert", aws.AwsDefaultRouteTableResourceType, newInvalidRouteAlert(
Message: "Skipped invalid route found in state for aws_default_route_table.default_table_from_state", "aws_default_route_table", "default_table_from_state",
}) ))
input := []resource.Resource{ input := []resource.Resource{
&aws.AwsRouteTable{ &aws.AwsRouteTable{

View File

@ -0,0 +1,63 @@
package aws
import (
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
)
type CloudfrontDistributionSupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
client repository.CloudfrontRepository
runner *terraform.ParallelResourceReader
}
func NewCloudfrontDistributionSupplier(provider *AWSTerraformProvider) *CloudfrontDistributionSupplier {
return &CloudfrontDistributionSupplier{
provider,
awsdeserializer.NewCloudfrontDistributionDeserializer(),
repository.NewCloudfrontClient(provider.session),
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s CloudfrontDistributionSupplier) Resources() ([]resource.Resource, error) {
distributions, err := s.client.ListAllDistributions()
if err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, aws.AwsCloudfrontDistributionResourceType)
}
for _, distribution := range distributions {
d := *distribution
s.runner.Run(func() (cty.Value, error) {
return s.readCloudfrontDistribution(d)
})
}
resources, err := s.runner.Wait()
if err != nil {
return nil, err
}
return s.deserializer.Deserialize(resources)
}
func (s CloudfrontDistributionSupplier) readCloudfrontDistribution(distribution cloudfront.DistributionSummary) (cty.Value, error) {
val, err := s.reader.ReadResource(terraform.ReadResourceArgs{
ID: *distribution.Id,
Ty: aws.AwsCloudfrontDistributionResourceType,
})
if err != nil {
logrus.Error(err)
return cty.NilVal, err
}
return *val, nil
}

View File

@ -0,0 +1,94 @@
package aws
import (
"context"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/aws/aws-sdk-go/aws/awserr"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/cloudskiff/driftctl/mocks"
"github.com/cloudskiff/driftctl/pkg/parallel"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
"github.com/cloudskiff/driftctl/pkg/resource"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
testmocks "github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestCloudfrontDistributionSupplier_Resources(t *testing.T) {
cases := []struct {
test string
dirName string
mocks func(client *mocks.CloudfrontRepository)
err error
}{
{
test: "no cloudfront distribution",
dirName: "cloudfront_distribution_empty",
mocks: func(client *mocks.CloudfrontRepository) {
client.On("ListAllDistributions").Return([]*cloudfront.DistributionSummary{}, nil)
},
err: nil,
},
{
test: "one cloudfront distribution",
dirName: "cloudfront_distribution_one",
mocks: func(client *mocks.CloudfrontRepository) {
client.On("ListAllDistributions").Return([]*cloudfront.DistributionSummary{
{Id: aws.String("E1M9CNS0XSHI19")},
}, nil)
},
err: nil,
},
{
test: "cannot list cloudfront distributions",
dirName: "cloudfront_distribution_empty",
mocks: func(client *mocks.CloudfrontRepository) {
client.On("ListAllDistributions").Return(nil, awserr.NewRequestFailure(nil, 403, ""))
},
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsCloudfrontDistributionResourceType),
},
}
for _, c := range cases {
shouldUpdate := c.dirName == *goldenfile.Update
providerLibrary := terraform.NewProviderLibrary()
supplierLibrary := resource.NewSupplierLibrary()
if shouldUpdate {
provider, err := InitTestAwsProvider(providerLibrary)
if err != nil {
t.Fatal(err)
}
supplierLibrary.AddSupplier(NewCloudfrontDistributionSupplier(provider))
}
t.Run(c.test, func(tt *testing.T) {
fakeCloudfront := mocks.CloudfrontRepository{}
c.mocks(&fakeCloudfront)
provider := testmocks.NewMockedGoldenTFProvider(c.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
cloudfrontDistributionDeserializer := awsdeserializer.NewCloudfrontDistributionDeserializer()
s := &CloudfrontDistributionSupplier{
provider,
cloudfrontDistributionDeserializer,
&fakeCloudfront,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()
assert.Equal(tt, c.err, err)
mock.AssertExpectationsForObjects(tt)
deserializers := []deserializer.CTYDeserializer{cloudfrontDistributionDeserializer}
test.CtyTestDiffMixed(got, c.dirName, provider, deserializers, shouldUpdate, tt)
})
}
}

View File

@ -67,6 +67,8 @@ func Init(alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary,
supplierLibrary.AddSupplier(NewSNSTopicPolicySupplier(provider)) supplierLibrary.AddSupplier(NewSNSTopicPolicySupplier(provider))
supplierLibrary.AddSupplier(NewSNSTopicSubscriptionSupplier(provider)) supplierLibrary.AddSupplier(NewSNSTopicSubscriptionSupplier(provider))
supplierLibrary.AddSupplier(NewDynamoDBTableSupplier(provider)) supplierLibrary.AddSupplier(NewDynamoDBTableSupplier(provider))
supplierLibrary.AddSupplier(NewRoute53HealthCheckSupplier(provider))
supplierLibrary.AddSupplier(NewCloudfrontDistributionSupplier(provider))
return nil return nil
} }

View File

@ -0,0 +1,38 @@
package repository
import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/aws/aws-sdk-go/service/cloudfront/cloudfrontiface"
)
type CloudfrontRepository interface {
ListAllDistributions() ([]*cloudfront.DistributionSummary, error)
}
type cloudfrontRepository struct {
client cloudfrontiface.CloudFrontAPI
}
func NewCloudfrontClient(session *session.Session) *cloudfrontRepository {
return &cloudfrontRepository{
cloudfront.New(session),
}
}
func (r *cloudfrontRepository) ListAllDistributions() ([]*cloudfront.DistributionSummary, error) {
var distributions []*cloudfront.DistributionSummary
input := cloudfront.ListDistributionsInput{}
err := r.client.ListDistributionsPages(&input,
func(resp *cloudfront.ListDistributionsOutput, lastPage bool) bool {
if resp.DistributionList != nil {
distributions = append(distributions, resp.DistributionList.Items...)
}
return !lastPage
},
)
if err != nil {
return nil, err
}
return distributions, nil
}

View File

@ -0,0 +1,81 @@
package repository
import (
"strings"
"testing"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/aws/aws-sdk-go/aws"
"github.com/stretchr/testify/mock"
"github.com/cloudskiff/driftctl/mocks"
"github.com/r3labs/diff/v2"
"github.com/stretchr/testify/assert"
)
func Test_cloudfrontRepository_ListAllDistributions(t *testing.T) {
tests := []struct {
name string
mocks func(client *mocks.CloudfrontClient)
want []*cloudfront.DistributionSummary
wantErr error
}{
{
name: "list multiple distributions",
mocks: func(client *mocks.CloudfrontClient) {
client.On("ListDistributionsPages",
&cloudfront.ListDistributionsInput{},
mock.MatchedBy(func(callback func(res *cloudfront.ListDistributionsOutput, lastPage bool) bool) bool {
callback(&cloudfront.ListDistributionsOutput{
DistributionList: &cloudfront.DistributionList{
Items: []*cloudfront.DistributionSummary{
{Id: aws.String("distribution1")},
{Id: aws.String("distribution2")},
{Id: aws.String("distribution3")},
},
},
}, false)
callback(&cloudfront.ListDistributionsOutput{
DistributionList: &cloudfront.DistributionList{
Items: []*cloudfront.DistributionSummary{
{Id: aws.String("distribution4")},
{Id: aws.String("distribution5")},
{Id: aws.String("distribution6")},
},
},
}, true)
return true
})).Return(nil)
},
want: []*cloudfront.DistributionSummary{
{Id: aws.String("distribution1")},
{Id: aws.String("distribution2")},
{Id: aws.String("distribution3")},
{Id: aws.String("distribution4")},
{Id: aws.String("distribution5")},
{Id: aws.String("distribution6")},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &mocks.CloudfrontClient{}
tt.mocks(client)
r := &cloudfrontRepository{
client: client,
}
got, err := r.ListAllDistributions()
assert.Equal(t, tt.wantErr, err)
changelog, err := diff.Diff(got, tt.want)
assert.Nil(t, err)
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s: %s -> %s", strings.Join(change.Path, "."), change.From, change.To)
}
t.Fail()
}
})
}
}

View File

@ -0,0 +1,34 @@
package repository
import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/service/route53/route53iface"
)
type Route53Repository interface {
ListAllHealthChecks() ([]*route53.HealthCheck, error)
}
type route53Repository struct {
client route53iface.Route53API
}
func NewRoute53Repository(session *session.Session) *route53Repository {
return &route53Repository{
route53.New(session),
}
}
func (r *route53Repository) ListAllHealthChecks() ([]*route53.HealthCheck, error) {
var tables []*route53.HealthCheck
input := &route53.ListHealthChecksInput{}
err := r.client.ListHealthChecksPages(input, func(res *route53.ListHealthChecksOutput, lastPage bool) bool {
tables = append(tables, res.HealthChecks...)
return !lastPage
})
if err != nil {
return nil, err
}
return tables, nil
}

View File

@ -0,0 +1,78 @@
package repository
import (
"strings"
"testing"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/aws"
"github.com/stretchr/testify/mock"
"github.com/cloudskiff/driftctl/mocks"
"github.com/r3labs/diff/v2"
"github.com/stretchr/testify/assert"
)
func Test_route53Repository_ListAllHealthChecks(t *testing.T) {
tests := []struct {
name string
mocks func(client *mocks.Route53Client)
want []*route53.HealthCheck
wantErr error
}{
{
name: "List with 2 pages",
mocks: func(client *mocks.Route53Client) {
client.On("ListHealthChecksPages",
&route53.ListHealthChecksInput{},
mock.MatchedBy(func(callback func(res *route53.ListHealthChecksOutput, lastPage bool) bool) bool {
callback(&route53.ListHealthChecksOutput{
HealthChecks: []*route53.HealthCheck{
{Id: aws.String("1")},
{Id: aws.String("2")},
{Id: aws.String("3")},
},
}, false)
callback(&route53.ListHealthChecksOutput{
HealthChecks: []*route53.HealthCheck{
{Id: aws.String("4")},
{Id: aws.String("5")},
{Id: aws.String("6")},
},
}, true)
return true
})).Return(nil)
},
want: []*route53.HealthCheck{
{Id: aws.String("1")},
{Id: aws.String("2")},
{Id: aws.String("3")},
{Id: aws.String("4")},
{Id: aws.String("5")},
{Id: aws.String("6")},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &mocks.Route53Client{}
tt.mocks(client)
r := &route53Repository{
client: client,
}
got, err := r.ListAllHealthChecks()
assert.Equal(t, tt.wantErr, err)
changelog, err := diff.Diff(got, tt.want)
assert.Nil(t, err)
if len(changelog) > 0 {
for _, change := range changelog {
t.Errorf("%s: %s -> %s", strings.Join(change.Path, "."), change.From, change.To)
}
t.Fail()
}
})
}
}

View File

@ -0,0 +1,64 @@
package aws
import (
"github.com/aws/aws-sdk-go/service/route53"
"github.com/cloudskiff/driftctl/pkg/remote/aws/repository"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
)
type Route53HealthCheckSupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
client repository.Route53Repository
runner *terraform.ParallelResourceReader
}
func NewRoute53HealthCheckSupplier(provider *AWSTerraformProvider) *Route53HealthCheckSupplier {
return &Route53HealthCheckSupplier{
provider,
awsdeserializer.NewRoute53HealthCheckDeserializer(),
repository.NewRoute53Repository(provider.session),
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s Route53HealthCheckSupplier) Resources() ([]resource.Resource, error) {
healthChecks, err := s.client.ListAllHealthChecks()
if err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, aws.AwsRoute53HealthCheckResourceType)
}
for _, healthCheck := range healthChecks {
healthCheck := healthCheck
s.runner.Run(func() (cty.Value, error) {
return s.readHealthCheck(healthCheck)
})
}
retrieve, err := s.runner.Wait()
if err != nil {
return nil, err
}
return s.deserializer.Deserialize(retrieve)
}
func (s Route53HealthCheckSupplier) readHealthCheck(healthCheck *route53.HealthCheck) (cty.Value, error) {
val, err := s.reader.ReadResource(terraform.ReadResourceArgs{
ID: *healthCheck.Id,
Ty: aws.AwsRoute53HealthCheckResourceType,
})
if err != nil {
logrus.Error(err)
return cty.NilVal, err
}
return *val, nil
}

View File

@ -0,0 +1,93 @@
package aws
import (
"context"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/cloudskiff/driftctl/mocks"
"github.com/cloudskiff/driftctl/pkg/parallel"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/cloudskiff/driftctl/test"
"github.com/cloudskiff/driftctl/test/goldenfile"
testmocks "github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/pkg/terraform"
)
func TestRoute53HealthCheckSupplier_Resources(t *testing.T) {
cases := []struct {
test string
dirName string
mocks func(client *mocks.Route53Repository)
err error
}{
{
test: "no health check",
dirName: "route53_health_check_empty",
mocks: func(client *mocks.Route53Repository) {
client.On("ListAllHealthChecks").Return([]*route53.HealthCheck{}, nil)
},
err: nil,
},
{
test: "Multiple health check",
dirName: "route53_health_check_multiple",
mocks: func(client *mocks.Route53Repository) {
client.On("ListAllHealthChecks").Return([]*route53.HealthCheck{
{Id: aws.String("7001a9df-ded4-4802-9909-668eb80b972b")},
{Id: aws.String("84fc318a-2e0d-41d6-b638-280e2f0f4e26")},
}, nil)
},
err: nil,
},
{
test: "cannot list health check",
dirName: "route53_health_check_empty",
mocks: func(client *mocks.Route53Repository) {
client.On("ListAllHealthChecks").Return(nil, awserr.NewRequestFailure(nil, 403, ""))
},
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsRoute53HealthCheckResourceType),
},
}
for _, c := range cases {
shouldUpdate := c.dirName == *goldenfile.Update
providerLibrary := terraform.NewProviderLibrary()
supplierLibrary := resource.NewSupplierLibrary()
if shouldUpdate {
provider, err := InitTestAwsProvider(providerLibrary)
if err != nil {
t.Fatal(err)
}
supplierLibrary.AddSupplier(NewRoute53HealthCheckSupplier(provider))
}
t.Run(c.test, func(tt *testing.T) {
fakeClient := mocks.Route53Repository{}
c.mocks(&fakeClient)
provider := testmocks.NewMockedGoldenTFProvider(c.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
deserializer := awsdeserializer.NewRoute53HealthCheckDeserializer()
s := &Route53HealthCheckSupplier{
provider,
deserializer,
&fakeClient,
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
}
got, err := s.Resources()
assert.Equal(tt, c.err, err)
mock.AssertExpectationsForObjects(tt)
test.CtyTestDiff(got, c.dirName, provider, deserializer, shouldUpdate, tt)
})
}
}

View File

@ -0,0 +1 @@
[]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
{
"Typ": "WyJvYmplY3QiLHsiYWxpYXNlcyI6WyJzZXQiLCJzdHJpbmciXSwiYXJuIjoic3RyaW5nIiwiY2FsbGVyX3JlZmVyZW5jZSI6InN0cmluZyIsImNvbW1lbnQiOiJzdHJpbmciLCJjdXN0b21fZXJyb3JfcmVzcG9uc2UiOlsic2V0IixbIm9iamVjdCIseyJlcnJvcl9jYWNoaW5nX21pbl90dGwiOiJudW1iZXIiLCJlcnJvcl9jb2RlIjoibnVtYmVyIiwicmVzcG9uc2VfY29kZSI6Im51bWJlciIsInJlc3BvbnNlX3BhZ2VfcGF0aCI6InN0cmluZyJ9XV0sImRlZmF1bHRfY2FjaGVfYmVoYXZpb3IiOlsibGlzdCIsWyJvYmplY3QiLHsiYWxsb3dlZF9tZXRob2RzIjpbInNldCIsInN0cmluZyJdLCJjYWNoZWRfbWV0aG9kcyI6WyJzZXQiLCJzdHJpbmciXSwiY29tcHJlc3MiOiJib29sIiwiZGVmYXVsdF90dGwiOiJudW1iZXIiLCJmaWVsZF9sZXZlbF9lbmNyeXB0aW9uX2lkIjoic3RyaW5nIiwiZm9yd2FyZGVkX3ZhbHVlcyI6WyJsaXN0IixbIm9iamVjdCIseyJjb29raWVzIjpbImxpc3QiLFsib2JqZWN0Iix7ImZvcndhcmQiOiJzdHJpbmciLCJ3aGl0ZWxpc3RlZF9uYW1lcyI6WyJzZXQiLCJzdHJpbmciXX1dXSwiaGVhZGVycyI6WyJzZXQiLCJzdHJpbmciXSwicXVlcnlfc3RyaW5nIjoiYm9vbCIsInF1ZXJ5X3N0cmluZ19jYWNoZV9rZXlzIjpbImxpc3QiLCJzdHJpbmciXX1dXSwibGFtYmRhX2Z1bmN0aW9uX2Fzc29jaWF0aW9uIjpbInNldCIsWyJvYmplY3QiLHsiZXZlbnRfdHlwZSI6InN0cmluZyIsImluY2x1ZGVfYm9keSI6ImJvb2wiLCJsYW1iZGFfYXJuIjoic3RyaW5nIn1dXSwibWF4X3R0bCI6Im51bWJlciIsIm1pbl90dGwiOiJudW1iZXIiLCJzbW9vdGhfc3RyZWFtaW5nIjoiYm9vbCIsInRhcmdldF9vcmlnaW5faWQiOiJzdHJpbmciLCJ0cnVzdGVkX3NpZ25lcnMiOlsibGlzdCIsInN0cmluZyJdLCJ2aWV3ZXJfcHJvdG9jb2xfcG9saWN5Ijoic3RyaW5nIn1dXSwiZGVmYXVsdF9yb290X29iamVjdCI6InN0cmluZyIsImRvbWFpbl9uYW1lIjoic3RyaW5nIiwiZW5hYmxlZCI6ImJvb2wiLCJldGFnIjoic3RyaW5nIiwiaG9zdGVkX3pvbmVfaWQiOiJzdHJpbmciLCJodHRwX3ZlcnNpb24iOiJzdHJpbmciLCJpZCI6InN0cmluZyIsImluX3Byb2dyZXNzX3ZhbGlkYXRpb25fYmF0Y2hlcyI6Im51bWJlciIsImlzX2lwdjZfZW5hYmxlZCI6ImJvb2wiLCJsYXN0X21vZGlmaWVkX3RpbWUiOiJzdHJpbmciLCJsb2dnaW5nX2NvbmZpZyI6WyJsaXN0IixbIm9iamVjdCIseyJidWNrZXQiOiJzdHJpbmciLCJpbmNsdWRlX2Nvb2tpZXMiOiJib29sIiwicHJlZml4Ijoic3RyaW5nIn1dXSwib3JkZXJlZF9jYWNoZV9iZWhhdmlvciI6WyJsaXN0IixbIm9iamVjdCIseyJhbGxvd2VkX21ldGhvZHMiOlsic2V0Iiwic3RyaW5nIl0sImNhY2hlZF9tZXRob2RzIjpbInNldCIsInN0cmluZyJdLCJjb21wcmVzcyI6ImJvb2wiLCJkZWZhdWx0X3R0bCI6Im51bWJlciIsImZpZWxkX2xldmVsX2VuY3J5cHRpb25faWQiOiJzdHJpbmciLCJmb3J3YXJkZWRfdmFsdWVzIjpbImxpc3QiLFsib2JqZWN0Iix7ImNvb2tpZXMiOlsibGlzdCIsWyJvYmplY3QiLHsiZm9yd2FyZCI6InN0cmluZyIsIndoaXRlbGlzdGVkX25hbWVzIjpbInNldCIsInN0cmluZyJdfV1dLCJoZWFkZXJzIjpbInNldCIsInN0cmluZyJdLCJxdWVyeV9zdHJpbmciOiJib29sIiwicXVlcnlfc3RyaW5nX2NhY2hlX2tleXMiOlsibGlzdCIsInN0cmluZyJdfV1dLCJsYW1iZGFfZnVuY3Rpb25fYXNzb2NpYXRpb24iOlsic2V0IixbIm9iamVjdCIseyJldmVudF90eXBlIjoic3RyaW5nIiwiaW5jbHVkZV9ib2R5IjoiYm9vbCIsImxhbWJkYV9hcm4iOiJzdHJpbmcifV1dLCJtYXhfdHRsIjoibnVtYmVyIiwibWluX3R0bCI6Im51bWJlciIsInBhdGhfcGF0dGVybiI6InN0cmluZyIsInNtb290aF9zdHJlYW1pbmciOiJib29sIiwidGFyZ2V0X29yaWdpbl9pZCI6InN0cmluZyIsInRydXN0ZWRfc2lnbmVycyI6WyJsaXN0Iiwic3RyaW5nIl0sInZpZXdlcl9wcm90b2NvbF9wb2xpY3kiOiJzdHJpbmcifV1dLCJvcmlnaW4iOlsic2V0IixbIm9iamVjdCIseyJjdXN0b21faGVhZGVyIjpbInNldCIsWyJvYmplY3QiLHsibmFtZSI6InN0cmluZyIsInZhbHVlIjoic3RyaW5nIn1dXSwiY3VzdG9tX29yaWdpbl9jb25maWciOlsibGlzdCIsWyJvYmplY3QiLHsiaHR0cF9wb3J0IjoibnVtYmVyIiwiaHR0cHNfcG9ydCI6Im51bWJlciIsIm9yaWdpbl9rZWVwYWxpdmVfdGltZW91dCI6Im51bWJlciIsIm9yaWdpbl9wcm90b2NvbF9wb2xpY3kiOiJzdHJpbmciLCJvcmlnaW5fcmVhZF90aW1lb3V0IjoibnVtYmVyIiwib3JpZ2luX3NzbF9wcm90b2NvbHMiOlsic2V0Iiwic3RyaW5nIl19XV0sImRvbWFpbl9uYW1lIjoic3RyaW5nIiwib3JpZ2luX2lkIjoic3RyaW5nIiwib3JpZ2luX3BhdGgiOiJzdHJpbmciLCJzM19vcmlnaW5fY29uZmlnIjpbImxpc3QiLFsib2JqZWN0Iix7Im9yaWdpbl9hY2Nlc3NfaWRlbnRpdHkiOiJzdHJpbmcifV1dfV1dLCJvcmlnaW5fZ3JvdXAiOlsic2V0IixbIm9iamVjdCIseyJmYWlsb3Zlcl9jcml0ZXJpYSI6WyJsaXN0IixbIm9iamVjdCIseyJzdGF0dXNfY29kZXMiOlsic2V0IiwibnVtYmVyIl19XV0sIm1lbWJlciI6WyJsaXN0IixbIm9iamVjdCIseyJvcmlnaW5faWQiOiJzdHJpbmcifV1dLCJvcmlnaW5faWQiOiJzdHJpbmcifV1dLCJwcmljZV9jbGFzcyI6InN0cmluZyIsInJlc3RyaWN0aW9ucyI6WyJsaXN0IixbIm9iamVjdCIseyJnZW9fcmVzdHJpY3Rpb24iOlsibGlzdCIsWyJvYmplY3QiLHsibG9jYXRpb25zIjpbInNldCIsInN0cmluZyJdLCJyZXN0cmljdGlvbl90eXBlIjoic3RyaW5nIn1dXX1dXSwicmV0YWluX29uX2RlbGV0ZSI6ImJvb2wiLCJzdGF0dXMiOiJzdHJpbmciLCJ0YWdzIjpbIm1hcCIsInN0cmluZyJdLCJ0cnVzdGVkX3NpZ25lcnMiOlsibGlzdCIsWyJvYmplY3QiLHsiZW5hYmxlZCI6ImJvb2wiLCJpdGVtcyI6WyJsaXN0IixbIm9iamVjdCIseyJhd3NfYWNjb3VudF9udW1iZXIiOiJzdHJpbmciLCJrZXlfcGFpcl9pZHMiOlsic2V0Iiwic3RyaW5nIl19XV19XV0sInZpZXdlcl9jZXJ0aWZpY2F0ZSI6WyJsaXN0IixbIm9iamVjdCIseyJhY21fY2VydGlmaWNhdGVfYXJuIjoic3RyaW5nIiwiY2xvdWRmcm9udF9kZWZhdWx0X2NlcnRpZmljYXRlIjoiYm9vbCIsImlhbV9jZXJ0aWZpY2F0ZV9pZCI6InN0cmluZyIsIm1pbmltdW1fcHJvdG9jb2xfdmVyc2lvbiI6InN0cmluZyIsInNzbF9zdXBwb3J0X21ldGhvZCI6InN0cmluZyJ9XV0sIndhaXRfZm9yX2RlcGxveW1lbnQiOiJib29sIiwid2ViX2FjbF9pZCI6InN0cmluZyJ9XQ==",
"Val": "eyJhbGlhc2VzIjpbXSwiYXJuIjoiYXJuOmF3czpjbG91ZGZyb250OjowNDcwODEwMTQzMTU6ZGlzdHJpYnV0aW9uL0UxTTlDTlMwWFNISTE5IiwiY2FsbGVyX3JlZmVyZW5jZSI6InRlcnJhZm9ybS0yMDIxMDIxNjEwMTczNDc5MjkwMDAwMDAwMSIsImNvbW1lbnQiOm51bGwsImN1c3RvbV9lcnJvcl9yZXNwb25zZSI6W10sImRlZmF1bHRfY2FjaGVfYmVoYXZpb3IiOlt7ImFsbG93ZWRfbWV0aG9kcyI6WyJHRVQiLCJIRUFEIl0sImNhY2hlZF9tZXRob2RzIjpbIkdFVCIsIkhFQUQiXSwiY29tcHJlc3MiOmZhbHNlLCJkZWZhdWx0X3R0bCI6ODY0MDAsImZpZWxkX2xldmVsX2VuY3J5cHRpb25faWQiOiIiLCJmb3J3YXJkZWRfdmFsdWVzIjpbeyJjb29raWVzIjpbeyJmb3J3YXJkIjoibm9uZSIsIndoaXRlbGlzdGVkX25hbWVzIjpbXX1dLCJoZWFkZXJzIjpbXSwicXVlcnlfc3RyaW5nIjpmYWxzZSwicXVlcnlfc3RyaW5nX2NhY2hlX2tleXMiOltdfV0sImxhbWJkYV9mdW5jdGlvbl9hc3NvY2lhdGlvbiI6W10sIm1heF90dGwiOjMxNTM2MDAwLCJtaW5fdHRsIjowLCJzbW9vdGhfc3RyZWFtaW5nIjpmYWxzZSwidGFyZ2V0X29yaWdpbl9pZCI6IlMzLWZvby1jbG91ZGZyb250IiwidHJ1c3RlZF9zaWduZXJzIjpbXSwidmlld2VyX3Byb3RvY29sX3BvbGljeSI6ImFsbG93LWFsbCJ9XSwiZGVmYXVsdF9yb290X29iamVjdCI6IiIsImRvbWFpbl9uYW1lIjoiZDFnMGR3MGkxd3ZsZ2QuY2xvdWRmcm9udC5uZXQiLCJlbmFibGVkIjpmYWxzZSwiZXRhZyI6IkUyQ0tCQU5MWFVQV0dRIiwiaG9zdGVkX3pvbmVfaWQiOiJaMkZEVE5EQVRBUVlXMiIsImh0dHBfdmVyc2lvbiI6Imh0dHAyIiwiaWQiOiJFMU05Q05TMFhTSEkxOSIsImluX3Byb2dyZXNzX3ZhbGlkYXRpb25fYmF0Y2hlcyI6MCwiaXNfaXB2Nl9lbmFibGVkIjpmYWxzZSwibGFzdF9tb2RpZmllZF90aW1lIjoiMjAyMS0wMi0xNiAxMDoxNzozNS40MDQgKzAwMDAgVVRDIiwibG9nZ2luZ19jb25maWciOltdLCJvcmRlcmVkX2NhY2hlX2JlaGF2aW9yIjpbXSwib3JpZ2luIjpbeyJjdXN0b21faGVhZGVyIjpbXSwiY3VzdG9tX29yaWdpbl9jb25maWciOltdLCJkb21haW5fbmFtZSI6ImZvby1jbG91ZGZyb250LnMzLmV1LXdlc3QtMy5hbWF6b25hd3MuY29tIiwib3JpZ2luX2lkIjoiUzMtZm9vLWNsb3VkZnJvbnQiLCJvcmlnaW5fcGF0aCI6IiIsInMzX29yaWdpbl9jb25maWciOltdfV0sIm9yaWdpbl9ncm91cCI6bnVsbCwicHJpY2VfY2xhc3MiOiJQcmljZUNsYXNzX0FsbCIsInJlc3RyaWN0aW9ucyI6W3siZ2VvX3Jlc3RyaWN0aW9uIjpbeyJsb2NhdGlvbnMiOltdLCJyZXN0cmljdGlvbl90eXBlIjoibm9uZSJ9XX1dLCJyZXRhaW5fb25fZGVsZXRlIjpudWxsLCJzdGF0dXMiOiJEZXBsb3llZCIsInRhZ3MiOnt9LCJ0cnVzdGVkX3NpZ25lcnMiOlt7ImVuYWJsZWQiOmZhbHNlLCJpdGVtcyI6W119XSwidmlld2VyX2NlcnRpZmljYXRlIjpbeyJhY21fY2VydGlmaWNhdGVfYXJuIjoiIiwiY2xvdWRmcm9udF9kZWZhdWx0X2NlcnRpZmljYXRlIjp0cnVlLCJpYW1fY2VydGlmaWNhdGVfaWQiOiIiLCJtaW5pbXVtX3Byb3RvY29sX3ZlcnNpb24iOiJUTFN2MSIsInNzbF9zdXBwb3J0X21ldGhvZCI6IiJ9XSwid2FpdF9mb3JfZGVwbG95bWVudCI6bnVsbCwid2ViX2FjbF9pZCI6IiJ9",
"Err": null
}

View File

@ -0,0 +1,98 @@
[
{
"aliases": [],
"arn": "arn:aws:cloudfront::047081014315:distribution/E1M9CNS0XSHI19",
"caller_reference": "terraform-20210216101734792900000001",
"comment": null,
"custom_error_response": [],
"default_cache_behavior": [
{
"allowed_methods": [
"GET",
"HEAD"
],
"cached_methods": [
"GET",
"HEAD"
],
"compress": false,
"default_ttl": 86400,
"field_level_encryption_id": "",
"forwarded_values": [
{
"cookies": [
{
"forward": "none",
"whitelisted_names": []
}
],
"headers": [],
"query_string": false,
"query_string_cache_keys": []
}
],
"lambda_function_association": [],
"max_ttl": 31536000,
"min_ttl": 0,
"smooth_streaming": false,
"target_origin_id": "S3-foo-cloudfront",
"trusted_signers": [],
"viewer_protocol_policy": "allow-all"
}
],
"default_root_object": "",
"domain_name": "d1g0dw0i1wvlgd.cloudfront.net",
"enabled": false,
"etag": "E2CKBANLXUPWGQ",
"hosted_zone_id": "Z2FDTNDATAQYW2",
"http_version": "http2",
"id": "E1M9CNS0XSHI19",
"in_progress_validation_batches": 0,
"is_ipv6_enabled": false,
"last_modified_time": "2021-02-16 10:17:35.404 +0000 UTC",
"logging_config": [],
"ordered_cache_behavior": [],
"origin": [
{
"custom_header": [],
"custom_origin_config": [],
"domain_name": "foo-cloudfront.s3.eu-west-3.amazonaws.com",
"origin_id": "S3-foo-cloudfront",
"origin_path": "",
"s3_origin_config": []
}
],
"origin_group": null,
"price_class": "PriceClass_All",
"restrictions": [
{
"geo_restriction": [
{
"locations": [],
"restriction_type": "none"
}
]
}
],
"retain_on_delete": null,
"status": "Deployed",
"tags": {},
"trusted_signers": [
{
"enabled": false,
"items": []
}
],
"viewer_certificate": [
{
"acm_certificate_arn": "",
"cloudfront_default_certificate": true,
"iam_certificate_id": "",
"minimum_protocol_version": "TLSv1",
"ssl_support_method": ""
}
],
"wait_for_deployment": null,
"web_acl_id": ""
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
provider "aws" {
region = "us-east-1"
}
terraform {
required_providers {
aws = "3.19.0"
}
}
resource "aws_s3_bucket" "foo_cloudfront" {
bucket = "foo-cloudfront"
acl = "private"
}
locals {
s3_origin_id = "S3-foo-cloudfront"
}
resource "aws_cloudfront_distribution" "foo_distribution" {
enabled = false
origin {
domain_name = aws_s3_bucket.foo_cloudfront.bucket_regional_domain_name
origin_id = local.s3_origin_id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id
viewer_protocol_policy = "allow-all"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}

View File

@ -0,0 +1 @@
[]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
{
"Typ": "WyJvYmplY3QiLHsiY2hpbGRfaGVhbHRoX3RocmVzaG9sZCI6Im51bWJlciIsImNoaWxkX2hlYWx0aGNoZWNrcyI6WyJzZXQiLCJzdHJpbmciXSwiY2xvdWR3YXRjaF9hbGFybV9uYW1lIjoic3RyaW5nIiwiY2xvdWR3YXRjaF9hbGFybV9yZWdpb24iOiJzdHJpbmciLCJkaXNhYmxlZCI6ImJvb2wiLCJlbmFibGVfc25pIjoiYm9vbCIsImZhaWx1cmVfdGhyZXNob2xkIjoibnVtYmVyIiwiZnFkbiI6InN0cmluZyIsImlkIjoic3RyaW5nIiwiaW5zdWZmaWNpZW50X2RhdGFfaGVhbHRoX3N0YXR1cyI6InN0cmluZyIsImludmVydF9oZWFsdGhjaGVjayI6ImJvb2wiLCJpcF9hZGRyZXNzIjoic3RyaW5nIiwibWVhc3VyZV9sYXRlbmN5IjoiYm9vbCIsInBvcnQiOiJudW1iZXIiLCJyZWZlcmVuY2VfbmFtZSI6InN0cmluZyIsInJlZ2lvbnMiOlsic2V0Iiwic3RyaW5nIl0sInJlcXVlc3RfaW50ZXJ2YWwiOiJudW1iZXIiLCJyZXNvdXJjZV9wYXRoIjoic3RyaW5nIiwic2VhcmNoX3N0cmluZyI6InN0cmluZyIsInRhZ3MiOlsibWFwIiwic3RyaW5nIl0sInR5cGUiOiJzdHJpbmcifV0=",
"Val": "eyJjaGlsZF9oZWFsdGhfdGhyZXNob2xkIjowLCJjaGlsZF9oZWFsdGhjaGVja3MiOltdLCJjbG91ZHdhdGNoX2FsYXJtX25hbWUiOm51bGwsImNsb3Vkd2F0Y2hfYWxhcm1fcmVnaW9uIjpudWxsLCJkaXNhYmxlZCI6ZmFsc2UsImVuYWJsZV9zbmkiOmZhbHNlLCJmYWlsdXJlX3RocmVzaG9sZCI6NSwiZnFkbiI6Im1vYWRpYi5uZXQiLCJpZCI6IjcwMDFhOWRmLWRlZDQtNDgwMi05OTA5LTY2OGViODBiOTcyYiIsImluc3VmZmljaWVudF9kYXRhX2hlYWx0aF9zdGF0dXMiOiIiLCJpbnZlcnRfaGVhbHRoY2hlY2siOmZhbHNlLCJpcF9hZGRyZXNzIjoiIiwibWVhc3VyZV9sYXRlbmN5IjpmYWxzZSwicG9ydCI6ODAsInJlZmVyZW5jZV9uYW1lIjpudWxsLCJyZWdpb25zIjpbXSwicmVxdWVzdF9pbnRlcnZhbCI6MzAsInJlc291cmNlX3BhdGgiOiIvIiwic2VhcmNoX3N0cmluZyI6IiIsInRhZ3MiOnsiTmFtZSI6Imh0dHAtbW9hZGliLW5ldCJ9LCJ0eXBlIjoiSFRUUCJ9",
"Err": null
}

View File

@ -0,0 +1,5 @@
{
"Typ": "WyJvYmplY3QiLHsiY2hpbGRfaGVhbHRoX3RocmVzaG9sZCI6Im51bWJlciIsImNoaWxkX2hlYWx0aGNoZWNrcyI6WyJzZXQiLCJzdHJpbmciXSwiY2xvdWR3YXRjaF9hbGFybV9uYW1lIjoic3RyaW5nIiwiY2xvdWR3YXRjaF9hbGFybV9yZWdpb24iOiJzdHJpbmciLCJkaXNhYmxlZCI6ImJvb2wiLCJlbmFibGVfc25pIjoiYm9vbCIsImZhaWx1cmVfdGhyZXNob2xkIjoibnVtYmVyIiwiZnFkbiI6InN0cmluZyIsImlkIjoic3RyaW5nIiwiaW5zdWZmaWNpZW50X2RhdGFfaGVhbHRoX3N0YXR1cyI6InN0cmluZyIsImludmVydF9oZWFsdGhjaGVjayI6ImJvb2wiLCJpcF9hZGRyZXNzIjoic3RyaW5nIiwibWVhc3VyZV9sYXRlbmN5IjoiYm9vbCIsInBvcnQiOiJudW1iZXIiLCJyZWZlcmVuY2VfbmFtZSI6InN0cmluZyIsInJlZ2lvbnMiOlsic2V0Iiwic3RyaW5nIl0sInJlcXVlc3RfaW50ZXJ2YWwiOiJudW1iZXIiLCJyZXNvdXJjZV9wYXRoIjoic3RyaW5nIiwic2VhcmNoX3N0cmluZyI6InN0cmluZyIsInRhZ3MiOlsibWFwIiwic3RyaW5nIl0sInR5cGUiOiJzdHJpbmcifV0=",
"Val": "eyJjaGlsZF9oZWFsdGhfdGhyZXNob2xkIjowLCJjaGlsZF9oZWFsdGhjaGVja3MiOltdLCJjbG91ZHdhdGNoX2FsYXJtX25hbWUiOm51bGwsImNsb3Vkd2F0Y2hfYWxhcm1fcmVnaW9uIjpudWxsLCJkaXNhYmxlZCI6ZmFsc2UsImVuYWJsZV9zbmkiOnRydWUsImZhaWx1cmVfdGhyZXNob2xkIjo1LCJmcWRuIjoibW9hZGliLm5ldCIsImlkIjoiODRmYzMxOGEtMmUwZC00MWQ2LWI2MzgtMjgwZTJmMGY0ZTI2IiwiaW5zdWZmaWNpZW50X2RhdGFfaGVhbHRoX3N0YXR1cyI6IiIsImludmVydF9oZWFsdGhjaGVjayI6ZmFsc2UsImlwX2FkZHJlc3MiOiIiLCJtZWFzdXJlX2xhdGVuY3kiOmZhbHNlLCJwb3J0Ijo0NDMsInJlZmVyZW5jZV9uYW1lIjpudWxsLCJyZWdpb25zIjpbXSwicmVxdWVzdF9pbnRlcnZhbCI6MzAsInJlc291cmNlX3BhdGgiOiIvIiwic2VhcmNoX3N0cmluZyI6Ik1vQWRpQiIsInRhZ3MiOnt9LCJ0eXBlIjoiSFRUUFNfU1RSX01BVENIIn0=",
"Err": null
}

View File

@ -0,0 +1,50 @@
[
{
"child_health_threshold": 0,
"child_healthchecks": [],
"cloudwatch_alarm_name": null,
"cloudwatch_alarm_region": null,
"disabled": false,
"enable_sni": false,
"failure_threshold": 5,
"fqdn": "moadib.net",
"id": "7001a9df-ded4-4802-9909-668eb80b972b",
"insufficient_data_health_status": "",
"invert_healthcheck": false,
"ip_address": "",
"measure_latency": false,
"port": 80,
"reference_name": null,
"regions": [],
"request_interval": 30,
"resource_path": "/",
"search_string": "",
"tags": {
"Name": "http-moadib-net"
},
"type": "HTTP"
},
{
"child_health_threshold": 0,
"child_healthchecks": [],
"cloudwatch_alarm_name": null,
"cloudwatch_alarm_region": null,
"disabled": false,
"enable_sni": true,
"failure_threshold": 5,
"fqdn": "moadib.net",
"id": "84fc318a-2e0d-41d6-b638-280e2f0f4e26",
"insufficient_data_health_status": "",
"invert_healthcheck": false,
"ip_address": "",
"measure_latency": false,
"port": 443,
"reference_name": null,
"regions": [],
"request_interval": 30,
"resource_path": "/",
"search_string": "MoAdiB",
"tags": {},
"type": "HTTPS_STR_MATCH"
}
]

File diff suppressed because it is too large Load Diff

View File

@ -4,14 +4,30 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/sirupsen/logrus"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error" remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/cloudskiff/driftctl/pkg/alerter" "github.com/cloudskiff/driftctl/pkg/alerter"
"github.com/sirupsen/logrus"
) )
type EnumerationAccessDeniedAlert struct {
message string
}
func NewEnumerationAccessDeniedAlert(supplierType, listedTypeError string) *EnumerationAccessDeniedAlert {
message := fmt.Sprintf("Ignoring %s from drift calculation: Listing %s is forbidden.", supplierType, listedTypeError)
return &EnumerationAccessDeniedAlert{message}
}
func (e *EnumerationAccessDeniedAlert) Message() string {
return e.message
}
func (e *EnumerationAccessDeniedAlert) ShouldIgnoreResource() bool {
return true
}
func HandleResourceEnumerationError(err error, alertr *alerter.Alerter) error { func HandleResourceEnumerationError(err error, alertr *alerter.Alerter) error {
listError, ok := err.(*remoteerror.ResourceEnumerationError) listError, ok := err.(*remoteerror.ResourceEnumerationError)
if !ok { if !ok {
@ -24,12 +40,11 @@ func HandleResourceEnumerationError(err error, alertr *alerter.Alerter) error {
} }
if reqerr.StatusCode() == 403 || (reqerr.StatusCode() == 400 && strings.Contains(reqerr.Code(), "AccessDenied")) { if reqerr.StatusCode() == 403 || (reqerr.StatusCode() == 400 && strings.Contains(reqerr.Code(), "AccessDenied")) {
message := fmt.Sprintf("Ignoring %s from drift calculation: Listing %s is forbidden.", listError.SupplierType(), listError.ListedTypeError()) logrus.WithFields(logrus.Fields{
logrus.Debugf(message) "supplier_type": listError.SupplierType(),
alertr.SendAlert(listError.SupplierType(), alerter.Alert{ "listed_type": listError.ListedTypeError(),
Message: message, }).Debugf("Got an access denied error")
ShouldIgnoreResource: true, alertr.SendAlert(listError.SupplierType(), NewEnumerationAccessDeniedAlert(listError.SupplierType(), listError.ListedTypeError()))
})
return nil return nil
} }

View File

@ -25,13 +25,13 @@ func TestHandleListAwsError(t *testing.T) {
{ {
name: "Handled error 403", name: "Handled error 403",
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(awserr.New("", "", errors.New("")), 403, ""), resourceaws.AwsVpcResourceType), err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(awserr.New("", "", errors.New("")), 403, ""), resourceaws.AwsVpcResourceType),
wantAlerts: alerter.Alerts{"aws_vpc": []alerter.Alert{alerter.Alert{Message: "Ignoring aws_vpc from drift calculation: Listing aws_vpc is forbidden.", ShouldIgnoreResource: true}}}, wantAlerts: alerter.Alerts{"aws_vpc": []alerter.Alert{NewEnumerationAccessDeniedAlert("aws_vpc", "aws_vpc")}},
wantErr: false, wantErr: false,
}, },
{ {
name: "Handled error AccessDenied", name: "Handled error AccessDenied",
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(awserr.New("AccessDeniedException", "", errors.New("")), 403, ""), resourceaws.AwsDynamodbTableResourceType), err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(awserr.New("AccessDeniedException", "", errors.New("")), 403, ""), resourceaws.AwsDynamodbTableResourceType),
wantAlerts: alerter.Alerts{"aws_dynamodb_table": []alerter.Alert{alerter.Alert{Message: "Ignoring aws_dynamodb_table from drift calculation: Listing aws_dynamodb_table is forbidden.", ShouldIgnoreResource: true}}}, wantAlerts: alerter.Alerts{"aws_dynamodb_table": []alerter.Alert{NewEnumerationAccessDeniedAlert("aws_dynamodb_table", "aws_dynamodb_table")}},
wantErr: false, wantErr: false,
}, },
{ {

View File

@ -0,0 +1,150 @@
// GENERATED, DO NOT EDIT THIS FILE
package aws
const AwsCloudfrontDistributionResourceType = "aws_cloudfront_distribution"
type AwsCloudfrontDistribution struct {
Aliases *[]string `cty:"aliases"`
Arn *string `cty:"arn" computed:"true"`
CallerReference *string `cty:"caller_reference" computed:"true"`
Comment *string `cty:"comment"`
DefaultRootObject *string `cty:"default_root_object"`
DomainName *string `cty:"domain_name" computed:"true"`
Enabled *bool `cty:"enabled"`
Etag *string `cty:"etag" computed:"true" diff:"-"`
HostedZoneId *string `cty:"hosted_zone_id" computed:"true"`
HttpVersion *string `cty:"http_version"`
Id string `cty:"id" computed:"true"`
InProgressValidationBatches *int `cty:"in_progress_validation_batches" computed:"true"`
IsIpv6Enabled *bool `cty:"is_ipv6_enabled"`
LastModifiedTime *string `cty:"last_modified_time" computed:"true" diff:"-"`
PriceClass *string `cty:"price_class"`
RetainOnDelete *bool `cty:"retain_on_delete" diff:"-"`
Status *string `cty:"status" computed:"true" diff:"-"`
Tags map[string]string `cty:"tags"`
TrustedSigners *[]struct {
Enabled *bool `cty:"enabled" computed:"true"`
Items *[]struct {
AwsAccountNumber *string `cty:"aws_account_number" computed:"true"`
KeyPairIds *[]string `cty:"key_pair_ids" computed:"true"`
} `cty:"items" computed:"true"`
} `cty:"trusted_signers" computed:"true"`
WaitForDeployment *bool `cty:"wait_for_deployment" diff:"-"`
WebAclId *string `cty:"web_acl_id"`
CustomErrorResponse *[]struct {
ErrorCachingMinTtl *int `cty:"error_caching_min_ttl"`
ErrorCode *int `cty:"error_code"`
ResponseCode *int `cty:"response_code"`
ResponsePagePath *string `cty:"response_page_path"`
} `cty:"custom_error_response"`
DefaultCacheBehavior *[]struct {
AllowedMethods *[]string `cty:"allowed_methods"`
CachedMethods *[]string `cty:"cached_methods"`
Compress *bool `cty:"compress"`
DefaultTtl *int `cty:"default_ttl"`
FieldLevelEncryptionId *string `cty:"field_level_encryption_id"`
MaxTtl *int `cty:"max_ttl"`
MinTtl *int `cty:"min_ttl"`
SmoothStreaming *bool `cty:"smooth_streaming"`
TargetOriginId *string `cty:"target_origin_id"`
TrustedSigners *[]string `cty:"trusted_signers"`
ViewerProtocolPolicy *string `cty:"viewer_protocol_policy"`
ForwardedValues *[]struct {
Headers *[]string `cty:"headers"`
QueryString *bool `cty:"query_string"`
QueryStringCacheKeys *[]string `cty:"query_string_cache_keys"`
Cookies *[]struct {
Forward *string `cty:"forward"`
WhitelistedNames *[]string `cty:"whitelisted_names"`
} `cty:"cookies"`
} `cty:"forwarded_values"`
LambdaFunctionAssociation *[]struct {
EventType *string `cty:"event_type"`
IncludeBody *bool `cty:"include_body"`
LambdaArn *string `cty:"lambda_arn"`
} `cty:"lambda_function_association"`
} `cty:"default_cache_behavior"`
LoggingConfig *[]struct {
Bucket *string `cty:"bucket"`
IncludeCookies *bool `cty:"include_cookies"`
Prefix *string `cty:"prefix"`
} `cty:"logging_config"`
OrderedCacheBehavior *[]struct {
AllowedMethods *[]string `cty:"allowed_methods"`
CachedMethods *[]string `cty:"cached_methods"`
Compress *bool `cty:"compress"`
DefaultTtl *int `cty:"default_ttl"`
FieldLevelEncryptionId *string `cty:"field_level_encryption_id"`
MaxTtl *int `cty:"max_ttl"`
MinTtl *int `cty:"min_ttl"`
PathPattern *string `cty:"path_pattern"`
SmoothStreaming *bool `cty:"smooth_streaming"`
TargetOriginId *string `cty:"target_origin_id"`
TrustedSigners *[]string `cty:"trusted_signers"`
ViewerProtocolPolicy *string `cty:"viewer_protocol_policy"`
ForwardedValues *[]struct {
Headers *[]string `cty:"headers"`
QueryString *bool `cty:"query_string"`
QueryStringCacheKeys *[]string `cty:"query_string_cache_keys"`
Cookies *[]struct {
Forward *string `cty:"forward"`
WhitelistedNames *[]string `cty:"whitelisted_names"`
} `cty:"cookies"`
} `cty:"forwarded_values"`
LambdaFunctionAssociation *[]struct {
EventType *string `cty:"event_type"`
IncludeBody *bool `cty:"include_body"`
LambdaArn *string `cty:"lambda_arn"`
} `cty:"lambda_function_association"`
} `cty:"ordered_cache_behavior"`
Origin *[]struct {
DomainName *string `cty:"domain_name"`
OriginId *string `cty:"origin_id"`
OriginPath *string `cty:"origin_path"`
CustomHeader *[]struct {
Name *string `cty:"name"`
Value *string `cty:"value"`
} `cty:"custom_header"`
CustomOriginConfig *[]struct {
HttpPort *int `cty:"http_port"`
HttpsPort *int `cty:"https_port"`
OriginKeepaliveTimeout *int `cty:"origin_keepalive_timeout"`
OriginProtocolPolicy *string `cty:"origin_protocol_policy"`
OriginReadTimeout *int `cty:"origin_read_timeout"`
OriginSslProtocols *[]string `cty:"origin_ssl_protocols"`
} `cty:"custom_origin_config"`
S3OriginConfig *[]struct {
OriginAccessIdentity *string `cty:"origin_access_identity"`
} `cty:"s3_origin_config"`
} `cty:"origin"`
OriginGroup *[]struct {
OriginId *string `cty:"origin_id"`
FailoverCriteria *[]struct {
StatusCodes *[]int `cty:"status_codes"`
} `cty:"failover_criteria"`
Member *[]struct {
OriginId *string `cty:"origin_id"`
} `cty:"member"`
} `cty:"origin_group"`
Restrictions *[]struct {
GeoRestriction *[]struct {
Locations *[]string `cty:"locations"`
RestrictionType *string `cty:"restriction_type"`
} `cty:"geo_restriction"`
} `cty:"restrictions"`
ViewerCertificate *[]struct {
AcmCertificateArn *string `cty:"acm_certificate_arn"`
CloudfrontDefaultCertificate *bool `cty:"cloudfront_default_certificate"`
IamCertificateId *string `cty:"iam_certificate_id"`
MinimumProtocolVersion *string `cty:"minimum_protocol_version"`
SslSupportMethod *string `cty:"ssl_support_method"`
} `cty:"viewer_certificate"`
}
func (r *AwsCloudfrontDistribution) TerraformId() string {
return r.Id
}
func (r *AwsCloudfrontDistribution) TerraformType() string {
return AwsCloudfrontDistributionResourceType
}

View File

@ -0,0 +1,55 @@
package aws
import "github.com/cloudskiff/driftctl/pkg/resource"
func (r *AwsCloudfrontDistribution) NormalizeForState() (resource.Resource, error) {
r.normalizeNilPtr()
return r, nil
}
func (r *AwsCloudfrontDistribution) NormalizeForProvider() (resource.Resource, error) {
r.normalizeNilPtr()
return r, nil
}
func (r *AwsCloudfrontDistribution) normalizeNilPtr() {
if r.Aliases != nil && len(*r.Aliases) == 0 {
r.Aliases = nil
}
if r.OriginGroup != nil && len(*r.OriginGroup) == 0 {
r.OriginGroup = nil
}
// Way too dirty, we must find another way to normalize to nil fields in deeply nested struct
// Here, this is for 3 fields, but in the CloudfrontDistribution resource there
// could be more fields that we would need to do that...
if r.DefaultCacheBehavior != nil {
for i, b := range *r.DefaultCacheBehavior {
if b.ForwardedValues != nil {
for j, f := range *b.ForwardedValues {
if f.Headers != nil && len(*f.Headers) == 0 {
(*(*r.DefaultCacheBehavior)[i].ForwardedValues)[j].Headers = nil
}
if f.Cookies != nil {
for k, c := range *f.Cookies {
if c.WhitelistedNames != nil && len(*c.WhitelistedNames) == 0 {
(*(*(*r.DefaultCacheBehavior)[i].ForwardedValues)[j].Cookies)[k].WhitelistedNames = nil
}
}
}
}
}
}
}
if r.Restrictions != nil {
for i, v := range *r.Restrictions {
if v.GeoRestriction != nil {
for j, g := range *v.GeoRestriction {
if g.Locations != nil && len(*g.Locations) == 0 {
(*(*r.Restrictions)[i].GeoRestriction)[j].Locations = nil
}
}
}
}
}
}

View File

@ -0,0 +1,81 @@
package aws_test
import (
"testing"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/cloudskiff/driftctl/pkg/analyser"
awsresources "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/r3labs/diff/v2"
"github.com/aws/aws-sdk-go/aws"
"github.com/cloudskiff/driftctl/test/acceptance"
"github.com/cloudskiff/driftctl/test/acceptance/awsutils"
)
func TestAcc_Aws_CloudfrontDistribution(t *testing.T) {
var mutatedDistribution string
acceptance.Run(t, acceptance.AccTestCase{
Path: "./testdata/acc/aws_cloudfront_distribution",
Args: []string{"scan", "--filter", "Type=='aws_cloudfront_distribution'"},
ShouldRefreshBeforeDestroy: true,
Checks: []acceptance.AccCheck{
{
Env: map[string]string{
"AWS_REGION": "us-east-1",
},
Check: func(result *acceptance.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertInfrastructureIsInSync()
result.AssertManagedCount(1)
mutatedDistribution = result.Managed()[0].TerraformId()
},
},
{
Env: map[string]string{
"AWS_REGION": "us-east-1",
},
PreExec: func() {
client := cloudfront.New(awsutils.Session())
res, err := client.GetDistributionConfig(&cloudfront.GetDistributionConfigInput{
Id: aws.String(mutatedDistribution),
})
if err != nil {
t.Fatal(err)
}
res.DistributionConfig.SetIsIPV6Enabled(true)
_, err = client.UpdateDistribution(&cloudfront.UpdateDistributionInput{
Id: aws.String(mutatedDistribution),
DistributionConfig: res.DistributionConfig,
IfMatch: res.ETag,
})
if err != nil {
t.Fatal(err)
}
},
Check: func(result *acceptance.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertDriftCountTotal(1)
result.AssertResourceHasDrift(
mutatedDistribution,
awsresources.AwsCloudfrontDistributionResourceType,
analyser.Change{
Change: diff.Change{
Type: diff.UPDATE,
Path: []string{"IsIpv6Enabled"},
From: false,
To: true,
},
},
)
},
},
},
})
}

View File

@ -0,0 +1,36 @@
// GENERATED, DO NOT EDIT THIS FILE
package aws
const AwsRoute53HealthCheckResourceType = "aws_route53_health_check"
type AwsRoute53HealthCheck struct {
ChildHealthThreshold *int `cty:"child_health_threshold"`
ChildHealthchecks *[]string `cty:"child_healthchecks"` // This became a slice ptr due to gocty
CloudwatchAlarmName *string `cty:"cloudwatch_alarm_name"`
CloudwatchAlarmRegion *string `cty:"cloudwatch_alarm_region"`
Disabled *bool `cty:"disabled"`
EnableSni *bool `cty:"enable_sni" computed:"true"`
FailureThreshold *int `cty:"failure_threshold"`
Fqdn *string `cty:"fqdn"`
Id string `cty:"id" computed:"true"`
InsufficientDataHealthStatus *string `cty:"insufficient_data_health_status"`
InvertHealthcheck *bool `cty:"invert_healthcheck"`
IpAddress *string `cty:"ip_address"`
MeasureLatency *bool `cty:"measure_latency"`
Port *int `cty:"port"`
ReferenceName *string `cty:"reference_name"`
Regions *[]string `cty:"regions"` // This became a slice ptr due to gocty
RequestInterval *int `cty:"request_interval"`
ResourcePath *string `cty:"resource_path"`
SearchString *string `cty:"search_string"`
Tags map[string]string `cty:"tags"`
Type *string `cty:"type"`
}
func (r *AwsRoute53HealthCheck) TerraformId() string {
return r.Id
}
func (r *AwsRoute53HealthCheck) TerraformType() string {
return AwsRoute53HealthCheckResourceType
}

View File

@ -0,0 +1,50 @@
package aws
import (
"fmt"
"github.com/cloudskiff/driftctl/pkg/resource"
)
func (r *AwsRoute53HealthCheck) String() string {
str := r.Id
name, hasName := r.Tags["Name"]
if hasName {
str = name
}
if r.Fqdn != nil && *r.Fqdn != "" {
str += fmt.Sprintf(" (fqdn: %s", *r.Fqdn)
str = r.addPortAndResPathString(str)
str += ")"
}
if r.IpAddress != nil && *r.IpAddress != "" {
str += fmt.Sprintf(" (ip: %s", *r.IpAddress)
str = r.addPortAndResPathString(str)
str += ")"
}
return str
}
func (r *AwsRoute53HealthCheck) addPortAndResPathString(str string) string {
if r.Port != nil {
str += fmt.Sprintf(", port: %d", *r.Port)
}
if r.ResourcePath != nil {
str += fmt.Sprintf(", path: %s", *r.ResourcePath)
}
return str
}
func (r *AwsRoute53HealthCheck) NormalizeForState() (resource.Resource, error) {
r.ChildHealthchecks = &[]string{}
r.Regions = &[]string{}
return r, nil
}
func (r *AwsRoute53HealthCheck) NormalizeForProvider() (resource.Resource, error) {
return r, nil
}

View File

@ -0,0 +1,61 @@
package aws
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
)
func TestAwsRoute53HealthCheck_String(t *testing.T) {
tests := []struct {
name string
healthCheck *AwsRoute53HealthCheck
want string
}{
{
name: "Just id",
healthCheck: &AwsRoute53HealthCheck{Id: "id"},
want: "id",
},
{
name: "Tag name with fqdn and respath",
healthCheck: &AwsRoute53HealthCheck{
Id: "id",
Tags: map[string]string{"Name": "name"},
Fqdn: aws.String("fq.dn"),
ResourcePath: aws.String("/toto"),
},
want: "name (fqdn: fq.dn, path: /toto)",
},
{
name: "Tag name with ip and port",
healthCheck: &AwsRoute53HealthCheck{
Id: "id",
Tags: map[string]string{"Name": "name"},
IpAddress: aws.String("10.0.0.10"),
Port: aws.Int(443),
},
want: "name (ip: 10.0.0.10, port: 443)",
},
{
name: "Tag name with ip, port ans respath",
healthCheck: &AwsRoute53HealthCheck{
Id: "id",
Tags: map[string]string{"Name": "name"},
IpAddress: aws.String("10.0.0.10"),
Port: aws.Int(443),
ResourcePath: aws.String("/toto"),
},
want: "name (ip: 10.0.0.10, port: 443, path: /toto)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := tt.healthCheck
if got := r.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,90 @@
package aws_test
import (
"testing"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/aws"
"github.com/cloudskiff/driftctl/pkg/analyser"
awsresources "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/cloudskiff/driftctl/test/acceptance"
"github.com/cloudskiff/driftctl/test/acceptance/awsutils"
"github.com/r3labs/diff/v2"
)
func TestAcc_AwsRoute53HealthCheck(t *testing.T) {
var mutatedHealthCheckID string
acceptance.Run(t, acceptance.AccTestCase{
Path: "./testdata/acc/aws_route53_health_check",
Args: []string{"scan", "--filter", "Type=='aws_route53_health_check'"},
Checks: []acceptance.AccCheck{
{
Env: map[string]string{
"AWS_REGION": "us-east-1",
},
Check: func(result *acceptance.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertInfrastructureIsInSync()
result.AssertManagedCount(2)
mutatedHealthCheckID = result.Managed()[0].TerraformId()
},
},
{
Env: map[string]string{
"AWS_REGION": "us-east-1",
},
PreExec: func() {
client := route53.New(awsutils.Session())
_, err := client.UpdateHealthCheck(&route53.UpdateHealthCheckInput{
Disabled: aws.Bool(true),
HealthCheckId: &mutatedHealthCheckID,
ResourcePath: aws.String("/bad"),
})
if err != nil {
t.Fatal(err)
}
},
Check: func(result *acceptance.ScanResult, stdout string, err error) {
if err != nil {
t.Fatal(err)
}
result.AssertDriftCountTotal(2)
result.AssertDeletedCount(0)
result.AssertManagedCount(2)
result.AssertResourceHasDrift(
mutatedHealthCheckID,
awsresources.AwsRoute53HealthCheckResourceType,
analyser.Change{
Change: diff.Change{
Type: diff.UPDATE,
Path: []string{"Disabled"},
From: false,
To: true,
},
Computed: false,
},
)
result.AssertResourceHasDrift(
mutatedHealthCheckID,
awsresources.AwsRoute53HealthCheckResourceType,
analyser.Change{
Change: diff.Change{
Type: diff.UPDATE,
Path: []string{"ResourcePath"},
From: "/",
To: "/bad",
},
Computed: false,
},
)
},
},
},
})
}

View File

@ -0,0 +1,44 @@
package deserializer
import (
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
type CloudfrontDistributionDeserializer struct {
}
func NewCloudfrontDistributionDeserializer() *CloudfrontDistributionDeserializer {
return &CloudfrontDistributionDeserializer{}
}
func (s *CloudfrontDistributionDeserializer) HandledType() resource.ResourceType {
return resourceaws.AwsCloudfrontDistributionResourceType
}
func (s CloudfrontDistributionDeserializer) Deserialize(rawList []cty.Value) ([]resource.Resource, error) {
resources := make([]resource.Resource, 0)
for _, rawResource := range rawList {
rawResource := rawResource
resource, err := decodeCloudfrontDistribution(&rawResource)
if err != nil {
logrus.WithFields(logrus.Fields{
"type": s.HandledType(),
}).Warnf("Error when deserializing resource %+v : %+v", rawResource, err)
return nil, err
}
resources = append(resources, resource)
}
return resources, nil
}
func decodeCloudfrontDistribution(raw *cty.Value) (*resourceaws.AwsCloudfrontDistribution, error) {
var decoded resourceaws.AwsCloudfrontDistribution
if err := gocty.FromCtyValue(*raw, &decoded); err != nil {
return nil, err
}
return &decoded, nil
}

View File

@ -0,0 +1,44 @@
package deserializer
import (
"github.com/cloudskiff/driftctl/pkg/resource"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
type Route53HealthCheckDeserializer struct {
}
func NewRoute53HealthCheckDeserializer() *Route53HealthCheckDeserializer {
return &Route53HealthCheckDeserializer{}
}
func (s *Route53HealthCheckDeserializer) HandledType() resource.ResourceType {
return resourceaws.AwsRoute53HealthCheckResourceType
}
func (s Route53HealthCheckDeserializer) Deserialize(rawList []cty.Value) ([]resource.Resource, error) {
resources := make([]resource.Resource, 0)
for _, rawResource := range rawList {
rawResource := rawResource
resource, err := decodeRoute53HealthCheck(&rawResource)
if err != nil {
logrus.Warnf("Error when deserializing resource %+v : %+v", rawResource, err)
return nil, err
}
resources = append(resources, resource)
}
return resources, nil
}
func decodeRoute53HealthCheck(raw *cty.Value) (*resourceaws.AwsRoute53HealthCheck, error) {
var decoded resourceaws.AwsRoute53HealthCheck
if err := gocty.FromCtyValue(*raw, &decoded); err != nil {
return nil, err
}
return &decoded, nil
}

View File

@ -0,0 +1,37 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "3.19.0"
constraints = "3.19.0"
hashes = [
"h1:xur9tF49NgsovNnmwmBR8RdpN8Fcg1TD4CKQPJD6n1A=",
"zh:185a5259153eb9ee4699d4be43b3d509386b473683392034319beee97d470c3b",
"zh:2d9a0a01f93e8d16539d835c02b8b6e1927b7685f4076e96cb07f7dd6944bc6c",
"zh:703f6da36b1b5f3497baa38fccaa7765fb8a2b6440344e4c97172516b49437dd",
"zh:770855565462abadbbddd98cb357d2f1a8f30f68a358cb37cbd5c072cb15b377",
"zh:8008db43149fe4345301f81e15e6d9ddb47aa5e7a31648f9b290af96ad86e92a",
"zh:8cdd27d375da6dcb7687f1fed126b7c04efce1671066802ee876dbbc9c66ec79",
"zh:be22ae185005690d1a017c1b909e0d80ab567e239b4f06ecacdba85080667c1c",
"zh:d2d02e72dbd80f607636cd6237a6c862897caabc635c7b50c0cb243d11246723",
"zh:d8f125b66a1eda2555c0f9bbdf12036a5f8d073499a22ca9e4812b68067fea31",
"zh:f5a98024c64d5d2973ff15b093725a074c0cb4afde07ef32c542e69f17ac90bc",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.0.1"
hashes = [
"h1:0QaSbRBgBi8vI/8IRwec1INdOqBxXbgsSFElx1O4k4g=",
"zh:0d4f683868324af056a9eb2b06306feef7c202c88dbbe6a4ad7517146a22fb50",
"zh:4824b3c7914b77d41dfe90f6f333c7ac9860afb83e2a344d91fbe46e5dfbec26",
"zh:4b82e43712f3cf0d0cbc95b2cbcd409ba8f0dc7848fdfb7c13633c27468ed04a",
"zh:78b3a2b860c3ebc973a794000015f5946eb59b82705d701d487475406b2612f1",
"zh:88bc65197bd74ff408d147b32f0045372ae3a3f2a2fdd7f734f315d988c0e4a2",
"zh:91bd3c9f625f177f3a5d641a64e54d4b4540cb071070ecda060a8261fb6eb2ef",
"zh:a6818842b28d800f784e0c93284ff602b0c4022f407e4750da03f50b853a9a2c",
"zh:c4a1a2b52abd05687e6cfded4a789dcd7b43e7a746e4d02dd1055370cf9a994d",
"zh:cf65041bf12fc3bde709c1d267dbe94142bc05adcabc4feb17da3b12249132ac",
"zh:e385e00e7425dda9d30b74ab4ffa4636f4b8eb23918c0b763f0ffab84ece0c5c",
]
}

View File

@ -0,0 +1,58 @@
provider "aws" {
region = "us-east-1"
}
terraform {
required_providers {
aws = "3.19.0"
}
}
resource "random_string" "prefix" {
length = 6
upper = false
special = false
}
resource "aws_s3_bucket" "foo_cloudfront" {
bucket = "${random_string.prefix.result}.foo-cloudfront"
acl = "private"
}
locals {
s3_origin_id = "S3-foo-cloudfront"
}
resource "aws_cloudfront_distribution" "foo_distribution" {
enabled = false
origin {
domain_name = aws_s3_bucket.foo_cloudfront.bucket_regional_domain_name
origin_id = local.s3_origin_id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id
viewer_protocol_policy = "allow-all"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}

View File

@ -0,0 +1,19 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "3.28.0"
hashes = [
"h1:zejsAukFmgZCOdQCk44L3cumXFs8YDSltRIjZN+izsU=",
"zh:1fee7fce319be5bea7df2e95f28a78a04e15c18bad5eb56dcc0ecc324c97f4b8",
"zh:2383ff31ef7411f7d4bef1ee288f0f79bec41cf220ac94c2b31f6a702b26f984",
"zh:2f450372a8aa7d32f62524159a5930e0251ba34f491d66f00239452a6d575921",
"zh:379d4fdc16a2245b50959f5bfcb24c71fb74b292b6cf9c2d267b6ce94dddd208",
"zh:9fd1078759edd79548ec52c6853668a69f22803c92c0ac202f5c43c1ace63ac0",
"zh:aef544e720ce79f97875cc4ef5dd163922e9f47a496e663d0a272e881d2dd32e",
"zh:e2f28ba5bde0403f3273e80860a80ab5e63420e0142c0e8e283b651b750a8ffe",
"zh:ebc859186fcdd4700cc7091a8ecf4e06cc6d2eceadaeadda0d0e49efc6456325",
"zh:ee7bced0660945206c6226de35ae465b52e406b12e9ff1075186af37962caa6f",
"zh:f33063481894f951ff1e76b94a8311041a4bd3f1f1f01d1a8580d6c893e13c2c",
]
}

View File

@ -0,0 +1,26 @@
provider "aws" {
region = "us-east-1"
}
resource "aws_route53_health_check" "http" {
fqdn = "moadib.net"
port = 80
type = "HTTP"
resource_path = "/"
failure_threshold = "5"
request_interval = "30"
tags = {
Name = "http-moadib-net"
}
}
resource "aws_route53_health_check" "https" {
failure_threshold = "5"
fqdn = "moadib.net"
port = 443
request_interval = "30"
resource_path = "/"
search_string = "MoAdiB"
type = "HTTPS_STR_MATCH"
}

View File

@ -38,14 +38,15 @@ type AccCheck struct {
} }
type AccTestCase struct { type AccTestCase struct {
Path string Path string
Args []string Args []string
OnStart func() OnStart func()
OnEnd func() OnEnd func()
Checks []AccCheck Checks []AccCheck
tmpResultFilePath string tmpResultFilePath string
originalEnv []string originalEnv []string
tf *tfexec.Terraform tf *tfexec.Terraform
ShouldRefreshBeforeDestroy bool
} }
func (c *AccTestCase) initTerraformExecutor() error { func (c *AccTestCase) initTerraformExecutor() error {
@ -167,6 +168,12 @@ func (c *AccTestCase) terraformApply() error {
} }
func (c *AccTestCase) terraformDestroy() error { func (c *AccTestCase) terraformDestroy() error {
if c.ShouldRefreshBeforeDestroy {
if err := c.terraformRefresh(); err != nil {
return err
}
}
logrus.Debug("Running terraform destroy ...") logrus.Debug("Running terraform destroy ...")
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
c.tf.SetStderr(stderr) c.tf.SetStderr(stderr)
@ -178,6 +185,18 @@ func (c *AccTestCase) terraformDestroy() error {
return nil return nil
} }
func (c *AccTestCase) terraformRefresh() error {
logrus.Debug("Running terraform refresh ...")
stderr := new(bytes.Buffer)
c.tf.SetStderr(stderr)
if err := c.tf.Refresh(context.Background()); err != nil {
return errors.Wrap(err, stderr.String())
}
logrus.Debug("Terraform refresh done")
return nil
}
func runDriftCtlCmd(driftctlCmd *cmd.DriftctlCmd) (*cobra.Command, string, error) { func runDriftCtlCmd(driftctlCmd *cmd.DriftctlCmd) (*cobra.Command, string, error) {
old := os.Stdout // keep backup of the real stdout old := os.Stdout // keep backup of the real stdout
r, w, _ := os.Pipe() r, w, _ := os.Pipe()