Merge pull request #75 from cloudskiff/stronger_state_reader

Ignore unknown attributes error in state
main
Elie 2021-01-06 13:41:00 +01:00 committed by GitHub
commit e131550ec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 172089 additions and 12 deletions

View File

@ -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"
@ -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
}
@ -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 {
@ -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 {

View File

@ -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"
@ -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},

View File

@ -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
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,195 @@
{
"version": 4,
"terraform_version": "0.14.2",
"serial": 17,
"lineage": "3f0e3848-13be-8734-9b0a-54239733c408",
"outputs": {},
"resources": [
{
"mode": "data",
"type": "aws_ami",
"name": "ubuntu",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"architecture": "x86_64",
"arn": "arn:aws:ec2:eu-west-3::image/ami-0f8e5edde3a79f541",
"block_device_mappings": [
{
"device_name": "/dev/sda1",
"ebs": {
"delete_on_termination": "true",
"encrypted": "false",
"iops": "0",
"snapshot_id": "snap-0185f1217f4670aa2",
"volume_size": "8",
"volume_type": "gp2"
},
"no_device": "",
"virtual_name": ""
},
{
"device_name": "/dev/sdb",
"ebs": {},
"no_device": "",
"virtual_name": "ephemeral0"
},
{
"device_name": "/dev/sdc",
"ebs": {},
"no_device": "",
"virtual_name": "ephemeral1"
}
],
"creation_date": "2021-01-06T01:26:13.000Z",
"description": "Canonical, Ubuntu, 20.04 LTS, amd64 focal image build on 2021-01-05",
"executable_users": null,
"filter": [
{
"name": "name",
"values": [
"ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
]
},
{
"name": "virtualization-type",
"values": [
"hvm"
]
}
],
"hypervisor": "xen",
"id": "ami-0f8e5edde3a79f541",
"image_id": "ami-0f8e5edde3a79f541",
"image_location": "099720109477/ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210105",
"image_owner_alias": null,
"image_type": "machine",
"kernel_id": null,
"most_recent": true,
"name": "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210105",
"name_regex": null,
"owner_id": "099720109477",
"owners": [
"099720109477"
],
"platform": null,
"product_codes": [],
"public": true,
"ramdisk_id": null,
"root_device_name": "/dev/sda1",
"root_device_type": "ebs",
"root_snapshot_id": "snap-0185f1217f4670aa2",
"sriov_net_support": "simple",
"state": "available",
"state_reason": {
"code": "UNSET",
"message": "UNSET"
},
"tags": {},
"virtualization_type": "hvm"
},
"sensitive_attributes": []
}
]
},
{
"mode": "managed",
"type": "aws_instance",
"name": "web",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"unsupported_attribute": "foobar",
"ami": "ami-0f8e5edde3a79f541",
"arn": "arn:aws:ec2:eu-west-3:929327065333:instance/i-002c7d44410fee60e",
"associate_public_ip_address": true,
"availability_zone": "eu-west-3a",
"cpu_core_count": 1,
"cpu_threads_per_core": 2,
"credit_specification": [
{
"cpu_credits": "unlimited"
}
],
"disable_api_termination": false,
"ebs_block_device": [],
"ebs_optimized": false,
"enclave_options": [
{
"enabled": false
}
],
"ephemeral_block_device": [],
"get_password_data": false,
"hibernation": false,
"host_id": null,
"iam_instance_profile": "",
"id": "i-002c7d44410fee60e",
"instance_initiated_shutdown_behavior": null,
"instance_state": "running",
"instance_type": "t3.micro",
"ipv6_address_count": 0,
"ipv6_addresses": [],
"key_name": "",
"metadata_options": [
{
"http_endpoint": "enabled",
"http_put_response_hop_limit": 1,
"http_tokens": "optional"
}
],
"monitoring": false,
"network_interface": [],
"outpost_arn": "",
"password_data": "",
"placement_group": "",
"primary_network_interface_id": "eni-004ddff1c7a130f8b",
"private_dns": "ip-172-31-11-192.eu-west-3.compute.internal",
"private_ip": "172.31.11.192",
"public_dns": "ec2-15-188-23-233.eu-west-3.compute.amazonaws.com",
"public_ip": "15.188.23.233",
"root_block_device": [
{
"delete_on_termination": true,
"device_name": "/dev/sda1",
"encrypted": false,
"iops": 100,
"kms_key_id": "",
"throughput": 0,
"volume_id": "vol-08b1deab4881e2b00",
"volume_size": 8,
"volume_type": "gp2"
}
],
"secondary_private_ips": [],
"security_groups": [
"default"
],
"source_dest_check": true,
"subnet_id": "subnet-63c5f90a",
"tags": {
"Name": "HelloWorld"
},
"tenancy": "default",
"timeouts": null,
"user_data": null,
"user_data_base64": null,
"volume_tags": {},
"vpc_security_group_ids": [
"sg-a74815c8"
]
},
"sensitive_attributes": [],
"private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6MTIwMDAwMDAwMDAwMCwidXBkYXRlIjo2MDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjEifQ==",
"dependencies": [
"data.aws_ami.ubuntu"
]
}
]
}
]
}