github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/alicloud/resource_alicloud_db_instance.go (about) 1 package alicloud 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "github.com/denverdino/aliyungo/common" 8 "github.com/denverdino/aliyungo/rds" 9 "github.com/hashicorp/terraform/helper/hashcode" 10 "github.com/hashicorp/terraform/helper/resource" 11 "github.com/hashicorp/terraform/helper/schema" 12 "log" 13 "strconv" 14 "strings" 15 "time" 16 ) 17 18 func resourceAlicloudDBInstance() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceAlicloudDBInstanceCreate, 21 Read: resourceAlicloudDBInstanceRead, 22 Update: resourceAlicloudDBInstanceUpdate, 23 Delete: resourceAlicloudDBInstanceDelete, 24 25 Schema: map[string]*schema.Schema{ 26 "engine": &schema.Schema{ 27 Type: schema.TypeString, 28 ValidateFunc: validateAllowedStringValue([]string{"MySQL", "SQLServer", "PostgreSQL", "PPAS"}), 29 ForceNew: true, 30 Required: true, 31 }, 32 "engine_version": &schema.Schema{ 33 Type: schema.TypeString, 34 ValidateFunc: validateAllowedStringValue([]string{"5.5", "5.6", "5.7", "2008r2", "2012", "9.4", "9.3"}), 35 ForceNew: true, 36 Required: true, 37 }, 38 "db_instance_class": &schema.Schema{ 39 Type: schema.TypeString, 40 Required: true, 41 }, 42 "db_instance_storage": &schema.Schema{ 43 Type: schema.TypeInt, 44 Required: true, 45 }, 46 47 "instance_charge_type": &schema.Schema{ 48 Type: schema.TypeString, 49 ValidateFunc: validateAllowedStringValue([]string{string(rds.Postpaid), string(rds.Prepaid)}), 50 Optional: true, 51 ForceNew: true, 52 Default: rds.Postpaid, 53 }, 54 "period": &schema.Schema{ 55 Type: schema.TypeInt, 56 ValidateFunc: validateAllowedIntValue([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 24, 36}), 57 Optional: true, 58 ForceNew: true, 59 Default: 1, 60 }, 61 62 "zone_id": &schema.Schema{ 63 Type: schema.TypeString, 64 Optional: true, 65 Computed: true, 66 }, 67 "multi_az": &schema.Schema{ 68 Type: schema.TypeBool, 69 Optional: true, 70 ForceNew: true, 71 }, 72 "db_instance_net_type": &schema.Schema{ 73 Type: schema.TypeString, 74 ValidateFunc: validateAllowedStringValue([]string{string(common.Internet), string(common.Intranet)}), 75 Optional: true, 76 }, 77 "allocate_public_connection": &schema.Schema{ 78 Type: schema.TypeBool, 79 Optional: true, 80 Default: false, 81 }, 82 83 "instance_network_type": &schema.Schema{ 84 Type: schema.TypeString, 85 ValidateFunc: validateAllowedStringValue([]string{string(common.VPC), string(common.Classic)}), 86 Optional: true, 87 Computed: true, 88 }, 89 "vswitch_id": &schema.Schema{ 90 Type: schema.TypeString, 91 ForceNew: true, 92 Optional: true, 93 }, 94 95 "master_user_name": &schema.Schema{ 96 Type: schema.TypeString, 97 ForceNew: true, 98 Optional: true, 99 }, 100 "master_user_password": &schema.Schema{ 101 Type: schema.TypeString, 102 ForceNew: true, 103 Optional: true, 104 Sensitive: true, 105 }, 106 107 "preferred_backup_period": &schema.Schema{ 108 Type: schema.TypeList, 109 Elem: &schema.Schema{Type: schema.TypeString}, 110 // terraform does not support ValidateFunc of TypeList attr 111 // ValidateFunc: validateAllowedStringValue([]string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}), 112 Optional: true, 113 }, 114 "preferred_backup_time": &schema.Schema{ 115 Type: schema.TypeString, 116 ValidateFunc: validateAllowedStringValue(rds.BACKUP_TIME), 117 Optional: true, 118 }, 119 "backup_retention_period": &schema.Schema{ 120 Type: schema.TypeInt, 121 ValidateFunc: validateIntegerInRange(7, 730), 122 Optional: true, 123 }, 124 125 "security_ips": &schema.Schema{ 126 Type: schema.TypeList, 127 Elem: &schema.Schema{Type: schema.TypeString}, 128 Computed: true, 129 Optional: true, 130 }, 131 132 "port": &schema.Schema{ 133 Type: schema.TypeString, 134 Computed: true, 135 }, 136 137 "connections": &schema.Schema{ 138 Type: schema.TypeList, 139 Elem: &schema.Resource{ 140 Schema: map[string]*schema.Schema{ 141 "connection_string": &schema.Schema{ 142 Type: schema.TypeString, 143 Required: true, 144 }, 145 "ip_type": &schema.Schema{ 146 Type: schema.TypeString, 147 Required: true, 148 }, 149 "ip_address": &schema.Schema{ 150 Type: schema.TypeString, 151 Optional: true, 152 }, 153 }, 154 }, 155 Computed: true, 156 }, 157 158 "db_mappings": &schema.Schema{ 159 Type: schema.TypeSet, 160 Elem: &schema.Resource{ 161 Schema: map[string]*schema.Schema{ 162 "db_name": &schema.Schema{ 163 Type: schema.TypeString, 164 Required: true, 165 }, 166 "character_set_name": &schema.Schema{ 167 Type: schema.TypeString, 168 ValidateFunc: validateAllowedStringValue(rds.CHARACTER_SET_NAME), 169 Required: true, 170 }, 171 "db_description": &schema.Schema{ 172 Type: schema.TypeString, 173 Optional: true, 174 }, 175 }, 176 }, 177 Optional: true, 178 Set: resourceAlicloudDatabaseHash, 179 }, 180 }, 181 } 182 } 183 184 func resourceAlicloudDatabaseHash(v interface{}) int { 185 var buf bytes.Buffer 186 m := v.(map[string]interface{}) 187 buf.WriteString(fmt.Sprintf("%s-", m["db_name"].(string))) 188 buf.WriteString(fmt.Sprintf("%s-", m["character_set_name"].(string))) 189 buf.WriteString(fmt.Sprintf("%s-", m["db_description"].(string))) 190 191 return hashcode.String(buf.String()) 192 } 193 194 func resourceAlicloudDBInstanceCreate(d *schema.ResourceData, meta interface{}) error { 195 client := meta.(*AliyunClient) 196 conn := client.rdsconn 197 198 args, err := buildDBCreateOrderArgs(d, meta) 199 if err != nil { 200 return err 201 } 202 203 resp, err := conn.CreateOrder(args) 204 205 if err != nil { 206 return fmt.Errorf("Error creating Alicloud db instance: %#v", err) 207 } 208 209 instanceId := resp.DBInstanceId 210 if instanceId == "" { 211 return fmt.Errorf("Error get Alicloud db instance id") 212 } 213 214 d.SetId(instanceId) 215 d.Set("instance_charge_type", d.Get("instance_charge_type")) 216 d.Set("period", d.Get("period")) 217 d.Set("period_type", d.Get("period_type")) 218 219 // wait instance status change from Creating to running 220 if err := conn.WaitForInstance(d.Id(), rds.Running, defaultLongTimeout); err != nil { 221 return fmt.Errorf("WaitForInstance %s got error: %#v", rds.Running, err) 222 } 223 224 if err := modifySecurityIps(d.Id(), d.Get("security_ips"), meta); err != nil { 225 return err 226 } 227 228 masterUserName := d.Get("master_user_name").(string) 229 masterUserPwd := d.Get("master_user_password").(string) 230 if masterUserName != "" && masterUserPwd != "" { 231 if err := client.CreateAccountByInfo(d.Id(), masterUserName, masterUserPwd); err != nil { 232 return fmt.Errorf("Create db account %s error: %v", masterUserName, err) 233 } 234 } 235 236 if d.Get("allocate_public_connection").(bool) { 237 if err := client.AllocateDBPublicConnection(d.Id(), DB_DEFAULT_CONNECT_PORT); err != nil { 238 return fmt.Errorf("Allocate public connection error: %v", err) 239 } 240 } 241 242 return resourceAlicloudDBInstanceUpdate(d, meta) 243 } 244 245 func modifySecurityIps(id string, ips interface{}, meta interface{}) error { 246 client := meta.(*AliyunClient) 247 ipList := expandStringList(ips.([]interface{})) 248 249 ipstr := strings.Join(ipList[:], COMMA_SEPARATED) 250 // default disable connect from outside 251 if ipstr == "" { 252 ipstr = LOCAL_HOST_IP 253 } 254 255 if err := client.ModifyDBSecurityIps(id, ipstr); err != nil { 256 return fmt.Errorf("Error modify security ips %s: %#v", ipstr, err) 257 } 258 return nil 259 } 260 261 func resourceAlicloudDBInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 262 client := meta.(*AliyunClient) 263 conn := client.rdsconn 264 d.Partial(true) 265 266 if d.HasChange("db_mappings") { 267 o, n := d.GetChange("db_mappings") 268 os := o.(*schema.Set) 269 ns := n.(*schema.Set) 270 271 var allDbs []string 272 remove := os.Difference(ns).List() 273 add := ns.Difference(os).List() 274 275 if len(remove) > 0 && len(add) > 0 { 276 return fmt.Errorf("Failure modify database, we neither support create and delete database simultaneous nor modify database attributes.") 277 } 278 279 if len(remove) > 0 { 280 for _, db := range remove { 281 dbm, _ := db.(map[string]interface{}) 282 if err := conn.DeleteDatabase(d.Id(), dbm["db_name"].(string)); err != nil { 283 return fmt.Errorf("Failure delete database %s: %#v", dbm["db_name"].(string), err) 284 } 285 } 286 } 287 288 if len(add) > 0 { 289 for _, db := range add { 290 dbm, _ := db.(map[string]interface{}) 291 dbName := dbm["db_name"].(string) 292 allDbs = append(allDbs, dbName) 293 294 if err := client.CreateDatabaseByInfo(d.Id(), dbName, dbm["character_set_name"].(string), dbm["db_description"].(string)); err != nil { 295 return fmt.Errorf("Failure create database %s: %#v", dbName, err) 296 } 297 298 } 299 } 300 301 if err := conn.WaitForAllDatabase(d.Id(), allDbs, rds.Running, 600); err != nil { 302 return fmt.Errorf("Failure create database %#v", err) 303 } 304 305 if user := d.Get("master_user_name").(string); user != "" { 306 for _, dbName := range allDbs { 307 if err := client.GrantDBPrivilege2Account(d.Id(), user, dbName); err != nil { 308 return fmt.Errorf("Failed to grant database %s readwrite privilege to account %s: %#v", dbName, user, err) 309 } 310 } 311 } 312 313 d.SetPartial("db_mappings") 314 } 315 316 if d.HasChange("preferred_backup_period") || d.HasChange("preferred_backup_time") || d.HasChange("backup_retention_period") { 317 period := d.Get("preferred_backup_period").([]interface{}) 318 periodList := expandStringList(period) 319 time := d.Get("preferred_backup_time").(string) 320 retention := d.Get("backup_retention_period").(int) 321 322 if time == "" || retention == 0 || len(periodList) < 1 { 323 return fmt.Errorf("Both backup_time, backup_period and retention_period are required to set backup policy.") 324 } 325 326 ps := strings.Join(periodList[:], COMMA_SEPARATED) 327 328 if err := client.ConfigDBBackup(d.Id(), time, ps, retention); err != nil { 329 return fmt.Errorf("Error set backup policy: %#v", err) 330 } 331 d.SetPartial("preferred_backup_period") 332 d.SetPartial("preferred_backup_time") 333 d.SetPartial("backup_retention_period") 334 } 335 336 if d.HasChange("security_ips") { 337 if err := modifySecurityIps(d.Id(), d.Get("security_ips"), meta); err != nil { 338 return err 339 } 340 d.SetPartial("security_ips") 341 } 342 343 if d.HasChange("db_instance_class") || d.HasChange("db_instance_storage") { 344 co, cn := d.GetChange("db_instance_class") 345 so, sn := d.GetChange("db_instance_storage") 346 classOld := co.(string) 347 classNew := cn.(string) 348 storageOld := so.(int) 349 storageNew := sn.(int) 350 351 // update except the first time, because we will do it in create function 352 if classOld != "" && storageOld != 0 { 353 chargeType := d.Get("instance_charge_type").(string) 354 if chargeType == string(rds.Prepaid) { 355 return fmt.Errorf("Prepaid db instance does not support modify db_instance_class or db_instance_storage") 356 } 357 358 if err := client.ModifyDBClassStorage(d.Id(), classNew, strconv.Itoa(storageNew)); err != nil { 359 return fmt.Errorf("Error modify db instance class or storage error: %#v", err) 360 } 361 } 362 } 363 364 d.Partial(false) 365 return resourceAlicloudDBInstanceRead(d, meta) 366 } 367 368 func resourceAlicloudDBInstanceRead(d *schema.ResourceData, meta interface{}) error { 369 client := meta.(*AliyunClient) 370 conn := client.rdsconn 371 372 instance, err := client.DescribeDBInstanceById(d.Id()) 373 if err != nil { 374 if notFoundError(err) { 375 d.SetId("") 376 return nil 377 } 378 return fmt.Errorf("Error Describe DB InstanceAttribute: %#v", err) 379 } 380 381 args := rds.DescribeDatabasesArgs{ 382 DBInstanceId: d.Id(), 383 } 384 385 resp, err := conn.DescribeDatabases(&args) 386 if err != nil { 387 return err 388 } 389 if resp.Databases.Database == nil { 390 d.SetId("") 391 return nil 392 } 393 394 d.Set("db_mappings", flattenDatabaseMappings(resp.Databases.Database)) 395 396 argn := rds.DescribeDBInstanceNetInfoArgs{ 397 DBInstanceId: d.Id(), 398 } 399 400 resn, err := conn.DescribeDBInstanceNetInfo(&argn) 401 if err != nil { 402 return err 403 } 404 d.Set("connections", flattenDBConnections(resn.DBInstanceNetInfos.DBInstanceNetInfo)) 405 406 ips, err := client.GetSecurityIps(d.Id()) 407 if err != nil { 408 log.Printf("Describe DB security ips error: %#v", err) 409 } 410 d.Set("security_ips", ips) 411 412 d.Set("engine", instance.Engine) 413 d.Set("engine_version", instance.EngineVersion) 414 d.Set("db_instance_class", instance.DBInstanceClass) 415 d.Set("port", instance.Port) 416 d.Set("db_instance_storage", instance.DBInstanceStorage) 417 d.Set("zone_id", instance.ZoneId) 418 d.Set("db_instance_net_type", instance.DBInstanceNetType) 419 d.Set("instance_network_type", instance.InstanceNetworkType) 420 421 return nil 422 } 423 424 func resourceAlicloudDBInstanceDelete(d *schema.ResourceData, meta interface{}) error { 425 conn := meta.(*AliyunClient).rdsconn 426 427 return resource.Retry(5*time.Minute, func() *resource.RetryError { 428 err := conn.DeleteInstance(d.Id()) 429 430 if err != nil { 431 return resource.RetryableError(fmt.Errorf("DB Instance in use - trying again while it is deleted.")) 432 } 433 434 args := &rds.DescribeDBInstancesArgs{ 435 DBInstanceId: d.Id(), 436 } 437 resp, err := conn.DescribeDBInstanceAttribute(args) 438 if err != nil { 439 return resource.NonRetryableError(err) 440 } else if len(resp.Items.DBInstanceAttribute) < 1 { 441 return nil 442 } 443 444 return resource.RetryableError(fmt.Errorf("DB in use - trying again while it is deleted.")) 445 }) 446 } 447 448 func buildDBCreateOrderArgs(d *schema.ResourceData, meta interface{}) (*rds.CreateOrderArgs, error) { 449 client := meta.(*AliyunClient) 450 args := &rds.CreateOrderArgs{ 451 RegionId: getRegion(d, meta), 452 // we does not expose this param to user, 453 // because create prepaid instance progress will be stopped when set auto_pay to false, 454 // then could not get instance info, cause timeout error 455 AutoPay: "true", 456 EngineVersion: d.Get("engine_version").(string), 457 Engine: rds.Engine(d.Get("engine").(string)), 458 DBInstanceStorage: d.Get("db_instance_storage").(int), 459 DBInstanceClass: d.Get("db_instance_class").(string), 460 Quantity: DEFAULT_INSTANCE_COUNT, 461 Resource: rds.DefaultResource, 462 } 463 464 bussStr, err := json.Marshal(DefaultBusinessInfo) 465 if err != nil { 466 return nil, fmt.Errorf("Failed to translate bussiness info %#v from json to string", DefaultBusinessInfo) 467 } 468 469 args.BusinessInfo = string(bussStr) 470 471 zoneId := d.Get("zone_id").(string) 472 args.ZoneId = zoneId 473 474 multiAZ := d.Get("multi_az").(bool) 475 if multiAZ { 476 if zoneId != "" { 477 return nil, fmt.Errorf("You cannot set the ZoneId parameter when the MultiAZ parameter is set to true") 478 } 479 izs, err := client.DescribeMultiIZByRegion() 480 if err != nil { 481 return nil, fmt.Errorf("Get multiAZ id error") 482 } 483 484 if len(izs) < 1 { 485 return nil, fmt.Errorf("Current region does not support MultiAZ.") 486 } 487 488 args.ZoneId = izs[0] 489 } 490 491 vswitchId := d.Get("vswitch_id").(string) 492 493 networkType := d.Get("instance_network_type").(string) 494 args.InstanceNetworkType = common.NetworkType(networkType) 495 496 if vswitchId != "" { 497 args.VSwitchId = vswitchId 498 499 // check InstanceNetworkType with vswitchId 500 if networkType == string(common.Classic) { 501 return nil, fmt.Errorf("When fill vswitchId, you shold set instance_network_type to VPC") 502 } else if networkType == "" { 503 args.InstanceNetworkType = common.VPC 504 } 505 506 // get vpcId 507 vpcId, err := client.GetVpcIdByVSwitchId(vswitchId) 508 509 if err != nil { 510 return nil, fmt.Errorf("VswitchId %s is not valid of current region", vswitchId) 511 } 512 // fill vpcId by vswitchId 513 args.VPCId = vpcId 514 515 // check vswitchId in zone 516 vsw, err := client.QueryVswitchById(vpcId, vswitchId) 517 if err != nil { 518 return nil, fmt.Errorf("VswitchId %s is not valid of current region", vswitchId) 519 } 520 521 if zoneId == "" { 522 args.ZoneId = vsw.ZoneId 523 } else if vsw.ZoneId != zoneId { 524 return nil, fmt.Errorf("VswitchId %s is not belong to the zone %s", vswitchId, zoneId) 525 } 526 } 527 528 if v := d.Get("db_instance_net_type").(string); v != "" { 529 args.DBInstanceNetType = common.NetType(v) 530 } 531 532 chargeType := d.Get("instance_charge_type").(string) 533 if chargeType != "" { 534 args.PayType = rds.DBPayType(chargeType) 535 } else { 536 args.PayType = rds.Postpaid 537 } 538 539 // if charge type is postpaid, the commodity code must set to bards 540 if chargeType == string(rds.Postpaid) { 541 args.CommodityCode = rds.Bards 542 } else { 543 args.CommodityCode = rds.Rds 544 } 545 546 period := d.Get("period").(int) 547 args.UsedTime, args.TimeType = TransformPeriod2Time(period, chargeType) 548 549 return args, nil 550 }