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 }