Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore unknown attributes error in state #75

Merged
merged 1 commit into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 59 additions & 10 deletions pkg/iac/terraform/state/terraform_state_reader.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
package state

import (
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
"github.com/hashicorp/terraform/addrs"

"github.com/cloudskiff/driftctl/pkg/iac"
"github.com/cloudskiff/driftctl/pkg/iac/config"
"github.com/cloudskiff/driftctl/pkg/iac/terraform/state/backend"
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/terraform"

"github.com/hashicorp/terraform/states/statefile"

"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statefile"
"github.com/sirupsen/logrus"
"github.com/zclconf/go-cty/cty"
ctyconvert "github.com/zclconf/go-cty/cty/convert"
ctyjson "github.com/zclconf/go-cty/cty/json"
)

const TerraformStateReaderSupplier = "tfstate"
Expand Down Expand Up @@ -54,11 +53,13 @@ func (r *TerraformStateReader) retrieve() (map[string][]cty.Value, error) {
stateResources := state.RootModule().Resources
resMap := make(map[string][]cty.Value)
for _, stateRes := range stateResources {
resName := stateRes.Addr.Resource.Name
resType := stateRes.Addr.Resource.Type
if stateRes.Addr.Resource.Mode != addrs.ManagedResourceMode {
logrus.WithFields(logrus.Fields{
"mode": stateRes.Addr.Resource.Mode,
"name": stateRes.Addr.Resource.Name,
"type": stateRes.Addr.Resource.Type,
"name": resName,
"type": resType,
}).Debug("Skipping state entry as it is not a managed resource")
continue
}
Expand All @@ -74,8 +75,28 @@ func (r *TerraformStateReader) retrieve() (map[string][]cty.Value, error) {
for _, instance := range stateRes.Instances {
decodedVal, err := instance.Current.Decode(schema.Block.ImpliedType())
if err != nil {
logrus.Error(err)
continue
// Try to do a manual type conversion if we got a path error
// It will allow driftctl to read state generated with a superior version of provider
// than the actually supported one
// by ignoring new fields
_, isPathError := err.(cty.PathError)
if isPathError {
logrus.WithFields(logrus.Fields{
"name": resName,
"type": resType,
"err": err.Error(),
}).Debug("Got a cty path error when deserializing state")

decodedVal, err = r.convertInstance(instance.Current, schema.Block.ImpliedType())
}

if err != nil {
logrus.WithFields(logrus.Fields{
"name": resName,
"type": resType,
}).Error("Unable to decode resource from state")
return nil, err
}
}
_, exists := resMap[stateRes.Addr.Resource.Type]
if !exists {
Expand All @@ -91,6 +112,34 @@ func (r *TerraformStateReader) retrieve() (map[string][]cty.Value, error) {
return resMap, nil
}

func (r *TerraformStateReader) convertInstance(instance *states.ResourceInstanceObjectSrc, ty cty.Type) (*states.ResourceInstanceObject, error) {
inputType, err := ctyjson.ImpliedType(instance.AttrsJSON)
if err != nil {
return nil, err
}
input, err := ctyjson.Unmarshal(instance.AttrsJSON, inputType)
if err != nil {
return nil, err
}

convertedVal, err := ctyconvert.Convert(input, ty)
if err != nil {
return nil, err
}

instanceObj := &states.ResourceInstanceObject{
Value: convertedVal,
Status: instance.Status,
Dependencies: instance.Dependencies,
Private: instance.Private,
CreateBeforeDestroy: instance.CreateBeforeDestroy,
}

logrus.Debug("Successfully converted resource")

return instanceObj, nil
}

func (r *TerraformStateReader) decode(values map[string][]cty.Value) ([]resource.Resource, error) {
results := make([]resource.Resource, 0)
for _, deserializer := range r.deserializers {
Expand Down
4 changes: 2 additions & 2 deletions pkg/iac/terraform/state/terraform_state_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import (
"strings"
"testing"

"github.com/cloudskiff/driftctl/test/goldenfile"

"github.com/cloudskiff/driftctl/pkg/iac"
"github.com/cloudskiff/driftctl/pkg/iac/terraform/state/backend"
"github.com/cloudskiff/driftctl/pkg/remote/aws"
"github.com/cloudskiff/driftctl/pkg/resource"
"github.com/cloudskiff/driftctl/pkg/terraform"
"github.com/cloudskiff/driftctl/test/goldenfile"
"github.com/cloudskiff/driftctl/test/mocks"

"github.com/r3labs/diff/v2"
Expand Down Expand Up @@ -50,6 +49,7 @@ func TestTerraformStateReader_Resources(t *testing.T) {
{name: "RDS DB instance", dirName: "db_instance", wantErr: false},
{name: "RDS DB Subnet group", dirName: "db_subnet_group", wantErr: false},
{name: "Lambda function", dirName: "lambda_function", wantErr: false},
{name: "unsupported attribute", dirName: "unsupported_attribute", wantErr: false},
{name: "Unsupported provider", dirName: "unsupported_provider", wantErr: false},
{name: "EC2 instance", dirName: "ec2_instance", wantErr: false},
{name: "EC2 key pair", dirName: "ec2_key_pair", wantErr: false},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
[
{
"Ami": "ami-0f8e5edde3a79f541",
"Arn": "arn:aws:ec2:eu-west-3:929327065333:instance/i-002c7d44410fee60e",
"AssociatePublicIpAddress": true,
"AvailabilityZone": "eu-west-3a",
"CpuCoreCount": 1,
"CpuThreadsPerCore": 2,
"DisableApiTermination": false,
"EbsOptimized": false,
"GetPasswordData": false,
"Hibernation": false,
"HostId": null,
"IamInstanceProfile": "",
"Id": "i-002c7d44410fee60e",
"InstanceInitiatedShutdownBehavior": null,
"InstanceState": "running",
"InstanceType": "t3.micro",
"Ipv6AddressCount": 0,
"Ipv6Addresses": [],
"KeyName": "",
"Monitoring": false,
"OutpostArn": "",
"PasswordData": "",
"PlacementGroup": "",
"PrimaryNetworkInterfaceId": "eni-004ddff1c7a130f8b",
"PrivateDns": "ip-172-31-11-192.eu-west-3.compute.internal",
"PrivateIp": "172.31.11.192",
"PublicDns": "ec2-15-188-23-233.eu-west-3.compute.amazonaws.com",
"PublicIp": "15.188.23.233",
"SecondaryPrivateIps": [],
"SecurityGroups": [
"default"
],
"SourceDestCheck": true,
"SubnetId": "subnet-63c5f90a",
"Tags": {
"Name": "HelloWorld"
},
"Tenancy": "default",
"UserData": null,
"UserDataBase64": null,
"VolumeTags": {},
"VpcSecurityGroupIds": [
"sg-a74815c8"
],
"CreditSpecification": [
{
"CpuCredits": "unlimited"
}
],
"EbsBlockDevice": null,
"EphemeralBlockDevice": [],
"MetadataOptions": [
{
"HttpEndpoint": "enabled",
"HttpPutResponseHopLimit": 1,
"HttpTokens": "optional"
}
],
"NetworkInterface": [],
"RootBlockDevice": [
{
"DeleteOnTermination": true,
"DeviceName": "/dev/sda1",
"Encrypted": false,
"Iops": 100,
"KmsKeyId": "",
"VolumeId": "vol-08b1deab4881e2b00",
"VolumeSize": 8,
"VolumeType": "gp2"
}
],
"Timeouts": null
}
]
Loading