2016-12-18 15:23:29 +01:00
package azurerm
import (
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/arm/redis"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/jen20/riviera/azure"
)
func resourceArmRedisCache ( ) * schema . Resource {
return & schema . Resource {
Create : resourceArmRedisCacheCreate ,
Read : resourceArmRedisCacheRead ,
Update : resourceArmRedisCacheUpdate ,
Delete : resourceArmRedisCacheDelete ,
Schema : map [ string ] * schema . Schema {
"name" : {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
"location" : {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
StateFunc : azureRMNormalizeLocation ,
} ,
"resource_group_name" : {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
"capacity" : {
Type : schema . TypeInt ,
Required : true ,
} ,
"family" : {
2017-06-01 13:46:36 +02:00
Type : schema . TypeString ,
Required : true ,
ValidateFunc : validateRedisFamily ,
DiffSuppressFunc : ignoreCaseDiffSuppressFunc ,
2016-12-18 15:23:29 +01:00
} ,
"sku_name" : {
2017-06-01 13:46:36 +02:00
Type : schema . TypeString ,
Required : true ,
ValidateFunc : validateRedisSku ,
DiffSuppressFunc : ignoreCaseDiffSuppressFunc ,
2016-12-18 15:23:29 +01:00
} ,
"shard_count" : {
Type : schema . TypeInt ,
Optional : true ,
} ,
"enable_non_ssl_port" : {
Type : schema . TypeBool ,
Default : false ,
Optional : true ,
} ,
"redis_configuration" : {
Type : schema . TypeList ,
Required : true ,
MaxItems : 1 ,
Elem : & schema . Resource {
Schema : map [ string ] * schema . Schema {
"maxclients" : {
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
} ,
"maxmemory_delta" : {
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
} ,
"maxmemory_reserved" : {
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
} ,
"maxmemory_policy" : {
Type : schema . TypeString ,
Optional : true ,
Default : "volatile-lru" ,
ValidateFunc : validateRedisMaxMemoryPolicy ,
} ,
} ,
} ,
} ,
"hostname" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"port" : {
Type : schema . TypeInt ,
Computed : true ,
} ,
"ssl_port" : {
Type : schema . TypeInt ,
Computed : true ,
} ,
"primary_access_key" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"secondary_access_key" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"tags" : tagsSchema ( ) ,
} ,
}
}
func resourceArmRedisCacheCreate ( d * schema . ResourceData , meta interface { } ) error {
client := meta . ( * ArmClient ) . redisClient
log . Printf ( "[INFO] preparing arguments for Azure ARM Redis Cache creation." )
name := d . Get ( "name" ) . ( string )
location := d . Get ( "location" ) . ( string )
resGroup := d . Get ( "resource_group_name" ) . ( string )
enableNonSSLPort := d . Get ( "enable_non_ssl_port" ) . ( bool )
capacity := int32 ( d . Get ( "capacity" ) . ( int ) )
family := redis . SkuFamily ( d . Get ( "family" ) . ( string ) )
sku := redis . SkuName ( d . Get ( "sku_name" ) . ( string ) )
tags := d . Get ( "tags" ) . ( map [ string ] interface { } )
expandedTags := expandTags ( tags )
parameters := redis . CreateParameters {
Name : & name ,
Location : & location ,
CreateProperties : & redis . CreateProperties {
EnableNonSslPort : & enableNonSSLPort ,
Sku : & redis . Sku {
Capacity : & capacity ,
Family : family ,
Name : sku ,
} ,
RedisConfiguration : expandRedisConfiguration ( d ) ,
} ,
Tags : expandedTags ,
}
if v , ok := d . GetOk ( "shard_count" ) ; ok {
shardCount := int32 ( v . ( int ) )
parameters . ShardCount = & shardCount
}
2017-06-01 11:18:22 +02:00
_ , error := client . Create ( resGroup , name , parameters , make ( chan struct { } ) )
err := <- error
2016-12-18 15:23:29 +01:00
if err != nil {
return err
}
read , err := client . Get ( resGroup , name )
if err != nil {
return err
}
if read . ID == nil {
return fmt . Errorf ( "Cannot read Redis Instance %s (resource group %s) ID" , name , resGroup )
}
log . Printf ( "[DEBUG] Waiting for Redis Instance (%s) to become available" , d . Get ( "name" ) )
stateConf := & resource . StateChangeConf {
Pending : [ ] string { "Updating" , "Creating" } ,
Target : [ ] string { "Succeeded" } ,
Refresh : redisStateRefreshFunc ( client , resGroup , name ) ,
Timeout : 60 * time . Minute ,
MinTimeout : 15 * time . Second ,
}
if _ , err := stateConf . WaitForState ( ) ; err != nil {
return fmt . Errorf ( "Error waiting for Redis Instance (%s) to become available: %s" , d . Get ( "name" ) , err )
}
d . SetId ( * read . ID )
return resourceArmRedisCacheRead ( d , meta )
}
func resourceArmRedisCacheUpdate ( d * schema . ResourceData , meta interface { } ) error {
client := meta . ( * ArmClient ) . redisClient
log . Printf ( "[INFO] preparing arguments for Azure ARM Redis Cache update." )
name := d . Get ( "name" ) . ( string )
resGroup := d . Get ( "resource_group_name" ) . ( string )
enableNonSSLPort := d . Get ( "enable_non_ssl_port" ) . ( bool )
capacity := int32 ( d . Get ( "capacity" ) . ( int ) )
family := redis . SkuFamily ( d . Get ( "family" ) . ( string ) )
sku := redis . SkuName ( d . Get ( "sku_name" ) . ( string ) )
tags := d . Get ( "tags" ) . ( map [ string ] interface { } )
expandedTags := expandTags ( tags )
parameters := redis . UpdateParameters {
UpdateProperties : & redis . UpdateProperties {
EnableNonSslPort : & enableNonSSLPort ,
Sku : & redis . Sku {
Capacity : & capacity ,
Family : family ,
Name : sku ,
} ,
Tags : expandedTags ,
} ,
}
if v , ok := d . GetOk ( "shard_count" ) ; ok {
if d . HasChange ( "shard_count" ) {
shardCount := int32 ( v . ( int ) )
parameters . ShardCount = & shardCount
}
}
if d . HasChange ( "redis_configuration" ) {
redisConfiguration := expandRedisConfiguration ( d )
parameters . RedisConfiguration = redisConfiguration
}
2017-02-23 17:14:55 +01:00
_ , err := client . Update ( resGroup , name , parameters )
2016-12-18 15:23:29 +01:00
if err != nil {
return err
}
read , err := client . Get ( resGroup , name )
if err != nil {
return err
}
if read . ID == nil {
return fmt . Errorf ( "Cannot read Redis Instance %s (resource group %s) ID" , name , resGroup )
}
log . Printf ( "[DEBUG] Waiting for Redis Instance (%s) to become available" , d . Get ( "name" ) )
stateConf := & resource . StateChangeConf {
Pending : [ ] string { "Updating" , "Creating" } ,
Target : [ ] string { "Succeeded" } ,
Refresh : redisStateRefreshFunc ( client , resGroup , name ) ,
Timeout : 60 * time . Minute ,
MinTimeout : 15 * time . Second ,
}
if _ , err := stateConf . WaitForState ( ) ; err != nil {
return fmt . Errorf ( "Error waiting for Redis Instance (%s) to become available: %s" , d . Get ( "name" ) , err )
}
d . SetId ( * read . ID )
return resourceArmRedisCacheRead ( d , meta )
}
func resourceArmRedisCacheRead ( d * schema . ResourceData , meta interface { } ) error {
client := meta . ( * ArmClient ) . redisClient
id , err := parseAzureResourceID ( d . Id ( ) )
if err != nil {
return err
}
resGroup := id . ResourceGroup
name := id . Path [ "Redis" ]
resp , err := client . Get ( resGroup , name )
2017-04-24 16:35:27 +02:00
// covers if the resource has been deleted outside of TF, but is still in the state
2016-12-18 15:23:29 +01:00
if resp . StatusCode == http . StatusNotFound {
d . SetId ( "" )
return nil
}
2017-04-24 12:38:47 +02:00
if err != nil {
return fmt . Errorf ( "Error making Read request on Azure Redis Cache %s: %s" , name , err )
}
2016-12-18 15:23:29 +01:00
keysResp , err := client . ListKeys ( resGroup , name )
if err != nil {
return fmt . Errorf ( "Error making ListKeys request on Azure Redis Cache %s: %s" , name , err )
}
d . Set ( "name" , name )
d . Set ( "resource_group_name" , resGroup )
d . Set ( "location" , azureRMNormalizeLocation ( * resp . Location ) )
d . Set ( "ssl_port" , resp . SslPort )
2017-04-14 10:49:49 +02:00
d . Set ( "hostname" , resp . HostName )
2016-12-18 15:23:29 +01:00
d . Set ( "port" , resp . Port )
d . Set ( "enable_non_ssl_port" , resp . EnableNonSslPort )
d . Set ( "capacity" , resp . Sku . Capacity )
d . Set ( "family" , resp . Sku . Family )
d . Set ( "sku_name" , resp . Sku . Name )
if resp . ShardCount != nil {
d . Set ( "shard_count" , resp . ShardCount )
}
redisConfiguration := flattenRedisConfiguration ( resp . RedisConfiguration )
d . Set ( "redis_configuration" , & redisConfiguration )
d . Set ( "primary_access_key" , keysResp . PrimaryKey )
d . Set ( "secondary_access_key" , keysResp . SecondaryKey )
flattenAndSetTags ( d , resp . Tags )
return nil
}
func resourceArmRedisCacheDelete ( d * schema . ResourceData , meta interface { } ) error {
redisClient := meta . ( * ArmClient ) . redisClient
id , err := parseAzureResourceID ( d . Id ( ) )
if err != nil {
return err
}
resGroup := id . ResourceGroup
name := id . Path [ "Redis" ]
2017-06-01 11:18:22 +02:00
deleteResp , error := redisClient . Delete ( resGroup , name , make ( chan struct { } ) )
resp := <- deleteResp
err = <- error
2016-12-18 15:23:29 +01:00
if resp . StatusCode != http . StatusOK {
return fmt . Errorf ( "Error issuing Azure ARM delete request of Redis Cache Instance '%s': %s" , name , err )
}
checkResp , _ := redisClient . Get ( resGroup , name )
if checkResp . StatusCode != http . StatusNotFound {
return fmt . Errorf ( "Error issuing Azure ARM delete request of Redis Cache Instance '%s': it still exists after deletion" , name )
}
return nil
}
2017-02-23 17:14:55 +01:00
func redisStateRefreshFunc ( client redis . GroupClient , resourceGroupName string , sgName string ) resource . StateRefreshFunc {
2016-12-18 15:23:29 +01:00
return func ( ) ( interface { } , string , error ) {
res , err := client . Get ( resourceGroupName , sgName )
if err != nil {
return nil , "" , fmt . Errorf ( "Error issuing read request in redisStateRefreshFunc to Azure ARM for Redis Cache Instance '%s' (RG: '%s'): %s" , sgName , resourceGroupName , err )
}
return res , * res . ProvisioningState , nil
}
}
func expandRedisConfiguration ( d * schema . ResourceData ) * map [ string ] * string {
configuration := d . Get ( "redis_configuration" ) . ( [ ] interface { } )
output := make ( map [ string ] * string )
if configuration == nil {
return & output
}
// TODO: can we use this to remove the below? \/
//config := configuration[0].(map[string]interface{})
for _ , v := range configuration {
config := v . ( map [ string ] interface { } )
maxClients := config [ "maxclients" ] . ( string )
if maxClients != "" {
output [ "maxclients" ] = azure . String ( maxClients )
}
maxMemoryDelta := config [ "maxmemory_delta" ] . ( string )
if maxMemoryDelta != "" {
output [ "maxmemory-delta" ] = azure . String ( maxMemoryDelta )
}
maxMemoryReserved := config [ "maxmemory_reserved" ] . ( string )
if maxMemoryReserved != "" {
output [ "maxmemory-reserved" ] = azure . String ( maxMemoryReserved )
}
maxMemoryPolicy := config [ "maxmemory_policy" ] . ( string )
if maxMemoryPolicy != "" {
output [ "maxmemory-policy" ] = azure . String ( maxMemoryPolicy )
}
}
return & output
}
func flattenRedisConfiguration ( configuration * map [ string ] * string ) map [ string ] * string {
redisConfiguration := make ( map [ string ] * string , len ( * configuration ) )
config := * configuration
redisConfiguration [ "maxclients" ] = config [ "maxclients" ]
redisConfiguration [ "maxmemory_delta" ] = config [ "maxmemory-delta" ]
redisConfiguration [ "maxmemory_reserved" ] = config [ "maxmemory-reserved" ]
redisConfiguration [ "maxmemory_policy" ] = config [ "maxmemory-policy" ]
return redisConfiguration
}
func validateRedisFamily ( v interface { } , k string ) ( ws [ ] string , errors [ ] error ) {
value := strings . ToLower ( v . ( string ) )
families := map [ string ] bool {
"c" : true ,
"p" : true ,
}
if ! families [ value ] {
errors = append ( errors , fmt . Errorf ( "Redis Family can only be C or P" ) )
}
return
}
func validateRedisMaxMemoryPolicy ( v interface { } , k string ) ( ws [ ] string , errors [ ] error ) {
value := strings . ToLower ( v . ( string ) )
families := map [ string ] bool {
"noeviction" : true ,
"allkeys-lru" : true ,
"volatile-lru" : true ,
"allkeys-random" : true ,
"volatile-random" : true ,
"volatile-ttl" : true ,
}
if ! families [ value ] {
errors = append ( errors , fmt . Errorf ( "Redis Max Memory Policy can only be 'noeviction' / 'allkeys-lru' / 'volatile-lru' / 'allkeys-random' / 'volatile-random' / 'volatile-ttl'" ) )
}
return
}
func validateRedisSku ( v interface { } , k string ) ( ws [ ] string , errors [ ] error ) {
value := strings . ToLower ( v . ( string ) )
skus := map [ string ] bool {
"basic" : true ,
"standard" : true ,
"premium" : true ,
}
if ! skus [ value ] {
errors = append ( errors , fmt . Errorf ( "Redis SKU can only be Basic, Standard or Premium" ) )
}
return
}