From d2611d4ce381d1d65a066ce583b40caab93dd3c3 Mon Sep 17 00:00:00 2001 From: Nathan McKinley Date: Thu, 14 Dec 2017 13:35:39 -0800 Subject: [PATCH] New data source: compute region instance group (#851) * Add new data source: compute region instance group manager's groups. * Add documentation for wait_for_instances and for the timeout mechanism in resourceComputeRegionInstanceGroupManagerCreate. --- ...e_compute_region_instance_group_manager.go | 160 ++++++++++++++++++ ...pute_region_instance_group_manager_test.go | 87 ++++++++++ google/provider.go | 29 ++-- ...e_compute_region_instance_group_manager.go | 71 +++++++- ...ompute_region_instance_group.html.markdown | 67 ++++++++ website/google.erb | 3 + 6 files changed, 397 insertions(+), 20 deletions(-) create mode 100644 google/data_source_google_compute_region_instance_group_manager.go create mode 100644 google/data_source_google_compute_region_instance_group_manager_test.go create mode 100644 website/docs/d/datasource_compute_region_instance_group.html.markdown diff --git a/google/data_source_google_compute_region_instance_group_manager.go b/google/data_source_google_compute_region_instance_group_manager.go new file mode 100644 index 00000000..826e79dc --- /dev/null +++ b/google/data_source_google_compute_region_instance_group_manager.go @@ -0,0 +1,160 @@ +package google + +import ( + "errors" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + compute "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" + "log" + "net/url" + "strconv" + "strings" +) + +func dataSourceGoogleComputeRegionInstanceGroup() *schema.Resource { + return &schema.Resource{ + Read: dataSourceComputeRegionInstanceGroupRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "instances": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instance": { + Type: schema.TypeString, + Required: true, + }, + + "status": { + Type: schema.TypeString, + Required: true, + }, + + "named_ports": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "port": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + }, + }, + }, + + "region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "project": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "self_link": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "size": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func dataSourceComputeRegionInstanceGroupRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + var project, region, name string + if self_link, ok := d.GetOk("self_link"); ok { + parsed, err := url.Parse(self_link.(string)) + if err != nil { + return err + } + s := strings.Split(parsed.Path, "/") + project, region, name = s[4], s[6], s[8] + // e.g. https://www.googleapis.com/compute/beta/projects/project_name/regions/region_name/instanceGroups/foobarbaz + + } else { + var err error + project, err = getProject(d, config) + if err != nil { + return err + } + + region, err = getRegion(d, config) + if err != nil { + return err + } + n, ok := d.GetOk("name") + name = n.(string) + if !ok { + return errors.New("Must provide either `self_link` or `name`.") + } + } + + instanceGroup, err := config.clientCompute.RegionInstanceGroups.Get( + project, region, name).Do() + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("Region Instance Group %q", name)) + } + + members, err := config.clientCompute.RegionInstanceGroups.ListInstances( + project, region, name, &compute.RegionInstanceGroupsListInstancesRequest{ + InstanceState: "ALL", + }).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + // The resource doesn't have any instances, which is okay. + d.Set("instances", nil) + } else { + return fmt.Errorf("Error reading RegionInstanceGroup Members: %s", err) + } + } else { + d.Set("instances", flattenInstancesWithNamedPorts(members.Items)) + } + d.Set("kind", instanceGroup.Kind) + d.SetId(strconv.FormatUint(instanceGroup.Id, 16)) + d.Set("self_link", instanceGroup.SelfLink) + d.Set("name", name) + d.Set("project", project) + d.Set("region", region) + return nil +} + +func flattenInstancesWithNamedPorts(insts []*compute.InstanceWithNamedPorts) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(insts)) + log.Printf("There were %d instances.\n", len(insts)) + for _, inst := range insts { + instMap := make(map[string]interface{}) + instMap["instance"] = inst.Instance + instMap["named_ports"] = flattenNamedPorts(inst.NamedPorts) + instMap["status"] = inst.Status + result = append(result, instMap) + } + return result +} diff --git a/google/data_source_google_compute_region_instance_group_manager_test.go b/google/data_source_google_compute_region_instance_group_manager_test.go new file mode 100644 index 00000000..0dc00ec1 --- /dev/null +++ b/google/data_source_google_compute_region_instance_group_manager_test.go @@ -0,0 +1,87 @@ +package google + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "testing" +) + +func TestAccDataSourceRegionInstanceGroupManager(t *testing.T) { + t.Parallel() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceRegionInstanceGroupManagerConfig("foobarbaz"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_compute_region_instance_group.data_source", "name", "foobarbaz"), + resource.TestCheckResourceAttr("data.google_compute_region_instance_group.data_source", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr("data.google_compute_region_instance_group.data_source", "instances.#", "10")), + }, + }, + }) +} + +func testAccDataSourceRegionInstanceGroupManagerConfig(instanceManagerName string) string { + return fmt.Sprintf(` +resource "google_compute_health_check" "autohealing" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + healthy_threshold = 2 + unhealthy_threshold = 10 + + http_health_check { + request_path = "/" + port = "80" + } +} + +resource "google_compute_target_pool" "foo" { + name = "%s" +} + +data "google_compute_image" "debian" { + project = "debian-cloud" + name = "debian-9-stretch-v20171129" +} + +resource "google_compute_instance_template" "foo" { + machine_type = "n1-standard-1" + disk { + source_image = "${data.google_compute_image.debian.self_link}" + } + network_interface { + access_config { + } + network = "default" + } +} + +resource "google_compute_region_instance_group_manager" "foo" { + name = "%s" + base_instance_name = "foo" + instance_template = "${google_compute_instance_template.foo.self_link}" + region = "us-central1" + target_pools = ["${google_compute_target_pool.foo.self_link}"] + target_size = 10 + + named_port { + name = "web" + port = 80 + } + wait_for_instances = true + + auto_healing_policies { + health_check = "${google_compute_health_check.autohealing.self_link}" + initial_delay_sec = 600 + } +} + +data "google_compute_region_instance_group" "data_source" { + self_link = "${google_compute_region_instance_group_manager.foo.instance_group}" +} +`, acctest.RandomWithPrefix("test-rigm-"), acctest.RandomWithPrefix("test-rigm-"), instanceManagerName) +} diff --git a/google/provider.go b/google/provider.go index 6d0ffe89..1c738639 100644 --- a/google/provider.go +++ b/google/provider.go @@ -60,20 +60,21 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "google_dns_managed_zone": dataSourceDnsManagedZone(), - "google_client_config": dataSourceGoogleClientConfig(), - "google_compute_address": dataSourceGoogleComputeAddress(), - "google_compute_image": dataSourceGoogleComputeImage(), - "google_compute_global_address": dataSourceGoogleComputeGlobalAddress(), - "google_compute_lb_ip_ranges": dataSourceGoogleComputeLbIpRanges(), - "google_compute_network": dataSourceGoogleComputeNetwork(), - "google_compute_subnetwork": dataSourceGoogleComputeSubnetwork(), - "google_compute_zones": dataSourceGoogleComputeZones(), - "google_compute_instance_group": dataSourceGoogleComputeInstanceGroup(), - "google_container_engine_versions": dataSourceGoogleContainerEngineVersions(), - "google_active_folder": dataSourceGoogleActiveFolder(), - "google_iam_policy": dataSourceGoogleIamPolicy(), - "google_storage_object_signed_url": dataSourceGoogleSignedUrl(), + "google_dns_managed_zone": dataSourceDnsManagedZone(), + "google_client_config": dataSourceGoogleClientConfig(), + "google_compute_address": dataSourceGoogleComputeAddress(), + "google_compute_image": dataSourceGoogleComputeImage(), + "google_compute_global_address": dataSourceGoogleComputeGlobalAddress(), + "google_compute_lb_ip_ranges": dataSourceGoogleComputeLbIpRanges(), + "google_compute_network": dataSourceGoogleComputeNetwork(), + "google_compute_subnetwork": dataSourceGoogleComputeSubnetwork(), + "google_compute_zones": dataSourceGoogleComputeZones(), + "google_compute_instance_group": dataSourceGoogleComputeInstanceGroup(), + "google_compute_region_instance_group": dataSourceGoogleComputeRegionInstanceGroup(), + "google_container_engine_versions": dataSourceGoogleContainerEngineVersions(), + "google_active_folder": dataSourceGoogleActiveFolder(), + "google_iam_policy": dataSourceGoogleIamPolicy(), + "google_storage_object_signed_url": dataSourceGoogleSignedUrl(), }, ResourcesMap: map[string]*schema.Resource{ diff --git a/google/resource_compute_region_instance_group_manager.go b/google/resource_compute_region_instance_group_manager.go index bf77f712..74bd4e6e 100644 --- a/google/resource_compute_region_instance_group_manager.go +++ b/google/resource_compute_region_instance_group_manager.go @@ -1,13 +1,16 @@ package google import ( + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "log" "fmt" computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" + "time" ) var RegionInstanceGroupManagerBaseApiVersion = v1 @@ -23,6 +26,11 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(15 * time.Minute), + }, Schema: map[string]*schema.Schema{ "base_instance_name": &schema.Schema{ @@ -110,6 +118,15 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { Optional: true, }, + // If true, the resource will report ready only after no instances are being created. + // This will not block future reads if instances are being recreated, and it respects + // the "createNoRetry" parameter that's available for this resource. + "wait_for_instances": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "auto_healing_policies": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -181,17 +198,18 @@ func resourceComputeRegionInstanceGroupManagerCreate(d *schema.ResourceData, met if err != nil { return err } - - return resourceComputeRegionInstanceGroupManagerRead(d, meta) + return resourceComputeRegionInstanceGroupManagerRead(d, config) } -func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta interface{}) error { +type getInstanceManagerFunc func(*schema.ResourceData, interface{}) (*computeBeta.InstanceGroupManager, error) + +func getManager(d *schema.ResourceData, meta interface{}) (*computeBeta.InstanceGroupManager, error) { computeApiVersion := getComputeApiVersion(d, RegionInstanceGroupManagerBaseApiVersion, RegionInstanceGroupManagerVersionedFeatures) config := meta.(*Config) project, err := getProject(d, config) if err != nil { - return err + return nil, err } region := d.Get("region").(string) @@ -203,7 +221,7 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta err = Convert(v1Manager, manager) if err != nil { - return err + return nil, err } case v0beta: manager, err = config.clientComputeBeta.RegionInstanceGroupManagers.Get(project, region, d.Id()).Do() @@ -212,6 +230,35 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta if err != nil { handleNotFoundError(err, d, fmt.Sprintf("Region Instance Manager %q", d.Get("name").(string))) } + return manager, nil +} + +func waitForInstancesRefreshFunc(f getInstanceManagerFunc, d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + m, err := f(d, meta) + if err != nil { + log.Printf("[WARNING] Error in fetching manager while waiting for instances to come up: %s\n", err) + return nil, "error", err + } + if creatingCount := m.CurrentActions.Creating + m.CurrentActions.CreatingWithoutRetries; creatingCount > 0 { + return creatingCount, "creating", nil + } else { + return creatingCount, "created", nil + } + } +} + +func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + manager, err := getManager(d, meta) + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } d.Set("base_instance_name", manager.BaseInstanceName) d.Set("instance_template", manager.InstanceTemplate) @@ -227,6 +274,18 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta d.Set("auto_healing_policies", flattenAutoHealingPolicies(manager.AutoHealingPolicies)) d.Set("self_link", ConvertSelfLinkToV1(manager.SelfLink)) + if d.Get("wait_for_instances").(bool) { + conf := resource.StateChangeConf{ + Pending: []string{"creating", "error"}, + Target: []string{"created"}, + Refresh: waitForInstancesRefreshFunc(getManager, d, meta), + Timeout: d.Timeout(schema.TimeoutCreate), + } + _, err := conf.WaitForState() + // If err is nil, success. + return err + } + return nil } @@ -447,7 +506,7 @@ func resourceComputeRegionInstanceGroupManagerDelete(d *schema.ResourceData, met } // Wait for the operation to complete - err = computeSharedOperationWait(config.clientCompute, op, project, "Deleting RegionInstanceGroupManager") + err = computeSharedOperationWaitTime(config.clientCompute, op, project, int(d.Timeout(schema.TimeoutDelete).Minutes()), "Deleting RegionInstanceGroupManager") d.SetId("") return nil diff --git a/website/docs/d/datasource_compute_region_instance_group.html.markdown b/website/docs/d/datasource_compute_region_instance_group.html.markdown new file mode 100644 index 00000000..ea7cad31 --- /dev/null +++ b/website/docs/d/datasource_compute_region_instance_group.html.markdown @@ -0,0 +1,67 @@ +--- +layout: "google" +page_title: "Google: google_compute_region_instance_group" +sidebar_current: "docs-google-datasource-compute-region-instance-group" +description: |- + Get the instances inside a Compute Region Instance Group within GCE. +--- + +# google\_compute\_region\_instance\_group + +Get a Compute Region Instance Group within GCE. +For more information, see [the official documentation](https://cloud.google.com/compute/docs/instance-groups/distributing-instances-with-regional-instance-groups) and [API](https://cloud.google.com/compute/docs/reference/latest/regionInstanceGroups). + +``` +data "google_compute_region_instance_group" "group" { + name = "instance-group-name" +} +``` + +The most common use of this datasource will be to fetch information about the instances inside regional managed instance groups, for instance: + +``` +resource "google_compute_region_instance_group_manager" "foo" { + name = "some_name" + ... + base_instance_name = "foo" + ... + instance_template = "${google_compute_instance_template.foo.self_link}" + target_pools = ["${google_compute_target_pool.foo.self_link}"] + ... +} + +data "google_compute_region_instance_group" "data_source" { + self_link = "${google_compute_region_instance_group_manager.foo.instance_group}" +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Optional) The name of the instance group. One of `name` or `self_link` must be provided. + +* `self_link` - (Optional) The link to the instance group. One of `name` or `self_link` must be provided. + +- - - + +* `project` - (Optional) The project in which the resource belongs. If it + is not provided, the provider project is used. + +* `region` - (Optional) The region in which the resource belongs. If `self_link` + is provided, this value is ignored. If neither `self_link` nor `region` are + provided, the provider region is used. + +## Attributes Reference + +The following arguments are exported: + +* `size` - The number of instances in the group. + +* `instances` - List of instances in the group, as a list of resources, each containing: + * `instance` - URL to the instance. + * `named_ports` - List of named ports in the group, as a list of resources, each containing: + * `port` - Integer port number + * `name` - String port name + * `status` - String description of current state of the instance. diff --git a/website/google.erb b/website/google.erb index 01b11f11..531a764c 100644 --- a/website/google.erb +++ b/website/google.erb @@ -34,6 +34,9 @@ > google_compute_zones + > + google_compute_region_instance_group + > google_compute_instance_group