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