0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-02 19:43:00 +00:00

feat: add import support for a lot of resources (#390)

* add import support for a lot of resources

* fix lints

* set user_id after importing

(cherry picked from commit c3d09ed00f6e1d7b0bb3ab01a1fc5c81510aa2e1)

* add tests, fix empty ID

* add import docs

* fix lint
This commit is contained in:
Leah 2023-07-05 03:37:14 +02:00 committed by GitHub
parent feac6b0128
commit 4147ff6a29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 322 additions and 24 deletions

View File

@ -43,3 +43,15 @@ resource "proxmox_virtual_environment_cluster_firewall" "example" {
## Attribute Reference
There are no additional attributes available for this resource.
## Important Notes
Be careful not to use this resource multiple times for the same node.
## Import
Instances can be imported without an ID, but you still need to pass one, e.g.,
```bash
$ terraform import proxmox_virtual_environment_cluster_firewall.example example
```

View File

@ -86,3 +86,11 @@ resource "proxmox_virtual_environment_cluster_firewall_security_group" "webserve
- `pos` - Position of the rule in the list.
There are no attribute references available for this resource.
## Import
Instances can be imported using the `name`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_cluster_firewall_security_group.webserver webserver
```

View File

@ -181,3 +181,11 @@ output "ubuntu_container_public_key" {
## Attribute Reference
There are no additional attributes available for this resource.
## Import
Instances can be imported using the `node_name` and the `vm_id`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_container.ubuntu_container first-node/1234
```

View File

@ -42,3 +42,11 @@ There are no additional attributes available for this resource.
## Important Notes
Be careful not to use this resource multiple times for the same node.
## Import
Instances can be imported using the `node_name`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_dns.first_node first-node
```

View File

@ -70,7 +70,7 @@ EOF
- `file_name` - (Optional) The file name to use instead of the source file
name.
- `insecure` - (Optional) Whether to skip the TLS verification step for
HTTPSsources (defaults to `false`).
HTTPS sources (defaults to `false`).
- `path` - (Required) A path to a local file or a URL.
- `source_raw` - (Optional) The raw source (conflicts with `source_file`).
- `data` - (Required) The raw data.
@ -93,3 +93,11 @@ file locally before uploading it.
You must ensure that you have at least `Size-in-MB * 2 + 1` MB of storage space
available (twice the size plus overhead because a multipart payload needs to be
created as another temporary file).
## Import
Instances can be imported using the `node_name`, `datastore_id`, `content_type` and the `file_name`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_file.cloud_config pve/local/snippets/example.cloud-config.yaml
```

View File

@ -32,3 +32,11 @@ resource "proxmox_virtual_environment_group" "operations_team" {
## Attribute Reference
- `members` - The group members as a list of `username@realm` entries
## Import
Instances can be imported using the `group_id`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_group.operations_team operations-team
```

View File

@ -42,3 +42,15 @@ resource "proxmox_virtual_environment_hosts" "first_node_host_entries" {
- `entries` - The host entries (conversion of `addresses` and `hostnames` into
objects).
- `hostnames` - The hostnames associated with each of the IP addresses.
## Important Notes
Be careful not to use this resource multiple times for the same node.
## Import
Instances can be imported using the `node_name`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_hosts.first_node_host_entries first-node
```

View File

@ -33,3 +33,11 @@ resource "proxmox_virtual_environment_pool" "operations_pool" {
- `node_name` - The node name.
- `type` - The member type.
- `vm_id` - The virtual machine identifier.
## Import
Instances can be imported using the `pool_id`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_pool.operations_pool operations-pool
```

View File

@ -31,3 +31,11 @@ resource "proxmox_virtual_environment_role" "operations_monitoring" {
## Attribute Reference
There are no additional attributes available for this resource.
## Import
Instances can be imported using the `role_id`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_role.operations_monitoring operations-monitoring
```

View File

@ -29,3 +29,11 @@ resource "proxmox_virtual_environment_time" "first_node_time" {
- `local_time` - The node's local time.
- `utc_time` - The node's local time formatted as UTC.
## Import
Instances can be imported using the `node_name`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_dns.first_node first-node
```

View File

@ -55,3 +55,11 @@ resource "proxmox_virtual_environment_role" "operations_monitoring" {
## Attribute Reference
There are no additional attributes available for this resource.
## Import
Instances can be imported using the `user_id`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_user.operations_automation operations-automation@pve
```

View File

@ -462,3 +462,11 @@ to force the migration step to migrate all disks to a specific datastore on the
target node. If you need certain disks to be on specific datastores, set
the `datastore_id` argument of the disks in the `disks` block to move the disks
to the correct datastore after the cloning and migrating succeeded.
## Import
Instances can be imported using the `node_name` and the `vm_id`, e.g.,
```bash
$ terraform import proxmox_virtual_environment_vm.ubuntu_vm first-node/4321
```

View File

@ -107,6 +107,9 @@ func Firewall() *schema.Resource {
ReadContext: selectFirewallAPI(firewallRead),
UpdateContext: selectFirewallAPI(firewallUpdate),
DeleteContext: selectFirewallAPI(firewallDelete),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

View File

@ -50,6 +50,9 @@ func SecurityGroup() *schema.Resource {
ReadContext: selectFirewallAPI(SecurityGroupRead),
UpdateContext: selectFirewallAPI(SecurityGroupUpdate),
DeleteContext: selectFirewallAPI(SecurityGroupDelete),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

View File

@ -643,6 +643,22 @@ func Container() *schema.Resource {
ReadContext: containerRead,
UpdateContext: containerUpdate,
DeleteContext: containerDelete,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) {
node, id, err := parseImportIDWithNodeName(d.Id())
if err != nil {
return nil, err
}
d.SetId(id)
err = d.Set(mkResourceVirtualEnvironmentContainerNodeName, node)
if err != nil {
return nil, fmt.Errorf("failed setting state during import: %w", err)
}
return []*schema.ResourceData{d}, nil
},
},
}
}

View File

@ -54,6 +54,20 @@ func DNS() *schema.Resource {
ReadContext: dnsRead,
UpdateContext: dnsUpdate,
DeleteContext: dnsDelete,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) {
nodeName := d.Id()
err := d.Set(mkResourceVirtualEnvironmentDNSNodeName, nodeName)
if err != nil {
return nil, fmt.Errorf("failed setting state during import: %w", err)
}
d.SetId(fmt.Sprintf("%s_dns", nodeName))
return []*schema.ResourceData{d}, nil
},
},
}
}

View File

@ -193,9 +193,41 @@ func File() *schema.Resource {
CreateContext: fileCreate,
ReadContext: fileRead,
DeleteContext: fileDelete,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) {
node, datastore, volumeID, err := fileParseImportID(d.Id())
if err != nil {
return nil, err
}
d.SetId(volumeID)
err = d.Set(mkResourceVirtualEnvironmentFileNodeName, node)
if err != nil {
return nil, fmt.Errorf("failed setting state during import: %w", err)
}
err = d.Set(mkResourceVirtualEnvironmentFileDatastoreID, datastore)
if err != nil {
return nil, fmt.Errorf("failed setting state during import: %w", err)
}
return []*schema.ResourceData{d}, nil
},
},
}
}
func fileParseImportID(id string) (string, string, string, error) {
parts := strings.SplitN(id, "/", 4)
if len(parts) != 4 || parts[0] == "" || parts[1] == "" || parts[2] == "" || parts[3] == "" {
return "", "", "", fmt.Errorf("unexpected format of ID (%s), expected node/datastore_id/content_type/file_name", id)
}
return parts[0], parts[1], strings.Join(parts[2:], "/"), nil
}
func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics

