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