github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/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 "zone": &schema.Schema{ 25 Type: schema.TypeString, 26 Required: true, 27 ForceNew: true, 28 }, 29 30 "node_version": &schema.Schema{ 31 Type: schema.TypeString, 32 Optional: true, 33 Computed: true, 34 }, 35 36 "cluster_ipv4_cidr": &schema.Schema{ 37 Type: schema.TypeString, 38 Optional: true, 39 Computed: true, 40 ForceNew: true, 41 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 42 value := v.(string) 43 _, ipnet, err := net.ParseCIDR(value) 44 45 if err != nil || ipnet == nil || value != ipnet.String() { 46 errors = append(errors, fmt.Errorf( 47 "%q must contain a valid CIDR", k)) 48 } 49 return 50 }, 51 }, 52 53 "description": &schema.Schema{ 54 Type: schema.TypeString, 55 Optional: true, 56 ForceNew: true, 57 }, 58 59 "endpoint": &schema.Schema{ 60 Type: schema.TypeString, 61 Computed: true, 62 }, 63 64 "logging_service": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 Computed: true, 68 ForceNew: true, 69 }, 70 71 "monitoring_service": &schema.Schema{ 72 Type: schema.TypeString, 73 Optional: true, 74 Computed: true, 75 ForceNew: true, 76 }, 77 78 "master_auth": &schema.Schema{ 79 Type: schema.TypeList, 80 Required: true, 81 ForceNew: true, 82 Elem: &schema.Resource{ 83 Schema: map[string]*schema.Schema{ 84 "client_certificate": &schema.Schema{ 85 Type: schema.TypeString, 86 Computed: true, 87 }, 88 "client_key": &schema.Schema{ 89 Type: schema.TypeString, 90 Computed: true, 91 }, 92 "cluster_ca_certificate": &schema.Schema{ 93 Type: schema.TypeString, 94 Computed: true, 95 }, 96 97 "password": &schema.Schema{ 98 Type: schema.TypeString, 99 Required: true, 100 ForceNew: true, 101 }, 102 103 "username": &schema.Schema{ 104 Type: schema.TypeString, 105 Required: true, 106 ForceNew: true, 107 }, 108 }, 109 }, 110 }, 111 112 "name": &schema.Schema{ 113 Type: schema.TypeString, 114 Required: true, 115 ForceNew: true, 116 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 117 value := v.(string) 118 119 if len(value) > 40 { 120 errors = append(errors, fmt.Errorf( 121 "%q cannot be longer than 40 characters", k)) 122 } 123 if !regexp.MustCompile("^[a-z0-9-]+$").MatchString(value) { 124 errors = append(errors, fmt.Errorf( 125 "%q can only contain lowercase letters, numbers and hyphens", k)) 126 } 127 if !regexp.MustCompile("^[a-z]").MatchString(value) { 128 errors = append(errors, fmt.Errorf( 129 "%q must start with a letter", k)) 130 } 131 if !regexp.MustCompile("[a-z0-9]$").MatchString(value) { 132 errors = append(errors, fmt.Errorf( 133 "%q must end with a number or a letter", k)) 134 } 135 return 136 }, 137 }, 138 139 "network": &schema.Schema{ 140 Type: schema.TypeString, 141 Optional: true, 142 Default: "default", 143 ForceNew: true, 144 }, 145 146 "node_config": &schema.Schema{ 147 Type: schema.TypeList, 148 Optional: true, 149 Computed: true, 150 ForceNew: true, 151 Elem: &schema.Resource{ 152 Schema: map[string]*schema.Schema{ 153 "machine_type": &schema.Schema{ 154 Type: schema.TypeString, 155 Optional: true, 156 Computed: true, 157 ForceNew: true, 158 }, 159 160 "disk_size_gb": &schema.Schema{ 161 Type: schema.TypeInt, 162 Optional: true, 163 Computed: true, 164 ForceNew: true, 165 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 166 value := v.(int) 167 168 if value < 10 { 169 errors = append(errors, fmt.Errorf( 170 "%q cannot be less than 10", k)) 171 } 172 return 173 }, 174 }, 175 176 "oauth_scopes": &schema.Schema{ 177 Type: schema.TypeList, 178 Elem: &schema.Schema{Type: schema.TypeString}, 179 Optional: true, 180 Computed: true, 181 ForceNew: true, 182 }, 183 }, 184 }, 185 }, 186 187 "initial_node_count": &schema.Schema{ 188 Type: schema.TypeInt, 189 Required: true, 190 ForceNew: true, 191 }, 192 193 "instance_group_urls": &schema.Schema{ 194 Type: schema.TypeList, 195 Computed: true, 196 Elem: &schema.Schema{Type: schema.TypeString}, 197 }, 198 }, 199 } 200 } 201 202 func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) error { 203 config := meta.(*Config) 204 205 zoneName := d.Get("zone").(string) 206 clusterName := d.Get("name").(string) 207 208 masterAuths := d.Get("master_auth").([]interface{}) 209 if len(masterAuths) > 1 { 210 return fmt.Errorf("Cannot specify more than one master_auth.") 211 } 212 masterAuth := masterAuths[0].(map[string]interface{}) 213 214 cluster := &container.Cluster{ 215 MasterAuth: &container.MasterAuth{ 216 Password: masterAuth["password"].(string), 217 Username: masterAuth["username"].(string), 218 }, 219 Name: clusterName, 220 InitialNodeCount: int64(d.Get("initial_node_count").(int)), 221 } 222 223 if v, ok := d.GetOk("cluster_ipv4_cidr"); ok { 224 cluster.ClusterIpv4Cidr = v.(string) 225 } 226 227 if v, ok := d.GetOk("description"); ok { 228 cluster.Description = v.(string) 229 } 230 231 if v, ok := d.GetOk("logging_service"); ok { 232 cluster.LoggingService = v.(string) 233 } 234 235 if v, ok := d.GetOk("monitoring_service"); ok { 236 cluster.MonitoringService = v.(string) 237 } 238 239 if v, ok := d.GetOk("network"); ok { 240 cluster.Network = v.(string) 241 } 242 243 if v, ok := d.GetOk("node_config"); ok { 244 nodeConfigs := v.([]interface{}) 245 if len(nodeConfigs) > 1 { 246 return fmt.Errorf("Cannot specify more than one node_config.") 247 } 248 nodeConfig := nodeConfigs[0].(map[string]interface{}) 249 250 cluster.NodeConfig = &container.NodeConfig{} 251 252 if v, ok = nodeConfig["machine_type"]; ok { 253 cluster.NodeConfig.MachineType = v.(string) 254 } 255 256 if v, ok = nodeConfig["disk_size_gb"]; ok { 257 cluster.NodeConfig.DiskSizeGb = int64(v.(int)) 258 } 259 260 if v, ok := nodeConfig["oauth_scopes"]; ok { 261 scopesList := v.([]interface{}) 262 scopes := []string{} 263 for _, v := range scopesList { 264 scopes = append(scopes, v.(string)) 265 } 266 267 cluster.NodeConfig.OauthScopes = scopes 268 } 269 } 270 271 req := &container.CreateClusterRequest{ 272 Cluster: cluster, 273 } 274 275 op, err := config.clientContainer.Projects.Zones.Clusters.Create( 276 config.Project, zoneName, req).Do() 277 if err != nil { 278 return err 279 } 280 281 // Wait until it's created 282 wait := resource.StateChangeConf{ 283 Pending: []string{"PENDING", "RUNNING"}, 284 Target: "DONE", 285 Timeout: 30 * time.Minute, 286 MinTimeout: 3 * time.Second, 287 Refresh: func() (interface{}, string, error) { 288 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 289 config.Project, zoneName, op.Name).Do() 290 log.Printf("[DEBUG] Progress of creating GKE cluster %s: %s", 291 clusterName, resp.Status) 292 return resp, resp.Status, err 293 }, 294 } 295 296 _, err = wait.WaitForState() 297 if err != nil { 298 return err 299 } 300 301 log.Printf("[INFO] GKE cluster %s has been created", clusterName) 302 303 d.SetId(clusterName) 304 305 return resourceContainerClusterRead(d, meta) 306 } 307 308 func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) error { 309 config := meta.(*Config) 310 311 zoneName := d.Get("zone").(string) 312 313 cluster, err := config.clientContainer.Projects.Zones.Clusters.Get( 314 config.Project, zoneName, d.Get("name").(string)).Do() 315 if err != nil { 316 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 317 log.Printf("[WARN] Removing Container Cluster %q because it's gone", d.Get("name").(string)) 318 // The resource doesn't exist anymore 319 d.SetId("") 320 321 return nil 322 } 323 324 return err 325 } 326 327 d.Set("name", cluster.Name) 328 d.Set("zone", cluster.Zone) 329 d.Set("endpoint", cluster.Endpoint) 330 331 masterAuth := []map[string]interface{}{ 332 map[string]interface{}{ 333 "username": cluster.MasterAuth.Username, 334 "password": cluster.MasterAuth.Password, 335 "client_certificate": cluster.MasterAuth.ClientCertificate, 336 "client_key": cluster.MasterAuth.ClientKey, 337 "cluster_ca_certificate": cluster.MasterAuth.ClusterCaCertificate, 338 }, 339 } 340 d.Set("master_auth", masterAuth) 341 342 d.Set("initial_node_count", cluster.InitialNodeCount) 343 d.Set("node_version", cluster.CurrentNodeVersion) 344 d.Set("cluster_ipv4_cidr", cluster.ClusterIpv4Cidr) 345 d.Set("description", cluster.Description) 346 d.Set("logging_service", cluster.LoggingService) 347 d.Set("monitoring_service", cluster.MonitoringService) 348 d.Set("network", cluster.Network) 349 d.Set("node_config", flattenClusterNodeConfig(cluster.NodeConfig)) 350 d.Set("instance_group_urls", cluster.InstanceGroupUrls) 351 352 return nil 353 } 354 355 func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) error { 356 config := meta.(*Config) 357 358 zoneName := d.Get("zone").(string) 359 clusterName := d.Get("name").(string) 360 desiredNodeVersion := d.Get("node_version").(string) 361 362 req := &container.UpdateClusterRequest{ 363 Update: &container.ClusterUpdate{ 364 DesiredNodeVersion: desiredNodeVersion, 365 }, 366 } 367 op, err := config.clientContainer.Projects.Zones.Clusters.Update( 368 config.Project, zoneName, clusterName, req).Do() 369 if err != nil { 370 return err 371 } 372 373 // Wait until it's updated 374 wait := resource.StateChangeConf{ 375 Pending: []string{"PENDING", "RUNNING"}, 376 Target: "DONE", 377 Timeout: 10 * time.Minute, 378 MinTimeout: 2 * time.Second, 379 Refresh: func() (interface{}, string, error) { 380 log.Printf("[DEBUG] Checking if GKE cluster %s is updated", clusterName) 381 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 382 config.Project, zoneName, op.Name).Do() 383 log.Printf("[DEBUG] Progress of updating GKE cluster %s: %s", 384 clusterName, resp.Status) 385 return resp, resp.Status, err 386 }, 387 } 388 389 _, err = wait.WaitForState() 390 if err != nil { 391 return err 392 } 393 394 log.Printf("[INFO] GKE cluster %s has been updated to %s", d.Id(), 395 desiredNodeVersion) 396 397 return resourceContainerClusterRead(d, meta) 398 } 399 400 func resourceContainerClusterDelete(d *schema.ResourceData, meta interface{}) error { 401 config := meta.(*Config) 402 403 zoneName := d.Get("zone").(string) 404 clusterName := d.Get("name").(string) 405 406 log.Printf("[DEBUG] Deleting GKE cluster %s", d.Get("name").(string)) 407 op, err := config.clientContainer.Projects.Zones.Clusters.Delete( 408 config.Project, zoneName, clusterName).Do() 409 if err != nil { 410 return err 411 } 412 413 // Wait until it's deleted 414 wait := resource.StateChangeConf{ 415 Pending: []string{"PENDING", "RUNNING"}, 416 Target: "DONE", 417 Timeout: 10 * time.Minute, 418 MinTimeout: 3 * time.Second, 419 Refresh: func() (interface{}, string, error) { 420 log.Printf("[DEBUG] Checking if GKE cluster %s is deleted", clusterName) 421 resp, err := config.clientContainer.Projects.Zones.Operations.Get( 422 config.Project, zoneName, op.Name).Do() 423 log.Printf("[DEBUG] Progress of deleting GKE cluster %s: %s", 424 clusterName, resp.Status) 425 return resp, resp.Status, err 426 }, 427 } 428 429 _, err = wait.WaitForState() 430 if err != nil { 431 return err 432 } 433 434 log.Printf("[INFO] GKE cluster %s has been deleted", d.Id()) 435 436 d.SetId("") 437 438 return nil 439 } 440 441 func flattenClusterNodeConfig(c *container.NodeConfig) []map[string]interface{} { 442 config := []map[string]interface{}{ 443 map[string]interface{}{ 444 "machine_type": c.MachineType, 445 "disk_size_gb": c.DiskSizeGb, 446 }, 447 } 448 449 if len(c.OauthScopes) > 0 { 450 config[0]["oauth_scopes"] = c.OauthScopes 451 } 452 453 return config 454 }