View File

@ -10,6 +10,7 @@ import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/stretchr/testify/require"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
)
@ -98,3 +99,41 @@ func TestFileSchema(t *testing.T) {
mkResourceVirtualEnvironmentFileSourceRawResize: schema.TypeInt,
})
}
func Test_fileParseImportID(t *testing.T) {
t.Parallel()
tests := []struct {
name string
value string
valid bool
expectedNodeName string
expectedDatastoreID string
expectedVolumeID string
}{
{"empty", "", false, "", "", ""},
{"missing slash", "invalid", false, "", "", ""},
{"missing parts", "invalid/invalid/invalid", false, "", "", ""},
{"valid", "node/datastore_id/content_type/file_name", true, "node", "datastore_id", "content_type/file_name"},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require := require.New(t)
nodeName, datastoreID, volumeID, err := fileParseImportID(tt.value)
if !tt.valid {
require.Error(err)
return
}
require.Nil(err)
require.Equal(tt.expectedNodeName, nodeName)
require.Equal(tt.expectedDatastoreID, datastoreID)
require.Equal(tt.expectedVolumeID, volumeID)
})
}
}

View File

@ -85,6 +85,9 @@ func Group() *schema.Resource {
ReadContext: groupRead,
UpdateContext: groupUpdate,
DeleteContext: groupDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

View File

@ -107,6 +107,20 @@ func Hosts() *schema.Resource {
ReadContext: hostsRead,
UpdateContext: hostsUpdate,
DeleteContext: hostsDelete,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) {
nodeName := d.Id()
err := d.Set(mkResourceVirtualEnvironmentHostsNodeName, nodeName)
if err != nil {
return nil, fmt.Errorf("failed setting state during import: %w", err)
}
d.SetId(fmt.Sprintf("%s_hosts", nodeName))
return []*schema.ResourceData{d}, nil
},
},
}
}

