0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00

feat(acme): implement resources and data sources for ACME plugins (#1479)

* feat(acme): implement CRUD API for proxmox cluster ACME plugins

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* feat(acme): implement acme_plugins data source

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* feat(acme): implement acme_plugin data source

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* feat(acme): implement plugin resource creation

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* feat(acme): implement plugin resource read

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* feat(acme): implement plugin resource update

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* feat(acme): implement plugin resource deletion

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* feat(acme): implement plugin resource import

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* docs(acme): generate documentation

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* fix: apply suggestions from code review

Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* refactor: extract common fields into BasePluginData

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* fix: restrict plugin resource to type=dns only

because type=standalone is not configurable and always enabled by
default.

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* fix: remove unused 'nodes' property

https://github.com/bpg/terraform-provider-proxmox/pull/1479/files#r1710916265

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* fix: remove "delete" property

https://github.com/bpg/terraform-provider-proxmox/pull/1479/files#r1710908809

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* feat: implement attribute deletion

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* fix: ignore empty lines in dns plugin data

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* fix: partial revert of code review suggestions

Joining the values with a string literal would produce \\n instead of \n
and splitting at \\n doesn't match a newline.

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* refactor: extract acme plugin models into separate file

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

* fix: format disable parameter as int

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>

---------

Signed-off-by: Björn Brauer <zaubernerd@zaubernerd.de>
Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Björn Brauer 2024-09-08 16:54:16 +02:00 committed by GitHub
parent 112f058e66
commit a6eb81af08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1049 additions and 1 deletions

View File

@ -0,0 +1,39 @@
---
layout: page
title: proxmox_virtual_environment_acme_plugin
parent: Data Sources
subcategory: Virtual Environment
description: |-
Retrieves a single ACME plugin by plugin ID name.
---
# Data Source: proxmox_virtual_environment_acme_plugin
Retrieves a single ACME plugin by plugin ID name.
## Example Usage
```terraform
data "proxmox_virtual_environment_acme_plugin" "example" {
plugin = "standalone"
}
output "data_proxmox_virtual_environment_acme_plugin" {
value = data.proxmox_virtual_environment_acme_plugin.example
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `plugin` (String) ACME Plugin ID name.
### Read-Only
- `api` (String) API plugin name.
- `data` (Map of String) DNS plugin data.
- `digest` (String) Prevent changes if current configuration file has a different digest. This can be used to prevent concurrent modifications.
- `type` (String) ACME challenge type (dns, standalone).
- `validation_delay` (Number) Extra delay in seconds to wait before requesting validation. Allows to cope with a long TTL of DNS records (0 - 172800).

View File

@ -0,0 +1,41 @@
---
layout: page
title: proxmox_virtual_environment_acme_plugins
parent: Data Sources
subcategory: Virtual Environment
description: |-
Retrieves the list of ACME plugins.
---
# Data Source: proxmox_virtual_environment_acme_plugins
Retrieves the list of ACME plugins.
## Example Usage
```terraform
data "proxmox_virtual_environment_acme_plugins" "example" {}
output "data_proxmox_virtual_environment_acme_plugins" {
value = data.proxmox_virtual_environment_acme_plugins.example.plugins
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Read-Only
- `plugins` (Attributes List) List of ACME plugins (see [below for nested schema](#nestedatt--plugins))
<a id="nestedatt--plugins"></a>
### Nested Schema for `plugins`
Read-Only:
- `api` (String) API plugin name.
- `data` (Map of String) DNS plugin data.
- `digest` (String) Prevent changes if current configuration file has a different digest. This can be used to prevent concurrent modifications.
- `plugin` (String) ACME Plugin ID name.
- `type` (String) ACME challenge type (dns, standalone).
- `validation_delay` (Number) Extra delay in seconds to wait before requesting validation. Allows to cope with a long TTL of DNS records (0 - 172800).

View File

@ -0,0 +1,50 @@
---
layout: page
title: proxmox_virtual_environment_acme_dns_plugin
parent: Resources
subcategory: Virtual Environment
description: |-
Manages an ACME plugin in a Proxmox VE cluster.
---
# Resource: proxmox_virtual_environment_acme_dns_plugin
Manages an ACME plugin in a Proxmox VE cluster.
## Example Usage
```terraform
resource "proxmox_virtual_environment_acme_dns_plugin" "example" {
plugin = "test"
api = "aws"
data = {
AWS_ACCESS_KEY_ID = "EXAMPLE"
AWS_SECRET_ACCESS_KEY = "EXAMPLE"
}
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `api` (String) API plugin name.
- `plugin` (String) ACME Plugin ID name.
### Optional
- `data` (Map of String) DNS plugin data.
- `digest` (String) SHA1 digest of the current configuration. Prevent changes if current configuration file has a different digest. This can be used to prevent concurrent modifications.
- `disable` (Boolean) Flag to disable the config.
- `validation_delay` (Number) Extra delay in seconds to wait before requesting validation. Allows to cope with a long TTL of DNS records (0 - 172800).
## Import
Import is supported using the following syntax:
```shell
#!/usr/bin/env sh
# ACME accounts can be imported using their name, e.g.:
terraform import proxmox_virtual_environment_acme_dns_plugin.example test
```

View File

@ -0,0 +1,7 @@
data "proxmox_virtual_environment_acme_plugin" "example" {
plugin = "standalone"
}
output "data_proxmox_virtual_environment_acme_plugin" {
value = data.proxmox_virtual_environment_acme_plugin.example
}

View File

@ -0,0 +1,5 @@
data "proxmox_virtual_environment_acme_plugins" "example" {}
output "data_proxmox_virtual_environment_acme_plugins" {
value = data.proxmox_virtual_environment_acme_plugins.example.plugins
}

View File

@ -0,0 +1,3 @@
#!/usr/bin/env sh
# ACME accounts can be imported using their name, e.g.:
terraform import proxmox_virtual_environment_acme_dns_plugin.example test

View File

@ -0,0 +1,8 @@
resource "proxmox_virtual_environment_acme_dns_plugin" "example" {
plugin = "test"
api = "aws"
data = {
AWS_ACCESS_KEY_ID = "EXAMPLE"
AWS_SECRET_ACCESS_KEY = "EXAMPLE"
}
}

View File

@ -0,0 +1,153 @@
/*
* 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 acme
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &acmePluginDatasource{}
_ datasource.DataSourceWithConfigure = &acmePluginDatasource{}
)
// NewACMEPluginDataSource is a helper function to simplify the provider implementation.
func NewACMEPluginDataSource() datasource.DataSource {
return &acmePluginDatasource{}
}
// acmePluginDatasource is the data source implementation for ACME plugin.
type acmePluginDatasource struct {
client *plugins.Client
}
// Metadata returns the data source type name.
func (d *acmePluginDatasource) Metadata(
_ context.Context,
req datasource.MetadataRequest,
resp *datasource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_acme_plugin"
}
// Schema returns the schema for the data source.
func (d *acmePluginDatasource) Schema(
_ context.Context,
_ datasource.SchemaRequest,
resp *datasource.SchemaResponse,
) {
resp.Schema = schema.Schema{
Description: "Retrieves a single ACME plugin by plugin ID name.",
Attributes: map[string]schema.Attribute{
"api": schema.StringAttribute{
Description: "API plugin name.",
Computed: true,
},
"data": schema.MapAttribute{
Description: "DNS plugin data.",
Computed: true,
ElementType: types.StringType,
},
"digest": schema.StringAttribute{
Description: "Prevent changes if current configuration file has a different digest. " +
"This can be used to prevent concurrent modifications.",
Computed: true,
},
"plugin": schema.StringAttribute{
Description: "ACME Plugin ID name.",
Required: true,
},
"type": schema.StringAttribute{
Description: "ACME challenge type (dns, standalone).",
Computed: true,
Validators: []validator.String{
stringvalidator.OneOf("dns", "standalone"),
},
},
"validation_delay": schema.Int64Attribute{
Description: "Extra delay in seconds to wait before requesting validation. " +
"Allows to cope with a long TTL of DNS records (0 - 172800).",
Computed: true,
Validators: []validator.Int64{
int64validator.Between(0, 172800),
},
},
},
}
}
// Configure adds the provider-configured client to the data source.
func (d *acmePluginDatasource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(proxmox.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
)
return
}
d.client = client.Cluster().ACME().Plugins()
}
// Read fetches the ACME plugin from the Proxmox cluster.
func (d *acmePluginDatasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var state acmePluginModel
resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
id := state.Plugin.ValueString()
plugin, err := d.client.Get(ctx, id)
if err != nil {
resp.Diagnostics.AddError(
"Unable to read ACME plugin",
err.Error(),
)
return
}
state.API = types.StringValue(plugin.API)
mapValue, diags := types.MapValueFrom(ctx, types.StringType, plugin.Data)
resp.Diagnostics.Append(diags...)
state.Data = mapValue
state.Digest = types.StringValue(plugin.Digest)
state.Plugin = types.StringValue(plugin.Plugin)
state.Type = types.StringValue(plugin.Type)
state.ValidationDelay = types.Int64Value(plugin.ValidationDelay)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

View File

@ -0,0 +1,158 @@
/*
* 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 acme
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &acmePluginsDatasource{}
_ datasource.DataSourceWithConfigure = &acmePluginsDatasource{}
)
// NewACMEPluginsDataSource is a helper function to simplify the provider implementation.
func NewACMEPluginsDataSource() datasource.DataSource {
return &acmePluginsDatasource{}
}
// acmePluginsDatasource is the data source implementation for ACME plugins.
type acmePluginsDatasource struct {
client *plugins.Client
}
// Metadata returns the data source type name.
func (d *acmePluginsDatasource) Metadata(
_ context.Context,
req datasource.MetadataRequest,
resp *datasource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_acme_plugins"
}
// Schema returns the schema for the data source.
func (d *acmePluginsDatasource) Schema(
_ context.Context,
_ datasource.SchemaRequest,
resp *datasource.SchemaResponse,
) {
resp.Schema = schema.Schema{
Description: "Retrieves the list of ACME plugins.",
Attributes: map[string]schema.Attribute{
"plugins": schema.ListNestedAttribute{
Description: "List of ACME plugins",
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"api": schema.StringAttribute{
Description: "API plugin name.",
Computed: true,
},
"data": schema.MapAttribute{
Description: "DNS plugin data.",
Computed: true,
ElementType: types.StringType,
},
"digest": schema.StringAttribute{
Description: "Prevent changes if current configuration file has a different digest. " +
"This can be used to prevent concurrent modifications.",
Computed: true,
},
"plugin": schema.StringAttribute{
Description: "ACME Plugin ID name.",
Computed: true,
},
"type": schema.StringAttribute{
Description: "ACME challenge type (dns, standalone).",
Computed: true,
Validators: []validator.String{
stringvalidator.OneOf("dns", "standalone"),
},
},
"validation_delay": schema.Int64Attribute{
Description: "Extra delay in seconds to wait before requesting validation. " +
"Allows to cope with a long TTL of DNS records (0 - 172800).",
Computed: true,
Validators: []validator.Int64{
int64validator.Between(0, 172800),
},
},
},
},
Computed: true,
},
},
}
}
// Configure adds the provider-configured client to the data source.
func (d *acmePluginsDatasource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(proxmox.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
)
return
}
d.client = client.Cluster().ACME().Plugins()
}
// Read fetches the list of ACME plugins from the Proxmox cluster.
func (d *acmePluginsDatasource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) {
var state acmePluginsModel
list, err := d.client.List(ctx)
if err != nil {
resp.Diagnostics.AddError(
"Unable to read ACME plugins",
err.Error(),
)
return
}
for _, plugin := range list {
mapValue, diags := types.MapValueFrom(ctx, types.StringType, plugin.Data)
resp.Diagnostics.Append(diags...)
state.Plugins = append(state.Plugins, acmePluginModel{
baseACMEPluginModel: baseACMEPluginModel{
API: types.StringValue(plugin.API),
Data: mapValue,
Digest: types.StringValue(plugin.Digest),
Plugin: types.StringValue(plugin.Plugin),
ValidationDelay: types.Int64Value(plugin.ValidationDelay),
},
Type: types.StringValue(plugin.Type),
})
}
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

View File

@ -0,0 +1,43 @@
/*
* 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 acme
import (
"github.com/hashicorp/terraform-plugin-framework/types"
)
// acmePluginsModel maps the schema data for the ACME plugins data source.
type acmePluginsModel struct {
Plugins []acmePluginModel `tfsdk:"plugins"`
}
type baseACMEPluginModel struct {
// API plugin name
API types.String `tfsdk:"api"`
// DNS plugin data
Data types.Map `tfsdk:"data"`
// Prevent changes if current configuration file has a different digest.
// This can be used to prevent concurrent modifications.
Digest types.String `tfsdk:"digest"`
// Plugin ID name
Plugin types.String `tfsdk:"plugin"`
// Extra delay in seconds to wait before requesting validation (0 - 172800)
ValidationDelay types.Int64 `tfsdk:"validation_delay"`
}
// acmePluginModel maps the schema data for an ACME plugin.
type acmePluginModel struct {
baseACMEPluginModel
Type types.String `tfsdk:"type"`
}
// acmePluginCreateModel maps the schema data for an ACME plugin.
type acmePluginCreateModel struct {
baseACMEPluginModel
// Flag to disable the config
Disable types.Bool `tfsdk:"disable"`
}

View File

@ -0,0 +1,306 @@
/*
* 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 acme
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins"
)
var (
_ resource.Resource = &acmePluginResource{}
_ resource.ResourceWithConfigure = &acmePluginResource{}
_ resource.ResourceWithImportState = &acmePluginResource{}
)
// NewACMEPluginResource creates a new resource for managing ACME plugins.
func NewACMEPluginResource() resource.Resource {
return &acmePluginResource{}
}
// acmePluginResource contains the resource's internal data.
type acmePluginResource struct {
// The ACME account API client
client plugins.Client
}
// Metadata defines the name of the resource.
func (r *acmePluginResource) Metadata(
_ context.Context,
req resource.MetadataRequest,
resp *resource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_acme_dns_plugin"
}
// Schema defines the schema for the resource.
func (r *acmePluginResource) Schema(
_ context.Context,
_ resource.SchemaRequest,
resp *resource.SchemaResponse,
) {
resp.Schema = schema.Schema{
Description: "Manages an ACME plugin in a Proxmox VE cluster.",
Attributes: map[string]schema.Attribute{
"api": schema.StringAttribute{
Description: "API plugin name.",
Required: true,
},
"data": schema.MapAttribute{
Description: "DNS plugin data.",
Optional: true,
ElementType: types.StringType,
},
"digest": schema.StringAttribute{
Description: "SHA1 digest of the current configuration.",
MarkdownDescription: "SHA1 digest of the current configuration. " +
"Prevent changes if current configuration file has a different digest. " +
"This can be used to prevent concurrent modifications.",
Optional: true,
Computed: true,
},
"disable": schema.BoolAttribute{
Description: "Flag to disable the config.",
Optional: true,
},
"plugin": schema.StringAttribute{
Description: "ACME Plugin ID name.",
Required: true,
},
"validation_delay": schema.Int64Attribute{
Description: "Extra delay in seconds to wait before requesting validation.",
MarkdownDescription: "Extra delay in seconds to wait before requesting validation. " +
"Allows to cope with a long TTL of DNS records (0 - 172800).",
Optional: true,
Computed: true,
Default: int64default.StaticInt64(30),
Validators: []validator.Int64{
int64validator.Between(0, 172800),
},
},
},
}
}
// Configure accesses the provider-configured Proxmox API client on behalf of the resource.
func (r *acmePluginResource) Configure(
_ context.Context,
req resource.ConfigureRequest,
resp *resource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(proxmox.Client)
if ok {
r.client = *client.Cluster().ACME().Plugins()
} else {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
)
}
}
// Create creates a new ACME plugin on the Proxmox cluster.
func (r *acmePluginResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan acmePluginCreateModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
createRequest := &plugins.ACMEPluginsCreateRequestBody{}
createRequest.Plugin = plan.Plugin.ValueString()
createRequest.Type = "dns"
createRequest.API = plan.API.ValueString()
data := make(plugins.DNSPluginData)
plan.Data.ElementsAs(ctx, &data, false)
createRequest.Data = &data
createRequest.Disable = plan.Disable.ValueBool()
createRequest.ValidationDelay = plan.ValidationDelay.ValueInt64()
err := r.client.Create(ctx, createRequest)
if err != nil {
if !strings.Contains(err.Error(), "already exists") {
resp.Diagnostics.AddError(
fmt.Sprintf("Unable to create ACME plugin '%s'", createRequest.Plugin),
err.Error(),
)
return
}
resp.Diagnostics.AddError(
fmt.Sprintf("ACME plugin '%s' already exists", createRequest.Plugin),
err.Error(),
)
}
plugin, err := r.client.Get(ctx, plan.Plugin.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Unable to read ACME plugin",
err.Error(),
)
return
}
plan.Digest = types.StringValue(plugin.Digest)
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}
// Read retrieves the current state of the ACME plugin from the Proxmox cluster.
func (r *acmePluginResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state acmePluginCreateModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
id := state.Plugin.ValueString()
plugin, err := r.client.Get(ctx, id)
if err != nil {
resp.Diagnostics.AddError(
"Unable to read ACME plugin",
err.Error(),
)
return
}
state.API = types.StringValue(plugin.API)
state.Digest = types.StringValue(plugin.Digest)
state.ValidationDelay = types.Int64Value(plugin.ValidationDelay)
mapValue, diags := types.MapValueFrom(ctx, types.StringType, plugin.Data)
resp.Diagnostics.Append(diags...)
state.Data = mapValue
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
// Update modifies an existing ACME plugin on the Proxmox cluster.
func (r *acmePluginResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state acmePluginCreateModel
toDelete := make([]string, 0)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
updateRequest := &plugins.ACMEPluginsUpdateRequestBody{}
updateRequest.API = plan.API.ValueString()
data := make(plugins.DNSPluginData)
plan.Data.ElementsAs(ctx, &data, false)
if plan.Data.IsNull() && !state.Data.IsNull() {
toDelete = append(toDelete, "data")
} else {
updateRequest.Data = &data
}
updateRequest.Digest = plan.Digest.ValueString()
if plan.Disable.IsNull() && !state.Disable.IsNull() || !plan.Disable.ValueBool() {
toDelete = append(toDelete, "disable")
} else {
updateRequest.Disable = plan.Disable.ValueBool()
}
if plan.ValidationDelay.IsNull() && !state.ValidationDelay.IsNull() {
toDelete = append(toDelete, "validation_delay")
} else {
updateRequest.ValidationDelay = plan.ValidationDelay.ValueInt64()
}
if len(toDelete) > 0 {
updateRequest.Delete = strings.Join(toDelete, ",")
}
err := r.client.Update(ctx, plan.Plugin.ValueString(), updateRequest)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Unable to update ACME account '%s'", plan.Plugin.ValueString()),
err.Error(),
)
return
}
plugin, err := r.client.Get(ctx, plan.Plugin.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Unable to read ACME plugin",
err.Error(),
)
return
}
plan.Digest = types.StringValue(plugin.Digest)
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}
// Delete removes an existing ACME plugin from the Proxmox cluster.
func (r *acmePluginResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state acmePluginCreateModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
err := r.client.Delete(ctx, state.Plugin.ValueString())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Unable to delete ACME plugin '%s'", state.Plugin.ValueString()),
err.Error(),
)
}
}
// ImportState retrieves the current state of an existing ACME plugin from the Proxmox cluster.
func (r *acmePluginResource) ImportState(
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
) {
resource.ImportStatePassthroughID(ctx, path.Root("plugin"), req, resp)
}

View File

@ -446,6 +446,7 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc
NewClusterOptionsResource,
NewDownloadFileResource,
acme.NewACMEAccountResource,
acme.NewACMEPluginResource,
apt.NewRepositoryResource,
apt.NewStandardRepositoryResource,
access.NewACLResource,
@ -465,6 +466,8 @@ func (p *proxmoxProvider) DataSources(_ context.Context) []func() datasource.Dat
NewVersionDataSource,
acme.NewACMEAccountsDataSource,
acme.NewACMEAccountDataSource,
acme.NewACMEPluginsDataSource,
acme.NewACMEPluginDataSource,
apt.NewRepositoryDataSource,
apt.NewStandardRepositoryDataSource,
ha.NewHAGroupDataSource,

View File

@ -11,6 +11,7 @@ import (
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/account"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins"
)
// Client is an interface for accessing the Proxmox ACME API.
@ -18,7 +19,7 @@ type Client struct {
api.Client
}
// ExpandPath expands a relative path to a full cluster API path.
// ExpandPath expands a relative path to a full cluster ACME API path.
func (c *Client) ExpandPath(path string) string {
return fmt.Sprintf("cluster/acme/%s", path)
}
@ -27,3 +28,8 @@ func (c *Client) ExpandPath(path string) string {
func (c *Client) Account() *account.Client {
return &account.Client{Client: c.Client}
}
// Plugins returns a client for managing the cluster's ACME plugins.
func (c *Client) Plugins() *plugins.Client {
return &plugins.Client{Client: c.Client}
}

View File

@ -0,0 +1,83 @@
/*
* 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 plugins
import (
"context"
"fmt"
"net/http"
"net/url"
"sort"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
)
// List returns a list of ACME plugins.
func (c *Client) List(ctx context.Context) ([]*ACMEPluginsListResponseData, error) {
resBody := &ACMEPluginsListResponseBody{}
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(""), nil, resBody)
if err != nil {
return nil, fmt.Errorf("error listing ACME plugins: %w", err)
}
if resBody.Data == nil {
return nil, api.ErrNoDataObjectInResponse
}
sort.Slice(resBody.Data, func(i, j int) bool {
return resBody.Data[i].Plugin < resBody.Data[j].Plugin
})
return resBody.Data, nil
}
// Get retrieves a single ACME plugin based on its identifier.
func (c *Client) Get(ctx context.Context, id string) (*ACMEPluginsGetResponseData, error) {
resBody := &ACMEPluginsGetResponseBody{}
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(url.PathEscape(id)), nil, resBody)
if err != nil {
return nil, fmt.Errorf("error reading ACME plugin: %w", err)
}
if resBody.Data == nil {
return nil, api.ErrNoDataObjectInResponse
}
return resBody.Data, nil
}
// Create creates a new ACME plugin.
func (c *Client) Create(ctx context.Context, data *ACMEPluginsCreateRequestBody) error {
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath(""), data, nil)
if err != nil {
return fmt.Errorf("error creating ACME plugin: %w", err)
}
return nil
}
// Update updates an existing ACME plugin.
func (c *Client) Update(ctx context.Context, id string, data *ACMEPluginsUpdateRequestBody) error {
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath(url.PathEscape(id)), data, nil)
if err != nil {
return fmt.Errorf("error updating ACME plugin: %w", err)
}
return nil
}
// Delete removes an ACME plugin.
func (c *Client) Delete(ctx context.Context, id string) error {
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(url.PathEscape(id)), nil, nil)
if err != nil {
return fmt.Errorf("error deleting ACME plugin: %w", err)
}
return nil
}

View File

@ -0,0 +1,117 @@
/*
* 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 plugins
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"strings"
)
// BaseACMEPluginData contains common fields for ACME plugin data.
type BaseACMEPluginData struct {
// ACME challenge type (dns, standalone).
Type string `json:"type,omitempty" url:"type,omitempty"`
// Prevent changes if current configuration file has a different digest. This can be used to prevent concurrent modifications.
Digest string `json:"digest,omitempty" url:"digest,omitempty"`
// API plugin name
API string `json:"api,omitempty" url:"api,omitempty"`
// Extra delay in seconds to wait before requesting validation. Allows to cope with a long TTL of DNS records (0 - 172800).
ValidationDelay int64 `json:"validation-delay,omitempty" url:"validation-delay,omitempty"`
}
// ACMEPluginsListResponseBody contains the body from an ACME plugins list response.
type ACMEPluginsListResponseBody struct {
// Unique identifier for ACME plugin instance.
Data []*ACMEPluginsListResponseData `json:"data,omitempty"`
}
// ACMEPluginsListResponseData contains the data from an ACME plugins list response.
type ACMEPluginsListResponseData struct {
BaseACMEPluginData
// ACME Plugin ID name
Plugin string `json:"plugin" url:"plugin"`
// DNS plugin data.
Data *DNSPluginData `json:"data,omitempty"`
}
// ACMEPluginsGetResponseBody contains the body from an ACME plugins get response.
type ACMEPluginsGetResponseBody struct {
Data *ACMEPluginsGetResponseData `json:"data,omitempty"`
}
// ACMEPluginsGetResponseData contains the data from an ACME plugins get response.
type ACMEPluginsGetResponseData struct {
BaseACMEPluginData
// ACME Plugin ID name
Plugin string `json:"plugin" url:"plugin"`
// DNS plugin data.
Data *DNSPluginData `json:"data"`
}
// ACMEPluginsCreateRequestBody contains the body for creating a new ACME plugin.
type ACMEPluginsCreateRequestBody struct {
BaseACMEPluginData
// ACME Plugin ID name
Plugin string `json:"id" url:"id"`
// DNS plugin data. (base64 encoded)
Data *DNSPluginData `url:"data,omitempty"`
// Flag to disable the config.
Disable bool `url:"disable,omitempty,int"`
}
// ACMEPluginsUpdateRequestBody contains the body for updating an existing ACME plugin.
type ACMEPluginsUpdateRequestBody struct {
BaseACMEPluginData
// DNS plugin data. (base64 encoded)
Data *DNSPluginData `url:"data,omitempty"`
// A list of settings you want to delete.
Delete string `url:"delete,omitempty"`
// Flag to disable the config.
Disable bool `url:"disable,omitempty,int"`
}
// DNSPluginData is a map of DNS plugin data.
type DNSPluginData map[string]string
// EncodeValues encodes the DNSPluginData into the URL values.
func (d DNSPluginData) EncodeValues(key string, v *url.Values) error {
values := make([]string, 0, len(d))
for key, value := range d {
values = append(values, fmt.Sprintf("%s=%s", key, value))
}
v.Add(key, base64.StdEncoding.EncodeToString([]byte(strings.Join(values, "\n"))))
return nil
}
// UnmarshalJSON unmarshals a DNSPluginData struct from JSON.
func (d *DNSPluginData) UnmarshalJSON(b []byte) error {
mapData := make(map[string]string)
s := ""
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("error unmarshaling json: %w", err)
}
for _, line := range strings.Split(s, "\n") {
if line == "" {
continue
}
before, after, _ := strings.Cut(line, "=")
mapData[before] = after
}
*d = mapData
return nil
}

View File

@ -0,0 +1,23 @@
/*
* 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 plugins
import (
"fmt"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
)
// Client is an interface for accessing the Proxmox ACME plugins API.
type Client struct {
api.Client
}
// ExpandPath expands a relative path to the Proxmox ACME plugins API path.
func (c *Client) ExpandPath(path string) string {
return fmt.Sprintf("cluster/acme/plugins/%s", path)
}

View File

@ -30,6 +30,8 @@ import (
//go:generate cp -R ../build/docs-gen/guides/. ../docs/guides/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_acme_account.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_acme_accounts.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_acme_plugin.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_acme_plugins.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_apt_repository.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_apt_standard_repository.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_hagroup.md ../docs/data-sources/
@ -43,6 +45,7 @@ import (
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_vm2.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_acl.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_acme_account.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_acme_dns_plugin.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_apt_repository.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_apt_standard_repository.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_cluster_options.md ../docs/resources/