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  }