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