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

feat(cluster): add proxmox_virtual_environment_metrics_server resource (#1719)

Signed-off-by: rafsaf <rafal.safin@rafsaf.pl>
This commit is contained in:
Rafał Safin 2025-02-03 19:11:26 +01:00 committed by GitHub
parent 4166f66731
commit d1cc2144f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1132 additions and 0 deletions

View File

@ -54,6 +54,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
- Default Linux Bridge "vmbr0" is VLAN aware (datacenter -> pve -> network -> edit & apply)
Create `example/terraform.tfvars` with the following variables:

View File

@ -0,0 +1,42 @@
---
layout: page
title: proxmox_virtual_environment_metrics_server
parent: Data Sources
subcategory: Virtual Environment
description: |-
Retrieves information about a specific PVE metric server.
---
# Data Source: proxmox_virtual_environment_metrics_server
Retrieves information about a specific PVE metric server.
## Example Usage
```terraform
data "proxmox_virtual_environment_metrics_server" "example" {
name = "example_influxdb"
}
output "data_proxmox_virtual_environment_metrics_server" {
value = {
server = data.proxmox_virtual_environment_metrics_server.example.server
port = data.proxmox_virtual_environment_metrics_server.example.port
}
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) Unique name that will be ID of this metric server in PVE.
### Read-Only
- `disable` (Boolean) Indicates if the metric server is disabled.
- `id` (String) The unique identifier of this resource.
- `port` (Number) Server network port.
- `server` (String) Server dns name or IP address.
- `type` (String) Plugin type. Either `graphite` or `influxdb`.

View File

@ -0,0 +1,68 @@
---
layout: page
title: proxmox_virtual_environment_metrics_server
parent: Resources
subcategory: Virtual Environment
description: |-
Manages PVE metrics server.
---
# Resource: proxmox_virtual_environment_metrics_server
Manages PVE metrics server.
## Example Usage
```terraform
resource "proxmox_virtual_environment_metrics_server" "influxdb_server" {
name = "example_influxdb_server"
server = "192.168.3.2"
port = 8089
type = "influxdb"
}
resource "proxmox_virtual_environment_metrics_server" "graphite_server" {
name = "example_graphite_server"
server = "192.168.4.2"
port = 2003
type = "graphite"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) Unique name that will be ID of this metric server in PVE.
- `port` (Number) Server network port.
- `server` (String) Server dns name or IP address.
- `type` (String) Plugin type. Choice is between `graphite` | `influxdb`.
### Optional
- `disable` (Boolean) Set this to `true` to disable this metric server.
- `graphite_path` (String) Root graphite path (ex: `proxmox.mycluster.mykey`).
- `graphite_proto` (String) Protocol to send graphite data. Choice is between `udp` | `tcp`. If not set, PVE default is `udp`.
- `influx_api_path_prefix` (String) An API path prefix inserted between `<host>:<port>/` and `/api2/`. Can be useful if the InfluxDB service runs behind a reverse proxy.
- `influx_bucket` (String) The InfluxDB bucket/db. Only necessary when using the http v2 api.
- `influx_db_proto` (String) Protocol for InfluxDB. Choice is between `udp` | `http` | `https`. If not set, PVE default is `udp`.
- `influx_max_body_size` (Number) InfluxDB max-body-size in bytes. Requests are batched up to this size. If not set, PVE default is `25000000`.
- `influx_organization` (String) The InfluxDB organization. Only necessary when using the http v2 api. Has no meaning when using v2 compatibility api.
- `influx_token` (String, Sensitive) The InfluxDB access token. Only necessary when using the http v2 api. If the v2 compatibility api is used, use `user:password` instead.
- `influx_verify` (Boolean) Set to `false` to disable certificate verification for https endpoints.
- `mtu` (Number) MTU (maximum transmission unit) for metrics transmission over UDP. If not set, PVE default is `1500` (allowed `512` - `65536`).
- `timeout` (Number) TCP socket timeout in seconds. If not set, PVE default is `1`.
### Read-Only
- `id` (String) The unique identifier of this resource.
## Import
Import is supported using the following syntax:
```shell
#!/usr/bin/env sh
terraform import proxmox_virtual_environment_metrics_server.example example
```

View File

@ -0,0 +1,3 @@
data "proxmox_virtual_environment_metrics_server" "example" {
name = proxmox_virtual_environment_metrics_server.influxdb_server.name
}

View File

@ -0,0 +1,24 @@
resource "proxmox_virtual_environment_metrics_server" "influxdb_server" {
name = "example_influxdb_server"
server = "192.168.3.2"
port = 18089
type = "influxdb"
}
resource "proxmox_virtual_environment_metrics_server" "graphite_server" {
name = "example_graphite_server"
server = "192.168.4.2"
port = 20033
type = "graphite"
}
resource "proxmox_virtual_environment_metrics_server" "graphite_server2" {
name = "example_graphite_server2"
server = "192.168.4.3"
port = 20033
type = "graphite"
mtu = 60000
timeout = 5
graphite_proto = "udp"
}

View File

@ -0,0 +1,10 @@
data "proxmox_virtual_environment_metrics_server" "example" {
name = "example_influxdb"
}
output "data_proxmox_virtual_environment_metrics_server" {
value = {
server = data.proxmox_virtual_environment_metrics_server.example.server
port = data.proxmox_virtual_environment_metrics_server.example.port
}
}

View File

@ -0,0 +1,2 @@
#!/usr/bin/env sh
terraform import proxmox_virtual_environment_metrics_server.example example

View File

@ -0,0 +1,13 @@
resource "proxmox_virtual_environment_metrics_server" "influxdb_server" {
name = "example_influxdb_server"
server = "192.168.3.2"
port = 8089
type = "influxdb"
}
resource "proxmox_virtual_environment_metrics_server" "graphite_server" {
name = "example_graphite_server"
server = "192.168.4.2"
port = 2003
type = "graphite"
}

View File

@ -0,0 +1,130 @@
/*
* 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 metrics
import (
"context"
"fmt"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/metrics"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &metricsServerDatasource{}
_ datasource.DataSourceWithConfigure = &metricsServerDatasource{}
)
type metricsServerDatasource struct {
client *metrics.Client
}
// NewMetricsServerDatasource creates new metrics server data source.
func NewMetricsServerDatasource() datasource.DataSource {
return &metricsServerDatasource{}
}
func (r *metricsServerDatasource) Metadata(
_ context.Context,
req datasource.MetadataRequest,
resp *datasource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_metrics_server"
}
func (r *metricsServerDatasource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
cfg, ok := req.ProviderData.(config.DataSource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData),
)
return
}
r.client = cfg.Client.Cluster().Metrics()
}
func (r *metricsServerDatasource) Schema(
_ context.Context,
_ datasource.SchemaRequest,
resp *datasource.SchemaResponse,
) {
resp.Schema = schema.Schema{
Description: "Retrieves information about a specific PVE metric server.",
Attributes: map[string]schema.Attribute{
"id": attribute.ID(),
"name": schema.StringAttribute{
Description: "Unique name that will be ID of this metric server in PVE.",
Required: true,
},
"disable": schema.BoolAttribute{
Description: "Indicates if the metric server is disabled.",
Computed: true,
},
"port": schema.Int64Attribute{
Description: "Server network port.",
Computed: true,
},
"server": schema.StringAttribute{
Description: "Server dns name or IP address.",
Computed: true,
},
"type": schema.StringAttribute{
Description: "Plugin type. Either `graphite` or `influxdb`.",
Computed: true,
},
},
}
}
func (r *metricsServerDatasource) Read(
ctx context.Context,
req datasource.ReadRequest,
resp *datasource.ReadResponse,
) {
var state metricsServerDatasourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
state.ID = state.Name
data, err := r.client.GetServer(ctx, state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Unable to Refresh Resource",
"An unexpected error occurred while attempting to refresh datasource state. "+
"Please retry the operation or report this issue to the provider developers.\n\n"+
"Error: "+err.Error(),
)
return
}
readModel := &metricsServerDatasourceModel{}
readModel.importFromAPI(state.ID.ValueString(), data)
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
}

View File

@ -0,0 +1,133 @@
/*
* 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 metrics
import (
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/metrics"
"github.com/hashicorp/terraform-plugin-framework/types"
)
type metricsServerModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Disable types.Bool `tfsdk:"disable"`
MTU types.Int64 `tfsdk:"mtu"`
Port types.Int64 `tfsdk:"port"`
Server types.String `tfsdk:"server"`
Timeout types.Int64 `tfsdk:"timeout"`
Type types.String `tfsdk:"type"`
InfluxAPIPathPrefix types.String `tfsdk:"influx_api_path_prefix"`
InfluxBucket types.String `tfsdk:"influx_bucket"`
InfluxDBProto types.String `tfsdk:"influx_db_proto"`
InfluxMaxBodySize types.Int64 `tfsdk:"influx_max_body_size"`
InfluxOrganization types.String `tfsdk:"influx_organization"`
InfluxToken types.String `tfsdk:"influx_token"`
InfluxVerify types.Bool `tfsdk:"influx_verify"`
GraphitePath types.String `tfsdk:"graphite_path"`
GraphiteProto types.String `tfsdk:"graphite_proto"`
}
func boolToInt64Ptr(boolPtr *bool) *int64 {
if boolPtr != nil {
var result int64
if *boolPtr {
result = int64(1)
} else {
result = int64(0)
}
return &result
}
return nil
}
func int64ToBoolPtr(int64ptr *int64) *bool {
if int64ptr != nil {
var result bool
if *int64ptr == 0 {
result = false
} else {
result = true
}
return &result
}
return nil
}
// importFromAPI takes data from metrics server PVE API response and set fields based on it.
// Note: API response does not contain name so it must be passed directly.
func (m *metricsServerModel) importFromAPI(name string, data *metrics.ServerData) {
m.ID = types.StringValue(name)
m.Name = types.StringValue(name)
m.Disable = types.BoolPointerValue(int64ToBoolPtr(data.Disable))
m.MTU = types.Int64PointerValue(data.MTU)
m.Port = types.Int64Value(data.Port)
m.Server = types.StringValue(data.Server)
m.Timeout = types.Int64PointerValue(data.Timeout)
m.Type = types.StringPointerValue(data.Type)
m.InfluxAPIPathPrefix = types.StringPointerValue(data.APIPathPrefix)
m.InfluxBucket = types.StringPointerValue(data.Bucket)
m.InfluxDBProto = types.StringPointerValue(data.InfluxDBProto)
m.InfluxMaxBodySize = types.Int64PointerValue(data.MaxBodySize)
m.InfluxOrganization = types.StringPointerValue(data.Organization)
m.InfluxToken = types.StringPointerValue(data.Token)
m.InfluxVerify = types.BoolPointerValue(int64ToBoolPtr(data.Verify))
m.GraphitePath = types.StringPointerValue(data.Path)
m.GraphiteProto = types.StringPointerValue(data.Proto)
}
// toAPIRequestBody creates metrics server request data for PUT and POST requests.
func (m *metricsServerModel) toAPIRequestBody() *metrics.ServerRequestData {
data := &metrics.ServerRequestData{}
data.ID = m.Name.ValueString()
data.Disable = boolToInt64Ptr(m.Disable.ValueBoolPointer())
data.MTU = m.MTU.ValueInt64Pointer()
data.Port = m.Port.ValueInt64()
data.Server = m.Server.ValueString()
data.Timeout = m.Timeout.ValueInt64Pointer()
data.Type = m.Type.ValueStringPointer()
data.APIPathPrefix = m.InfluxAPIPathPrefix.ValueStringPointer()
data.Bucket = m.InfluxBucket.ValueStringPointer()
data.InfluxDBProto = m.InfluxDBProto.ValueStringPointer()
data.MaxBodySize = m.InfluxMaxBodySize.ValueInt64Pointer()
data.Organization = m.InfluxOrganization.ValueStringPointer()
data.Token = m.InfluxToken.ValueStringPointer()
data.Verify = boolToInt64Ptr(m.InfluxVerify.ValueBoolPointer())
data.Path = m.GraphitePath.ValueStringPointer()
data.Proto = m.GraphiteProto.ValueStringPointer()
return data
}
type metricsServerDatasourceModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Disable types.Bool `tfsdk:"disable"`
Port types.Int64 `tfsdk:"port"`
Server types.String `tfsdk:"server"`
Type types.String `tfsdk:"type"`
}
// importFromAPI takes data from metrics server PVE API response and set fields based on it.
// Note: API response does not contain name so it must be passed directly.
func (m *metricsServerDatasourceModel) importFromAPI(name string, data *metrics.ServerData) {
m.ID = types.StringValue(name)
m.Name = types.StringValue(name)
m.Disable = types.BoolPointerValue(int64ToBoolPtr(data.Disable))
m.Port = types.Int64Value(data.Port)
m.Server = types.StringValue(data.Server)
m.Type = types.StringPointerValue(data.Type)
}

View File

@ -0,0 +1,366 @@
/*
* 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 metrics
import (
"context"
"errors"
"fmt"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/metrics"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
var (
_ resource.Resource = &metricsServerResource{}
_ resource.ResourceWithConfigure = &metricsServerResource{}
_ resource.ResourceWithImportState = &metricsServerResource{}
)
type metricsServerResource struct {
client *metrics.Client
}
// NewMetricsServerResource creates new metrics server resource.
func NewMetricsServerResource() resource.Resource {
return &metricsServerResource{}
}
func (r *metricsServerResource) Metadata(
_ context.Context,
req resource.MetadataRequest,
resp *resource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_metrics_server"
}
func (r *metricsServerResource) Configure(
_ context.Context,
req resource.ConfigureRequest,
resp *resource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData),
)
return
}
r.client = cfg.Client.Cluster().Metrics()
}
func (r *metricsServerResource) Schema(
_ context.Context,
_ resource.SchemaRequest,
resp *resource.SchemaResponse,
) {
resp.Schema = schema.Schema{
Description: "Manages PVE metrics server.",
Attributes: map[string]schema.Attribute{
"id": attribute.ID(),
"name": schema.StringAttribute{
Description: "Unique name that will be ID of this metric server in PVE.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"disable": schema.BoolAttribute{
Description: "Set this to `true` to disable this metric server.",
Optional: true,
Default: nil,
},
"mtu": schema.Int64Attribute{
Description: "MTU (maximum transmission unit) for metrics transmission over UDP. " +
"If not set, PVE default is `1500` (allowed `512` - `65536`).",
Validators: []validator.Int64{int64validator.Between(512, 65536)},
Optional: true,
Default: nil,
},
"port": schema.Int64Attribute{
Description: "Server network port.",
Required: true,
Validators: []validator.Int64{int64validator.Between(1, 65536)},
},
"server": schema.StringAttribute{
Description: "Server dns name or IP address.",
Required: true,
},
"timeout": schema.Int64Attribute{
Description: "TCP socket timeout in seconds. If not set, PVE default is `1`.",
Optional: true,
Default: nil,
},
"type": schema.StringAttribute{
Description: "Plugin type. Choice is between `graphite` | `influxdb`.",
Required: true,
Validators: []validator.String{stringvalidator.OneOf("graphite", "influxdb")},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"influx_api_path_prefix": schema.StringAttribute{
Description: "An API path prefix inserted between `<host>:<port>/` and `/api2/`." +
" Can be useful if the InfluxDB service runs behind a reverse proxy.",
Optional: true,
Default: nil,
},
"influx_bucket": schema.StringAttribute{
Description: "The InfluxDB bucket/db. Only necessary when using the http v2 api.",
Optional: true,
Default: nil,
},
"influx_db_proto": schema.StringAttribute{
Description: "Protocol for InfluxDB. Choice is between `udp` | `http` | `https`. " +
"If not set, PVE default is `udp`.",
Validators: []validator.String{stringvalidator.OneOf("udp", "http", "https")},
Optional: true,
Default: nil,
},
"influx_max_body_size": schema.Int64Attribute{
Description: "InfluxDB max-body-size in bytes. Requests are batched up to this " +
"size. If not set, PVE default is `25000000`.",
Optional: true,
Default: nil,
},
"influx_organization": schema.StringAttribute{
Description: "The InfluxDB organization. Only necessary when using the http v2 " +
"api. Has no meaning when using v2 compatibility api.",
Optional: true,
Default: nil,
},
"influx_token": schema.StringAttribute{
Description: "The InfluxDB access token. Only necessary when using the http v2 " +
"api. If the v2 compatibility api is used, use `user:password` instead.",
Optional: true,
Default: nil,
Sensitive: true,
},
"influx_verify": schema.BoolAttribute{
Description: "Set to `false` to disable certificate verification for https " +
"endpoints.",
Optional: true,
Default: nil,
},
"graphite_path": schema.StringAttribute{
Description: "Root graphite path (ex: `proxmox.mycluster.mykey`).",
Optional: true,
Default: nil,
},
"graphite_proto": schema.StringAttribute{
Description: "Protocol to send graphite data. Choice is between `udp` | `tcp`. " +
"If not set, PVE default is `udp`.",
Validators: []validator.String{stringvalidator.OneOf("udp", "tcp")},
Optional: true,
Default: nil,
},
},
}
}
func (r *metricsServerResource) Read(
ctx context.Context,
req resource.ReadRequest,
resp *resource.ReadResponse,
) {
var state metricsServerModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
data, err := r.client.GetServer(ctx, state.ID.ValueString())
if err != nil {
if errors.Is(err, api.ErrResourceDoesNotExist) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError(
"Unable to Refresh Resource",
"An unexpected error occurred while attempting to refresh resource state. "+
"Please retry the operation or report this issue to the provider developers.\n\n"+
"Error: "+err.Error(),
)
return
}
readModel := &metricsServerModel{}
readModel.importFromAPI(state.ID.ValueString(), data)
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
}
func (r *metricsServerResource) Create(
ctx context.Context,
req resource.CreateRequest,
resp *resource.CreateResponse,
) {
var plan metricsServerModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
reqData := plan.toAPIRequestBody()
err := r.client.CreateServer(ctx, reqData)
if err != nil {
resp.Diagnostics.AddError(
"Unable to Create Resource",
"An unexpected error occurred while creating the resource create request.\n\n"+
"Error: "+err.Error(),
)
return
}
plan.ID = plan.Name
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func checkDelete(planField, stateField attr.Value, toDelete *[]string, apiName string) {
// we need to remove field via api field if there is value in state
// but someone decided to use PVE default and removed value from resource
if planField.IsNull() && !stateField.IsNull() {
*toDelete = append(*toDelete, apiName)
}
}
func (r *metricsServerResource) Update(
ctx context.Context,
req resource.UpdateRequest,
resp *resource.UpdateResponse,
) {
var plan metricsServerModel
var state metricsServerModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
var toDelete []string
checkDelete(plan.Disable, state.Disable, &toDelete, "disable")
checkDelete(plan.MTU, state.MTU, &toDelete, "mtu")
checkDelete(plan.Timeout, state.Timeout, &toDelete, "timeout")
checkDelete(plan.InfluxAPIPathPrefix, state.InfluxAPIPathPrefix, &toDelete, "api-path-prefix")
checkDelete(plan.InfluxBucket, state.InfluxBucket, &toDelete, "bucket")
checkDelete(plan.InfluxDBProto, state.InfluxDBProto, &toDelete, "influxdbproto")
checkDelete(plan.InfluxMaxBodySize, state.InfluxMaxBodySize, &toDelete, "max-body-size")
checkDelete(plan.InfluxOrganization, state.InfluxOrganization, &toDelete, "organization")
checkDelete(plan.InfluxToken, state.InfluxToken, &toDelete, "token")
checkDelete(plan.InfluxVerify, state.InfluxVerify, &toDelete, "verify-certificate")
checkDelete(plan.GraphitePath, state.GraphitePath, &toDelete, "path")
checkDelete(plan.GraphiteProto, state.GraphiteProto, &toDelete, "proto")
reqData := plan.toAPIRequestBody()
reqData.Delete = &toDelete
err := r.client.UpdateServer(ctx, reqData)
if err != nil {
resp.Diagnostics.AddError(
"Unable to Update Resource",
"An unexpected error occurred while creating the resource update request.\n\n"+
"Error: "+err.Error(),
)
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *metricsServerResource) Delete(
ctx context.Context,
req resource.DeleteRequest,
resp *resource.DeleteResponse,
) {
var state metricsServerModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
err := r.client.DeleteServer(ctx, state.ID.ValueString())
if err != nil {
if errors.Is(err, api.ErrResourceDoesNotExist) {
return
}
resp.Diagnostics.AddError(
"Unable to Delete Resource",
"An unexpected error occurred while creating the resource delete request.\n\n"+
"Error: "+err.Error(),
)
return
}
}
func (r *metricsServerResource) ImportState(
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
) {
data, err := r.client.GetServer(ctx, req.ID)
if err != nil {
if errors.Is(err, api.ErrResourceDoesNotExist) {
resp.Diagnostics.AddError(
"Resource does not exist",
"Resource you try to import does not exist.\n\n"+
"Error: "+err.Error(),
)
return
}
resp.Diagnostics.AddError(
"Unable to Import Resource",
"An unexpected error occurred while attempting to import resource state.\n\n"+
"Error: "+err.Error(),
)
return
}
readModel := &metricsServerModel{}
readModel.importFromAPI(req.ID, data)
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
}

View File

@ -0,0 +1,177 @@
//go:build acceptance || all
/*
* 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 metrics_test
import (
"testing"
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccResourceMetricsServer(t *testing.T) {
te := test.InitEnvironment(t)
tests := []struct {
name string
steps []resource.TestStep
}{
{"create influxdb udp server & update it & again to default mtu", []resource.TestStep{
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_metrics_server" "acc_influxdb_server" {
name = "acc_example_influxdb_server"
server = "192.168.3.2"
port = 18089
type = "influxdb"
mtu = 1000
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_metrics_server.acc_influxdb_server", map[string]string{
"id": "acc_example_influxdb_server",
"name": "acc_example_influxdb_server",
"mtu": "1000",
"port": "18089",
"server": "192.168.3.2",
"type": "influxdb",
}),
test.NoResourceAttributesSet("proxmox_virtual_environment_metrics_server.acc_influxdb_server", []string{
"disable",
"timeout",
"influx_api_path_prefix",
"influx_bucket",
"influx_db_proto",
"influx_max_body_size",
"influx_organization",
"influx_token",
"influx_verify",
"graphite_path",
"graphite_proto",
}),
),
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_metrics_server" "acc_influxdb_server" {
name = "acc_example_influxdb_server"
server = "192.168.3.2"
port = 18089
type = "influxdb"
mtu = 1000
influx_bucket = "xxxxx"
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_metrics_server.acc_influxdb_server", map[string]string{
"id": "acc_example_influxdb_server",
"name": "acc_example_influxdb_server",
"mtu": "1000",
"port": "18089",
"server": "192.168.3.2",
"type": "influxdb",
"influx_bucket": "xxxxx",
}),
test.NoResourceAttributesSet("proxmox_virtual_environment_metrics_server.acc_influxdb_server", []string{
"disable",
"timeout",
"influx_api_path_prefix",
"influx_db_proto",
"influx_max_body_size",
"influx_organization",
"influx_token",
"influx_verify",
"graphite_path",
"graphite_proto",
}),
),
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_metrics_server" "acc_influxdb_server" {
name = "acc_example_influxdb_server"
server = "192.168.3.2"
port = 18089
type = "influxdb"
influx_bucket = "xxxxx"
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_metrics_server.acc_influxdb_server", map[string]string{
"id": "acc_example_influxdb_server",
"name": "acc_example_influxdb_server",
"port": "18089",
"server": "192.168.3.2",
"type": "influxdb",
"influx_bucket": "xxxxx",
}),
test.NoResourceAttributesSet("proxmox_virtual_environment_metrics_server.acc_influxdb_server", []string{
"disable",
"timeout",
"mtu",
"influx_api_path_prefix",
"influx_db_proto",
"influx_max_body_size",
"influx_organization",
"influx_token",
"influx_verify",
"graphite_path",
"graphite_proto",
}),
),
},
}},
{"create graphite udp metrics server & import it", []resource.TestStep{
{
ResourceName: "proxmox_virtual_environment_metrics_server.acc_graphite_server",
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_metrics_server" "acc_graphite_server" {
name = "acc_example_graphite_server"
server = "192.168.3.2"
port = 18089
type = "graphite"
}`),
},
{
ResourceName: "proxmox_virtual_environment_metrics_server.acc_graphite_server",
ImportState: true,
ImportStateVerify: true,
},
}},
{"create graphite udp metrics server & test datasource", []resource.TestStep{
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_metrics_server" "acc_graphite_server2" {
name = "acc_example_graphite_server2"
server = "192.168.3.2"
port = 18089
type = "graphite"
}
data "proxmox_virtual_environment_metrics_server" "acc_graphite_server2" {
name = proxmox_virtual_environment_metrics_server.acc_graphite_server2.name
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("data.proxmox_virtual_environment_metrics_server.acc_graphite_server2", map[string]string{
"id": "acc_example_graphite_server2",
"name": "acc_example_graphite_server2",
"port": "18089",
"server": "192.168.3.2",
"type": "graphite",
}),
),
},
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.AccProviders,
Steps: tt.steps,
})
})
}
}

View File

@ -26,6 +26,7 @@ import (
"github.com/bpg/terraform-provider-proxmox/fwprovider/access"
"github.com/bpg/terraform-provider-proxmox/fwprovider/acme"
"github.com/bpg/terraform-provider-proxmox/fwprovider/cluster/metrics"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/fwprovider/ha"
"github.com/bpg/terraform-provider-proxmox/fwprovider/hardwaremapping"
@ -509,6 +510,7 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc
network.NewLinuxBridgeResource,
network.NewLinuxVLANResource,
vm.NewResource,
metrics.NewMetricsServerResource,
}
}
@ -529,6 +531,7 @@ func (p *proxmoxProvider) DataSources(_ context.Context) []func() datasource.Dat
hardwaremapping.NewPCIDataSource,
hardwaremapping.NewUSBDataSource,
vm.NewDataSource,
metrics.NewMetricsServerDatasource,
}
}

View File

@ -14,6 +14,7 @@ import (
clusterfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/firewall"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/metrics"
"github.com/bpg/terraform-provider-proxmox/proxmox/firewall"
)
@ -48,3 +49,8 @@ func (c *Client) HardwareMapping() *mapping.Client {
func (c *Client) ACME() *acme.Client {
return &acme.Client{Client: c}
}
// Metrics returns a client for managing the cluster's metrics features.
func (c *Client) Metrics() *metrics.Client {
return &metrics.Client{Client: c}
}

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

View File

@ -0,0 +1,82 @@
/*
* 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 metrics
import (
"context"
"fmt"
"net/http"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
)
// GetServer retrieves the metrics server data.
func (c *Client) GetServer(ctx context.Context, id string) (*ServerData, error) {
resBody := &ServerResponseBody{}
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(id), nil, resBody)
if err != nil {
return nil, fmt.Errorf("error reading metrics server: %w", err)
}
if resBody.Data == nil {
return nil, api.ErrNoDataObjectInResponse
}
return resBody.Data, nil
}
// GetServers lists the metrics servers.
func (c *Client) GetServers(ctx context.Context) (*[]ServerData, error) {
resBody := &ServersResponseBody{}
err := c.DoRequest(ctx, http.MethodGet, c.ExpandPath(""), nil, resBody)
if err != nil {
return nil, fmt.Errorf("error reading list of metrics servers: %w", err)
}
if resBody.Data == nil {
return nil, api.ErrNoDataObjectInResponse
}
return resBody.Data, nil
}
// UpdateServer updates the metrics server.
func (c *Client) UpdateServer(ctx context.Context, data *ServerRequestData) error {
// PVE API does not allow to pass "type" in PUT requests, this doesn't makes any sense
// since other required params like port, server must still be there
// while we could spawn another struct, let's just fix it silently
data.Type = nil
err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath(data.ID), data, nil)
if err != nil {
return fmt.Errorf("error updating metrics server: %w", err)
}
return nil
}
// CreateServer creates the metrics server.
func (c *Client) CreateServer(ctx context.Context, data *ServerRequestData) error {
err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath(data.ID), data, nil)
if err != nil {
return fmt.Errorf("error creating metrics server: %w", err)
}
return nil
}
// DeleteServer deletes the metrics server.
func (c *Client) DeleteServer(ctx context.Context, id string) error {
err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(id), nil, nil)
if err != nil {
return fmt.Errorf("error updating metrics server: %w", err)
}
return nil
}

View File

@ -0,0 +1,47 @@
/*
* 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 metrics
// ServerData contains the data from a metrics server response.
type ServerData struct {
Disable *int64 `json:"disable,omitempty" url:"disable,omitempty"`
ID string `json:"id,omitempty" url:"id,omitempty"`
MTU *int64 `json:"mtu" url:"mtu,omitempty"`
Port int64 `json:"port" url:"port"`
Server string `json:"server" url:"server"`
Timeout *int64 `json:"timeout,omitempty" url:"timeout,omitempty"`
Type *string `json:"type,omitempty" url:"type,omitempty"`
// influxdb only options
APIPathPrefix *string `json:"api-path-prefix,omitempty" url:"api-path-prefix,omitempty"`
Bucket *string `json:"bucket,omitempty" url:"bucket,omitempty"`
InfluxDBProto *string `json:"influxdbproto,omitempty" url:"influxdbproto,omitempty"`
MaxBodySize *int64 `json:"max-body-size,omitempty" url:"max-body-size,omitempty"`
Organization *string `json:"organization,omitempty" url:"organization,omitempty"`
Token *string `json:"token,omitempty" url:"token,omitempty"`
Verify *int64 `json:"verify-certificate,omitempty" url:"verify-certificate,omitempty"`
// graphite only options
Path *string `json:"path,omitempty" url:"path,omitempty"`
Proto *string `json:"proto,omitempty" url:"proto,omitempty"`
}
// ServerResponseBody contains the body from a metrics server response.
type ServerResponseBody struct {
Data *ServerData `json:"data,omitempty"`
}
// ServersResponseBody contains the body from a metrics server list response.
type ServersResponseBody struct {
Data *[]ServerData `json:"data,omitempty"`
}
// ServerRequestData contains the data for a metric server post/put request.
type ServerRequestData struct {
ServerData
Delete *[]string `url:"delete,omitempty"`
}

View File

@ -43,6 +43,7 @@ import (
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_haresources.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_version.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_vm2.md ../docs/data-sources/
//go:generate cp ../build/docs-gen/data-sources/virtual_environment_metrics_server.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/
@ -58,3 +59,4 @@ import (
//go:generate cp ../build/docs-gen/resources/virtual_environment_network_linux_vlan.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_user_token.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_vm2.md ../docs/resources/
//go:generate cp ../build/docs-gen/resources/virtual_environment_metrics_server.md ../docs/resources/