124 lines
3.4 KiB
Go
124 lines
3.4 KiB
Go
package terraform
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
gojson "encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/terraform/providers"
|
|
"github.com/pkg/errors"
|
|
"github.com/snyk/driftctl/pkg/terraform"
|
|
"github.com/snyk/driftctl/test/goldenfile"
|
|
"github.com/snyk/driftctl/test/mocks"
|
|
"github.com/snyk/driftctl/test/schemas"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
type FakeTerraformProvider struct {
|
|
realProvider terraform.TerraformProvider
|
|
shouldUpdate bool
|
|
response string
|
|
}
|
|
|
|
func NewFakeTerraformProvider(realProvider terraform.TerraformProvider) *FakeTerraformProvider {
|
|
return &FakeTerraformProvider{realProvider: realProvider}
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) ShouldUpdate() {
|
|
p.shouldUpdate = true
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) Schema() map[string]providers.Schema {
|
|
return p.readSchema()
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) WithResponse(response string) *FakeTerraformProvider {
|
|
p.response = response
|
|
return p
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) ReadResource(args terraform.ReadResourceArgs) (*cty.Value, error) {
|
|
if p.response == "" {
|
|
return nil, errors.New("WithResponse should be called before ReadResource to specify a directory to fetch fake response")
|
|
}
|
|
if p.shouldUpdate {
|
|
readResource, err := p.realProvider.ReadResource(args)
|
|
p.writeResource(args, readResource, err)
|
|
return readResource, err
|
|
}
|
|
|
|
return p.readResource(args)
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) readSchema() map[string]providers.Schema {
|
|
schema, err := schemas.ReadTestSchema(p.realProvider.Name(), p.realProvider.Version())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return schema
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) writeResource(args terraform.ReadResourceArgs, readResource *cty.Value, err error) {
|
|
var readRes = mocks.ReadResource{
|
|
Value: readResource,
|
|
Err: err,
|
|
}
|
|
|
|
marshalled, err := gojson.Marshal(&readRes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fileName := p.getFileName(args)
|
|
goldenfile.WriteFile(p.response, marshalled, fileName)
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) readResource(args terraform.ReadResourceArgs) (*cty.Value, error) {
|
|
fileName := p.getFileName(args)
|
|
content := goldenfile.ReadFile(p.response, fileName)
|
|
var readRes mocks.ReadResource
|
|
if err := gojson.Unmarshal(content, &readRes); err != nil {
|
|
panic(err)
|
|
}
|
|
return readRes.Value, readRes.Err
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) getFileName(args terraform.ReadResourceArgs) string {
|
|
suffix := ""
|
|
keys := make([]string, 0, len(args.Attributes))
|
|
for k := range args.Attributes {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
for _, k := range keys {
|
|
suffix = fmt.Sprintf("%s-%s", suffix, args.Attributes[k])
|
|
}
|
|
|
|
// ext4 and many other filesystems has a maximum filename length of 255 bytes
|
|
// See https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
|
|
// Solution: we create a SHA1 hash of the filename so the length stay constant
|
|
// We should do that no matter the length, but it requires to regenerate every single file
|
|
// TODO: Use SHA1 filenames for all resource golden files
|
|
resourceUID := fmt.Sprintf("%s-%s%s", args.Ty, args.ID, suffix)
|
|
if len(resourceUID) > 239 {
|
|
h := sha1.New()
|
|
_, _ = io.WriteString(h, resourceUID)
|
|
resourceUID = fmt.Sprintf("%x", h.Sum(nil))
|
|
}
|
|
|
|
fileName := fmt.Sprintf("%s.res.golden.json", resourceUID)
|
|
return fileName
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) Cleanup() {}
|
|
|
|
func (p *FakeTerraformProvider) Name() string {
|
|
return p.realProvider.Name()
|
|
}
|
|
|
|
func (p *FakeTerraformProvider) Version() string {
|
|
return p.realProvider.Version()
|
|
}
|