From afbdc8779650232c9b9fcd98e674041e6ec04db8 Mon Sep 17 00:00:00 2001 From: Alexander Kita Date: Tue, 19 Nov 2024 10:33:36 -0600 Subject: [PATCH] Refactor Image Resource --- ibm/service/power/ibm_pi_constants.go | 13 + ibm/service/power/resource_ibm_pi_image.go | 279 ++++++++++----------- website/docs/r/pi_image.html.markdown | 10 +- 3 files changed, 159 insertions(+), 143 deletions(-) diff --git a/ibm/service/power/ibm_pi_constants.go b/ibm/service/power/ibm_pi_constants.go index 77ae403ce0..699877025d 100644 --- a/ibm/service/power/ibm_pi_constants.go +++ b/ibm/service/power/ibm_pi_constants.go @@ -34,9 +34,17 @@ const ( Arg_IBMiCSS = "pi_ibmi_css" Arg_IBMiPHA = "pi_ibmi_pha" Arg_IBMiRDSUsers = "pi_ibmi_rds_users" + Arg_ImageAccessKey = "pi_image_access_key" + Arg_ImageBucketAccess = "pi_image_bucket_access" + Arg_ImageBucketFileName = "pi_image_bucket_file_name" + Arg_ImageBucketName = "pi_image_bucket_name" + Arg_ImageBucketRegion = "pi_image_bucket_region" Arg_ImageID = "pi_image_id" Arg_ImageImportDetails = "pi_image_import_details" Arg_ImageName = "pi_image_name" + Arg_ImageSecretKey = "pi_image_secret_key" + Arg_ImageStoragePool = "pi_image_storage_pool" + Arg_ImageStorageType = "pi_image_storage_type" Arg_InstanceID = "pi_instance_id" Arg_InstanceName = "pi_instance_name" Arg_IPAddress = "pi_ip_address" @@ -522,23 +530,28 @@ const ( State_Found = "Found" State_Inactive = "inactive" State_InProgress = "in progress" + State_inProgress = "inProgress" State_InUse = "in-use" State_NotFound = "not found" State_Pending = "pending" State_PENDING = "PENDING" State_PendingReclamation = "pending_reclamation" State_Provisioning = "provisioning" + State_Queued = "queued" + State_ReadyForProcessing = "readyForProcessing" State_Removed = "removed" State_Removing = "removing" State_Resize = "resize" State_RESIZE = "RESIZE" State_Retry = "retry" + State_Running = "running" State_Shutoff = "shutoff" State_SHUTOFF = "SHUTOFF" State_Stopping = "stopping" State_Up = "up" State_Updating = "updating" State_VerifyResize = "verify_resize" + State_Waiting = "waiting" // Timeout values Timeout_Active = 2 * time.Minute diff --git a/ibm/service/power/resource_ibm_pi_image.go b/ibm/service/power/resource_ibm_pi_image.go index 1814bbd06a..cd92d62185 100644 --- a/ibm/service/power/resource_ibm_pi_image.go +++ b/ibm/service/power/resource_ibm_pi_image.go @@ -11,12 +11,12 @@ import ( "github.com/IBM/go-sdk-core/v5/core" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - st "github.com/IBM-Cloud/power-go-client/clients/instance" + "github.com/IBM-Cloud/power-go-client/clients/instance" "github.com/IBM-Cloud/power-go-client/errors" - "github.com/IBM-Cloud/power-go-client/helpers" "github.com/IBM-Cloud/power-go-client/power/client/p_cloud_images" "github.com/IBM-Cloud/power-go-client/power/models" "github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns" @@ -39,134 +39,129 @@ func ResourceIBMPIImage() *schema.Resource { }, Schema: map[string]*schema.Schema{ - helpers.PICloudInstanceId: { - Type: schema.TypeString, - Required: true, - Description: "PI cloud instance ID", - ForceNew: true, - }, - helpers.PIImageName: { - Type: schema.TypeString, - Required: true, - Description: "Image name", - DiffSuppressFunc: flex.ApplyOnce, - ForceNew: true, + // Arguments + Arg_AffinityInstance: { + ConflictsWith: []string{Arg_AffinityVolume}, + Description: "PVM Instance (ID or Name) to base storage affinity policy against; required if requesting storage affinity and pi_affinity_volume is not provided", + ForceNew: true, + Optional: true, + Type: schema.TypeString, }, - helpers.PIImageId: { - Type: schema.TypeString, - Optional: true, - ExactlyOneOf: []string{helpers.PIImageId, helpers.PIImageBucketName}, - Description: "Instance image id", - DiffSuppressFunc: flex.ApplyOnce, - ConflictsWith: []string{helpers.PIImageBucketName}, - ForceNew: true, + Arg_AffinityPolicy: { + Description: "Affinity policy for image; ignored if pi_image_storage_pool provided; for policy affinity requires one of pi_affinity_instance or pi_affinity_volume to be specified; for policy anti-affinity requires one of pi_anti_affinity_instances or pi_anti_affinity_volumes to be specified", + ForceNew: true, + Optional: true, + Type: schema.TypeString, + ValidateFunc: validate.ValidateAllowedStringValues([]string{Affinity, AntiAffinity}), }, - - // COS import variables - helpers.PIImageBucketName: { - Type: schema.TypeString, - Optional: true, - ExactlyOneOf: []string{helpers.PIImageId, helpers.PIImageBucketName}, - Description: "Cloud Object Storage bucket name; bucket-name[/optional/folder]", - ConflictsWith: []string{helpers.PIImageId}, - RequiredWith: []string{helpers.PIImageBucketRegion, helpers.PIImageBucketFileName}, + Arg_AffinityVolume: { + ConflictsWith: []string{Arg_AffinityInstance}, + Description: "Volume (ID or Name) to base storage affinity policy against; required if requesting affinity and pi_affinity_instance is not provided", ForceNew: true, - }, - helpers.PIImageBucketAccess: { + Optional: true, Type: schema.TypeString, + }, + Arg_AntiAffinityInstances: { + ConflictsWith: []string{Arg_AntiAffinityVolumes}, + Description: "List of pvmInstances to base storage anti-affinity policy against; required if requesting anti-affinity and pi_anti_affinity_volumes is not provided", + Elem: &schema.Schema{Type: schema.TypeString}, + ForceNew: true, Optional: true, - Description: "Indicates if the bucket has public or private access", - Default: "public", - ValidateFunc: validate.ValidateAllowedStringValues([]string{"public", "private"}), - ConflictsWith: []string{helpers.PIImageId}, + Type: schema.TypeList, + }, + Arg_AntiAffinityVolumes: { + ConflictsWith: []string{Arg_AntiAffinityInstances}, + Description: "List of volumes to base storage anti-affinity policy against; required if requesting anti-affinity and pi_anti_affinity_instances is not provided", + Elem: &schema.Schema{Type: schema.TypeString}, ForceNew: true, + Optional: true, + Type: schema.TypeList, }, - helpers.PIImageAccessKey: { + Arg_CloudInstanceID: { + Description: "The GUID of the service instance associated with an account.", + ForceNew: true, + Required: true, Type: schema.TypeString, - Optional: true, + ValidateFunc: validation.NoZeroValues, + }, + Arg_ImageAccessKey: { Description: "Cloud Object Storage access key; required for buckets with private access", ForceNew: true, - Sensitive: true, - RequiredWith: []string{helpers.PIImageSecretKey}, - }, - helpers.PIImageSecretKey: { - Type: schema.TypeString, Optional: true, - Description: "Cloud Object Storage secret key; required for buckets with private access", - ForceNew: true, + RequiredWith: []string{Arg_ImageSecretKey}, Sensitive: true, - RequiredWith: []string{helpers.PIImageAccessKey}, + Type: schema.TypeString, }, - helpers.PIImageBucketRegion: { - Type: schema.TypeString, - Optional: true, - Description: "Cloud Object Storage region", - ConflictsWith: []string{helpers.PIImageId}, - RequiredWith: []string{helpers.PIImageBucketName}, + Arg_ImageBucketAccess: { + ConflictsWith: []string{Arg_ImageID}, + Default: Public, + Description: "Indicates if the bucket has public or private access", ForceNew: true, - }, - helpers.PIImageBucketFileName: { - Type: schema.TypeString, Optional: true, + Type: schema.TypeString, + ValidateFunc: validate.ValidateAllowedStringValues([]string{Public, Private}), + }, + Arg_ImageBucketFileName: { + ConflictsWith: []string{Arg_ImageID}, Description: "Cloud Object Storage image filename", - ConflictsWith: []string{helpers.PIImageId}, - RequiredWith: []string{helpers.PIImageBucketName}, ForceNew: true, - }, - helpers.PIImageStorageType: { - Type: schema.TypeString, - Optional: true, - Description: "Type of storage; If not specified, default is tier3", - ForceNew: true, - }, - helpers.PIImageStoragePool: { - Type: schema.TypeString, - Optional: true, - Description: "Storage pool where the image will be loaded, if provided then pi_affinity_policy will be ignored", - ForceNew: true, - }, - Arg_AffinityPolicy: { - Type: schema.TypeString, - Optional: true, - Description: "Affinity policy for image; ignored if pi_image_storage_pool provided; for policy affinity requires one of pi_affinity_instance or pi_affinity_volume to be specified; for policy anti-affinity requires one of pi_anti_affinity_instances or pi_anti_affinity_volumes to be specified", - ValidateFunc: validate.ValidateAllowedStringValues([]string{"affinity", "anti-affinity"}), - ForceNew: true, - }, - Arg_AffinityVolume: { - Type: schema.TypeString, Optional: true, - Description: "Volume (ID or Name) to base storage affinity policy against; required if requesting affinity and pi_affinity_instance is not provided", - ConflictsWith: []string{Arg_AffinityInstance}, - ForceNew: true, - }, - Arg_AffinityInstance: { + RequiredWith: []string{Arg_ImageBucketName}, Type: schema.TypeString, - Optional: true, - Description: "PVM Instance (ID or Name) to base storage affinity policy against; required if requesting storage affinity and pi_affinity_volume is not provided", - ConflictsWith: []string{Arg_AffinityVolume}, - ForceNew: true, }, - Arg_AntiAffinityVolumes: { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Description: "List of volumes to base storage anti-affinity policy against; required if requesting anti-affinity and pi_anti_affinity_instances is not provided", - ConflictsWith: []string{Arg_AntiAffinityInstances}, + Arg_ImageBucketName: { + ConflictsWith: []string{Arg_ImageID}, + Description: "Cloud Object Storage bucket name; bucket-name[/optional/folder]", + ExactlyOneOf: []string{Arg_ImageID, Arg_ImageBucketName}, ForceNew: true, + Optional: true, + RequiredWith: []string{Arg_ImageBucketRegion, Arg_ImageBucketFileName}, + Type: schema.TypeString, }, - Arg_AntiAffinityInstances: { - Type: schema.TypeList, + Arg_ImageBucketRegion: { + ConflictsWith: []string{Arg_ImageID}, + Description: "Cloud Object Storage region", + ForceNew: true, Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Description: "List of pvmInstances to base storage anti-affinity policy against; required if requesting anti-affinity and pi_anti_affinity_volumes is not provided", - ConflictsWith: []string{Arg_AntiAffinityVolumes}, + RequiredWith: []string{Arg_ImageBucketName}, + Type: schema.TypeString, + }, + Arg_ImageID: { + ConflictsWith: []string{Arg_ImageBucketName}, + Description: "Instance image id", + ExactlyOneOf: []string{Arg_ImageID, Arg_ImageBucketName}, ForceNew: true, + Optional: true, + Type: schema.TypeString, + }, + Arg_ImageName: { + Description: "Image name", + ForceNew: true, + Required: true, + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + Arg_ImageSecretKey: { + Description: "Cloud Object Storage secret key; required for buckets with private access", + ForceNew: true, + Optional: true, + RequiredWith: []string{Arg_ImageAccessKey}, + Sensitive: true, + Type: schema.TypeString, + }, + Arg_ImageStoragePool: { + Description: "Storage pool where the image will be loaded, if provided then pi_affinity_policy will be ignored", + ForceNew: true, + Optional: true, + Type: schema.TypeString, + }, + Arg_ImageStorageType: { + Description: "Type of storage; If not specified, default is tier3", + ForceNew: true, + Optional: true, + Type: schema.TypeString, }, Arg_ImageImportDetails: { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ Attr_LicenseType: { @@ -189,6 +184,10 @@ func ResourceIBMPIImage() *schema.Resource { }, }, }, + ForceNew: true, + MaxItems: 1, + Optional: true, + Type: schema.TypeList, }, Arg_UserTags: { Description: "The user tags attached to this resource.", @@ -198,16 +197,16 @@ func ResourceIBMPIImage() *schema.Resource { Type: schema.TypeSet, }, - // Computed Attribute + // Attribute Attr_CRN: { Computed: true, Description: "The CRN of this resource.", Type: schema.TypeString, }, - "image_id": { - Type: schema.TypeString, + Attr_ImageID: { Computed: true, Description: "Image ID", + Type: schema.TypeString, }, }, } @@ -220,12 +219,12 @@ func resourceIBMPIImageCreate(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(err) } - cloudInstanceID := d.Get(helpers.PICloudInstanceId).(string) - imageName := d.Get(helpers.PIImageName).(string) + cloudInstanceID := d.Get(Arg_CloudInstanceID).(string) + imageName := d.Get(Arg_ImageName).(string) - client := st.NewIBMPIImageClient(ctx, sess, cloudInstanceID) + client := instance.NewIBMPIImageClient(ctx, sess, cloudInstanceID) // image copy - if v, ok := d.GetOk(helpers.PIImageId); ok { + if v, ok := d.GetOk(Arg_ImageID); ok { imageid := v.(string) source := "root-project" var body = &models.CreateImage{ @@ -262,11 +261,11 @@ func resourceIBMPIImageCreate(ctx context.Context, d *schema.ResourceData, meta } // COS image import - if v, ok := d.GetOk(helpers.PIImageBucketName); ok { + if v, ok := d.GetOk(Arg_ImageBucketName); ok { bucketName := v.(string) - bucketImageFileName := d.Get(helpers.PIImageBucketFileName).(string) - bucketRegion := d.Get(helpers.PIImageBucketRegion).(string) - bucketAccess := d.Get(helpers.PIImageBucketAccess).(string) + bucketImageFileName := d.Get(Arg_ImageBucketFileName).(string) + bucketRegion := d.Get(Arg_ImageBucketRegion).(string) + bucketAccess := d.Get(Arg_ImageBucketAccess).(string) body := &models.CreateCosImageImportJob{ ImageName: &imageName, @@ -276,17 +275,17 @@ func resourceIBMPIImageCreate(ctx context.Context, d *schema.ResourceData, meta Region: &bucketRegion, } - if v, ok := d.GetOk(helpers.PIImageAccessKey); ok { + if v, ok := d.GetOk(Arg_ImageAccessKey); ok { body.AccessKey = v.(string) } - if v, ok := d.GetOk(helpers.PIImageSecretKey); ok { + if v, ok := d.GetOk(Arg_ImageSecretKey); ok { body.SecretKey = v.(string) } - if v, ok := d.GetOk(helpers.PIImageStorageType); ok { + if v, ok := d.GetOk(Arg_ImageStorageType); ok { body.StorageType = v.(string) } - if v, ok := d.GetOk(helpers.PIImageStoragePool); ok { + if v, ok := d.GetOk(Arg_ImageStoragePool); ok { body.StoragePool = v.(string) } if ap, ok := d.GetOk(Arg_AffinityPolicy); ok { @@ -295,7 +294,7 @@ func resourceIBMPIImageCreate(ctx context.Context, d *schema.ResourceData, meta AffinityPolicy: &policy, } - if policy == "affinity" { + if policy == Affinity { if av, ok := d.GetOk(Arg_AffinityVolume); ok { afvol := av.(string) affinity.AffinityVolume = &afvol @@ -333,7 +332,7 @@ func resourceIBMPIImageCreate(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(err) } - jobClient := st.NewIBMPIJobClient(ctx, sess, cloudInstanceID) + jobClient := instance.NewIBMPIJobClient(ctx, sess, cloudInstanceID) _, err = waitForIBMPIJobCompleted(ctx, jobClient, *imageResponse.ID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -371,7 +370,7 @@ func resourceIBMPIImageRead(ctx context.Context, d *schema.ResourceData, meta in return diag.FromErr(err) } - imageC := st.NewIBMPIImageClient(ctx, sess, cloudInstanceID) + imageC := instance.NewIBMPIImageClient(ctx, sess, cloudInstanceID) imagedata, err := imageC.Get(imageID) if err != nil { uErr := errors.Unwrap(err) @@ -394,8 +393,8 @@ func resourceIBMPIImageRead(ctx context.Context, d *schema.ResourceData, meta in } d.Set(Arg_UserTags, tags) } - d.Set("image_id", imageid) - d.Set(helpers.PICloudInstanceId, cloudInstanceID) + d.Set(Attr_ImageID, imageid) + d.Set(Arg_CloudInstanceID, cloudInstanceID) return nil } @@ -430,7 +429,7 @@ func resourceIBMPIImageDelete(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(err) } - imageC := st.NewIBMPIImageClient(ctx, sess, cloudInstanceID) + imageC := instance.NewIBMPIImageClient(ctx, sess, cloudInstanceID) err = imageC.Delete(imageID) if err != nil { return diag.FromErr(err) @@ -440,12 +439,12 @@ func resourceIBMPIImageDelete(ctx context.Context, d *schema.ResourceData, meta return nil } -func isWaitForIBMPIImageAvailable(ctx context.Context, client *st.IBMPIImageClient, id string, timeout time.Duration) (interface{}, error) { +func isWaitForIBMPIImageAvailable(ctx context.Context, client *instance.IBMPIImageClient, id string, timeout time.Duration) (interface{}, error) { log.Printf("Waiting for Power Image (%s) to be available.", id) - stateConf := &resource.StateChangeConf{ - Pending: []string{"retry", helpers.PIImageQueStatus}, - Target: []string{helpers.PIImageActiveStatus}, + stateConf := &retry.StateChangeConf{ + Pending: []string{State_Retry, State_Queued}, + Target: []string{State_Active}, Refresh: isIBMPIImageRefreshFunc(ctx, client, id), Timeout: timeout, Delay: 20 * time.Second, @@ -455,7 +454,7 @@ func isWaitForIBMPIImageAvailable(ctx context.Context, client *st.IBMPIImageClie return stateConf.WaitForStateContext(ctx) } -func isIBMPIImageRefreshFunc(ctx context.Context, client *st.IBMPIImageClient, id string) resource.StateRefreshFunc { +func isIBMPIImageRefreshFunc(ctx context.Context, client *instance.IBMPIImageClient, id string) retry.StateRefreshFunc { log.Printf("Calling the isIBMPIImageRefreshFunc Refresh Function....") return func() (interface{}, string, error) { @@ -464,18 +463,18 @@ func isIBMPIImageRefreshFunc(ctx context.Context, client *st.IBMPIImageClient, i return nil, "", err } - if image.State == "active" { - return image, helpers.PIImageActiveStatus, nil + if image.State == State_Active { + return image, State_Active, nil } - return image, helpers.PIImageQueStatus, nil + return image, State_Queued, nil } } -func waitForIBMPIJobCompleted(ctx context.Context, client *st.IBMPIJobClient, jobID string, timeout time.Duration) (interface{}, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{helpers.JobStatusQueued, helpers.JobStatusReadyForProcessing, helpers.JobStatusInProgress, helpers.JobStatusRunning, helpers.JobStatusWaiting}, - Target: []string{helpers.JobStatusCompleted, helpers.JobStatusFailed}, +func waitForIBMPIJobCompleted(ctx context.Context, client *instance.IBMPIJobClient, jobID string, timeout time.Duration) (interface{}, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{State_Queued, State_ReadyForProcessing, State_inProgress, State_Running, State_Waiting}, + Target: []string{State_Completed, State_Failed}, Refresh: func() (interface{}, string, error) { job, err := client.Get(jobID) if err != nil { @@ -486,9 +485,9 @@ func waitForIBMPIJobCompleted(ctx context.Context, client *st.IBMPIJobClient, jo log.Printf("[DEBUG] get job failed with empty response") return nil, "", fmt.Errorf("failed to get job status for job id %s", jobID) } - if *job.Status.State == helpers.JobStatusFailed { + if *job.Status.State == State_Failed { log.Printf("[DEBUG] job status failed with message: %v", job.Status.Message) - return nil, helpers.JobStatusFailed, fmt.Errorf("job status failed for job id %s with message: %v", jobID, job.Status.Message) + return nil, State_Failed, fmt.Errorf("job status failed for job id %s with message: %v", jobID, job.Status.Message) } return job, *job.Status.State, nil }, diff --git a/website/docs/r/pi_image.html.markdown b/website/docs/r/pi_image.html.markdown index c65d404edd..79f928df62 100644 --- a/website/docs/r/pi_image.html.markdown +++ b/website/docs/r/pi_image.html.markdown @@ -1,5 +1,4 @@ --- - subcategory: "Power Systems" layout: "ibm" page_title: "IBM: pi_image" @@ -59,8 +58,8 @@ Example usage: The ibm_pi_image provides the following [timeouts](https://www.terraform.io/docs/language/resources/syntax.html) configuration options: -- **create** - (Default 60 minutes) Used for creating an image. -- **delete** - (Default 60 minutes) Used for deleting an image. +- **create** - (Default 60 minutes) Used for creating image. +- **delete** - (Default 60 minutes) Used for deleting image. ## Argument reference @@ -83,8 +82,13 @@ Review the argument references that you can specify for your resource. - `pi_image_bucket_access` - (Optional, String) Indicates if the bucket has public or private access. The default value is `public`. - `pi_image_bucket_file_name` - (Optional, String) Cloud Object Storage image filename - `pi_image_bucket_file_name` is required with `pi_image_bucket_name` +- `pi_image_bucket_name` - (Optional, String) Cloud Object Storage bucket name; `bucket-name[/optional/folder]` + - Either `pi_image_bucket_name` or `pi_image_id` is required. - `pi_image_bucket_region` - (Optional, String) Cloud Object Storage region. Supported COS regions are: `au-syd`, `br-sao`, `ca-tor`, `eu-de`, `eu-es`, `eu-gb`, `jp-osa`, `jp-tok`, `us-east`, `us-south`. - `pi_image_bucket_region` is required with `pi_image_bucket_name` +- `pi_image_id` - (Optional, String) Image ID of existing source image; required for copy image. + - Either `pi_image_id` or `pi_image_bucket_name` is required. +- `pi_image_name` - (Required, String) The name of an image. - `pi_image_secret_key` - (Optional, String, Sensitive) Cloud Object Storage secret key; required for buckets with private access. - `pi_image_secret_key` is required with `pi_image_access_key` - `pi_image_storage_pool` - (Optional, String) Storage pool where the image will be loaded, if provided then `pi_affinity_policy` will be ignored. Used only when importing an image from cloud storage.