Skip to content

Conversation

@lonelysadness
Copy link

Hello,
I've started work on ACME certificate ordering, and it is functioning in my tests using the Desec provider with the DNS-01 challenge.

While the implementation could likely use further optimization, I wanted to share it here as a starting point. Apologies if any parts of the code are rough. Feedback and suggestions are welcome!

Contributor's Note

  • I have added / updated documentation in /docs for any user-facing features or additions.
  • I have added / updated acceptance tests in /fwprovider/tests for any new or updated resources / data sources.
  • I have ran make example to verify that the change works as expected.

Proof of Work

Community Note

  • Please vote on this pull request by adding a 👍 reaction to the original pull request comment to help the community and maintainers prioritize this request
  • Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for pull request followers and do not help prioritize the request

Relates #2157

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @lonelysadness, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the Proxmox Terraform provider by adding a new resource for automated ACME SSL certificate management. This new functionality allows users to declare and manage their Proxmox node certificates, including ordering new ones and handling renewals, directly within their Terraform configurations. It integrates with Proxmox's ACME client, supporting various challenge types and providing a streamlined approach to maintaining secure Proxmox environments.

Highlights

  • New ACME Certificate Resource: Introduces proxmox_virtual_environment_acme_certificate for managing SSL certificates on Proxmox VE nodes.
  • Automated Certificate Management: Enables ordering and renewal of ACME certificates (e.g., Let's Encrypt) directly through Terraform.
  • Challenge Type Support: Supports both HTTP-01 (standalone) and DNS-01 challenges, with configurable DNS plugins.
  • Force Renewal Option: Provides a force attribute to trigger certificate renewal even if not due.
  • Import Functionality: Allows importing existing ACME certificate configurations from Proxmox nodes into Terraform state.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new resource proxmox_virtual_environment_acme_certificate for managing ACME certificates. The implementation is a good starting point and covers creation, update, deletion, and import, along with documentation and examples.

My review focuses on improving robustness and correctness. Key points include:

  • Replacing time.Sleep with a more reliable polling mechanism to avoid race conditions.
  • Ensuring the correct certificate is identified after creation/update.
  • Using the appropriate API endpoint for updates to guarantee configuration changes are applied.
  • Minor improvements in documentation and context propagation.

The changes to proxmox/nodes/config.go and proxmox/nodes/config_types.go are solid improvements. Overall, this is a great feature addition.

@lonelysadness lonelysadness marked this pull request as draft October 28, 2025 23:35
@lonelysadness
Copy link
Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a valuable new resource for managing ACME certificates in Proxmox. The implementation is well-structured, particularly the use of a retry mechanism with exponential backoff for polling certificate status. However, there are several critical issues that need to be addressed, including a syntax error, a missing context parameter causing a build failure, and a decoding bug that would lead to a runtime panic. The documentation examples also require corrections to be functional. I've provided detailed comments and suggestions to resolve these issues.

Comment on lines 26 to 36
resource "proxmox_virtual_environment_acme_certificate" "example" {
node_name = "pve"
# Depends on the ACME account being created first
depends_on = [
proxmox_virtual_environment_acme_account.example
]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This example for a basic HTTP-01 challenge is missing the required account and domains attributes, which will cause it to fail. A similar issue exists in the 'Force Certificate Renewal' example starting on line 80. Please update both examples to include these required attributes to make them functional for users.

@lonelysadness
Copy link
Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new resource for managing ACME certificates, which is a great addition. The implementation is mostly solid, with good use of retries for asynchronous operations and a thoughtful approach to finding the correct certificate. I've found a critical issue in the certificate matching logic that needs to be addressed, as it will cause a panic. I've also suggested improvements to the documentation examples to make them complete and functional. Additionally, there's some dead code in the client library that could be cleaned up. Overall, this is a good starting point and with these fixes, it will be a valuable feature.

@lonelysadness lonelysadness force-pushed the feat/acme-certificate-ordering branch from 30d4168 to 8257ed7 Compare October 29, 2025 08:04
@lonelysadness
Copy link
Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new resource for managing ACME certificates, which is a valuable addition. The implementation is well-structured, particularly the use of a retry mechanism for handling asynchronous certificate ordering. My review focuses on improving the resource's lifecycle management to prevent stale configurations on Proxmox nodes. Specifically, I've suggested changes to ensure that ACME configurations are properly cleaned up during delete and update operations. These adjustments will enhance the resource's reliability and idempotency.

Comment on lines 451 to 490
func (r *acmeCertificateResource) Delete(
ctx context.Context,
req resource.DeleteRequest,
resp *resource.DeleteResponse,
) {
var state acmeCertificateModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

// Note: We don't actually delete the certificate from the node
// as it's needed for the node's services. We just remove it from Terraform state.
// If users want to remove the certificate from the node, they should use
// the Proxmox UI or manually delete it via the API.
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The Delete function currently only removes the resource from Terraform state, leaving the ACME configuration on the Proxmox node. This can lead to unexpected behavior and stale configurations. While the certificate files should remain, the node's configuration related to ACME (acme and acmedomain0-4 properties) should be cleaned up. This can be achieved by calling nodeClient.UpdateConfig with the delete parameter.

func (r *acmeCertificateResource) Delete(
	ctx context.Context,
	req resource.DeleteRequest,
	resp *resource.DeleteResponse,
) {
	var state acmeCertificateModel

	resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

	if resp.Diagnostics.HasError() {
		return
	}

	nodeName := state.NodeName.ValueString()
	nodeClient := r.client.Node(nodeName)

	// Clean up the ACME configuration from the node. The certificate files will remain.
	toDelete := "acme,acmedomain0,acmedomain1,acmedomain2,acmedomain3,acmedomain4"
	configUpdate := &nodes.ConfigUpdateRequestBody{
		Delete: &toDelete,
	}

	if err := nodeClient.UpdateConfig(ctx, configUpdate); err != nil {
		// Log a warning as the resource is being deleted anyway, but the user should be notified.
		resp.Diagnostics.AddWarning(
			"Failed to clean up node ACME configuration",
			fmt.Sprintf("An error occurred while cleaning up ACME settings for node %s on delete: %s. Manual cleanup of /etc/pve/nodes/%s/config may be required.", nodeName, err.Error(), nodeName),
		)
	}
}

Comment on lines 615 to 649
if len(dnsDomains) > 0 {
if len(dnsDomains) > 5 {
return fmt.Errorf("Proxmox supports a maximum of 5 DNS challenge domains, got %d", len(dnsDomains))
}

for i, domain := range dnsDomains {
domainConfig := &nodes.ACMEDomainConfig{
Domain: domain.Domain.ValueString(),
}

if !domain.Plugin.IsNull() {
plugin := domain.Plugin.ValueString()
domainConfig.Plugin = &plugin
}

if !domain.Alias.IsNull() {
alias := domain.Alias.ValueString()
domainConfig.Alias = &alias
}

// Map to the appropriate acmedomain field
switch i {
case 0:
configUpdate.ACMEDomain0 = domainConfig
case 1:
configUpdate.ACMEDomain1 = domainConfig
case 2:
configUpdate.ACMEDomain2 = domainConfig
case 3:
configUpdate.ACMEDomain3 = domainConfig
case 4:
configUpdate.ACMEDomain4 = domainConfig
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation for configuring DNS challenge domains does not clean up acmedomainX slots that are no longer in use. For instance, if a user changes from 3 DNS domains to 1, the configurations for acmedomain1 and acmedomain2 will persist on the Proxmox node. To ensure the resource is idempotent, you should explicitly clear any unused acmedomainX slots by using the delete parameter in the update request.

	if len(dnsDomains) > 5 {
		return fmt.Errorf("Proxmox supports a maximum of 5 DNS challenge domains, got %d", len(dnsDomains))
	}

	for i, domain := range dnsDomains {
		domainConfig := &nodes.ACMEDomainConfig{
			Domain: domain.Domain.ValueString(),
		}

		if !domain.Plugin.IsNull() {
			plugin := domain.Plugin.ValueString()
			domainConfig.Plugin = &plugin
		}

		if !domain.Alias.IsNull() {
			alias := domain.Alias.ValueString()
			domainConfig.Alias = &alias
		}

		// Map to the appropriate acmedomain field
		switch i {
		case 0:
			configUpdate.ACMEDomain0 = domainConfig
		case 1:
			configUpdate.ACMEDomain1 = domainConfig
		case 2:
			configUpdate.ACMEDomain2 = domainConfig
		case 3:
			configUpdate.ACMEDomain3 = domainConfig
		case 4:
			configUpdate.ACMEDomain4 = domainConfig
		}
	}

	// Clean up unused acmedomain slots
	var toDelete []string
	for i := len(dnsDomains); i < 5; i++ {
		toDelete = append(toDelete, fmt.Sprintf("acmedomain%d", i))
	}
	if len(toDelete) > 0 {
		deleteValue := strings.Join(toDelete, ",")
		configUpdate.Delete = &deleteValue
	}

lonelysadness and others added 14 commits October 29, 2025 09:25
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: lonelysadness <[email protected]>
Signed-off-by: lonelysadness <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: lonelysadness <[email protected]>
Signed-off-by: lonelysadness <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: lonelysadness <[email protected]>
Signed-off-by: lonelysadness <[email protected]>
Signed-off-by: lonelysadness <[email protected]>
@lonelysadness lonelysadness force-pushed the feat/acme-certificate-ordering branch from fd54e2b to fd0130c Compare October 29, 2025 08:25
@lonelysadness
Copy link
Author

I think I have a good functional starting point, I'll wait for @bpg opinion or someone else's. Thank you.

PS: Sorry for the spam with Gemini, I didn't see the mess it made in the PR.

@lonelysadness lonelysadness marked this pull request as ready for review October 29, 2025 20:01
@bpg
Copy link
Owner

bpg commented Oct 30, 2025

I think I have a good functional starting point, I'll wait for @bpg opinion or someone else's. Thank you.

PS: Sorry for the spam with Gemini, I didn't see the mess it made in the PR.

All good, thanks for contributing this @lonelysadness! ❤️

It will take some time to go over the PR tho...

Signed-off-by: Pavel Boldyrev <[email protected]>
// - Set PROXMOX_VE_ACC_ACME_DOMAIN environment variable
// - Set PROXMOX_VE_ACC_ACME_DNS_PLUGIN (optional, for DNS-01 challenge)
// The test will be skipped if these are not set.
func TestAccResourceACMECertificate(t *testing.T) {
Copy link
Owner

Choose a reason for hiding this comment

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

Could you add more details here or to the PR description how to set up a test environment to run the tests, what acme server to use, how to set it up, etc.?

Just links to the docs and brief description, enough to anyone else to set that up from scratch.

Comment on lines +38 to +40
if nodeName == "" {
nodeName = "pve"
}
Copy link
Owner

Choose a reason for hiding this comment

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

this is redundant, te.NodeName defaults to pve

}]`

te.AddTemplateVars(map[string]interface{}{
"NodeName": nodeName,
Copy link
Owner

Choose a reason for hiding this comment

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

it is already defined

},
retry.Attempts(30), // Maximum 30 attempts
retry.Delay(1*time.Second), // Start with 1 second delay
retry.DelayType(retry.BackOffDelay), // Use exponential backoff
Copy link
Owner

Choose a reason for hiding this comment

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

exponential backoff with 30 attempts is a way too long IMO,
prob. just a fixed delay (5 sec? 10 sec?) backoff?

retry.Attempts(30), // Maximum 30 attempts
retry.Delay(1*time.Second), // Start with 1 second delay
retry.DelayType(retry.BackOffDelay), // Use exponential backoff
retry.MaxJitter(500*time.Millisecond), // Add jitter to prevent thundering herd
Copy link
Owner

Choose a reason for hiding this comment

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

not sure if that will prevent anything in this scenario 😅

Force: &force,
}

taskID, err := nodeClient.OrderCertificate(ctx, orderReq)
Copy link
Owner

Choose a reason for hiding this comment

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

looks like we'll send the order call regardless if there were changes or not, or value of the force flag.

should we add a condition here like if plan.Force.ValueBool() || !plan.Account.Equal(state.Account) || !plan.Domains.Equal(state.Domains) {....

// It prioritizes ACME certificates (issued by certificate authorities like Let's Encrypt)
// over Proxmox-generated certificates. When multiple certificates match the configured domains,
// it returns the one with the most matching domains.
func (r *acmeCertificateResource) findMatchingCertificate(
Copy link
Owner

Choose a reason for hiding this comment

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

this logic seems pretty complex
i'd like to see unit test covering matching scenarios with some edge cases

@bpg bpg added the ⌛ pending author's response Requested additional information from the reporter label Nov 1, 2025
@lonelysadness
Copy link
Author

Thanks for all your feedback. I was busy, I'll take a look in the week !

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

Labels

⌛ pending author's response Requested additional information from the reporter size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants