Merge pull request #897 from cloudskiff/add_state_source_in_resources

Add resource source for terraform state
main
Elie 2021-07-30 17:48:24 +02:00 committed by GitHub
commit 261bf01444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 172003 additions and 17 deletions

View File

@ -1,7 +1,23 @@
package config package config
import "fmt"
type SupplierConfig struct { type SupplierConfig struct {
Key string Key string
Backend string Backend string
Path string Path string
} }
func (c *SupplierConfig) String() string {
str := c.Key
if c.Backend != "" {
str += fmt.Sprintf("+%s", c.Backend)
}
if str != "" {
str += "://"
}
if c.Path != "" {
str += c.Path
}
return str
}

View File

@ -0,0 +1,42 @@
package config
import "testing"
func TestSupplierConfig_String(t *testing.T) {
tests := []struct {
name string
config SupplierConfig
want string
}{
{
name: "test with empty config",
config: SupplierConfig{},
want: "",
},
{
name: "test with empty path",
config: SupplierConfig{
Key: "tfstate",
Backend: "s3",
Path: "",
},
want: "tfstate+s3://",
},
{
name: "test valid config",
config: SupplierConfig{
Key: "tfstate",
Backend: "s3",
Path: "my-bucket/terraform.tfstate",
},
want: "tfstate+s3://my-bucket/terraform.tfstate",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.config.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -25,6 +25,11 @@ import (
const TerraformStateReaderSupplier = "tfstate" const TerraformStateReaderSupplier = "tfstate"
type decodedRes struct {
source resource.Source
val cty.Value
}
type TerraformStateReader struct { type TerraformStateReader struct {
library *terraform.ProviderLibrary library *terraform.ProviderLibrary
config config.SupplierConfig config config.SupplierConfig
@ -50,7 +55,7 @@ func NewReader(config config.SupplierConfig, library *terraform.ProviderLibrary,
return &reader, nil return &reader, nil
} }
func (r *TerraformStateReader) retrieve() (map[string][]cty.Value, error) { func (r *TerraformStateReader) retrieve() (map[string][]decodedRes, error) {
b, err := backend.GetBackend(r.config, r.backendOptions) b, err := backend.GetBackend(r.config, r.backendOptions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -63,7 +68,7 @@ func (r *TerraformStateReader) retrieve() (map[string][]cty.Value, error) {
return nil, err return nil, err
} }
resMap := make(map[string][]cty.Value) resMap := make(map[string][]decodedRes)
for moduleName, module := range state.Modules { for moduleName, module := range state.Modules {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"module": moduleName, "module": moduleName,
@ -133,12 +138,14 @@ func (r *TerraformStateReader) retrieve() (map[string][]cty.Value, error) {
} }
} }
_, exists := resMap[stateRes.Addr.Resource.Type] _, exists := resMap[stateRes.Addr.Resource.Type]
val := decodedRes{
source: resource.NewTerraformStateSource(r.config.String(), moduleName, resName),
val: decodedVal.Value,
}
if !exists { if !exists {
resMap[stateRes.Addr.Resource.Type] = []cty.Value{ resMap[stateRes.Addr.Resource.Type] = []decodedRes{val}
decodedVal.Value,
}
} else { } else {
resMap[stateRes.Addr.Resource.Type] = append(resMap[stateRes.Addr.Resource.Type], decodedVal.Value) resMap[stateRes.Addr.Resource.Type] = append(resMap[stateRes.Addr.Resource.Type], val)
} }
} }
} }
@ -175,16 +182,24 @@ func (r *TerraformStateReader) convertInstance(instance *states.ResourceInstance
return instanceObj, nil return instanceObj, nil
} }
func (r *TerraformStateReader) decode(values map[string][]cty.Value) ([]resource.Resource, error) { func (r *TerraformStateReader) decode(valFromState map[string][]decodedRes) ([]resource.Resource, error) {
results := make([]resource.Resource, 0) results := make([]resource.Resource, 0)
for ty, val := range values { for ty, val := range valFromState {
decodedResources, err := r.deserializer.Deserialize(ty, val) for _, stateVal := range val {
if err != nil { res, err := r.deserializer.DeserializeOne(ty, stateVal.val)
logrus.WithField("ty", ty).Warnf("Could not read from state: %+v", err) if err != nil {
continue logrus.WithFields(logrus.Fields{
"type": ty,
"name": stateVal.source.InternalName(),
"state": stateVal.source.Source(),
}).Warnf("Could not read from state: %+v", err)
continue
}
stateResource, _ := res.(*resource.AbstractResource)
stateResource.Source = stateVal.source
results = append(results, stateResource)
} }
results = append(results, decodedResources...)
} }
return results, nil return results, nil

View File

@ -43,6 +43,52 @@ func TestReadStateInvalid(t *testing.T) {
} }
} }
// Check that resource sources are properly set
func TestTerraformStateReader_Source(t *testing.T) {
progress := &output.MockProgress{}
progress.On("Inc").Return().Times(1)
progress.On("Stop").Return().Times(1)
provider := mocks.NewMockedGoldenTFProvider("source", nil, false)
library := terraform.NewProviderLibrary()
library.AddProvider(terraform.AWS, provider)
repo := testresource.InitFakeSchemaRepository(terraform.AWS, "3.19.0")
resourceaws.InitResourcesMetadata(repo)
factory := terraform.NewTerraformResourceFactory(repo)
r := &TerraformStateReader{
config: config.SupplierConfig{
Key: "tfstate",
Path: path.Join(goldenfile.GoldenFilePath, "source", "terraform.tfstate"),
},
library: library,
progress: progress,
deserializer: resource.NewDeserializer(factory),
}
got, err := r.Resources()
assert.Nil(t, err)
assert.Len(t, got, 2)
for _, res := range got {
if res.TerraformType() == resourceaws.AwsS3BucketResourceType {
assert.Equal(t, &resource.TerraformStateSource{
State: "tfstate://test/source/terraform.tfstate",
Module: "",
Name: "bucket",
}, res.(*resource.AbstractResource).Source)
}
if res.TerraformType() == resourceaws.AwsIamUserResourceType {
assert.Equal(t, &resource.TerraformStateSource{
State: "tfstate://test/source/terraform.tfstate",
Module: "module.iam_iam-user",
Name: "this_no_pgp",
}, res.(*resource.AbstractResource).Source)
}
}
}
func TestTerraformStateReader_AWS_Resources(t *testing.T) { func TestTerraformStateReader_AWS_Resources(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,81 @@
{
"version": 4,
"terraform_version": "0.14.4",
"serial": 88,
"lineage": "dcb149dc-5e8b-bb81-e690-3980485675f5",
"outputs": {},
"resources": [
{
"module": "module.iam_iam-user",
"mode": "managed",
"type": "aws_iam_access_key",
"name": "this_no_pgp",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"index_key": 0,
"schema_version": 0,
"attributes": {
"encrypted_secret": null,
"id": "AKIA5QYBVVD2VIEMMUIQ",
"key_fingerprint": null,
"pgp_key": null,
"secret": "",
"ses_smtp_password_v4": "",
"status": "Active",
"user": "MODULE-USER"
},
"sensitive_attributes": [],
"private": "bnVsbA==",
"dependencies": [
"module.iam_iam-user.aws_iam_user.this"
]
}
]
},
{
"mode": "managed",
"type": "aws_s3_bucket",
"name": "bucket",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"acceleration_status": "",
"acl": "private",
"arn": "arn:aws:s3:::bucket-martin-test-drift",
"bucket": "bucket-martin-test-drift",
"bucket_domain_name": "bucket-martin-test-drift.s3.amazonaws.com",
"bucket_prefix": null,
"bucket_regional_domain_name": "bucket-martin-test-drift.s3.eu-west-3.amazonaws.com",
"cors_rule": [],
"force_destroy": false,
"grant": [],
"hosted_zone_id": "Z3R1K369G5AVDG",
"id": "bucket-martin-test-drift",
"lifecycle_rule": [],
"logging": [],
"object_lock_configuration": [],
"policy": null,
"region": "eu-west-3",
"replication_configuration": [],
"request_payer": "BucketOwner",
"server_side_encryption_configuration": [],
"tags": {},
"versioning": [
{
"enabled": false,
"mfa_delete": false
}
],
"website": [],
"website_domain": null,
"website_endpoint": null
},
"private": "bnVsbA=="
}
]
}
]
}

View File

@ -17,11 +17,40 @@ type Resource interface {
Schema() *Schema Schema() *Schema
} }
type Source interface {
Source() string
Namespace() string
InternalName() string
}
type TerraformStateSource struct {
State string
Module string
Name string
}
func NewTerraformStateSource(state, module, name string) *TerraformStateSource {
return &TerraformStateSource{state, module, name}
}
func (s *TerraformStateSource) Source() string {
return s.State
}
func (s *TerraformStateSource) Namespace() string {
return s.Module
}
func (s *TerraformStateSource) InternalName() string {
return s.Name
}
type AbstractResource struct { type AbstractResource struct {
Id string Id string
Type string Type string
Attrs *Attributes Attrs *Attributes
Sch *Schema `json:"-" diff:"-"` Sch *Schema `json:"-" diff:"-"`
Source Source `json:"-"`
} }
func (a *AbstractResource) Schema() *Schema { func (a *AbstractResource) Schema() *Schema {