From 72f7cb81a8bbe0fcd9aa739073db6e552820a7de Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Thu, 3 Oct 2024 20:18:37 -0400 Subject: [PATCH] feat(provider): reliable sequential and random `vm_id` generation (#1557) Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- .vscode/extensions.json | 1 + .vscode/settings.json | 29 +++- docs/index.md | 16 ++- fwprovider/access/resource_acl.go | 9 +- fwprovider/access/resource_user_token.go | 10 +- fwprovider/acme/datasource_acme_account.go | 27 ++-- fwprovider/acme/datasource_acme_accounts.go | 11 +- fwprovider/acme/datasource_acme_plugin.go | 11 +- fwprovider/acme/datasource_acme_plugins.go | 11 +- fwprovider/acme/resource_acme_account.go | 17 +-- fwprovider/acme/resource_acme_dns_plugin.go | 19 +-- fwprovider/config/datasource.go | 14 ++ fwprovider/config/doc.go | 8 ++ fwprovider/config/resource.go | 19 +++ fwprovider/datasource_version.go | 9 +- fwprovider/ha/datasource_hagroup.go | 11 +- fwprovider/ha/datasource_hagroups.go | 11 +- fwprovider/ha/datasource_haresource.go | 17 +-- fwprovider/ha/datasource_haresources.go | 17 +-- fwprovider/ha/resource_hagroup.go | 17 +-- fwprovider/ha/resource_haresource.go | 16 ++- fwprovider/hardwaremapping/datasource.go | 10 +- fwprovider/hardwaremapping/datasource_pci.go | 10 +- fwprovider/hardwaremapping/datasource_usb.go | 10 +- fwprovider/hardwaremapping/resource_pci.go | 12 +- fwprovider/hardwaremapping/resource_usb.go | 13 +- fwprovider/network/resource_linux_bridge.go | 8 +- fwprovider/network/resource_linux_vlan.go | 8 +- fwprovider/nodes/apt/datasource_repo.go | 10 +- .../nodes/apt/datasource_standard_repo.go | 10 +- fwprovider/nodes/apt/resource_repo.go | 10 +- .../nodes/apt/resource_standard_repo.go | 10 +- fwprovider/provider.go | 121 ++++++++++------ fwprovider/resource_download_file.go | 5 +- fwprovider/resource_options.go | 5 +- fwprovider/test/resource_container_test.go | 2 - fwprovider/test/test_environment.go | 6 +- fwprovider/vm/cdrom/resource_test.go | 7 - fwprovider/vm/cpu/resource_test.go | 7 - fwprovider/vm/datasource.go | 28 ++-- fwprovider/vm/resource.go | 17 ++- fwprovider/vm/resource_test.go | 16 +-- fwprovider/vm/vga/resource_test.go | 7 - go.mod | 2 + go.sum | 4 + proxmox/cluster/cluster.go | 60 -------- proxmox/cluster/id_generator.go | 136 ++++++++++++++++++ proxmoxtf/config.go | 21 ++- proxmoxtf/provider/provider.go | 20 ++- proxmoxtf/provider/schema.go | 20 +++ proxmoxtf/resource/container/container.go | 8 +- proxmoxtf/resource/vm/vm.go | 8 +- 52 files changed, 571 insertions(+), 340 deletions(-) create mode 100644 fwprovider/config/datasource.go create mode 100644 fwprovider/config/doc.go create mode 100644 fwprovider/config/resource.go create mode 100644 proxmox/cluster/id_generator.go diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2f4ff167..40ecdfd7 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,5 +7,6 @@ "hashicorp.terraform", "joshbolduc.commitlint", "PKief.material-icon-theme", + "psioniq.psi-header" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index adf2923f..706d1676 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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 } diff --git a/docs/index.md b/docs/index.md index 5ae410d3..d1f564c7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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/` 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`. diff --git a/fwprovider/access/resource_acl.go b/fwprovider/access/resource_acl.go index 59960400..0997a3a1 100644 --- a/fwprovider/access/resource_acl.go +++ b/fwprovider/access/resource_acl.go @@ -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) { diff --git a/fwprovider/access/resource_user_token.go b/fwprovider/access/resource_user_token.go index bf487c3d..68fa20db 100644 --- a/fwprovider/access/resource_user_token.go +++ b/fwprovider/access/resource_user_token.go @@ -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( diff --git a/fwprovider/acme/datasource_acme_account.go b/fwprovider/acme/datasource_acme_account.go index 1eed0774..44d0baff 100644 --- a/fwprovider/acme/datasource_acme_account.go +++ b/fwprovider/acme/datasource_acme_account.go @@ -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)...) } diff --git a/fwprovider/acme/datasource_acme_accounts.go b/fwprovider/acme/datasource_acme_accounts.go index dd7f6076..cc25cf33 100644 --- a/fwprovider/acme/datasource_acme_accounts.go +++ b/fwprovider/acme/datasource_acme_accounts.go @@ -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. diff --git a/fwprovider/acme/datasource_acme_plugin.go b/fwprovider/acme/datasource_acme_plugin.go index ad3d850b..d9299678 100644 --- a/fwprovider/acme/datasource_acme_plugin.go +++ b/fwprovider/acme/datasource_acme_plugin.go @@ -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. diff --git a/fwprovider/acme/datasource_acme_plugins.go b/fwprovider/acme/datasource_acme_plugins.go index 0e16fcee..c799d701 100644 --- a/fwprovider/acme/datasource_acme_plugins.go +++ b/fwprovider/acme/datasource_acme_plugins.go @@ -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. diff --git a/fwprovider/acme/resource_acme_account.go b/fwprovider/acme/resource_acme_account.go index d4446556..4b9d3cf3 100644 --- a/fwprovider/acme/resource_acme_account.go +++ b/fwprovider/acme/resource_acme_account.go @@ -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. diff --git a/fwprovider/acme/resource_acme_dns_plugin.go b/fwprovider/acme/resource_acme_dns_plugin.go index 4dd4cbf8..781cee6d 100644 --- a/fwprovider/acme/resource_acme_dns_plugin.go +++ b/fwprovider/acme/resource_acme_dns_plugin.go @@ -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. diff --git a/fwprovider/config/datasource.go b/fwprovider/config/datasource.go new file mode 100644 index 00000000..8590a4f6 --- /dev/null +++ b/fwprovider/config/datasource.go @@ -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 +} diff --git a/fwprovider/config/doc.go b/fwprovider/config/doc.go new file mode 100644 index 00000000..f89a68b1 --- /dev/null +++ b/fwprovider/config/doc.go @@ -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 diff --git a/fwprovider/config/resource.go b/fwprovider/config/resource.go new file mode 100644 index 00000000..892251ed --- /dev/null +++ b/fwprovider/config/resource.go @@ -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 +} diff --git a/fwprovider/datasource_version.go b/fwprovider/datasource_version.go index bc6bd190..eb7d3283 100644 --- a/fwprovider/datasource_version.go +++ b/fwprovider/datasource_version.go @@ -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. diff --git a/fwprovider/ha/datasource_hagroup.go b/fwprovider/ha/datasource_hagroup.go index c803bf15..54ed6455 100644 --- a/fwprovider/ha/datasource_hagroup.go +++ b/fwprovider/ha/datasource_hagroup.go @@ -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. diff --git a/fwprovider/ha/datasource_hagroups.go b/fwprovider/ha/datasource_hagroups.go index 7814ac3b..704ee2d7 100644 --- a/fwprovider/ha/datasource_hagroups.go +++ b/fwprovider/ha/datasource_hagroups.go @@ -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. diff --git a/fwprovider/ha/datasource_haresource.go b/fwprovider/ha/datasource_haresource.go index 2625302d..83bdf617 100644 --- a/fwprovider/ha/datasource_haresource.go +++ b/fwprovider/ha/datasource_haresource.go @@ -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. diff --git a/fwprovider/ha/datasource_haresources.go b/fwprovider/ha/datasource_haresources.go index 8e64cb0c..ebf5a357 100644 --- a/fwprovider/ha/datasource_haresources.go +++ b/fwprovider/ha/datasource_haresources.go @@ -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. diff --git a/fwprovider/ha/resource_hagroup.go b/fwprovider/ha/resource_hagroup.go index f0c41d1f..0383aaf0 100644 --- a/fwprovider/ha/resource_hagroup.go +++ b/fwprovider/ha/resource_hagroup.go @@ -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. diff --git a/fwprovider/ha/resource_haresource.go b/fwprovider/ha/resource_haresource.go index f6a7aea9..c97f2f11 100644 --- a/fwprovider/ha/resource_haresource.go +++ b/fwprovider/ha/resource_haresource.go @@ -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. diff --git a/fwprovider/hardwaremapping/datasource.go b/fwprovider/hardwaremapping/datasource.go index 06bfabe3..2c7ec5af 100644 --- a/fwprovider/hardwaremapping/datasource.go +++ b/fwprovider/hardwaremapping/datasource.go @@ -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. diff --git a/fwprovider/hardwaremapping/datasource_pci.go b/fwprovider/hardwaremapping/datasource_pci.go index bae1ae73..900cf40c 100644 --- a/fwprovider/hardwaremapping/datasource_pci.go +++ b/fwprovider/hardwaremapping/datasource_pci.go @@ -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. diff --git a/fwprovider/hardwaremapping/datasource_usb.go b/fwprovider/hardwaremapping/datasource_usb.go index b3cf7719..f499620c 100644 --- a/fwprovider/hardwaremapping/datasource_usb.go +++ b/fwprovider/hardwaremapping/datasource_usb.go @@ -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. diff --git a/fwprovider/hardwaremapping/resource_pci.go b/fwprovider/hardwaremapping/resource_pci.go index 32b64e75..c90197b0 100644 --- a/fwprovider/hardwaremapping/resource_pci.go +++ b/fwprovider/hardwaremapping/resource_pci.go @@ -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. diff --git a/fwprovider/hardwaremapping/resource_usb.go b/fwprovider/hardwaremapping/resource_usb.go index eec1876e..5f27481c 100644 --- a/fwprovider/hardwaremapping/resource_usb.go +++ b/fwprovider/hardwaremapping/resource_usb.go @@ -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. diff --git a/fwprovider/network/resource_linux_bridge.go b/fwprovider/network/resource_linux_bridge.go index 159a7318..f60029e8 100644 --- a/fwprovider/network/resource_linux_bridge.go +++ b/fwprovider/network/resource_linux_bridge.go @@ -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 diff --git a/fwprovider/network/resource_linux_vlan.go b/fwprovider/network/resource_linux_vlan.go index 462632d0..24ff7f30 100644 --- a/fwprovider/network/resource_linux_vlan.go +++ b/fwprovider/network/resource_linux_vlan.go @@ -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 diff --git a/fwprovider/nodes/apt/datasource_repo.go b/fwprovider/nodes/apt/datasource_repo.go index 696f086d..de91f02a 100644 --- a/fwprovider/nodes/apt/datasource_repo.go +++ b/fwprovider/nodes/apt/datasource_repo.go @@ -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. diff --git a/fwprovider/nodes/apt/datasource_standard_repo.go b/fwprovider/nodes/apt/datasource_standard_repo.go index d987cda4..cfa1fe23 100644 --- a/fwprovider/nodes/apt/datasource_standard_repo.go +++ b/fwprovider/nodes/apt/datasource_standard_repo.go @@ -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. diff --git a/fwprovider/nodes/apt/resource_repo.go b/fwprovider/nodes/apt/resource_repo.go index 8d64f7a9..ea202c7d 100644 --- a/fwprovider/nodes/apt/resource_repo.go +++ b/fwprovider/nodes/apt/resource_repo.go @@ -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 diff --git a/fwprovider/nodes/apt/resource_standard_repo.go b/fwprovider/nodes/apt/resource_standard_repo.go index 68f9bf12..93a60ea2 100644 --- a/fwprovider/nodes/apt/resource_standard_repo.go +++ b/fwprovider/nodes/apt/resource_standard_repo.go @@ -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. diff --git a/fwprovider/provider.go b/fwprovider/provider.go index 8397208e..ed7e2679 100644 --- a/fwprovider/provider.go +++ b/fwprovider/provider.go @@ -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 { diff --git a/fwprovider/resource_download_file.go b/fwprovider/resource_download_file.go index 872acbd0..9ed8214a 100644 --- a/fwprovider/resource_download_file.go +++ b/fwprovider/resource_download_file.go @@ -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( diff --git a/fwprovider/resource_options.go b/fwprovider/resource_options.go index 213c2ddf..877bb1cd 100644 --- a/fwprovider/resource_options.go +++ b/fwprovider/resource_options.go @@ -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. diff --git a/fwprovider/test/resource_container_test.go b/fwprovider/test/resource_container_test.go index 560f24bb..9415262c 100644 --- a/fwprovider/test/resource_container_test.go +++ b/fwprovider/test/resource_container_test.go @@ -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" diff --git a/fwprovider/test/test_environment.go b/fwprovider/test/test_environment.go index 30aca243..c8d325f5 100644 --- a/fwprovider/test/test_environment.go +++ b/fwprovider/test/test_environment.go @@ -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) diff --git a/fwprovider/vm/cdrom/resource_test.go b/fwprovider/vm/cdrom/resource_test.go index 12b91d8b..59d4240a 100644 --- a/fwprovider/vm/cdrom/resource_test.go +++ b/fwprovider/vm/cdrom/resource_test.go @@ -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 diff --git a/fwprovider/vm/cpu/resource_test.go b/fwprovider/vm/cpu/resource_test.go index eaa0de42..87904b64 100644 --- a/fwprovider/vm/cpu/resource_test.go +++ b/fwprovider/vm/cpu/resource_test.go @@ -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 diff --git a/fwprovider/vm/datasource.go b/fwprovider/vm/datasource.go index 364aaae8..1841c618 100644 --- a/fwprovider/vm/datasource.go +++ b/fwprovider/vm/datasource.go @@ -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)...) } diff --git a/fwprovider/vm/resource.go b/fwprovider/vm/resource.go index b921e592..e2634dbc 100644 --- a/fwprovider/vm/resource.go +++ b/fwprovider/vm/resource.go @@ -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() { diff --git a/fwprovider/vm/resource_test.go b/fwprovider/vm/resource_test.go index 7ae28b47..ed6d8243 100644 --- a/fwprovider/vm/resource_test.go +++ b/fwprovider/vm/resource_test.go @@ -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 } diff --git a/fwprovider/vm/vga/resource_test.go b/fwprovider/vm/vga/resource_test.go index 831e913d..b1bcba75 100644 --- a/fwprovider/vm/vga/resource_test.go +++ b/fwprovider/vm/vga/resource_test.go @@ -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 } diff --git a/go.mod b/go.mod index d2ea8e91..fd2e379d 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index bad8cde5..8460dab3 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/proxmox/cluster/cluster.go b/proxmox/cluster/cluster.go index 13b69db7..14e63b70 100644 --- a/proxmox/cluster/cluster.go +++ b/proxmox/cluster/cluster.go @@ -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{ diff --git a/proxmox/cluster/id_generator.go b/proxmox/cluster/id_generator.go new file mode 100644 index 00000000..12d09400 --- /dev/null +++ b/proxmox/cluster/id_generator.go @@ -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 +} diff --git a/proxmoxtf/config.go b/proxmoxtf/config.go index b7d3c575..3829dd52 100644 --- a/proxmoxtf/config.go +++ b/proxmoxtf/config.go @@ -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 +} diff --git a/proxmoxtf/provider/provider.go b/proxmoxtf/provider/provider.go index a7d8518c..18e1a5c2 100644 --- a/proxmoxtf/provider/provider.go +++ b/proxmoxtf/provider/provider.go @@ -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 } diff --git a/proxmoxtf/provider/schema.go b/proxmoxtf/provider/schema.go index e23ae28c..1522aea1 100644 --- a/proxmoxtf/provider/schema.go +++ b/proxmoxtf/provider/schema.go @@ -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), + }, } } diff --git a/proxmoxtf/resource/container/container.go b/proxmoxtf/resource/container/container.go index 6d1404ce..c92e9a97 100644 --- a/proxmoxtf/resource/container/container.go +++ b/proxmoxtf/resource/container/container.go @@ -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 { diff --git a/proxmoxtf/resource/vm/vm.go b/proxmoxtf/resource/vm/vm.go index ef9f1783..8dbf1131 100644 --- a/proxmoxtf/resource/vm/vm.go +++ b/proxmoxtf/resource/vm/vm.go @@ -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 {