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 }