mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-29 18:21:10 +00:00
feat(access): add ACL resource (#1166)
* feat: add ACL resource Signed-off-by: hrmny <8845940+ForsakenHarmony@users.noreply.github.com> * chore: move code under /access, cleanup acc tests Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --------- Signed-off-by: hrmny <8845940+ForsakenHarmony@users.noreply.github.com> Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
8220271eee
commit
afcbb415a3
2
Makefile
2
Makefile
@ -94,7 +94,7 @@ test:
|
||||
|
||||
.PHONY: testacc
|
||||
testacc:
|
||||
@# explicitly add TF_ACC=1 to trigger the acceptance tests, `testacc.env` might be missing or incomplete
|
||||
@# explicitly add TF_ACC=1 to trigger the acceptance tests, `testacc.env` might be missing or incomplete
|
||||
@TF_ACC=1 env $$(cat testacc.env | xargs) go test --timeout=30m -count=1 -v github.com/bpg/terraform-provider-proxmox/fwprovider/tests/...
|
||||
|
||||
.PHONY: lint
|
||||
|
72
docs/resources/virtual_environment_acl.md
Normal file
72
docs/resources/virtual_environment_acl.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
layout: page
|
||||
title: proxmox_virtual_environment_acl
|
||||
parent: Resources
|
||||
subcategory: Virtual Environment
|
||||
description: |-
|
||||
Manages ACLs on the Proxmox cluster.
|
||||
ACLs are used to control access to resources in the Proxmox cluster.
|
||||
Each ACL consists of a path, a user, group or token, a role, and a flag to allow propagation of permissions.
|
||||
---
|
||||
|
||||
# Resource: proxmox_virtual_environment_acl
|
||||
|
||||
Manages ACLs on the Proxmox cluster.
|
||||
|
||||
ACLs are used to control access to resources in the Proxmox cluster.
|
||||
Each ACL consists of a path, a user, group or token, a role, and a flag to allow propagation of permissions.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "proxmox_virtual_environment_user" "operations_automation" {
|
||||
comment = "Managed by Terraform"
|
||||
password = "a-strong-password"
|
||||
user_id = "operations-automation@pve"
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_role" "operations_monitoring" {
|
||||
role_id = "operations-monitoring"
|
||||
|
||||
privileges = [
|
||||
"VM.Monitor",
|
||||
]
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_acl" "operations_automation_monitoring" {
|
||||
user_id = proxmox_virtual_environment_user.operations_automation.user_id
|
||||
role_id = proxmox_virtual_environment_role.operations_monitoring.role_id
|
||||
|
||||
path = "/vms/1234"
|
||||
propagate = true
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `path` (String) Access control path
|
||||
- `role_id` (String) The role to apply
|
||||
|
||||
### Optional
|
||||
|
||||
- `group_id` (String) The group the ACL should apply to (mutually exclusive with `token_id` and `user_id`)
|
||||
- `propagate` (Boolean) Allow to propagate (inherit) permissions.
|
||||
- `token_id` (String) The token the ACL should apply to (mutually exclusive with `group_id` and `user_id`)
|
||||
- `user_id` (String) The user the ACL should apply to (mutually exclusive with `group_id` and `token_id`)
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The unique identifier of this resource.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env sh
|
||||
# ACL can be imported using its unique identifier, e.g.: {path}?entity_id={group|user@realm|user@realm!token}?role_id={role}
|
||||
terraform import proxmox_virtual_environment_acl.operations_automation_monitoring /?entity_id=monitor@pve&role_id=operations-monitoring
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
# ACL can be imported using its unique identifier, e.g.: {path}?entity_id={group|user@realm|user@realm!token}?role_id={role}
|
||||
terraform import proxmox_virtual_environment_acl.operations_automation_monitoring /?entity_id=monitor@pve&role_id=operations-monitoring
|
@ -0,0 +1,21 @@
|
||||
resource "proxmox_virtual_environment_user" "operations_automation" {
|
||||
comment = "Managed by Terraform"
|
||||
password = "a-strong-password"
|
||||
user_id = "operations-automation@pve"
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_role" "operations_monitoring" {
|
||||
role_id = "operations-monitoring"
|
||||
|
||||
privileges = [
|
||||
"VM.Monitor",
|
||||
]
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_acl" "operations_automation_monitoring" {
|
||||
user_id = proxmox_virtual_environment_user.operations_automation.user_id
|
||||
role_id = proxmox_virtual_environment_role.operations_monitoring.role_id
|
||||
|
||||
path = "/vms/1234"
|
||||
propagate = true
|
||||
}
|
274
fwprovider/access/resource_acl.go
Normal file
274
fwprovider/access/resource_acl.go
Normal file
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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 access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/structure"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
_ resource.Resource = (*aclResource)(nil)
|
||||
_ resource.ResourceWithConfigure = (*aclResource)(nil)
|
||||
_ resource.ResourceWithImportState = (*aclResource)(nil)
|
||||
_ resource.ResourceWithConfigValidators = (*aclResource)(nil)
|
||||
)
|
||||
|
||||
type aclResource struct {
|
||||
client proxmox.Client
|
||||
}
|
||||
|
||||
// NewACLResource creates a new ACL resource.
|
||||
func NewACLResource() resource.Resource {
|
||||
return &aclResource{}
|
||||
}
|
||||
|
||||
func (r *aclResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Manages ACLs on the Proxmox cluster",
|
||||
MarkdownDescription: "Manages ACLs on the Proxmox cluster.\n\n" +
|
||||
"ACLs are used to control access to resources in the Proxmox cluster.\n" +
|
||||
"Each ACL consists of a path, a user, group or token, a role, and a flag to allow propagation of permissions.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"group_id": schema.StringAttribute{
|
||||
Description: "The group the ACL should apply to (mutually exclusive with `token_id` and `user_id`)",
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"id": structure.IDAttribute(),
|
||||
"path": schema.StringAttribute{
|
||||
Description: "Access control path",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"propagate": schema.BoolAttribute{
|
||||
Description: "Allow to propagate (inherit) permissions.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: booldefault.StaticBool(true),
|
||||
},
|
||||
"role_id": schema.StringAttribute{
|
||||
Description: "The role to apply",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"token_id": schema.StringAttribute{
|
||||
Description: "The token the ACL should apply to (mutually exclusive with `group_id` and `user_id`)",
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"user_id": schema.StringAttribute{
|
||||
Description: "The user the ACL should apply to (mutually exclusive with `group_id` and `token_id`)",
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *aclResource) ConfigValidators(_ context.Context) []resource.ConfigValidator {
|
||||
return []resource.ConfigValidator{
|
||||
resourcevalidator.Conflicting(
|
||||
path.MatchRoot("group_id"),
|
||||
path.MatchRoot("token_id"),
|
||||
path.MatchRoot("user_id"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *aclResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, ok := req.ProviderData.(proxmox.Client)
|
||||
|
||||
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),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
r.client = client
|
||||
}
|
||||
|
||||
func (r *aclResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_acl"
|
||||
}
|
||||
|
||||
func (r *aclResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan aclResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
body := plan.intoUpdateBody()
|
||||
|
||||
err := r.client.Access().UpdateACL(ctx, body)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Unable to create ACL", apiCallFailed+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = plan.generateID()
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Unable to create ACL", "failed to generate ID: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
|
||||
}
|
||||
|
||||
func (r *aclResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state aclResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
acls, err := r.client.Access().GetACL(ctx)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Unable read ACL", apiCallFailed+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, acl := range acls {
|
||||
switch acl.Type {
|
||||
case "group":
|
||||
if acl.UserOrGroupID != state.GroupID.ValueString() {
|
||||
continue
|
||||
}
|
||||
case "token":
|
||||
if acl.UserOrGroupID != state.TokenID.ValueString() {
|
||||
continue
|
||||
}
|
||||
case "user":
|
||||
if acl.UserOrGroupID != state.UserID.ValueString() {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
// ignore unknown values
|
||||
continue
|
||||
}
|
||||
|
||||
if acl.Path != state.Path {
|
||||
continue
|
||||
}
|
||||
|
||||
if acl.RoleID != state.RoleID {
|
||||
continue
|
||||
}
|
||||
|
||||
state.Propagate = ptr.Or(acl.Propagate.PointerBool(), true)
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp.State.RemoveResource(ctx)
|
||||
}
|
||||
|
||||
func (r *aclResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var (
|
||||
state aclResourceModel
|
||||
plan aclResourceModel
|
||||
)
|
||||
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
stateBody := state.intoUpdateBody()
|
||||
stateBody.Delete = proxmoxtypes.CustomBool(true).Pointer()
|
||||
|
||||
err := r.client.Access().UpdateACL(ctx, stateBody)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Unable to delete old ACL", apiCallFailed+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
planBody := plan.intoUpdateBody()
|
||||
|
||||
err = r.client.Access().UpdateACL(ctx, planBody)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Unable to create ACL", apiCallFailed+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
|
||||
}
|
||||
|
||||
func (r *aclResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state aclResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
stateBody := state.intoUpdateBody()
|
||||
stateBody.Delete = proxmoxtypes.CustomBool(true).Pointer()
|
||||
|
||||
err := r.client.Access().UpdateACL(ctx, stateBody)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Unable to delete old ACL", apiCallFailed+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (r *aclResource) ImportState(
|
||||
ctx context.Context,
|
||||
req resource.ImportStateRequest,
|
||||
resp *resource.ImportStateResponse,
|
||||
) {
|
||||
model, err := parseACLResourceModelFromID(req.ID)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Unable to import ACL", "failed to parse ID: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||
}
|
||||
|
||||
const apiCallFailed = "API call failed: "
|
125
fwprovider/access/resource_acl_model.go
Normal file
125
fwprovider/access/resource_acl_model.go
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 access
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
||||
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
||||
)
|
||||
|
||||
type aclResourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
|
||||
GroupID types.String `tfsdk:"group_id"`
|
||||
Path string `tfsdk:"path"`
|
||||
Propagate bool `tfsdk:"propagate"`
|
||||
RoleID string `tfsdk:"role_id"`
|
||||
TokenID types.String `tfsdk:"token_id"`
|
||||
UserID types.String `tfsdk:"user_id"`
|
||||
}
|
||||
|
||||
type aclResourceIDFields struct {
|
||||
EntityID string `schema:"entity_id"`
|
||||
RoleID string `schema:"role_id"`
|
||||
}
|
||||
|
||||
const aclIDFormat = "{path}?entity_id={group|user@realm|user@realm!token}?role_id={role}"
|
||||
|
||||
func (r *aclResourceModel) generateID() error {
|
||||
encoder := schema.NewEncoder()
|
||||
|
||||
fields := aclResourceIDFields{
|
||||
EntityID: r.GroupID.ValueString() + r.TokenID.ValueString() + r.UserID.ValueString(),
|
||||
RoleID: r.RoleID,
|
||||
}
|
||||
v := url.Values{}
|
||||
|
||||
err := encoder.Encode(fields, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode ACL resource ID: %w", err)
|
||||
}
|
||||
|
||||
r.ID = types.StringValue(r.Path + "?" + v.Encode())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseACLResourceModelFromID(id string) (*aclResourceModel, error) {
|
||||
path, query, found := strings.Cut(id, "?")
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("invalid ACL resource ID format %#v, expected %v", id, aclIDFormat)
|
||||
}
|
||||
|
||||
v, err := url.ParseQuery(query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ACL resource ID format %#v, expected %v: %w", id, aclIDFormat, err)
|
||||
}
|
||||
|
||||
decoder := schema.NewDecoder()
|
||||
|
||||
fields := aclResourceIDFields{}
|
||||
|
||||
err = decoder.Decode(&fields, v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ACL resource ID format %#v, expected %v: %w", id, aclIDFormat, err)
|
||||
}
|
||||
|
||||
model := &aclResourceModel{
|
||||
ID: types.StringValue(id),
|
||||
GroupID: types.StringNull(),
|
||||
Path: path,
|
||||
Propagate: false,
|
||||
RoleID: fields.RoleID,
|
||||
TokenID: types.StringNull(),
|
||||
UserID: types.StringNull(),
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(fields.EntityID, "!"):
|
||||
model.TokenID = types.StringValue(fields.EntityID)
|
||||
case strings.Contains(fields.EntityID, "@"):
|
||||
model.UserID = types.StringValue(fields.EntityID)
|
||||
default:
|
||||
model.GroupID = types.StringValue(fields.EntityID)
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (r *aclResourceModel) intoUpdateBody() *access.ACLUpdateRequestBody {
|
||||
body := &access.ACLUpdateRequestBody{
|
||||
Groups: nil,
|
||||
Path: r.Path,
|
||||
Propagate: proxmoxtypes.CustomBoolPtr(r.Propagate),
|
||||
Roles: []string{r.RoleID},
|
||||
Tokens: nil,
|
||||
Users: nil,
|
||||
}
|
||||
|
||||
if !r.GroupID.IsNull() {
|
||||
body.Groups = []string{r.GroupID.ValueString()}
|
||||
}
|
||||
|
||||
if !r.TokenID.IsNull() {
|
||||
body.Tokens = []string{r.TokenID.ValueString()}
|
||||
}
|
||||
|
||||
if !r.UserID.IsNull() {
|
||||
body.Users = []string{r.UserID.ValueString()}
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
@ -144,7 +144,7 @@ func (r *userTokenResource) Create(ctx context.Context, req resource.CreateReque
|
||||
|
||||
body := access.UserTokenCreateRequestBody{
|
||||
Comment: plan.Comment.ValueStringPointer(),
|
||||
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBoolPointer()),
|
||||
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBool()),
|
||||
}
|
||||
|
||||
if !plan.ExpirationDate.IsNull() && plan.ExpirationDate.ValueString() != "" {
|
||||
@ -215,7 +215,7 @@ func (r *userTokenResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
|
||||
body := access.UserTokenUpdateRequestBody{
|
||||
Comment: plan.Comment.ValueStringPointer(),
|
||||
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBoolPointer()),
|
||||
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBool()),
|
||||
}
|
||||
|
||||
if !plan.ExpirationDate.IsNull() && plan.ExpirationDate.ValueString() != "" {
|
||||
|
@ -451,6 +451,7 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc
|
||||
vm.NewVMResource,
|
||||
NewClusterOptionsResource,
|
||||
NewDownloadFileResource,
|
||||
access.NewACLResource,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,11 @@ import (
|
||||
// IDAttribute generates an attribute definition suitable for the always-present `id` attribute.
|
||||
func IDAttribute(desc ...string) schema.StringAttribute {
|
||||
attr := schema.StringAttribute{
|
||||
Computed: true,
|
||||
Computed: true,
|
||||
Description: "The unique identifier of this resource.",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
Description: "The unique identifier of this resource.",
|
||||
}
|
||||
|
||||
if len(desc) > 0 {
|
||||
|
174
fwprovider/tests/resource_acl_test.go
Normal file
174
fwprovider/tests/resource_acl_test.go
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
||||
)
|
||||
|
||||
func TestAccAcl_User(t *testing.T) {
|
||||
te := initTestEnvironment(t)
|
||||
|
||||
userID := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||
te.addTemplateVars(map[string]any{
|
||||
"UserID": userID,
|
||||
})
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: te.accProviders,
|
||||
CheckDestroy: nil,
|
||||
PreCheck: func() {
|
||||
err := te.accessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
|
||||
ID: userID,
|
||||
Password: gofakeit.Password(true, true, true, true, false, 8),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
err := te.accessClient().DeleteUser(context.Background(), userID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
},
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_acl" "test" {
|
||||
user_id = "{{.UserID}}"
|
||||
path = "/"
|
||||
role_id = "NoAccess"
|
||||
}`),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testResourceAttributes("proxmox_virtual_environment_acl.test", map[string]string{
|
||||
"path": "/",
|
||||
"role_id": "NoAccess",
|
||||
"user_id": userID,
|
||||
"propagate": "true",
|
||||
}),
|
||||
testNoResourceAttributesSet("proxmox_virtual_environment_acl.test", []string{
|
||||
"group_id",
|
||||
"token_id",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
ResourceName: "proxmox_virtual_environment_acl.test",
|
||||
ImportState: true,
|
||||
ImportStateIdFunc: testAccACLImportStateIDFunc(),
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
{
|
||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_acl" "test" {
|
||||
user_id = "{{.UserID}}"
|
||||
path = "/"
|
||||
role_id = "PVEPoolUser"
|
||||
}`),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testResourceAttributes("proxmox_virtual_environment_acl.test", map[string]string{
|
||||
"path": "/",
|
||||
"role_id": "PVEPoolUser",
|
||||
"user_id": userID,
|
||||
"propagate": "true",
|
||||
}),
|
||||
testNoResourceAttributesSet("proxmox_virtual_environment_acl.test", []string{
|
||||
"group_id",
|
||||
"token_id",
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAcl_Validators(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
te := initTestEnvironment(t)
|
||||
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: te.accProviders,
|
||||
CheckDestroy: nil,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
PlanOnly: true,
|
||||
Config: `resource "proxmox_virtual_environment_acl" "test" {
|
||||
group_id = "test"
|
||||
path = "/"
|
||||
role_id = "test"
|
||||
token_id = "test"
|
||||
}`,
|
||||
ExpectError: regexp.MustCompile(`.*Error: Invalid Attribute Combination`),
|
||||
},
|
||||
{
|
||||
PlanOnly: true,
|
||||
Config: `resource "proxmox_virtual_environment_acl" "test" {
|
||||
path = "/"
|
||||
role_id = "test"
|
||||
token_id = "test"
|
||||
user_id = "test"
|
||||
}`,
|
||||
ExpectError: regexp.MustCompile(`.*Error: Invalid Attribute Combination`),
|
||||
},
|
||||
{
|
||||
PlanOnly: true,
|
||||
Config: `resource "proxmox_virtual_environment_acl" "test" {
|
||||
group_id = "test"
|
||||
path = "/"
|
||||
role_id = "test"
|
||||
user_id = "test"
|
||||
}`,
|
||||
ExpectError: regexp.MustCompile(`.*Error: Invalid Attribute Combination`),
|
||||
},
|
||||
{
|
||||
PlanOnly: true,
|
||||
Config: `resource "proxmox_virtual_environment_acl" "test" {
|
||||
group_id = "test"
|
||||
path = "/"
|
||||
role_id = "test"
|
||||
token_id = "test"
|
||||
user_id = "test"
|
||||
}`,
|
||||
ExpectError: regexp.MustCompile(`.*Error: Invalid Attribute Combination`),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccACLImportStateIDFunc() resource.ImportStateIdFunc {
|
||||
return func(s *terraform.State) (string, error) {
|
||||
resourceName := "proxmox_virtual_environment_acl.test"
|
||||
|
||||
rs, ok := s.RootModule().Resources[resourceName]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("not found: %s", resourceName)
|
||||
}
|
||||
|
||||
path := rs.Primary.Attributes["path"]
|
||||
|
||||
groupID := rs.Primary.Attributes["group_id"]
|
||||
tokenID := rs.Primary.Attributes["token_id"]
|
||||
userID := rs.Primary.Attributes["user_id"]
|
||||
|
||||
roleID := rs.Primary.Attributes["role_id"]
|
||||
|
||||
v := url.Values{
|
||||
"entity_id": []string{groupID + tokenID + userID},
|
||||
"role_id": []string{roleID},
|
||||
}
|
||||
|
||||
return path + "?" + v.Encode(), nil
|
||||
}
|
||||
}
|
@ -14,8 +14,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/storage"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -133,11 +133,11 @@ func TestAccResourceDownloadFile(t *testing.T) {
|
||||
_ = te.nodeStorageClient().DeleteDatastoreFile(ctx, "iso/fake_file.iso") //nolint: errcheck
|
||||
|
||||
err := te.nodeStorageClient().DownloadFileByURL(ctx, &storage.DownloadURLPostRequestBody{
|
||||
Content: types.StrPtr("iso"),
|
||||
FileName: types.StrPtr("fake_file.iso"),
|
||||
Node: types.StrPtr(te.nodeName),
|
||||
Storage: types.StrPtr(te.datastoreID),
|
||||
URL: types.StrPtr(fakeFileISO),
|
||||
Content: ptr.Ptr("iso"),
|
||||
FileName: ptr.Ptr("fake_file.iso"),
|
||||
Node: ptr.Ptr(te.nodeName),
|
||||
Storage: ptr.Ptr(te.datastoreID),
|
||||
URL: ptr.Ptr(fakeFileISO),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -23,9 +23,9 @@ func TestAccResourceUser(t *testing.T) {
|
||||
|
||||
te := initTestEnvironment(t)
|
||||
|
||||
username := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||
userID := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||
te.addTemplateVars(map[string]any{
|
||||
"Username": username,
|
||||
"UserID": userID,
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
@ -36,35 +36,35 @@ func TestAccResourceUser(t *testing.T) {
|
||||
{
|
||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
|
||||
comment = "Managed by Terraform"
|
||||
email = "{{.Username}}"
|
||||
email = "{{.UserID}}"
|
||||
enabled = true
|
||||
expiration_date = "2034-01-01T22:00:00Z"
|
||||
first_name = "First"
|
||||
last_name = "Last"
|
||||
user_id = "{{.Username}}"
|
||||
user_id = "{{.UserID}}"
|
||||
}`),
|
||||
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
|
||||
"comment": "Managed by Terraform",
|
||||
"email": username,
|
||||
"email": userID,
|
||||
"enabled": "true",
|
||||
"expiration_date": "2034-01-01T22:00:00Z",
|
||||
"first_name": "First",
|
||||
"last_name": "Last",
|
||||
"user_id": username,
|
||||
"user_id": userID,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
|
||||
enabled = false
|
||||
expiration_date = "2035-01-01T22:00:00Z"
|
||||
user_id = "{{.Username}}"
|
||||
user_id = "{{.UserID}}"
|
||||
first_name = "First One"
|
||||
}`),
|
||||
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
|
||||
"enabled": "false",
|
||||
"expiration_date": "2035-01-01T22:00:00Z",
|
||||
"first_name": "First One",
|
||||
"user_id": username,
|
||||
"user_id": userID,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -89,11 +89,11 @@ func TestAccResourceUserToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
te := initTestEnvironment(t)
|
||||
username := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||
userID := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||
tokenName := gofakeit.Word()
|
||||
|
||||
te.addTemplateVars(map[string]any{
|
||||
"Username": username,
|
||||
"UserID": userID,
|
||||
"TokenName": tokenName,
|
||||
})
|
||||
|
||||
@ -106,13 +106,13 @@ func TestAccResourceUserToken(t *testing.T) {
|
||||
"create and update user token",
|
||||
func() {
|
||||
err := te.accessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
|
||||
ID: username,
|
||||
ID: userID,
|
||||
Password: gofakeit.Password(true, true, true, true, false, 8),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
err := te.accessClient().DeleteUser(context.Background(), username)
|
||||
err := te.accessClient().DeleteUser(context.Background(), userID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
},
|
||||
@ -122,14 +122,14 @@ func TestAccResourceUserToken(t *testing.T) {
|
||||
comment = "Managed by Terraform"
|
||||
expiration_date = "2034-01-01T22:00:00Z"
|
||||
token_name = "{{.TokenName}}"
|
||||
user_id = "{{.Username}}"
|
||||
user_id = "{{.UserID}}"
|
||||
}`),
|
||||
Check: testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
|
||||
"comment": "Managed by Terraform",
|
||||
"expiration_date": "2034-01-01T22:00:00Z",
|
||||
"id": fmt.Sprintf("%s!%s", username, tokenName),
|
||||
"user_id": username,
|
||||
"value": fmt.Sprintf("%s!%s=.*", username, tokenName),
|
||||
"id": fmt.Sprintf("%s!%s", userID, tokenName),
|
||||
"user_id": userID,
|
||||
"value": fmt.Sprintf("%s!%s=.*", userID, tokenName),
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -138,7 +138,7 @@ func TestAccResourceUserToken(t *testing.T) {
|
||||
expiration_date = "2033-01-01T01:01:01Z"
|
||||
privileges_separation = false
|
||||
token_name = "{{.TokenName}}"
|
||||
user_id = "{{.Username}}"
|
||||
user_id = "{{.UserID}}"
|
||||
}`),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
|
||||
@ -146,7 +146,7 @@ func TestAccResourceUserToken(t *testing.T) {
|
||||
"expiration_date": "2033-01-01T01:01:01Z",
|
||||
"privileges_separation": "false",
|
||||
"token_name": tokenName,
|
||||
"user_id": username,
|
||||
"user_id": userID,
|
||||
}),
|
||||
testNoResourceAttributesSet("proxmox_virtual_environment_user_token.user_token", []string{
|
||||
"value",
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
|
||||
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||
)
|
||||
|
||||
// Ensure the implementations satisfy the required interfaces.
|
||||
@ -67,7 +67,7 @@ func (v Value) ValueStringPointer(ctx context.Context, diags *diag.Diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
return proxmoxtypes.StrPtr(strings.Join(sanitizedTags, ";"))
|
||||
return ptr.Ptr(strings.Join(sanitizedTags, ";"))
|
||||
}
|
||||
|
||||
// SetValue converts a string of tags to a tags set value.
|
||||
|
@ -137,7 +137,7 @@ func (r *vmResource) create(ctx context.Context, plan vmModel, diags *diag.Diagn
|
||||
Description: plan.Description.ValueStringPointer(),
|
||||
Name: plan.Name.ValueStringPointer(),
|
||||
Tags: plan.Tags.ValueStringPointer(ctx, diags),
|
||||
Template: proxmoxtypes.CustomBoolPtr(plan.Template.ValueBoolPointer()),
|
||||
Template: proxmoxtypes.CustomBoolPtr(plan.Template.ValueBool()),
|
||||
VMID: int(plan.ID.ValueInt64()),
|
||||
}
|
||||
|
||||
|
1
go.mod
1
go.mod
@ -10,6 +10,7 @@ require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-querystring v1.1.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/schema v1.2.1
|
||||
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
|
||||
github.com/hashicorp/terraform-plugin-framework v1.8.0
|
||||
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1
|
||||
|
2
go.sum
2
go.sum
@ -52,6 +52,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM=
|
||||
github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
@ -20,7 +20,7 @@ func (c *Client) aclPath() string {
|
||||
}
|
||||
|
||||
// GetACL retrieves the access control list.
|
||||
func (c *Client) GetACL(ctx context.Context) ([]*ACLGetResponseData, error) {
|
||||
func (c *Client) GetACL(ctx context.Context) ([]ACLGetResponseData, error) {
|
||||
resBody := &ACLGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.aclPath(), nil, resBody)
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
// ACLGetResponseBody contains the body from an access control list response.
|
||||
type ACLGetResponseBody struct {
|
||||
Data []*ACLGetResponseData `json:"data,omitempty"`
|
||||
Data []ACLGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ACLGetResponseData contains the data from an access control list response.
|
||||
@ -31,5 +31,6 @@ type ACLUpdateRequestBody struct {
|
||||
Path string `json:"path" url:"path"`
|
||||
Propagate *types.CustomBool `json:"propagate,omitempty" url:"propagate,omitempty,int"`
|
||||
Roles []string `json:"roles" url:"roles,comma"`
|
||||
Tokens []string `json:"tokens,omitempty" url:"tokens,omitempty,comma"`
|
||||
Users []string `json:"users,omitempty" url:"users,omitempty,comma"`
|
||||
}
|
||||
|
21
proxmox/helpers/ptr/ptr.go
Normal file
21
proxmox/helpers/ptr/ptr.go
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 ptr
|
||||
|
||||
// Ptr creates a ptr from a value to use it inline.
|
||||
func Ptr[T any](val T) *T {
|
||||
return &val
|
||||
}
|
||||
|
||||
// Or will dereference a pointer and return the given value if it's nil.
|
||||
func Or[T any](p *T, or T) T {
|
||||
if p != nil {
|
||||
return *p
|
||||
}
|
||||
|
||||
return or
|
||||
}
|
@ -215,35 +215,6 @@ func (d CustomStorageDevice) EncodeValues(key string, v *url.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the CustomStorageDevice.
|
||||
func (d CustomStorageDevice) Copy() *CustomStorageDevice {
|
||||
return &CustomStorageDevice{
|
||||
AIO: types.CopyString(d.AIO),
|
||||
Backup: d.Backup.Copy(),
|
||||
BurstableReadSpeedMbps: types.CopyInt(d.BurstableReadSpeedMbps),
|
||||
BurstableWriteSpeedMbps: types.CopyInt(d.BurstableWriteSpeedMbps),
|
||||
Cache: types.CopyString(d.Cache),
|
||||
DatastoreID: types.CopyString(d.DatastoreID),
|
||||
Discard: types.CopyString(d.Discard),
|
||||
Enabled: d.Enabled,
|
||||
FileID: types.CopyString(d.FileID),
|
||||
FileVolume: d.FileVolume,
|
||||
Format: types.CopyString(d.Format),
|
||||
Interface: types.CopyString(d.Interface),
|
||||
IopsRead: types.CopyInt(d.IopsRead),
|
||||
IopsWrite: types.CopyInt(d.IopsWrite),
|
||||
IOThread: d.IOThread.Copy(),
|
||||
MaxIopsRead: types.CopyInt(d.MaxIopsRead),
|
||||
MaxIopsWrite: types.CopyInt(d.MaxIopsWrite),
|
||||
MaxReadSpeedMbps: types.CopyInt(d.MaxReadSpeedMbps),
|
||||
MaxWriteSpeedMbps: types.CopyInt(d.MaxWriteSpeedMbps),
|
||||
Media: types.CopyString(d.Media),
|
||||
Replicate: d.Replicate.Copy(),
|
||||
Size: d.Size.Copy(),
|
||||
SSD: d.SSD.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
// CustomStorageDevices handles map of QEMU storage device per disk interface.
|
||||
type CustomStorageDevices map[string]*CustomStorageDevice
|
||||
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
@ -29,26 +30,26 @@ func TestCustomStorageDevice_UnmarshalJSON(t *testing.T) {
|
||||
name: "simple volume",
|
||||
line: `"local-lvm:vm-2041-disk-0,discard=on,ssd=1,iothread=1,size=8G,cache=writeback"`,
|
||||
want: &CustomStorageDevice{
|
||||
Cache: types.StrPtr("writeback"),
|
||||
Discard: types.StrPtr("on"),
|
||||
Cache: ptr.Ptr("writeback"),
|
||||
Discard: ptr.Ptr("on"),
|
||||
Enabled: true,
|
||||
FileVolume: "local-lvm:vm-2041-disk-0",
|
||||
IOThread: types.BoolPtr(true),
|
||||
IOThread: types.CustomBoolPtr(true),
|
||||
Size: ds8gig,
|
||||
SSD: types.BoolPtr(true),
|
||||
SSD: types.CustomBoolPtr(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "raw volume type",
|
||||
line: `"nfs:2041/vm-2041-disk-0.raw,discard=ignore,ssd=1,iothread=1,size=8G"`,
|
||||
want: &CustomStorageDevice{
|
||||
Discard: types.StrPtr("ignore"),
|
||||
Discard: ptr.Ptr("ignore"),
|
||||
Enabled: true,
|
||||
FileVolume: "nfs:2041/vm-2041-disk-0.raw",
|
||||
Format: types.StrPtr("raw"),
|
||||
IOThread: types.BoolPtr(true),
|
||||
Format: ptr.Ptr("raw"),
|
||||
IOThread: types.CustomBoolPtr(true),
|
||||
Size: ds8gig,
|
||||
SSD: types.BoolPtr(true),
|
||||
SSD: types.CustomBoolPtr(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -84,21 +85,21 @@ func TestCustomStorageDevice_IsCloudInitDrive(t *testing.T) {
|
||||
}, {
|
||||
name: "on directory storage",
|
||||
device: CustomStorageDevice{
|
||||
Media: types.StrPtr("cdrom"),
|
||||
Media: ptr.Ptr("cdrom"),
|
||||
FileVolume: "local:131/vm-131-cloudinit.qcow2",
|
||||
},
|
||||
want: true,
|
||||
}, {
|
||||
name: "on block storage",
|
||||
device: CustomStorageDevice{
|
||||
Media: types.StrPtr("cdrom"),
|
||||
Media: ptr.Ptr("cdrom"),
|
||||
FileVolume: "local-lvm:vm-131-cloudinit",
|
||||
},
|
||||
want: true,
|
||||
}, {
|
||||
name: "wrong VM ID",
|
||||
device: CustomStorageDevice{
|
||||
Media: types.StrPtr("cdrom"),
|
||||
Media: ptr.Ptr("cdrom"),
|
||||
FileVolume: "local-lvm:vm-123-cloudinit",
|
||||
},
|
||||
want: false,
|
||||
@ -132,13 +133,13 @@ func TestCustomStorageDevice_StorageInterface(t *testing.T) {
|
||||
{
|
||||
name: "virtio0",
|
||||
device: CustomStorageDevice{
|
||||
Interface: types.StrPtr("virtio0"),
|
||||
Interface: ptr.Ptr("virtio0"),
|
||||
},
|
||||
want: "virtio",
|
||||
}, {
|
||||
name: "scsi13",
|
||||
device: CustomStorageDevice{
|
||||
Interface: types.StrPtr("scsi13"),
|
||||
Interface: ptr.Ptr("scsi13"),
|
||||
},
|
||||
want: "scsi",
|
||||
},
|
||||
@ -174,10 +175,10 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
|
||||
iface: "sata",
|
||||
devices: CustomStorageDevices{
|
||||
"virtio0": &CustomStorageDevice{
|
||||
Interface: types.StrPtr("virtio0"),
|
||||
Interface: ptr.Ptr("virtio0"),
|
||||
},
|
||||
"scsi13": &CustomStorageDevice{
|
||||
Interface: types.StrPtr("scsi13"),
|
||||
Interface: ptr.Ptr("scsi13"),
|
||||
},
|
||||
},
|
||||
want: CustomStorageDevices{},
|
||||
@ -187,21 +188,21 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
|
||||
iface: "virtio",
|
||||
devices: CustomStorageDevices{
|
||||
"virtio0": &CustomStorageDevice{
|
||||
Interface: types.StrPtr("virtio0"),
|
||||
Interface: ptr.Ptr("virtio0"),
|
||||
},
|
||||
"scsi13": &CustomStorageDevice{
|
||||
Interface: types.StrPtr("scsi13"),
|
||||
Interface: ptr.Ptr("scsi13"),
|
||||
},
|
||||
"virtio1": &CustomStorageDevice{
|
||||
Interface: types.StrPtr("virtio1"),
|
||||
Interface: ptr.Ptr("virtio1"),
|
||||
},
|
||||
},
|
||||
want: CustomStorageDevices{
|
||||
"virtio0": &CustomStorageDevice{
|
||||
Interface: types.StrPtr("virtio0"),
|
||||
Interface: ptr.Ptr("virtio0"),
|
||||
},
|
||||
"virtio1": &CustomStorageDevice{
|
||||
Interface: types.StrPtr("virtio1"),
|
||||
Interface: ptr.Ptr("virtio1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -239,10 +240,10 @@ func TestCustomPCIDevice_UnmarshalJSON(t *testing.T) {
|
||||
want: &CustomPCIDevice{
|
||||
DeviceIDs: &[]string{"81:00.4"},
|
||||
MDev: nil,
|
||||
PCIExpress: types.BoolPtr(false),
|
||||
ROMBAR: types.BoolPtr(true),
|
||||
PCIExpress: types.CustomBoolPtr(false),
|
||||
ROMBAR: types.CustomBoolPtr(true),
|
||||
ROMFile: nil,
|
||||
XVGA: types.BoolPtr(false),
|
||||
XVGA: types.CustomBoolPtr(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -250,12 +251,12 @@ func TestCustomPCIDevice_UnmarshalJSON(t *testing.T) {
|
||||
line: `"mapping=mappeddevice,pcie=0,rombar=1,x-vga=0"`,
|
||||
want: &CustomPCIDevice{
|
||||
DeviceIDs: nil,
|
||||
Mapping: types.StrPtr("mappeddevice"),
|
||||
Mapping: ptr.Ptr("mappeddevice"),
|
||||
MDev: nil,
|
||||
PCIExpress: types.BoolPtr(false),
|
||||
ROMBAR: types.BoolPtr(true),
|
||||
PCIExpress: types.CustomBoolPtr(false),
|
||||
ROMBAR: types.CustomBoolPtr(true),
|
||||
ROMFile: nil,
|
||||
XVGA: types.BoolPtr(false),
|
||||
XVGA: types.CustomBoolPtr(false),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -289,8 +290,8 @@ func TestCustomNUMADevice_UnmarshalJSON(t *testing.T) {
|
||||
want: &CustomNUMADevice{
|
||||
CPUIDs: []string{"1-2", "3-4"},
|
||||
HostNodeNames: &[]string{"1-2"},
|
||||
Memory: types.IntPtr(1024),
|
||||
Policy: types.StrPtr("preferred"),
|
||||
Memory: ptr.Ptr(1024),
|
||||
Policy: ptr.Ptr("preferred"),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -298,7 +299,7 @@ func TestCustomNUMADevice_UnmarshalJSON(t *testing.T) {
|
||||
line: `"cpus=1-2,memory=1024"`,
|
||||
want: &CustomNUMADevice{
|
||||
CPUIDs: []string{"1-2"},
|
||||
Memory: types.IntPtr(1024),
|
||||
Memory: ptr.Ptr(1024),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -328,15 +329,15 @@ func TestCustomUSBDevice_UnmarshalJSON(t *testing.T) {
|
||||
name: "id only usb device",
|
||||
line: `"host=0000:81"`,
|
||||
want: &CustomUSBDevice{
|
||||
HostDevice: types.StrPtr("0000:81"),
|
||||
HostDevice: ptr.Ptr("0000:81"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "usb device with more details",
|
||||
line: `"host=81:00,usb3=0"`,
|
||||
want: &CustomUSBDevice{
|
||||
HostDevice: types.StrPtr("81:00"),
|
||||
USB3: types.BoolPtr(false),
|
||||
HostDevice: ptr.Ptr("81:00"),
|
||||
USB3: types.CustomBoolPtr(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -344,8 +345,8 @@ func TestCustomUSBDevice_UnmarshalJSON(t *testing.T) {
|
||||
line: `"mapping=mappeddevice,usb=0"`,
|
||||
want: &CustomUSBDevice{
|
||||
HostDevice: nil,
|
||||
Mapping: types.StrPtr("mappeddevice"),
|
||||
USB3: types.BoolPtr(false),
|
||||
Mapping: ptr.Ptr("mappeddevice"),
|
||||
USB3: types.CustomBoolPtr(false),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||
)
|
||||
|
||||
// CustomBool allows a JSON boolean value to also be an integer.
|
||||
@ -38,13 +40,9 @@ type CustomPrivileges []string
|
||||
// CustomTimestamp allows a JSON boolean value to also be a unix timestamp.
|
||||
type CustomTimestamp time.Time
|
||||
|
||||
// CustomBoolPtr creates a CustomBool pointer from a boolean pointer.
|
||||
func CustomBoolPtr(v *bool) *CustomBool {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return BoolPtr(*v)
|
||||
// CustomBoolPtr creates a pointer to a CustomBool.
|
||||
func CustomBoolPtr(b bool) *CustomBool {
|
||||
return ptr.Ptr(CustomBool(b))
|
||||
}
|
||||
|
||||
// MarshalJSON converts a boolean to a JSON value.
|
||||
@ -88,15 +86,6 @@ func (r *CustomBool) FromValue(tfValue types.Bool) {
|
||||
*r = CustomBool(tfValue.ValueBool())
|
||||
}
|
||||
|
||||
// Copy returns a copy of the boolean.
|
||||
func (r *CustomBool) Copy() *CustomBool {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return BoolPtr(bool(*r))
|
||||
}
|
||||
|
||||
// MarshalJSON converts a boolean to a JSON value.
|
||||
func (r *CustomCommaSeparatedList) MarshalJSON() ([]byte, error) {
|
||||
s := strings.Join(*r, ",")
|
||||
|
@ -76,17 +76,6 @@ func (r *DiskSize) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the disk size.
|
||||
func (r *DiskSize) Copy() *DiskSize {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := *r
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
// ParseDiskSize parses a disk size string into a number of bytes.
|
||||
func ParseDiskSize(size string) (DiskSize, error) {
|
||||
matches := sizeRegex.FindStringSubmatch(size)
|
||||
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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 types
|
||||
|
||||
// StrPtr returns a pointer to a string.
|
||||
func StrPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// IntPtr returns a pointer to an int.
|
||||
func IntPtr(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
// BoolPtr returns a pointer to a bool.
|
||||
func BoolPtr(s bool) *CustomBool {
|
||||
customBool := CustomBool(s)
|
||||
return &customBool
|
||||
}
|
||||
|
||||
// CopyString copies content of a string pointer.
|
||||
func CopyString(s *string) *string {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return StrPtr(*s)
|
||||
}
|
||||
|
||||
// CopyInt copies content of an int pointer.
|
||||
func CopyInt(i *int) *int {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return IntPtr(*i)
|
||||
}
|
@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/disk"
|
||||
@ -3270,7 +3271,7 @@ func vmGetSMBIOS(d *schema.ResourceData) *vms.CustomSMBIOS {
|
||||
}
|
||||
|
||||
if smbios.UUID == nil || *smbios.UUID == "" {
|
||||
smbios.UUID = types.StrPtr(uuid.New().String())
|
||||
smbios.UUID = ptr.Ptr(uuid.New().String())
|
||||
}
|
||||
|
||||
return &smbios
|
||||
|
@ -45,5 +45,6 @@ import (
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_haresource.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_cluster_options.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_download_file.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_acl.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_user_token.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_vm2.md ../docs/resources/
|
||||
|
Loading…
Reference in New Issue
Block a user