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