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