Skip to content

Conversation

@NemoDacremont
Copy link
Contributor

Add resources for CT templates, it is heavily inspired of resource_storage_iso.go
resource.

Implemented :

  • Create Template
  • Read Template
  • Delete Template

This enables creating template by adding resources, removing template by removing the resource from the .tf file when it alread exists.

This can be tested with the following config (after a make local-dev-install)

terraform {
  required_providers {
    proxmox = {
      source  = "localhost/telmate/proxmox"
      version = "3.0.3"
    }
  }
}

# provider "blog" {}
provider "proxmox" {
  pm_tls_insecure = true
  pm_api_url = "https://localhost:8006/api2/json"
  pm_user = "root@pam"
  pm_password = "vagrant"  # Use the right password
}

resource "proxmox_storage_template" "alpine-321" {
  pve_node = "pve"
  storage = "local"
  template = "alpine-3.21-default_20241217_amd64.tar.xz"
}

fixes #1419

@Tinyblargon Tinyblargon added size/L Denotes a PR that changes 100-499 lines, ignoring generated files type/feature Completely new functionality test/needen This PR has to be tested labels Oct 9, 2025
@Tinyblargon
Copy link
Collaborator

@NemoDacremont This feature looks promising.

One minor thing: We should have some computed id so the lxc resource can reference it directly.

One major thing: Could we look into up streaming the logic to interact with the PVE API to the SDK project? As the Terraform provider should only be a facade, since it's quite difficult to test.

defer lock.unlock()

storage := strings.SplitN(d.Id(), ":", 2)[0]
templateURL := fmt.Sprintf("/nodes/%s/storage/%s/content/%s", d.Get("pve_node").(string), storage, d.Id())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we look into up streaming the logic to interact with the PVE API to the SDK project? As the Terraform provider should only be a facade, since it's quite difficult to test.

I'm not sure to understand what you meant, are you talking about this part forging manually the request url ? For the other parts, I feel like it uses correctly the sdk.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was about the url and https://github.com/Telmate/terraform-provider-proxmox/pull/1420/files#diff-94914ef253ac2ff7283aee16ba7efba607cded5e7eb3c5143e8d4fd96fbefcd8R95
The SDK has an old legacy part which is client.GetStorageContent as the SDK should never expose the raw API response, as it requires the consumer to understand the internals of the API across PVE versions.
I guess for now it's fine as those parts have to be improved in the SDK first.

@NemoDacremont
Copy link
Contributor Author

One minor thing: We should have some computed id so the lxc resource can reference it directly.

I actually don't know terraform that much, what computed id ? Do you have an example I can refer to ?

@NemoDacremont
Copy link
Contributor Author

@Tinyblargon Thank you for your previous feedback, I finally took the time to understand what computed attributes are.

I added the computed attribute os_template to the resource, and updated the documentation to show how to use it.

One major thing: Could we look into up streaming the logic to interact with the PVE API to the SDK project? As the Terraform provider should only be a facade, since it's quite difficult to test.

About that, I think I also get what you meant, is it about these two features ?

  • Add client.DeleteTemplate or Template.Delete to the SDK to avoid using client.Delete(url)
  • Add client.GetTemplate(nodeName, storage, template) or client.GetTemplate(osTemplate) to the SDK instead of looping through the storageContent till finding the template

Can you confirm it is the logic that should be up streamed to the SDK project ? If this is the case, I can try working on it.

Finally, I tested the computed attribute with the following config if you wish to test it (proxmox v9) :

resource "proxmox_storage_template" "template_example" {
  pve_node = "pve"
  storage = "local"
  template = "alpine-3.22-default_20250617_amd64.tar.xz"
}


resource "proxmox_lxc" "lxc_examplte" {
  vmid                    = 123

  cores                   = 1
  memory                  = 1024
  swap                    = 0

  hostname                = "test"
  target_node             = "pve"
  ostemplate              = proxmox_storage_template.template_example.os_template
  password                = "12345"
  start                   = true
  force                   = false
  unprivileged            = false

  features {
    nesting = true
  }

  rootfs {
    size    = "1G"
    storage = "local-lvm"
  }

  network {
    name     = "eth0"
    bridge   = "vmbr2"
    firewall = true
    ip       = "dhcp"
  }
}

@Tinyblargon
Copy link
Collaborator

About that, I think I also get what you meant, is it about these two features ?

Add client.DeleteTemplate or Template.Delete to the SDK to avoid using client.Delete(url)
Add client.GetTemplate(nodeName, storage, template) or client.GetTemplate(osTemplate) to the SDK instead of looping through the storageContent till finding the template

Can you confirm it is the logic that should be up streamed to the SDK project ? If this is the case, I can try working on it.

Yes that's exactly the logic I meant.

Preferably Template.Delete(), Template.Download(), Template.Exists() where Template is the config of the template. Probably something like:

type LxcTemplate struct {
    Node NodeName
    Storage string
    File string
}

@Tinyblargon
Copy link
Collaborator

We do already have https://github.com/Telmate/proxmox-api-go/blob/0e194e6b413db3c7030797fe850dd5596b284948/proxmox/config__lxc__new.go#L766 not sure if we should extend that or not, as we don't need to know the node there, so adding it would just be confusing. But I'm feel having ConfigLxcTemplate and LxcTemplate is kind of silly,

@NemoDacremont
Copy link
Contributor Author

NemoDacremont commented Oct 28, 2025

@Tinyblargon I think this patch starts to reach a state I like. Here's a summary of what happened and what I tested to help to reproduce.

Latest commits

