add support for dynamodb_table

main
Martin Guibert 2021-02-11 12:21:49 +01:00
parent cb5b62b3df
commit 73ffe8280f
28 changed files with 691419 additions and 7 deletions

View File

@ -135,7 +135,14 @@ As AWS documentation recommends, the below policy is granting only the permissio
"sns:ListTagsForResource",
"sns:ListSubscriptions",
"sns:ListSubscriptionsByTopic",
"sns:GetSubscriptionAttributes"
"sns:GetSubscriptionAttributes",
"dynamodb:ListTables",
"dynamodb:DescribeTable",
"dynamodb:DescribeGlobalTable",
"dynamodb:ListTagsOfResource",
"dynamodb:DescribeTimeToLive",
"dynamodb:DescribeTableReplicaAutoScaling",
"dynamodb:DescribeContinuousBackups"
]
}
]
@ -258,3 +265,8 @@ As AWS documentation recommends, the below policy is granting only the permissio
- [x] aws_sns_topic_subscription
- [ ] aws_sns_platform_application
- [ ] aws_sns_sms_preferences
## DynamoDB
- [x] aws_dynamodb_table
- [ ] aws_dynamodb_global_table
- [ ] aws_dynamodb_table_item

View File

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

3460
mocks/DynamodbClient.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -52,5 +52,6 @@ func Deserializers() []deserializer.CTYDeserializer {
awsdeserializer.NewSNSTopicDeserializer(),
awsdeserializer.NewSNSTopicPolicyDeserializer(),
awsdeserializer.NewSNSTopicSubscriptionDeserializer(),
awsdeserializer.NewDynamoDBTableDeserializer(),
}
}

View File

