mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31: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
|
.PHONY: testacc
|
||||||
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/...
|
@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
|
.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{
|
body := access.UserTokenCreateRequestBody{
|
||||||
Comment: plan.Comment.ValueStringPointer(),
|
Comment: plan.Comment.ValueStringPointer(),
|
||||||
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBoolPointer()),
|
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBool()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !plan.ExpirationDate.IsNull() && plan.ExpirationDate.ValueString() != "" {
|
if !plan.ExpirationDate.IsNull() && plan.ExpirationDate.ValueString() != "" {
|
||||||
@ -215,7 +215,7 @@ func (r *userTokenResource) Update(ctx context.Context, req resource.UpdateReque
|
|||||||
|
|
||||||
body := access.UserTokenUpdateRequestBody{
|
body := access.UserTokenUpdateRequestBody{
|
||||||
Comment: plan.Comment.ValueStringPointer(),
|
Comment: plan.Comment.ValueStringPointer(),
|
||||||
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBoolPointer()),
|
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBool()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !plan.ExpirationDate.IsNull() && plan.ExpirationDate.ValueString() != "" {
|
if !plan.ExpirationDate.IsNull() && plan.ExpirationDate.ValueString() != "" {
|
||||||
|
@ -451,6 +451,7 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc
|
|||||||
vm.NewVMResource,
|
vm.NewVMResource,
|
||||||
NewClusterOptionsResource,
|
NewClusterOptionsResource,
|
||||||
NewDownloadFileResource,
|
NewDownloadFileResource,
|
||||||
|
access.NewACLResource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ import (
|
|||||||
// IDAttribute generates an attribute definition suitable for the always-present `id` attribute.
|
// IDAttribute generates an attribute definition suitable for the always-present `id` attribute.
|
||||||
func IDAttribute(desc ...string) schema.StringAttribute {
|
func IDAttribute(desc ...string) schema.StringAttribute {
|
||||||
attr := schema.StringAttribute{
|
attr := schema.StringAttribute{
|
||||||
Computed: true,
|
Computed: true,
|
||||||
|
Description: "The unique identifier of this resource.",
|
||||||
PlanModifiers: []planmodifier.String{
|
PlanModifiers: []planmodifier.String{
|
||||||
stringplanmodifier.UseStateForUnknown(),
|
stringplanmodifier.UseStateForUnknown(),
|
||||||
},
|
},
|
||||||
Description: "The unique identifier of this resource.",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(desc) > 0 {
|
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/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
"github.com/stretchr/testify/require"
|
"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/nodes/storage"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -133,11 +133,11 @@ func TestAccResourceDownloadFile(t *testing.T) {
|
|||||||
_ = te.nodeStorageClient().DeleteDatastoreFile(ctx, "iso/fake_file.iso") //nolint: errcheck
|
_ = te.nodeStorageClient().DeleteDatastoreFile(ctx, "iso/fake_file.iso") //nolint: errcheck
|
||||||
|
|
||||||
err := te.nodeStorageClient().DownloadFileByURL(ctx, &storage.DownloadURLPostRequestBody{
|
err := te.nodeStorageClient().DownloadFileByURL(ctx, &storage.DownloadURLPostRequestBody{
|
||||||
Content: types.StrPtr("iso"),
|
Content: ptr.Ptr("iso"),
|
||||||
FileName: types.StrPtr("fake_file.iso"),
|
FileName: ptr.Ptr("fake_file.iso"),
|
||||||
Node: types.StrPtr(te.nodeName),
|
Node: ptr.Ptr(te.nodeName),
|
||||||
Storage: types.StrPtr(te.datastoreID),
|
Storage: ptr.Ptr(te.datastoreID),
|
||||||
URL: types.StrPtr(fakeFileISO),
|
URL: ptr.Ptr(fakeFileISO),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -23,9 +23,9 @@ func TestAccResourceUser(t *testing.T) {
|
|||||||
|
|
||||||
te := initTestEnvironment(t)
|
te := initTestEnvironment(t)
|
||||||
|
|
||||||
username := fmt.Sprintf("%s@pve", gofakeit.Username())
|
userID := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||||
te.addTemplateVars(map[string]any{
|
te.addTemplateVars(map[string]any{
|
||||||
"Username": username,
|
"UserID": userID,
|
||||||
})
|
})
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -36,35 +36,35 @@ func TestAccResourceUser(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
|
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
|
||||||
comment = "Managed by Terraform"
|
comment = "Managed by Terraform"
|
||||||
email = "{{.Username}}"
|
email = "{{.UserID}}"
|
||||||
enabled = true
|
enabled = true
|
||||||
expiration_date = "2034-01-01T22:00:00Z"
|
expiration_date = "2034-01-01T22:00:00Z"
|
||||||
first_name = "First"
|
first_name = "First"
|
||||||
last_name = "Last"
|
last_name = "Last"
|
||||||
user_id = "{{.Username}}"
|
user_id = "{{.UserID}}"
|
||||||
}`),
|
}`),
|
||||||
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
|
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
|
||||||
"comment": "Managed by Terraform",
|
"comment": "Managed by Terraform",
|
||||||
"email": username,
|
"email": userID,
|
||||||
"enabled": "true",
|
"enabled": "true",
|
||||||
"expiration_date": "2034-01-01T22:00:00Z",
|
"expiration_date": "2034-01-01T22:00:00Z",
|
||||||
"first_name": "First",
|
"first_name": "First",
|
||||||
"last_name": "Last",
|
"last_name": "Last",
|
||||||
"user_id": username,
|
"user_id": userID,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
|
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
|
||||||
enabled = false
|
enabled = false
|
||||||
expiration_date = "2035-01-01T22:00:00Z"
|
expiration_date = "2035-01-01T22:00:00Z"
|
||||||
user_id = "{{.Username}}"
|
user_id = "{{.UserID}}"
|
||||||
first_name = "First One"
|
first_name = "First One"
|
||||||
}`),
|
}`),
|
||||||
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
|
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
|
||||||
"enabled": "false",
|
"enabled": "false",
|
||||||
"expiration_date": "2035-01-01T22:00:00Z",
|
"expiration_date": "2035-01-01T22:00:00Z",
|
||||||
"first_name": "First One",
|
"first_name": "First One",
|
||||||
"user_id": username,
|
"user_id": userID,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -89,11 +89,11 @@ func TestAccResourceUserToken(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
te := initTestEnvironment(t)
|
te := initTestEnvironment(t)
|
||||||
username := fmt.Sprintf("%s@pve", gofakeit.Username())
|
userID := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||||
tokenName := gofakeit.Word()
|
tokenName := gofakeit.Word()
|
||||||
|
|
||||||
te.addTemplateVars(map[string]any{
|
te.addTemplateVars(map[string]any{
|
||||||
"Username": username,
|
"UserID": userID,
|
||||||
"TokenName": tokenName,
|
"TokenName": tokenName,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -106,13 +106,13 @@ func TestAccResourceUserToken(t *testing.T) {
|
|||||||
"create and update user token",
|
"create and update user token",
|
||||||
func() {
|
func() {
|
||||||
err := te.accessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
|
err := te.accessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
|
||||||
ID: username,
|
ID: userID,
|
||||||
Password: gofakeit.Password(true, true, true, true, false, 8),
|
Password: gofakeit.Password(true, true, true, true, false, 8),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err := te.accessClient().DeleteUser(context.Background(), username)
|
err := te.accessClient().DeleteUser(context.Background(), userID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -122,14 +122,14 @@ func TestAccResourceUserToken(t *testing.T) {
|
|||||||
comment = "Managed by Terraform"
|
comment = "Managed by Terraform"
|
||||||
expiration_date = "2034-01-01T22:00:00Z"
|
expiration_date = "2034-01-01T22:00:00Z"
|
||||||
token_name = "{{.TokenName}}"
|
token_name = "{{.TokenName}}"
|
||||||
user_id = "{{.Username}}"
|
user_id = "{{.UserID}}"
|
||||||
}`),
|
}`),
|
||||||
Check: testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
|
Check: testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
|
||||||
"comment": "Managed by Terraform",
|
"comment": "Managed by Terraform",
|
||||||
"expiration_date": "2034-01-01T22:00:00Z",
|
"expiration_date": "2034-01-01T22:00:00Z",
|
||||||
"id": fmt.Sprintf("%s!%s", username, tokenName),
|
"id": fmt.Sprintf("%s!%s", userID, tokenName),
|
||||||
"user_id": username,
|
"user_id": userID,
|
||||||
"value": fmt.Sprintf("%s!%s=.*", username, tokenName),
|
"value": fmt.Sprintf("%s!%s=.*", userID, tokenName),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -138,7 +138,7 @@ func TestAccResourceUserToken(t *testing.T) {
|
|||||||
expiration_date = "2033-01-01T01:01:01Z"
|
expiration_date = "2033-01-01T01:01:01Z"
|
||||||
privileges_separation = false
|
privileges_separation = false
|
||||||
token_name = "{{.TokenName}}"
|
token_name = "{{.TokenName}}"
|
||||||
user_id = "{{.Username}}"
|
user_id = "{{.UserID}}"
|
||||||
}`),
|
}`),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
|
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",
|
"expiration_date": "2033-01-01T01:01:01Z",
|
||||||
"privileges_separation": "false",
|
"privileges_separation": "false",
|
||||||
"token_name": tokenName,
|
"token_name": tokenName,
|
||||||
"user_id": username,
|
"user_id": userID,
|
||||||
}),
|
}),
|
||||||
testNoResourceAttributesSet("proxmox_virtual_environment_user_token.user_token", []string{
|
testNoResourceAttributesSet("proxmox_virtual_environment_user_token.user_token", []string{
|
||||||
"value",
|
"value",
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
"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.
|
// 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.
|
// 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(),
|
Description: plan.Description.ValueStringPointer(),
|
||||||
Name: plan.Name.ValueStringPointer(),
|
Name: plan.Name.ValueStringPointer(),
|
||||||
Tags: plan.Tags.ValueStringPointer(ctx, diags),
|
Tags: plan.Tags.ValueStringPointer(ctx, diags),
|
||||||
Template: proxmoxtypes.CustomBoolPtr(plan.Template.ValueBoolPointer()),
|
Template: proxmoxtypes.CustomBoolPtr(plan.Template.ValueBool()),
|
||||||
VMID: int(plan.ID.ValueInt64()),
|
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-cmp v0.6.0
|
||||||
github.com/google/go-querystring v1.1.0
|
github.com/google/go-querystring v1.1.0
|
||||||
github.com/google/uuid v1.6.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/go-cty v1.4.1-0.20200723130312-85980079f637
|
||||||
github.com/hashicorp/terraform-plugin-framework v1.8.0
|
github.com/hashicorp/terraform-plugin-framework v1.8.0
|
||||||
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1
|
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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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.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 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
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.
|
// 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{}
|
resBody := &ACLGetResponseBody{}
|
||||||
|
|
||||||
err := c.DoRequest(ctx, http.MethodGet, c.aclPath(), nil, resBody)
|
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.
|
// ACLGetResponseBody contains the body from an access control list response.
|
||||||
type ACLGetResponseBody struct {
|
type ACLGetResponseBody struct {
|
||||||
Data []*ACLGetResponseData `json:"data,omitempty"`
|
Data []ACLGetResponseData `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACLGetResponseData contains the data from an access control list response.
|
// ACLGetResponseData contains the data from an access control list response.
|
||||||
@ -31,5 +31,6 @@ type ACLUpdateRequestBody struct {
|
|||||||
Path string `json:"path" url:"path"`
|
Path string `json:"path" url:"path"`
|
||||||
Propagate *types.CustomBool `json:"propagate,omitempty" url:"propagate,omitempty,int"`
|
Propagate *types.CustomBool `json:"propagate,omitempty" url:"propagate,omitempty,int"`
|
||||||
Roles []string `json:"roles" url:"roles,comma"`
|
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"`
|
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
|
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.
|
// CustomStorageDevices handles map of QEMU storage device per disk interface.
|
||||||
type CustomStorageDevices map[string]*CustomStorageDevice
|
type CustomStorageDevices map[string]*CustomStorageDevice
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,26 +30,26 @@ func TestCustomStorageDevice_UnmarshalJSON(t *testing.T) {
|
|||||||
name: "simple volume",
|
name: "simple volume",
|
||||||
line: `"local-lvm:vm-2041-disk-0,discard=on,ssd=1,iothread=1,size=8G,cache=writeback"`,
|
line: `"local-lvm:vm-2041-disk-0,discard=on,ssd=1,iothread=1,size=8G,cache=writeback"`,
|
||||||
want: &CustomStorageDevice{
|
want: &CustomStorageDevice{
|
||||||
Cache: types.StrPtr("writeback"),
|
Cache: ptr.Ptr("writeback"),
|
||||||
Discard: types.StrPtr("on"),
|
Discard: ptr.Ptr("on"),
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
FileVolume: "local-lvm:vm-2041-disk-0",
|
FileVolume: "local-lvm:vm-2041-disk-0",
|
||||||
IOThread: types.BoolPtr(true),
|
IOThread: types.CustomBoolPtr(true),
|
||||||
Size: ds8gig,
|
Size: ds8gig,
|
||||||
SSD: types.BoolPtr(true),
|
SSD: types.CustomBoolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "raw volume type",
|
name: "raw volume type",
|
||||||
line: `"nfs:2041/vm-2041-disk-0.raw,discard=ignore,ssd=1,iothread=1,size=8G"`,
|
line: `"nfs:2041/vm-2041-disk-0.raw,discard=ignore,ssd=1,iothread=1,size=8G"`,
|
||||||
want: &CustomStorageDevice{
|
want: &CustomStorageDevice{
|
||||||
Discard: types.StrPtr("ignore"),
|
Discard: ptr.Ptr("ignore"),
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
FileVolume: "nfs:2041/vm-2041-disk-0.raw",
|
FileVolume: "nfs:2041/vm-2041-disk-0.raw",
|
||||||
Format: types.StrPtr("raw"),
|
Format: ptr.Ptr("raw"),
|
||||||
IOThread: types.BoolPtr(true),
|
IOThread: types.CustomBoolPtr(true),
|
||||||
Size: ds8gig,
|
Size: ds8gig,
|
||||||
SSD: types.BoolPtr(true),
|
SSD: types.CustomBoolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -84,21 +85,21 @@ func TestCustomStorageDevice_IsCloudInitDrive(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "on directory storage",
|
name: "on directory storage",
|
||||||
device: CustomStorageDevice{
|
device: CustomStorageDevice{
|
||||||
Media: types.StrPtr("cdrom"),
|
Media: ptr.Ptr("cdrom"),
|
||||||
FileVolume: "local:131/vm-131-cloudinit.qcow2",
|
FileVolume: "local:131/vm-131-cloudinit.qcow2",
|
||||||
},
|
},
|
||||||
want: true,
|
want: true,
|
||||||
}, {
|
}, {
|
||||||
name: "on block storage",
|
name: "on block storage",
|
||||||
device: CustomStorageDevice{
|
device: CustomStorageDevice{
|
||||||
Media: types.StrPtr("cdrom"),
|
Media: ptr.Ptr("cdrom"),
|
||||||
FileVolume: "local-lvm:vm-131-cloudinit",
|
FileVolume: "local-lvm:vm-131-cloudinit",
|
||||||
},
|
},
|
||||||
want: true,
|
want: true,
|
||||||
}, {
|
}, {
|
||||||
name: "wrong VM ID",
|
name: "wrong VM ID",
|
||||||
device: CustomStorageDevice{
|
device: CustomStorageDevice{
|
||||||
Media: types.StrPtr("cdrom"),
|
Media: ptr.Ptr("cdrom"),
|
||||||
FileVolume: "local-lvm:vm-123-cloudinit",
|
FileVolume: "local-lvm:vm-123-cloudinit",
|
||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
@ -132,13 +133,13 @@ func TestCustomStorageDevice_StorageInterface(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "virtio0",
|
name: "virtio0",
|
||||||
device: CustomStorageDevice{
|
device: CustomStorageDevice{
|
||||||
Interface: types.StrPtr("virtio0"),
|
Interface: ptr.Ptr("virtio0"),
|
||||||
},
|
},
|
||||||
want: "virtio",
|
want: "virtio",
|
||||||
}, {
|
}, {
|
||||||
name: "scsi13",
|
name: "scsi13",
|
||||||
device: CustomStorageDevice{
|
device: CustomStorageDevice{
|
||||||
Interface: types.StrPtr("scsi13"),
|
Interface: ptr.Ptr("scsi13"),
|
||||||
},
|
},
|
||||||
want: "scsi",
|
want: "scsi",
|
||||||
},
|
},
|
||||||
@ -174,10 +175,10 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
|
|||||||
iface: "sata",
|
iface: "sata",
|
||||||
devices: CustomStorageDevices{
|
devices: CustomStorageDevices{
|
||||||
"virtio0": &CustomStorageDevice{
|
"virtio0": &CustomStorageDevice{
|
||||||
Interface: types.StrPtr("virtio0"),
|
Interface: ptr.Ptr("virtio0"),
|
||||||
},
|
},
|
||||||
"scsi13": &CustomStorageDevice{
|
"scsi13": &CustomStorageDevice{
|
||||||
Interface: types.StrPtr("scsi13"),
|
Interface: ptr.Ptr("scsi13"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: CustomStorageDevices{},
|
want: CustomStorageDevices{},
|
||||||
@ -187,21 +188,21 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
|
|||||||
iface: "virtio",
|
iface: "virtio",
|
||||||
devices: CustomStorageDevices{
|
devices: CustomStorageDevices{
|
||||||
"virtio0": &CustomStorageDevice{
|
"virtio0": &CustomStorageDevice{
|
||||||
Interface: types.StrPtr("virtio0"),
|
Interface: ptr.Ptr("virtio0"),
|
||||||
},
|
},
|
||||||
"scsi13": &CustomStorageDevice{
|
"scsi13": &CustomStorageDevice{
|
||||||
Interface: types.StrPtr("scsi13"),
|
Interface: ptr.Ptr("scsi13"),
|
||||||
},
|
},
|
||||||
"virtio1": &CustomStorageDevice{
|
"virtio1": &CustomStorageDevice{
|
||||||
Interface: types.StrPtr("virtio1"),
|
Interface: ptr.Ptr("virtio1"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: CustomStorageDevices{
|
want: CustomStorageDevices{
|
||||||
"virtio0": &CustomStorageDevice{
|
"virtio0": &CustomStorageDevice{
|
||||||
Interface: types.StrPtr("virtio0"),
|
Interface: ptr.Ptr("virtio0"),
|
||||||
},
|
},
|
||||||
"virtio1": &CustomStorageDevice{
|
"virtio1": &CustomStorageDevice{
|
||||||
Interface: types.StrPtr("virtio1"),
|
Interface: ptr.Ptr("virtio1"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -239,10 +240,10 @@ func TestCustomPCIDevice_UnmarshalJSON(t *testing.T) {
|
|||||||
want: &CustomPCIDevice{
|
want: &CustomPCIDevice{
|
||||||
DeviceIDs: &[]string{"81:00.4"},
|
DeviceIDs: &[]string{"81:00.4"},
|
||||||
MDev: nil,
|
MDev: nil,
|
||||||
PCIExpress: types.BoolPtr(false),
|
PCIExpress: types.CustomBoolPtr(false),
|
||||||
ROMBAR: types.BoolPtr(true),
|
ROMBAR: types.CustomBoolPtr(true),
|
||||||
ROMFile: nil,
|
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"`,
|
line: `"mapping=mappeddevice,pcie=0,rombar=1,x-vga=0"`,
|
||||||
want: &CustomPCIDevice{
|
want: &CustomPCIDevice{
|
||||||
DeviceIDs: nil,
|
DeviceIDs: nil,
|
||||||
Mapping: types.StrPtr("mappeddevice"),
|
Mapping: ptr.Ptr("mappeddevice"),
|
||||||
MDev: nil,
|
MDev: nil,
|
||||||
PCIExpress: types.BoolPtr(false),
|
PCIExpress: types.CustomBoolPtr(false),
|
||||||
ROMBAR: types.BoolPtr(true),
|
ROMBAR: types.CustomBoolPtr(true),
|
||||||
ROMFile: nil,
|
ROMFile: nil,
|
||||||
XVGA: types.BoolPtr(false),
|
XVGA: types.CustomBoolPtr(false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -289,8 +290,8 @@ func TestCustomNUMADevice_UnmarshalJSON(t *testing.T) {
|
|||||||
want: &CustomNUMADevice{
|
want: &CustomNUMADevice{
|
||||||
CPUIDs: []string{"1-2", "3-4"},
|
CPUIDs: []string{"1-2", "3-4"},
|
||||||
HostNodeNames: &[]string{"1-2"},
|
HostNodeNames: &[]string{"1-2"},
|
||||||
Memory: types.IntPtr(1024),
|
Memory: ptr.Ptr(1024),
|
||||||
Policy: types.StrPtr("preferred"),
|
Policy: ptr.Ptr("preferred"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -298,7 +299,7 @@ func TestCustomNUMADevice_UnmarshalJSON(t *testing.T) {
|
|||||||
line: `"cpus=1-2,memory=1024"`,
|
line: `"cpus=1-2,memory=1024"`,
|
||||||
want: &CustomNUMADevice{
|
want: &CustomNUMADevice{
|
||||||
CPUIDs: []string{"1-2"},
|
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",
|
name: "id only usb device",
|
||||||
line: `"host=0000:81"`,
|
line: `"host=0000:81"`,
|
||||||
want: &CustomUSBDevice{
|
want: &CustomUSBDevice{
|
||||||
HostDevice: types.StrPtr("0000:81"),
|
HostDevice: ptr.Ptr("0000:81"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "usb device with more details",
|
name: "usb device with more details",
|
||||||
line: `"host=81:00,usb3=0"`,
|
line: `"host=81:00,usb3=0"`,
|
||||||
want: &CustomUSBDevice{
|
want: &CustomUSBDevice{
|
||||||
HostDevice: types.StrPtr("81:00"),
|
HostDevice: ptr.Ptr("81:00"),
|
||||||
USB3: types.BoolPtr(false),
|
USB3: types.CustomBoolPtr(false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -344,8 +345,8 @@ func TestCustomUSBDevice_UnmarshalJSON(t *testing.T) {
|
|||||||
line: `"mapping=mappeddevice,usb=0"`,
|
line: `"mapping=mappeddevice,usb=0"`,
|
||||||
want: &CustomUSBDevice{
|
want: &CustomUSBDevice{
|
||||||
HostDevice: nil,
|
HostDevice: nil,
|
||||||
Mapping: types.StrPtr("mappeddevice"),
|
Mapping: ptr.Ptr("mappeddevice"),
|
||||||
USB3: types.BoolPtr(false),
|
USB3: types.CustomBoolPtr(false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"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.
|
// 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.
|
// CustomTimestamp allows a JSON boolean value to also be a unix timestamp.
|
||||||
type CustomTimestamp time.Time
|
type CustomTimestamp time.Time
|
||||||
|
|
||||||
// CustomBoolPtr creates a CustomBool pointer from a boolean pointer.
|
// CustomBoolPtr creates a pointer to a CustomBool.
|
||||||
func CustomBoolPtr(v *bool) *CustomBool {
|
func CustomBoolPtr(b bool) *CustomBool {
|
||||||
if v == nil {
|
return ptr.Ptr(CustomBool(b))
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return BoolPtr(*v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON converts a boolean to a JSON value.
|
// MarshalJSON converts a boolean to a JSON value.
|
||||||
@ -88,15 +86,6 @@ func (r *CustomBool) FromValue(tfValue types.Bool) {
|
|||||||
*r = CustomBool(tfValue.ValueBool())
|
*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.
|
// MarshalJSON converts a boolean to a JSON value.
|
||||||
func (r *CustomCommaSeparatedList) MarshalJSON() ([]byte, error) {
|
func (r *CustomCommaSeparatedList) MarshalJSON() ([]byte, error) {
|
||||||
s := strings.Join(*r, ",")
|
s := strings.Join(*r, ",")
|
||||||
|
@ -76,17 +76,6 @@ func (r *DiskSize) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
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.
|
// ParseDiskSize parses a disk size string into a number of bytes.
|
||||||
func ParseDiskSize(size string) (DiskSize, error) {
|
func ParseDiskSize(size string) (DiskSize, error) {
|
||||||
matches := sizeRegex.FindStringSubmatch(size)
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/disk"
|
"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 == "" {
|
if smbios.UUID == nil || *smbios.UUID == "" {
|
||||||
smbios.UUID = types.StrPtr(uuid.New().String())
|
smbios.UUID = ptr.Ptr(uuid.New().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &smbios
|
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_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_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_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_user_token.md ../docs/resources/
|
||||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_vm2.md ../docs/resources/
|
//go:generate cp ../build/docs-gen/resources/virtual_environment_vm2.md ../docs/resources/
|
||||||
|
Loading…
Reference in New Issue
Block a user