github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/google/resource_container_cluster.go (about)

     1  package google
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net"
     7  	"regexp"
     8  
     9  	"github.com/hashicorp/terraform/helper/resource"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  	"google.golang.org/api/container/v1"
    12  	"google.golang.org/api/googleapi"
    13  )
    14  
    15  var (
    16  	instanceGroupManagerURL = regexp.MustCompile("^https://www.googleapis.com/compute/v1/projects/([a-z][a-z0-9-]{5}(?:[-a-z0-9]{0,23}[a-z0-9])?)/zones/([a-z0-9-]*)/instanceGroupManagers/([^/]*)")
    17  )
    18  
    19  func resourceContainerCluster() *schema.Resource {
    20  	return &schema.Resource{
    21  		Create: resourceContainerClusterCreate,
    22  		Read:   resourceContainerClusterRead,
    23  		Update: resourceContainerClusterUpdate,
    24  		Delete: resourceContainerClusterDelete,
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"master_auth": &schema.Schema{
    28  				Type:     schema.TypeList,
    29  				Required: true,
    30  				ForceNew: true,
    31  				Elem: &schema.Resource{
    32  					Schema: map[string]*schema.Schema{
    33  						"client_certificate": &schema.Schema{
    34  							Type:     schema.TypeString,
    35  							Computed: true,
    36  						},
    37  						"client_key": &schema.Schema{
    38  							Type:      schema.TypeString,
    39  							Computed:  true,
    40  							Sensitive: true,
    41  						},
    42  						"cluster_ca_certificate": &schema.Schema{
    43  							Type:     schema.TypeString,
    44  							Computed: true,
    45  						},
    46  						"password": &schema.Schema{
    47  							Type:      schema.TypeString,
    48  							Required:  true,
    49  							ForceNew:  true,
    50  							Sensitive: true,
    51  						},
    52  						"username": &schema.Schema{
    53  							Type:     schema.TypeString,
    54  							Required: true,
    55  							ForceNew: true,
    56  						},
    57  					},
    58  				},
    59  			},
    60  
    61  			"name": &schema.Schema{
    62  				Type:     schema.TypeString,
    63  				Required: true,
    64  				ForceNew: true,
    65  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    66  					value := v.(string)
    67  
    68  					if len(value) > 40 {
    69  						errors = append(errors, fmt.Errorf(
    70  							"%q cannot be longer than 40 characters", k))
    71  					}
    72  					if !regexp.MustCompile("^[a-z0-9-]+$").MatchString(value) {
    73  						errors = append(errors, fmt.Errorf(
    74  							"%q can only contain lowercase letters, numbers and hyphens", k))
    75  					}
    76  					if !regexp.MustCompile("^[a-z]").MatchString(value) {
    77  						errors = append(errors, fmt.Errorf(
    78  							"%q must start with a letter", k))
    79  					}
    80  					if !regexp.MustCompile("[a-z0-9]$").MatchString(value) {
    81  						errors = append(errors, fmt.Errorf(
    82  							"%q must end with a number or a letter", k))
    83  					}
    84  					return
    85  				},
    86  			},
    87  
    88  			"zone": &schema.Schema{
    89  				Type:     schema.TypeString,
    90  				Required: true,
    91  				ForceNew: true,
    92  			},
    93  
    94  			"initial_node_count": &schema.Schema{
    95  				Type:     schema.TypeInt,
    96  				Optional: true,
    97  				ForceNew: true,
    98  			},
    99  
   100  			"additional_zones": &schema.Schema{
   101  				Type:     schema.TypeList,
   102  				Optional: true,
   103  				Computed: true,
   104  				ForceNew: true,
   105  				Elem:     &schema.Schema{Type: schema.TypeString},
   106  			},
   107  
   108  			"cluster_ipv4_cidr": &schema.Schema{
   109  				Type:     schema.TypeString,
   110  				Optional: true,
   111  				Computed: true,
   112  				ForceNew: true,
   113  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   114  					value := v.(string)
   115  					_, ipnet, err := net.ParseCIDR(value)
   116  
   117  					if err != nil || ipnet == nil || value != ipnet.String() {
   118  						errors = append(errors, fmt.Errorf(
   119  							"%q must contain a valid CIDR", k))
   120  					}
   121  					return
   122  				},
   123  			},
   124  
   125  			"description": &schema.Schema{
   126  				Type:     schema.TypeString,
   127  				Optional: true,
   128  				ForceNew: true,
   129  			},
   130  
   131  			"endpoint": &schema.Schema{
   132  				Type:     schema.TypeString,
   133  				Computed: true,
   134  			},
   135  
   136  			"instance_group_urls": &schema.Schema{
   137  				Type:     schema.TypeList,
   138  				Computed: true,
   139  				Elem:     &schema.Schema{Type: schema.TypeString},
   140  			},
   141  
   142  			"logging_service": &schema.Schema{
   143  				Type:     schema.TypeString,
   144  				Optional: true,
   145  				Computed: true,
   146  				ForceNew: true,
   147  			},
   148  
   149  			"monitoring_service": &schema.Schema{
   150  				Type:     schema.TypeString,
   151  				Optional: true,
   152  				Computed: true,
   153  				ForceNew: true,
   154  			},
   155  
   156  			"network": &schema.Schema{
   157  				Type:     schema.TypeString,
   158  				Optional: true,
   159  				Default:  "default",
   160  				ForceNew: true,
   161  			},
   162  			"subnetwork": &schema.Schema{
   163  				Type:     schema.TypeString,
   164  				Optional: true,
   165  				ForceNew: true,
   166  			},
   167  			"addons_config": &schema.Schema{
   168  				Type:     schema.TypeList,
   169  				Optional: true,
   170  				ForceNew: true,
   171  				MaxItems: 1,
   172  				Elem: &schema.Resource{
   173  					Schema: map[string]*schema.Schema{
   174  						"http_load_balancing": &schema.Schema{
   175  							Type:     schema.TypeList,
   176  							Optional: true,
   177  							ForceNew: true,
   178  							MaxItems: 1,
   179  							Elem: &schema.Resource{
   180  								Schema: map[string]*schema.Schema{
   181  									"disabled": &schema.Schema{
   182  										Type:     schema.TypeBool,
   183  										Optional: true,
   184  										ForceNew: true,
   185  									},
   186  								},
   187  							},
   188  						},
   189  						"horizontal_pod_autoscaling": &schema.Schema{
   190  							Type:     schema.TypeList,
   191  							Optional: true,
   192  							ForceNew: true,
   193  							MaxItems: 1,
   194  							Elem: &schema.Resource{
   195  								Schema: map[string]*schema.Schema{
   196  									"disabled": &schema.Schema{
   197  										Type:     schema.TypeBool,
   198  										Optional: true,
   199  										ForceNew: true,
   200  									},
   201  								},
   202  							},
   203  						},
   204  					},
   205  				},
   206  			},
   207  			"node_config": &schema.Schema{
   208  				Type:     schema.TypeList,
   209  				Optional: true,
   210  				Computed: true,
   211  				ForceNew: true,
   212  				Elem: &schema.Resource{
   213  					Schema: map[string]*schema.Schema{
   214  						"machine_type": &schema.Schema{
   215  							Type:     schema.TypeString,
   216  							Optional: true,
   217  							Computed: true,
   218  							ForceNew: true,
   219  						},
   220  
   221  						"disk_size_gb": &schema.Schema{
   222  							Type:     schema.TypeInt,
   223  							Optional: true,
   224  							Computed: true,
   225  							ForceNew: true,
   226  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   227  								value := v.(int)
   228  
   229  								if value < 10 {
   230  									errors = append(errors, fmt.Errorf(
   231  										"%q cannot be less than 10", k))
   232  								}
   233  								return
   234  							},
   235  						},
   236  
   237  						"local_ssd_count": &schema.Schema{
   238  							Type:     schema.TypeInt,
   239  							Optional: true,
   240  							Computed: true,
   241  							ForceNew: true,
   242  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   243  								value := v.(int)
   244  
   245  								if value < 0 {
   246  									errors = append(errors, fmt.Errorf(
   247  										"%q cannot be negative", k))
   248  								}
   249  								return
   250  							},
   251  						},
   252  
   253  						"oauth_scopes": &schema.Schema{
   254  							Type:     schema.TypeList,
   255  							Optional: true,
   256  							Computed: true,
   257  							ForceNew: true,
   258  							Elem: &schema.Schema{
   259  								Type: schema.TypeString,
   260  								StateFunc: func(v interface{}) string {
   261  									return canonicalizeServiceScope(v.(string))
   262  								},
   263  							},
   264  						},
   265  
   266  						"service_account": &schema.Schema{
   267  							Type:     schema.TypeString,
   268  							Optional: true,
   269  							Computed: true,
   270  							ForceNew: true,
   271  						},
   272  
   273  						"metadata": &schema.Schema{
   274  							Type:     schema.TypeMap,
   275  							Optional: true,
   276  							ForceNew: true,
   277  							Elem:     schema.TypeString,
   278  						},
   279  
   280  						"image_type": &schema.Schema{
   281  							Type:     schema.TypeString,
   282  							Optional: true,
   283  							Computed: true,
   284  							ForceNew: true,
   285  						},
   286  					},
   287  				},
   288  			},
   289  
   290  			"node_version": &schema.Schema{
   291  				Type:     schema.TypeString,
   292  				Optional: true,
   293  				Computed: true,
   294  			},
   295  
   296  			"node_pool": &schema.Schema{
   297  				Type:     schema.TypeList,
   298  				Optional: true,
   299  				Computed: true,
   300  				ForceNew: true, // TODO(danawillow): Add ability to add/remove nodePools
   301  				Elem: &schema.Resource{
   302  					Schema: map[string]*schema.Schema{
   303  						"initial_node_count": &schema.Schema{
   304  							Type:     schema.TypeInt,
   305  							Required: true,
   306  							ForceNew: true,
   307  						},
   308  
   309  						"name": &schema.Schema{
   310  							Type:          schema.TypeString,
   311  							Optional:      true,
   312  							Computed:      true,
   313  							ConflictsWith: []string{"node_pool.name_prefix"},
   314  							ForceNew:      true,
   315  						},
   316  
   317  						"name_prefix": &schema.Schema{
   318  							Type:     schema.TypeString,
   319  							Optional: true,
   320  							ForceNew: true,
   321  						},
   322  					},
   323  				},
   324  			},
   325  
   326  			"project": &schema.Schema{
   327  				Type:     schema.TypeString,
   328  				Optional: true,
   329  				ForceNew: true,
   330  			},
   331  		},
   332  	}
   333  }
   334  
   335  func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) error {
   336  	config := meta.(*Config)
   337  
   338  	project, err := getProject(d, config)
   339  	if err != nil {
   340  		return err
   341  	}
   342  
   343  	zoneName := d.Get("zone").(string)
   344  	clusterName := d.Get("name").(string)
   345  
   346  	masterAuths := d.Get("master_auth").([]interface{})
   347  	if len(masterAuths) > 1 {
   348  		return fmt.Errorf("Cannot specify more than one master_auth.")
   349  	}
   350  	masterAuth := masterAuths[0].(map[string]interface{})
   351  
   352  	cluster := &container.Cluster{
   353  		MasterAuth: &container.MasterAuth{
   354  			Password: masterAuth["password"].(string),
   355  			Username: masterAuth["username"].(string),
   356  		},
   357  		Name:             clusterName,
   358  		InitialNodeCount: int64(d.Get("initial_node_count").(int)),
   359  	}
   360  
   361  	if v, ok := d.GetOk("node_version"); ok {
   362  		cluster.InitialClusterVersion = v.(string)
   363  	}
   364  
   365  	if v, ok := d.GetOk("additional_zones"); ok {
   366  		locationsList := v.([]interface{})
   367  		locations := []string{}
   368  		for _, v := range locationsList {
   369  			location := v.(string)
   370  			locations = append(locations, location)
   371  			if location == zoneName {
   372  				return fmt.Errorf("additional_zones should not contain the original 'zone'.")
   373  			}
   374  		}
   375  		locations = append(locations, zoneName)
   376  		cluster.Locations = locations
   377  	}
   378  
   379  	if v, ok := d.GetOk("cluster_ipv4_cidr"); ok {
   380  		cluster.ClusterIpv4Cidr = v.(string)
   381  	}
   382  
   383  	if v, ok := d.GetOk("description"); ok {
   384  		cluster.Description = v.(string)
   385  	}
   386  
   387  	if v, ok := d.GetOk("logging_service"); ok {
   388  		cluster.LoggingService = v.(string)
   389  	}
   390  
   391  	if v, ok := d.GetOk("monitoring_service"); ok {
   392  		cluster.MonitoringService = v.(string)
   393  	}
   394  
   395  	if _, ok := d.GetOk("network"); ok {
   396  		network, err := getNetworkName(d, "network")
   397  		if err != nil {
   398  			return err
   399  		}
   400  		cluster.Network = network
   401  	}
   402  
   403  	if v, ok := d.GetOk("subnetwork"); ok {
   404  		cluster.Subnetwork = v.(string)
   405  	}
   406  
   407  	if v, ok := d.GetOk("addons_config"); ok {
   408  		addonsConfig := v.([]interface{})[0].(map[string]interface{})
   409  		cluster.AddonsConfig = &container.AddonsConfig{}
   410  
   411  		if v, ok := addonsConfig["http_load_balancing"]; ok && len(v.([]interface{})) > 0 {
   412  			addon := v.([]interface{})[0].(map[string]interface{})
   413  			cluster.AddonsConfig.HttpLoadBalancing = &container.HttpLoadBalancing{
   414  				Disabled: addon["disabled"].(bool),
   415  			}
   416  		}
   417  
   418  		if v, ok := addonsConfig["horizontal_pod_autoscaling"]; ok && len(v.([]interface{})) > 0 {
   419  			addon := v.([]interface{})[0].(map[string]interface{})
   420  			cluster.AddonsConfig.HorizontalPodAutoscaling = &container.HorizontalPodAutoscaling{
   421  				Disabled: addon["disabled"].(bool),
   422  			}
   423  		}
   424  	}
   425  	if v, ok := d.GetOk("node_config"); ok {
   426  		nodeConfigs := v.([]interface{})
   427  		if len(nodeConfigs) > 1 {
   428  			return fmt.Errorf("Cannot specify more than one node_config.")
   429  		}
   430  		nodeConfig := nodeConfigs[0].(map[string]interface{})
   431  
   432  		cluster.NodeConfig = &container.NodeConfig{}
   433  
   434  		if v, ok = nodeConfig["machine_type"]; ok {
   435  			cluster.NodeConfig.MachineType = v.(string)
   436  		}
   437  
   438  		if v, ok = nodeConfig["disk_size_gb"]; ok {
   439  			cluster.NodeConfig.DiskSizeGb = int64(v.(int))
   440  		}
   441  
   442  		if v, ok = nodeConfig["local_ssd_count"]; ok {
   443  			cluster.NodeConfig.LocalSsdCount = int64(v.(int))
   444  		}
   445  
   446  		if v, ok := nodeConfig["oauth_scopes"]; ok {
   447  			scopesList := v.([]interface{})
   448  			scopes := []string{}
   449  			for _, v := range scopesList {
   450  				scopes = append(scopes, canonicalizeServiceScope(v.(string)))
   451  			}
   452  
   453  			cluster.NodeConfig.OauthScopes = scopes
   454  		}
   455  
   456  		if v, ok = nodeConfig["service_account"]; ok {
   457  			cluster.NodeConfig.ServiceAccount = v.(string)
   458  		}
   459  
   460  		if v, ok = nodeConfig["metadata"]; ok {
   461  			m := make(map[string]string)
   462  			for k, val := range v.(map[string]interface{}) {
   463  				m[k] = val.(string)
   464  			}
   465  			cluster.NodeConfig.Metadata = m
   466  		}
   467  
   468  		if v, ok = nodeConfig["image_type"]; ok {
   469  			cluster.NodeConfig.ImageType = v.(string)
   470  		}
   471  	}
   472  
   473  	nodePoolsCount := d.Get("node_pool.#").(int)
   474  	if nodePoolsCount > 0 {
   475  		nodePools := make([]*container.NodePool, 0, nodePoolsCount)
   476  		for i := 0; i < nodePoolsCount; i++ {
   477  			prefix := fmt.Sprintf("node_pool.%d", i)
   478  
   479  			nodeCount := d.Get(prefix + ".initial_node_count").(int)
   480  
   481  			var name string
   482  			if v, ok := d.GetOk(prefix + ".name"); ok {
   483  				name = v.(string)
   484  			} else if v, ok := d.GetOk(prefix + ".name_prefix"); ok {
   485  				name = resource.PrefixedUniqueId(v.(string))
   486  			} else {
   487  				name = resource.UniqueId()
   488  			}
   489  
   490  			nodePool := &container.NodePool{
   491  				Name:             name,
   492  				InitialNodeCount: int64(nodeCount),
   493  			}
   494  
   495  			nodePools = append(nodePools, nodePool)
   496  		}
   497  		cluster.NodePools = nodePools
   498  	}
   499  
   500  	req := &container.CreateClusterRequest{
   501  		Cluster: cluster,
   502  	}
   503  
   504  	op, err := config.clientContainer.Projects.Zones.Clusters.Create(
   505  		project, zoneName, req).Do()
   506  	if err != nil {
   507  		return err
   508  	}
   509  
   510  	// Wait until it's created
   511  	waitErr := containerOperationWait(config, op, project, zoneName, "creating GKE cluster", 30, 3)
   512  	if waitErr != nil {
   513  		// The resource didn't actually create
   514  		d.SetId("")
   515  		return waitErr
   516  	}
   517  
   518  	log.Printf("[INFO] GKE cluster %s has been created", clusterName)
   519  
   520  	d.SetId(clusterName)
   521  
   522  	return resourceContainerClusterRead(d, meta)
   523  }
   524  
   525  func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) error {
   526  	config := meta.(*Config)
   527  
   528  	project, err := getProject(d, config)
   529  	if err != nil {
   530  		return err
   531  	}
   532  
   533  	zoneName := d.Get("zone").(string)
   534  
   535  	cluster, err := config.clientContainer.Projects.Zones.Clusters.Get(
   536  		project, zoneName, d.Get("name").(string)).Do()
   537  	if err != nil {
   538  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
   539  			log.Printf("[WARN] Removing Container Cluster %q because it's gone", d.Get("name").(string))
   540  			// The resource doesn't exist anymore
   541  			d.SetId("")
   542  
   543  			return nil
   544  		}
   545  
   546  		return err
   547  	}
   548  
   549  	d.Set("name", cluster.Name)
   550  	d.Set("zone", cluster.Zone)
   551  
   552  	locations := []string{}
   553  	if len(cluster.Locations) > 1 {
   554  		for _, location := range cluster.Locations {
   555  			if location != cluster.Zone {
   556  				locations = append(locations, location)
   557  			}
   558  		}
   559  	}
   560  	d.Set("additional_zones", locations)
   561  
   562  	d.Set("endpoint", cluster.Endpoint)
   563  
   564  	masterAuth := []map[string]interface{}{
   565  		map[string]interface{}{
   566  			"username":               cluster.MasterAuth.Username,
   567  			"password":               cluster.MasterAuth.Password,
   568  			"client_certificate":     cluster.MasterAuth.ClientCertificate,
   569  			"client_key":             cluster.MasterAuth.ClientKey,
   570  			"cluster_ca_certificate": cluster.MasterAuth.ClusterCaCertificate,
   571  		},
   572  	}
   573  	d.Set("master_auth", masterAuth)
   574  
   575  	d.Set("initial_node_count", cluster.InitialNodeCount)
   576  	d.Set("node_version", cluster.CurrentNodeVersion)
   577  	d.Set("cluster_ipv4_cidr", cluster.ClusterIpv4Cidr)
   578  	d.Set("description", cluster.Description)
   579  	d.Set("logging_service", cluster.LoggingService)
   580  	d.Set("monitoring_service", cluster.MonitoringService)
   581  	d.Set("network", d.Get("network").(string))
   582  	d.Set("subnetwork", cluster.Subnetwork)
   583  	d.Set("node_config", flattenClusterNodeConfig(cluster.NodeConfig))
   584  	d.Set("node_pool", flattenClusterNodePools(d, cluster.NodePools))
   585  
   586  	if igUrls, err := getInstanceGroupUrlsFromManagerUrls(config, cluster.InstanceGroupUrls); err != nil {
   587  		return err
   588  	} else {
   589  		d.Set("instance_group_urls", igUrls)
   590  	}
   591  
   592  	return nil
   593  }
   594  
   595  func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) error {
   596  	config := meta.(*Config)
   597  
   598  	project, err := getProject(d, config)
   599  	if err != nil {
   600  		return err
   601  	}
   602  
   603  	zoneName := d.Get("zone").(string)
   604  	clusterName := d.Get("name").(string)
   605  	desiredNodeVersion := d.Get("node_version").(string)
   606  
   607  	req := &container.UpdateClusterRequest{
   608  		Update: &container.ClusterUpdate{
   609  			DesiredNodeVersion: desiredNodeVersion,
   610  		},
   611  	}
   612  	op, err := config.clientContainer.Projects.Zones.Clusters.Update(
   613  		project, zoneName, clusterName, req).Do()
   614  	if err != nil {
   615  		return err
   616  	}
   617  
   618  	// Wait until it's updated
   619  	waitErr := containerOperationWait(config, op, project, zoneName, "updating GKE cluster", 10, 2)
   620  	if waitErr != nil {
   621  		return waitErr
   622  	}
   623  
   624  	log.Printf("[INFO] GKE cluster %s has been updated to %s", d.Id(),
   625  		desiredNodeVersion)
   626  
   627  	return resourceContainerClusterRead(d, meta)
   628  }
   629  
   630  func resourceContainerClusterDelete(d *schema.ResourceData, meta interface{}) error {
   631  	config := meta.(*Config)
   632  
   633  	project, err := getProject(d, config)
   634  	if err != nil {
   635  		return err
   636  	}
   637  
   638  	zoneName := d.Get("zone").(string)
   639  	clusterName := d.Get("name").(string)
   640  
   641  	log.Printf("[DEBUG] Deleting GKE cluster %s", d.Get("name").(string))
   642  	op, err := config.clientContainer.Projects.Zones.Clusters.Delete(
   643  		project, zoneName, clusterName).Do()
   644  	if err != nil {
   645  		return err
   646  	}
   647  
   648  	// Wait until it's deleted
   649  	waitErr := containerOperationWait(config, op, project, zoneName, "deleting GKE cluster", 10, 3)
   650  	if waitErr != nil {
   651  		return waitErr
   652  	}
   653  
   654  	log.Printf("[INFO] GKE cluster %s has been deleted", d.Id())
   655  
   656  	d.SetId("")
   657  
   658  	return nil
   659  }
   660  
   661  // container engine's API currently mistakenly returns the instance group manager's
   662  // URL instead of the instance group's URL in its responses. This shim detects that
   663  // error, and corrects it, by fetching the instance group manager URL and retrieving
   664  // the instance group manager, then using that to look up the instance group URL, which
   665  // is then substituted.
   666  //
   667  // This should be removed when the API response is fixed.
   668  func getInstanceGroupUrlsFromManagerUrls(config *Config, igmUrls []string) ([]string, error) {
   669  	instanceGroupURLs := make([]string, 0, len(igmUrls))
   670  	for _, u := range igmUrls {
   671  		if !instanceGroupManagerURL.MatchString(u) {
   672  			instanceGroupURLs = append(instanceGroupURLs, u)
   673  			continue
   674  		}
   675  		matches := instanceGroupManagerURL.FindStringSubmatch(u)
   676  		instanceGroupManager, err := config.clientCompute.InstanceGroupManagers.Get(matches[1], matches[2], matches[3]).Do()
   677  		if err != nil {
   678  			return nil, fmt.Errorf("Error reading instance group manager returned as an instance group URL: %s", err)
   679  		}
   680  		instanceGroupURLs = append(instanceGroupURLs, instanceGroupManager.InstanceGroup)
   681  	}
   682  	return instanceGroupURLs, nil
   683  }
   684  
   685  func flattenClusterNodeConfig(c *container.NodeConfig) []map[string]interface{} {
   686  	config := []map[string]interface{}{
   687  		map[string]interface{}{
   688  			"machine_type":    c.MachineType,
   689  			"disk_size_gb":    c.DiskSizeGb,
   690  			"local_ssd_count": c.LocalSsdCount,
   691  			"service_account": c.ServiceAccount,
   692  			"metadata":        c.Metadata,
   693  			"image_type":      c.ImageType,
   694  		},
   695  	}
   696  
   697  	if len(c.OauthScopes) > 0 {
   698  		config[0]["oauth_scopes"] = c.OauthScopes
   699  	}
   700  
   701  	return config
   702  }
   703  
   704  func flattenClusterNodePools(d *schema.ResourceData, c []*container.NodePool) []map[string]interface{} {
   705  	count := len(c)
   706  
   707  	nodePools := make([]map[string]interface{}, 0, count)
   708  
   709  	for i, np := range c {
   710  		nodePool := map[string]interface{}{
   711  			"name":               np.Name,
   712  			"name_prefix":        d.Get(fmt.Sprintf("node_pool.%d.name_prefix", i)),
   713  			"initial_node_count": np.InitialNodeCount,
   714  		}
   715  		nodePools = append(nodePools, nodePool)
   716  	}
   717  
   718  	return nodePools
   719  }