@ -79,6 +79,7 @@ func TestTerraformStateReader_Resources(t *testing.T) {
{name: "SNS Topic", dirName: "sns_topic", wantErr: false},
{name: "SNS Topic Policy", dirName: "sns_topic_policy", wantErr: false},
{name: "SNS Topic Subscription", dirName: "sns_topic_subscription", wantErr: false},
{name: "DynamoDB table", dirName: "dynamodb_table", wantErr: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -0,0 +1,106 @@
[
{
"Arn": "arn:aws:dynamodb:us-east-1:526954929923:table/GameScores",
"BillingMode": "PROVISIONED",
"HashKey": "UserId",
"Id": "GameScores",
"Name": "GameScores",
"RangeKey": "GameTitle",
"ReadCapacity": 20,
"StreamArn": "",
"StreamEnabled": false,
"StreamLabel": "",
"StreamViewType": "",
"Tags": {
"Environment": "production",
"Name": "dynamodb-table-1"
},
"WriteCapacity": 20,
"Attribute": [
{
"Name": "GameTitle",
"Type": "S"
},
{
"Name": "TopScore",
"Type": "N"
},
{
"Name": "UserId",
"Type": "S"
}
],
"GlobalSecondaryIndex": [
{
"HashKey": "GameTitle",
"Name": "GameTitleIndex",
"NonKeyAttributes": [
"UserId"
],
"ProjectionType": "INCLUDE",
"RangeKey": "TopScore",
"ReadCapacity": 10,
"WriteCapacity": 10
}
],
"LocalSecondaryIndex": [],
"PointInTimeRecovery": [
{
"Enabled": false
}
],
"Replica": [],
"ServerSideEncryption": [],
"Timeouts": null,
"Ttl": [
{
"AttributeName": "",
"Enabled": false
}
]
},
{
"Arn": "arn:aws:dynamodb:us-east-1:526954929923:table/example",
"BillingMode": "PAY_PER_REQUEST",
"HashKey": "TestTableHashKey",
"Id": "example",
"Name": "example",
"RangeKey": null,
"ReadCapacity": 0,
"StreamArn": "arn:aws:dynamodb:us-east-1:526954929923:table/example/stream/2021-02-10T15:07:54.928",
"StreamEnabled": true,
"StreamLabel": "2021-02-10T15:07:54.928",
"StreamViewType": "NEW_AND_OLD_IMAGES",
"Tags": {},
"WriteCapacity": 0,
"Attribute": [
{
"Name": "TestTableHashKey",
"Type": "S"
}
],
"GlobalSecondaryIndex": [],
"LocalSecondaryIndex": [],
"PointInTimeRecovery": [
{
"Enabled": false
}
],
"Replica": [
{
"RegionName": "us-east-2"
},
{
"RegionName": "us-west-2"
}
],
"ServerSideEncryption": [],
"Timeouts": null,
"Ttl": [
{
"AttributeName": "",
"Enabled": false
}
]
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,139 @@
{
"version": 4,
"terraform_version": "0.14.5",
"serial": 45,
"lineage": "30081725-54a2-ce02-6ff5-45c4d961c652",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_dynamodb_table",
"name": "basic-dynamodb-table",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"arn": "arn:aws:dynamodb:us-east-1:526954929923:table/GameScores",
"attribute": [
{
"name": "GameTitle",
"type": "S"
},
{
"name": "TopScore",
"type": "N"
},
{
"name": "UserId",
"type": "S"
}
],
"billing_mode": "PROVISIONED",
"global_secondary_index": [
{
"hash_key": "GameTitle",
"name": "GameTitleIndex",
"non_key_attributes": [
"UserId"
],
"projection_type": "INCLUDE",
"range_key": "TopScore",
"read_capacity": 10,
"write_capacity": 10
}
],
"hash_key": "UserId",
"id": "GameScores",
"local_secondary_index": [],
"name": "GameScores",
"point_in_time_recovery": [
{
"enabled": false
}
],
"range_key": "GameTitle",
"read_capacity": 20,
"replica": [],
"server_side_encryption": [],
"stream_arn": "",
"stream_enabled": false,
"stream_label": "",
"stream_view_type": "",
"tags": {
"Environment": "production",
"Name": "dynamodb-table-1"
},
"timeouts": null,
"ttl": [
{
"attribute_name": "",
"enabled": false
}
],
"write_capacity": 20
},
"sensitive_attributes": [],
"private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6NjAwMDAwMDAwMDAwLCJ1cGRhdGUiOjM2MDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjEifQ=="
}
]
},
{
"mode": "managed",
"type": "aws_dynamodb_table",
"name": "example",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"arn": "arn:aws:dynamodb:us-east-1:526954929923:table/example",
"attribute": [
{
"name": "TestTableHashKey",
"type": "S"
}
],
"billing_mode": "PAY_PER_REQUEST",
"global_secondary_index": [],
"hash_key": "TestTableHashKey",
"id": "example",
"local_secondary_index": [],
"name": "example",
"point_in_time_recovery": [
{
"enabled": false
}
],
"range_key": null,
"read_capacity": 0,
"replica": [
{
"region_name": "us-east-2"
},
{
"region_name": "us-west-2"
}
],
"server_side_encryption": [],
"stream_arn": "arn:aws:dynamodb:us-east-1:526954929923:table/example/stream/2021-02-10T15:07:54.928",
"stream_enabled": true,
"stream_label": "2021-02-10T15:07:54.928",
"stream_view_type": "NEW_AND_OLD_IMAGES",
"tags": {},
"timeouts": null,
"ttl": [
{
"attribute_name": "",
"enabled": false
}
],
"write_capacity": 0
},
"sensitive_attributes": [],
"private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6NjAwMDAwMDAwMDAwLCJ1cGRhdGUiOjM2MDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjEifQ=="
}
]
}
]
}

View File

