0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-29 18:21: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",
"joshbolduc.commitlint",
"PKief.material-icon-theme",
"psioniq.psi-header"
]
}

29
.vscode/settings.json vendored
View File

@ -1,7 +1,7 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.guides.bracketPairsHorizontal": true,
"editor.guides.bracketPairs": false,
"editor.guides.bracketPairsHorizontal": false,
"editor.guides.highlightActiveBracketPair": true,
"editor.rulers": [
100
@ -26,12 +26,14 @@
"cputype",
"cpuunits",
"customdiff",
"Datasource",
"datasource",
"datasources",
"deepcode",
"directsync",
"efidisk",
"efidisks",
"FSTRIM",
"fwprovider",
"gocritic",
"gosimple",
"hookscript",
@ -110,4 +112,25 @@
"version": "include",
"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.
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:
- 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/*
```
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
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.
- 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.
## 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
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.
- `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`)
- `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/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/helpers/ptr"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
@ -108,19 +109,17 @@ func (r *aclResource) Configure(_ context.Context, req resource.ConfigureRequest
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T. Please report this issue to the provider developers.",
req.ProviderData),
fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
)
return
}
r.client = client
r.client = cfg.Client
}
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/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/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
@ -121,18 +122,19 @@ func (r *userTokenResource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"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
r.client = cfg.Client
r.client = cfg.Client
}
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/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"
)
@ -131,18 +131,17 @@ func (d *acmeAccountDatasource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.DataSource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
"Unexpected DataSource Configure Type",
fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
)
return
}
d.client = client.Cluster().ACME().Account()
d.client = cfg.Client.Cluster().ACME().Account()
}
// Read retrieves the ACME account information.
@ -157,7 +156,7 @@ func (d *acmeAccountDatasource) Read(ctx context.Context, req datasource.ReadReq
name := state.Name.ValueString()
account, err := d.client.Get(ctx, name)
accountData, err := d.client.Get(ctx, name)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Unable to read ACME account '%s'", name),
@ -167,15 +166,15 @@ func (d *acmeAccountDatasource) Read(ctx context.Context, req datasource.ReadReq
return
}
contactList := make([]types.String, len(account.Account.Contact))
for i, contact := range account.Account.Contact {
contactList := make([]types.String, len(accountData.Account.Contact))
for i, contact := range accountData.Account.Contact {
contactList[i] = types.StringValue(contact)
}
data := &accountDataModel{
Contact: contactList,
CreatedAt: types.StringValue(account.Account.CreatedAt),
Status: types.StringValue(account.Account.Status),
CreatedAt: types.StringValue(accountData.Account.CreatedAt),
Status: types.StringValue(accountData.Account.Status),
}
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.Directory = types.StringValue(account.Directory)
state.Location = types.StringValue(account.Location)
state.TOS = types.StringValue(account.TOS)
state.Directory = types.StringValue(accountData.Directory)
state.Location = types.StringValue(accountData.Location)
state.TOS = types.StringValue(accountData.TOS)
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/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"
)
@ -77,18 +77,17 @@ func (d *acmeAccountsDatasource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.DataSource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
"Unexpected DataSource Configure Type",
fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
)
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.

View File

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

View File

@ -22,7 +22,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"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"
)
@ -40,7 +40,7 @@ func NewACMEAccountResource() resource.Resource {
// acmeAccountResource contains the resource's internal data.
type acmeAccountResource struct {
// The ACME account API client
client account.Client
client *account.Client
}
// acmeAccountModel maps the schema data for the ACME account resource.
@ -137,16 +137,17 @@ func (r *acmeAccountResource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
if ok {
r.client = *client.Cluster().ACME().Account()
} else {
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"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().ACME().Account()
}
// 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/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"
)
@ -36,8 +36,8 @@ func NewACMEPluginResource() resource.Resource {
// acmePluginResource contains the resource's internal data.
type acmePluginResource struct {
// The ACME account API client
client plugins.Client
// The ACME plugin API client
client *plugins.Client
}
// Metadata defines the name of the resource.
@ -108,16 +108,17 @@ func (r *acmePluginResource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
if ok {
r.client = *client.Cluster().ACME().Plugins()
} else {
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"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().ACME().Plugins()
}
// 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/types"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox"
)
@ -85,19 +86,17 @@ func (d *versionDatasource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.DataSource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
)
return
}
d.client = client
d.client = cfg.Client
}
// 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/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"
)
@ -89,18 +89,17 @@ func (d *haGroupDatasource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.DataSource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
"Unexpected DataSource Configure Type",
fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
)
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.

View File

@ -16,8 +16,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"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"
)
@ -77,18 +77,17 @@ func (d *haGroupsDatasource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.DataSource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
"Unexpected DataSource Configure Type",
fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
)
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.

View File

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

View File

@ -18,8 +18,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"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"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
@ -92,16 +92,17 @@ func (d *haResourcesDatasource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
if ok {
d.client = client.Cluster().HA().Resources()
} else {
cfg, ok := req.ProviderData.(config.DataSource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T",
req.ProviderData),
"Unexpected DataSource Configure Type",
fmt.Sprintf("Expected config.DataSource, got: %T", 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.

View File

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

View File

@ -13,7 +13,7 @@ import (
"strings"
"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"
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...
type haResourceResource struct {
// The HA resources API client
client haresources.Client
client *haresources.Client
}
// Ensure the resource implements the expected interfaces.
@ -144,15 +144,17 @@ func (r *haResourceResource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
if ok {
r.client = *client.Cluster().HA().Resources()
} else {
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"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.

View File

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

View File

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

View File

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

View File

@ -23,9 +23,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"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"
"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"
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.
type pciResource struct {
// 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.
@ -89,15 +89,17 @@ func (r *pciResource) Configure(_ context.Context, req resource.ConfigureRequest
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"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.

View File

@ -22,9 +22,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"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"
"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"
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.
type usbResource struct {
// 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.
@ -88,16 +88,17 @@ func (r *usbResource) Configure(_ context.Context, req resource.ConfigureRequest
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"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.

View File

@ -25,6 +25,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"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"
"github.com/bpg/terraform-provider-proxmox/proxmox"
@ -250,18 +251,17 @@ func (r *linuxBridgeResource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"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
r.client = cfg.Client
}
//nolint:dupl

View File

@ -23,6 +23,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"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"
"github.com/bpg/terraform-provider-proxmox/proxmox"
@ -223,18 +224,17 @@ func (r *linuxVLANResource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"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
r.client = cfg.Client
}
//nolint:dupl

View File

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

View File

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

View File

@ -26,6 +26,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"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/proxmox"
api "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/apt/repositories"
@ -105,18 +106,17 @@ func (r *repositoryResource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected resource configuration type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData),
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
)
return
}
r.client = client
r.client = cfg.Client
}
// 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/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"
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
"github.com/bpg/terraform-provider-proxmox/proxmox"
@ -106,18 +107,17 @@ func (r *standardRepositoryResource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected resource configuration type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData),
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected config.Resource, got: %T", req.ProviderData),
)
return
}
r.client = client
r.client = cfg.Client
}
// 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/acme"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/fwprovider/ha"
"github.com/bpg/terraform-provider-proxmox/fwprovider/hardwaremapping"
"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/proxmox"
"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/ssh"
"github.com/bpg/terraform-provider-proxmox/utils"
@ -85,11 +87,13 @@ type proxmoxProviderModel struct {
Port types.Int64 `tfsdk:"port"`
} `tfsdk:"node"`
} `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) {
// resp.TypeName = "proxmox"
resp.TypeName = "proxmox_virtual_environment"
resp.Version = p.version
}
@ -140,6 +144,20 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
Optional: 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{
Description: "The alternative temporary directory.",
Optional: true,
@ -242,8 +260,8 @@ func (p *proxmoxProvider) Configure(
tflog.Info(ctx, "Configuring the Proxmox provider...")
// Retrieve provider data from configuration
var config proxmoxProviderModel
diags := req.Config.Get(ctx, &config)
var cfg proxmoxProviderModel
diags := req.Config.Get(ctx, &cfg)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@ -253,7 +271,7 @@ func (p *proxmoxProvider) Configure(
// If practitioner provided a configuration value for any of the
// attributes, it must be a known value.
if config.Endpoint.IsUnknown() {
if cfg.Endpoint.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("endpoint"),
"Unknown Proxmox VE API Endpoint",
@ -280,36 +298,40 @@ func (p *proxmoxProvider) Configure(
username := utils.GetAnyStringEnv("PROXMOX_VE_USERNAME")
password := utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD")
if !config.Endpoint.IsNull() {
endpoint = config.Endpoint.ValueString()
if !cfg.APIToken.IsNull() {
apiToken = cfg.APIToken.ValueString()
}
if !config.Insecure.IsNull() {
insecure = config.Insecure.ValueBool()
if !cfg.Endpoint.IsNull() {
endpoint = cfg.Endpoint.ValueString()
}
if !config.MinTLS.IsNull() {
minTLS = config.MinTLS.ValueString()
if !cfg.Insecure.IsNull() {
insecure = cfg.Insecure.ValueBool()
}
if !config.AuthTicket.IsNull() {
authTicket = config.AuthTicket.ValueString()
if !cfg.MinTLS.IsNull() {
minTLS = cfg.MinTLS.ValueString()
}
if !config.CSRFPreventionToken.IsNull() {
csrfPreventionToken = config.CSRFPreventionToken.ValueString()
if !cfg.AuthTicket.IsNull() {
authTicket = cfg.AuthTicket.ValueString()
}
if !config.APIToken.IsNull() {
apiToken = config.APIToken.ValueString()
if !cfg.CSRFPreventionToken.IsNull() {
csrfPreventionToken = cfg.CSRFPreventionToken.ValueString()
}
if !config.Username.IsNull() {
username = config.Username.ValueString()
if !cfg.APIToken.IsNull() {
apiToken = cfg.APIToken.ValueString()
}
if !config.Password.IsNull() {
password = config.Password.ValueString()
if !cfg.Username.IsNull() {
username = cfg.Username.ValueString()
}
if !cfg.Password.IsNull() {
password = cfg.Password.ValueString()
}
if endpoint == "" {
@ -371,40 +393,40 @@ func (p *proxmoxProvider) Configure(
nodeOverrides := map[string]ssh.ProxmoxNode{}
//nolint: nestif
if len(config.SSH) > 0 {
if !config.SSH[0].Username.IsNull() {
sshUsername = config.SSH[0].Username.ValueString()
if len(cfg.SSH) > 0 {
if !cfg.SSH[0].Username.IsNull() {
sshUsername = cfg.SSH[0].Username.ValueString()
}
if !config.SSH[0].Password.IsNull() {
sshPassword = config.SSH[0].Password.ValueString()
if !cfg.SSH[0].Password.IsNull() {
sshPassword = cfg.SSH[0].Password.ValueString()
}
if !config.SSH[0].Agent.IsNull() {
sshAgent = config.SSH[0].Agent.ValueBool()
if !cfg.SSH[0].Agent.IsNull() {
sshAgent = cfg.SSH[0].Agent.ValueBool()
}
if !config.SSH[0].AgentSocket.IsNull() {
sshAgentSocket = config.SSH[0].AgentSocket.ValueString()
if !cfg.SSH[0].AgentSocket.IsNull() {
sshAgentSocket = cfg.SSH[0].AgentSocket.ValueString()
}
if !config.SSH[0].PrivateKey.IsNull() {
sshPrivateKey = config.SSH[0].PrivateKey.ValueString()
if !cfg.SSH[0].PrivateKey.IsNull() {
sshPrivateKey = cfg.SSH[0].PrivateKey.ValueString()
}
if !config.SSH[0].Socks5Server.IsNull() {
sshSocks5Server = config.SSH[0].Socks5Server.ValueString()
if !cfg.SSH[0].Socks5Server.IsNull() {
sshSocks5Server = cfg.SSH[0].Socks5Server.ValueString()
}
if !config.SSH[0].Socks5Username.IsNull() {
sshSocks5Username = config.SSH[0].Socks5Username.ValueString()
if !cfg.SSH[0].Socks5Username.IsNull() {
sshSocks5Username = cfg.SSH[0].Socks5Username.ValueString()
}
if !config.SSH[0].Socks5Password.IsNull() {
sshSocks5Password = config.SSH[0].Socks5Password.ValueString()
if !cfg.SSH[0].Socks5Password.IsNull() {
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())
if nodePort == 0 {
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
tmpDirOverride := utils.GetAnyStringEnv("PROXMOX_VE_TMPDIR", "PM_VE_TMPDIR")
if !config.TmpDir.IsNull() {
tmpDirOverride = config.TmpDir.ValueString()
if !cfg.TmpDir.IsNull() {
tmpDirOverride = cfg.TmpDir.ValueString()
}
client := proxmox.NewClient(apiClient, sshClient, tmpDirOverride)
resp.ResourceData = client
resp.DataSourceData = client
resp.ResourceData = config.Resource{
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 {

View File

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

View File

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

View File

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

View File

@ -15,7 +15,6 @@ import (
"testing"
"text/template"
"github.com/brianvoe/gofakeit/v7"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
@ -81,6 +80,7 @@ provider "proxmox" {
port = %s
}
}
//random_vm_ids = true
}
`, nodeName, nodeAddress, nodePort)
@ -128,10 +128,6 @@ func (e *Environment) RenderConfig(cfg string) string {
tmpl, err := template.New("config").Parse("{{.ProviderConfig}}" + cfg)
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
err = tmpl.Execute(&buf, e.templateVars)
require.NoError(e.t, err)

View File

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

View File

@ -29,7 +29,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cpu"
}`),
Check: resource.ComposeTestCheckFunc(
@ -45,7 +44,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cpu"
cpu = {
cores = 2
@ -69,7 +67,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-cpu"
cpu = {
# affinity = "0-1" only root can set affinity
@ -135,7 +132,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-cpu"
cpu = {
cores = 2
@ -145,7 +141,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
}
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "test-cpu"
clone = {
id = proxmox_virtual_environment_vm2.template_vm.id
@ -163,7 +158,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-cpu"
cpu = {
cores = 2
@ -173,7 +167,6 @@ func TestAccResourceVM2CPU(t *testing.T) {
}
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "test-cpu"
clone = {
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
import (
@ -7,6 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
"github.com/bpg/terraform-provider-proxmox/proxmox"
)
@ -45,37 +52,36 @@ func (d *Datasource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.DataSource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *proxmox.Client, got: %T", req.ProviderData),
"Unexpected DataSource Configure Type",
fmt.Sprintf("Expected config.DataSource, got: %T", req.ProviderData),
)
return
}
d.client = client
d.client = cfg.Client
}
//nolint:dupl
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() {
return
}
timeout, diags := config.Timeouts.Read(ctx, defaultReadTimeout)
timeout, diags := model.Timeouts.Read(ctx, defaultReadTimeout)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
exists := read(ctx, d.client, &config, &resp.Diagnostics)
exists := read(ctx, d.client, &model, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
@ -83,12 +89,12 @@ func (d *Datasource) Read(ctx context.Context, req datasource.ReadRequest, resp
if !exists {
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)
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-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/cpu"
"github.com/bpg/terraform-provider-proxmox/fwprovider/vm/vga"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"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"
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
@ -48,7 +50,8 @@ var (
// Resource implements the resource.Resource interface for managing VMs.
type Resource struct {
client proxmox.Client
client proxmox.Client
idGenerator cluster.IDGenerator
}
// NewResource creates a new resource for managing VMs.
@ -75,8 +78,7 @@ func (r *Resource) Configure(
return
}
client, ok := req.ProviderData.(proxmox.Client)
cfg, ok := req.ProviderData.(config.Resource)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
@ -86,7 +88,8 @@ func (r *Resource) Configure(
return
}
r.client = client
r.client = cfg.Client
r.idGenerator = cfg.IDGenerator
}
// Create creates a new VM.
@ -106,13 +109,13 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp
defer cancel()
if plan.ID.ValueInt64() == 0 {
id, err := r.client.Cluster().GetVMID(ctx)
id, err := r.idGenerator.NextID(ctx)
if err != nil {
resp.Diagnostics.AddError("Failed to get VM ID", err.Error())
resp.Diagnostics.AddError("Failed to generate VM ID", err.Error())
return
}
plan.ID = types.Int64Value(int64(*id))
plan.ID = types.Int64Value(int64(id))
}
if resp.Diagnostics.HasError() {

View File

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

View File

@ -29,7 +29,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-vga"
}`),
Check: test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
@ -41,7 +40,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-vga"
vga = {
type = "std"
@ -62,7 +60,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID}}
name = "test-vga"
vga = {
type = "std"
@ -107,7 +104,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-vga"
vga = {
type = "qxl"
@ -116,7 +112,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
}
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID2}}
name = "test-vga"
clone = {
id = proxmox_virtual_environment_vm2.template_vm.id
@ -133,7 +128,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "template_vm" {
node_name = "{{.NodeName}}"
id = {{.RandomVMID1}}
name = "template-vga"
vga = {
type = "qxl"
@ -143,7 +137,6 @@ func TestAccResourceVM2VGA(t *testing.T) {
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
name = "test-cpu"
id = {{.RandomVMID2}}
clone = {
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/davecgh/go-spew v1.1.1 // 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/hashicorp/errwrap v1.1.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/oklog/run v1.1.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/v5 v5.4.1 // 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-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/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/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
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/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.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/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=

View File

@ -11,27 +11,13 @@ import (
"errors"
"fmt"
"net/http"
"sync"
"github.com/hashicorp/terraform-plugin-log/tflog"
"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.
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.
func (c *Client) GetNextID(ctx context.Context, vmID *int) (*int, error) {
reqBody := &NextIDRequestBody{
@ -52,52 +38,6 @@ func (c *Client) GetNextID(ctx context.Context, vmID *int) (*int, error) {
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.
func (c *Client) GetClusterResources(ctx context.Context, resourceType string) ([]*ResourcesListResponseData, error) {
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/api"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
"github.com/bpg/terraform-provider-proxmox/proxmox/ssh"
)
@ -20,6 +21,7 @@ type ProviderConfiguration struct {
apiClient api.Client
sshClient ssh.Client
tmpDirOverride string
idGenerator cluster.IDGenerator
}
// NewProviderConfiguration creates a new provider configuration.
@ -27,12 +29,22 @@ func NewProviderConfiguration(
apiClient api.Client,
sshClient ssh.Client,
tmpDirOverride string,
) ProviderConfiguration {
return ProviderConfiguration{
idCfg cluster.IDGeneratorConfig,
) (ProviderConfiguration, error) {
cfg := ProviderConfiguration{
apiClient: apiClient,
sshClient: sshClient,
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.
@ -60,3 +72,8 @@ func (c *ProviderConfiguration) TempDir() string {
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/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/ssh"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
@ -210,7 +211,24 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
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
}

View File

@ -24,6 +24,9 @@ const (
mkProviderPassword = "password"
mkProviderUsername = "username"
mkProviderTmpDir = "tmp_dir"
mkProviderRandomVMIDs = "random_vm_ids"
mkProviderRandomVMIDStart = "random_vm_id_start"
mkProviderRandomVMIDEnd = "random_vm_id_end"
mkProviderSSH = "ssh"
mkProviderSSHUsername = "username"
mkProviderSSHPassword = "password"
@ -240,5 +243,22 @@ func createSchema() map[string]*schema.Schema {
Description: "The alternative temporary directory.",
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)
if !hasVMID {
vmIDNew, err := client.Cluster().GetVMID(ctx)
vmIDNew, err := config.GetIDGenerator().NextID(ctx)
if err != nil {
return diag.FromErr(err)
}
vmID = *vmIDNew
vmID = vmIDNew
err = d.Set(mkVMID, vmID)
if err != nil {
@ -1696,12 +1696,12 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf
vmID := vmIDUntyped.(int)
if !hasVMID {
vmIDNew, err := client.Cluster().GetVMID(ctx)
vmIDNew, err := config.GetIDGenerator().NextID(ctx)
if err != nil {
return diag.FromErr(err)
}
vmID = *vmIDNew
vmID = vmIDNew
err = d.Set(mkVMID, vmID)
if err != nil {

View File

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