github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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/schema" 10 "google.golang.org/api/container/v1" 11 "google.golang.org/api/googleapi" 12 ) 13 14 func resourceContainerCluster() *schema.Resource { 15 return &schema.Resource{ 16 Create: resourceContainerClusterCreate, 17 Read: resourceContainerClusterRead, 18 Update: resourceContainerClusterUpdate, 19 Delete: resourceContainerClusterDelete, 20 21 Schema: map[string]*schema.Schema{ 22 "initial_node_count": &schema.Schema{ 23 Type: schema.TypeInt, 24 Required: true, 25 ForceNew: true, 26 }, 27 28 "master_auth": &schema.Schema{ 29 Type: schema.TypeList, 30 Required: true, 31 ForceNew: true, 32 Elem: &schema.Resource{ 33 Schema: map[string]*schema.Schema{ 34 "client_certificate": &schema.Schema{ 35 Type: schema.TypeString, 36 Computed: true, 37 }, 38 "client_key": &schema.Schema{ 39 Type: schema.TypeString, 40 Computed: 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 }, 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 "additional_zones": &schema.Schema{ 94 Type: schema.TypeList, 95 Optional: true, 96 Computed: true, 97 ForceNew: true, 98 Elem: &schema.Schema{Type: schema.TypeString}, 99 }, 100 101 "cluster_ipv4_cidr": &schema.Schema{ 102 Type: schema.TypeString, 103 Optional: true, 104 Computed: true, 105 ForceNew: true, 106 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 107 value := v.(string) 108 _, ipnet, err := net.ParseCIDR(value) 109 110 if err != nil || ipnet == nil || value != ipnet.String() { 111 errors = append(errors, fmt.Errorf( 112 "%q must contain a valid CIDR", k)) 113 } 114 return 115 }, 116 }, 117 118 "description": &schema.Schema{ 119 Type: schema.TypeString, 120 Optional: true, 121 ForceNew: true, 122 }, 123 124 "endpoint": &schema.Schema{ 125 Type: schema.TypeString, 126 Computed: true, 127 }, 128 129 "instance_group_urls": &schema.Schema{ 130 Type: schema.TypeList, 131 Computed: true, 132 Elem: &schema.Schema{Type: schema.TypeString}, 133 }, 134 135 "logging_service": &schema.Schema{ 136 Type: schema.TypeString, 137 Optional: true, 138 Computed: true, 139 ForceNew: true, 140 }, 141 142 "monitoring_service": &schema.Schema{ 143 Type: schema.TypeString, 144 Optional: true, 145 Computed: true, 146 ForceNew: true, 147 }, 148 149 "network": &schema.Schema{ 150 Type: schema.TypeString, 151 Optional: true, 152 Default: "default", 153 ForceNew: true, 154 }, 155 "subnetwork": &schema.Schema{ 156 Type: schema.TypeString, 157 Optional: true, 158 ForceNew: true, 159 }, 160 "addons_config": &schema.Schema{ 161 Type: schema.TypeList, 162 Optional: true, 163 ForceNew: true, 164 MaxItems: 1, 165 Elem: &schema.Resource{ 166 Schema: map[string]*schema.Schema{ 167 "http_load_balancing": &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 "disabled": &schema.Schema{ 175 Type: schema.TypeBool, 176 Optional: true, 177 ForceNew: true, 178 }, 179 }, 180 }, 181 }, 182 "horizontal_pod_autoscaling": &schema.Schema{ 183 Type: schema.TypeList, 184 Optional: true, 185 ForceNew: true, 186 MaxItems: 1, 187 Elem: &schema.Resource{ 188 Schema: map[string]*schema.Schema{ 189 "disabled": &schema.Schema{ 190 Type: schema.TypeBool, 191 Optional: true, 192 ForceNew: true, 193 }, 194 }, 195 }, 196 }, 197 }, 198 }, 199 }, 200 "node_config": &schema.Schema{ 201 Type: schema.TypeList, 202 Optional: true, 203 Computed: true, 204 ForceNew: true, 205 Elem: &schema.Resource{ 206 Schema: map[string]*schema.Schema{ 207 "machine_type": &schema.Schema{ 208 Type: schema.TypeString, 209 Optional: true, 210 Computed: true, 211 ForceNew: true, 212 }, 213 214 "disk_size_gb": &schema.Schema{ 215 Type: schema.TypeInt, 216 Optional: true, 217 Computed: true, 218 ForceNew: true, 219 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 220 value := v.(int) 221 222 if value < 10 { 223 errors = append(errors, fmt.Errorf( 224 "%q cannot be less than 10", k)) 225 } 226 return 227 }, 228 }, 229 230 "oauth_scopes": &schema.Schema{ 231 Type: schema.TypeList, 232 Optional: true, 233 Computed: true, 234 ForceNew: true, 235 Elem: &schema.Schema{ 236 Type: schema.TypeString, 237 StateFunc: func(v interface{}) string { 238 return canonicalizeServiceScope(v.(string)) 239 }, 240 }, 241 }, 242 }, 243 }, 244 }, 245 246 "node_version": &schema.Schema{ 247 Type: schema.TypeString, 248 Optional: true, 249 Computed: true, 250 }, 251 252 "project": &schema.Schema{ 253 Type: schema.TypeString, 254 Optional: true, 255 ForceNew: true, 256 }, 257 }, 258 } 259 } 260 261 func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) error { 262 config := meta.(*Config) 263 264 project, err := getProject(d, config) 265 if err != nil { 266 return err 267 } 268 269 zoneName := d.Get("zone").(string) 270 clusterName := d.Get("name").(string) 271 272 masterAuths := d.Get("master_auth").([]interface{}) 273 if len(masterAuths) > 1 { 274 return fmt.Errorf("Cannot specify more than one master_auth.") 275 } 276 masterAuth := masterAuths[0].(map[string]interface{}) 277 278 cluster := &container.Cluster{ 279 MasterAuth: &container.MasterAuth{ 280 Password: masterAuth["password"].(string), 281 Username: masterAuth["username"].(string), 282 }, 283 Name: clusterName, 284 InitialNodeCount: int64(d.Get("initial_node_count").(int)), 285 } 286 287 if v, ok := d.GetOk("node_version"); ok { 288 cluster.InitialClusterVersion = v.(string) 289 } 290 291 if v, ok := d.GetOk("additional_zones"); ok { 292 locationsList := v.([]interface{}) 293 locations := []string{} 294 for _, v := range locationsList { 295 location := v.(string) 296 locations = append(locations, location) 297 if location == zoneName { 298 return fmt.Errorf("additional_zones should not contain the original 'zone'.") 299 } 300 } 301 locations = append(locations, zoneName) 302 cluster.Locations = locations 303 } 304 305 if v, ok := d.GetOk("cluster_ipv4_cidr"); ok { 306 cluster.ClusterIpv4Cidr = v.(string) 307 } 308 309 if v, ok := d.GetOk("description"); ok { 310 cluster.Description = v.(string) 311 } 312 313 if v, ok := d.GetOk("logging_service"); ok { 314 cluster.LoggingService = v.(string) 315 } 316 317 if v, ok := d.GetOk("monitoring_service"); ok { 318 cluster.MonitoringService = v.(string) 319 } 320 321 if _, ok := d.GetOk("network"); ok { 322 network, err := getNetworkName(d, "network") 323 if err != nil { 324 return err 325 } 326 cluster.Network = network 327 } 328 329 if v, ok := d.GetOk("subnetwork"); ok { 330 cluster.Subnetwork = v.(string) 331 } 332 333 if v, ok := d.GetOk("addons_config"); ok { 334 addonsConfig := v.([]interface{})[0].(map[string]interface{}) 335 cluster.AddonsConfig = &container.AddonsConfig{} 336 337 if v, ok := addonsConfig["http_load_balancing"]; ok { 338 addon := v.([]interface{})[0].(map[string]interface{}) 339 cluster.AddonsConfig.HttpLoadBalancing = &container.HttpLoadBalancing{ 340 Disabled: addon["disabled"].(bool), 341 } 342 } 343 344 if v, ok := addonsConfig["horizontal_pod_autoscaling"]; ok { 345 addon := v.([]interface{})[0].(map[string]interface{}) 346 cluster.AddonsConfig.HorizontalPodAutoscaling = &container.HorizontalPodAutoscaling{ 347 Disabled: addon["disabled"].(bool), 348 } 349 } 350 } 351 if v, ok := d.GetOk("node_config"); ok { 352 nodeConfigs := v.([]interface{}) 353 if len(nodeConfigs) > 1 { 354 return fmt.Errorf("Cannot specify more than one node_config.") 355 } 356 nodeConfig := nodeConfigs[0].(map[string]interface{}) 357 358 cluster.NodeConfig = &container.NodeConfig{} 359 360 if v, ok = nodeConfig["machine_type"]; ok { 361 cluster.NodeConfig.MachineType = v.(string) 362 } 363 364 if v, ok = nodeConfig["disk_size_gb"]; ok { 365 cluster.NodeConfig.DiskSizeGb = int64(v.(int)) 366 } 367 368 if v, ok := nodeConfig["oauth_scopes"]; ok { 369 scopesList := v.([]interface{}) 370 scopes := []string{} 371 for _, v := range scopesList { 372 scopes = append(scopes, canonicalizeServiceScope(v.(string))) 373 } 374 375 cluster.NodeConfig.OauthScopes = scopes 376 } 377 } 378 379 req := &container.CreateClusterRequest{ 380 Cluster: cluster, 381 } 382 383 op, err := config.clientContainer.Projects.Zones.Clusters.Create( 384 project, zoneName, req).Do() 385 if err != nil { 386 return err 387 } 388 389 // Wait until it's created 390 waitErr := containerOperationWait(config, op, project, zoneName, "creating GKE cluster", 30, 3) 391 if waitErr != nil { 392 // The resource didn't actually create 393 d.SetId("") 394 return waitErr 395 } 396 397 log.Printf("[INFO] GKE cluster %s has been created", clusterName) 398 399 d.SetId(clusterName) 400 401 return resourceContainerClusterRead(d, meta) 402 } 403 404 func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) error { 405 config := meta.(*Config) 406 407 project, err := getProject(d, config) 408 if err != nil { 409 return err 410 } 411 412 zoneName := d.Get("zone").(string) 413 414 cluster, err := config.clientContainer.Projects.Zones.Clusters.Get( 415 project, zoneName, d.Get("name").(string)).Do() 416 if err != nil { 417 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 418 log.Printf("[WARN] Removing Container Cluster %q because it's gone", d.Get("name").(string)) 419 // The resource doesn't exist anymore 420 d.SetId("") 421 422 return nil 423 } 424 425 return err 426 } 427 428 d.Set("name", cluster.Name) 429 d.Set("zone", cluster.Zone) 430 431 locations := []string{} 432 if len(cluster.Locations) > 1 { 433 for _, location := range cluster.Locations { 434 if location != cluster.Zone { 435 locations = append(locations, location) 436 } 437 } 438 } 439 d.Set("additional_zones", locations) 440 441 d.Set("endpoint", cluster.Endpoint) 442 443 masterAuth := []map[string]interface{}{ 444 map[string]interface{}{ 445 "username": cluster.MasterAuth.Username, 446 "password": cluster.MasterAuth.Password, 447 "client_certificate": cluster.MasterAuth.ClientCertificate, 448 "client_key": cluster.MasterAuth.ClientKey, 449 "cluster_ca_certificate": cluster.MasterAuth.ClusterCaCertificate, 450 }, 451 } 452 d.Set("master_auth", masterAuth) 453 454 d.Set("initial_node_count", cluster.InitialNodeCount) 455 d.Set("node_version", cluster.CurrentNodeVersion) 456 d.Set("cluster_ipv4_cidr", cluster.ClusterIpv4Cidr) 457 d.Set("description", cluster.Description) 458 d.Set("logging_service", cluster.LoggingService) 459 d.Set("monitoring_service", cluster.MonitoringService) 460 d.Set("network", d.Get("network").(string)) 461 d.Set("subnetwork", cluster.Subnetwork) 462 d.Set("node_config", flattenClusterNodeConfig(cluster.NodeConfig)) 463 d.Set("instance_group_urls", cluster.InstanceGroupUrls) 464 465 return nil 466 } 467 468 func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) error { 469 config := meta.(*Config) 470 471 project, err := getProject(d, config) 472 if err != nil { 473 return err 474 } 475 476 zoneName := d.Get("zone").(string) 477 clusterName := d.Get("name").(string) 478 desiredNodeVersion := d.Get("node_version").(string) 479 480 req := &container.UpdateClusterRequest{ 481 Update: &container.ClusterUpdate{ 482 DesiredNodeVersion: desiredNodeVersion, 483 }, 484 } 485 op, err := config.clientContainer.Projects.Zones.Clusters.Update( 486 project, zoneName, clusterName, req).Do() 487 if err != nil { 488 return err 489 } 490 491 // Wait until it's updated 492 waitErr := containerOperationWait(config, op, project, zoneName, "updating GKE cluster", 10, 2) 493 if waitErr != nil { 494 return waitErr 495 } 496 497 log.Printf("[INFO] GKE cluster %s has been updated to %s", d.Id(), 498 desiredNodeVersion) 499 500 return resourceContainerClusterRead(d, meta) 501 } 502 503 func resourceContainerClusterDelete(d *schema.ResourceData, meta interface{}) error { 504 config := meta.(*Config) 505 506 project, err := getProject(d, config) 507 if err != nil { 508 return err 509 } 510 511 zoneName := d.Get("zone").(string) 512 clusterName := d.Get("name").(string) 513 514 log.Printf("[DEBUG] Deleting GKE cluster %s", d.Get("name").(string)) 515 op, err := config.clientContainer.Projects.Zones.Clusters.Delete( 516 project, zoneName, clusterName).Do() 517 if err != nil { 518 return err 519 } 520 521 // Wait until it's deleted 522 waitErr := containerOperationWait(config, op, project, zoneName, "deleting GKE cluster", 10, 3) 523 if waitErr != nil { 524 return waitErr 525 } 526 527 log.Printf("[INFO] GKE cluster %s has been deleted", d.Id()) 528 529 d.SetId("") 530 531 return nil 532 } 533 534 func flattenClusterNodeConfig(c *container.NodeConfig) []map[string]interface{} { 535 config := []map[string]interface{}{ 536 map[string]interface{}{ 537 "machine_type": c.MachineType, 538 "disk_size_gb": c.DiskSizeGb, 539 }, 540 } 541 542 if len(c.OauthScopes) > 0 { 543 config[0]["oauth_scopes"] = c.OauthScopes 544 } 545 546 return config 547 }