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

feat(provider): reliable sequential and random vm_id generation (#1557)

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-10-03 20:18:37 -04:00 committed by GitHub
parent 55bacffc38
commit 72f7cb81a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 571 additions and 340 deletions

View File

@ -7,5 +7,6 @@
"hashicorp.terraform", "hashicorp.terraform",
"joshbolduc.commitlint", "joshbolduc.commitlint",
"PKief.material-icon-theme", "PKief.material-icon-theme",
"psioniq.psi-header"
] ]
} }

29
.vscode/settings.json vendored
View File

@ -1,7 +1,7 @@
{ {
"editor.bracketPairColorization.enabled": true, "editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true, "editor.guides.bracketPairs": false,
"editor.guides.bracketPairsHorizontal": true, "editor.guides.bracketPairsHorizontal": false,
"editor.guides.highlightActiveBracketPair": true, "editor.guides.highlightActiveBracketPair": true,
"editor.rulers": [ "editor.rulers": [
100 100
@ -26,12 +26,14 @@
"cputype", "cputype",
"cpuunits", "cpuunits",
"customdiff", "customdiff",
"Datasource", "datasource",
"datasources",
"deepcode", "deepcode",
"directsync", "directsync",
"efidisk", "efidisk",
"efidisks", "efidisks",
"FSTRIM", "FSTRIM",
"fwprovider",
"gocritic", "gocritic",
"gosimple", "gosimple",
"hookscript", "hookscript",
@ -110,4 +112,25 @@
"version": "include", "version": "include",
"datasource": "database", "datasource": "database",
}, },
"psi-header.config": {
"forceToTop": true,
"blankLinesAfter": 1,
"license": "Apache-2.0",
},
"psi-header.changes-tracking": {
"include": ["go"],
"isActive": true,
"enforceHeader": true,
},
"psi-header.templates": [
{
"language": "*",
"template": [
"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/.",
],
},
],
"makefile.configureOnOpen": false
} }

View File

