Repository: cycloidio/terracost
Pull request #68 (2021-12-29 09:11:24)

 aws: add support for elasticache resource
add support for

  * aws_eip
  * aws_aws_elasticache_cluster
  * aws_elasticache_replication_group


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a940aec..4df2c60 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 ## [Unreleased]
 
+### Added
+
+- AWS support for `aws_eip`, `aws_aws_elasticache_cluster`, `aws_elasticache_replication_group`
+  ([Pull #68](https://github.com/cycloidio/terracost/pull/68))
+
+### Changed
+
+- AWS ingester change to keep consistency between name from CSV
+  ([Pull #68](https://github.com/cycloidio/terracost/pull/68))
+
 ## [0.5.0] _2022-01-18_
 
 ### Added
diff --git a/README.md b/README.md
index 2e2b0b5..936acc3 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,10 @@ file, err := os.Open("path/to/tfplan.json")
 plan, err := terracost.EstimateTerraformPlan(ctx, backend, file)
 
 for _, res := range plan.ResourceDifferences() {
-    fmt.Printf("%s: %s -> %s\n", res.Address, res.PriorCost().String(), res.PlannedCost().String())
+  priorCost, err := res.PriorCost()
+  plannedCost, err := res.PlannedCost()
+
+  fmt.Printf("%s: %s -> %s\n", res.Address, priorCost, plannedCost)
 }
 ```
 
diff --git a/aws/field/field.go b/aws/field/field.go
index 14b7af8..4bf0eeb 100644
--- a/aws/field/field.go
+++ b/aws/field/field.go
@@ -7,36 +7,34 @@ type Field uint8
 
 // List of fields used by the AWS pricing offer file (CSV).
 const (
-	SKU                Field = iota // SKU
-	OfferTermCode                   // OfferTermCode
-	RateCode                        // RateCode
-	PurchaseOption                  // TermType
-	PriceDescription                // PriceDescription
-	EffectiveDate                   // EffectiveDate
-	StartingRange                   // StartingRange
-	EndingRange                     // EndingRange
-	Unit                            // Unit
-	PricePerUnit                    // PricePerUnit
-	Currency                        // Currency
-	TermLength                      // LeaseContractLength
-	TermPurchaseOption              // PurchaseOption
-	TermOfferingClass               // OfferingClass
-	ProductFamily                   // Product Family
-	ServiceCode                     // serviceCode
-	Location                        // Location
-	InstanceType                    // Instance Type
-	StorageMedia                    // Storage Media
-	VolumeType                      // Volume Type
-	Tenancy                         // Tenancy
-	OperatingSystem                 // Operating System
-	UsageType                       // usageType
-	CapacityStatus                  // CapacityStatus
-	PreInstalledSW                  // Pre Installed S/W
-	VolumeAPIName                   // Volume API Name
+	///// Product Attributes /////
+	SKU             Field = iota // SKU
+	CapacityStatus               // CapacityStatus
+	Group                        // Group
+	InstanceType                 // Instance Type
+	Location                     // Location
+	OperatingSystem              // Operating System
+	PreInstalledSW               // Pre Installed S/W
+	ProductFamily                // Product Family
+	ServiceCode                  // serviceCode
+	Tenancy                      // Tenancy
+	UsageType                    // usageType
+	VolumeAPIName                // Volume API Name
+	VolumeType                   // Volume Type
+
+	// ElastiCache
+	CacheEngine // Cache Engine
 
 	// RDS fields
 	DatabaseEngine   // Database Engine
 	DatabaseEdition  // Database Edition
-	LicenseModel     // License Model
 	DeploymentOption // Deployment Option
+	LicenseModel     // License Model
+
+	///// Price Attributes /////
+	Currency      // Currency
+	PricePerUnit  // PricePerUnit
+	StartingRange // StartingRange
+	TermType      // TermType
+	Unit          // Unit
 )
diff --git a/aws/field/field_gen.go b/aws/field/field_gen.go
index 03c887d..6c43dd9 100644
--- a/aws/field/field_gen.go
+++ b/aws/field/field_gen.go
@@ -7,11 +7,11 @@ import (
 	"strings"
 )
 
-const _FieldName = "SKUOfferTermCodeRateCodeTermTypePriceDescriptionEffectiveDateStartingRangeEndingRangeUnitPricePerUnitCurrencyLeaseContractLengthPurchaseOptionOfferingClassProduct FamilyserviceCodeLocationInstance TypeStorage MediaVolume TypeTenancyOperating SystemusageTypeCapacityStatusPre Installed S/WVolume API NameDatabase EngineDatabase EditionLicense ModelDeployment Option"
+const _FieldName = "SKUCapacityStatusGroupInstance TypeLocationOperating SystemPre Installed S/WProduct FamilyserviceCodeTenancyusageTypeVolume API NameVolume TypeCache EngineDatabase EngineDatabase EditionDeployment OptionLicense ModelCurrencyPricePerUnitStartingRangeTermTypeUnit"
 
-var _FieldIndex = [...]uint16{0, 3, 16, 24, 32, 48, 61, 74, 85, 89, 101, 109, 128, 142, 155, 169, 180, 188, 201, 214, 225, 232, 248, 257, 271, 288, 303, 318, 334, 347, 364}
+var _FieldIndex = [...]uint16{0, 3, 17, 22, 35, 43, 59, 76, 90, 101, 108, 117, 132, 143, 155, 170, 186, 203, 216, 224, 236, 249, 257, 261}
 
-const _FieldLowerName = "skuoffertermcoderatecodetermtypepricedescriptioneffectivedatestartingrangeendingrangeunitpriceperunitcurrencyleasecontractlengthpurchaseoptionofferingclassproduct familyservicecodelocationinstance typestorage mediavolume typetenancyoperating systemusagetypecapacitystatuspre installed s/wvolume api namedatabase enginedatabase editionlicense modeldeployment option"
+const _FieldLowerName = "skucapacitystatusgroupinstance typelocationoperating systempre installed s/wproduct familyservicecodetenancyusagetypevolume api namevolume typecache enginedatabase enginedatabase editiondeployment optionlicense modelcurrencypriceperunitstartingrangetermtypeunit"
 
 func (i Field) String() string {
 	if i >= Field(len(_FieldIndex)-1) {
@@ -25,133 +25,105 @@ func (i Field) String() string {
 func _FieldNoOp() {
 	var x [1]struct{}
 	_ = x[SKU-(0)]
-	_ = x[OfferTermCode-(1)]
-	_ = x[RateCode-(2)]
-	_ = x[PurchaseOption-(3)]
-	_ = x[PriceDescription-(4)]
-	_ = x[EffectiveDate-(5)]
-	_ = x[StartingRange-(6)]
-	_ = x[EndingRange-(7)]
-	_ = x[Unit-(8)]
-	_ = x[PricePerUnit-(9)]
-	_ = x[Currency-(10)]
-	_ = x[TermLength-(11)]
-	_ = x[TermPurchaseOption-(12)]
-	_ = x[TermOfferingClass-(13)]
-	_ = x[ProductFamily-(14)]
-	_ = x[ServiceCode-(15)]
-	_ = x[Location-(16)]
-	_ = x[InstanceType-(17)]
-	_ = x[StorageMedia-(18)]
-	_ = x[VolumeType-(19)]
-	_ = x[Tenancy-(20)]
-	_ = x[OperatingSystem-(21)]
-	_ = x[UsageType-(22)]
-	_ = x[CapacityStatus-(23)]
-	_ = x[PreInstalledSW-(24)]
-	_ = x[VolumeAPIName-(25)]
-	_ = x[DatabaseEngine-(26)]
-	_ = x[DatabaseEdition-(27)]
-	_ = x[LicenseModel-(28)]
-	_ = x[DeploymentOption-(29)]
+	_ = x[CapacityStatus-(1)]
+	_ = x[Group-(2)]
+	_ = x[InstanceType-(3)]
+	_ = x[Location-(4)]
+	_ = x[OperatingSystem-(5)]
+	_ = x[PreInstalledSW-(6)]
+	_ = x[ProductFamily-(7)]
+	_ = x[ServiceCode-(8)]
+	_ = x[Tenancy-(9)]
+	_ = x[UsageType-(10)]
+	_ = x[VolumeAPIName-(11)]
+	_ = x[VolumeType-(12)]
+	_ = x[CacheEngine-(13)]
+	_ = x[DatabaseEngine-(14)]
+	_ = x[DatabaseEdition-(15)]
+	_ = x[DeploymentOption-(16)]
+	_ = x[LicenseModel-(17)]
+	_ = x[Currency-(18)]
+	_ = x[PricePerUnit-(19)]
+	_ = x[StartingRange-(20)]
+	_ = x[TermType-(21)]
+	_ = x[Unit-(22)]
 }
 
-var _FieldValues = []Field{SKU, OfferTermCode, RateCode, PurchaseOption, PriceDescription, EffectiveDate, StartingRange, EndingRange, Unit, PricePerUnit, Currency, TermLength, TermPurchaseOption, TermOfferingClass, ProductFamily, ServiceCode, Location, InstanceType, StorageMedia, VolumeType, Tenancy, OperatingSystem, UsageType, CapacityStatus, PreInstalledSW, VolumeAPIName, DatabaseEngine, DatabaseEdition, LicenseModel, DeploymentOption}
+var _FieldValues = []Field{SKU, CapacityStatus, Group, InstanceType, Location, OperatingSystem, PreInstalledSW, ProductFamily, ServiceCode, Tenancy, UsageType, VolumeAPIName, VolumeType, CacheEngine, DatabaseEngine, DatabaseEdition, DeploymentOption, LicenseModel, Currency, PricePerUnit, StartingRange, TermType, Unit}
 
 var _FieldNameToValueMap = map[string]Field{
 	_FieldName[0:3]:          SKU,
 	_FieldLowerName[0:3]:     SKU,
-	_FieldName[3:16]:         OfferTermCode,
-	_FieldLowerName[3:16]:    OfferTermCode,
-	_FieldName[16:24]:        RateCode,
-	_FieldLowerName[16:24]:   RateCode,
-	_FieldName[24:32]:        PurchaseOption,
-	_FieldLowerName[24:32]:   PurchaseOption,
-	_FieldName[32:48]:        PriceDescription,
-	_FieldLowerName[32:48]:   PriceDescription,
-	_FieldName[48:61]:        EffectiveDate,
-	_FieldLowerName[48:61]:   EffectiveDate,
-	_FieldName[61:74]:        StartingRange,
-	_FieldLowerName[61:74]:   StartingRange,
-	_FieldName[74:85]:        EndingRange,
-	_FieldLowerName[74:85]:   EndingRange,
-	_FieldName[85:89]:        Unit,
-	_FieldLowerName[85:89]:   Unit,
-	_FieldName[89:101]:       PricePerUnit,
-	_FieldLowerName[89:101]:  PricePerUnit,
-	_FieldName[101:109]:      Currency,
-	_FieldLowerName[101:109]: Currency,
-	_FieldName[109:128]:      TermLength,
-	_FieldLowerName[109:128]: TermLength,
-	_FieldName[128:142]:      TermPurchaseOption,
-	_FieldLowerName[128:142]: TermPurchaseOption,
-	_FieldName[142:155]:      TermOfferingClass,
-	_FieldLowerName[142:155]: TermOfferingClass,
-	_FieldName[155:169]:      ProductFamily,
-	_FieldLowerName[155:169]: ProductFamily,
-	_FieldName[169:180]:      ServiceCode,
-	_FieldLowerName[169:180]: ServiceCode,
-	_FieldName[180:188]:      Location,
-	_FieldLowerName[180:188]: Location,
-	_FieldName[188:201]:      InstanceType,
-	_FieldLowerName[188:201]: InstanceType,
-	_FieldName[201:214]:      StorageMedia,
-	_FieldLowerName[201:214]: StorageMedia,
-	_FieldName[214:225]:      VolumeType,
-	_FieldLowerName[214:225]: VolumeType,
-	_FieldName[225:232]:      Tenancy,
-	_FieldLowerName[225:232]: Tenancy,
-	_FieldName[232:248]:      OperatingSystem,
-	_FieldLowerName[232:248]: OperatingSystem,
-	_FieldName[248:257]:      UsageType,
-	_FieldLowerName[248:257]: UsageType,
-	_FieldName[257:271]:      CapacityStatus,
-	_FieldLowerName[257:271]: CapacityStatus,
-	_FieldName[271:288]:      PreInstalledSW,
-	_FieldLowerName[271:288]: PreInstalledSW,
-	_FieldName[288:303]:      VolumeAPIName,
-	_FieldLowerName[288:303]: VolumeAPIName,
-	_FieldName[303:318]:      DatabaseEngine,
-	_FieldLowerName[303:318]: DatabaseEngine,
-	_FieldName[318:334]:      DatabaseEdition,
-	_FieldLowerName[318:334]: DatabaseEdition,
-	_FieldName[334:347]:      LicenseModel,
-	_FieldLowerName[334:347]: LicenseModel,
-	_FieldName[347:364]:      DeploymentOption,
-	_FieldLowerName[347:364]: DeploymentOption,
+	_FieldName[3:17]:         CapacityStatus,
+	_FieldLowerName[3:17]:    CapacityStatus,
+	_FieldName[17:22]:        Group,
+	_FieldLowerName[17:22]:   Group,
+	_FieldName[22:35]:        InstanceType,
+	_FieldLowerName[22:35]:   InstanceType,
+	_FieldName[35:43]:        Location,
+	_FieldLowerName[35:43]:   Location,
+	_FieldName[43:59]:        OperatingSystem,
+	_FieldLowerName[43:59]:   OperatingSystem,
+	_FieldName[59:76]:        PreInstalledSW,
+	_FieldLowerName[59:76]:   PreInstalledSW,
+	_FieldName[76:90]:        ProductFamily,
+	_FieldLowerName[76:90]:   ProductFamily,
+	_FieldName[90:101]:       ServiceCode,
+	_FieldLowerName[90:101]:  ServiceCode,
+	_FieldName[101:108]:      Tenancy,
+	_FieldLowerName[101:108]: Tenancy,
+	_FieldName[108:117]:      UsageType,
+	_FieldLowerName[108:117]: UsageType,
+	_FieldName[117:132]:      VolumeAPIName,
+	_FieldLowerName[117:132]: VolumeAPIName,
+	_FieldName[132:143]:      VolumeType,
+	_FieldLowerName[132:143]: VolumeType,
+	_FieldName[143:155]:      CacheEngine,
+	_FieldLowerName[143:155]: CacheEngine,
+	_FieldName[155:170]:      DatabaseEngine,
+	_FieldLowerName[155:170]: DatabaseEngine,
+	_FieldName[170:186]:      DatabaseEdition,
+	_FieldLowerName[170:186]: DatabaseEdition,
+	_FieldName[186:203]:      DeploymentOption,
+	_FieldLowerName[186:203]: DeploymentOption,
+	_FieldName[203:216]:      LicenseModel,
+	_FieldLowerName[203:216]: LicenseModel,
+	_FieldName[216:224]:      Currency,
+	_FieldLowerName[216:224]: Currency,
+	_FieldName[224:236]:      PricePerUnit,
+	_FieldLowerName[224:236]: PricePerUnit,
+	_FieldName[236:249]:      StartingRange,
+	_FieldLowerName[236:249]: StartingRange,
+	_FieldName[249:257]:      TermType,
+	_FieldLowerName[249:257]: TermType,
+	_FieldName[257:261]:      Unit,
+	_FieldLowerName[257:261]: Unit,
 }
 
 var _FieldNames = []string{
 	_FieldName[0:3],
-	_FieldName[3:16],
-	_FieldName[16:24],
-	_FieldName[24:32],
-	_FieldName[32:48],
-	_FieldName[48:61],
-	_FieldName[61:74],
-	_FieldName[74:85],
-	_FieldName[85:89],
-	_FieldName[89:101],
-	_FieldName[101:109],
-	_FieldName[109:128],
-	_FieldName[128:142],
-	_FieldName[142:155],
-	_FieldName[155:169],
-	_FieldName[169:180],
-	_FieldName[180:188],
-	_FieldName[188:201],
-	_FieldName[201:214],
-	_FieldName[214:225],
-	_FieldName[225:232],
-	_FieldName[232:248],
-	_FieldName[248:257],
-	_FieldName[257:271],
-	_FieldName[271:288],
-	_FieldName[288:303],
-	_FieldName[303:318],
-	_FieldName[318:334],
-	_FieldName[334:347],
-	_FieldName[347:364],
+	_FieldName[3:17],
+	_FieldName[17:22],
+	_FieldName[22:35],
+	_FieldName[35:43],
+	_FieldName[43:59],
+	_FieldName[59:76],
+	_FieldName[76:90],
+	_FieldName[90:101],
+	_FieldName[101:108],
+	_FieldName[108:117],
+	_FieldName[117:132],
+	_FieldName[132:143],
+	_FieldName[143:155],
+	_FieldName[155:170],
+	_FieldName[170:186],
+	_FieldName[186:203],
+	_FieldName[203:216],
+	_FieldName[216:224],
+	_FieldName[224:236],
+	_FieldName[236:249],
+	_FieldName[249:257],
+	_FieldName[257:261],
 }
 
 // FieldString retrieves an enum value from the enum constants string name.
@@ -160,8 +132,8 @@ func FieldString(s string) (Field, error) {
 	if val, ok := _FieldNameToValueMap[s]; ok {
 		return val, nil
 	}
-
-	if val, ok := _FieldNameToValueMap[strings.ToLower(s)]; ok {
+	s = strings.ToLower(s)
+	if val, ok := _FieldNameToValueMap[s]; ok {
 		return val, nil
 	}
 	return 0, fmt.Errorf("%s does not belong to Field values", s)
diff --git a/aws/ingester.go b/aws/ingester.go
index b7c696b..c6a4060 100644
--- a/aws/ingester.go
+++ b/aws/ingester.go
@@ -187,42 +187,35 @@ func readColumnPositions(values []string) map[string]int {
 	return columns
 }
 
-// columnToAttribute is a mapping from column title to the product.Product attribute name under which the value will
+// columnProductToIngest is a mapping from column title to the product.Product attribute name under which the value will
 // be stored.
-var columnToAttribute = map[field.Field]string{
-	field.UsageType:       "usagetype",
-	field.InstanceType:    "instanceType",
-	field.OperatingSystem: "operatingSystem",
-	field.PreInstalledSW:  "preInstalledSw",
-	field.CapacityStatus:  "capacitystatus",
-	field.Tenancy:         "tenancy",
-	field.VolumeAPIName:   "volumeApiName",
-	field.VolumeType:      "volumeType",
-	field.StorageMedia:    "storageMedia",
+
+var columnProductToIngest = map[field.Field]string{
+	field.CapacityStatus:  "CapacityStatus",
+	field.Group:           "Group",
+	field.InstanceType:    "InstanceType",
+	field.OperatingSystem: "OperatingSystem",
+	field.PreInstalledSW:  "PreInstalledSW",
+	field.Tenancy:         "Tenancy",
+	field.UsageType:       "UsageType",
+	field.VolumeAPIName:   "VolumeAPIName",
+	field.VolumeType:      "VolumeType",
+
+	// ElastiCache
+	field.CacheEngine: "CacheEngine",
 
 	// RDS attributes
-	field.DeploymentOption: "deploymentOption",
-	field.LicenseModel:     "licenseModel",
-	field.DatabaseEdition:  "databaseEdition",
-	field.DatabaseEngine:   "databaseEngine",
+	field.DatabaseEngine:   "DatabaseEngine",
+	field.DatabaseEdition:  "DatabaseEdition",
+	field.DeploymentOption: "DeploymentOption",
+	field.LicenseModel:     "LicenseModel",
 }
 
-// columnToPriceAttribute is a mapping from column title to the price.Price attribute name under which the value will
+// columnPriceToIngest is a mapping from column title to the price.Price attribute name under which the value will
 // be stored.
-var columnToPriceAttribute = map[field.Field]string{
-	field.PriceDescription:   "description",
-	field.StartingRange:      "startUsageAmount",
-	field.EndingRange:        "endUsageAmount",
-	field.TermLength:         "termLength",
-	field.TermPurchaseOption: "termPurchaseOption",
-	field.TermOfferingClass:  "termOfferingClass",
-	field.EffectiveDate:      "effectiveDateStart",
-}
-
-// purchaseOptions is a mapping from the values used in the CSV file to the expected values.
-var purchaseOptions = map[string]string{
-	"OnDemand": "on_demand",
-	"Reserved": "reserved",
+var columnPriceToIngest = map[field.Field]string{
+	field.StartingRange: "StartingRange",
+	field.TermType:      "TermType",
 }
 
 func newPriceWithProduct(values map[field.Field]string) (*price.WithProduct, error) {
@@ -233,10 +226,9 @@ func newPriceWithProduct(values map[field.Field]string) (*price.WithProduct, err
 		return nil, fmt.Errorf("failed to parse PricePerUnit: %w", err)
 	}
 
-	priceAttrs := map[string]string{
-		"purchaseOption": purchaseOptions[values[field.PurchaseOption]],
-	}
-	for col, attr := range columnToPriceAttribute {
+	priceAttrs := map[string]string{}
+
+	for col, attr := range columnPriceToIngest {
 		if values[col] != "" {
 			priceAttrs[attr] = values[col]
 		}
@@ -256,7 +248,8 @@ func newPriceWithProduct(values map[field.Field]string) (*price.WithProduct, err
 
 func newProduct(values map[field.Field]string) *product.Product {
 	attributes := make(map[string]string)
-	for col, attr := range columnToAttribute {
+
+	for col, attr := range columnProductToIngest {
 		if values[col] != "" {
 			attributes[attr] = values[col]
 		}
diff --git a/aws/services.go b/aws/services.go
index 1341c39..971fa7d 100644
--- a/aws/services.go
+++ b/aws/services.go
@@ -2,9 +2,10 @@ package aws
 
 // SupportedServices is a list of all AWS services that are supported by Terracost.
 var supportedServices = map[string]struct{}{
-	"AmazonEC2": {},
-	"AmazonRDS": {},
-	"AWSELB":    {},
+	"AmazonEC2":         {},
+	"AmazonRDS":         {},
+	"AWSELB":            {},
+	"AmazonElastiCache": {},
 }
 
 // IsServiceSupported returns true if the AWS service is valid and supported by Terracost (e.g. for ingestion.)
diff --git a/aws/terraform/db_instance.go b/aws/terraform/db_instance.go
index 679e625..beea8dd 100644
--- a/aws/terraform/db_instance.go
+++ b/aws/terraform/db_instance.go
@@ -129,18 +129,18 @@ func (inst *DBInstance) Components() []query.Component {
 func (inst *DBInstance) databaseInstanceComponent() query.Component {
 	instClass := inst.instanceType
 	attrFilters := []*product.AttributeFilter{
-		{Key: "instanceType", Value: util.StringPtr(inst.instanceType)},
-		{Key: "deploymentOption", Value: util.StringPtr(inst.deploymentOption)},
-		{Key: "databaseEngine", Value: util.StringPtr(inst.databaseEngine)},
+		{Key: "InstanceType", Value: util.StringPtr(inst.instanceType)},
+		{Key: "DeploymentOption", Value: util.StringPtr(inst.deploymentOption)},
+		{Key: "DatabaseEngine", Value: util.StringPtr(inst.databaseEngine)},
 	}
 
 	if inst.databaseEdition != "" {
-		f := &product.AttributeFilter{Key: "databaseEdition", Value: util.StringPtr(inst.databaseEdition)}
+		f := &product.AttributeFilter{Key: "DatabaseEdition", Value: util.StringPtr(inst.databaseEdition)}
 		attrFilters = append(attrFilters, f)
 	}
 
 	if inst.licenseModel != "" {
-		f := &product.AttributeFilter{Key: "licenseModel", Value: util.StringPtr(inst.licenseModel)}
+		f := &product.AttributeFilter{Key: "LicenseModel", Value: util.StringPtr(inst.licenseModel)}
 		attrFilters = append(attrFilters, f)
 	}
 
@@ -158,7 +158,7 @@ func (inst *DBInstance) databaseInstanceComponent() query.Component {
 		PriceFilter: &price.Filter{
 			Unit: util.StringPtr("Hrs"),
 			AttributeFilters: []*price.AttributeFilter{
-				{Key: "purchaseOption", Value: util.StringPtr("on_demand")},
+				{Key: "TermType", Value: util.StringPtr("OnDemand")},
 			},
 		},
 	}
@@ -186,8 +186,8 @@ func (inst *DBInstance) storageComponent() query.Component {
 			Family:   util.StringPtr("Database Storage"),
 			Location: util.StringPtr(inst.region.String()),
 			AttributeFilters: []*product.AttributeFilter{
-				{Key: "deploymentOption", Value: util.StringPtr(inst.deploymentOption)},
-				{Key: "volumeType", Value: util.StringPtr(volumeType)},
+				{Key: "DeploymentOption", Value: util.StringPtr(inst.deploymentOption)},
+				{Key: "VolumeType", Value: util.StringPtr(volumeType)},
 			},
 		},
 	}
@@ -204,7 +204,7 @@ func (inst *DBInstance) iopsComponent() query.Component {
 			Family:   util.StringPtr("Provisioned IOPS"),
 			Location: util.StringPtr(inst.region.String()),
 			AttributeFilters: []*product.AttributeFilter{
-				{Key: "deploymentOption", Value: util.StringPtr(inst.deploymentOption)},
+				{Key: "DeploymentOption", Value: util.StringPtr(inst.deploymentOption)},
 			},
 		},
 	}
diff --git a/aws/terraform/eip.go b/aws/terraform/eip.go
new file mode 100644
index 0000000..b702979
--- /dev/null
+++ b/aws/terraform/eip.go
@@ -0,0 +1,92 @@
+package terraform
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/shopspring/decimal"
+
+	"github.com/cycloidio/terracost/aws/region"
+	"github.com/cycloidio/terracost/price"
+	"github.com/cycloidio/terracost/product"
+	"github.com/cycloidio/terracost/query"
+	"github.com/cycloidio/terracost/util"
+)
+
+// ElasticIP represents an ElasticIP instance definition that can be cost-estimated.
+type ElasticIP struct {
+	providerKey           string
+	region                region.Code
+	customerOwnedIpv4Pool string
+	instance              string
+	networkInterface      string
+}
+
+type elasticIPValues struct {
+	CustomerOwnedIpv4Pool string `mapstructure:"customer_owned_ipv4_pool"`
+	Instance              string `mapstructure:"instance"`
+	NetworkInterface      string `mapstructure:"network_interface"`
+}
+
+func decodeElasticIPValues(tfVals map[string]interface{}) (elasticIPValues, error) {
+	var v elasticIPValues
+	if err := mapstructure.Decode(tfVals, &v); err != nil {
+		return v, err
+	}
+	return v, nil
+}
+
+// NewInstance creates a new Instance from Terraform values.
+func (p *Provider) newElasticIP(vals elasticIPValues) *ElasticIP {
+
+	inst := &ElasticIP{
+		providerKey:           p.key,
+		region:                p.region,
+		customerOwnedIpv4Pool: vals.CustomerOwnedIpv4Pool,
+		instance:              vals.Instance,
+		networkInterface:      vals.NetworkInterface,
+	}
+
+	return inst
+}
+
+// Components returns the price component queries that make up this Instance.
+func (inst *ElasticIP) Components() []query.Component {
+	// An Elastic IP address doesn’t incur charges as long as all the following conditions are true:
+	// * The Elastic IP address is associated with an EC2 instance.
+	// * The instance associated with the Elastic IP address is running.
+	// * The instance has only one Elastic IP address attached to it.
+	// * The Elastic IP address is associated with an attached network interface
+	if len(inst.customerOwnedIpv4Pool) > 0 || len(inst.instance) > 0 || len(inst.networkInterface) > 0 {
+		return []query.Component{}
+	}
+
+	components := []query.Component{inst.elasticIPInstanceComponent()}
+
+	return components
+}
+
+func (inst *ElasticIP) elasticIPInstanceComponent() query.Component {
+
+	attrFilters := []*product.AttributeFilter{
+		{Key: "Group", Value: util.StringPtr("ElasticIP:IdleAddress")},
+	}
+
+	return query.Component{
+		Name:           "Elastic IP",
+		Details:        []string{"ElasticIP:IdleAddress"},
+		HourlyQuantity: decimal.NewFromInt(1),
+		ProductFilter: &product.Filter{
+			Provider:         util.StringPtr(inst.providerKey),
+			Service:          util.StringPtr("AmazonEC2"),
+			Family:           util.StringPtr("IP Address"),
+			Location:         util.StringPtr(inst.region.String()),
+			AttributeFilters: attrFilters,
+		},
+		PriceFilter: &price.Filter{
+			Unit: util.StringPtr("Hrs"),
+			AttributeFilters: []*price.AttributeFilter{
+				{Key: "TermType", Value: util.StringPtr("OnDemand")},
+				{Key: "StartingRange", Value: util.StringPtr("1")},
+			},
+		},
+	}
+}
diff --git a/aws/terraform/elasticache_cluster.go b/aws/terraform/elasticache_cluster.go
new file mode 100644
index 0000000..4fb111d
--- /dev/null
+++ b/aws/terraform/elasticache_cluster.go
@@ -0,0 +1,145 @@
+package terraform
+
+import (
+	"strings"
+
+	"github.com/mitchellh/mapstructure"
+	"github.com/shopspring/decimal"
+
+	"github.com/cycloidio/terracost/aws/region"
+	"github.com/cycloidio/terracost/price"
+	"github.com/cycloidio/terracost/product"
+	"github.com/cycloidio/terracost/query"
+	"github.com/cycloidio/terracost/util"
+)
+
+// ElastiCache represents an ElastiCache instance definition that can be cost-estimated.
+type ElastiCache struct {
+	providerKey string
+
+	region       region.Code
+	instanceType string
+
+	// cacheEngine can be one of "Memcached" or "Redis".
+	cacheEngine string
+
+	// # num_cache_nodes: The initial number of cache nodes that the cache cluster will have
+	numCacheNodes decimal.Decimal
+
+	// If replicationGroupID is set, aws_elasticache_replication_group will be use to define the cost
+	replicationGroupID string
+
+	snapshotRetentionLimit decimal.Decimal
+}
+
+type elastiCacheValues struct {
+	NodeType               string `mapstructure:"node_type"`
+	AvailabilityZone       string `mapstructure:"availability_zone"`
+	Engine                 string `mapstructure:"engine"`
+	ReplicationGroupID     string `mapstructure:"replication_group_id"`
+	NumCacheNodes          int64  `mapstructure:"num_cache_nodes"`
+	SnapshotRetentionLimit int64  `mapstructure:"snapshot_retention_limit"`
+}
+
+var cacheTypeMap = map[string]string{
+	"memcached": "Memcached",
+	"redis":     "Redis",
+}
+
+func decodeElastiCacheValues(tfVals map[string]interface{}) (elastiCacheValues, error) {
+	var v elastiCacheValues
+	if err := mapstructure.Decode(tfVals, &v); err != nil {
+		return v, err
+	}
+	return v, nil
+}
+
+// NewInstance creates a new Instance from Terraform values.
+func (p *Provider) newElastiCache(vals elastiCacheValues) *ElastiCache {
+	cacheType := cacheTypeMap[vals.Engine]
+
+	inst := &ElastiCache{
+		providerKey:            p.key,
+		region:                 p.region,
+		instanceType:           vals.NodeType,
+		cacheEngine:            cacheType,
+		numCacheNodes:          decimal.NewFromInt(vals.NumCacheNodes),
+		replicationGroupID:     vals.ReplicationGroupID,
+		snapshotRetentionLimit: decimal.NewFromInt(vals.SnapshotRetentionLimit),
+	}
+
+	if reg := region.NewFromZone(vals.AvailabilityZone); reg.Valid() {
+		inst.region = reg
+	}
+
+	return inst
+}
+
+// Components returns the price component queries that make up this Instance.
+func (inst *ElastiCache) Components() []query.Component {
+	// If replicationGroupID is set, aws_elasticache_replication_group will be use to define the cost
+	if len(inst.replicationGroupID) > 0 {
+		return []query.Component{}
+	}
+
+	components := []query.Component{inst.elastiCacheInstanceComponent()}
+
+	if inst.snapshotRetentionLimit.GreaterThan(decimal.NewFromInt(0)) && strings.HasPrefix(inst.cacheEngine, "Redis") {
+		components = append(components, inst.backupStorageComponent())
+	}
+
+	return components
+}
+
+func (inst *ElastiCache) elastiCacheInstanceComponent() query.Component {
+	instClass := inst.cacheEngine
+	attrFilters := []*product.AttributeFilter{
+		{Key: "InstanceType", Value: util.StringPtr(inst.instanceType)},
+		{Key: "CacheEngine", Value: util.StringPtr(inst.cacheEngine)},
+	}
+
+	return query.Component{
+		Name:           "Cache instance",
+		Details:        []string{instClass},
+		HourlyQuantity: inst.numCacheNodes,
+		ProductFilter: &product.Filter{
+			Provider:         util.StringPtr(inst.providerKey),
+			Service:          util.StringPtr("AmazonElastiCache"),
+			Family:           util.StringPtr("Cache Instance"),
+			Location:         util.StringPtr(inst.region.String()),
+			AttributeFilters: attrFilters,
+		},
+		PriceFilter: &price.Filter{
+			Unit: util.StringPtr("Hrs"),
+			AttributeFilters: []*price.AttributeFilter{
+				{Key: "TermType", Value: util.StringPtr("OnDemand")},
+			},
+		},
+	}
+}
+
+func (inst *ElastiCache) backupStorageComponent() query.Component {
+	// MonthlyQuantity = snapshotRetentionLimit * backupSnapshotSize
+	// TODO: If/When usage estimation will be supported, backupSnapshotSize might have a different value from 0
+
+	backupSnapshotSize := decimal.NewFromInt(0)
+	monthlyQuantityTotal := backupSnapshotSize.Mul(inst.snapshotRetentionLimit)
+
+	return query.Component{
+		Name:            "Backup storage",
+		Details:         []string{monthlyQuantityTotal.String()},
+		MonthlyQuantity: monthlyQuantityTotal,
+		ProductFilter: &product.Filter{
+			Provider: util.StringPtr(inst.providerKey),
+			Service:  util.StringPtr("AmazonElastiCache"),
+			Family:   util.StringPtr("Storage Snapshot"),
+			Location: util.StringPtr(inst.region.String()),
+		},
+		PriceFilter: &price.Filter{
+			Unit: util.StringPtr("GB-Mo"),
+			AttributeFilters: []*price.AttributeFilter{
+				{Key: "TermType", Value: util.StringPtr("OnDemand")},
+			},
+		},
+	}
+}
diff --git a/aws/terraform/elasticache_replication_group.go b/aws/terraform/elasticache_replication_group.go
new file mode 100644
index 0000000..81a1671
--- /dev/null
+++ b/aws/terraform/elasticache_replication_group.go
@@ -0,0 +1,134 @@
+package terraform
+
+import (
+	"strings"
+
+	"github.com/mitchellh/mapstructure"
+	"github.com/shopspring/decimal"
+
+	"github.com/cycloidio/terracost/aws/region"
+	"github.com/cycloidio/terracost/query"
+)
+
+// ElastiCacheReplication represents an ElastiCacheReplication instance definition that can be cost-estimated.
+type ElastiCacheReplication struct {
+	providerKey string
+
+	region       region.Code
+	instanceType string
+
+	// cacheEngine can be one of "Memcached" or "Redis".
+	cacheEngine string
+
+	// # num_cache_nodes: The initial number of cache nodes that the cache cluster will have
+	numCacheNodes decimal.Decimal
+
+	snapshotRetentionLimit decimal.Decimal
+
+	globalReplicationGroupID string
+}
+
+type elastiCacheReplicationValues struct {
+	NodeType          string   `mapstructure:"node_type"`
+	AvailabilityZones []string `mapstructure:"availability_zones"`
+	Engine            string   `mapstructure:"engine"`
+	ClusterMode       []struct {
+		NumNodeGroups        int64 `mapstructure:"num_node_groups"`
+		ReplicasPerNodeGroup int64 `mapstructure:"replicas_per_node_group"`
+	} `mapstructure:"cluster_mode"`
+	NumberCacheClusters      int64  `mapstructure:"number_cache_clusters"`
+	SnapshotRetentionLimit   int64  `mapstructure:"snapshot_retention_limit"`
+	GlobalReplicationGroupID string `mapstructure:"global_replication_group_id"`
+}
+
+func decodeElastiCacheReplicationValues(tfVals map[string]interface{}) (elastiCacheReplicationValues, error) {
+	var v elastiCacheReplicationValues
+	if err := mapstructure.Decode(tfVals, &v); err != nil {
+		return v, err
+	}
+	return v, nil
+}
+
+// NewInstance creates a new Instance from Terraform values.
+func (p *Provider) newElastiCacheReplication(vals elastiCacheReplicationValues) *ElastiCacheReplication {
+
+	cacheType := "Redis"
+	if len(vals.Engine) > 0 {
+		// cacheTypeMap from elasticache_cluster.go
+		cacheType = cacheTypeMap[vals.Engine]
+	}
+
+	numCacheNodes := decimal.NewFromInt(vals.NumberCacheClusters)
+	if len(vals.ClusterMode) > 0 {
+		nodeGroups := decimal.NewFromInt(vals.ClusterMode[0].NumNodeGroups)
+		replicasNode := decimal.NewFromInt(vals.ClusterMode[0].ReplicasPerNodeGroup)
+		numCacheNodes = nodeGroups.Mul(replicasNode).Add(nodeGroups)
+	}
+
+	inst := &ElastiCacheReplication{
+		providerKey:              p.key,
+		region:                   p.region,
+		instanceType:             vals.NodeType,
+		cacheEngine:              cacheType,
+		numCacheNodes:            numCacheNodes,
+		snapshotRetentionLimit:   decimal.NewFromInt(vals.SnapshotRetentionLimit),
+		globalReplicationGroupID: vals.GlobalReplicationGroupID,
+	}
+
+	if len(vals.AvailabilityZones) > 0 {
+		if reg := region.NewFromZone(vals.AvailabilityZones[0]); reg.Valid() {
+			inst.region = reg
+		}
+	}
+
+	return inst
+}
+
+// Components returns the price component queries that make up this Instance.
+func (inst *ElastiCacheReplication) Components() []query.Component {
+	// If global_replication_group_id is set, node_type & num_node_groups can't be defined. So no cost found
+	if len(inst.globalReplicationGroupID) > 0 {
+		return []query.Component{}
+	}
+
+	components := []query.Component{inst.elastiCacheReplicationInstanceComponent()}
+
+	if inst.snapshotRetentionLimit.GreaterThan(decimal.NewFromInt(0)) && strings.HasPrefix(inst.cacheEngine, "Redis") {
+		components = append(components, inst.backupStorageComponent())
+	}
+
+	return components
+}
+
+func (inst *ElastiCacheReplication) elastiCacheReplicationInstanceComponent() query.Component {
+
+	// Currently, cost is the same as ElastiCache
+	// Use ElastiCache function to generate the right query
+	elastiCacheInst := &ElastiCache{
+		providerKey:            inst.providerKey,
+		region:                 inst.region,
+		instanceType:           inst.instanceType,
+		cacheEngine:            inst.cacheEngine,
+		numCacheNodes:          inst.numCacheNodes,
+		snapshotRetentionLimit: inst.snapshotRetentionLimit,
+	}
+
+	return elastiCacheInst.elastiCacheInstanceComponent()
+
+}
+
+func (inst *ElastiCacheReplication) backupStorageComponent() query.Component {
+
+	// Currently, cost is the same as ElastiCache
+	// Use ElastiCache function to generate the right query
+	elastiCacheInst := &ElastiCache{
+		providerKey:            inst.providerKey,
+		region:                 inst.region,
+		instanceType:           inst.instanceType,
+		cacheEngine:            inst.cacheEngine,
+		numCacheNodes:          inst.numCacheNodes,
+		snapshotRetentionLimit: inst.snapshotRetentionLimit,
+	}
+
+	return elastiCacheInst.backupStorageComponent()
+}
diff --git a/aws/terraform/instance.go b/aws/terraform/instance.go
index f5cc4cd..536cb14 100644
--- a/aws/terraform/instance.go
+++ b/aws/terraform/instance.go
@@ -122,17 +122,17 @@ func (inst *Instance) computeComponent() query.Component {
 			Family:   util.StringPtr("Compute Instance"),
 			Location: util.StringPtr(inst.region.String()),
 			AttributeFilters: []*product.AttributeFilter{
-				{Key: "capacitystatus", Value: util.StringPtr(inst.capacityStatus)},
-				{Key: "instanceType", Value: util.StringPtr(inst.instanceType)},
-				{Key: "tenancy", Value: util.StringPtr(inst.tenancy)},
-				{Key: "operatingSystem", Value: util.StringPtr(inst.operatingSystem)},
-				{Key: "preInstalledSw", Value: util.StringPtr(inst.preInstalledSW)},
+				{Key: "CapacityStatus", Value: util.StringPtr(inst.capacityStatus)},
+				{Key: "InstanceType", Value: util.StringPtr(inst.instanceType)},
+				{Key: "Tenancy", Value: util.StringPtr(inst.tenancy)},
+				{Key: "OperatingSystem", Value: util.StringPtr(inst.operatingSystem)},
+				{Key: "PreInstalledSW", Value: util.StringPtr(inst.preInstalledSW)},
 			},
 		},
 		PriceFilter: &price.Filter{
 			Unit: util.StringPtr("Hrs"),
 			AttributeFilters: []*price.AttributeFilter{
-				{Key: "purchaseOption", Value: util.StringPtr("on_demand")},
+				{Key: "TermType", Value: util.StringPtr("OnDemand")},
 			},
 		},
 	}
diff --git a/aws/terraform/lb.go b/aws/terraform/lb.go
index 893a398..800aa9a 100644
--- a/aws/terraform/lb.go
+++ b/aws/terraform/lb.go
@@ -76,7 +76,7 @@ func (lb *LB) loadBalancerComponent() query.Component {
 			Family:   util.StringPtr(family),
 			Location: util.StringPtr(lb.region.String()),
 			AttributeFilters: []*product.AttributeFilter{
-				{Key: "usagetype", ValueRegex: util.StringPtr("LoadBalancerUsage")},
+				{Key: "UsageType", ValueRegex: util.StringPtr("LoadBalancerUsage")},
 			},
 		},
 		PriceFilter: &price.Filter{
diff --git a/aws/terraform/provider.go b/aws/terraform/provider.go
index 208faea..afae488 100644
--- a/aws/terraform/provider.go
+++ b/aws/terraform/provider.go
@@ -41,6 +41,24 @@ func (p *Provider) ResourceComponents(tfRes terraform.Resource) []query.Componen
 			return nil
 		}
 		return p.newVolume(vals).Components()
+	case "aws_elasticache_cluster":
+		vals, err := decodeElastiCacheValues(tfRes.Values)
+		if err != nil {
+			return nil
+		}
+		return p.newElastiCache(vals).Components()
+	case "aws_elasticache_replication_group":
+		vals, err := decodeElastiCacheReplicationValues(tfRes.Values)
+		if err != nil {
+			return nil
+		}
+		return p.newElastiCacheReplication(vals).Components()
+	case "aws_eip":
+		vals, err := decodeElasticIPValues(tfRes.Values)
+		if err != nil {
+			return nil
+		}
+		return p.newElasticIP(vals).Components()
 	case "aws_db_instance":
 		vals, err := decodeDBInstanceValues(tfRes.Values)
 		if err != nil {
diff --git a/aws/terraform/volume.go b/aws/terraform/volume.go
index 44622e6..e791ac5 100644
--- a/aws/terraform/volume.go
+++ b/aws/terraform/volume.go
@@ -89,7 +89,7 @@ func (v *Volume) storageComponent() query.Component {
 			Family:   util.StringPtr("Storage"),
 			Location: util.StringPtr(v.region.String()),
 			AttributeFilters: []*product.AttributeFilter{
-				{Key: "volumeApiName", Value: util.StringPtr(v.volumeType)},
+				{Key: "VolumeAPIName", Value: util.StringPtr(v.volumeType)},
 			},
 		},
 	}
@@ -106,8 +106,8 @@ func (v *Volume) iopsComponent() query.Component {
 			Family:   util.StringPtr("System Operation"),
 			Location: util.StringPtr(v.region.String()),
 			AttributeFilters: []*product.AttributeFilter{
-				{Key: "volumeApiName", Value: util.StringPtr(v.volumeType)},
-				{Key: "usagetype", ValueRegex: util.StringPtr("^EBS:VolumeP-IOPS")},
+				{Key: "VolumeAPIName", Value: util.StringPtr(v.volumeType)},
+				{Key: "UsageType", ValueRegex: util.StringPtr("^EBS:VolumeP-IOPS")},
 			},
 		},
 	}
diff --git a/docs/aws.md b/docs/aws.md
index 46e0713..33de8e3 100644
--- a/docs/aws.md
+++ b/docs/aws.md
@@ -7,7 +7,7 @@ use for AWS has [this](https://docs.aws.amazon.com/cur/latest/userguide/product-
 
 1. Familiarize yourself with the official AWS pricing page for the service as well as the Terraform documentation for the resource you want to add. Note all factors that influence the cost.
 2. Download and familiarize yourself with the pricing data CSV. This can be done by first checking the [index.json](https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/index.json), finding the respective service under the `offers` key and downloading the file at the URL under the `currentVersionUrl` (replace `json` with `csv`).
-3. Find the names of all columns that contain relevant cost factors and check that the `aws/field/field.go` file contains them - add them if this is not the case and also to the `aws/ingester.go` so it's categorized to the right entity. The constant name should be a correct Go identifier, while the comment should contain the name as it appears in the CSV file.
+3. Find the names of all columns that contain relevant cost factors and check that the `aws/field/field.go` file contains them - add them if this is not the case and also to the `aws/ingester.go` so it's categorized to the right entity (Price or Product). The constant name should be a correct Go identifier, while the comment should contain the variable name as it appears in `aws/field/field.go`.
 4. Run `make generate` to regenerate the field list.
 5. Create a new file in the `aws/terraform` directory with the name of the Terraform resource (without the `aws` prefix), e.g. for `aws_db_instance` it would be `db_instance.go`. It should include two new structs: `Resource` (that is an intermediate struct containing only the relevant cost factors) and `resourceValues` (that directly represents the values from the Terraform resource.) Additionally, the `Resource` struct must implement the `Components` method that returns `[]query.Component`. See the other existing resources for inspiration.
 6. Add the terraform resource to the `aws/terraform/provider.go`  on the `ResourceComponents`
@@ -18,8 +18,11 @@ use for AWS has [this](https://docs.aws.amazon.com/cur/latest/userguide/product-
 ## List of supported resources and attributes
 
 * [`aws_instance`](#aws_instance)
-* [`aws_db_instance`](#aws_db_instance)
 * [`aws_ebs_volume`](#aws_ebs_volume)
+* [`aws_elasticache_cluster`](#aws_elasticache_cluster)
+* [`aws_elasticache_replication_group`](#aws_elasticache_replication_group)
+* [`aws_eip`](#aws_eip)
+* [`aws_db_instance`](#aws_db_instance)
 * [`aws_lb/aws_alb`](#aws_lb--aws_alb)
 * [`aws_elb`](#aws_elb)
 
@@ -67,6 +70,36 @@ use for AWS has [this](https://docs.aws.amazon.com/cur/latest/userguide/product-
 * Volume size - 8GB by default
 * Provisioned IOPS - only for "io1" and "io2" volume types; 100 by default
 
+### `aws_elasticache_cluster`
+
+#### Cost factors
+
+* Location
+* InstanceType
+* CacheEngine - "Memcached" or "Redis"
+
+#### Additional notes
+
+* HourlyQuantity is incresed regarding the number of cache nodes
+
+### `aws_elasticache_replication_group`
+
+#### Cost factors
+
+* Location
+* InstanceType
+* CacheEngine - "Memcached" or "Redis"
+
+#### Additional notes
+
+* HourlyQuantity is incresed regarding the number of cache nodes
+
+### `aws_eip`
+
+#### Cost factors
+
+* Group - IdleAddress by default
+* StartingRange - 1 by default
 
 ### `aws_lb` / `aws_alb`
 


Test patch:
diff --git a/aws/ingester_test.go b/aws/ingester_test.go
index 4dceddf..c960d57 100644
--- a/aws/ingester_test.go
+++ b/aws/ingester_test.go
@@ -56,9 +56,9 @@ func TestIngester_Ingest(t *testing.T) {
 			Family:   "Compute Instance",
 			Location: "eu-west-3",
 			Attributes: map[string]string{
-				"instanceType":    "m5.xlarge",
-				"operatingSystem": "Linux",
-				"tenancy":         "Shared",
+				"InstanceType":    "m5.xlarge",
+				"OperatingSystem": "Linux",
+				"Tenancy":         "Shared",
 			},
 		}
 
@@ -69,7 +69,7 @@ func TestIngester_Ingest(t *testing.T) {
 					Unit:       "Hrs",
 					Currency:   "USD",
 					Value:      decimal.RequireFromString("1.234"),
-					Attributes: map[string]string{"purchaseOption": "on_demand"},
+					Attributes: map[string]string{"TermType": "OnDemand"},
 				},
 			},
 			{
@@ -78,7 +78,7 @@ func TestIngester_Ingest(t *testing.T) {
 					Unit:       "Hrs",
 					Currency:   "USD",
 					Value:      decimal.RequireFromString("0.987"),
-					Attributes: map[string]string{"purchaseOption": "reserved"},
+					Attributes: map[string]string{"TermType": "Reserved"},
 				},
 			},
 			{
@@ -89,14 +89,14 @@ func TestIngester_Ingest(t *testing.T) {
 					Family:   "Storage",
 					Location: "eu-west-3",
 					Attributes: map[string]string{
-						"volumeApiName": "gp2",
+						"VolumeAPIName": "gp2",
 					},
 				},
 				Price: price.Price{
 					Unit:       "GB-Mo",
 					Currency:   "USD",
 					Value:      decimal.RequireFromString("0.456"),
-					Attributes: map[string]string{"purchaseOption": "on_demand"},
+					Attributes: map[string]string{"TermType": "OnDemand"},
 				},
 			},
 		}
diff --git a/aws/terraform/db_instance_test.go b/aws/terraform/db_instance_test.go
index 51a1be6..da21209 100644
--- a/aws/terraform/db_instance_test.go
+++ b/aws/terraform/db_instance_test.go
@@ -42,15 +42,15 @@ func TestDBInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Database Instance"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "instanceType", Value: util.StringPtr("db.t2.xlarge")},
-						{Key: "deploymentOption", Value: util.StringPtr("Single-AZ")},
-						{Key: "databaseEngine", Value: util.StringPtr("PostgreSQL")},
+						{Key: "InstanceType", Value: util.StringPtr("db.t2.xlarge")},
+						{Key: "DeploymentOption", Value: util.StringPtr("Single-AZ")},
+						{Key: "DatabaseEngine", Value: util.StringPtr("PostgreSQL")},
 					},
 				},
 				PriceFilter: &price.Filter{
 					Unit: util.StringPtr("Hrs"),
 					AttributeFilters: []*price.AttributeFilter{
-						{Key: "purchaseOption", Value: util.StringPtr("on_demand")},
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
 					},
 				},
 			},
@@ -65,8 +65,8 @@ func TestDBInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Database Storage"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "deploymentOption", Value: util.StringPtr("Single-AZ")},
-						{Key: "volumeType", Value: util.StringPtr("General Purpose")},
+						{Key: "DeploymentOption", Value: util.StringPtr("Single-AZ")},
+						{Key: "VolumeType", Value: util.StringPtr("General Purpose")},
 					},
 				},
 			},
@@ -102,15 +102,15 @@ func TestDBInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Database Instance"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "instanceType", Value: util.StringPtr("db.t2.xlarge")},
-						{Key: "deploymentOption", Value: util.StringPtr("Single-AZ")},
-						{Key: "databaseEngine", Value: util.StringPtr("PostgreSQL")},
+						{Key: "InstanceType", Value: util.StringPtr("db.t2.xlarge")},
+						{Key: "DeploymentOption", Value: util.StringPtr("Single-AZ")},
+						{Key: "DatabaseEngine", Value: util.StringPtr("PostgreSQL")},
 					},
 				},
 				PriceFilter: &price.Filter{
 					Unit: util.StringPtr("Hrs"),
 					AttributeFilters: []*price.AttributeFilter{
-						{Key: "purchaseOption", Value: util.StringPtr("on_demand")},
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
 					},
 				},
 			},
@@ -125,8 +125,8 @@ func TestDBInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Database Storage"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "deploymentOption", Value: util.StringPtr("Single-AZ")},
-						{Key: "volumeType", Value: util.StringPtr("Provisioned IOPS")},
+						{Key: "DeploymentOption", Value: util.StringPtr("Single-AZ")},
+						{Key: "VolumeType", Value: util.StringPtr("Provisioned IOPS")},
 					},
 				},
 			},
@@ -140,7 +140,7 @@ func TestDBInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Provisioned IOPS"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "deploymentOption", Value: util.StringPtr("Single-AZ")},
+						{Key: "DeploymentOption", Value: util.StringPtr("Single-AZ")},
 					},
 				},
 			},
@@ -176,17 +176,17 @@ func TestDBInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Database Instance"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "instanceType", Value: util.StringPtr("db.t2.xlarge")},
-						{Key: "deploymentOption", Value: util.StringPtr("Multi-AZ")},
-						{Key: "databaseEngine", Value: util.StringPtr("Oracle")},
-						{Key: "databaseEdition", Value: util.StringPtr("Standard One")},
-						{Key: "licenseModel", Value: util.StringPtr("Bring your own license")},
+						{Key: "InstanceType", Value: util.StringPtr("db.t2.xlarge")},
+						{Key: "DeploymentOption", Value: util.StringPtr("Multi-AZ")},
+						{Key: "DatabaseEngine", Value: util.StringPtr("Oracle")},
+						{Key: "DatabaseEdition", Value: util.StringPtr("Standard One")},
+						{Key: "LicenseModel", Value: util.StringPtr("Bring your own license")},
 					},
 				},
 				PriceFilter: &price.Filter{
 					Unit: util.StringPtr("Hrs"),
 					AttributeFilters: []*price.AttributeFilter{
-						{Key: "purchaseOption", Value: util.StringPtr("on_demand")},
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
 					},
 				},
 			},
@@ -201,8 +201,8 @@ func TestDBInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Database Storage"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "deploymentOption", Value: util.StringPtr("Multi-AZ")},
-						{Key: "volumeType", Value: util.StringPtr("General Purpose")},
+						{Key: "DeploymentOption", Value: util.StringPtr("Multi-AZ")},
+						{Key: "VolumeType", Value: util.StringPtr("General Purpose")},
 					},
 				},
 			},
diff --git a/aws/terraform/eip_test.go b/aws/terraform/eip_test.go
new file mode 100644
index 0000000..e688450
--- /dev/null
+++ b/aws/terraform/eip_test.go
@@ -0,0 +1,110 @@
+package terraform
+
+import (
+	"testing"
+
+	"github.com/shopspring/decimal"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/cycloidio/terracost/price"
+	"github.com/cycloidio/terracost/product"
+	"github.com/cycloidio/terracost/query"
+	"github.com/cycloidio/terracost/terraform"
+	"github.com/cycloidio/terracost/util"
+)
+
+func TestElasticIP_Components(t *testing.T) {
+	p, err := NewProvider("aws", "us-east-1")
+	require.NoError(t, err)
+
+	t.Run("EIP", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_eip.test",
+			Type:         "aws_eip",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"vpc": true,
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Elastic IP",
+				Details:        []string{"ElasticIP:IdleAddress"},
+				HourlyQuantity: decimal.NewFromInt(1),
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonEC2"),
+					Family:   util.StringPtr("IP Address"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "Group", Value: util.StringPtr("ElasticIP:IdleAddress")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+						{Key: "StartingRange", Value: util.StringPtr("1")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("EIPCustomerOwnedIpv4Pool", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_eip.test",
+			Type:         "aws_eip",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"customer_owned_ipv4_pool": "customer-owned-ipv4-pool-id",
+			},
+		}
+
+		expected := []query.Component{}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("EIPInstance", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_eip.test",
+			Type:         "aws_eip",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"instance": "instance-id",
+			},
+		}
+
+		expected := []query.Component{}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("EIPNetworkInterface", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_eip.test",
+			Type:         "aws_eip",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"network_interface": "network-interface-id",
+			},
+		}
+
+		expected := []query.Component{}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+}
diff --git a/aws/terraform/elasticache_cluster_test.go b/aws/terraform/elasticache_cluster_test.go
new file mode 100644
index 0000000..961d064
--- /dev/null
+++ b/aws/terraform/elasticache_cluster_test.go
@@ -0,0 +1,221 @@
+package terraform
+
+import (
+	"testing"
+
+	"github.com/shopspring/decimal"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/cycloidio/terracost/price"
+	"github.com/cycloidio/terracost/product"
+	"github.com/cycloidio/terracost/query"
+	"github.com/cycloidio/terracost/terraform"
+	"github.com/cycloidio/terracost/util"
+)
+
+func TestElastiCache_Components(t *testing.T) {
+	p, err := NewProvider("aws", "us-east-1")
+	require.NoError(t, err)
+
+	t.Run("RedisEngine", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_cluster.test",
+			Type:         "aws_elasticache_cluster",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type":       "cache.m4.large",
+				"engine":          "redis",
+				"num_cache_nodes": 1,
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Cache instance",
+				HourlyQuantity: decimal.NewFromInt(1),
+				Details:        []string{"Redis"},
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Cache Instance"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "InstanceType", Value: util.StringPtr("cache.m4.large")},
+						{Key: "CacheEngine", Value: util.StringPtr("Redis")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("RedisSnapShotRetentionLimit", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_cluster.test",
+			Type:         "aws_elasticache_cluster",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type":                "cache.m4.large",
+				"engine":                   "redis",
+				"num_cache_nodes":          1,
+				"snapshot_retention_limit": 5,
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Cache instance",
+				HourlyQuantity: decimal.NewFromInt(1),
+				Details:        []string{"Redis"},
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Cache Instance"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "InstanceType", Value: util.StringPtr("cache.m4.large")},
+						{Key: "CacheEngine", Value: util.StringPtr("Redis")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+			{
+				Name:            "Backup storage",
+				Details:         []string{"0"},
+				MonthlyQuantity: decimal.NewFromInt(0),
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Storage Snapshot"),
+					Location: util.StringPtr("us-east-1"),
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("GB-Mo"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("RedisReplicationGroupID", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_cluster.test",
+			Type:         "aws_elasticache_cluster",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type":            "cache.m4.large",
+				"engine":               "redis",
+				"replication_group_id": "replication-group-1",
+			},
+		}
+
+		expected := []query.Component{}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("MemcacheEngine", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_cluster.test",
+			Type:         "aws_elasticache_cluster",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type":       "cache.m4.large",
+				"engine":          "memcached",
+				"num_cache_nodes": 1,
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Cache instance",
+				HourlyQuantity: decimal.NewFromInt(1),
+				Details:        []string{"Memcached"},
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Cache Instance"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "InstanceType", Value: util.StringPtr("cache.m4.large")},
+						{Key: "CacheEngine", Value: util.StringPtr("Memcached")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("MemcacheNumCacheNodes", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_cluster.test",
+			Type:         "aws_elasticache_cluster",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type":       "cache.m4.large",
+				"engine":          "memcached",
+				"num_cache_nodes": 2,
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Cache instance",
+				HourlyQuantity: decimal.NewFromInt(2),
+				Details:        []string{"Memcached"},
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Cache Instance"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "InstanceType", Value: util.StringPtr("cache.m4.large")},
+						{Key: "CacheEngine", Value: util.StringPtr("Memcached")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+}
diff --git a/aws/terraform/elasticache_replication_group_test.go b/aws/terraform/elasticache_replication_group_test.go
new file mode 100644
index 0000000..edb40f2
--- /dev/null
+++ b/aws/terraform/elasticache_replication_group_test.go
@@ -0,0 +1,228 @@
+package terraform
+
+import (
+	"testing"
+
+	"github.com/shopspring/decimal"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/cycloidio/terracost/price"
+	"github.com/cycloidio/terracost/product"
+	"github.com/cycloidio/terracost/query"
+	"github.com/cycloidio/terracost/terraform"
+	"github.com/cycloidio/terracost/util"
+)
+
+func TestElastiCacheReplication_Components(t *testing.T) {
+	p, err := NewProvider("aws", "us-east-1")
+	require.NoError(t, err)
+
+	//1 group 1 node
+	t.Run("RedisEngineDefault", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_replication_group.test",
+			Type:         "aws_elasticache_replication_group",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type":             "cache.m4.large",
+				"number_cache_clusters": 1,
+				"availability_zones":    []string{"us-east-1a", "us-east-1b"},
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Cache instance",
+				HourlyQuantity: decimal.NewFromInt(1),
+				Details:        []string{"Redis"},
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Cache Instance"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "InstanceType", Value: util.StringPtr("cache.m4.large")},
+						{Key: "CacheEngine", Value: util.StringPtr("Redis")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("RedisEngineGlobalReplicationGroupID", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_replication_group.test",
+			Type:         "aws_elasticache_replication_group",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"number_cache_clusters":       1,
+				"availability_zones":          []string{"us-east-1a", "us-east-1b"},
+				"global_replication_group_id": "global-replication-group-1",
+			},
+		}
+
+		expected := []query.Component{}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("RedisSnapShotRetentionLimit", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_replication_group.test",
+			Type:         "aws_elasticache_replication_group",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type":                "cache.m4.large",
+				"engine":                   "redis",
+				"number_cache_clusters":    1,
+				"snapshot_retention_limit": 5,
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Cache instance",
+				HourlyQuantity: decimal.NewFromInt(1),
+				Details:        []string{"Redis"},
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Cache Instance"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "InstanceType", Value: util.StringPtr("cache.m4.large")},
+						{Key: "CacheEngine", Value: util.StringPtr("Redis")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+			{
+				Name:            "Backup storage",
+				Details:         []string{"0"},
+				MonthlyQuantity: decimal.NewFromInt(0),
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Storage Snapshot"),
+					Location: util.StringPtr("us-east-1"),
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("GB-Mo"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("RedisEngineNumCacheNodes", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_replication_group.test",
+			Type:         "aws_elasticache_replication_group",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type":             "cache.m4.large",
+				"engine":                "redis",
+				"number_cache_clusters": 2,
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Cache instance",
+				HourlyQuantity: decimal.NewFromInt(2),
+				Details:        []string{"Redis"},
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Cache Instance"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "InstanceType", Value: util.StringPtr("cache.m4.large")},
+						{Key: "CacheEngine", Value: util.StringPtr("Redis")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("RedisEngineClusterMode", func(t *testing.T) {
+		tfres := terraform.Resource{
+			Address:      "aws_elasticache_replication_group.test",
+			Type:         "aws_elasticache_replication_group",
+			Name:         "test",
+			ProviderName: "aws",
+			Values: map[string]interface{}{
+				"node_type": "cache.m4.large",
+				"engine":    "redis",
+				"cluster_mode": []map[string]int{
+					map[string]int{
+						"replicas_per_node_group": 3,
+						"num_node_groups":         2,
+					},
+				},
+			},
+		}
+
+		expected := []query.Component{
+			{
+				Name:           "Cache instance",
+				HourlyQuantity: decimal.NewFromInt(8),
+				Details:        []string{"Redis"},
+				ProductFilter: &product.Filter{
+					Provider: util.StringPtr("aws"),
+					Service:  util.StringPtr("AmazonElastiCache"),
+					Family:   util.StringPtr("Cache Instance"),
+					Location: util.StringPtr("us-east-1"),
+					AttributeFilters: []*product.AttributeFilter{
+						{Key: "InstanceType", Value: util.StringPtr("cache.m4.large")},
+						{Key: "CacheEngine", Value: util.StringPtr("Redis")},
+					},
+				},
+				PriceFilter: &price.Filter{
+					Unit: util.StringPtr("Hrs"),
+					AttributeFilters: []*price.AttributeFilter{
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
+					},
+				},
+			},
+		}
+
+		actual := p.ResourceComponents(tfres)
+		assert.Equal(t, expected, actual)
+	})
+
+}
diff --git a/aws/terraform/instance_test.go b/aws/terraform/instance_test.go
index 10250df..6a295df 100644
--- a/aws/terraform/instance_test.go
+++ b/aws/terraform/instance_test.go
@@ -41,17 +41,17 @@ func TestInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Compute Instance"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "capacitystatus", Value: util.StringPtr("Used")},
-						{Key: "instanceType", Value: util.StringPtr("m5.xlarge")},
-						{Key: "tenancy", Value: util.StringPtr("Shared")},
-						{Key: "operatingSystem", Value: util.StringPtr("Linux")},
-						{Key: "preInstalledSw", Value: util.StringPtr("NA")},
+						{Key: "CapacityStatus", Value: util.StringPtr("Used")},
+						{Key: "InstanceType", Value: util.StringPtr("m5.xlarge")},
+						{Key: "Tenancy", Value: util.StringPtr("Shared")},
+						{Key: "OperatingSystem", Value: util.StringPtr("Linux")},
+						{Key: "PreInstalledSW", Value: util.StringPtr("NA")},
 					},
 				},
 				PriceFilter: &price.Filter{
 					Unit: util.StringPtr("Hrs"),
 					AttributeFilters: []*price.AttributeFilter{
-						{Key: "purchaseOption", Value: util.StringPtr("on_demand")},
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
 					},
 				},
 			},
@@ -66,7 +66,7 @@ func TestInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Storage"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "volumeApiName", Value: util.StringPtr("gp2")},
+						{Key: "VolumeAPIName", Value: util.StringPtr("gp2")},
 					},
 				},
 			},
@@ -106,17 +106,17 @@ func TestInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Compute Instance"),
 					Location: util.StringPtr("us-east-1"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "capacitystatus", Value: util.StringPtr("Used")},
-						{Key: "instanceType", Value: util.StringPtr("m5.xlarge")},
-						{Key: "tenancy", Value: util.StringPtr("Dedicated")},
-						{Key: "operatingSystem", Value: util.StringPtr("Linux")},
-						{Key: "preInstalledSw", Value: util.StringPtr("NA")},
+						{Key: "CapacityStatus", Value: util.StringPtr("Used")},
+						{Key: "InstanceType", Value: util.StringPtr("m5.xlarge")},
+						{Key: "Tenancy", Value: util.StringPtr("Dedicated")},
+						{Key: "OperatingSystem", Value: util.StringPtr("Linux")},
+						{Key: "PreInstalledSW", Value: util.StringPtr("NA")},
 					},
 				},
 				PriceFilter: &price.Filter{
 					Unit: util.StringPtr("Hrs"),
 					AttributeFilters: []*price.AttributeFilter{
-						{Key: "purchaseOption", Value: util.StringPtr("on_demand")},
+						{Key: "TermType", Value: util.StringPtr("OnDemand")},
 					},
 				},
 			},
@@ -131,7 +131,7 @@ func TestInstance_Components(t *testing.T) {
 					Family:   util.StringPtr("Storage"),
 					Location: util.StringPtr("us-east-1"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "volumeApiName", Value: util.StringPtr("st1")},
+						{Key: "VolumeAPIName", Value: util.StringPtr("st1")},
 					},
 				},
 			},
diff --git a/aws/terraform/lb_test.go b/aws/terraform/lb_test.go
index f5d8ca7..3398dc5 100644
--- a/aws/terraform/lb_test.go
+++ b/aws/terraform/lb_test.go
@@ -37,7 +37,7 @@ func TestLB_Components(t *testing.T) {
 					Family:   util.StringPtr("Load Balancer-Application"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "usagetype", ValueRegex: util.StringPtr("LoadBalancerUsage")},
+						{Key: "UsageType", ValueRegex: util.StringPtr("LoadBalancerUsage")},
 					},
 				},
 				PriceFilter: &price.Filter{
@@ -70,7 +70,7 @@ func TestLB_Components(t *testing.T) {
 					Family:   util.StringPtr("Load Balancer-Network"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "usagetype", ValueRegex: util.StringPtr("LoadBalancerUsage")},
+						{Key: "UsageType", ValueRegex: util.StringPtr("LoadBalancerUsage")},
 					},
 				},
 				PriceFilter: &price.Filter{
@@ -103,7 +103,7 @@ func TestLB_Components(t *testing.T) {
 					Family:   util.StringPtr("Load Balancer-Gateway"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "usagetype", ValueRegex: util.StringPtr("LoadBalancerUsage")},
+						{Key: "UsageType", ValueRegex: util.StringPtr("LoadBalancerUsage")},
 					},
 				},
 				PriceFilter: &price.Filter{
@@ -134,7 +134,7 @@ func TestLB_Components(t *testing.T) {
 					Family:   util.StringPtr("Load Balancer"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "usagetype", ValueRegex: util.StringPtr("LoadBalancerUsage")},
+						{Key: "UsageType", ValueRegex: util.StringPtr("LoadBalancerUsage")},
 					},
 				},
 				PriceFilter: &price.Filter{
diff --git a/aws/terraform/volume_test.go b/aws/terraform/volume_test.go
index 6378fdb..c578518 100644
--- a/aws/terraform/volume_test.go
+++ b/aws/terraform/volume_test.go
@@ -41,7 +41,7 @@ func TestVolume_Components(t *testing.T) {
 					Family:   util.StringPtr("Storage"),
 					Location: util.StringPtr("eu-west-3"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "volumeApiName", Value: util.StringPtr("gp2")},
+						{Key: "VolumeAPIName", Value: util.StringPtr("gp2")},
 					},
 				},
 			},
@@ -77,7 +77,7 @@ func TestVolume_Components(t *testing.T) {
 					Family:   util.StringPtr("Storage"),
 					Location: util.StringPtr("us-east-1"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "volumeApiName", Value: util.StringPtr("io2")},
+						{Key: "VolumeAPIName", Value: util.StringPtr("io2")},
 					},
 				},
 			},
@@ -91,8 +91,8 @@ func TestVolume_Components(t *testing.T) {
 					Family:   util.StringPtr("System Operation"),
 					Location: util.StringPtr("us-east-1"),
 					AttributeFilters: []*product.AttributeFilter{
-						{Key: "volumeApiName", Value: util.StringPtr("io2")},
-						{Key: "usagetype", ValueRegex: util.StringPtr("^EBS:VolumeP-IOPS")},
+						{Key: "VolumeAPIName", Value: util.StringPtr("io2")},
+						{Key: "UsageType", ValueRegex: util.StringPtr("^EBS:VolumeP-IOPS")},
 					},
 				},
 			},
diff --git a/e2e/aws_estimation_test.go b/e2e/aws_estimation_test.go
index c000546..616cee4 100644
--- a/e2e/aws_estimation_test.go
+++ b/e2e/aws_estimation_test.go
@@ -72,11 +72,11 @@ func TestAWSEstimation(t *testing.T) {
 			Family:   "Compute Instance",
 			Location: "us-east-1",
 			Attributes: map[string]string{
-				"capacitystatus":  "Used",
-				"instanceType":    "t2.micro",
-				"tenancy":         "Shared",
-				"operatingSystem": "Linux",
-				"preInstalledSw":  "NA",
+				"CapacityStatus":  "Used",
+				"InstanceType":    "t2.micro",
+				"Tenancy":         "Shared",
+				"OperatingSystem": "Linux",
+				"PreInstalledSW":  "NA",
 			},
 		},
 		{
@@ -86,11 +86,11 @@ func TestAWSEstimation(t *testing.T) {
 			Family:   "Compute Instance",
 			Location: "us-east-1",
 			Attributes: map[string]string{
-				"capacitystatus":  "Used",
-				"instanceType":    "t2.xlarge",
-				"tenancy":         "Shared",
-				"operatingSystem": "Linux",
-				"preInstalledSw":  "NA",
+				"CapacityStatus":  "Used",
+				"InstanceType":    "t2.xlarge",
+				"Tenancy":         "Shared",
+				"OperatingSystem": "Linux",
+				"PreInstalledSW":  "NA",
 			},
 		},
 		{
@@ -100,7 +100,7 @@ func TestAWSEstimation(t *testing.T) {
 			Family:   "Storage",
 			Location: "us-east-1",
 			Attributes: map[string]string{
-				"volumeApiName": "gp2",
+				"VolumeAPIName": "gp2",
 			},
 		},
 	}
@@ -119,7 +119,7 @@ func TestAWSEstimation(t *testing.T) {
 				Currency: "USD",
 				Value:    decimal.NewFromFloat(0.12),
 				Attributes: map[string]string{
-					"purchaseOption": "on_demand",
+					"TermType": "OnDemand",
 				},
 			},
 		},
@@ -130,7 +130,7 @@ func TestAWSEstimation(t *testing.T) {
 				Currency: "USD",
 				Value:    decimal.NewFromFloat(1.23),
 				Attributes: map[string]string{
-					"purchaseOption": "on_demand",
+					"TermType": "OnDemand",
 				},
 			},
 		},
@@ -141,7 +141,7 @@ func TestAWSEstimation(t *testing.T) {
 				Currency: "USD",
 				Value:    decimal.NewFromFloat(0.45),
 				Attributes: map[string]string{
-					"purchaseOption": "on_demand",
+					"TermType": "OnDemand",
 				},
 			},
 		},
@@ -274,7 +274,7 @@ func TestAWSEstimation(t *testing.T) {
 
 			pcost, err := plan.PlannedCost()
 			assert.NoError(t, err)
-			assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(32.374), "USD"), pcost)
+			assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(52.814), "USD"), pcost)
 		})
 		t.Run("SuccessMagento", func(t *testing.T) {
 
@@ -285,7 +285,7 @@ func TestAWSEstimation(t *testing.T) {
 
 			pcost, err := plan.PlannedCost()
 			assert.NoError(t, err)
-			assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(50.794), "USD"), pcost)
+			assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(71.234), "USD"), pcost)
 		})
 	})
 }
diff --git a/ingestion_test.go b/ingestion_test.go
index bd751ba..bbc3a41 100644
--- a/ingestion_test.go
+++ b/ingestion_test.go
@@ -32,7 +32,7 @@ func TestIngestPricing(t *testing.T) {
 				Unit:       "Hrs",
 				Currency:   "USD",
 				Value:      decimal.RequireFromString("1.23"),
-				Attributes: map[string]string{"purchaseOption": "on_demand"},
+				Attributes: map[string]string{"TermType": "OnDemand"},
 			},
 		},
 		{
@@ -41,7 +41,7 @@ func TestIngestPricing(t *testing.T) {
 				Unit:       "Hrs",
 				Currency:   "USD",
 				Value:      decimal.RequireFromString("0.98"),
-				Attributes: map[string]string{"purchaseOption": "reserved"},
+				Attributes: map[string]string{"TermType": "Reserved"},
 			},
 		},
 	}