github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/builtin/providers/google/resource_container_cluster.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 "net" 7 "regexp" 8 "time" 9 10 "github.com/hashicorp/terraform/helper/resource" 11 "github.com/hashicorp/terraform/helper/schema" 12 "google.golang.org/api/container/v1" 13 "google.golang.org/api/googleapi" 14 ) 15 16 func resourceContainerCluster() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceContainerClusterCreate, 19 Read: resourceContainerClusterRead, 20 Update: resourceContainerClusterUpdate, 21 Delete: resourceContainerClusterDelete, 22 23 Schema: map[string]*schema.Schema{ 24 "initial_node_count": &schema.Schema{ 25 Type: schema.TypeInt, 26 Required: true, 27 ForceNew: true, 28 }, 29 30 "master_auth": &schema.Schema{ 31 Type: schema.TypeList, 32 Required: true, 33 ForceNew: true, 34 Elem: &schema.Resource{ 35 Schema: map[string]*schema.Schema{ 36 "client_certificate": &schema.Schema{ 37 Type: schema.TypeString, 38 Computed: true, 39 }, 40 "client_key": &schema.Schema{ 41 Type: schema.TypeString, 42 Computed: true, 43 }, 44 "cluster_ca_certificate": &schema.Schema{ 45 Type: schema.TypeString, 46 Computed: true, 47 }, 48 "password": &schema.Schema{ 49 Type: schema.TypeString, 50 Required: true, 51 ForceNew: true, 52 }, 53 "username": &schema.Schema{ 54 Type: schema.TypeString, 55 Required: true, 56 ForceNew: true, 57 }, 58 }, 59 }, 60 }, 61 62 "name": &schema.Schema{ 63 Type: schema.TypeString, 64 Required: true, 65 ForceNew: true, 66 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 67 value := v.(string) 68 69 if len(value) > 40 { 70 errors = append(errors, fmt.Errorf( 71 "%q cannot be longer than 40 characters", k)) 72 } 73 if !regexp.MustCompile("^[a-z0-9-]+$").MatchString(value) { 74 errors = append(errors, fmt.Errorf( 75 "%q can only contain lowercase letters, numbers and hyphens", k)) 76 } 77 if !regexp.MustCompile("^[a-z]").MatchString(value) { 78 errors = append(errors, fmt.Errorf( 79 "%q must start with a letter", k)) 80 } 81 if !regexp.MustCompile("[a-z0-9]$").MatchString(value) { 82 errors = append(errors, fmt.Errorf( 83 "%q must end with a number or a letter", k)) 84 } 85 return 86 }, 87 }, 88 89 "zone": &schema.Schema{ 90 Type: schema.TypeString, 91 Required: true, 92 ForceNew: true, 93 }, 94 95 "additional_zones": &schema.Schema{ 96 Type: schema.TypeList, 97 Optional: true, 98 ForceNew: true, 99 Elem: &schema.Schema{Type: schema.TypeString}, 100 }, 101 102 "cluster_ipv4_cidr": &schema.Schema{ 103 Type: schema.TypeString, 104 Optional: true, 105 Computed: true, 106 ForceNew: true, 107 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 108 value := v.(string) 109 _, ipnet, err := net.ParseCIDR(value) 110 111 if err != nil || ipnet == nil || value != ipnet.String() { 112 errors = append(errors, fmt.Errorf( 113 "%q must contain a valid CIDR", k)) 114 } 115 return 116 }, 117 }, 118 119 "description": &schema.Schema{ 120 Type: schema.TypeString, 121 Optional: true, 122 ForceNew: true, 123 }, 124 125 "endpoint": &schema.Schema{ 126 Type: schema.TypeString, 127 Computed: true, 128 }, 129 130 "instance_group_urls": &schema.Schema{ 131 Type: schema.TypeList, 132 Computed: true, 133 Elem: &schema.Schema{Type: schema.TypeString}, 134 }, 135 136 "logging_service": &schema.Schema{ 137 Type: schema.TypeString, 138 Optional: true, 139 Computed: true, 140 ForceNew: true, 141 }, 142 143 "monitoring_service": &schema.Schema{ 144 Type: schema.TypeString, 145 Optional: true, 146 Computed: true, 147 ForceNew: true, 148 }, 149 150 "network": &schema.Schema{ 151 Type: schema.TypeString, 152 Optional: true, 153 Default: "default", 154 ForceNew: true, 155 }, 156 "subnetwork": &schema.Schema{ 157 Type: schema.TypeString, 158 Optional: true, 159 ForceNew: true, 160 }, 161 "addons_config": &schema.Schema{ 162 Type: schema.TypeList, 163 Optional: true, 164 ForceNew: true, 165 MaxItems: 1, 166 Elem: &schema.Resource{ 167 Schema: map[string]*schema.Schema{ 168 "http_load_balancing": &schema.Schema{ 169 Type: schema.TypeList, 170 Optional: true, 171 ForceNew: true, 172 MaxItems: 1, 173 Elem: &schema.Resource{ 174 Schema: map[string]*schema.Schema{ 175 "disabled": &schema.Schema{ 176 Type: schema.TypeBool, 177 Optional: true, 178 ForceNew: true, 179 }, 180 }, 181 }, 182 }, 183 "horizontal_pod_autoscaling": &schema.Schema{ 184 Type: schema.TypeList, 185 Optional: true, 186 ForceNew: true, 187 MaxItems: 1, 188 Elem: &schema.Resource{ 189 Schema: map[string]*schema.Schema{ 190 "disabled": &schema.Schema{ 191 Type: schema.TypeBool, 192 Optional: true, 193 ForceNew: true, 194 }, 195 }, 196 }, 197 }, 198 }, 199 }, 200 }, 201 "node_config": &schema.Schema{ 202 Type: schema.TypeList, 203 Optional: true, 204 Computed: true, 205 ForceNew: true, 206 Elem: &schema.Resource{ 207 Schema: map[string]*schema.Schema{ 208 "machine_type": &schema.Schema{ 209 Type: schema.TypeString, 210 Optional: true, 211 Computed: true, 212 ForceNew: true, 213 }, 214 215 "disk_size_gb": &schema.Schema{ 216 Type: schema.TypeInt, 217 Optional: true, 218 Computed: true, 219 ForceNew: true, 220 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 221 value := v.(int) 222 223 if value < 10 { 224 errors = append(errors, fmt.Errorf( 225 "%q cannot be less than 10", k)) 226 } 227 return 228 }, 229 }, 230 231 "oauth_scopes": &schema.Schema{ 232 Type: schema.TypeList, 233 Optional: true, 234 Computed: true, 235 ForceNew: true, 236 Elem: &schema.Schema{ 237 Type: schema.TypeString, 238 StateFunc: func(v interface{}) string { 239 return canonicalizeServiceScope(v.(string)) 240 }, 241 }, 242 }, 243 }, 244 }, 245 }, 246 247 "node_version": &schema.Schema{ 248 Type: schema.TypeString, 249 Optional: true, 250 Computed: true, 251 }, 252 253 "project": &schema.Schema{ 254 Type: schema.TypeString, 255 Optional: true, 256 ForceNew: true, 257 }, 258 }, 259 } 260 } 261 262 func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) error { 263 config := meta.(*Config) 264 265 project, err := getProject(d, config) 266 if err != nil { 267 return err 268 } 269 270 zoneName := d.Get("zone").(string) 271 clusterName := d.Get("name").(string) 272 273 masterAuths := d.Get("master_auth").([]interface{}) 274 if len(masterAuths) > 1 { 275 return fmt.Errorf("Cannot specify more than one master_auth.") 276 } 277 masterAuth := masterAuths[0].(map[string]interface{}) 278 279 cluster := &container.Cluster{ 280 MasterAuth: &container.MasterAuth{ 281 Password: masterAuth["password"].(string), 282 Username: masterAuth["username"].(string), 283 }, 284 Name: clusterName, 285 InitialNodeCount: int64(d.Get("initial_node_count").(int)), 286 } 287 288 if v, ok := d.GetOk("node_version"); ok { 289 cluster.InitialClusterVersion = v.(string) 290 } 291 292 if v, ok := d.GetOk("additional_zones"); ok { 293 locationsList := v.([]interface{}) 294 locations := []string{} 295 zoneInLocations := false 296 for _, v := range locationsList { 297 location := v.(string) 298 locations = append(locations, location) 299 if location == zoneName { 300 zoneInLocations = true 301 } 302 } 303 if !zoneInLocations { 304 // zone must be in locations if specified separately 305 locations = append(locations, zoneName) 306 } 307 cluster.Locations = locations 308 } 309 310 if v, ok := d.GetOk("cluster_ipv4_cidr"); ok { 311 cluster.ClusterIpv4Cidr = v.(string) 312 } 313 314 if v, ok := d.GetOk("description"); ok { 315 cluster.Description = v.(string) 316 } 317 318 if v, ok := d.GetOk("logging_service"); ok { 319 cluster.LoggingService = v.(string) 320 } 321 322 if v, ok := d.GetOk("monitoring_service"); ok { 323 cluster.MonitoringService = v.(string) 324 } 325 326 if _, ok := d.GetOk("network"); ok { 327 network, err := getNetworkName(d, "network") 328 if err != nil { 329 return err 330 } 331 cluster.Network = network 332 } 333 334 if v, ok := d.GetOk("subnetwork"); ok { 335 cluster.Subnetwork = v.(string) 336 } 337 338 if v, ok := d.GetOk("addons_config"); ok { 339 addonsConfig := v.([]interface{})[0].(map[string]interface{}) 340 cluster.AddonsConfig = &container.AddonsConfig{} 341 342 if v, ok := addonsConfig["http_load_balancing"]; ok { 343 addon := v.([]interface{})[0].(map[string]interface{}) 344 cluster.AddonsConfig.HttpLoadBalancing = &container.HttpLoadBalancing{ 345 Disabled: addon["disabled"].(bool), 346 } 347 } 348 349 if v, ok := addonsConfig["horizontal_pod_autoscaling"]; ok { 350 addon := v.([]interface{})[0].(map[string]interface{}) 351 cluster.AddonsConfig.HorizontalPodAutoscaling = &container.HorizontalPodAutoscaling{ 352 Disabled: addon["disabled"].(bool), 353 } 354 } 355 } 356 if v, ok := d.GetOk("node_config"); ok { 357 nodeConfigs := v.([]interface{}) 358 if len(nodeConfigs) > 1 { 359 return fmt.Errorf("Cannot specify more than one node_config.") 360 } 361 nodeConfig := nodeConfigs[0].(map[string]interface{}) 362 363 cluster.NodeConfig = &container.NodeConfig{} 364 365 if v, ok = nodeConfig["machine_type"]; ok { 366 cluster.NodeConfig.MachineType = v.(string) 367 } 368 369 if v, ok = nodeConfig["disk_size_gb"]; ok { 370 cluster.NodeConfig.DiskSizeGb = int64(v.(int)) 371 } 372 373 if v, ok := nodeConfig["oauth_scopes"]; ok { 374 scopesList := v.([]interface{}) 375 scopes := []string{} 376 for _, v := range scopesList { 377 scopes = append(scopes, canonicalizeServiceScope(v.(string))) 378 } 379 380 cluster.NodeConfig.OauthScopes = scopes 381 } 382 } 383 384 req := &container.CreateClusterRequest{ 385 Cluster: cluster, 386 } 387 388 op, err := config.clientContainer.Projects.Zones.Clusters.Create( 389 project, zoneName, req).Do() 390 if err != nil { 391 return err 392 } 393 394 // Wait until it's created 395 wait := resource.StateChangeConf{ 396 Pending: []string{"PENDING", "RUNNING"}, 397 Target: []string{"DONE"}, 398 Timeout: 30 * time.Minute, 399 MinTimeout: 3 * time.Second, 400 Refresh: func() (interface{}, string, error) { 401 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 402 project, zoneName, op.Name).Do() 403 log.Printf("[DEBUG] Progress of creating GKE cluster %s: %s", 404 clusterName, resp.Status) 405 return resp, resp.Status, err 406 }, 407 } 408 409 _, err = wait.WaitForState() 410 if err != nil { 411 return err 412 } 413 414 log.Printf("[INFO] GKE cluster %s has been created", clusterName) 415 416 d.SetId(clusterName) 417 418 return resourceContainerClusterRead(d, meta) 419 } 420 421 func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) error { 422 config := meta.(*Config) 423 424 project, err := getProject(d, config) 425 if err != nil { 426 return err 427 } 428 429 zoneName := d.Get("zone").(string) 430 431 cluster, err := config.clientContainer.Projects.Zones.Clusters.Get( 432 project, zoneName, d.Get("name").(string)).Do() 433 if err != nil { 434 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 435 log.Printf("[WARN] Removing Container Cluster %q because it's gone", d.Get("name").(string)) 436 // The resource doesn't exist anymore 437 d.SetId("") 438 439 return nil 440 } 441 442 return err 443 } 444 445 d.Set("name", cluster.Name) 446 d.Set("zone", cluster.Zone) 447 d.Set("additional_zones", cluster.Locations) 448 d.Set("endpoint", cluster.Endpoint) 449 450 masterAuth := []map[string]interface{}{ 451 map[string]interface{}{ 452 "username": cluster.MasterAuth.Username, 453 "password": cluster.MasterAuth.Password, 454 "client_certificate": cluster.MasterAuth.ClientCertificate, 455 "client_key": cluster.MasterAuth.ClientKey, 456 "cluster_ca_certificate": cluster.MasterAuth.ClusterCaCertificate, 457 }, 458 } 459 d.Set("master_auth", masterAuth) 460 461 d.Set("initial_node_count", cluster.InitialNodeCount) 462 d.Set("node_version", cluster.CurrentNodeVersion) 463 d.Set("cluster_ipv4_cidr", cluster.ClusterIpv4Cidr) 464 d.Set("description", cluster.Description) 465 d.Set("logging_service", cluster.LoggingService) 466 d.Set("monitoring_service", cluster.MonitoringService) 467 d.Set("network", d.Get("network").(string)) 468 d.Set("subnetwork", cluster.Subnetwork) 469 d.Set("node_config", flattenClusterNodeConfig(cluster.NodeConfig)) 470 d.Set("instance_group_urls", cluster.InstanceGroupUrls) 471 472 return nil 473 } 474 475 func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) error { 476 config := meta.(*Config) 477 478 project, err := getProject(d, config) 479 if err != nil { 480 return err 481 } 482 483 zoneName := d.Get("zone").(string) 484 clusterName := d.Get("name").(string) 485 desiredNodeVersion := d.Get("node_version").(string) 486 487 req := &container.UpdateClusterRequest{ 488 Update: &container.ClusterUpdate{ 489 DesiredNodeVersion: desiredNodeVersion, 490 }, 491 } 492 op, err := config.clientContainer.Projects.Zones.Clusters.Update( 493 project, zoneName, clusterName, req).Do() 494 if err != nil { 495 return err 496 } 497 498 // Wait until it's updated 499 wait := resource.StateChangeConf{ 500 Pending: []string{"PENDING", "RUNNING"}, 501 Target: []string{"DONE"}, 502 Timeout: 10 * time.Minute, 503 MinTimeout: 2 * time.Second, 504 Refresh: func() (interface{}, string, error) { 505 log.Printf("[DEBUG] Checking if GKE cluster %s is updated", clusterName) 506 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 507 project, zoneName, op.Name).Do() 508 log.Printf("[DEBUG] Progress of updating GKE cluster %s: %s", 509 clusterName, resp.Status) 510 return resp, resp.Status, err 511 }, 512 } 513 514 _, err = wait.WaitForState() 515 if err != nil { 516 return err 517 } 518 519 log.Printf("[INFO] GKE cluster %s has been updated to %s", d.Id(), 520 desiredNodeVersion) 521 522 return resourceContainerClusterRead(d, meta) 523 } 524 525 func resourceContainerClusterDelete(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 clusterName := d.Get("name").(string) 535 536 log.Printf("[DEBUG] Deleting GKE cluster %s", d.Get("name").(string)) 537 op, err := config.clientContainer.Projects.Zones.Clusters.Delete( 538 project, zoneName, clusterName).Do() 539 if err != nil { 540 return err 541 } 542 543 // Wait until it's deleted 544 wait := resource.StateChangeConf{ 545 Pending: []string{"PENDING", "RUNNING"}, 546 Target: []string{"DONE"}, 547 Timeout: 10 * time.Minute, 548 MinTimeout: 3 * time.Second, 549 Refresh: func() (interface{}, string, error) { 550 log.Printf("[DEBUG] Checking if GKE cluster %s is deleted", clusterName) 551 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 552 project, zoneName, op.Name).Do() 553 log.Printf("[DEBUG] Progress of deleting GKE cluster %s: %s", 554 clusterName, resp.Status) 555 return resp, resp.Status, err 556 }, 557 } 558 559 _, err = wait.WaitForState() 560 if err != nil { 561 return err 562 } 563 564 log.Printf("[INFO] GKE cluster %s has been deleted", d.Id()) 565 566 d.SetId("") 567 568 return nil 569 } 570 571 func flattenClusterNodeConfig(c *container.NodeConfig) []map[string]interface{} { 572 config := []map[string]interface{}{ 573 map[string]interface{}{ 574 "machine_type": c.MachineType, 575 "disk_size_gb": c.DiskSizeGb, 576 }, 577 } 578 579 if len(c.OauthScopes) > 0 { 580 config[0]["oauth_scopes"] = c.OauthScopes 581 } 582 583 return config 584 }