github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/azurerm/resource_arm_redis_cache.go (about)

     1  package azurerm
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"net/http"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/Azure/azure-sdk-for-go/arm/redis"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  	"github.com/jen20/riviera/azure"
    15  )
    16  
    17  func resourceArmRedisCache() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceArmRedisCacheCreate,
    20  		Read:   resourceArmRedisCacheRead,
    21  		Update: resourceArmRedisCacheUpdate,
    22  		Delete: resourceArmRedisCacheDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"name": {
    26  				Type:     schema.TypeString,
    27  				Required: true,
    28  				ForceNew: true,
    29  			},
    30  
    31  			"location": {
    32  				Type:      schema.TypeString,
    33  				Required:  true,
    34  				ForceNew:  true,
    35  				StateFunc: azureRMNormalizeLocation,
    36  			},
    37  
    38  			"resource_group_name": {
    39  				Type:     schema.TypeString,
    40  				Required: true,
    41  				ForceNew: true,
    42  			},
    43  
    44  			"capacity": {
    45  				Type:     schema.TypeInt,
    46  				Required: true,
    47  			},
    48  
    49  			"family": {
    50  				Type:             schema.TypeString,
    51  				Required:         true,
    52  				ValidateFunc:     validateRedisFamily,
    53  				DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
    54  			},
    55  
    56  			"sku_name": {
    57  				Type:             schema.TypeString,
    58  				Required:         true,
    59  				ValidateFunc:     validateRedisSku,
    60  				DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
    61  			},
    62  
    63  			"shard_count": {
    64  				Type:     schema.TypeInt,
    65  				Optional: true,
    66  			},
    67  
    68  			"enable_non_ssl_port": {
    69  				Type:     schema.TypeBool,
    70  				Default:  false,
    71  				Optional: true,
    72  			},
    73  
    74  			"redis_configuration": {
    75  				Type:     schema.TypeList,
    76  				Required: true,
    77  				MaxItems: 1,
    78  				Elem: &schema.Resource{
    79  					Schema: map[string]*schema.Schema{
    80  						"maxclients": {
    81  							Type:     schema.TypeString,
    82  							Optional: true,
    83  							Computed: true,
    84  						},
    85  
    86  						"maxmemory_delta": {
    87  							Type:     schema.TypeString,
    88  							Optional: true,
    89  							Computed: true,
    90  						},
    91  
    92  						"maxmemory_reserved": {
    93  							Type:     schema.TypeString,
    94  							Optional: true,
    95  							Computed: true,
    96  						},
    97  
    98  						"maxmemory_policy": {
    99  							Type:         schema.TypeString,
   100  							Optional:     true,
   101  							Default:      "volatile-lru",
   102  							ValidateFunc: validateRedisMaxMemoryPolicy,
   103  						},
   104  					},
   105  				},
   106  			},
   107  
   108  			"hostname": {
   109  				Type:     schema.TypeString,
   110  				Computed: true,
   111  			},
   112  
   113  			"port": {
   114  				Type:     schema.TypeInt,
   115  				Computed: true,
   116  			},
   117  
   118  			"ssl_port": {
   119  				Type:     schema.TypeInt,
   120  				Computed: true,
   121  			},
   122  
   123  			"primary_access_key": {
   124  				Type:     schema.TypeString,
   125  				Computed: true,
   126  			},
   127  
   128  			"secondary_access_key": {
   129  				Type:     schema.TypeString,
   130  				Computed: true,
   131  			},
   132  
   133  			"tags": tagsSchema(),
   134  		},
   135  	}
   136  }
   137  
   138  func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error {
   139  	client := meta.(*ArmClient).redisClient
   140  	log.Printf("[INFO] preparing arguments for Azure ARM Redis Cache creation.")
   141  
   142  	name := d.Get("name").(string)
   143  	location := d.Get("location").(string)
   144  	resGroup := d.Get("resource_group_name").(string)
   145  
   146  	enableNonSSLPort := d.Get("enable_non_ssl_port").(bool)
   147  
   148  	capacity := int32(d.Get("capacity").(int))
   149  	family := redis.SkuFamily(d.Get("family").(string))
   150  	sku := redis.SkuName(d.Get("sku_name").(string))
   151  
   152  	tags := d.Get("tags").(map[string]interface{})
   153  	expandedTags := expandTags(tags)
   154  
   155  	parameters := redis.CreateParameters{
   156  		Name:     &name,
   157  		Location: &location,
   158  		CreateProperties: &redis.CreateProperties{
   159  			EnableNonSslPort: &enableNonSSLPort,
   160  			Sku: &redis.Sku{
   161  				Capacity: &capacity,
   162  				Family:   family,
   163  				Name:     sku,
   164  			},
   165  			RedisConfiguration: expandRedisConfiguration(d),
   166  		},
   167  		Tags: expandedTags,
   168  	}
   169  
   170  	if v, ok := d.GetOk("shard_count"); ok {
   171  		shardCount := int32(v.(int))
   172  		parameters.ShardCount = &shardCount
   173  	}
   174  
   175  	_, error := client.Create(resGroup, name, parameters, make(chan struct{}))
   176  	err := <-error
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	read, err := client.Get(resGroup, name)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	if read.ID == nil {
   186  		return fmt.Errorf("Cannot read Redis Instance %s (resource group %s) ID", name, resGroup)
   187  	}
   188  
   189  	log.Printf("[DEBUG] Waiting for Redis Instance (%s) to become available", d.Get("name"))
   190  	stateConf := &resource.StateChangeConf{
   191  		Pending:    []string{"Updating", "Creating"},
   192  		Target:     []string{"Succeeded"},
   193  		Refresh:    redisStateRefreshFunc(client, resGroup, name),
   194  		Timeout:    60 * time.Minute,
   195  		MinTimeout: 15 * time.Second,
   196  	}
   197  	if _, err := stateConf.WaitForState(); err != nil {
   198  		return fmt.Errorf("Error waiting for Redis Instance (%s) to become available: %s", d.Get("name"), err)
   199  	}
   200  
   201  	d.SetId(*read.ID)
   202  
   203  	return resourceArmRedisCacheRead(d, meta)
   204  }
   205  
   206  func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error {
   207  	client := meta.(*ArmClient).redisClient
   208  	log.Printf("[INFO] preparing arguments for Azure ARM Redis Cache update.")
   209  
   210  	name := d.Get("name").(string)
   211  	resGroup := d.Get("resource_group_name").(string)
   212  
   213  	enableNonSSLPort := d.Get("enable_non_ssl_port").(bool)
   214  
   215  	capacity := int32(d.Get("capacity").(int))
   216  	family := redis.SkuFamily(d.Get("family").(string))
   217  	sku := redis.SkuName(d.Get("sku_name").(string))
   218  
   219  	tags := d.Get("tags").(map[string]interface{})
   220  	expandedTags := expandTags(tags)
   221  
   222  	parameters := redis.UpdateParameters{
   223  		UpdateProperties: &redis.UpdateProperties{
   224  			EnableNonSslPort: &enableNonSSLPort,
   225  			Sku: &redis.Sku{
   226  				Capacity: &capacity,
   227  				Family:   family,
   228  				Name:     sku,
   229  			},
   230  			Tags: expandedTags,
   231  		},
   232  	}
   233  
   234  	if v, ok := d.GetOk("shard_count"); ok {
   235  		if d.HasChange("shard_count") {
   236  			shardCount := int32(v.(int))
   237  			parameters.ShardCount = &shardCount
   238  		}
   239  	}
   240  
   241  	if d.HasChange("redis_configuration") {
   242  		redisConfiguration := expandRedisConfiguration(d)
   243  		parameters.RedisConfiguration = redisConfiguration
   244  	}
   245  
   246  	_, err := client.Update(resGroup, name, parameters)
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	read, err := client.Get(resGroup, name)
   252  	if err != nil {
   253  		return err
   254  	}
   255  	if read.ID == nil {
   256  		return fmt.Errorf("Cannot read Redis Instance %s (resource group %s) ID", name, resGroup)
   257  	}
   258  
   259  	log.Printf("[DEBUG] Waiting for Redis Instance (%s) to become available", d.Get("name"))
   260  	stateConf := &resource.StateChangeConf{
   261  		Pending:    []string{"Updating", "Creating"},
   262  		Target:     []string{"Succeeded"},
   263  		Refresh:    redisStateRefreshFunc(client, resGroup, name),
   264  		Timeout:    60 * time.Minute,
   265  		MinTimeout: 15 * time.Second,
   266  	}
   267  	if _, err := stateConf.WaitForState(); err != nil {
   268  		return fmt.Errorf("Error waiting for Redis Instance (%s) to become available: %s", d.Get("name"), err)
   269  	}
   270  
   271  	d.SetId(*read.ID)
   272  
   273  	return resourceArmRedisCacheRead(d, meta)
   274  }
   275  
   276  func resourceArmRedisCacheRead(d *schema.ResourceData, meta interface{}) error {
   277  	client := meta.(*ArmClient).redisClient
   278  
   279  	id, err := parseAzureResourceID(d.Id())
   280  	if err != nil {
   281  		return err
   282  	}
   283  	resGroup := id.ResourceGroup
   284  	name := id.Path["Redis"]
   285  
   286  	resp, err := client.Get(resGroup, name)
   287  
   288  	// covers if the resource has been deleted outside of TF, but is still in the state
   289  	if resp.StatusCode == http.StatusNotFound {
   290  		d.SetId("")
   291  		return nil
   292  	}
   293  
   294  	if err != nil {
   295  		return fmt.Errorf("Error making Read request on Azure Redis Cache %s: %s", name, err)
   296  	}
   297  
   298  	keysResp, err := client.ListKeys(resGroup, name)
   299  	if err != nil {
   300  		return fmt.Errorf("Error making ListKeys request on Azure Redis Cache %s: %s", name, err)
   301  	}
   302  
   303  	d.Set("name", name)
   304  	d.Set("resource_group_name", resGroup)
   305  	d.Set("location", azureRMNormalizeLocation(*resp.Location))
   306  	d.Set("ssl_port", resp.SslPort)
   307  	d.Set("hostname", resp.HostName)
   308  	d.Set("port", resp.Port)
   309  	d.Set("enable_non_ssl_port", resp.EnableNonSslPort)
   310  	d.Set("capacity", resp.Sku.Capacity)
   311  	d.Set("family", resp.Sku.Family)
   312  	d.Set("sku_name", resp.Sku.Name)
   313  
   314  	if resp.ShardCount != nil {
   315  		d.Set("shard_count", resp.ShardCount)
   316  	}
   317  
   318  	redisConfiguration := flattenRedisConfiguration(resp.RedisConfiguration)
   319  	d.Set("redis_configuration", &redisConfiguration)
   320  
   321  	d.Set("primary_access_key", keysResp.PrimaryKey)
   322  	d.Set("secondary_access_key", keysResp.SecondaryKey)
   323  
   324  	flattenAndSetTags(d, resp.Tags)
   325  
   326  	return nil
   327  }
   328  
   329  func resourceArmRedisCacheDelete(d *schema.ResourceData, meta interface{}) error {
   330  	redisClient := meta.(*ArmClient).redisClient
   331  
   332  	id, err := parseAzureResourceID(d.Id())
   333  	if err != nil {
   334  		return err
   335  	}
   336  	resGroup := id.ResourceGroup
   337  	name := id.Path["Redis"]
   338  
   339  	deleteResp, error := redisClient.Delete(resGroup, name, make(chan struct{}))
   340  	resp := <-deleteResp
   341  	err = <-error
   342  
   343  	if resp.StatusCode != http.StatusOK {
   344  		return fmt.Errorf("Error issuing Azure ARM delete request of Redis Cache Instance '%s': %s", name, err)
   345  	}
   346  
   347  	checkResp, _ := redisClient.Get(resGroup, name)
   348  	if checkResp.StatusCode != http.StatusNotFound {
   349  		return fmt.Errorf("Error issuing Azure ARM delete request of Redis Cache Instance '%s': it still exists after deletion", name)
   350  	}
   351  
   352  	return nil
   353  }
   354  
   355  func redisStateRefreshFunc(client redis.GroupClient, resourceGroupName string, sgName string) resource.StateRefreshFunc {
   356  	return func() (interface{}, string, error) {
   357  		res, err := client.Get(resourceGroupName, sgName)
   358  		if err != nil {
   359  			return nil, "", fmt.Errorf("Error issuing read request in redisStateRefreshFunc to Azure ARM for Redis Cache Instance '%s' (RG: '%s'): %s", sgName, resourceGroupName, err)
   360  		}
   361  
   362  		return res, *res.ProvisioningState, nil
   363  	}
   364  }
   365  
   366  func expandRedisConfiguration(d *schema.ResourceData) *map[string]*string {
   367  	configuration := d.Get("redis_configuration").([]interface{})
   368  
   369  	output := make(map[string]*string)
   370  
   371  	if configuration == nil {
   372  		return &output
   373  	}
   374  
   375  	// TODO: can we use this to remove the below? \/
   376  	//config := configuration[0].(map[string]interface{})
   377  
   378  	for _, v := range configuration {
   379  		config := v.(map[string]interface{})
   380  
   381  		maxClients := config["maxclients"].(string)
   382  		if maxClients != "" {
   383  			output["maxclients"] = azure.String(maxClients)
   384  		}
   385  
   386  		maxMemoryDelta := config["maxmemory_delta"].(string)
   387  		if maxMemoryDelta != "" {
   388  			output["maxmemory-delta"] = azure.String(maxMemoryDelta)
   389  		}
   390  
   391  		maxMemoryReserved := config["maxmemory_reserved"].(string)
   392  		if maxMemoryReserved != "" {
   393  			output["maxmemory-reserved"] = azure.String(maxMemoryReserved)
   394  		}
   395  
   396  		maxMemoryPolicy := config["maxmemory_policy"].(string)
   397  		if maxMemoryPolicy != "" {
   398  			output["maxmemory-policy"] = azure.String(maxMemoryPolicy)
   399  		}
   400  	}
   401  
   402  	return &output
   403  }
   404  
   405  func flattenRedisConfiguration(configuration *map[string]*string) map[string]*string {
   406  	redisConfiguration := make(map[string]*string, len(*configuration))
   407  	config := *configuration
   408  
   409  	redisConfiguration["maxclients"] = config["maxclients"]
   410  	redisConfiguration["maxmemory_delta"] = config["maxmemory-delta"]
   411  	redisConfiguration["maxmemory_reserved"] = config["maxmemory-reserved"]
   412  	redisConfiguration["maxmemory_policy"] = config["maxmemory-policy"]
   413  
   414  	return redisConfiguration
   415  }
   416  
   417  func validateRedisFamily(v interface{}, k string) (ws []string, errors []error) {
   418  	value := strings.ToLower(v.(string))
   419  	families := map[string]bool{
   420  		"c": true,
   421  		"p": true,
   422  	}
   423  
   424  	if !families[value] {
   425  		errors = append(errors, fmt.Errorf("Redis Family can only be C or P"))
   426  	}
   427  	return
   428  }
   429  
   430  func validateRedisMaxMemoryPolicy(v interface{}, k string) (ws []string, errors []error) {
   431  	value := strings.ToLower(v.(string))
   432  	families := map[string]bool{
   433  		"noeviction":      true,
   434  		"allkeys-lru":     true,
   435  		"volatile-lru":    true,
   436  		"allkeys-random":  true,
   437  		"volatile-random": true,
   438  		"volatile-ttl":    true,
   439  	}
   440  
   441  	if !families[value] {
   442  		errors = append(errors, fmt.Errorf("Redis Max Memory Policy can only be 'noeviction' / 'allkeys-lru' / 'volatile-lru' / 'allkeys-random' / 'volatile-random' / 'volatile-ttl'"))
   443  	}
   444  
   445  	return
   446  }
   447  
   448  func validateRedisSku(v interface{}, k string) (ws []string, errors []error) {
   449  	value := strings.ToLower(v.(string))
   450  	skus := map[string]bool{
   451  		"basic":    true,
   452  		"standard": true,
   453  		"premium":  true,
   454  	}
   455  
   456  	if !skus[value] {
   457  		errors = append(errors, fmt.Errorf("Redis SKU can only be Basic, Standard or Premium"))
   458  	}
   459  	return
   460  }