github.com/cbroglie/terraform@v0.7.0-rc3.0.20170410193827-735dfc416d46/builtin/providers/alicloud/resource_alicloud_instance.go (about)

     1  package alicloud
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"github.com/denverdino/aliyungo/common"
    10  	"github.com/denverdino/aliyungo/ecs"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  	"strings"
    13  )
    14  
    15  func resourceAliyunInstance() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceAliyunInstanceCreate,
    18  		Read:   resourceAliyunInstanceRead,
    19  		Update: resourceAliyunInstanceUpdate,
    20  		Delete: resourceAliyunInstanceDelete,
    21  
    22  		Schema: map[string]*schema.Schema{
    23  			"availability_zone": &schema.Schema{
    24  				Type:     schema.TypeString,
    25  				Optional: true,
    26  				ForceNew: true,
    27  				Computed: true,
    28  			},
    29  
    30  			"image_id": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  			},
    34  
    35  			"instance_type": &schema.Schema{
    36  				Type:     schema.TypeString,
    37  				Required: true,
    38  			},
    39  
    40  			"security_groups": &schema.Schema{
    41  				Type:     schema.TypeSet,
    42  				Elem:     &schema.Schema{Type: schema.TypeString},
    43  				Optional: true,
    44  			},
    45  
    46  			"allocate_public_ip": &schema.Schema{
    47  				Type:     schema.TypeBool,
    48  				Optional: true,
    49  				Default:  false,
    50  			},
    51  
    52  			"instance_name": &schema.Schema{
    53  				Type:         schema.TypeString,
    54  				Optional:     true,
    55  				Default:      "ECS-Instance",
    56  				ValidateFunc: validateInstanceName,
    57  			},
    58  
    59  			"description": &schema.Schema{
    60  				Type:         schema.TypeString,
    61  				Optional:     true,
    62  				ValidateFunc: validateInstanceDescription,
    63  			},
    64  
    65  			"internet_charge_type": &schema.Schema{
    66  				Type:         schema.TypeString,
    67  				Optional:     true,
    68  				ForceNew:     true,
    69  				ValidateFunc: validateInternetChargeType,
    70  			},
    71  			"internet_max_bandwidth_in": &schema.Schema{
    72  				Type:     schema.TypeString,
    73  				Optional: true,
    74  				ForceNew: true,
    75  			},
    76  			"internet_max_bandwidth_out": &schema.Schema{
    77  				Type:         schema.TypeInt,
    78  				Optional:     true,
    79  				ForceNew:     true,
    80  				ValidateFunc: validateInternetMaxBandWidthOut,
    81  			},
    82  			"host_name": &schema.Schema{
    83  				Type:     schema.TypeString,
    84  				Optional: true,
    85  				Computed: true,
    86  			},
    87  			"password": &schema.Schema{
    88  				Type:      schema.TypeString,
    89  				Optional:  true,
    90  				Sensitive: true,
    91  			},
    92  			"io_optimized": &schema.Schema{
    93  				Type:         schema.TypeString,
    94  				Required:     true,
    95  				ForceNew:     true,
    96  				ValidateFunc: validateIoOptimized,
    97  			},
    98  
    99  			"system_disk_category": &schema.Schema{
   100  				Type:     schema.TypeString,
   101  				Default:  "cloud",
   102  				Optional: true,
   103  				ForceNew: true,
   104  				ValidateFunc: validateAllowedStringValue([]string{
   105  					string(ecs.DiskCategoryCloud),
   106  					string(ecs.DiskCategoryCloudSSD),
   107  					string(ecs.DiskCategoryCloudEfficiency),
   108  					string(ecs.DiskCategoryEphemeralSSD),
   109  				}),
   110  			},
   111  			"system_disk_size": &schema.Schema{
   112  				Type:         schema.TypeInt,
   113  				Optional:     true,
   114  				Computed:     true,
   115  				ForceNew:     true,
   116  				ValidateFunc: validateIntegerInRange(40, 500),
   117  			},
   118  
   119  			//subnet_id and vswitch_id both exists, cause compatible old version, and aws habit.
   120  			"subnet_id": &schema.Schema{
   121  				Type:     schema.TypeString,
   122  				Optional: true,
   123  				ForceNew: true,
   124  				Computed: true, //add this schema cause subnet_id not used enter parameter, will different, so will be ForceNew
   125  			},
   126  
   127  			"vswitch_id": &schema.Schema{
   128  				Type:     schema.TypeString,
   129  				Optional: true,
   130  				ForceNew: true,
   131  			},
   132  
   133  			"instance_charge_type": &schema.Schema{
   134  				Type:         schema.TypeString,
   135  				Optional:     true,
   136  				ForceNew:     true,
   137  				ValidateFunc: validateInstanceChargeType,
   138  			},
   139  			"period": &schema.Schema{
   140  				Type:     schema.TypeInt,
   141  				Optional: true,
   142  				ForceNew: true,
   143  			},
   144  
   145  			"public_ip": &schema.Schema{
   146  				Type:     schema.TypeString,
   147  				Optional: true,
   148  				Computed: true,
   149  			},
   150  
   151  			"private_ip": &schema.Schema{
   152  				Type:     schema.TypeString,
   153  				Computed: true,
   154  			},
   155  
   156  			"status": &schema.Schema{
   157  				Type:     schema.TypeString,
   158  				Computed: true,
   159  			},
   160  
   161  			"user_data": &schema.Schema{
   162  				Type:     schema.TypeString,
   163  				Optional: true,
   164  				ForceNew: true,
   165  			},
   166  
   167  			"tags": tagsSchema(),
   168  		},
   169  	}
   170  }
   171  
   172  func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   173  	conn := meta.(*AliyunClient).ecsconn
   174  
   175  	// create postpaid instance by runInstances API
   176  	if v := d.Get("instance_charge_type").(string); v != string(common.PrePaid) {
   177  		return resourceAliyunRunInstance(d, meta)
   178  	}
   179  
   180  	args, err := buildAliyunInstanceArgs(d, meta)
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	instanceID, err := conn.CreateInstance(args)
   186  	if err != nil {
   187  		return fmt.Errorf("Error creating Aliyun ecs instance: %#v", err)
   188  	}
   189  
   190  	d.SetId(instanceID)
   191  
   192  	d.Set("password", d.Get("password"))
   193  	//d.Set("system_disk_category", d.Get("system_disk_category"))
   194  	//d.Set("system_disk_size", d.Get("system_disk_size"))
   195  
   196  	if d.Get("allocate_public_ip").(bool) {
   197  		_, err := conn.AllocatePublicIpAddress(d.Id())
   198  		if err != nil {
   199  			log.Printf("[DEBUG] AllocatePublicIpAddress for instance got error: %#v", err)
   200  		}
   201  	}
   202  
   203  	// after instance created, its status is pending,
   204  	// so we need to wait it become to stopped and then start it
   205  	if err := conn.WaitForInstance(d.Id(), ecs.Stopped, defaultTimeout); err != nil {
   206  		log.Printf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Stopped, err)
   207  	}
   208  
   209  	if err := conn.StartInstance(d.Id()); err != nil {
   210  		return fmt.Errorf("Start instance got error: %#v", err)
   211  	}
   212  
   213  	if err := conn.WaitForInstance(d.Id(), ecs.Running, defaultTimeout); err != nil {
   214  		log.Printf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Running, err)
   215  	}
   216  
   217  	return resourceAliyunInstanceUpdate(d, meta)
   218  }
   219  
   220  func resourceAliyunRunInstance(d *schema.ResourceData, meta interface{}) error {
   221  	conn := meta.(*AliyunClient).ecsconn
   222  	newConn := meta.(*AliyunClient).ecsNewconn
   223  
   224  	args, err := buildAliyunInstanceArgs(d, meta)
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	runArgs, err := buildAliyunRunInstancesArgs(d, meta)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	runArgs.CreateInstanceArgs = *args
   235  
   236  	// runInstances is support in version 2016-03-14
   237  	instanceIds, err := newConn.RunInstances(runArgs)
   238  
   239  	if err != nil {
   240  		return fmt.Errorf("Error creating Aliyun ecs instance: %#v", err)
   241  	}
   242  
   243  	d.SetId(instanceIds[0])
   244  
   245  	d.Set("password", d.Get("password"))
   246  	d.Set("system_disk_category", d.Get("system_disk_category"))
   247  	d.Set("system_disk_size", d.Get("system_disk_size"))
   248  
   249  	if d.Get("allocate_public_ip").(bool) {
   250  		_, err := conn.AllocatePublicIpAddress(d.Id())
   251  		if err != nil {
   252  			log.Printf("[DEBUG] AllocatePublicIpAddress for instance got error: %#v", err)
   253  		}
   254  	}
   255  
   256  	// after instance created, its status change from pending, starting to running
   257  	if err := conn.WaitForInstanceAsyn(d.Id(), ecs.Running, defaultTimeout); err != nil {
   258  		log.Printf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Running, err)
   259  	}
   260  
   261  	return resourceAliyunInstanceUpdate(d, meta)
   262  }
   263  
   264  func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error {
   265  	client := meta.(*AliyunClient)
   266  	conn := client.ecsconn
   267  
   268  	instance, err := client.QueryInstancesById(d.Id())
   269  
   270  	if err != nil {
   271  		if notFoundError(err) {
   272  			d.SetId("")
   273  			return nil
   274  		}
   275  		return fmt.Errorf("Error DescribeInstanceAttribute: %#v", err)
   276  	}
   277  
   278  	disk, diskErr := client.QueryInstanceSystemDisk(d.Id())
   279  
   280  	if diskErr != nil {
   281  		if notFoundError(diskErr) {
   282  			d.SetId("")
   283  			return nil
   284  		}
   285  		return fmt.Errorf("Error DescribeSystemDisk: %#v", err)
   286  	}
   287  
   288  	d.Set("instance_name", instance.InstanceName)
   289  	d.Set("description", instance.Description)
   290  	d.Set("status", instance.Status)
   291  	d.Set("availability_zone", instance.ZoneId)
   292  	d.Set("host_name", instance.HostName)
   293  	d.Set("image_id", instance.ImageId)
   294  	d.Set("instance_type", instance.InstanceType)
   295  	d.Set("system_disk_category", disk.Category)
   296  	d.Set("system_disk_size", disk.Size)
   297  
   298  	// In Classic network, internet_charge_type is valid in any case, and its default value is 'PayByBanwidth'.
   299  	// In VPC network, internet_charge_type is valid when instance has public ip, and its default value is 'PayByBanwidth'.
   300  	d.Set("internet_charge_type", instance.InternetChargeType)
   301  
   302  	if d.Get("allocate_public_ip").(bool) {
   303  		d.Set("public_ip", instance.PublicIpAddress.IpAddress[0])
   304  	}
   305  
   306  	if ecs.StringOrBool(instance.IoOptimized).Value {
   307  		d.Set("io_optimized", "optimized")
   308  	} else {
   309  		d.Set("io_optimized", "none")
   310  	}
   311  
   312  	if d.Get("subnet_id").(string) != "" || d.Get("vswitch_id").(string) != "" {
   313  		ipAddress := instance.VpcAttributes.PrivateIpAddress.IpAddress[0]
   314  		d.Set("private_ip", ipAddress)
   315  		d.Set("subnet_id", instance.VpcAttributes.VSwitchId)
   316  		d.Set("vswitch_id", instance.VpcAttributes.VSwitchId)
   317  	} else {
   318  		ipAddress := strings.Join(ecs.IpAddressSetType(instance.InnerIpAddress).IpAddress, ",")
   319  		d.Set("private_ip", ipAddress)
   320  	}
   321  
   322  	if d.Get("user_data").(string) != "" {
   323  		ud, err := conn.DescribeUserdata(&ecs.DescribeUserdataArgs{
   324  			RegionId:   getRegion(d, meta),
   325  			InstanceId: d.Id(),
   326  		})
   327  
   328  		if err != nil {
   329  			log.Printf("[ERROR] DescribeUserData for instance got error: %#v", err)
   330  		}
   331  		d.Set("user_data", userDataHashSum(ud.UserData))
   332  	}
   333  
   334  	tags, _, err := conn.DescribeTags(&ecs.DescribeTagsArgs{
   335  		RegionId:     getRegion(d, meta),
   336  		ResourceType: ecs.TagResourceInstance,
   337  		ResourceId:   d.Id(),
   338  	})
   339  
   340  	if err != nil {
   341  		log.Printf("[ERROR] DescribeTags for instance got error: %#v", err)
   342  	}
   343  	d.Set("tags", tagsToMap(tags))
   344  
   345  	return nil
   346  }
   347  
   348  func resourceAliyunInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   349  
   350  	client := meta.(*AliyunClient)
   351  	conn := client.ecsconn
   352  
   353  	d.Partial(true)
   354  
   355  	if err := setTags(client, ecs.TagResourceInstance, d); err != nil {
   356  		log.Printf("[DEBUG] Set tags for instance got error: %#v", err)
   357  		return fmt.Errorf("Set tags for instance got error: %#v", err)
   358  	} else {
   359  		d.SetPartial("tags")
   360  	}
   361  
   362  	attributeUpdate := false
   363  	args := &ecs.ModifyInstanceAttributeArgs{
   364  		InstanceId: d.Id(),
   365  	}
   366  
   367  	if d.HasChange("instance_name") {
   368  		log.Printf("[DEBUG] ModifyInstanceAttribute instance_name")
   369  		d.SetPartial("instance_name")
   370  		args.InstanceName = d.Get("instance_name").(string)
   371  
   372  		attributeUpdate = true
   373  	}
   374  
   375  	if d.HasChange("description") {
   376  		log.Printf("[DEBUG] ModifyInstanceAttribute description")
   377  		d.SetPartial("description")
   378  		args.Description = d.Get("description").(string)
   379  
   380  		attributeUpdate = true
   381  	}
   382  
   383  	if d.HasChange("host_name") {
   384  		log.Printf("[DEBUG] ModifyInstanceAttribute host_name")
   385  		d.SetPartial("host_name")
   386  		args.HostName = d.Get("host_name").(string)
   387  
   388  		attributeUpdate = true
   389  	}
   390  
   391  	passwordUpdate := false
   392  	if d.HasChange("password") {
   393  		log.Printf("[DEBUG] ModifyInstanceAttribute password")
   394  		d.SetPartial("password")
   395  		args.Password = d.Get("password").(string)
   396  
   397  		attributeUpdate = true
   398  		passwordUpdate = true
   399  	}
   400  
   401  	if attributeUpdate {
   402  		if err := conn.ModifyInstanceAttribute(args); err != nil {
   403  			return fmt.Errorf("Modify instance attribute got error: %#v", err)
   404  		}
   405  	}
   406  
   407  	if passwordUpdate {
   408  		if v, ok := d.GetOk("status"); ok && v.(string) != "" {
   409  			if ecs.InstanceStatus(d.Get("status").(string)) == ecs.Running {
   410  				log.Printf("[DEBUG] RebootInstance after change password")
   411  				if err := conn.RebootInstance(d.Id(), false); err != nil {
   412  					return fmt.Errorf("RebootInstance got error: %#v", err)
   413  				}
   414  
   415  				if err := conn.WaitForInstance(d.Id(), ecs.Running, defaultTimeout); err != nil {
   416  					return fmt.Errorf("WaitForInstance got error: %#v", err)
   417  				}
   418  			}
   419  		}
   420  	}
   421  
   422  	if d.HasChange("security_groups") {
   423  		o, n := d.GetChange("security_groups")
   424  		os := o.(*schema.Set)
   425  		ns := n.(*schema.Set)
   426  
   427  		rl := expandStringList(os.Difference(ns).List())
   428  		al := expandStringList(ns.Difference(os).List())
   429  
   430  		if len(al) > 0 {
   431  			err := client.JoinSecurityGroups(d.Id(), al)
   432  			if err != nil {
   433  				return err
   434  			}
   435  		}
   436  		if len(rl) > 0 {
   437  			err := client.LeaveSecurityGroups(d.Id(), rl)
   438  			if err != nil {
   439  				return err
   440  			}
   441  		}
   442  
   443  		d.SetPartial("security_groups")
   444  	}
   445  
   446  	d.Partial(false)
   447  	return resourceAliyunInstanceRead(d, meta)
   448  }
   449  
   450  func resourceAliyunInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   451  	client := meta.(*AliyunClient)
   452  	conn := client.ecsconn
   453  
   454  	instance, err := client.QueryInstancesById(d.Id())
   455  	if err != nil {
   456  		if notFoundError(err) {
   457  			return nil
   458  		}
   459  		return fmt.Errorf("Error DescribeInstanceAttribute: %#v", err)
   460  	}
   461  
   462  	if instance.Status != ecs.Stopped {
   463  		if err := conn.StopInstance(d.Id(), true); err != nil {
   464  			return err
   465  		}
   466  
   467  		if err := conn.WaitForInstance(d.Id(), ecs.Stopped, defaultTimeout); err != nil {
   468  			return err
   469  		}
   470  	}
   471  
   472  	if err := conn.DeleteInstance(d.Id()); err != nil {
   473  		return err
   474  	}
   475  
   476  	return nil
   477  }
   478  func buildAliyunRunInstancesArgs(d *schema.ResourceData, meta interface{}) (*ecs.RunInstanceArgs, error) {
   479  	args := &ecs.RunInstanceArgs{
   480  		MaxAmount: DEFAULT_INSTANCE_COUNT,
   481  		MinAmount: DEFAULT_INSTANCE_COUNT,
   482  	}
   483  
   484  	bussStr, err := json.Marshal(DefaultBusinessInfo)
   485  	if err != nil {
   486  		log.Printf("Failed to translate bussiness info %#v from json to string", DefaultBusinessInfo)
   487  	}
   488  
   489  	args.BusinessInfo = string(bussStr)
   490  
   491  	subnetValue := d.Get("subnet_id").(string)
   492  	vswitchValue := d.Get("vswitch_id").(string)
   493  	//networkValue := d.Get("instance_network_type").(string)
   494  
   495  	// because runInstance is not compatible with createInstance, force NetworkType value to classic
   496  	if subnetValue == "" && vswitchValue == "" {
   497  		args.NetworkType = string(ClassicNet)
   498  	}
   499  
   500  	return args, nil
   501  }
   502  
   503  func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.CreateInstanceArgs, error) {
   504  	client := meta.(*AliyunClient)
   505  
   506  	args := &ecs.CreateInstanceArgs{
   507  		RegionId:     getRegion(d, meta),
   508  		InstanceType: d.Get("instance_type").(string),
   509  	}
   510  
   511  	imageID := d.Get("image_id").(string)
   512  
   513  	args.ImageId = imageID
   514  
   515  	systemDiskCategory := ecs.DiskCategory(d.Get("system_disk_category").(string))
   516  	systemDiskSize := d.Get("system_disk_size").(int)
   517  
   518  	zoneID := d.Get("availability_zone").(string)
   519  	// check instanceType and systemDiskCategory, when zoneID is not empty
   520  	if zoneID != "" {
   521  		zone, err := client.DescribeZone(zoneID)
   522  		if err != nil {
   523  			return nil, err
   524  		}
   525  
   526  		if err := client.ResourceAvailable(zone, ecs.ResourceTypeInstance); err != nil {
   527  			return nil, err
   528  		}
   529  
   530  		if err := client.DiskAvailable(zone, systemDiskCategory); err != nil {
   531  			return nil, err
   532  		}
   533  
   534  		args.ZoneId = zoneID
   535  
   536  	}
   537  
   538  	args.SystemDisk = ecs.SystemDiskType{
   539  		Category: systemDiskCategory,
   540  		Size:     systemDiskSize,
   541  	}
   542  
   543  	sgs, ok := d.GetOk("security_groups")
   544  
   545  	if ok {
   546  		sgList := expandStringList(sgs.(*schema.Set).List())
   547  		sg0 := sgList[0]
   548  		// check security group instance exist
   549  		_, err := client.DescribeSecurity(sg0)
   550  		if err == nil {
   551  			args.SecurityGroupId = sg0
   552  		}
   553  	}
   554  
   555  	if v := d.Get("instance_name").(string); v != "" {
   556  		args.InstanceName = v
   557  	}
   558  
   559  	if v := d.Get("description").(string); v != "" {
   560  		args.Description = v
   561  	}
   562  
   563  	log.Printf("[DEBUG] SystemDisk is %d", systemDiskSize)
   564  	if v := d.Get("internet_charge_type").(string); v != "" {
   565  		args.InternetChargeType = common.InternetChargeType(v)
   566  	}
   567  
   568  	if v := d.Get("internet_max_bandwidth_out").(int); v != 0 {
   569  		args.InternetMaxBandwidthOut = v
   570  	}
   571  
   572  	if v := d.Get("host_name").(string); v != "" {
   573  		args.HostName = v
   574  	}
   575  
   576  	if v := d.Get("password").(string); v != "" {
   577  		args.Password = v
   578  	}
   579  
   580  	if v := d.Get("io_optimized").(string); v != "" {
   581  		if v == "optimized" {
   582  			args.IoOptimized = ecs.IoOptimized("true")
   583  		} else {
   584  			args.IoOptimized = ecs.IoOptimized("false")
   585  		}
   586  	}
   587  
   588  	vswitchValue := d.Get("subnet_id").(string)
   589  	if vswitchValue == "" {
   590  		vswitchValue = d.Get("vswitch_id").(string)
   591  	}
   592  	if vswitchValue != "" {
   593  		args.VSwitchId = vswitchValue
   594  		if d.Get("allocate_public_ip").(bool) && args.InternetMaxBandwidthOut <= 0 {
   595  			return nil, fmt.Errorf("Invalid internet_max_bandwidth_out result in allocation public ip failed in the VPC.")
   596  		}
   597  	}
   598  
   599  	if v := d.Get("instance_charge_type").(string); v != "" {
   600  		args.InstanceChargeType = common.InstanceChargeType(v)
   601  	}
   602  
   603  	log.Printf("[DEBUG] period is %d", d.Get("period").(int))
   604  	if v := d.Get("period").(int); v != 0 {
   605  		args.Period = v
   606  	} else if args.InstanceChargeType == common.PrePaid {
   607  		return nil, fmt.Errorf("period is required for instance_charge_type is PrePaid")
   608  	}
   609  
   610  	if v := d.Get("user_data").(string); v != "" {
   611  		args.UserData = v
   612  	}
   613  
   614  	return args, nil
   615  }
   616  
   617  func userDataHashSum(user_data string) string {
   618  	// Check whether the user_data is not Base64 encoded.
   619  	// Always calculate hash of base64 decoded value since we
   620  	// check against double-encoding when setting it
   621  	v, base64DecodeError := base64.StdEncoding.DecodeString(user_data)
   622  	if base64DecodeError != nil {
   623  		v = []byte(user_data)
   624  	}
   625  	return string(v)
   626  }