From dd881097a4d3004b07298fc313ed4f4df60fbb03 Mon Sep 17 00:00:00 2001 From: ParthaI <47887552+ParthaI@users.noreply.github.com> Date: Thu, 1 Feb 2024 13:08:06 +0530 Subject: [PATCH] Add table gcp_compute_machine_image Closes #517 (#519) --- docs/tables/gcp_compute_machine_image.md | 229 ++++++++++++++++++ .../dependencies.txt | 0 .../test-get-expected.json | 8 + .../test-get-query.sql | 3 + .../test-list-expected.json | 7 + .../test-list-query.sql | 3 + .../test-notfound-expected.json | 1 + .../test-notfound-query.sql | 3 + .../test-turbot-expected.json | 6 + .../test-turbot-query.sql | 3 + .../gcp_compute_machine_image/variables.json | 1 + .../gcp_compute_machine_image/variables.tf | 78 ++++++ gcp/plugin.go | 1 + gcp/table_gcp_compute_machine_image.go | 229 ++++++++++++++++++ 14 files changed, 572 insertions(+) create mode 100644 docs/tables/gcp_compute_machine_image.md create mode 100644 gcp-test/tests/gcp_compute_machine_image/dependencies.txt create mode 100644 gcp-test/tests/gcp_compute_machine_image/test-get-expected.json create mode 100644 gcp-test/tests/gcp_compute_machine_image/test-get-query.sql create mode 100644 gcp-test/tests/gcp_compute_machine_image/test-list-expected.json create mode 100644 gcp-test/tests/gcp_compute_machine_image/test-list-query.sql create mode 100644 gcp-test/tests/gcp_compute_machine_image/test-notfound-expected.json create mode 100644 gcp-test/tests/gcp_compute_machine_image/test-notfound-query.sql create mode 100644 gcp-test/tests/gcp_compute_machine_image/test-turbot-expected.json create mode 100644 gcp-test/tests/gcp_compute_machine_image/test-turbot-query.sql create mode 100644 gcp-test/tests/gcp_compute_machine_image/variables.json create mode 100644 gcp-test/tests/gcp_compute_machine_image/variables.tf create mode 100644 gcp/table_gcp_compute_machine_image.go diff --git a/docs/tables/gcp_compute_machine_image.md b/docs/tables/gcp_compute_machine_image.md new file mode 100644 index 00000000..c8966d5d --- /dev/null +++ b/docs/tables/gcp_compute_machine_image.md @@ -0,0 +1,229 @@ +--- +title: "Steampipe Table: gcp_compute_machine_image - Query Google Cloud Platform Compute Machine Image using SQL" +description: "Allows users to query Compute Machine Images in Google Cloud Platform, providing detailed information about available machine images and their specifications." +--- + +# Table: gcp_compute_machine_image - Query Google Cloud Platform Compute Machine Image using SQL + +A machine image is a Compute Engine resource that stores all the configuration, metadata, permissions, and data from multiple disks of a virtual machine (VM) instance. You can use a machine image in many system maintenance, backup and recovery, and instance cloning scenarios. + +## Table Usage Guide + +The `gcp_compute_machine_image` table provides insights into the available machine images within Google Cloud Platform's Compute Engine. As a cloud architect or DevOps engineer, you can explore machine image-specific details through this table, kind, source instance, instance properties, image status, image storage, and associated metadata. Utilize it to understand the specifications of each machine image, aiding in the selection of the most suitable machine image for your applications based on performance requirements and cost efficiency. + +## Examples + +### Basic info +Assess the elements within your Google Cloud Platform to understand the capacity and capabilities of each machine image. This can help to get the metadata about the compute images. + +```sql+postgres +select + name, + id, + description, + creation_timestamp, + guest_flush, + source_instance +from + gcp_compute_machine_image; +``` + +```sql+sqlite +select + name, + id, + description, + creation_timestamp, + guest_flush, + source_instance +from + gcp_compute_machine_image; +``` + +### List machine images that are available +Ensures that only machine images that are ready for deployment or use are considered, which is critical for operational stability and reliability. Useful in automated scripts or applications where only machine images in a 'READY' state should be utilized. Helps in maintaining a clean and efficient image repository by focusing on images that are fully prepared and excluding those that are still in preparation or have been deprecated. + +```sql+postgres +select + name, + id, + description, + creation_timestamp, + status +from + gcp_compute_machine_image +where + status = 'READY'; +``` + +```sql+sqlite +select + name, + id, + description, + creation_timestamp, + status +from + gcp_compute_machine_image +where + status = 'READY'; +``` + +### List the top 5 machine images that consume highest storage +This query is particularly useful in cloud infrastructure management and optimization, where understanding and managing storage utilization is a key concern. It helps administrators and users quickly identify the most space-efficient machine images available in their GCP environment. + +```sql+postgres +select + name, + id, + self_link, + status, + total_storage_bytes +from + gcp_compute_machine_image +order by + total_storage_bytes asc +limit 5; +``` + +```sql+sqlite +select + name, + id, + self_link, + status, + total_storage_bytes +from + gcp_compute_machine_image +order by + total_storage_bytes asc +limit 5; +``` + +### Get instance properties of the machine images +Useful for analyzing the detailed configurations of machine images, including hardware features, network settings, and security configurations. Assists in planning and optimizing cloud infrastructure based on the capabilities and configurations of available machine images. + +```sql+postgres +select + name, + id, + instance_properties -> 'advancedMachineFeatures' as advanced_machine_features, + instance_properties ->> 'canIpForward' as can_ip_forward, + instance_properties -> 'confidentialInstanceConfig' as confidential_instance_config, + instance_properties ->> 'description' as description, + instance_properties -> 'disks' as disks, + instance_properties -> 'guestAccelerators' as guest_accelerators, + instance_properties ->> 'keyRevocationActionType' as key_revocation_action_type, + instance_properties -> 'labels' as labels, + instance_properties ->> 'machineType' as machine_type, + instance_properties -> 'metadata' as metadata, + instance_properties -> 'minCpuPlatform' as min_cpu_platform, + instance_properties -> 'networkInterfaces' as network_interfaces, + instance_properties -> 'networkPerformanceConfig' as network_performance_config, + instance_properties -> 'privateIpv6GoogleAccess' as private_ipv6_google_access, + instance_properties ->> 'reservationAffinity' as reservation_affinity, + instance_properties -> 'resourceManagerTags' as resource_manager_tags, + instance_properties -> 'resourcePolicies' as resource_policies, + instance_properties -> 'scheduling' as scheduling, + instance_properties -> 'serviceAccounts' as service_accounts, + instance_properties -> 'shieldedInstanceConfig' as shielded_instance_config, + instance_properties -> 'tags' as tags +from + gcp_compute_machine_image; +``` + +```sql+sqlite +select + name, + id, + json_extract(instance_properties, '$.advancedMachineFeatures') as advanced_machine_features, + json_extract(instance_properties, '$.canIpForward') as can_ip_forward, + json_extract(instance_properties, '$.confidentialInstanceConfig') as confidential_instance_config, + json_extract(instance_properties, '$.description') as description, + json_extract(instance_properties, '$.disks') as disks, + json_extract(instance_properties, '$.guestAccelerators') as guest_accelerators, + json_extract(instance_properties, '$.keyRevocationActionType') as key_revocation_action_type, + json_extract(instance_properties, '$.labels') as labels, + json_extract(instance_properties, '$.machineType') as machine_type, + json_extract(instance_properties, '$.metadata') as metadata, + json_extract(instance_properties, '$.minCpuPlatform') as min_cpu_platform, + json_extract(instance_properties, '$.networkInterfaces') as network_interfaces, + json_extract(instance_properties, '$.networkPerformanceConfig') as network_performance_config, + json_extract(instance_properties, '$.privateIpv6GoogleAccess') as private_ipv6_google_access, + json_extract(instance_properties, '$.reservationAffinity') as reservation_affinity, + json_extract(instance_properties, '$.resourceManagerTags') as resource_manager_tags, + json_extract(instance_properties, '$.resourcePolicies') as resource_policies, + json_extract(instance_properties, '$.scheduling') as scheduling, + json_extract(instance_properties, '$.serviceAccounts') as service_accounts, + json_extract(instance_properties, '$.shieldedInstanceConfig') as shielded_instance_config, + json_extract(instance_properties, '$.tags') as tags +from + gcp_compute_machine_image; +``` + +### Get encryption details of the machine image +Understanding the encryption methods and keys used for each machine image is vital for security and compliance. It helps ensure that sensitive data is properly protected and that the encryption methods meet required standards. The query aids in auditing the encryption practices and managing the encryption keys across different machine images. It's particularly useful in environments with strict data protection policies. + +```sql+postgres +select + name, + machine_image_encryption_key ->> 'KmsKeyName' as kms_key_name, + machine_image_encryption_key ->> 'KmsKeyServiceAccount' as kms_key_service_account, + machine_image_encryption_key ->> 'RawKey' as raw_key, + machine_image_encryption_key ->> 'RsaEncryptedKey' as rsa_encrypted_key, + machine_image_encryption_key ->> 'Sha256' as sha256 +from + gcp_compute_machine_image; +``` + +```sql+sqlite +select + name, + json_extract(machine_image_encryption_key, '$.KmsKeyName') as kms_key_name, + json_extract(machine_image_encryption_key, '$.KmsKeyServiceAccount') as kms_key_service_account, + json_extract(machine_image_encryption_key, '$.RawKey') as raw_key, + json_extract(machine_image_encryption_key, '$.RsaEncryptedKey') as rsa_encrypted_key, + json_extract(machine_image_encryption_key, '$.Sha256') as sha256 +from + gcp_compute_machine_image; +``` + +### Get the machine type details for the machine images +Analyzing the memory, CPU, and disk capabilities of machine types can inform decisions about image deployment based on performance needs. Knowing the deprecation status and creation timestamp of machine types helps in compliance and migration planning. + +```sql+postgres +select + i.name as image_name, + i.id image_id, + i.instance_properties ->> 'machineType' as machine_type, + t.creation_timestamp as machine_type_creation_timestamp, + t.memory_mb as machine_type_memory_mb, + t.maximum_persistent_disks as machine_type_maximum_persistent_disks, + t.is_shared_cpu as machine_type_is_shared_cpu, + t.zone as machine_type_zone, + t.deprecated as machine_type_deprecated +from + gcp_compute_machine_image as i, + gcp_compute_machine_type as t +where + t.name = (i.instance_properties ->> 'machineType') and t.zone = split_part(i.source_instance, '/', 9); +``` + +```sql+sqlite +select + i.name as image_name, + i.id as image_id, + json_extract(i.instance_properties, '$.machineType') as machine_type, + t.creation_timestamp as machine_type_creation_timestamp, + t.memory_mb as machine_type_memory_mb, + t.maximum_persistent_disks as machine_type_maximum_persistent_disks, + t.is_shared_cpu as machine_type_is_shared_cpu, + t.zone as machine_type_zone, + t.deprecated as machine_type_deprecated +from + gcp_compute_machine_image as i, + gcp_compute_machine_type as t +where + t.name = json_extract(i.instance_properties, '$.machineType') + and t.zone = substr(i.source_instance, instr(i.source_instance, '/', -1) + 1); +``` \ No newline at end of file diff --git a/gcp-test/tests/gcp_compute_machine_image/dependencies.txt b/gcp-test/tests/gcp_compute_machine_image/dependencies.txt new file mode 100644 index 00000000..e69de29b diff --git a/gcp-test/tests/gcp_compute_machine_image/test-get-expected.json b/gcp-test/tests/gcp_compute_machine_image/test-get-expected.json new file mode 100644 index 00000000..86d7ca81 --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/test-get-expected.json @@ -0,0 +1,8 @@ +[ + { + "kind": "compute#machineImage", + "name": "{{ resourceName }}", + "self_link": "{{ output.self_link.value }}", + "title": "{{ resourceName }}" + } +] diff --git a/gcp-test/tests/gcp_compute_machine_image/test-get-query.sql b/gcp-test/tests/gcp_compute_machine_image/test-get-query.sql new file mode 100644 index 00000000..19bc6d38 --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/test-get-query.sql @@ -0,0 +1,3 @@ +select name, title, kind, self_link +from gcp.gcp_compute_machine_image +where name = '{{ resourceName }}'; \ No newline at end of file diff --git a/gcp-test/tests/gcp_compute_machine_image/test-list-expected.json b/gcp-test/tests/gcp_compute_machine_image/test-list-expected.json new file mode 100644 index 00000000..a2f06dbf --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/test-list-expected.json @@ -0,0 +1,7 @@ +[ + { + "akas": ["{{ output.resource_aka.value }}"], + "name": "{{ resourceName }}", + "title": "{{ resourceName }}" + } +] diff --git a/gcp-test/tests/gcp_compute_machine_image/test-list-query.sql b/gcp-test/tests/gcp_compute_machine_image/test-list-query.sql new file mode 100644 index 00000000..d491611f --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/test-list-query.sql @@ -0,0 +1,3 @@ +select name, title, akas +from gcp.gcp_compute_machine_image +where akas::text = '["{{ output.resource_aka.value }}"]'; \ No newline at end of file diff --git a/gcp-test/tests/gcp_compute_machine_image/test-notfound-expected.json b/gcp-test/tests/gcp_compute_machine_image/test-notfound-expected.json new file mode 100644 index 00000000..19765bd5 --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/test-notfound-expected.json @@ -0,0 +1 @@ +null diff --git a/gcp-test/tests/gcp_compute_machine_image/test-notfound-query.sql b/gcp-test/tests/gcp_compute_machine_image/test-notfound-query.sql new file mode 100644 index 00000000..83893d45 --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/test-notfound-query.sql @@ -0,0 +1,3 @@ +select name, id +from gcp.gcp_compute_machine_image +where name = 'dummy{{ resourceName }}'; \ No newline at end of file diff --git a/gcp-test/tests/gcp_compute_machine_image/test-turbot-expected.json b/gcp-test/tests/gcp_compute_machine_image/test-turbot-expected.json new file mode 100644 index 00000000..bd3dd708 --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/test-turbot-expected.json @@ -0,0 +1,6 @@ +[ + { + "akas": ["{{ output.resource_aka.value }}"], + "title": "{{ resourceName }}" + } +] diff --git a/gcp-test/tests/gcp_compute_machine_image/test-turbot-query.sql b/gcp-test/tests/gcp_compute_machine_image/test-turbot-query.sql new file mode 100644 index 00000000..2239583d --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/test-turbot-query.sql @@ -0,0 +1,3 @@ +select title, akas +from gcp.gcp_compute_machine_image +where name = '{{ resourceName }}'; \ No newline at end of file diff --git a/gcp-test/tests/gcp_compute_machine_image/variables.json b/gcp-test/tests/gcp_compute_machine_image/variables.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/variables.json @@ -0,0 +1 @@ +{} diff --git a/gcp-test/tests/gcp_compute_machine_image/variables.tf b/gcp-test/tests/gcp_compute_machine_image/variables.tf new file mode 100644 index 00000000..9c9d378a --- /dev/null +++ b/gcp-test/tests/gcp_compute_machine_image/variables.tf @@ -0,0 +1,78 @@ + +variable "resource_name" { + type = string + default = "turbot-test-20200125-create-update" + description = "Name of the resource used throughout the test." +} + +variable "gcp_project" { + type = string + default = "parker-aaa" + description = "GCP project used for the test." +} + +variable "gcp_region" { + type = string + default = "us-east1" + description = "GCP region used for the test." +} + +variable "gcp_zone" { + type = string + default = "us-east1-b" +} + +provider "google" { + project = var.gcp_project + region = var.gcp_region + zone = var.gcp_zone +} + +data "google_client_config" "current" {} + +data "null_data_source" "resource" { + inputs = { + scope = "gcp://cloudresourcemanager.googleapis.com/projects/${data.google_client_config.current.project}" + } +} + +resource "google_compute_instance" "names_test_resource" { + provider = google-beta + name = var.resource_name + machine_type = "f1-micro" + zone = "us-east1-b" + project = var.gcp_project + + boot_disk { + initialize_params { + image = "debian-cloud/debian-11" + } + } + + network_interface { + network = "default" + } +} + +resource "google_compute_machine_image" "names_test_resource" { + provider = google-beta + project = var.gcp_project + name = var.resource_name + source_instance = google_compute_instance.names_test_resource.self_link +} + +output "machine_type" { + value = "f1-micro" +} + +output "resource_name" { + value = var.resource_name +} + +output "self_link" { + value = google_compute_machine_image.names_test_resource.self_link +} + +output "resource_aka" { + value = "gcp://compute.googleapis.com/projects/${var.gcp_project}/machineImages/${var.resource_name}" +} diff --git a/gcp/plugin.go b/gcp/plugin.go index 9be58f53..d035dd95 100644 --- a/gcp/plugin.go +++ b/gcp/plugin.go @@ -68,6 +68,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "gcp_compute_instance_metric_cpu_utilization_daily": tableGcpComputeInstanceMetricCpuUtilizationDaily(ctx), "gcp_compute_instance_metric_cpu_utilization_hourly": tableGcpComputeInstanceMetricCpuUtilizationHourly(ctx), "gcp_compute_instance_template": tableGcpComputeInstanceTemplate(ctx), + "gcp_compute_machine_image": tableGcpComputeMachineImage(ctx), "gcp_compute_machine_type": tableGcpComputeMachineType(ctx), "gcp_compute_network": tableGcpComputeNetwork(ctx), "gcp_compute_node_group": tableGcpComputeNodeGroup(ctx), diff --git a/gcp/table_gcp_compute_machine_image.go b/gcp/table_gcp_compute_machine_image.go new file mode 100644 index 00000000..299deeea --- /dev/null +++ b/gcp/table_gcp_compute_machine_image.go @@ -0,0 +1,229 @@ +package gcp + +import ( + "context" + "strings" + + "github.com/turbot/go-kit/types" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + "google.golang.org/api/compute/v1" +) + +//// TABLE DEFINITION + +func tableGcpComputeMachineImage(ctx context.Context) *plugin.Table { + return &plugin.Table{ + Name: "gcp_compute_machine_image", + Description: "GCP Compute Machine Image", + Get: &plugin.GetConfig{ + KeyColumns: plugin.SingleColumn("name"), + Hydrate: getComputeMachineImage, + }, + List: &plugin.ListConfig{ + Hydrate: listComputeMachineImages, + }, + Columns: []*plugin.Column{ + { + Name: "name", + Description: "Name of the resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "id", + Description: "A unique identifier for this machine image. The server defines this identifier.", + Type: proto.ColumnType_INT, + }, + { + Name: "self_link", + Description: "The URL for this machine image. The server defines this URL.", + Type: proto.ColumnType_STRING, + }, + { + Name: "creation_timestamp", + Description: "The creation timestamp for this machine image in RFC3339 text format.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromGo().NullIfZero(), + }, + { + Name: "description", + Description: "An optional description of this resource. Provide this property when you create the resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "guest_flush", + Description: "Whether to attempt an application consistent machine image by informing the OS to prepare for the snapshot process.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "kind", + Description: "The resource type, which is always compute#machineImage for machine image.", + Type: proto.ColumnType_STRING, + }, + { + Name: "source_instance", + Description: "The source instance used to create the machine image.", + Type: proto.ColumnType_STRING, + }, + { + Name: "status", + Description: "The status of the machine image.", + Type: proto.ColumnType_STRING, + }, + { + Name: "total_storage_bytes", + Description: "Total size of the storage used by the machine image.", + Type: proto.ColumnType_INT, + }, + + // JSON columns + { + Name: "instance_properties", + Description: "Properties of source instance.", + Type: proto.ColumnType_JSON, + }, + { + Name: "machine_image_encryption_key", + Description: "Encrypts the machine image using a customer-supplied encryption key. After you encrypt a machine image using a customer-supplied key, you must provide the same key if you use the machine image later.", + Type: proto.ColumnType_JSON, + }, + { + Name: "saved_disks", + Description: "An array of Machine Image specific properties for disks attached to the source instance.", + Type: proto.ColumnType_JSON, + }, + { + Name: "source_disk_encryption_keys", + Description: "The customer-supplied encryption key of the disks attached to the source instance. Required if the source disk is protected by a customer-supplied encryption key.", + Type: proto.ColumnType_JSON, + }, + { + Name: "storage_locations", + Description: "The regional or multi-regional Cloud Storage bucket location where the machine image is stored.", + Type: proto.ColumnType_JSON, + }, + + // Steampipe standard columns + { + Name: "title", + Description: ColumnDescriptionTitle, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + { + Name: "akas", + Description: ColumnDescriptionAkas, + Type: proto.ColumnType_JSON, + Transform: transform.FromP(machineImageTurbotData, "Akas"), + }, + + // GCP standard columns + { + Name: "project", + Description: ColumnDescriptionProject, + Type: proto.ColumnType_STRING, + Transform: transform.FromP(machineImageTurbotData, "Project"), + }, + }, + } +} + +//// LIST FUNCTION + +func listComputeMachineImages(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create Service Connection + service, err := ComputeService(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("gcp_compute_machine_image.listComputeMachineImages", "connection_error", err) + return nil, err + } + + // Max limit is set as per documentation + // https://cloud.google.com/compute/docs/reference/rest/v1/machineImages/list#query-parameters + pageSize := types.Int64(500) + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < *pageSize { + pageSize = limit + } + } + + // Get project details + getProjectCached := plugin.HydrateFunc(getProject).WithCache() + projectId, err := getProjectCached(ctx, d, h) + if err != nil { + plugin.Logger(ctx).Error("gcp_compute_machine_image.listComputeMachineImages.getProjectCached", "cached_function", err) + return nil, err + } + project := projectId.(string) + + resp := service.MachineImages.List(project).MaxResults(*pageSize) + if err := resp.Pages(ctx, func(page *compute.MachineImageList) error { + for _, machineImage := range page.Items { + d.StreamListItem(ctx, machineImage) + + // Check if context has been cancelled or if the limit has been hit (if specified) + // if there is a limit, it will return the number of rows required to reach this limit + if d.RowsRemaining(ctx) == 0 { + page.NextPageToken = "" + return nil + } + } + return nil + }); err != nil { + plugin.Logger(ctx).Error("gcp_compute_machine_image.listComputeMachineImages", "api_error", err) + return nil, err + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getComputeMachineImage(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create Service Connection + service, err := ComputeService(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("gcp_compute_machine_image.getComputeMachineImage", "connection_error", err) + return nil, err + } + + // Get project details + getProjectCached := plugin.HydrateFunc(getProject).WithCache() + projectId, err := getProjectCached(ctx, d, h) + if err != nil { + plugin.Logger(ctx).Error("gcp_compute_machine_image.getComputeMachineImage.getProjectCached", "cache_error", err) + return nil, err + } + project := projectId.(string) + machineImageName := d.EqualsQualString("name") + + // Return nil, if no input provided + if machineImageName == "" { + return nil, nil + } + + resp, err := service.MachineImages.Get(project, machineImageName).Do() + if err != nil { + plugin.Logger(ctx).Error("gcp_compute_machine_image.getComputeMachineImage", "api_error", err) + return nil, err + } + return resp, nil +} + +//// TRANSFORM FUNCTIONS + +func machineImageTurbotData(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*compute.MachineImage) + param := d.Param.(string) + + project := strings.Split(data.SelfLink, "/")[6] + + turbotData := map[string]interface{}{ + "Project": project, + "Akas": []string{"gcp://compute.googleapis.com/projects/" + project + "/machineImages/" + data.Name}, + } + + return turbotData[param], nil +}