github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/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  			"zone": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  
    30  			"node_version": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Optional: true,
    33  				Computed: true,
    34  			},
    35  
    36  			"cluster_ipv4_cidr": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Optional: true,
    39  				Computed: true,
    40  				ForceNew: true,
    41  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    42  					value := v.(string)
    43  					_, ipnet, err := net.ParseCIDR(value)
    44  
    45  					if err != nil || ipnet == nil || value != ipnet.String() {
    46  						errors = append(errors, fmt.Errorf(
    47  							"%q must contain a valid CIDR", k))
    48  					}
    49  					return
    50  				},
    51  			},
    52  
    53  			"description": &schema.Schema{
    54  				Type:     schema.TypeString,
    55  				Optional: true,
    56  				ForceNew: true,
    57  			},
    58  
    59  			"endpoint": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Computed: true,
    62  			},
    63  
    64  			"logging_service": &schema.Schema{
    65  				Type:     schema.TypeString,
    66  				Optional: true,
    67  				Computed: true,
    68  				ForceNew: true,
    69  			},
    70  
    71  			"monitoring_service": &schema.Schema{
    72  				Type:     schema.TypeString,
    73  				Optional: true,
    74  				Computed: true,
    75  				ForceNew: true,
    76  			},
    77  
    78  			"master_auth": &schema.Schema{
    79  				Type:     schema.TypeList,
    80  				Required: true,
    81  				ForceNew: true,
    82  				Elem: &schema.Resource{
    83  					Schema: map[string]*schema.Schema{
    84  						"client_certificate": &schema.Schema{
    85  							Type:     schema.TypeString,
    86  							Computed: true,
    87  						},
    88  						"client_key": &schema.Schema{
    89  							Type:     schema.TypeString,
    90  							Computed: true,
    91  						},
    92  						"cluster_ca_certificate": &schema.Schema{
    93  							Type:     schema.TypeString,
    94  							Computed: true,
    95  						},
    96  
    97  						"password": &schema.Schema{
    98  							Type:     schema.TypeString,
    99  							Required: true,
   100  							ForceNew: true,
   101  						},
   102  
   103  						"username": &schema.Schema{
   104  							Type:     schema.TypeString,
   105  							Required: true,
   106  							ForceNew: true,
   107  						},
   108  					},
   109  				},
   110  			},
   111  
   112  			"name": &schema.Schema{
   113  				Type:     schema.TypeString,
   114  				Required: true,
   115  				ForceNew: true,
   116  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   117  					value := v.(string)
   118  
   119  					if len(value) > 40 {
   120  						errors = append(errors, fmt.Errorf(
   121  							"%q cannot be longer than 40 characters", k))
   122  					}
   123  					if !regexp.MustCompile("^[a-z0-9-]+$").MatchString(value) {
   124  						errors = append(errors, fmt.Errorf(
   125  							"%q can only contain lowercase letters, numbers and hyphens", k))
   126  					}
   127  					if !regexp.MustCompile("^[a-z]").MatchString(value) {
   128  						errors = append(errors, fmt.Errorf(
   129  							"%q must start with a letter", k))
   130  					}
   131  					if !regexp.MustCompile("[a-z0-9]$").MatchString(value) {
   132  						errors = append(errors, fmt.Errorf(
   133  							"%q must end with a number or a letter", k))
   134  					}
   135  					return
   136  				},
   137  			},
   138  
   139  			"network": &schema.Schema{
   140  				Type:     schema.TypeString,
   141  				Optional: true,
   142  				Default:  "default",
   143  				ForceNew: true,
   144  			},
   145  
   146  			"node_config": &schema.Schema{
   147  				Type:     schema.TypeList,
   148  				Optional: true,
   149  				Computed: true,
   150  				ForceNew: true,
   151  				Elem: &schema.Resource{
   152  					Schema: map[string]*schema.Schema{
   153  						"machine_type": &schema.Schema{
   154  							Type:     schema.TypeString,
   155  							Optional: true,
   156  							Computed: true,
   157  							ForceNew: true,
   158  						},
   159  
   160  						"disk_size_gb": &schema.Schema{
   161  							Type:     schema.TypeInt,
   162  							Optional: true,
   163  							Computed: true,
   164  							ForceNew: true,
   165  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   166  								value := v.(int)
   167  
   168  								if value < 10 {
   169  									errors = append(errors, fmt.Errorf(
   170  										"%q cannot be less than 10", k))
   171  								}
   172  								return
   173  							},
   174  						},
   175  
   176  						"oauth_scopes": &schema.Schema{
   177  							Type:     schema.TypeList,
   178  							Elem:     &schema.Schema{Type: schema.TypeString},
   179  							Optional: true,
   180  							Computed: true,
   181  							ForceNew: true,
   182  						},
   183  					},
   184  				},
   185  			},
   186  
   187  			"initial_node_count": &schema.Schema{
   188  				Type:     schema.TypeInt,
   189  				Required: true,
   190  				ForceNew: true,
   191  			},
   192  
   193  			"instance_group_urls": &schema.Schema{
   194  				Type:     schema.TypeList,
   195  				Computed: true,
   196  				Elem:     &schema.Schema{Type: schema.TypeString},
   197  			},
   198  		},
   199  	}
   200  }
   201  
   202  func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) error {
   203  	config := meta.(*Config)
   204  
   205  	zoneName := d.Get("zone").(string)
   206  	clusterName := d.Get("name").(string)
   207  
   208  	masterAuths := d.Get("master_auth").([]interface{})
   209  	if len(masterAuths) > 1 {
   210  		return fmt.Errorf("Cannot specify more than one master_auth.")
   211  	}
   212  	masterAuth := masterAuths[0].(map[string]interface{})
   213  
   214  	cluster := &container.Cluster{
   215  		MasterAuth: &container.MasterAuth{
   216  			Password: masterAuth["password"].(string),
   217  			Username: masterAuth["username"].(string),
   218  		},
   219  		Name:             clusterName,
   220  		InitialNodeCount: int64(d.Get("initial_node_count").(int)),
   221  	}
   222  
   223  	if v, ok := d.GetOk("cluster_ipv4_cidr"); ok {
   224  		cluster.ClusterIpv4Cidr = v.(string)
   225  	}
   226  
   227  	if v, ok := d.GetOk("description"); ok {
   228  		cluster.Description = v.(string)
   229  	}
   230  
   231  	if v, ok := d.GetOk("logging_service"); ok {
   232  		cluster.LoggingService = v.(string)
   233  	}
   234  
   235  	if v, ok := d.GetOk("monitoring_service"); ok {
   236  		cluster.MonitoringService = v.(string)
   237  	}
   238  
   239  	if v, ok := d.GetOk("network"); ok {
   240  		cluster.Network = v.(string)
   241  	}
   242  
   243  	if v, ok := d.GetOk("node_config"); ok {
   244  		nodeConfigs := v.([]interface{})
   245  		if len(nodeConfigs) > 1 {
   246  			return fmt.Errorf("Cannot specify more than one node_config.")
   247  		}
   248  		nodeConfig := nodeConfigs[0].(map[string]interface{})
   249  
   250  		cluster.NodeConfig = &container.NodeConfig{}
   251  
   252  		if v, ok = nodeConfig["machine_type"]; ok {
   253  			cluster.NodeConfig.MachineType = v.(string)
   254  		}
   255  
   256  		if v, ok = nodeConfig["disk_size_gb"]; ok {
   257  			cluster.NodeConfig.DiskSizeGb = int64(v.(int))
   258  		}
   259  
   260  		if v, ok := nodeConfig["oauth_scopes"]; ok {
   261  			scopesList := v.([]interface{})
   262  			scopes := []string{}
   263  			for _, v := range scopesList {
   264  				scopes = append(scopes, v.(string))
   265  			}
   266  
   267  			cluster.NodeConfig.OauthScopes = scopes
   268  		}
   269  	}
   270  
   271  	req := &container.CreateClusterRequest{
   272  		Cluster: cluster,
   273  	}
   274  
   275  	op, err := config.clientContainer.Projects.Zones.Clusters.Create(
   276  		config.Project, zoneName, req).Do()
   277  	if err != nil {
   278  		return err
   279  	}
   280  
   281  	// Wait until it's created
   282  	wait := resource.StateChangeConf{
   283  		Pending:    []string{"PENDING", "RUNNING"},
   284  		Target:     []string{"DONE"},
   285  		Timeout:    30 * time.Minute,
   286  		MinTimeout: 3 * time.Second,
   287  		Refresh: func() (interface{}, string, error) {
   288  			resp, err := config.clientContainer.Projects.Zones.Operations.Get(
   289  				config.Project, zoneName, op.Name).Do()
   290  			log.Printf("[DEBUG] Progress of creating GKE cluster %s: %s",
   291  				clusterName, resp.Status)
   292  			return resp, resp.Status, err
   293  		},
   294  	}
   295  
   296  	_, err = wait.WaitForState()
   297  	if err != nil {
   298  		return err
   299  	}
   300  
   301  	log.Printf("[INFO] GKE cluster %s has been created", clusterName)
   302  
   303  	d.SetId(clusterName)
   304  
   305  	return resourceContainerClusterRead(d, meta)
   306  }
   307  
   308  func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) error {
   309  	config := meta.(*Config)
   310  
   311  	zoneName := d.Get("zone").(string)
   312  
   313  	cluster, err := config.clientContainer.Projects.Zones.Clusters.Get(
   314  		config.Project, zoneName, d.Get("name").(string)).Do()
   315  	if err != nil {
   316  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
   317  			log.Printf("[WARN] Removing Container Cluster %q because it's gone", d.Get("name").(string))
   318  			// The resource doesn't exist anymore
   319  			d.SetId("")
   320  
   321  			return nil
   322  		}
   323  
   324  		return err
   325  	}
   326  
   327  	d.Set("name", cluster.Name)
   328  	d.Set("zone", cluster.Zone)
   329  	d.Set("endpoint", cluster.Endpoint)
   330  
   331  	masterAuth := []map[string]interface{}{
   332  		map[string]interface{}{
   333  			"username":               cluster.MasterAuth.Username,
   334  			"password":               cluster.MasterAuth.Password,
   335  			"client_certificate":     cluster.MasterAuth.ClientCertificate,
   336  			"client_key":             cluster.MasterAuth.ClientKey,
   337  			"cluster_ca_certificate": cluster.MasterAuth.ClusterCaCertificate,
   338  		},
   339  	}
   340  	d.Set("master_auth", masterAuth)
   341  
   342  	d.Set("initial_node_count", cluster.InitialNodeCount)
   343  	d.Set("node_version", cluster.CurrentNodeVersion)
   344  	d.Set("cluster_ipv4_cidr", cluster.ClusterIpv4Cidr)
   345  	d.Set("description", cluster.Description)
   346  	d.Set("logging_service", cluster.LoggingService)
   347  	d.Set("monitoring_service", cluster.MonitoringService)
   348  	d.Set("network", cluster.Network)
   349  	d.Set("node_config", flattenClusterNodeConfig(cluster.NodeConfig))
   350  	d.Set("instance_group_urls", cluster.InstanceGroupUrls)
   351  
   352  	return nil
   353  }
   354  
   355  func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) error {
   356  	config := meta.(*Config)
   357  
   358  	zoneName := d.Get("zone").(string)
   359  	clusterName := d.Get("name").(string)
   360  	desiredNodeVersion := d.Get("node_version").(string)
   361  
   362  	req := &container.UpdateClusterRequest{
   363  		Update: &container.ClusterUpdate{
   364  			DesiredNodeVersion: desiredNodeVersion,
   365  		},
   366  	}
   367  	op, err := config.clientContainer.Projects.Zones.Clusters.Update(
   368  		config.Project, zoneName, clusterName, req).Do()
   369  	if err != nil {
   370  		return err
   371  	}
   372  
   373  	// Wait until it's updated
   374  	wait := resource.StateChangeConf{
   375  		Pending:    []string{"PENDING", "RUNNING"},
   376  		Target:     []string{"DONE"},
   377  		Timeout:    10 * time.Minute,
   378  		MinTimeout: 2 * time.Second,
   379  		Refresh: func() (interface{}, string, error) {
   380  			log.Printf("[DEBUG] Checking if GKE cluster %s is updated", clusterName)
   381  			resp, err := config.clientContainer.Projects.Zones.Operations.Get(
   382  				config.Project, zoneName, op.Name).Do()
   383  			log.Printf("[DEBUG] Progress of updating GKE cluster %s: %s",
   384  				clusterName, resp.Status)
   385  			return resp, resp.Status, err
   386  		},
   387  	}
   388  
   389  	_, err = wait.WaitForState()
   390  	if err != nil {
   391  		return err
   392  	}
   393  
   394  	log.Printf("[INFO] GKE cluster %s has been updated to %s", d.Id(),
   395  		desiredNodeVersion)
   396  
   397  	return resourceContainerClusterRead(d, meta)
   398  }
   399  
   400  func resourceContainerClusterDelete(d *schema.ResourceData, meta interface{}) error {
   401  	config := meta.(*Config)
   402  
   403  	zoneName := d.Get("zone").(string)
   404  	clusterName := d.Get("name").(string)
   405  
   406  	log.Printf("[DEBUG] Deleting GKE cluster %s", d.Get("name").(string))
   407  	op, err := config.clientContainer.Projects.Zones.Clusters.Delete(
   408  		config.Project, zoneName, clusterName).Do()
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	// Wait until it's deleted
   414  	wait := resource.StateChangeConf{
   415  		Pending:    []string{"PENDING", "RUNNING"},
   416  		Target:     []string{"DONE"},
   417  		Timeout:    10 * time.Minute,
   418  		MinTimeout: 3 * time.Second,
   419  		Refresh: func() (interface{}, string, error) {
   420  			log.Printf("[DEBUG] Checking if GKE cluster %s is deleted", clusterName)
   421  			resp, err := config.clientContainer.Projects.Zones.Operations.Get(
   422  				config.Project, zoneName, op.Name).Do()
   423  			log.Printf("[DEBUG] Progress of deleting GKE cluster %s: %s",
   424  				clusterName, resp.Status)
   425  			return resp, resp.Status, err
   426  		},
   427  	}
   428  
   429  	_, err = wait.WaitForState()
   430  	if err != nil {
   431  		return err
   432  	}
   433  
   434  	log.Printf("[INFO] GKE cluster %s has been deleted", d.Id())
   435  
   436  	d.SetId("")
   437  
   438  	return nil
   439  }
   440  
   441  func flattenClusterNodeConfig(c *container.NodeConfig) []map[string]interface{} {
   442  	config := []map[string]interface{}{
   443  		map[string]interface{}{
   444  			"machine_type": c.MachineType,
   445  			"disk_size_gb": c.DiskSizeGb,
   446  		},
   447  	}
   448  
   449  	if len(c.OauthScopes) > 0 {
   450  		config[0]["oauth_scopes"] = c.OauthScopes
   451  	}
   452  
   453  	return config
   454  }