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  }