@ -0,0 +1,66 @@
package aws
import (
"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 DynamoDBTableSupplier struct {
reader terraform.ResourceReader
deserializer deserializer.CTYDeserializer
repository repository.DynamoDBRepository
runner *terraform.ParallelResourceReader
}
func NewDynamoDBTableSupplier(provider *TerraformProvider) *DynamoDBTableSupplier {
return &DynamoDBTableSupplier{
provider,
awsdeserializer.NewDynamoDBTableDeserializer(),
repository.NewDynamoDBRepository(provider.session),
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
}
}
func (s DynamoDBTableSupplier) Resources() ([]resource.Resource, error) {
tables, err := s.repository.ListAllTables()
if err != nil {
return nil, remoteerror.NewResourceEnumerationError(err, aws.AwsDynamodbTableResourceType)
}
for _, table := range tables {
table := table
s.runner.Run(func() (cty.Value, error) {
return s.readTable(table)
})
}
retrieve, err := s.runner.Wait()
if err != nil {
return nil, err
}
return s.deserializer.Deserialize(retrieve)
}
func (s DynamoDBTableSupplier) readTable(tableName *string) (cty.Value, error) {
val, err := s.reader.ReadResource(terraform.ReadResourceArgs{
ID: *tableName,
Ty: aws.AwsDynamodbTableResourceType,
Attributes: map[string]string{
"table_name": *tableName,
},
})
if err != nil {
logrus.Error(err)
return cty.NilVal, err
}
return *val, nil
}

View File

@ -0,0 +1,101 @@
package aws
import (
"context"
"errors"
"testing"
"github.com/aws/aws-sdk-go/aws"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
resourceaws "github.com/cloudskiff/driftctl/pkg/resource/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/cloudskiff/driftctl/pkg/parallel"
awsdeserializer "github.com/cloudskiff/driftctl/pkg/resource/aws/deserializer"
"github.com/cloudskiff/driftctl/test/goldenfile"
mocks2 "github.com/cloudskiff/driftctl/test/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/cloudskiff/driftctl/mocks"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test"
)
func TestDynamoDBTableSupplier_Resources(t *testing.T) {
cases := []struct {
test string
dirName string
mocks func(client *mocks.DynamoDBRepository)
err error
}{
{
test: "no DynamoDB Table",
dirName: "dynamodb_table_empty",
mocks: func(client *mocks.DynamoDBRepository) {
client.On("ListAllTables").Return([]*string{}, nil)
},
err: nil,
},
{
test: "Multiple DynamoDB Table",
dirName: "dynamodb_table_multiple",
mocks: func(client *mocks.DynamoDBRepository) {
client.On("ListAllTables").Return([]*string{
aws.String("GameScores"),
aws.String("example"),
}, nil)
},
err: nil,
},
{
test: "cannot list DynamoDB Table",
dirName: "dynamodb_table_list",
mocks: func(client *mocks.DynamoDBRepository) {
client.On("ListAllTables").Return(nil, awserr.NewRequestFailure(awserr.New("AccessDeniedException", "", errors.New("")), 400, ""))
},
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(awserr.New("AccessDeniedException", "", errors.New("")), 400, ""), resourceaws.AwsDynamodbTableResourceType),
},
}
for _, c := range cases {
shouldUpdate := c.dirName == *goldenfile.Update
providerLibrary := terraform.NewProviderLibrary()
supplierLibrary := resource.NewSupplierLibrary()
if shouldUpdate {
provider, err := NewTerraFormProvider()
if err != nil {
t.Fatal(err)
}
providerLibrary.AddProvider(terraform.AWS, provider)
supplierLibrary.AddSupplier(NewDynamoDBTableSupplier(provider))
}
t.Run(c.test, func(tt *testing.T) {
fakeClient := mocks.DynamoDBRepository{}
c.mocks(&fakeClient)
provider := mocks2.NewMockedGoldenTFProvider(c.dirName, providerLibrary.Provider(terraform.AWS), shouldUpdate)
dynamoDBTableDeserializer := awsdeserializer.NewDynamoDBTableDeserializer()
s := &DynamoDBTableSupplier{
provider,
dynamoDBTableDeserializer,
&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, dynamoDBTableDeserializer, shouldUpdate, tt)
})
}
}

View File

@ -62,6 +62,7 @@ func Init(alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary,
supplierLibrary.AddSupplier(NewSNSTopicSupplier(provider))
supplierLibrary.AddSupplier(NewSNSTopicPolicySupplier(provider))
supplierLibrary.AddSupplier(NewSNSTopicSubscriptionSupplier(provider))
supplierLibrary.AddSupplier(NewDynamoDBTableSupplier(provider))
return nil
}

View File

