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