In the last patches, I use the latest methods I introduced in my recent PR on Telmate/proxmox-api-go#495. I also introduced _toConfigContent_Template which helps with preventing duplicating code to translate *ResourceData into ConfigContent_Template.

To summarize, the patch :

  • Adds _toConfigContent_Template to translate d to a template config
  • Uses template.Download instead of pveSDK.DownloadLxcTemplate
  • Uses template.Exists instead of looping through unclear content storage
  • Uses template.Delete instead of forging a specific URL to delete the template

The CI fails obviously because the SDK version requires to be bumped before it can pass the tests. However, beware that it seems there is other issues with the latest commit as explained below

Tests

I tested this patch, and here is how you can reproduce them. I tested on the base Proxmox image v9.0.11 I build with NemoDacremont/proxmox-ve for VirtualBox. I followed your tip, and used replace github.com/Telmate/proxmox-api-go => <my local path>/proxmox-api-go in the terraform provider go.mod and ran go mod tidy. I used the latest master commit, which was 0ccdf663a0fb11fe20a757ce567b4d82046f6aaa on Telmate/proxmox-api-go.

However, I had issues while compiling the provider, and had to comment out lines defining Onboot and Startup attributes in ConfigQemu struct, since it seems to not exist anymore. To easily reproduce it, here is the exact diff I applied : https://gist.github.com/NemoDacremont/df7f81f28d870d51b5d1fb2b46e034e5

Test: Create, Read, Delete, Recreate

Using this terraform config :

resource "proxmox_storage_template" "template_example" {
  pve_node = "pve"
  storage = "local"
  template = "alpine-3.22-default_20250617_amd64.tar.xz"
}

I applied once to test it downloading the template, then I applied again to make sure it detected it already being there. I finished by removing it and applying to make sure it was deleted. I finally recreated it to make sure it could redownload it.

Download template and a LXC using the computed attribute

In order to make sure the LXC could use the computed attribute os_template in its configuration, and that the template was downloaded before the LXC creation, I tested with the following config :

resource "proxmox_storage_template" "template_example" {
  pve_node = "pve"
  storage = "local"
  template = "alpine-3.22-default_20250617_amd64.tar.xz"
}

resource "proxmox_lxc" "lxc_servers" {
  vmid                    = 123

  cores                   = 1
  memory                  = 1024
  swap                    = 0
  hostname                = "test"
  target_node             = "pve"
  ostemplate              = proxmox_storage_template.template_example.os_template
  password                = "password"
  unprivileged            = true

  features {
    nesting = true
  }

  rootfs {
    size    = "1G"
    storage = "local-lvm"
  }
}

It worked well, and I could start the LXC manually then.

@NemoDacremont
Copy link
Contributor Author

Hi @Tinyblargon

I've been busy lately, and I couldn't find time to continue what I was doing on proxmox-api, I'll catch up later when I'll have more time. Meanwhile, I've seen that you bumped the proxmox-api version, and it seems like this feature should pass the CI now if you're interested in merging it

@Tinyblargon
Copy link
Collaborator

@NemoDacremont There are 2 minor changes I'd make to the Terraform interface. If it's okay with you I can merge this as is and do the minor renames. Everything else looks good.

  1. pve_node should be node.
  2. template should be file.

In the future I'd like to add options to download from a url, and support values from the template menu in PVE.
In this situation template would become:

resource "proxmox_storage_template" "template_example" {
  node = "pve"
  storage = "local"
  template {
    package = "debian-13-standard"
    version = "13.1-2" # no version will download what is the latest version at resource creation.
  }
}

@NemoDacremont
Copy link
Contributor Author

Ok no problem @Tinyblargon , I've just started working on it, I'll leave the download from url for a later PR if that's fine with you. The move from pve_node to node will be fairly straightforward, and I'll see what I can do for the template field, but it should be ok

@NemoDacremont
Copy link
Contributor Author

NemoDacremont commented Jan 7, 2026

Hey @Tinyblargon, I could find time to finish the changes you requested

If it's okay with you I can merge this as is and do the minor renames. Everything else looks good.

Well, maybe I shouldn't have read your message that fast, maybe it would have been simpler for you to just do it...

In the future I'd like to add options to download from a url, and support values from the template menu in PVE. In this situation template would become:

As expected, I didn't implement the download from url, but I introduced a new block "template" that supports "file" or package[+version] to define the template.

The patch might be a little verbose, however I think it works fine and hope it meets your expectations with the package and version.

I also updated the docs to define a resource template.

Feel free to change the PR or merge and apply another fix.

I tested with this terraform file :

resource "proxmox_storage_template" "alpine322" {
  node = "pve"
  storage = "local"
  template {
    file = "alpine-3.22-default_20250617_amd64.tar.xz"
  }
}

# resource "proxmox_storage_template" "alpine322" {  # Should fail with explicit error message
#   node = "pve"
#   storage = "local"
#   template {
#   }
# }

# resource "proxmox_storage_template" "alpine322" {
#   node = "pve"
#   storage = "local"
#   template {
#     package = "alpine-3.22-default"
#   }
# }

# resource "proxmox_storage_template" "alpine322" {
#   node = "pve"
#   storage = "local"
#   template {
#     package = "alpine-3.22-default"
#     version = "20250617"
#   }
# }

# resource "proxmox_storage_template" "alpine322" {  # Should fail, version doesn't exists
#   node = "pve"
#   storage = "local"
#   template {
#     package = "alpine-3.22-default"
#     version = "1337"
#   }
# }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Denotes a PR that changes 100-499 lines, ignoring generated files test/needen This PR has to be tested type/feature Completely new functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Add CT templates resource

2 participants