diff --git a/internal/output/text.go b/internal/output/text.go index fe8e7ca..9bdd3a8 100644 --- a/internal/output/text.go +++ b/internal/output/text.go @@ -17,7 +17,7 @@ func GenerateReportText(report estimation.EstimationReport) string { tableString.WriteString("\n Average estimation of CO2 emissions per instance: \n\n") table := tablewriter.NewWriter(tableString) - table.SetHeader([]string{"resource type", "name", "count", "emissions per instance"}) + table.SetHeader([]string{"resource type", "address", "count", "emissions per instance"}) // Default sort estimations := report.Resources @@ -26,7 +26,7 @@ func GenerateReportText(report estimation.EstimationReport) string { for _, resource := range report.Resources { table.Append([]string{ resource.Resource.GetIdentification().ResourceType, - resource.Resource.GetIdentification().Name, + resource.Resource.GetAddress(), fmt.Sprintf("%v", resource.Count), fmt.Sprintf(" %v %v", resource.CarbonEmissions.StringFixed(4), report.Info.UnitCarbonEmissionsTime), }) diff --git a/internal/plan/json.go b/internal/plan/json.go new file mode 100644 index 0000000..32e295f --- /dev/null +++ b/internal/plan/json.go @@ -0,0 +1,34 @@ +package plan + +import ( + "strings" + + "github.com/carboniferio/carbonifer/internal/utils" +) + +const allResourcesQuery = ".planned_values | .. | objects | select(has(\"resources\")) | .resources[]" + +func getJSON(query string, json interface{}) ([]interface{}, error) { + + if strings.HasPrefix(query, "select(") { + results, err := utils.GetJSON(allResourcesQuery+" | "+query, *TfPlan) + if len(results) > 0 && err == nil { + return results, nil + } + return nil, err + } + + if strings.HasPrefix(query, ".configuration") || strings.HasPrefix(query, ".prior_state") || strings.HasPrefix(query, ".planned_values") { + results, err := utils.GetJSON(query, *TfPlan) + if len(results) > 0 && err == nil { + return results, nil + } + return nil, err + } + + results, err := utils.GetJSON(query, json) + if len(results) > 0 && err == nil { + return results, nil + } + return nil, err +} diff --git a/internal/plan/json_getters.go b/internal/plan/json_getters.go index 5b0e511..4d38ac1 100644 --- a/internal/plan/json_getters.go +++ b/internal/plan/json_getters.go @@ -90,17 +90,10 @@ func getSliceItems(context tfContext) ([]interface{}, error) { return nil, err } } - jsonResults, err := utils.GetJSON(path, context.Resource) + jsonResults, err := getJSON(path, context.Resource) if err != nil { return nil, errors.Wrapf(err, "Cannot get item: %v", path) } - // if no result, try to get it from the whole plan - if len(jsonResults) == 0 && TfPlan != nil { - jsonResults, err = utils.GetJSON(path, *TfPlan) - if err != nil { - return nil, errors.Wrapf(err, "Cannot get item in full plan: %v", path) - } - } for _, jsonResultsI := range jsonResults { switch jsonResults := jsonResultsI.(type) { case map[string]interface{}: @@ -214,24 +207,15 @@ func getValue(key string, context *tfContext) (*valueWithUnit, error) { } path := pathRaw if strings.Contains(pathRaw, "${") { - fmt.Println("pathRaw: ", pathRaw) path, err = resolvePlaceholders(path, context) if err != nil { return nil, errors.Wrapf(err, "Cannot resolve placeholders for %v", path) } - fmt.Println("path: ", path) } - valueFounds, err := utils.GetJSON(path, context.Resource) + valueFounds, err := getJSON(path, context.Resource) if err != nil { return nil, errors.Wrapf(err, "Cannot get value for %v", path) } - if len(valueFounds) == 0 && TfPlan != nil { - // Try to resolve it against the whole plan - valueFounds, err = utils.GetJSON(path, *TfPlan) - if err != nil { - return nil, errors.Wrapf(err, "Cannot get value in the whole plan for %v", path) - } - } if len(valueFounds) > 0 { if len(valueFounds) > 1 { return nil, errors.Errorf("Found more than one value for property %v of resource type %v", key, context.ResourceAddress) diff --git a/internal/plan/mappings/aws/ec2_ebs.yaml b/internal/plan/mappings/aws/ec2_ebs.yaml index ea85550..960c357 100644 --- a/internal/plan/mappings/aws/ec2_ebs.yaml +++ b/internal/plan/mappings/aws/ec2_ebs.yaml @@ -1,7 +1,7 @@ compute_resource: aws_ebs_volume: paths: - - .planned_values.root_module.resources[] | select(.type == "aws_ebs_volume") + - select(.type == "aws_ebs_volume") type: resource properties: name: diff --git a/internal/plan/mappings/aws/ec2_instance.yaml b/internal/plan/mappings/aws/ec2_instance.yaml index d09d27f..e80f405 100644 --- a/internal/plan/mappings/aws/ec2_instance.yaml +++ b/internal/plan/mappings/aws/ec2_instance.yaml @@ -1,13 +1,13 @@ compute_resource: aws_instance: paths: - - .planned_values.root_module.resources[] | select(.type == "aws_instance") + - select(.type == "aws_instance") type: resource variables: properties: ami: - paths: - - '.prior_state.values.root_module.resources[] | select(.values.image_id == "${this.values.ami}")' + - select(.values.image_id == "${this.values.ami}") reference: return_path: true provider_region: diff --git a/internal/plan/mappings/aws/rds_instance.yaml b/internal/plan/mappings/aws/rds_instance.yaml index 349aa6c..7650335 100644 --- a/internal/plan/mappings/aws/rds_instance.yaml +++ b/internal/plan/mappings/aws/rds_instance.yaml @@ -1,7 +1,7 @@ compute_resource: aws_db_instance: paths: - - .planned_values.root_module.resources[] | select(.type == "aws_db_instance") + - select(.type == "aws_db_instance") type: resource variables: properties: @@ -10,8 +10,8 @@ compute_resource: - '.configuration.root_module.resources[] | select(.address == "${this.address}") | .expressions.replicate_source_db?.references[]? | select(endswith("id")) | gsub("\\.id$"; "")' reference: paths: - - .planned_values.root_module.resources[] | select(.address == "${key}") - - .planned_values.root_module.child_modules[] | select(.address == ("${key}" | split(".")[0:2] | join("."))) | .resources[] | select(.name == ("${key}" | split(".")[2])) + - select(.address == "${key}") + - select(.address == ("${key}" | split(".")[0:2] | join("."))) | .resources[] | select(.name == ("${key}" | split(".")[2])) - .prior_state.values.root_module.resources[] | select(.address == "${key}") return_path: true properties: diff --git a/internal/plan/mappings/gcp/compute.yaml b/internal/plan/mappings/gcp/compute.yaml index 751d4ad..b5b21e4 100644 --- a/internal/plan/mappings/gcp/compute.yaml +++ b/internal/plan/mappings/gcp/compute.yaml @@ -1,6 +1,6 @@ compute_resource: google_compute_instance: - paths: .planned_values.root_module.resources[] | select(.type == "google_compute_instance") + paths: 'select(.type == "google_compute_instance")' type: resource properties: name: @@ -91,7 +91,7 @@ compute_resource: - default: ssd google_compute_instance_from_template: paths: - - .planned_values.root_module.resources[] | select(.type == "google_compute_instance_from_template") + - select(.type == "google_compute_instance_from_template") type: resource variables: properties: @@ -100,7 +100,7 @@ compute_resource: - '.configuration.root_module.resources[] | select(.address == "${this.address}") | .expressions.source_instance_template.references[] | select(endswith("id")) | gsub("\\.id$"; "")' reference: paths: - - .planned_values.root_module.resources[] | select(.address == "${key}") + - select(.address == "${key}") - .planned_values.root_module.child_modules[] | select(.address == ("${key}" | split(".")[0:2] | join("."))) | .resources[] | select(.name == ("${key}" | split(".")[2])) - .prior_state.values.root_module.resources[] | select(.address == "${key}") return_path: true diff --git a/internal/plan/mappings/gcp/compute_group.yaml b/internal/plan/mappings/gcp/compute_group.yaml index 189894e..c390c0f 100644 --- a/internal/plan/mappings/gcp/compute_group.yaml +++ b/internal/plan/mappings/gcp/compute_group.yaml @@ -1,8 +1,8 @@ compute_resource: google_compute_instance_group_manager: paths: - - .planned_values.root_module.resources[] | select(.type == "google_compute_instance_group_manager") - - .planned_values.root_module.resources[] | select(.type == "google_compute_region_instance_group_manager") + - select(.type == "google_compute_instance_group_manager") + - select(.type == "google_compute_region_instance_group_manager") type: resource variables: properties: @@ -11,8 +11,7 @@ compute_resource: - '.configuration.root_module.resources[] | select(.address == "${this.address}") | .expressions.version[0].instance_template.references[] | select(endswith("id")) | gsub("\\.id$"; "")' reference: paths: - - .planned_values.root_module.resources[] | select(.address == "${key}") - - .planned_values.root_module.child_modules[].resources | map(select(.address == "${key}")) + - select(.address == "${key}") - .prior_state.values.root_module.resources[] | select(.address == "${key}") return_path: true autoscaler: @@ -20,8 +19,8 @@ compute_resource: - '(.configuration.root_module.resources[] | select(.expressions.target?.references[]? == "${this.address}") | .address)' reference: paths: - - .planned_values.root_module.resources[] | select(.address == "${key}") - - .planned_values.root_module.child_modules[].resources | map(select(.address == "${key}")) + - select(.address == "${key}") + - map(select(.address == "${key}")) - .prior_state.values.root_module.resources[] | select(.address == "${key}") return_path: true properties: diff --git a/internal/plan/mappings/gcp/disk.yaml b/internal/plan/mappings/gcp/disk.yaml index afc9502..7742bdb 100644 --- a/internal/plan/mappings/gcp/disk.yaml +++ b/internal/plan/mappings/gcp/disk.yaml @@ -1,8 +1,8 @@ compute_resource: google_compute_disk: paths: - - .planned_values.root_module.resources[] | select(.type == "google_compute_disk") - - .planned_values.root_module.resources[] | select(.type == "google_compute_region_disk") + - select(.type == "google_compute_disk") + - select(.type == "google_compute_region_disk") type: resource properties: name: diff --git a/internal/plan/mappings/gcp/sql_database.yaml b/internal/plan/mappings/gcp/sql_database.yaml index 52bba5b..ba2f3e4 100644 --- a/internal/plan/mappings/gcp/sql_database.yaml +++ b/internal/plan/mappings/gcp/sql_database.yaml @@ -1,6 +1,6 @@ compute_resource: google_sql_database_instance: - paths: .planned_values.root_module.resources[] | select(.type == "google_sql_database_instance") + paths: select(.type == "google_sql_database_instance") type: resource properties: name: diff --git a/internal/plan/resolver.go b/internal/plan/resolver.go index 46b29b2..0ea3fe2 100644 --- a/internal/plan/resolver.go +++ b/internal/plan/resolver.go @@ -6,7 +6,6 @@ import ( "regexp" "github.com/carboniferio/carbonifer/internal/data" - "github.com/carboniferio/carbonifer/internal/utils" "github.com/pkg/errors" "github.com/shopspring/decimal" log "github.com/sirupsen/logrus" @@ -76,7 +75,7 @@ func resolveReference(key string, reference *Reference, context *tfContext) (int return nil, err } for _, path := range paths { - referencedItems, err := utils.GetJSON(path, *TfPlan) + referencedItems, err := getJSON(path, *TfPlan) if err != nil { errW := errors.Wrapf(err, "Cannot find referenced path in terraform plan: '%v'", path) return nil, errW diff --git a/internal/plan/resources.go b/internal/plan/resources.go index 42f3c98..457f5be 100644 --- a/internal/plan/resources.go +++ b/internal/plan/resources.go @@ -21,19 +21,27 @@ var TfPlan *map[string]interface{} func GetResources(tfplan *map[string]interface{}) (map[string]resources.Resource, error) { TfPlan = tfplan + plannedResources := []interface{}{} + // Get resources from Terraform plan - plannedResourcesResult, err := utils.GetJSON(".planned_values.root_module.resources", *TfPlan) + jqPath := ".planned_values | .. | objects | select(has(\"resources\")) | .resources[]" + plannedResourcesResult, err := utils.GetJSON(jqPath, *TfPlan) + if err != nil { return nil, err } - if len(plannedResourcesResult) == 0 { - return nil, errors.New("No resources found in Terraform plan") + if len(plannedResourcesResult) > 0 { + for _, plannedResourceI := range plannedResourcesResult { + plannedResource, ok := plannedResourceI.(map[string]interface{}) + if !ok { + return nil, errors.Errorf("Cannot parse planned resource %v", plannedResourceI) + } + plannedResources = append(plannedResources, plannedResource) + } } - plannedResources := plannedResourcesResult[0].([]interface{}) - log.Debugf("Reading resources from Terraform plan: %d resources", len(plannedResources)) - resourcesMap := map[string]resources.Resource{} // Get compute resources + resourcesMap := map[string]resources.Resource{} mapping, err := GetMapping() if err != nil { errW := errors.Wrap(err, "Cannot get mapping") @@ -69,6 +77,7 @@ func GetResources(tfplan *map[string]interface{}) (map[string]resources.Resource } unsupportedResource := resources.UnsupportedResource{ Identification: &resources.ResourceIdentification{ + Address: resourceAddress, Name: resource["name"].(string), ResourceType: resourceType, Provider: provider, @@ -111,7 +120,7 @@ func getResourcesOfType(resourceType string, mapping *ResourceMapping) ([]resour resourcesResult := []resources.Resource{} for _, path := range paths { log.Debugf(" Reading resources of type '%s' from path '%s'", resourceType, path) - resourcesFound, err := utils.GetJSON(path, *TfPlan) + resourcesFound, err := getJSON(path, *TfPlan) if err != nil { errW := errors.Wrapf(err, "Cannot find resource for path %v", path) return nil, errW @@ -182,6 +191,7 @@ func GetComputeResource(resourceI interface{}, resourceMapping *ResourceMapping, ResourceType: *resourceType, Provider: provider, Region: *region, + Address: resourceAddress, }, Specs: &resources.ComputeResourceSpecs{ HddStorage: decimal.Zero, diff --git a/internal/plan/test/resources_aws_rds_test.go b/internal/plan/test/resources_aws_rds_test.go index 862d7d4..715ec37 100644 --- a/internal/plan/test/resources_aws_rds_test.go +++ b/internal/plan/test/resources_aws_rds_test.go @@ -27,6 +27,7 @@ func TestGetResource_RDS(t *testing.T) { wantResources := map[string]resources.Resource{ "aws_db_instance.first": resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "aws_db_instance.first", Name: "first", ResourceType: "aws_db_instance", Provider: providers.AWS, @@ -43,6 +44,7 @@ func TestGetResource_RDS(t *testing.T) { }, "aws_db_instance.second": resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "aws_db_instance.second", Name: "second", ResourceType: "aws_db_instance", Provider: providers.AWS, @@ -59,6 +61,7 @@ func TestGetResource_RDS(t *testing.T) { }, "aws_db_instance.third": resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "aws_db_instance.third", Name: "third", ResourceType: "aws_db_instance", Provider: providers.AWS, @@ -78,7 +81,7 @@ func TestGetResource_RDS(t *testing.T) { assert.NoError(t, err) gotResources, err := plan.GetResources(tfPlan) assert.NoError(t, err) - for _, res := range gotResources { - assert.Equal(t, wantResources[res.GetAddress()], res) + for _, got := range gotResources { + assert.Equal(t, wantResources[got.GetAddress()], got) } } diff --git a/internal/plan/test/resources_aws_test.go b/internal/plan/test/resources_aws_test.go index 8e51ce5..e642324 100644 --- a/internal/plan/test/resources_aws_test.go +++ b/internal/plan/test/resources_aws_test.go @@ -28,6 +28,7 @@ func TestGetResource_DiskFromAMI(t *testing.T) { "aws_instance.foo": resources.ComputeResource{ Identification: &resources.ResourceIdentification{ Name: "foo", + Address: "aws_instance.foo", ResourceType: "aws_instance", Provider: providers.AWS, Region: "eu-west-3", @@ -43,6 +44,7 @@ func TestGetResource_DiskFromAMI(t *testing.T) { }, "aws_ebs_volume.ebs_volume": resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "aws_ebs_volume.ebs_volume", Name: "ebs_volume", ResourceType: "aws_ebs_volume", Provider: providers.AWS, @@ -57,6 +59,7 @@ func TestGetResource_DiskFromAMI(t *testing.T) { }, "aws_network_interface.foo": resources.UnsupportedResource{ Identification: &resources.ResourceIdentification{ + Address: "aws_network_interface.foo", Name: "foo", ResourceType: "aws_network_interface", Provider: providers.AWS, @@ -65,6 +68,7 @@ func TestGetResource_DiskFromAMI(t *testing.T) { }, "aws_subnet.my_subnet": resources.UnsupportedResource{ Identification: &resources.ResourceIdentification{ + Address: "aws_subnet.my_subnet", Name: "my_subnet", ResourceType: "aws_subnet", Provider: providers.AWS, diff --git a/internal/plan/test/resources_childmodules_test.go b/internal/plan/test/resources_childmodules_test.go new file mode 100644 index 0000000..c6a9dce --- /dev/null +++ b/internal/plan/test/resources_childmodules_test.go @@ -0,0 +1,105 @@ +package plan_test + +import ( + "path" + "testing" + + "github.com/carboniferio/carbonifer/internal/plan" + "github.com/carboniferio/carbonifer/internal/providers" + "github.com/carboniferio/carbonifer/internal/resources" + "github.com/carboniferio/carbonifer/internal/terraform" + "github.com/carboniferio/carbonifer/internal/testutils" + "github.com/shopspring/decimal" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func TestGetResource_ChildModules(t *testing.T) { + + testutils.SkipWithCreds(t) + + // reset + terraform.ResetTerraformExec() + + wd := path.Join(testutils.RootDir, "test/terraform/gcp_large") + viper.Set("workdir", wd) + + wantResources := map[string]resources.Resource{ + "module.backend.module.db.google_sql_database_instance.instance": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "instance", + ResourceType: "google_sql_database_instance", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + Address: "module.backend.module.db.google_sql_database_instance.instance", + }, + Specs: &resources.ComputeResourceSpecs{ + VCPUs: int32(1), + MemoryMb: int32(1740), + ReplicationFactor: 2, + HddStorage: decimal.Zero, + SsdStorage: decimal.NewFromInt(10), + }, + }, + "module.backend.module.middleware.module.api_ms.google_compute_instance.cbf-test-vm": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "cbf-test-vm", + ResourceType: "google_compute_instance", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + Address: "module.backend.module.middleware.module.api_ms.google_compute_instance.cbf-test-vm", + }, + Specs: &resources.ComputeResourceSpecs{ + VCPUs: int32(12), + MemoryMb: int32(87040), + ReplicationFactor: 1, + HddStorage: decimal.NewFromInt(10), + SsdStorage: decimal.Zero, + }, + }, + "module.backend.module.middleware.module.users_ms.google_compute_instance.cbf-test-vm": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "cbf-test-vm", + ResourceType: "google_compute_instance", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + Address: "module.backend.module.middleware.module.users_ms.google_compute_instance.cbf-test-vm", + }, + Specs: &resources.ComputeResourceSpecs{ + VCPUs: int32(2), + MemoryMb: int32(7680), + ReplicationFactor: 1, + HddStorage: decimal.NewFromInt(10), + SsdStorage: decimal.Zero, + }, + }, + "module.network.google_compute_network.vpc_network": resources.UnsupportedResource{ + Identification: &resources.ResourceIdentification{ + Name: "vpc_network", + ResourceType: "google_compute_network", + Provider: providers.GCP, + Count: 1, + Address: "module.network.google_compute_network.vpc_network", + }, + }, + "module.network.google_compute_subnetwork.default": resources.UnsupportedResource{ + Identification: &resources.ResourceIdentification{ + Name: "default", + ResourceType: "google_compute_subnetwork", + Provider: providers.GCP, + Count: 1, + Address: "module.network.google_compute_subnetwork.default", + }, + }, + } + tfPlan, err := terraform.TerraformPlan() + assert.NoError(t, err) + gotResources, err := plan.GetResources(tfPlan) + assert.NoError(t, err) + for _, got := range gotResources { + assert.Equal(t, wantResources[got.GetAddress()], got) + } +} diff --git a/internal/plan/test/resources_gcp_test.go b/internal/plan/test/resources_gcp_test.go index 42a5e1a..d478050 100644 --- a/internal/plan/test/resources_gcp_test.go +++ b/internal/plan/test/resources_gcp_test.go @@ -104,6 +104,7 @@ func TestGetResource(t *testing.T) { }, want: resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "google_compute_disk.disk1", Name: "disk1", ResourceType: "google_compute_disk", Provider: providers.GCP, @@ -125,6 +126,7 @@ func TestGetResource(t *testing.T) { }, want: resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "google_compute_disk.disk2", Name: "disk2", ResourceType: "google_compute_disk", Provider: providers.GCP, @@ -146,6 +148,7 @@ func TestGetResource(t *testing.T) { }, want: resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "google_compute_region_disk.diskr", Name: "diskr", ResourceType: "google_compute_region_disk", Provider: providers.GCP, @@ -167,6 +170,7 @@ func TestGetResource(t *testing.T) { }, want: resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "google_compute_instance.attachedgpu", Name: "attachedgpu", ResourceType: "google_compute_instance", Provider: providers.GCP, @@ -194,6 +198,7 @@ func TestGetResource(t *testing.T) { }, want: resources.ComputeResource{ Identification: &resources.ResourceIdentification{ + Address: "google_compute_instance.defaultgpu", Name: "defaultgpu", ResourceType: "google_compute_instance", Provider: providers.GCP, diff --git a/internal/resources/aws_ebs.go b/internal/resources/aws_ebs.go index df3308f..2ec4ff3 100644 --- a/internal/resources/aws_ebs.go +++ b/internal/resources/aws_ebs.go @@ -1,9 +1,5 @@ package resources -import ( - "fmt" -) - type EbsDataResource struct { Identification *ResourceIdentification DataImageSpecs []*DataImageSpecs @@ -15,7 +11,7 @@ func (r EbsDataResource) GetIdentification() *ResourceIdentification { } func (r EbsDataResource) GetAddress() string { - return fmt.Sprintf("data.%v.%v", r.GetIdentification().ResourceType, r.GetIdentification().Name) + return r.Identification.Address } func (r EbsDataResource) GetKey() string { diff --git a/internal/resources/compute.go b/internal/resources/compute.go index 8ff02c6..155a1e2 100644 --- a/internal/resources/compute.go +++ b/internal/resources/compute.go @@ -1,8 +1,6 @@ package resources import ( - "fmt" - "github.com/carboniferio/carbonifer/internal/providers" "github.com/shopspring/decimal" ) @@ -26,6 +24,7 @@ type ResourceIdentification struct { Provider providers.Provider Region string Count int64 + Address string } // ComputeResource is the struct that contains the info of a compute resource @@ -46,7 +45,7 @@ func (r ComputeResource) GetIdentification() *ResourceIdentification { // GetAddress returns the address of the resource func (r ComputeResource) GetAddress() string { - return fmt.Sprintf("%v.%v", r.GetIdentification().ResourceType, r.GetIdentification().Name) + return r.Identification.Address } // UnsupportedResource is the struct that contains the info of an unsupported resource @@ -66,7 +65,7 @@ func (r UnsupportedResource) GetIdentification() *ResourceIdentification { // GetAddress returns the address of the resource func (r UnsupportedResource) GetAddress() string { - return fmt.Sprintf("%v.%v", r.GetIdentification().ResourceType, r.GetIdentification().Name) + return r.Identification.Address } // Resource is the interface that contains the info of a resource diff --git a/internal/resources/data.go b/internal/resources/data.go index 097d935..c5904e7 100644 --- a/internal/resources/data.go +++ b/internal/resources/data.go @@ -1,7 +1,5 @@ package resources -import "fmt" - // DataImageSpecs is the struct that contains the specs of a data image type DataImageSpecs struct { DiskSizeGb float64 @@ -22,7 +20,7 @@ func (r DataImageResource) GetIdentification() *ResourceIdentification { // GetAddress returns the address of the resource func (r DataImageResource) GetAddress() string { - return fmt.Sprintf("data.%v.%v", r.GetIdentification().ResourceType, r.GetIdentification().Name) + return r.Identification.Address } // GetKey returns the key of the resource diff --git a/pkg/estimate/estimate_test.go b/pkg/estimate/estimate_test.go index 0b00d41..c90642c 100644 --- a/pkg/estimate/estimate_test.go +++ b/pkg/estimate/estimate_test.go @@ -23,6 +23,7 @@ func TestGetEstimation(t *testing.T) { name: "e2-standard-2", args: args{ resource: resources.GenericResource{ + Address: "google_compute_instance.e2-standard-2", Name: "e2-standard-2", Region: "europe-west4", Provider: providers.GCP, @@ -41,6 +42,7 @@ func TestGetEstimation(t *testing.T) { }, want: EstimationReport{ Resource: resources.GenericResource{ + Address: "google_compute_instance.e2-standard-2", Name: "e2-standard-2", Region: "europe-west4", Provider: providers.GCP, diff --git a/pkg/resources/ressource.go b/pkg/resources/ressource.go index ab4cfaa..1b0c20f 100644 --- a/pkg/resources/ressource.go +++ b/pkg/resources/ressource.go @@ -13,6 +13,7 @@ import ( // GenericResource is a struct that contains the information of a generic resource type GenericResource struct { + Address string Name string Region string Provider providers.Provider @@ -43,12 +44,13 @@ func (g GenericResource) GetIdentification() *resources.ResourceIdentification { Provider: internalProvider.Provider(g.Provider), Region: g.Region, Count: 1, + Address: g.Address, } } // GetAddress returns the address of the resource func (g GenericResource) GetAddress() string { - return fmt.Sprintf("%v.%v", g.GetIdentification().ResourceType, g.GetIdentification().Name) + return g.Address } // Storage is the struct that contains the storage of a resource diff --git a/pkg/resources/ressource_test.go b/pkg/resources/ressource_test.go index 83bbe76..aebfd31 100644 --- a/pkg/resources/ressource_test.go +++ b/pkg/resources/ressource_test.go @@ -9,6 +9,7 @@ import ( func TestGenericResource_IsSupported(t *testing.T) { type fields struct { + Address string Name string Region string Provider providers.Provider @@ -40,6 +41,7 @@ func TestGenericResource_IsSupported(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := GenericResource{ + Address: tt.fields.Address, Name: tt.fields.Name, Region: tt.fields.Region, Provider: tt.fields.Provider, diff --git a/test/outputs/nothing.txt b/test/outputs/nothing.txt index 022f056..84ba7fa 100644 --- a/test/outputs/nothing.txt +++ b/test/outputs/nothing.txt @@ -1,9 +1,9 @@ Average estimation of CO2 emissions per instance: - --------------- ------- ------- ------------------------ - resource type name count emissions per instance - --------------- ------- ------- ------------------------ - --------------- ------- ------- ------------------------ - Total 0 0.0000 gCO2eq/h - --------------- ------- ------- ------------------------ \ No newline at end of file + --------------- --------- ------- ------------------------ + resource type address count emissions per instance + --------------- --------- ------- ------------------------ + --------------- --------- ------- ------------------------ + Total 0 0.0000 gCO2eq/h + --------------- --------- ------- ------------------------ \ No newline at end of file diff --git a/test/terraform/gcp_large/main.tf b/test/terraform/gcp_large/main.tf new file mode 100644 index 0000000..f34e6a7 --- /dev/null +++ b/test/terraform/gcp_large/main.tf @@ -0,0 +1,20 @@ +provider "google" { + #project = "carbonifer-sandbox" + region = var.region + credentials = file("/Users/olivier/carbonifer/carbonifer-study/accounts/carbonifer-sandbox-c59717d85f65.json") + //access_token = "foo" +} + +module "network" { + source = "./modules/network" + project_id = var.project_id + region = var.region +} + +module "backend" { + source = "./modules/backend" + project_id = var.project_id + region = var.region + subnet_name = module.network.subnet_name +} + diff --git a/test/terraform/gcp_large/modules/backend/database/main.tf b/test/terraform/gcp_large/modules/backend/database/main.tf new file mode 100644 index 0000000..2764a61 --- /dev/null +++ b/test/terraform/gcp_large/modules/backend/database/main.tf @@ -0,0 +1,11 @@ +resource "google_sql_database_instance" "instance" { + name = "my-database-instance" + region = var.region + project = var.project_id + database_version = "POSTGRES_14" + settings { + tier = "db-g1-small" + availability_type = "REGIONAL" + } +} + diff --git a/test/terraform/gcp_large/modules/backend/database/variables.tf b/test/terraform/gcp_large/modules/backend/database/variables.tf new file mode 100644 index 0000000..4b50a8c --- /dev/null +++ b/test/terraform/gcp_large/modules/backend/database/variables.tf @@ -0,0 +1,2 @@ +variable "project_id" {} +variable "region" {} \ No newline at end of file diff --git a/test/terraform/gcp_large/modules/backend/main.tf b/test/terraform/gcp_large/modules/backend/main.tf new file mode 100644 index 0000000..84fa9df --- /dev/null +++ b/test/terraform/gcp_large/modules/backend/main.tf @@ -0,0 +1,15 @@ +module "middleware" { + source = "./middleware" + project_id = var.project_id + region = var.region + subnet_name = var.subnet_name +} + +module "db" { + source = "./database" + project_id = var.project_id + region = var.region +} + + + diff --git a/test/terraform/gcp_large/modules/backend/middleware/main.tf b/test/terraform/gcp_large/modules/backend/middleware/main.tf new file mode 100644 index 0000000..f7fe0f9 --- /dev/null +++ b/test/terraform/gcp_large/modules/backend/middleware/main.tf @@ -0,0 +1,19 @@ +module "users_ms" { + source = "../../compute" + project_id = var.project_id + region = var.region + subnet_name = var.subnet_name + instance_role = "users" + instance_type = "n1-standard-2" +} + +module "api_ms" { + source = "../../compute" + project_id = var.project_id + region = var.region + subnet_name = var.subnet_name + instance_role = "api" + instance_type = "a2-highgpu-1g" +} + + diff --git a/test/terraform/gcp_large/modules/backend/middleware/variables.tf b/test/terraform/gcp_large/modules/backend/middleware/variables.tf new file mode 100644 index 0000000..f2dff1f --- /dev/null +++ b/test/terraform/gcp_large/modules/backend/middleware/variables.tf @@ -0,0 +1,3 @@ +variable "project_id" {} +variable "region" {} +variable "subnet_name" {} \ No newline at end of file diff --git a/test/terraform/gcp_large/modules/backend/variables.tf b/test/terraform/gcp_large/modules/backend/variables.tf new file mode 100644 index 0000000..f2dff1f --- /dev/null +++ b/test/terraform/gcp_large/modules/backend/variables.tf @@ -0,0 +1,3 @@ +variable "project_id" {} +variable "region" {} +variable "subnet_name" {} \ No newline at end of file diff --git a/test/terraform/gcp_large/modules/compute/main.tf b/test/terraform/gcp_large/modules/compute/main.tf new file mode 100644 index 0000000..bd9a421 --- /dev/null +++ b/test/terraform/gcp_large/modules/compute/main.tf @@ -0,0 +1,28 @@ +resource "google_compute_instance" "cbf-test-vm" { + name = "cbf-compute-${var.instance_role}" + machine_type = var.instance_type + zone = "${var.region}-a" + project = var.project_id + tags = ["ssh"] + + boot_disk { + initialize_params { + image = data.google_compute_image.debian.self_link + } + } + + + # Install Flask + metadata_startup_script = "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask" + + network_interface { + subnetwork = var.subnet_name + } + +} + + +data "google_compute_image" "debian" { + family = "debian-11" + project = "debian-cloud" +} diff --git a/test/terraform/gcp_large/modules/compute/variables.tf b/test/terraform/gcp_large/modules/compute/variables.tf new file mode 100644 index 0000000..b35f121 --- /dev/null +++ b/test/terraform/gcp_large/modules/compute/variables.tf @@ -0,0 +1,5 @@ +variable "project_id" {} +variable "region" {} +variable "subnet_name" {} +variable "instance_role" {} +variable "instance_type" {} \ No newline at end of file diff --git a/test/terraform/gcp_large/modules/network/main.tf b/test/terraform/gcp_large/modules/network/main.tf new file mode 100644 index 0000000..e1c9d47 --- /dev/null +++ b/test/terraform/gcp_large/modules/network/main.tf @@ -0,0 +1,14 @@ +resource "google_compute_network" "vpc_network" { + name = "cbf-network" + auto_create_subnetworks = false + mtu = 1460 + project = var.project_id +} + +resource "google_compute_subnetwork" "default" { + name = "cbf-subnet" + ip_cidr_range = "10.0.1.0/24" + region = var.region + network = google_compute_network.vpc_network.id + project = var.project_id +} \ No newline at end of file diff --git a/test/terraform/gcp_large/modules/network/output.tf b/test/terraform/gcp_large/modules/network/output.tf new file mode 100644 index 0000000..b4b9fe3 --- /dev/null +++ b/test/terraform/gcp_large/modules/network/output.tf @@ -0,0 +1,3 @@ +output "subnet_name" { + value = google_compute_subnetwork.default.name +} diff --git a/test/terraform/gcp_large/modules/network/variables.tf b/test/terraform/gcp_large/modules/network/variables.tf new file mode 100644 index 0000000..6ee262a --- /dev/null +++ b/test/terraform/gcp_large/modules/network/variables.tf @@ -0,0 +1,2 @@ +variable "project_id" {} +variable "region" {} diff --git a/test/terraform/gcp_large/variables.tf b/test/terraform/gcp_large/variables.tf new file mode 100644 index 0000000..3cdd7cd --- /dev/null +++ b/test/terraform/gcp_large/variables.tf @@ -0,0 +1,7 @@ +variable "region" { + default = "europe-west9" +} + +variable "project_id" { + default = "carbonifer-sandbox" +} \ No newline at end of file