Repository: ovh/terraform-provider-ovh

Issue:
[NEW] Datasource: ovh_cloud_project_images


### Description

GET /cloud/project/{serviceName}/image


### Requested Resource(s) and/or Data Source(s)


* ovh_cloud_project_images


Hints from issue comments:

Solution patch:
diff --git a/docs/data-sources/cloud_project_image.md b/docs/data-sources/cloud_project_image.md
new file mode 100644
index 00000000..7cd3b630
--- /dev/null
+++ b/docs/data-sources/cloud_project_image.md
@@ -0,0 +1,41 @@
+---
+subcategory : "Cloud Project"
+---
+
+# ovh_cloud_project_image (Data Source)
+
+Get information about an image in the given public cloud project.
+
+## Example Usage
+
+```terraform
+data "ovh_cloud_project_image" "image" {
+  service_name = "<public cloud project ID>"
+  image_id     = "<image ID>"
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `image_id` (String) Image ID
+- `service_name` (String) Public cloud project ID
+
+### Read-Only
+
+- `creation_date` (String) Image creation date
+- `flavor_type` (String) Image usable only for this type of flavor if not null
+- `id` (String) Image ID
+- `min_disk` (Number) Minimum disks required to use image
+- `min_ram` (Number) Minimum RAM required to use image
+- `name` (String) Image name
+- `plan_code` (String) Order plan code
+- `region` (String) Image region
+- `size` (Number) Image size (in GiB)
+- `status` (String) Image status
+- `tags` (List of String) Tags about the image
+- `type` (String) Image type
+- `user` (String) User to connect with
+- `visibility` (String) Image visibility
diff --git a/docs/data-sources/cloud_project_images.md b/docs/data-sources/cloud_project_images.md
new file mode 100644
index 00000000..51c3d426
--- /dev/null
+++ b/docs/data-sources/cloud_project_images.md
@@ -0,0 +1,54 @@
+---
+subcategory : "Cloud Project"
+---
+
+# ovh_cloud_project_images (Data Source)
+
+Get available images in the given public cloud project.
+
+## Example Usage
+
+```terraform
+data "ovh_cloud_project_images" "images" {
+  service_name = "<public cloud project ID>"
+  region       = "WAW1"
+  os_type      = "linux"
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `service_name` (String) Public cloud project ID
+
+### Optional
+
+- `flavor_type` (String) Get compatible images with flavor type
+- `os_type` (String) Image OS (Allowed values: baremetal-linux ┃ bsd ┃ linux ┃ windows)
+- `region` (String) Image region
+
+### Read-Only
+
+- `images` (Attributes Set) (see [below for nested schema](#nestedatt--images))
+
+<a id="nestedatt--images"></a>
+### Nested Schema for `images`
+
+Read-Only:
+
+- `creation_date` (String) Image creation date
+- `flavor_type` (String) Image usable only for this type of flavor if not null
+- `id` (String) Image ID
+- `min_disk` (Number) Minimum disks required to use image
+- `min_ram` (Number) Minimum RAM required to use image
+- `name` (String) Image name
+- `plan_code` (String) Order plan code
+- `region` (String) Image region
+- `size` (Number) Image size (in GiB)
+- `status` (String) Image status
+- `tags` (List of String) Tags about the image
+- `type` (String) Image type
+- `user` (String) User to connect with
+- `visibility` (String) Image visibility
diff --git a/examples/data-sources/ovh_cloud_project_image/data-source.tf b/examples/data-sources/ovh_cloud_project_image/data-source.tf
new file mode 100644
index 00000000..0c3faf37
--- /dev/null
+++ b/examples/data-sources/ovh_cloud_project_image/data-source.tf
@@ -0,0 +1,4 @@
+data "ovh_cloud_project_image" "image" {
+  service_name = "<public cloud project ID>"
+  image_id     = "<image ID>"
+}
diff --git a/examples/data-sources/ovh_cloud_project_images/data-source.tf b/examples/data-sources/ovh_cloud_project_images/data-source.tf
new file mode 100644
index 00000000..5a1975a7
--- /dev/null
+++ b/examples/data-sources/ovh_cloud_project_images/data-source.tf
@@ -0,0 +1,5 @@
+data "ovh_cloud_project_images" "images" {
+  service_name = "<public cloud project ID>"
+  region       = "WAW1"
+  os_type      = "linux"
+}
\ No newline at end of file
diff --git a/ovh/data_cloud_project_image.go b/ovh/data_cloud_project_image.go
new file mode 100644
index 00000000..e906ad98
--- /dev/null
+++ b/ovh/data_cloud_project_image.go
@@ -0,0 +1,68 @@
+package ovh
+
+import (
+	"context"
+	"fmt"
+	"net/url"
+
+	"github.com/hashicorp/terraform-plugin-framework/datasource"
+)
+
+var _ datasource.DataSourceWithConfigure = (*cloudProjectImageDataSource)(nil)
+
+func NewCloudProjectImageDataSource() datasource.DataSource {
+	return &cloudProjectImageDataSource{}
+}
+
+type cloudProjectImageDataSource struct {
+	config *Config
+}
+
+func (d *cloudProjectImageDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = req.ProviderTypeName + "_cloud_project_image"
+}
+
+func (d *cloudProjectImageDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+	if req.ProviderData == nil {
+		return
+	}
+
+	config, ok := req.ProviderData.(*Config)
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Unexpected Data Source Configure Type",
+			fmt.Sprintf("Expected *Config, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	d.config = config
+}
+
+func (d *cloudProjectImageDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	resp.Schema = CloudProjectImageDataSourceSchema(ctx)
+}
+
+func (d *cloudProjectImageDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var data CloudProjectImageModel
+
+	// Read Terraform configuration data into the model
+	resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	// Read API call logic
+	endpoint := "/cloud/project/" + url.PathEscape(data.ServiceName.ValueString()) + "/image/" + url.PathEscape(data.ImageId.ValueString())
+	if err := d.config.OVHClient.Get(endpoint, &data); err != nil {
+		resp.Diagnostics.AddError(
+			fmt.Sprintf("Error calling Get %s", endpoint),
+			err.Error(),
+		)
+		return
+	}
+
+	// Save data into Terraform state
+	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/ovh/data_cloud_project_image_gen.go b/ovh/data_cloud_project_image_gen.go
new file mode 100644
index 00000000..4fa167c8
--- /dev/null
+++ b/ovh/data_cloud_project_image_gen.go
@@ -0,0 +1,203 @@
+// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
+
+package ovh
+
+import (
+	"context"
+	ovhtypes "github.com/ovh/terraform-provider-ovh/v2/ovh/types"
+
+	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+)
+
+func CloudProjectImageDataSourceSchema(ctx context.Context) schema.Schema {
+	attrs := map[string]schema.Attribute{
+		"creation_date": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Image creation date",
+			MarkdownDescription: "Image creation date",
+		},
+		"flavor_type": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Image usable only for this type of flavor if not null",
+			MarkdownDescription: "Image usable only for this type of flavor if not null",
+		},
+		"id": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Image id",
+			MarkdownDescription: "Image id",
+		},
+		"image_id": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Required:            true,
+			Description:         "Image ID",
+			MarkdownDescription: "Image ID",
+		},
+		"min_disk": schema.Int64Attribute{
+			CustomType:          ovhtypes.TfInt64Type{},
+			Computed:            true,
+			Description:         "Minimum disks required to use image",
+			MarkdownDescription: "Minimum disks required to use image",
+		},
+		"min_ram": schema.Int64Attribute{
+			CustomType:          ovhtypes.TfInt64Type{},
+			Computed:            true,
+			Description:         "Minimum RAM required to use image",
+			MarkdownDescription: "Minimum RAM required to use image",
+		},
+		"name": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Image name",
+			MarkdownDescription: "Image name",
+		},
+		"plan_code": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Order plan code",
+			MarkdownDescription: "Order plan code",
+		},
+		"region": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Image region",
+			MarkdownDescription: "Image region",
+		},
+		"service_name": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Required:            true,
+			Description:         "Service name",
+			MarkdownDescription: "Service name",
+		},
+		"size": schema.NumberAttribute{
+			CustomType:          ovhtypes.TfNumberType{},
+			Computed:            true,
+			Description:         "Image size (in GiB)",
+			MarkdownDescription: "Image size (in GiB)",
+		},
+		"status": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Image status",
+			MarkdownDescription: "Image status",
+		},
+		"tags": schema.ListAttribute{
+			CustomType:          ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx),
+			Computed:            true,
+			Description:         "Tags about the image",
+			MarkdownDescription: "Tags about the image",
+		},
+		"type": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Image type",
+			MarkdownDescription: "Image type",
+		},
+		"user": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "User to connect with",
+			MarkdownDescription: "User to connect with",
+		},
+		"visibility": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Computed:            true,
+			Description:         "Image visibility",
+			MarkdownDescription: "Image visibility",
+		},
+	}
+
+	return schema.Schema{
+		Description: "Get image",
+		Attributes:  attrs,
+	}
+}
+
+type CloudProjectImageModel struct {
+	CreationDate ovhtypes.TfStringValue                             `tfsdk:"creation_date" json:"creationDate"`
+	FlavorType   ovhtypes.TfStringValue                             `tfsdk:"flavor_type" json:"flavorType"`
+	Id           ovhtypes.TfStringValue                             `tfsdk:"id" json:"id"`
+	ImageId      ovhtypes.TfStringValue                             `tfsdk:"image_id" json:"imageId"`
+	MinDisk      ovhtypes.TfInt64Value                              `tfsdk:"min_disk" json:"minDisk"`
+	MinRam       ovhtypes.TfInt64Value                              `tfsdk:"min_ram" json:"minRam"`
+	Name         ovhtypes.TfStringValue                             `tfsdk:"name" json:"name"`
+	PlanCode     ovhtypes.TfStringValue                             `tfsdk:"plan_code" json:"planCode"`
+	Region       ovhtypes.TfStringValue                             `tfsdk:"region" json:"region"`
+	ServiceName  ovhtypes.TfStringValue                             `tfsdk:"service_name" json:"serviceName"`
+	Size         ovhtypes.TfNumberValue                             `tfsdk:"size" json:"size"`
+	Status       ovhtypes.TfStringValue                             `tfsdk:"status" json:"status"`
+	Tags         ovhtypes.TfListNestedValue[ovhtypes.TfStringValue] `tfsdk:"tags" json:"tags"`
+	Type         ovhtypes.TfStringValue                             `tfsdk:"type" json:"type"`
+	User         ovhtypes.TfStringValue                             `tfsdk:"user" json:"user"`
+	Visibility   ovhtypes.TfStringValue                             `tfsdk:"visibility" json:"visibility"`
+}
+
+func (v *CloudProjectImageModel) MergeWith(other *CloudProjectImageModel) {
+
+	if (v.CreationDate.IsUnknown() || v.CreationDate.IsNull()) && !other.CreationDate.IsUnknown() {
+		v.CreationDate = other.CreationDate
+	}
+
+	if (v.FlavorType.IsUnknown() || v.FlavorType.IsNull()) && !other.FlavorType.IsUnknown() {
+		v.FlavorType = other.FlavorType
+	}
+
+	if (v.Id.IsUnknown() || v.Id.IsNull()) && !other.Id.IsUnknown() {
+		v.Id = other.Id
+	}
+
+	if (v.ImageId.IsUnknown() || v.ImageId.IsNull()) && !other.ImageId.IsUnknown() {
+		v.ImageId = other.ImageId
+	}
+
+	if (v.MinDisk.IsUnknown() || v.MinDisk.IsNull()) && !other.MinDisk.IsUnknown() {
+		v.MinDisk = other.MinDisk
+	}
+
+	if (v.MinRam.IsUnknown() || v.MinRam.IsNull()) && !other.MinRam.IsUnknown() {
+		v.MinRam = other.MinRam
+	}
+
+	if (v.Name.IsUnknown() || v.Name.IsNull()) && !other.Name.IsUnknown() {
+		v.Name = other.Name
+	}
+
+	if (v.PlanCode.IsUnknown() || v.PlanCode.IsNull()) && !other.PlanCode.IsUnknown() {
+		v.PlanCode = other.PlanCode
+	}
+
+	if (v.Region.IsUnknown() || v.Region.IsNull()) && !other.Region.IsUnknown() {
+		v.Region = other.Region
+	}
+
+	if (v.ServiceName.IsUnknown() || v.ServiceName.IsNull()) && !other.ServiceName.IsUnknown() {
+		v.ServiceName = other.ServiceName
+	}
+
+	if (v.Size.IsUnknown() || v.Size.IsNull()) && !other.Size.IsUnknown() {
+		v.Size = other.Size
+	}
+
+	if (v.Status.IsUnknown() || v.Status.IsNull()) && !other.Status.IsUnknown() {
+		v.Status = other.Status
+	}
+
+	if (v.Tags.IsUnknown() || v.Tags.IsNull()) && !other.Tags.IsUnknown() {
+		v.Tags = other.Tags
+	}
+
+	if (v.Type.IsUnknown() || v.Type.IsNull()) && !other.Type.IsUnknown() {
+		v.Type = other.Type
+	}
+
+	if (v.User.IsUnknown() || v.User.IsNull()) && !other.User.IsUnknown() {
+		v.User = other.User
+	}
+
+	if (v.Visibility.IsUnknown() || v.Visibility.IsNull()) && !other.Visibility.IsUnknown() {
+		v.Visibility = other.Visibility
+	}
+
+}
diff --git a/ovh/data_cloud_project_images.go b/ovh/data_cloud_project_images.go
new file mode 100644
index 00000000..37d1f47f
--- /dev/null
+++ b/ovh/data_cloud_project_images.go
@@ -0,0 +1,77 @@
+package ovh
+
+import (
+	"context"
+	"fmt"
+	"net/url"
+
+	"github.com/hashicorp/terraform-plugin-framework/datasource"
+)
+
+var _ datasource.DataSourceWithConfigure = (*cloudProjectImagesDataSource)(nil)
+
+func NewCloudProjectImagesDataSource() datasource.DataSource {
+	return &cloudProjectImagesDataSource{}
+}
+
+type cloudProjectImagesDataSource struct {
+	config *Config
+}
+
+func (d *cloudProjectImagesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = req.ProviderTypeName + "_cloud_project_images"
+}
+
+func (d *cloudProjectImagesDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+	if req.ProviderData == nil {
+		return
+	}
+
+	config, ok := req.ProviderData.(*Config)
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Unexpected Data Source Configure Type",
+			fmt.Sprintf("Expected *Config, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	d.config = config
+}
+
+func (d *cloudProjectImagesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	resp.Schema = CloudProjectImagesDataSourceSchema(ctx)
+}
+
+func (d *cloudProjectImagesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var data CloudProjectImagesModel
+
+	// Read Terraform configuration data into the model
+	resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	// Add query parameters
+	queryParams := url.Values{}
+	if !data.FlavorType.IsNull() && !data.FlavorType.IsUnknown() {
+		queryParams.Add("flavorType", data.FlavorType.ValueString())
+	}
+	if !data.OsType.IsNull() && !data.OsType.IsUnknown() {
+		queryParams.Add("osType", data.OsType.ValueString())
+	}
+	if !data.Region.IsNull() && !data.Region.IsUnknown() {
+		queryParams.Add("region", data.Region.ValueString())
+	}
+
+	// Read API call logic
+	endpoint := "/cloud/project/" + url.PathEscape(data.ServiceName.ValueString()) + "/image?" + queryParams.Encode()
+	if err := d.config.OVHClient.Get(endpoint, &data.Images); err != nil {
+		resp.Diagnostics.AddError(fmt.Sprintf("Error calling Get %s", endpoint), err.Error())
+		return
+	}
+
+	// Save data into Terraform state
+	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/ovh/data_cloud_project_images_gen.go b/ovh/data_cloud_project_images_gen.go
new file mode 100644
index 00000000..1e5a8536
--- /dev/null
+++ b/ovh/data_cloud_project_images_gen.go
@@ -0,0 +1,1331 @@
+// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
+
+package ovh
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+	"github.com/hashicorp/terraform-plugin-framework/attr"
+	"github.com/hashicorp/terraform-plugin-framework/diag"
+	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+	"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+	"github.com/hashicorp/terraform-plugin-go/tftypes"
+	ovhtypes "github.com/ovh/terraform-provider-ovh/v2/ovh/types"
+	"strings"
+
+	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+)
+
+func CloudProjectImagesDataSourceSchema(ctx context.Context) schema.Schema {
+	attrs := map[string]schema.Attribute{
+		"images": schema.SetNestedAttribute{
+			NestedObject: schema.NestedAttributeObject{
+				Attributes: map[string]schema.Attribute{
+					"creation_date": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Image creation date",
+						MarkdownDescription: "Image creation date",
+					},
+					"flavor_type": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Image usable only for this type of flavor if not null",
+						MarkdownDescription: "Image usable only for this type of flavor if not null",
+					},
+					"id": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Image id",
+						MarkdownDescription: "Image id",
+					},
+					"min_disk": schema.Int64Attribute{
+						CustomType:          ovhtypes.TfInt64Type{},
+						Computed:            true,
+						Description:         "Minimum disks required to use image",
+						MarkdownDescription: "Minimum disks required to use image",
+					},
+					"min_ram": schema.Int64Attribute{
+						CustomType:          ovhtypes.TfInt64Type{},
+						Computed:            true,
+						Description:         "Minimum RAM required to use image",
+						MarkdownDescription: "Minimum RAM required to use image",
+					},
+					"name": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Image name",
+						MarkdownDescription: "Image name",
+					},
+					"plan_code": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Order plan code",
+						MarkdownDescription: "Order plan code",
+					},
+					"region": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Image region",
+						MarkdownDescription: "Image region",
+					},
+					"size": schema.NumberAttribute{
+						CustomType:          ovhtypes.TfNumberType{},
+						Computed:            true,
+						Description:         "Image size (in GiB)",
+						MarkdownDescription: "Image size (in GiB)",
+					},
+					"status": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Image status",
+						MarkdownDescription: "Image status",
+					},
+					"tags": schema.ListAttribute{
+						CustomType:          ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx),
+						Computed:            true,
+						Description:         "Tags about the image",
+						MarkdownDescription: "Tags about the image",
+					},
+					"type": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Image type",
+						MarkdownDescription: "Image type",
+					},
+					"user": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "User to connect with",
+						MarkdownDescription: "User to connect with",
+					},
+					"visibility": schema.StringAttribute{
+						CustomType:          ovhtypes.TfStringType{},
+						Computed:            true,
+						Description:         "Image visibility",
+						MarkdownDescription: "Image visibility",
+					},
+				},
+				CustomType: CloudProjectImagesType{
+					ObjectType: types.ObjectType{
+						AttrTypes: CloudProjectImagesValue{}.AttributeTypes(ctx),
+					},
+				},
+			},
+			CustomType: ovhtypes.NewTfListNestedType[CloudProjectImagesValue](ctx),
+			Computed:   true,
+		},
+		"flavor_type": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Optional:            true,
+			Computed:            true,
+			Description:         "Get compatible images with flavor type",
+			MarkdownDescription: "Get compatible images with flavor type",
+		},
+		"os_type": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Optional:            true,
+			Computed:            true,
+			Description:         "Image OS",
+			MarkdownDescription: "Image OS",
+			Validators: []validator.String{
+				stringvalidator.OneOf(
+					"baremetal-linux",
+					"bsd",
+					"linux",
+					"windows",
+				),
+			},
+		},
+		"region": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Optional:            true,
+			Computed:            true,
+			Description:         "Image region",
+			MarkdownDescription: "Image region",
+		},
+		"service_name": schema.StringAttribute{
+			CustomType:          ovhtypes.TfStringType{},
+			Required:            true,
+			Description:         "Service name",
+			MarkdownDescription: "Service name",
+		},
+	}
+
+	return schema.Schema{
+		Description: "Get images",
+		Attributes:  attrs,
+	}
+}
+
+type CloudProjectImagesModel struct {
+	Images      ovhtypes.TfListNestedValue[CloudProjectImagesValue] `tfsdk:"images" json:"images"`
+	FlavorType  ovhtypes.TfStringValue                              `tfsdk:"flavor_type" json:"flavorType"`
+	OsType      ovhtypes.TfStringValue                              `tfsdk:"os_type" json:"osType"`
+	Region      ovhtypes.TfStringValue                              `tfsdk:"region" json:"region"`
+	ServiceName ovhtypes.TfStringValue                              `tfsdk:"service_name" json:"serviceName"`
+}
+
+func (v *CloudProjectImagesModel) MergeWith(other *CloudProjectImagesModel) {
+	if (v.Images.IsUnknown() || v.Images.IsNull()) && !other.Images.IsUnknown() {
+		v.Images = other.Images
+	}
+
+	if (v.FlavorType.IsUnknown() || v.FlavorType.IsNull()) && !other.FlavorType.IsUnknown() {
+		v.FlavorType = other.FlavorType
+	}
+
+	if (v.OsType.IsUnknown() || v.OsType.IsNull()) && !other.OsType.IsUnknown() {
+		v.OsType = other.OsType
+	}
+
+	if (v.Region.IsUnknown() || v.Region.IsNull()) && !other.Region.IsUnknown() {
+		v.Region = other.Region
+	}
+
+	if (v.ServiceName.IsUnknown() || v.ServiceName.IsNull()) && !other.ServiceName.IsUnknown() {
+		v.ServiceName = other.ServiceName
+	}
+}
+
+var _ basetypes.ObjectTypable = CloudProjectImagesType{}
+
+type CloudProjectImagesType struct {
+	basetypes.ObjectType
+}
+
+func (t CloudProjectImagesType) Equal(o attr.Type) bool {
+	other, ok := o.(CloudProjectImagesType)
+
+	if !ok {
+		return false
+	}
+
+	return t.ObjectType.Equal(other.ObjectType)
+}
+
+func (t CloudProjectImagesType) String() string {
+	return "CloudProjectImagesType"
+}
+
+func (t CloudProjectImagesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	attributes := in.Attributes()
+
+	creationDateAttribute, ok := attributes["creation_date"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`creation_date is missing from object`)
+
+		return nil, diags
+	}
+
+	creationDateVal, ok := creationDateAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`creation_date expected to be ovhtypes.TfStringValue, was: %T`, creationDateAttribute))
+	}
+
+	flavorTypeAttribute, ok := attributes["flavor_type"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`flavor_type is missing from object`)
+
+		return nil, diags
+	}
+
+	flavorTypeVal, ok := flavorTypeAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`flavor_type expected to be ovhtypes.TfStringValue, was: %T`, flavorTypeAttribute))
+	}
+
+	idAttribute, ok := attributes["id"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`id is missing from object`)
+
+		return nil, diags
+	}
+
+	idVal, ok := idAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`id expected to be ovhtypes.TfStringValue, was: %T`, idAttribute))
+	}
+
+	minDiskAttribute, ok := attributes["min_disk"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`min_disk is missing from object`)
+
+		return nil, diags
+	}
+
+	minDiskVal, ok := minDiskAttribute.(ovhtypes.TfInt64Value)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`min_disk expected to be ovhtypes.TfInt64Value, was: %T`, minDiskAttribute))
+	}
+
+	minRamAttribute, ok := attributes["min_ram"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`min_ram is missing from object`)
+
+		return nil, diags
+	}
+
+	minRamVal, ok := minRamAttribute.(ovhtypes.TfInt64Value)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`min_ram expected to be ovhtypes.TfInt64Value, was: %T`, minRamAttribute))
+	}
+
+	nameAttribute, ok := attributes["name"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`name is missing from object`)
+
+		return nil, diags
+	}
+
+	nameVal, ok := nameAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`name expected to be ovhtypes.TfStringValue, was: %T`, nameAttribute))
+	}
+
+	planCodeAttribute, ok := attributes["plan_code"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`plan_code is missing from object`)
+
+		return nil, diags
+	}
+
+	planCodeVal, ok := planCodeAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`plan_code expected to be ovhtypes.TfStringValue, was: %T`, planCodeAttribute))
+	}
+
+	regionAttribute, ok := attributes["region"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`region is missing from object`)
+
+		return nil, diags
+	}
+
+	regionVal, ok := regionAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`region expected to be ovhtypes.TfStringValue, was: %T`, regionAttribute))
+	}
+
+	sizeAttribute, ok := attributes["size"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`size is missing from object`)
+
+		return nil, diags
+	}
+
+	sizeVal, ok := sizeAttribute.(ovhtypes.TfNumberValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`size expected to be ovhtypes.TfNumberValue, was: %T`, sizeAttribute))
+	}
+
+	statusAttribute, ok := attributes["status"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`status is missing from object`)
+
+		return nil, diags
+	}
+
+	statusVal, ok := statusAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`status expected to be ovhtypes.TfStringValue, was: %T`, statusAttribute))
+	}
+
+	tagsAttribute, ok := attributes["tags"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`tags is missing from object`)
+
+		return nil, diags
+	}
+
+	tagsVal, ok := tagsAttribute.(ovhtypes.TfListNestedValue[ovhtypes.TfStringValue])
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`tags expected to be ovhtypes.TfListNestedValue[ovhtypes.TfStringValue], was: %T`, tagsAttribute))
+	}
+
+	typeAttribute, ok := attributes["type"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`type is missing from object`)
+
+		return nil, diags
+	}
+
+	typeVal, ok := typeAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`type expected to be ovhtypes.TfStringValue, was: %T`, typeAttribute))
+	}
+
+	userAttribute, ok := attributes["user"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`user is missing from object`)
+
+		return nil, diags
+	}
+
+	userVal, ok := userAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`user expected to be ovhtypes.TfStringValue, was: %T`, userAttribute))
+	}
+
+	visibilityAttribute, ok := attributes["visibility"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`visibility is missing from object`)
+
+		return nil, diags
+	}
+
+	visibilityVal, ok := visibilityAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`visibility expected to be ovhtypes.TfStringValue, was: %T`, visibilityAttribute))
+	}
+
+	if diags.HasError() {
+		return nil, diags
+	}
+
+	return CloudProjectImagesValue{
+		CreationDate:           creationDateVal,
+		FlavorType:             flavorTypeVal,
+		Id:                     idVal,
+		MinDisk:                minDiskVal,
+		MinRam:                 minRamVal,
+		Name:                   nameVal,
+		PlanCode:               planCodeVal,
+		Region:                 regionVal,
+		Size:                   sizeVal,
+		Status:                 statusVal,
+		Tags:                   tagsVal,
+		CloudProjectImagesType: typeVal,
+		User:                   userVal,
+		Visibility:             visibilityVal,
+		state:                  attr.ValueStateKnown,
+	}, diags
+}
+
+func NewCloudProjectImagesValueNull() CloudProjectImagesValue {
+	return CloudProjectImagesValue{
+		state: attr.ValueStateNull,
+	}
+}
+
+func NewCloudProjectImagesValueUnknown() CloudProjectImagesValue {
+	return CloudProjectImagesValue{
+		state: attr.ValueStateUnknown,
+	}
+}
+
+func NewCloudProjectImagesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (CloudProjectImagesValue, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
+	ctx := context.Background()
+
+	for name, attributeType := range attributeTypes {
+		attribute, ok := attributes[name]
+
+		if !ok {
+			diags.AddError(
+				"Missing CloudProjectImagesValue Attribute Value",
+				"While creating a CloudProjectImagesValue value, a missing attribute value was detected. "+
+					"A CloudProjectImagesValue must contain values for all attributes, even if null or unknown. "+
+					"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+					fmt.Sprintf("CloudProjectImagesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
+			)
+
+			continue
+		}
+
+		if !attributeType.Equal(attribute.Type(ctx)) {
+			diags.AddError(
+				"Invalid CloudProjectImagesValue Attribute Type",
+				"While creating a CloudProjectImagesValue value, an invalid attribute value was detected. "+
+					"A CloudProjectImagesValue must use a matching attribute type for the value. "+
+					"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+					fmt.Sprintf("CloudProjectImagesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
+					fmt.Sprintf("CloudProjectImagesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
+			)
+		}
+	}
+
+	for name := range attributes {
+		_, ok := attributeTypes[name]
+
+		if !ok {
+			diags.AddError(
+				"Extra CloudProjectImagesValue Attribute Value",
+				"While creating a CloudProjectImagesValue value, an extra attribute value was detected. "+
+					"A CloudProjectImagesValue must not contain values beyond the expected attribute types. "+
+					"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
+					fmt.Sprintf("Extra CloudProjectImagesValue Attribute Name: %s", name),
+			)
+		}
+	}
+
+	if diags.HasError() {
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	creationDateAttribute, ok := attributes["creation_date"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`creation_date is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	creationDateVal, ok := creationDateAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`creation_date expected to be ovhtypes.TfStringValue, was: %T`, creationDateAttribute))
+	}
+
+	flavorTypeAttribute, ok := attributes["flavor_type"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`flavor_type is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	flavorTypeVal, ok := flavorTypeAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`flavor_type expected to be ovhtypes.TfStringValue, was: %T`, flavorTypeAttribute))
+	}
+
+	idAttribute, ok := attributes["id"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`id is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	idVal, ok := idAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`id expected to be ovhtypes.TfStringValue, was: %T`, idAttribute))
+	}
+
+	minDiskAttribute, ok := attributes["min_disk"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`min_disk is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	minDiskVal, ok := minDiskAttribute.(ovhtypes.TfInt64Value)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`min_disk expected to be ovhtypes.TfInt64Value, was: %T`, minDiskAttribute))
+	}
+
+	minRamAttribute, ok := attributes["min_ram"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`min_ram is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	minRamVal, ok := minRamAttribute.(ovhtypes.TfInt64Value)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`min_ram expected to be ovhtypes.TfInt64Value, was: %T`, minRamAttribute))
+	}
+
+	nameAttribute, ok := attributes["name"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`name is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	nameVal, ok := nameAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`name expected to be ovhtypes.TfStringValue, was: %T`, nameAttribute))
+	}
+
+	planCodeAttribute, ok := attributes["plan_code"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`plan_code is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	planCodeVal, ok := planCodeAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`plan_code expected to be ovhtypes.TfStringValue, was: %T`, planCodeAttribute))
+	}
+
+	regionAttribute, ok := attributes["region"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`region is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	regionVal, ok := regionAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`region expected to be ovhtypes.TfStringValue, was: %T`, regionAttribute))
+	}
+
+	sizeAttribute, ok := attributes["size"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`size is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	sizeVal, ok := sizeAttribute.(ovhtypes.TfNumberValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`size expected to be ovhtypes.TfNumberValue, was: %T`, sizeAttribute))
+	}
+
+	statusAttribute, ok := attributes["status"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`status is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	statusVal, ok := statusAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`status expected to be ovhtypes.TfStringValue, was: %T`, statusAttribute))
+	}
+
+	tagsAttribute, ok := attributes["tags"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`tags is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	tagsVal, ok := tagsAttribute.(ovhtypes.TfListNestedValue[ovhtypes.TfStringValue])
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`tags expected to be ovhtypes.TfListNestedValue[ovhtypes.TfStringValue], was: %T`, tagsAttribute))
+	}
+
+	typeAttribute, ok := attributes["type"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`type is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	typeVal, ok := typeAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`type expected to be ovhtypes.TfStringValue, was: %T`, typeAttribute))
+	}
+
+	userAttribute, ok := attributes["user"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`user is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	userVal, ok := userAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`user expected to be ovhtypes.TfStringValue, was: %T`, userAttribute))
+	}
+
+	visibilityAttribute, ok := attributes["visibility"]
+
+	if !ok {
+		diags.AddError(
+			"Attribute Missing",
+			`visibility is missing from object`)
+
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	visibilityVal, ok := visibilityAttribute.(ovhtypes.TfStringValue)
+
+	if !ok {
+		diags.AddError(
+			"Attribute Wrong Type",
+			fmt.Sprintf(`visibility expected to be ovhtypes.TfStringValue, was: %T`, visibilityAttribute))
+	}
+
+	if diags.HasError() {
+		return NewCloudProjectImagesValueUnknown(), diags
+	}
+
+	return CloudProjectImagesValue{
+		CreationDate:           creationDateVal,
+		FlavorType:             flavorTypeVal,
+		Id:                     idVal,
+		MinDisk:                minDiskVal,
+		MinRam:                 minRamVal,
+		Name:                   nameVal,
+		PlanCode:               planCodeVal,
+		Region:                 regionVal,
+		Size:                   sizeVal,
+		Status:                 statusVal,
+		Tags:                   tagsVal,
+		CloudProjectImagesType: typeVal,
+		User:                   userVal,
+		Visibility:             visibilityVal,
+		state:                  attr.ValueStateKnown,
+	}, diags
+}
+
+func NewCloudProjectImagesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) CloudProjectImagesValue {
+	object, diags := NewCloudProjectImagesValue(attributeTypes, attributes)
+
+	if diags.HasError() {
+		// This could potentially be added to the diag package.
+		diagsStrings := make([]string, 0, len(diags))
+
+		for _, diagnostic := range diags {
+			diagsStrings = append(diagsStrings, fmt.Sprintf(
+				"%s | %s | %s",
+				diagnostic.Severity(),
+				diagnostic.Summary(),
+				diagnostic.Detail()))
+		}
+
+		panic("NewCloudProjectImagesValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
+	}
+
+	return object
+}
+
+func (t CloudProjectImagesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
+	if in.Type() == nil {
+		return NewCloudProjectImagesValueNull(), nil
+	}
+
+	if !in.Type().Equal(t.TerraformType(ctx)) {
+		return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
+	}
+
+	if !in.IsKnown() {
+		return NewCloudProjectImagesValueUnknown(), nil
+	}
+
+	if in.IsNull() {
+		return NewCloudProjectImagesValueNull(), nil
+	}
+
+	attributes := map[string]attr.Value{}
+
+	val := map[string]tftypes.Value{}
+
+	err := in.As(&val)
+
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range val {
+		a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
+
+		if err != nil {
+			return nil, err
+		}
+
+		attributes[k] = a
+	}
+
+	return NewCloudProjectImagesValueMust(CloudProjectImagesValue{}.AttributeTypes(ctx), attributes), nil
+}
+
+func (t CloudProjectImagesType) ValueType(ctx context.Context) attr.Value {
+	return CloudProjectImagesValue{}
+}
+
+var _ basetypes.ObjectValuable = CloudProjectImagesValue{}
+
+type CloudProjectImagesValue struct {
+	CreationDate           ovhtypes.TfStringValue                             `tfsdk:"creation_date" json:"creationDate"`
+	FlavorType             ovhtypes.TfStringValue                             `tfsdk:"flavor_type" json:"flavorType"`
+	Id                     ovhtypes.TfStringValue                             `tfsdk:"id" json:"id"`
+	MinDisk                ovhtypes.TfInt64Value                              `tfsdk:"min_disk" json:"minDisk"`
+	MinRam                 ovhtypes.TfInt64Value                              `tfsdk:"min_ram" json:"minRam"`
+	Name                   ovhtypes.TfStringValue                             `tfsdk:"name" json:"name"`
+	PlanCode               ovhtypes.TfStringValue                             `tfsdk:"plan_code" json:"planCode"`
+	Region                 ovhtypes.TfStringValue                             `tfsdk:"region" json:"region"`
+	Size                   ovhtypes.TfNumberValue                             `tfsdk:"size" json:"size"`
+	Status                 ovhtypes.TfStringValue                             `tfsdk:"status" json:"status"`
+	Tags                   ovhtypes.TfListNestedValue[ovhtypes.TfStringValue] `tfsdk:"tags" json:"tags"`
+	CloudProjectImagesType ovhtypes.TfStringValue                             `tfsdk:"type" json:"type"`
+	User                   ovhtypes.TfStringValue                             `tfsdk:"user" json:"user"`
+	Visibility             ovhtypes.TfStringValue                             `tfsdk:"visibility" json:"visibility"`
+	state                  attr.ValueState
+}
+
+func (v *CloudProjectImagesValue) UnmarshalJSON(data []byte) error {
+	type JsonCloudProjectImagesValue CloudProjectImagesValue
+
+	var tmp JsonCloudProjectImagesValue
+	if err := json.Unmarshal(data, &tmp); err != nil {
+		return err
+	}
+	v.CreationDate = tmp.CreationDate
+	v.FlavorType = tmp.FlavorType
+	v.Id = tmp.Id
+	v.MinDisk = tmp.MinDisk
+	v.MinRam = tmp.MinRam
+	v.Name = tmp.Name
+	v.PlanCode = tmp.PlanCode
+	v.Region = tmp.Region
+	v.Size = tmp.Size
+	v.Status = tmp.Status
+	v.Tags = tmp.Tags
+	v.CloudProjectImagesType = tmp.CloudProjectImagesType
+	v.User = tmp.User
+	v.Visibility = tmp.Visibility
+
+	v.state = attr.ValueStateKnown
+
+	return nil
+}
+
+func (v *CloudProjectImagesValue) MergeWith(other *CloudProjectImagesValue) {
+
+	if (v.CreationDate.IsUnknown() || v.CreationDate.IsNull()) && !other.CreationDate.IsUnknown() {
+		v.CreationDate = other.CreationDate
+	}
+
+	if (v.FlavorType.IsUnknown() || v.FlavorType.IsNull()) && !other.FlavorType.IsUnknown() {
+		v.FlavorType = other.FlavorType
+	}
+
+	if (v.Id.IsUnknown() || v.Id.IsNull()) && !other.Id.IsUnknown() {
+		v.Id = other.Id
+	}
+
+	if (v.MinDisk.IsUnknown() || v.MinDisk.IsNull()) && !other.MinDisk.IsUnknown() {
+		v.MinDisk = other.MinDisk
+	}
+
+	if (v.MinRam.IsUnknown() || v.MinRam.IsNull()) && !other.MinRam.IsUnknown() {
+		v.MinRam = other.MinRam
+	}
+
+	if (v.Name.IsUnknown() || v.Name.IsNull()) && !other.Name.IsUnknown() {
+		v.Name = other.Name
+	}
+
+	if (v.PlanCode.IsUnknown() || v.PlanCode.IsNull()) && !other.PlanCode.IsUnknown() {
+		v.PlanCode = other.PlanCode
+	}
+
+	if (v.Region.IsUnknown() || v.Region.IsNull()) && !other.Region.IsUnknown() {
+		v.Region = other.Region
+	}
+
+	if (v.Size.IsUnknown() || v.Size.IsNull()) && !other.Size.IsUnknown() {
+		v.Size = other.Size
+	}
+
+	if (v.Status.IsUnknown() || v.Status.IsNull()) && !other.Status.IsUnknown() {
+		v.Status = other.Status
+	}
+
+	if (v.Tags.IsUnknown() || v.Tags.IsNull()) && !other.Tags.IsUnknown() {
+		v.Tags = other.Tags
+	}
+
+	if (v.CloudProjectImagesType.IsUnknown() || v.CloudProjectImagesType.IsNull()) && !other.CloudProjectImagesType.IsUnknown() {
+		v.CloudProjectImagesType = other.CloudProjectImagesType
+	}
+
+	if (v.User.IsUnknown() || v.User.IsNull()) && !other.User.IsUnknown() {
+		v.User = other.User
+	}
+
+	if (v.Visibility.IsUnknown() || v.Visibility.IsNull()) && !other.Visibility.IsUnknown() {
+		v.Visibility = other.Visibility
+	}
+
+	if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown {
+		v.state = other.state
+	}
+}
+
+func (v CloudProjectImagesValue) Attributes() map[string]attr.Value {
+	return map[string]attr.Value{
+		"creationDate": v.CreationDate,
+		"flavorType":   v.FlavorType,
+		"id":           v.Id,
+		"minDisk":      v.MinDisk,
+		"minRam":       v.MinRam,
+		"name":         v.Name,
+		"planCode":     v.PlanCode,
+		"region":       v.Region,
+		"size":         v.Size,
+		"status":       v.Status,
+		"tags":         v.Tags,
+		"type":         v.CloudProjectImagesType,
+		"user":         v.User,
+		"visibility":   v.Visibility,
+	}
+}
+func (v CloudProjectImagesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
+	attrTypes := make(map[string]tftypes.Type, 14)
+
+	var val tftypes.Value
+	var err error
+
+	attrTypes["creation_date"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["flavor_type"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["id"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["min_disk"] = basetypes.Int64Type{}.TerraformType(ctx)
+	attrTypes["min_ram"] = basetypes.Int64Type{}.TerraformType(ctx)
+	attrTypes["name"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["plan_code"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["region"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["size"] = basetypes.NumberType{}.TerraformType(ctx)
+	attrTypes["status"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["tags"] = basetypes.ListType{
+		ElemType: types.StringType,
+	}.TerraformType(ctx)
+	attrTypes["type"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["user"] = basetypes.StringType{}.TerraformType(ctx)
+	attrTypes["visibility"] = basetypes.StringType{}.TerraformType(ctx)
+
+	objectType := tftypes.Object{AttributeTypes: attrTypes}
+
+	switch v.state {
+	case attr.ValueStateKnown:
+		vals := make(map[string]tftypes.Value, 14)
+
+		val, err = v.CreationDate.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["creation_date"] = val
+
+		val, err = v.FlavorType.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["flavor_type"] = val
+
+		val, err = v.Id.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["id"] = val
+
+		val, err = v.MinDisk.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["min_disk"] = val
+
+		val, err = v.MinRam.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["min_ram"] = val
+
+		val, err = v.Name.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["name"] = val
+
+		val, err = v.PlanCode.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["plan_code"] = val
+
+		val, err = v.Region.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["region"] = val
+
+		val, err = v.Size.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["size"] = val
+
+		val, err = v.Status.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["status"] = val
+
+		val, err = v.Tags.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["tags"] = val
+
+		val, err = v.CloudProjectImagesType.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["type"] = val
+
+		val, err = v.User.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["user"] = val
+
+		val, err = v.Visibility.ToTerraformValue(ctx)
+
+		if err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		vals["visibility"] = val
+
+		if err := tftypes.ValidateValue(objectType, vals); err != nil {
+			return tftypes.NewValue(objectType, tftypes.UnknownValue), err
+		}
+
+		return tftypes.NewValue(objectType, vals), nil
+	case attr.ValueStateNull:
+		return tftypes.NewValue(objectType, nil), nil
+	case attr.ValueStateUnknown:
+		return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
+	default:
+		panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
+	}
+}
+
+func (v CloudProjectImagesValue) IsNull() bool {
+	return v.state == attr.ValueStateNull
+}
+
+func (v CloudProjectImagesValue) IsUnknown() bool {
+	return v.state == attr.ValueStateUnknown
+}
+
+func (v CloudProjectImagesValue) String() string {
+	return "CloudProjectImagesValue"
+}
+
+func (v CloudProjectImagesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	objVal, diags := types.ObjectValue(
+		map[string]attr.Type{
+			"creation_date": ovhtypes.TfStringType{},
+			"flavor_type":   ovhtypes.TfStringType{},
+			"id":            ovhtypes.TfStringType{},
+			"min_disk":      ovhtypes.TfInt64Type{},
+			"min_ram":       ovhtypes.TfInt64Type{},
+			"name":          ovhtypes.TfStringType{},
+			"plan_code":     ovhtypes.TfStringType{},
+			"region":        ovhtypes.TfStringType{},
+			"size":          ovhtypes.TfNumberType{},
+			"status":        ovhtypes.TfStringType{},
+			"tags":          ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx),
+			"type":          ovhtypes.TfStringType{},
+			"user":          ovhtypes.TfStringType{},
+			"visibility":    ovhtypes.TfStringType{},
+		},
+		map[string]attr.Value{
+			"creation_date": v.CreationDate,
+			"flavor_type":   v.FlavorType,
+			"id":            v.Id,
+			"min_disk":      v.MinDisk,
+			"min_ram":       v.MinRam,
+			"name":          v.Name,
+			"plan_code":     v.PlanCode,
+			"region":        v.Region,
+			"size":          v.Size,
+			"status":        v.Status,
+			"tags":          v.Tags,
+			"type":          v.CloudProjectImagesType,
+			"user":          v.User,
+			"visibility":    v.Visibility,
+		})
+
+	return objVal, diags
+}
+
+func (v CloudProjectImagesValue) Equal(o attr.Value) bool {
+	other, ok := o.(CloudProjectImagesValue)
+
+	if !ok {
+		return false
+	}
+
+	if v.state != other.state {
+		return false
+	}
+
+	if v.state != attr.ValueStateKnown {
+		return true
+	}
+
+	if !v.CreationDate.Equal(other.CreationDate) {
+		return false
+	}
+
+	if !v.FlavorType.Equal(other.FlavorType) {
+		return false
+	}
+
+	if !v.Id.Equal(other.Id) {
+		return false
+	}
+
+	if !v.MinDisk.Equal(other.MinDisk) {
+		return false
+	}
+
+	if !v.MinRam.Equal(other.MinRam) {
+		return false
+	}
+
+	if !v.Name.Equal(other.Name) {
+		return false
+	}
+
+	if !v.PlanCode.Equal(other.PlanCode) {
+		return false
+	}
+
+	if !v.Region.Equal(other.Region) {
+		return false
+	}
+
+	if !v.Size.Equal(other.Size) {
+		return false
+	}
+
+	if !v.Status.Equal(other.Status) {
+		return false
+	}
+
+	if !v.Tags.Equal(other.Tags) {
+		return false
+	}
+
+	if !v.CloudProjectImagesType.Equal(other.CloudProjectImagesType) {
+		return false
+	}
+
+	if !v.User.Equal(other.User) {
+		return false
+	}
+
+	if !v.Visibility.Equal(other.Visibility) {
+		return false
+	}
+
+	return true
+}
+
+func (v CloudProjectImagesValue) Type(ctx context.Context) attr.Type {
+	return CloudProjectImagesType{
+		basetypes.ObjectType{
+			AttrTypes: v.AttributeTypes(ctx),
+		},
+	}
+}
+
+func (v CloudProjectImagesValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
+	return map[string]attr.Type{
+		"creation_date": ovhtypes.TfStringType{},
+		"flavor_type":   ovhtypes.TfStringType{},
+		"id":            ovhtypes.TfStringType{},
+		"min_disk":      ovhtypes.TfInt64Type{},
+		"min_ram":       ovhtypes.TfInt64Type{},
+		"name":          ovhtypes.TfStringType{},
+		"plan_code":     ovhtypes.TfStringType{},
+		"region":        ovhtypes.TfStringType{},
+		"size":          ovhtypes.TfNumberType{},
+		"status":        ovhtypes.TfStringType{},
+		"tags":          ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx),
+		"type":          ovhtypes.TfStringType{},
+		"user":          ovhtypes.TfStringType{},
+		"visibility":    ovhtypes.TfStringType{},
+	}
+}
diff --git a/ovh/provider_new.go b/ovh/provider_new.go
index c88f62ed..9b1bbcb6 100644
--- a/ovh/provider_new.go
+++ b/ovh/provider_new.go
@@ -193,6 +193,8 @@ func (p *OvhProvider) DataSources(_ context.Context) []func() datasource.DataSou
 		NewCloudProjectFlavorsDataSource,
 		NewCloudProjectFloatingipsDataSource,
 		NewCloudProjectGatewayInterfaceDataSource,
+		NewCloudProjectImageDataSource,
+		NewCloudProjectImagesDataSource,
 		NewCloudProjectLoadbalancerDataSource,
 		NewCloudProjectLoadbalancersDataSource,
 		NewCloudProjectLoadbalancerFlavorsDataSource,
diff --git a/templates/data-sources/cloud_project_image.md.tmpl b/templates/data-sources/cloud_project_image.md.tmpl
new file mode 100644
index 00000000..f12a847d
--- /dev/null
+++ b/templates/data-sources/cloud_project_image.md.tmpl
@@ -0,0 +1,36 @@
+---
+subcategory : "Cloud Project"
+---
+
+# ovh_cloud_project_image (Data Source)
+
+Get information about an image in the given public cloud project.
+
+## Example Usage
+
+{{tffile "examples/data-sources/ovh_cloud_project_image/data-source.tf"}}
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `image_id` (String) Image ID
+- `service_name` (String) Public cloud project ID
+
+### Read-Only
+
+- `creation_date` (String) Image creation date
+- `flavor_type` (String) Image usable only for this type of flavor if not null
+- `id` (String) Image ID
+- `min_disk` (Number) Minimum disks required to use image
+- `min_ram` (Number) Minimum RAM required to use image
+- `name` (String) Image name
+- `plan_code` (String) Order plan code
+- `region` (String) Image region
+- `size` (Number) Image size (in GiB)
+- `status` (String) Image status
+- `tags` (List of String) Tags about the image
+- `type` (String) Image type
+- `user` (String) User to connect with
+- `visibility` (String) Image visibility
diff --git a/templates/data-sources/cloud_project_images.md.tmpl b/templates/data-sources/cloud_project_images.md.tmpl
new file mode 100644
index 00000000..9b347406
--- /dev/null
+++ b/templates/data-sources/cloud_project_images.md.tmpl
@@ -0,0 +1,48 @@
+---
+subcategory : "Cloud Project"
+---
+
+# ovh_cloud_project_images (Data Source)
+
+Get available images in the given public cloud project.
+
+## Example Usage
+
+{{tffile "examples/data-sources/ovh_cloud_project_images/data-source.tf"}}
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `service_name` (String) Public cloud project ID
+
+### Optional
+
+- `flavor_type` (String) Get compatible images with flavor type
+- `os_type` (String) Image OS (Allowed values: baremetal-linux ┃ bsd ┃ linux ┃ windows)
+- `region` (String) Image region
+
+### Read-Only
+
+- `images` (Attributes Set) (see [below for nested schema](#nestedatt--images))
+
+<a id="nestedatt--images"></a>
+### Nested Schema for `images`
+
+Read-Only:
+
+- `creation_date` (String) Image creation date
+- `flavor_type` (String) Image usable only for this type of flavor if not null
+- `id` (String) Image ID
+- `min_disk` (Number) Minimum disks required to use image
+- `min_ram` (Number) Minimum RAM required to use image
+- `name` (String) Image name
+- `plan_code` (String) Order plan code
+- `region` (String) Image region
+- `size` (Number) Image size (in GiB)
+- `status` (String) Image status
+- `tags` (List of String) Tags about the image
+- `type` (String) Image type
+- `user` (String) User to connect with
+- `visibility` (String) Image visibility



Test patch:
diff --git a/ovh/data_cloud_project_image_test.go b/ovh/data_cloud_project_image_test.go
new file mode 100644
index 00000000..4b893668
--- /dev/null
+++ b/ovh/data_cloud_project_image_test.go
@@ -0,0 +1,42 @@
+package ovh
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccDataSourceCloudProjectImage_basic(t *testing.T) {
+	serviceName := os.Getenv("OVH_CLOUD_PROJECT_SERVICE_TEST")
+	imageID := os.Getenv("OVH_CLOUD_PROJECT_IMAGE_TEST")
+	config := fmt.Sprintf(`
+		data "ovh_cloud_project_image" "image" {
+			service_name = "%s"
+			image_id     = "%s"
+		}
+	`, serviceName, imageID)
+
+	resource.Test(t, resource.TestCase{
+		PreCheck: func() {
+			testAccPreCheckCloud(t)
+			testAccCheckCloudProjectExists(t)
+		},
+		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: config,
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("data.ovh_cloud_project_image.image", "service_name", serviceName),
+					resource.TestCheckResourceAttr("data.ovh_cloud_project_image.image", "id", imageID),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_image.image", "creation_date"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_image.image", "name"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_image.image", "region"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_image.image", "size"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_image.image", "type"),
+				),
+			},
+		},
+	})
+}
diff --git a/ovh/data_cloud_project_images_test.go b/ovh/data_cloud_project_images_test.go
new file mode 100644
index 00000000..9b297f3d
--- /dev/null
+++ b/ovh/data_cloud_project_images_test.go
@@ -0,0 +1,41 @@
+package ovh
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccDataSourceCloudProjectImages_basic(t *testing.T) {
+	serviceName := os.Getenv("OVH_CLOUD_PROJECT_SERVICE_TEST")
+	config := fmt.Sprintf(`
+		data "ovh_cloud_project_images" "images" {
+			service_name = "%s"
+		}
+	`, serviceName)
+
+	resource.Test(t, resource.TestCase{
+		PreCheck: func() {
+			testAccPreCheckCloud(t)
+			testAccCheckCloudProjectExists(t)
+		},
+		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: config,
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("data.ovh_cloud_project_images.images", "service_name", serviceName),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_images.images", "images.#"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_images.images", "images.0.id"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_images.images", "images.0.creation_date"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_images.images", "images.0.name"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_images.images", "images.0.region"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_images.images", "images.0.size"),
+					resource.TestCheckResourceAttrSet("data.ovh_cloud_project_images.images", "images.0.type"),
+				),
+			},
+		},
+	})
+}