4.7 KiB
Adding a new resource type to driftctl
1 Defining the resource
First step is to implement a new resource will be to define a go struct representing all fields that needs to be monitored for this kind of resource. You can find example in already implemented resource like aws.S3Bucket
type AwsS3Bucket struct {
AccelerationStatus *string `cty:"acceleration_status"`
Acl *string `cty:"acl" diff:"-"`
Arn *string `cty:"arn"`
Bucket *string `cty:"bucket"`
...
Your new type will need to implement resource.Resource
interface in order for driftctl to retrieve its type and a unique identifier for it.
type Resource interface {
TerraformId() string
TerraformType() string
}
Some resources are read differently by the terraform state reader and the supplier. You can optionally implement resource.NormalizedResource
to add a normalization step before the comparison is made.
type NormalizedResource interface {
NormalizeForState() (Resource, error)
NormalizeForProvider() (Resource, error)
}
For example S3Bucket policy is encoded in json but the formatting (newline and tabs) differs when read using the state reader. S3Bucket implements resource.NormalizedResource
:
func (s S3Bucket) NormalizeForState() (resource.Resource, error) {
err := normalizePolicy(&s)
return &s, err
}
func (s S3Bucket) NormalizeForProvider() (resource.Resource, error) {
err := normalizePolicy(&s)
return &s, err
}
func normalizePolicy(s *S3Bucket) error {
if s.Policy.Policy != nil {
jsonString, err := structure.NormalizeJsonString(*s.Policy.Policy)
if err != nil {
return err
}
s.Policy.Policy = &jsonString
}
return nil
}
You can implement different normalization for the state representation and the supplier one.
2 Supplier and Deserializer
Then you will have to implement two interfaces:
resource.supplier
is used to read resources list. It will call the cloud provider sdk to get the list of resources, and the terraform provider to get the details for each of these resources.remote.CTYDeserializer
is used to transform terraform cty output into your resource
Supplier
This is used to read resources list. It will call the cloud provider sdk to get the list of resources, and the terraform provider to get the details for each of these resources. You can use an already implemented resource as example. Supplier constructor could use these arguments:
- an instance of
ParallelRunner
that you will use to parallelize your call to the supplier:
results := make(map[string][]cty.Value)
for _, bucket := range response.Buckets {
b := *bucket
s.runner.Run(func() error {
return s.readBucket(b, results)
})
}
if err := s.runner.Wait(); err != nil {
return nil, err
}
- an instance of
terraform.ResourceReader
that you can use to read resource using the supplier:
s3Bucket, err := s.reader.ReadResource(aws.AwsS3BucketResourceType, name)
if err != nil {
logrus.Warnf("Error reading bucket %s[%s]: %+v", name, aws.AwsS3BucketResourceType, err)
return err
}
appendValueIntoMap(results, aws.AwsS3BucketResourceType, s3Bucket)
- an instance of the cloud provider sdk that you will use to retrieve resources list.
Deserializer
The deserializer is used when reading resource from the terraform provider or from the state.
The interface contains a Deserialize(values []cty.Value) ([]resource.Resource, error)
method that you'll implement.
You should then deserialize the obtained cty values into your resource and return the list.
Example: aws_s3_bucket_deserializer.go
3 Adding your resource
There are two files you are going to edit to make driftctl aware of your new resource.
For the state reader you will need to add your CTYDeserializer
implementation into iac/deserializers.go
Just add an instance in the list:
func Deserializers() []remote.CTYDeserializer {
return []remote.CTYDeserializer{
aws.NewS3BucketDeserializer(),
...
}
}
Then in the cloud provider's init file (e.g. in remote/aws/init.go
) add your new implementation for resource.Supplier
:
func Init() error {
provider, err := NewTerraFormProvider()
if err != nil {
return err
}
terraform.AddProvider(terraform.AWS, provider)
resource.AddSupplier(NewS3BucketSupplier(provider.Runner().SubRunner(), s3.New(provider.session)))
...
}
Don't forget to add unit tests after adding a new resource. You can also add acceptance test if you think it makes sense.