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  }