0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-29 18:21:10 +00:00

feat: add support for 'import' content type in Proxmox file resources (#1983)

Signed-off-by: Marco Attia <54147992+Vaneixus@users.noreply.github.com>
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Marco Attia 2025-06-28 01:23:22 +00:00 committed by GitHub
parent a76cc6256d
commit 2d9e0b585e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 189 additions and 29 deletions

View File

@ -71,7 +71,7 @@ The following assumptions are made about the test environment:
- It has one node named `pve`
- The node has local storages named `local` and `local-lvm`
- The "Snippets" content type is enabled in the `local` storage
- The "Snippets" and "Import" content types are enabled in the `local` storage
- Default Linux Bridge "vmbr0" is VLAN aware (datacenter -> pve -> network -> edit & apply)
Create `example/terraform.tfvars` with the following variables:

View File

@ -92,4 +92,4 @@ Goal is to have a proxmox node in VM using <https://virt-manager.org/> for a job
10. Now you can run `make example`.
11. If you see error with proxmox_virtual_environment_file: the datastore "local" does not support content type "snippets"; supported content types are: `[backup, iso, vztmpl]`, you need to enable them, see <https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_file#snippets>.
11. If you see error with proxmox_virtual_environment_file: the datastore "local" does not support content type "snippets"; supported content types are: `[backup, iso, vztmpl, import]`, you need to enable them, see <https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_file#snippets>.

View File

@ -4,12 +4,12 @@ title: proxmox_virtual_environment_download_file
parent: Resources
subcategory: Virtual Environment
description: |-
Manages files upload using PVE download-url API. It can be fully compatible and faster replacement for image files created using proxmox_virtual_environment_file. Supports images for VMs (ISO images) and LXC (CT Templates).
Manages files upload using PVE download-url API. It can be fully compatible and faster replacement for image files created using proxmox_virtual_environment_file. Supports images for VMs (ISO and disk images) and LXC (CT Templates).
---
# Resource: proxmox_virtual_environment_download_file
Manages files upload using PVE download-url API. It can be fully compatible and faster replacement for image files created using `proxmox_virtual_environment_file`. Supports images for VMs (ISO images) and LXC (CT Templates).
Manages files upload using PVE download-url API. It can be fully compatible and faster replacement for image files created using `proxmox_virtual_environment_file`. Supports images for VMs (ISO and disk images) and LXC (CT Templates).
~> Besides the `Datastore.AllocateTemplate` privilege, this resource requires both the `Sys.Audit` and `Sys.Modify` privileges.<br><br>
For more details, see the [`download-url`](https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node}/storage/{storage}/download-url) API documentation under the "Required permissions" section.
@ -27,6 +27,16 @@ resource "proxmox_virtual_environment_download_file" "release_20231228_debian_12
checksum_algorithm = "sha512"
}
resource "proxmox_virtual_environment_download_file" "release_20231228_debian_12_bookworm_qcow2" {
content_type = "import"
datastore_id = "local"
file_name = "debian-12-generic-amd64-20231228-1609.qcow2"
node_name = "pve"
url = "https://cloud.debian.org/images/cloud/bookworm/20231228-1609/debian-12-generic-amd64-20231228-1609.qcow2"
checksum = "d2fbcf11fb28795842e91364d8c7b69f1870db09ff299eb94e4fbbfa510eb78d141e74c1f4bf6dfa0b7e33d0c3b66e6751886feadb4e9916f778bab1776bdf1b"
checksum_algorithm = "sha512"
}
resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_qcow2_img" {
content_type = "iso"
datastore_id = "local"
@ -35,6 +45,14 @@ resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_
url = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
}
resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_qcow2" {
content_type = "import"
datastore_id = "local"
file_name = "debian-12-generic-amd64.qcow2"
node_name = "pve"
url = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
}
resource "proxmox_virtual_environment_download_file" "latest_ubuntu_22_jammy_qcow2_img" {
content_type = "iso"
datastore_id = "local"
@ -73,7 +91,7 @@ resource "proxmox_virtual_environment_download_file" "latest_ubuntu_22_jammy_lxc
### Required
- `content_type` (String) The file content type. Must be `iso` for VM images or `vztmpl` for LXC images.
- `content_type` (String) The file content type. Must be `iso` or `import` for VM images or `vztmpl` for LXC images.
- `datastore_id` (String) The identifier for the target datastore.
- `node_name` (String) The node name.
- `url` (String) The URL to download the file from. Must match regex: `https?://.*`.
@ -83,7 +101,7 @@ resource "proxmox_virtual_environment_download_file" "latest_ubuntu_22_jammy_lxc
- `checksum` (String) The expected checksum of the file.
- `checksum_algorithm` (String) The algorithm to calculate the checksum of the file. Must be `md5` | `sha1` | `sha224` | `sha256` | `sha384` | `sha512`.
- `decompression_algorithm` (String) Decompress the downloaded file using the specified compression algorithm. Must be one of `gz` | `lzo` | `zst` | `bz2`.
- `file_name` (String) The file name. If not provided, it is calculated using `url`. PVE will raise 'wrong file extension' error for some popular extensions file `.raw` or `.qcow2`. Workaround is to use e.g. `.img` instead.
- `file_name` (String) The file name. If not provided, it is calculated using `url`. PVE will raise 'wrong file extension' error for some popular extensions file `.raw` or `.qcow2` on PVE versions prior to 8.4. Workaround is to use e.g. `.img` instead.
- `overwrite` (Boolean) By default `true`. If `true` and file size has changed in the datastore, it will be replaced. If `false`, there will be no check.
- `overwrite_unmanaged` (Boolean) If `true` and a file with the same name already exists in the datastore, it will be deleted and the new file will be downloaded. If `false` and the file already exists, an error will be returned.
- `upload_timeout` (Number) The file download timeout seconds. Default is 600 (10min).

View File

@ -7,7 +7,7 @@ subcategory: Virtual Environment
# Resource: proxmox_virtual_environment_file
Use this resource to upload files to a Proxmox VE node. The file can be a backup, an ISO image, a snippet, or a container template depending on the `content_type` attribute.
Use this resource to upload files to a Proxmox VE node. The file can be a backup, an ISO image, a Disk Image, a snippet, or a container template depending on the `content_type` attribute.
## Example Usage
@ -33,6 +33,8 @@ resource "proxmox_virtual_environment_file" "backup" {
-> Consider using `proxmox_virtual_environment_download_file` resource instead. Using this resource for images is less efficient (requires to transfer uploaded image to node) though still supported.
-> Importing Disks is not enabled by default in new Proxmox installations. You need to enable them in the 'Datacenter>Storage' section of the proxmox interface before first using this resource with `content_type = "import"`.
```hcl
resource "proxmox_virtual_environment_file" "ubuntu_container_template" {
content_type = "iso"
@ -45,6 +47,18 @@ resource "proxmox_virtual_environment_file" "ubuntu_container_template" {
}
```
```hcl
resource "proxmox_virtual_environment_file" "ubuntu_container_template" {
content_type = "import"
datastore_id = "local"
node_name = "pve"
source_file {
path = "https://cloud-images.ubuntu.com/jammy/20230929/jammy-server-cloudimg-amd64-disk-kvm.img"
}
}
```
### Snippets
-> Snippets are not enabled by default in new Proxmox installations. You need to enable them in the 'Datacenter>Storage' section of the proxmox interface before first using this resource.
@ -126,6 +140,7 @@ resource "proxmox_virtual_environment_file" "ubuntu_container_template" {
- `backup` (allowed extensions: `.vzdump`, `.tar.gz`, `.tar.xz`, `tar.zst`)
- `iso` (allowed extensions: `.iso`, `.img`)
- `snippets` (allowed extensions: any)
- `import` (allowed extensions: `.raw`, `.qcow2`, `.vmdk`)
- `vztmpl` (allowed extensions: `.tar.gz`, `.tar.xz`, `tar.zst`)
- `datastore_id` - (Required) The datastore id.
- `file_mode` - The file mode in octal format, e.g. `0700` or `600`. Note that the prefixes `0o` and `0x` is not supported! Setting this attribute is also only allowed for `root@pam` authenticated user.

View File

@ -20,3 +20,13 @@ resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_
overwrite = true
overwrite_unmanaged = true
}
resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_qcow2" {
content_type = "import"
datastore_id = "local"
file_name = "debian-12-generic-amd64.qcow2"
node_name = "pve"
url = var.latest_debian_12_bookworm_qcow2_img_url
overwrite = true
overwrite_unmanaged = true
}

View File

@ -8,6 +8,16 @@ resource "proxmox_virtual_environment_download_file" "release_20231228_debian_12
checksum_algorithm = "sha512"
}
resource "proxmox_virtual_environment_download_file" "release_20231228_debian_12_bookworm_qcow2" {
content_type = "import"
datastore_id = "local"
file_name = "debian-12-generic-amd64-20231228-1609.qcow2"
node_name = "pve"
url = "https://cloud.debian.org/images/cloud/bookworm/20231228-1609/debian-12-generic-amd64-20231228-1609.qcow2"
checksum = "d2fbcf11fb28795842e91364d8c7b69f1870db09ff299eb94e4fbbfa510eb78d141e74c1f4bf6dfa0b7e33d0c3b66e6751886feadb4e9916f778bab1776bdf1b"
checksum_algorithm = "sha512"
}
resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_qcow2_img" {
content_type = "iso"
datastore_id = "local"
@ -16,6 +26,14 @@ resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_
url = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
}
resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_qcow2" {
content_type = "import"
datastore_id = "local"
file_name = "debian-12-generic-amd64.qcow2"
node_name = "pve"
url = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
}
resource "proxmox_virtual_environment_download_file" "latest_ubuntu_22_jammy_qcow2_img" {
content_type = "iso"
datastore_id = "local"

View File

@ -0,0 +1,19 @@
resource "proxmox_virtual_environment_file" "latest_debian_12_bookworm_qcow2" {
content_type = "import"
datastore_id = "local"
node_name = "pve"
source_file {
path = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
}
}
resource "proxmox_virtual_environment_file" "release_20231228_debian_12_bookworm_qcow2" {
content_type = "import"
datastore_id = "local"
node_name = "pve"
source_file {
path = "https://cloud.debian.org/images/cloud/bookworm/20231228-1609/debian-12-generic-amd64-20231228-1609.qcow2"
}
}

View File

@ -115,7 +115,7 @@ func (d *versionDatasource) Read(ctx context.Context, _ datasource.ReadRequest,
state.Release = types.StringValue(version.Release)
state.RepositoryID = types.StringValue(version.RepositoryID)
state.Version = types.StringValue(version.Version)
state.Version = types.StringValue(version.Version.String())
state.ID = types.StringValue("version")

View File

@ -153,15 +153,16 @@ func (r *downloadFileResource) Schema(
Description: "Manages files upload using PVE download-url API. ",
MarkdownDescription: "Manages files upload using PVE download-url API. " +
"It can be fully compatible and faster replacement for image files created using " +
"`proxmox_virtual_environment_file`. Supports images for VMs (ISO images) and LXC (CT Templates).",
"`proxmox_virtual_environment_file`. Supports images for VMs (ISO and disk images) and LXC (CT Templates).",
Attributes: map[string]schema.Attribute{
"id": attribute.ResourceID(),
"content_type": schema.StringAttribute{
Description: "The file content type. Must be `iso` for VM images or `vztmpl` for LXC images.",
Description: "The file content type. Must be `iso` or `import` for VM images or `vztmpl` for LXC images.",
Required: true,
Validators: []validator.String{stringvalidator.OneOf([]string{
"iso",
"vztmpl",
"import",
}...)},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
@ -170,7 +171,8 @@ func (r *downloadFileResource) Schema(
"file_name": schema.StringAttribute{
Description: "The file name. If not provided, it is calculated " +
"using `url`. PVE will raise 'wrong file extension' error for some popular " +
"extensions file `.raw` or `.qcow2`. Workaround is to use e.g. `.img` instead.",
"extensions file `.raw` or `.qcow2` on PVE versions prior to 8.4. " +
"Workaround is to use e.g. `.img` instead.",
Computed: true,
Required: false,
Optional: true,

View File

@ -60,7 +60,7 @@ func TestAccResourceDownloadFile(t *testing.T) {
}`),
ExpectError: regexp.MustCompile(`Attribute url must match HTTP URL regex`),
}}},
{"download qcow2 file", []resource.TestStep{{
{"download qcow2 file to iso storage", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_download_file" "qcow2_image" {
content_type = "iso"
@ -91,6 +91,21 @@ func TestAccResourceDownloadFile(t *testing.T) {
}),
),
}}},
{"download qcow2 file to import storage", []resource.TestStep{{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_download_file" "qcow2_image" {
content_type = "import"
node_name = "{{.NodeName}}"
datastore_id = "{{.DatastoreID}}"
file_name = "fake_qcow2_file.qcow2"
url = "{{.FakeFileQCOW2}}"
checksum = "688787d8ff144c502c7f5cffaafe2cc588d86079f9de88304c26b0cb99ce91c6"
checksum_algorithm = "sha256"
overwrite_unmanaged = true
}`),
// the details sais "Image is not in qcow2 format", but we can't assert that
ExpectError: regexp.MustCompile(`Error downloading file from url`),
}}},
{"download & update iso file", []resource.TestStep{
{
Config: te.RenderConfig(`

View File

@ -0,0 +1,20 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package version
import "github.com/hashicorp/go-version"
// MinimumProxmoxVersion is the minimum supported Proxmox version by the provider.
//
//nolint:gochecknoglobals
var MinimumProxmoxVersion = ProxmoxVersion{*version.Must(version.NewVersion("8.0.0"))}
// SupportImportContentType checks if the Proxmox version supports the `import` content type when uploading disk images.
// See https://bugzilla.proxmox.com/show_bug.cgi?id=2424
func (v *ProxmoxVersion) SupportImportContentType() bool {
return v.GreaterThanOrEqual(version.Must(version.NewVersion("8.4.0")))
}

View File

@ -6,6 +6,12 @@
package version
import (
"fmt"
"github.com/hashicorp/go-version"
)
// ResponseBody contains the body from a version response.
type ResponseBody struct {
Data *ResponseData `json:"data,omitempty"`
@ -16,5 +22,21 @@ type ResponseData struct {
Console string `json:"console"`
Release string `json:"release"`
RepositoryID string `json:"repoid"`
Version string `json:"version"`
Version ProxmoxVersion `json:"version"`
}
type ProxmoxVersion struct {
version.Version
}
func (v *ProxmoxVersion) UnmarshalJSON(data []byte) error {
// Unmarshal the version string into a go-version Version object
ver, err := version.NewVersion(string(data))
if err != nil {
return fmt.Errorf("failed to parse version %q: %w", string(data), err)
}
v.Version = *ver
return nil
}

View File

@ -28,7 +28,9 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/version"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/validators"
"github.com/bpg/terraform-provider-proxmox/utils"
@ -325,9 +327,6 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
var diags diag.Diagnostics
contentType, dg := fileGetContentType(d)
diags = append(diags, dg...)
fileName, err := fileGetSourceFileName(d)
diags = append(diags, diag.FromErr(err)...)
@ -345,6 +344,9 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
return diag.FromErr(err)
}
contentType, dg := fileGetContentType(ctx, d, capi)
diags = append(diags, dg...)
list, err := capi.Node(nodeName).Storage(datastoreID).ListDatastoreFiles(ctx)
if err != nil {
return diag.FromErr(err)
@ -406,7 +408,7 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
"url": sourceFilePath,
})
version, e := api.GetMinTLSVersion(sourceFileMinTLS)
minTLSVersion, e := api.GetMinTLSVersion(sourceFileMinTLS)
if e != nil {
return diag.FromErr(e)
}
@ -414,7 +416,7 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
httpClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: version,
MinVersion: minTLSVersion,
InsecureSkipVerify: sourceFileInsecure,
},
},
@ -553,7 +555,7 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
}
switch *contentType {
case "iso", "vztmpl":
case "iso", "vztmpl", "import":
_, err = capi.Node(nodeName).Storage(datastoreID).APIUpload(
ctx, request, config.TempDir(),
)
@ -600,7 +602,7 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
}
volID, di := fileGetVolumeID(d)
volID, di := fileGetVolumeID(ctx, d, capi)
diags = append(diags, di...)
if diags.HasError() {
return diags
@ -617,11 +619,20 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
return diags
}
func fileGetContentType(d *schema.ResourceData) (*string, diag.Diagnostics) {
func fileGetContentType(ctx context.Context, d *schema.ResourceData, c proxmox.Client) (*string, diag.Diagnostics) {
contentType := d.Get(mkResourceVirtualEnvironmentFileContentType).(string)
sourceFile := d.Get(mkResourceVirtualEnvironmentFileSourceFile).([]interface{})
sourceRaw := d.Get(mkResourceVirtualEnvironmentFileSourceRaw).([]interface{})
ver := version.MinimumProxmoxVersion
if versionResp, err := c.Version().Version(ctx); err == nil {
ver = versionResp.Version
} else {
tflog.Warn(ctx, fmt.Sprintf("failed to determine Proxmox VE version, assume %v", ver), map[string]interface{}{
"error": err,
})
}
sourceFilePath := ""
if len(sourceFile) > 0 {
@ -638,11 +649,15 @@ func fileGetContentType(d *schema.ResourceData) (*string, diag.Diagnostics) {
mkResourceVirtualEnvironmentFileSourceRaw,
)
}
if contentType == "" {
if strings.HasSuffix(sourceFilePath, ".tar.gz") ||
strings.HasSuffix(sourceFilePath, ".tar.xz") {
contentType = "vztmpl"
} else if ver.SupportImportContentType() &&
(strings.HasSuffix(sourceFilePath, ".qcow2") ||
strings.HasSuffix(sourceFilePath, ".raw") ||
strings.HasSuffix(sourceFilePath, ".vmdk")) {
contentType = "import"
} else {
ext := strings.TrimLeft(strings.ToLower(filepath.Ext(sourceFilePath)), ".")
@ -715,14 +730,14 @@ func fileGetSourceFileName(d *schema.ResourceData) (*string, error) {
return &sourceFileFileName, nil
}
func fileGetVolumeID(d *schema.ResourceData) (fileVolumeID, diag.Diagnostics) {
func fileGetVolumeID(ctx context.Context, d *schema.ResourceData, c proxmox.Client) (fileVolumeID, diag.Diagnostics) {
fileName, err := fileGetSourceFileName(d)
if err != nil {
return fileVolumeID{}, diag.FromErr(err)
}
datastoreID := d.Get(mkResourceVirtualEnvironmentFileDatastoreID).(string)
contentType, diags := fileGetContentType(d)
contentType, diags := fileGetContentType(ctx, d, c)
return fileVolumeID{
datastoreID: datastoreID,

View File

@ -118,11 +118,16 @@ func Test_fileParseVolumeID(t *testing.T) {
{"missing type", "local:/file.ido", fileVolumeID{}, true},
{"missing file", "local:iso", fileVolumeID{}, true},
{"missing file", "local:iso/", fileVolumeID{}, true},
{"valid", "local:iso/file.iso", fileVolumeID{
{"valid iso", "local:iso/file.iso", fileVolumeID{
datastoreID: "local",
contentType: "iso",
fileName: "file.iso",
}, false},
{"valid import", "local:import/file.qcow2", fileVolumeID{
datastoreID: "local",
contentType: "import",
fileName: "file.qcow2",
}, false},
}
for _, tt := range tests {

View File

@ -24,6 +24,7 @@ func ContentType() schema.SchemaValidateDiagFunc {
"iso",
"snippets",
"vztmpl",
"import",
}, false))
}

View File

@ -92,4 +92,4 @@ Goal is to have a proxmox node in VM using <https://virt-manager.org/> for a job
10. Now you can run `make example`.
11. If you see error with proxmox_virtual_environment_file: the datastore "local" does not support content type "snippets"; supported content types are: `[backup, iso, vztmpl]`, you need to enable them, see <https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_file#snippets>.
11. If you see error with proxmox_virtual_environment_file: the datastore "local" does not support content type "snippets"; supported content types are: `[backup, iso, vztmpl, import]`, you need to enable them, see <https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_file#snippets>.