@ -0,0 +1,34 @@
package repository
import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
)
type DynamoDBRepository interface {
ListAllTables() ([]*string, error)
}
type dynamoDBRepository struct {
client dynamodbiface.DynamoDBAPI
}
func NewDynamoDBRepository(session *session.Session) *dynamoDBRepository {
return &dynamoDBRepository{
dynamodb.New(session),
}
}
func (r *dynamoDBRepository) ListAllTables() ([]*string, error) {
var tables []*string
input := &dynamodb.ListTablesInput{}
err := r.client.ListTablesPages(input, func(res *dynamodb.ListTablesOutput, lastPage bool) bool {
tables = append(tables, res.TableNames...)
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/dynamodb"
"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_dynamoDBRepository_ListAllTopics(t *testing.T) {
tests := []struct {
name string
mocks func(client *mocks.DynamodbClient)
want []*string
wantErr error
}{
{
name: "List with 2 pages",
mocks: func(client *mocks.DynamodbClient) {
client.On("ListTablesPages",
&dynamodb.ListTablesInput{},
mock.MatchedBy(func(callback func(res *dynamodb.ListTablesOutput, lastPage bool) bool) bool {
callback(&dynamodb.ListTablesOutput{
TableNames: []*string{
aws.String("1"),
aws.String("2"),
aws.String("3"),
},
}, false)
callback(&dynamodb.ListTablesOutput{
TableNames: []*string{
aws.String("4"),
aws.String("5"),
aws.String("6"),
},
}, true)
return true
})).Return(nil)
},
want: []*string{
aws.String("1"),
aws.String("2"),
aws.String("3"),
aws.String("4"),
aws.String("5"),
aws.String("6"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &mocks.DynamodbClient{}
tt.mocks(client)
r := &dynamoDBRepository{
client: client,
}
got, err := r.ListAllTables()
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 @@
[]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
[]

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -0,0 +1,111 @@
[
{
"arn": "arn:aws:dynamodb:us-east-1:526954929923:table/GameScores",
"attribute": [
{
"name": "GameTitle",
"type": "S"
},
{
"name": "TopScore",
"type": "N"
},
{
"name": "UserId",
"type": "S"
}
],
"billing_mode": "PROVISIONED",
"global_secondary_index": [
{
"hash_key": "GameTitle",
"name": "GameTitleIndex",
"non_key_attributes": [
"UserId"
],
"projection_type": "INCLUDE",
"range_key": "TopScore",
"read_capacity": 10,
"write_capacity": 10
}
],
"hash_key": "UserId",
"id": "GameScores",
"local_secondary_index": [],
"name": "GameScores",
"point_in_time_recovery": [
{
"enabled": false
}
],
"range_key": "GameTitle",
"read_capacity": 20,
"replica": [],
"server_side_encryption": [],
"stream_arn": "",
"stream_enabled": false,
"stream_label": "",
"stream_view_type": "",
"tags": {
"Environment": "production",
"Name": "dynamodb-table-1"
},
"timeouts": {
"create": null,
"delete": null,
"update": null
},
"ttl": [
{
"attribute_name": "TimeToExist",
"enabled": true
}
],
"write_capacity": 20
},
{
"arn": "arn:aws:dynamodb:us-east-1:526954929923:table/example",
"attribute": [
{
"name": "TestTableHashKey",
"type": "S"
}
],
"billing_mode": "PAY_PER_REQUEST",
"global_secondary_index": [],
"hash_key": "TestTableHashKey",
"id": "example",
"local_secondary_index": [],
"name": "example",
"point_in_time_recovery": [
{
"enabled": false
}
],
"range_key": null,
"read_capacity": 0,
"replica": [
{
"region_name": "us-east-2"
}
],
"server_side_encryption": [],
"stream_arn": "arn:aws:dynamodb:us-east-1:526954929923:table/example/stream/2021-02-11T09:50:20.750",
"stream_enabled": true,
"stream_label": "2021-02-11T09:50:20.750",
"stream_view_type": "NEW_AND_OLD_IMAGES",
"tags": {},
"timeouts": {
"create": null,
"delete": null,
"update": null
},
"ttl": [
{
"attribute_name": "",
"enabled": false
}
],
"write_capacity": 0
}
]

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,14 @@ package remote
import (
"fmt"
"strings"
"github.com/sirupsen/logrus"
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/cloudskiff/driftctl/pkg/alerter"
"github.com/sirupsen/logrus"
)
func HandleResourceEnumerationError(err error, alertr *alerter.Alerter) error {
@ -21,7 +23,7 @@ func HandleResourceEnumerationError(err error, alertr *alerter.Alerter) error {
return err
}
if reqerr.StatusCode() == 403 {
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.Debugf(message)
alertr.SendAlert(listError.SupplierType(), alerter.Alert{

View File

@ -23,20 +23,26 @@ func TestHandleListAwsError(t *testing.T) {
wantErr bool
}{
{
name: "Handled error",
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 403, ""), resourceaws.AwsVpcResourceType),
name: "Handled error 403",
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}}},
wantErr: false,
},
{
name: "Handled error AccessDenied",
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}}},
wantErr: false,
},
{
name: "Not Handled error code",
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(nil, 404, ""), resourceaws.AwsVpcResourceType),
err: remoteerror.NewResourceEnumerationError(awserr.NewRequestFailure(awserr.New("", "", errors.New("")), 404, ""), resourceaws.AwsVpcResourceType),
wantAlerts: map[string][]alerter.Alert{},
wantErr: true,
},
{
name: "Not Handled supplier error",
err: remoteerror.NewSupplierError(awserr.NewRequestFailure(nil, 403, ""), map[string]string{}, resourceaws.AwsVpcResourceType),
err: remoteerror.NewSupplierError(awserr.NewRequestFailure(awserr.New("", "", errors.New("")), 403, ""), map[string]string{}, resourceaws.AwsVpcResourceType),
wantAlerts: map[string][]alerter.Alert{},
wantErr: true,
},

View File

@ -0,0 +1,66 @@
// GENERATED, DO NOT EDIT THIS FILE
package aws
const AwsDynamodbTableResourceType = "aws_dynamodb_table"
type AwsDynamodbTable struct {
Arn *string `cty:"arn" computed:"true"`
BillingMode *string `cty:"billing_mode"`
HashKey *string `cty:"hash_key"`
Id string `cty:"id" computed:"true"`
Name *string `cty:"name"`
RangeKey *string `cty:"range_key"`
ReadCapacity *int `cty:"read_capacity"`
StreamArn *string `cty:"stream_arn" computed:"true"`
StreamEnabled *bool `cty:"stream_enabled"`
StreamLabel *string `cty:"stream_label" computed:"true"`
StreamViewType *string `cty:"stream_view_type" computed:"true"`
Tags map[string]string `cty:"tags"`
WriteCapacity *int `cty:"write_capacity"`
Attribute *[]struct {
Name *string `cty:"name"`
Type *string `cty:"type"`
} `cty:"attribute"`
GlobalSecondaryIndex *[]struct {
HashKey *string `cty:"hash_key"`
Name *string `cty:"name"`
NonKeyAttributes []string `cty:"non_key_attributes"`
ProjectionType *string `cty:"projection_type"`
RangeKey *string `cty:"range_key"`
ReadCapacity *int `cty:"read_capacity"`
WriteCapacity *int `cty:"write_capacity"`
} `cty:"global_secondary_index"`
LocalSecondaryIndex *[]struct {
Name *string `cty:"name"`
NonKeyAttributes []string `cty:"non_key_attributes"`
ProjectionType *string `cty:"projection_type"`
RangeKey *string `cty:"range_key"`
} `cty:"local_secondary_index"`
PointInTimeRecovery *[]struct {
Enabled *bool `cty:"enabled"`
} `cty:"point_in_time_recovery"`
Replica *[]struct {
RegionName *string `cty:"region_name"`
} `cty:"replica"`
ServerSideEncryption *[]struct {
Enabled *bool `cty:"enabled"`
KmsKeyArn *string `cty:"kms_key_arn" computed:"true"`
} `cty:"server_side_encryption"`
Timeouts *struct {
Create *string `cty:"create"`
Delete *string `cty:"delete"`
Update *string `cty:"update"`
} `cty:"timeouts" diff:"-"`
Ttl *[]struct {
AttributeName *string `cty:"attribute_name"`
Enabled *bool `cty:"enabled"`
} `cty:"ttl"`
}
func (r *AwsDynamodbTable) TerraformId() string {
return r.Id
}
func (r *AwsDynamodbTable) TerraformType() string {
return AwsDynamodbTableResourceType
}

View File

@ -0,0 +1,28 @@
package aws_test
import (
"testing"
"github.com/cloudskiff/driftctl/test/acceptance"
)
func TestAcc_AwsDynamoDBTable(t *testing.T) {
acceptance.Run(t, acceptance.AccTestCase{
Path: "./testdata/acc/aws_dynamodb_table",
Args: []string{"scan", "--filter", "Type=='aws_dynamodb_table'"},
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)
},
},
},
})
}

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 DynamoDBTableDeserializer struct {
}
func NewDynamoDBTableDeserializer() *DynamoDBTableDeserializer {
return &DynamoDBTableDeserializer{}
}
func (s *DynamoDBTableDeserializer) HandledType() resource.ResourceType {
return resourceaws.AwsDynamodbTableResourceType
}
func (s DynamoDBTableDeserializer) Deserialize(rawList []cty.Value) ([]resource.Resource, error) {
resources := make([]resource.Resource, 0)
for _, rawResource := range rawList {
rawResource := rawResource
resource, err := decodeDynamoDBTable(&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 decodeDynamoDBTable(raw *cty.Value) (*resourceaws.AwsDynamodbTable, error) {
var decoded resourceaws.AwsDynamodbTable
if err := gocty.FromCtyValue(*raw, &decoded); err != nil {
return nil, err
}
return &decoded, nil
}

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.27.0"
hashes = [
"h1:ccxtk7jAtmBPvAEXswOEYJcyp5jTD9QlQeg8GEzYmxQ=",
"zh:2986eb5a1ffbb0336c6390aad533b62efc832aa8aa5460d523e1f2daa4f42f79",
"zh:825317cdb80860833125a856c0befc877cba22d41c631c5a7ca22400693d4356",
"zh:a47aad668cc74058f508c56c5407cd715dbb9b6389aa68d37543e897895db43f",
"zh:c0011502d0eb4637918127c3987a8cc07a015ea00f74f4956fd111c736286a4d",
"zh:d5088ab51043bb2239132f4ed3760292b6aa4f7296232e4b8017f8c5c34f051a",
"zh:d893658e983eb17a23a8124c79a910cc729cb1d751d5509b8e756101c828ad91",
"zh:dcc4384ee79ea9492c87eb01e664f7f6b1f1d156471476f30b28336c9d9a4aec",
"zh:e4abfaf013f31791cd029af7b6f989f73e3efca28fe2917057b428d051c4085f",
"zh:f2a4d9446d23afe2a42421e7d5f902d34451fb31b7787b5e3aef95c08fec5ced",
"zh:f54a6af10b077db9dc11556c27f59ba5c60e1b2ba96fe3aa9cd90d8c67d980f6",
]
}

View File

@ -0,0 +1,64 @@
provider "aws" {
region = "us-east-1"
}
resource "aws_dynamodb_table" "basic-dynamodb-table" {
name = "GameScores"
billing_mode = "PROVISIONED"
read_capacity = 20
write_capacity = 20
hash_key = "UserId"
range_key = "GameTitle"
attribute {
name = "UserId"
type = "S"
}
attribute {
name = "GameTitle"
type = "S"
}
attribute {
name = "TopScore"
type = "N"
}
ttl {
attribute_name = "TimeToExist"
enabled = false
}
global_secondary_index {
name = "GameTitleIndex"
hash_key = "GameTitle"
range_key = "TopScore"
write_capacity = 10
read_capacity = 10
projection_type = "INCLUDE"
non_key_attributes = ["UserId"]
}
tags = {
Name = "dynamodb-table-1"
Environment = "production"
}
}
resource "aws_dynamodb_table" "example" {
name = "example"
hash_key = "TestTableHashKey"
billing_mode = "PAY_PER_REQUEST"
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES"
attribute {
name = "TestTableHashKey"
type = "S"
}
replica {
region_name = "us-east-2"
}
}