github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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 150 "node_config": &schema.Schema{ 151 Type: schema.TypeList, 152 Optional: true, 153 Computed: true, 154 ForceNew: true, 155 Elem: &schema.Resource{ 156 Schema: map[string]*schema.Schema{ 157 "machine_type": &schema.Schema{ 158 Type: schema.TypeString, 159 Optional: true, 160 Computed: true, 161 ForceNew: true, 162 }, 163 164 "disk_size_gb": &schema.Schema{ 165 Type: schema.TypeInt, 166 Optional: true, 167 Computed: true, 168 ForceNew: true, 169 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 170 value := v.(int) 171 172 if value < 10 { 173 errors = append(errors, fmt.Errorf( 174 "%q cannot be less than 10", k)) 175 } 176 return 177 }, 178 }, 179 180 "oauth_scopes": &schema.Schema{ 181 Type: schema.TypeList, 182 Elem: &schema.Schema{Type: schema.TypeString}, 183 Optional: true, 184 Computed: true, 185 ForceNew: true, 186 }, 187 }, 188 }, 189 }, 190 191 "node_version": &schema.Schema{ 192 Type: schema.TypeString, 193 Optional: true, 194 Computed: true, 195 }, 196 197 "project": &schema.Schema{ 198 Type: schema.TypeString, 199 Optional: true, 200 ForceNew: true, 201 }, 202 }, 203 } 204 } 205 206 func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) error { 207 config := meta.(*Config) 208 209 project, err := getProject(d, config) 210 if err != nil { 211 return err 212 } 213 214 zoneName := d.Get("zone").(string) 215 clusterName := d.Get("name").(string) 216 217 masterAuths := d.Get("master_auth").([]interface{}) 218 if len(masterAuths) > 1 { 219 return fmt.Errorf("Cannot specify more than one master_auth.") 220 } 221 masterAuth := masterAuths[0].(map[string]interface{}) 222 223 cluster := &container.Cluster{ 224 MasterAuth: &container.MasterAuth{ 225 Password: masterAuth["password"].(string), 226 Username: masterAuth["username"].(string), 227 }, 228 Name: clusterName, 229 InitialNodeCount: int64(d.Get("initial_node_count").(int)), 230 } 231 232 if v, ok := d.GetOk("cluster_ipv4_cidr"); ok { 233 cluster.ClusterIpv4Cidr = v.(string) 234 } 235 236 if v, ok := d.GetOk("description"); ok { 237 cluster.Description = v.(string) 238 } 239 240 if v, ok := d.GetOk("logging_service"); ok { 241 cluster.LoggingService = v.(string) 242 } 243 244 if v, ok := d.GetOk("monitoring_service"); ok { 245 cluster.MonitoringService = v.(string) 246 } 247 248 if v, ok := d.GetOk("network"); ok { 249 cluster.Network = v.(string) 250 } 251 252 if v, ok := d.GetOk("node_config"); ok { 253 nodeConfigs := v.([]interface{}) 254 if len(nodeConfigs) > 1 { 255 return fmt.Errorf("Cannot specify more than one node_config.") 256 } 257 nodeConfig := nodeConfigs[0].(map[string]interface{}) 258 259 cluster.NodeConfig = &container.NodeConfig{} 260 261 if v, ok = nodeConfig["machine_type"]; ok { 262 cluster.NodeConfig.MachineType = v.(string) 263 } 264 265 if v, ok = nodeConfig["disk_size_gb"]; ok { 266 cluster.NodeConfig.DiskSizeGb = int64(v.(int)) 267 } 268 269 if v, ok := nodeConfig["oauth_scopes"]; ok { 270 scopesList := v.([]interface{}) 271 scopes := []string{} 272 for _, v := range scopesList { 273 scopes = append(scopes, v.(string)) 274 } 275 276 cluster.NodeConfig.OauthScopes = scopes 277 } 278 } 279 280 req := &container.CreateClusterRequest{ 281 Cluster: cluster, 282 } 283 284 op, err := config.clientContainer.Projects.Zones.Clusters.Create( 285 project, zoneName, req).Do() 286 if err != nil { 287 return err 288 } 289 290 // Wait until it's created 291 wait := resource.StateChangeConf{ 292 Pending: []string{"PENDING", "RUNNING"}, 293 Target: []string{"DONE"}, 294 Timeout: 30 * time.Minute, 295 MinTimeout: 3 * time.Second, 296 Refresh: func() (interface{}, string, error) { 297 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 298 project, zoneName, op.Name).Do() 299 log.Printf("[DEBUG] Progress of creating GKE cluster %s: %s", 300 clusterName, resp.Status) 301 return resp, resp.Status, err 302 }, 303 } 304 305 _, err = wait.WaitForState() 306 if err != nil { 307 return err 308 } 309 310 log.Printf("[INFO] GKE cluster %s has been created", clusterName) 311 312 d.SetId(clusterName) 313 314 return resourceContainerClusterRead(d, meta) 315 } 316 317 func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) error { 318 config := meta.(*Config) 319 320 project, err := getProject(d, config) 321 if err != nil { 322 return err 323 } 324 325 zoneName := d.Get("zone").(string) 326 327 cluster, err := config.clientContainer.Projects.Zones.Clusters.Get( 328 project, zoneName, d.Get("name").(string)).Do() 329 if err != nil { 330 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 331 log.Printf("[WARN] Removing Container Cluster %q because it's gone", d.Get("name").(string)) 332 // The resource doesn't exist anymore 333 d.SetId("") 334 335 return nil 336 } 337 338 return err 339 } 340 341 d.Set("name", cluster.Name) 342 d.Set("zone", cluster.Zone) 343 d.Set("endpoint", cluster.Endpoint) 344 345 masterAuth := []map[string]interface{}{ 346 map[string]interface{}{ 347 "username": cluster.MasterAuth.Username, 348 "password": cluster.MasterAuth.Password, 349 "client_certificate": cluster.MasterAuth.ClientCertificate, 350 "client_key": cluster.MasterAuth.ClientKey, 351 "cluster_ca_certificate": cluster.MasterAuth.ClusterCaCertificate, 352 }, 353 } 354 d.Set("master_auth", masterAuth) 355 356 d.Set("initial_node_count", cluster.InitialNodeCount) 357 d.Set("node_version", cluster.CurrentNodeVersion) 358 d.Set("cluster_ipv4_cidr", cluster.ClusterIpv4Cidr) 359 d.Set("description", cluster.Description) 360 d.Set("logging_service", cluster.LoggingService) 361 d.Set("monitoring_service", cluster.MonitoringService) 362 d.Set("network", cluster.Network) 363 d.Set("node_config", flattenClusterNodeConfig(cluster.NodeConfig)) 364 d.Set("instance_group_urls", cluster.InstanceGroupUrls) 365 366 return nil 367 } 368 369 func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) error { 370 config := meta.(*Config) 371 372 project, err := getProject(d, config) 373 if err != nil { 374 return err 375 } 376 377 zoneName := d.Get("zone").(string) 378 clusterName := d.Get("name").(string) 379 desiredNodeVersion := d.Get("node_version").(string) 380 381 req := &container.UpdateClusterRequest{ 382 Update: &container.ClusterUpdate{ 383 DesiredNodeVersion: desiredNodeVersion, 384 }, 385 } 386 op, err := config.clientContainer.Projects.Zones.Clusters.Update( 387 project, zoneName, clusterName, req).Do() 388 if err != nil { 389 return err 390 } 391 392 // Wait until it's updated 393 wait := resource.StateChangeConf{ 394 Pending: []string{"PENDING", "RUNNING"}, 395 Target: []string{"DONE"}, 396 Timeout: 10 * time.Minute, 397 MinTimeout: 2 * time.Second, 398 Refresh: func() (interface{}, string, error) { 399 log.Printf("[DEBUG] Checking if GKE cluster %s is updated", clusterName) 400 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 401 project, zoneName, op.Name).Do() 402 log.Printf("[DEBUG] Progress of updating GKE cluster %s: %s", 403 clusterName, resp.Status) 404 return resp, resp.Status, err 405 }, 406 } 407 408 _, err = wait.WaitForState() 409 if err != nil { 410 return err 411 } 412 413 log.Printf("[INFO] GKE cluster %s has been updated to %s", d.Id(), 414 desiredNodeVersion) 415 416 return resourceContainerClusterRead(d, meta) 417 } 418 419 func resourceContainerClusterDelete(d *schema.ResourceData, meta interface{}) error { 420 config := meta.(*Config) 421 422 project, err := getProject(d, config) 423 if err != nil { 424 return err 425 } 426 427 zoneName := d.Get("zone").(string) 428 clusterName := d.Get("name").(string) 429 430 log.Printf("[DEBUG] Deleting GKE cluster %s", d.Get("name").(string)) 431 op, err := config.clientContainer.Projects.Zones.Clusters.Delete( 432 project, zoneName, clusterName).Do() 433 if err != nil { 434 return err 435 } 436 437 // Wait until it's deleted 438 wait := resource.StateChangeConf{ 439 Pending: []string{"PENDING", "RUNNING"}, 440 Target: []string{"DONE"}, 441 Timeout: 10 * time.Minute, 442 MinTimeout: 3 * time.Second, 443 Refresh: func() (interface{}, string, error) { 444 log.Printf("[DEBUG] Checking if GKE cluster %s is deleted", clusterName) 445 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 446 project, zoneName, op.Name).Do() 447 log.Printf("[DEBUG] Progress of deleting GKE cluster %s: %s", 448 clusterName, resp.Status) 449 return resp, resp.Status, err 450 }, 451 } 452 453 _, err = wait.WaitForState() 454 if err != nil { 455 return err 456 } 457 458 log.Printf("[INFO] GKE cluster %s has been deleted", d.Id()) 459 460 d.SetId("") 461 462 return nil 463 } 464 465 func flattenClusterNodeConfig(c *container.NodeConfig) []map[string]interface{} { 466 config := []map[string]interface{}{ 467 map[string]interface{}{ 468 "machine_type": c.MachineType, 469 "disk_size_gb": c.DiskSizeGb, 470 }, 471 } 472 473 if len(c.OauthScopes) > 0 { 474 config[0]["oauth_scopes"] = c.OauthScopes 475 } 476 477 return config 478 }