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