github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/alicloud/resource_alicloud_instance.go (about)

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