View File

@ -85,6 +85,9 @@ func Pool() *schema.Resource {
ReadContext: poolRead,
UpdateContext: poolUpdate,
DeleteContext: poolDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

View File

@ -44,6 +44,9 @@ func Role() *schema.Resource {
ReadContext: roleRead,
UpdateContext: roleUpdate,
DeleteContext: roleDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

View File

@ -54,6 +54,20 @@ func Time() *schema.Resource {
ReadContext: timeRead,
UpdateContext: timeUpdate,
DeleteContext: timeDelete,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) {
nodeName := d.Id()
err := d.Set(mkResourceVirtualEnvironmentTimeNodeName, nodeName)
if err != nil {
return nil, fmt.Errorf("failed setting state during import: %w", err)
}
d.SetId(fmt.Sprintf("%s_time", nodeName))
return []*schema.ResourceData{d}, nil
},
},
}
}

View File

@ -144,6 +144,9 @@ func User() *schema.Resource {
ReadContext: userRead,
UpdateContext: userUpdate,
DeleteContext: userDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}
@ -248,6 +251,11 @@ func userRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
return diag.FromErr(err)
}
var diags diag.Diagnostics
err = d.Set(mkResourceVirtualEnvironmentUserUserID, userID)
diags = append(diags, diag.FromErr(err)...)
var aclParsed []interface{}
for _, v := range acl {
@ -268,8 +276,6 @@ func userRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
}
}
var diags diag.Diagnostics
err = d.Set(mkResourceVirtualEnvironmentUserACL, aclParsed)
diags = append(diags, diag.FromErr(err)...)

View File

@ -7,7 +7,6 @@
package resource
import (
"errors"
"fmt"
"reflect"
"regexp"
@ -16,9 +15,6 @@ import (
"time"
"unicode"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
@ -561,22 +557,12 @@ func getCloudInitTypeValidator() schema.SchemaValidateDiagFunc {
}, false))
}
type ErrorDiags diag.Diagnostics
func parseImportIDWithNodeName(id string) (string, string, error) {
nodeName, id, found := strings.Cut(id, "/")
func (diags ErrorDiags) Errors() []error {
var es []error
for i := range diags {
if diags[i].Severity == diag.Error {
s := fmt.Sprintf("Error: %s", diags[i].Summary)
if diags[i].Detail != "" {
s = fmt.Sprintf("%s: %s", s, diags[i].Detail)
}
es = append(es, errors.New(s))
}
if !found {
return "", "", fmt.Errorf("unexpected format of ID (%s), expected node/id", id)
}
return es
}
func (diags ErrorDiags) Error() string {
return multierror.ListFormatFunc(diags.Errors())
return nodeName, id, nil
}

View File

@ -8,6 +8,8 @@ package resource
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_getCPUTypeValidator(t *testing.T) {
@ -29,11 +31,51 @@ func Test_getCPUTypeValidator(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require := require.New(t)
f := getCPUTypeValidator()
res := f(tt.value, nil)
if !res.HasError() != tt.valid {
t.Errorf("validate: '%s', want %v got %v", tt.value, tt.valid, res)
if tt.valid {
require.Empty(res, "validate: '%s'", tt.value)
} else {
require.NotEmpty(res, "validate: '%s'", tt.value)
}
})
}
}
func Test_parseImportIDWIthNodeName(t *testing.T) {
t.Parallel()
tests := []struct {
name string
value string
valid bool
expectedNodeName string
expectedID string
}{
{"empty", "", false, "", ""},
{"missing slash", "invalid", false, "", ""},
{"valid", "host/id", true, "host", "id"},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require := require.New(t)
nodeName, id, err := parseImportIDWithNodeName(tt.value)
if !tt.valid {
require.Error(err)
return
}
require.Nil(err)
require.Equal(tt.expectedNodeName, nodeName)
require.Equal(tt.expectedID, id)
})
}
}

View File

@ -1344,6 +1344,22 @@ func VM() *schema.Resource {
},
),
),
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) {
node, id, err := parseImportIDWithNodeName(d.Id())
if err != nil {
return nil, err
}
d.SetId(id)
err = d.Set(mkResourceVirtualEnvironmentVMNodeName, node)
if err != nil {
return nil, fmt.Errorf("failed setting state during import: %w", err)
}
return []*schema.ResourceData{d}, nil
},
},
}
}