From a1cbd1cee0a0676d6eb44b9e92dceee787096d8f Mon Sep 17 00:00:00 2001 From: chenhanzhang Date: Fri, 5 Jul 2024 16:36:59 +0800 Subject: [PATCH] resource/alicloud_image: add new attribute boot_mode, detection_strategy, features etc. --- alicloud/provider.go | 2 +- alicloud/resource_alicloud_image.go | 627 +++++++++++++++++------ alicloud/resource_alicloud_image_test.go | 336 +++++++++++- alicloud/service_alicloud_ecs_v2.go | 80 ++- website/docs/r/image.html.markdown | 139 +++-- 5 files changed, 980 insertions(+), 204 deletions(-) diff --git a/alicloud/provider.go b/alicloud/provider.go index 5ae5f3a32eaf..6e68cbe06f02 100644 --- a/alicloud/provider.go +++ b/alicloud/provider.go @@ -971,7 +971,7 @@ func Provider() terraform.ResourceProvider { "alicloud_vpc_ha_vip": resourceAliCloudVpcHaVip(), "alicloud_config_remediation": resourceAliCloudConfigRemediation(), "alicloud_instance": resourceAliCloudInstance(), - "alicloud_image": resourceAliCloudImage(), + "alicloud_image": resourceAliCloudEcsImage(), "alicloud_reserved_instance": resourceAliCloudReservedInstance(), "alicloud_copy_image": resourceAliCloudImageCopy(), "alicloud_image_export": resourceAliCloudImageExport(), diff --git a/alicloud/resource_alicloud_image.go b/alicloud/resource_alicloud_image.go index a526c7c753c8..976f1298b846 100644 --- a/alicloud/resource_alicloud_image.go +++ b/alicloud/resource_alicloud_image.go @@ -1,114 +1,129 @@ +// Package alicloud. This file is generated automatically. Please do not modify it manually, thank you! package alicloud import ( + "fmt" "log" "strconv" + "strings" "time" + "github.com/PaesslerAG/jsonpath" util "github.com/alibabacloud-go/tea-utils/service" - - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) -func resourceAliCloudImage() *schema.Resource { +func resourceAliCloudEcsImage() *schema.Resource { return &schema.Resource{ - Create: resourceAliCloudImageCreate, - Read: resourceAliCloudImageRead, - Update: resourceAliCloudImageUpdate, - Delete: resourceAliCloudImageDelete, + Create: resourceAliCloudEcsImageCreate, + Read: resourceAliCloudEcsImageRead, + Update: resourceAliCloudEcsImageUpdate, + Delete: resourceAliCloudEcsImageDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), Delete: schema.DefaultTimeout(10 * time.Minute), }, Schema: map[string]*schema.Schema{ "architecture": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "x86_64", - ValidateFunc: validation.StringInSlice([]string{ - "x86_64", - "i386", - }, false), - }, - "instance_id": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"disk_device_mapping", "snapshot_id"}, - }, - "snapshot_id": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"instance_id", "disk_device_mapping"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "x86_64", + ValidateFunc: StringInSlice([]string{"i386", "x86_64", "arm64"}, false), }, - "description": { + "boot_mode": { Type: schema.TypeString, Optional: true, + Computed: true, }, - "name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - Deprecated: "Attribute 'name' has been deprecated from version 1.69.0. Use `image_name` instead.", - }, - "image_name": { + "create_time": { Type: schema.TypeString, - Optional: true, Computed: true, }, - "platform": { + "description": { Type: schema.TypeString, Optional: true, - ForceNew: true, - Computed: true, }, - "resource_group_id": { + "detection_strategy": { Type: schema.TypeString, Optional: true, }, "disk_device_mapping": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Computed: true, - ConflictsWith: []string{"instance_id", "snapshot_id"}, + Type: schema.TypeList, + Optional: true, + Computed: true, + ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "disk_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: StringInSlice([]string{"system", "data"}, false), + }, + "snapshot_id": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, + }, + "progress": { + Type: schema.TypeString, + Computed: true, + }, + "format": { + Type: schema.TypeString, Computed: true, }, "device": { Type: schema.TypeString, Optional: true, - ForceNew: true, Computed: true, + ForceNew: true, }, "size": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: IntAtMost(32768), + }, + "import_oss_object": { + Type: schema.TypeString, + Computed: true, + }, + "remain_time": { Type: schema.TypeInt, - Optional: true, - ForceNew: true, Computed: true, }, - "snapshot_id": { + "import_oss_bucket": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "features": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nvme_support": { Type: schema.TypeString, Optional: true, - ForceNew: true, Computed: true, + ForceNew: true, }, }, }, @@ -118,6 +133,49 @@ func resourceAliCloudImage() *schema.Resource { Optional: true, Default: false, }, + "image_family": { + Type: schema.TypeString, + Optional: true, + }, + "image_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"name"}, + }, + "image_version": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "instance_id": { + Type: schema.TypeString, + Optional: true, + }, + "license_type": { + Type: schema.TypeString, + Optional: true, + }, + "platform": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: StringInSlice([]string{"Aliyun", "Anolis", "CentOS", "Ubuntu", "CoreOS", "SUSE", "Debian", "OpenSUSE", "FreeBSD", "RedHat", "Kylin", "UOS", "Fedora", "Fedora CoreOS", "CentOS Stream", "AlmaLinux", "Rocky Linux", "Gentoo", "Customized Linux", "Others Linux", "Windows Server 2022", "Windows Server 2019", "Windows Server 2016", "Windows Server 2012", "Windows Server 2008", "Windows Server 2003"}, false), + }, + "resource_group_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "snapshot_id": { + Type: schema.TypeString, + Optional: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, "tags": tagsSchema(), // Not the public attribute and it used to automatically delete dependence snapshots while deleting the image. // Available in 1.136.0 @@ -125,139 +183,412 @@ func resourceAliCloudImage() *schema.Resource { Type: schema.TypeBool, Optional: true, }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Deprecated: "Field 'name' has been deprecated since provider version 1.227.0. New field 'image_name' instead.", + }, }, } } -func resourceAliCloudImageCreate(d *schema.ResourceData, meta interface{}) error { + +func resourceAliCloudEcsImageCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*connectivity.AliyunClient) - ecsService := EcsService{client} - // Make sure the instance status is Running or Stopped + action := "CreateImage" + var request map[string]interface{} + var response map[string]interface{} + query := make(map[string]interface{}) + conn, err := client.NewEcsClient() + if err != nil { + return WrapError(err) + } + request = make(map[string]interface{}) + request["RegionId"] = client.RegionId + request["ClientToken"] = buildClientToken(action) + + if v, ok := d.GetOk("resource_group_id"); ok { + request["ResourceGroupId"] = v + } + if v, ok := d.GetOk("tags"); ok { + tagsMap := ConvertTags(v.(map[string]interface{})) + request = expandTagsToMap(request, tagsMap) + } + + if v, ok := d.GetOk("description"); ok { + request["Description"] = v + } + if v, ok := d.GetOk("platform"); ok { + request["Platform"] = v + } + if v, ok := d.GetOk("architecture"); ok { + request["Architecture"] = v + } + if v, ok := d.GetOk("name"); ok { + request["ImageName"] = v + } + + if v, ok := d.GetOk("image_name"); ok { + request["ImageName"] = v + } + if v, ok := d.GetOk("image_version"); ok { + request["ImageVersion"] = v + } + if v, ok := d.GetOk("snapshot_id"); ok { + request["SnapshotId"] = v + } + if v, ok := d.GetOk("image_family"); ok { + request["ImageFamily"] = v + } + if v, ok := d.GetOk("boot_mode"); ok { + request["BootMode"] = v + } + if v, ok := d.GetOk("detection_strategy"); ok { + request["DetectionStrategy"] = v + } if v, ok := d.GetOk("instance_id"); ok { - instance, err := ecsService.DescribeInstance(v.(string)) - if err != nil { - return WrapError(err) - } - status := Status(instance.Status) - if status != Running && status != Stopped { - return WrapError(Error("You must make sure that the status of the specified instance is Running or Stopped. ")) + request["InstanceId"] = v + } + if v, ok := d.GetOk("disk_device_mapping"); ok { + diskDeviceMappingMaps := make([]interface{}, 0) + for _, dataLoop1 := range v.([]interface{}) { + dataLoop1Tmp := dataLoop1.(map[string]interface{}) + dataLoop1Map := make(map[string]interface{}) + dataLoop1Map["SnapshotId"] = dataLoop1Tmp["snapshot_id"] + dataLoop1Map["Size"] = dataLoop1Tmp["size"] + dataLoop1Map["Device"] = dataLoop1Tmp["device"] + dataLoop1Map["DiskType"] = dataLoop1Tmp["disk_type"] + diskDeviceMappingMaps = append(diskDeviceMappingMaps, dataLoop1Map) } + request["DiskDeviceMapping"] = diskDeviceMappingMaps } - // The snapshot cannot be a snapshot created before July 15, 2013 (inclusive) - if snapshotId, ok := d.GetOk("snapshot_id"); ok { - snapshot, err := ecsService.DescribeSnapshot(snapshotId.(string)) + runtime := util.RuntimeOptions{} + runtime.SetAutoretry(true) + wait := incrementalWait(3*time.Second, 5*time.Second) + err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2014-05-26"), StringPointer("AK"), query, request, &runtime) if err != nil { - return WrapError(err) + if IsExpectedErrors(err, []string{"IncorrectInstanceStatus"}) || NeedRetry(err) { + wait() + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) } - snapshotCreationTime, err := time.Parse("2006-01-02T15:04:05Z", snapshot.CreationTime) - if err != nil { - return WrapErrorf(err, IdMsg, snapshotId) + addDebug(action, response, request) + return nil + }) + + if err != nil { + return WrapErrorf(err, DefaultErrorMsg, "alicloud_image", action, AlibabaCloudSdkGoERROR) + } + + d.SetId(fmt.Sprint(response["ImageId"])) + + ecsServiceV2 := EcsServiceV2{client} + stateConf := BuildStateConf([]string{}, []string{"Available"}, d.Timeout(schema.TimeoutCreate), 10*time.Second, ecsServiceV2.EcsImageStateRefreshFunc(d.Id(), "Status", []string{"CreateFailed"})) + if _, err := stateConf.WaitForState(); err != nil { + return WrapErrorf(err, IdMsg, d.Id()) + } + + return resourceAliCloudEcsImageRead(d, meta) +} + +func resourceAliCloudEcsImageRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*connectivity.AliyunClient) + ecsServiceV2 := EcsServiceV2{client} + + objectRaw, err := ecsServiceV2.DescribeEcsImage(d.Id()) + if err != nil { + if !d.IsNewResource() && NotFoundError(err) { + log.Printf("[DEBUG] Resource alicloud_image DescribeEcsImage Failed!!! %s", err) + d.SetId("") + return nil } - deadlineTime, _ := time.Parse("2006-01-02T15:04:05Z", "2013-07-16T00:00:00Z") - if deadlineTime.After(snapshotCreationTime) { - return WrapError(Error("the specified snapshot cannot be created on or before July 15, 2013.")) + return WrapError(err) + } + + if objectRaw["Architecture"] != nil { + d.Set("architecture", objectRaw["Architecture"]) + } + if objectRaw["BootMode"] != nil { + d.Set("boot_mode", objectRaw["BootMode"]) + } + if objectRaw["CreationTime"] != nil { + d.Set("create_time", objectRaw["CreationTime"]) + } + if objectRaw["Description"] != nil { + d.Set("description", objectRaw["Description"]) + } + if objectRaw["ImageFamily"] != nil { + d.Set("image_family", objectRaw["ImageFamily"]) + } + if objectRaw["ImageName"] != nil { + d.Set("image_name", objectRaw["ImageName"]) + } + if objectRaw["ImageVersion"] != nil { + d.Set("image_version", objectRaw["ImageVersion"]) + } + if objectRaw["Platform"] != nil { + d.Set("platform", objectRaw["Platform"]) + } + if objectRaw["ResourceGroupId"] != nil { + d.Set("resource_group_id", objectRaw["ResourceGroupId"]) + } + if objectRaw["Status"] != nil { + d.Set("status", objectRaw["Status"]) + } + + diskDeviceMapping1Raw, _ := jsonpath.Get("$.DiskDeviceMappings.DiskDeviceMapping", objectRaw) + diskDeviceMappingMaps := make([]map[string]interface{}, 0) + if diskDeviceMapping1Raw != nil { + for _, diskDeviceMappingChild1Raw := range diskDeviceMapping1Raw.([]interface{}) { + diskDeviceMappingMap := make(map[string]interface{}) + diskDeviceMappingChild1Raw := diskDeviceMappingChild1Raw.(map[string]interface{}) + diskDeviceMappingMap["device"] = diskDeviceMappingChild1Raw["Device"] + diskDeviceMappingMap["disk_type"] = diskDeviceMappingChild1Raw["Type"] + diskDeviceMappingMap["format"] = diskDeviceMappingChild1Raw["Format"] + diskDeviceMappingMap["import_oss_object"] = diskDeviceMappingChild1Raw["ImportOSSObject"] + diskDeviceMappingMap["import_oss_bucket"] = diskDeviceMappingChild1Raw["ImportOSSBucket"] + diskDeviceMappingMap["progress"] = diskDeviceMappingChild1Raw["Progress"] + diskDeviceMappingMap["remain_time"] = diskDeviceMappingChild1Raw["RemainTime"] + diskDeviceMappingMap["size"] = diskDeviceMappingChild1Raw["Size"] + diskDeviceMappingMap["snapshot_id"] = diskDeviceMappingChild1Raw["SnapshotId"] + + diskDeviceMappingMaps = append(diskDeviceMappingMaps, diskDeviceMappingMap) + } + } + d.Set("disk_device_mapping", diskDeviceMappingMaps) + featuresMaps := make([]map[string]interface{}, 0) + featuresMap := make(map[string]interface{}) + features1Raw := make(map[string]interface{}) + if objectRaw["Features"] != nil { + features1Raw = objectRaw["Features"].(map[string]interface{}) + } + if len(features1Raw) > 0 { + featuresMap["nvme_support"] = features1Raw["NvmeSupport"] + + featuresMaps = append(featuresMaps, featuresMap) + } + if objectRaw["Features"] != nil { + d.Set("features", featuresMaps) + } + tagsMaps, _ := jsonpath.Get("$.Tags.Tag", objectRaw) + d.Set("tags", tagsToMap(tagsMaps)) + + d.Set("name", d.Get("image_name")) + return nil +} + +func resourceAliCloudEcsImageUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*connectivity.AliyunClient) + var request map[string]interface{} + var response map[string]interface{} + var query map[string]interface{} + update := false + d.Partial(true) + action := "ModifyImageAttribute" + conn, err := client.NewEcsClient() + if err != nil { + return WrapError(err) + } + request = make(map[string]interface{}) + query = make(map[string]interface{}) + query["ImageId"] = d.Id() + request["RegionId"] = client.RegionId + if d.HasChange("description") { + update = true + request["Description"] = d.Get("description") + } + + if d.HasChange("name") { + update = true + request["ImageName"] = d.Get("name") + } + + if d.HasChange("image_name") { + update = true + request["ImageName"] = d.Get("image_name") + } + + if d.HasChange("image_family") { + update = true + request["ImageFamily"] = d.Get("image_family") + } + + if d.HasChange("boot_mode") { + update = true + request["BootMode"] = d.Get("boot_mode") + } + + if v, ok := d.GetOk("license_type"); ok { + request["LicenseType"] = v + } + if v, ok := d.GetOk("features"); ok { + jsonPathResult5, err := jsonpath.Get("$[0].nvme_support", v) + if err == nil && jsonPathResult5 != "" { + request["Features.NvmeSupport"] = jsonPathResult5 } } - request := ecs.CreateCreateImageRequest() - request.RegionId = client.RegionId - if instanceId, ok := d.GetOk("instance_id"); ok { - request.InstanceId = instanceId.(string) - } - if value, ok := d.GetOk("disk_device_mapping"); ok { - diskDeviceMappings := value.([]interface{}) - if diskDeviceMappings != nil && len(diskDeviceMappings) > 0 { - mappings := make([]ecs.CreateImageDiskDeviceMapping, 0, len(diskDeviceMappings)) - for _, diskDeviceMapping := range diskDeviceMappings { - mapping := diskDeviceMapping.(map[string]interface{}) - deviceMapping := ecs.CreateImageDiskDeviceMapping{ - SnapshotId: mapping["snapshot_id"].(string), - Size: strconv.Itoa(mapping["size"].(int)), - DiskType: mapping["disk_type"].(string), - Device: mapping["device"].(string), + if update { + runtime := util.RuntimeOptions{} + runtime.SetAutoretry(true) + wait := incrementalWait(3*time.Second, 5*time.Second) + err = resource.Retry(d.Timeout(schema.TimeoutUpdate), func() *resource.RetryError { + response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2014-05-26"), StringPointer("AK"), query, request, &runtime) + if err != nil { + if NeedRetry(err) { + wait() + return resource.RetryableError(err) } - mappings = append(mappings, deviceMapping) + return resource.NonRetryableError(err) } - request.DiskDeviceMapping = &mappings + addDebug(action, response, request) + return nil + }) + if err != nil { + return WrapErrorf(err, DefaultErrorMsg, d.Id(), action, AlibabaCloudSdkGoERROR) } } + update = false + action = "JoinResourceGroup" + conn, err = client.NewEcsClient() + if err != nil { + return WrapError(err) + } + request = make(map[string]interface{}) + query = make(map[string]interface{}) + query["ResourceId"] = d.Id() + request["RegionId"] = client.RegionId + if _, ok := d.GetOk("resource_group_id"); ok && d.HasChange("resource_group_id") { + update = true + request["ResourceGroupId"] = d.Get("resource_group_id") + } - tags := d.Get("tags").(map[string]interface{}) - if tags != nil && len(tags) > 0 { - imageTags := make([]ecs.CreateImageTag, 0, len(tags)) - for k, v := range tags { - imageTag := ecs.CreateImageTag{ - Key: k, - Value: v.(string), + request["ResourceType"] = "image" + if update { + runtime := util.RuntimeOptions{} + runtime.SetAutoretry(true) + wait := incrementalWait(3*time.Second, 5*time.Second) + err = resource.Retry(d.Timeout(schema.TimeoutUpdate), func() *resource.RetryError { + response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2014-05-26"), StringPointer("AK"), query, request, &runtime) + if err != nil { + if NeedRetry(err) { + wait() + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) } - imageTags = append(imageTags, imageTag) + addDebug(action, response, request) + return nil + }) + if err != nil { + return WrapErrorf(err, DefaultErrorMsg, d.Id(), action, AlibabaCloudSdkGoERROR) + } + } + + if d.HasChange("tags") { + ecsServiceV2 := EcsServiceV2{client} + if err := ecsServiceV2.SetResourceTags(d, "image"); err != nil { + return WrapError(err) } - request.Tag = &imageTags } - if snapshotId, ok := d.GetOk("snapshot_id"); ok { - request.SnapshotId = snapshotId.(string) + d.Partial(false) + ecsServiceV2 := EcsServiceV2{client} + stateConf := BuildStateConf([]string{}, []string{d.Get("description").(string)}, d.Timeout(schema.TimeoutDelete), 5*time.Second, ecsServiceV2.EcsImageStateRefreshFunc(d.Id(), "Description", []string{})) + if _, err := stateConf.WaitForState(); err != nil { + return WrapErrorf(err, IdMsg, d.Id()) } - request.ResourceGroupId = d.Get("resource_group_id").(string) - request.Platform = d.Get("platform").(string) - request.ImageName = d.Get("image_name").(string) - request.Description = d.Get("description").(string) - request.Architecture = d.Get("architecture").(string) + return resourceAliCloudEcsImageRead(d, meta) +} + +func resourceAliCloudEcsImageDelete(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*connectivity.AliyunClient) + ecsService := EcsService{client} + object, err := ecsService.DescribeImageById(d.Id()) + if err != nil { + if NotFoundError(err) { + d.SetId("") + return nil + } + return WrapError(err) + } + action := "DeleteImage" + var request map[string]interface{} + var response map[string]interface{} + query := make(map[string]interface{}) + conn, err := client.NewEcsClient() + if err != nil { + return WrapError(err) + } + request = make(map[string]interface{}) + query["ImageId"] = d.Id() + request["RegionId"] = client.RegionId + + if v, ok := d.GetOkExists("force"); ok { + request["Force"] = v + } + runtime := util.RuntimeOptions{} + runtime.SetAutoretry(true) + wait := incrementalWait(3*time.Second, 5*time.Second) + err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { + response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2014-05-26"), StringPointer("AK"), query, request, &runtime) - err := resource.Retry(5*time.Minute, func() *resource.RetryError { - raw, err := client.WithEcsClient(func(ecsClient *ecs.Client) (interface{}, error) { - return ecsClient.CreateImage(request) - }) if err != nil { - if IsExpectedErrors(err, []string{"IncorrectInstanceStatus"}) { - time.Sleep(time.Second) + if NeedRetry(err) { + wait() return resource.RetryableError(err) } return resource.NonRetryableError(err) } - addDebug(request.GetActionName(), raw, request.RpcRequest, request) - response, _ := raw.(*ecs.CreateImageResponse) - d.SetId(response.ImageId) + addDebug(action, response, request) return nil }) if err != nil { - return WrapErrorf(err, DefaultErrorMsg, d.Id(), request.GetActionName(), AlibabaCloudSdkGoERROR) + return WrapErrorf(err, DefaultErrorMsg, d.Id(), action, AlibabaCloudSdkGoERROR) } - stateConf := BuildStateConf([]string{"Creating"}, []string{"Available"}, d.Timeout(schema.TimeoutCreate), 1*time.Minute, ecsService.ImageStateRefreshFunc(d.Id(), []string{"CreateFailed", "UnAvailable"})) + ecsServiceV2 := EcsServiceV2{client} + stateConf := BuildStateConf([]string{}, []string{""}, d.Timeout(schema.TimeoutDelete), 5*time.Second, ecsServiceV2.EcsImageStateRefreshFunc(d.Id(), "ImageId", []string{})) if _, err := stateConf.WaitForState(); err != nil { return WrapErrorf(err, IdMsg, d.Id()) } - return resourceAliCloudImageRead(d, meta) -} -func resourceAliCloudImageUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*connectivity.AliyunClient) - ecsService := EcsService{client} - err := ecsService.updateImage(d) - if err != nil { - return WrapError(err) - } - if d.HasChange("resource_group_id") { - action := "JoinResourceGroup" - request := map[string]interface{}{ - "ResourceType": "image", - "ResourceId": d.Id(), - "RegionId": client.RegionId, - "ResourceGroupId": d.Get("resource_group_id"), - } - conn, err := client.NewEcsClient() - if err != nil { - return WrapError(err) + + if v, ok := d.GetOk("delete_auto_snapshot"); ok && v.(bool) { + errs := map[string]error{} + + for _, item := range object.DiskDeviceMappings.DiskDeviceMapping { + if item.SnapshotId == "" { + continue + } + request := ecs.CreateDeleteSnapshotRequest() + request.RegionId = ecsService.client.RegionId + request.SnapshotId = item.SnapshotId + request.Force = requests.NewBoolean(true) + raw, err := ecsService.client.WithEcsClient(func(ecsClient *ecs.Client) (interface{}, error) { + return ecsClient.DeleteSnapshot(request) + }) + if err != nil { + errs[item.SnapshotId] = err + } + addDebug(request.GetActionName(), raw, request.RpcRequest, request) } - response, err := conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2014-05-26"), StringPointer("AK"), nil, request, &util.RuntimeOptions{}) - if err != nil { - return WrapErrorf(err, DefaultErrorMsg, d.Id(), action, AlibabaCloudSdkGoERROR) + if len(errs) > 0 { + errParts := []string{"Errors while deleting associated snapshots:"} + for snapshotId, err := range errs { + errParts = append(errParts, fmt.Sprintf("%s: %s", snapshotId, err)) + } + errParts = append(errParts, "These are no longer managed by Terraform and must be deleted manually.") + return WrapError(fmt.Errorf(strings.Join(errParts, "\n"))) } - addDebug(action, response, request) - d.SetPartial("resource_group_id") } - return resourceAliCloudImageRead(d, meta) + return nil } + func resourceAliCloudImageRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*connectivity.AliyunClient) @@ -288,12 +619,6 @@ func resourceAliCloudImageRead(d *schema.ResourceData, meta interface{}) error { return WrapError(err) } -func resourceAliCloudImageDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*connectivity.AliyunClient) - ecsService := EcsService{client} - return ecsService.deleteImage(d) -} - func FlattenImageDiskDeviceMappings(list []ecs.DiskDeviceMapping) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) for _, i := range list { diff --git a/alicloud/resource_alicloud_image_test.go b/alicloud/resource_alicloud_image_test.go index 782f7ba284fb..20bba4da97c1 100644 --- a/alicloud/resource_alicloud_image_test.go +++ b/alicloud/resource_alicloud_image_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/resource" ) -func TestAccAlicloudECSImageBasic(t *testing.T) { +func TestAccAliCloudECSImageBasic(t *testing.T) { var v ecs.Image resourceId := "alicloud_image.default" @@ -38,7 +38,7 @@ func TestAccAlicloudECSImageBasic(t *testing.T) { Config: testAccConfig(map[string]interface{}{ "instance_id": "${alicloud_instance.default.id}", "description": fmt.Sprintf("tf-testAccEcsImageConfigBasic%ddescription", rand), - "image_name": name, + "name": name, "tags": map[string]string{ "Created": "TF", "For": "acceptance test123", @@ -46,7 +46,7 @@ func TestAccAlicloudECSImageBasic(t *testing.T) { }), Check: resource.ComposeTestCheckFunc( testAccCheck(map[string]string{ - "image_name": name, + "name": name, "description": fmt.Sprintf("tf-testAccEcsImageConfigBasic%ddescription", rand), "tags.%": "2", "tags.Created": "TF", @@ -66,11 +66,11 @@ func TestAccAlicloudECSImageBasic(t *testing.T) { }, { Config: testAccConfig(map[string]interface{}{ - "image_name": fmt.Sprintf("tf-testAccEcsImageConfigBasic%dchange", rand), + "name": fmt.Sprintf("tf-testAccEcsImageConfigBasic%dchange", rand), }), Check: resource.ComposeTestCheckFunc( testAccCheck(map[string]string{ - "image_name": fmt.Sprintf("tf-testAccEcsImageConfigBasic%dchange", rand), + "name": fmt.Sprintf("tf-testAccEcsImageConfigBasic%dchange", rand), }), ), }, @@ -92,7 +92,7 @@ func TestAccAlicloudECSImageBasic(t *testing.T) { { Config: testAccConfig(map[string]interface{}{ "description": fmt.Sprintf("tf-testAccEcsImageConfigBasic%ddescription", rand), - "image_name": name, + "name": name, "tags": map[string]string{ "Created": "TF", "For": "acceptance test123", @@ -101,7 +101,7 @@ func TestAccAlicloudECSImageBasic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheck(map[string]string{ "description": fmt.Sprintf("tf-testAccEcsImageConfigBasic%ddescription", rand), - "image_name": name, + "name": name, "tags.%": "2", "tags.Created": "TF", "tags.For": "acceptance test123", @@ -112,7 +112,7 @@ func TestAccAlicloudECSImageBasic(t *testing.T) { }) } -func TestAccAlicloudECSImageBasic1(t *testing.T) { +func TestAccAliCloudECSImageBasic1(t *testing.T) { var v ecs.Image resourceId := "alicloud_image.default" @@ -161,7 +161,7 @@ func TestAccAlicloudECSImageBasic1(t *testing.T) { }) } -func TestAccAlicloudECSImageBasic2(t *testing.T) { +func TestAccAliCloudECSImageBasic2(t *testing.T) { var v ecs.Image resourceId := "alicloud_image.default" @@ -232,6 +232,7 @@ data "alicloud_instance_types" "default" { data "alicloud_images" "default" { name_regex = "^ubuntu_[0-9]+_[0-9]+_x64*" owners = "system" + instance_type = data.alicloud_instance_types.default.ids.0 } data "alicloud_vpcs" "default" { @@ -285,6 +286,7 @@ data "alicloud_instance_types" "default" { data "alicloud_images" "default" { name_regex = "^ubuntu_[0-9]+_[0-9]+_x64*" owners = "system" + instance_type = data.alicloud_instance_types.default.ids.0 } data "alicloud_vpcs" "default" { @@ -345,3 +347,319 @@ resource "alicloud_ecs_snapshot" "default" { `, name) } + +func TestAccAliCloudEcsImageBasic7009(t *testing.T) { + var v map[string]interface{} + resourceId := "alicloud_image.default" + ra := resourceAttrInit(resourceId, AlicloudEcsImageMap7009) + rc := resourceCheckInitWithDescribeMethod(resourceId, &v, func() interface{} { + return &EcsServiceV2{testAccProvider.Meta().(*connectivity.AliyunClient)} + }, "DescribeEcsImage") + rac := resourceAttrCheckInit(rc, ra) + testAccCheck := rac.resourceAttrMapUpdateSet() + rand := acctest.RandIntRange(10000, 99999) + name := fmt.Sprintf("tf-testacc%secsimage%d", defaultRegionToTest, rand) + testAccConfig := resourceTestAccConfigFunc(resourceId, name, AlicloudEcsImageBasicDependence7009) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + IDRefreshName: resourceId, + Providers: testAccProviders, + CheckDestroy: rac.checkResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccConfig(map[string]interface{}{ + "image_name": name, + "instance_id": "${alicloud_instance.default.id}", + "platform": "Ubuntu", + "force": "true", + "delete_auto_snapshot": "true", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "image_name": name, + "platform": "Ubuntu", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "description": "create", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "description": "create", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "boot_mode": "BIOS", + "license_type": "BYOL", + "features": []map[string]interface{}{ + { + "nvme_support": "supported", + }, + }, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "boot_mode": "BIOS", + "license_type": "BYOL", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "image_family": "test-tf", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "image_family": "test-tf", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "resource_group_id": "${data.alicloud_resource_manager_resource_groups.default.ids.1}", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "resource_group_id": CHECKSET, + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "description": "test-creat", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "description": "test-creat", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "image_name": name + "_update", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "image_name": name + "_update", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "boot_mode": "UEFI", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "boot_mode": "UEFI", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "image_family": "test-tf-123", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "image_family": "test-tf-123", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "resource_group_id": "${data.alicloud_resource_manager_resource_groups.default.ids.1}", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "resource_group_id": CHECKSET, + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "description": "test-aaaa", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "description": "test-aaaa", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "description": "create", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "description": "create", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "image_name": name + "_update", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "image_name": name + "_update", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "boot_mode": "BIOS", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "boot_mode": "BIOS", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "image_family": "test-tf", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "image_family": "test-tf", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "description": "create", + "instance_id": "${alicloud_instance.default.id}", + "image_name": name + "_update", + "detection_strategy": "Standard", + "architecture": "x86_64", + "boot_mode": "BIOS", + "image_family": "test-tf", + "image_version": "1", + "resource_group_id": "${data.alicloud_resource_manager_resource_groups.default.ids.0}", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "description": "create", + "instance_id": CHECKSET, + "image_name": name + "_update", + "detection_strategy": "Standard", + "architecture": "x86_64", + "boot_mode": "BIOS", + "image_family": "test-tf", + "image_version": "1", + "resource_group_id": CHECKSET, + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "tags": map[string]string{ + "Created": "TF", + "For": "Test", + }, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "tags.%": "2", + "tags.Created": "TF", + "tags.For": "Test", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "tags": map[string]string{ + "Created": "TF-update", + "For": "Test-update", + }, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "tags.%": "2", + "tags.Created": "TF-update", + "tags.For": "Test-update", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "tags": REMOVEKEY, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "tags.%": "0", + "tags.Created": REMOVEKEY, + "tags.For": REMOVEKEY, + }), + ), + }, + { + ResourceName: resourceId, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"detection_strategy", "features", "instance_id", "license_type", "snapshot_id", "delete_auto_snapshot", "force"}, + }, + }, + }) +} + +var AlicloudEcsImageMap7009 = map[string]string{ + "status": CHECKSET, + "create_time": CHECKSET, +} + +func AlicloudEcsImageBasicDependence7009(name string) string { + return fmt.Sprintf(` +variable "name" { + default = "%s" +} + +data "alicloud_instance_types" "default" { + instance_type_family = "ecs.sn1ne" +} + +data "alicloud_resource_manager_resource_groups" "default" {} + +data "alicloud_images" "default" { + name_regex = "^ubuntu_[0-9]+_[0-9]+_x64*" + owners = "system" + instance_type = data.alicloud_instance_types.default.ids.0 +} + +data "alicloud_vpcs" "default" { + name_regex = "^default-NODELETING$" +} +data "alicloud_vswitches" "default" { + vpc_id = data.alicloud_vpcs.default.ids.0 + zone_id = data.alicloud_instance_types.default.instance_types.0.availability_zones.0 +} +resource "alicloud_vswitch" "vswitch" { + count = length(data.alicloud_vswitches.default.ids) > 0 ? 0 : 1 + vpc_id = data.alicloud_vpcs.default.ids.0 + cidr_block = cidrsubnet(data.alicloud_vpcs.default.vpcs[0].cidr_block, 8, 8) + zone_id = data.alicloud_instance_types.default.instance_types.0.availability_zones.0 + vswitch_name = var.name +} + +locals { + vswitch_id = length(data.alicloud_vswitches.default.ids) > 0 ? data.alicloud_vswitches.default.ids[0] : concat(alicloud_vswitch.vswitch.*.id, [""])[0] +} +resource "alicloud_security_group" "default" { + name = "${var.name}" + vpc_id = data.alicloud_vpcs.default.ids.0 +} +resource "alicloud_instance" "default" { + image_id = "${data.alicloud_images.default.ids[0]}" + instance_type = "${data.alicloud_instance_types.default.ids[0]}" + security_groups = "${[alicloud_security_group.default.id]}" + vswitch_id = local.vswitch_id + instance_name = "${var.name}" +} + +`, name) +} diff --git a/alicloud/service_alicloud_ecs_v2.go b/alicloud/service_alicloud_ecs_v2.go index 9db47ead6297..3d89f83d95ad 100644 --- a/alicloud/service_alicloud_ecs_v2.go +++ b/alicloud/service_alicloud_ecs_v2.go @@ -128,7 +128,6 @@ func (s *EcsServiceV2) SetResourceTags(d *schema.ResourceData, resourceType stri wait := incrementalWait(3*time.Second, 5*time.Second) err = resource.Retry(d.Timeout(schema.TimeoutUpdate), func() *resource.RetryError { response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2014-05-26"), StringPointer("AK"), query, request, &runtime) - if err != nil { if NeedRetry(err) { wait() @@ -168,7 +167,6 @@ func (s *EcsServiceV2) SetResourceTags(d *schema.ResourceData, resourceType stri wait := incrementalWait(3*time.Second, 5*time.Second) err = resource.Retry(d.Timeout(schema.TimeoutUpdate), func() *resource.RetryError { response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2014-05-26"), StringPointer("AK"), query, request, &runtime) - if err != nil { if NeedRetry(err) { wait() @@ -184,10 +182,86 @@ func (s *EcsServiceV2) SetResourceTags(d *schema.ResourceData, resourceType stri } } - d.SetPartial("tags") } return nil } // SetResourceTags >>> tag function encapsulated. + +// DescribeEcsImage <<< Encapsulated get interface for Ecs Image. + +func (s *EcsServiceV2) DescribeEcsImage(id string) (object map[string]interface{}, err error) { + client := s.client + var request map[string]interface{} + var response map[string]interface{} + var query map[string]interface{} + action := "DescribeImages" + conn, err := client.NewEcsClient() + if err != nil { + return object, WrapError(err) + } + request = make(map[string]interface{}) + query = make(map[string]interface{}) + query["ImageId"] = id + query["RegionId"] = client.RegionId + + runtime := util.RuntimeOptions{} + runtime.SetAutoretry(true) + wait := incrementalWait(3*time.Second, 5*time.Second) + err = resource.Retry(1*time.Minute, func() *resource.RetryError { + response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2014-05-26"), StringPointer("AK"), query, request, &runtime) + + if err != nil { + if NeedRetry(err) { + wait() + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + addDebug(action, response, request) + return nil + }) + if err != nil { + if IsExpectedErrors(err, []string{"InvalidImageId.NotFound"}) { + return object, WrapErrorf(Error(GetNotFoundMessage("Image", id)), NotFoundMsg, response) + } + addDebug(action, response, request) + return object, WrapErrorf(err, DefaultErrorMsg, id, action, AlibabaCloudSdkGoERROR) + } + + v, err := jsonpath.Get("$.Images.Image[*]", response) + if err != nil { + return object, WrapErrorf(err, FailedGetAttributeMsg, id, "$.Images.Image[*]", response) + } + + if len(v.([]interface{})) == 0 { + return object, WrapErrorf(Error(GetNotFoundMessage("Image", id)), NotFoundMsg, response) + } + + return v.([]interface{})[0].(map[string]interface{}), nil +} + +func (s *EcsServiceV2) EcsImageStateRefreshFunc(id string, field string, failStates []string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + object, err := s.DescribeEcsImage(id) + if err != nil { + if NotFoundError(err) { + return object, "", nil + } + return nil, "", WrapError(err) + } + + v, err := jsonpath.Get(field, object) + currentStatus := fmt.Sprint(v) + + for _, failState := range failStates { + if currentStatus == failState { + return object, currentStatus, WrapError(Error(FailedToReachTargetStatus, currentStatus)) + } + } + return object, currentStatus, nil + } +} + +// DescribeEcsImage >>> Encapsulated. diff --git a/website/docs/r/image.html.markdown b/website/docs/r/image.html.markdown index a2a1987207f4..3dfe6219e161 100644 --- a/website/docs/r/image.html.markdown +++ b/website/docs/r/image.html.markdown @@ -2,14 +2,13 @@ subcategory: "ECS" layout: "alicloud" page_title: "Alicloud: alicloud_image" -sidebar_current: "docs-alicloud-resource-image" description: |- - Provides an ECS image resource. + Provides a Alicloud ECS Image resource. --- # alicloud_image -Creates a custom image. You can then use a custom image to create ECS instances (RunInstances) or change the system disk for an existing instance (ReplaceSystemDisk). +Provides a ECS Image resource. -> **NOTE:** If you want to create a template from an ECS instance, you can specify the instance ID (InstanceId) to create a custom image. You must make sure that the status of the specified instance is Running or Stopped. After a successful invocation, each disk of the specified instance has a new snapshot created. @@ -17,10 +16,14 @@ Creates a custom image. You can then use a custom image to create ECS instances -> **NOTE:** If you want to combine snapshots of multiple disks into an image template, you can specify DiskDeviceMapping to create a custom image. +For information about ECS Image and how to use it, see [What is Image](https://www.alibabacloud.com/help/en/ecs/developer-reference/api-ecs-2014-05-26-createimage). + -> **NOTE:** Available since v1.64.0. ## Example Usage +Basic Usage + ```terraform data "alicloud_zones" "default" { available_resource_creation = "Instance" @@ -31,8 +34,9 @@ data "alicloud_instance_types" "default" { } data "alicloud_images" "default" { - name_regex = "^ubuntu_[0-9]+_[0-9]+_x64*" - owners = "system" + name_regex = "^ubuntu_[0-9]+_[0-9]+_x64*" + owners = "system" + instance_type = data.alicloud_instance_types.default.ids[0] } resource "alicloud_vpc" "default" { @@ -83,46 +87,101 @@ resource "alicloud_image" "default" { ## Argument Reference The following arguments are supported: +* `architecture` - (Optional, ForceNew, Computed) The system architecture of the system disk. If you specify a data disk snapshot to create the system disk of the custom image, you must use Architecture to specify the system architecture of the system disk. Valid values: `i386`, `x86\_64`, `arm64`. Default value: `x86\_64`. +* `boot_mode` - (Optional, Computed, Available since v1.227.0) The new boot mode of the image. Valid values: -* `instance_id` - (Optional, ForceNew, Conflict with `snapshot_id ` and `disk_device_mapping `) The instance ID. -* `image_name` - (Optional) The image name. It must be 2 to 128 characters in length, and must begin with a letter or Chinese character (beginning with http:// or https:// is not allowed). It can contain digits, colons (:), underscores (_), or hyphens (-). Default value: null. -* `description` - (Optional) The description of the image. It must be 2 to 256 characters in length and must not start with http:// or https://. Default value: null. -* `snapshot_id` - (Optional, ForceNew, Conflict with `instance_id ` and `disk_device_mapping `) Specifies a snapshot that is used to create a custom image. -* `architecture` - (Optional, ForceNew) Specifies the architecture of the system disk after you specify a data disk snapshot as the data source of the system disk for creating an image. Valid values: `i386` , Default is `x86_64`. -* `platform` - (Optional, ForceNew) The distribution of the operating system for the system disk in the custom image. - If you specify a data disk snapshot to create the system disk of the custom image, you must use the Platform parameter - to specify the distribution of the operating system for the system disk. Default value: Others Linux. - More valid values refer to [CreateImage OpenAPI](https://www.alibabacloud.com/help/en/elastic-compute-service/latest/createimage) - **NOTE**: It's default value is Ubuntu before version 1.197.0. -* `tags` - (Optional) The tag value of an image. The value of N ranges from 1 to 20. -* `resource_group_id` - (Optional, Available in 1.115.0+) The ID of the enterprise resource group to which a custom image belongs -* `disk_device_mapping` - (Optional, ForceNew, Conflict with `snapshot_id ` and `instance_id `) Description of the system with disks and snapshots under the image. - * `disk_type` - (Optional, ForceNew) Specifies the type of a disk in the combined custom image. If you specify this parameter, you can use a data disk snapshot as the data source of a system disk for creating an image. If it is not specified, the disk type is determined by the corresponding snapshot. Valid values: `system`, `data`, - * `size` - (Optional, ForceNew) Specifies the size of a disk in the combined custom image, in GiB. Value range: 5 to 2000. - * `snapshot_id` - (Optional, ForceNew) Specifies a snapshot that is used to create a combined custom image. - * `device` - (Optional, ForceNew)Specifies the name of a disk in the combined custom image. Value range: /dev/xvda to /dev/xvdz. -* `force` - (Optional) Indicates whether to force delete the custom image, Default is `false`. - - true:Force deletes the custom image, regardless of whether the image is currently being used by other instances. - - false:Verifies that the image is not currently in use by any other instances before deleting the image. - -## Timeouts + * BIOS: Basic Input/Output System (BIOS) -The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration-0-11/resources.html#timeouts) for certain actions: + * UEFI: Unified Extensible Firmware Interface (UEFI) + + * UEFI-Preferred: BIOS and UEFI + +-> **NOTE:** Before you change the boot mode, we recommend that you obtain the boot modes supported by the image. If you specify an unsupported boot mode for the image, ECS instances that use the image cannot start as expected. If you do not know which boot modes are supported by the image, we recommend that you use the image check feature to perform a check. For information about the image check feature, see [Overview](https://www.alibabacloud.com/help/en/doc-detail/439819.html). + +-> **NOTE:** For information about the UEFI-Preferred boot mode, see [Best practices for ECS instance boot modes](https://www.alibabacloud.com/help/en/doc-detail/2244655.html). + +* `description` - (Optional) The new description of the custom image. The description must be 2 to 256 characters in length It cannot start with `http://` or `https://`. This parameter is empty by default, which specifies that the original description is retained. +* `detection_strategy` - (Optional, Available since v1.227.0) The mode in which to check the custom image. If you do not specify this parameter, the image is not checked. Only the standard check mode is supported. + +-> **NOTE:** This parameter is supported for most Linux and Windows operating system versions. For information about image check items and operating system limits for image check, see [Overview of image check](https://www.alibabacloud.com/help/en/doc-detail/439819.html) and [Operating system limits for image check](https://www.alibabacloud.com/help/en/doc-detail/475800.html). + +* `disk_device_mapping` - (Optional, ForceNew, Computed) Snapshot information for the image See [`disk_device_mapping`](#disk_device_mapping) below. +* `features` - (Optional, Computed, Available since v1.227.0) Features See [`features`](#features) below. +* `force` - (Optional) Whether to perform forced deletion. Value range: + - true: forcibly deletes the custom image, ignoring whether the current image is used by other instances. + - false: The custom image is deleted normally. Before deleting the custom image, check whether the current image is used by other instances. + + Default value: false +* `delete_auto_snapshot` - (Optional, Available since 1.136.0) Not the public attribute and it used to automatically delete dependence snapshots while deleting the image. +* `image_family` - (Optional, Available since v1.227.0) The name of the image family. The name must be 2 to 128 characters in length. It must start with a letter and cannot start with acs: or aliyun. It cannot contain http:// or https://. It can contain letters, digits, periods (.), colons (:), underscores (\_), and hyphens (-). By default, this parameter is empty. +* `image_name` - (Optional) The name of the custom image. The name must be 2 to 128 characters in length. It must start with a letter and cannot start with acs: or aliyun. It cannot contain http:// or https://. It can contain letters, digits, periods (.), colons (:), underscores (\_), and hyphens (-). By default, this parameter is empty. In this case, the original name is retained. +* `image_version` - (Optional, ForceNew, Available since v1.227.0) The image version. + +-> **NOTE:** If you specify an instance by configuring `InstanceId`, and the instance uses an Alibaba Cloud Marketplace image or a custom image that is created from an Alibaba Cloud Marketplace image, you must leave this parameter empty or set this parameter to the value of ImageVersion of the instance. + +* `instance_id` - (Optional) The instance ID. +* `license_type` - (Optional, Available since v1.227.0) The type of the license that is used to activate the operating system after the image is imported. Set the value to BYOL. BYOL: The license that comes with the source operating system is used. When you use the BYOL license, make sure that your license key is supported by Alibaba Cloud. +* `platform` - (Optional, ForceNew, Computed) The operating system distribution for the system disk in the custom image. If you specify a data disk snapshot to create the system disk of the custom image, use Platform to specify the operating system distribution for the system disk. Valid values: `Aliyun`, `Anolis`, `CentOS`, `Ubuntu`, `CoreOS`, `SUSE`, `Debian`, `OpenSUSE`, `FreeBSD`, `RedHat`, `Kylin`, `UOS`, `Fedora`, `Fedora CoreOS`, `CentOS Stream`, `AlmaLinux`, `Rocky Linux`, `Gentoo`, `Customized Linux`, `Others Linux`, `Windows Server 2022`, `Windows Server 2019`, `Windows Server 2016`, `Windows Server 2012`, `Windows Server 2008`, `Windows Server 2003`. Default value: `Others Linux`. +* `resource_group_id` - (Optional, Computed) The ID of the resource group to which to assign the custom image. If you do not specify this parameter, the image is assigned to the default resource group. + +-> **NOTE:** If you call the CreateImage operation as a Resource Access Management (RAM) user who does not have the permissions to manage the default resource group and do not specify `ResourceGroupId`, the `Forbbiden: User not authorized to operate on the specified resource` error message is returned. You must specify the ID of a resource group that the RAM user has the permissions to manage or grant the RAM user the permissions to manage the default resource group before you call the CreateImage operation again. + +* `snapshot_id` - (Optional) The ID of the snapshot that you want to use to create the custom image. +* `tags` - (Optional, Map) The tag + +The following arguments will be discarded. Please use new fields as soon as possible: +* `name` - (Deprecated since v1.227.0). Field 'name' has been deprecated from provider version 1.227.0. New field 'image_name' instead. + +### `disk_device_mapping` + +The disk_device_mapping supports the following: +* `device` - (Optional, ForceNew, Computed) The device name of disk N in the custom image. Valid values: + - For disks other than basic disks, such as standard SSDs, ultra disks, and enhanced SSDs (ESSDs), the valid values range from /dev/vda to /dev/vdz in alphabetical order. + - For basic disks, the valid values range from /dev/xvda to /dev/xvdz in alphabetical order. +* `disk_type` - (Optional, ForceNew, Computed) The type of disk N in the custom image. You can specify this parameter to create the system disk of the custom image from a data disk snapshot. If you do not specify this parameter, the disk type is determined by the corresponding snapshot. Valid values: + - system: system disk. You can specify only one snapshot to use to create the system disk in the custom image. + - data: data disk. You can specify up to 16 snapshots to use to create data disks in the custom image. +* `size` - (Optional, ForceNew, Computed) The size of disk N in the custom image. Unit: GiB. The valid values and default value of DiskDeviceMapping.N.Size vary based on the value of DiskDeviceMapping.N.SnapshotId. + - If no corresponding snapshot IDs are specified in the value of DiskDeviceMapping.N.SnapshotId, DiskDeviceMapping.N.Size has the following valid values and default values: + * For basic disks, the valid values range from 5 to 2000, and the default value is 5. + * For other disks, the valid values range from 20 to 32768, and the default value is 20. + - If a corresponding snapshot ID is specified in the value of DiskDeviceMapping.N.SnapshotId, the value of DiskDeviceMapping.N.Size must be greater than or equal to the size of the specified snapshot. The default value of DiskDeviceMapping.N.Size is the size of the specified snapshot. +* `snapshot_id` - (Optional, ForceNew, Computed) The ID of snapshot N to use to create the custom image. . + +### `features` + +The features supports the following: +* `nvme_support` - (Optional, ForceNew, Computed) Specifies whether to support the Non-Volatile Memory Express (NVMe) protocol. Valid values: + - supported: The image supports NVMe. Instances created from this image also support NVMe. + - unsupported: The image does not support NVMe. Instances created from this image do not support NVMe. -* `create` - (Defaults to 10 mins) Used when creating the image (until it reaches the initial `Available` status). -* `delete` - (Defaults to 10 mins) Used when terminating the image. - - ## Attributes Reference - - The following attributes are exported: - -* `id` - ID of the image. + +The following attributes are exported: +* `id` - The ID of the resource supplied above. +* `create_time` - The create time +* `disk_device_mapping` - Snapshot information for the image + * `format` - Image format. + * `import_oss_object` - Import the object of the OSS to which the image file belongs. + * `import_oss_bucket` - Import the bucket of the OSS to which the image belongs. + * `progress` - Copy the progress of the task. + * `remain_time` - For an image being replicated, return the remaining time of the replication task, in seconds. +* `status` - The status of the image. By default, if you do not specify this parameter, only images in the Available state are returned. + + Default value: Available. You can specify multiple values for this parameter. Separate the values with commas (,). + + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration-0-11/resources.html#timeouts) for certain actions: +* `create` - (Defaults to 10 mins) Used when create the Image. +* `delete` - (Defaults to 10 mins) Used when delete the Image. +* `update` - (Defaults to 10 mins) Used when update the Image. ## Import - - image can be imported using the id, e.g. + +ECS Image can be imported using the id, e.g. ```shell -$ terraform import alicloud_image.default m-uf66871ape***yg1q*** -``` +$ terraform import alicloud_image.example +``` \ No newline at end of file