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