package google import ( "strconv" "strings" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" containerBeta "google.golang.org/api/container/v1beta1" ) // Matches gke-default scope from https://cloud.google.com/sdk/gcloud/reference/container/clusters/create var defaultOauthScopes = []string{ "https://www.googleapis.com/auth/devstorage.read_only", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/monitoring", "https://www.googleapis.com/auth/service.management.readonly", "https://www.googleapis.com/auth/servicecontrol", "https://www.googleapis.com/auth/trace.append", } var schemaNodeConfig = &schema.Schema{ Type: schema.TypeList, Optional: true, Computed: true, ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "disk_size_gb": { Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: true, ValidateFunc: validation.IntAtLeast(10), }, "disk_type": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd"}, false), }, "guest_accelerator": { Type: schema.TypeList, Optional: true, Computed: true, ForceNew: true, // Legacy config mode allows removing GPU's from an existing resource // See https://www.terraform.io/docs/configuration/attr-as-blocks.html ConfigMode: schema.SchemaConfigModeAttr, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "count": { Type: schema.TypeInt, Required: true, ForceNew: true, }, "type": { Type: schema.TypeString, Required: true, ForceNew: true, DiffSuppressFunc: linkDiffSuppress, }, }, }, }, "image_type": { Type: schema.TypeString, Optional: true, Computed: true, }, "labels": { Type: schema.TypeMap, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "local_ssd_count": { Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: true, ValidateFunc: validation.IntAtLeast(0), }, "machine_type": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, "metadata": { Type: schema.TypeMap, Optional: true, Computed: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "min_cpu_platform": { Type: schema.TypeString, Optional: true, ForceNew: true, }, "oauth_scopes": { Type: schema.TypeSet, Optional: true, Computed: true, ForceNew: true, Elem: &schema.Schema{ Type: schema.TypeString, StateFunc: func(v interface{}) string { return canonicalizeServiceScope(v.(string)) }, }, Set: stringScopeHashcode, }, "preemptible": { Type: schema.TypeBool, Optional: true, ForceNew: true, Default: false, }, "service_account": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, "tags": { Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "taint": { Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", Type: schema.TypeList, Optional: true, ForceNew: true, DiffSuppressFunc: taintDiffSuppress, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": { Type: schema.TypeString, Required: true, ForceNew: true, }, "value": { Type: schema.TypeString, Required: true, ForceNew: true, }, "effect": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"NO_SCHEDULE", "PREFER_NO_SCHEDULE", "NO_EXECUTE"}, false), }, }, }, }, "workload_metadata_config": { Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", Type: schema.TypeList, Optional: true, ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "node_metadata": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"UNSPECIFIED", "SECURE", "EXPOSE"}, false), }, }, }, }, }, }, } func expandNodeConfig(v interface{}) *containerBeta.NodeConfig { nodeConfigs := v.([]interface{}) nc := &containerBeta.NodeConfig{ // Defaults can't be set on a list/set in the schema, so set the default on create here. OauthScopes: defaultOauthScopes, } if len(nodeConfigs) == 0 { return nc } nodeConfig := nodeConfigs[0].(map[string]interface{}) if v, ok := nodeConfig["machine_type"]; ok { nc.MachineType = v.(string) } if v, ok := nodeConfig["guest_accelerator"]; ok { accels := v.([]interface{}) guestAccelerators := make([]*containerBeta.AcceleratorConfig, 0, len(accels)) for _, raw := range accels { data := raw.(map[string]interface{}) if data["count"].(int) == 0 { continue } guestAccelerators = append(guestAccelerators, &containerBeta.AcceleratorConfig{ AcceleratorCount: int64(data["count"].(int)), AcceleratorType: data["type"].(string), }) } nc.Accelerators = guestAccelerators } if v, ok := nodeConfig["disk_size_gb"]; ok { nc.DiskSizeGb = int64(v.(int)) } if v, ok := nodeConfig["disk_type"]; ok { nc.DiskType = v.(string) } if v, ok := nodeConfig["local_ssd_count"]; ok { nc.LocalSsdCount = int64(v.(int)) } if scopes, ok := nodeConfig["oauth_scopes"]; ok { scopesSet := scopes.(*schema.Set) scopes := make([]string, scopesSet.Len()) for i, scope := range scopesSet.List() { scopes[i] = canonicalizeServiceScope(scope.(string)) } nc.OauthScopes = scopes } if v, ok := nodeConfig["service_account"]; ok { nc.ServiceAccount = v.(string) } if v, ok := nodeConfig["metadata"]; ok { m := make(map[string]string) for k, val := range v.(map[string]interface{}) { m[k] = val.(string) } nc.Metadata = m } if v, ok := nodeConfig["image_type"]; ok { nc.ImageType = v.(string) } if v, ok := nodeConfig["labels"]; ok { m := make(map[string]string) for k, val := range v.(map[string]interface{}) { m[k] = val.(string) } nc.Labels = m } if v, ok := nodeConfig["tags"]; ok { tagsList := v.([]interface{}) tags := []string{} for _, v := range tagsList { tags = append(tags, v.(string)) } nc.Tags = tags } // Preemptible Is Optional+Default, so it always has a value nc.Preemptible = nodeConfig["preemptible"].(bool) if v, ok := nodeConfig["min_cpu_platform"]; ok { nc.MinCpuPlatform = v.(string) } return nc } func flattenNodeConfig(c *containerBeta.NodeConfig) []map[string]interface{} { config := make([]map[string]interface{}, 0, 1) if c == nil { return config } config = append(config, map[string]interface{}{ "machine_type": c.MachineType, "disk_size_gb": c.DiskSizeGb, "disk_type": c.DiskType, "guest_accelerator": flattenContainerGuestAccelerators(c.Accelerators), "local_ssd_count": c.LocalSsdCount, "service_account": c.ServiceAccount, "metadata": c.Metadata, "image_type": c.ImageType, "labels": c.Labels, "tags": c.Tags, "preemptible": c.Preemptible, "min_cpu_platform": c.MinCpuPlatform, }) if len(c.OauthScopes) > 0 { config[0]["oauth_scopes"] = schema.NewSet(stringScopeHashcode, convertStringArrToInterface(c.OauthScopes)) } return config } func flattenContainerGuestAccelerators(c []*containerBeta.AcceleratorConfig) []map[string]interface{} { result := []map[string]interface{}{} for _, accel := range c { result = append(result, map[string]interface{}{ "count": accel.AcceleratorCount, "type": accel.AcceleratorType, }) } return result } func taintDiffSuppress(k, old, new string, d *schema.ResourceData) bool { if strings.HasSuffix(k, "#") { oldCount, oldErr := strconv.Atoi(old) newCount, newErr := strconv.Atoi(new) // If either of them isn't a number somehow, or if there's one that we didn't have before. return oldErr != nil || newErr != nil || oldCount == newCount+1 } else { lastDot := strings.LastIndex(k, ".") taintKey := d.Get(k[:lastDot] + ".key").(string) if taintKey == "nvidia.com/gpu" { return true } else { return false } } }