@ -234,7 +234,7 @@ When using a non-root user for the SSH connection, the user **must** have the `s
~> The `root` user on the Proxmox node must be configured with `bash` as the default shell. ~> The `root` user on the Proxmox node must be configured with `bash` as the default shell.
You can configure the `sudo` privilege for the user via the command line on the Proxmox host. You can configure the `sudo` privilege for the user via the command line on the Proxmox host.
In the example below, we create a user `terraform` and assign the `sudo` privilege to it. Run the following commands on the Proxmox node in the root shell: In the example below, we create a user `terraform` and assign the `sudo` privilege to it. Run the following commands on the Proxmox node in the root shell:
- Create a new system user: - Create a new system user:
@ -257,11 +257,12 @@ In the example below, we create a user `terraform` and assign the `sudo` privile
terraform ALL=(root) NOPASSWD: /usr/bin/tee /var/lib/vz/* terraform ALL=(root) NOPASSWD: /usr/bin/tee /var/lib/vz/*
``` ```
If you're using a different datastore for snippets, not the default `local`, you should add the datastore's mount point to the sudoers file as well, for example: If you're using a different datastore for snippets, not the default `local`, you should add the datastore's mount point to the sudoers file as well, for example:
```text ```text
terraform ALL=(root) NOPASSWD: /usr/bin/tee /mnt/pve/cephfs/* terraform ALL=(root) NOPASSWD: /usr/bin/tee /mnt/pve/cephfs/*
``` ```
You can find the mount point of the datastore by running `pvesh get /storage/<name>` on the Proxmox node. You can find the mount point of the datastore by running `pvesh get /storage/<name>` on the Proxmox node.
- Copy your SSH public key to the `~/.ssh/authorized_keys` file of the `terraform` user on the target node. - Copy your SSH public key to the `~/.ssh/authorized_keys` file of the `terraform` user on the target node.
@ -388,6 +389,14 @@ The workaround is to use password authentication for those operations.
-> You can also configure additional Proxmox users and roles using [`virtual_environment_user`](https://registry.terraform.io/providers/bpg/proxmox/latest/docs/data-sources/virtual_environment_user) and [`virtual_environment_role`](https://registry.terraform.io/providers/bpg/proxmox/latest/docs/data-sources/virtual_environment_role) resources of the provider. -> You can also configure additional Proxmox users and roles using [`virtual_environment_user`](https://registry.terraform.io/providers/bpg/proxmox/latest/docs/data-sources/virtual_environment_user) and [`virtual_environment_role`](https://registry.terraform.io/providers/bpg/proxmox/latest/docs/data-sources/virtual_environment_role) resources of the provider.
## VM and Container ID Assignment
When creating VMs and Containers, you can specify the optional `vm_id` attribute to set the ID of the VM or Container. However, the ID is a mandatory attribute in the Proxmox API and must be unique within the cluster. If the `vm_id` attribute is not specified, the provider will generate a unique ID and assign it to the resource.
The Proxmox API provides a helper function to retrieve the “next available” unique ID in the cluster, but there is no option to reserve an ID before a resource is created. Instead, the provider uses a file-based locking technique to reserve retrieved sequential IDs and prevent duplicates. However, conflicts cannot be fully avoided, especially when multiple resources are created simultaneously by different provider instances.
To mitigate this issue, you can set the `random_vm_ids` attribute to `true` in the `provider` block. This will generate a random ID for each VM or Container when the `vm_id` attribute is not specified. The generated ID is checked for uniqueness through the Proxmox API before resource creation, significantly reducing the risk of conflicts.
## Temporary Directory ## Temporary Directory
Using `proxmox_virtual_environment_file` with `.iso` files or disk images can require large amount of space in the temporary directory of the computer running terraform. Using `proxmox_virtual_environment_file` with `.iso` files or disk images can require large amount of space in the temporary directory of the computer running terraform.
@ -426,3 +435,6 @@ In addition to [generic provider arguments](https://www.terraform.io/docs/config
- `address` - (Required) The FQDN/IP address of the node. - `address` - (Required) The FQDN/IP address of the node.
- `port` - (Optional) SSH port of the node. Defaults to 22. - `port` - (Optional) SSH port of the node. Defaults to 22.
- `tmp_dir` - (Optional) Use custom temporary directory. (can also be sourced from `PROXMOX_VE_TMPDIR`) - `tmp_dir` - (Optional) Use custom temporary directory. (can also be sourced from `PROXMOX_VE_TMPDIR`)
- `random_vm_ids` - (Optional) Use random VM ID for VMs and Containers when `vm_id` attribute is not specified. Defaults to `false`.
- `random_vm_id_start` - (Optional) The start of the range for random VM IDs. Defaults to `10000`.
- `random_vm_id_end` - (Optional) The end of the range for random VM IDs. Defaults to `99999`.

View File

@ -19,6 +19,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr" "github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
@ -108,19 +109,17 @@ func (r *aclResource) Configure(_ context.Context, req resource.ConfigureRequest
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T. Please report this issue to the provider developers.", fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
req.ProviderData),
) )
return return
} }
r.client = client r.client = cfg.Client
} }
func (r *aclResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { func (r *aclResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {

View File

@ -23,6 +23,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/access" "github.com/bpg/terraform-provider-proxmox/proxmox/access"
@ -121,18 +122,19 @@ func (r *userTokenResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
) )
return return
} }
r.client = client r.client = cfg.Client
r.client = cfg.Client
} }
func (r *userTokenResource) Metadata( func (r *userTokenResource) Metadata(

View File

@ -15,7 +15,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/account" "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/account"
) )
@ -131,18 +131,17 @@ func (d *acmeAccountDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return return
} }
d.client = client.Cluster().ACME().Account() d.client = cfg.Client.Cluster().ACME().Account()
} }
// Read retrieves the ACME account information. // Read retrieves the ACME account information.
@ -157,7 +156,7 @@ func (d *acmeAccountDatasource) Read(ctx context.Context, req datasource.ReadReq
name := state.Name.ValueString() name := state.Name.ValueString()
account, err := d.client.Get(ctx, name) accountData, err := d.client.Get(ctx, name)
if err != nil { if err != nil {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
fmt.Sprintf("Unable to read ACME account '%s'", name), fmt.Sprintf("Unable to read ACME account '%s'", name),
@ -167,15 +166,15 @@ func (d *acmeAccountDatasource) Read(ctx context.Context, req datasource.ReadReq
return return
} }
contactList := make([]types.String, len(account.Account.Contact)) contactList := make([]types.String, len(accountData.Account.Contact))
for i, contact := range account.Account.Contact { for i, contact := range accountData.Account.Contact {
contactList[i] = types.StringValue(contact) contactList[i] = types.StringValue(contact)
} }
data := &accountDataModel{ data := &accountDataModel{
Contact: contactList, Contact: contactList,
CreatedAt: types.StringValue(account.Account.CreatedAt), CreatedAt: types.StringValue(accountData.Account.CreatedAt),
Status: types.StringValue(account.Account.Status), Status: types.StringValue(accountData.Account.Status),
} }
accountObject, diags := types.ObjectValueFrom(ctx, data.attrTypes(), data) accountObject, diags := types.ObjectValueFrom(ctx, data.attrTypes(), data)
@ -183,9 +182,9 @@ func (d *acmeAccountDatasource) Read(ctx context.Context, req datasource.ReadReq
state.Account = accountObject state.Account = accountObject
state.Directory = types.StringValue(account.Directory) state.Directory = types.StringValue(accountData.Directory)
state.Location = types.StringValue(account.Location) state.Location = types.StringValue(accountData.Location)
state.TOS = types.StringValue(account.TOS) state.TOS = types.StringValue(accountData.TOS)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
} }

View File

@ -15,7 +15,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/account" "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/account"
) )
@ -77,18 +77,17 @@ func (d *acmeAccountsDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return return
} }
d.client = client.Cluster().ACME().Account() d.client = cfg.Client.Cluster().ACME().Account()
} }
// Read fetches the list of ACME Accounts from the Proxmox cluster then converts it to a list of strings. // Read fetches the list of ACME Accounts from the Proxmox cluster then converts it to a list of strings.

View File

@ -17,7 +17,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins" "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins"
) )
@ -102,18 +102,17 @@ func (d *acmePluginDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return return
} }
d.client = client.Cluster().ACME().Plugins() d.client = cfg.Client.Cluster().ACME().Plugins()
} }
// Read fetches the ACME plugin from the Proxmox cluster. // Read fetches the ACME plugin from the Proxmox cluster.

View File

@ -17,7 +17,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins" "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins"
) )
@ -110,18 +110,17 @@ func (d *acmePluginsDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return return
} }
d.client = client.Cluster().ACME().Plugins() d.client = cfg.Client.Cluster().ACME().Plugins()
} }
// Read fetches the list of ACME plugins from the Proxmox cluster. // Read fetches the list of ACME plugins from the Proxmox cluster.

View File

@ -22,7 +22,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/account" "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/account"
) )
@ -40,7 +40,7 @@ func NewACMEAccountResource() resource.Resource {
// acmeAccountResource contains the resource's internal data. // acmeAccountResource contains the resource's internal data.
type acmeAccountResource struct { type acmeAccountResource struct {
// The ACME account API client // The ACME account API client
client account.Client client *account.Client
} }
// acmeAccountModel maps the schema data for the ACME account resource. // acmeAccountModel maps the schema data for the ACME account resource.
@ -137,16 +137,17 @@ func (r *acmeAccountResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if ok { if !ok {
r.client = *client.Cluster().ACME().Account()
} else {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
req.ProviderData),
) )
return
} }
r.client = cfg.Client.Cluster().ACME().Account()
} }
// Create creates a new ACME account on the Proxmox cluster. // Create creates a new ACME account on the Proxmox cluster.

View File

@ -19,7 +19,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins" "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/acme/plugins"
) )
@ -36,8 +36,8 @@ func NewACMEPluginResource() resource.Resource {
// acmePluginResource contains the resource's internal data. // acmePluginResource contains the resource's internal data.
type acmePluginResource struct { type acmePluginResource struct {
// The ACME account API client // The ACME plugin API client
client plugins.Client client *plugins.Client
} }
// Metadata defines the name of the resource. // Metadata defines the name of the resource.
@ -108,16 +108,17 @@ func (r *acmePluginResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if ok { if !ok {
r.client = *client.Cluster().ACME().Plugins()
} else {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
req.ProviderData),
) )
return
} }
r.client = cfg.Client.Cluster().ACME().Plugins()
} }
// Create creates a new ACME plugin on the Proxmox cluster. // Create creates a new ACME plugin on the Proxmox cluster.

View File

@ -0,0 +1,14 @@
/*
* 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 config
import "github.com/bpg/terraform-provider-proxmox/proxmox"
// DataSource is the global configuration for all datasources.
type DataSource struct {
Client proxmox.Client
}

8
fwprovider/config/doc.go Normal file
View File

@ -0,0 +1,8 @@
/*
* 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 config provides the global provider's configuration for all resources and datasources.
package config

View File

@ -0,0 +1,19 @@
/*
* 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 config
import (
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
)
// Resource is the global configuration for all resources.
type Resource struct {
Client proxmox.Client
IDGenerator cluster.IDGenerator
}

View File

@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
) )
@ -85,19 +86,17 @@ func (d *versionDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return return
} }
d.client = client d.client = cfg.Client
} }
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.

View File

@ -15,8 +15,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox"
hagroups "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/groups" hagroups "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/groups"
) )
@ -89,18 +89,17 @@ func (d *haGroupDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return return
} }
d.client = client.Cluster().HA().Groups() d.client = cfg.Client.Cluster().HA().Groups()
} }
// Read fetches the list of HA groups from the Proxmox cluster then converts it to a list of strings. // Read fetches the list of HA groups from the Proxmox cluster then converts it to a list of strings.

View File

@ -16,8 +16,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox"
hagroups "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/groups" hagroups "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/groups"
) )
@ -77,18 +77,17 @@ func (d *haGroupsDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return return
} }
d.client = client.Cluster().HA().Groups() d.client = cfg.Client.Cluster().HA().Groups()
} }
// Read fetches the list of HA groups from the Proxmox cluster then converts it to a list of strings. // Read fetches the list of HA groups from the Proxmox cluster then converts it to a list of strings.

View File

@ -15,7 +15,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
haresources "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/resources" haresources "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/resources"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
) )
@ -96,16 +96,17 @@ func (d *haResourceDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if ok { if !ok {
d.client = client.Cluster().HA().Resources()
} else {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return
} }
d.client = cfg.Client.Cluster().HA().Resources()
} }
// Read fetches the specified HA resource. // Read fetches the specified HA resource.

View File

@ -18,8 +18,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox"
haresources "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/resources" haresources "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/resources"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
) )
@ -92,16 +92,17 @@ func (d *haResourcesDatasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if ok { if !ok {
d.client = client.Cluster().HA().Resources()
} else {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
req.ProviderData),
) )
return
} }
d.client = cfg.Client.Cluster().HA().Resources()
} }
// Read fetches the list of HA resources from the Proxmox cluster then converts it to a list of strings. // Read fetches the list of HA resources from the Proxmox cluster then converts it to a list of strings.

View File

@ -24,8 +24,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox"
hagroups "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/groups" hagroups "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/groups"
) )
@ -43,7 +43,7 @@ func NewHAGroupResource() resource.Resource {
// hagroupResource contains the resource's internal data. // hagroupResource contains the resource's internal data.
type hagroupResource struct { type hagroupResource struct {
// The HA groups API client // The HA groups API client
client hagroups.Client client *hagroups.Client
} }
// Metadata defines the name of the resource. // Metadata defines the name of the resource.
@ -130,16 +130,17 @@ func (r *hagroupResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if ok { if !ok {
r.client = *client.Cluster().HA().Groups()
} else {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
req.ProviderData),
) )
return
} }
r.client = cfg.Client.Cluster().HA().Groups()
} }
// Create creates a new HA group on the Proxmox cluster. // Create creates a new HA group on the Proxmox cluster.

View File

@ -13,7 +13,7 @@ import (
"strings" "strings"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
haresources "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/resources" haresources "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/ha/resources"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
@ -35,7 +35,7 @@ import (
// and the entity name in the API is "ha resource", so... // and the entity name in the API is "ha resource", so...
type haResourceResource struct { type haResourceResource struct {
// The HA resources API client // The HA resources API client
client haresources.Client client *haresources.Client
} }
// Ensure the resource implements the expected interfaces. // Ensure the resource implements the expected interfaces.
@ -144,15 +144,17 @@ func (r *haResourceResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if ok { if !ok {
r.client = *client.Cluster().HA().Resources()
} else {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
) )
return
} }
r.client = cfg.Client.Cluster().HA().Resources()
} }
// Create creates a new HA resource. // Create creates a new HA resource.

View File

@ -20,7 +20,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping" "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping"
) )
@ -42,17 +42,17 @@ func (d *dataSource) Configure(_ context.Context, req datasource.ConfigureReques
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
) )
return return
} }
d.client = client.Cluster().HardwareMapping() d.client = cfg.Client.Cluster().HardwareMapping()
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.

View File

@ -16,9 +16,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox"
mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping" mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping"
) )
@ -45,17 +45,17 @@ func (d *pciDataSource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
) )
return return
} }
d.client = client.Cluster().HardwareMapping() d.client = cfg.Client.Cluster().HardwareMapping()
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.

View File

@ -16,9 +16,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox"
mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping" mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping"
) )
@ -44,17 +44,17 @@ func (d *usbDataSource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
) )
return return
} }
d.client = client.Cluster().HardwareMapping() d.client = cfg.Client.Cluster().HardwareMapping()
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.

View File

@ -23,9 +23,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox"
mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping" mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping"
) )
@ -40,7 +40,7 @@ var (
// pciResource contains the PCI hardware mapping resource's internal data. // pciResource contains the PCI hardware mapping resource's internal data.
type pciResource struct { type pciResource struct {
// client is the hardware mapping API client. // client is the hardware mapping API client.
client mappings.Client client *mappings.Client
} }
// read reads information about a PCI hardware mapping from the Proxmox VE API. // read reads information about a PCI hardware mapping from the Proxmox VE API.
@ -89,15 +89,17 @@ func (r *pciResource) Configure(_ context.Context, req resource.ConfigureRequest
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
) )
return
} }
r.client = *client.Cluster().HardwareMapping() r.client = cfg.Client.Cluster().HardwareMapping()
} }
// Create creates a new PCI hardware mapping. // Create creates a new PCI hardware mapping.

View File

@ -22,9 +22,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/hardwaremapping"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox"
mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping" mappings "github.com/bpg/terraform-provider-proxmox/proxmox/cluster/mapping"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types/hardwaremapping"
) )
@ -39,7 +39,7 @@ var (
// usbResource contains the USB hardware mapping resource's internal data. // usbResource contains the USB hardware mapping resource's internal data.
type usbResource struct { type usbResource struct {
// client is the hardware mapping API client. // client is the hardware mapping API client.
client mappings.Client client *mappings.Client
} }
// read reads information about a USB hardware mapping from the Proxmox VE API. // read reads information about a USB hardware mapping from the Proxmox VE API.
@ -88,16 +88,17 @@ func (r *usbResource) Configure(_ context.Context, req resource.ConfigureRequest
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
) )
return
} }
r.client = *client.Cluster().HardwareMapping() r.client = cfg.Client.Cluster().HardwareMapping()
} }
// Create creates a new USB hardware mapping. // Create creates a new USB hardware mapping.

View File

@ -25,6 +25,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
@ -250,18 +251,17 @@ func (r *linuxBridgeResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
) )
return return
} }
r.client = client r.client = cfg.Client
} }
//nolint:dupl //nolint:dupl

View File

@ -23,6 +23,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
@ -223,18 +224,17 @@ func (r *linuxVLANResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
) )
return return
} }
r.client = client r.client = cfg.Client
} }
//nolint:dupl //nolint:dupl

View File

@ -17,6 +17,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
) )
@ -43,18 +44,17 @@ func (d *repositoryDataSource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
) )
return return
} }
d.client = client d.client = cfg.Client
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.

View File

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/nodes/apt" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/nodes/apt"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
@ -42,18 +43,17 @@ func (d *standardRepositoryDataSource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
) )
return return
} }
d.client = client d.client = cfg.Client
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.

View File

@ -26,6 +26,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
api "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/apt/repositories" api "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/apt/repositories"
@ -105,18 +106,17 @@ func (r *repositoryResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected resource configuration type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
) )
return return
} }
r.client = client r.client = cfg.Client
} }
// Create modifies the activation state of an existing APT repository, including the addition of standard repositories // Create modifies the activation state of an existing APT repository, including the addition of standard repositories

View File

@ -22,6 +22,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/nodes/apt" customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types/nodes/apt"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
@ -106,18 +107,17 @@ func (r *standardRepositoryResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected resource configuration type", "Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
) )
return return
} }
r.client = client r.client = cfg.Client
} }
// Create adds an APT standard repository to the repository source lists. // Create adds an APT standard repository to the repository source lists.

View File

@ -26,6 +26,7 @@ import (
"github.com/bpg/terraform-provider-proxmox/fwprovider/access" "github.com/bpg/terraform-provider-proxmox/fwprovider/access"
"github.com/bpg/terraform-provider-proxmox/fwprovider/acme" "github.com/bpg/terraform-provider-proxmox/fwprovider/acme"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/fwprovider/ha" "github.com/bpg/terraform-provider-proxmox/fwprovider/ha"
"github.com/bpg/terraform-provider-proxmox/fwprovider/hardwaremapping" "github.com/bpg/terraform-provider-proxmox/fwprovider/hardwaremapping"
"github.com/bpg/terraform-provider-proxmox/fwprovider/network" "github.com/bpg/terraform-provider-proxmox/fwprovider/network"
@ -33,6 +34,7 @@ import (
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm" "github.com/bpg/terraform-provider-proxmox/fwprovider/vm"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes" "github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh" "github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
"github.com/bpg/terraform-provider-proxmox/utils" "github.com/bpg/terraform-provider-proxmox/utils"
@ -85,11 +87,13 @@ type proxmoxProviderModel struct {
Port types.Int64 `tfsdk:"port"` Port types.Int64 `tfsdk:"port"`
} `tfsdk:"node"` } `tfsdk:"node"`
} `tfsdk:"ssh"` } `tfsdk:"ssh"`
TmpDir types.String `tfsdk:"tmp_dir"` TmpDir types.String `tfsdk:"tmp_dir"`
RandomVMIDs types.Bool `tfsdk:"random_vm_ids"`
RandomVMIDStat types.Int64 `tfsdk:"random_vm_id_start"`
RandomVMIDEnd types.Int64 `tfsdk:"random_vm_id_end"`
} }
func (p *proxmoxProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { func (p *proxmoxProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
// resp.TypeName = "proxmox"
resp.TypeName = "proxmox_virtual_environment" resp.TypeName = "proxmox_virtual_environment"
resp.Version = p.version resp.Version = p.version
} }
@ -140,6 +144,20 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
Optional: true, Optional: true,
Sensitive: true, Sensitive: true,
}, },
"random_vm_ids": schema.BoolAttribute{
Description: "Whether to generate random VM / Container IDs.",
Optional: true,
},
"random_vm_id_start": schema.Int64Attribute{
Description: "The starting number for random VM / Container IDs.",
Optional: true,
Validators: []validator.Int64{int64validator.Between(100, 999999999)},
},
"random_vm_id_end": schema.Int64Attribute{
Description: "The ending number for random VM / Container IDs.",
Optional: true,
Validators: []validator.Int64{int64validator.Between(100, 999999999)},
},
"tmp_dir": schema.StringAttribute{ "tmp_dir": schema.StringAttribute{
Description: "The alternative temporary directory.", Description: "The alternative temporary directory.",
Optional: true, Optional: true,
@ -242,8 +260,8 @@ func (p *proxmoxProvider) Configure(
tflog.Info(ctx, "Configuring the Proxmox provider...") tflog.Info(ctx, "Configuring the Proxmox provider...")
// Retrieve provider data from configuration // Retrieve provider data from configuration
var config proxmoxProviderModel var cfg proxmoxProviderModel
diags := req.Config.Get(ctx, &config) diags := req.Config.Get(ctx, &cfg)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -253,7 +271,7 @@ func (p *proxmoxProvider) Configure(
// If practitioner provided a configuration value for any of the // If practitioner provided a configuration value for any of the
// attributes, it must be a known value. // attributes, it must be a known value.
if config.Endpoint.IsUnknown() { if cfg.Endpoint.IsUnknown() {
resp.Diagnostics.AddAttributeError( resp.Diagnostics.AddAttributeError(
path.Root("endpoint"), path.Root("endpoint"),
"Unknown Proxmox VE API Endpoint", "Unknown Proxmox VE API Endpoint",
@ -280,36 +298,40 @@ func (p *proxmoxProvider) Configure(
username := utils.GetAnyStringEnv("PROXMOX_VE_USERNAME") username := utils.GetAnyStringEnv("PROXMOX_VE_USERNAME")
password := utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD") password := utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD")
if !config.Endpoint.IsNull() { if !cfg.APIToken.IsNull() {
endpoint = config.Endpoint.ValueString() apiToken = cfg.APIToken.ValueString()
} }
if !config.Insecure.IsNull() { if !cfg.Endpoint.IsNull() {
insecure = config.Insecure.ValueBool() endpoint = cfg.Endpoint.ValueString()
} }
if !config.MinTLS.IsNull() { if !cfg.Insecure.IsNull() {
minTLS = config.MinTLS.ValueString() insecure = cfg.Insecure.ValueBool()
} }
if !config.AuthTicket.IsNull() { if !cfg.MinTLS.IsNull() {
authTicket = config.AuthTicket.ValueString() minTLS = cfg.MinTLS.ValueString()
} }
if !config.CSRFPreventionToken.IsNull() { if !cfg.AuthTicket.IsNull() {
csrfPreventionToken = config.CSRFPreventionToken.ValueString() authTicket = cfg.AuthTicket.ValueString()
} }
if !config.APIToken.IsNull() { if !cfg.CSRFPreventionToken.IsNull() {
apiToken = config.APIToken.ValueString() csrfPreventionToken = cfg.CSRFPreventionToken.ValueString()
} }
if !config.Username.IsNull() { if !cfg.APIToken.IsNull() {
username = config.Username.ValueString() apiToken = cfg.APIToken.ValueString()
} }
if !config.Password.IsNull() { if !cfg.Username.IsNull() {
password = config.Password.ValueString() username = cfg.Username.ValueString()
}
if !cfg.Password.IsNull() {
password = cfg.Password.ValueString()
} }
if endpoint == "" { if endpoint == "" {
@ -371,40 +393,40 @@ func (p *proxmoxProvider) Configure(
nodeOverrides := map[string]ssh.ProxmoxNode{} nodeOverrides := map[string]ssh.ProxmoxNode{}
//nolint: nestif //nolint: nestif
if len(config.SSH) > 0 { if len(cfg.SSH) > 0 {
if !config.SSH[0].Username.IsNull() { if !cfg.SSH[0].Username.IsNull() {
sshUsername = config.SSH[0].Username.ValueString() sshUsername = cfg.SSH[0].Username.ValueString()
} }
if !config.SSH[0].Password.IsNull() { if !cfg.SSH[0].Password.IsNull() {
sshPassword = config.SSH[0].Password.ValueString() sshPassword = cfg.SSH[0].Password.ValueString()
} }
if !config.SSH[0].Agent.IsNull() { if !cfg.SSH[0].Agent.IsNull() {
sshAgent = config.SSH[0].Agent.ValueBool() sshAgent = cfg.SSH[0].Agent.ValueBool()
} }
if !config.SSH[0].AgentSocket.IsNull() { if !cfg.SSH[0].AgentSocket.IsNull() {
sshAgentSocket = config.SSH[0].AgentSocket.ValueString() sshAgentSocket = cfg.SSH[0].AgentSocket.ValueString()
} }
if !config.SSH[0].PrivateKey.IsNull() { if !cfg.SSH[0].PrivateKey.IsNull() {
sshPrivateKey = config.SSH[0].PrivateKey.ValueString() sshPrivateKey = cfg.SSH[0].PrivateKey.ValueString()
} }
if !config.SSH[0].Socks5Server.IsNull() { if !cfg.SSH[0].Socks5Server.IsNull() {
sshSocks5Server = config.SSH[0].Socks5Server.ValueString() sshSocks5Server = cfg.SSH[0].Socks5Server.ValueString()
} }
if !config.SSH[0].Socks5Username.IsNull() { if !cfg.SSH[0].Socks5Username.IsNull() {
sshSocks5Username = config.SSH[0].Socks5Username.ValueString() sshSocks5Username = cfg.SSH[0].Socks5Username.ValueString()
} }
if !config.SSH[0].Socks5Password.IsNull() { if !cfg.SSH[0].Socks5Password.IsNull() {
sshSocks5Password = config.SSH[0].Socks5Password.ValueString() sshSocks5Password = cfg.SSH[0].Socks5Password.ValueString()
} }
for _, n := range config.SSH[0].Nodes { for _, n := range cfg.SSH[0].Nodes {
nodePort := int32(n.Port.ValueInt64()) nodePort := int32(n.Port.ValueInt64())
if nodePort == 0 { if nodePort == 0 {
nodePort = 22 nodePort = 22
@ -447,14 +469,27 @@ func (p *proxmoxProvider) Configure(
// Intentionally use 'PROXMOX_VE_TMPDIR' with 'TMP' instead of 'TEMP', to match os.TempDir's use of $TMPDIR // Intentionally use 'PROXMOX_VE_TMPDIR' with 'TMP' instead of 'TEMP', to match os.TempDir's use of $TMPDIR
tmpDirOverride := utils.GetAnyStringEnv("PROXMOX_VE_TMPDIR", "PM_VE_TMPDIR") tmpDirOverride := utils.GetAnyStringEnv("PROXMOX_VE_TMPDIR", "PM_VE_TMPDIR")
if !config.TmpDir.IsNull() { if !cfg.TmpDir.IsNull() {
tmpDirOverride = config.TmpDir.ValueString() tmpDirOverride = cfg.TmpDir.ValueString()
} }
client := proxmox.NewClient(apiClient, sshClient, tmpDirOverride) client := proxmox.NewClient(apiClient, sshClient, tmpDirOverride)
resp.ResourceData = client resp.ResourceData = config.Resource{
resp.DataSourceData = client Client: client,
IDGenerator: cluster.NewIDGenerator(
client.Cluster(),
cluster.IDGeneratorConfig{
RandomIDs: cfg.RandomVMIDs.ValueBool(),
RandomIDStat: int(cfg.RandomVMIDStat.ValueInt64()),
RandomIDEnd: int(cfg.RandomVMIDEnd.ValueInt64()),
},
),
}
resp.DataSourceData = config.DataSource{
Client: client,
}
} }
func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resource { func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resource {

View File

@ -28,6 +28,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "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/api"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
@ -333,7 +334,7 @@ func (r *downloadFileResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
@ -344,7 +345,7 @@ func (r *downloadFileResource) Configure(
return return
} }
r.client = client r.client = cfg.Client
} }
func (r *downloadFileResource) Create( func (r *downloadFileResource) Create(

View File

@ -22,6 +22,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute" "github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators" "github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster" "github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
@ -697,7 +698,7 @@ func (r *clusterOptionsResource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
@ -708,7 +709,7 @@ func (r *clusterOptionsResource) Configure(
return return
} }
r.client = client r.client = cfg.Client
} }
// Create update must-existing cluster options. // Create update must-existing cluster options.

View File

@ -114,7 +114,6 @@ func TestAccResourceContainer(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_container" "test_container" { resource "proxmox_virtual_environment_container" "test_container" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
vm_id = {{.RandomVMID}}
started = false started = false
disk { disk {
datastore_id = "local-lvm" datastore_id = "local-lvm"
@ -190,7 +189,6 @@ func TestAccResourceContainer(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_container" "test_container" { resource "proxmox_virtual_environment_container" "test_container" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
vm_id = {{.RandomVMID}}
template = true template = true
disk { disk {
datastore_id = "local-lvm" datastore_id = "local-lvm"

View File

@ -15,7 +15,6 @@ import (
"testing" "testing"
"text/template" "text/template"
"github.com/brianvoe/gofakeit/v7"
"github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tfprotov6"
@ -81,6 +80,7 @@ provider "proxmox" {
port = %s port = %s
} }
} }
//random_vm_ids = true
} }
`, nodeName, nodeAddress, nodePort) `, nodeName, nodeAddress, nodePort)
@ -128,10 +128,6 @@ func (e *Environment) RenderConfig(cfg string) string {
tmpl, err := template.New("config").Parse("{{.ProviderConfig}}" + cfg) tmpl, err := template.New("config").Parse("{{.ProviderConfig}}" + cfg)
require.NoError(e.t, err) require.NoError(e.t, err)
e.templateVars["RandomVMID"] = gofakeit.IntRange(100_000, 999_999)
e.templateVars["RandomVMID1"] = gofakeit.IntRange(100_000, 999_999)
e.templateVars["RandomVMID2"] = gofakeit.IntRange(100_000, 999_999)
var buf bytes.Buffer var buf bytes.Buffer
err = tmpl.Execute(&buf, e.templateVars) err = tmpl.Execute(&buf, e.templateVars)
require.NoError(e.t, err) require.NoError(e.t, err)

View File

@ -31,7 +31,6 @@ func TestAccResourceVM2CDROM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cdrom" name = "test-cdrom"
cdrom = { cdrom = {
"ide3" = {} "ide3" = {}
@ -46,7 +45,6 @@ func TestAccResourceVM2CDROM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cdrom" name = "test-cdrom"
cdrom = { cdrom = {
"ide3" = {}, "ide3" = {},
@ -66,7 +64,6 @@ func TestAccResourceVM2CDROM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cdrom" name = "test-cdrom"
cdrom = { cdrom = {
"scsi2" = { "scsi2" = {
@ -107,7 +104,6 @@ func TestAccResourceVM2CDROM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" { resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-cdrom" name = "template-cdrom"
cdrom = { cdrom = {
"ide3" = { "ide3" = {
@ -117,7 +113,6 @@ func TestAccResourceVM2CDROM(t *testing.T) {
} }
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "test-cdrom" name = "test-cdrom"
clone = { clone = {
id = proxmox_virtual_environment_vm2.template_vm.id id = proxmox_virtual_environment_vm2.template_vm.id
@ -132,7 +127,6 @@ func TestAccResourceVM2CDROM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" { resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-cdrom" name = "template-cdrom"
cdrom = { cdrom = {
"ide1" = { "ide1" = {
@ -145,7 +139,6 @@ func TestAccResourceVM2CDROM(t *testing.T) {
} }
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "test-cpu" name = "test-cpu"
clone = { clone = {
id = proxmox_virtual_environment_vm2.template_vm.id id = proxmox_virtual_environment_vm2.template_vm.id

View File

@ -29,7 +29,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cpu" name = "test-cpu"
}`), }`),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
@ -45,7 +44,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cpu" name = "test-cpu"
cpu = { cpu = {
cores = 2 cores = 2
@ -69,7 +67,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cpu" name = "test-cpu"
cpu = { cpu = {
# affinity = "0-1" only root can set affinity # affinity = "0-1" only root can set affinity
@ -135,7 +132,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" { resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-cpu" name = "template-cpu"
cpu = { cpu = {
cores = 2 cores = 2
@ -145,7 +141,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
} }
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "test-cpu" name = "test-cpu"
clone = { clone = {
id = proxmox_virtual_environment_vm2.template_vm.id id = proxmox_virtual_environment_vm2.template_vm.id
@ -163,7 +158,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" { resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-cpu" name = "template-cpu"
cpu = { cpu = {
cores = 2 cores = 2
@ -173,7 +167,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
} }
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "test-cpu" name = "test-cpu"
clone = { clone = {
id = proxmox_virtual_environment_vm2.template_vm.id id = proxmox_virtual_environment_vm2.template_vm.id

View File

@ -1,3 +1,9 @@
/*
* 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 vm package vm
import ( import (
@ -7,6 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
) )
@ -45,37 +52,36 @@ func (d *Datasource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.DataSource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected DataSource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData), fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
) )
return return
} }
d.client = client d.client = cfg.Client
} }
//nolint:dupl //nolint:dupl
func (d *Datasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { func (d *Datasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var config Model var model Model
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
timeout, diags := config.Timeouts.Read(ctx, defaultReadTimeout) timeout, diags := model.Timeouts.Read(ctx, defaultReadTimeout)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, timeout) ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel() defer cancel()
exists := read(ctx, d.client, &config, &resp.Diagnostics) exists := read(ctx, d.client, &model, &resp.Diagnostics)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@ -83,12 +89,12 @@ func (d *Datasource) Read(ctx context.Context, req datasource.ReadRequest, resp
if !exists { if !exists {
tflog.Info(ctx, "VM does not exist, removing from the state", map[string]interface{}{ tflog.Info(ctx, "VM does not exist, removing from the state", map[string]interface{}{
"id": config.ID.ValueInt64(), "id": model.ID.ValueInt64(),
}) })
resp.State.RemoveResource(ctx) resp.State.RemoveResource(ctx)
return return
} }
resp.Diagnostics.Append(resp.State.Set(ctx, config)...) resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
} }

View File

@ -21,11 +21,13 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cdrom" "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cdrom"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu" "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/cpu"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga" "github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms" "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types" proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
) )
@ -48,7 +50,8 @@ var (
// Resource implements the resource.Resource interface for managing VMs. // Resource implements the resource.Resource interface for managing VMs.
type Resource struct { type Resource struct {
client proxmox.Client client proxmox.Client
idGenerator cluster.IDGenerator
} }
// NewResource creates a new resource for managing VMs. // NewResource creates a new resource for managing VMs.
@ -75,8 +78,7 @@ func (r *Resource) Configure(
return return
} }
client, ok := req.ProviderData.(proxmox.Client) cfg, ok := req.ProviderData.(config.Resource)
if !ok { if !ok {
resp.Diagnostics.AddError( resp.Diagnostics.AddError(
"Unexpected Resource Configure Type", "Unexpected Resource Configure Type",
@ -86,7 +88,8 @@ func (r *Resource) Configure(
return return
} }
r.client = client r.client = cfg.Client
r.idGenerator = cfg.IDGenerator
} }
// Create creates a new VM. // Create creates a new VM.
@ -106,13 +109,13 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp
defer cancel() defer cancel()
if plan.ID.ValueInt64() == 0 { if plan.ID.ValueInt64() == 0 {
id, err := r.client.Cluster().GetVMID(ctx) id, err := r.idGenerator.NextID(ctx)
if err != nil { if err != nil {
resp.Diagnostics.AddError("Failed to get VM ID", err.Error()) resp.Diagnostics.AddError("Failed to generate VM ID", err.Error())
return return
} }
plan.ID = types.Int64Value(int64(*id)) plan.ID = types.Int64Value(int64(id))
} }
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {

View File

@ -9,6 +9,7 @@
package vm_test package vm_test
import ( import (
"math/rand"
"regexp" "regexp"
"testing" "testing"
@ -21,6 +22,9 @@ func TestAccResourceVM(t *testing.T) {
t.Parallel() t.Parallel()
te := test.InitEnvironment(t) te := test.InitEnvironment(t)
te.AddTemplateVars(map[string]interface{}{
"TestVMID": 100000 + rand.Intn(99999),
})
tests := []struct { tests := []struct {
name string name string
@ -44,14 +48,13 @@ func TestAccResourceVM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}} id = {{.TestVMID}}
}`), }`),
}}}, }}},
{"set an invalid VM name", []resource.TestStep{{ {"set an invalid VM name", []resource.TestStep{{
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "not a valid DNS name" name = "not a valid DNS name"
}`), }`),
ExpectError: regexp.MustCompile(`name must be a valid DNS name`), ExpectError: regexp.MustCompile(`name must be a valid DNS name`),
@ -61,7 +64,6 @@ func TestAccResourceVM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-vm" name = "test-vm"
description = "test description" description = "test description"
}`), }`),
@ -97,7 +99,6 @@ func TestAccResourceVM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-tags" name = "test-tags"
tags = ["tag2", "tag1"] tags = ["tag2", "tag1"]
}`), }`),
@ -146,7 +147,6 @@ func TestAccResourceVM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
tags = ["", "tag1"] tags = ["", "tag1"]
}`), }`),
ExpectError: regexp.MustCompile(`string length must be at least 1, got: 0`), ExpectError: regexp.MustCompile(`string length must be at least 1, got: 0`),
@ -155,7 +155,6 @@ func TestAccResourceVM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
tags = [" ", "tag1"] tags = [" ", "tag1"]
}`), }`),
ExpectError: regexp.MustCompile(`must be a non-empty and non-whitespace string`), ExpectError: regexp.MustCompile(`must be a non-empty and non-whitespace string`),
@ -164,7 +163,6 @@ func TestAccResourceVM(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
description = trimspace(<<-EOT description = trimspace(<<-EOT
my my
description description
@ -203,14 +201,12 @@ func TestAccResourceVM2Clone(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template" name = "template"
description = "template description" description = "template description"
template = true template = true
} }
resource "proxmox_virtual_environment_vm2" "test_vm_clone" { resource "proxmox_virtual_environment_vm2" "test_vm_clone" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "clone" name = "clone"
clone = { clone = {
id = proxmox_virtual_environment_vm2.test_vm.id id = proxmox_virtual_environment_vm2.test_vm.id
@ -234,13 +230,11 @@ func TestAccResourceVM2Clone(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
template = true template = true
tags = ["tag1", "tag2"] tags = ["tag1", "tag2"]
} }
resource "proxmox_virtual_environment_vm2" "test_vm_clone" { resource "proxmox_virtual_environment_vm2" "test_vm_clone" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
clone = { clone = {
id = proxmox_virtual_environment_vm2.test_vm.id id = proxmox_virtual_environment_vm2.test_vm.id
} }

View File

@ -29,7 +29,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-vga" name = "test-vga"
}`), }`),
Check: test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{ Check: test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
@ -41,7 +40,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-vga" name = "test-vga"
vga = { vga = {
type = "std" type = "std"
@ -62,7 +60,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-vga" name = "test-vga"
vga = { vga = {
type = "std" type = "std"
@ -107,7 +104,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" { resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-vga" name = "template-vga"
vga = { vga = {
type = "qxl" type = "qxl"
@ -116,7 +112,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
} }
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "test-vga" name = "test-vga"
clone = { clone = {
id = proxmox_virtual_environment_vm2.template_vm.id id = proxmox_virtual_environment_vm2.template_vm.id
@ -133,7 +128,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(` Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" { resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-vga" name = "template-vga"
vga = { vga = {
type = "qxl" type = "qxl"
@ -143,7 +137,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
resource "proxmox_virtual_environment_vm2" "test_vm" { resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}" node_name = "{{.NodeName}}"
name = "test-cpu" name = "test-cpu"
id = {{.RandomVMID2}}
clone = { clone = {
id = proxmox_virtual_environment_vm2.template_vm.id id = proxmox_virtual_environment_vm2.template_vm.id
} }

2
go.mod
View File

@ -33,6 +33,7 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect github.com/cloudflare/circl v1.3.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.16.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect
@ -62,6 +63,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.1.0 // indirect github.com/oklog/run v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect

4
go.sum
View File

@ -36,6 +36,8 @@ github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZt
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -152,6 +154,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=

View File

@ -11,27 +11,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"sync"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/proxmox/api"
) )
const (
getVMIDStep = 1
)
// ErrVMDoesNotExist is returned when the VM identifier cannot be found on any cluster node. // ErrVMDoesNotExist is returned when the VM identifier cannot be found on any cluster node.
var ErrVMDoesNotExist = errors.New("unable to find VM identifier on any cluster node") var ErrVMDoesNotExist = errors.New("unable to find VM identifier on any cluster node")
var (
//nolint:gochecknoglobals
getVMIDCounter = -1
//nolint:gochecknoglobals
getVMIDCounterMutex = &sync.Mutex{}
)
// GetNextID retrieves the next free VM identifier for the cluster. // GetNextID retrieves the next free VM identifier for the cluster.
func (c *Client) GetNextID(ctx context.Context, vmID *int) (*int, error) { func (c *Client) GetNextID(ctx context.Context, vmID *int) (*int, error) {
reqBody := &NextIDRequestBody{ reqBody := &NextIDRequestBody{
@ -52,52 +38,6 @@ func (c *Client) GetNextID(ctx context.Context, vmID *int) (*int, error) {
return (*int)(resBody.Data), nil return (*int)(resBody.Data), nil
} }
// GetVMID retrieves the next available VM identifier.
func (c *Client) GetVMID(ctx context.Context) (*int, error) {
getVMIDCounterMutex.Lock()
defer getVMIDCounterMutex.Unlock()
if getVMIDCounter < 0 {
nextVMID, err := c.GetNextID(ctx, nil)
if err != nil {
return nil, err
}
if nextVMID == nil {
return nil, errors.New("unable to retrieve the next available VM identifier")
}
getVMIDCounter = *nextVMID + getVMIDStep
tflog.Debug(ctx, "next VM identifier", map[string]interface{}{
"id": *nextVMID,
})
return nextVMID, nil
}
vmID := getVMIDCounter
for vmID <= 2147483637 {
_, err := c.GetNextID(ctx, &vmID)
if err != nil {
vmID += getVMIDStep
continue
}
getVMIDCounter = vmID + getVMIDStep
tflog.Debug(ctx, "next VM identifier", map[string]interface{}{
"id": vmID,
})
return &vmID, nil
}
return nil, errors.New("unable to determine the next available VM identifier")
}
// GetClusterResources retrieves current resources for cluster. // GetClusterResources retrieves current resources for cluster.
func (c *Client) GetClusterResources(ctx context.Context, resourceType string) ([]*ResourcesListResponseData, error) { func (c *Client) GetClusterResources(ctx context.Context, resourceType string) ([]*ResourcesListResponseData, error) {
reqBody := &ResourcesListRequestBody{ reqBody := &ResourcesListRequestBody{

View File

@ -0,0 +1,136 @@
/*
* 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 cluster
import (
"bytes"
"context"
"errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"strconv"
"time"
"github.com/avast/retry-go/v4"
"github.com/rogpeppe/go-internal/lockedfile"
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
)
const (
idGeneratorLockFile = "terraform-provider-proxmox-id-gen.lock"
idGeneratorSequenceFile = "terraform-provider-proxmox-id-gen.seq"
)
// IDGenerator is responsible for generating unique identifiers for VMs and Containers.
type IDGenerator struct {
client *Client
config IDGeneratorConfig
}
// IDGeneratorConfig is the configuration for the IDGenerator.
type IDGeneratorConfig struct {
RandomIDs bool
RandomIDStat int
RandomIDEnd int
lockFName string
seqFName string
}
// NewIDGenerator creates a new IDGenerator with the given parameters.
func NewIDGenerator(client *Client, config IDGeneratorConfig) IDGenerator {
if config.RandomIDStat == 0 {
config.RandomIDStat = 10000
}
if config.RandomIDEnd == 0 {
config.RandomIDEnd = 99999
}
config.lockFName = filepath.Join(os.TempDir(), idGeneratorLockFile)
config.seqFName = filepath.Join(os.TempDir(), idGeneratorSequenceFile)
unlock, err := lockedfile.MutexAt(config.lockFName).Lock()
if err == nil {
defer unlock()
// delete the sequence file if it is older than 10 seconds
// this is to prevent the sequence file from growing indefinitely,
// while giving some protection against parallel runs of the provider
// that might interfere with each other and reset the sequence at the same time
stat, err := os.Stat(config.seqFName)
if err == nil && time.Since(stat.ModTime()) > 10*time.Second {
_ = os.Remove(config.seqFName)
}
}
return IDGenerator{client, config}
}
// NextID returns the next available VM identifier.
func (g IDGenerator) NextID(ctx context.Context) (int, error) {
// lock the ID generator to prevent concurrent access
// it should be unlocked only when the new ID is successfully
// retrieved (and optionally written to the sequence file)
unlock, err := lockedfile.MutexAt(g.config.lockFName).Lock()
if err != nil {
return -1, fmt.Errorf("unable to lock the ID generator: %w", err)
}
defer unlock()
id, err := retry.DoWithData(func() (*int, error) {
var newID *int
if g.config.RandomIDs {
//nolint:gosec
newID = ptr.Ptr(rand.Intn(g.config.RandomIDEnd-g.config.RandomIDStat) + g.config.RandomIDStat)
} else {
newID, err = nextSequentialID(g.config.seqFName)
if err != nil {
return nil, err
}
}
return g.client.GetNextID(ctx, newID)
})
if err != nil {
return -1, fmt.Errorf("unable to retrieve the next available VM identifier: %w", err)
}
if !g.config.RandomIDs {
var b bytes.Buffer
_, _ = fmt.Fprintf(&b, "%d", *id)
if err := lockedfile.Write(g.config.seqFName, &b, 0o666); err != nil {
return -1, fmt.Errorf("unable to write the ID generator file: %w", err)
}
}
return *id, nil
}
func nextSequentialID(seqFName string) (*int, error) {
buf, err := lockedfile.Read(seqFName)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, nil //nolint:nilnil
}
return nil, fmt.Errorf("unable to read the ID generator sequence file: %w", err)
}
id, err := strconv.Atoi(string(buf))
if err != nil {
return nil, fmt.Errorf("unable to parse the ID generator file: %w", err)
}
return ptr.Ptr(id + 1), nil
}

View File

@ -12,6 +12,7 @@ import (
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh" "github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
) )
@ -20,6 +21,7 @@ type ProviderConfiguration struct {
apiClient api.Client apiClient api.Client
sshClient ssh.Client sshClient ssh.Client
tmpDirOverride string tmpDirOverride string
idGenerator cluster.IDGenerator
} }
// NewProviderConfiguration creates a new provider configuration. // NewProviderConfiguration creates a new provider configuration.
@ -27,12 +29,22 @@ func NewProviderConfiguration(
apiClient api.Client, apiClient api.Client,
sshClient ssh.Client, sshClient ssh.Client,
tmpDirOverride string, tmpDirOverride string,
) ProviderConfiguration { idCfg cluster.IDGeneratorConfig,
return ProviderConfiguration{ ) (ProviderConfiguration, error) {
cfg := ProviderConfiguration{
apiClient: apiClient, apiClient: apiClient,
sshClient: sshClient, sshClient: sshClient,
tmpDirOverride: tmpDirOverride, tmpDirOverride: tmpDirOverride,
} }
client, err := cfg.GetClient()
if err != nil {
return cfg, err
}
cfg.idGenerator = cluster.NewIDGenerator(client.Cluster(), idCfg)
return cfg, nil
} }
// GetClient returns the Proxmox API client. // GetClient returns the Proxmox API client.
@ -60,3 +72,8 @@ func (c *ProviderConfiguration) TempDir() string {
return os.TempDir() return os.TempDir()
} }
// GetIDGenerator returns the IDGenerator.
func (c *ProviderConfiguration) GetIDGenerator() cluster.IDGenerator {
return c.idGenerator
}

View File

@ -17,6 +17,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/proxmox/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes" "github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh" "github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf" "github.com/bpg/terraform-provider-proxmox/proxmoxtf"
@ -210,7 +211,24 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
tmpDirOverride = v.(string) tmpDirOverride = v.(string)
} }
config := proxmoxtf.NewProviderConfiguration(apiClient, sshClient, tmpDirOverride) idCfg := cluster.IDGeneratorConfig{}
if v, ok := d.GetOk(mkProviderRandomVMIDs); ok {
idCfg.RandomIDs = v.(bool)
}
if v, ok := d.GetOk(mkProviderRandomVMIDStart); ok {
idCfg.RandomIDStat = v.(int)
}
if v, ok := d.GetOk(mkProviderRandomVMIDEnd); ok {
idCfg.RandomIDEnd = v.(int)
}
config, err := proxmoxtf.NewProviderConfiguration(apiClient, sshClient, tmpDirOverride, idCfg)
if err != nil {
return nil, diag.Errorf("error creating provider's configuration: %s", err)
}
return config, nil return config, nil
} }

View File

@ -24,6 +24,9 @@ const (
mkProviderPassword = "password" mkProviderPassword = "password"
mkProviderUsername = "username" mkProviderUsername = "username"
mkProviderTmpDir = "tmp_dir" mkProviderTmpDir = "tmp_dir"
mkProviderRandomVMIDs = "random_vm_ids"
mkProviderRandomVMIDStart = "random_vm_id_start"
mkProviderRandomVMIDEnd = "random_vm_id_end"
mkProviderSSH = "ssh" mkProviderSSH = "ssh"
mkProviderSSHUsername = "username" mkProviderSSHUsername = "username"
mkProviderSSHPassword = "password" mkProviderSSHPassword = "password"
@ -240,5 +243,22 @@ func createSchema() map[string]*schema.Schema {
Description: "The alternative temporary directory.", Description: "The alternative temporary directory.",
ValidateFunc: validation.StringIsNotEmpty, ValidateFunc: validation.StringIsNotEmpty,
}, },
mkProviderRandomVMIDs: {
Type: schema.TypeBool,
Optional: true,
Description: "Whether to generate random VM / Container IDs.",
},
mkProviderRandomVMIDStart: {
Type: schema.TypeInt,
Optional: true,
Description: "The starting number for random VM / Container IDs.",
ValidateFunc: validation.IntBetween(100, 999999999),
},
mkProviderRandomVMIDEnd: {
Type: schema.TypeInt,
Optional: true,
Description: "The ending number for random VM / Container IDs.",
ValidateFunc: validation.IntBetween(100, 999999999),
},
} }
} }

View File

@ -983,12 +983,12 @@ func containerCreateClone(ctx context.Context, d *schema.ResourceData, m interfa
vmID := vmIDUntyped.(int) vmID := vmIDUntyped.(int)
if !hasVMID { if !hasVMID {
vmIDNew, err := client.Cluster().GetVMID(ctx) vmIDNew, err := config.GetIDGenerator().NextID(ctx)
if err != nil { if err != nil {
return diag.FromErr(err) return diag.FromErr(err)
} }
vmID = *vmIDNew vmID = vmIDNew
err = d.Set(mkVMID, vmID) err = d.Set(mkVMID, vmID)
if err != nil { if err != nil {
@ -1696,12 +1696,12 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf
vmID := vmIDUntyped.(int) vmID := vmIDUntyped.(int)
if !hasVMID { if !hasVMID {
vmIDNew, err := client.Cluster().GetVMID(ctx) vmIDNew, err := config.GetIDGenerator().NextID(ctx)
if err != nil { if err != nil {
return diag.FromErr(err) return diag.FromErr(err)
} }
vmID = *vmIDNew vmID = vmIDNew
err = d.Set(mkVMID, vmID) err = d.Set(mkVMID, vmID)
if err != nil { if err != nil {

View File

@ -1713,12 +1713,12 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
vmID := vmIDUntyped.(int) vmID := vmIDUntyped.(int)
if !hasVMID { if !hasVMID {
vmIDNew, err := client.Cluster().GetVMID(ctx) vmIDNew, err := config.GetIDGenerator().NextID(ctx)
if err != nil { if err != nil {
return diag.FromErr(err) return diag.FromErr(err)
} }
vmID = *vmIDNew vmID = vmIDNew
err = d.Set(mkVMID, vmID) err = d.Set(mkVMID, vmID)
if err != nil { if err != nil {
@ -2509,12 +2509,12 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
vmID := vmIDUntyped.(int) vmID := vmIDUntyped.(int)
if !hasVMID { if !hasVMID {
vmIDNew, e := client.Cluster().GetVMID(ctx) vmIDNew, e := config.GetIDGenerator().NextID(ctx)
if e != nil { if e != nil {
return diag.FromErr(e) return diag.FromErr(e)
} }
vmID = *vmIDNew vmID = vmIDNew
e = d.Set(mkVMID, vmID) e = d.Set(mkVMID, vmID)
if e != nil { if e != nil {